Imported Upstream version 3.2.0 82/220782/2 upstream/3.2.0
authorSeonah Moon <seonah1.moon@samsung.com>
Mon, 23 Dec 2019 07:27:56 +0000 (16:27 +0900)
committerSeonah Moon <seonah1.moon@samsung.com>
Mon, 23 Dec 2019 07:28:47 +0000 (16:28 +0900)
Change-Id: Iaf62cf2451f42473deaaf945f25543ffc3a1209b

1097 files changed:
.gitignore
.mailmap [new file with mode: 0644]
.travis.yml
CMakeLists.txt
LICENSE
Makefile.projbuild [new file with mode: 0644]
README.build.md [deleted file]
README.esp8266.md [deleted file]
README.lws-meta.md [deleted file]
README.md
README.problems.md [deleted file]
READMEs/README.build.md [new file with mode: 0644]
READMEs/README.ci.md [new file with mode: 0644]
READMEs/README.coding.md [moved from README.coding.md with 56% similarity]
READMEs/README.content-security-policy.md [new file with mode: 0644]
READMEs/README.contributing.md [new file with mode: 0644]
READMEs/README.crypto-apis.md [new file with mode: 0644]
READMEs/README.esp32.md [moved from README.esp32.md with 66% similarity]
READMEs/README.generic-sessions.md [moved from README.generic-sessions.md with 100% similarity]
READMEs/README.generic-table.md [moved from README.generic-table.md with 100% similarity]
READMEs/README.http-fallback.md [new file with mode: 0644]
READMEs/README.lws_dll.md [new file with mode: 0644]
READMEs/README.lws_sequencer.md [new file with mode: 0644]
READMEs/README.lws_struct.md [new file with mode: 0644]
READMEs/README.lwsws.md [moved from README.lwsws.md with 75% similarity]
READMEs/README.plugin-acme.md [new file with mode: 0644]
READMEs/README.plugin-sshd-base.md [new file with mode: 0644]
READMEs/README.porting.md [new file with mode: 0644]
READMEs/README.problems.md [new file with mode: 0644]
READMEs/README.release-policy.md [new file with mode: 0644]
READMEs/README.test-apps.md [moved from README.test-apps.md with 75% similarity]
READMEs/README.unix-domain-reverse-proxy.md [new file with mode: 0644]
READMEs/README.vulnerability-reporting.md [new file with mode: 0644]
READMEs/mainpage.md [new file with mode: 0644]
READMEs/release-checklist [new file with mode: 0644]
appveyor.yml
autobahn-test.sh [deleted file]
changelog
cmake/FindLibWebSockets.cmake [moved from FindLibWebSockets.cmake with 100% similarity]
cmake/FindMiniz.cmake [new file with mode: 0644]
cmake/UseRPMTools.cmake
cmake/lws_config.h.in [new file with mode: 0644]
cmake/lws_config_private.h.in [moved from lws_config_private.h.in with 93% similarity]
component.mk
contrib/Android.mk [moved from Android.mk with 100% similarity]
contrib/abi/README.md
contrib/abi/libwebsockets.json
contrib/android-make-script.sh
contrib/cross-aarch64.cmake [moved from cross-aarch64.cmake with 50% similarity]
contrib/cross-arm-android-gnueabi.cmake [new file with mode: 0644]
contrib/cross-arm-linux-gnueabihf.cmake [moved from cross-arm-linux-gnueabihf.cmake with 53% similarity]
contrib/cross-esp32.cmake [moved from cross-esp32.cmake with 53% similarity]
contrib/cross-ming.cmake [moved from cross-ming.cmake with 54% similarity]
contrib/cross-openwrt-makefile [moved from cross-openwrt-makefile with 97% similarity]
contrib/cross-w32.cmake [new file with mode: 0644]
contrib/cross-w64.cmake [new file with mode: 0644]
doc-assets/abstract-overview.svg [new file with mode: 0644]
doc-assets/accept-flow-1.svg [new file with mode: 0644]
doc-assets/accept-flow-2.svg [new file with mode: 0644]
doc-assets/accept-flow-3.svg [new file with mode: 0644]
doc-assets/http-proxy-overview.svg [new file with mode: 0644]
doc-assets/lws-crypto-overview.svg [new file with mode: 0644]
doc-assets/lws-fts.svg [new file with mode: 0644]
doc-assets/lws-overview.png [new file with mode: 0644]
doc-assets/lws-relpol-1.svg [new file with mode: 0644]
doc-assets/lws-relpol-2.svg [new file with mode: 0644]
doc-assets/lws-relpol-3.svg [new file with mode: 0644]
doc-assets/lws-relpol-4.svg [new file with mode: 0644]
doc-assets/lws-relpol-5.svg [new file with mode: 0644]
doc-assets/lws-smp-example.png [new file with mode: 0644]
doc-assets/lws-smp-ov.png [new file with mode: 0644]
doc-assets/lws_dll.svg [new file with mode: 0644]
doc-assets/lws_sequencer.svg [new file with mode: 0644]
doc-assets/lws_struct-overview.svg [new file with mode: 0644]
doc-assets/lwsac.svg [new file with mode: 0644]
doc-assets/threadpool-states.svg [new file with mode: 0644]
doc-assets/threadpool.svg [new file with mode: 0644]
doc-assets/wss2.png [new file with mode: 0644]
include/libwebsockets.h [new file with mode: 0644]
include/libwebsockets/abstract/abstract.h [new file with mode: 0644]
include/libwebsockets/abstract/protocols.h [new file with mode: 0644]
include/libwebsockets/abstract/protocols/smtp.h [new file with mode: 0644]
include/libwebsockets/abstract/transports.h [new file with mode: 0644]
include/libwebsockets/abstract/transports/raw-skt.h [new file with mode: 0644]
include/libwebsockets/abstract/transports/unit-test.h [new file with mode: 0644]
include/libwebsockets/lws-adopt.h [new file with mode: 0644]
include/libwebsockets/lws-callbacks.h [new file with mode: 0644]
include/libwebsockets/lws-cgi.h [new file with mode: 0644]
include/libwebsockets/lws-client.h [new file with mode: 0644]
include/libwebsockets/lws-context-vhost.h [new file with mode: 0644]
include/libwebsockets/lws-dbus.h [new file with mode: 0644]
include/libwebsockets/lws-diskcache.h [new file with mode: 0644]
include/libwebsockets/lws-dsh.h [new file with mode: 0644]
include/libwebsockets/lws-esp32.h [new file with mode: 0644]
include/libwebsockets/lws-fts.h [new file with mode: 0644]
include/libwebsockets/lws-genaes.h [new file with mode: 0644]
include/libwebsockets/lws-gencrypto.h [new file with mode: 0644]
include/libwebsockets/lws-genec.h [new file with mode: 0644]
include/libwebsockets/lws-genhash.h [new file with mode: 0644]
include/libwebsockets/lws-genrsa.h [new file with mode: 0644]
include/libwebsockets/lws-http.h [new file with mode: 0644]
include/libwebsockets/lws-jose.h [new file with mode: 0644]
include/libwebsockets/lws-jwe.h [new file with mode: 0644]
include/libwebsockets/lws-jwk.h [new file with mode: 0644]
include/libwebsockets/lws-jws.h [new file with mode: 0644]
include/libwebsockets/lws-lejp.h [moved from lib/lejp.h with 64% similarity]
include/libwebsockets/lws-logs.h [new file with mode: 0644]
include/libwebsockets/lws-lwsac.h [new file with mode: 0644]
include/libwebsockets/lws-misc.h [new file with mode: 0644]
include/libwebsockets/lws-network-helper.h [new file with mode: 0644]
include/libwebsockets/lws-optee.h [new file with mode: 0644]
include/libwebsockets/lws-plugin-generic-sessions.h [new file with mode: 0644]
include/libwebsockets/lws-protocols-plugins.h [new file with mode: 0644]
include/libwebsockets/lws-purify.h [new file with mode: 0644]
include/libwebsockets/lws-retry.h [new file with mode: 0644]
include/libwebsockets/lws-ring.h [new file with mode: 0644]
include/libwebsockets/lws-sequencer.h [new file with mode: 0644]
include/libwebsockets/lws-service.h [new file with mode: 0644]
include/libwebsockets/lws-sha1-base64.h [new file with mode: 0644]
include/libwebsockets/lws-spa.h [new file with mode: 0644]
include/libwebsockets/lws-stats.h [new file with mode: 0644]
include/libwebsockets/lws-struct.h [new file with mode: 0644]
include/libwebsockets/lws-system.h [new file with mode: 0644]
include/libwebsockets/lws-test-sequencer.h [new file with mode: 0644]
include/libwebsockets/lws-threadpool.h [new file with mode: 0644]
include/libwebsockets/lws-timeout-timer.h [new file with mode: 0644]
include/libwebsockets/lws-tokenize.h [new file with mode: 0644]
include/libwebsockets/lws-vfs.h [new file with mode: 0644]
include/libwebsockets/lws-write.h [new file with mode: 0644]
include/libwebsockets/lws-writeable.h [new file with mode: 0644]
include/libwebsockets/lws-ws-close.h [new file with mode: 0644]
include/libwebsockets/lws-ws-ext.h [new file with mode: 0644]
include/libwebsockets/lws-ws-state.h [new file with mode: 0644]
include/libwebsockets/lws-x509.h [new file with mode: 0644]
lib/.gitignore [deleted file]
lib/README.md [new file with mode: 0644]
lib/abstract/README.md [new file with mode: 0644]
lib/abstract/abstract.c [new file with mode: 0644]
lib/abstract/private.h [new file with mode: 0644]
lib/abstract/protocols/smtp/smtp.c [new file with mode: 0644]
lib/abstract/test-sequencer.c [new file with mode: 0644]
lib/abstract/transports/raw-skt.c [new file with mode: 0644]
lib/abstract/transports/unit-test.c [new file with mode: 0644]
lib/alloc.c [deleted file]
lib/client-handshake.c [deleted file]
lib/client-parser.c [deleted file]
lib/client.c [deleted file]
lib/context.c [deleted file]
lib/core-net/adopt.c [new file with mode: 0644]
lib/core-net/client.c [new file with mode: 0644]
lib/core-net/close.c [new file with mode: 0644]
lib/core-net/connect.c [new file with mode: 0644]
lib/core-net/dummy-callback.c [new file with mode: 0644]
lib/core-net/lws-dsh.c [new file with mode: 0644]
lib/core-net/network.c [new file with mode: 0644]
lib/core-net/output.c [new file with mode: 0644]
lib/core-net/pollfd.c [new file with mode: 0644]
lib/core-net/private.h [new file with mode: 0644]
lib/core-net/sequencer.c [new file with mode: 0644]
lib/core-net/server.c [new file with mode: 0644]
lib/core-net/service.c [new file with mode: 0644]
lib/core-net/sorted-usec-list.c [new file with mode: 0644]
lib/core-net/stats.c [new file with mode: 0644]
lib/core-net/vhost.c [new file with mode: 0644]
lib/core-net/wsi-timeout.c [new file with mode: 0644]
lib/core-net/wsi.c [new file with mode: 0644]
lib/core/alloc.c [new file with mode: 0644]
lib/core/buflist.c [new file with mode: 0644]
lib/core/context.c [new file with mode: 0644]
lib/core/libwebsockets.c [new file with mode: 0644]
lib/core/logs.c [new file with mode: 0644]
lib/core/lws_dll.c [new file with mode: 0644]
lib/core/lws_dll2.c [new file with mode: 0644]
lib/core/private.h [new file with mode: 0644]
lib/core/vfs.c [new file with mode: 0644]
lib/event-libs/README.md [new file with mode: 0644]
lib/event-libs/libev/libev.c [new file with mode: 0644]
lib/event-libs/libev/private.h [new file with mode: 0644]
lib/event-libs/libevent/libevent.c [new file with mode: 0644]
lib/event-libs/libevent/private.h [new file with mode: 0644]
lib/event-libs/libuv/libuv.c [new file with mode: 0644]
lib/event-libs/libuv/private.h [new file with mode: 0644]
lib/event-libs/poll/poll.c [new file with mode: 0644]
lib/event-libs/poll/private.h [new file with mode: 0644]
lib/event-libs/private.h [new file with mode: 0644]
lib/extension-permessage-deflate.c [deleted file]
lib/handshake.c [deleted file]
lib/header.c [deleted file]
lib/hpack.c [deleted file]
lib/http2.c [deleted file]
lib/jose/README.md [new file with mode: 0644]
lib/jose/jwe/enc/aescbc.c [new file with mode: 0644]
lib/jose/jwe/enc/aesgcm.c [new file with mode: 0644]
lib/jose/jwe/enc/aeskw.c [new file with mode: 0644]
lib/jose/jwe/jwe-ecdh-es-aeskw.c [new file with mode: 0644]
lib/jose/jwe/jwe-rsa-aescbc.c [new file with mode: 0644]
lib/jose/jwe/jwe-rsa-aesgcm.c [new file with mode: 0644]
lib/jose/jwe/jwe.c [new file with mode: 0644]
lib/jose/jwe/private.h [new file with mode: 0644]
lib/jose/jwk/jwk.c [new file with mode: 0644]
lib/jose/jws/jose.c [new file with mode: 0644]
lib/jose/jws/jws.c [new file with mode: 0644]
lib/jose/jws/private.h [new file with mode: 0644]
lib/jose/private.h [new file with mode: 0644]
lib/lextable.h [deleted file]
lib/libev.c [deleted file]
lib/libevent.c [deleted file]
lib/libuv.c [deleted file]
lib/libwebsockets.c [deleted file]
lib/libwebsockets.h [deleted file]
lib/lws-plat-esp8266.c [deleted file]
lib/lws-plat-optee.c [deleted file]
lib/lws-plat-unix.c [deleted file]
lib/lws-plat-win.c [deleted file]
lib/misc/base64-decode.c [moved from lib/base64-decode.c with 80% similarity]
lib/misc/daemonize.c [moved from lib/daemonize.c with 81% similarity]
lib/misc/dir.c [new file with mode: 0644]
lib/misc/diskcache.c [new file with mode: 0644]
lib/misc/fts/README.md [new file with mode: 0644]
lib/misc/fts/private.h [new file with mode: 0644]
lib/misc/fts/trie-fd.c [new file with mode: 0644]
lib/misc/fts/trie.c [new file with mode: 0644]
lib/misc/getifaddrs.c [moved from lib/getifaddrs.c with 95% similarity]
lib/misc/getifaddrs.h [moved from lib/getifaddrs.h with 100% similarity]
lib/misc/lejp.c [moved from lib/lejp.c with 66% similarity]
lib/misc/lws-ring.c [new file with mode: 0644]
lib/misc/lws-struct-lejp.c [new file with mode: 0644]
lib/misc/lws-struct-sqlite.c [new file with mode: 0644]
lib/misc/lwsac/README.md [new file with mode: 0644]
lib/misc/lwsac/cached-file.c [new file with mode: 0644]
lib/misc/lwsac/lwsac.c [new file with mode: 0644]
lib/misc/lwsac/private.h [new file with mode: 0644]
lib/misc/peer-limits.c [new file with mode: 0644]
lib/misc/romfs.c [moved from lib/romfs.c with 95% similarity]
lib/misc/romfs.h [moved from lib/romfs.h with 100% similarity]
lib/misc/sha-1.c [moved from lib/sha-1.c with 96% similarity]
lib/misc/threadpool/README.md [new file with mode: 0644]
lib/misc/threadpool/threadpool.c [new file with mode: 0644]
lib/output.c [deleted file]
lib/parsers.c [deleted file]
lib/plat/esp32/esp32-fds.c [new file with mode: 0644]
lib/plat/esp32/esp32-file.c [new file with mode: 0644]
lib/plat/esp32/esp32-helpers.c [moved from lib/lws-plat-esp32.c with 53% similarity]
lib/plat/esp32/esp32-init.c [new file with mode: 0644]
lib/plat/esp32/esp32-misc.c [new file with mode: 0644]
lib/plat/esp32/esp32-pipe.c [new file with mode: 0644]
lib/plat/esp32/esp32-service.c [new file with mode: 0644]
lib/plat/esp32/esp32-sockets.c [new file with mode: 0644]
lib/plat/esp32/esp_attr.h [new file with mode: 0644]
lib/plat/esp32/private.h [new file with mode: 0644]
lib/plat/optee/lws-plat-optee.c [new file with mode: 0644]
lib/plat/optee/network.c [new file with mode: 0644]
lib/plat/optee/private.h [new file with mode: 0644]
lib/plat/unix/private.h [new file with mode: 0644]
lib/plat/unix/unix-caps.c [new file with mode: 0644]
lib/plat/unix/unix-fds.c [new file with mode: 0644]
lib/plat/unix/unix-file.c [new file with mode: 0644]
lib/plat/unix/unix-init.c [new file with mode: 0644]
lib/plat/unix/unix-misc.c [new file with mode: 0644]
lib/plat/unix/unix-pipe.c [new file with mode: 0644]
lib/plat/unix/unix-plugins.c [new file with mode: 0644]
lib/plat/unix/unix-service.c [new file with mode: 0644]
lib/plat/unix/unix-sockets.c [new file with mode: 0644]
lib/plat/windows/private.h [new file with mode: 0644]
lib/plat/windows/windows-fds.c [new file with mode: 0644]
lib/plat/windows/windows-file.c [new file with mode: 0644]
lib/plat/windows/windows-init.c [new file with mode: 0644]
lib/plat/windows/windows-misc.c [new file with mode: 0644]
lib/plat/windows/windows-pipe.c [new file with mode: 0644]
lib/plat/windows/windows-plugins.c [new file with mode: 0644]
lib/plat/windows/windows-service.c [new file with mode: 0644]
lib/plat/windows/windows-sockets.c [new file with mode: 0644]
lib/pollfd.c [deleted file]
lib/private-libwebsockets.h [deleted file]
lib/roles/README.md [new file with mode: 0644]
lib/roles/cgi/cgi-server.c [new file with mode: 0644]
lib/roles/cgi/ops-cgi.c [new file with mode: 0644]
lib/roles/cgi/private.h [new file with mode: 0644]
lib/roles/dbus/README.md [new file with mode: 0644]
lib/roles/dbus/dbus.c [new file with mode: 0644]
lib/roles/dbus/private.h [new file with mode: 0644]
lib/roles/h1/ops-h1.c [new file with mode: 0644]
lib/roles/h1/private.h [new file with mode: 0644]
lib/roles/h2/hpack.c [new file with mode: 0644]
lib/roles/h2/http2.c [new file with mode: 0644]
lib/roles/h2/huftable.h [moved from lib/huftable.h with 100% similarity]
lib/roles/h2/minihuf.c [moved from lib/minihuf.c with 98% similarity]
lib/roles/h2/ops-h2.c [new file with mode: 0644]
lib/roles/h2/private.h [new file with mode: 0644]
lib/roles/http/client/client-handshake.c [new file with mode: 0644]
lib/roles/http/client/client.c [new file with mode: 0644]
lib/roles/http/compression/README.md [new file with mode: 0644]
lib/roles/http/compression/brotli/brotli.c [new file with mode: 0644]
lib/roles/http/compression/deflate/deflate.c [new file with mode: 0644]
lib/roles/http/compression/private.h [new file with mode: 0644]
lib/roles/http/compression/stream.c [new file with mode: 0644]
lib/roles/http/header.c [new file with mode: 0644]
lib/roles/http/lextable-strings.h [moved from lib/lextable-strings.h with 88% similarity]
lib/roles/http/lextable.h [new file with mode: 0644]
lib/roles/http/minilex.c [moved from lib/minilex.c with 100% similarity]
lib/roles/http/private.h [new file with mode: 0644]
lib/roles/http/server/access-log.c [new file with mode: 0644]
lib/roles/http/server/fops-zip.c [moved from lib/fops-zip.c with 98% similarity]
lib/roles/http/server/lejp-conf.c [moved from lib/lejp-conf.c with 66% similarity]
lib/roles/http/server/lws-spa.c [new file with mode: 0644]
lib/roles/http/server/parsers.c [new file with mode: 0644]
lib/roles/http/server/ranges.c [moved from lib/ranges.c with 96% similarity]
lib/roles/http/server/rewrite.c [moved from lib/rewrite.c with 78% similarity]
lib/roles/http/server/server.c [new file with mode: 0644]
lib/roles/listen/ops-listen.c [new file with mode: 0644]
lib/roles/pipe/ops-pipe.c [new file with mode: 0644]
lib/roles/private.h [new file with mode: 0644]
lib/roles/raw-file/ops-raw-file.c [new file with mode: 0644]
lib/roles/raw-proxy/ops-raw-proxy.c [new file with mode: 0644]
lib/roles/raw-proxy/private.h [new file with mode: 0644]
lib/roles/raw-skt/ops-raw-skt.c [new file with mode: 0644]
lib/roles/ws/client-parser-ws.c [new file with mode: 0644]
lib/roles/ws/client-ws.c [new file with mode: 0644]
lib/roles/ws/ext/extension-permessage-deflate.c [new file with mode: 0644]
lib/roles/ws/ext/extension-permessage-deflate.h [moved from lib/extension-permessage-deflate.h with 79% similarity]
lib/roles/ws/ext/extension.c [moved from lib/extension.c with 70% similarity]
lib/roles/ws/ops-ws.c [new file with mode: 0644]
lib/roles/ws/private.h [new file with mode: 0644]
lib/roles/ws/server-ws.c [new file with mode: 0644]
lib/server-handshake.c [deleted file]
lib/server.c [deleted file]
lib/service.c [deleted file]
lib/smtp.c [deleted file]
lib/ssl-client.c [deleted file]
lib/ssl-http2.c [deleted file]
lib/ssl-server.c [deleted file]
lib/ssl.c [deleted file]
lib/tls/lws-gencrypto-common.c [new file with mode: 0644]
lib/tls/lws-genec-common.c [new file with mode: 0644]
lib/tls/mbedtls/lws-genaes.c [new file with mode: 0644]
lib/tls/mbedtls/lws-gencrypto.c [new file with mode: 0644]
lib/tls/mbedtls/lws-genec.c [new file with mode: 0644]
lib/tls/mbedtls/lws-genhash.c [new file with mode: 0644]
lib/tls/mbedtls/lws-genrsa.c [new file with mode: 0644]
lib/tls/mbedtls/mbedtls-client.c [new file with mode: 0644]
lib/tls/mbedtls/mbedtls-server.c [new file with mode: 0644]
lib/tls/mbedtls/private.h [new file with mode: 0644]
lib/tls/mbedtls/ssl.c [new file with mode: 0644]
lib/tls/mbedtls/tls.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl3.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_cert.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_code.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_dbg.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_lib.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_methods.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_pkey.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_stack.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_types.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/ssl_x509.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/tls1.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/internal/x509_vfy.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/openssl/ssl.h [new file with mode: 0755]
lib/tls/mbedtls/wrapper/include/platform/ssl_pm.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/include/platform/ssl_port.h [new file with mode: 0644]
lib/tls/mbedtls/wrapper/library/ssl_cert.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/library/ssl_lib.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/library/ssl_methods.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/library/ssl_pkey.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/library/ssl_stack.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/library/ssl_x509.c [new file with mode: 0644]
lib/tls/mbedtls/wrapper/platform/ssl_pm.c [new file with mode: 0755]
lib/tls/mbedtls/wrapper/platform/ssl_port.c [new file with mode: 0644]
lib/tls/mbedtls/x509.c [new file with mode: 0644]
lib/tls/openssl/lws-genaes.c [new file with mode: 0644]
lib/tls/openssl/lws-gencrypto.c [new file with mode: 0644]
lib/tls/openssl/lws-genec.c [new file with mode: 0644]
lib/tls/openssl/lws-genhash.c [new file with mode: 0644]
lib/tls/openssl/lws-genrsa.c [new file with mode: 0644]
lib/tls/openssl/openssl-client.c [new file with mode: 0644]
lib/tls/openssl/openssl-server.c [new file with mode: 0644]
lib/tls/openssl/private.h [new file with mode: 0644]
lib/tls/openssl/ssl.c [new file with mode: 0644]
lib/tls/openssl/tls.c [new file with mode: 0644]
lib/tls/openssl/x509.c [new file with mode: 0644]
lib/tls/private-network.h [new file with mode: 0644]
lib/tls/private.h [new file with mode: 0644]
lib/tls/tls-client.c [new file with mode: 0644]
lib/tls/tls-network.c [new file with mode: 0644]
lib/tls/tls-server.c [new file with mode: 0644]
lib/tls/tls.c [new file with mode: 0644]
libwebsockets.dox
libwebsockets.spec [deleted file]
lws_config.h.in [deleted file]
lwsws/etc-lwsws-conf.d-localhost-EXAMPLE
lwsws/main.c
lwsws/usr-lib-systemd-system-lwsws.service
mainpage.md [deleted file]
minimal-examples/README.md [new file with mode: 0644]
minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt [new file with mode: 0644]
minimal-examples/abstract/protocols/smtp-client/README.md [new file with mode: 0644]
minimal-examples/abstract/protocols/smtp-client/main.c [new file with mode: 0644]
minimal-examples/api-tests/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/canned-1.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/canned-2.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-fts/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-gencrypto/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-gencrypto/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-gencrypto/lws-genaes.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-gencrypto/lws-genec.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-gencrypto/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-gencrypto/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-jose/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-jose/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-jose/jwe.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-jose/jwk.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-jose/jws.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-jose/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-jose/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-lws_dsh/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_dsh/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_dsh/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_dsh/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-lws_sequencer/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_sequencer/libwebsockets.org.cer [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_sequencer/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_struct-json/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_struct-json/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_struct-json/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_struct-json/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-lws_tokenize/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_tokenize/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_tokenize/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-lws_tokenize/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-lwsac/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-lwsac/main.c [new file with mode: 0644]
minimal-examples/api-tests/api-test-lwsac/selftest.sh [new file with mode: 0755]
minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt [new file with mode: 0644]
minimal-examples/api-tests/api-test-smtp_client/README.md [new file with mode: 0644]
minimal-examples/api-tests/api-test-smtp_client/main.c [new file with mode: 0644]
minimal-examples/client-server/README.md [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/CMakeLists.txt [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/README.md [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/minimal-ws-proxy.c [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/mount-origin/example.js [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/mount-origin/favicon.ico [moved from test-server/favicon.ico with 100% similarity]
minimal-examples/client-server/minimal-ws-proxy/mount-origin/index.html [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/client-server/minimal-ws-proxy/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/crypto/README.md [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwe/CMakeLists.txt [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwe/README.md [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwe/key-rsa-4096.private [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwe/key-rsa-4096.pub [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwe/main.c [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwk/CMakeLists.txt [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwk/README.md [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jwk/main.c [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jws/CMakeLists.txt [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jws/README.md [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-jws/main.c [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-x509/CMakeLists.txt [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-x509/README.md [new file with mode: 0644]
minimal-examples/crypto/minimal-crypto-x509/main.c [new file with mode: 0644]
minimal-examples/dbus-client/README.md [new file with mode: 0644]
minimal-examples/dbus-client/minimal-dbus-client/CMakeLists.txt [new file with mode: 0644]
minimal-examples/dbus-client/minimal-dbus-client/README.md [new file with mode: 0644]
minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c [new file with mode: 0644]
minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/CMakeLists.txt [new file with mode: 0644]
minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/README.md [new file with mode: 0644]
minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c [new file with mode: 0644]
minimal-examples/dbus-server/README.md [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-server/CMakeLists.txt [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-server/README.md [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-server/main.c [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-ws-proxy/CMakeLists.txt [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-ws-proxy/README.md [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-ws-proxy/main.c [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-ws-proxy/org.libwebsockets.wsclientproxy.conf [new file with mode: 0644]
minimal-examples/dbus-server/minimal-dbus-ws-proxy/protocol_lws_minimal_dbus_ws_proxy.c [new file with mode: 0644]
minimal-examples/http-client/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-certinfo/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-certinfo/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-certinfo/warmcat.com.cer [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-custom-headers/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-custom-headers/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-custom-headers/warmcat.com.cer [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-hugeurl/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-hugeurl/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-hugeurl/selftest.sh [new file with mode: 0755]
minimal-examples/http-client/minimal-http-client-hugeurl/warmcat.com.cer [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-multi/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-multi/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-multi/selftest.sh [new file with mode: 0755]
minimal-examples/http-client/minimal-http-client-multi/warmcat.com.cer [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-post/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-post/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-post/libwebsockets.org.cer [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client-post/selftest.sh [new file with mode: 0755]
minimal-examples/http-client/minimal-http-client/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client/README.md [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client/minimal-http-client.c [new file with mode: 0644]
minimal-examples/http-client/minimal-http-client/selftest.sh [new file with mode: 0755]
minimal-examples/http-client/minimal-http-client/warmcat.com.cer [new file with mode: 0644]
minimal-examples/http-server/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/ba-passwords [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-secret-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-basicauth/mount-secret-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/error.css [moved from test-server/android/app/src/main/libs/placeholder with 100% similarity]
minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/ba-passwords [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/deaddrop.css [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/deaddrop.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/drop.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-deaddrop/uploads/user1/placeholder.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/candide.zip [moved from test-server/candide.zip with 100% similarity]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/leaf.jpg [moved from test-server/leaf.jpg with 100% similarity]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/lws-common.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.css [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.js [moved from test-server/test.html with 79% similarity]
minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/mount-origin/after-form1.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-get/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/after-form1.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-lwsac/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-lwsac/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-lwsac/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-lwsac/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/mount-origin/after-form1.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-form-post/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/lws-fts.index [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/minimal-http-server.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/dorian-gray-wikipedia.jpg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/lws-fts.css [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/lws-fts.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-fulltext-search/the-picture-of-dorian-gray.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/minimal-http-server-generic-sessions.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/admin-login.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/example.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/failed-login.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/http2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lws-common.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs-logo.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs.css [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/md5.min.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/needadmin/admin-login.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/needauth/successful-login.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-forgot-fail.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-forgot-ok.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-register-fail.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-register-ok.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-verify-fail.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-verify-ok.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/seats.jpg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/sent-forgot-fail.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/sent-forgot-ok.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/successful-login.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/test.tar.bz2 [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/minimal-http-server.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/mount-origin/http2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-proxy/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/minimal-http-server-smp.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-smp/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/example.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/minimal-http-server-sse.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/mount-origin/example.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-sse/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/example.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/http2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/example.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/http2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/localhost-100y.cert [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/localhost-100y.key [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/example.js [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/http2.png [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server-tls/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/CMakeLists.txt [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/README.md [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/minimal-http-server.c [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/mount-origin/404.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/mount-origin/index.html [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/http-server/minimal-http-server/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/raw/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-adopt-tcp/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-adopt-tcp/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-adopt-udp/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/localhost-100y.cert [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/localhost-100y.key [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/404.html [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/index.html [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/raw/minimal-raw-file/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-file/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-file/minimal-raw-file.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-netcat/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-netcat/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-netcat/minimal-raw-netcat.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/localhost-100y.cert [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/localhost-100y.key [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/404.html [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/index.html [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-proxy/minimal-raw-proxy.c [new file with mode: 0644]
minimal-examples/raw/minimal-raw-vhost/CMakeLists.txt [new file with mode: 0644]
minimal-examples/raw/minimal-raw-vhost/README.md [new file with mode: 0644]
minimal-examples/raw/minimal-raw-vhost/localhost-100y.cert [new file with mode: 0644]
minimal-examples/raw/minimal-raw-vhost/localhost-100y.key [new file with mode: 0644]
minimal-examples/raw/minimal-raw-vhost/minimal-raw-vhost.c [new file with mode: 0644]
minimal-examples/selftests-library.sh [new file with mode: 0755]
minimal-examples/selftests.sh [new file with mode: 0755]
minimal-examples/ws-client/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-echo/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-echo/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-echo/protocol_lws_minimal_client_echo.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-ping/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-ping/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-ping/libwebsockets.org.cer [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-pmd-bulk/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-pmd-bulk/protocol_lws_minimal_pmd_bulk.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-rx/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-rx/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-rx/libwebsockets.org.cer [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-rx/selftest.sh [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-spam/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-spam/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-spam/libwebsockets.org.cer [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-spam/selftest.sh [new file with mode: 0755]
minimal-examples/ws-client/minimal-ws-client-tx/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-tx/README.md [new file with mode: 0644]
minimal-examples/ws-client/minimal-ws-client-tx/minimal-ws-client.c [new file with mode: 0644]
minimal-examples/ws-server/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/minimal-ws-broker.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-broker/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-echo/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-echo/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-echo/protocol_lws_minimal_server_echo.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-bulk/protocol_lws_minimal_pmd_bulk.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd-corner/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-pmd/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-ring/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads-smp/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/minimal-ws-server.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server-threads/protocol_lws_minimal.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/CMakeLists.txt [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/README.md [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/localhost-100y.cert [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/localhost-100y.key [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/mount-origin/example.js [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/mount-origin/favicon.ico [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/mount-origin/index.html [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/mount-origin/libwebsockets.org-logo.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/mount-origin/strict-csp.svg [new file with mode: 0644]
minimal-examples/ws-server/minimal-ws-server/protocol_lws_minimal.c [new file with mode: 0644]
module.json [deleted file]
plugin-standalone/protocol_example_standalone.c
plugins/acme-client/protocol_lws_acme_client.c [new file with mode: 0644]
plugins/deaddrop/README.md [new file with mode: 0644]
plugins/deaddrop/assets/deaddrop.css [new file with mode: 0644]
plugins/deaddrop/assets/deaddrop.js [new file with mode: 0644]
plugins/deaddrop/assets/drop.svg [new file with mode: 0644]
plugins/deaddrop/assets/index.html [new file with mode: 0644]
plugins/deaddrop/protocol_lws_deaddrop.c [new file with mode: 0644]
plugins/generic-sessions/assets/index.html
plugins/generic-sessions/assets/lwsgs.css [new file with mode: 0644]
plugins/generic-sessions/assets/lwsgs.js
plugins/generic-sessions/handlers.c
plugins/generic-sessions/private-lwsgs.h
plugins/generic-sessions/protocol_generic_sessions.c
plugins/generic-sessions/protocol_lws_messageboard.c
plugins/generic-sessions/utils.c
plugins/generic-table/assets/lwsgt.js
plugins/generic-table/protocol_table_dirlisting.c
plugins/protocol_client_loopback_test.c
plugins/protocol_dumb_increment.c
plugins/protocol_esp32_lws_ota.c
plugins/protocol_esp32_lws_scan.c
plugins/protocol_fulltext_demo.c [new file with mode: 0644]
plugins/protocol_lws_meta.c [deleted file]
plugins/protocol_lws_mirror.c
plugins/protocol_lws_raw_test.c
plugins/protocol_lws_server_status.c
plugins/protocol_lws_sshd_demo.c [new file with mode: 0644]
plugins/protocol_lws_status.c
plugins/protocol_post_demo.c
plugins/raw-proxy/README.md [new file with mode: 0644]
plugins/raw-proxy/protocol_lws_raw_proxy.c [new file with mode: 0644]
plugins/server-status.css [new file with mode: 0644]
plugins/server-status.html
plugins/server-status.js [new file with mode: 0644]
plugins/ssh-base/crypto/chacha.c [new file with mode: 0644]
plugins/ssh-base/crypto/ed25519.c [new file with mode: 0644]
plugins/ssh-base/crypto/fe25519.c [new file with mode: 0644]
plugins/ssh-base/crypto/fe25519.h [new file with mode: 0644]
plugins/ssh-base/crypto/ge25519.c [new file with mode: 0644]
plugins/ssh-base/crypto/ge25519.h [new file with mode: 0644]
plugins/ssh-base/crypto/ge25519_base.data [new file with mode: 0644]
plugins/ssh-base/crypto/poly1305.c [new file with mode: 0644]
plugins/ssh-base/crypto/sc25519.c [new file with mode: 0644]
plugins/ssh-base/crypto/sc25519.h [new file with mode: 0644]
plugins/ssh-base/crypto/smult_curve25519_ref.c [new file with mode: 0644]
plugins/ssh-base/include/lws-plugin-ssh.h [new file with mode: 0644]
plugins/ssh-base/include/lws-plugin-sshd-static-build-includes.h [new file with mode: 0644]
plugins/ssh-base/include/lws-ssh.h [new file with mode: 0644]
plugins/ssh-base/kex-25519.c [new file with mode: 0644]
plugins/ssh-base/sshd.c [new file with mode: 0644]
plugins/ssh-base/telnet.c [new file with mode: 0644]
release-checklist [deleted file]
scripts/FindLibWebSockets.cmake [deleted file]
scripts/attack.sh [moved from test-server/attack.sh with 61% similarity]
scripts/autobahn-test-client.sh [new file with mode: 0755]
scripts/autobahn-test-server.sh [new file with mode: 0755]
scripts/build-gcov.sh [new file with mode: 0755]
scripts/client-ca/certindex.txt [new file with mode: 0644]
scripts/client-ca/create-ca.sh [new file with mode: 0755]
scripts/client-ca/create-client-cert.sh [new file with mode: 0755]
scripts/client-ca/create-server-cert.sh [new file with mode: 0755]
scripts/client-ca/serial [new file with mode: 0644]
scripts/client-ca/tmp.cnf [new file with mode: 0644]
scripts/esp32.mk
scripts/gcov.sh [new file with mode: 0755]
scripts/h2load-smp.sh [new file with mode: 0755]
scripts/h2load.sh [new file with mode: 0755]
scripts/h2spec.sh [new file with mode: 0755]
scripts/libwebsockets.spec [new file with mode: 0644]
scripts/test-dbus-proxy.sh [new file with mode: 0755]
scripts/travis_control.sh [new file with mode: 0755]
scripts/travis_install.sh [new file with mode: 0755]
test-apps/1.png [new file with mode: 0644]
test-apps/2.png [new file with mode: 0644]
test-apps/3.png [new file with mode: 0644]
test-apps/4.png [new file with mode: 0644]
test-apps/5.png [new file with mode: 0644]
test-apps/6.png [new file with mode: 0644]
test-apps/7.png [new file with mode: 0644]
test-apps/8.png [new file with mode: 0644]
test-apps/android/README [moved from test-server/android/README with 100% similarity]
test-apps/android/app/app.iml [moved from test-server/android/app/app.iml with 100% similarity]
test-apps/android/app/build.gradle [moved from test-server/android/app/build.gradle with 100% similarity]
test-apps/android/app/src/main/AndroidManifest.xml [moved from test-server/android/app/src/main/AndroidManifest.xml with 100% similarity]
test-apps/android/app/src/main/java/org/libwebsockets/client/LwsService.java [moved from test-server/android/app/src/main/java/org/libwebsockets/client/LwsService.java with 100% similarity]
test-apps/android/app/src/main/java/org/libwebsockets/client/MainActivity.java [moved from test-server/android/app/src/main/java/org/libwebsockets/client/MainActivity.java with 100% similarity]
test-apps/android/app/src/main/java/org/libwebsockets/client/ThreadService.java [moved from test-server/android/app/src/main/java/org/libwebsockets/client/ThreadService.java with 100% similarity]
test-apps/android/app/src/main/jni/Android.mk [moved from test-server/android/app/src/main/jni/Android.mk with 100% similarity]
test-apps/android/app/src/main/jni/Application.mk [moved from test-server/android/app/src/main/jni/Application.mk with 100% similarity]
test-apps/android/app/src/main/jni/LwsService.cpp [moved from test-server/android/app/src/main/jni/LwsService.cpp with 99% similarity]
test-apps/android/app/src/main/jni/NativeLibs.mk [moved from test-server/android/app/src/main/jni/NativeLibs.mk with 99% similarity]
test-apps/android/app/src/main/libs/placeholder [new file with mode: 0644]
test-apps/android/app/src/main/res/drawable/warmcat.png [moved from test-server/android/app/src/main/res/drawable/warmcat.png with 100% similarity]
test-apps/android/app/src/main/res/layout/activity_main.xml [moved from test-server/android/app/src/main/res/layout/activity_main.xml with 100% similarity]
test-apps/android/app/src/main/res/mipmap-hdpi/ic_launcher.png [moved from test-server/android/app/src/main/res/mipmap-hdpi/ic_launcher.png with 100% similarity]
test-apps/android/app/src/main/res/mipmap-mdpi/ic_launcher.png [moved from test-server/android/app/src/main/res/mipmap-mdpi/ic_launcher.png with 100% similarity]
test-apps/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png [moved from test-server/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png with 100% similarity]
test-apps/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png [moved from test-server/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png with 100% similarity]
test-apps/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png [moved from test-server/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png with 100% similarity]
test-apps/android/app/src/main/res/values/colors.xml [moved from test-server/android/app/src/main/res/values/colors.xml with 100% similarity]
test-apps/android/app/src/main/res/values/dimens.xml [moved from test-server/android/app/src/main/res/values/dimens.xml with 100% similarity]
test-apps/android/app/src/main/res/values/strings.xml [moved from test-server/android/app/src/main/res/values/strings.xml with 100% similarity]
test-apps/android/app/src/main/res/values/styles.xml [moved from test-server/android/app/src/main/res/values/styles.xml with 100% similarity]
test-apps/android/build.gradle [moved from test-server/android/build.gradle with 100% similarity]
test-apps/android/gradle.properties [moved from test-server/android/gradle.properties with 100% similarity]
test-apps/android/settings.gradle [moved from test-server/android/settings.gradle with 100% similarity]
test-apps/candide.zip [new file with mode: 0644]
test-apps/favicon.ico [new file with mode: 0644]
test-apps/http2.png [new file with mode: 0644]
test-apps/leaf.jpg [new file with mode: 0644]
test-apps/libwebsockets-test-server.service [moved from test-server/libwebsockets-test-server.service with 100% similarity]
test-apps/libwebsockets.org-logo.png [moved from test-server/libwebsockets.org-logo.png with 100% similarity]
test-apps/libwebsockets.org-logo.svg [new file with mode: 0644]
test-apps/lws-cgi-test.sh [moved from test-server/lws-cgi-test.sh with 90% similarity]
test-apps/lws-common.js [new file with mode: 0644]
test-apps/lws-ssh-test-keys [new file with mode: 0644]
test-apps/lws-ssh-test-keys.pub [new file with mode: 0644]
test-apps/private/index.html [moved from test-server/private/index.html with 100% similarity]
test-apps/test-client.c [moved from test-server/test-client.c with 70% similarity]
test-apps/test-lejp.c [new file with mode: 0644]
test-apps/test-server.c [moved from test-server/test-server.c with 68% similarity]
test-apps/test-sshd.c [new file with mode: 0644]
test-apps/test.css [new file with mode: 0644]
test-apps/test.html [new file with mode: 0644]
test-apps/test.js [new file with mode: 0644]
test-apps/wss-over-h2.png [new file with mode: 0644]
test-server/.gitignore [deleted file]
test-server/fuzxy.c [deleted file]
test-server/lws-common.js [deleted file]
test-server/test-echo.c [deleted file]
test-server/test-fraggle.c [deleted file]
test-server/test-ping.c [deleted file]
test-server/test-server-dumb-increment.c [deleted file]
test-server/test-server-http.c [deleted file]
test-server/test-server-libev.c [deleted file]
test-server/test-server-libevent.c [deleted file]
test-server/test-server-libuv.c [deleted file]
test-server/test-server-pthreads.c [deleted file]
test-server/test-server-v2.0.c [deleted file]
test-server/test-server.h [deleted file]
travis_install.sh [deleted file]
win32port/version.rc.in [new file with mode: 0644]
win32port/win32helpers/getopt_long.c
win32port/zlib/crc32.c
win32port/zlib/deflate.c
win32port/zlib/gzclose.c [deleted file]
win32port/zlib/gzio.c [deleted file]
win32port/zlib/inflate.c
win32port/zlib/zconf.h

index 7a1adf8..21289f3 100644 (file)
@@ -1,4 +1,12 @@
 #Ignore build files
+CMakeCache.txt
+CMakeFiles
+build
+cmake_install.cmake
+lws-minimal*
+Makefile
+.cproject
+.project
 config.h
 config.log
 config.status
@@ -38,3 +46,12 @@ ar-lib
 libwebsockets.pc
 build/
 *.swp
+doc
+/build2/
+/build3/
+/cov-int/
+/.vs/
+/build-mtls/
+/build-mingw64/
+/n9/
+/bb/
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..b14d9d1
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,3 @@
+Andy Green <andy@warmcat.com> <andy.green@linaro.org>
+Joakim Söderberg <joakim.soderberg@gmail.com>
+
index 3f5293c..df21b90 100644 (file)
@@ -4,26 +4,37 @@ env:
   global:
     - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI="
   matrix:
-    - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON"
-    - LWS_METHOD=default
-    - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON"
-    - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON"
-    - LWS_METHOD=noext CMAKE_ARGS="-DLWS_WITHOUT_EXTENSIONS=ON"
+    - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/ -DLWS_WITH_GENCRYPTO=1 -DLWS_WITH_JOSE=1"
+    - LWS_METHOD=lwsws2 CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/ -DLWS_WITH_LWS_DSH=1"
+    - LWS_METHOD=default CMAKE_ARGS="-DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG"
+    - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=noext CMAKE_ARGS="-DLWS_WITHOUT_EXTENSIONS=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=nonetwork CMAKE_ARGS="-DLWS_WITH_NETWORK=0"
     - LWS_METHOD=libev CMAKE_ARGS="-DLWS_WITH_LIBEV=ON"
     - LWS_METHOD=noipv6 CMAKE_ARGS="-DLWS_IPV6=OFF"
     - LWS_METHOD=nossl CMAKE_ARGS="-DLWS_WITH_SSL=OFF"
     - LWS_METHOD=nodaemon CMAKE_ARGS="-DLWS_WITHOUT_DAEMONIZE=ON"
     - LWS_METHOD=cgi CMAKE_ARGS="-DLWS_WITH_CGI=ON"
     - LWS_METHOD=nologs CMAKE_ARGS="-DLWS_WITH_NO_LOGS=ON"
+    - LWS_METHOD=smp CMAKE_ARGS="-DLWS_MAX_SMP=32 -DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=nows CMAKE_ARGS="-DLWS_ROLE_WS=0"
+    - LWS_METHOD=threadpool CMAKE_ARGS="-DLWS_WITH_THREADPOOL=1 -DLWS_WITH_MINIMAL_EXAMPLES=1"
 
 os:
   - linux
   - osx
 language: generic
 install:
-  - ./travis_install.sh
+  - ./scripts/travis_install.sh
+#  - ./travis-tool.sh github_package jimhester/covr
+
+#after_success:
+#  - Rscript -e 'covr::coveralls()'
+
 script:
-  - if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then mkdir build && cd build && cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. && cmake --build .; else if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "linux" ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build .; fi ; fi
+  - ./scripts/travis_control.sh
 sudo: required
 dist: trusty
 addons:
index 933ed63..4daeec8 100644 (file)
 cmake_minimum_required(VERSION 2.8.9)
 
+# General Advice
+#
+# For selecting between DEBUG / RELEASE, use -DCMAKE_BUILD_TYPE=DEBUG or =RELEASE
+#   debug builds include source level debug info and extra logging
+
+set(LWS_WITH_BUNDLED_ZLIB_DEFAULT OFF)
+if(WIN32)
+       set(LWS_WITH_BUNDLED_ZLIB_DEFAULT ON)
+endif()
+
+set(LWS_ROLE_RAW 1)
+set(LWS_WITH_POLL 1)
+
+#
+# Select features recommended for PC distro packaging
+#
+option(LWS_WITH_DISTRO_RECOMMENDED "Enable features recommended for distro packaging" OFF)
+option(LWS_FOR_GITOHASHI "Enable features recommended for use with gitohashi" OFF)
+
+#
+# Major individual features
+#
+option(LWS_WITH_NETWORK "Compile with network-related code" ON)
+option(LWS_ROLE_H1 "Compile with support for http/1 (needed for ws)" ON)
+option(LWS_ROLE_WS "Compile with support for websockets" ON)
+option(LWS_ROLE_DBUS "Compile with support for DBUS" OFF)
+option(LWS_ROLE_RAW_PROXY "Raw packet proxy" OFF)
+option(LWS_WITH_HTTP2 "Compile with server support for HTTP/2" ON)
+option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF)
+option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF)
+option(LWS_IPV6 "Compile with support for ipv6" OFF)
+option(LWS_UNIX_SOCK "Compile with support for UNIX domain socket" OFF)
+option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF)
+option(LWS_WITH_HTTP_PROXY "Support for HTTP proxying" OFF)
+option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" OFF)
+option(LWS_WITH_SOCKS5 "Allow use of SOCKS5 proxy on client connections" OFF)
+option(LWS_WITH_GENERIC_SESSIONS "With the Generic Sessions plugin" OFF)
+option(LWS_WITH_PEER_LIMITS "Track peers and restrict resources a single peer can allocate" OFF)
+option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF)
+option(LWS_WITH_RANGES "Support http ranges (RFC7233)" OFF)
+option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF)
+option(LWS_WITH_THREADPOOL "Managed worker thread pool support (relies on pthreads)" OFF)
+option(LWS_WITH_HTTP_STREAM_COMPRESSION "Support HTTP stream compression" OFF)
+option(LWS_WITH_HTTP_BROTLI "Also offer brotli http stream compression (requires LWS_WITH_HTTP_STREAM_COMPRESSION)" OFF)
+option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF)
+option(LWS_WITH_HUBBUB "Enable libhubbub rewriting support" OFF)
+option(LWS_WITH_FTS "Full Text Search support" OFF)
+#
+# TLS library options... all except mbedTLS are basically OpenSSL variants.
+#
+option(LWS_WITH_SSL "Include SSL support (defaults to OpenSSL or similar, mbedTLS if LWS_WITH_MBEDTLS is set)" ON)
+option(LWS_WITH_MBEDTLS "Use mbedTLS (>=2.0) replacement for OpenSSL. When setting this, you also may need to specify LWS_MBEDTLS_LIBRARIES and LWS_MBEDTLS_INCLUDE_DIRS" OFF)
+option(LWS_WITH_BORINGSSL "Use BoringSSL replacement for OpenSSL" OFF)
+option(LWS_WITH_CYASSL "Use CyaSSL replacement for OpenSSL. When setting this, you also need to specify LWS_CYASSL_LIBRARIES and LWS_CYASSL_INCLUDE_DIRS" OFF)
+option(LWS_WITH_WOLFSSL "Use wolfSSL replacement for OpenSSL. When setting this, you also need to specify LWS_WOLFSSL_LIBRARIES and LWS_WOLFSSL_INCLUDE_DIRS" OFF)
+option(LWS_SSL_CLIENT_USE_OS_CA_CERTS "SSL support should make use of the OS-installed CA root certs" ON)
+#
+# Event library options (may select multiple, or none for default poll()
+#
+option(LWS_WITH_LIBEV "Compile with support for libev" OFF)
+option(LWS_WITH_LIBUV "Compile with support for libuv" OFF)
+option(LWS_WITH_LIBEVENT "Compile with support for libevent" OFF)
+#
+# Static / Dynamic build options
+#
+option(LWS_WITH_STATIC "Build the static version of the library" ON)
+option(LWS_WITH_SHARED "Build the shared version of the library" ON)
+option(LWS_LINK_TESTAPPS_DYNAMIC "Link the test apps to the shared version of the library. Default is to link statically" OFF)
+option(LWS_STATIC_PIC "Build the static version of the library with position-independent code" OFF)
+#
+# Specific platforms
+#
+option(LWS_WITH_ESP32 "Build for ESP32" OFF)
+option(LWS_WITH_ESP32_HELPER "Build ESP32 helper" OFF)
+option(LWS_PLAT_OPTEE "Build for OPTEE" OFF)
+#
+# Client / Server / Test Apps build control
+#
+option(LWS_WITHOUT_CLIENT "Don't build the client part of the library" OFF)
+option(LWS_WITHOUT_SERVER "Don't build the server part of the library" OFF)
+option(LWS_WITHOUT_TESTAPPS "Don't build the libwebsocket-test-apps" OFF)
+option(LWS_WITHOUT_TEST_SERVER "Don't build the test server" OFF)
+option(LWS_WITHOUT_TEST_SERVER_EXTPOLL "Don't build the test server version that uses external poll" OFF)
+option(LWS_WITHOUT_TEST_PING "Don't build the ping test application" OFF)
+option(LWS_WITHOUT_TEST_CLIENT "Don't build the client test application" OFF)
+#
+# Extensions (permessage-deflate)
+#
+option(LWS_WITHOUT_EXTENSIONS "Don't compile with extensions" ON)
+#
+# Helpers + misc
+#
+option(LWS_WITHOUT_BUILTIN_GETIFADDRS "Don't use the BSD getifaddrs implementation from libwebsockets if it is missing (this will result in a compilation error) ... The default is to assume that your libc provides it. On some systems such as uclibc it doesn't exist." OFF)
+option(LWS_FALLBACK_GETHOSTBYNAME "Also try to do dns resolution using gethostbyname if getaddrinfo fails" OFF)
+option(LWS_WITHOUT_BUILTIN_SHA1 "Don't build the lws sha-1 (eg, because openssl will provide it" OFF)
+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_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF)
+option(LWS_WITH_LEJP "With the Lightweight JSON Parser" ON)
+option(LWS_WITH_SQLITE3 "Require SQLITE3 support" OFF)
+option(LWS_WITH_STRUCT_JSON "Generic struct serialization to and from JSON" ON)
+option(LWS_WITH_STRUCT_SQLITE3 "Generic struct serialization to and from SQLITE3" OFF)
+option(LWS_WITH_SMTP "Provide SMTP support" OFF)
+if (WIN32 OR LWS_WITH_ESP32)
+option(LWS_WITH_DIR "Directory scanning api support" OFF)
+option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" OFF)
+else()
+option(LWS_WITH_DIR "Directory scanning api support" ON)
+option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" ON)
+endif()
+option(LWS_WITH_NO_LOGS "Disable all logging from being compiled in" OFF)
+option(LWS_AVOID_SIGPIPE_IGN "Android 7+ reportedly needs this" OFF)
+option(LWS_WITH_STATS "Keep statistics of lws internal operations" OFF)
+option(LWS_WITH_JOSE "JSON Web Signature / Encryption / Keys (RFC7515/6/) API" OFF)
+option(LWS_WITH_GENCRYPTO "Enable support for Generic Crypto apis independent of TLS backend" OFF)
+option(LWS_WITH_SELFTESTS "Selftests run at context creation" OFF)
+option(LWS_WITH_GCOV "Build with gcc gcov coverage instrumentation" OFF)
+option(LWS_WITH_EXPORT_LWSTARGETS "Export libwebsockets CMake targets.  Disable if they conflict with an outer cmake project." ON)
+option(LWS_REPRODUCIBLE "Build libwebsockets reproducible. It removes the build user and hostname from the build" ON)
+option(LWS_WITH_MINIMAL_EXAMPLES "Also build the normally standalone minimal examples, for QA" OFF)
+option(LWS_WITH_LWSAC "lwsac Chunk Allocation api" ON)
+option(LWS_WITH_CUSTOM_HEADERS "Store and allow querying custom HTTP headers (H1 only)" ON)
+option(LWS_WITH_DISKCACHE "Hashed cache directory with lazy LRU deletion to size limit" OFF)
+option(LWS_WITH_ASAN "Build with gcc runtime sanitizer options enabled (needs libasan)" OFF)
+option(LWS_WITH_DIR "Directory scanning api support" OFF)
+option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" OFF)
+option(LWS_WITH_ZLIB "Include zlib support (required for extensions)" OFF)
+option(LWS_WITH_BUNDLED_ZLIB "Use bundled zlib version (Windows only)" ${LWS_WITH_BUNDLED_ZLIB_DEFAULT})
+option(LWS_WITH_MINIZ "Use miniz instead of zlib" OFF)
+option(LWS_WITH_DEPRECATED_LWS_DLL "Migrate to lws_dll2 instead ASAP" OFF)
+option(LWS_WITH_SEQUENCER "lws_seq_t support" ON)
+option(LWS_WITH_EXTERNAL_POLL "Support external POLL integration using callback messages (not recommended)" OFF)
+option(LWS_WITH_LWS_DSH "Support lws_dsh_t Disordered Shared Heap" OFF)
+#
+# to use miniz, enable both LWS_WITH_ZLIB and LWS_WITH_MINIZ
+#
+# End of user settings
+#
+
+# Workaround for ESP-IDF
+# Detect ESP_PLATFORM environment flag, if exist, set LWS_WITH_ESP32.
+# Otherwise the user may not be able to run configuration ESP-IDF in the first time.
+if(ESP_PLATFORM)
+       message(STATUS "ESP-IDF enabled")
+       set(LWS_WITH_ESP32 ON)
+else()
+       set(LWS_WITH_ESP32_HELPER OFF)
+endif()
+
+if (WIN32 OR LWS_WITH_ESP32)
+       message(STATUS "No LWS_WITH_DIR and LWS_WITH_DIR")
+       set(LWS_WITH_DIR OFF)
+       set(LWS_WITH_LEJP_CONF OFF)
+       message("LWS_WITH_DIR ${LWS_WITH_DIR}")
+else()
+       message(STATUS "Compiled with LWS_WITH_DIR and LWS_WITH_DIR")
+       set(LWS_WITH_DIR ON)
+       set(LWS_WITH_LEJP_CONF ON)
+endif()
+
+if (LWS_FOR_GITOHASHI)
+       set(LWS_WITH_THREADPOOL 1)
+       set(LWS_WITH_HTTP2 1)
+       set(LWS_UNIX_SOCK 1)
+       set(LWS_WITH_HTTP_PROXY 1)
+       set(LWS_WITH_FTS 1)
+       set(LWS_WITH_DISKCACHE 1)
+       set(LWS_WITH_LWSAC 1)
+       set(LWS_WITH_LEJP_CONF 1)
+endif()
+
+if(LWS_WITH_DISTRO_RECOMMENDED)
+       set(LWS_WITH_HTTP2 1)
+       set(LWS_WITH_LWSWS 1)
+       set(LWS_WITH_CGI 1)
+       set(LWS_IPV6 1)
+       set(LWS_WITH_ZIP_FOPS 1)
+       set(LWS_WITH_SOCKS5 1)
+       set(LWS_WITH_RANGES 1)
+       set(LWS_WITH_ACME 1)
+       set(LWS_WITH_SERVER_STATUS 1)
+       set(LWS_WITH_LIBUV 1)
+       set(LWS_WITH_LIBEV 1)
+       # libev + libevent cannot coexist at build-time
+       set(LWS_WITH_LIBEVENT 0)
+       set(LWS_WITHOUT_EXTENSIONS 0)
+       set(LWS_ROLE_DBUS 1)
+       set(LWS_WITH_FTS 1)
+       set(LWS_WITH_THREADPOOL 1)
+       set(LWS_UNIX_SOCK 1)
+       set(LWS_WITH_HTTP_PROXY 1)
+       set(LWS_WITH_DISKCACHE 1)
+       set(LWS_WITH_LWSAC 1)
+       set(LWS_WITH_LEJP_CONF 1)
+       set(LWS_WITH_PLUGINS 1)
+       set(LWS_ROLE_RAW_PROXY 1)
+       set(LWS_WITH_GENCRYPTO 1)
+       set(LWS_WITH_JOSE 1)
+endif()
+
+if (NOT LWS_WITH_NETWORK)
+       set(LWS_ROLE_H1 0)
+       set(LWS_ROLE_WS 0)
+       set(LWS_ROLE_RAW 0)
+       set(LWS_WITHOUT_EXTENSIONS 1)
+       set(LWS_WITHOUT_SERVER 1)
+       set(LWS_WITHOUT_CLIENT 1)
+       set(LWS_WITH_HTTP2 0)
+       set(LWS_WITH_SOCKS5 0)
+       set(LWS_UNIX_SOCK 0)
+       set(LWS_WITH_HTTP_PROXY 0)
+       set(LWS_WITH_PLUGINS 0)
+       set(LWS_WITH_LWSWS 0)
+       set(LWS_WITH_CGI 0)
+       set(LWS_ROLE_RAW_PROXY 0)
+       set(LWS_WITH_PEER_LIMITS 0)
+       set(LWS_WITH_GENERIC_SESSIONS 0)
+       set(LWS_WITH_HTTP_STREAM_COMPRESSION 0)
+       set(LWS_WITH_HTTP_BROTLI 0)
+       set(LWS_WITH_POLL 0)
+       set(LWS_WITH_SEQUENCER 0)
+       set(LWS_ROLE_DBUS 0)
+       set(LWS_WITH_LWS_DSH 0)
+endif()
+
+if (LWS_WITH_STRUCT_SQLITE3)
+       set(LWS_WITH_SQLITE3 1)
+endif()
+
+# do you care about this?  Then send me a patch where it disables it on travis
+# but allows it on APPLE
+if (APPLE)
+       set(LWS_ROLE_DBUS 0)
+endif()
+
 if(NOT DEFINED CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type")
 endif()
 
+# microsoft... that's why you can't have nice things
+
+if (WIN32 OR LWS_WITH_ESP32)
+       set(LWS_UNIX_SOCK 0)
+endif()
+
+if (LWS_WITH_ESP32)
+       set(LWS_WITH_LWSAC 0)
+       set(LWS_WITH_FTS 0)
+endif()
+
 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_MAJOR "3")
+set(CPACK_PACKAGE_VERSION_MINOR "2")
 set(CPACK_PACKAGE_VERSION_PATCH "0")
+set(CPACK_PACKAGE_RELEASE 1)
+set(CPACK_GENERATOR "RPM")
 set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
 set(CPACK_PACKAGE_VENDOR "andy@warmcat.com")
+set(CPACK_PACKAGE_CONTACT "andy@warmcat.com")
 set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PACKAGE} ${PACKAGE_VERSION}")
-set(SOVERSION "11")
+set(SOVERSION "15")
 if(NOT CPACK_GENERATOR)
     if(UNIX)
         set(CPACK_GENERATOR "TGZ")
@@ -33,92 +282,64 @@ set(LWS_LIBRARY_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH})
 
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
 
+
 message(STATUS "CMAKE_TOOLCHAIN_FILE='${CMAKE_TOOLCHAIN_FILE}'")
 
+if(WIN32)
+       configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32port/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/win32port/version.rc @ONLY)
+       set(RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/win32port/version.rc)
+endif()
+
 # Try to find the current Git hash.
 find_package(Git)
 if(GIT_EXECUTABLE)
        execute_process(
-    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
-    COMMAND "${GIT_EXECUTABLE}" describe
-    OUTPUT_VARIABLE GIT_HASH
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-    )
-       execute_process(
-    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
-    COMMAND "whoami"
-    OUTPUT_VARIABLE GIT_USER
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-    )
-       execute_process(
-    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
-    COMMAND "hostname"
-    OUTPUT_VARIABLE GIT_HOST
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-    )
-       string(REGEX REPLACE "([^\\])[\\]([^\\])" "\\1\\\\\\\\\\2" GIT_USER ${GIT_USER})
-    set(LWS_BUILD_HASH ${GIT_USER}@${GIT_HOST}-${GIT_HASH})
-    message("Git commit hash: ${LWS_BUILD_HASH}")
+               WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+               COMMAND "${GIT_EXECUTABLE}" describe --tags
+               OUTPUT_VARIABLE GIT_HASH
+               OUTPUT_STRIP_TRAILING_WHITESPACE
+               )
+       set(LWS_BUILD_HASH ${GIT_HASH})
+
+       # append the build user and hostname
+       if(NOT LWS_REPRODUCIBLE)
+               execute_process(
+                       WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+                       COMMAND "whoami"
+                       OUTPUT_VARIABLE GIT_USER
+                       OUTPUT_STRIP_TRAILING_WHITESPACE
+                       )
+               execute_process(
+                       WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+                       COMMAND "hostname"
+                       OUTPUT_VARIABLE GIT_HOST
+                       OUTPUT_STRIP_TRAILING_WHITESPACE
+                       )
+               string(REGEX REPLACE "([^\\])[\\]([^\\])" "\\1\\\\\\\\\\2" GIT_USER ${GIT_USER})
+               set(LWS_BUILD_HASH ${GIT_USER}@${GIT_HOST}-${GIT_HASH})
+       endif()
+
+       message("Git commit hash: ${LWS_BUILD_HASH}")
 endif()
 
-set(LWS_USE_BUNDLED_ZLIB_DEFAULT OFF)
-if(WIN32)
-       set(LWS_USE_BUNDLED_ZLIB_DEFAULT ON)
+# translate old functionality enables to set up ROLE enables so nothing changes
+if (LWS_WITH_HTTP2 AND LWS_WITHOUT_SERVER)
+       set(LWS_WITH_HTTP2 0)
+       message("HTTP2 disabled due to LWS_WITHOUT_SERVER")
 endif()
 
-option(LWS_WITH_STATIC "Build the static version of the library" ON)
-option(LWS_WITH_SHARED "Build the shared version of the library" ON)
-option(LWS_WITH_SSL "Include SSL support (default OpenSSL, wolfSSL if LWS_USE_WOLFSSL is set)" ON)
-option(LWS_USE_BORINGSSL "Use BoringSSL replacement for OpenSSL" OFF)
-option(LWS_USE_CYASSL "Use CyaSSL replacement for OpenSSL. When setting this, you also need to specify LWS_CYASSL_LIBRARIES and LWS_CYASSL_INCLUDE_DIRS" OFF)
-option(LWS_USE_WOLFSSL "Use wolfSSL replacement for OpenSSL. When setting this, you also need to specify LWS_WOLFSSL_LIBRARIES and LWS_WOLFSSL_INCLUDE_DIRS" OFF)
-option(LWS_WITH_ZLIB "Include zlib support (required for extensions)" ON)
-option(LWS_WITH_LIBEV "Compile with support for libev" OFF)
-option(LWS_WITH_LIBUV "Compile with support for libuv" OFF)
-option(LWS_WITH_LIBEVENT "Compile with support for libevent" OFF)
-option(LWS_USE_BUNDLED_ZLIB "Use bundled zlib version (Windows only)" ${LWS_USE_BUNDLED_ZLIB_DEFAULT})
-option(LWS_SSL_CLIENT_USE_OS_CA_CERTS "SSL support should make use of the OS-installed CA root certs" ON)
-option(LWS_WITHOUT_BUILTIN_GETIFADDRS "Don't use the BSD getifaddrs implementation from libwebsockets if it is missing (this will result in a compilation error) ... The default is to assume that your libc provides it. On some systems such as uclibc it doesn't exist." OFF)
-option(LWS_WITHOUT_BUILTIN_SHA1 "Don't build the lws sha-1 (eg, because openssl will provide it" OFF)
-option(LWS_WITHOUT_CLIENT "Don't build the client part of the library" OFF)
-option(LWS_WITHOUT_SERVER "Don't build the server part of the library" OFF)
-option(LWS_LINK_TESTAPPS_DYNAMIC "Link the test apps to the shared version of the library. Default is to link statically" OFF)
-option(LWS_WITHOUT_TESTAPPS "Don't build the libwebsocket-test-apps" OFF)
-option(LWS_WITHOUT_TEST_SERVER "Don't build the test server" OFF)
-option(LWS_WITHOUT_TEST_SERVER_EXTPOLL "Don't build the test server version that uses external poll" OFF)
-option(LWS_WITHOUT_TEST_PING "Don't build the ping test application" OFF)
-option(LWS_WITHOUT_TEST_ECHO "Don't build the echo test application" OFF)
-option(LWS_WITHOUT_TEST_CLIENT "Don't build the client test application" OFF)
-option(LWS_WITHOUT_TEST_FRAGGLE "Don't build the ping test application" OFF)
-option(LWS_WITHOUT_EXTENSIONS "Don't compile with extensions" OFF)
-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_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)
-option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF)
-option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF)
-option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF)
-option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF)
-option(LWS_WITH_LEJP "With the Lightweight JSON Parser" OFF)
-option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" OFF)
-option(LWS_WITH_GENERIC_SESSIONS "With the Generic Sessions plugin" OFF)
-option(LWS_WITH_SQLITE3 "Require SQLITE3 support" OFF)
-option(LWS_WITH_SMTP "Provide SMTP support" OFF)
-option(LWS_WITH_ESP8266 "Build for ESP8266" OFF)
-option(LWS_WITH_ESP32 "Build for ESP32" OFF)
-option(LWS_PLAT_OPTEE "Build for OPTEE" OFF)
-option(LWS_WITH_NO_LOGS "Disable all logging from being compiled in" OFF)
-option(LWS_STATIC_PIC "Build the static version of the library with position-independent code" OFF)
-option(LWS_WITH_RANGES "Support http ranges (RFC7233)" ON)
-option(LWS_FALLBACK_GETHOSTBYNAME "Also try to do dns resolution using gethostbyname if getaddrinfo fails" OFF)
-option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" ON)
-option(LWS_AVOID_SIGPIPE_IGN "Android 7+ seems to need this" OFF)
-option(LWS_WITH_STATS "Keep statistics of lws internal operations" OFF)
-option(LWS_WITH_SOCKS5 "Allow use of SOCKS5 proxy on client connections" OFF)
+if (LWS_WITH_HTTP2)
+       set(LWS_ROLE_H2 1)
+endif()
+if (LWS_WITH_CGI)
+       set(LWS_ROLE_CGI 1)
+endif()
+
+if (NOT LWS_ROLE_WS)
+       set(LWS_WITHOUT_EXTENSIONS 1)
+endif()
+
+include_directories(include plugins)
 
 if (LWS_WITH_LWSWS)
  message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV")
@@ -128,6 +349,29 @@ if (LWS_WITH_LWSWS)
  set(LWS_WITH_SERVER_STATUS 1)
  set(LWS_WITH_LEJP 1)
  set(LWS_WITH_LEJP_CONF 1)
+ set(LWS_WITH_PEER_LIMITS 1)
+ set(LWS_ROLE_RAW_PROXY 1)
+endif()
+
+# sshd plugin
+if (LWS_WITH_PLUGINS)
+ set(LWS_WITH_GENCRYPTO 1)
+endif()
+
+if (LWS_ROLE_RAW_PROXY)
+ set (LWS_WITHOUT_CLIENT 0)
+ set (LWS_WITHOUT_SERVER 0)
+endif()
+
+if (LWS_WITH_ACME)
+ set (LWS_WITHOUT_CLIENT 0)
+ set (LWS_WITHOUT_SERVER 0)
+ set (LWS_WITH_JOSE 1)
+endif()
+
+if (LWS_WITH_JOSE)
+ set(LWS_WITH_LEJP 1)
+ set(LWS_WITH_GENCRYPTO 1)
 endif()
 
 if (LWS_WITH_PLUGINS AND NOT LWS_WITH_LIBUV)
@@ -135,41 +379,20 @@ message(STATUS "LWS_WITH_PLUGINS --> Enabling LWS_WITH_LIBUV")
  set(LWS_WITH_LIBUV 1)
 endif()
 
-if (LWS_WITH_SMTP AND NOT LWS_WITH_LIBUV)
-message(STATUS "LWS_WITH_SMTP --> Enabling LWS_WITH_LIBUV")
- set(LWS_WITH_LIBUV 1)
+if (LWS_WITH_PLUGINS OR LWS_WITH_CGI)
+       # sshd plugin
+ set(LWS_WITH_GENCRYPTO 1)
 endif()
 
 if (LWS_WITH_GENERIC_SESSIONS)
  set(LWS_WITH_SQLITE3 1)
  set(LWS_WITH_SMTP 1)
-endif()
-
-if (LWS_WITH_SMTP AND NOT LWS_WITH_LIBUV)
-message(STATUS "LWS_WITH_SMTP --> Enabling LWS_WITH_LIBUV")
- set(LWS_WITH_LIBUV 1)
-endif()
-
-if (LWS_WITH_ESP8266)
- set(LWS_WITH_SHARED OFF)
- set(LWS_WITH_SSL OFF)
- set(LWS_WITH_ZLIB OFF)
- set(LWS_WITHOUT_CLIENT ON)
- set(LWS_WITHOUT_TESTAPPS ON)
- set(LWS_WITHOUT_EXTENSIONS ON)
- set(LWS_WITH_PLUGINS OFF)
- set(LWS_WITH_RANGES OFF)
- # this implies no pthreads in the lib
- set(LWS_MAX_SMP 1)
- set(LWS_HAVE_MALLOC 1)
- set(LWS_HAVE_REALLOC 1)
- set(LWS_HAVE_GETIFADDRS 1)
- set(LWS_WITH_ZIP_FOPS 0)
+ set(LWS_WITH_STRUCT_SQLITE3 1)
 endif()
 
 if (LWS_WITH_ESP32)
  set(LWS_WITH_SHARED OFF)
- set(LWS_WITH_SSL ON)
+ set(LWS_WITH_MBEDTLS ON)
   # set(LWS_WITHOUT_CLIENT ON)
  set(LWS_WITHOUT_TESTAPPS ON)
  set(LWS_WITHOUT_EXTENSIONS ON)
@@ -181,21 +404,59 @@ if (LWS_WITH_ESP32)
  set(LWS_HAVE_REALLOC 1)
  set(LWS_HAVE_GETIFADDRS 1)
  set(LWS_WITH_ZIP_FOPS 1)
+ set(LWS_WITH_CUSTOM_HEADERS 0)
 endif()
 
 
 if (WIN32)
-# this implies no pthreads in the lib
 set(LWS_MAX_SMP 1)
+set(LWS_WITH_THREADPOOL 0)
 endif()
 
-
 if (LWS_WITHOUT_SERVER)
 set(LWS_WITH_LWSWS OFF)
 endif()
 
+if (LWS_WITH_LEJP_CONF)
+       set(LWS_WITH_DIR 1)
+endif()
+
+# confirm H1 relationships
+
+if (NOT LWS_ROLE_H1 AND LWS_ROLE_H2)
+       message(FATAL_ERROR "H2 requires LWS_ROLE_H1")
+endif()
+
+if (NOT LWS_ROLE_H1 AND LWS_ROLE_WS)
+       message(FATAL_ERROR "WS requires LWS_ROLE_H1")
+endif()
+
+if (NOT LWS_ROLE_H1 AND LWS_ROLE_CGI)
+       message(FATAL_ERROR "CGI requires LWS_ROLE_H1")
+endif()
+
+# confirm HTTP relationships
+
+if (NOT LWS_ROLE_H1 AND NOT LWS_ROLE_H2 AND LWS_WITH_HTTP_PROXY)
+       message(FATAL_ERROR "LWS_WITH_LWSWS requires LWS_ROLE_H1")
+endif()
+
+if (NOT LWS_ROLE_H1 AND NOT LWS_ROLE_H2 AND LWS_WITH_HTTP_PROXY)
+       message(FATAL_ERROR "LWS_WITH_HTTP_PROXY requires LWS_ROLE_H1")
+endif()
+
+if (NOT LWS_ROLE_H1 AND NOT LWS_ROLE_H2 AND LWS_WITH_RANGES)
+       message(FATAL_ERROR "LWS_WITH_RANGES requires LWS_ROLE_H1")
+endif()
+
+if (NOT LWS_ROLE_H1 AND NOT LWS_ROLE_H2 AND LWS_WITH_ACCESS_LOG)
+       message(FATAL_ERROR "LWS_WITH_ACCESS_LOG requires LWS_ROLE_H1")
+endif()
+
+
 if (LWS_WITH_HTTP_PROXY AND (LWS_WITHOUT_CLIENT OR LWS_WITHOUT_SERVER))
-       message(FATAL_ERROR "You have to enable both client and server for http proxy")
+       message("You have to enable both client and server for http proxy")
+       set(LWS_WITH_HTTP_PROXY 0)
 endif()
 
 # Allow the user to override installation directories.
@@ -205,37 +466,34 @@ set(LWS_INSTALL_INCLUDE_DIR   include CACHE PATH "Installation directory for hea
 set(LWS_INSTALL_EXAMPLES_DIR  bin CACHE PATH "Installation directory for example files")
 
 # Allow the user to use the old CyaSSL options/library in stead of wolfSSL
-if (LWS_USE_CYASSL AND LWS_USE_WOLFSSL)
-       message(FATAL_ERROR "LWS_USE_CYASSL and LWS_USE_WOLFSSL are mutually exclusive!")
+if (LWS_WITH_CYASSL AND LWS_WITH_WOLFSSL)
+       message(FATAL_ERROR "LWS_WITH_CYASSL and LWS_WITH_WOLFSSL are mutually exclusive!")
 endif()
-if (LWS_USE_CYASSL)
+if (LWS_WITH_CYASSL)
        # Copy CyaSSL options to the wolfSSL options
-       set(LWS_USE_WOLFSSL ${LWS_USE_CYASSL} CACHE BOOL "Use wolfSSL/CyaSSL instead of OpenSSL" FORCE)
+       set(LWS_WITH_WOLFSSL ${LWS_WITH_CYASSL} CACHE BOOL "Use wolfSSL/CyaSSL instead of OpenSSL" FORCE)
        set(LWS_WOLFSSL_LIBRARIES ${LWS_CYASSL_LIBRARIES} CACHE PATH "Path to wolfSSL/CyaSSL libraries" FORCE)
        set(LWS_WOLFSSL_INCLUDE_DIRS ${LWS_CYASSL_INCLUDE_DIRS} CACHE PATH "Path to wolfSSL/CyaSSL header files" FORCE)
 endif()
 
-if (LWS_WITHOUT_CLIENT AND LWS_WITHOUT_SERVER)
-       message(FATAL_ERROR "Makes no sense to compile with neither client nor server.")
-endif()
-
 if (NOT (LWS_WITH_STATIC OR LWS_WITH_SHARED))
        message(FATAL_ERROR "Makes no sense to compile with neither static nor shared libraries.")
 endif()
 
-if (NOT LWS_WITHOUT_EXTENSIONS)
-       if (NOT LWS_WITH_ZLIB)
-               message(FATAL_ERROR "zlib is required for extensions.")
-       endif()
+if (NOT LWS_WITHOUT_EXTENSIONS OR LWS_WITH_ZIP_FOPS)
+       set(LWS_WITH_ZLIB 1)
 endif()
 
-set(LWS_ZLIB_LIBRARIES CACHE PATH "Path to the zlib library")
-set(LWS_ZLIB_INCLUDE_DIRS CACHE PATH "Path to the zlib include directory")
+# if you gave LWS_WITH_MINIZ, point to MINIZ here if not found
+# automatically
+
+set(LWS_ZLIB_LIBRARIES CACHE PATH "Path to the zlib/miniz library")
+set(LWS_ZLIB_INCLUDE_DIRS CACHE PATH "Path to the zlib/miniz include directory")
 set(LWS_OPENSSL_LIBRARIES CACHE PATH "Path to the OpenSSL library")
 set(LWS_OPENSSL_INCLUDE_DIRS CACHE PATH "Path to the OpenSSL include directory")
 set(LWS_WOLFSSL_LIBRARIES CACHE PATH "Path to the wolfSSL library")
 set(LWS_WOLFSSL_INCLUDE_DIRS CACHE PATH "Path to the wolfSSL include directory")
-set( CACHE PATH "Path to the libev library")
+set(LWS_LIBEV_LIBRARIES CACHE PATH "Path to the libev library")
 set(LWS_LIBEV_INCLUDE_DIRS CACHE PATH "Path to the libev include directory")
 set(LWS_LIBUV_LIBRARIES CACHE PATH "Path to the libuv library")
 set(LWS_LIBUV_INCLUDE_DIRS CACHE PATH "Path to the libuv include directory")
@@ -249,7 +507,12 @@ if (NOT LWS_WITH_SSL)
        set(LWS_WITHOUT_BUILTIN_SHA1 OFF)
 endif()
 
-if (LWS_WITH_SSL AND NOT LWS_USE_WOLFSSL)
+if (LWS_WITH_BORINGSSL)
+       # boringssl deprecated EVP_PKEY
+       set (LWS_WITH_GENHASH OFF)
+endif()
+
+if (LWS_WITH_SSL AND NOT LWS_WITH_WOLFSSL AND NOT LWS_WITH_MBEDTLS)
        if ("${LWS_OPENSSL_LIBRARIES}" STREQUAL "" OR "${LWS_OPENSSL_INCLUDE_DIRS}" STREQUAL "")
        else()
                if (NOT LWS_WITH_ESP32)
@@ -260,13 +523,13 @@ if (LWS_WITH_SSL AND NOT LWS_USE_WOLFSSL)
        endif()
 endif()
 
-if (LWS_WITH_SSL AND LWS_USE_WOLFSSL)
+if (LWS_WITH_SSL AND LWS_WITH_WOLFSSL)
        if ("${LWS_WOLFSSL_LIBRARIES}" STREQUAL "" OR "${LWS_WOLFSSL_INCLUDE_DIRS}" STREQUAL "")
                if (NOT WOLFSSL_FOUND)
-                       if (LWS_USE_CYASSL)
-                               message(FATAL_ERROR "You must set LWS_CYASSL_LIBRARIES and LWS_CYASSL_INCLUDE_DIRS when LWS_USE_CYASSL is turned on.")
+                       if (LWS_WITH_CYASSL)
+                               message(FATAL_ERROR "You must set LWS_CYASSL_LIBRARIES and LWS_CYASSL_INCLUDE_DIRS when LWS_WITH_CYASSL is turned on.")
                        else()
-                               message(FATAL_ERROR "You must set LWS_WOLFSSL_LIBRARIES and LWS_WOLFSSL_INCLUDE_DIRS when LWS_USE_WOLFSSL is turned on.")
+                               message(FATAL_ERROR "You must set LWS_WOLFSSL_LIBRARIES and LWS_WOLFSSL_INCLUDE_DIRS when LWS_WITH_WOLFSSL is turned on.")
                        endif()
                endif()
        else()
@@ -275,12 +538,44 @@ if (LWS_WITH_SSL AND LWS_USE_WOLFSSL)
                set(WOLFSSL_FOUND 1)
        endif()
        set(USE_WOLFSSL 1)
-       if (LWS_USE_CYASSL)
+       set(LWS_WITH_TLS 1)
+       if (LWS_WITH_CYASSL)
                set(USE_OLD_CYASSL 1)
        endif()
 endif()
 
-if (LWS_WITH_ZLIB AND NOT LWS_USE_BUNDLED_ZLIB)
+if (LWS_WITH_SSL AND LWS_WITH_MBEDTLS)
+       if ("${LWS_MBEDTLS_LIBRARIES}" STREQUAL "" OR "${LWS_MBEDTLS_INCLUDE_DIRS}" STREQUAL "" AND NOT LWS_WITH_ESP32)
+
+               find_path(LWS_MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
+
+               find_library(MBEDTLS_LIBRARY mbedtls)
+               find_library(MBEDX509_LIBRARY mbedx509)
+               find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
+
+               set(LWS_MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
+
+               include(FindPackageHandleStandardArgs)
+               find_package_handle_standard_args(MBEDTLS DEFAULT_MSG
+                       LWS_MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
+
+               mark_as_advanced(LWS_MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
+
+               if ("${LWS_MBEDTLS_LIBRARIES}" STREQUAL "" OR "${LWS_MBEDTLS_INCLUDE_DIRS}" STREQUAL "")
+                       message(FATAL_ERROR "You must set LWS_MBEDTLS_LIBRARIES and LWS_MBEDTLS_INCLUDE_DIRS when LWS_WITH_MBEDTLS is turned on.")
+               endif()
+       endif()
+       set(MBEDTLS_LIBRARIES ${LWS_MBEDTLS_LIBRARIES})
+       set(MBEDTLS_INCLUDE_DIRS ${LWS_MBEDTLS_INCLUDE_DIRS})
+       set(MBEDTLS_FOUND 1)
+       set(USE_MBEDTLS 1)
+endif()
+
+if (LWS_WITH_HTTP_STREAM_COMPRESSION)
+       set(LWS_WITH_ZLIB 1)
+endif()
+
+if (LWS_WITH_ZLIB AND NOT LWS_WITH_BUNDLED_ZLIB)
        if ("${LWS_ZLIB_LIBRARIES}" STREQUAL "" OR "${LWS_ZLIB_INCLUDE_DIRS}" STREQUAL "")
        else()
                set(ZLIB_LIBRARIES ${LWS_ZLIB_LIBRARIES})
@@ -326,6 +621,10 @@ if (LWS_WITH_SQLITE3)
 endif()
 
 
+if (LWS_WITH_LIBEV AND LWS_WITH_LIBEVENT)
+       message(FATAL_ERROR "Sorry libev and libevent conflict with each others' namespace, you can only have one or the other")
+endif()
+
 # The base dir where the test-apps look for the SSL certs.
 set(LWS_OPENSSL_CLIENT_CERTS ../share CACHE PATH "Server SSL certificate directory")
 if (WIN32)
@@ -339,12 +638,10 @@ else()
        set(LWS_OPENSSL_CLIENT_CERTS /etc/pki/tls/certs/ CACHE PATH "Client SSL certificate directory")
 endif()
 
-if (LWS_WITHOUT_EXTENSIONS)
-       set(LWS_NO_EXTENSIONS 1)
-endif()
-
-if (LWS_WITH_SSL)
+# LWS_OPENSSL_SUPPORT deprecated... use LWS_WITH_TLS
+if (LWS_WITH_SSL OR LWS_WITH_MBEDTLS)
        set(LWS_OPENSSL_SUPPORT 1)
+       set(LWS_WITH_TLS 1)
 endif()
 
 if (LWS_SSL_CLIENT_USE_OS_CA_CERTS)
@@ -368,41 +665,45 @@ if (LWS_WITHOUT_CLIENT)
 endif()
 
 if (LWS_WITH_LIBEV)
-       set(LWS_USE_LIBEV 1)
+       set(LWS_WITH_LIBEV 1)
 endif()
 
 if (LWS_WITH_LIBUV)
-       set(LWS_USE_LIBUV 1)
+       set(LWS_WITH_LIBUV 1)
 endif()
 
 if (LWS_WITH_LIBEVENT)
-       set(LWS_USE_LIBEVENT 1)
+       set(LWS_WITH_LIBEVENT 1)
 endif()
 
 if (LWS_IPV6)
-       set(LWS_USE_IPV6 1)
+       set(LWS_WITH_IPV6 1)
 endif()
 
 if (LWS_UNIX_SOCK)
-    set(LWS_USE_UNIX_SOCK 1)
+    set(LWS_WITH_UNIX_SOCK 1)
 endif()
 
 if (LWS_WITH_HTTP2)
-       set(LWS_USE_HTTP2 1)
+       set(LWS_WITH_HTTP2 1)
 endif()
 
 if ("${LWS_MAX_SMP}" STREQUAL "")
        set(LWS_MAX_SMP 1)
 endif()
 
+# using any abstract protocol enables LWS_WITH_ABSTRACT
 
-if (LWS_WITH_ESP8266)
-set(CMAKE_C_FLAGS "-nostdlib ${CMAKE_C_FLAGS}")
+if (LWS_WITH_SMTP)
+       set(LWS_WITH_ABSTRACT 1)
 endif()
 
+
+
 if (MINGW)
        set(LWS_MINGW_SUPPORT 1)
        set(CMAKE_C_FLAGS "-D__USE_MINGW_ANSI_STDIO ${CMAKE_C_FLAGS}")
+       add_definitions(-DWINVER=0x0601 -D_WIN32_WINNT=0x0601)
 endif()
 
 if (LWS_SSL_SERVER_WITH_ECDH_CERT)
@@ -450,12 +751,25 @@ include(CheckIncludeFile)
 include(CheckIncludeFiles)
 include(CheckLibraryExists)
 include(CheckTypeSize)
+include(CheckCSourceCompiles)
 
 if (LWS_WITHOUT_BUILTIN_SHA1)
        set(LWS_SHA1_USE_OPENSSL_NAME 1)
 endif()
 
-CHECK_FUNCTION_EXISTS(bzero LWS_HAVE_BZERO)
+if (HAIKU)
+       set(CMAKE_REQUIRED_LIBRARIES network)
+endif()
+
+CHECK_C_SOURCE_COMPILES(
+       "#include <malloc.h>
+       int main(int argc, char **argv) { return malloc_trim(0); }
+       " LWS_HAVE_MALLOC_TRIM)
+CHECK_C_SOURCE_COMPILES(
+       "#include <malloc.h>
+       int main(int argc, char **argv) { return (int)malloc_usable_size((void *)0); }
+       " LWS_HAVE_MALLOC_USABLE_SIZE)
+
 CHECK_FUNCTION_EXISTS(fork LWS_HAVE_FORK)
 CHECK_FUNCTION_EXISTS(getenv LWS_HAVE_GETENV)
 CHECK_FUNCTION_EXISTS(malloc LWS_HAVE_MALLOC)
@@ -473,6 +787,7 @@ CHECK_FUNCTION_EXISTS(getloadavg LWS_HAVE_GETLOADAVG)
 CHECK_FUNCTION_EXISTS(atoll LWS_HAVE_ATOLL)
 CHECK_FUNCTION_EXISTS(_atoi64 LWS_HAVE__ATOI64)
 CHECK_FUNCTION_EXISTS(_stat32i64 LWS_HAVE__STAT32I64)
+CHECK_FUNCTION_EXISTS(clock_gettime LWS_HAVE_CLOCK_GETTIME)
 
 if (NOT LWS_HAVE_GETIFADDRS)
        if (LWS_WITHOUT_BUILTIN_GETIFADDRS)
@@ -484,7 +799,6 @@ endif()
 CHECK_INCLUDE_FILE(dlfcn.h LWS_HAVE_DLFCN_H)
 CHECK_INCLUDE_FILE(fcntl.h LWS_HAVE_FCNTL_H)
 CHECK_INCLUDE_FILE(in6addr.h LWS_HAVE_IN6ADDR_H)
-CHECK_INCLUDE_FILE(inttypes.h LWS_HAVE_INTTYPES_H)
 CHECK_INCLUDE_FILE(memory.h LWS_HAVE_MEMORY_H)
 CHECK_INCLUDE_FILE(netinet/in.h LWS_HAVE_NETINET_IN_H)
 CHECK_INCLUDE_FILE(stdint.h LWS_HAVE_STDINT_H)
@@ -499,16 +813,65 @@ CHECK_INCLUDE_FILE(sys/types.h LWS_HAVE_SYS_TYPES_H)
 CHECK_INCLUDE_FILE(unistd.h LWS_HAVE_UNISTD_H)
 CHECK_INCLUDE_FILE(vfork.h LWS_HAVE_VFORK_H)
 CHECK_INCLUDE_FILE(sys/capability.h LWS_HAVE_SYS_CAPABILITY_H)
+CHECK_INCLUDE_FILE(malloc.h LWS_HAVE_MALLOC_H)
+CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+CHECK_INCLUDE_FILE(inttypes.h LWS_HAVE_INTTYPES_H)
 
-CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP) 
+CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP)
+
+if (LWS_ROLE_DBUS)
+
+       if (NOT LWS_DBUS_LIB)
+               set(LWS_DBUS_LIB "dbus-1")
+       endif()
+
+       CHECK_LIBRARY_EXISTS(${LWS_DBUS_LIB} dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
+       if (NOT LWS_HAVE_LIBDBUS)
+               message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE1)
+               # look in fedora and debian / ubuntu place
+               if (EXISTS "/usr/include/dbus-1.0")
+                       set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
+               endif()
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE2)
+               # look in fedora... debian / ubuntu has the ARCH in the path...
+               if (EXISTS "/usr/lib64/dbus-1.0/include")
+                       set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
+               endif()
+       endif()
+
+       set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
+
+       CHECK_C_SOURCE_COMPILES("#include <dbus/dbus.h>
+       int main(void) {
+               return 0;
+       }" LWS_DBUS_CHECK_OK)
+endif()
 
 if (LWS_WITH_LIBUV)
 CHECK_INCLUDE_FILE(uv-version.h LWS_HAVE_UV_VERSION_H)
+  # libuv changed the location in 1.21.0. Retain both
+  # checks temporarily to ensure a smooth transition.
+  if (NOT LWS_HAVE_UV_VERSION_H)
+    CHECK_INCLUDE_FILE(uv/version.h LWS_HAVE_NEW_UV_VERSION_H)
+  endif()
 endif()
 
 
-if (LWS_WITH_ZLIB AND NOT LWS_USE_BUNDLED_ZLIB)
-       CHECK_INCLUDE_FILE(zlib.h LWS_HAVE_ZLIB_H)
+if (LWS_WITH_ZLIB AND NOT LWS_WITH_BUNDLED_ZLIB)
+       if (LWS_WITH_MINIZ)
+               CHECK_INCLUDE_FILE(miniz.h LWS_HAVE_ZLIB_H)
+       else()
+               CHECK_INCLUDE_FILE(zlib.h LWS_HAVE_ZLIB_H)
+       endif()
 endif()
 
 # TODO: These can also be tested to see whether they actually work...
@@ -523,180 +886,536 @@ CHECK_C_SOURCE_COMPILES("#include <stdint.h>
                return 0;
        }" LWS_HAS_INTPTR_T)
 
-# These don't work Cross...
-#CHECK_TYPE_SIZE(pid_t PID_T_SIZE)
-#CHECK_TYPE_SIZE(size_t SIZE_T_SIZE)
-#CHECK_TYPE_SIZE("void *" LWS_SIZEOFPTR LANGUAGE C)
+set(CMAKE_REQUIRED_FLAGS "-pthread")   
+CHECK_C_SOURCE_COMPILES("#define _GNU_SOURCE 
+       #include <pthread.h> 
+       int main(void) { 
+               pthread_t th = 0;
+               pthread_setname_np(th, NULL);
+               return 0;
+       }" LWS_HAS_PTHREAD_SETNAME_NP)
+
+CHECK_C_SOURCE_COMPILES("#include <stddef.h>
+       #include <getopt.h> 
+       int main(void) { 
+               void *p = (void *)getopt_long;
+               return p != NULL;
+       }" LWS_HAS_GETOPT_LONG)
+
+
+if (NOT PID_T_SIZE)
+       set(pid_t int)
+endif()
+
+if (NOT SIZE_T_SIZE)
+       set(size_t "unsigned int")
+endif()
+
+if (NOT LWS_HAVE_MALLOC)
+       set(malloc rpl_malloc)
+endif()
+
+if (NOT LWS_HAVE_REALLOC)
+       set(realloc rpl_realloc)
+endif()
+
+if (UNIX)
+       execute_process(COMMAND uname -n OUTPUT_VARIABLE NODENAME)
+       # Need to chomp the \n at end of output.
+       string(REGEX REPLACE "[\n]+" "" NODENAME "${NODENAME}")
+
+       if( NODENAME STREQUAL "smartos" )
+               add_definitions( "-D__smartos__" )
+               set(SMARTOS 1)
+       endif()
+endif()
+
+if (MSVC)
+       # Turn off stupid microsoft security warnings.
+       add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
+endif(MSVC)
+
+include_directories("${PROJECT_SOURCE_DIR}/lib")
+
+# Group headers and sources.
+# Some IDEs use this for nicer file structure.
+set(HDR_PRIVATE
+       lib/core/private.h)
+
+set(HDR_PUBLIC
+       "${PROJECT_SOURCE_DIR}/include/libwebsockets.h"
+       "${PROJECT_BINARY_DIR}/lws_config.h"
+       "${PROJECT_SOURCE_DIR}/plugins/ssh-base/include/lws-plugin-ssh.h"
+       )
+
+set(SOURCES
+       lib/core/alloc.c
+       lib/core/buflist.c
+       lib/core/context.c
+       lib/core/lws_dll2.c
+       lib/core/libwebsockets.c
+       lib/core/logs.c
+       lib/misc/base64-decode.c
+       lib/core/vfs.c
+       lib/misc/lws-ring.c
+)
+
+if (LWS_WITH_DEPRECATED_LWS_DLL)
+       list(APPEND SOURCES
+               lib/core/lws_dll.c)
+endif()
+       
+if (LWS_WITH_NETWORK)
+       list(APPEND SOURCES
+               lib/core-net/dummy-callback.c
+               lib/core-net/output.c
+               lib/core-net/close.c
+               lib/core-net/network.c
+               lib/core-net/vhost.c
+               lib/core-net/pollfd.c
+               lib/core-net/service.c
+               lib/core-net/sorted-usec-list.c
+               lib/core-net/stats.c
+               lib/core-net/wsi.c
+               lib/core-net/wsi-timeout.c
+               lib/core-net/adopt.c
+               lib/roles/pipe/ops-pipe.c
+       )
+
+       if (LWS_WITH_LWS_DSH)
+               list(APPEND SOURCES
+                       lib/core-net/lws-dsh.c)
+       endif()
+
+       if (LWS_WITH_SEQUENCER)
+               list(APPEND SOURCES
+                       lib/core-net/sequencer.c)
+       endif()
+
+       if (LWS_WITH_ABSTRACT)
+               list(APPEND SOURCES
+                       lib/abstract/abstract.c
+               )
+               if (LWS_WITH_SEQUENCER)
+                       list(APPEND SOURCES
+                               lib/abstract/test-sequencer.c)
+               endif()
+       endif()
+
+       if (LWS_WITH_STATS)
+               list(APPEND SOURCES
+                       lib/core-net/stats.c
+               )
+       endif()
+endif()
+
+if (LWS_WITH_DIR)
+       list(APPEND SOURCES lib/misc/dir.c)
+endif()
+       
+if (LWS_WITH_THREADPOOL AND UNIX AND LWS_HAVE_PTHREAD_H)
+       list(APPEND SOURCES lib/misc/threadpool/threadpool.c)
+endif()
+
+if (LWS_ROLE_H1 OR LWS_ROLE_H2)
+       list(APPEND SOURCES
+               lib/roles/http/header.c
+               lib/roles/http/server/parsers.c)
+       if (LWS_WITH_HTTP_STREAM_COMPRESSION)
+               list(APPEND SOURCES
+                       lib/roles/http/compression/stream.c
+                       lib/roles/http/compression/deflate/deflate.c)
+               if (LWS_WITH_HTTP_BROTLI)
+                       list(APPEND SOURCES
+                               lib/roles/http/compression/brotli/brotli.c)
+               endif()
+       endif()
+endif()
+
+if (LWS_ROLE_H1)
+       list(APPEND SOURCES
+               lib/roles/h1/ops-h1.c)
+endif()
+
+if (LWS_ROLE_WS)
+       list(APPEND SOURCES
+               lib/roles/ws/ops-ws.c)
+       if (NOT LWS_WITHOUT_CLIENT)
+               list(APPEND SOURCES
+                       lib/roles/ws/client-ws.c
+                       lib/roles/ws/client-parser-ws.c)
+       endif()
+       if (NOT LWS_WITHOUT_SERVER)
+               list(APPEND SOURCES
+                       lib/roles/ws/server-ws.c)
+       endif()
+endif()
+
+if (LWS_ROLE_RAW)
+       list(APPEND SOURCES
+               lib/roles/raw-skt/ops-raw-skt.c
+               lib/roles/raw-file/ops-raw-file.c)
+               
+       if (LWS_WITH_ABSTRACT)
+               list(APPEND SOURCES
+                       lib/abstract/transports/raw-skt.c)
+       endif()
+endif()
 
-if (NOT PID_T_SIZE)
-       set(pid_t int)
+if (LWS_ROLE_RAW_PROXY)
+       list(APPEND SOURCES
+               lib/roles/raw-proxy/ops-raw-proxy.c)
 endif()
 
-if (NOT SIZE_T_SIZE)
-       set(size_t "unsigned int")
+if (LWS_ROLE_CGI)
+       list(APPEND SOURCES
+               lib/roles/cgi/cgi-server.c
+               lib/roles/cgi/ops-cgi.c)
 endif()
 
-if (NOT LWS_HAVE_MALLOC)
-       set(malloc rpl_malloc)
+if (LWS_ROLE_DBUS)
+       list(APPEND SOURCES
+               lib/roles/dbus/dbus.c)
 endif()
 
-if (NOT LWS_HAVE_REALLOC)
-       set(realloc rpl_realloc)
+if (LWS_WITH_ACCESS_LOG)
+       list(APPEND SOURCES
+               lib/roles/http/server/access-log.c)
 endif()
 
+if (LWS_WITH_PEER_LIMITS)
+       list(APPEND SOURCES
+               lib/misc/peer-limits.c)
+endif()
 
-if (MSVC)
-       # Turn off stupid microsoft security warnings.
-       add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
-endif(MSVC)
+if (LWS_WITH_LWSAC)
+       list(APPEND SOURCES
+               lib/misc/lwsac/lwsac.c
+               lib/misc/lwsac/cached-file.c)
+endif()
 
-include_directories("${PROJECT_SOURCE_DIR}/lib")
+if (LWS_WITH_FTS)
+       list(APPEND SOURCES
+               lib/misc/fts/trie.c
+               lib/misc/fts/trie-fd.c)
+endif()
 
-# Group headers and sources.
-# Some IDEs use this for nicer file structure.
-set(HDR_PRIVATE
-       lib/private-libwebsockets.h)
+if (LWS_WITH_DISKCACHE)
+       list(APPEND SOURCES
+               lib/misc/diskcache.c)
+endif()
 
-set(HDR_PUBLIC
-       "${PROJECT_SOURCE_DIR}/lib/libwebsockets.h"
-       "${PROJECT_BINARY_DIR}/lws_config.h")
+if (LWS_WITH_STRUCT_JSON)
+       list(APPEND SOURCES
+               lib/misc/lws-struct-lejp.c)
+endif()
 
-set(SOURCES
-       lib/base64-decode.c
-       lib/handshake.c
-       lib/libwebsockets.c
-       lib/service.c
-       lib/pollfd.c
-       lib/output.c
-       lib/parsers.c
-       lib/context.c
-       lib/alloc.c
-       lib/header.c)
+if (LWS_WITH_STRUCT_SQLITE3)
+       list(APPEND SOURCES
+               lib/misc/lws-struct-sqlite.c)
+endif()
 
 if (NOT LWS_WITHOUT_CLIENT)
        list(APPEND SOURCES
-               lib/client.c
-               lib/client-handshake.c
-               lib/client-parser.c)
+               lib/core-net/connect.c
+               lib/core-net/client.c
+               lib/roles/http/client/client.c
+               lib/roles/http/client/client-handshake.c)
+endif()
+
+if (NOT LWS_WITHOUT_SERVER)
+       list(APPEND SOURCES
+               lib/core-net/server.c
+               lib/roles/listen/ops-listen.c)
+endif()
+
+if (LWS_WITH_MBEDTLS)
+       set(LWS_WITH_SSL ON)
+       
+       include_directories(lib/tls/mbedtls/wrapper/include)
+       include_directories(lib/tls/mbedtls/wrapper/include/platform)
+       include_directories(lib/tls/mbedtls/wrapper/include/internal)
+       include_directories(lib/tls/mbedtls/wrapper/include/openssl)
+       
+       if (LWS_WITH_NETWORK)
+               list(APPEND HDR_PRIVATE
+                       lib/tls/mbedtls/wrapper/include/internal/ssl3.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_cert.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_code.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_dbg.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_lib.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_methods.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_pkey.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_stack.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_types.h
+                       lib/tls/mbedtls/wrapper/include/internal/ssl_x509.h
+                       lib/tls/mbedtls/wrapper/include/internal/tls1.h
+                       lib/tls/mbedtls/wrapper/include/internal/x509_vfy.h)
+       
+               list(APPEND HDR_PRIVATE
+                       lib/tls/mbedtls/wrapper/include/openssl/ssl.h)
+       
+               list(APPEND HDR_PRIVATE
+                       lib/tls/mbedtls/wrapper/include/platform/ssl_pm.h
+                       lib/tls/mbedtls/wrapper/include/platform/ssl_port.h)
+       
+               list(APPEND SOURCES
+                       lib/tls/mbedtls/wrapper/library/ssl_cert.c
+                       lib/tls/mbedtls/wrapper/library/ssl_lib.c
+                       lib/tls/mbedtls/wrapper/library/ssl_methods.c
+                       lib/tls/mbedtls/wrapper/library/ssl_pkey.c
+                       lib/tls/mbedtls/wrapper/library/ssl_stack.c
+                       lib/tls/mbedtls/wrapper/library/ssl_x509.c)
+       
+               list(APPEND SOURCES
+                       lib/tls/mbedtls/wrapper/platform/ssl_pm.c
+                       lib/tls/mbedtls/wrapper/platform/ssl_port.c)
+       endif()
 endif()
 
 if (LWS_WITH_SSL)
        list(APPEND SOURCES
-               lib/ssl.c)
+               lib/tls/tls.c
+       )
+       if (LWS_WITH_NETWORK)
+               list(APPEND SOURCES
+                       lib/tls/tls-network.c
+               )
+       endif()
+               
+       if (LWS_WITH_MBEDTLS)
+               list(APPEND SOURCES
+                       lib/tls/mbedtls/tls.c
+                       lib/tls/mbedtls/x509.c
+               )
+               if (LWS_WITH_NETWORK)
+                       list(APPEND SOURCES
+                               lib/tls/mbedtls/ssl.c
+                       )
+               endif()
+               if (LWS_WITH_GENCRYPTO)
+                       list(APPEND SOURCES
+                               lib/tls/mbedtls/lws-genhash.c
+                               lib/tls/mbedtls/lws-genrsa.c
+                               lib/tls/mbedtls/lws-genaes.c
+                               lib/tls/lws-genec-common.c
+                               lib/tls/mbedtls/lws-genec.c
+                               lib/tls/mbedtls/lws-gencrypto.c
+                       )
+               endif()
+       else()
+               list(APPEND SOURCES
+                       lib/tls/openssl/tls.c
+                       lib/tls/openssl/x509.c
+               )
+               if (LWS_WITH_NETWORK)
+                       list(APPEND SOURCES
+                               lib/tls/openssl/ssl.c
+                       )
+               endif()
+               if (LWS_WITH_GENCRYPTO)
+                       list(APPEND SOURCES
+                               lib/tls/openssl/lws-genhash.c
+                               lib/tls/openssl/lws-genrsa.c
+                               lib/tls/openssl/lws-genaes.c
+                               lib/tls/lws-genec-common.c
+                               lib/tls/openssl/lws-genec.c
+                               lib/tls/openssl/lws-gencrypto.c
+                       )
+               endif()
+       endif()
                
        if (NOT LWS_WITHOUT_SERVER)
                list(APPEND SOURCES
-               lib/ssl-server.c)
+                       lib/tls/tls-server.c)
+               if (LWS_WITH_MBEDTLS)
+                       list(APPEND SOURCES
+                               lib/tls/mbedtls/mbedtls-server.c)
+               else()
+                       list(APPEND SOURCES
+                               lib/tls/openssl/openssl-server.c)
+               endif()
        endif()
        if (NOT LWS_WITHOUT_CLIENT)
                list(APPEND SOURCES
-               lib/ssl-client.c)
+                       lib/tls/tls-client.c)
+               if (LWS_WITH_MBEDTLS)
+                       list(APPEND SOURCES
+                               lib/tls/mbedtls/mbedtls-client.c)
+               else()
+                       list(APPEND SOURCES
+                               lib/tls/openssl/openssl-client.c)
+               endif()
+               
        endif()
 endif()
 
 if (NOT LWS_WITHOUT_BUILTIN_SHA1)
        list(APPEND SOURCES
-               lib/sha-1.c)
+               lib/misc/sha-1.c)
 endif()
 
-if (LWS_WITH_HTTP2)
+if (LWS_WITH_HTTP2 AND NOT LWS_WITHOUT_SERVER)
        list(APPEND SOURCES
-               lib/http2.c
-               lib/hpack.c
-               lib/ssl-http2.c)
+               lib/roles/h2/http2.c
+               lib/roles/h2/hpack.c
+               lib/roles/h2/ops-h2.c)
 endif()
 # select the active platform files
 
 if (WIN32)
        list(APPEND SOURCES
-               lib/lws-plat-win.c)
+               lib/plat/windows/windows-fds.c
+               lib/plat/windows/windows-file.c
+               lib/plat/windows/windows-init.c
+               lib/plat/windows/windows-misc.c
+               lib/plat/windows/windows-pipe.c
+               lib/plat/windows/windows-plugins.c
+               lib/plat/windows/windows-service.c
+               lib/plat/windows/windows-sockets.c
+               )
 else()
 
-       if (LWS_WITH_ESP8266)
+       if (LWS_PLAT_OPTEE)
                list(APPEND SOURCES
-                       lib/lws-plat-esp8266.c)
+                       lib/plat/optee/lws-plat-optee.c
+               )
+               if (LWS_WITH_NETWORK)
+                       list(APPEND SOURCES
+                               lib/plat/optee/network.c
+                       )
+               endif()
        else()
-               if (LWS_PLAT_OPTEE)
+               if (LWS_WITH_ESP32)
                        list(APPEND SOURCES
-                               lib/lws-plat-optee.c)
+                               lib/plat/esp32/esp32-fds.c
+                               lib/plat/esp32/esp32-file.c
+                               lib/plat/esp32/esp32-init.c
+                               lib/plat/esp32/esp32-misc.c
+                               lib/plat/esp32/esp32-pipe.c
+                               lib/plat/esp32/esp32-service.c
+                               lib/plat/esp32/esp32-sockets.c
+                               lib/misc/romfs.c)
+                       if(LWS_WITH_ESP32_HELPER)
+                               list(APPEND SOURCES lib/plat/esp32/esp32-helpers.c)
+                       endif()
                else()
-                       if (LWS_WITH_ESP32)
-                               list(APPEND SOURCES
-                                       lib/lws-plat-esp32.c
-                                       lib/romfs.c)
-                       else()
+                       set(LWS_PLAT_UNIX 1)
+                       list(APPEND SOURCES
+                               lib/plat/unix/unix-caps.c
+                               lib/plat/unix/unix-file.c
+                               lib/plat/unix/unix-misc.c
+                               lib/plat/unix/unix-init.c
+                       )
+                       if (LWS_WITH_NETWORK)
                                list(APPEND SOURCES
-                                       lib/lws-plat-unix.c)
+                                       lib/plat/unix/unix-pipe.c
+                                       lib/plat/unix/unix-service.c
+                                       lib/plat/unix/unix-sockets.c
+                                       lib/plat/unix/unix-fds.c
+                               )
+                       endif()
+                               
+                       if (LWS_WITH_PLUGINS AND LWS_WITH_LIBUV)
+                               list(APPEND SOURCES lib/plat/unix/unix-plugins.c)
                        endif()
                endif()
        endif()
 endif()
 
-if (NOT LWS_WITHOUT_SERVER)
+if ((LWS_ROLE_H1 OR LWS_ROLE_H2) AND NOT LWS_WITHOUT_SERVER)
        list(APPEND SOURCES
-               lib/server.c
-               lib/server-handshake.c)
+               lib/roles/http/server/server.c
+               lib/roles/http/server/lws-spa.c)
 endif()
 
-if (NOT LWS_WITHOUT_EXTENSIONS)
+if (LWS_ROLE_WS AND NOT LWS_WITHOUT_EXTENSIONS)
        list(APPEND HDR_PRIVATE
-               lib/extension-permessage-deflate.h)
+               lib/roles/ws/ext/extension-permessage-deflate.h)
        list(APPEND SOURCES
-               lib/extension.c
-               lib/extension-permessage-deflate.c)
+               lib/roles/ws/ext/extension.c
+               lib/roles/ws/ext/extension-permessage-deflate.c)
 endif()
 
 if (LWS_WITH_HTTP_PROXY)
        list(APPEND SOURCES
-               lib/rewrite.c)
+               lib/roles/http/server/rewrite.c)
 endif()
 
-if (LWS_WITH_LIBEV)
+if (LWS_WITH_POLL AND LWS_WITH_NETWORK)
        list(APPEND SOURCES
-               lib/libev.c)
+               lib/event-libs/poll/poll.c)
 endif()
 
-if (LWS_WITH_LIBUV)
+if (LWS_WITH_LIBUV AND LWS_WITH_NETWORK)
        list(APPEND SOURCES
-               lib/libuv.c)
+               lib/event-libs/libuv/libuv.c)
 endif()
 
-if (LWS_WITH_LIBEVENT)
+if (LWS_WITH_LIBEVENT AND LWS_WITH_NETWORK)
+       list(APPEND SOURCES
+               lib/event-libs/libevent/libevent.c)
+endif()
+
+if (LWS_WITH_LIBEV AND LWS_WITH_NETWORK)
        list(APPEND SOURCES
-               lib/libevent.c)
+               lib/event-libs/libev/libev.c)
 endif()
 
 if (LWS_WITH_LEJP)
        list(APPEND SOURCES
-               lib/lejp.c)
-       list(APPEND HDR_PUBLIC
-               lib/lejp.h)
+               lib/misc/lejp.c)
 endif()        
-if (LWS_WITH_LEJP_CONF)
+if (LWS_WITH_LEJP_CONF AND LWS_WITH_NETWORK AND NOT LWS_PLAT_OPTEE)
                list(APPEND SOURCES
-                       "lib/lejp-conf.c"
+                       "lib/roles/http/server/lejp-conf.c"
                )
 endif()
 
+if (LWS_WITH_ABSTRACT)
+       list(APPEND SOURCES
+               lib/abstract/transports/unit-test.c)
+endif()
+
 if (LWS_WITH_SMTP)
        list(APPEND SOURCES
-               lib/smtp.c)
+               lib/abstract/protocols/smtp/smtp.c)
 endif()
 
 if (LWS_WITH_RANGES)
        list(APPEND SOURCES
-               lib/ranges.c)
+               lib/roles/http/server/ranges.c)
 endif()
 
 if (LWS_WITH_ZIP_FOPS)
        if (LWS_WITH_ZLIB)
                list(APPEND SOURCES
-                       lib/fops-zip.c)
+                       lib/roles/http/server/fops-zip.c)
        else()
                message(FATAL_ERROR "Pre-zipped file support (LWS_WITH_ZIP_FOPS) requires ZLIB (LWS_WITH_ZLIB)")
        endif()
 endif()
 
+if (LWS_WITH_JOSE)
+       list(APPEND SOURCES
+               lib/jose/jwk/jwk.c
+               lib/jose/jws/jose.c
+               lib/jose/jws/jws.c
+               lib/jose/jwe/jwe.c
+               lib/jose/jwe/enc/aescbc.c
+               lib/jose/jwe/enc/aesgcm.c
+               lib/jose/jwe/enc/aeskw.c
+               lib/jose/jwe/jwe-rsa-aescbc.c
+               lib/jose/jwe/jwe-rsa-aesgcm.c
+               lib/jose/jwe/jwe-ecdh-es-aeskw.c
+               )
+endif()
+
+if (LWS_WITH_JOSE OR LWS_WITH_GENCRYPTO)
+       list(APPEND SOURCES
+               lib/tls/lws-gencrypto-common.c)
+endif()
+
 # Add helper files for Windows.
 if (WIN32)
        set(WIN32_HELPERS_PATH win32port/win32helpers)
@@ -716,46 +1435,82 @@ else()
        # Unix.
        if (NOT LWS_WITHOUT_DAEMONIZE)
                list(APPEND SOURCES
-                       lib/daemonize.c)
+                       lib/misc/daemonize.c)
        endif()
 endif()
 
 if (UNIX)
        if (NOT LWS_HAVE_GETIFADDRS)
-               list(APPEND HDR_PRIVATE lib/getifaddrs.h)
-               list(APPEND SOURCES lib/getifaddrs.c)
+               list(APPEND HDR_PRIVATE lib/misc/getifaddrs.h)
+               list(APPEND SOURCES lib/misc/getifaddrs.c)
        endif()
 endif()
 
-if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
+if ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
+       set(COMPILER_IS_CLANG ON)
+endif()
+
+if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG)
     include (CheckCCompilerFlag)
     CHECK_C_COMPILER_FLAG(-fvisibility=hidden LWS_HAVE_VISIBILITY)
     if (LWS_HAVE_VISIBILITY)
                 set(VISIBILITY_FLAG -fvisibility=hidden)
     endif()
-    if (UNIX OR LWS_WITH_ESP8266)
-               set(CMAKE_C_FLAGS "-Wall -Werror ${VISIBILITY_FLAG} ${CMAKE_C_FLAGS}" )
+    if (LWS_WITH_GCOV)
+           set (GCOV_FLAGS "-fprofile-arcs -ftest-coverage ")
+    endif()
+
+       if (LWS_WITH_ASAN)
+               set (ASAN_FLAGS "-fsanitize=address -fsanitize=undefined -fsanitize-address-use-after-scope -fsanitize-undefined-trap-on-error")
+               if (NOT COMPILER_IS_CLANG)
+                       set (ASAN_FLAGS "${ASAN_FLAGS} -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak")
+               endif()
+               message("Enabling ASAN")
+       endif()
+
+       check_c_compiler_flag("-Wignored-qualifiers" LWS_GCC_HAS_IGNORED_QUALIFIERS)
+       check_c_compiler_flag("-Wtype-limits" LWS_GCC_HAS_TYPE_LIMITS)
+
+       if (LWS_GCC_HAS_IGNORED_QUALIFIERS)
+               set(CMAKE_C_FLAGS "-Wignored-qualifiers ${CMAKE_C_FLAGS}" )
+       endif()
+
+       if (LWS_GCC_HAS_TYPE_LIMITS)
+               set(CMAKE_C_FLAGS "-Wtype-limits ${CMAKE_C_FLAGS}" )
+       endif()
+
+    if (UNIX AND NOT LWS_WITH_ESP32)
+           set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}" )
     else()
-               set(CMAKE_C_FLAGS "-Wall ${VISIBILITY_FLAG} ${CMAKE_C_FLAGS}" )
+           set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} ${GCOV_FLAGS} ${CMAKE_C_FLAGS}" )
     endif()
 endif ()
 
+if (LWS_PLAT_OPTEE)
+       set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --sysroot ../../../../lib/libutils/isoc/include -I../../../../lib/libutils/isoc/include -I../../../../lib/libutils/ext/include" )
+endif()
+
 if ((CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) AND NOT LWS_WITHOUT_TESTAPPS)
-       if (UNIX AND LWS_MAX_SMP GREATER 1)
+       if (UNIX AND LWS_HAVE_PTHREAD_H)
        # jeez clang understands -pthread but dies if he sees it at link time!
        # http://stackoverflow.com/questions/2391194/what-is-gs-pthread-equiv-in-clang
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread" )
     endif()
 endif()
 
-if ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
+if (COMPILER_IS_CLANG)
+
        # otherwise osx blows a bunch of openssl deprecated api errors
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations" )
+       if (UNIX AND LWS_HAVE_PTHREAD_H)
+               set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread" )
+       endif()
 endif()
 
 source_group("Headers Private"  FILES ${HDR_PRIVATE})
 source_group("Headers Public"   FILES ${HDR_PUBLIC})
 source_group("Sources"          FILES ${SOURCES})
+source_group("Resources"        FILES ${RESOURCES})
 
 #
 # Create the lib.
@@ -781,9 +1536,16 @@ if (LWS_WITH_STATIC)
        endif()
        add_custom_command(
                      TARGET websockets
-                     COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/lib/libwebsockets.h
+                     COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/include/libwebsockets.h
                                                         ${CMAKE_CURRENT_BINARY_DIR}/include/libwebsockets.h
        )
+
+       add_custom_command(
+                     TARGET websockets
+                     COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/include/libwebsockets/
+                                                                       ${CMAKE_CURRENT_BINARY_DIR}/include/libwebsockets
+       )
+
        add_custom_command(
                      TARGET websockets
                      COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/lws_config.h
@@ -796,7 +1558,8 @@ if (LWS_WITH_SHARED)
        add_library(websockets_shared SHARED
                                ${HDR_PRIVATE}
                                ${HDR_PUBLIC}
-                               ${SOURCES})
+                               ${SOURCES}
+                               ${RESOURCES})
        list(APPEND LWS_LIBRARIES websockets_shared)
 
        # We want the shared lib to be named "libwebsockets"
@@ -820,9 +1583,16 @@ if (LWS_WITH_SHARED)
 
        add_custom_command(
                      TARGET websockets_shared
-                     COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/lib/libwebsockets.h
+                     COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/include/libwebsockets.h
                                                         ${CMAKE_CURRENT_BINARY_DIR}/include/libwebsockets.h
        )
+
+       add_custom_command(
+                     TARGET websockets
+                     COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/include/libwebsockets
+                                                                       ${CMAKE_CURRENT_BINARY_DIR}/include/libwebsockets
+       )
+
        add_custom_command(
                      TARGET websockets_shared
                      COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/lws_config.h
@@ -834,7 +1604,7 @@ endif()
 
 # Set the so version of the lib.
 # Equivalent to LDFLAGS=-version-info x:x:x
-if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
+if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG)
        foreach(lib ${LWS_LIBRARIES})
                set_target_properties(${lib}
                        PROPERTIES
@@ -849,10 +1619,10 @@ set(LIB_LIST)
 #
 
 #
-# ZLIB (Only needed for deflate extensions).
+# ZLIB (needed for deflate extension and if LWS_WITH_HTTP_STREAM_COMPRESSION)
 #
 if (LWS_WITH_ZLIB)
-       if (LWS_USE_BUNDLED_ZLIB)
+       if (LWS_WITH_BUNDLED_ZLIB)
                if (WIN32)
                        set(WIN32_ZLIB_PATH "win32port/zlib")
                        set(ZLIB_SRCS
@@ -860,8 +1630,6 @@ if (LWS_WITH_ZLIB)
                                ${WIN32_ZLIB_PATH}/compress.c
                                ${WIN32_ZLIB_PATH}/crc32.c
                                ${WIN32_ZLIB_PATH}/deflate.c
-                               ${WIN32_ZLIB_PATH}/gzclose.c
-                               ${WIN32_ZLIB_PATH}/gzio.c
                                ${WIN32_ZLIB_PATH}/gzlib.c
                                ${WIN32_ZLIB_PATH}/gzread.c
                                ${WIN32_ZLIB_PATH}/gzwrite.c
@@ -884,21 +1652,31 @@ if (LWS_WITH_ZLIB)
                        message(FATAL_ERROR "Don't have bundled zlib for that platform")
                endif()
        elseif (NOT ZLIB_FOUND)
-               find_package(ZLIB REQUIRED)
+               if (LWS_WITH_MINIZ)
+                       find_package(Miniz REQUIRED)
+                       set(ZLIB_INCLUDE_DIRS ${MINIZ_INCLUDE_DIRS})
+                       set(ZLIB_LIBRARIES ${MINIZ_LIBRARIES})
+               else()
+                       find_package(ZLIB REQUIRED)
+               endif()
        endif()
-       message("zlib include dirs: ${ZLIB_INCLUDE_DIRS}")
-       message("zlib libraries: ${ZLIB_LIBRARIES}")
+       message("zlib/miniz include dirs: ${ZLIB_INCLUDE_DIRS}")
+       message("zlib/miniz libraries: ${ZLIB_LIBRARIES}")
        include_directories(${ZLIB_INCLUDE_DIRS})
        list(APPEND LIB_LIST ${ZLIB_LIBRARIES})
 endif()
 
+if (LWS_WITH_HTTP_BROTLI)
+       list(APPEND LIB_LIST brotlienc brotlidec brotlidec)
+endif()
+
 #
 # OpenSSL
 #
 if (LWS_WITH_SSL)
        message("Compiling with SSL support")
        set(chose_ssl 0)
-       if (LWS_USE_WOLFSSL)
+       if (LWS_WITH_WOLFSSL)
                # Use wolfSSL as OpenSSL replacement.
                # TODO: Add a find_package command for this also.
                message("wolfSSL include dir: ${WOLFSSL_INCLUDE_DIRS}")
@@ -908,7 +1686,7 @@ if (LWS_WITH_SSL)
                # the wolfssl/ subdirectory which contains the OpenSSL
                # compatibility layer headers.
 
-               if (LWS_USE_CYASSL)
+               if (LWS_WITH_CYASSL)
                        foreach(inc ${WOLFSSL_INCLUDE_DIRS})
                                include_directories("${inc}" "${inc}/cyassl")
                        endforeach()
@@ -922,8 +1700,20 @@ if (LWS_WITH_SSL)
                set(chose_ssl 1)
        endif()
 
+       if (LWS_WITH_MBEDTLS)
+               message("MBEDTLS include dir: ${MBEDTLS_INCLUDE_DIRS}")
+               message("MBEDTLS libraries: ${MBEDTLS_LIBRARIES}")
+
+               foreach(inc ${MBEDTLS_INCLUDE_DIRS})
+                       include_directories("${inc}" "${inc}/mbedtls")
+               endforeach()
+
+               list(APPEND LIB_LIST "${MBEDTLS_LIBRARIES}")
+               set(chose_ssl 1)
+       endif()
+
        if (NOT chose_ssl)
-               if (NOT OPENSSL_FOUND AND NOT LWS_USE_BORINGSSL)
+               if (NOT OPENSSL_FOUND AND NOT LWS_WITH_BORINGSSL)
                        # TODO: Add support for STATIC also.
                if (NOT LWS_WITH_ESP32)
                        find_package(OpenSSL REQUIRED)
@@ -941,13 +1731,17 @@ if (LWS_WITH_SSL)
                        list(APPEND LIB_LIST ${OPENSSL_LIBRARIES})
                endif()
 
-       # older (0.98) Openssl lacks this
-       set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS})
-       check_include_file(openssl/ecdh.h LWS_HAVE_OPENSSL_ECDH_H)
+       if (NOT LWS_WITH_MBEDTLS)
+               # older (0.98) Openssl lacks this
+               set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIRS})
+               check_include_file(openssl/ecdh.h LWS_HAVE_OPENSSL_ECDH_H)
 
-       if (LWS_SSL_SERVER_WITH_ECDH_CERT AND NOT LWS_HAVE_OPENSSL_ECDH_H)
-               message(FATAL_ERROR "Missing openssl/ecdh.h, so cannot use LWS_SSL_SERVER_WITH_ECDH_CERT")
-       endif()
+               if (LWS_SSL_SERVER_WITH_ECDH_CERT AND NOT LWS_HAVE_OPENSSL_ECDH_H)
+                       message(FATAL_ERROR "Missing openssl/ecdh.h, so cannot use LWS_SSL_SERVER_WITH_ECDH_CERT")
+               endif()
+       else()
+               unset(LWS_HAVE_OPENSSL_ECDH_H)
+       endif(NOT LWS_WITH_MBEDTLS)
        endif()
 
 endif(LWS_WITH_SSL)
@@ -1009,11 +1803,18 @@ if (LWS_WITH_SQLITE3)
 endif()
 
 
-if (LWS_WITH_HTTP_PROXY)
+if (LWS_WITH_HUBBUB)
        find_library(LIBHUBBUB_LIBRARIES NAMES hubbub)
        list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} )
 endif()
 
+if (LWS_ROLE_DBUS)
+       message("dbus include dir 1: ${LWS_DBUS_INCLUDE1}")
+       message("dbus include dir 2: ${LWS_DBUS_INCLUDE2}")
+       include_directories("${LWS_DBUS_INCLUDE1}")
+       include_directories("${LWS_DBUS_INCLUDE2}")
+       list(APPEND LIB_LIST ${LWS_DBUS_LIB})
+endif()
 
 #
 # Platform specific libs.
@@ -1024,15 +1825,25 @@ elseif (WIN32)
        list(APPEND LIB_LIST ws2_32.lib userenv.lib psapi.lib iphlpapi.lib)
 endif()
 
+if (${CMAKE_SYSTEM_NAME} MATCHES "QNX")
+       list(APPEND LIB_LIST socket)
+endif()
+
 if (UNIX)
        list(APPEND LIB_LIST m)
 endif()
 
-if (LWS_HAVE_LIBCAP)
-       list(APPEND LIB_LIST cap )
+if(SMARTOS)
+       list(APPEND LIB_LIST socket)
 endif()
 
+if (HAIKU)
+       list(APPEND LIB_LIST network)
+endif()
 
+if (LWS_HAVE_LIBCAP)
+       list(APPEND LIB_LIST cap )
+endif()
 
 # Setup the linking for all libs.
 foreach (lib ${LWS_LIBRARIES})
@@ -1041,29 +1852,89 @@ endforeach()
 
 set (temp ${CMAKE_REQUIRED_LIBRARIES})
 set(CMAKE_REQUIRED_LIBRARIES ${LIB_LIST})
+
+if (LWS_WITH_ZLIB)
+       if (LWS_WITH_BUNDLED_ZLIB)
+               if (WIN32)
+                       # it's trying to delete internal zlib entry
+                       LIST(REMOVE_AT CMAKE_REQUIRED_LIBRARIES 0 )
+               endif()
+       endif()
+endif()
+
 CHECK_FUNCTION_EXISTS(SSL_CTX_set1_param LWS_HAVE_SSL_CTX_set1_param)
 CHECK_FUNCTION_EXISTS(SSL_set_info_callback LWS_HAVE_SSL_SET_INFO_CALLBACK)
 CHECK_FUNCTION_EXISTS(X509_VERIFY_PARAM_set1_host LWS_HAVE_X509_VERIFY_PARAM_set1_host)
-if (LWS_WITH_ESP32)
+CHECK_FUNCTION_EXISTS(RSA_set0_key LWS_HAVE_RSA_SET0_KEY)
+CHECK_FUNCTION_EXISTS(X509_get_key_usage LWS_HAVE_X509_get_key_usage)
+CHECK_FUNCTION_EXISTS(EVP_PKEY_new_raw_private_key LWS_HAVE_SSL_CTX_EVP_PKEY_new_raw_private_key)
+CHECK_FUNCTION_EXISTS(SSL_CTX_get0_certificate LWS_HAVE_SSL_CTX_get0_certificate)
+CHECK_FUNCTION_EXISTS(SSL_get0_alpn_selected LWS_HAVE_SSL_get0_alpn_selected)
+CHECK_FUNCTION_EXISTS(SSL_set_alpn_protos LWS_HAVE_SSL_set_alpn_protos)
+CHECK_FUNCTION_EXISTS(EVP_aes_128_cfb8 LWS_HAVE_EVP_aes_128_cfb8)
+CHECK_FUNCTION_EXISTS(EVP_aes_128_cfb128 LWS_HAVE_EVP_aes_128_cfb128)
+CHECK_FUNCTION_EXISTS(EVP_aes_192_cfb8 LWS_HAVE_EVP_aes_192_cfb8)
+CHECK_FUNCTION_EXISTS(EVP_aes_192_cfb128 LWS_HAVE_EVP_aes_192_cfb128)
+CHECK_FUNCTION_EXISTS(EVP_aes_256_cfb8 LWS_HAVE_EVP_aes_256_cfb8)
+CHECK_FUNCTION_EXISTS(EVP_aes_256_cfb128 LWS_HAVE_EVP_aes_256_cfb128)
+CHECK_FUNCTION_EXISTS(EVP_aes_128_xts LWS_HAVE_EVP_aes_128_xts)
+CHECK_FUNCTION_EXISTS(RSA_verify_pss_mgf1 LWS_HAVE_RSA_verify_pss_mgf1)
+CHECK_FUNCTION_EXISTS(HMAC_CTX_new LWS_HAVE_HMAC_CTX_new)
+CHECK_FUNCTION_EXISTS(SSL_CTX_set_ciphersuites LWS_HAVE_SSL_CTX_set_ciphersuites)
+if (LWS_WITH_SSL AND NOT LWS_WITH_MBEDTLS)
+ if (UNIX)
+ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl)
+ endif()
+CHECK_C_SOURCE_COMPILES("#include <openssl/ssl.h>\nint main(void) { STACK_OF(X509) *c = NULL; SSL_CTX *ctx = NULL; return (int)SSL_CTX_get_extra_chain_certs_only(ctx, &c); }\n" LWS_HAVE_SSL_EXTRA_CHAIN_CERTS)
+CHECK_C_SOURCE_COMPILES("#include <openssl/ssl.h>\nint main(void) { EVP_MD_CTX *md_ctx = NULL; EVP_MD_CTX_free(md_ctx); return 0; }\n" LWS_HAVE_EVP_MD_CTX_free)
+CHECK_FUNCTION_EXISTS(ECDSA_SIG_set0 LWS_HAVE_ECDSA_SIG_set0)
+CHECK_FUNCTION_EXISTS(BN_bn2binpad LWS_HAVE_BN_bn2binpad)
+CHECK_FUNCTION_EXISTS(EVP_aes_128_wrap LWS_HAVE_EVP_aes_128_wrap)
+CHECK_FUNCTION_EXISTS(EC_POINT_get_affine_coordinates LWS_HAVE_EC_POINT_get_affine_coordinates)
+endif()
+if (LWS_WITH_MBEDTLS)
        set(LWS_HAVE_TLS_CLIENT_METHOD 1)
+       if (NOT LWS_WITH_ESP32)
+               # not supported in esp-idf openssl wrapper yet, but is in our version
+               set(LWS_HAVE_X509_VERIFY_PARAM_set1_host 1)
+       endif()
+
+       CHECK_FUNCTION_EXISTS(mbedtls_ssl_conf_alpn_protocols LWS_HAVE_mbedtls_ssl_conf_alpn_protocols)
+       CHECK_FUNCTION_EXISTS(mbedtls_ssl_get_alpn_protocol LWS_HAVE_mbedtls_ssl_get_alpn_protocol)
+       CHECK_FUNCTION_EXISTS(mbedtls_ssl_conf_sni LWS_HAVE_mbedtls_ssl_conf_sni)
+       CHECK_FUNCTION_EXISTS(mbedtls_ssl_set_hs_ca_chain LWS_HAVE_mbedtls_ssl_set_hs_ca_chain)
+       CHECK_FUNCTION_EXISTS(mbedtls_ssl_set_hs_own_cert LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       CHECK_FUNCTION_EXISTS(mbedtls_ssl_set_hs_authmode LWS_HAVE_mbedtls_ssl_set_hs_authmode)
+       CHECK_FUNCTION_EXISTS(mbedtls_net_init LWS_HAVE_mbedtls_net_init)
+
 else()
 CHECK_FUNCTION_EXISTS(TLS_client_method LWS_HAVE_TLS_CLIENT_METHOD)
 CHECK_FUNCTION_EXISTS(TLSv1_2_client_method LWS_HAVE_TLSV1_2_CLIENT_METHOD)
 endif()
+
+# ideally we want to use pipe2()
+
+CHECK_C_SOURCE_COMPILES("#define _GNU_SOURCE\n#include <unistd.h>\nint main(void) {int fd[2];\n return pipe2(fd, 0);\n}\n" LWS_HAVE_PIPE2)
+
+# tcp keepalive needs this on linux to work practically... but it only exists
+# after kernel 2.6.37
+
+CHECK_C_SOURCE_COMPILES("#include <netinet/tcp.h>\nint main(void) { return TCP_USER_TIMEOUT; }\n" LWS_HAVE_TCP_USER_TIMEOUT)
+
 set(CMAKE_REQUIRED_LIBRARIES ${temp})
 # Generate the lws_config.h that includes all the public compilation settings.
 configure_file(
-       "${PROJECT_SOURCE_DIR}/lws_config.h.in"
+       "${PROJECT_SOURCE_DIR}/cmake/lws_config.h.in"
        "${PROJECT_BINARY_DIR}/lws_config.h")
 
 # Generate the lws_config.h that includes all the private compilation settings.
 configure_file(
-       "${PROJECT_SOURCE_DIR}/lws_config_private.h.in"
+       "${PROJECT_SOURCE_DIR}/cmake/lws_config_private.h.in"
        "${PROJECT_BINARY_DIR}/lws_config_private.h")
 
 # Generate self-signed SSL certs for the test-server.
 
-if (LWS_WITH_SSL AND NOT LWS_USE_WOLFSSL)
+if (LWS_WITH_SSL AND NOT LWS_WITH_WOLFSSL)
        message("Searching for OpenSSL executable and dlls")
        find_package(OpenSSLbins)
        message("OpenSSL executable: ${OPENSSL_EXECUTABLE}")
@@ -1078,7 +1949,7 @@ endif()
 
 set(GENCERTS 0)
 
-if (LWS_WITH_SSL AND OPENSSL_EXECUTABLE AND NOT LWS_WITHOUT_TEST_SERVER)
+if (LWS_WITH_SSL AND OPENSSL_EXECUTABLE AND NOT LWS_WITHOUT_TEST_SERVER AND NOT LWS_WITHOUT_SERVER AND NOT LWS_WITHOUT_TESTAPPS)
        set(GENCERTS 1)
 endif()
 if (LWS_WITH_ESP32)
@@ -1155,7 +2026,7 @@ endif()
 # Test applications
 #
 set(TEST_APP_LIST)
-if (NOT LWS_WITHOUT_TESTAPPS)
+if ((LWS_ROLE_H1 OR LWS_ROLE_H2) AND NOT LWS_WITHOUT_TESTAPPS)
        #
        # Helper function for adding a test app.
        #
@@ -1212,6 +2083,13 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                        endif()
                        target_link_libraries(${TEST_NAME} websockets)
                        add_dependencies(${TEST_NAME} websockets)
+                       if (UNIX AND LWS_WITH_SSL AND NOT LWS_WITH_MBEDTLS)
+                               target_link_libraries(${TEST_NAME} dl)
+                       endif()
+               endif()
+
+               if (LWS_WITH_HTTP_STREAM_COMPRESSION)
+                       target_link_libraries(${TEST_NAME} z)
                endif()
 
                # Set test app specific defines.
@@ -1231,12 +2109,15 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 
        if (UNIX AND LWS_WITH_PLUGINS)
                set(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
-               if(NOT(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD"))
+               if(NOT((${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") OR (${CMAKE_SYSTEM_NAME} MATCHES "QNX")))
                        target_link_libraries(websockets dl)
                endif()
        endif()
 
-
+       if (LWS_WITH_LIBEV)
+               # libev generates a big mess of warnings with gcc, maintainer claims gcc to blame
+               set_source_files_properties( lib/event-libs/libev/libev.c PROPERTIES COMPILE_FLAGS "-Wno-error" )
+       endif()
 
 
        if (NOT LWS_WITHOUT_SERVER)
@@ -1244,70 +2125,34 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                # test-server
                #
                if (NOT LWS_WITHOUT_TEST_SERVER)
-                       create_test_app(test-server "test-server/test-server.c"
-                               "test-server/test-server-http.c"
-                               "test-server/test-server-dumb-increment.c"
+                       create_test_app(test-server "test-apps/test-server.c"
+                               ""
+                               ""
                                ""
                                ""
                                "")
-                       if (UNIX)
-                               create_test_app(test-fuzxy "test-server/fuzxy.c"
-                                       ""
-                                       ""
-                                       ""
-                                       ""
-                                       "")
-                       endif()
-                       if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) AND LWS_MAX_SMP GREATER 1)
-                               create_test_app(test-server-pthreads
-                                       "test-server/test-server-pthreads.c"
-                                       "test-server/test-server-http.c"
-                                       "test-server/test-server-dumb-increment.c"
-                                       ""
-                                       ""
-                                       "")
-                       endif()
-                       if (NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
-                               AND LWS_WITH_LIBEV)
-                               create_test_app(test-server-libev
-                                       "test-server/test-server-libev.c"
-                                       "test-server/test-server-http.c"
-                                       "test-server/test-server-dumb-increment.c"
-                                       ""
-                                       ""
-                                       "")
-                               # libev generates a big mess of warnings with gcc, maintainers blame gcc
-                               set_source_files_properties( test-server/test-server-libev.c PROPERTIES COMPILE_FLAGS "-Wno-error" )
-                       endif()
-                       if (NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
-                               AND LWS_WITH_LIBUV)
-                               create_test_app(test-server-libuv
-                                       "test-server/test-server-libuv.c"
-                                       "test-server/test-server-http.c"
-                                       ""
-                                       ""
-                                       ""
-                                       "")
-                       endif()
-                       if (NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
-                               AND LWS_WITH_LIBEVENT)
-                               create_test_app(test-server-libevent
-                                       "test-server/test-server-libevent.c"
-                                       "test-server/test-server-http.c"
-                                       "test-server/test-server-dumb-increment.c"
-                                       ""
-                                       ""
-                                       "")
+
+                       if (LWS_WITH_CGI)
+                       create_test_app(test-sshd "test-apps/test-sshd.c"
+                               ""
+                               ""
+                               ""
+                               ""
+                               "")
+                       target_include_directories(test-sshd PRIVATE "${PROJECT_SOURCE_DIR}/plugins/ssh-base/include")
+
                        endif()
+
                endif()
 
                #
                # test-server-extpoll
                #
-               if (NOT LWS_WITHOUT_TEST_SERVER_EXTPOLL)
-                       create_test_app(test-server-extpoll "test-server/test-server.c"
-                               "test-server/test-server-http.c"
-                               "test-server/test-server-dumb-increment.c"
+               if (NOT LWS_WITHOUT_TEST_SERVER_EXTPOLL AND NOT WIN32)
+                       create_test_app(test-server-extpoll
+                               "test-apps/test-server.c"
+                               ""
+                               ""
                                ""
                                ""
                                "")
@@ -1325,13 +2170,10 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                        endif(WIN32)
                endif()
 
-               #
-               # test-server-v2.0
-               #
-               if (LWS_WITH_PLUGINS)
+               if (LWS_WITH_LEJP)
                        create_test_app(
-                               test-server-v2.0
-                               "test-server/test-server-v2.0.c"
+                               test-lejp
+                               "test-apps/test-lejp.c"
                                ""
                                ""
                                ""
@@ -1340,13 +2182,17 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                endif()
 
                # Data files for running the test server.
-               set(TEST_SERVER_DATA
-                       "${PROJECT_SOURCE_DIR}/test-server/favicon.ico"
-                       "${PROJECT_SOURCE_DIR}/test-server/leaf.jpg"
-                       "${PROJECT_SOURCE_DIR}/test-server/candide.zip"
-                       "${PROJECT_SOURCE_DIR}/test-server/libwebsockets.org-logo.png"
-                       "${PROJECT_SOURCE_DIR}/test-server/lws-common.js"
-                       "${PROJECT_SOURCE_DIR}/test-server/test.html")
+               list(APPEND TEST_SERVER_DATA
+                       "${PROJECT_SOURCE_DIR}/test-apps/favicon.ico"
+                       "${PROJECT_SOURCE_DIR}/test-apps/leaf.jpg"
+                       "${PROJECT_SOURCE_DIR}/test-apps/candide.zip"
+                       "${PROJECT_SOURCE_DIR}/test-apps/libwebsockets.org-logo.svg"
+                       "${PROJECT_SOURCE_DIR}/test-apps/http2.png"
+                       "${PROJECT_SOURCE_DIR}/test-apps/wss-over-h2.png"
+                       "${PROJECT_SOURCE_DIR}/test-apps/lws-common.js"
+                       "${PROJECT_SOURCE_DIR}/test-apps/test.html"
+                       "${PROJECT_SOURCE_DIR}/test-apps/test.css"
+                       "${PROJECT_SOURCE_DIR}/test-apps/test.js")
 
                add_custom_command(TARGET test-server
                                                POST_BUILD 
@@ -1368,34 +2214,14 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                # test-client
                #
                if (NOT LWS_WITHOUT_TEST_CLIENT)
-                       create_test_app(test-client "test-server/test-client.c" "" "" "" "" "")
-               endif()
-
-               #
-               # test-fraggle
-               #
-               if (NOT LWS_WITHOUT_TEST_FRAGGLE)
-                       create_test_app(test-fraggle "test-server/test-fraggle.c" "" "" "" "" "")
-               endif()
-
-               #
-               # test-ping
-               #
-               if (NOT LWS_WITHOUT_TEST_PING)
-                       create_test_app(test-ping "test-server/test-ping.c" "" "" "" "" "")
-               endif()
-               #
-               # test-echo
-               #
-               if (NOT LWS_WITHOUT_TEST_ECHO)
-                       create_test_app(test-echo "test-server/test-echo.c" "" "" "" "" "")
+                       create_test_app(test-client "test-apps/test-client.c" "" "" "" "" "")
                endif()
 
        endif(NOT LWS_WITHOUT_CLIENT)
        
        
        if (LWS_WITH_PLUGINS AND LWS_WITH_SHARED)
-               macro(create_plugin PLUGIN_NAME MAIN_SRC S2 S3)
+               macro(create_plugin PLUGIN_NAME PLUGIN_INCLUDE MAIN_SRC S2 S3)
 
                set(PLUGIN_SRCS ${MAIN_SRC})
 
@@ -1427,6 +2253,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                
                target_link_libraries(${PLUGIN_NAME} websockets_shared)
                add_dependencies(${PLUGIN_NAME} websockets_shared)
+               include_directories(${PLUGIN_INCLUDE})
 
                # Set test app specific defines.
                set_property(TARGET ${PLUGIN_NAME}
@@ -1434,6 +2261,9 @@ if (NOT LWS_WITHOUT_TESTAPPS)
                             INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/plugins"
                )
 
+               SET_TARGET_PROPERTIES(${PLUGIN_NAME}
+                               PROPERTIES COMPILE_FLAGS ${CMAKE_C_FLAGS})
+
 #              set_target_properties(${PLUGIN_NAME}
 #                      PROPERTIES
 #                      OUTPUT_NAME ${PLUGIN_NAME})
@@ -1442,36 +2272,67 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 
                endmacro()
                
-
-               create_plugin(protocol_lws_meta
-                             "plugins/protocol_lws_meta.c" "" "")
-               create_plugin(protocol_dumb_increment
+if (LWS_ROLE_WS)
+               create_plugin(protocol_dumb_increment ""
                              "plugins/protocol_dumb_increment.c" "" "")
-               create_plugin(protocol_lws_mirror
+               create_plugin(protocol_lws_mirror ""
                              "plugins/protocol_lws_mirror.c" "" "")
-               create_plugin(protocol_lws_status
+               create_plugin(protocol_lws_status ""
                              "plugins/protocol_lws_status.c" "" "")
-               create_plugin(protocol_post_demo
-                             "plugins/protocol_post_demo.c" "" "")
-               create_plugin(protocol_lws_table_dirlisting
+               create_plugin(protocol_lws_table_dirlisting ""
                              "plugins/generic-table/protocol_table_dirlisting.c" "" "")
                if (NOT WIN32)
-                     create_plugin(protocol_lws_raw_test
+                       create_plugin(protocol_lws_raw_test ""
                              "plugins/protocol_lws_raw_test.c" "" "")
+
+                       create_plugin(protocol_deaddrop ""
+                             "plugins/deaddrop/protocol_lws_deaddrop.c" "" "")
+
                endif()
 
 if (LWS_WITH_SERVER_STATUS)
-               create_plugin(protocol_lws_server_status
+               create_plugin(protocol_lws_server_status ""
                              "plugins/protocol_lws_server_status.c" "" "")
 endif()
 
 if (NOT LWS_WITHOUT_CLIENT)
-               create_plugin(protocol_client_loopback_test
+               create_plugin(protocol_client_loopback_test ""
                               "plugins/protocol_client_loopback_test.c" "" "")
-endif(NOT LWS_WITHOUT_CLIENT)
+endif()
 
-if (LWS_WITH_GENERIC_SESSIONS)
-       create_plugin(protocol_generic_sessions
+endif()
+
+               create_plugin(protocol_post_demo ""
+                             "plugins/protocol_post_demo.c" "" "")
+
+if (LWS_ROLE_RAW_PROXY)
+               create_plugin(protocol_lws_raw_proxy ""
+                             "plugins/raw-proxy/protocol_lws_raw_proxy.c" "" "")
+endif()
+
+if (LWS_WITH_FTS)
+               create_plugin(protocol_fulltext_demo ""
+                             "plugins/protocol_fulltext_demo.c" "" "")
+endif()
+
+
+if (LWS_WITH_SSL)
+               create_plugin(protocol_lws_ssh_base "plugins/ssh-base/include"
+                             "plugins/ssh-base/sshd.c;plugins/ssh-base/telnet.c;plugins/ssh-base/kex-25519.c" "plugins/ssh-base/crypto/chacha.c;plugins/ssh-base/crypto/ed25519.c;plugins/ssh-base/crypto/fe25519.c;plugins/ssh-base/crypto/ge25519.c;plugins/ssh-base/crypto/poly1305.c;plugins/ssh-base/crypto/sc25519.c;plugins/ssh-base/crypto/smult_curve25519_ref.c" "")
+               create_plugin(protocol_lws_sshd_demo "plugins/ssh-base/include" "plugins/protocol_lws_sshd_demo.c" "" "")
+
+               include_directories("${PROJECT_SOURCE_DIR}/plugins/ssh-base/include")
+endif()
+
+
+
+if (LWS_WITH_ACME)
+               create_plugin(protocol_lws_acme_client ""
+                             "plugins/acme-client/protocol_lws_acme_client.c" "" "")
+endif()
+
+if (LWS_WITH_GENERIC_SESSIONS AND LWS_ROLE_WS)
+       create_plugin(protocol_generic_sessions ""
                       "plugins/generic-sessions/protocol_generic_sessions.c"
                      "plugins/generic-sessions/utils.c"
                      "plugins/generic-sessions/handlers.c")
@@ -1482,7 +2343,7 @@ if (LWS_WITH_GENERIC_SESSIONS)
                target_link_libraries(protocol_generic_sessions sqlite3 )
        endif(WIN32)
 
-               create_plugin(protocol_lws_messageboard
+               create_plugin(protocol_lws_messageboard ""
                              "plugins/generic-sessions/protocol_lws_messageboard.c" "" "")
        if (WIN32)
                target_link_libraries(protocol_lws_messageboard ${LWS_SQLITE3_LIBRARIES})
@@ -1490,7 +2351,7 @@ if (LWS_WITH_GENERIC_SESSIONS)
                target_link_libraries(protocol_lws_messageboard sqlite3 )
        endif(WIN32)
 
-endif(LWS_WITH_GENERIC_SESSIONS)
+endif(LWS_WITH_GENERIC_SESSIONS AND LWS_ROLE_WS)
 
 
        endif(LWS_WITH_PLUGINS AND LWS_WITH_SHARED)
@@ -1499,7 +2360,7 @@ endif(LWS_WITH_GENERIC_SESSIONS)
        # Copy OpenSSL dlls to the output directory on Windows.
        # (Otherwise we'll get an error when trying to run)
        #
-       if (WIN32 AND LWS_WITH_SSL AND NOT LWS_USE_WOLFSSL)
+       if (WIN32 AND LWS_WITH_SSL AND NOT LWS_WITH_WOLFSSL)
                if(OPENSSL_BIN_FOUND)
                        message("OpenSSL dlls found:")
                        message("  Libeay: ${LIBEAY_BIN}")
@@ -1512,10 +2373,20 @@ endif(LWS_WITH_GENERIC_SESSIONS)
                                add_custom_command(TARGET ${TARGET_BIN}
                                        POST_BUILD
                                        COMMAND "${CMAKE_COMMAND}" -E copy "${SSLEAY_BIN}" "$<TARGET_FILE_DIR:${TARGET_BIN}>" VERBATIM)
+
+                               #
+                               # Win32: if we are using libuv, also need to copy it in the output dir
+                               #
+                               if (WIN32 AND LWS_WITH_LIBUV)
+                                       STRING(REPLACE ".lib" ".dll" LIBUV_BIN ${LIBUV_LIBRARIES})
+                                       add_custom_command(TARGET ${TARGET_BIN}
+                                               POST_BUILD
+                                               COMMAND "${CMAKE_COMMAND}" -E copy "${LIBUV_BIN}" "$<TARGET_FILE_DIR:${TARGET_BIN}>" VERBATIM)
+                               endif()
                        endforeach()
                endif()
        endif()
-endif(NOT LWS_WITHOUT_TESTAPPS)
+endif((LWS_ROLE_H1 OR LWS_ROLE_H2) AND NOT LWS_WITHOUT_TESTAPPS)
 
 if (LWS_WITH_LWSWS)
                list(APPEND LWSWS_SRCS
@@ -1604,8 +2475,11 @@ endif()
 set(LWS_INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files")
 
 # Export targets (This is used for other CMake projects to easily find the libraries and include files).
-export(TARGETS ${LWS_LIBRARIES}
-        FILE "${PROJECT_BINARY_DIR}/LibwebsocketsTargets.cmake")
+if (LWS_WITH_EXPORT_LWSTARGETS)
+    export(TARGETS ${LWS_LIBRARIES}
+            FILE "${PROJECT_BINARY_DIR}/LibwebsocketsTargets.cmake")
+endif()
+
 export(PACKAGE libwebsockets)
 
 # Generate the config file for the build-tree.
@@ -1647,6 +2521,9 @@ configure_file(${PROJECT_SOURCE_DIR}/cmake/LibwebsocketsConfigVersion.cmake.in
 # Installation.
 #
 
+install(DIRECTORY include/libwebsockets
+       DESTINATION "${LWS_INSTALL_INCLUDE_DIR}" COMPONENT dev_headers)
+
 # Install libs and headers.
 install(TARGETS ${LWS_LIBRARIES}
                EXPORT LibwebsocketsTargets
@@ -1654,6 +2531,7 @@ install(TARGETS ${LWS_LIBRARIES}
                ARCHIVE DESTINATION "${LWS_INSTALL_LIB_DIR}${LIB_SUFFIX}" COMPONENT libraries
                RUNTIME DESTINATION "${LWS_INSTALL_BIN_DIR}" COMPONENT libraries # Windows DLLs
                PUBLIC_HEADER DESTINATION "${LWS_INSTALL_INCLUDE_DIR}" COMPONENT dev)
+               
 set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries")
 set(CPACK_COMPONENT_DEV_DISPLAY_NAME "Development files")
 
@@ -1677,11 +2555,11 @@ if (NOT LWS_WITHOUT_TESTAPPS AND NOT LWS_WITHOUT_SERVER)
                        DESTINATION share/libwebsockets-test-server
                COMPONENT examples)
 
-               install(FILES "${PROJECT_SOURCE_DIR}/test-server/private/index.html"
+               install(FILES "${PROJECT_SOURCE_DIR}/test-apps/private/index.html"
                        DESTINATION share/libwebsockets-test-server/private
                        COMPONENT examples)
 if (LWS_WITH_CGI)
-       set(CGI_TEST_SCRIPT "${PROJECT_SOURCE_DIR}/test-server/lws-cgi-test.sh")
+       set(CGI_TEST_SCRIPT "${PROJECT_SOURCE_DIR}/test-apps/lws-cgi-test.sh")
        install(FILES ${CGI_TEST_SCRIPT}
                        PERMISSIONS  OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ
                        DESTINATION share/libwebsockets-test-server
@@ -1689,6 +2567,13 @@ if (LWS_WITH_CGI)
        endif()
 endif()
 
+
+if (NOT LWS_WITHOUT_TEST_SERVER AND NOT LWS_WITHOUT_SERVER AND NOT LWS_WITHOUT_TESTAPPS)
+       install(FILES test-apps/lws-ssh-test-keys;test-apps/lws-ssh-test-keys.pub
+               DESTINATION share/libwebsockets-test-server
+               COMPONENT examples)
+endif()
+
 # plugins
 
 if (LWS_WITH_PLUGINS)
@@ -1696,8 +2581,16 @@ if (LWS_WITH_PLUGINS)
                PERMISSIONS  OWNER_WRITE OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ
                DESTINATION share/libwebsockets-test-server/plugins
                COMPONENT plugins)
+
+       if (NOT WIN32)
+               install(FILES plugins/deaddrop/assets/index.html;plugins/deaddrop/assets/deaddrop.js;plugins/deaddrop/assets/deaddrop.css;plugins/deaddrop/assets/drop.svg
+                       DESTINATION share/libwebsockets-test-server/deaddrop
+                       COMPONENT plugins)
+       endif()
+
+
 if (LWS_WITH_SERVER_STATUS)
-       install(FILES plugins/server-status.html;plugins/lwsws-logo.png
+       install(FILES plugins/server-status.html;plugins/server-status.js;plugins/server-status.css;plugins/lwsws-logo.png
                DESTINATION share/libwebsockets-test-server/server-status
                        COMPONENT examples)
 endif()
@@ -1707,6 +2600,7 @@ if (LWS_WITH_GENERIC_SESSIONS)
                      plugins/generic-sessions/assets/seats.jpg
                      plugins/generic-sessions/assets/failed-login.html
                      plugins/generic-sessions/assets/lwsgs.js
+                     plugins/generic-sessions/assets/lwsgs.css
                      plugins/generic-sessions/assets/post-register-fail.html
                      plugins/generic-sessions/assets/post-register-ok.html
                      plugins/generic-sessions/assets/post-verify-ok.html
@@ -1741,18 +2635,20 @@ install(FILES
                DESTINATION "${LWS_INSTALL_CMAKE_DIR}" COMPONENT dev)
 
 # Install exports for the install-tree.
-install(EXPORT LibwebsocketsTargets
-               DESTINATION "${LWS_INSTALL_CMAKE_DIR}" COMPONENT dev)
+if (LWS_WITH_EXPORT_LWSTARGETS)
+    install(EXPORT LibwebsocketsTargets
+            DESTINATION "${LWS_INSTALL_CMAKE_DIR}" COMPONENT dev)
+endif()
 
 # build subdir is not part of sources
-set(CPACK_SOURCE_IGNORE_FILES $(CPACK_SOURCE_IGNORE_FILES) ".git" "build" "tgz" "tar.gz")
+set(CPACK_SOURCE_IGNORE_FILES $(CPACK_SOURCE_IGNORE_FILES) "/.git/" "/build/" "\\\\.tgz$" "\\\\.tar\\\\.gz$")
 
 # Most people are more used to "make dist" compared to "make package_source"
 add_custom_target(dist COMMAND "${CMAKE_MAKE_PROGRAM}" package_source)
 
 include(UseRPMTools)
 if (RPMTools_FOUND)
-       RPMTools_ADD_RPM_TARGETS(libwebsockets libwebsockets.spec)
+       RPMTools_ADD_RPM_TARGETS(libwebsockets scripts/libwebsockets.spec)
 endif()
 
 message("---------------------------------------------------------------------")
@@ -1762,11 +2658,12 @@ message(" LWS_WITH_STATIC = ${LWS_WITH_STATIC}")
 message(" LWS_WITH_SHARED = ${LWS_WITH_SHARED}")
 message(" LWS_WITH_SSL = ${LWS_WITH_SSL} (SSL Support)")
 message(" LWS_SSL_CLIENT_USE_OS_CA_CERTS = ${LWS_SSL_CLIENT_USE_OS_CA_CERTS}")
-message(" LWS_USE_WOLFSSL = ${LWS_USE_WOLFSSL} (wolfSSL/CyaSSL replacement for OpenSSL)")
-if (LWS_USE_WOLFSSL)
+message(" LWS_WITH_WOLFSSL = ${LWS_WITH_WOLFSSL} (wolfSSL/CyaSSL replacement for OpenSSL)")
+if (LWS_WITH_WOLFSSL)
        message("   LWS_WOLFSSL_LIBRARIES = ${LWS_WOLFSSL_LIBRARIES}")
        message("   LWS_WOLFSSL_INCLUDE_DIRS = ${LWS_WOLFSSL_INCLUDE_DIRS}")
 endif()
+message(" LWS_WITH_MBEDTLS = ${LWS_WITH_MBEDTLS} (mbedTLS replacement for OpenSSL)")
 message(" LWS_WITHOUT_BUILTIN_SHA1 = ${LWS_WITHOUT_BUILTIN_SHA1}")
 message(" LWS_WITHOUT_BUILTIN_GETIFADDRS = ${LWS_WITHOUT_BUILTIN_GETIFADDRS}")
 message(" LWS_WITHOUT_CLIENT = ${LWS_WITHOUT_CLIENT}")
@@ -1776,23 +2673,23 @@ message(" LWS_WITHOUT_TESTAPPS = ${LWS_WITHOUT_TESTAPPS}")
 message(" LWS_WITHOUT_TEST_SERVER = ${LWS_WITHOUT_TEST_SERVER}")
 message(" LWS_WITHOUT_TEST_SERVER_EXTPOLL = ${LWS_WITHOUT_TEST_SERVER_EXTPOLL}")
 message(" LWS_WITHOUT_TEST_PING = ${LWS_WITHOUT_TEST_PING}")
-message(" LWS_WITHOUT_TEST_ECHO = ${LWS_WITHOUT_TEST_ECHO}")
 message(" LWS_WITHOUT_TEST_CLIENT = ${LWS_WITHOUT_TEST_CLIENT}")
-message(" LWS_WITHOUT_TEST_FRAGGLE = ${LWS_WITHOUT_TEST_FRAGGLE}")
 message(" LWS_WITHOUT_EXTENSIONS = ${LWS_WITHOUT_EXTENSIONS}")
 message(" LWS_WITH_LATENCY = ${LWS_WITH_LATENCY}")
 message(" LWS_WITHOUT_DAEMONIZE = ${LWS_WITHOUT_DAEMONIZE}")
-message(" LWS_USE_LIBEV = ${LWS_USE_LIBEV}")
-message(" LWS_USE_LIBUV = ${LWS_USE_LIBUV}")
-message(" LWS_USE_LIBEVENT = ${LWS_USE_LIBEVENT}")
+message(" LWS_WITH_LIBEV = ${LWS_WITH_LIBEV}")
+message(" LWS_WITH_LIBUV = ${LWS_WITH_LIBUV}")
+message(" LWS_WITH_LIBEVENT = ${LWS_WITH_LIBEVENT}")
 message(" LWS_IPV6 = ${LWS_IPV6}")
 message(" LWS_UNIX_SOCK = ${LWS_UNIX_SOCK}")
 message(" LWS_WITH_HTTP2 = ${LWS_WITH_HTTP2}")
 message(" LWS_SSL_SERVER_WITH_ECDH_CERT = ${LWS_SSL_SERVER_WITH_ECDH_CERT}")
 message(" LWS_MAX_SMP = ${LWS_MAX_SMP}")
+message(" LWS_HAVE_PTHREAD_H = ${LWS_HAVE_PTHREAD_H}")
 message(" LWS_WITH_CGI = ${LWS_WITH_CGI}")
 message(" LWS_HAVE_OPENSSL_ECDH_H = ${LWS_HAVE_OPENSSL_ECDH_H}")
 message(" LWS_HAVE_SSL_CTX_set1_param = ${LWS_HAVE_SSL_CTX_set1_param}")
+message(" LWS_HAVE_RSA_SET0_KEY = ${LWS_HAVE_RSA_SET0_KEY}")
 message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}")
 message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}")
 message(" PLUGINS = ${PLUGINS_LIST}")
@@ -1812,10 +2709,13 @@ message(" LWS_WITH_STATS = ${LWS_WITH_STATS}")
 message(" LWS_WITH_SOCKS5 = ${LWS_WITH_SOCKS5}")
 message(" LWS_HAVE_SYS_CAPABILITY_H = ${LWS_HAVE_SYS_CAPABILITY_H}")
 message(" LWS_HAVE_LIBCAP = ${LWS_HAVE_LIBCAP}")
+message(" LWS_WITH_PEER_LIMITS = ${LWS_WITH_PEER_LIMITS}")
 message(" LWS_HAVE_ATOLL = ${LWS_HAVE_ATOLL}")
 message(" LWS_HAVE__ATOI64 = ${LWS_HAVE__ATOI64}")
 message(" LWS_HAVE_STAT32I64 = ${LWS_HAVE_STAT32I64}")
 message(" LWS_HAS_INTPTR_T = ${LWS_HAS_INTPTR_T}")
+message(" LWS_WITH_EXPORT_LWSTARGETS = ${LWS_WITH_EXPORT_LWSTARGETS}")
+message(" LWS_WITH_ABSTRACT = ${LWS_WITH_ABSTRACT}")
 
 message("---------------------------------------------------------------------")
 
@@ -1828,5 +2728,32 @@ if (LWS_WITH_SHARED)
        set(LIBWEBSOCKETS_LIBRARIES_SHARED websockets_shared CACHE STRING "Libwebsocket shared library")
 endif()
 
+if (LWS_WITH_MINIMAL_EXAMPLES)
+       MACRO(SUBDIRLIST result curdir)
+         FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
+         SET(dirlist "")
+
+         FOREACH(child ${children})
+           IF(IS_DIRECTORY ${curdir}/${child})
+               LIST(APPEND dirlist ${child})
+           ENDIF()
+         ENDFOREACH()
+
+         SET(${result} ${dirlist})
+       ENDMACRO()
+
+       SUBDIRLIST(SUBDIRS "${PROJECT_SOURCE_DIR}/minimal-examples")
+       FOREACH(subdir ${SUBDIRS})
+
+               SUBDIRLIST(SUBDIRS2 "${PROJECT_SOURCE_DIR}/minimal-examples/${subdir}")
+               FOREACH(subdir2 ${SUBDIRS2})
+                       if (EXISTS "${PROJECT_SOURCE_DIR}/minimal-examples/${subdir}/${subdir2}/CMakeLists.txt")
+                               message("Processing ${PROJECT_SOURCE_DIR}/minimal-examples/${subdir}/${subdir2}")
+                               add_subdirectory("${PROJECT_SOURCE_DIR}/minimal-examples/${subdir}/${subdir2}")
+                       endif()
+               ENDFOREACH()
+       ENDFOREACH()
+ENDIF()
+
 # This must always be last!
 include(CPack)
diff --git a/LICENSE b/LICENSE
index 9ce0f41..6c7cd90 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -33,19 +33,21 @@ to get original sources with the liberal terms.
 
 Original liberal license retained
 
-  - lib/sha-1.c         - 3-clause BSD license retained, link to original
+  - lib/misc/sha-1.c    - 3-clause BSD license retained, link to original
   - win32port/zlib      - ZLIB license (see zlib.h)
+  - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls)
 
 Relicensed to libwebsocket license
 
-  - lib/base64-decode.c - relicensed to LGPL2.1+SLE, link to original
-  - lib/daemonize.c     - relicensed from Public Domain to LGPL2.1+SLE,
-                          link to original Public Domain version
+  - lib/misc/base64-decode.c - relicensed to LGPL2.1+SLE, link to original
+  - lib/misc/daemonize.c     - relicensed from Public Domain to LGPL2.1+SLE,
+                               link to original Public Domain version
 
 Public Domain (CC-zero) to simplify reuse
 
-  - test-server/*.c
-  - test-server/*.h
+  - test-apps/*.c
+  - test-apps/*.h
+  - minimal-examples/*
   - lwsws/*
 
 ------ end of exceptions
diff --git a/Makefile.projbuild b/Makefile.projbuild
new file mode 100644 (file)
index 0000000..3145eaf
--- /dev/null
@@ -0,0 +1 @@
+CPPFLAGS += -I$(BUILD_DIR_BASE)/libwebsockets/include
diff --git a/README.build.md b/README.build.md
deleted file mode 100644 (file)
index dd3494a..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-Notes about building lws
-========================
-
-
-@section cm Introduction to CMake
-
-CMake is a multi-platform build tool that can generate build files for many
-different target platforms. See more info at http://www.cmake.org
-
-CMake also allows/recommends you to do "out of source"-builds, that is,
-the build files are separated from your sources, so there is no need to
-create elaborate clean scripts to get a clean source tree, instead you
-simply remove your build directory.
-
-Libwebsockets has been tested to build successfully on the following platforms
-with SSL support (for OpenSSL/wolfSSL/BoringSSL):
-
-- Windows (Visual Studio)
-- Windows (MinGW)
-- Linux (x86 and ARM)
-- OSX
-- NetBSD
-
-
-@section build1 Building the library and test apps
-
-The project settings used by CMake to generate the platform specific build
-files is called [CMakeLists.txt](CMakeLists.txt). CMake then uses one of its "Generators" to
-output a Visual Studio project or Make file for instance. To see a list of
-the available generators for your platform, simply run the "cmake" command.
-
-Note that by default OpenSSL will be linked, if you don't want SSL support
-see below on how to toggle compile options.
-
-
-@section bu Building on Unix:
-
-1. Install CMake 2.8 or greater: http://cmake.org/cmake/resources/software.html
-   (Most Unix distributions comes with a packaged version also)
-
-2. Install OpenSSL.
-
-3. Generate the build files (default is Make files):
-```
-        $ cd /path/to/src
-        $ mkdir build
-        $ cd build
-        $ cmake ..
-```
-
-4. Finally you can build using the generated Makefile:
-```
-       $ make && sudo make install
-```
-**NOTE**: The `build/`` directory can have any name and be located anywhere
- on your filesystem, and that the argument `..` given to cmake is simply
- the source directory of **libwebsockets** containing the [CMakeLists.txt](CMakeLists.txt)
- project file. All examples in this file assumes you use ".."
-
-**NOTE2**:
-A common option you may want to give is to set the install path, same
-as --prefix= with autotools.  It defaults to /usr/local.
-You can do this by, eg
-```
-       $ cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .
-```
-
-**NOTE3**:
-On machines that want libraries in lib64, you can also add the
-following to the cmake line
-```
-       -DLIB_SUFFIX=64
-```
-
-**NOTE4**:
-If you are building against a non-distro OpenSSL (eg, in order to get
-access to ALPN support only in newer OpenSSL versions) the nice way to
-express that in one cmake command is eg,
-```
-       $ cmake .. -DOPENSSL_ROOT_DIR=/usr/local/ssl \
-                -DCMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE=/usr/local/ssl \
-                -DLWS_WITH_HTTP2=1
-```
-
-When you run the test apps using non-distro SSL, you have to force them
-to use your libs, not the distro ones
-```
-       $ LD_LIBRARY_PATH=/usr/local/ssl/lib libwebsockets-test-server --ssl
-```
-
-To get it to build on latest openssl (2016-04-10) it needed this approach
-```
-       cmake .. -DLWS_WITH_HTTP2=1 -DLWS_OPENSSL_INCLUDE_DIRS=/usr/local/include/openssl -DLWS_OPENSSL_LIBRARIES="/usr/local/lib64/libssl.so;/usr/local/lib64/libcrypto.so"
-```
-
-Mac users have reported
-
-```
- $ export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2k; cmake ..; make -j4
-```
-
-worked for them when using "homebrew" OpenSSL
-
-**NOTE5**:
-To build with debug info and _DEBUG for lower priority debug messages
-compiled in, use
-```
-       $ cmake .. -DCMAKE_BUILD_TYPE=DEBUG
-```
-
-**NOTE6**
-To build on Solaris the linker needs to be informed to use lib socket
-and libnsl, and only builds in 64bit mode.
-
-```bash
-       $ cmake .. -DCMAKE_C_FLAGS=-m64 -DCMAKE_EXE_LINKER_FLAGS="-lsocket -lnsl"
-```
-
-4. Finally you can build using the generated Makefile:
-
-```bash
-       $ make
- ```
-
-@section lcap Linux Capabilities
-
-On Linux, lws now lets you retain selected root capabilities when dropping
-privileges.  If libcap-dev or similar package is installed providing
-sys/capabilities.h, and libcap or similar package is installed providing
-libcap.so, CMake will enable the capability features.
-
-The context creation info struct .caps[] and .count_caps members can then
-be set by user code to enable selected root capabilities to survive the
-transition to running under an unprivileged user.
-
-@section cmq Quirk of cmake
-
-When changing cmake options, for some reason the only way to get it to see the
-changes sometimes is delete the contents of your build directory and do the
-cmake from scratch.
-
-deleting build/CMakeCache.txt may be enough.
-
-
-@section cmw Building on Windows (Visual Studio)
-
-1. Install CMake 2.6 or greater: http://cmake.org/cmake/resources/software.html
-
-2. Install OpenSSL binaries. http://www.openssl.org/related/binaries.html
-
-   (**NOTE**: Preferably in the default location to make it easier for CMake to find them)
-
-   **NOTE2**: 
-   Be sure that OPENSSL_CONF environment variable is defined and points at 
-   <OpenSSL install location>\bin\openssl.cfg
-        
-3. Generate the Visual studio project by opening the Visual Studio cmd prompt:
-
-```
-       cd <path to src>
-       md build
-       cd build
-       cmake -G "Visual Studio 10" ..
-```
-
-   (**NOTE**: There is also a cmake-gui available on Windows if you prefer that)
-   
-   **NOTE2**:
-   See this link to find out the version number corresponding to your Visual Studio edition:
-   http://superuser.com/a/194065
-
-4. Now you should have a generated Visual Studio Solution in  your
-   `<path to src>/build` directory, which can be used to build.
-
-5. Some additional deps may be needed
-
- - iphlpapi.lib
- - psapi.lib
- - userenv.lib
-
-6. If you're using libuv, you must make sure to compile libuv with the same multithread-dll / Mtd attributes as libwebsockets itself
-
-
-@section cmwmgw Building on Windows (MinGW)
-
-1. Install MinGW: http://sourceforge.net/projects/mingw/files
-
-   (**NOTE**: Preferably in the default location C:\MinGW)
-
-2. Fix up MinGW headers
-
-   a) If still necessary, sdd the following lines to C:\MinGW\include\winsock2.h:
-```
-       #if(_WIN32_WINNT >= 0x0600)
-
-       typedef struct pollfd {
-
-               SOCKET  fd;
-               SHORT   events;
-               SHORT   revents;
-
-       } WSAPOLLFD, *PWSAPOLLFD, FAR *LPWSAPOLLFD;
-
-       WINSOCK_API_LINKAGE int WSAAPI WSAPoll(LPWSAPOLLFD fdArray, ULONG fds, INT timeout);
-
-       #endif // (_WIN32_WINNT >= 0x0600)
-```
-
-       Update crtdefs.h line 47 to say:
-
-```
-       typedef __int64 ssize_t;
-```
-
-   b) Create C:\MinGW\include\mstcpip.h and copy and paste the content from following link into it:
-
-   https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-headers/include/mstcpip.h
-
-3. Install CMake 2.6 or greater: http://cmake.org/cmake/resources/software.html
-
-4. Install OpenSSL binaries. http://www.openssl.org/related/binaries.html
-
-   (**NOTE**: Preferably in the default location to make it easier for CMake to find them)
-
-   **NOTE2**: 
-   Be sure that OPENSSL_CONF environment variable is defined and points at 
-   <OpenSSL install location>\bin\openssl.cfg
-
-5. Generate the build files (default is Make files) using MSYS shell:
-```
-       $ cd /drive/path/to/src
-       $ mkdir build
-       $ cd build
-       $ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW ..
-```
-   (**NOTE**: The `build/`` directory can have any name and be located anywhere
-    on your filesystem, and that the argument `..` given to cmake is simply
-    the source directory of **libwebsockets** containing the [CMakeLists.txt](CMakeLists.txt)
-    project file. All examples in this file assumes you use "..")
-
-   **NOTE2**:
-   To generate build files allowing to create libwebsockets binaries with debug information
-   set the CMAKE_BUILD_TYPE flag to DEBUG:
-```
-       $ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW -DCMAKE_BUILD_TYPE=DEBUG ..
-```
-6. Finally you can build using the generated Makefile and get the results deployed into your MinGW installation:
-
-```
-       $ make
-       $ make install
-```
-
-@section optee Building for OP-TEE
-
-OP-TEE is a "Secure World" Trusted Execution Environment.
-
-Although lws is only part of the necessary picture to have an https-enabled
-TA, it does support OP-TEE as a platform and if you provide the other
-pieces, does work very well.
-
-Select it in cmake with `-DLWS_PLAT_OPTEE=1`
-
-
-@section cmco Setting compile options
-
-To set compile time flags you can either use one of the CMake gui applications
-or do it via the command line.
-
-@subsection cmcocl Command line
-
-To list available options (omit the H if you don't want the help text):
-
-       cmake -LH ..
-
-Then to set an option and build (for example turn off SSL support):
-
-       cmake -DLWS_WITH_SSL=0 ..
-or
-       cmake -DLWS_WITH_SSL:BOOL=OFF ..
-
-@subsection cmcoug Unix GUI
-
-If you have a curses-enabled build you simply type:
-(not all packages include this, my debian install does not for example).
-
-       ccmake
-
-@subsection cmcowg Windows GUI
-
-On windows CMake comes with a gui application:
-       Start -> Programs -> CMake -> CMake (cmake-gui)
-
-
-@section wolf wolfSSL/CyaSSL replacement for OpenSSL
-
-wolfSSL/CyaSSL is a lightweight SSL library targeted at embedded systems:
-https://www.wolfssl.com/wolfSSL/Products-wolfssl.html
-
-It contains a OpenSSL compatibility layer which makes it possible to pretty
-much link to it instead of OpenSSL, giving a much smaller footprint.
-
-**NOTE**: wolfssl needs to be compiled using the `--enable-opensslextra` flag for
-this to work.
-
-@section wolf1 Compiling libwebsockets with wolfSSL
-
-```
-       cmake .. -DLWS_USE_WOLFSSL=1 \
-                -DLWS_WOLFSSL_INCLUDE_DIRS=/path/to/wolfssl \
-                -DLWS_WOLFSSL_LIBRARIES=/path/to/wolfssl/wolfssl.a ..
-```
-
-**NOTE**: On windows use the .lib file extension for `LWS_WOLFSSL_LIBRARIES` instead.
-
-@section cya Compiling libwebsockets with CyaSSL
-
-```
-       cmake .. -DLWS_USE_CYASSL=1 \
-                -DLWS_CYASSL_INCLUDE_DIRS=/path/to/cyassl \
-                -DLWS_CYASSL_LIBRARIES=/path/to/wolfssl/cyassl.a ..
-```
-
-**NOTE**: On windows use the .lib file extension for `LWS_CYASSL_LIBRARIES` instead.
-
-@section esp32 Building for ESP32
-
-Step 1, get ESP-IDF with lws integrated as a component
-
-```
-    $ git clone --int --recursive https://github.com/lws-team/lws-esp-idf
-```
-
-Step 2: Get Application including the test plugins
-
-```
-    $ git clone https://github.com/lws-team/lws-esp32
-```
-
-Set your IDF_PATH to point to the esp-idf you downloaded in 1)
-
-There's docs for how to build the lws-esp32 test app and reproduce it in the README.md here
-
-https://github.com/lws-team/lws-esp32/blob/master/README.md
-
-
-@section extplugins Building plugins outside of lws itself
-
-The directory ./plugin-standalone/ shows how easy it is to create plugins
-outside of lws itself.  First build lws itself with -DLWS_WITH_PLUGINS,
-then use the same flow to build the standalone plugin
-```
-       cd ./plugin-standalone
-       mkdir build
-       cd build
-       cmake ..
-       make && sudo make install
-```
-
-if you changed the default plugin directory when you built lws, you must
-also give the same arguments to cmake here (eg,
-` -DCMAKE_INSTALL_PREFIX:PATH=/usr/something/else...` )
-
-Otherwise if you run lwsws or libwebsockets-test-server-v2.0, it will now
-find the additional plugin "libprotocol_example_standalone.so"
-```
-       lwsts[21257]:   Plugins:
-       lwsts[21257]:    libprotocol_dumb_increment.so
-       lwsts[21257]:    libprotocol_example_standalone.so
-       lwsts[21257]:    libprotocol_lws_mirror.so
-       lwsts[21257]:    libprotocol_lws_server_status.so
-       lwsts[21257]:    libprotocol_lws_status.so
-```
-If you have multiple vhosts, you must enable plugins at the vhost
-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
-
-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
-```
-       lwsts[4752]:  Compiled with OpenSSL support
-       lwsts[4752]:  Using SSL mode
-       lwsts[4752]:  HTTP2 / ALPN enabled
-```
-at lws startup.
-
-For non-SSL HTTP2.0 upgrade
-```
-       $ nghttp -nvasu http://localhost:7681/test.htm
-```
-For SSL / ALPN HTTP2.0 upgrade
-```
-       $ nghttp -nvas https://localhost:7681/test.html
-```
-
-@section cross Cross compiling
-
-To enable cross-compiling **libwebsockets** using CMake you need to create
-a "Toolchain file" that you supply to CMake when generating your build files.
-CMake will then use the cross compilers and build paths specified in this file
-to look for dependencies and such.
-
-**Libwebsockets** includes an example toolchain file [cross-arm-linux-gnueabihf.cmake](cross-arm-linux-gnueabihf.cmake)
-you can use as a starting point.
-
-The commandline to configure for cross with this would look like
-```
-       $ cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr \
-                -DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake \
-                -DLWS_WITHOUT_EXTENSIONS=1 -DLWS_WITH_SSL=0
-```
-The example shows how to build with no external cross lib dependencies, you
-need to provide the cross libraries otherwise.
-
-**NOTE**: start from an EMPTY build directory if you had a non-cross build in there
-       before the settings will be cached and your changes ignored.
-
-Additional information on cross compilation with CMake:
-       http://www.vtk.org/Wiki/CMake_Cross_Compiling
-
-@section mem Memory efficiency
-
-Embedded server-only configuration without extensions (ie, no compression
-on websocket connections), but with full v13 websocket features and http
-server, built on ARM Cortex-A9:
-
-Update at 8dac94d (2013-02-18)
-```
-       $ ./configure --without-client --without-extensions --disable-debug --without-daemonize
-
-       Context Creation, 1024 fd limit[2]:   16720 (includes 12 bytes per fd)
-       Per-connection [3]:                      72 bytes, +1328 during headers
-
-       .text   .rodata .data   .bss
-       11512   2784    288     4
-```
-This shows the impact of the major configuration with/without options at
-13ba5bbc633ea962d46d using Ubuntu ARM on a PandaBoard ES.
-
-These are accounting for static allocations from the library elf, there are
-additional dynamic allocations via malloc.  These are a bit old now but give
-the right idea for relative "expense" of features.
-
-Static allocations, ARM9
-
-|                                | .text   | .rodata | .data | .bss |
-|--------------------------------|---------|---------|-------|------|
-| All (no without)               | 35024   | 9940    | 336   | 4104 |
-| without client                 | 25684   | 7144    | 336   | 4104 |
-| without client, exts           | 21652   | 6288    | 288   | 4104 |
-| without client, exts, debug[1] | 19756   | 3768    | 288   | 4104 |
-| without server                 | 30304   | 8160    | 336   | 4104 |
-| without server, exts           | 25382   | 7204    | 288   | 4104 |
-| without server, exts, debug[1] | 23712   | 4256    | 288   | 4104 |
-
-[1] `--disable-debug` only removes messages below `lwsl_notice`.  Since that is
-the default logging level the impact is not noticeable, error, warn and notice
-logs are all still there.
-
-[2] `1024` fd per process is the default limit (set by ulimit) in at least Fedora
-and Ubuntu.  You can make significant savings tailoring this to actual expected
-peak fds, ie, at a limit of `20`, context creation allocation reduces to `4432 +
-240 = 4672`)
-
-[3] known header content is freed after connection establishment
diff --git a/README.esp8266.md b/README.esp8266.md
deleted file mode 100644 (file)
index ffcf757..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-ESP8266 lws port
-----------------
-
-lws can now work well on the ESP8266.
-
-You should get the ESP8266 Espressif SDK-based project here
-
-https://github.com/lws-team/esplws
-
-which includes lws as an "app" in the build.  The project provides full AP-based setup over the web, and once the device has been configured to associate to a local AP, a separate station vhost with the lws test protocols.
-
-Instructions for building that are here
-
-https://github.com/lws-team/esplws/blob/master/README.md
-
-There are also instructions there for how to remove the test apps from the build and customize your own station content.
-
-
-Information about lws integration on ESP8266
---------------------------------------------
-
-The following existing lws features are used to make a nice integration:
-
- - vhosts: there are separate vhosts for the configuration AP mode and the normal station mode.
-
- - file_ops: the lws file operations are overridden and handled by a ROMFS parser
-
- - mounts: mounts are used to serve files automatically from the ROMFS
-
- - plugins: standalone protocol plugins are included into the build, so there are clean individual implementations for each protocol, while everything is statically linked
-
- - lws stability and security features like bytewise parsers, sophisticated timeouts, http/1.1 keepalive support
-
-
diff --git a/README.lws-meta.md b/README.lws-meta.md
deleted file mode 100644 (file)
index dbca4c0..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-# lws-meta protocol
-
-lws-meta is a lightweight ws subprotocol that accepts other ws connections
-to the same server inside it and multiplexes their access to the connection.
-
-```
-  Client                                                    Server
-  conn1: \                                                / :conn1
-  conn2:  =   mux ------ lws-meta ws protocol ----- mux  =  :conn2
-  conn3: /                                                \ :conn3
-```
-
-You may have n client ws connections back to the server, but you now
-only have one tcp connection (and one SSL wrapper if using SSL) instead
-of n of those.
-
-If you currently make multiple ws connections back to the server, so you
-can have different protocols active in one webpage, this if for you.
-
- - The subprotocol code for the connections inside a lws-meta connection
-   need zero changes from being a normal ws connection.  It is unaware
-   it is inside an lws-meta parent connection.
-
- - The traffic on the lws-meta connection is indistinguishable from
-   standard ws traffic, so intermediaries won't object to it
-
- - The multiplexing is done in the protocol, **not by an extension**.  So
-   it's compatible with all browsers.
-
- - Javascript helper code is provided to very simply use lws-meta
-   protocol instead of direct connections.  The lws test server has
-   been converted to use this by default.
-
-# Converting your server
-
-1) include the provided lws-meta plugin (plugins/protocl_lws_meta.c) as an
-active protocol for your server.  You can do that using runtime plugins, or
-include the plugin sources into your server at build-time.  The lws test
-server uses the latter approach.
-
-That's all you need to do on the server side.
-
-# Converting your browser JS
-
-1) import lws-common.js
-
-2) Instantiate a parent lws-meta connection object
-
-```
-var lws_meta = new lws_meta_ws();
-```
-
-3) Connect the lws-meta object to your server
-
-```
-lws_meta.new_parent(get_appropriate_ws_url("?mirror=" + mirror_name));
-```
-
-4) Convert your actual ws connections to go via the lws_meta object
-
-```
-var my_ws = lws_meta.new_ws("", "dumb-increment-protocol");
-```
-
-The first arg is the URL path, the second arg is the ws protocol you want.
-
-That's it.  my_ws will get `onopen()`, `onmessage()` etc calls as before.
-
-# lws-meta wire protocol
-
-lws-meta works by adding some bytes at the start of a message indicating
-which channel the message applies to.
-
-Channel messages are atomic on the wire.  The reason is if we tried to
-intersperse other channel fragments between one channels message fragments,
-an intermediary would observe violations of the ws framing rule about
-having to start a message with TEXT or BINARY, and use only CONTINUATION
-for the subsequent fragments.  Eg
-
-```
-  [ ch1 TEXT NOFIN ] [ ch2 BINARY FIN ] [ ch1 CONTINUATION FIN ]
-```
-
-is illegal to an observer that doesn't understand lws-meta headers in the
-packet payloads.  So to avoid this situation, only complete messages may
-be sent from one subchannel in each direction at a time.
-
-Consequently, only the first fragment of each message is modified to
-have the extra two bytes identifying the subchannel it is aimed at, since
-the rest of the message from the same subchannel is defined to follow.
-
-If it makes latencies, modify the protocol sending large messages to
-send smaller messages, so the transmission of messages from other channels
-can be sent inbetween the smaller messages.
-
-## lws-meta commands
-
-1) CSTRING indicates a string terminated by 0x00 byte
-
-2) Channel IDs are sent with 0x20 added to them, to guarantee valid UTF-8
-
-### 0x41: RX: LWS_META_CMD_OPEN_SUBCHANNEL
-
-   - CSTRING: protocol name
-   - CSTRING: url
-   - CSTRING: cookie (7 bytes max)
-
-Client is requesting to open a new channel with the given protocol name,
-at the given url.  The cookie (eg, channel name) is only used in
-LWS_META_CMD_OPEN_RESULT, when the channel id is assigned, so it is
-applied to the right channel.
-
-### 0x42: TX: LWS_META_CMD_OPEN_RESULT
-
-   - CSTRING cookie
-   - BYTE channel id (0 indicates failed)
-   - CSTRING: selected protocol name
-
-The server is informing the client of the results of a previous
-open request.  The cookie the client sent to identify the request
-is returned along with a channel id to be used subsequently.  If
-the channel ID is 0 (after subtracting the transport offset of
-0x20) then the open request has failed.
-
-### 0x43: TX: LWS_META_CMD_CLOSE_NOTIFY
-
-   - BYTE channel id
-   - BYTE: payload length + 0x20
-   - BYTE: close code MSB
-   - BYTE: close code LSB
-   - PAYLOAD: payload (< 123 bytes)
-
-Server notifies the client that a child has closed, for whatever reason.
-
-### 0x44: RX: LWS_META_CMD_CLOSE_RQ
-   - BYTE: channel id
-   - BYTE: payload length + 0x20
-   - BYTE: close code MSB
-   - BYTE: close code LSB
-   - PAYLOAD: payload (< 123 bytes)
-
-The client requests to close a child connection
-
-### 0x45: TX: LWS_META_CMD_WRITE
-
-   - BYTE: channel id
-
-Normal write of payload n from lws-meta perspective is actually
-LWS_META_CMD_WRITE, channel id, then (n - 2) bytes of payload
-
-The command only appears at the start of a message, continuations do
-not have the command.
-
-## Protocol Notes
-
- - Once the subchannel is up, overhead is only +2 bytes per message
-
- - Close reasons are supported in both directions
-
- - Ping and Pong are only supported at the lws-meta level, using normal ws ping and pong packets.
-
- - Only the final close of the tcp lws-meta connection itself goes out as
-   a normal ws close frame.  Subchannels close is done in a normal TEXT
-   message using LWS_META_CMD_CLOSE_RQ and then the close packet payload.
-   This is so intermediaries do not mistake subchannel closures for the
-   tcp / ws link going down.
-
-   Messages that start with LWS_META_CMD_OPEN_SUBCHANNEL only contain those
-   commands but may contain any number of them for the whole duration of the
-   message.  The lws-meta js support collects child open requests made before
-   the parent lws-meta connection is open, and dumps them all in a single
-   message when it does open.
-
-   Messages that start with LWS_META_CMD_OPEN_RESULT or LWS_META_CMD_CLOSE_NOTIFY
-   only contain those two commands, but they may contain any number of them
-   for the whole duration of the message.
-
-
-# Current Implemention Limitations
-
- - only server side is supported in lws.  The client side JS for
-   a browser is supported.
-
- - max number of child connections per parent at the moment is 8
-
- - child connection URL paramter when opening the connection is
-   ignored
-
- - there is no ah attached when the child connections are
-   established inside the lws-meta parent.  So header access
-   functions will fail.
index 107ae48..aa3ed30 100644 (file)
--- a/README.md
+++ b/README.md
-[![Travis Build Status](https://travis-ci.org/warmcat/libwebsockets.svg)](https://travis-ci.org/warmcat/libwebsockets)
-[![Appveyor Build status](https://ci.appveyor.com/api/projects/status/qfasji8mnfnd2r8t?svg=true)](https://ci.appveyor.com/project/lws-team/libwebsockets)
-[![Coverity Scan Build Status](https://scan.coverity.com/projects/3576/badge.svg)](https://scan.coverity.com/projects/3576)
+[![Travis Build Status](https://travis-ci.org/warmcat/libwebsockets.svg)](https://travis-ci.org/warmcat/libwebsockets) [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/qfasji8mnfnd2r8t?svg=true)](https://ci.appveyor.com/project/lws-team/libwebsockets) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3576/badge.svg)](https://scan.coverity.com/projects/3576) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2266/badge)](https://bestpractices.coreinfrastructure.org/projects/2266) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/144fb195a83046e484a75c8b4c6cfc99)](https://www.codacy.com/app/lws-team/libwebsockets?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=warmcat/libwebsockets&amp;utm_campaign=Badge_Grade)
 
-libwebsockets
--------------
+# Libwebsockets
+
+Libwebsockets is a simple-to-use, pure C library providing client and server
+for **http/1**, **http/2**, **websockets** and other protocols in a security-minded,
+lightweight, configurable, scalable and flexible way.  It's easy to build and
+cross-build via cmake and is suitable for tasks from embedded RTOS through mass
+cloud serving.
+
+[50 minimal examples](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples) for
+various scenarios, CC0-licensed (public domain) for cut-and-paste, allow you to get started quickly.
+
+![overview](./doc-assets/lws-overview.png)
 
 News
 ----
 
-v2.3 is out... see the changelog https://github.com/warmcat/libwebsockets/blob/v2.3-stable/changelog
+## V3.2 relase last planned LGPLv2.1+SLE release
+
+As foretold the v3.2 release is the last planned release that will have the code
+under LGPLv2.1+SLE.  Master has those parts changed to MIT license; the pieces
+that were CC0 or another liberal license remain the same.
+
+## License change plan
+
+Lws is planning to change the pieces that are currently LGPLv2.1+SLE to MIT
+https://opensource.org/licenses/MIT .  Stuff that is already CC0 or another
+permissive license will stay as it is.
+
+This license change is making an already permissive license (it was already LGPL,
+and the SLE removed most restrictions already) even more permissive.
+So I expect most contributors either don't much care or are happy about it.
+Contributors who object should contact me via:
+
+ - the lws mailing list https://libwebsockets.org/mailman/listinfo/libwebsockets
+ - github issue https://github.com/warmcat/libwebsockets , or
+ - email to `andy@warmcat.com`
+
+...before **Aug 11 2019**, and I'll rewrite the related code before the change.
+There'll be a last release of the currently-licensed stuff (probably v3.2) and
+then the same code will have the licese grant changed in the sources, become
+master and also have an otherwise identical release, probably v4.0.  The v3.2
+stuff won't be maintained (by me anyway... it's FOSS though) but the v4.0
+stuff which is the same except the license will get the usual v4.0-stable
+treatment.
+
+Even after the change I will continue to rely on users to help me with bug
+reports and patches, work together on new features.  The license will no
+longer require it but the practical advantages from staying aligned with
+upstream lws for users remain the same.
+
+## New features on master
+
+ - `LWS_WITH_NETWORK` cmake option (default on) allows one-step removal of vhost,
+   wsi, roles, event loop and all network-related code from the build.  This
+   enables use-cases where you actually need unrelated features like JOSE or FTS
+   compactly.  lws_context still exists and if tls is enabled, the tls-related code
+   is still built so the crypto is available, just nothing related to network.
+
+ - New Crypto-agile APIs + JOSE / JWS / JWE / JWK support... apis work exactly
+   the same with OpenSSL or mbedTLS tls library backends, and allow key cycling
+   and crypto algorithm changes while allowing for grace periods
+
+   [README.crypto-apis](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.crypto-apis.md)
+
+ - CMake config simplification for crypto: `-DLWS_WITH_GENCRYPTO=1` for all
+   generic cipher and hash apis built (which work the same on mbedtls and
+   OpenSSL transparently), and `-DLWS_WITH_JOSE=1` for all JOSE, JWK, JWS
+   and JWE support built (which use gencrypto and so also work the same
+   regardless of tls library backend).
+
+ - **`x.509`** - new generic x509 api allows PEM-based certificate and key
+   trust relationship verification, and conversion between x.509 keys and
+   JWK.  Works for EC and RSA keys, and on mbedtls and OpenSSl the same.
+
+   [x.509 api](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-x509.h), 
+   [x.509 minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-x509)
+
+ - **`JWE`** - JWE (RFC7516) Algorithms with CI tests:
+
+|Key Encryption|Payload authentication + crypt|Enc + Dec Support|
+|---|---|---|
+|`RSAES-PKCS1-v1.5` 2048b & 4096b|`AES_128_CBC_HMAC_SHA_256`|Enc + Dec|
+|`RSAES-PKCS1-v1.5` 2048b|`AES_192_CBC_HMAC_SHA_384`|Enc + Dec|
+|`RSAES-PKCS1-v1.5` 2048b|`AES_256_CBC_HMAC_SHA_512`|Enc + Dec|
+|`RSAES-OAEP`|`AES_256_GCM`|Enc + Dec|
+|`AES128KW`, `AES192KW`, `AES256KW`|`AES_128_CBC_HMAC_SHA_256`|Enc + Dec|
+|`AES128KW`, `AES192KW`, `AES256KW`|`AES_192_CBC_HMAC_SHA_384`|Enc + Dec|
+|`AES128KW`, `AES192KW`, `AES256KW`|`AES_256_CBC_HMAC_SHA_512`|Enc + Dec|
+|`ECDH-ES` (P-256/384/521 key)|`AES_128/192/256_GCM`|Enc + Dec|
+|`ECDH-ES+A128/192/256KW` (P-256/384/521 key)|`AES_128/192/256_GCM`|Enc + Dec|
+
+All tests pass on both OpenSSL and mbedTLS backends, using keys generated on
+both OpenSSL and mbedTLS in the tests.
+
+A minimal example tool shows how to encrypt and decrypt compact JWE objects
+from the commandline for all supported algorithms.
+
+   [jwe api](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jwe.h), 
+   [jwe unit tests](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-jose/jwe.c), 
+   [jwe minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwe)
+
+ - **`lws-genec` ECDSA** - JWS-compatible ECDSA is supported on both OpenSSL and mbedtls.
+
+ - **`JWS`** - JWS (RFC7515) is now supported for none, HS256/384/512, RS256/384/512, and ES256/384/512, on both OpenSSL and mbedtls.  There's a minimal example tool that signs and verifies compact
+ representation JWS from stdin.
+   [jws api](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jws.h), 
+   [jws unit tests](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-jose/jws.c), 
+   [jws minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwe)
+
+ - **`JWK`** - JWK (RFC7517) now supports oct, RSA and EC keys including JSON key
+   arrays on both OpenSSL and mbedtls.  A minimal example tool shows how to create
+   new JSON JWK keys to specified parameters from the commandline for all supported
+   ciphers.
+
+   [jwk minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwk)
+
+ - **`lws-genrsa` OAEP + PSS support** - in addition to PKCS#1 1.5 padding, OAEP and PSS are
+   now supported on both mbedtls and openssl backends.
+
+ - **`lws-genaes` Generic AES crypto** - thin api layer works identically with both mbedtls and openssl
+   backends.  Supports CBC, CFB128, CFB8, CTR, ECB, OFB, XTS and GCM variants.  Unit tests in CI.
+   [genaes api](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genaes.h),
+   [api test](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-gencrypto),
+   CMake config: `-DLWS_WITH_GENCRYPTO=1`
+
+ - **http fallback support** - you can specify a role and protocol to apply if non-http or non-tls
+   packets arrive at an http(s) listen port.  For example, you can specify that the new `raw proxy`
+   role + protocol should be used, to proxy your sshd port over :443 or :80.  Without affecting
+   normal http(s) serving on those ports but allowing, eg, `ssh -p 443 invalid@libwebsockets.org`.
+   [http fallback docs](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.http-fallback.md)
+
+ - **raw tcp proxy role and protocol** - adding raw tcp proxying is now trivial using the built-in lws
+   implementation.  You can control the onward connection using a pvo in the format "ipv4:server.com:port"
+   [raw proxy minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/raw/minimal-raw-proxy),
+   [raw proxy docs](https://libwebsockets.org/git/libwebsockets/tree/plugins/raw-proxy),
+   Cmake config: `-DLWS_ROLE_RAW_PROXY=1 -DLWS_WITH_PLUGINS=1`
+
+ - **deaddrop HTML file upload protocol** - protocol and minimal example for file upload and sharing using
+   drag and drop and a file picker.  Integrated with basic auth, uploaded files marked with upload user,
+   and files owned by the authenticated user may be deleted via the UI.  Supports multiple simultaneous
+   uploads both by drag-and-drop and from the file picker.
+   [deaddrop minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/http-server/minimal-http-server-deaddrop)
+
+ - **basic auth for ws(s)** - You can apply basic auth credential requirement to ws connections same
+   as on mounts now.  Just add a pvo "basic-auth" with the value being the credentials file path when
+   enabling the ws protocol for the vhost.
 
-ESP32 is now supported in lws!  Download the
+## v3.1 released: new features in v3.1
 
- - factory https://github.com/warmcat/lws-esp32-factory and
- - test server app https://github.com/warmcat/lws-esp32-test-server-demos
+ - **lws threadpool** - lightweight pool of pthreads integrated to lws wsi, with all
+   synchronization to event loop handled internally, queue for excess tasks
+   [threadpool docs](https://libwebsockets.org/git/libwebsockets/tree/lib/misc/threadpool), 
+   [threadpool minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/ws-server/minimal-ws-server-threadpool), 
+   Cmake config: `-DLWS_WITH_THREADPOOL=1`
 
+ - **libdbus support** integrated on lws event loop
+   [lws dbus docs](https://libwebsockets.org/git/libwebsockets/tree/lib/roles/dbus), 
+   [lws dbus client minimal examples](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/dbus-client), 
+   [lws dbus server minimal examples](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/dbus-server), 
+   Cmake config: `-DLWS_ROLE_DBUS=1`
+
+ - **lws allocated chunks (lwsac)** - helpers for optimized mass allocation of small
+   objects inside a few larger malloc chunks... if you need to allocate a lot of
+   inter-related structs for a limited time, this removes per-struct allocation
+   library overhead completely and removes the need for any destruction handling
+   [lwsac docs](https://libwebsockets.org/git/libwebsockets/tree/lib/misc/lwsac), 
+   [lwsac minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-lwsac), 
+   Cmake Config: `-DLWS_WITH_LWSAC=1`
+
+ - **lws tokenizer** - helper api for robustly tokenizing your own strings without
+   allocating or adding complexity.  Configurable by flags for common delimiter
+   sets and comma-separated-lists in the tokenizer.  Detects and reports syntax
+   errors.
+   [lws_tokenize docs](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-tokenize.h), 
+   [lws_tokenize minimal example / api test](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-lws_tokenize)
+
+ - **lws full-text search** - optimized trie generation, serialization,
+   autocomplete suggestion generation and instant global search support extensible
+   to huge corpuses of UTF-8 text while remaining super lightweight on resources.
+   [full-text search docs](https://libwebsockets.org/git/libwebsockets/tree/lib/misc/fts), 
+   [full-text search minimal example / api test](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-fts), 
+   [demo](https://libwebsockets.org/ftsdemo/), 
+   [demo sources](https://libwebsockets.org/git/libwebsockets/tree/plugins/protocol_fulltext_demo.c), 
+   Cmake config: `-DLWS_WITH_FTS=1 -DLWS_WITH_LWSAC=1`
+
+ - **gzip + brotli http server-side compression** - h1 and h2 detection of client support
+   for server compression, and auto-application to files with mimetypes "text/*",
+   "application/javascript" and "image/svg.xml".
+   Cmake config: `-DLWS_WITH_HTTP_STREAM_COMPRESSION=1` for gzip, optionally also give
+   `-DLWS_WITH_HTTP_BROTLI=1` for preferred `br` brotli compression
+
+ - **managed disk cache** - API for managing a directory containing cached files
+   with hashed names, and automatic deletion of LRU files once the cache is
+   above a given limit.
+   [lws diskcache docs](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-diskcache.h), 
+   Cmake config: `-DLWS_WITH_DISKCACHE=1`
+
+ - **http reverse proxy** - lws mounts support proxying h1 or h2 requests to
+   a local or remote IP, or unix domain socket over h1.  This allows microservice
+   type architectures where parts of the common URL space are actually handled
+   by external processes which may be remote or on the same machine.
+   [lws gitohashi serving](https://libwebsockets.org/git/) is handled this way.
+   [unix domain sockets reverse proxy docs](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.unix-domain-reverse-proxy.md), 
+   CMake config: `-DLWS_WITH_HTTP_PROXY=1` and `-DLWS_UNIX_SOCK=1` for Unix Domain Sockets
+
+ - **update minimal examples for strict Content Security Policy** the minimal
+   examples now show the best practices around Content Security Policy and
+   disabling inline Javascript.  Updated examples that are served with the
+   recommended security restrictions show a new "Strict Content Security Policy"
+   graphic.  [Read how to upgrade your applications to use a strict CSP](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.content-security-policy.md).
+
+ - **release policy docs** - unsure what branch, version or tag to use, or how
+   to follow master cleanly?  [Read the release policy docs](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.release-policy.md)
+   which explain how and why lws is developed, released and maintained.
+
+## v3.0.1 released
+
+See the git log for the list of fixes.
+
+## v3.0.0 released
+
+See the changelog for info https://libwebsockets.org/git/libwebsockets/tree/changelog?h=v3.0-stable
+
+## Major CI improvements for QA
+
+The Travis build of lws done on every commit now runs:
+
+Tests|Count|Explanation
+---|---|---
+Build / Linux / gcc|16|-Wall -Werror cmake config variants
+Build / Mac / Clang|16|-Wall -Werror cmake config variants
+Build / Windows / MSVC|7|default
+Selftests|openssl:43, mbedtls:43|minimal examples built and run against each other and remote server
+attack.sh|225|Correctness, robustness and security tests for http parser
+Autobahn Server|480|Testing lws ws client, including permessage-deflate
+Autobahn Client|480|Testing lws ws server, including permaessage-deflate
+h2spec|openssl:146, mbedtls:146|Http/2 server compliance suite (in strict mode)
+h2load|openssl:6, mbedtls:6|Http/2 server load tool (checks 10K / 100K in h1 and h2, at 1, 10, 100 concurrency)
+h2load SMP|6|Http/2 and http/1.1 server load checks on SMP server build
+
+The over 1,500 tests run on every commit take 1hr 15 of compute time to complete.
+If any problems are found, it breaks the travis build, generating an email.
+
+Codacy also checks every patch and the information used to keep lws at zero issues.
+
+Current master is checked by Coverity at least daily and kept at zero issues.
+
+Current master passes all the tests and these new CI arrangements will help
+keep it that way.
+
+## Lws has the first official ws-over-h2 server support
+
+![wss-over-h2](./doc-assets/wss2.png)
+
+There's a new [RFC](https://tools.ietf.org/html/rfc8441) that enables multiplexing ws connections
+over an http/2 link.  Compared to making individual tcp and tls connections for
+each ws link back to the same server, this makes your site start up radically
+faster, and since all the connections are in one tls tunnel, with considerable memory
+reduction serverside.
+
+To enable it on master you just need -DLWS_WITH_HTTP2=1 at cmake.  No changes to
+existing code are necessary for either http/2 (if you use the official header creation
+apis if you return your own headers, as shown in the test apps for several versions)
+or to take advantage of ws-over-h2.  When built with http/2 support, it automatically
+falls back to http/1 and traditional ws upgrade if that's all the client can handle.
+
+Currently only Chrome Canary v67 supports this ws-over-h2 encapsulation (chrome
+must be started with `--enable-websocket-over-http2` switch to enable it currently),
+and patches exist for Firefox.  Authors of both browser implementations tested
+against the lws server implementation.
+
+## New "minimal examples"
+
+https://libwebsockets.org/git/libwebsockets/tree/minimal-examples
+
+These are like the test apps, but focus on doing one thing, the best way, with the
+minimum amount of code.  For example the minimal-http-server serves the cwd on
+http/1 or http/2 in 50 LOC.  Same thing with tls is just three more lines.
+
+They build standalone, so it's easier to copy them directly to start your own project; they
+are CC0 licensed (public domain) to facilitate that.
+
+## Windows binary builds
+
+32- and 64-bit Windows binary builds are available via Appveyor.  Visit
+[lws on Appveyor](https://ci.appveyor.com/project/lws-team/libwebsockets),
+click on a build, the ARTIFACTS, and unzip the zip file at `C:\Program Files (x86)/libwebsockets`.
+
+## Support
 
 This is the libwebsockets C library for lightweight websocket clients and
 servers.  For support, visit
 
  https://libwebsockets.org
- https://github.com/warmcat/libwebsockets
 
 and consider joining the project mailing list at
 
@@ -28,65 +301,7 @@ and consider joining the project mailing list at
 
 You can get the latest version of the library from git:
 
-- https://github.com/warmcat/libwebsockets
 - https://libwebsockets.org/git
 
 Doxygen API docs for master: https://libwebsockets.org/lws-api-doc-master/html/index.html
 
-
-After libwebsockets 1.3, tags will be signed using a key corresponding to this public key
-
-```
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v1
-
-mQINBFRe35QBEADZA7snW7MoEXkT2deDYZeggVD3694dg1o5G4q36NWjC8Pn/b2V
-d+L9Nmw8ydKIv8PLJW762rnveQpPYRqCRD8X4bVTYzYz3qsOl5BrYf6cuVn0ZrPB
-13TVRg+NZwUaVxc7O+tdOvvEBdA9OCIygctPNK9Nyh53xs5gPHhghZrKVrt0xM1A
-2LYsgoHmMBCCY25SHb1nuapvhA3LvuJb4cNNVRCukCoA6yx0uhSEz2AUPJSLqnZ9
-XnNBMKq+1a9C+y7jo4O78upTTmuOmRmNEVAu7pxCSUXDrNa87T8n6vFkV/MiW8nv
-VmhppKJrKPJ0KxJF9b7uG6eKosfoK2PKyE7pAoDN1fuNyBTB0dkFAwyTCN8hmhOg
-z71QrCltotq/AxSCsKzgFkDBL7D3KUM10QR5kmznjcm8tFWHoSttPR334z/1Yepf
-ATqH/tfYydW42qeeHgKjfeegnlI65nTDtwYW6lSqZsXg+/ABg0ki9m5HA6l713ig
-gRbVHSNkiz56O+UOqBtfcJZBc8QZqqixq8rbP2Is0HBBEtD+aFMuKx/sQ3ULkQs2
-8dZ5qsGTBT/xHmqpHJsIFX/jwjY5zeEiFbnO5bMH7YLmkjynVsn5zxTyXKQJe29C
-Uq0Yd9+JpDhHnZoiz/1hIIBsr89Z4Yy6c59YNJ3yJEOast0ODERcKSaUAQARAQAB
-tC9BbmR5IEdyZWVuIChMaW5hcm8ga2V5KSA8YW5keS5ncmVlbkBsaW5hcm8ub3Jn
-PokCPQQTAQoAJwUCVF7flAIbAwUJBaOagAULCQgHAwUVCgkICwUWAwIBAAIeAQIX
-gAAKCRA8ZxoDS3lTexApD/9WT7JWy3tK33OIACYV40XwLEhRam4Xku4rhtsoIeJK
-P0k/wa7J2PpceX6gKV+QBsOx3UbUfpqZ/Mu7ff3M0J6W87XpKRROAmP43zyiBkmM
-A6v0pJXozknmCU28p3DuLC8spVDFg9N52xV7Qb+9TDHcTYiVi4swKYuDEuHBC/qa
-M69+ANgsGbrMFRypxtU7OEhls3AEo3Cq03xD8QvLjFyPpYp1f0vNRFm2Jjgm2CRe
-YLVsCGxG35Dz7DpJHekHNxje6xsZ2w9Q38M0rLQ0ICOVQ+E1Dir3hwmZQWASzMMi
-+R0P+MVYpVt5y7KtiLywJ4BzNogS7gY3wQxksJOFA1uuk5h/hO54a361mcdA0Ta5
-HHhGKRw87lVjEQSaRjFZmHFClB+Sb8MuWR51JTzVS5HtJlcNqcWhF63vZ8bZ7b6y
-Aj8cXNjH6ULXyX3QnTUWXX/QU3an3yh8iPONWOGP5d5Hi/qejHGIhP2L5H+h05CP
-aZQYFLjjebYgEHijuA28eKWsBsoBPFSLpLloHTDkiycgFdV2AkQcxZN9ZElAqURP
-xUkEIscQg3YhExGiVEtaxBp1/p/WctMxs5HNoi0Oc97ZUcKvSCz9FDGXX9wYBpRf
-gzjNn055Xn4QyxBDnp5DrYT0ft/8BEnRK0JP6z3gNfnhOxZo4XA+M6w4Hjh3tI2A
-3rkCDQRUXt+UARAA0yHmONtW3L1HpvWFR+VgVNHa1HBWWk7lMsI6ajeiUK/lN3F/
-+vNbux46bPj/sNT9twbWmYhv6c0yVzCpmv5M5ztefS7mW/zPNLJmCmH32kAvVFr1
-Z90R/X+Z1Uh8wCCU72S2pSIXQFza3LF53pbpKi5m1F2icYcx+35egAvvZVZtcrMu
-TjHUa+N9mFKxa7tb5PI8Lv93nRLwB7aKkp5PKy9Yvse0jACrAAGeIpI73H467/wO
-ujermKlyPOOv+Lpjd7kedWKdaweitva7FVI20K/afn4AwCI8HJUIqVbil0Yrg9Le
-M1TRsRydzMQQejsb/cWi3fQ3U3HxvSJijKltckPMqjJaXbqmrLz3FOA5Km0ciIOB
-WW0Qq0WREcS3rc5FHU29duS9OAieAWFYyLDieug4nQ29KQE6I0lMqLnz8vWYtbmw
-6AHk9i2GsXOZiPnztuADgt9o9Os8fm7ZiacA1LISl86P7wpFk+Gf4LRvv8Fk08NV
-b2K1BY4YC9KP+AynyYQmxmyB1YQCh/dZHiD4ikGKttHAy4ZsMW6IRL5bRP0Z97pA
-lyBtXP0cGTJtuPt2feh0zaiA7blZ/IDXkB1UqH6jnTa71d1FeNKtVFi8FhPIREN6
-Rc5imyRxubZEgsxhdjqGgdT5k6Qr42SewAN391ygutpgGizGQtTwzvmKa0UAEQEA
-AYkCJQQYAQoADwUCVF7flAIbDAUJBaOagAAKCRA8ZxoDS3lTewuBD/9/rakAMCRC
-+WmbUVpCbJSWP5ViH87Xko4ku437gq56whcGjQpxfCYt8oeVgS8fZetUOHs2gspJ
-CEc8TYLUFntfyt2AzKU29oQoizPm33W9S1u7aRGWsVVutd2sqUaQUDsl9z35+Ka9
-YcWoATJSWBgnhSAmNcM60OG0P5qrZloTlbRSlDZTSZT3RvY4JWtWCubGsjEpXO4h
-ZqbKCu3KgV/6NOuTLciriSOZ/iyva3WsCP2S8mRRvma7x04oMTEWX80zozTCK8gG
-XqqS9eDhCkRbdmMyUQbHIhc/ChYchO5+fQ1o0zMS5cv6xgkhWI3NJRUkNdXolH9a
-5F9q4CmCTcdEZkqpnjsLNiQLIENfHbgC0A5IjR6YgN6qAP8ZJ5hBgyTfyKkwB7bW
-DcCnuoC9R79hkI8nWkoRVou9tdzKxo0bGR6O4CfLj+4d3hpWkv9Rw7Xxygo5JOqN
-4cNZGtHkmIFFk9fSXul5rkjfF/XmThIwoI8aHSBZ7j3IMtmkKVkBjNjiTfbgW8RT
-XIIR+QQdVLOyJqq+NZC/SrKVQITg0ToYJutRTUJViqyz5b3psJo5o2SW6jcexQpE
-cX6tdPyGz3o0aywfJ9dcN6izleSV1gYmXmIoS0cQyezVqTUkT8C12zeRB7mtWsDa
-+AWJGq/WfB7N6pPh8S/XMW4e6ptuUodjiA==
-=HV8t
------END PGP PUBLIC KEY BLOCK-----
-```
diff --git a/README.problems.md b/README.problems.md
deleted file mode 100644 (file)
index dfd572a..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-Debugging problems
-==================
-
-Library is a component
-----------------------
-
-As a library, lws is always just a component in a bigger application.
-
-When users have a problem involving lws, what is happening in the bigger
-application is usually critical to understand what is going on (and where the
-solution lies).
-
-Many users are able to share their sources, but others decide not to, for
-presumed "commercial advantage" or whatever.  (In any event, it can be painful
-looking through large chunks of someone else's sources for problems when that
-is not the library author's responsibility.)
-
-This makes answering questions like "what is wrong with my code I am not
-going to show you?" or even "what is wrong with my code?" very difficult.
-
-Even if it's clear there is a problem somewhere, it cannot be understood or
-reproduced by anyone else if it needs user code that isn't provided.
-
-The biggest question is, "is this an lws problem actually"?
-
-
-Use the test apps as sanity checks
-----------------------------------
-
-The test server and client are extremely useful for sanity checks and debugging
-guidance.
-
- - test apps work on your platform, then either
-   - your user code is broken, align it to how the test apps work, or,
-   - something from your code is required to show an lws problem, provide a
-     minimal patch on a test app so it can be reproduced
-     
- - test apps break on your platform, but work on, eg, x86_64, either
-   - toolchain or platform-specific (eg, OS) issue, or
-   - lws platform support issue
-
- - test apps break everywhere
-   - sounds like lws problem, info to reproduce and / or a patch is appreciated
diff --git a/READMEs/README.build.md b/READMEs/README.build.md
new file mode 100644 (file)
index 0000000..17044f6
--- /dev/null
@@ -0,0 +1,732 @@
+Notes about building lws
+========================
+
+
+@section cm Introduction to CMake
+
+CMake is a multi-platform build tool that can generate build files for many
+different target platforms. See more info at http://www.cmake.org
+
+CMake also allows/recommends you to do "out of source"-builds, that is,
+the build files are separated from your sources, so there is no need to
+create elaborate clean scripts to get a clean source tree, instead you
+simply remove your build directory.
+
+Libwebsockets has been tested to build successfully on the following platforms
+with SSL support (for OpenSSL/wolfSSL/BoringSSL):
+
+- Windows (Visual Studio)
+- Windows (MinGW)
+- Linux (x86 and ARM)
+- OSX
+- NetBSD
+
+
+@section build1 Building the library and test apps
+
+The project settings used by CMake to generate the platform specific build
+files is called [CMakeLists.txt](../CMakeLists.txt). CMake then uses one of its "Generators" to
+output a Visual Studio project or Make file for instance. To see a list of
+the available generators for your platform, simply run the "cmake" command.
+
+Note that by default OpenSSL will be linked, if you don't want SSL support
+see below on how to toggle compile options.
+
+
+@section bu Building on Unix:
+
+1. Install CMake 2.8 or greater: http://cmake.org/cmake/resources/software.html
+   (Most Unix distributions comes with a packaged version also)
+
+2. Install OpenSSL.
+
+3. Generate the build files (default is Make files):
+```
+        $ cd /path/to/src
+        $ mkdir build
+        $ cd build
+        $ cmake ..
+```
+
+4. Finally you can build using the generated Makefile:
+```
+    $ make && sudo make install
+```
+**NOTE**: The `build/`` directory can have any name and be located anywhere
+ on your filesystem, and that the argument `..` given to cmake is simply
+ the source directory of **libwebsockets** containing the [CMakeLists.txt](../CMakeLists.txt)
+ project file. All examples in this file assumes you use ".."
+
+**NOTE2**:
+A common option you may want to give is to set the install path, same
+as --prefix= with autotools.  It defaults to /usr/local.
+You can do this by, eg
+```
+    $ cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .
+```
+
+**NOTE3**:
+On machines that want libraries in lib64, you can also add the
+following to the cmake line
+```
+    -DLIB_SUFFIX=64
+```
+
+**NOTE4**:
+If you are building against a non-distro OpenSSL (eg, in order to get
+access to ALPN support only in newer OpenSSL versions) the nice way to
+express that in one cmake command is eg,
+```
+    $ cmake .. -DOPENSSL_ROOT_DIR=/usr/local/ssl \
+         -DCMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE=/usr/local/ssl \
+         -DLWS_WITH_HTTP2=1
+```
+
+When you run the test apps using non-distro SSL, you have to force them
+to use your libs, not the distro ones
+```
+    $ LD_LIBRARY_PATH=/usr/local/ssl/lib libwebsockets-test-server --ssl
+```
+
+To get it to build on latest openssl (2016-04-10) it needed this approach
+```
+    cmake .. -DLWS_WITH_HTTP2=1 -DLWS_OPENSSL_INCLUDE_DIRS=/usr/local/include/openssl -DLWS_OPENSSL_LIBRARIES="/usr/local/lib64/libssl.so;/usr/local/lib64/libcrypto.so"
+```
+
+Mac users have reported
+
+```
+ $ export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2k; cmake ..; make -j4
+```
+
+worked for them when using "homebrew" OpenSSL
+
+**NOTE5**:
+To build with debug info and _DEBUG for lower priority debug messages
+compiled in, use
+```
+    $ cmake .. -DCMAKE_BUILD_TYPE=DEBUG
+```
+
+**NOTE6**
+To build on Solaris the linker needs to be informed to use lib socket
+and libnsl, and only builds in 64bit mode.
+
+```bash
+    $ cmake .. -DCMAKE_C_FLAGS=-m64 -DCMAKE_EXE_LINKER_FLAGS="-lsocket -lnsl"
+```
+
+**NOTE7**
+
+Build and test flow against boringssl.  Notice `LWS_WITH_GENHASH` is currently
+unavailable with boringssl due to their removing the necessary apis.
+
+Build current HEAD boringssl
+
+```
+ $ cd /projects
+ $ git clone https://boringssl.googlesource.com/boringssl
+ $ cd boringssl
+ $ mkdir build
+ $ cd build
+ $ cmake ..  -DBUILD_SHARED_LIBS=1
+ $ make -j8
+```
+
+Build and test lws against it
+
+```
+ $ cd /projects/libwebsockets/build
+ $ cmake .. -DOPENSSL_LIBRARIES="/projects/boringssl/build/ssl/libssl.so;\
+   /projects/boringssl/build/crypto/libcrypto.so" \
+   -DOPENSSL_INCLUDE_DIRS=/projects/boringssl/include \
+   -DLWS_WITH_BORINGSSL=1 -DCMAKE_BUILD_TYPE=DEBUG
+ $ make -j8 && sudo make install
+ $ LD_PRELOAD="/projects/boringssl/build/ssl/libssl.so \
+   /projects/boringssl/build/crypto/libcrypto.so" \
+   /usr/local/bin/libwebsockets-test-server -s
+```
+
+4. Finally you can build using the generated Makefile:
+
+```bash
+    $ make
+ ```
+
+@section lcap Linux Capabilities
+
+On Linux, lws now lets you retain selected root capabilities when dropping
+privileges.  If libcap-dev or similar package is installed providing
+sys/capabilities.h, and libcap or similar package is installed providing
+libcap.so, CMake will enable the capability features.
+
+The context creation info struct .caps[] and .count_caps members can then
+be set by user code to enable selected root capabilities to survive the
+transition to running under an unprivileged user.
+
+@section cmq Quirk of cmake
+
+When changing cmake options, for some reason the only way to get it to see the
+changes sometimes is delete the contents of your build directory and do the
+cmake from scratch.
+
+deleting build/CMakeCache.txt may be enough.
+
+
+@section cmw Building on Windows (Visual Studio)
+
+1. Install CMake 2.6 or greater: http://cmake.org/cmake/resources/software.html
+
+2. Install OpenSSL binaries. https://wiki.openssl.org/index.php/Binaries
+
+   (**NOTE**: Preferably in the default location to make it easier for CMake to find them)
+
+   **NOTE2**: 
+   Be sure that OPENSSL_CONF environment variable is defined and points at 
+   <OpenSSL install location>\bin\openssl.cfg
+
+3. Generate the Visual studio project by opening the Visual Studio cmd prompt:
+
+```
+    cd <path to src>
+    md build
+    cd build
+    cmake -G "Visual Studio 10" ..
+```
+
+   (**NOTE**: There is also a cmake-gui available on Windows if you prefer that)
+   
+   **NOTE2**:
+   See this link to find out the version number corresponding to your Visual Studio edition:
+   http://superuser.com/a/194065
+
+4. Now you should have a generated Visual Studio Solution in  your
+   `<path to src>/build` directory, which can be used to build.
+
+5. Some additional deps may be needed
+
+ - iphlpapi.lib
+ - psapi.lib
+ - userenv.lib
+
+6. If you're using libuv, you must make sure to compile libuv with the same multithread-dll / Mtd attributes as libwebsockets itself
+
+
+@section cmwmgw Building on Windows (MinGW)
+
+1. Install MinGW
+
+    For Fedora, it's, eg, `dnf install mingw64-gcc`
+
+2. Install current CMake package
+
+    For Fedora, it's `dnf install cmake`
+
+3. Instal mingw-built OpenSSL pieces
+
+    For Fedora, it's `mingw64-openssl.noarch mingw64-openssl-static.noarch`
+
+    mingw64-cmake as described below will auto-find the libs and includes
+    for build.  But to execute the apps, they either need to go into the same
+    `/usr/x86_64-w64-mingw32/sys-root/mingw/bin/` as the dlls are installed to,
+    or the dlls have to be copied into the same dir as your app executable.
+
+4. Generate the build files (default is Make files) using MSYS shell.
+
+   For Fedora, they provide a `mingw64-cmake` wrapper in the package
+   `mingw64-filesystem`, with this you can run that instead of cmake directly
+   and don't have to get involved with setting the cmake generator.
+
+   Otherwise doing it by hand is like this:
+
+```
+    $ cd /drive/path/to/src
+    $ mkdir build
+    $ cd build
+    $ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW ..
+```
+
+   To generate build files allowing to create libwebsockets binaries with debug information
+   set the CMAKE_BUILD_TYPE flag to DEBUG:
+```
+    $ cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/MinGW -DCMAKE_BUILD_TYPE=DEBUG ..
+```
+5. Finally you can build using the generated Makefile and get the results deployed into your MinGW installation:
+
+```
+    $ make && make install
+```
+
+@section distro Selecting CMake options useful for distros
+
+Distro packagers should select the CMake option "LWS_WITH_DISTRO_RECOMMENDED",
+which selects common additional options like support for various event libraries,
+plugins and lwsws.
+
+@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.
+
+NOTE: one major difference with mbedTLS is it does not load the system trust
+store by default.  That has advantages and disadvantages, but the disadvantage
+is you must provide the CA cert to lws built against mbedTLS for it to be able
+to validate it, ie, use -A with the test client.  The minimal test clients
+have the CA cert for warmcat.com and libwebsockets.org and use it if they see
+they were built with mbedTLS.
+
+@section optee Building for OP-TEE
+
+OP-TEE is a "Secure World" Trusted Execution Environment.
+
+Although lws is only part of the necessary picture to have an https-enabled
+TA, it does support OP-TEE as a platform and if you provide the other
+pieces, does work very well.
+
+Select it in cmake with `-DLWS_PLAT_OPTEE=1`
+
+
+@section cmco Setting compile options
+
+To set compile time flags you can either use one of the CMake gui applications
+or do it via the command line.
+
+@subsection cmcocl Command line
+
+To list available options (omit the H if you don't want the help text):
+
+    cmake -LH ..
+
+Then to set an option and build (for example turn off SSL support):
+
+    cmake -DLWS_WITH_SSL=0 ..
+or
+    cmake -DLWS_WITH_SSL:BOOL=OFF ..
+
+@subsection cmcoug Unix GUI
+
+If you have a curses-enabled build you simply type:
+(not all packages include this, my debian install does not for example).
+
+    ccmake
+
+@subsection cmcowg Windows GUI
+
+On windows CMake comes with a gui application:
+    Start -> Programs -> CMake -> CMake (cmake-gui)
+
+
+@section wolf wolfSSL/CyaSSL replacement for OpenSSL
+
+wolfSSL/CyaSSL is a lightweight SSL library targeted at embedded systems:
+https://www.wolfssl.com/wolfSSL/Products-wolfssl.html
+
+It contains a OpenSSL compatibility layer which makes it possible to pretty
+much link to it instead of OpenSSL, giving a much smaller footprint.
+
+**NOTE**: wolfssl needs to be compiled using the `--enable-opensslextra` flag for
+this to work.
+
+@section wolf1 Compiling libwebsockets with wolfSSL
+
+```
+    cmake .. -DLWS_WITH_WOLFSSL=1 \
+         -DLWS_WOLFSSL_INCLUDE_DIRS=/path/to/wolfssl \
+         -DLWS_WOLFSSL_LIBRARIES=/path/to/wolfssl/wolfssl.a ..
+```
+
+**NOTE**: On windows use the .lib file extension for `LWS_WOLFSSL_LIBRARIES` instead.
+
+@section cya Compiling libwebsockets with CyaSSL
+
+```
+    cmake .. -DLWS_WITH_CYASSL=1 \
+         -DLWS_CYASSL_INCLUDE_DIRS=/path/to/cyassl \
+         -DLWS_CYASSL_LIBRARIES=/path/to/wolfssl/cyassl.a ..
+```
+
+**NOTE**: On windows use the .lib file extension for `LWS_CYASSL_LIBRARIES` instead.
+
+@section gzip Selecting GZIP or MINIZ
+
+By default lws supports gzip when compression is needed.  But you can tell it to use
+MINIZ instead by using `-DLWS_WITH_MINIZ=1`.
+
+For native build cmake will try to find an existing libminiz.so or .a and build
+against that and the found includes automatically.
+
+For cross-build or building against local miniz, you need the following kind of
+cmake to tell it where to get miniz
+
+```
+cmake .. -DLWS_WITH_MINIZ=1 -DLWS_WITH_ZIP_FOPS=1 -DMINIZ_INCLUDE_DIRS="/projects/miniz;/projects/miniz/build" -DMINIZ_LIBRARIES=/projects/miniz/build/libminiz.so.2.1.0  
+```
+
+@section esp32 Building for ESP32
+
+Building for ESP32 requires the ESP-IDF framework. It can be built under Linux, OSX or Windows (MSYS2).
+
+1. Install ESP-IDF, follow the getting started guide here - http://esp-idf.readthedocs.io/en/latest/get-started/
+2. Set ESP-IDF to last known working version (assuming ESP-IDF is in `~/esp/esp-idf`) :
+```
+    cd ~/esp/esp-idf
+    git checkout 0c50b65a34cd6b3954f7435193411a88adb49cb0
+    git submodule update --recursive
+```
+3. Add `libwebsockets` as a submodule in the `components` folder of your ESP-IDF project:
+```
+    git submodule add https://github.com/warmcat/libwebsockets.git components/libwebsockets
+```
+4. If on Windows (MSYS2) you will need to install CMake in the MSYS2 environment:
+```
+    pacman -S mingw-w64-i686-cmake
+```
+If you're on Linux or OSX ensure CMake version is at least 3.7.
+
+@section extplugins Building plugins outside of lws itself
+
+The directory ./plugin-standalone/ shows how easy it is to create plugins
+outside of lws itself.  First build lws itself with -DLWS_WITH_PLUGINS,
+then use the same flow to build the standalone plugin
+```
+    cd ./plugin-standalone
+    mkdir build
+    cd build
+    cmake ..
+    make && sudo make install
+```
+
+if you changed the default plugin directory when you built lws, you must
+also give the same arguments to cmake here (eg,
+` -DCMAKE_INSTALL_PREFIX:PATH=/usr/something/else...` )
+
+Otherwise if you run lwsws or libwebsockets-test-server-v2.0, it will now
+find the additional plugin "libprotocol_example_standalone.so"
+```
+    lwsts[21257]:   Plugins:
+    lwsts[21257]:    libprotocol_dumb_increment.so
+    lwsts[21257]:    libprotocol_example_standalone.so
+    lwsts[21257]:    libprotocol_lws_mirror.so
+    lwsts[21257]:    libprotocol_lws_server_status.so
+    lwsts[21257]:    libprotocol_lws_status.so
+```
+If you have multiple vhosts, you must enable plugins at the vhost
+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 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.  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
+    lwsts[4752]:  HTTP2 / ALPN enabled
+```
+at lws startup.
+
+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
+```
+
+```
+145 tests, 145 passed, 0 skipped, 0 failed
+
+```
+
+@section coverage Automated Coverage Testing
+
+./test-apps/attack.sh contains scripted tests that are the basis
+of the automated test coverage assessment available for gcc and clang.
+
+To reproduce
+
+ $ cd build
+ $ cmake .. -DLWS_WITH_GCOV=1 -DCMAKE_BUILD_TYPE=DEBUG
+ $ ../scripts/build-gcov.sh
+ $ ../test-apps/attack.sh
+ $ ../scripts/gcov.sh
+...
+Lines executed:51.24% of 8279
+
+@section windowsprebuilt Using Windows binary builds on Appveyor
+
+The CI builds on Appveyor now produce usable binary outputs.  Visit
+
+[lws on Appveyor](https://ci.appveyor.com/project/lws-team/libwebsockets)
+
+and select one of the builds, then click on ARTIFACTS at the top right.  The zip file
+want to be unpacked into `C:\Program Files (x86)/libwebsockets`, after that, you should be able to run the test server, by running it from `bin/Release/libwebsockets-test-server.exe` and opening a browser on http://127.0.0.1:7681
+
+@section cross Cross compiling
+
+To enable cross-compiling **libwebsockets** using CMake you need to create
+a "Toolchain file" that you supply to CMake when generating your build files.
+CMake will then use the cross compilers and build paths specified in this file
+to look for dependencies and such.
+
+**Libwebsockets** includes an example toolchain file [cross-arm-linux-gnueabihf.cmake](../contrib/cross-arm-linux-gnueabihf.cmake)
+you can use as a starting point.
+
+The commandline to configure for cross with this would look like
+```
+    $ cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr/lib/my-cross-root \
+         -DCMAKE_TOOLCHAIN_FILE=../contrib/cross-arm-linux-gnueabihf.cmake \
+         -DLWS_WITHOUT_EXTENSIONS=1 -DLWS_WITH_SSL=0 \
+         -DLWS_WITH_ZIP_FOPS=0 -DLWS_WITH_ZLIB=0
+```
+The example shows how to build with no external cross lib dependencies, you
+need to provide the cross libraries otherwise.
+
+**NOTE**: start from an EMPTY build directory if you had a non-cross build in there
+    before the settings will be cached and your changes ignored.
+    Delete `build/CMakeCache.txt` at least before trying a new cmake config
+    to ensure you are really building the options you think you are.
+
+Additional information on cross compilation with CMake:
+    http://www.vtk.org/Wiki/CMake_Cross_Compiling
+
+@section cross_example Complex Cross compiling example
+
+Here are step by step instructions for cross-building the external projects needed for lws with lwsws + mbedtls as an example.
+
+In the example, my toolchain lives in `/projects/aist-tb/arm-tc` and is named `arm-linux-gnueabihf`.  So you will need to adapt those to where your toolchain lives and its name where you see them here.
+
+Likewise I do all this in /tmp but it has no special meaning, you can adapt that to somewhere else.
+
+All "foreign" cross-built binaries are sent into `/tmp/cross` so they cannot be confused for 'native' x86_64 stuff on your host machine in /usr/[local/]....
+
+## Prepare the cmake toolchain file
+
+1) `cd /tmp`
+
+2) `wget -O mytoolchainfile https://raw.githubusercontent.com/warmcat/libwebsockets/master/contrib/cross-arm-linux-gnueabihf.cmake` 
+
+3) Edit `/tmp/mytoolchainfile` adapting `CROSS_PATH`, `CMAKE_C_COMPILER` and `CMAKE_CXX_COMPILER` to reflect your toolchain install dir and path to your toolchain C and C++ compilers respectively.  For my case:
+
+```
+set(CROSS_PATH /projects/aist-tb/arm-tc/)
+set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/arm-linux-gnueabihf-gcc")
+set(CMAKE_CXX_COMPILER "${CROSS_PATH}/bin/arm-linux-gnueabihf-g++")
+```
+
+## 1/4: Building libuv cross:
+
+1) `export PATH=/projects/aist-tb/arm-tc/bin:$PATH`  Notice there is a **/bin** on the end of the toolchain path
+
+2) `cd /tmp ; mkdir cross` we will put the cross-built libs in /tmp/cross
+
+3) `git clone https://github.com/libuv/libuv.git` get libuv
+
+4) `cd libuv`
+
+5) `./autogen.sh`
+
+```
++ libtoolize --copy
+libtoolize: putting auxiliary files in '.'.
+libtoolize: copying file './ltmain.sh'
+libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
+libtoolize: copying file 'm4/libtool.m4'
+libtoolize: copying file 'm4/ltoptions.m4'
+libtoolize: copying file 'm4/ltsugar.m4'
+libtoolize: copying file 'm4/ltversion.m4'
+libtoolize: copying file 'm4/lt~obsolete.m4'
++ aclocal -I m4
++ autoconf
++ automake --add-missing --copy
+configure.ac:38: installing './ar-lib'
+configure.ac:25: installing './compile'
+configure.ac:22: installing './config.guess'
+configure.ac:22: installing './config.sub'
+configure.ac:21: installing './install-sh'
+configure.ac:21: installing './missing'
+Makefile.am: installing './depcomp'
+```
+If it has problems, you will need to install `automake`, `libtool` etc.
+
+6) `./configure  --host=arm-linux-gnueabihf --prefix=/tmp/cross`
+
+7) `make && make install` this will install to `/tmp/cross/...`
+
+8) `file /tmp/cross/lib/libuv.so.1.0.0`  Check it's really built for ARM
+```
+/tmp/cross/lib/libuv.so.1.0.0: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=cdde0bc945e51db6001a9485349c035baaec2b46, with debug_info, not stripped
+```
+
+## 2/4: Building zlib cross
+
+1) `cd /tmp`
+
+2) `git clone https://github.com/madler/zlib.git`
+
+3) `CC=arm-linux-gnueabihf-gcc ./configure --prefix=/tmp/cross`
+```
+Checking for shared library support...
+Building shared library libz.so.1.2.11 with arm-linux-gnueabihf-gcc.
+Checking for size_t... Yes.
+Checking for off64_t... Yes.
+Checking for fseeko... Yes.
+Checking for strerror... Yes.
+Checking for unistd.h... Yes.
+Checking for stdarg.h... Yes.
+Checking whether to use vs[n]printf() or s[n]printf()... using vs[n]printf().
+Checking for vsnprintf() in stdio.h... Yes.
+Checking for return value of vsnprintf()... Yes.
+Checking for attribute(visibility) support... Yes.
+```
+
+4)  `make && make install`
+```
+arm-linux-gnueabihf-gcc -O3 -D_LARGEFILE64_SOURCE=1 -DHAVE_HIDDEN -I. -c -o example.o test/example.c
+...
+rm -f /tmp/cross/include/zlib.h /tmp/cross/include/zconf.h
+cp zlib.h zconf.h /tmp/cross/include
+chmod 644 /tmp/cross/include/zlib.h /tmp/cross/include/zconf.h
+```
+
+5) `file /tmp/cross/lib/libz.so.1.2.11`  This is just to confirm we built an ARM lib as expected
+```
+/tmp/cross/lib/libz.so.1.2.11: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=6f8ffef84389b1417d2fd1da1bd0c90f748f300d, with debug_info, not stripped
+```
+
+## 3/4: Building mbedtls cross
+
+1) `cd /tmp`
+
+2) `git clone https://github.com/ARMmbed/mbedtls.git`
+
+3) `cd mbedtls ; mkdir build ; cd build`
+
+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`
+
+5) `file /tmp/cross/lib/libmbedcrypto.so.2.6.0`
+```
+/tmp/cross/lib/libmbedcrypto.so.2.6.0: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=bcca195e78bd4fd2fb37f36ab7d72d477d609d87, with debug_info, not stripped
+```
+
+## 4/4: Building libwebsockets with everything
+
+1) `cd /tmp`
+
+2) `git clone ssh://git@github.com/warmcat/libwebsockets`
+
+3) `cd libwebsockets ; mkdir build ; cd build`
+
+4)  (this is all one line on the commandline)
+```
+cmake .. -DCMAKE_TOOLCHAIN_FILE=/tmp/mytoolchainfile \
+-DCMAKE_INSTALL_PREFIX:PATH=/tmp/cross \
+-DLWS_WITH_LWSWS=1 \
+-DLWS_WITH_MBEDTLS=1 \
+-DLWS_MBEDTLS_LIBRARIES="/tmp/cross/lib/libmbedcrypto.so;/tmp/cross/lib/libmbedtls.so;/tmp/cross/lib/libmbedx509.so" \
+-DLWS_MBEDTLS_INCLUDE_DIRS=/tmp/cross/include \
+-DLWS_LIBUV_LIBRARIES=/tmp/cross/lib/libuv.so \
+-DLWS_LIBUV_INCLUDE_DIRS=/tmp/cross/include \
+-DLWS_ZLIB_LIBRARIES=/tmp/cross/lib/libz.so \
+-DLWS_ZLIB_INCLUDE_DIRS=/tmp/cross/include
+```
+
+3) `make && make install`
+
+4) `file /tmp/cross/lib/libwebsockets.so.11`
+```
+/tmp/cross/lib/libwebsockets.so.11: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=81e59c6534f8e9629a9fc9065c6e955ce96ca690, with debug_info, not stripped
+```
+
+5) `arm-linux-gnueabihf-objdump -p /tmp/cross/lib/libwebsockets.so.11 | grep NEEDED`  Confirm that the lws library was linked against everything we expect (libm / libc are provided by your toolchain)
+```
+  NEEDED               libz.so.1
+  NEEDED               libmbedcrypto.so.0
+  NEEDED               libmbedtls.so.10
+  NEEDED               libmbedx509.so.0
+  NEEDED               libuv.so.1
+  NEEDED               libm.so.6
+  NEEDED               libc.so.6
+```
+
+You will also find the lws test apps in `/tmp/cross/bin`... to run lws on the target you will need to copy the related things from /tmp/cross... all the .so from /tmp/cross/lib and anything from /tmp/cross/bin you want.
+
+@section mem Memory efficiency
+
+Embedded server-only configuration without extensions (ie, no compression
+on websocket connections), but with full v13 websocket features and http
+server, built on ARM Cortex-A9:
+
+Update at 8dac94d (2013-02-18)
+```
+    $ ./configure --without-client --without-extensions --disable-debug --without-daemonize
+
+    Context Creation, 1024 fd limit[2]:   16720 (includes 12 bytes per fd)
+    Per-connection [3]:                      72 bytes, +1328 during headers
+
+    .text      .rodata .data   .bss
+    11512      2784    288     4
+```
+This shows the impact of the major configuration with/without options at
+13ba5bbc633ea962d46d using Ubuntu ARM on a PandaBoard ES.
+
+These are accounting for static allocations from the library elf, there are
+additional dynamic allocations via malloc.  These are a bit old now but give
+the right idea for relative "expense" of features.
+
+Static allocations, ARM9
+
+|                                | .text   | .rodata | .data | .bss |
+|--------------------------------|---------|---------|-------|------|
+| All (no without)               | 35024   | 9940    | 336   | 4104 |
+| without client                 | 25684   | 7144    | 336   | 4104 |
+| without client, exts           | 21652   | 6288    | 288   | 4104 |
+| without client, exts, debug[1] | 19756   | 3768    | 288   | 4104 |
+| without server                 | 30304   | 8160    | 336   | 4104 |
+| without server, exts           | 25382   | 7204    | 288   | 4104 |
+| without server, exts, debug[1] | 23712   | 4256    | 288   | 4104 |
+
+[1] `--disable-debug` only removes messages below `lwsl_notice`.  Since that is
+the default logging level the impact is not noticeable, error, warn and notice
+logs are all still there.
+
+[2] `1024` fd per process is the default limit (set by ulimit) in at least Fedora
+and Ubuntu.  You can make significant savings tailoring this to actual expected
+peak fds, ie, at a limit of `20`, context creation allocation reduces to `4432 +
+240 = 4672`)
+
+[3] known header content is freed after connection establishment
diff --git a/READMEs/README.ci.md b/READMEs/README.ci.md
new file mode 100644 (file)
index 0000000..ed5d55a
--- /dev/null
@@ -0,0 +1,29 @@
+## Need for CI
+
+Generally if we're adding something that's supposed to work ongoing, the stuff
+should be exercised in CI (at least Travis).
+
+If there are few users for a particular feature, experience has shown that
+refactors or other upheaval can easily break it into a state of uselessness
+without anyone noticing until later.
+
+Therefore here's a description of how to add something to the CI tests... this
+is certainly a nonproductive PITA and I have never been thanked for the work
+involved.  But if the promise of the various features working is going to
+remain alive, it's necessary to include CI test where possible with new
+nontrivial code.
+
+## Integration points
+
+### cmake
+
+`.travis.yml` maps the various test activities to CMake options needed.
+
+### including dependent packages into travis
+
+See `./scripts/travis_install.sh`
+
+### performing prepared test actions
+
+See `./scripts/travis_control.sh`
+
similarity index 56%
rename from README.coding.md
rename to READMEs/README.coding.md
index c1c4a3f..c66bc48 100644 (file)
@@ -1,6 +1,104 @@
 Notes about coding with lws
 ===========================
 
+@section era Old lws and lws v2.0
+
+Originally lws only supported the "manual" method of handling everything in the
+user callback found in test-server.c / test-server-http.c.
+
+Since v2.0, the need for most or all of this manual boilerplate has been
+eliminated: the protocols[0] http stuff is provided by a generic lib export
+`lws_callback_http_dummy()`.  You can serve parts of your filesystem at part of
+the URL space using mounts, the dummy http callback will do the right thing.
+
+It's much preferred to use the "automated" v2.0 type scheme, because it's less
+code and it's easier to support.
+
+The minimal examples all use the modern, recommended way.
+
+If you just need generic serving capability, without the need to integrate lws
+to some other app, consider not writing any server code at all, and instead use
+the generic server `lwsws`, and writing your special user code in a standalone
+"plugin".  The server is configured for mounts etc using JSON, see
+./READMEs/README.lwsws.md.
+
+Although the "plugins" are dynamically loaded if you use lwsws or lws built
+with libuv, actually they may perfectly well be statically included if that
+suits your situation better, eg, ESP32 test server, where the platform does
+not support processes or dynamic loading, just #includes the plugins
+one after the other and gets the same benefit from the same code.
+
+Isolating and collating the protocol code in one place also makes it very easy
+to maintain and understand.
+
+So it if highly recommended you put your protocol-specific code into the
+form of a "plugin" at the source level, even if you have no immediate plan to
+use it dynamically-loaded.
+
+@section writeable Only send data when socket writeable
+
+You should only send data on a websocket connection from the user callback
+`LWS_CALLBACK_SERVER_WRITEABLE` (or `LWS_CALLBACK_CLIENT_WRITEABLE` for
+clients).
+
+If you want to send something, do NOT just send it but request a callback
+when the socket is writeable using
+
+ - `lws_callback_on_writable(wsi)` for a specific `wsi`, or
+ - `lws_callback_on_writable_all_protocol(protocol)` for all connections
+using that protocol to get a callback when next writeable.
+
+Usually you will get called back immediately next time around the service
+loop, but if your peer is slow or temporarily inactive the callback will be
+delayed accordingly.  Generating what to write and sending it should be done
+in the ...WRITEABLE callback.
+
+See the test server code for an example of how to do this.
+
+Otherwise evolved libs like libuv get this wrong, they will allow you to "send"
+anything you want but it only uses up your local memory (and costs you
+memcpys) until the socket can actually accept it.  It is much better to regulate
+your send action by the downstream peer readiness to take new data in the first
+place, avoiding all the wasted buffering.
+
+Libwebsockets' concept is that the downstream peer is truly the boss, if he,
+or our connection to him, cannot handle anything new, we should not generate
+anything new for him.  This is how unix shell piping works, you may have
+`cat a.txt | grep xyz > remote", but actually that does not cat anything from
+a.txt while remote cannot accept anything new. 
+
+@section oneper Only one lws_write per WRITEABLE callback
+
+From v2.5, lws strictly enforces only one lws_write() per WRITEABLE callback.
+
+You will receive a message about "Illegal back-to-back write of ... detected"
+if there is a second lws_write() before returning to the event loop.
+
+This is because with http/2, the state of the network connection carrying a
+wsi is unrelated to any state of the wsi.  The situation on http/1 where a
+new request implied a new tcp connection and new SSL buffer, so you could
+assume some window for writes is no longer true.  Any lws_write() can fail
+and be buffered for completion by lws; it will be auto-completed by the
+event loop.
+
+Note that if you are handling your own http responses, writing the headers
+needs to be done with a separate lws_write() from writing any payload.  That
+means after writing the headers you must call `lws_callback_on_writable(wsi)`
+and send any payload from the writable callback.
+
+@section otherwr Do not rely on only your own WRITEABLE requests appearing
+
+Libwebsockets may generate additional `LWS_CALLBACK_CLIENT_WRITEABLE` events
+if it met network conditions where it had to buffer your send data internally.
+
+So your code for `LWS_CALLBACK_CLIENT_WRITEABLE` needs to own the decision
+about what to send, it can't assume that just because the writeable callback
+came something is ready to send.
+
+It's quite possible you get an 'extra' writeable callback at any time and
+just need to `return 0` and wait for the expected callback later.
+
 @section dae Daemonization
 
 There's a helper api `lws_daemonize` built by default that does everything you
@@ -10,8 +108,7 @@ headless background process and exit the starting process.
 
 Notice stdout, stderr, stdin are all redirected to /dev/null to enforce your
 daemon is headless, so you'll need to sort out alternative logging, by, eg,
-syslog.
-
+syslog via `lws_set_log_level(..., lwsl_emit_syslog)`.
 
 @section conns Maximum number of connections
 
@@ -25,81 +122,153 @@ If you want to restrict that allocation, or increase it, you can use ulimit or
 similar to change the available number of file descriptors, and when restarted
 **libwebsockets** will adapt accordingly.
 
+@section peer_limits optional LWS_WITH_PEER_LIMITS
+
+If you select `LWS_WITH_PEER_LIMITS` at cmake, then lws will track peer IPs
+and monitor how many connections and ah resources they are trying to use
+at one time.  You can choose to limit these at context creation time, using
+`info.ip_limit_ah` and `info.ip_limit_wsi`.
+
+Note that although the ah limit is 'soft', ie, the connection will just wait
+until the IP is under the ah limit again before attaching a new ah, the
+wsi limit is 'hard', lws will drop any additional connections from the
+IP until it's under the limit again.
+
+If you use these limits, you should consider multiple clients may simultaneously
+try to access the site through NAT, etc.  So the limits should err on the side
+of being generous, while still making it impossible for one IP to exhaust
+all the server resources.
 
 @section evtloop Libwebsockets is singlethreaded
 
-Libwebsockets works in a serialized event loop, in a single thread.
+Libwebsockets works in a serialized event loop, in a single thread.  It supports
+the default poll() backend, and libuv, libev, and libevent event loop
+libraries that also take this locking-free, nonblocking event loop approach that
+is not threadsafe.  There are several advantages to this technique, but one
+disadvantage, it doesn't integrate easily if there are multiple threads that
+want to use libwebsockets.
 
-Directly performing websocket actions from other threads is not allowed.
-Aside from the internal data being inconsistent in `forked()` processes,
-the scope of a `wsi` (`struct websocket`) can end at any time during service
-with the socket closing and the `wsi` freed.
+However integration to multithreaded apps is possible if you follow some guidelines.
 
-Websocket write activities should only take place in the
-`LWS_CALLBACK_SERVER_WRITEABLE` callback as described below.
+1) Aside from two APIs, directly calling lws apis from other threads is not allowed.
 
-[This network-programming necessity to link the issue of new data to
-the peer taking the previous data is not obvious to all users so let's
-repeat that in other words:
+2) If you want to keep a list of live wsi, you need to use lifecycle callbacks on
+the protocol in the service thread to manage the list, with your own locking.
+Typically you use an ESTABLISHED callback to add ws wsi to your list and a CLOSED
+callback to remove them.
 
-***ONLY DO LWS_WRITE FROM THE WRITEABLE CALLBACK***
+3) LWS regulates your write activity by being able to let you know when you may
+write more on a connection.  That reflects the reality that you cannot succeed to
+send data to a peer that has no room for it, so you should not generate or buffer
+write data until you know the peer connection can take more.
 
-There is another network-programming truism that surprises some people which
-is if the sink for the data cannot accept more:
+Other libraries pretend that the guy doing the writing is the boss who decides
+what happens, and absorb as much as you want to write to local buffering.  That does
+not scale to a lot of connections, because it will exhaust your memory and waste
+time copying data around in memory needlessly.
 
-***YOU MUST PERFORM RX FLOW CONTROL***
+The truth is the receiver, along with the network between you, is the boss who
+decides what will happen.  If he stops accepting data, no data will move.  LWS is
+designed to reflect that.
 
-See the mirror protocol implementations for example code.
+If you have something to send, you call `lws_callback_on_writable()` on the
+connection, and when it is writeable, you will get a `LWS_CALLBACK_SERVER_WRITEABLE`
+callback, where you should generate the data to send and send it with `lws_write()`.
 
-Only live connections appear in the user callbacks, so this removes any
-possibility of trying to used closed and freed wsis.
+You cannot send data using `lws_write()` outside of the WRITEABLE callback.
 
-If you need to service other socket or file descriptors as well as the
-websocket ones, you can combine them together with the websocket ones
-in one poll loop, see "External Polling Loop support" below, and
-still do it all in one thread / process context.
+4) For multithreaded apps, this corresponds to a need to be able to provoke the
+`lws_callback_on_writable()` action and to wake the service thread from its event
+loop wait (sleeping in `poll()` or `epoll()` or whatever).  The rules above
+mean directly sending data on the connection from another thread is out of the
+question.
 
-If you insist on trying to use it from multiple threads, take special care if
-you might simultaneously create more than one context from different threads.
+Therefore the two apis mentioned above that may be used from another thread are
 
-SSL_library_init() is called from the context create api and it also is not
-reentrant.  So at least create the contexts sequentially.
+ - For LWS using the default poll() event loop, `lws_callback_on_writable()`
 
+ - For LWS using libuv/libev/libevent event loop, `lws_cancel_service()`
 
-@section writeable Only send data when socket writeable
+If you are using the default poll() event loop, one "foreign thread" at a time may
+call `lws_callback_on_writable()` directly for a wsi.  You need to use your own
+locking around that to serialize multiple thread access to it.
 
-You should only send data on a websocket connection from the user callback
-`LWS_CALLBACK_SERVER_WRITEABLE` (or `LWS_CALLBACK_CLIENT_WRITEABLE` for
-clients).
+If you implement LWS_CALLBACK_GET_THREAD_ID in protocols[0], then LWS will detect
+when it has been called from a foreign thread and automatically use
+`lws_cancel_service()` to additionally wake the service loop from its wait.
 
-If you want to send something, do not just send it but request a callback
-when the socket is writeable using
+For libuv/libev/libevent event loop, they cannot handle being called from other
+threads.  So there is a slightly different scheme, you may call `lws_cancel_service()` 
+to force the event loop to end immediately.  This then broadcasts a callback (in the
+service thread context) `LWS_CALLBACK_EVENT_WAIT_CANCELLED`, to all protocols on all
+vhosts, where you can perform your own locking and walk a list of wsi that need
+`lws_callback_on_writable()` calling on them.
 
- - `lws_callback_on_writable(context, wsi)` for a specific `wsi`, or
- - `lws_callback_on_writable_all_protocol(protocol)` for all connections
-using that protocol to get a callback when next writeable.
+`lws_cancel_service()` is very cheap to call.
 
-Usually you will get called back immediately next time around the service
-loop, but if your peer is slow or temporarily inactive the callback will be
-delayed accordingly.  Generating what to write and sending it should be done
-in the ...WRITEABLE callback.
+5) The obverse of this truism about the receiver being the boss is the case where
+we are receiving.  If we get into a situation we actually can't usefully
+receive any more, perhaps because we are passing the data on and the guy we want
+to send to can't receive any more, then we should "turn off RX" by using the
+RX flow control API, `lws_rx_flow_control(wsi, 0)`.  When something happens where we
+can accept more RX, (eg, we learn our onward connection is writeable) we can call
+it again to re-enable it on the incoming wsi.
 
-See the test server code for an example of how to do this.
+LWS stops calling back about RX immediately you use flow control to disable RX, it
+buffers the data internally if necessary.  So you will only see RX when you can
+handle it.  When flow control is disabled, LWS stops taking new data in... this makes
+the situation known to the sender by TCP "backpressure", the tx window fills and the
+sender finds he cannot write any more to the connection.
 
+See the mirror protocol implementations for example code.
 
-@section otherwr Do not rely on only your own WRITEABLE requests appearing
+If you need to service other socket or file descriptors as well as the
+websocket ones, you can combine them together with the websocket ones
+in one poll loop, see "External Polling Loop support" below, and
+still do it all in one thread / process context.  If the need is less
+architectural, you can also create RAW mode client and serving sockets; this
+is how the lws plugin for the ssh server works.
 
-Libwebsockets may generate additional `LWS_CALLBACK_CLIENT_WRITEABLE` events
-if it met network conditions where it had to buffer your send data internally.
+@section anonprot Working without a protocol name
 
-So your code for `LWS_CALLBACK_CLIENT_WRITEABLE` needs to own the decision
-about what to send, it can't assume that just because the writeable callback
-came it really is time to send something.
+Websockets allows connections to negotiate without a protocol name...
+in that case by default it will bind to the first protocol in your
+vhost protocols[] array.
 
-It's quite possible you get an 'extra' writeable callback at any time and
-just need to `return 0` and wait for the expected callback later.
+You can tell the vhost to use a different protocol by attaching a
+pvo (per-vhost option) to the 
+
+```
+/*
+ * this sets a per-vhost, per-protocol option name:value pair
+ * the effect is to set this protocol to be the default one for the vhost,
+ * ie, selected if no Protocol: header is sent with the ws upgrade.
+ */
+
+static const struct lws_protocol_vhost_options pvo_opt = {
+       NULL,
+       NULL,
+       "default",
+       "1"
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,
+       &pvo_opt,
+       "my-protocol",
+       ""
+};
+
+...
+
+       context_info.pvo = &pvo;
+...
 
+```
+
+Will select "my-protocol" from your protocol list (even if it came
+in by plugin) as being the target of client connections that don't
+specify a protocol.
 
 @section closing Closing connections from the user side
 
@@ -136,8 +305,7 @@ Clients with limited storage and RAM will find this useful; the memory needed
 for the inflate case is constrained so that only one input buffer at a time
 is ever in memory.
 
-To use this feature, ensure LWS_WITH_ZIP_FOPS is enabled at CMake (it is by
-default).
+To use this feature, ensure LWS_WITH_ZIP_FOPS is enabled at CMake.
 
 `libwebsockets-test-server-v2.0` includes a mount using this technology
 already, run that test server and navigate to http://localhost:7681/ziptest/candide.html
@@ -210,7 +378,19 @@ If you are not building with _DEBUG defined, ie, without this
 
 then log levels below notice do not actually get compiled in.
 
+@section asan Building with ASAN
+
+Under GCC you can select for the build to be instrumented with the Address
+Sanitizer, using `cmake .. -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_ASAN=1`.  LWS is routinely run during development with valgrind, but ASAN is capable of finding different issues at runtime, like operations which are not strictly defined in the C
+standard and depend on platform behaviours.
+
+Run your application like this
+
+```
+       $ sudo ASAN_OPTIONS=verbosity=2:halt_on_error=1  /usr/local/bin/lwsws
+```
 
+and attach gdb to catch the place it halts.
 
 @section extpoll External Polling Loop support
 
@@ -220,10 +400,9 @@ external polling array.  That's needed if **libwebsockets** will
 cooperate with an existing poll array maintained by another
 server.
 
-Four callbacks `LWS_CALLBACK_ADD_POLL_FD`, `LWS_CALLBACK_DEL_POLL_FD`,
-`LWS_CALLBACK_SET_MODE_POLL_FD` and `LWS_CALLBACK_CLEAR_MODE_POLL_FD`
-appear in the callback for protocol 0 and allow interface code to
-manage socket descriptors in other poll loops.
+Three callbacks `LWS_CALLBACK_ADD_POLL_FD`, `LWS_CALLBACK_DEL_POLL_FD`
+and `LWS_CALLBACK_CHANGE_MODE_POLL_FD` appear in the callback for protocol 0
+and allow interface code to manage socket descriptors in other poll loops.
 
 You can pass all pollfds that need service to `lws_service_fd()`, even
 if the socket or file does not belong to **libwebsockets** it is safe.
@@ -244,6 +423,26 @@ reflecting the real event:
  - use LWS_POLLHUP / LWS_POLLIN / LWS_POLLOUT from libwebsockets.h to avoid
    losing windows compatibility
 
+You also need to take care about "forced service" somehow... these are cases
+where the network event was consumed, incoming data was all read, for example,
+but the work arising from it was not completed.  There will not be any more
+network event to trigger the remaining work, Eg, we read compressed data, but
+we did not use up all the decompressed data before returning to the event loop
+because we had to write some of it.
+
+Lws provides an API to determine if anyone is waiting for forced service,
+`lws_service_adjust_timeout(context, 1, tsi)`, normally tsi is 0.  If it returns
+0, then at least one connection has pending work you can get done by calling
+`lws_service_tsi(context, -1, tsi)`, again normally tsi is 0.
+
+For eg, the default poll() event loop, or libuv/ev/event, lws does this
+checking for you and handles it automatically.  But in the external polling
+loop case, you must do it explicitly.  Handling it after every normal service
+triggered by the external poll fd should be enough, since the situations needing
+it are initially triggered by actual network events.
+
+An example of handling it is shown in the test-server code specific to
+external polling.
 
 @section cpp Using with in c++ apps
 
@@ -251,7 +450,7 @@ The library is ready for use by C++ apps.  You can get started quickly by
 copying the test server
 
 ```
-       $ cp test-server/test-server.c test.cpp
+       $ cp test-apps/test-server.c test.cpp
 ```
 
 and building it in C++ like this
@@ -278,6 +477,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
 
@@ -321,6 +589,20 @@ if left `NULL`, then the "DEFAULT" set of ciphers are all possible to select.
 You can also set it to `"ALL"` to allow everything (including insecure ciphers).
 
 
+@section sslcerts Passing your own cert information direct to SSL_CTX
+
+For most users it's enough to pass the SSL certificate and key information by
+giving filepaths to the info.ssl_cert_filepath and info.ssl_private_key_filepath
+members when creating the vhost.
+
+If you want to control that from your own code instead, you can do so by leaving
+the related info members NULL, and setting the info.options flag
+LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX at vhost creation time.  That will create
+the vhost SSL_CTX without any certificate, and allow you to use the callback
+LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS to add your certificate to
+the SSL_CTX directly.  The vhost SSL_CTX * is in the user parameter in that
+callback.
+
 @section clientasync Async nature of client connections
 
 When you call `lws_client_connect_info(..)` and get a `wsi` back, it does not
@@ -337,7 +619,7 @@ other reasons, if any of that happens you'll get a
 After attempting the connection and getting back a non-`NULL` `wsi` you should
 loop calling `lws_service()` until one of the above callbacks occurs.
 
-As usual, see [test-client.c](test-server/test-client.c) for example code.
+As usual, see [test-client.c](../test-apps/test-client.c) for example code.
 
 Notice that the client connection api tries to progress the connection
 somewhat before returning.  That means it's possible to get callbacks like
@@ -472,7 +754,9 @@ callbacks on the named protocol
 
 starting with LWS_CALLBACK_RAW_ADOPT_FILE.
 
-`protocol-lws-raw-test` plugin provides a method for testing this with
+The minimal example `raw/minimal-raw-file` demonstrates how to use it.
+
+`protocol-lws-raw-test` plugin also provides a method for testing this with
 `libwebsockets-test-server-v2.0`:
 
 The plugin creates a FIFO on your system called "/tmp/lws-test-raw"
@@ -493,7 +777,8 @@ HTTP[s] and WS[s].  If the first bytes written on the connection are not a
 valid HTTP method, then the connection switches to RAW mode.
 
 This is disabled by default, you enable it by setting the `.options` flag
-LWS_SERVER_OPTION_FALLBACK_TO_RAW when creating the vhost.
+LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG, and setting
+`.listen_accept_role` to `"raw-skt"` when creating the vhost.
 
 RAW mode socket connections receive the following callbacks
 
@@ -505,16 +790,8 @@ RAW mode socket connections receive the following callbacks
 ```
 
 You can control which protocol on your vhost handles these RAW mode
-incoming connections by marking the selected protocol with a pvo `raw`, eg
-
-```
-        "protocol-lws-raw-test": {
-                 "status": "ok",
-                 "raw": "1"
-        },
-```
-
-The "raw" pvo marks this protocol as being used for RAW connections.
+incoming connections by setting the vhost info struct's `.listen_accept_protocol`
+to the vhost protocol name to use.
 
 `protocol-lws-raw-test` plugin provides a method for testing this with
 `libwebsockets-test-server-v2.0`:
@@ -556,6 +833,46 @@ and in another window, connect to it using the test client
 The connection should succeed, and text typed in the netcat window (including a CRLF)
 will be received in the client.
 
+@section rawudp RAW UDP socket integration
+
+Lws provides an api to create, optionally bind, and adopt a RAW UDP
+socket (RAW here means an uninterpreted normal UDP socket, not a
+"raw socket").
+
+```
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags,
+                    const char *protocol_name, struct lws *parent_wsi);
+```
+
+`flags` should be `LWS_CAUDP_BIND` if the socket will receive packets.
+
+The callbacks `LWS_CALLBACK_RAW_ADOPT`, `LWS_CALLBACK_RAW_CLOSE`,
+`LWS_CALLBACK_RAW_RX` and `LWS_CALLBACK_RAW_WRITEABLE` apply to the
+wsi.  But UDP is different than TCP in some fundamental ways.
+
+For receiving on a UDP connection, data becomes available at
+`LWS_CALLBACK_RAW_RX` as usual, but because there is no specific
+connection with UDP, it is necessary to also get the source address of
+the data separately, using `struct lws_udp * lws_get_udp(wsi)`.
+You should take a copy of the `struct lws_udp` itself (not the
+pointer) and save it for when you want to write back to that peer.
+
+Writing is also a bit different for UDP.  By default, the system has no
+idea about the receiver state and so asking for a `callback_on_writable()`
+always believes that the socket is writeable... the callback will
+happen next time around the event loop.
+
+With UDP, there is no single "connection".  You need to write with sendto() and
+direct the packets to a specific destination.  To return packets to a
+peer who sent something earlier and you copied his `struct lws_udp`, you
+use the .sa and .salen members as the last two parameters of the sendto().
+
+The kernel may not accept to buffer / write everything you wanted to send.
+So you are responsible to watch the result of sendto() and resend the
+unsent part next time (which may involve adding new protocol headers to
+the remainder depending on what you are doing).
+
 @section ecdh ECDH Support
 
 ECDH Certs are now supported.  Enable the CMake option
@@ -598,7 +915,7 @@ Returning nonzero from the callback will close the wsi.
 
 SMP support is integrated into LWS without any internal threading.  It's
 very simple to use, libwebsockets-test-server-pthread shows how to do it,
-use -j <n> argument there to control the number of service threads up to 32.
+use -j n argument there to control the number of service threads up to 32.
 
 Two new members are added to the info struct
 
@@ -628,35 +945,74 @@ You can set fd_limit_per_thread to a nonzero number to control this manually, eg
 the overall supported fd limit is less than the process allowance.
 
 You can control the context basic data allocation for multithreading from Cmake
-using -DLWS_MAX_SMP=, if not given it's set to 32.  The serv_buf allocation
+using -DLWS_MAX_SMP=, if not given it's set to 1.  The serv_buf allocation
 for the threads (currently 4096) is made at runtime only for active threads.
 
 Because lws will limit the requested number of actual threads supported
 according to LWS_MAX_SMP, there is an api lws_get_count_threads(context) to
 discover how many threads were actually allowed when the context was created.
 
-It's required to implement locking in the user code in the same way that
-libwebsockets-test-server-pthread does it, for the FD locking callbacks.
+See the test-server-pthreads.c sample for how to use.
+
+@section smplocking SMP Locking Helpers
+
+Lws provide a set of pthread mutex helpers that reduce to no code or
+variable footprint in the case that LWS_MAX_SMP == 1.
+
+Define your user mutex like this
+
+```
+       lws_pthread_mutex(name);
+```
+
+If LWS_MAX_SMP > 1, this produces `pthread_mutex_t name;`.  In the case
+LWS_MAX_SMP == 1, it produces nothing.
+
+Likewise these helpers for init, destroy, lock and unlock
+
 
-There is no knowledge or dependency in lws itself about pthreads.  How the
-locking is implemented is entirely up to the user code.
+```
+       void lws_pthread_mutex_init(pthread_mutex_t *lock)
+       void lws_pthread_mutex_destroy(pthread_mutex_t *lock)
+       void lws_pthread_mutex_lock(pthread_mutex_t *lock)
+       void lws_pthread_mutex_unlock(pthread_mutex_t *lock)
+```
+
+resolve to nothing if LWS_MAX_SMP == 1, otherwise produce the equivalent
+pthread api.
 
+pthreads is required in lws only if LWS_MAX_SMP > 1.
 
-@section libevuv Libev / Libuv support
+
+@section libevuv libev / libuv / libevent support
 
 You can select either or both
 
        -DLWS_WITH_LIBEV=1
        -DLWS_WITH_LIBUV=1
+       -DLWS_WITH_LIBEVENT=1
 
 at cmake configure-time.  The user application may use one of the
 context init options flags
 
        LWS_SERVER_OPTION_LIBEV
        LWS_SERVER_OPTION_LIBUV
+       LWS_SERVER_OPTION_LIBEVENT
+
+to indicate it will use one of the event libraries at runtime.
 
-to indicate it will use either of the event libraries.
+libev has some problems, its headers conflict with libevent, they both define
+critical constants like EV_READ to different values.  Attempts
+to discuss clearing that up with libevent and libev did not get anywhere useful.
 
+In addition building anything with libev using gcc spews warnings, the
+maintainer is aware of this for many years, and blames gcc.  We worked
+around this by disabling -Werror on the parts of lws that use libev.
+
+For these reasons and the response I got trying to raise these issues with
+them, if you have a choice about event loop, I would gently encourage you
+to avoid libev.  Where lws uses an event loop itself, eg in lwsws, we use
+libuv.
 
 @section extopts Extension option control from user code
 
@@ -728,7 +1084,41 @@ prepare the client SSL context for the vhost after creating the vhost, since
 this is not normally done if the vhost was set up to listen / serve.  Call
 the api lws_init_vhost_client_ssl() to also allow client SSL on the vhost.
 
+@section clipipe Pipelining Client Requests to same host
+
+If you are opening more client requests to the same host and port, you
+can give the flag LCCSCF_PIPELINE on `info.ssl_connection` to indicate
+you wish to pipeline them.
+
+Without the flag, the client connections will occur concurrently using a
+socket and tls wrapper if requested for each connection individually.
+That is fast, but resource-intensive.
+
+With the flag, lws will queue subsequent client connections on the first
+connection to the same host and port.  When it has confirmed from the
+first connection that pipelining / keep-alive is supported by the server,
+it lets the queued client pipeline connections send their headers ahead
+of time to create a pipeline of requests on the server side.
+
+In this way only one tcp connection and tls wrapper is required to transfer
+all the transactions sequentially.  It takes a little longer but it
+can make a significant difference to resources on both sides.
 
+If lws learns from the first response header that keepalive is not possible,
+then it marks itself with that information and detaches any queued clients
+to make their own individual connections as a fallback.
+
+Lws can also intelligently combine multiple ongoing client connections to
+the same host and port into a single http/2 connection with multiple
+streams if the server supports it.
+
+Unlike http/1 pipelining, with http/2 the client connections all occur
+simultaneously using h2 stream multiplexing inside the one tcp + tls
+connection.
+
+You can turn off the h2 client support either by not building lws with
+`-DLWS_WITH_HTTP2=1` or giving the `LCCSCF_NOT_H2` flag in the client
+connection info struct `ssl_connection` member.
 
 @section vhosts Using lws vhosts
 
@@ -888,6 +1278,15 @@ This allocation is only deleted / replaced when the connection accesses a
 URL region with a different protocol (or the default protocols[0] if no
 CALLBACK area matches it).
 
+This "binding connection to a protocol" lifecycle in managed by
+`LWS_CALLBACK_HTTP_BIND_PROTOCOL` and `LWS_CALLBACK_HTTP_DROP_PROTOCOL`.
+Because of HTTP/1.1 connection pipelining, one connection may perform
+many transactions, each of which may map to different URLs and need
+binding to different protocols.  So these messages are used to
+create the binding of the wsi to your protocol including any
+allocations, and to destroy the binding, at which point you should
+destroy any related allocations.
+
 @section BINDTODEV SO_BIND_TO_DEVICE
 
 The .bind_iface flag in the context / vhost creation struct lets you
@@ -930,7 +1329,7 @@ also add this to your own html easily
 
  - include lws-common.js from your HEAD section
  
-   <script src="/lws-common.js"></script>
+   \<script src="/lws-common.js">\</script>
    
  - dim the page during initialization, in a script section on your page
  
@@ -943,3 +1342,20 @@ also add this to your own html easily
  - in your ws onClose(), reapply the dimming
  
    lws_gray_out(true,{'zindex':'499'});
+
+@section errstyle Styling http error pages
+
+In the code, http errors should be handled by `lws_return_http_status()`.
+
+There are basically two ways... the vhost can be told to redirect to an "error
+page" URL in response to specifically a 404... this is controlled by the
+context / vhost info struct (`struct lws_context_creation_info`) member
+`.error_document_404`... if non-null the client is redirected to this string.
+
+If it wasn't redirected, then the response code html is synthesized containing
+the user-selected text message and attempts to pull in `/error.css` for styling.
+
+If this file exists, it can be used to style the error page.  See 
+https://libwebsockets.org/git/badrepo for an example of what can be done (
+and https://libwebsockets.org/error.css for the corresponding css).
+
diff --git a/READMEs/README.content-security-policy.md b/READMEs/README.content-security-policy.md
new file mode 100644 (file)
index 0000000..0fe0cc2
--- /dev/null
@@ -0,0 +1,148 @@
+## Using Content Security Policy (CSP)
+
+### What is it?
+
+Modern browsers have recently implemented a new feature providing
+a sort of "selinux for your web page".  If the server sends some
+new headers describing the security policy for the content, then
+the browser strictly enforces it.
+
+### Why would we want to do that?
+
+Scripting on webpages is pretty universal, sometimes the scripts
+come from third parties, and sometimes attackers find a way to
+inject scripts into the DOM, eg, through scripts in content.
+
+CSP lets the origin server define what is legitimate for the page it
+served and everything else is denied.
+
+The CSP for warmcat.com and libwebsockets.org looks like this,
+I removed a handful of whitelisted image sources like travis
+status etc for clarity...
+
+```
+"content-security-policy": "default-src 'none'; img-src 'self' data:; script-src 'self'; font-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none';",
+"x-content-type-options": "nosniff",
+"x-xss-protection": "1; mode=block",
+"x-frame-options": "deny",
+"referrer-policy": "no-referrer"
+```
+
+The result of this is the browser won't let the site content be iframed, and it
+will reject any inline styles or inline scripts.  Fonts, css, ajax, ws and
+images are only allowed to come from 'self', ie, the server that served the
+page.  You may inject your script, or deceptive styles: it won't run or be shown.
+
+Because inline scripts are banned, the usual methods for XSS are dead;
+the attacker can't even load js from another server.  So these rules
+provide a very significant increase in client security.
+
+### Implications of strict CSP
+
+Halfhearted CSP isn't worth much.  The only useful approach is to start
+with `default-src 'none'` which disables everything, and then whitelist the
+minimum needed for the pages to operate.
+
+"Minimum needed for the pages to operate" doesn't mean defeat the protections
+necessary so everything in the HTML can stay the same... it means adapt the
+pages to want the minimum and then enable the minimum.
+
+The main point is segregation of styles and script away from the content, in
+files referenced in the document `<head>` section, along these lines:
+
+```
+<head>
+ <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ <link rel="stylesheet" type="text/css" href="test.css"/>
+ <script type='text/javascript' src="/lws-common.js"></script>
+ <script type='text/javascript' src='test.js'></script>
+ <title>Minimal Websocket test app</title>
+</head>
+```
+
+#### Inline styles must die
+
+All styling must go in one or more `.css` file(s) best served by the same
+server... while you can whitelist other sources in the CSP if you have to,
+unless you control that server as well, you are allowing whoever gains
+access to that server access to your users.
+
+Inline styles are no longer allowed (eg, "style='font-size:120%'" in the
+HTML)... they must be replaced by reference to one or more CSS class, which
+in this case includes "font-size:120%".  This has always been the best
+practice anyway, and your pages will be cleaner and more maintainable.
+
+#### Inline scripts must die
+
+Inline scripts need to be placed in a `.js` file and loaded in the page head
+section, again it should only be from the server that provided the page.
+
+Then, any kind of inline script, yours or injected or whatever, will be
+completely rejected by the browser.
+
+#### onXXX must be replaced by eventListener
+
+Inline `onclick()` etc are kinds of inline scripting and are banned.
+
+Modern browsers have offered a different system called ["EventListener" for
+a while](https://developer.mozilla.org/en-US/docs/Web/API/EventListener)
+which allows binding of events to DOM elements in JS.
+
+A bunch of different named events are possible to listen on, commonly the
+`.js` file will ask for one or both of
+
+```
+window.addEventListener("load", function() {
+...
+}, false);
+
+document.addEventListener("DOMContentLoaded", function() {
+...
+}, false);
+```
+
+These give the JS a way to trigger when either everything on the page has
+been "loaded" or the DOM has been populated from the initial HTML.  These
+can set up other event listeners on the DOM objects and aftwards the
+events will drive what happens on the page from user interaction and / or
+timers etc.
+
+If you have `onclick` in your HTML today, you would replace it with an id
+for the HTML element, then eg in the DOMContentLoaded event listener,
+apply 
+
+```
+   document.getElementById("my-id").addEventListener("click", function() {
+   ...
+   }, false);
+```
+
+ie the .js file becomes the only place with the "business logic" of the
+elements mentioned in the HTML, applied at runtime.
+
+#### Do you really need external sources?
+
+Do your scripts and fonts really need to come from external sources?
+If your caching policy is liberal, they are not actually that expensive
+to serve once and then the user is using his local copy for the next
+days.
+
+Some external sources are marked as anti-privacy in modern browsers, meaning
+they track your users, in turn meaning if your site refers to them, you
+will lose your green padlock in the browser.  If the content license allows
+it, hosting them on "self", ie, the same server that provided the HTML,
+will remove that problem.
+
+Bringing in scripts from external sources is actually quite scary from the
+security perspective.  If someone hacks the `ajax.googleapis.com` site to serve
+a hostile, modified jquery, half the Internet will instantly
+become malicious.  However if you serve it yourself, unless your server
+was specifically targeted you know it will continue to serve what you
+expect.
+
+Since these scripts are usually sent with cache control headers for local
+caching duration of 1 year, the cost of serving them yourself under the same
+conditions is small but your susceptibility to attack is reduced to only taking
+care of your own server.  And there is a privacy benefit that google is not
+informed of your users' IPs and activities on your site.
+
diff --git a/READMEs/README.contributing.md b/READMEs/README.contributing.md
new file mode 100644 (file)
index 0000000..34df25e
--- /dev/null
@@ -0,0 +1,41 @@
+## Contributing to lws
+
+### How to contribute
+
+Sending a patch with a bug report is very welcome.
+
+For nontrivial problems, it's probably best to discuss on the mailing list,
+or on github if you prefer, how to best solve it.
+
+However your contribution is coming is fine:
+
+ - paste a `git diff`
+
+ - send a patch series by mail or mailing list
+
+ - paste in a github issue
+
+ - github PR
+
+are all OK.
+
+### Coding Standards
+
+Code should look roughly like the existing code, which follows linux kernel
+coding style.
+
+If there are non-functional problems I will clean them out when I apply the
+patch.
+
+If there are functional problems (eg broken error paths etc) if they are
+small compared to the working part I will also clean them.  If there are
+larger problems, or consequences to the patch will have to discuss how to
+solve them with a retry.
+
+### Funding specific work
+
+If there is a feature you wish was supported in lws, consider paying for the
+work to be done.  The maintainer is a consultant and if we can agree the
+task, you can quickly get a high quality result that does just what you need,
+maintained ongoing along with the rest of lws.
+
diff --git a/READMEs/README.crypto-apis.md b/READMEs/README.crypto-apis.md
new file mode 100644 (file)
index 0000000..4a04687
--- /dev/null
@@ -0,0 +1,181 @@
+# Lws Crypto Apis
+
+## Overview
+
+![lws crypto overview](/doc-assets/lws-crypto-overview.svg)
+
+Lws provides a "generic" crypto layer on top of both OpenSSL and
+compatible tls library, and mbedtls.  Using this layer, your code
+can work without any changes on both types of tls library crypto
+backends... it's as simple as rebuilding lws with `-DLWS_WITH_MBEDTLS=0`
+or `=1` at cmake.
+
+The generic layer can be used directly (as in, eg, the sshd plugin),
+or via another layer on top, which processes JOSE JSON objects using
+JWS (JSON Web Signatures), JWK (JSON Web Keys), and JWE (JSON Web
+Encryption).
+
+The `JW` apis use the generic apis (`lws_genrsa_`, etc) to get the crypto tasks
+done, so anything they can do you can also get done using the generic apis.
+The main difference is that with the generic apis, you must instantiate the
+correct types and use type-specfic apis.  With the `JW` apis, there is only
+one interface for all operations, with the details hidden in the api and
+controlled by the JSON objects.
+
+Because of this, the `JW` apis are often preferred because they give you
+"crypto agility" cheaply... to change your crypto to another supported algorithm
+once it's working, you literally just change your JSON defining the keys and
+JWE or JWS algorithm.  (It's up to you to define your policy for which
+combinations are acceptable by querying the parsed JW structs).
+
+## Crypto supported in generic layer
+
+### Generic Hash
+
+ - SHA1
+ - SHA256
+ - SHA384
+ - SHA512
+
+### Generic HMAC
+
+ - SHA256
+ - SHA384
+ - SHA512
+
+### Generic AES
+
+ - CBC
+ - CFB128
+ - CFB8
+ - CTR
+ - ECB
+ - OFB
+ - XTS
+ - GCM
+ - KW (Key Wrap)
+
+### Generic RSA
+
+ - PKCS 1.5
+ - OAEP / PSS
+
+### Generic EC
+
+ - ECDH
+ - ECDSA
+ - P256 / P384 / P521 (sic) curves
+
+## Using the generic layer
+
+All the necessary includes are part of `libwebsockets.h`.
+
+Enable `-DLWS_WITH_GENCRYPTO=1` at cmake.
+
+|api|header|Functionality|
+|---|---|---|
+|genhash|[./include/libwebsockets/lws-genhash.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genhash.h)|Provides SHA1 + SHA2 hashes and hmac|
+|genrsa|[./include/libwebsockets/lws-genrsa.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genrsa.h)|Provides RSA encryption, decryption, signing, verification, key generation and creation|
+|genaes|[./include/libwebsockets/lws-genaes.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genaes.h)|Provides AES in all common variants for encryption and decryption|
+|genec|[./include/libwebsockets/lws-genec.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genec.h)|Provides Elliptic Curve for encryption, decryption, signing, verification, key generation and creation|
+|x509|[./include/libwebsockets/lws-x509.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-x509.h)|Apis for X.509 Certificate loading, parsing, and stack verification, plus JWK key extraction from PEM X.509 certificate / private key|
+
+Unit tests for these apis, which serve as usage examples, can be found in [./minimal-examples/api-tests/api-test-gencrypto](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-gencrypto)
+
+### Keys in the generic layer
+
+The necessary types and defines are brought in by `libwebsockets.h`.
+
+Keys are represented only by an array of `struct lws_jwk_elements`... the
+length of the array is defined by the cipher... it's one of
+
+|key elements count|definition|
+|---|---|
+|`LWS_COUNT_OCT_KEY_ELEMENTS`|1|
+|`LWS_COUNT_RSA_KEY_ELEMENTS`|8|
+|`LWS_COUNT_EC_KEY_ELEMENTS`|4|
+|`LWS_COUNT_AES_KEY_ELEMENTS`|1|
+
+`struct lws_jwk_elements` is a simple pointer / length combination used to
+store arbitrary octets that make up the key element's binary representation.
+
+## Using the JOSE layer
+
+The JOSE (JWK / JWS / JWE) stuff is a crypto-agile JSON-based layer
+that uses the gencrypto support underneath.
+
+"Crypto Agility" means the JSON structs include information about the
+algorithms and ciphers used in that particular object, making it easy to
+upgrade system crypto strength or cycle keys over time while supporting a
+transitional period where the old and new keys or algorithms + ciphers
+are also valid.
+
+Uniquely lws generic support means the JOSE stuff also has "tls library
+agility", code written to the lws generic or JOSE apis is completely unchanged
+even if the underlying tls library changes between OpenSSL and mbedtls, meaning
+sharing code between server and client sides is painless.
+
+All the necessary includes are part of `libwebsockets.h`.
+
+Enable `-DLWS_WITH_JOSE=1` at CMake.
+
+|api|header|Functionality|
+|---|---|---|
+|JOSE|[./include/libwebsockets/lws-jose.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jose.h)|Provides crypto agility for JWS / JWE|
+|JWE|[./include/libwebsockets/lws-jwe.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jwe.h)|Provides Encryption and Decryption services for RFC7516 JWE JSON|
+|JWS|[./include/libwebsockets/lws-jws.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jws.h)|Provides signature and verifcation services for RFC7515 JWS JSON|
+|JWK|[./include/libwebsockets/lws-jwk.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jwk.h)|Provides signature and verifcation services for RFC7517 JWK JSON, both "keys" arrays and singletons|
+
+Minimal examples are provided in the form of commandline tools for JWK / JWS / JWE / x509 handling:
+
+ - [JWK minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwk)
+ - [JWS minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jws)
+ - [JWE minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwe)
+ - [X509 minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-x509)
+
+Unit tests for these apis, which serve as usage examples, can be found in [./minimal-examples/api-tests/api-test-jose](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-jose)
+
+## Crypto supported in the JOSE layer
+
+The JOSE RFCs define specific short names for different algorithms
+
+### JWS
+
+|JSOE name|Hash|Signature|
+---|---|---
+|RS256, RS384, RS512|SHA256/384/512|RSA
+|ES256, ES384, ES521|SHA256/384/512|EC
+
+### JWE
+
+|Key Encryption|Payload authentication + crypt|
+|---|---|
+|`RSAES-PKCS1-v1.5` 2048b & 4096b|`AES_128_CBC_HMAC_SHA_256`|
+|`RSAES-PKCS1-v1.5` 2048b|`AES_192_CBC_HMAC_SHA_384`|
+|`RSAES-PKCS1-v1.5` 2048b|`AES_256_CBC_HMAC_SHA_512`|
+|`RSAES-OAEP`|`AES_256_GCM`|
+|`AES128KW`, `AES192KW`, `AES256KW`|`AES_128_CBC_HMAC_SHA_256`|
+|`AES128KW`, `AES192KW`, `AES256KW`|`AES_192_CBC_HMAC_SHA_384`|
+|`AES128KW`, `AES192KW`, `AES256KW`|`AES_256_CBC_HMAC_SHA_512`|
+|`ECDH-ES` (P-256/384/521 key)|`AES_128/192/256_GCM`|
+|`ECDH-ES+A128/192/256KW` (P-256/384/521 key)|`AES_128/192/256_GCM`|
+
+### Keys in the JOSE layer
+
+Keys in the JOSE layer use a `struct lws_jwk`, this contains two arrays of
+`struct lws_jwk_elements` sized for the worst case (currently RSA).  One
+array contains the key elements as described for the generic case, and the
+other contains various nonencrypted key metadata taken from JWK JSON.
+
+|metadata index|function|
+|---|---|
+|`JWK_META_KTY`|Key type, eg, "EC"|
+|`JWK_META_KID`|Arbitrary ID string|
+|`JWK_META_USE`|What the public key may be used to validate, "enc" or "sig"|
+|`JWK_META_KEY_OPS`|Which operations the key is authorized for, eg, "encrypt"|
+|`JWK_META_X5C`|Optional X.509 cert version of the key|
+|`JWK_META_ALG`|Optional overall crypto algorithm the key is intended for use with|
+
+`lws_jwk_destroy()` should be called when the jwk is going out of scope... this
+takes care to zero down any key element data in the jwk.
+
similarity index 66%
rename from README.esp32.md
rename to READMEs/README.esp32.md
index 6f64891..1abcfb6 100644 (file)
@@ -1,6 +1,8 @@
 ESP32 Support
 =============
 
+See \ref esp32 for details on how to build lws as a component in an ESP-IDF project.
+
 Lws provides a "factory" application
 
 https://github.com/warmcat/lws-esp32-factory
@@ -21,3 +23,15 @@ Factory Reset or Uninitialized|Factory|AP: ESP_012345|80|http://192.168.4.1|fact
 User configuration|Factory|AP: config-model-serial|443|https://192.168.4.1|index.html - user set up his AP information
 Operation|OTA|Station only|443|https://model-serial.local|OTA application
 
+## Basic Auth
+
+The lws-esp32-test-server-demos app also demos basic auth.
+
+On a normal platform this is done by binding a mount to a text file somewhere in the filesystem, which
+contains user:password information one per line.
+
+On ESP32 there is not necessarily any generic VFS in use.  So instead, the basic auth lookup is bound to
+a given nvs domain, where the username is the key and the password the value.  main/main.c in the test
+demos app shows how to both make the mount use basic auth, and how to set a user:password combination
+using nvs.
+
diff --git a/READMEs/README.http-fallback.md b/READMEs/README.http-fallback.md
new file mode 100644 (file)
index 0000000..120b00f
--- /dev/null
@@ -0,0 +1,172 @@
+# Http fallback and raw proxying
+
+Lws has several interesting options and features that can be applied to get
+some special behaviours... this article discusses them and how they work.
+
+## Overview of normal vhost selection
+
+Lws supports multiple http or https vhosts sharing a listening socket on the
+same port.
+
+For unencrypted http, the Host: header is used to select which vhost the
+connection should bind to, by comparing what is given there against the
+names the server was configured with for the various vhosts.  If no match, it
+selects the first configured vhost.
+
+For TLS, it has an extension called SNI (Server Name Indication) which tells
+the server early in the TLS handshake the host name the connection is aimed at.
+That allows lws to select the vhost early, and use vhost-specific TLS certs
+so everything is happy.  Again, if there is no match the connection proceeds
+using the first configured vhost and its certs.
+
+## Http(s) fallback options
+
+What happens if you try to connect, eg, an ssh client to the http server port
+(this is not an idle question...)?  Obviously the http server part or the tls
+part of lws will fail the connection and close it.  (We will look at that flow
+in a moment in detail for both unencrypted and tls listeners.)
+
+However if the first configured vhost for the port was created with the
+vhost creation info struct `.options` flag `LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG`,
+then instead of the error, the connection transitions to whatever role was
+given in the vhost creation info struct `.listen_accept_role` and `.listen_accept_protocol`.
+
+With lejp-conf / lwsws, the options can be applied to the first vhost using:
+
+```
+   "listen-accept-role": "the-role-name",
+   "listen-accept-protocol": "the-protocol-name",
+   "fallback-listen-accept": "1"
+```
+
+See `./minimal-examples/raw/minimal-raw-fallback-http-server` for examples of
+all the options in use via commandline flags.
+
+So long as the first packet for the protocol doesn't look like GET, POST, or
+a valid tls packet if connection to an https vhost, this allows the one listen
+socket to handle both http(s) and a second protocol, as we will see, like ssh.
+
+Notice there is a restriction that no vhost selection processing is possible,
+neither for tls listeners nor plain http ones... the packet belonging to a
+different protocol will not send any Host: header nor tls SNI.
+
+Therefore although the flags and settings are applied to the first configured
+vhost, actually their effect is global for a given listen port.  If enabled,
+all vhosts on the same listen port will do the fallback action.
+
+### Plain http flow
+
+![plain http flow](/doc-assets/accept-flow-1.svg)
+
+Normally, if the first received packet does not contain a valid HTTP method,
+then the connection is dropped.  Which is what you want from an http server.
+
+However if enabled, the connection can transition to the defined secondary
+role / protocol.
+
+|Flag|lejp-conf / lwsws|Function|
+|---|---|---|
+|`LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG`|`"fallback-listen-accept": "1"`|Enable fallback processing|
+
+### TLS https flow
+
+![tls https flow](/doc-assets/accept-flow-2.svg)
+
+If the port is listening with tls, the point that a packet from a different
+protocol will fail is earlier, when the tls tunnel is being set up.
+
+|Flag|lejp-conf / lwsws|Function|
+|---|---|---|
+|`LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG`|`"fallback-listen-accept": "1"`|Enable fallback processing|
+|`LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS`|`"redirect-http": "1"`|Treat invalid tls packet as http, issue http redirect to https://|
+|`LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER`|`"allow-http-on-https": "1"`|Accept unencrypted http connections on this tls port (dangerous)|
+
+The latter two options are higher priority than, and defeat, the first one.
+
+### Non-http listener
+
+![non-http flow](/doc-assets/accept-flow-3.svg)
+
+It's also possible to skip the fallback processing and just force the first
+vhost on the port to use the specified role and protocol in the first place.
+
+|Flag|lejp-conf / lwsws|Function|
+|---|---|---|
+|LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG|`"apply-listen-accept": "1"`|Force vhost to use listen-accept-role / listen-accept-protocol|
+
+## Using http(s) fallback with raw-proxy
+
+If enabled for build with `cmake .. -DLWS_ROLE_RAW_PROXY=1 -DLWS_WITH_PLUGINS=1`
+then lws includes ready-to-use support for raw tcp proxying.
+
+This can be used standalone on the first vhost on a port, but most intriguingly
+it can be specified as the fallback for http(s)...
+
+See `./minimal-examples/raw/minimal-raw-proxy-fallback.c` for a working example.
+
+### fallback with raw-proxy in code
+
+On the first vhost for the port, specify the required "onward" pvo to configure
+the raw-proxy protocol...you can adjust the "ipv4:127.0.0.1:22" to whatever you
+want...
+
+```
+       static struct lws_protocol_vhost_options pvo1 = {
+               NULL,
+               NULL,
+               "onward",               /* pvo name */
+               "ipv4:127.0.0.1:22"     /* pvo value */
+       };
+
+       static const struct lws_protocol_vhost_options pvo = {
+               NULL,                   /* "next" pvo linked-list */
+               &pvo1,                  /* "child" pvo linked-list */
+               "raw-proxy",            /* protocol name we belong to on this vhost */
+               ""                      /* ignored */
+       };
+```
+
+... and set up the fallback enable and bindings...
+
+```
+       info.options |= LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG;
+       info.listen_accept_role = "raw_proxy";
+       info.listen_accept_proxy = "raw_proxy";
+       info.pvo = &pvo;
+```
+
+### fallback with raw-proxy in JSON conf
+
+On the first vhost for the port, enable the raw-proxy protocol on the vhost and
+set the pvo config
+
+```
+                "ws-protocols": [{
+                        "raw-proxy": {
+                         "status": "ok",
+                         "onward": "ipv4:127.0.0.1:22"
+                        }
+                 }],
+```
+
+Enable the fallback behaviour on the vhost and the role / protocol binding
+
+```
+       "listen-accept-role": "raw-proxy",
+       "listen-accept-protocol": "raw-proxy",
+       "fallback-listen-accept": "1"
+```
+
+### Testing
+
+With this configured, the listen port will function normally for http or https
+depending on how it was set up.
+
+But if you try to connect to it with an ssh client, that will also work fine.
+
+The libwebsockets.org server is set up in this way, you can confirm it by
+visiting `https://libwebsockets.org` on port 443 as usual, but also trying
+`ssh -p 443 invalid@libwebsockets.org`... you will get permission denied from
+your ssh client.  With valid credentials in fact that works perfectly for
+ssh, scp, git-over-ssh etc all on port 443...
+
diff --git a/READMEs/README.lws_dll.md b/READMEs/README.lws_dll.md
new file mode 100644 (file)
index 0000000..d814b68
--- /dev/null
@@ -0,0 +1,130 @@
+# lws_dll Doubly-linked list
+
+## Introduction
+
+Lws supports two kinds of doubly-linked list, `lws_dll` and `lws_dll2`.
+
+Unless memory is at a big premium, or it has to work on lws < v3.2, it's
+best to simply use `lws_dll2`.
+
+![lws_dll overview](../doc-assets/lws_dll.svg)
+
+## How to use
+
+The basics are the same for lws_dll and lws_dll2.
+
+The list objects point only to themselves, and you use the `lws_container_of`
+macro to get a pointer to your struct that contains the list object.  Doing
+it this way
+
+ - the list object does not have to be the first thing in your struct
+ - your struct can contain multiple list objects and appear on lists belonging
+   to multiple owners simultaenously,
+   
+### lws_dll Minimal example
+
+```
+struct mystruct {
+       ....
+       lws_dll list;
+       ...
+};
+
+lws_dll owner;
+```
+
+Adding a mystruct to the owner list (...add_tail() works the same way but adds
+to the other end of the list)
+
+```
+       struct mystruct *p;
+       
+       ...
+       
+       lws_dll_add_head(&p->list, &owner);
+```
+
+Removing the list object from its owner
+
+```
+       lws_dll2_remove(&p->list, &owner);
+```
+
+If you have a `struct lws_dll *d` pointing to `list` in struct mystruct, you can
+convert it to a `struct mystruct *p` ike this
+
+```
+       struct mystruct *p = lws_container_of(d, struct lws_dll, list);
+```
+
+### lws_dll2 Minimal example
+
+
+```
+struct mystruct {
+       ....
+       lws_dll2 list;
+       ...
+};
+
+lws_dll2_owner owner;
+```
+
+Adding a mystruct to the owner list (...add_tail() works the same way but adds
+to the other end of the list)
+
+```
+       struct mystruct *p;
+       
+       ...
+       
+       lws_dll2_add_head(&p->list, &owner);
+```
+
+Removing the list object from its owner (notice compared to lws_dll, it doesn't
+need to be told the owner)
+
+```
+       lws_dll2_remove(&p->list);
+```
+
+If you have a `struct lws_dll2 *d` pointing to `list` in struct mystruct, you
+can convert it to a `struct mystruct *p` ike this
+
+```
+       struct mystruct *p = lws_container_of(d, struct lws_dll2, list);
+```
+
+## Summary Comparing lws_dll and lws_dll2
+
+ - both offer a doubly-linked list object, and (since v3.2) track both the
+   head and tail in an "list owner" object
+   
+ - both are initalized by memsetting to 0
+
+ - for `lws_dll`, it reuses an `lws_dll` as the "owner", for `lws_dll2`, there's a
+   specific `lws_dll2_owner` structure for that
+   
+ - `lws_dll2_owner` also keeps count of the number of list elements
+ - `lws_dll2` knows which owner's list it is participating on.  So it can remove
+   itself and update the owner without the caller needing to know its owner.
+   In the case there are several potential owners list objects may be on, this
+   is very convenient.
+
+ - `lws_dll` is simpler and has a smaller footprint (two pointers per entry vs
+   three).  But you have to know the exact list owner to perform operations on
+   it.
+## apis
+|function|lws_dll|lws_dll2|
+|---|---|---|
+|add entry at head|`void lws_dll_add_head(struct lws_dll *d, struct lws_dll *phead)`|`void lws_dll2_add_head(struct lws_dll2 *d, struct lws_dll2_owner *owner)`|
+|add entry at tail|`void lws_dll_add_tail(struct lws_dll *d, struct lws_dll *phead);`|`void lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner)`|
+|remove entry from its owning list|`void lws_dll_remove_track_tail(struct lws_dll *d, struct lws_dll *phead)`|`void lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner)`|
+|get owner|(not supported)|`struct lws_dll2_owner * lws_dll2_owner(const struct lws_dll2 *d)`|
+|check if item is detached from any list|`lws_dll_is_detached(struct lws_dll *d, struct lws_dll *phead)|int lws_dll2_is_detached(const struct lws_dll2 *d)`|
+|iterate through items on list|`int lws_dll_foreach_safe(struct lws_dll *phead, void *user, int (*cb)(struct lws_dll *d, void *user))|int lws_dll2_foreach_safe(struct lws_dll2_owner *owner, void *user, int (*cb)(struct lws_dll2 *d, void *user))`|
+
diff --git a/READMEs/README.lws_sequencer.md b/READMEs/README.lws_sequencer.md
new file mode 100644 (file)
index 0000000..fab0573
--- /dev/null
@@ -0,0 +1,149 @@
+# `lws_sequencer_t` introduction
+
+Often a single network action like a client GET is just part of a
+larger series of actions, perhaps involving different connections.
+
+Since lws operates inside an event loop, if the outer sequencing
+doesn't, it can be awkward to synchronize these steps with what's
+happening on the network with a particular connection on the event
+loop thread.
+
+![lws_sequencer](/doc-assets/lws_sequencer.svg)
+
+`lws_sequencer_t` provides a generic way to stage multi-step
+operations from inside the event loop.  Because it participates
+in the event loop similar to a wsi, it always operates from the
+service thread context and can access structures that share the
+service thread without locking.  It can also provide its own
+higher-level timeout handling.
+
+Naturally you can have many of them running in the same event
+loop operating independently.
+
+Sequencers themselves bind to a pt (per-thread) service thread,
+by default there's only one of these and it's the same as saying
+they bind to an `lws_context`.  The sequencer callback may create
+wsi which in turn are bound to a vhost, but the sequencer itself
+is above all that.
+
+## Sequencer timeouts
+
+The sequencer additionally maintains its own second-resolution timeout
+checked by lws for the step being sequenced... this is independent of
+any lws wsi timeouts which tend to be set and reset for very short-term
+timeout protection inside one transaction.
+
+The sequencer timeout operates separately and above any wsi timeout, and
+is typically only reset by the sequencer callback when it receives an
+event indicating a step completed or failed, or it sets up the next sequence
+step.
+
+If the sequencer timeout expires, then the sequencer receives a queued
+`LWSSEQ_TIMED_OUT` message informing it, and it can take corrective action
+or schedule a retry of the step.  This message is queued and sent normally
+under the service thread context and in order of receipt.
+
+Unlike lws timeouts which force the wsi to close, the sequencer timeout
+only sends the message.  This allows the timeout to be used to, eg, wait
+out a retry cooloff period and then start the retry when the
+`LWSSEQ_TIMED_OUT` is received, according to the state of the sequencer.
+
+## Creating an `lws_sequencer_t`
+
+```
+typedef struct lws_seq_info {
+       struct lws_context              *context;   /* lws_context for seq */
+       int                             tsi;        /* thread service idx */
+       size_t                          user_size;  /* size of user alloc */
+       void                            **puser;    /* place ptr to user */
+       lws_seq_event_cb                cb;         /* seq callback */
+       const char                      *name;      /* seq name */
+       const lws_retry_bo_t            *retry;     /* retry policy */
+} lws_seq_info_t;
+```
+
+```
+lws_sequencer_t *
+lws_sequencer_create(lws_seq_info_t *info);
+```
+
+When created, in lws the sequencer objects are bound to a 'per-thread',
+which is by default the same as to say bound to the `lws_context`.  You
+can tag them with an opaque user data pointer, and they are also bound to
+a user-specified callback which handles sequencer events
+
+```
+typedef int (*lws_seq_event_cb)(struct lws_sequencer *seq, void *user_data,
+                               lws_seq_events_t event, void *data);
+```
+
+`lws_sequencer_t` objects are private to lws and opaque to the user.  A small
+set of apis lets you perform operations on the pointer returned by the
+create api.
+
+## Queueing events on a sequencer
+
+Each sequencer object can be passed "events", which are held on a per-sequencer
+queue and handled strictly in the order they arrived on subsequent event loops.
+`LWSSEQ_CREATED` and `LWSSEQ_DESTROYED` events are produced by lws reflecting
+the sequencer's lifecycle, but otherwise the event indexes have a user-defined
+meaning and are queued on the sequencer by user code for eventual consumption
+by user code in the sequencer callback.
+
+Pending events are removed from the sequencer queues and sent to the sequencer
+callback from inside the event loop at a rate of one per event loop wait.
+
+## Destroying sequencers
+
+`lws_sequencer_t` objects are cleaned up during context destruction if they are
+still around.
+
+Normally the sequencer callback receives a queued message that
+informs it that it's either failed at the current step, or succeeded and that
+was the last step, and requests that it should be destroyed by returning
+`LWSSEQ_RET_DESTROY` from the sequencer callback.
+
+## Lifecycle considerations
+
+Sequencers may spawn additional assets like client wsi as part of the sequenced
+actions... the lifecycle of the sequencer and the assets overlap but do not
+necessarily depend on each other... that is a wsi created by the sequencer may
+outlive the sequencer.
+
+It's important therefore to detach assets from the sequencer and the sequencer
+from the assets when each step is over and the asset is "out of scope" for the
+sequencer.  It doesn't necessarily mean closing the assets, just making sure
+pointers are invalidated.  For example, if a client wsi held a pointer to the
+sequencer as its `.user_data`, when the wsi is out of scope for the sequencer
+it can set it to NULL, eg, `lws_set_wsi_user(wsi, NULL);`.
+
+Under some conditions wsi may want to hang around a bit to see if there is a
+subsequent client wsi transaction they can be reused on.  They will clean
+themselves up when they time out.
+
+## Watching wsi lifecycle from a sequencer
+
+When a sequencer is creating a wsi as part of its sequence, it will be very
+interested in lifecycle events.  At client wsi creation time, the sequencer
+callback can set info->seq to itself in order to receive lifecycle messages
+about its wsi.
+
+|message|meaning|
+|---|---|
+|`LWSSEQ_WSI_CONNECTED`|The wsi has become connected|
+|`LWSSEQ_WSI_CONN_FAIL`|The wsi has failed to connect|
+|`LWSSEQ_WSI_CONN_CLOSE`|The wsi had been connected, but has now closed|
+
+By receiving these, the sequencer can understand when it should attempt
+reconnections or that it cannot progress the sequence.
+
+When dealing with wsi that were created by the sequencer, they may close at
+any time, eg, be closed by the remote peer or an intermediary.  The
+`LWSSEQ_WSI_CONN_CLOSE` message may have been queued but since they are
+strictly handled in the order they arrived, before it was
+handled an earlier message may want to cause some api to be called on
+the now-free()-d wsi.  To detect this situation safely, there is a
+sequencer api `lws_sequencer_check_wsi()` which peeks the message
+buffer and returns nonzero if it later contains an `LWSSEQ_WSI_CONN_CLOSE`
+already.
+
diff --git a/READMEs/README.lws_struct.md b/READMEs/README.lws_struct.md
new file mode 100644 (file)
index 0000000..00ca08a
--- /dev/null
@@ -0,0 +1,38 @@
+# lws_struct
+
+## Overview
+
+lws_struct provides a lightweight method for serializing and deserializing C
+structs to and from JSON, and to and from sqlite3.
+
+![lws_struct overview](../doc-assets/lws_struct-overview.svg)
+
+ - you provide a metadata array describing struct members one-time, then call
+   generic apis to serialize and deserialize 
+
+ - supports flat structs, single child struct pointers, and unbounded arrays /
+   linked-lists of child objects automatically using [lws_dll2 linked-lists](./README.lws_dll.md)
+ - supports boolean and C types char, int, long, long long in explicitly signed
+   and unsigned forms
+ - supports both char * type string members where the unbounded content is
+   separate and pointed to, and fixed length char array[] type members where
+   the content is part of the struct
+
+ - huge linear strings are supported by storing to a temp lwsac of chained chunks,
+   which is written into a single linear chunk in the main lwsac once the
+   total string length is known
+   
+ - deserialization allocates into an [lwsac](../lib/misc/lwsac/README.md), so everything is inside as few
+   heap allocations as possible while still able to expand to handle arbitrary
+   array or strins sizes
+   
+ - when deserialized structs are finished with, a single call to free the
+   lwsac frees the whole thing without having to walk it
+   
+ - stateful serializaton and deserialization allows as-you-get packets incremental
+   parsing and production of chunks of as-you-can-send incremental serialization
+   output cleanly
+
+## Examples
similarity index 75%
rename from README.lwsws.md
rename to READMEs/README.lwsws.md
index 04ba81a..d2006b3 100644 (file)
@@ -41,8 +41,8 @@ There is a single file intended for global settings
 
        {
          "global": {
-          "uid": "48",  # apache user
-          "gid": "48",  # apache user
+          "username": "apache",
+          "groupname": "apache",
           "count-threads": "1",
           "server-string": "myserver v1", # returned in http headers
           "ws-pingpong-secs": "200", # confirm idle established ws connections this often
@@ -79,6 +79,12 @@ on port 7681, non-SSL is provided.  To set it up
        # sudo lwsws
 ```
 
+@section lwswsacme Using Letsencrypt or other ACME providers
+
+Lws supports automatic provisioning and renewal of TLS certificates.
+
+See ./READMEs/README.plugin-acme.md for examples of how to set it up on an lwsws vhost.
+
 @section lwsogo Other Global Options
 
  - `reject-service-keywords` allows you to return an HTTP error code and message of your choice
@@ -182,7 +188,7 @@ Vhosts can select which plugins they want to offer and give them per-vhost setti
 ```
 
 The "x":"y" parameters like "status":"ok" are made available to the protocol during its per-vhost
-LWS_CALLBACK_PROTOCOL_INIT (@in is a pointer to a linked list of struct lws_protocol_vhost_options
+LWS_CALLBACK_PROTOCOL_INIT (in is a pointer to a linked list of struct lws_protocol_vhost_options
 containing the name and value pointers).
 
 To indicate that a protocol should be used when no Protocol: header is sent
@@ -196,6 +202,18 @@ by the client, you can use "default": "1"
             }]
 ```
 
+Similarly, if your vhost is serving a raw protocol, you can mark the protocol
+to be selected using "raw": "1"
+```
+            "ws-protocols": [{
+              "warmcat-timezoom": {
+                "status": "ok",
+                "raw": "1"
+              }
+            }]
+```
+
+See also "apply-listen-accept" below.
 
 @section lwswsovo Lwsws Other vhost options
 
@@ -205,19 +223,27 @@ by the client, you can use "default": "1"
 
  - `keeplive-timeout` (in secs) defaults to 60 for lwsws, it may be set as a vhost option
 
- - `interface` lets you specify which network interface to listen on, if not given listens on all
+ - `interface` lets you specify which network interface to listen on, if not given listens on all.  If the network interface is not usable (eg, ethernet cable out) it will be logged at startup with such vhost not listening, and lws will poll for it and bind a listen socket to the interface if and when it becomes available.
 
  - "`unix-socket`": "1" causes the unix socket specified in the interface option to be used instead of an INET socket
 
+ - "`unix-socket-perms`": "user:group" allows you to control the unix permissons on the listening unix socket.  It's always get to `0600` mode, but you can control the user and group for the socket fd at creation time.  This allows you to use unix user and groups to control who may open the other end of the unix socket on the local system.
+
  - "`sts`": "1" causes lwsws to send a Strict Transport Security header with responses that informs the client he should never accept to connect to this address using http.  This is needed to get the A+ security rating from SSL Labs for your server.
 
  - "`access-log`": "filepath"   sets where apache-compatible access logs will be written
 
  - `"enable-client-ssl"`: `"1"` enables the vhost's client SSL context, you will need this if you plan to create client conections on the vhost that will use SSL.  You don't need it if you only want http / ws client connections.
 
- - "`ciphers`": "<cipher list>"   sets the allowed list of ciphers and key exchange protocols for the vhost.  The default list is restricted to only those providing PFS (Perfect Forward Secrecy) on the author's Fedora system.
+ - "`ciphers`": "<cipher list>"  OPENSSL only: sets the allowed list of TLS <= 1.2 ciphers and key exchange protocols for the serving SSL_CTX on the vhost.  The default list is restricted to only those providing PFS (Perfect Forward Secrecy) on the author's Fedora system.
  
- If you need to allow weaker ciphers,you can provide an alternative list here per-vhost.
+ If you need to allow weaker ciphers, you can provide an alternative list here per-vhost.
+
+ - "`client-ssl-ciphers`": "<cipher list>"  OPENSSL only: sets the allowed list of <= TLS1.2 ciphers and key exchange protocols for the client SSL_CTX on the vhost
+
+ - "`tls13-ciphers`": "<cipher list>"  OPENSSL 1.1.1+ only: sets allowed list of TLS1.3+ ciphers and key exchange protocols for the client SSL_CTX on the vhost.  The default is to allow all.
+
+ - "`client-tls13-ciphers`": "<cipher list>"  OPENSSL 1.1.1+ only: sets the allowed list of TLS1.3+ ciphers and key exchange protocols for the client SSL_CTX on the vhost.  The default is to allow all.
  
  - "`ecdh-curve`": "<curve name>"   The default ecdh curve is "prime256v1", but you can override it here, per-vhost
 
@@ -241,6 +267,8 @@ by the client, you can use "default": "1"
  - "`ssl-option-clear'": "<decimal>"   Clears the SSL option flag value for the vhost.
  It may be used multiple times and OR's the flags together.
 
+ - "`ssl-client-option-set`" and "`ssl-client-option-clear`" work the same way for the vhost Client SSL context
+
  - "`headers':: [{ "header1": "h1value", "header2": "h2value" }] 
 
 allows you to set arbitrary headers on every file served by the vhost
@@ -257,6 +285,8 @@ recommended vhost headers for good client security are
 
 ```
 
+ - "`apply-listen-accept`": "on"  This vhost only serves a non-http protocol, specified in "listen-accept-role" and "listen-accept-protocol"
+
 @section lwswsm Lwsws Mounts
 
 Where mounts are given in the vhost definition, then directory contents may
@@ -321,7 +351,7 @@ provide them using "pmo"
                }]
               }
 
-2) When using a cgi:// protcol origin at a mountpoint, you may also give cgi environment variables specific to the mountpoint like this
+2) When using a cgi:// protocol origin at a mountpoint, you may also give cgi environment variables specific to the mountpoint like this
 ```
               {
                "mountpoint": "/git",
@@ -390,7 +420,7 @@ Content-Type: header.
 7) A mount can be protected by HTTP Basic Auth.  This only makes sense when using
 https, since otherwise the password can be sniffed.
 
-You can add a `basic-auth` entry on a mount like this
+You can add a `basic-auth` entry on an http mount like this
 
 ```
 {
@@ -416,6 +446,77 @@ The file should be readable by lwsws, and for a little bit of extra security not
 have a file suffix, so lws would reject to serve it even if it could find it on
 a mount.
 
+After successful authentication, `WSI_TOKEN_HTTP_AUTHORIZATION` contains the
+authenticated username.
+
+In the case you want to also protect being able to connect to a ws protocol on
+a particular vhost by requiring the http part can authenticate using Basic
+Auth before the ws upgrade, this is also possible.  In this case, the
+"basic-auth": and filepath to the credentials file is passed as a pvo in the
+"ws-protocols" section of the vhost definition.
+
+@section lwswscc Requiring a Client Cert on a vhost
+
+You can make a vhost insist to get a client certificate from the peer before
+allowing the connection with
+
+```
+       "client-cert-required": "1"
+```
+
+the connection will only proceed if the client certificate was signed by the
+same CA as the server has been told to trust.
+
+@section rawconf Configuring Fallback and Raw vhosts
+
+Lws supports some unusual modes for vhost listen sockets, which may be
+configured entirely using the JSON per-vhost config language in the related
+vhost configuration section.
+
+There are three main uses for them
+
+1) A vhost bound to a specific role and protocol, not http.  This binds all
+incoming connections on the vhost listen socket to the "raw-proxy" role and
+protocol "myprotocol".
+
+```
+       "listen-accept-role":           "raw-proxy",
+       "listen-accept-protocol":       "myprotocol",
+       "apply-listen-accept":          "1"
+```
+
+2) A vhost that wants to treat noncompliant connections for http or https as
+   belonging to a secondary fallback role and protocol.  This causes non-https
+   connections to an https listener to stop being treated as https, to lose the
+   tls wrapper, and bind to role "raw-proxy" and protocol "myprotocol".  For
+   example, connect a browser on your external IP :443 as usual and it serves
+   as normal, but if you have configured the raw-proxy to portforward
+   127.0.0.1:22, then connecting your ssh client to your external port 443 will
+   instead proxy your sshd over :443 with no http or tls getting in the way.
+
+```
+       "listen-accept-role":           "raw-proxy",
+       "listen-accept-protocol":       "myprotocol",
+       "fallback-listen-accept":       "1",
+       "allow-non-tls":                "1"
+```
+
+3) A vhost wants to either redirect stray http traffic back to https, or to
+   actually serve http on an https listen socket (this is not recommended
+   since it allows anyone to drop the security assurances of https by
+   accident or design).
+
+```
+       "allow-non-tls":                "1",
+       "redirect-http":                "1",
+```
+
+...or,
+
+```
+       "allow-non-tls":                "1",
+       "allow-http-on-https":          "1",
+```
 
 @section lwswspl Lwsws Plugins
 
@@ -576,3 +677,26 @@ Prepare the log directory like this
        sudo mkdir /var/log/lwsws
        sudo chmod 700 /var/log/lwsws
 ```
+
+@section lwswsgdb Debugging lwsws with gdb
+
+Hopefully you won't need to debug lwsws itself, but you may want to debug your plugins.  start lwsws like this to have everything running under gdb
+
+```
+sudo gdb -ex "set follow-fork-mode child" -ex "run" --args /usr/local/bin/lwsws
+
+```
+
+this will give nice backtraces in lwsws itself and in plugins, if they were built with symbols.
+
+@section lwswsvgd Running lwsws under valgrind
+
+You can just run lwsws under valgrind as usual and get valid results.  However the results / analysis part of valgrind runs
+after the plugins have removed themselves, this means valgrind backtraces into plugin code is opaque, without
+source-level info because the dynamic library is gone.
+
+There's a simple workaround, use LD_PRELOAD=<plugin.so> before running lwsws, this has the loader bring the plugin
+in before executing lwsws as if it was a direct dependency.  That means it's still mapped until the whole process
+exits after valgtind has done its thing.
+
+
diff --git a/READMEs/README.plugin-acme.md b/READMEs/README.plugin-acme.md
new file mode 100644 (file)
index 0000000..8d3af4c
--- /dev/null
@@ -0,0 +1,180 @@
+lws-acme-client Plugin
+======================
+
+## Introduction
+
+lws-acme-client is a protcol plugin for libwebsockets that implements an
+ACME client able to communicate with let's encrypt and other certificate
+providers.
+
+It implements `tls-sni-01` challenge, and is able to provision tls certificates
+"from thin air" that are accepted by all the major browsers.  It also manages
+re-requesting the certificate when it only has two weeks left to run.
+
+It works with both the OpenSSL and mbedTLS backends.
+
+## Overview for use
+
+You need to:
+
+ - Provide name resolution to the IP with your server, ie, myserver.com needs to
+ resolve to the IP that hosts your server
+
+ - Enable port forwarding / external firewall access to your port, usually 443
+
+ - Enable the "lws-acme-client" plugin on the vhosts you want it to manage
+   certs for
+
+ - Add per-vhost options describing what should be in the certificate
+
+After that the plugin will sort everything else out.
+
+## Example lwsws setup
+
+```
+ "vhosts": [ {
+       "name":                    "home.warmcat.com",
+       "port":                    "443",
+        "host-ssl-cert":           "/etc/lwsws/acme/home.warmcat.com.crt.pem",
+        "host-ssl-key":            "/etc/lwsws/acme/home.warmcat.com.key.pem",
+        "ignore-missing-cert":     "1",
+       "access-log":              "/var/log/lwsws/test-access-log",
+        "ws-protocols": [{
+         "lws-acme-client": {
+           "auth-path":           "/etc/lwsws/acme/auth.jwk",
+           "cert-path":           "/etc/lwsws/acme/home.warmcat.com.crt.pem",
+           "key-path":            "/etc/lwsws/acme/home.warmcat.com.key.pem",
+           "directory-url":       "https://acme-staging.api.letsencrypt.org/directory",
+           "country":             "TW",
+           "state":               "Taipei",
+           "locality":            "Xiaobitan",
+           "organization":        "Crash Barrier Ltd",
+           "common-name":         "home.warmcat.com",
+           "email":               "andy@warmcat.com"
+         },
+         ...
+```
+
+## Required PVOs
+
+Notice that the `"host-ssl-cert"` and `"host-ssl-key"` entries have the same
+meaning as usual, they point to your certificate and private key.  However
+because the ACME plugin can provision these, you should also mark the vhost with
+`"ignore-missing-cert" : "1"`, so lwsws will ignore what will initially be
+missing certificate / keys on that vhost, and will set about creating the
+necessary certs and keys instead of erroring out.
+
+You must make sure the directories mentioned here exist, lws doesn't create them
+for you.  They should be 0700 root:root, even if you drop lws privileges.
+
+If you are implementing support in code, this corresponds to making sure the
+vhost creating `info.options` has the `LWS_SERVER_OPTION_IGNORE_MISSING_CERT`
+bit set.
+
+Similarly, in code, the each of the per-vhost options shown above can be
+provided in a linked-list of structs at vhost creation time.  See
+`./test-apps/test-server-v2.0.c` for example code for providing pvos.
+
+### auth-path
+
+This is where the plugin will store the auth keys it generated.
+
+### cert-path
+
+Where the plugin will store the certificate file.  Should match `host-ssl-cert`
+that the vhost wants to use.
+
+The path should include at least one 0700 root:root directory.
+
+### key-path
+
+Where the plugin will store the certificate keys.  Again it should match
+`host-ssl-key` the vhost is trying to use.
+
+The path should include at least one 0700 root:root directory.
+
+### directory-url
+
+This defines the URL of the certification server you will get your
+certificates from.  For let's encrypt, they have a "practice" one
+
+ - `https://acme-staging.api.letsencrypt.org/directory`
+
+and they have a "real" one
+
+ - `https://acme-v01.api.letsencrypt.org/directory`
+
+the main difference is the CA certificate for the real one is in most browsers
+already, but the staging one's CA certificate isn't.  The staging server will
+also let you abuse it more in terms of repeated testing etc.
+
+It's recommended you confirm expected operation with the staging directory-url,
+and then switch to the "real" URL.
+
+### common-name
+
+Your server DNS name, like "libwebsockets.org".  The remote ACME server will
+use this to find your server to perform the SNI challenges.
+
+### email
+
+The contact email address for the certificate.
+
+## Optional PVOs
+
+These are not included in the cert by letsencrypt
+
+### country
+
+Two-letter country code for the certificate
+
+### state
+
+State "or province" for the certificate
+
+### locality
+
+Locality for the certificate
+
+### organization
+
+Your company name
+
+## Security / Key storage considerations
+
+The `lws-acme-client` plugin is able to provision and update your certificate
+and keys in an entirely root-only storage environment, even though lws runs
+as a different uid / gid with no privileges to access the storage dir.
+
+It does this by opening and holding two WRONLY fds on "update paths" inside the
+root directory structure for each cert and key it manages; these are the normal
+cert and key paths with `.upd` appended.  If during the time the server is up
+the certs become within two weeks of expiry, the `lws-acme-client` plugin will
+negotiate new certs and write them to the file descriptors.
+
+Next time the server starts, if it sees `.upd` cert and keys, it will back up
+the old ones and copy them into place as the new ones, before dropping privs.
+
+To also handle the long-uptime server case, lws will update the vhost with the
+new certs using in-memory temporary copies of the cert and key after updating
+the cert.
+
+In this way the cert and key live in root-only storage but the vhost is kept up
+to date dynamically with any cert changes as well.
+
+## Multiple vhosts using same cert
+
+In the case you have multiple vhosts using of the same cert, just attach
+the `lws-acme-client` plugin to one instance.  When the cert updates, all the
+vhosts are informed and vhosts using the same filepath to access the cert will
+be able to update their cert.
+
+## Implementation point
+
+You will need to remove the auth keys when switching from OpenSSL to
+mbedTLS.  They will be regenerated automatically.  It's the file at this
+path:
+
+```
+"auth-path":      "/etc/lwsws/acme/auth.jwk",
+```
diff --git a/READMEs/README.plugin-sshd-base.md b/READMEs/README.plugin-sshd-base.md
new file mode 100644 (file)
index 0000000..65d67f8
--- /dev/null
@@ -0,0 +1,250 @@
+ssh-base Plugin
+================
+
+## Introduction
+
+lws-ssh-base is a protcol plugin for libwebsockets that implements a
+generic, abstract, ssh server.
+
+ - very small footprint in code and memory, takes up small part of ESP32
+ - written with security in mind: valgrind and Coverity -clean
+ - binds to one or more vhosts, that controls listen port(s)
+ - all IO and settings abstracted through a single "ops" struct from user code
+ - each instance on a vhost has its own "ops" struct, defining server keys,
+   auth method and functions to implement IO and other operations
+
+ - The plugin has no built-in behaviours like check ~/.ssh/authorized_keys,
+   treat auth usernames as system usernames, or spawn the user's shell.
+   Everything potentially dangerous is left to the user ops code to decide
+   how to handle.  It's NOT like sshd where running it implies it will accept
+   existing keys for any system user, will spawn a shell, etc, unless you
+   implement those parts in the ops callbacks.
+   
+ - The plugin requires extra code around it in the form of the ops struct
+   handlers.  So it's role is something like an abstract base class for an ssh
+   server.  All the crypto, protocol sequencing and state machine are inside,
+   but all the IO except the network connection is outside.
+   
+ - Built as part of libwebsockets, like all plugins may be dynamically loaded
+   at runtime or built statically.  Test app `libwebsockets-test-sshd` provided
+   
+ - Uses hash and RSA functions from either mbedTLS or OpenSSL automatically,
+   according to which library libwebsockets was built for
+
+To maintain its small size, it implements a single "best of breed" crypto for
+the following functions:
+
+|Function|Crypto|
+|---|---|
+|KEX|curve25519-sha256@libssh.org|
+|Server host key|ssh-rsa (4096b)|
+|Encryption|chacha20-poly1305@openssh.com|
+|Compression|None|
+
+## License
+
+lws-ssh-base is Free Software, available under libwebsocket's LGPLv2 +
+static linking exception license.
+
+The crypto parts are available elsewhere under a BSD license.  But for
+simplicity the whole plugin is under LGPLv2.
+
+## Generating your own keys
+
+```
+ $ ssh-keygen -t rsa -b 4096 -f mykeys
+```
+
+will ask for a passphrase and generate the private key in `mykeys` and the
+public key in `mykeys.pub`.  If you already have a suitable RSA key you use
+with ssh, you can just use that directly.
+
+lws installs a test keypair in /usr[/local]/share/libwebsockets-test-server
+that the test apps will accept.
+
+## Example code
+
+1) There's a working example app `libwebsockets-test-sshd` included that
+spawns a bash shell when an ssh client authenticates.  The username used on
+the remote ssh has no meaning, it spawns the shell under the credentials of
+"lws-test-sshd" was run under.  It accepts the lws ssh test key which is
+installed into /usr[/local]/share/libwebsockets-test-server.
+
+Start the server like this (it wants root only because the server key is stored
+in /etc)
+
+```
+ $ sudo libwebsockets-test-sshd
+```
+
+Connect to it using the test private key like this
+
+```
+ $ ssh -p 2200 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser@127.0.0.1
+```
+
+2) There's also a working example plugin `lws-sshd-demo` that "subclasses" the
+abstract `lws-ssh-base` plugin to make a protocol which can be used from,
+eg, lwsws.  For an lwsws vhost that listens on port 2222 and responds with
+the lws-sshd-demo ssh server, the related config is:
+
+```
+        {
+                "name": "sshd",
+                "port": "2222",
+                "onlyraw": "1",
+                "ws-protocols": [{
+                        "lws-ssh-base": {
+                                "status": "ok",
+                                "ops-from": "lws-sshd-demo"
+                        },
+                        "lws-sshd-demo": {
+                                "status": "ok",
+                                "raw": "1"
+                        }
+                }]
+        }
+```
+
+
+
+## Integration to other apps
+
+### Step 0: Build and install libwebsockets
+
+For the `libwebsockets-test-sshd` example, you will need CMake options
+`LWS_WITH_CGI`, since it uses lws helpers to spawn a shell.
+
+lws-ssh-base itself doesn't require CGI support in libwebsockets.
+
+### Step 1: make the code available in your app
+
+Include `lws-plugin-ssh-base` in your app, either as a runtime plugin or by using
+the lws static include scheme.
+
+To bring in the whole of the ssh-base plugin
+into your app in one step, statically, just include
+`plugins/ssh-base/include/lws-plugin-sshd-static-build-includes.h`, you can see
+an example of this in `./test-apps/test-sshd.c`.
+
+### Step 2: define your `struct lws_ssh_ops`
+
+`plugins/ssh-base/include/lws-plugin-ssh.h` defines
+`struct lws_ssh_ops` which is used for all customization and integration
+of the plugin per vhost.  Eg,
+
+```
+static const struct lws_ssh_ops ssh_ops = {
+       .channel_create                 = ssh_ops_channel_create,
+       .channel_destroy                = ssh_ops_channel_destroy,
+       .tx_waiting                     = ssh_ops_tx_waiting,
+       .tx                             = ssh_ops_tx,
+       .rx                             = ssh_ops_rx,
+       .get_server_key                 = ssh_ops_get_server_key,
+       .set_server_key                 = ssh_ops_set_server_key,
+       .set_env                        = ssh_ops_set_env,
+       .pty_req                        = ssh_ops_pty_req,
+       .child_process_io               = ssh_ops_child_process_io,
+       .child_process_terminated       = ssh_ops_child_process_terminated,
+       .exec                           = ssh_ops_exec,
+       .shell                          = ssh_ops_shell,
+       .is_pubkey_authorized           = ssh_ops_is_pubkey_authorized,
+       .banner                         = ssh_ops_banner,
+       .disconnect_reason              = ssh_ops_disconnect_reason,
+       .server_string                  = "SSH-2.0-Libwebsockets",
+       .api_version                    = 1,
+};
+```
+The `ssh_ops_...()` functions are your implementations for the operations
+needed by the plugin for your purposes.
+
+### Step 3: enable `lws-ssh-base` protocol to a vhost and configure using pvo
+
+A pointer to your struct lws_ssh_ops is passed into the vhost instance of the
+protocol using per-vhost options
+
+```
+static const struct lws_protocol_vhost_options pvo_ssh_ops = {
+       NULL,
+       NULL,
+       "ops",
+       (void *)&ssh_ops
+};
+
+static const struct lws_protocol_vhost_options pvo_ssh = {
+       NULL,
+       &pvo_ssh_ops,
+       "lws-sshd-base",
+       "" /* ignored, just matches the protocol name above */
+};
+
+...
+       info.port = 22;
+       info.options = LWS_SERVER_OPTION_ONLY_RAW;
+       info.vhost_name = "sshd";
+       info.protocols = protocols_sshd;
+       info.pvo = &pvo_ssh;
+
+       vh_sshd = lws_create_vhost(context, &info);
+```
+
+There are two possible pvos supported, "ops", shown above, directly passes the
+ops structure in using the value on the "ops" pvo.
+
+To support other protocols that want to provide ops to lws-ssh-base themselves
+for a particular vhost, you can also provide a pvo `"ops-from"` whose value is
+the name of the protocol also enabled on this vhost, whose protocol ".user"
+pointer points to the ops struct lws-ssh-base should use.
+
+## Integration to other plugins
+
+A worked example of using the abstract `lws-ssh-base` plugin from another
+plugin that provides the ops struct is in `./plugins/protocol_lws_sshd_demo`.
+
+The key points to note
+
+ - the plugin sets the ops struct for the vhost instantiation of `lws-ssh-base`
+ by passing a pointer to the ops struct in its `lws_protocols` struct `user`
+ member.
+ - the config for the vhost tells `lws-ssh-base` to pick up the ops struct
+ pointer using an "ops-from" pvo that indicates the protocol name.
+```
+                       "lws-ssh-base": {
+                                "status": "ok",
+                                "ops-from": "lws-sshd-demo"
+                        },
+```
+
+ - the config for the vhost tells lws this vhost only serves RAW (ie, no http)
+```
+         {
+                "name": "sshd",
+                "port": "2222",
+                "onlyraw": "1",
+                ...
+```
+
+ - the config for the vhost marks the protocol that uses `lws-ssh-base`, not
+ `lws-ssh-base` itself, as the protocol to be served for raw connections
+
+```
+                        "lws-sshd-demo": {
+                                "status": "ok",
+                                "raw": "1"
+                         ...
+```
+
+## Notes
+
+You can have the vhost it binds to listen on a nonstandard port.  The ssh
+commandline app cane be told to connect to a non-22 port with
+`ssh -p portnum user@hostname`
+
+
diff --git a/READMEs/README.porting.md b/READMEs/README.porting.md
new file mode 100644 (file)
index 0000000..7b9238c
--- /dev/null
@@ -0,0 +1,60 @@
+# Guidance for porting to new platform
+
+Where differences existed between the initial POSIX platform for lws and other
+supported platforms like Windows, `lws_plat_...()` apis were added to move
+handling to platform-specific code in `./lib/plat/`.
+
+Depending o which platform is built, different platform-specific implementations
+of these `lws_plat...()` apis get built.
+
+## 1) Prepare the cmake cross-build file if necessary
+
+CMake isolates its settings for cross-build into a separate file, which can be
+used to different cmake projects for the same platform as well.
+
+Find a similar examples already in `./contrib/cross-*` and copy and adapt it
+as needed,
+
+All settings related to toolchain should go in there.  For cross-toolchain,
+the convention is to pass the path to its installed directory in `CROSS_PATH`
+environment variable.
+
+## 2) Copy the closest platform dir in ./lib/plat
+
+Wholesale copy the closest existing platform dir to `/lib/plat/myplatform` and
+rename the files.
+
+Remove stuff specific to the original platform.
+
+## 3) Add a flag in CMakeLists.txt
+
+Cut and paste a flag to select your platform, preferably `LWS_PLAT_MYPLATFORM` or so
+
+## 4) Add a section to force-select and deselect other cmake options based on platform flag
+
+Some options on by default may not make sense on your platform, and others off
+by default may be mandatory.  After the options() section in CMakeLists.txt, you
+can use this kind of structure
+
+```
+       if (LWS_PLAT_MYPLATFORM)
+               set(LWS_WITH_XXXX 0)
+       endif()
+```
+
+to enforce implicit requirements of your platform.  Optional stuff should be set by
+running cmake commandline as usual.
+
+## 5) Add building your platform files into CMakeLists.txt
+
+Add entries in CMakeLists.txt for building stuff in `./lib/plat/myplatform` when
+`LWS_PLAT_MYPLATFORM` is enabled.
+
+## 6) Adapt your copied ./lib/plat/myplatform/ files
+
+You can now do test builds using the cross-build file, your platform flag in
+cmake, and your copied ./lib/plat content... this last part since it was
+copied from another platform will initially be a plentiful source of errors.
+
+You can iteratively build and adapt the platform files.
+
diff --git a/READMEs/README.problems.md b/READMEs/README.problems.md
new file mode 100644 (file)
index 0000000..6d12f6f
--- /dev/null
@@ -0,0 +1,66 @@
+Debugging problems
+==================
+
+Check it's still a problem with latest lws
+------------------------------------------
+
+Older versions of lws don't attract any new work after they are released
+(see [the release policy](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.release-policy.md) for details);
+for a while they will get backported bugfixes but that's it.
+
+All new work and bugfixes happen on master branch.
+
+Old, old versions may be convenient for you to use for some reason.  But unless
+you pay for support or have contributed work to lws so we feel we owe you some
+consideration, nobody else has any reason to particularly care about solving
+issues on ancient versions.  Whereas if the problem exists on master, and can be
+reproduced by developers, it usually gets attention, often immediately.
+
+If the problem doesn't exist on master, you can either use master or check also
+the -stable branch of the last released version to see if it was already solved
+there.
+
+Library is a component
+----------------------
+
+As a library, lws is always just a component in a bigger application.
+
+When users have a problem involving lws, what is happening in the bigger
+application is usually critical to understand what is going on (and where the
+solution lies).  Sometimes access to the remote peer like server or client is also
+necessary to provoke the symptom.  Sometimes, the problem is in lws, but
+sometimes the problem is not in lws but in these other pieces.
+
+Many users are able to share their sources, but others decide not to, for
+presumed "commercial advantage" or whatever.  (In any event, it can be painful
+looking through large chunks of someone else's sources for problems when that
+is not the library author's responsibility.)
+
+This makes answering questions like "what is wrong with my code I am not
+going to show you?" or even "what is wrong with my code?" very difficult.
+
+Even if it's clear there is a problem somewhere, it cannot be understood or
+reproduced by anyone else if it needs user code that isn't provided.
+
+The biggest question is, "is this an lws problem actually"?  To solve that
+the best solution is to strip out all or as much user code as possible,
+and see if the problem is still coming.
+
+
+Use the test apps / minimal examples as sanity checks
+-----------------------------------------------------
+
+The test server and client, and any more specifically relevant minimal example
+ are extremely useful for sanity checks and debugging guidance.
+
+ - **test apps work on your platform**, then either
+   - your user code is broken, align it to how the test apps work, or,
+   - something from your code is required to show an lws problem, provide a
+     minimal patch on a test app so it can be reproduced
+     
+ - **test apps break on your platform**, but work on, eg, x86_64, either
+   - toolchain or platform-specific (eg, OS) issue, or
+   - lws platform support issue
+
+ - **test apps break everywhere**
+   - sounds like lws problem, info to reproduce and / or a patch is appreciated
diff --git a/READMEs/README.release-policy.md b/READMEs/README.release-policy.md
new file mode 100644 (file)
index 0000000..6fb457a
--- /dev/null
@@ -0,0 +1,88 @@
+# lws release policy
+
+## Master branch
+
+Master branch is the default and all new work happens there.  It's unstable and
+subject to history rewrites, patches moving about and being squashed etc.  In
+terms of it working, it is subject to passing CI tests including a battery of
+runtime tests, so if it is passing CI as it usually is then it's probably in
+usable shape.
+
+![all work happens on master](../doc-assets/lws-relpol-1.svg)
+
+If you have patches (you are a hero) they should be targeted at master.
+
+To follow such a branch, `git pull` is the wrong tool... the starting point
+of what you currently have may no longer exist remotely due to rearranging the
+patches there.  Instead use a flow like this:
+
+```
+ $ git fetch https://libwebsockets.org/repo/libwebsockets +master:m && git reset --hard m
+```
+
+This fetches current remote master into local branch `m`, and then forces your
+local checkout to exactly match `m`.  This replaces your checked-out tree
+including any of your local changes, so stash those first, or use stgit or so
+to pop them before updating your basis against lws master.
+
+## Stable branches
+
+Master is very useful for coordinating development, and integrating WIP,
+but for distros or integration into large user projects some stability is often
+more desirable than the latest development work.
+
+Periodically, when master seems in good shape and various new developments seem
+to be working, it's copied out into a versioned stable branch, like `v3.0-stable`.
+
+![stable branches are copied from master](../doc-assets/lws-relpol-2.svg)
+
+The initial copy is tagged with, eg, `v3.0.0`.
+
+(At that time, master's logical version is set to "...99", eg, `v3.0.99` so
+version comparisons show that version of master is "later" than any other
+v3.0 version, which will never reach 99 point releases itself, but "earlier"
+than, eg, v3.1.)
+
+## Backport policy
+
+Work continues on master, and as part of that usually bugs are reported and / or
+fixes found that may apply not just to current master, but the version of master
+that was copied to form the last -stable branch.
+
+In that case, the patch may be backported to the last stable branch to also fix
+the bug there.  In the case of refactors or major internal improvements, these
+typically do not get backported.
+
+This applies only to fixes and public API-neutral internal changes to lws... if
+new features were backported or API changes allowed, then there would be
+multiple apis under the same version name and library soname, which is
+madness.
+
+When new stable releases are made, the soname is bumped reflecting the API is
+different than that of previous versions.
+
+![backports from master to stable](../doc-assets/lws-relpol-3.svg)
+
+If there is something you need in a later lws version that is not backported,
+you need to either backport it yourself (remember that lws is LGPL and you must
+provide your changes when you distribute the binary) or use a later lws version.
+Using a more recent version of lws is almost always the correct way.
+
+## Stable point releases
+
+Periodically fix patches pile up on the -stable branch and are tagged out as
+"point releases".  So if the original stable release was "v3.0.0", the point
+release may be "v3.0.1".
+
+![point releases of stable](../doc-assets/lws-relpol-4.svg)
+
+## Critical fixes
+
+Sometimes a bug is found and fixed that had been hiding for a few versions.
+If the bug has some security dimension or is otherwise important, we may
+backport it to a few recent releases, not just the last one.  This is pretty
+uncommon though.
+
+![backport to multiple stable branches](../doc-assets/lws-relpol-5.svg)
+
+
similarity index 75%
rename from README.test-apps.md
rename to READMEs/README.test-apps.md
index 06854e0..7565b43 100644 (file)
@@ -2,7 +2,7 @@ Overview of lws test apps
 =========================
 
 Are you building a client?  You just need to look at the test client
-[libwebsockets-test-client](test-server/test-client.c).
+[libwebsockets-test-client](../test-apps/test-client.c).
 
 If you are building a standalone server, there are three choices, in order of
 preferability.
@@ -13,12 +13,12 @@ Lws provides a generic web server app that can be configured with JSON
 config files.  https://libwebsockets.org itself uses this method.
 
 With lwsws handling the serving part, you only need to write an lws protocol
-plugin.  See [plugin-standalone](plugin-standalone) for an example of how
+plugin.  See [plugin-standalone](../plugin-standalone) for an example of how
 to do that outside lws itself, using lws public apis.
 
  $ cmake .. -DLWS_WITH_LWSWS=1
 
-See [README.lwsws.md](README.lwsws.md) for information on how to configure
+See [README.lwsws.md](../READMEs/README.lwsws.md) for information on how to configure
 lwsws.
 
 NOTE this method implies libuv is used by lws, to provide crossplatform
@@ -28,11 +28,13 @@ implementations of timers, dynamic lib loading etc for plugins and lwsws.
 
 This method lets you configure web serving in code, instead of using lwsws.
 
-Plugins are still used, which implies libuv needed.
+Plugins are still used, but you have a choice whether to dynamically load
+them or statically include them.  In this example, they are dynamically
+loaded.
 
  $ cmake .. -DLWS_WITH_PLUGINS=1
 
-See [test-server-v2.0.c](test-server/test-server-v2.0.c)
+See [test-server-v2.0.c](../test-apps/test-server-v2.0.c)
 
 3) protocols in the server app
 
@@ -43,13 +45,23 @@ combined code is all squidged together and is much less maintainable.
 This method is still supported in lws but all ongoing and future work is
 being done in protocol plugins only.
 
+You can simply include the plugin contents and have it buit statically into
+your server, just define this before including the plugin source
+
+```
+#define LWS_PLUGIN_STATIC
+```
+
+This gets you most of the advantages without needing dynamic loading +
+libuv.
+
 
 Notes about lws test apps
 =========================
 
 @section tsb Testing server with a browser
 
-If you run [libwebsockets-test-server](test-server/test-server.c) and point your browser
+If you run [libwebsockets-test-server](../test-apps/test-server.c) and point your browser
 (eg, Chrome) to
 
        http://127.0.0.1:7681
@@ -74,7 +86,7 @@ terminates.
 
 To stop the daemon, do
 ```
-       $ kill cat /tmp/.lwsts-lock 
+       $ kill \`cat /tmp/.lwsts-lock\`
 ```
 If it finds a stale lock (the pid mentioned in the file does not exist
 any more) it will delete the lock and create a new one during startup.
@@ -82,6 +94,60 @@ any more) it will delete the lock and create a new one during startup.
 If the lock is valid, the daemon will exit with a note on stderr that
 it was already running.
 
+@section clicert Testing Client Certs
+
+Here is a very quick way to create a CA, and a client and server cert from it,
+for testing.
+
+```
+$ cp -rp ./scripts/client-ca /tmp
+$ cd /tmp/client-ca
+$ ./create-ca.sh
+$ ./create-server-cert.sh server
+$ ./create-client-cert.sh client
+```
+
+The last step wants an export password, you will need this password again to
+import the p12 format certificate into your browser.
+
+This will get you the following
+
+|name|function|
+|----|--------|
+|ca.pem|Your Certificate Authority cert|
+|ca.key|Private key for the CA cert|
+|client.pem|Client certificate, signed by your CA|
+|client.key|Client private key|
+|client.p12|combined client.pem + client.key in p12 format for browsers|
+|server.pem|Server cert, signed by your CA|
+|server.key|Server private key|
+
+You can confirm yourself the client and server certs are signed by the CA.
+
+```
+ $ openssl verify -verbose -trusted ca.pem server.pem
+ $ openssl verify -verbose -trusted ca.pem client.pem
+```
+
+Import the client.p12 file into your browser.  In FFOX57 it's
+
+ - preferences
+ - Privacy & Security
+ - Certificates | View Certificates
+ - Certificate Manager | Your Certificates | Import...
+ - Enter the password you gave when creating client1.p12
+ - Click OK.
+
+You can then run the test server like this:
+
+```
+ $ libwebsockets-test-server -s -A ca.pem -K server.key -C server.pem -v
+```
+
+When you connect your browser to https://localhost:7681 after accepting the
+selfsigned server cert, your browser will pop up a prompt to send the server
+your client cert (the -v switch enables this).  The server will only accept
+a client cert that has been signed by ca.pem.
 
 @section sssl Using SSL on the server side
 
@@ -100,7 +166,7 @@ certificates in the browser and the connection will proceed
 in first https and then websocket wss, acting exactly the
 same.
 
-[test-server.c](test-server/test-server.c) is all that is needed to use libwebsockets for
+[test-server.c](../test-apps/test-server.c) is all that is needed to use libwebsockets for
 serving both the script html over http and websockets.
 
 @section lwstsdynvhost Dynamic Vhosts
@@ -111,6 +177,28 @@ to toggle the creation and destruction of an identical second vhost on port + 1.
 This is intended as a test and demonstration for how to bring up and remove
 vhosts dynamically.
 
+@section unixskt Testing Unix Socket Server support
+
+Start the test server with -U and the path to create the unix domain socket
+
+```
+ $ libwebsockets-test-server -U /tmp/uds
+```
+
+On exit, lws will delete the socket inode.
+
+To test the client side, eg
+
+```
+ $ nc -C -U /tmp/uds -i 30
+```
+
+and type
+
+`GET / HTTP/1.1`
+
+followed by two ENTER.  The contents of test.html should be returned.
+
 @section wscl Testing websocket client support
 
 If you run the test server as described above, you can also
@@ -150,30 +238,6 @@ For those two options libuv is needed to support the protocol plugins, if
 that's not possible then the other variations with their own protocol code
 should be considered.
 
-
-@section echo Testing simple echo
-
-You can test against `echo.websockets.org` as a sanity test like
-this (the client connects to port `80` by default):
-
-```
-       $ libwebsockets-test-echo --client echo.websocket.org
-```
-
-This echo test is of limited use though because it doesn't
-negotiate any protocol.  You can run the same test app as a
-local server, by default on localhost:7681
-```
-       $ libwebsockets-test-echo
-```
-and do the echo test against the local echo server
-```
-       $ libwebsockets-test-echo --client localhost --port 7681
-```
-If you add the `--ssl` switch to both the client and server, you can also test
-with an encrypted link.
-
-
 @section tassl Testing SSL on the client side
 
 To test SSL/WSS client action, just run the client test with
@@ -327,8 +391,8 @@ version 13.
 
 Since libwebsockets runs using `poll()` and a single threaded approach, any
 unexpected latency coming from system calls would be bad news.  There's now
-a latency tracking scheme that can be built in with `--with-latency` at
-configure-time, logging the time taken for system calls to complete and if
+a latency tracking scheme that can be built in with `-DLWS_WITH_LATENCY=1` at
+cmake, logging the time taken for system calls to complete and if
 the whole action did complete that time or was deferred.
 
 You can see the detailed data by enabling logging level 512 (eg, `-d 519` on
@@ -348,37 +412,29 @@ treatment to the other app during that call.
 
 @section autobahn Autobahn Test Suite
 
-Lws can be tested against the autobahn websocket fuzzer.
+Lws can be tested against the autobahn websocket fuzzer in both client and
+server modes
 
 1) pip install autobahntestsuite
 
-2) wstest -m fuzzingserver
-
-3) Run tests like this
-
-libwebsockets-test-echo --client localhost --port 9001 -u "/runCase?case=20&agent=libwebsockets" -v -d 65535 -n 1
+2) From your build dir:
 
-(this runs test 20)
-
-4) In a browser, go here
-
-http://localhost:8080/test_browser.html
+```
+ $ cmake .. -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_MINIMAL_EXAMPLES=1 && make
+```
 
-fill in "libwebsockets" in "User Agent Identifier" and press "Update Reports (Manual)"
+3) ../scripts/autobahn-test.sh
 
-5) In a browser go to the directory you ran wstest in (eg, /projects/libwebsockets)
+4) In a browser go to the directory you ran wstest in (eg, /projects/libwebsockets)
 
-file:///projects/libwebsockets/reports/clients/index.html
+file:///projects/libwebsockets/build/reports/clients/index.html
 
 to see the results
 
 
 @section autobahnnotes Autobahn Test Notes
 
-1) Autobahn tests the user code + lws implementation.  So to get the same
-results, you need to follow test-echo.c in terms of user implementation.
-
-2) Two of the tests make no sense for Libwebsockets to support and we fail them.
+1) Two of the tests make no sense for Libwebsockets to support and we fail them.
 
  - Tests 2.10 + 2.11: sends multiple pings on one connection.  Lws policy is to
 only allow one active ping in flight on each connection, the rest are dropped.
@@ -386,5 +442,7 @@ The autobahn test itself admits this is not part of the standard, just someone's
 random opinion about how they think a ws server should act.  So we will fail
 this by design and it is no problem about RFC6455 compliance.
 
+2) Currently two parts of autobahn are broken and we skip them
+
+https://github.com/crossbario/autobahn-testsuite/issues/71
  
diff --git a/READMEs/README.unix-domain-reverse-proxy.md b/READMEs/README.unix-domain-reverse-proxy.md
new file mode 100644 (file)
index 0000000..1db8abd
--- /dev/null
@@ -0,0 +1,101 @@
+## Unix Domain Sockets Reverse Proxy
+
+### Introduction
+
+lws is able to use a mount to place reverse proxies into the URL space.
+
+These are particularly useful when using Unix Domain Sockets, basically
+files in the server filesystem, to communicate between lws and a separate
+server process and integrate the result into a coherent URL namespace on
+the lws side.  It's also possible to proxy using tcp sockets.
+
+![overview](../doc-assets/http-proxy-overview.svg)
+
+This has the advantage that the actual web server that forwards the
+data from the unix socket owner is in a different process than the server
+that serves on the unix socket.  If it has problems, they do not affect
+the actual public-facing web server.  The unix domain socket server may
+be in a completely different language than the web server.
+
+Compared to CGI, there are no forks to make a connection to the unix
+domain socket server.
+
+### Mount origin format
+
+Unix Domain Sockets are effectively "files" in the server filesystem, and
+are defined by their filepath.  The "server" side that is to be proxied opens
+the socket and listens on it, which creates a file in the server filesystem.
+The socket understands either http or https protocol.
+
+Lws can be told to act as a proxy for that at a mountpoint in the lws vhost
+url space.
+
+If your mount is expressed in C code, then the mount type is LWSMPRO_HTTP or
+LWSMPRO_HTTPS depending on the protocol the unix socket understands, and the
+origin address has the form `+/path/to/unix/socket:/path/inside/mount`.
+
+The + at the start indicates it is a local unix socket we are proxying, and
+the ':' acts as a delimiter for the socket path, since unlike other addresses
+the unix socket path can contain '/' itself.
+
+### Connectivity rules and translations
+
+Onward proxy connections from lws to the Unix Domain Socket happen using
+http/1.1.  That implies `transfer-encoding: chunking` in the case that the
+length of the output is not known beforehand.
+
+Lws takes care of stripping any chunking (which is illegal in h2) and
+translating between h1 and h2 header formats if the return connection is
+actually in http/2.
+
+The h1 onward proxy connection translates the following headers from the return
+connection, which may be h1 or h2:
+
+Header|Function
+---|---
+host|Which vhost
+etag|Information on any etag the client has cached for this URI
+if-modified-since|Information on the freshness of any etag the client has cached for this URI
+accept-language|Which languages the return path client prefers
+accept-encoding|Which compression encodings the client can accept
+cache-control|Information from the return path client about cache acceptability
+x-forwarded-for|The IP address of the return path client
+
+This implies that the proxied connection can
+
+ - return 301 etc to say the return path client's etag is still valid
+
+ - choose to compress using an acceptable content-encoding
+
+The following headers are translated from the headers replied via the onward
+connection (always h1) back to the return path (which may be h1 or h2)
+
+Header|Function
+---|---
+content-length|If present, an assertion of how much payload is expected
+content-type|The mimetype of the payload
+etag|The canonical etag for the content at this URI
+accept-language|This is returned to the return path client because there is no easy way for the return path client to know what it sent originally.  It allows clientside selection of i18n.
+content-encoding|Any compression format on the payload (selected from what the client sent in accept-encoding, if anything)
+cache-control|The onward server's response about cacheability of its payload
+
+### h1 -> h2 conversion
+
+Chunked encoding that may have been used on the outgoing proxy client connection
+is removed for h2 return connections (chunked encoding is illegal for h2).
+
+Headers are converted to all lower-case and hpack format for h2 return connections.
+
+Header and payload proxying is staged according to when the return connection
+(which may be an h2 child stream) is writable.
+
+### Behaviour if unix domain socket server unavailable
+
+If the server that listens on the unix domain socket is down or being restarted,
+lws understands that it couldn't connect to it and returns a clean 503 response
+`HTTP_STATUS_SERVICE_UNAVAILABLE` along with a brief human-readable explanation.
+
+The generated status page produced will try to bring in a stylesheet
+`/error.css`.  This allows you to produce a styled error pages with logos,
+graphics etc.  See [this](https://libwebsockets.org/git/badrepo) for an example of what you can do with it.
+
diff --git a/READMEs/README.vulnerability-reporting.md b/READMEs/README.vulnerability-reporting.md
new file mode 100644 (file)
index 0000000..ae06435
--- /dev/null
@@ -0,0 +1,12 @@
+## Vulnerability Reporting
+
+If you become aware of an issue with lws that has a security
+dimension for users, please contact `andy@warmcat.com` by
+direct email.
+
+## Procedure for announcing vulnerability fixes
+
+The problem and fixed versions will be announced on the
+libwebsockets mailing list and a note added to the master
+README.md.
+
diff --git a/READMEs/mainpage.md b/READMEs/mainpage.md
new file mode 100644 (file)
index 0000000..2c6f180
--- /dev/null
@@ -0,0 +1,23 @@
+##Libwebsockets API introduction
+
+Libwebsockets covers a lot of interesting features for people making embedded servers or clients
+
+ - HTTP(S) serving and client operation
+ - HTTP/2 support for serving and client operation
+ - 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 / 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
+
+Please note you just need in include libwebsockets.h.  It includes all the individual
+includes in /usr/include/libwebsockets/ itself.
+
+You can browse by api category <a href="modules.html">here</a>
+
+A collection of READMEs for build, coding, lwsws etc are <a href="pages.html">here</a>
+
diff --git a/READMEs/release-checklist b/READMEs/release-checklist
new file mode 100644 (file)
index 0000000..e142130
--- /dev/null
@@ -0,0 +1,85 @@
+Release Checklist
+-----------------
+
+1) non-CI QA
+ a) valgrind test servers + client + browser
+
+2) soname bump?
+
+ a) We need one if we added / changed / removed apis
+
+  - CMakeLists.txt
+
+   set(SOVERSION "6")
+
+  - scripts/libwebsockets.spec
+
+  -/%{_libdir}/libwebsockets.so.6
+  +/%{_libdir}/libwebsockets.so.7
+
+3) changelog
+
+ a) Add next version tag header.
+
+ b) Classify as
+
+    - NEW
+    - CHANGE
+    - REMOVE
+
+4) main version bump
+
+  - CMakeLists.txt
+
+   set(CPACK_PACKAGE_VERSION_MAJOR "1")
+   set(CPACK_PACKAGE_VERSION_MINOR "6")
+   set(CPACK_PACKAGE_VERSION_PATCH "0")
+
+5) specfile
+
+ a) rpm version bump to match CMake one
+
+  scripts/libwebsockets.spec
+
+   Version: 1.6.0
+
+ b) Summarize changelog
+
+  scripts/libwebsockets.spec
+
+%changelog
+* Sun Jan 17 2016 Andrew Cooks <acooks@linux.com> 1.6.4-1
+- Bump version to 1.6.4
+- MINOR fix xyz
+
+ c) Use -DLWS_WITH_DISTRO_RECOMMENDED=1 then make package and adapt the .spec
+    to match the file list
+
+6) Announce latest version on README.md
+
+7) Make sure all new READMEs and public headers are in libwebsockets.dox
+
+8) signed tag
+
+  git tag -s vX.Y[.Z]
+
+9) git
+
+ a) push
+ b) final CI check, if fail delete tag, kill pushed tags, restart flow
+
+10) website
+
+ a) update latest tag for release branch
+
+11) post-relase version bump
+
+Bump the PATCH part of the version to 99
+
+-set(CPACK_PACKAGE_VERSION_PATCH "0")
++set(CPACK_PACKAGE_VERSION_PATCH "99")
+
+to reflect it's newer than any stable release but not a new version yet.
+
index 748ab1d..809ff62 100644 (file)
@@ -1,5 +1,11 @@
 environment:
   matrix:
+    - LWS_METHOD: jose
+      CMAKE_ARGS: -DLWS_WITH_JOSE=1
+
+    - LWS_METHOD: x64
+      CMAKE_ARGS: -DCMAKE_GENERATOR_PLATFORM=x64 -DLWS_WITH_HTTP2=1 -DLWS_WITH_PLUGINS=1 -DLIBUV_INCLUDE_DIRS=C:\assets\libuv64\include -DLIBUV_LIBRARIES=C:\assets\libuv64\libuv.lib
+
     - LWS_METHOD: lwsws
       CMAKE_ARGS: -DLWS_WITH_LWSWS=1 -DSQLITE3_INCLUDE_DIRS=C:\assets\sqlite3 -DSQLITE3_LIBRARIES=C:\assets\sqlite3\sqlite3.lib -DLIBUV_INCLUDE_DIRS=C:\assets\libuv\include -DLIBUV_LIBRARIES=C:\assets\libuv\libuv.lib
 
@@ -16,21 +22,21 @@ environment:
 
     - LWS_METHOD: nossl
       CMAKE_ARGS: -DLWS_WITH_SSL=OFF
+
 install:
   - appveyor DownloadFile https://libwebsockets.org:444/win-libuv.zip
   - mkdir c:\assets
   - mkdir c:\assets\libuv
   - 7z x -oc:\assets\libuv win-libuv.zip
-#  - appveyor DownloadFile https://slproweb.com/download/Win32OpenSSL-1_0_2h.exe
-#  - appveyor DownloadFile https://libwebsockets.org:444/Win32OpenSSL-1_0_2L.exe
-#  - Win32OpenSSL-1_0_2L.exe /silent /verysilent /sp- /suppressmsgboxes
+  - appveyor DownloadFile https://libwebsockets.org:444/win-libuv64.zip
+  - mkdir c:\assets\libuv64
+  - 7z x -oc:\assets\libuv64 win-libuv64.zip
   - appveyor DownloadFile https://libwebsockets.org:444/nsis-3.0rc1-setup.exe
   - cmd /c start /wait nsis-3.0rc1-setup.exe /S /D=C:\nsis
   - appveyor DownloadFile https://libwebsockets.org:444/sqlite-dll-win32-x86-3130000.zip
   - mkdir c:\assets\sqlite3
   - 7z x -oc:\assets\sqlite3 sqlite-dll-win32-x86-3130000.zip
   - SET PATH=C:\Program Files\NSIS\;C:\Program Files (x86)\NSIS\;c:\nsis;%PATH%
-build:
 
 build_script:
   - md build
@@ -38,21 +44,34 @@ build_script:
   - cmake -DCMAKE_BUILD_TYPE=Release %CMAKE_ARGS% ..
   - cmake --build . --config Release
 
-# TODO: Keeps breaking Windows build, should be rewritten using CPack properly instead...
 after_build:
-   - 7z a lws.zip %APPVEYOR_BUILD_FOLDER%\build\lib\Release\websockets.lib %APPVEYOR_BUILD_FOLDER%\build\lib\Release\websockets.exp %APPVEYOR_BUILD_FOLDER%\build\bin\Release\websockets.dll %APPVEYOR_BUILD_FOLDER%\lib\libwebsockets.h  %APPVEYOR_BUILD_FOLDER%\build\lws_config.h  %APPVEYOR_BUILD_FOLDER%\build\bin\Release\*.exe
-#  - cd ..
-#  - cd win32port
-#  - makensis -DVERSION=%APPVEYOR_BUILD_VERSION% libwebsockets.nsi
-
+  - cd %APPVEYOR_BUILD_FOLDER%
+  - mkdir staging
+  - mkdir staging\include
+  - cp -r %APPVEYOR_BUILD_FOLDER%\build\bin %APPVEYOR_BUILD_FOLDER%\build\lib staging
+  - if EXIST staging\bin\share mv staging\bin\share staging
+  - if NOT EXIST staging\share\libwebsockets-test-server mkdir staging\share\libwebsockets-test-server
+  - IF EXIST %APPVEYOR_BUILD_FOLDER%\build\libwebsockets-test-server.pem cp %APPVEYOR_BUILD_FOLDER%\build\libwebsockets-test-server.pem staging\share\libwebsockets-test-server
+  - IF EXIST %APPVEYOR_BUILD_FOLDER%\build\libwebsockets-test-server.key.pem cp %APPVEYOR_BUILD_FOLDER%\build\libwebsockets-test-server.key.pem staging\share\libwebsockets-test-server
+  - IF EXIST %APPVEYOR_BUILD_FOLDER%\build\lws_config.h cp %APPVEYOR_BUILD_FOLDER%\build\lws_config.h staging\include
+  - cp %APPVEYOR_BUILD_FOLDER%\include\libwebsockets.h staging\include
+  - cp -r %APPVEYOR_BUILD_FOLDER%\include\libwebsockets staging\include
+  - 7z a build\lws-%LWS_METHOD%-%APPVEYOR_BUILD_ID%.zip %APPVEYOR_BUILD_FOLDER%\staging\*
 
 artifacts:
-  - path: lws.zip
-    name: lws.zip
-    type: Zip
+  - path: build\lws-%LWS_METHOD%-%APPVEYOR_BUILD_ID%.zip
 
-    #cache:
-    #  - C:\OpenSSL-Win32
+#deploy:
+#- provider: BinTray
+#  username: lws-team
+#  api_key:
+#    secure: nDpZ7P/wrk98DwJPMC6KpCC23QrVP8f3RxvKzBaqOmb9LiVrg1IyO1cc5vcgShZC
+#  subject: lws-team
+#  repo: libwebsockets
+#  package: windows
+#  publish: true
+#  override: true
+#  explode: false
 
 matrix:
   fast_finish: true
diff --git a/autobahn-test.sh b/autobahn-test.sh
deleted file mode 100755 (executable)
index 91b9171..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-
-set -u
-
-N=1
-OS=`uname`
-
-for i in '1.1.1' '1.1.2' '1.1.3' '1.1.4' '1.1.5' '1.1.6' '1.1.7' '1.1.8' '1.2.1' '1.2.2' '1.2.3' '1.2.4' '1.2.5' '1.2.6' '1.2.7' '1.2.8' '2.1' '2.2' '2.3' '2.4' '2.5' '2.6' '2.7' '2.8' '2.9' '2.10' '2.11' '3.1' '3.2' '3.3' '3.4' '3.5' '3.6' '3.7' '4.1.1' '4.1.2' '4.1.3' '4.1.4' '4.1.5' '4.2.1' '4.2.2' '4.2.3' '4.2.4' '4.2.5' '5.1' '5.2' '5.3' '5.4' '5.5' '5.6' '5.7' '5.8' '5.9' '5.10' '5.11' '5.12' '5.13' '5.14' '5.15' '5.16' '5.17' '5.18' '5.19' '5.20' '6.1.1' '6.1.2' '6.1.3' '6.2.1' '6.2.2' '6.2.3' '6.2.4' '6.3.1' '6.3.2' '6.4.1' '6.4.2' '6.4.3' '6.4.4' '6.5.1' '6.5.2' '6.5.3' '6.5.4' '6.5.5' '6.6.1' '6.6.2' '6.6.3' '6.6.4' '6.6.5' '6.6.6' '6.6.7' '6.6.8' '6.6.9' '6.6.10' '6.6.11' '6.7.1' '6.7.2' '6.7.3' '6.7.4' '6.8.1' '6.8.2' '6.9.1' '6.9.2' '6.9.3' '6.9.4' '6.10.1' '6.10.2' '6.10.3' '6.11.1' '6.11.2' '6.11.3' '6.11.4' '6.11.5' '6.12.1' '6.12.2' '6.12.3' '6.12.4' '6.12.5' '6.12.6' '6.12.7' '6.12.8' '6.13.1' '6.13.2' '6.13.3' '6.13.4' '6.13.5' '6.14.1' '6.14.2' '6.14.3' '6.14.4' '6.14.5' '6.14.6' '6.14.7' '6.14.8' '6.14.9' '6.14.10' '6.15.1' '6.16.1' '6.16.2' '6.16.3' '6.17.1' '6.17.2' '6.17.3' '6.17.4' '6.17.5' '6.18.1' '6.18.2' '6.18.3' '6.18.4' '6.18.5' '6.19.1' '6.19.2' '6.19.3' '6.19.4' '6.19.5' '6.20.1' '6.20.2' '6.20.3' '6.20.4' '6.20.5' '6.20.6' '6.20.7' '6.21.1' '6.21.2' '6.21.3' '6.21.4' '6.21.5' '6.21.6' '6.21.7' '6.21.8' '6.22.1' '6.22.2' '6.22.3' '6.22.4' '6.22.5' '6.22.6' '6.22.7' '6.22.8' '6.22.9' '6.22.10' '6.22.11' '6.22.12' '6.22.13' '6.22.14' '6.22.15' '6.22.16' '6.22.17' '6.22.18' '6.22.19' '6.22.20' '6.22.21' '6.22.22' '6.22.23' '6.22.24' '6.22.25' '6.22.26' '6.22.27' '6.22.28' '6.22.29' '6.22.30' '6.22.31' '6.22.32' '6.22.33' '6.22.34' '6.23.1' '6.23.2' '6.23.3' '6.23.4' '6.23.5' '6.23.6' '6.23.7' '7.1.1' '7.1.2' '7.1.3' '7.1.4' '7.1.5' '7.1.6' '7.3.1' '7.3.2' '7.3.3' '7.3.4' '7.3.5' '7.3.6' '7.5.1' '7.7.1' '7.7.2' '7.7.3' '7.7.4' '7.7.5' '7.7.6' '7.7.7' '7.7.8' '7.7.9' '7.7.10' '7.7.11' '7.7.12' '7.7.13' '7.9.1' '7.9.2' '7.9.3' '7.9.4' '7.9.5' '7.9.6' '7.9.7' '7.9.8' '7.9.9' '7.9.10' '7.9.11' '7.9.12' '7.9.13' '7.13.1' '7.13.2' '9.1.1' '9.1.2' '9.1.3' '9.1.4' '9.1.5' '9.1.6' '9.2.1' '9.2.2' '9.2.3' '9.2.4' '9.2.5' '9.2.6' '9.3.1' '9.3.2' '9.3.3' '9.3.4' '9.3.5' '9.3.6' '9.3.7' '9.3.8' '9.3.9' '9.4.1' '9.4.2' '9.4.3' '9.4.4' '9.4.5' '9.4.6' '9.4.7' '9.4.8' '9.4.9' '9.5.1' '9.5.2' '9.5.3' '9.5.4' '9.5.5' '9.5.6' '9.6.1' '9.6.2' '9.6.3' '9.6.4' '9.6.5' '9.6.6' '9.7.1' '9.7.2' '9.7.3' '9.7.4' '9.7.5' '9.7.6' '9.8.1' '9.8.2' '9.8.3' '9.8.4' '9.8.5' '9.8.6' '10.1.1' '12.1.1' '12.1.2' '12.1.3' '12.1.4' '12.1.5' '12.1.6' '12.1.7' '12.1.8' '12.1.9' '12.1.10' '12.1.11' '12.1.12' '12.1.13' '12.1.14' '12.1.15' '12.1.16' '12.1.17' '12.1.18' '12.2.1' '12.2.2' '12.2.3' '12.2.4' '12.2.5' '12.2.6' '12.2.7' '12.2.8' '12.2.9' '12.2.10' '12.2.11' '12.2.12' '12.2.13' '12.2.14' '12.2.15' '12.2.16' '12.2.17' '12.2.18' '12.3.1' '12.3.2' '12.3.3' '12.3.4' '12.3.5' '12.3.6' '12.3.7' '12.3.8' '12.3.9' '12.3.10' '12.3.11' '12.3.12' '12.3.13' '12.3.14' '12.3.15' '12.3.16' '12.3.17' '12.3.18' '12.4.1' '12.4.2' '12.4.3' '12.4.4' '12.4.5' '12.4.6' '12.4.7' '12.4.8' '12.4.9' '12.4.10' '12.4.11' '12.4.12' '12.4.13' '12.4.14' '12.4.15' '12.4.16' '12.4.17' '12.4.18' '12.5.1' '12.5.2' '12.5.3' '12.5.4' '12.5.5' '12.5.6' '12.5.7' '12.5.8' '12.5.9' '12.5.10' '12.5.11' '12.5.12' '12.5.13' '12.5.14' '12.5.15' '12.5.16' '12.5.17' '12.5.18' '13.1.1' '13.1.2' '13.1.3' '13.1.4' '13.1.5' '13.1.6' '13.1.7' '13.1.8' '13.1.9' '13.1.10' '13.1.11' '13.1.12' '13.1.13' '13.1.14' '13.1.15' '13.1.16' '13.1.17' '13.1.18' '13.2.1' '13.2.2' '13.2.3' '13.2.4' '13.2.5' '13.2.6' '13.2.7' '13.2.8' '13.2.9' '13.2.10' '13.2.11' '13.2.12' '13.2.13' '13.2.14' '13.2.15' '13.2.16' '13.2.17' '13.2.18' '13.3.1' '13.3.2' '13.3.3' '13.3.4' '13.3.5' '13.3.6' '13.3.7' '13.3.8' '13.3.9' '13.3.10' '13.3.11' '13.3.12' '13.3.13' '13.3.14' '13.3.15' '13.3.16' '13.3.17' '13.3.18' '13.4.1' '13.4.2' '13.4.3' '13.4.4' '13.4.5' '13.4.6' '13.4.7' '13.4.8' '13.4.9' '13.4.10' '13.4.11' '13.4.12' '13.4.13' '13.4.14' '13.4.15' '13.4.16' '13.4.17' '13.4.18' '13.5.1' '13.5.2' '13.5.3' '13.5.4' '13.5.5' '13.5.6' '13.5.7' '13.5.8' '13.5.9' '13.5.10' '13.5.11' '13.5.12' '13.5.13' '13.5.14' '13.5.15' '13.5.16' '13.5.17' '13.5.18' '13.6.1' '13.6.2' '13.6.3' '13.6.4' '13.6.5' '13.6.6' '13.6.7' '13.6.8' '13.6.9' '13.6.10' '13.6.11' '13.6.12' '13.6.13' '13.6.14' '13.6.15' '13.6.16' '13.6.17' '13.6.18' '13.7.1' '13.7.2' '13.7.3' '13.7.4' '13.7.5' '13.7.6' '13.7.7' '13.7.8' '13.7.9' '13.7.10' '13.7.11' '13.7.12' '13.7.13' '13.7.14' '13.7.15' '13.7.16' '13.7.17' '13.7.18' ; do
-       libwebsockets-test-echo --client 127.0.0.1 --port 9001 -u "/runCase?case=$N&agent=libwebsockets" -v -n 1 &
-
-       C=99
-       while [ $C -gt 8 ] ; do
-               if [ $OS=SunOS ] ; then
-                       C=`ps -ef | grep libwebsockets-test-echo | wc -l`
-               else
-                       C=`ps fax | grep libwebsockets-test-echo | wc -l`
-               fi
-               if [ $C -gt 8 ] ; then
-                       sleep 1s
-               fi
-       done
-
-       N=$(( $N + 1 ))
-done
-
-echo "waiting for forks to complete..."
-
-while [ 1 ] ; do
-       if [ $OS=SunOS ] ; then
-               n=`ps -ef | grep libwebsocket | grep -v grep | wc -l`
-       else
-               n=`ps fax | grep libwebsocket | grep -v grep | wc -l`
-       fi
-       echo "$n forks running..."
-       if [ $n -eq 0 ] ; then
-               echo "Completed"
-               exit 0
-       fi
-       sleep 1s
-done
-
index 74151af..0783fee 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,6 +1,344 @@
 Changelog
 ---------
 
+v3.2.0
+======
+
+ - This is the last planned release under LGPLv2+SLE.  It's not planned to be
+   maintained like previous releases, please switch to master for the latest
+   stuff or continue to use v3.1-stable until the next release under the
+   new MIT license.
+
+ - NEW: completely refactored scheduler with a unified, sorted us-resolution
+   linked-list implementation.  All polled checks like timeout are migrated
+   to use the new timers, which also work on the event lib implementations.
+   Faster operation, us-resolution timeouts and generic scheduled callbacks
+   from the event loop.
+
+ - NEW: lws_dsh specialized buffer memory allocator that can borrow space
+   from other cooperating buffers on the same list.
+
+ - NEW: lws_sequencer allows managing multi-connection processes and
+   retries
+
+ - NEW: memory buffer cert support
+
+ - NEW: LWS_WITH_NETWORK in CMake... can be configured without any network-
+   related code at all
+
+ - NEW: builds on QNX 6.5 and SmartOS
+
+ - NEW: JOSE / JWK / JWS / JWE support, for all common ciphers and algs,
+   works on OpenSSL and mbedtls backends
+
+ - NEW: gencrypto now has genaes and genec in addition to genrsa, works
+   on OpenSSL and mbedtls backends
+
+ - NEW: raw_proxy role
+
+ - NEW: Basic Auth works on ws connections
+
+ - CHANGE: REMOVED: LWS_WITH_GENRSA, LWS_WITH_GENHASH, LWS_WITH_GENEC,
+ LWS_WITH_GENAES have all been removed and combined into LWS_WITH_GENCRYPTO
+
+ - CHANGE: REMOVED: LWS_WITH_JWS, LWS_WITH_JWE have been removed and combined
+ into LWS_WITH_JOSE
+
+v3.1.0
+======
+
+ - CHANGE: REMOVED: lws_client_connect() and lws_client_connect_extended()
+   compatibility apis for lws_client_connect_via_info() have been marked as
+   deprecated for several versions and are now removed.  Use
+   lws_client_connect_via_info() directly instead.
+
+ - CHANGE: CMAKE:
+     - LWS_WITH_HTTP2:         now defaults ON
+
+ - CHANGE: Minimal examples updated to use Content Security Policy best
+   practices, using
+   `LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE` vhost
+   option flag and disabling of inline style and scripts.  A side-effect of
+   this is that buffers used to marshal headers have to be prepared to take
+   more content than previously... LWS_RECOMMENDED_MIN_HEADER_SPACE (2048
+   currently) is available for user (and internal) use to logically tie the
+   buffer size to this usecase (and follow future increases).
+
+ - NEW: CMAKE
+     - LWS_FOR_GITOHASHI: sets various cmake options suitable for gitohashi
+     - LWS_WITH_ASAN: for Linux, enable build with ASAN
+
+     Don't forget LWS_WITH_DISTRO_RECOMMENDED, which enables a wide range of lws
+     options suitable for a distro build of the library.
+     
+ - NEW: lws threadpool - lightweight pool of pthreads integrated to lws wsi, with
+   all synchronization to event loop handled internally, queue for excess tasks
+   [threadpool docs](https://libwebsockets.org/git/libwebsockets/tree/lib/misc/threadpool)
+   [threadpool minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/ws-server/minimal-ws-server-threadpool)
+   Cmake config: `-DLWS_WITH_THREADPOOL=1`
+
+ - NEW: libdbus support integrated on lws event loop
+   [lws dbus docs](https://libwebsockets.org/git/libwebsockets/tree/lib/roles/dbus)
+   [lws dbus client minimal examples](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/dbus-client)
+   [lws dbus server minimal examples](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/dbus-server)
+   Cmake config: `-DLWS_ROLE_DBUS=1`
+
+ - NEW: lws allocated chunks (lwsac) - helpers for optimized mass allocation of small
+   objects inside a few larger malloc chunks... if you need to allocate a lot of
+   inter-related structs for a limited time, this removes per-struct allocation
+   library overhead completely and removes the need for any destruction handling
+   [lwsac docs](https://libwebsockets.org/git/libwebsockets/tree/lib/misc/lwsac)
+   [lwsac minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-lwsac)
+   Cmake Config: `-DLWS_WITH_LWSAC=1`
+
+ - NEW: lws tokenizer - helper api for robustly tokenizing your own strings without
+   allocating or adding complexity.  Configurable by flags for common delimiter
+   sets and comma-separated-lists in the tokenizer.  Detects and reports syntax
+   errors.
+   [lws_tokenize docs](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-tokenize.h)
+   [lws_tokenize minimal example / api test](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-lws_tokenize)
+
+ - NEW: lws full-text search - optimized trie generation, serialization,
+   autocomplete suggestion generation and instant global search support extensible
+   to huge corpuses of UTF-8 text while remaining super lightweight on resources.
+   [full-text search docs](https://libwebsockets.org/git/libwebsockets/tree/lib/misc/fts)
+   [full-text search minimal example / api test](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-fts)
+   [demo](https://libwebsockets.org/ftsdemo/)
+   [demo sources](https://libwebsockets.org/git/libwebsockets/tree/plugins/protocol_fulltext_demo.c)
+   Cmake config: `-DLWS_WITH_FTS=1 -DLWS_WITH_LWSAC=1`
+
+ - NEW: gzip + brotli http server-side compression - h1 and h2 automatic advertising
+   of server compression and application to files with mimetypes "text/*",
+   "application/javascript" and "image/svg.xml".
+   Cmake config: `-DLWS_WITH_HTTP_STREAM_COMPRESSION=1`, `-DLWS_WITH_HTTP_BROTLI=1`
+
+ - NEW: managed disk cache - API for managing a directory containing cached files
+   with hashed names, and automatic deletion of LRU files once the cache is
+   above a given limit.
+   [lws diskcache docs](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-diskcache.h)
+   Cmake config: `-DLWS_WITH_DISKCACHE=1`
+
+ - NEW: http reverse proxy - lws mounts support proxying h1 or h2 requests to
+   a local or remote IP, or unix domain socket over h1.  This allows microservice
+   type architectures where parts of the common URL space are actually handled
+   by external processes which may be remote or on the same machine.
+   [lws gitohashi serving](https://libwebsockets.org/git/) is handled this way.
+   CMake config: `-DLWS_WITH_HTTP_PROXY=1`
+   
+ - NEW: lws_buflist - internally several types of ad-hoc malloc'd buffer have
+   been replaced by a new, exported api `struct lws_buflist`.  This allows
+   multiple buffers to be chained and drawn down in strict FIFO order.
+
+ - NEW: In the case of h1 upgrade, the connection header is checked to contain
+   "upgrade".   The vhost flag LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK
+   also causes the Host: header to be confirmed to match the vhost name and
+   listen port.
+   
+ - NEW: If no 404 redirect for `lws_return_http_status()` is specified for the vhost,
+   the status page produced will try to bring in a stylesheet `/error.css`.  This allows
+   you to produce styled 404 or other error pages with logos, graphics etc.  See
+   https://libwebsockets.org/git/badrepo for an example of what you can do with it.
+
+v3.0.0
+======
+
+ - CHANGE: Clients used to call LWS_CALLBACK_CLOSED same as servers...
+   LWS_CALLBACK_CLIENT_CLOSED has been introduced and is called for clients
+   now.
+   
+ - CHANGE: LWS_CALLBACK_CLIENT_CONNECTION_ERROR used to only be directed at
+   protocols[0].  However in many cases, the protocol to bind to was provided
+   at client connection info time and the wsi bound accordingly.  In those
+   cases, CONNECTION_ERROR is directed at the bound protocol, not protcols[0]
+   any more.
+
+ - CHANGE: CMAKE: the following cmake defaults have changed with this version:
+     - LWS_WITH_ZIP_FOPS:      now defaults OFF
+     - LWS_WITH_RANGES:        now defaults OFF
+     - LWS_WITH_ZLIB:          now defaults OFF
+     - LWS_WITHOUT_EXTENSIONS: now defaults ON
+     
+ - CHANGE: REMOVED: lws_alloc_vfs_file() (read a file to malloc buffer)
+ - CHANGE: REMOVED: lws_read() (no longer useful outside of lws internals)
+ - CHANGE: REMOVED: ESP8266... ESP32 is now within the same price range and much
+   more performant
+   
+ - CHANGE: soname bump... don't forget to `ldconfig`
+     
+ - NEW: all event libraries support "foreign" loop integration where lws itself
+   if just a temporary user of the loop unrelated to the actual loop lifecycle.
+   
+   See `minimal-http-server-eventlib-foreign` for example code demonstrating
+   this for all the event libraries.
+   
+   Internal loop in lws is also supported and demonstrated by
+   `minimal-http-server-eventlib`.
+ - NEW: ws-over-h2 support.  This is a new RFC-on-the-way supported by Chrome
+   and shortly firefox that allows ws connections to be multiplexed back to the
+   server on the same tcp + tls wrapper h2 connection that the html and scripts
+   came in on.  This is hugely faster that discrete connections.
+ - NEW: UDP socket adoption and related event callbacks
+ - NEW: Multi-client connection binding, queuing and pipelining support.
+   Lws detects multiple client connections to the same server and port, and
+   optimizes how it handles them according to the server type and provided
+   flags.  For http/1.0, all occur with individual parallel connections.  For
+   http/1.1, you can enable keepalive pipelining, so the connections occur
+   sequentially on a single network connection.  For http/2, they all occur
+   as parallel streams within a single h2 network connection.
+   
+   See minimal-http-client-multi for example code. 
+   
+ - NEW: High resolution timer API for wsi, get a callback on your wsi with
+   LWS_CALLBACK_TIMER, set and reset the timer with lws_set_timer_usecs(wsi, us)
+   Actual resolution depends on event backend.  Works with all backends, poll,
+   libuv, libevent, and libev.
+   
+ - NEW: Protocols can arrange vhost-protocol instance specific callbacks with
+   second resolution using `lws_timed_callback_vh_protocol()`
+
+ - NEW: ACME client plugin for self-service TLS certificates
+  
+ - NEW: RFC7517 JSON Web Keys RFC7638 JWK thumbprint, and RFC7515 JSON Web
+    signatures support
+  
+ - NEW: lws_cancel_service() now provides a generic way to synchronize events
+   from other threads, which appear as a LWS_CALLBACK_EVENT_WAIT_CANCELLED
+   callback on all protocols.  This is compatible with all the event libraries.
+
+ - NEW: support BSD poll() where changes to the poll wait while waiting are
+   undone.
+
+ - NEW: Introduce generic hash, hmac and RSA apis that operate the same
+   regardless of OpenSSL or mbedTLS tls backend
+  
+ - NEW: Introduce X509 element query api that works the same regardless of
+   OpenSSL or mbedTLS tls backend
+    
+ - NEW: Introduce over 30 "minimal examples" in ./minimal-examples... these
+   replace most of the old test servers
+   
+    - test-echo -> minimal-ws-server-echo and minimal-ws-client-echo
+
+    - test-server-libuv / -libevent / -libev ->
+         minimal-https-server-eventlib / -eventlib-foreign / -eventlib-demos
+
+    - test-server-v2.0 -> folded into all the minimal servers
+
+    - test-server direct http serving -> minimal-http-server-dynamic
+    
+   The minimal examples allow individual standalone build using their own
+   small CMakeLists.txt.
+   
+ - NEW: lws now detects any back-to-back writes that did not go through the
+   event loop inbetween and reports them.  This will flag any possibility of
+   failure rather than wait until the problem happens.
+   
+ - NEW: CMake has LWS_WITH_DISTRO_RECOMMENDED to select features that are
+   appropriate for distros
+   
+ - NEW: Optional vhost URL `error_document_404` if given causes a redirect there
+   instead of serve the default 404 page.
+   
+ - NEW: lws_strncpy() wrapper guarantees NUL in copied string even if it was
+   truncated to fit.
+   
+ - NEW: for client connections, local protocol binding name can be separated
+   from the ws subprotocol name if needed, using .local_protocol_name
+
+ - NEW: Automatic detection of time discontiguities
+   
+ - NEW: Applies TCP_USER_TIMEOUT for Linux tcp keepalive where available
+     
+ - QA: 1600 tests run on each commit in Travis CI, including almost all
+   Autobahn in client and server mode, various h2load tests, h2spec, attack.sh
+   the minimal example selftests and others.
+
+ - QA: fix small warnings introduced on gcc8.x (eg, Fedora 28)
+ - QA: Add most of -Wextra on gcc (-Wsign-compare, -Wignored-qualifiers,
+   -Wtype-limits, -Wuninitialized)
+   
+ - QA: clean out warnings on windows
+ - QA: pass all 146 h2spec tests now on strict
+ - QA: introduce 35 selftests that operate different minimal examples against
+   each other and confirm the results.
+ - QA: LWS_WITH_MINIMAL_EXAMPLES allows mass build of all relevant minimal-
+   examples with the LWS build, for CI and to make all the example binaries
+   available from the lws build dir ./bin
+ - REFACTOR: the lws source directory layout in ./lib has been radically
+   improved, and there are now README.md files in selected subdirs with extra
+   documentation of interest to people working on lws itself.
+
+ - REFACTOR: pipelined transactions return to the event loop before starting the
+   next part. 
+ - REFACTOR: TLS: replace all TLS library constants with generic LWS ones and
+   adapt all the TLS library code to translate to these common ones.
+   
+   Isolated all the tls-related private stuff in `./lib/tls/private.h`, and all
+   the mbedTLS stuff in `./lib/tls/mbedtls` + openSSL stuff in
+   `./lib/tls/openssl`
+   
+ - REFACTOR: the various kinds of wsi possible with lws have been extracted
+   from the main code and isolated into "roles" in `./lib/roles` which
+   communicate with the core code via an ops struct.  Everything related to
+   ah is migrated to the http role.
+   
+   wsi modes are eliminated and replaced by the ops pointer for the role the
+   wsi is performing.  Generic states for wsi are available to control the
+   lifecycle using core code.
+   
+   Adding new "roles" is now much easier with the changes and ops struct to
+   plug into.
+
+ - REFACTOR: reduce four different kinds of buffer management in lws into a
+   generic scatter-gather struct lws_buflist. 
+
+ - REFACTOR: close notifications go through event loop
+
+
+v2.4.0
+======
+
+ - HTTP/2 server support is now mature and usable!  LWS_WITH_HTTP2=1 enables it.
+   Uses ALPN to serve HTTP/2, HTTP/1 and ws[s] connections all from the same
+   listen port seamlessly.  (Requires ALPN-capable OpenSSL 1.1 or mbedTLS).
+
+ - LWS_WITH_MBEDTLS=1 at CMake now builds and works against mbedTLS instead of
+   OpenSSL.  Most things work identically, although on common targets where
+   OpenSSL has acceleration, mbedTLS is many times slower in operation.  However
+   it is a lot smaller codewise.
+   
+ - Generic hash apis introduced that work the same on mbedTLS or OpenSSL backend
+ - LWS_WITH_PEER_LIMITS tracks IPs across all vhosts and allows restrictions on
+   both the number of simultaneous connections and wsi in use for any single IP
+
+ - lws_ring apis provide a generic single- or multi-tail ringbuffer... mirror
+   protocol now uses this.  Features include ring elements may be sized to fit
+   structs in the ringbuffer, callback when no tail any longer needs an element
+   and it can be deleted, and zerocopy options to write new members directly
+   into the ringbuffer, and use the ringbuffer element by address too.
+ - abstract ssh 2 server plugin included, with both plugin and standalone
+   demos provided.  You can bind the plugin to a vhost and also serve full-
+   strength ssh from the vhost.  IO from the ssh server is controlled by an
+   "ops" struct of callbacks for tx, rx, auth etc.
+   
+ - Many fixes, cleanups, source refactors and other improvements.
+
+
 v2.3.0
 ======
 
diff --git a/cmake/FindMiniz.cmake b/cmake/FindMiniz.cmake
new file mode 100644 (file)
index 0000000..105cee4
--- /dev/null
@@ -0,0 +1,35 @@
+# This module tries to find miniz library and include files
+#
+# MINIZ_INCLUDE_DIR, path where to find miniz.h
+# MINIZ_LIBRARY_DIR, path where to find libminiz.so
+# MINIZ_LIBRARIES, the library to link against
+# MINIZ_FOUND, If false, do not try to use miniz
+#
+# This currently works probably only for Linux
+
+FIND_PATH ( MINIZ_INCLUDE_DIR miniz.h
+    /usr/local/include
+    /usr/include
+)
+
+FIND_LIBRARY ( MINIZ_LIBRARIES libminiz.so libminiz.a libminiz.so.2 libminiz.so.0.1
+    /usr/local/lib
+    /usr/local/lib64
+    /usr/lib
+    /usr/lib64
+)
+
+GET_FILENAME_COMPONENT( MINIZ_LIBRARY_DIR ${MINIZ_LIBRARIES} PATH )
+
+SET ( MINIZ_FOUND "NO" )
+IF ( MINIZ_INCLUDE_DIR )
+    IF ( MINIZ_LIBRARIES )
+        SET ( MINIZ_FOUND "YES" )
+    ENDIF ( MINIZ_LIBRARIES )
+ENDIF ( MINIZ_INCLUDE_DIR )
+
+MARK_AS_ADVANCED(
+    MINIZ_LIBRARY_DIR
+    MINIZ_INCLUDE_DIR
+    MINIZ_LIBRARIES
+)
index 63aac73..36f9d3d 100755 (executable)
@@ -111,7 +111,7 @@ rm -rf build_tree
 mkdir build_tree
 cd build_tree
 cmake -DCMAKE_INSTALL_PREFIX=%{rpmprefix} ../%{srcdirname}
-make
+make %{?_smp_mflags}
 
 %install
 cd ../build_tree
diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in
new file mode 100644 (file)
index 0000000..9690a48
--- /dev/null
@@ -0,0 +1,146 @@
+/* lws_config.h  Generated from lws_config.h.in  */
+
+#ifndef NDEBUG
+       #ifndef _DEBUG
+               #define _DEBUG
+       #endif
+#endif
+
+#define LWS_INSTALL_DATADIR "${CMAKE_INSTALL_PREFIX}/share"
+#define LWS_LIBRARY_VERSION_MAJOR ${LWS_LIBRARY_VERSION_MAJOR}
+#define LWS_LIBRARY_VERSION_MINOR ${LWS_LIBRARY_VERSION_MINOR}
+#define LWS_LIBRARY_VERSION_PATCH ${LWS_LIBRARY_VERSION_PATCH}
+/* LWS_LIBRARY_VERSION_NUMBER looks like 1005001 for e.g. version 1.5.1 */
+#define LWS_LIBRARY_VERSION_NUMBER (LWS_LIBRARY_VERSION_MAJOR * 1000000) + \
+                                       (LWS_LIBRARY_VERSION_MINOR * 1000) + \
+                                       LWS_LIBRARY_VERSION_PATCH
+#define LWS_MAX_SMP ${LWS_MAX_SMP}
+
+#cmakedefine LWS_AVOID_SIGPIPE_IGN
+#cmakedefine LWS_BUILD_HASH "${LWS_BUILD_HASH}"
+#cmakedefine LWS_BUILTIN_GETIFADDRS
+#cmakedefine LWS_FALLBACK_GETHOSTBYNAME
+#cmakedefine LWS_HAS_INTPTR_T
+#cmakedefine LWS_HAS_GETOPT_LONG
+#cmakedefine LWS_HAVE__ATOI64
+#cmakedefine LWS_HAVE_ATOLL
+#cmakedefine LWS_HAVE_BN_bn2binpad
+#cmakedefine LWS_HAVE_CLOCK_GETTIME
+#cmakedefine LWS_HAVE_EC_POINT_get_affine_coordinates
+#cmakedefine LWS_HAVE_ECDSA_SIG_set0
+#cmakedefine LWS_HAVE_EVP_MD_CTX_free
+#cmakedefine LWS_HAVE_EVP_aes_128_wrap
+#cmakedefine LWS_HAVE_EVP_aes_128_cfb8
+#cmakedefine LWS_HAVE_EVP_aes_128_cfb128
+#cmakedefine LWS_HAVE_EVP_aes_192_cfb8
+#cmakedefine LWS_HAVE_EVP_aes_192_cfb128
+#cmakedefine LWS_HAVE_EVP_aes_256_cfb8
+#cmakedefine LWS_HAVE_EVP_aes_256_cfb128
+#cmakedefine LWS_HAVE_EVP_aes_128_xts
+#cmakedefine LWS_HAVE_LIBCAP
+#cmakedefine LWS_HAVE_HMAC_CTX_new
+#cmakedefine LWS_HAVE_MALLOC_H
+#cmakedefine LWS_HAVE_MALLOC_TRIM
+#cmakedefine LWS_HAVE_MALLOC_USABLE_SIZE
+#cmakedefine LWS_HAVE_mbedtls_net_init
+#cmakedefine LWS_HAVE_mbedtls_ssl_conf_alpn_protocols
+#cmakedefine LWS_HAVE_mbedtls_ssl_get_alpn_protocol
+#cmakedefine LWS_HAVE_mbedtls_ssl_conf_sni
+#cmakedefine LWS_HAVE_mbedtls_ssl_set_hs_ca_chain
+#cmakedefine LWS_HAVE_mbedtls_ssl_set_hs_own_cert
+#cmakedefine LWS_HAVE_mbedtls_ssl_set_hs_authmode
+#cmakedefine LWS_HAVE_NEW_UV_VERSION_H
+#cmakedefine LWS_HAVE_OPENSSL_ECDH_H
+#cmakedefine LWS_HAVE_PIPE2
+#cmakedefine LWS_HAVE_PTHREAD_H
+#cmakedefine LWS_HAVE_RSA_SET0_KEY
+#cmakedefine LWS_HAVE_RSA_verify_pss_mgf1
+#cmakedefine LWS_HAVE_SSL_CTX_get0_certificate
+#cmakedefine LWS_HAVE_SSL_CTX_set1_param
+#cmakedefine LWS_HAVE_SSL_CTX_set_ciphersuites
+#cmakedefine LWS_HAVE_SSL_EXTRA_CHAIN_CERTS
+#cmakedefine LWS_HAVE_SSL_get0_alpn_selected
+#cmakedefine LWS_HAVE_SSL_CTX_EVP_PKEY_new_raw_private_key
+#cmakedefine LWS_HAVE_SSL_set_alpn_protos
+#cmakedefine LWS_HAVE_SSL_SET_INFO_CALLBACK
+#cmakedefine LWS_HAVE__STAT32I64
+#cmakedefine LWS_HAVE_STDINT_H
+#cmakedefine LWS_HAVE_SYS_CAPABILITY_H
+#cmakedefine LWS_HAVE_TLS_CLIENT_METHOD
+#cmakedefine LWS_HAVE_TLSV1_2_CLIENT_METHOD
+#cmakedefine LWS_HAVE_UV_VERSION_H
+#cmakedefine LWS_HAVE_X509_get_key_usage
+#cmakedefine LWS_HAVE_X509_VERIFY_PARAM_set1_host
+#cmakedefine LWS_LATENCY
+#cmakedefine LWS_LIBRARY_VERSION "${LWS_LIBRARY_VERSION}"
+#cmakedefine LWS_MINGW_SUPPORT
+#cmakedefine LWS_NO_CLIENT
+#cmakedefine LWS_NO_DAEMONIZE
+#cmakedefine LWS_NO_SERVER
+#cmakedefine LWS_OPENSSL_CLIENT_CERTS "${LWS_OPENSSL_CLIENT_CERTS}"
+#cmakedefine LWS_OPENSSL_SUPPORT
+#cmakedefine LWS_PLAT_OPTEE
+#cmakedefine LWS_PLAT_UNIX
+#cmakedefine LWS_ROLE_CGI
+#cmakedefine LWS_ROLE_DBUS
+#cmakedefine LWS_ROLE_H1
+#cmakedefine LWS_ROLE_H2
+#cmakedefine LWS_ROLE_RAW
+#cmakedefine LWS_ROLE_RAW_PROXY
+#cmakedefine LWS_ROLE_WS
+#cmakedefine LWS_SHA1_USE_OPENSSL_NAME
+#cmakedefine LWS_SSL_CLIENT_USE_OS_CA_CERTS
+#cmakedefine LWS_SSL_SERVER_WITH_ECDH_CERT
+#cmakedefine LWS_WITH_ABSTRACT
+#cmakedefine LWS_WITH_ACCESS_LOG
+#cmakedefine LWS_WITH_ACME
+#cmakedefine LWS_WITH_BORINGSSL
+#cmakedefine LWS_WITH_CGI
+#cmakedefine LWS_WITH_CUSTOM_HEADERS
+#cmakedefine LWS_WITH_DEPRECATED_LWS_DLL
+#cmakedefine LWS_WITH_DIR
+#cmakedefine LWS_WITH_ESP32
+#cmakedefine LWS_WITH_EXTERNAL_POLL
+#cmakedefine LWS_WITH_FTS
+#cmakedefine LWS_WITH_GENCRYPTO
+#cmakedefine LWS_WITH_GENERIC_SESSIONS
+#cmakedefine LWS_WITH_HTTP2
+#cmakedefine LWS_WITH_HTTP_BROTLI
+#cmakedefine LWS_WITH_HTTP_PROXY
+#cmakedefine LWS_WITH_HTTP_STREAM_COMPRESSION
+#cmakedefine LWS_WITH_IPV6
+#cmakedefine LWS_WITH_JOSE
+#cmakedefine LWS_WITH_LEJP
+#cmakedefine LWS_WITH_LIBEV
+#cmakedefine LWS_WITH_LIBEVENT
+#cmakedefine LWS_WITH_LIBUV
+#cmakedefine LWS_WITH_LWSAC
+#cmakedefine LWS_WITH_MBEDTLS
+#cmakedefine LWS_WITH_MINIZ
+#cmakedefine LWS_WITH_NETWORK
+#cmakedefine LWS_WITH_NO_LOGS
+#cmakedefine LWS_WITHOUT_CLIENT
+#cmakedefine LWS_WITHOUT_EXTENSIONS
+#cmakedefine LWS_WITHOUT_SERVER
+#cmakedefine LWS_WITH_PEER_LIMITS
+#cmakedefine LWS_WITH_PLUGINS
+#cmakedefine LWS_WITH_POLARSSL
+#cmakedefine LWS_WITH_POLL
+#cmakedefine LWS_WITH_RANGES
+#cmakedefine LWS_WITH_SELFTESTS
+#cmakedefine LWS_WITH_SEQUENCER
+#cmakedefine LWS_WITH_SERVER_STATUS
+#cmakedefine LWS_WITH_SMTP
+#cmakedefine LWS_WITH_SOCKS5
+#cmakedefine LWS_WITH_STATEFUL_URLDECODE
+#cmakedefine LWS_WITH_STATS
+#cmakedefine LWS_WITH_STRUCT_SQLITE3
+#cmakedefine LWS_WITH_SQLITE3
+#cmakedefine LWS_WITH_THREADPOOL
+#cmakedefine LWS_WITH_TLS
+#cmakedefine LWS_WITH_UNIX_SOCK
+#cmakedefine LWS_WITH_ZIP_FOPS
+#cmakedefine USE_OLD_CYASSL
+#cmakedefine USE_WOLFSSL
+
+${LWS_SIZEOFPTR_CODE}
similarity index 93%
rename from lws_config_private.h.in
rename to cmake/lws_config_private.h.in
index 8ad39a2..55bd4fb 100644 (file)
@@ -10,9 +10,6 @@
  * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */
 #cmakedefine USE_CYASSL
 
-/* Define to 1 if you have the `bzero' function. */
-#cmakedefine LWS_HAVE_BZERO
-
 /* Define to 1 if you have the <dlfcn.h> header file. */
 #cmakedefine LWS_HAVE_DLFCN_H
 
 /* Define to 1 if you have the <in6addr.h> header file. */
 #cmakedefine LWS_HAVE_IN6ADDR_H
 
-/* Define to 1 if you have the <inttypes.h> header file. */
-#cmakedefine LWS_HAVE_INTTYPES_H
-
-/* Define to 1 if you have the `ssl' library (-lssl). */
-//#cmakedefine LWS_HAVE_LIBSSL
-
 /* Define to 1 if your system has a GNU libc compatible `malloc' function, and
    to 0 otherwise. */
 #cmakedefine LWS_HAVE_MALLOC
@@ -87,6 +78,8 @@
 /* Define to 1 if you have the <unistd.h> header file. */
 #cmakedefine LWS_HAVE_UNISTD_H
 
+#cmakedefine LWS_HAVE_TCP_USER_TIMEOUT
+
 /* Define to 1 if you have the `vfork' function. */
 #cmakedefine LWS_HAVE_VFORK
 
 /* Define if the inline keyword doesn't exist. */
 #cmakedefine inline ${inline}
 
+#cmakedefine LWS_WITH_ZLIB
+#cmakedefine LWS_HAS_PTHREAD_SETNAME_NP
 
+/* Defined if you have the <inttypes.h> header file. */
+#cmakedefine LWS_HAVE_INTTYPES_H
index 61754b5..67b8abb 100644 (file)
@@ -1,19 +1,20 @@
-COMPONENT_ADD_INCLUDEDIRS := ../../../../../../../../../../../../../../../../../../$(COMPONENT_BUILD_DIR)/include
+COMPONENT_DEPENDS := mbedtls openssl
+#COMPONENT_ADD_INCLUDEDIRS := ../../../../../../../../../../../../../../../../../../../../$(COMPONENT_BUILD_DIR)/include
 
-COMPONENT_OWNBUILDTARGET:= 1
+COMPONENT_OWNBUILDTARGET := 1
 
-CROSS_PATH1:=$(shell which xtensa-esp32-elf-gcc )
-CROSS_PATH:= $(shell dirname $(CROSS_PATH1) )/..
+CROSS_PATH1 := $(shell which xtensa-esp32-elf-gcc )
+CROSS_PATH := $(shell dirname $(CROSS_PATH1) )/..
 
-#-DLWS_USE_BORINGSSL=1 \
-#              -DOPENSSL_ROOT_DIR="${PWD}/../../boringssl" \
-#              -DOPENSSL_LIBRARIES="${PWD}/../../boringssl/build/ssl/libssl.a;${PWD}/../../boringssl/build/crypto/libcrypto.a" \
-#              -DOPENSSL_INCLUDE_DIRS="${PWD}/../../boringssl/include" \
-
-# -DNDEBUG=1 after cflags
-#              -DOPENSSL_LIBRARIES=x \
-#              -DCOMPONENT_PATH=$(COMPONENT_PATH) \
+# detect MSYS2 environment and set generator flag if found
+# also set executable extension to .exe so that tools can be properly found
+# and disable bundled zlib
+MSYS_VERSION = $(if $(findstring Msys, $(shell uname -o)),$(word 1, $(subst ., ,$(shell uname -r))),0)
+ifneq ($(MSYS_VERSION),0)
+       MSYS_FLAGS = -DLWS_WITH_BUNDLED_ZLIB=0 -DEXECUTABLE_EXT=.exe -G'MSYS Makefiles'
+endif
 
+# -DNDEBUG=1 after cflags stops debug etc being built
 .PHONY: build
 build:
        cd $(COMPONENT_BUILD_DIR) ; \
@@ -22,13 +23,19 @@ build:
                -DIDF_PATH=$(IDF_PATH) \
                -DCROSS_PATH=$(CROSS_PATH) \
                -DBUILD_DIR_BASE=$(BUILD_DIR_BASE) \
-               -DCMAKE_TOOLCHAIN_FILE=$(COMPONENT_PATH)/cross-esp32.cmake \
+               -DCMAKE_TOOLCHAIN_FILE=$(COMPONENT_PATH)/contrib/cross-esp32.cmake \
                -DCMAKE_BUILD_TYPE=RELEASE \
-               -DOPENSSL_INCLUDE_DIR=${IDF_PATH}/components/openssl/include \
+               -DLWS_MBEDTLS_INCLUDE_DIRS="${IDF_PATH}/components/openssl/include;${IDF_PATH}/components/mbedtls/mbedtls/include;${IDF_PATH}/components/mbedtls/port/include" \
                -DLWS_WITH_STATS=0 \
+               -DLWS_WITH_HTTP2=1 \
+               -DLWS_WITH_RANGES=1 \
+               -DLWS_WITH_ACME=1 \
+               -DLWS_WITH_ZLIB=1 \
+               -DLWS_WITH_ZIP_FOPS=1 \
                -DZLIB_LIBRARY=$(BUILD_DIR_BASE)/zlib/libzlib.a \
                -DZLIB_INCLUDE_DIR=$(COMPONENT_PATH)/../zlib \
-               -DLWS_WITH_ESP32=1 ;\
+               -DLWS_WITH_ESP32=1 \
+               $(MSYS_FLAGS) ; \
        make && \
        cp ${COMPONENT_BUILD_DIR}/lib/libwebsockets.a ${COMPONENT_BUILD_DIR}/liblibwebsockets.a
 
@@ -36,6 +43,3 @@ clean: myclean
 
 myclean:
        rm -rf ./build
-
-INCLUDES := $(INCLUDES) -I build/ 
-
similarity index 100%
rename from Android.mk
rename to contrib/Android.mk
index 1b09a1a..e0d8769 100644 (file)
@@ -20,7 +20,13 @@ LGPL2 / GPL2 at your choice.
 Installation
 ------------
 
-The author provides an easy way to install the various tools he provides:
+The abi monitoring stuff is now packaged in, eg, fedora, which is a lot
+easier than using the helper script.
+
+```
+# dnf install abi-tracker vtable-dumper
+
+Otherwise, the author provides an "easy way" to install the various tools he provides:
 
     git clone https://github.com/lvc/installer
        cd installer
index c72fda8..1357afa 100644 (file)
     "ABIDiff":        "Off"
   },
   {
+    "Number":         "3.0.0",
+    "Installed":      "installed/libwebsockets/3.0.0",
+    "Source":         "src/libwebsockets/3.0.0/libwebsockets-3.0.0.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.4.2",
+    "Installed":      "installed/libwebsockets/2.4.2",
+    "Source":         "src/libwebsockets/2.4.2/libwebsockets-2.4.2.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.4.1",
+    "Installed":      "installed/libwebsockets/2.4.1",
+    "Source":         "src/libwebsockets/2.4.1/libwebsockets-2.4.1.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.4.0",
+    "Installed":      "installed/libwebsockets/2.4.0",
+    "Source":         "src/libwebsockets/2.4.0/libwebsockets-2.4.0.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.3.0",
+    "Installed":      "installed/libwebsockets/2.3.0",
+    "Source":         "src/libwebsockets/2.3.0/libwebsockets-2.3.0.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.2.2",
+    "Installed":      "installed/libwebsockets/2.2.2",
+    "Source":         "src/libwebsockets/2.2.2/libwebsockets-2.2.2.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.2.1",
+    "Installed":      "installed/libwebsockets/2.2.1",
+    "Source":         "src/libwebsockets/2.2.1/libwebsockets-2.2.1.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.2.0",
+    "Installed":      "installed/libwebsockets/2.2.0",
+    "Source":         "src/libwebsockets/2.2.0/libwebsockets-2.2.0.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.1.1",
+    "Installed":      "installed/libwebsockets/2.1.1",
+    "Source":         "src/libwebsockets/2.1.1/libwebsockets-2.1.1.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "2.1.0",
+    "Installed":      "installed/libwebsockets/2.1.0",
+    "Source":         "src/libwebsockets/2.1.0/libwebsockets-2.1.0.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "1.7.9",
+    "Installed":      "installed/libwebsockets/1.7.9",
+    "Source":         "src/libwebsockets/1.7.9/libwebsockets-1.7.9.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "1.7.8",
+    "Installed":      "installed/libwebsockets/1.7.8",
+    "Source":         "src/libwebsockets/1.7.8/libwebsockets-1.7.8.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "1.7.7",
+    "Installed":      "installed/libwebsockets/1.7.7",
+    "Source":         "src/libwebsockets/1.7.7/libwebsockets-1.7.7.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "1.7.6",
+    "Installed":      "installed/libwebsockets/1.7.6",
+    "Source":         "src/libwebsockets/1.7.6/libwebsockets-1.7.6.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
+    "Number":         "1.7.5",
+    "Installed":      "installed/libwebsockets/1.7.5",
+    "Source":         "src/libwebsockets/1.7.5/libwebsockets-1.7.5.tar.gz",
+    "Changelog":      "changelog",
+    "HeadersDiff":    "On",
+    "PkgDiff":        "Off",
+    "ABIView":        "Off",
+    "ABIDiff":        "Off"
+  },
+  {
     "Number":         "1.7.4",
     "Installed":      "installed/libwebsockets/1.7.4",
     "Source":         "src/libwebsockets/1.7.4/libwebsockets-1.7.4.tar.gz",
index 8ad36cb..1ef8607 100755 (executable)
@@ -3,52 +3,40 @@
 #
 # Build libwebsockets static library for Android
 #
-# requires debian package xutils-dev for makedepend (openssl make depend)
-#
-
-# This is based on http://stackoverflow.com/questions/11929773/compiling-the-latest-openssl-for-android/
-# via https://github.com/warmcat/libwebsockets/pull/502
 
 # path to NDK
-export NDK=/opt/Android/SDK/ndk-bundle
-
+export NDK=/opt/ndk_r17/android-ndk-r17-beta2-linux-x86_64/android-ndk-r17-beta2
+export ANDROID_NDK=${NDK}
+export TOOLCHAIN=${NDK}/toolchain
+export CORSS_SYSROOT=${NDK}/sysroot
+export SYSROOT=${NDK}/platforms/android-22/arch-arm
 set -e
 
-# Download packages libz, openssl and libwebsockets
-
-[ ! -f zlib-1.2.8.tar.gz ] && {
-wget http://prdownloads.sourceforge.net/libpng/zlib-1.2.8.tar.gz
-}
-
-[ ! -f openssl-1.0.2g.tar.gz ] && {
-wget https://openssl.org/source/openssl-1.0.2g.tar.gz
-}
-
-[ ! -f libwebsockets.tar.gz ] && {
-git clone https://github.com/warmcat/libwebsockets.git
-tar caf libwebsockets.tar.gz libwebsockets
-}
+# Download packages libz, libuv, mbedtls and libwebsockets
+#zlib-1.2.8
+#libuv-1.x
+#mbedtls-2.11.0
+#libwebsockets-3.0.0
 
-# Clean then Unzip
-
-[ -d zlib-1.2.8 ] && rm -fr zlib-1.2.8
-[ -d openssl-1.0.2g ] && rm -fr openssl-1.0.2g
-[ -d libwebsockets ] && rm -fr libwebsockets
-[ -d android-toolchain-arm ] && rm -fr android-toolchain-arm
-tar xf zlib-1.2.8.tar.gz
-tar xf openssl-1.0.2g.tar.gz
-tar xf libwebsockets.tar.gz
 
 # create a local android toolchain
+API=${3:-24}
+
 $NDK/build/tools/make-standalone-toolchain.sh \
- --platform=android-9 \
  --toolchain=arm-linux-androideabi-4.9 \
- --install-dir=`pwd`/android-toolchain-arm
+ --arch=arm \
+ --install-dir=`pwd`/android-toolchain-arm \
+ --platform=android-$API \
+ --stl=libc++ \
+ --force \
+ --verbose
 
 # setup environment to use the gcc/ld from the android toolchain
-export TOOLCHAIN_PATH=`pwd`/android-toolchain-arm/bin
+export INSTALL_PATH=/opt/libwebsockets_android/android-toolchain-arm
+export TOOLCHAIN_PATH=`pwd`/android-toolchain-arm
 export TOOL=arm-linux-androideabi
-export NDK_TOOLCHAIN_BASENAME=${TOOLCHAIN_PATH}/${TOOL}
+export NDK_TOOLCHAIN_BASENAME=${TOOLCHAIN_PATH}/bin/${TOOL}
+export PATH=`pwd`/android-toolchain-arm/bin:$PATH
 export CC=$NDK_TOOLCHAIN_BASENAME-gcc
 export CXX=$NDK_TOOLCHAIN_BASENAME-g++
 export LINK=${CXX}
@@ -56,59 +44,73 @@ export LD=$NDK_TOOLCHAIN_BASENAME-ld
 export AR=$NDK_TOOLCHAIN_BASENAME-ar
 export RANLIB=$NDK_TOOLCHAIN_BASENAME-ranlib
 export STRIP=$NDK_TOOLCHAIN_BASENAME-strip
+export PLATFORM=android
+export CFLAGS="D__ANDROID_API__=$API"
 
-# setup buildflags
-export ARCH_FLAGS="-mthumb"
-export ARCH_LINK=
-export CPPFLAGS=" ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 "
-export CXXFLAGS=" ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 -frtti -fexceptions "
-export CFLAGS=" ${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64 "
-export LDFLAGS=" ${ARCH_LINK} "
+# configure and build libuv
+[ ! -f ./android-toolchain-arm/lib/libuv.so ] && {
+cd libuv
+echo "=============================================>> build libuv"
 
-# configure and build zlib
-[ ! -f ./android-toolchain-arm/lib/libz.a ] && {
-cd zlib-1.2.8
-PATH=$TOOLCHAIN_PATH:$PATH ./configure --static --prefix=$TOOLCHAIN_PATH/..
+PATH=$TOOLCHAIN_PATH:$PATH make clean
 PATH=$TOOLCHAIN_PATH:$PATH make
 PATH=$TOOLCHAIN_PATH:$PATH make install
+echo "<<============================================= build libuv"
 cd ..
 }
 
-# configure and build openssl
-[ ! -f ./android-toolchain-arm/lib/libssl.a ] && {
-PREFIX=$TOOLCHAIN_PATH/..
-cd openssl-1.0.2g
-./Configure android --prefix=${PREFIX} no-shared no-idea no-mdc2 no-rc5 no-zlib no-zlib-dynamic enable-tlsext no-ssl2 no-ssl3 enable-ec enable-ecdh enable-ecp
-PATH=$TOOLCHAIN_PATH:$PATH make depend
+# configure and build zlib
+[ ! -f ./android-toolchain-arm/lib/libz.so ] && {
+cd zlib-1.2.8
+echo "=============================================>> build libz"
+
+PATH=$TOOLCHAIN_PATH:$PATH make clean 
 PATH=$TOOLCHAIN_PATH:$PATH make
-PATH=$TOOLCHAIN_PATH:$PATH make install_sw
+PATH=$TOOLCHAIN_PATH:$PATH make install
+echo "<<============================================= build libz"
 cd ..
 }
 
+# configure and build mbedtls
+[ ! -f ./android-toolchain-arm/lib/libmbedtls.so ] && {
+echo "=============================================>> build mbedtls"
+PREFIX=$TOOLCHAIN_PATH
+cd mbedtls-2.11.0
+[ ! -d build ] && mkdir build
+cd build
+export CFLAGS="$CFLAGS -fomit-frame-pointer"
+
+PATH=$TOOLCHAIN_PATH:$PATH cmake .. -DCMAKE_TOOLCHAIN_FILE=`pwd`/../cross-arm-android-gnueabi.cmake \
+  -DCMAKE_INSTALL_PREFIX:PATH=${INSTALL_PATH} \
+  -DCMAKE_BUILD_TYPE=RELEASE -DUSE_SHARED_MBEDTLS_LIBRARY=On
+  
+PATH=$TOOLCHAIN_PATH:$PATH make clean
+PATH=$TOOLCHAIN_PATH:$PATH make SHARED=1
+PATH=$TOOLCHAIN_PATH:$PATH make install
+echo "<<============================================= build mbedtls"
+cd ../..
+}
+
 # configure and build libwebsockets
-[ ! -f ./android-toolchain-arm/lib/libwebsockets.a ] && {
+[ ! -f ./android-toolchain-arm/lib/libwebsockets.so ] && {
 cd libwebsockets
 [ ! -d build ] && mkdir build
 cd build
-PATH=$TOOLCHAIN_PATH:$PATH cmake \
-  -DCMAKE_C_COMPILER=$CC \
-  -DCMAKE_AR=$AR \
-  -DCMAKE_RANLIB=$RANLIB \
-  -DCMAKE_C_FLAGS="$CFLAGS" \
-  -DCMAKE_INSTALL_PREFIX=$TOOLCHAIN_PATH/.. \
-  -DLWS_WITH_SHARED=OFF \
-  -DLWS_WITH_STATIC=ON \
-  -DLWS_WITHOUT_DAEMONIZE=ON \
-  -DLWS_WITHOUT_TESTAPPS=ON \
-  -DLWS_IPV6=OFF \
-  -DLWS_USE_BUNDLED_ZLIB=OFF \
-  -DLWS_WITH_SSL=ON  \
-  -DLWS_WITH_HTTP2=ON \
-  -DLWS_OPENSSL_LIBRARIES="$TOOLCHAIN_PATH/../lib/libssl.a;$TOOLCHAIN_PATH/../lib/libcrypto.a" \
-  -DLWS_OPENSSL_INCLUDE_DIRS=$TOOLCHAIN_PATH/../include \
-  -DCMAKE_BUILD_TYPE=Debug \
-  ..
+echo "=============================================>> build libwebsockets"
+
+PATH=$TOOLCHAIN_PATH:$PATH cmake .. -DCMAKE_TOOLCHAIN_FILE=`pwd`/../cross-arm-android-gnueabi.cmake \
+  -DCMAKE_INSTALL_PREFIX:PATH=${INSTALL_PATH} \
+  -DLWS_WITH_LWSWS=1 \
+  -DLWS_WITH_MBEDTLS=1 \
+  -DLWS_WITHOUT_TESTAPPS=1 \
+  -DLWS_MBEDTLS_LIBRARIES="${INSTALL_PATH}/lib/libmbedcrypto.a;${INSTALL_PATH}/lib/libmbedtls.a;${INSTALL_PATH}/lib/libmbedx509.a" \
+  -DLWS_MBEDTLS_INCLUDE_DIRS=${INSTALL_PATH}/include \
+  -DLWS_LIBUV_LIBRARIES=${INSTALL_PATH}/lib/libuv.so \
+  -DLWS_LIBUV_INCLUDE_DIRS=${INSTALL_PATH}/include \
+  -DLWS_ZLIB_LIBRARIES=${INSTALL_PATH}/lib/libz.so \
+  -DLWS_ZLIB_INCLUDE_DIRS=${INSTALL_PATH}/include 
 PATH=$TOOLCHAIN_PATH:$PATH make
 PATH=$TOOLCHAIN_PATH:$PATH make install
+echo "<<============================================= build libwebsockets"
 cd ../..
 }
similarity index 50%
rename from cross-aarch64.cmake
rename to contrib/cross-aarch64.cmake
index d85a641..c8d880d 100644 (file)
@@ -14,8 +14,24 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64)
 set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc")
 set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++")
 
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
+
 #-nostdlib
-SET(CMAKE_C_FLAGS "-DGCC_VER=\"\\\"$(GCC_VER)\\\"\" -DARM64=1 -D__LP64__=1 -Os -g3 -fpie -mstrict-align -DOPTEE_DEV_KIT=../../../optee_os/out/arm-plat-hikey/export-ta_arm64/include -fPIC -ffunction-sections -fdata-sections" CACHE STRING "" FORCE)
+SET(CMAKE_C_FLAGS "-DGCC_VER=\"\\\"$(GCC_VER)\\\"\" -DARM64=1 -D__LP64__=1 -Os -g3 -fpie -mstrict-align -DOPTEE_DEV_KIT=../../../../out/arm-plat-hikey/export-ta_arm64/include -I../../../../lib/libutee/include  -fPIC -ffunction-sections -fdata-sections -I../../../../core/include" CACHE STRING "" FORCE)
 
 
 # Where to look for the target environment. (More paths can be added here)
diff --git a/contrib/cross-arm-android-gnueabi.cmake b/contrib/cross-arm-android-gnueabi.cmake
new file mode 100644 (file)
index 0000000..da9aaae
--- /dev/null
@@ -0,0 +1,46 @@
+#
+# CMake Toolchain file for crosscompiling on ARM.
+#
+# This can be used when running cmake in the following way:
+#  cd build/
+#  cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake
+#
+
+set(CROSS_PATH /opt/libwebsockets_android/android-toolchain-arm)
+
+# Target operating system name.
+set(CMAKE_SYSTEM_NAME Android)
+
+# Target build dynamic libs.
+set(BUILD_SHARED_LIBS ON)
+
+# Name of C compiler.
+set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/arm-linux-androideabi-gcc")
+set(CMAKE_CXX_COMPILER "${CROSS_PATH}/bin/arm-linux-androideabi-g++")
+
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
+
+# Where to look for the target environment. (More paths can be added here)
+set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
+
+# Adjust the default behavior of the FIND_XXX() commands:
+# search programs in the host environment only.
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+
+# Search headers and libraries in the target environment only.
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
similarity index 53%
rename from cross-arm-linux-gnueabihf.cmake
rename to contrib/cross-arm-linux-gnueabihf.cmake
index 12cf3e9..289f27a 100644 (file)
@@ -15,6 +15,22 @@ set(CMAKE_SYSTEM_NAME Linux)
 set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/arm-linux-gnueabihf-gcc")
 set(CMAKE_CXX_COMPILER "${CROSS_PATH}/bin/arm-linux-gnueabihf-g++")
 
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
+
 # Where to look for the target environment. (More paths can be added here)
 set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
 
similarity index 53%
rename from cross-esp32.cmake
rename to contrib/cross-esp32.cmake
index f978e80..2c8996b 100644 (file)
 set(CMAKE_SYSTEM_NAME Linux)
 
 # Name of C compiler.
-set(CMAKE_C_COMPILER   "${CROSS_PATH}/bin/xtensa-esp32-elf-gcc")
-set(CMAKE_AR           "${CROSS_PATH}/bin/xtensa-esp32-elf-ar")
-set(CMAKE_RANLIB       "${CROSS_PATH}/bin/xtensa-esp32-elf-ranlib")
-set(CMAKE_LINKER       "${CROSS_PATH}/bin/xtensa-esp32-elf-ld")
+set(CMAKE_C_COMPILER   "${CROSS_PATH}/bin/xtensa-esp32-elf-gcc${EXECUTABLE_EXT}")
+set(CMAKE_AR           "${CROSS_PATH}/bin/xtensa-esp32-elf-ar${EXECUTABLE_EXT}")
+set(CMAKE_RANLIB       "${CROSS_PATH}/bin/xtensa-esp32-elf-ranlib${EXECUTABLE_EXT}")
+set(CMAKE_LINKER       "${CROSS_PATH}/bin/xtensa-esp32-elf-ld${EXECUTABLE_EXT}")
+
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
 
 SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror \
        -I${BUILD_DIR_BASE}/include \
+       -I${IDF_PATH}/components/newlib/platform_include \
        -I${IDF_PATH}/components/mdns/include \
+       -I${IDF_PATH}/components/heap/include \
        -I${IDF_PATH}/components/driver/include \
        -I${IDF_PATH}/components/spi_flash/include \
        -I${IDF_PATH}/components/nvs_flash/include \
@@ -29,6 +47,8 @@ SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror \
        -I${IDF_PATH}/components/bootloader_support/include/ \
        -I${IDF_PATH}/components/app_update/include/ \
        -I$(IDF_PATH)/components/soc/esp32/include/ \
+       -I$(IDF_PATH)/components/soc/include/ \
+       -I$(IDF_PATH)/components/vfs/include/ \
        ${LWS_C_FLAGS} -Os \
        -I${IDF_PATH}/components/nvs_flash/test_nvs_host \
        -I${IDF_PATH}/components/freertos/include" CACHE STRING "" FORCE)
similarity index 54%
rename from cross-ming.cmake
rename to contrib/cross-ming.cmake
index 94989f2..1b21014 100644 (file)
@@ -18,6 +18,22 @@ set(CMAKE_C_COMPILER "${CROSS_PATH}/x86_64-w64-mingw32-gcc")
 set(CMAKE_RC_COMPILER "${CROSS_PATH}/x86_64-w64-mingw32-windres")
 set(CMAKE_C_FLAGS "-Wno-error")
 
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
+
 # Where to look for the target environment. (More paths can be added here)
 set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
 
similarity index 97%
rename from cross-openwrt-makefile
rename to contrib/cross-openwrt-makefile
index 2504370..8acab6b 100644 (file)
@@ -25,13 +25,13 @@ CMAKE_OPTIONS += -DLWS_WITHOUT_TESTAPPS=$(if $(CONFIG_PACKAGE_libwebsockets-exam
 
 # for wolfssl, define these in addition to LWS_OPENSSL_SUPPORT and
 # edit package/libs/wolfssl/Makefile to include --enable-opensslextra
-# CMAKE_OPTIONS += -DLWS_USE_WOLFSSL=ON
+# CMAKE_OPTIONS += -DLWS_WITH_WOLFSSL=ON
 # CMAKE_OPTIONS += -DLWS_WOLFSSL_LIBRARIES=$(STAGING_DIR)/usr/lib/libwolfssl.so
 # CMAKE_OPTIONS += -DLWS_WOLFSSL_INCLUDE_DIRS=$(STAGING_DIR)/usr/include
 
 # for cyassl, define these in addition to LWS_OPENSSL_SUPPORT and
 # edit package/libs/wolfssl/Makefile to include --enable-opensslextra
-# CMAKE_OPTIONS += -DLWS_USE_CYASSL=ON
+# CMAKE_OPTIONS += -DLWS_WITH_CYASSL=ON
 # CMAKE_OPTIONS += -DLWS_CYASSL_LIBRARIES=$(STAGING_DIR)/usr/lib/libcyassl.so
 # CMAKE_OPTIONS += -DLWS_CYASSL_INCLUDE_DIRS=$(STAGING_DIR)/usr/include
 
diff --git a/contrib/cross-w32.cmake b/contrib/cross-w32.cmake
new file mode 100644 (file)
index 0000000..0512885
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# CMake Toolchain file for crosscompiling on 32bit Windows platforms.
+#
+# This can be used when running cmake in the following way:
+#  cd build/
+#  cmake .. -DCMAKE_TOOLCHAIN_FILE=../contrib/cross-w32.cmake -DLWS_WITH_SSL=0
+#
+
+set(CROSS_PATH /opt/mingw32)
+
+# Target operating system name.
+set(CMAKE_SYSTEM_NAME Windows)
+
+# Name of C compiler.
+set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/i686-w64-mingw32-gcc")
+set(CMAKE_CXX_COMPILER "${CROSS_PATH}/bin/i686-w64-mingw32-g++")
+set(CMAKE_RC_COMPILER "${CROSS_PATH}/bin/i686-w64-mingw32-windres")
+set(CMAKE_C_FLAGS "-Wno-error")
+
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
+
+# Where to look for the target environment. (More paths can be added here)
+set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
+
+# Adjust the default behavior of the FIND_XXX() commands:
+# search programs in the host environment only.
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+
+# Search headers and libraries in the target environment only.
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/contrib/cross-w64.cmake b/contrib/cross-w64.cmake
new file mode 100644 (file)
index 0000000..4fff882
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# CMake Toolchain file for crosscompiling on 64bit Windows platforms.
+#
+# This can be used when running cmake in the following way:
+#  cd build/
+#  cmake .. -DCMAKE_TOOLCHAIN_FILE=../contrib/cross-w64.cmake -DLWS_WITH_SSL=0
+#
+
+set(CROSS_PATH /opt/mingw64)
+
+# Target operating system name.
+set(CMAKE_SYSTEM_NAME Windows)
+
+# Name of C compiler.
+set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/x86_64-w64-mingw32-gcc")
+set(CMAKE_CXX_COMPILER "${CROSS_PATH}/bin/x86_64-w64-mingw32-g++")
+set(CMAKE_RC_COMPILER "${CROSS_PATH}/bin/x86_64-w64-mingw32-windres")
+set(CMAKE_C_FLAGS "-Wno-error")
+
+#
+# Different build system distros set release optimization level to different
+# things according to their local policy, eg, Fedora is -O2 and Ubuntu is -O3
+# here.  Actually the build system's local policy is completely unrelated to
+# our desire for cross-build release optimization policy for code built to run
+# on a completely different target than the build system itself.
+#
+# Since this goes last on the compiler commandline we have to override it to a
+# sane value for cross-build here.  Notice some gcc versions enable broken
+# optimizations with -O3.
+#
+if (CMAKE_BUILD_TYPE MATCHES RELEASE OR CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES release)
+       set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} -O2)
+       set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O2)
+endif()
+
+# Where to look for the target environment. (More paths can be added here)
+set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")
+
+# Adjust the default behavior of the FIND_XXX() commands:
+# search programs in the host environment only.
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+
+# Search headers and libraries in the target environment only.
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/doc-assets/abstract-overview.svg b/doc-assets/abstract-overview.svg
new file mode 100644 (file)
index 0000000..db1ac47
--- /dev/null
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="189.59mm" height="108.24mm" version="1.1" viewBox="0 0 189.59 108.24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <defs>
+  <marker id="Arrow1Send-70-8" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="Arrow1Send-70" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="Arrow1Send-7-0" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="Arrow1Send-6" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="Arrow1Send-7" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="Arrow1Send" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <filter id="filter5390" x="-.017241" y="-.021661" width="1.0345" height="1.0433" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.9381369"/>
+  </filter>
+ </defs>
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g transform="translate(673.86 359.62)">
+  <path transform="matrix(1.4034 0 0 .97704 -753.56 -386.92)" d="m59.044 32.54h130.59v103.94h-130.59z" filter="url(#filter5390)"/>
+  <path d="m-672.06-359.62h182.71v103.94h-182.71z" fill="#fff"/>
+  <path d="m-597.49-344.31h40.632v52.35h-40.632z" fill="#b3b3b3" stroke-width=".24059"/>
+  <path d="m-551.66-344.59h54.618v33.451h-54.618z" fill="#999" stroke="#999" stroke-width=".22298"/>
+  <path d="m-663.73-344.21h62.177v33.451h-62.177z" fill="#ccc" stroke-width=".23791"/>
+  <g>
+   <g stroke-width=".15691" aria-label="lws_abs_t">
+    <path d="m-596.55-347.57q0 0.21968 0.0565 0.31382 0.0628 0.0942 0.16947 0.0942 0.13181 0 0.30755-0.069l0.0439 0.36404q-0.0816 0.0502-0.23223 0.0816-0.14436 0.0314-0.26361 0.0314-0.23851 0-0.38915-0.14436-0.14436-0.15064-0.14436-0.52096v-3.7973h0.45191z"/>
+    <path d="m-593.89-349.96 0.55861 1.8328 0.11298 0.60255h0.0126l0.0941-0.6151 0.42681-1.8202h0.4268l-0.83478 3.2073h-0.25733l-0.63394-2.0587-0.0879-0.52723h-0.0126l-0.0879 0.53351-0.6151 2.0524h-0.25734l-0.85989-3.2073h0.4833l0.48329 1.8265 0.0753 0.60882h0.0125l0.11298-0.62138 0.51468-1.8139z"/>
+    <path d="m-591.92-347.34q0.12553 0.0753 0.295 0.13181 0.17574 0.0502 0.35776 0.0502 0.20713 0 0.35149-0.10043 0.14436-0.1067 0.14436-0.33893 0-0.19457-0.0879-0.3201t-0.22596-0.22596q-0.1318-0.10042-0.28872-0.18202-0.15691-0.0879-0.29499-0.20713-0.13181-0.11925-0.21968-0.28244-0.0879-0.16319-0.0879-0.41425 0-0.4017 0.21341-0.60255 0.21968-0.20713 0.6151-0.20713 0.25734 0 0.44563 0.0502 0.1883 0.0439 0.32638 0.12553l-0.11925 0.37659q-0.11926-0.0628-0.27617-0.10042-0.15691-0.0439-0.3201-0.0439-0.22596 0-0.33266 0.0942-0.10042 0.0942-0.10042 0.295 0 0.15691 0.0879 0.26989 0.0879 0.1067 0.21968 0.20085 0.13808 0.0879 0.29499 0.18202 0.15692 0.0941 0.28873 0.22595 0.13808 0.12553 0.22595 0.30755 0.0879 0.17575 0.0879 0.44564 0 0.17574-0.0565 0.33265-0.0565 0.15692-0.17574 0.27617-0.11298 0.11298-0.28872 0.18202-0.16947 0.069-0.4017 0.069-0.27617 0-0.47702-0.0565-0.20085-0.0502-0.33893-0.13808z"/>
+    <path d="m-590.12-345.95h2.0964v0.40798h-2.0964z"/>
+    <path d="m-587.68-349.77q0.18202-0.11298 0.43936-0.17574 0.26361-0.0628 0.55233-0.0628 0.26362 0 0.42053 0.0816 0.16319 0.0753 0.25106 0.2134 0.0942 0.13181 0.11926 0.30755 0.0314 0.16947 0.0314 0.35776 0 0.37659-0.0188 0.73436-0.0126 0.35776-0.0126 0.67786 0 0.23851 0.0126 0.44564 0.0188 0.20085 0.0628 0.38287h-0.34521l-0.1067-0.37032h-0.0251q-0.0942 0.16319-0.27617 0.28245-0.18202 0.11925-0.48957 0.11925-0.33894 0-0.55861-0.23223-0.21341-0.23851-0.21341-0.65276 0-0.26989 0.0879-0.45191 0.0942-0.18202 0.25734-0.295 0.16947-0.11298 0.39543-0.15691 0.23223-0.0502 0.51467-0.0502 0.0628 0 0.12553 0 0.0628 0 0.13181 6e-3 0.0188-0.19457 0.0188-0.34521 0-0.35776-0.1067-0.50212t-0.38915-0.14436q-0.17574 0-0.38287 0.0565-0.20712 0.0502-0.34521 0.13181zm1.362 1.5189q-0.0628-6e-3 -0.12553-6e-3 -0.0628-6e-3 -0.12553-6e-3 -0.15064 0-0.295 0.0251t-0.25734 0.0879-0.18202 0.16946q-0.0628 0.10671-0.0628 0.2699 0 0.25106 0.11926 0.38914 0.12553 0.13809 0.3201 0.13809 0.26362 0 0.40798-0.12554 0.14436-0.12553 0.20085-0.27616z"/>
+    <path d="m-585.14-351.22h0.45191v1.4938h0.0188q0.25734-0.31383 0.68414-0.31383 0.4833 0 0.72181 0.38287 0.24478 0.38287 0.24478 1.2114 0 0.84733-0.32638 1.2616-0.3201 0.41425-0.9101 0.41425-0.28872 0-0.52723-0.0628-0.23851-0.069-0.35776-0.15692zm0.45191 3.9354q0.0879 0.0502 0.2134 0.0816 0.13181 0.0251 0.27617 0.0251 0.32638 0 0.51468-0.30755 0.19457-0.31383 0.19457-0.96031 0-0.2699-0.0377-0.4833-0.0314-0.21968-0.1067-0.37659-0.069-0.15691-0.1883-0.23851-0.11297-0.0879-0.27616-0.0879-0.22596 0-0.3766 0.13808-0.14436 0.13181-0.2134 0.36404z"/>
+    <path d="m-582.47-347.34q0.12553 0.0753 0.29499 0.13181 0.17575 0.0502 0.35777 0.0502 0.20712 0 0.35148-0.10043 0.14436-0.1067 0.14436-0.33893 0-0.19457-0.0879-0.3201t-0.22595-0.22596q-0.13181-0.10042-0.28872-0.18202-0.15692-0.0879-0.295-0.20713-0.13181-0.11925-0.21968-0.28244-0.0879-0.16319-0.0879-0.41425 0-0.4017 0.2134-0.60255 0.21968-0.20713 0.6151-0.20713 0.25734 0 0.44564 0.0502 0.18829 0.0439 0.32638 0.12553l-0.11926 0.37659q-0.11925-0.0628-0.27617-0.10042-0.15691-0.0439-0.3201-0.0439-0.22595 0-0.33266 0.0942-0.10042 0.0942-0.10042 0.295 0 0.15691 0.0879 0.26989 0.0879 0.1067 0.21968 0.20085 0.13808 0.0879 0.295 0.18202 0.15691 0.0941 0.28872 0.22595 0.13808 0.12553 0.22595 0.30755 0.0879 0.17575 0.0879 0.44564 0 0.17574-0.0565 0.33265-0.0565 0.15692-0.17575 0.27617-0.11297 0.11298-0.28872 0.18202-0.16946 0.069-0.4017 0.069-0.27616 0-0.47701-0.0565-0.20085-0.0502-0.33894-0.13808z"/>
+    <path d="m-580.67-345.95h2.0964v0.40798h-2.0964z"/>
+    <path d="m-578.51-349.96h0.38287v-0.62138l0.45191-0.14436v0.76574h0.67786v0.40798h-0.67786v1.8704q0 0.27617 0.0628 0.4017 0.069 0.11925 0.21968 0.11925 0.12553 0 0.2134-0.0251 0.0942-0.0314 0.20085-0.0753l0.0879 0.35776q-0.13809 0.069-0.30756 0.1067-0.16319 0.0439-0.34521 0.0439-0.31382 0-0.45191-0.20085-0.1318-0.20713-0.1318-0.66532v-1.9332h-0.38287z"/>
+   </g>
+   <g>
+    <g dominant-baseline="auto" stroke-width=".10741" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="tx client_conn close ask_for_writeable set_timeout state">
+     <path d="m-630.55-341.48h0.26209v-0.42536l0.30935-0.0988v0.52418h0.46403v0.27928h-0.46403v1.2804q0 0.18904 0.043 0.27497 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14609-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.24491q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14178-0.0902-0.45543v-1.3233h-0.26209z"/>
+     <path d="m-628.76-340.43-0.56714-1.0484h0.3695l0.31794 0.61441 0.0859 0.2406 0.0902-0.2406 0.32654-0.61441h0.33943l-0.57144 1.0312 0.60581 1.1171h-0.35232l-0.36091-0.67456-0.0945-0.25779-0.0988 0.25779-0.36091 0.67456h-0.33943z"/>
+     <path d="m-629.15-334.07q-0.10741 0.0816-0.2449 0.12031-0.13749 0.0387-0.28787 0.0387-0.20623 0-0.34802-0.0773-0.14179-0.0816-0.23201-0.22772-0.0859-0.15038-0.1289-0.35661-0.0387-0.21053-0.0387-0.46403 0-0.54995 0.19335-0.83782 0.19764-0.28787 0.56284-0.28787 0.16757 0 0.28787 0.0301 0.12031 0.0301 0.20624 0.0773l-0.0859 0.27068q-0.17187-0.0988-0.3738-0.0988-0.23202 0-0.35232 0.20624-0.11601 0.20194-0.11601 0.64018 0 0.17616 0.0258 0.33084 0.0258 0.15467 0.0859 0.27068 0.0602 0.11171 0.15468 0.18045 0.0945 0.0644 0.23631 0.0644 0.11171 0 0.20623-0.0387 0.0988-0.0387 0.15897-0.0902z"/>
+     <path d="m-628.48-334.47q0 0.15038 0.0387 0.21483 0.043 0.0644 0.116 0.0644 0.0902 0 0.21053-0.0473l0.0301 0.2492q-0.0559 0.0344-0.15897 0.0559-0.0988 0.0215-0.18046 0.0215-0.16327 0-0.26638-0.0988-0.0988-0.10312-0.0988-0.35661v-2.5994h0.30935z"/>
+     <path d="m-627.76-336.11h0.30935v2.1483h-0.30935zm-0.0558-0.65308q0-0.10311 0.0558-0.16756 0.0602-0.0645 0.15468-0.0645 0.0945 0 0.15467 0.0645 0.0644 0.0601 0.0644 0.16756 0 0.10312-0.0644 0.16327-0.0602 0.0559-0.15467 0.0559-0.0945 0-0.15468-0.0602-0.0558-0.0602-0.0558-0.15897z"/>
+     <path d="m-625.59-334.11q-0.10312 0.0945-0.26209 0.14608-0.15898 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15038-0.14179-0.35661-0.043-0.20623-0.043-0.46403 0-0.54995 0.20193-0.83782 0.20194-0.28787 0.57144-0.28787 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.12031 0.0945 0.0902 0.15038 0.25349 0.0601 0.16327 0.0601 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18476 0.0301 0.33514 0.0301 0.15037 0.0945 0.25779 0.0644 0.10311 0.16326 0.16327 0.10312 0.0558 0.2535 0.0558 0.11601 0 0.22772-0.043 0.116-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.116 0.15038-0.13749 0.47262z"/>
+     <path d="m-624.01-333.96v-1.3104q0-0.32224-0.0773-0.46402-0.073-0.14609-0.26638-0.14609-0.17187 0-0.28358 0.10312t-0.16326 0.2535v1.5639h-0.30936v-2.1483h0.22342l0.0559 0.22771h0.0129q0.0816-0.116 0.21912-0.19764 0.14179-0.0816 0.33513-0.0816 0.13749 0 0.24061 0.0387 0.10741 0.0387 0.17616 0.13319 0.073 0.0902 0.10741 0.2449 0.0387 0.15468 0.0387 0.39099v1.3921z"/>
+     <path d="m-623.43-336.11h0.26208v-0.42536l0.30936-0.0988v0.52418h0.46402v0.27927h-0.46402v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21482 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45543v-1.3233h-0.26208z"/>
+     <path d="m-622.29-333.36h1.435v0.27928h-1.435z"/>
+     <path d="m-619.42-334.07q-0.10741 0.0816-0.2449 0.12031-0.13749 0.0387-0.28787 0.0387-0.20623 0-0.34802-0.0773-0.14178-0.0816-0.23201-0.22772-0.0859-0.15038-0.1289-0.35661-0.0387-0.21053-0.0387-0.46403 0-0.54995 0.19335-0.83782 0.19764-0.28787 0.56284-0.28787 0.16757 0 0.28787 0.0301 0.12031 0.0301 0.20624 0.0773l-0.0859 0.27068q-0.17187-0.0988-0.3738-0.0988-0.23202 0-0.35232 0.20624-0.11601 0.20194-0.11601 0.64018 0 0.17616 0.0258 0.33084 0.0258 0.15467 0.0859 0.27068 0.0602 0.11171 0.15468 0.18045 0.0945 0.0644 0.23631 0.0644 0.11171 0 0.20623-0.0387 0.0988-0.0387 0.15898-0.0902z"/>
+     <path d="m-619.24-335.04q0-0.58003 0.19765-0.85071 0.20193-0.27498 0.57144-0.27498 0.39528 0 0.58003 0.27927 0.18905 0.27928 0.18905 0.84642 0 0.58433-0.20194 0.85501-0.20194 0.27069-0.56714 0.27069-0.39529 0-0.58433-0.27928-0.18476-0.27927-0.18476-0.84642zm0.32225 0q0 0.18905 0.0215 0.34373 0.0258 0.15467 0.0773 0.26638 0.0558 0.11171 0.14178 0.17616 0.0859 0.0601 0.20624 0.0601 0.22342 0 0.33513-0.19764 0.11171-0.20194 0.11171-0.64878 0-0.18475-0.0258-0.33942-0.0215-0.15898-0.0773-0.27069-0.0516-0.11171-0.13749-0.17186-0.0859-0.0645-0.20623-0.0645-0.21913 0-0.33513 0.20194-0.11171 0.20194-0.11171 0.64448z"/>
+     <path d="m-616.2-333.96v-1.3104q0-0.32224-0.0773-0.46402-0.073-0.14609-0.26639-0.14609-0.17186 0-0.28357 0.10312t-0.16327 0.2535v1.5639h-0.30935v-2.1483h0.22342l0.0559 0.22771h0.0129q0.0816-0.116 0.21912-0.19764 0.14178-0.0816 0.33513-0.0816 0.13749 0 0.24061 0.0387 0.10741 0.0387 0.17615 0.13319 0.073 0.0902 0.10742 0.2449 0.0387 0.15468 0.0387 0.39099v1.3921z"/>
+     <path d="m-614.31-333.96v-1.3104q0-0.32224-0.0773-0.46402-0.073-0.14609-0.26639-0.14609-0.17186 0-0.28357 0.10312t-0.16327 0.2535v1.5639h-0.30935v-2.1483h0.22342l0.0559 0.22771h0.0129q0.0816-0.116 0.21913-0.19764 0.14178-0.0816 0.33513-0.0816 0.13749 0 0.2406 0.0387 0.10742 0.0387 0.17616 0.13319 0.073 0.0902 0.10742 0.2449 0.0387 0.15468 0.0387 0.39099v1.3921z"/>
+     <path d="m-629.15-328.7q-0.10741 0.0816-0.2449 0.1203t-0.28787 0.0387q-0.20623 0-0.34802-0.0773-0.14179-0.0816-0.23201-0.22772-0.0859-0.15038-0.1289-0.35661-0.0387-0.21054-0.0387-0.46403 0-0.54996 0.19335-0.83783 0.19764-0.28786 0.56284-0.28786 0.16757 0 0.28787 0.0301 0.12031 0.0301 0.20624 0.0773l-0.0859 0.27068q-0.17187-0.0988-0.3738-0.0988-0.23202 0-0.35232 0.20623-0.11601 0.20194-0.11601 0.64019 0 0.17616 0.0258 0.33083 0.0258 0.15468 0.0859 0.27068 0.0602 0.11171 0.15468 0.18046 0.0945 0.0644 0.23631 0.0644 0.11171 0 0.20623-0.0387 0.0988-0.0387 0.15897-0.0902z"/>
+     <path d="m-628.48-329.1q0 0.15037 0.0387 0.21482 0.043 0.0644 0.116 0.0644 0.0902 0 0.21053-0.0473l0.0301 0.2492q-0.0559 0.0344-0.15897 0.0558-0.0988 0.0215-0.18046 0.0215-0.16327 0-0.26638-0.0988-0.0988-0.10312-0.0988-0.35662v-2.5994h0.30935z"/>
+     <path d="m-627.9-329.67q0-0.58003 0.19764-0.85072 0.20194-0.27497 0.57144-0.27497 0.39528 0 0.58003 0.27927 0.18905 0.27928 0.18905 0.84642 0 0.58433-0.20194 0.85501-0.20193 0.27068-0.56714 0.27068-0.39528 0-0.58433-0.27927-0.18475-0.27928-0.18475-0.84642zm0.32224 0q0 0.18905 0.0215 0.34372 0.0258 0.15468 0.0773 0.26639 0.0558 0.11171 0.14179 0.17616 0.0859 0.0601 0.20623 0.0601 0.22342 0 0.33513-0.19764 0.11171-0.20194 0.11171-0.64878 0-0.18475-0.0258-0.33943-0.0215-0.15897-0.0773-0.27068-0.0516-0.11171-0.13749-0.17186-0.0859-0.0645-0.20623-0.0645-0.21912 0-0.33513 0.20194-0.11171 0.20194-0.11171 0.64448z"/>
+     <path d="m-625.99-328.94q0.0859 0.0516 0.20194 0.0902 0.1203 0.0344 0.2449 0.0344 0.14179 0 0.24061-0.0687 0.0988-0.073 0.0988-0.23201 0-0.13319-0.0602-0.21912-0.0602-0.0859-0.15468-0.15468-0.0902-0.0687-0.19764-0.1246-0.10741-0.0602-0.20194-0.14178-0.0902-0.0816-0.15038-0.19335-0.0602-0.11171-0.0602-0.28357 0-0.27498 0.14609-0.41247 0.15037-0.14178 0.42106-0.14178 0.17615 0 0.30505 0.0344 0.1289 0.0301 0.22342 0.0859l-0.0816 0.25779q-0.0816-0.043-0.18905-0.0687-0.10741-0.0301-0.21912-0.0301-0.15468 0-0.22772 0.0645-0.0688 0.0644-0.0688 0.20194 0 0.10741 0.0602 0.18475 0.0602 0.073 0.15037 0.13749 0.0945 0.0601 0.20194 0.1246 0.10742 0.0644 0.19764 0.15467 0.0945 0.0859 0.15468 0.21053 0.0602 0.12031 0.0602 0.30506 0 0.1203-0.0387 0.22771-0.0387 0.10742-0.1203 0.18905-0.0773 0.0773-0.19764 0.1246-0.11601 0.0473-0.27498 0.0473-0.18905 0-0.32654-0.0387-0.13749-0.0344-0.23201-0.0945z"/>
+     <path d="m-623.22-328.74q-0.10312 0.0945-0.26209 0.14608-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35231-0.0773-0.14609-0.0816-0.24491-0.22772-0.0945-0.15038-0.14178-0.35661-0.043-0.20624-0.043-0.46403 0-0.54996 0.20194-0.83783 0.20194-0.28786 0.57144-0.28786 0.1203 0 0.23631 0.0301 0.1203 0.0301 0.21483 0.1203 0.0945 0.0902 0.15037 0.2535 0.0602 0.16327 0.0602 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0644 0.10312 0.16327 0.16327 0.10312 0.0559 0.2535 0.0559 0.116 0 0.22771-0.043 0.11601-0.043 0.17616-0.10311zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19765 0-0.31365 0.15038-0.11601 0.15038-0.13749 0.47262z"/>
+     <path d="m-630.36-325.24q0.1246-0.0773 0.30076-0.12031 0.18046-0.043 0.3781-0.043 0.18045 0 0.28787 0.0559 0.11171 0.0516 0.17186 0.14609 0.0645 0.0902 0.0816 0.21053 0.0215 0.116 0.0215 0.2449 0 0.25779-0.0129 0.50269-9e-3 0.24491-9e-3 0.46403 0 0.16327 9e-3 0.30505 0.0129 0.13749 0.043 0.26209h-0.23631l-0.073-0.25349h-0.0172q-0.0645 0.11171-0.18905 0.19334-0.12459 0.0816-0.33513 0.0816-0.23201 0-0.38239-0.15898-0.14608-0.16326-0.14608-0.44684 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17616-0.20193 0.11601-0.0773 0.27068-0.10742 0.15897-0.0344 0.35232-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.1332 0.0129-0.23631 0-0.24491-0.073-0.34373-0.073-0.0988-0.26638-0.0988-0.1203 0-0.26209 0.0387-0.14179 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10312 0-0.20194 0.0172-0.0988 0.0172-0.17615 0.0601-0.0773 0.043-0.1246 0.11601-0.043 0.073-0.043 0.18475 0 0.17186 0.0816 0.26639 0.0859 0.0945 0.21913 0.0945 0.18045 0 0.27927-0.0859 0.0988-0.0859 0.13749-0.18905z"/>
+     <path d="m-628.65-323.57q0.0859 0.0516 0.20194 0.0902 0.1203 0.0344 0.2449 0.0344 0.14178 0 0.24061-0.0687 0.0988-0.073 0.0988-0.23201 0-0.1332-0.0602-0.21913-0.0602-0.0859-0.15467-0.15467-0.0902-0.0687-0.19764-0.1246-0.10742-0.0602-0.20194-0.14179-0.0902-0.0816-0.15038-0.19334-0.0602-0.11171-0.0602-0.28357 0-0.27498 0.14608-0.41247 0.15038-0.14179 0.42106-0.14179 0.17616 0 0.30506 0.0344 0.12889 0.0301 0.22342 0.0859l-0.0816 0.2578q-0.0816-0.043-0.18904-0.0688-0.10742-0.0301-0.21913-0.0301-0.15467 0-0.22771 0.0645-0.0688 0.0644-0.0688 0.20193 0 0.10742 0.0602 0.18475 0.0602 0.073 0.15038 0.13749 0.0945 0.0601 0.20194 0.1246 0.10741 0.0644 0.19764 0.15468 0.0945 0.0859 0.15468 0.21053 0.0601 0.1203 0.0601 0.30505 0 0.12031-0.0387 0.22772t-0.12031 0.18905q-0.0773 0.0773-0.19764 0.1246-0.116 0.0473-0.27497 0.0473-0.18905 0-0.32654-0.0387-0.13749-0.0344-0.23202-0.0945z"/>
+     <path d="m-626.7-324.19h-0.15898v0.96672h-0.30935v-3.0076h0.30935v1.8303l0.14179-0.0602 0.50269-0.91087h0.35662l-0.50699 0.8679-0.15038 0.13749 0.17615 0.16757 0.55426 0.97531h-0.3738z"/>
+     <path d="m-625.75-322.62h1.435v0.27927h-1.435z"/>
+     <path d="m-624.24-325.37h0.26209v-0.1203q0-0.40388 0.11601-0.58433 0.11601-0.18046 0.39528-0.18046 0.11171 0 0.20194 0.0129 0.0902 0.0129 0.18475 0.0559l-0.0773 0.26638q-0.0773-0.0344-0.14608-0.043-0.0644-0.0129-0.1246-0.0129-0.0859 0-0.13319 0.0344-0.0473 0.0344-0.073 0.10741-0.0215 0.073-0.0301 0.18905-4e-3 0.11171-4e-3 0.27498h0.44684v0.27928h-0.44684v1.869h-0.30935v-1.869h-0.26209z"/>
+     <path d="m-623.08-324.3q0-0.58004 0.19764-0.85072 0.20193-0.27498 0.57144-0.27498 0.39528 0 0.58003 0.27928 0.18905 0.27927 0.18905 0.84642 0 0.58433-0.20194 0.85501t-0.56714 0.27068q-0.39529 0-0.58433-0.27928-0.18475-0.27927-0.18475-0.84641zm0.32224 0q0 0.18904 0.0215 0.34372 0.0258 0.15467 0.0773 0.26638 0.0558 0.11171 0.14178 0.17616 0.0859 0.0602 0.20624 0.0602 0.22342 0 0.33513-0.19764 0.11171-0.20193 0.11171-0.64877 0-0.18476-0.0258-0.33943-0.0215-0.15897-0.0773-0.27068-0.0516-0.11171-0.13749-0.17186-0.0859-0.0645-0.20623-0.0645-0.21913 0-0.33513 0.20193-0.11171 0.20194-0.11171 0.64449z"/>
+     <path d="m-621.13-325.37h0.21913l0.0558 0.22772h0.0129q0.0602-0.1246 0.15468-0.19335 0.0988-0.073 0.2363-0.073 0.0988 0 0.22343 0.0387l-0.0602 0.31365q-0.11171-0.0387-0.19764-0.0387-0.13749 0-0.22342 0.0816-0.0859 0.0773-0.11171 0.21053v1.5811h-0.30935z"/>
+     <path d="m-620.21-322.62h1.435v0.27927h-1.435z"/>
+     <path d="m-617.37-325.37 0.38239 1.2546 0.0773 0.41247h9e-3l0.0644-0.42106 0.29217-1.246h0.29216l-0.57144 2.1955h-0.17616l-0.43395-1.4093-0.0601-0.36091h-9e-3l-0.0602 0.3652-0.42106 1.405h-0.17616l-0.58863-2.1955h0.33084l0.33083 1.2503 0.0516 0.41677h9e-3l0.0773-0.42536 0.35231-1.2417z"/>
+     <path d="m-615.99-325.37h0.21913l0.0558 0.22772h0.0129q0.0601-0.1246 0.15468-0.19335 0.0988-0.073 0.23631-0.073 0.0988 0 0.22342 0.0387l-0.0602 0.31365q-0.11171-0.0387-0.19764-0.0387-0.13749 0-0.22342 0.0816-0.0859 0.0773-0.11171 0.21053v1.5811h-0.30935z"/>
+     <path d="m-614.76-325.37h0.30935v2.1483h-0.30935zm-0.0559-0.65307q0-0.10312 0.0559-0.16757 0.0602-0.0644 0.15467-0.0644 0.0945 0 0.15468 0.0644 0.0644 0.0602 0.0644 0.16757 0 0.10311-0.0644 0.16326-0.0602 0.0559-0.15468 0.0559-0.0945 0-0.15467-0.0602-0.0559-0.0602-0.0559-0.15897z"/>
+     <path d="m-614.09-325.37h0.26209v-0.42536l0.30935-0.0988v0.52418h0.46402v0.27928h-0.46402v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14609-0.0172 0.0644-0.0215 0.13748-0.0516l0.0602 0.24491q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30936-0.13749-0.0902-0.14178-0.0902-0.45543v-1.3233h-0.26209z"/>
+     <path d="m-611.45-323.37q-0.10312 0.0945-0.26209 0.14608-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35231-0.0773-0.14609-0.0816-0.24491-0.22772-0.0945-0.15037-0.14178-0.35661-0.043-0.20623-0.043-0.46402 0-0.54996 0.20194-0.83783t0.57144-0.28787q0.1203 0 0.23631 0.0301 0.1203 0.0301 0.21483 0.1203 0.0945 0.0902 0.15037 0.2535 0.0602 0.16327 0.0602 0.42535 0 0.0731-9e-3 0.15898-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0645 0.10312 0.16327 0.16327 0.10312 0.0559 0.2535 0.0559 0.116 0 0.22771-0.043 0.11601-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32225-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19765 0-0.31365 0.15038-0.11601 0.15037-0.13749 0.47262z"/>
+     <path d="m-610.98-325.24q0.1246-0.0773 0.30076-0.12031 0.18045-0.043 0.37809-0.043 0.18046 0 0.28787 0.0559 0.11171 0.0516 0.17186 0.14609 0.0645 0.0902 0.0816 0.21053 0.0215 0.116 0.0215 0.2449 0 0.25779-0.0129 0.50269-9e-3 0.24491-9e-3 0.46403 0 0.16327 9e-3 0.30505 0.0129 0.13749 0.043 0.26209h-0.23631l-0.073-0.25349h-0.0172q-0.0645 0.11171-0.18905 0.19334-0.1246 0.0816-0.33513 0.0816-0.23201 0-0.38239-0.15898-0.14609-0.16326-0.14609-0.44684 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17615-0.20193 0.11601-0.0773 0.27069-0.10742 0.15897-0.0344 0.35231-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.1332 0.0129-0.23631 0-0.24491-0.073-0.34373-0.073-0.0988-0.26639-0.0988-0.1203 0-0.26209 0.0387-0.14178 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10312 0-0.20194 0.0172-0.0988 0.0172-0.17616 0.0601-0.0773 0.043-0.1246 0.11601-0.043 0.073-0.043 0.18475 0 0.17186 0.0816 0.26639 0.0859 0.0945 0.21912 0.0945 0.18046 0 0.27928-0.0859 0.0988-0.0859 0.13749-0.18905z"/>
+     <path d="m-609.24-326.23h0.30935v1.0226h0.0129q0.17615-0.21483 0.46832-0.21483 0.33083 0 0.4941 0.26209 0.16757 0.26209 0.16757 0.82923 0 0.58004-0.22342 0.86361-0.21913 0.28357-0.623 0.28357-0.19764 0-0.36091-0.043-0.16327-0.0473-0.2449-0.10741zm0.30935 2.6939q0.0602 0.0344 0.14608 0.0559 0.0902 0.0172 0.18905 0.0172 0.22342 0 0.35231-0.21053 0.1332-0.21482 0.1332-0.65737 0-0.18475-0.0258-0.33083-0.0215-0.15038-0.073-0.25779-0.0473-0.10742-0.1289-0.16327-0.0773-0.0602-0.18905-0.0602-0.15467 0-0.25779 0.0945-0.0988 0.0902-0.14608 0.2492z"/>
+     <path d="m-607.05-323.73q0 0.15038 0.0387 0.21483 0.043 0.0645 0.11601 0.0645 0.0902 0 0.21053-0.0473l0.0301 0.2492q-0.0558 0.0344-0.15897 0.0559-0.0988 0.0215-0.18045 0.0215-0.16327 0-0.26639-0.0988-0.0988-0.10311-0.0988-0.35661v-2.5994h0.30935z"/>
+     <path d="m-605.09-323.37q-0.10311 0.0945-0.26208 0.14608-0.15898 0.0516-0.33514 0.0516-0.20193 0-0.35231-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15037-0.14179-0.35661-0.043-0.20623-0.043-0.46402 0-0.54996 0.20194-0.83783t0.57144-0.28787q0.1203 0 0.23631 0.0301 0.1203 0.0301 0.21483 0.1203 0.0945 0.0902 0.15038 0.2535 0.0602 0.16327 0.0602 0.42535 0 0.0731-9e-3 0.15898-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0644 0.10312 0.16327 0.16327 0.10312 0.0559 0.2535 0.0559 0.11601 0 0.22772-0.043 0.116-0.043 0.17615-0.10312zm-0.2406-1.1515q9e-3 -0.32225-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.11601 0.15037-0.13749 0.47262z"/>
+     <path d="m-630.37-318.2q0.0859 0.0516 0.20194 0.0902 0.1203 0.0344 0.2449 0.0344 0.14179 0 0.24061-0.0687 0.0988-0.0731 0.0988-0.23202 0-0.13319-0.0602-0.21912-0.0602-0.0859-0.15468-0.15468-0.0902-0.0687-0.19764-0.1246-0.10741-0.0602-0.20193-0.14178-0.0902-0.0816-0.15038-0.19335-0.0602-0.11171-0.0602-0.28357 0-0.27498 0.14608-0.41247 0.15038-0.14178 0.42106-0.14178 0.17616 0 0.30505 0.0344 0.1289 0.0301 0.22342 0.0859l-0.0816 0.25779q-0.0816-0.043-0.18905-0.0687-0.10741-0.0301-0.21912-0.0301-0.15468 0-0.22772 0.0645-0.0687 0.0644-0.0687 0.20194 0 0.10741 0.0602 0.18475 0.0601 0.073 0.15038 0.13749 0.0945 0.0601 0.20193 0.1246 0.10742 0.0644 0.19764 0.15467 0.0945 0.0859 0.15468 0.21054 0.0602 0.1203 0.0602 0.30505 0 0.1203-0.0387 0.22772-0.0387 0.10741-0.1203 0.18904-0.0773 0.0773-0.19764 0.1246-0.11601 0.0473-0.27498 0.0473-0.18905 0-0.32654-0.0387-0.13749-0.0344-0.23201-0.0945z"/>
+     <path d="m-627.6-318q-0.10311 0.0945-0.26209 0.14609-0.15897 0.0516-0.33513 0.0516-0.20193 0-0.35231-0.0773-0.14608-0.0816-0.24491-0.22772-0.0945-0.15038-0.14178-0.35661-0.043-0.20624-0.043-0.46403 0-0.54996 0.20194-0.83782 0.20194-0.28787 0.57144-0.28787 0.1203 0 0.23631 0.0301 0.1203 0.0301 0.21483 0.12031 0.0945 0.0902 0.15038 0.25349 0.0602 0.16327 0.0602 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18476 0.0301 0.33513 0.0301 0.15038 0.0945 0.2578 0.0644 0.10311 0.16327 0.16327 0.10312 0.0558 0.2535 0.0558 0.116 0 0.22771-0.043 0.11601-0.043 0.17616-0.10312zm-0.2406-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038t-0.13749 0.47262z"/>
+     <path d="m-627.33-320h0.26208v-0.42536l0.30936-0.0988v0.52418h0.46402v0.27927h-0.46402v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21482 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45543v-1.3233h-0.26208z"/>
+     <path d="m-626.18-317.25h1.435v0.27928h-1.435z"/>
+     <path d="m-624.71-320h0.26209v-0.42536l0.30935-0.0988v0.52418h0.46403v0.27927h-0.46403v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15037 0.0816 0.0859 0 0.14609-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45543v-1.3233h-0.26209z"/>
+     <path d="m-623.26-320h0.30935v2.1483h-0.30935zm-0.0558-0.65308q0-0.10311 0.0558-0.16756 0.0602-0.0645 0.15468-0.0645 0.0945 0 0.15467 0.0645 0.0645 0.0601 0.0645 0.16756 0 0.10312-0.0645 0.16327-0.0602 0.0559-0.15467 0.0559-0.0945 0-0.15468-0.0602-0.0558-0.0602-0.0558-0.15897z"/>
+     <path d="m-621.38-317.85v-1.2761q0-0.17186-0.0129-0.29216-9e-3 -0.1246-0.043-0.20194-0.0344-0.0773-0.0945-0.11171-0.0601-0.0387-0.15897-0.0387-0.14608 0-0.2492 0.11601-0.0988 0.11171-0.13749 0.25779v1.5468h-0.30935v-2.1483h0.21912l0.0559 0.22771h0.0129q0.0902-0.1246 0.21483-0.20193 0.1246-0.0773 0.31794-0.0773 0.16327 0 0.26639 0.073 0.10741 0.0687 0.16756 0.2492 0.0773-0.15038 0.21912-0.23631 0.14609-0.0859 0.31795-0.0859 0.14178 0 0.2406 0.0387 0.10312 0.0344 0.16327 0.12889 0.0644 0.0902 0.0945 0.24491 0.0301 0.15038 0.0301 0.38239v1.405h-0.30935v-1.3663q0-0.27928-0.0558-0.41677-0.0516-0.13749-0.24491-0.13749-0.16326 0-0.26208 0.10312-0.0945 0.0988-0.1332 0.27068v1.5468z"/>
+     <path d="m-618.3-318q-0.10312 0.0945-0.26209 0.14609-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15038-0.14179-0.35661-0.043-0.20624-0.043-0.46403 0-0.54996 0.20193-0.83782 0.20194-0.28787 0.57144-0.28787 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.12031 0.0945 0.0902 0.15038 0.25349 0.0601 0.16327 0.0601 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18476 0.0301 0.33513 0.0301 0.15038 0.0945 0.2578 0.0645 0.10311 0.16327 0.16327 0.10311 0.0558 0.25349 0.0558 0.11601 0 0.22772-0.043 0.116-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.116 0.15038-0.13749 0.47262z"/>
+     <path d="m-617.94-318.93q0-0.58003 0.19764-0.85071 0.20193-0.27498 0.57144-0.27498 0.39528 0 0.58003 0.27927 0.18905 0.27928 0.18905 0.84642 0 0.58433-0.20194 0.85501-0.20194 0.27069-0.56714 0.27069-0.39529 0-0.58433-0.27928-0.18475-0.27927-0.18475-0.84642zm0.32224 0q0 0.18905 0.0215 0.34373 0.0258 0.15467 0.0773 0.26638 0.0558 0.11171 0.14178 0.17616 0.0859 0.0601 0.20624 0.0601 0.22342 0 0.33513-0.19764 0.11171-0.20194 0.11171-0.64878 0-0.18475-0.0258-0.33942-0.0215-0.15898-0.0773-0.27069-0.0516-0.11171-0.13749-0.17186-0.0859-0.0645-0.20623-0.0645-0.21913 0-0.33513 0.20194-0.11171 0.20194-0.11171 0.64448z"/>
+     <path d="m-615.72-320v1.3147q0 0.32654 0.0644 0.46832 0.0687 0.13749 0.24491 0.13749 0.0902 0 0.15897-0.0344 0.073-0.0387 0.1289-0.0988 0.0558-0.0601 0.0988-0.13749 0.043-0.0773 0.0687-0.15897v-1.4909h0.30935v1.5382q0 0.15467 9e-3 0.32224 0.0129 0.16327 0.0344 0.28787h-0.21913l-0.0773-0.30076h-0.0129q-0.073 0.14179-0.21053 0.2492-0.13749 0.10312-0.34373 0.10312-0.13749 0-0.2406-0.0344-0.10312-0.0344-0.17616-0.1246-0.073-0.0902-0.11171-0.2449-0.0344-0.15897-0.0344-0.40387v-1.3921z"/>
+     <path d="m-614.35-320h0.26209v-0.42536l0.30935-0.0988v0.52418h0.46402v0.27927h-0.46402v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0645-0.0215 0.13749-0.0516l0.0601 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21482 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45543v-1.3233h-0.26209z"/>
+     <path d="m-630.37-312.83q0.0859 0.0516 0.20194 0.0902 0.1203 0.0344 0.2449 0.0344 0.14179 0 0.24061-0.0687 0.0988-0.073 0.0988-0.23201 0-0.13319-0.0602-0.21912-0.0602-0.0859-0.15468-0.15468-0.0902-0.0687-0.19764-0.1246-0.10741-0.0602-0.20193-0.14179-0.0902-0.0816-0.15038-0.19334-0.0602-0.11171-0.0602-0.28357 0-0.27498 0.14608-0.41247 0.15038-0.14179 0.42106-0.14179 0.17616 0 0.30505 0.0344 0.1289 0.0301 0.22342 0.0859l-0.0816 0.25779q-0.0816-0.043-0.18905-0.0687-0.10741-0.0301-0.21912-0.0301-0.15468 0-0.22772 0.0645-0.0687 0.0644-0.0687 0.20194 0 0.10741 0.0602 0.18475 0.0601 0.073 0.15038 0.13749 0.0945 0.0601 0.20193 0.1246 0.10742 0.0644 0.19764 0.15467 0.0945 0.0859 0.15468 0.21053 0.0602 0.1203 0.0602 0.30506 0 0.1203-0.0387 0.22771-0.0387 0.10742-0.1203 0.18905-0.0773 0.0773-0.19764 0.1246-0.11601 0.0473-0.27498 0.0473-0.18905 0-0.32654-0.0387-0.13749-0.0344-0.23201-0.0945z"/>
+     <path d="m-629.1-314.63h0.26209v-0.42535l0.30935-0.0988v0.52417h0.46403v0.27928h-0.46403v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15037 0.0816 0.0859 0 0.14609-0.0172 0.0645-0.0215 0.13749-0.0516l0.0601 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45544v-1.3233h-0.26209z"/>
+     <path d="m-627.72-314.5q0.1246-0.0773 0.30076-0.1203 0.18045-0.043 0.3781-0.043 0.18045 0 0.28786 0.0559 0.11171 0.0516 0.17187 0.14608 0.0644 0.0902 0.0816 0.21053 0.0215 0.11601 0.0215 0.2449 0 0.25779-0.0129 0.5027-9e-3 0.2449-9e-3 0.46402 0 0.16327 9e-3 0.30506 0.0129 0.13749 0.043 0.26209h-0.23631l-0.073-0.2535h-0.0172q-0.0644 0.11171-0.18905 0.19335-0.1246 0.0816-0.33513 0.0816-0.23201 0-0.38239-0.15897-0.14608-0.16327-0.14608-0.44684 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17616-0.20194 0.116-0.0773 0.27068-0.10741 0.15897-0.0344 0.35232-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.13319 0.0129-0.23631 0-0.2449-0.073-0.34372-0.073-0.0988-0.26638-0.0988-0.12031 0-0.26209 0.0387-0.14179 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10312 0-0.20194 0.0172-0.0988 0.0172-0.17616 0.0602-0.0773 0.043-0.1246 0.116-0.043 0.073-0.043 0.18476 0 0.17186 0.0816 0.26638 0.0859 0.0945 0.21913 0.0945 0.18045 0 0.27927-0.0859 0.0988-0.0859 0.13749-0.18904z"/>
+     <path d="m-626.2-314.63h0.26209v-0.42535l0.30935-0.0988v0.52417h0.46403v0.27928h-0.46403v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45544v-1.3233h-0.26209z"/>
+     <path d="m-623.55-312.63q-0.10312 0.0945-0.26209 0.14608-0.15898 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22771-0.0945-0.15038-0.14179-0.35661-0.043-0.20624-0.043-0.46403 0-0.54996 0.20193-0.83783 0.20194-0.28787 0.57144-0.28787 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.1203 0.0945 0.0902 0.15038 0.2535 0.0602 0.16327 0.0602 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0644 0.10312 0.16326 0.16327 0.10312 0.0559 0.2535 0.0559 0.11601 0 0.22772-0.043 0.116-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.116 0.15038-0.13749 0.47262z"/>
+    </g>
+    <g dominant-baseline="auto" stroke-width=".10741" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="accept rx writeable closed heartbeat">
+     <path d="m-539.98-339.16q0.1246-0.0773 0.30076-0.1203 0.18045-0.043 0.37809-0.043 0.18046 0 0.28787 0.0559 0.11171 0.0516 0.17186 0.14608 0.0644 0.0902 0.0816 0.21053 0.0215 0.11601 0.0215 0.24491 0 0.25779-0.0129 0.50269-9e-3 0.2449-9e-3 0.46403 0 0.16327 9e-3 0.30505 0.0129 0.13749 0.043 0.26209h-0.23631l-0.073-0.25349h-0.0172q-0.0644 0.11171-0.18905 0.19334-0.1246 0.0816-0.33513 0.0816-0.23201 0-0.38239-0.15897-0.14608-0.16327-0.14608-0.44684 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17616-0.20194 0.116-0.0773 0.27068-0.10741 0.15897-0.0344 0.35232-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.13319 0.0129-0.23631 0-0.2449-0.073-0.34372-0.073-0.0988-0.26638-0.0988-0.12031 0-0.26209 0.0387-0.14179 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10312 0-0.20194 0.0172-0.0988 0.0172-0.17616 0.0601-0.0773 0.043-0.1246 0.11601-0.043 0.073-0.043 0.18475 0 0.17186 0.0816 0.26639 0.0859 0.0945 0.21913 0.0945 0.18045 0 0.27927-0.0859 0.0988-0.0859 0.13749-0.18905z"/>
+     <path d="m-537.05-337.25q-0.10741 0.0816-0.2449 0.12031-0.13749 0.0387-0.28787 0.0387-0.20623 0-0.34802-0.0773-0.14178-0.0816-0.23201-0.22772-0.0859-0.15038-0.1289-0.35661-0.0387-0.21053-0.0387-0.46403 0-0.54995 0.19335-0.83782 0.19764-0.28787 0.56285-0.28787 0.16756 0 0.28786 0.0301 0.12031 0.0301 0.20624 0.0773l-0.0859 0.27068q-0.17186-0.0988-0.3738-0.0988-0.23202 0-0.35232 0.20624-0.116 0.20194-0.116 0.64018 0 0.17616 0.0258 0.33084 0.0258 0.15467 0.0859 0.27068 0.0602 0.11171 0.15467 0.18045 0.0945 0.0645 0.23631 0.0645 0.11171 0 0.20623-0.0387 0.0988-0.0387 0.15898-0.0902z"/>
+     <path d="m-535.6-337.25q-0.10741 0.0816-0.2449 0.12031-0.13749 0.0387-0.28787 0.0387-0.20623 0-0.34802-0.0773-0.14178-0.0816-0.23201-0.22772-0.0859-0.15038-0.1289-0.35661-0.0387-0.21053-0.0387-0.46403 0-0.54995 0.19334-0.83782 0.19764-0.28787 0.56285-0.28787 0.16756 0 0.28786 0.0301 0.12031 0.0301 0.20624 0.0773l-0.0859 0.27068q-0.17186-0.0988-0.3738-0.0988-0.23201 0-0.35232 0.20624-0.116 0.20194-0.116 0.64018 0 0.17616 0.0258 0.33084 0.0258 0.15467 0.0859 0.27068 0.0601 0.11171 0.15467 0.18045 0.0945 0.0645 0.23631 0.0645 0.11171 0 0.20624-0.0387 0.0988-0.0387 0.15897-0.0902z"/>
+     <path d="m-534.05-337.29q-0.10312 0.0945-0.26209 0.14608-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15038-0.14179-0.35661-0.043-0.20623-0.043-0.46403 0-0.54995 0.20193-0.83782 0.20194-0.28787 0.57144-0.28787 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.1203 0.0945 0.0902 0.15038 0.25349 0.0601 0.16327 0.0601 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17187h-1.0913q0 0.18475 0.0301 0.33513 0.0301 0.15037 0.0945 0.25779 0.0644 0.10312 0.16327 0.16327 0.10311 0.0559 0.25349 0.0559 0.11601 0 0.22772-0.043 0.116-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.116 0.15038-0.13749 0.47262z"/>
+     <path d="m-533.56-339.29h0.21912l0.0473 0.23201h0.0172q0.15897-0.28357 0.4984-0.28357 0.33942 0 0.50699 0.2535 0.17186 0.25349 0.17186 0.82923 0 0.27068-0.0559 0.4898-0.0558 0.21483-0.15897 0.36951-0.10311 0.15037-0.25349 0.23201-0.14609 0.0773-0.32654 0.0773-0.1246 0-0.19764-0.0172-0.073-0.0129-0.15897-0.0602v0.88509h-0.30935zm0.30935 1.8088q0.0602 0.0516 0.13319 0.0816 0.0773 0.0301 0.20194 0.0301 0.22771 0 0.36091-0.23201 0.13319-0.23201 0.13319-0.66167 0-0.18045-0.0258-0.32654-0.0215-0.14608-0.073-0.2492-0.0516-0.10741-0.13319-0.16326-0.0773-0.0602-0.19335-0.0602-0.31365 0-0.40387 0.3824z"/>
+     <path d="m-531.91-339.29h0.26209v-0.42536l0.30935-0.0988v0.52418h0.46403v0.27927h-0.46403v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0601 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14178-0.0902-0.45543v-1.3233h-0.26209z"/>
+     <path d="m-533.45-333.92h0.21912l0.0559 0.22772h0.0129q0.0602-0.1246 0.15467-0.19334 0.0988-0.073 0.23631-0.073 0.0988 0 0.22342 0.0387l-0.0602 0.31365q-0.11171-0.0387-0.19764-0.0387-0.13749 0-0.22342 0.0816-0.0859 0.0773-0.11171 0.21053v1.5811h-0.30935z"/>
+     <path d="m-531.84-332.87-0.56714-1.0484h0.3695l0.31794 0.61441 0.0859 0.24061 0.0902-0.24061 0.32654-0.61441h0.33942l-0.57143 1.0312 0.60581 1.1171h-0.35232l-0.36091-0.67455-0.0945-0.25779-0.0988 0.25779-0.36091 0.67455h-0.33943z"/>
+     <path d="m-543.28-328.55 0.38239 1.2546 0.0773 0.41247h9e-3l0.0644-0.42106 0.29216-1.246h0.29217l-0.57144 2.1955h-0.17616l-0.43395-1.4093-0.0602-0.36091h-9e-3l-0.0602 0.3652-0.42106 1.405h-0.17616l-0.58862-2.1955h0.33083l0.33083 1.2503 0.0516 0.41677h9e-3l0.0773-0.42536 0.35232-1.2417z"/>
+     <path d="m-541.89-328.55h0.21912l0.0559 0.22772h0.0129q0.0602-0.1246 0.15468-0.19335 0.0988-0.073 0.23631-0.073 0.0988 0 0.22342 0.0387l-0.0602 0.31365q-0.11171-0.0387-0.19764-0.0387-0.13749 0-0.22342 0.0816-0.0859 0.0773-0.11171 0.21053v1.5811h-0.30935z"/>
+     <path d="m-540.66-328.55h0.30935v2.1483h-0.30935zm-0.0558-0.65307q0-0.10312 0.0558-0.16757 0.0602-0.0644 0.15468-0.0644 0.0945 0 0.15467 0.0644 0.0644 0.0602 0.0644 0.16757 0 0.10311-0.0644 0.16327-0.0602 0.0558-0.15467 0.0558-0.0945 0-0.15468-0.0601-0.0558-0.0602-0.0558-0.15897z"/>
+     <path d="m-540-328.55h0.26209v-0.42536l0.30935-0.0988v0.52418h0.46403v0.27928h-0.46403v1.2804q0 0.18904 0.043 0.27497 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.24491q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14178-0.0902-0.45543v-1.3233h-0.26209z"/>
+     <path d="m-537.35-326.55q-0.10311 0.0945-0.26208 0.14608-0.15898 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22771-0.0945-0.15038-0.14179-0.35662-0.043-0.20623-0.043-0.46402 0-0.54996 0.20193-0.83783 0.20194-0.28787 0.57144-0.28787 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.1203 0.0945 0.0902 0.15038 0.2535 0.0602 0.16327 0.0602 0.42535 0 0.073-9e-3 0.15898-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0644 0.10312 0.16326 0.16327 0.10312 0.0559 0.2535 0.0559 0.11601 0 0.22772-0.043 0.116-0.043 0.17615-0.10312zm-0.2406-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038t-0.13749 0.47262z"/>
+     <path d="m-536.88-328.42q0.1246-0.0773 0.30076-0.12031 0.18045-0.043 0.37809-0.043 0.18046 0 0.28787 0.0559 0.11171 0.0516 0.17186 0.14609 0.0644 0.0902 0.0816 0.21053 0.0215 0.116 0.0215 0.2449 0 0.25779-0.0129 0.50269-9e-3 0.24491-9e-3 0.46403 0 0.16327 9e-3 0.30506 0.0129 0.13748 0.043 0.26208h-0.23631l-0.073-0.25349h-0.0172q-0.0644 0.11171-0.18905 0.19334-0.1246 0.0816-0.33513 0.0816-0.23201 0-0.38239-0.15897-0.14608-0.16327-0.14608-0.44685 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17616-0.20193 0.116-0.0773 0.27068-0.10742 0.15897-0.0344 0.35232-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.1332 0.0129-0.23631 0-0.24491-0.073-0.34373-0.073-0.0988-0.26638-0.0988-0.12031 0-0.26209 0.0387-0.14179 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10312 0-0.20194 0.0172-0.0988 0.0172-0.17616 0.0602-0.0773 0.043-0.1246 0.11601-0.043 0.073-0.043 0.18475 0 0.17186 0.0816 0.26639 0.0859 0.0945 0.21913 0.0945 0.18045 0 0.27927-0.0859 0.0988-0.0859 0.13749-0.18905z"/>
+     <path d="m-535.14-329.41h0.30935v1.0226h0.0129q0.17616-0.21483 0.46832-0.21483 0.33084 0 0.4941 0.26209 0.16757 0.26209 0.16757 0.82923 0 0.58004-0.22342 0.86361-0.21912 0.28357-0.623 0.28357-0.19764 0-0.36091-0.043-0.16327-0.0473-0.2449-0.10741zm0.30935 2.6939q0.0601 0.0344 0.14608 0.0559 0.0902 0.0172 0.18905 0.0172 0.22342 0 0.35232-0.21054 0.13319-0.21482 0.13319-0.65737 0-0.18475-0.0258-0.33083-0.0215-0.15038-0.073-0.25779-0.0473-0.10742-0.1289-0.16327-0.0773-0.0602-0.18905-0.0602-0.15467 0-0.25779 0.0945-0.0988 0.0902-0.14608 0.2492z"/>
+     <path d="m-532.95-326.92q0 0.15038 0.0387 0.21483 0.043 0.0644 0.11601 0.0644 0.0902 0 0.21053-0.0473l0.0301 0.2492q-0.0559 0.0344-0.15897 0.0558-0.0988 0.0215-0.18046 0.0215-0.16327 0-0.26638-0.0988-0.0988-0.10311-0.0988-0.35661v-2.5994h0.30936z"/>
+     <path d="m-531-326.55q-0.10312 0.0945-0.26209 0.14608-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22771-0.0945-0.15038-0.14179-0.35662-0.043-0.20623-0.043-0.46402 0-0.54996 0.20194-0.83783 0.20193-0.28787 0.57144-0.28787 0.1203 0 0.23631 0.0301 0.1203 0.0301 0.21482 0.1203 0.0945 0.0902 0.15038 0.2535 0.0601 0.16327 0.0601 0.42535 0 0.073-9e-3 0.15898-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0645 0.10312 0.16327 0.16327 0.10311 0.0559 0.25349 0.0559 0.11601 0 0.22772-0.043 0.11601-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31364 0.15038-0.11601 0.15038-0.13749 0.47262z"/>
+     <path d="m-538.79-321.14q-0.10742 0.0816-0.24491 0.12031-0.13749 0.0387-0.28787 0.0387-0.20623 0-0.34802-0.0773-0.14178-0.0816-0.23201-0.22772-0.0859-0.15038-0.12889-0.35661-0.0387-0.21053-0.0387-0.46403 0-0.54995 0.19334-0.83782 0.19764-0.28787 0.56285-0.28787 0.16756 0 0.28787 0.0301 0.1203 0.0301 0.20623 0.0773l-0.0859 0.27068q-0.17186-0.0988-0.3738-0.0988-0.23201 0-0.35232 0.20624-0.116 0.20194-0.116 0.64018 0 0.17616 0.0258 0.33084 0.0258 0.15467 0.0859 0.27068 0.0602 0.11171 0.15467 0.18045 0.0945 0.0645 0.23631 0.0645 0.11171 0 0.20624-0.0387 0.0988-0.0387 0.15897-0.0902z"/>
+     <path d="m-538.12-321.54q0 0.15038 0.0387 0.21483 0.043 0.0644 0.11601 0.0644 0.0902 0 0.21053-0.0473l0.0301 0.2492q-0.0558 0.0344-0.15897 0.0559-0.0988 0.0215-0.18045 0.0215-0.16327 0-0.26639-0.0988-0.0988-0.10312-0.0988-0.35661v-2.5994h0.30935z"/>
+     <path d="m-537.54-322.11q0-0.58003 0.19764-0.85071 0.20194-0.27498 0.57144-0.27498 0.39529 0 0.58004 0.27927 0.18904 0.27928 0.18904 0.84642 0 0.58433-0.20193 0.85501-0.20194 0.27069-0.56715 0.27069-0.39528 0-0.58433-0.27928-0.18475-0.27927-0.18475-0.84642zm0.32224 0q0 0.18905 0.0215 0.34373 0.0258 0.15467 0.0773 0.26638 0.0559 0.11171 0.14179 0.17616 0.0859 0.0602 0.20623 0.0602 0.22342 0 0.33513-0.19764 0.11171-0.20194 0.11171-0.64878 0-0.18475-0.0258-0.33942-0.0215-0.15898-0.0773-0.27069-0.0516-0.11171-0.13749-0.17186-0.0859-0.0644-0.20624-0.0644-0.21912 0-0.33513 0.20194-0.11171 0.20194-0.11171 0.64448z"/>
+     <path d="m-535.63-321.39q0.0859 0.0516 0.20193 0.0902 0.12031 0.0344 0.24491 0.0344 0.14178 0 0.2406-0.0687 0.0988-0.073 0.0988-0.23202 0-0.13319-0.0602-0.21912-0.0602-0.0859-0.15467-0.15468-0.0902-0.0687-0.19765-0.1246-0.10741-0.0601-0.20193-0.14178-0.0902-0.0816-0.15038-0.19335-0.0602-0.11171-0.0602-0.28357 0-0.27498 0.14608-0.41247 0.15038-0.14178 0.42106-0.14178 0.17616 0 0.30505 0.0344 0.1289 0.0301 0.22342 0.0859l-0.0816 0.25779q-0.0816-0.043-0.18905-0.0687-0.10741-0.0301-0.21912-0.0301-0.15468 0-0.22772 0.0644-0.0687 0.0645-0.0687 0.20194 0 0.10741 0.0602 0.18475 0.0602 0.073 0.15038 0.13749 0.0945 0.0602 0.20194 0.1246 0.10741 0.0644 0.19764 0.15468 0.0945 0.0859 0.15467 0.21053 0.0602 0.1203 0.0602 0.30505 0 0.1203-0.0387 0.22772-0.0387 0.10741-0.1203 0.18904-0.0773 0.0773-0.19764 0.1246-0.11601 0.0473-0.27498 0.0473-0.18905 0-0.32653-0.0387-0.13749-0.0344-0.23202-0.0945z"/>
+     <path d="m-532.86-321.18q-0.10312 0.0945-0.26209 0.14608-0.15898 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15038-0.14179-0.35661-0.043-0.20623-0.043-0.46403 0-0.54995 0.20193-0.83782 0.20194-0.28787 0.57144-0.28787 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.12031 0.0945 0.0902 0.15038 0.25349 0.0602 0.16327 0.0602 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18476 0.0301 0.33514 0.0301 0.15037 0.0945 0.25779 0.0644 0.10311 0.16326 0.16327 0.10312 0.0559 0.2535 0.0559 0.11601 0 0.22772-0.043 0.116-0.043 0.17616-0.10312zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.116 0.15038-0.13749 0.47262z"/>
+     <path d="m-531.02-321.77q0 0.21913 4e-3 0.39958 4e-3 0.17616 0.0301 0.34802h-0.21053l-0.0688-0.25779h-0.0172q-0.0602 0.12889-0.18905 0.21483-0.1289 0.0859-0.30935 0.0859-0.34802 0-0.51988-0.27069-0.16757-0.27068-0.16757-0.85071 0-0.54996 0.20623-0.83353 0.21053-0.28357 0.57574-0.28357 0.1246 0 0.19764 0.0172 0.073 0.0129 0.15897 0.0473v-0.88509h0.30935zm-0.30935-1.0698q-0.0602-0.0516-0.13749-0.073-0.073-0.0258-0.19764-0.0258-0.22771 0-0.35661 0.20624-0.1246 0.20623-0.1246 0.63589 0 0.18904 0.0215 0.34372 0.0258 0.15038 0.073 0.26209 0.0516 0.11171 0.1289 0.17186 0.0816 0.0602 0.19764 0.0602 0.30935 0 0.39528-0.3652z"/>
+     <path d="m-543.78-315.66v-1.3061q0-0.30076-0.073-0.45544-0.0688-0.15897-0.27928-0.15897-0.15037 0-0.27497 0.10741-0.12031 0.10742-0.16327 0.27069v1.5424h-0.30935v-3.0076h0.30935v1.0612h0.0129q0.0859-0.11171 0.21053-0.18045 0.12889-0.073 0.31794-0.073 0.14179 0 0.2449 0.0387 0.10742 0.0387 0.17616 0.1332 0.0688 0.0945 0.10312 0.25349 0.0344 0.15468 0.0344 0.38669v1.3878z"/>
+     <path d="m-541.71-315.81q-0.10312 0.0945-0.26209 0.14608-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15038-0.14178-0.35661-0.043-0.20624-0.043-0.46403 0-0.54996 0.20194-0.83783 0.20194-0.28786 0.57144-0.28786 0.1203 0 0.23631 0.0301 0.1203 0.0301 0.21483 0.1203 0.0945 0.0902 0.15037 0.2535 0.0602 0.16327 0.0602 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0645 0.10312 0.16327 0.16327 0.10312 0.0559 0.2535 0.0559 0.116 0 0.22771-0.043 0.11601-0.043 0.17616-0.10311zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27069-0.15038-0.19764 0-0.31364 0.15038-0.11601 0.15038-0.13749 0.47262z"/>
+     <path d="m-541.25-317.68q0.1246-0.0773 0.30075-0.1203 0.18046-0.043 0.3781-0.043 0.18046 0 0.28787 0.0559 0.11171 0.0516 0.17186 0.14608 0.0645 0.0902 0.0816 0.21053 0.0215 0.11601 0.0215 0.2449 0 0.25779-0.0129 0.5027-9e-3 0.2449-9e-3 0.46402 0 0.16327 9e-3 0.30506 0.0129 0.13749 0.043 0.26209h-0.23631l-0.073-0.2535h-0.0172q-0.0645 0.11171-0.18905 0.19335-0.1246 0.0816-0.33513 0.0816-0.23202 0-0.38239-0.15897-0.14609-0.16327-0.14609-0.44684 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17616-0.20194 0.11601-0.0773 0.27069-0.10741 0.15897-0.0344 0.35231-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.13319 0.0129-0.23631 0-0.2449-0.073-0.34372-0.073-0.0988-0.26639-0.0988-0.1203 0-0.26209 0.0387-0.14178 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10311 0-0.20193 0.0172-0.0988 0.0172-0.17616 0.0601-0.0773 0.043-0.1246 0.116-0.043 0.073-0.043 0.18476 0 0.17186 0.0816 0.26638 0.0859 0.0945 0.21912 0.0945 0.18046 0 0.27928-0.0859 0.0988-0.0859 0.13749-0.18904z"/>
+     <path d="m-539.51-317.81h0.21913l0.0559 0.22772h0.0129q0.0602-0.1246 0.15468-0.19334 0.0988-0.0731 0.23631-0.0731 0.0988 0 0.22342 0.0387l-0.0602 0.31365q-0.11171-0.0387-0.19764-0.0387-0.13749 0-0.22342 0.0816-0.0859 0.0773-0.11171 0.21053v1.5811h-0.30936z"/>
+     <path d="m-538.44-317.81h0.26209v-0.42535l0.30935-0.0988v0.52417h0.46403v0.27928h-0.46403v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0602 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45544v-1.3233h-0.26209z"/>
+     <path d="m-537.05-318.67h0.30935v1.0226h0.0129q0.17616-0.21482 0.46832-0.21482 0.33083 0 0.4941 0.26208 0.16757 0.26209 0.16757 0.82924 0 0.58003-0.22342 0.8636-0.21913 0.28357-0.623 0.28357-0.19764 0-0.36091-0.043-0.16327-0.0473-0.2449-0.10742zm0.30935 2.6939q0.0602 0.0344 0.14608 0.0559 0.0902 0.0172 0.18905 0.0172 0.22342 0 0.35231-0.21053 0.1332-0.21483 0.1332-0.65737 0-0.18475-0.0258-0.33084-0.0215-0.15038-0.073-0.25779-0.0473-0.10741-0.1289-0.16327-0.0773-0.0602-0.18905-0.0602-0.15467 0-0.25779 0.0945-0.0988 0.0902-0.14608 0.2492z"/>
+     <path d="m-533.9-315.81q-0.10312 0.0945-0.26209 0.14608-0.15897 0.0516-0.33513 0.0516-0.20194 0-0.35232-0.0773-0.14608-0.0816-0.2449-0.22772-0.0945-0.15038-0.14179-0.35661-0.043-0.20624-0.043-0.46403 0-0.54996 0.20193-0.83783 0.20194-0.28786 0.57144-0.28786 0.12031 0 0.23631 0.0301 0.12031 0.0301 0.21483 0.1203 0.0945 0.0902 0.15038 0.2535 0.0602 0.16327 0.0602 0.42536 0 0.073-9e-3 0.15897-4e-3 0.0816-0.0129 0.17186h-1.0913q0 0.18475 0.0301 0.33513t0.0945 0.25779q0.0644 0.10312 0.16327 0.16327 0.10311 0.0559 0.25349 0.0559 0.11601 0 0.22772-0.043 0.11601-0.043 0.17616-0.10311zm-0.24061-1.1515q9e-3 -0.32224-0.0902-0.47262-0.0988-0.15038-0.27068-0.15038-0.19764 0-0.31365 0.15038-0.116 0.15038-0.13749 0.47262z"/>
+     <path d="m-533.43-317.68q0.1246-0.0773 0.30076-0.1203 0.18046-0.043 0.3781-0.043 0.18045 0 0.28787 0.0559 0.11171 0.0516 0.17186 0.14608 0.0644 0.0902 0.0816 0.21053 0.0215 0.11601 0.0215 0.2449 0 0.25779-0.0129 0.5027-9e-3 0.2449-9e-3 0.46402 0 0.16327 9e-3 0.30506 0.0129 0.13749 0.043 0.26209h-0.23631l-0.073-0.2535h-0.0172q-0.0644 0.11171-0.18905 0.19335-0.1246 0.0816-0.33513 0.0816-0.23201 0-0.38239-0.15897-0.14608-0.16327-0.14608-0.44684 0-0.18475 0.0602-0.30935 0.0644-0.1246 0.17616-0.20194 0.11601-0.0773 0.27068-0.10741 0.15897-0.0344 0.35232-0.0344 0.043 0 0.0859 0 0.043 0 0.0902 4e-3 0.0129-0.13319 0.0129-0.23631 0-0.2449-0.0731-0.34372-0.073-0.0988-0.26638-0.0988-0.1203 0-0.26209 0.0387-0.14179 0.0344-0.23631 0.0902zm0.93235 1.0398q-0.043-4e-3 -0.0859-4e-3 -0.043-4e-3 -0.0859-4e-3 -0.10312 0-0.20194 0.0172-0.0988 0.0172-0.17615 0.0601-0.0773 0.043-0.1246 0.116-0.043 0.073-0.043 0.18476 0 0.17186 0.0816 0.26638 0.0859 0.0945 0.21913 0.0945 0.18045 0 0.27927-0.0859 0.0988-0.0859 0.13749-0.18904z"/>
+     <path d="m-531.91-317.81h0.26209v-0.42535l0.30935-0.0988v0.52417h0.46403v0.27928h-0.46403v1.2804q0 0.18905 0.043 0.27498 0.0473 0.0816 0.15038 0.0816 0.0859 0 0.14608-0.0172 0.0644-0.0215 0.13749-0.0516l0.0601 0.2449q-0.0945 0.0473-0.21053 0.073-0.11171 0.0301-0.23631 0.0301-0.21483 0-0.30935-0.13749-0.0902-0.14179-0.0902-0.45544v-1.3233h-0.26209z"/>
+    </g>
+    <g dominant-baseline="auto" stroke-width=".19164" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Abstract Transport">
+     <path d="m-658.04-337.68h-1.5178l-0.42927 1.5024h-0.56725l1.6174-5.4502h0.31429l1.6174 5.4502h-0.59791zm-1.3645-0.51359h1.2265l-0.4676-1.6558-0.14564-0.80488h-0.0153l-0.14564 0.82021z"/>
+     <path d="m-656.52-341.54h0.55192v1.8244h0.023q0.31429-0.38328 0.83554-0.38328 0.59025 0 0.88154 0.4676 0.29896 0.4676 0.29896 1.4794 0 1.0348-0.39861 1.5408-0.39094 0.50592-1.1115 0.50592-0.35261 0-0.6439-0.0767-0.29129-0.0843-0.43694-0.19164zm0.55192 4.8063q0.10732 0.0613 0.26063 0.0996 0.16098 0.0307 0.33728 0.0307 0.39861 0 0.62858-0.37561 0.23763-0.38328 0.23763-1.1728 0-0.32962-0.046-0.59025-0.0383-0.26829-0.13032-0.45993-0.0843-0.19164-0.22996-0.29129-0.13798-0.10732-0.33729-0.10732-0.27596 0-0.45993 0.16864-0.17631 0.16098-0.26063 0.4446z"/>
+     <path d="m-653.26-336.8q0.15331 0.092 0.36028 0.16097 0.21464 0.0613 0.43694 0.0613 0.25296 0 0.42927-0.12265 0.17631-0.13031 0.17631-0.41394 0-0.23763-0.10732-0.39094t-0.27596-0.27596q-0.16097-0.12265-0.35261-0.2223-0.19164-0.10732-0.36028-0.25297-0.16098-0.14564-0.2683-0.34495-0.10732-0.1993-0.10732-0.50592 0-0.4906 0.26063-0.7359 0.2683-0.25296 0.75123-0.25296 0.31428 0 0.54425 0.0613 0.22997 0.0537 0.39861 0.15331l-0.14565 0.45994q-0.14564-0.0767-0.33728-0.12265-0.19164-0.0537-0.39094-0.0537-0.27596 0-0.40628 0.11498-0.12265 0.11499-0.12265 0.36028 0 0.19164 0.10732 0.32962 0.10732 0.13032 0.2683 0.2453 0.16864 0.10732 0.36028 0.2223 0.19164 0.11499 0.35261 0.27596 0.16864 0.15331 0.27596 0.37561 0.10732 0.21464 0.10732 0.54426 0 0.21463-0.069 0.40627t-0.21464 0.33729q-0.13798 0.13798-0.35261 0.2223-0.20697 0.0843-0.4906 0.0843-0.33728 0-0.58258-0.069-0.2453-0.0613-0.41394-0.16864z"/>
+     <path d="m-650.99-340.01h0.4676v-0.75889l0.55192-0.17631v0.9352h0.82788v0.49826h-0.82788v2.2843q0 0.33729 0.0766 0.4906 0.0843 0.14564 0.2683 0.14564 0.15331 0 0.26063-0.0307 0.11498-0.0383 0.2453-0.092l0.10731 0.43694q-0.16864 0.0843-0.37561 0.13032-0.1993 0.0537-0.4216 0.0537-0.38328 0-0.55192-0.24529-0.16098-0.25297-0.16098-0.81255v-2.361h-0.4676z"/>
+     <path d="m-648.49-340.01h0.39094l0.0997 0.40627h0.023q0.10732-0.2223 0.27596-0.34495 0.17631-0.13031 0.42161-0.13031 0.17631 0 0.39861 0.069l-0.10732 0.55958q-0.19931-0.069-0.35262-0.069-0.24529 0-0.39861 0.14565-0.15331 0.13798-0.1993 0.37561v2.8209h-0.55192z"/>
+     <path d="m-646.44-339.78q0.2223-0.13798 0.53659-0.21463 0.32196-0.0767 0.67457-0.0767 0.32195 0 0.51359 0.0996 0.19931 0.092 0.30663 0.26063 0.11498 0.16098 0.14564 0.37561 0.0383 0.20697 0.0383 0.43694 0 0.45993-0.023 0.89687-0.0153 0.43694-0.0153 0.82788 0 0.29129 0.0153 0.54426 0.023 0.24529 0.0767 0.46759h-0.42161l-0.13031-0.45226h-0.0307q-0.11499 0.1993-0.33729 0.34495-0.2223 0.14564-0.59791 0.14564-0.41394 0-0.68224-0.28362-0.26063-0.29129-0.26063-0.79722 0-0.32962 0.10732-0.55192 0.11499-0.2223 0.31429-0.36028 0.20697-0.13798 0.48293-0.19164 0.28363-0.0613 0.62858-0.0613 0.0766 0 0.15331 0 0.0766 0 0.16097 8e-3 0.023-0.23763 0.023-0.42161 0-0.43693-0.13031-0.61324-0.13032-0.17631-0.47527-0.17631-0.21463 0-0.4676 0.069-0.25296 0.0613-0.4216 0.16098zm1.6634 1.8551q-0.0767-8e-3 -0.15331-8e-3 -0.0767-8e-3 -0.15331-8e-3 -0.18398 0-0.36028 0.0307-0.17631 0.0307-0.31429 0.10732-0.13798 0.0766-0.2223 0.20697-0.0767 0.13031-0.0767 0.32961 0 0.30663 0.14565 0.47527 0.15331 0.16864 0.39094 0.16864 0.32195 0 0.49826-0.15331t0.2453-0.33728z"/>
+     <path d="m-641.21-336.37q-0.19164 0.14565-0.43693 0.21464-0.2453 0.069-0.5136 0.069-0.36794 0-0.62091-0.13798-0.25296-0.14565-0.41394-0.40627-0.15331-0.2683-0.22996-0.63625-0.069-0.37561-0.069-0.82788 0-0.98119 0.34495-1.4948 0.35261-0.51359 1.0042-0.51359 0.29896 0 0.5136 0.0537 0.21463 0.0537 0.36794 0.13798l-0.15331 0.48293q-0.30662-0.17631-0.6669-0.17631-0.41394 0-0.62858 0.36795-0.20697 0.36028-0.20697 1.1422 0 0.31429 0.046 0.59025t0.15331 0.48293q0.10732 0.19931 0.27596 0.32196 0.16864 0.11498 0.4216 0.11498 0.19931 0 0.36795-0.069 0.17631-0.069 0.28363-0.16098z"/>
+     <path d="m-640.99-340.01h0.46759v-0.75889l0.55192-0.17631v0.9352h0.82788v0.49826h-0.82788v2.2843q0 0.33729 0.0767 0.4906 0.0843 0.14564 0.26829 0.14564 0.15332 0 0.26063-0.0307 0.11499-0.0383 0.2453-0.092l0.10732 0.43694q-0.16864 0.0843-0.37561 0.13032-0.19931 0.0537-0.42161 0.0537-0.38328 0-0.55192-0.24529-0.16098-0.25297-0.16098-0.81255v-2.361h-0.46759z"/>
+     <path d="m-657.27-331.43h-1.3185v4.837h-0.57492v-4.837h-1.3185v-0.52892h3.2119z"/>
+     <path d="m-657.37-330.43h0.39094l0.0996 0.40628h0.023q0.10732-0.22231 0.27596-0.34495 0.17631-0.13032 0.42161-0.13032 0.1763 0 0.39861 0.069l-0.10732 0.55959q-0.19931-0.069-0.35262-0.069-0.2453 0-0.39861 0.14564-0.15331 0.13798-0.1993 0.37561v2.8209h-0.55192z"/>
+     <path d="m-655.31-330.2q0.2223-0.13798 0.53659-0.21464 0.32195-0.0767 0.67457-0.0767 0.32195 0 0.51359 0.0996 0.19931 0.092 0.30662 0.26063 0.11499 0.16097 0.14565 0.37561 0.0383 0.20697 0.0383 0.43694 0 0.45993-0.023 0.89687-0.0153 0.43693-0.0153 0.82788 0 0.29129 0.0153 0.54425 0.023 0.2453 0.0767 0.4676h-0.42161l-0.13031-0.45227h-0.0307q-0.11498 0.19931-0.33728 0.34495-0.2223 0.14565-0.59791 0.14565-0.41394 0-0.68224-0.28363-0.26063-0.29129-0.26063-0.79722 0-0.32961 0.10732-0.55192 0.11498-0.2223 0.31429-0.36028 0.20697-0.13798 0.48293-0.19164 0.28362-0.0613 0.62857-0.0613 0.0767 0 0.15332 0 0.0766 0 0.16097 8e-3 0.023-0.23764 0.023-0.42161 0-0.43694-0.13032-0.61324-0.13031-0.17631-0.47526-0.17631-0.21464 0-0.4676 0.069-0.25296 0.0613-0.4216 0.16097zm1.6634 1.8551q-0.0767-8e-3 -0.15331-8e-3 -0.0767-8e-3 -0.15331-8e-3 -0.18398 0-0.36029 0.0307-0.1763 0.0307-0.31428 0.10732-0.13798 0.0767-0.2223 0.20697-0.0767 0.13032-0.0767 0.32962 0 0.30662 0.14565 0.47526 0.15331 0.16865 0.39094 0.16865 0.32195 0 0.49826-0.15331 0.17631-0.15332 0.2453-0.33729z"/>
+     <path d="m-650.24-326.59v-2.338q0-0.57491-0.13798-0.82788-0.13032-0.26062-0.47527-0.26062-0.30662 0-0.50592 0.18397-0.19931 0.18397-0.29129 0.45227v2.7903h-0.55192v-3.8328h0.39861l0.0997 0.40628h0.023q0.14565-0.20697 0.39095-0.35262 0.25296-0.14565 0.59791-0.14565 0.2453 0 0.42927 0.069 0.19164 0.069 0.31429 0.23764 0.13031 0.16097 0.19164 0.43693 0.069 0.27596 0.069 0.69757v2.4836z"/>
+     <path d="m-648.9-327.22q0.15332 0.092 0.36029 0.16098 0.21463 0.0613 0.43693 0.0613 0.25297 0 0.42927-0.12264 0.17631-0.13032 0.17631-0.41394 0-0.23764-0.10732-0.39095-0.10731-0.15331-0.27596-0.27596-0.16097-0.12265-0.35261-0.2223-0.19164-0.10732-0.36028-0.25296-0.16098-0.14565-0.2683-0.34495-0.10732-0.19931-0.10732-0.50593 0-0.49059 0.26063-0.73589 0.2683-0.25297 0.75123-0.25297 0.31429 0 0.54425 0.0613 0.22997 0.0537 0.39861 0.15331l-0.14564 0.45993q-0.14565-0.0767-0.33729-0.12265-0.19164-0.0537-0.39094-0.0537-0.27596 0-0.40628 0.11498-0.12265 0.11498-0.12265 0.36028 0 0.19164 0.10732 0.32962 0.10732 0.13031 0.2683 0.2453 0.16864 0.10731 0.36028 0.2223 0.19164 0.11498 0.35261 0.27596 0.16865 0.15331 0.27596 0.37561 0.10732 0.21464 0.10732 0.54425 0 0.21464-0.069 0.40628t-0.21463 0.33728q-0.13798 0.13798-0.35262 0.2223-0.20697 0.0843-0.4906 0.0843-0.33728 0-0.58258-0.069-0.2453-0.0613-0.41394-0.16865z"/>
+     <path d="m-646.25-330.43h0.39094l0.0843 0.41394h0.0307q0.28363-0.50593 0.88921-0.50593t0.90453 0.45227q0.30663 0.45227 0.30663 1.4794 0 0.48293-0.0997 0.87388-0.0996 0.38328-0.28362 0.65924-0.18397 0.26829-0.45227 0.41394-0.26063 0.13798-0.58258 0.13798-0.2223 0-0.35262-0.0307-0.13031-0.023-0.28362-0.10731v1.5791h-0.55192zm0.55192 3.2272q0.10732 0.092 0.23763 0.14565 0.13798 0.0537 0.36028 0.0537 0.40628 0 0.64391-0.41394t0.23763-1.1805q0-0.32195-0.046-0.58258-0.0383-0.26063-0.13032-0.44461-0.092-0.19163-0.23763-0.29129-0.13798-0.10731-0.34495-0.10731-0.55959 0-0.72056 0.68223z"/>
+     <path d="m-643.08-328.51q0-1.0348 0.35262-1.5178 0.36028-0.4906 1.0195-0.4906 0.70523 0 1.0348 0.49827 0.33728 0.49826 0.33728 1.5101 0 1.0425-0.36028 1.5254t-1.0118 0.48293q-0.70523 0-1.0425-0.49827-0.32962-0.49826-0.32962-1.5101zm0.57492 0q0 0.33728 0.0383 0.61324 0.046 0.27596 0.13798 0.47527 0.0996 0.1993 0.25296 0.31429 0.15331 0.10731 0.36795 0.10731 0.39861 0 0.59791-0.35261 0.19931-0.36028 0.19931-1.1575 0-0.32962-0.046-0.60558-0.0383-0.28362-0.13798-0.48293-0.092-0.1993-0.24529-0.30662-0.15332-0.11498-0.36795-0.11498-0.39094 0-0.59791 0.36028-0.19931 0.36028-0.19931 1.1498z"/>
+     <path d="m-639.6-330.43h0.39095l0.0996 0.40628h0.023q0.10732-0.22231 0.27596-0.34495 0.17631-0.13032 0.42161-0.13032 0.17631 0 0.39861 0.069l-0.10732 0.55959q-0.1993-0.069-0.35261-0.069-0.2453 0-0.39861 0.14564-0.15331 0.13798-0.19931 0.37561v2.8209h-0.55192z"/>
+     <path d="m-637.71-330.43h0.4676v-0.75889l0.55192-0.17631v0.9352h0.82788v0.49826h-0.82788v2.2843q0 0.33728 0.0767 0.49059 0.0843 0.14565 0.2683 0.14565 0.15331 0 0.26063-0.0307 0.11498-0.0383 0.24529-0.092l0.10732 0.43694q-0.16864 0.0843-0.37561 0.13031-0.19931 0.0537-0.42161 0.0537-0.38327 0-0.55192-0.2453-0.16097-0.25296-0.16097-0.81255v-2.361h-0.4676z"/>
+    </g>
+    <g dominant-baseline="auto" stroke-width=".19164" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Abstract Protocol">
+     <path d="m-518.21-338.44h-1.5178l-0.42928 1.5024h-0.56725l1.6174-5.4502h0.31428l1.6174 5.4502h-0.59792zm-1.3645-0.51359h1.2265l-0.4676-1.6558-0.14565-0.80488h-0.0153l-0.14565 0.82021z"/>
+     <path d="m-516.69-342.3h0.55192v1.8244h0.023q0.31429-0.38327 0.83555-0.38327 0.59025 0 0.88154 0.46759 0.29895 0.4676 0.29895 1.4795 0 1.0348-0.3986 1.5408-0.39095 0.50593-1.1115 0.50593-0.35262 0-0.64391-0.0767-0.29129-0.0843-0.43693-0.19163zm0.55192 4.8063q0.10731 0.0613 0.26063 0.0997 0.16097 0.0307 0.33728 0.0307 0.39861 0 0.62858-0.37561 0.23763-0.38328 0.23763-1.1728 0-0.32962-0.046-0.59025-0.0383-0.2683-0.13031-0.45993-0.0843-0.19164-0.22997-0.2913-0.13798-0.10731-0.33728-0.10731-0.27596 0-0.45993 0.16864-0.17631 0.16098-0.26063 0.4446z"/>
+     <path d="m-513.42-337.56q0.15331 0.092 0.36028 0.16098 0.21464 0.0613 0.43694 0.0613 0.25296 0 0.42927-0.12265 0.17631-0.13032 0.17631-0.41394 0-0.23764-0.10732-0.39095t-0.27596-0.27596q-0.16098-0.12265-0.35262-0.2223-0.19164-0.10732-0.36028-0.25296-0.16098-0.14565-0.26829-0.34495-0.10732-0.19931-0.10732-0.50593 0-0.49059 0.26063-0.73589 0.26829-0.25296 0.75122-0.25296 0.31429 0 0.54426 0.0613 0.22996 0.0537 0.39861 0.15331l-0.14565 0.45993q-0.14565-0.0766-0.33728-0.12264-0.19164-0.0537-0.39095-0.0537-0.27596 0-0.40627 0.11498-0.12265 0.11498-0.12265 0.36028 0 0.19164 0.10732 0.32962 0.10731 0.13031 0.26829 0.2453 0.16864 0.10732 0.36028 0.2223t0.35262 0.27596q0.16864 0.15331 0.27596 0.37561 0.10732 0.21464 0.10732 0.54426 0 0.21463-0.069 0.40627t-0.21464 0.33728q-0.13798 0.13798-0.35262 0.22231-0.20697 0.0843-0.49059 0.0843-0.33729 0-0.58258-0.069-0.2453-0.0613-0.41394-0.16865z"/>
+     <path d="m-511.16-340.77h0.46759v-0.75889l0.55192-0.17631v0.9352h0.82788v0.49826h-0.82788v2.2843q0 0.33728 0.0767 0.49059 0.0843 0.14565 0.26829 0.14565 0.15332 0 0.26063-0.0307 0.11499-0.0383 0.2453-0.092l0.10732 0.43694q-0.16864 0.0843-0.37561 0.13031-0.19931 0.0537-0.42161 0.0537-0.38328 0-0.55192-0.2453-0.16098-0.25296-0.16098-0.81255v-2.361h-0.46759z"/>
+     <path d="m-508.66-340.77h0.39094l0.0996 0.40628h0.023q0.10732-0.2223 0.27596-0.34495 0.17631-0.13032 0.4216-0.13032 0.17631 0 0.39861 0.069l-0.10732 0.55959q-0.1993-0.069-0.35261-0.069-0.2453 0-0.39861 0.14564-0.15331 0.13798-0.1993 0.37562v2.8209h-0.55192z"/>
+     <path d="m-506.6-340.54q0.2223-0.13798 0.53659-0.21464 0.32195-0.0766 0.67457-0.0766 0.32195 0 0.51359 0.0996 0.1993 0.092 0.30662 0.26063 0.11498 0.16098 0.14565 0.37561 0.0383 0.20697 0.0383 0.43694 0 0.45993-0.023 0.89687-0.0153 0.43693-0.0153 0.82788 0 0.29129 0.0153 0.54425 0.023 0.2453 0.0767 0.4676h-0.4216l-0.13032-0.45227h-0.0307q-0.11498 0.19931-0.33728 0.34495-0.2223 0.14565-0.59792 0.14565-0.41394 0-0.68223-0.28363-0.26063-0.29129-0.26063-0.79721 0-0.32962 0.10732-0.55192 0.11498-0.22231 0.31429-0.36029 0.20697-0.13798 0.48293-0.19163 0.28362-0.0613 0.62857-0.0613 0.0767 0 0.15331 0 0.0767 0 0.16098 8e-3 0.023-0.23764 0.023-0.42161 0-0.43694-0.13032-0.61324-0.13031-0.17631-0.47526-0.17631-0.21464 0-0.4676 0.069-0.25297 0.0613-0.42161 0.16098zm1.6634 1.8551q-0.0766-8e-3 -0.15331-8e-3 -0.0767-8e-3 -0.15331-8e-3 -0.18397 0-0.36028 0.0307t-0.31429 0.10732q-0.13798 0.0767-0.2223 0.20697-0.0767 0.13032-0.0767 0.32962 0 0.30662 0.14564 0.47527 0.15331 0.16864 0.39095 0.16864 0.32195 0 0.49826-0.15331 0.1763-0.15331 0.24529-0.33729z"/>
+     <path d="m-501.38-337.12q-0.19164 0.14565-0.43694 0.21464-0.2453 0.069-0.51359 0.069-0.36795 0-0.62091-0.13798-0.25297-0.14565-0.41394-0.40628-0.15331-0.26829-0.22997-0.63624-0.069-0.37561-0.069-0.82788 0-0.98119 0.34495-1.4948 0.35262-0.51359 1.0042-0.51359 0.29896 0 0.51359 0.0537 0.21464 0.0537 0.36795 0.13798l-0.15331 0.48293q-0.30662-0.1763-0.66691-0.1763-0.41394 0-0.62857 0.36794-0.20697 0.36028-0.20697 1.1422 0 0.31429 0.046 0.59025t0.15331 0.48293q0.10732 0.1993 0.27596 0.32195 0.16864 0.11499 0.42161 0.11499 0.1993 0 0.36795-0.069 0.1763-0.069 0.28362-0.16098z"/>
+     <path d="m-501.15-340.77h0.4676v-0.75889l0.55192-0.17631v0.9352h0.82788v0.49826h-0.82788v2.2843q0 0.33728 0.0767 0.49059 0.0843 0.14565 0.26829 0.14565 0.15331 0 0.26063-0.0307 0.11498-0.0383 0.2453-0.092l0.10731 0.43694q-0.16864 0.0843-0.37561 0.13031-0.1993 0.0537-0.4216 0.0537-0.38328 0-0.55192-0.2453-0.16098-0.25296-0.16098-0.81255v-2.361h-0.4676z"/>
+     <path d="m-520.32-332.66q0.24529-0.069 0.52125-0.092t0.54426-0.023q0.30662 0 0.60558 0.069 0.29895 0.069 0.53659 0.25296 0.24529 0.18398 0.39094 0.50593 0.15331 0.32195 0.15331 0.82022 0 0.48293-0.14564 0.82021-0.13798 0.33729-0.37562 0.55192-0.22996 0.20697-0.53659 0.30662-0.29895 0.092-0.62091 0.092-0.0307 0-0.0996 0-0.069 0-0.14564 0-0.0767-8e-3 -0.15331-0.0153-0.069-8e-3 -0.0997-0.0153v2.039h-0.57491zm1.0962 0.41394q-0.15331 0-0.29896 0.0153-0.13798 8e-3 -0.2223 0.0307v2.2843q0.0307 0.0153 0.092 0.023 0.069 0 0.13798 8e-3 0.069 0 0.13031 0 0.069 0 0.0997 0 0.21463 0 0.41394-0.0537 0.1993-0.0537 0.35261-0.19164 0.15331-0.14565 0.2453-0.38328 0.0996-0.2453 0.0996-0.61324 0-0.32196-0.092-0.53659-0.0843-0.21464-0.22997-0.34495-0.13798-0.13032-0.32962-0.18398-0.19164-0.0537-0.39861-0.0537z"/>
+     <path d="m-517.11-331.18h0.39094l0.0996 0.40627h0.023q0.10732-0.2223 0.27596-0.34495 0.17631-0.13031 0.4216-0.13031 0.17631 0 0.39861 0.069l-0.10731 0.55958q-0.19931-0.069-0.35262-0.069-0.2453 0-0.39861 0.14565-0.15331 0.13798-0.1993 0.37561v2.8209h-0.55192z"/>
+     <path d="m-515.18-329.27q0-1.0348 0.35262-1.5178 0.36028-0.4906 1.0195-0.4906 0.70523 0 1.0348 0.49826 0.33728 0.49826 0.33728 1.5101 0 1.0425-0.36028 1.5254t-1.0118 0.48293q-0.70523 0-1.0425-0.49826-0.32962-0.49826-0.32962-1.5101zm0.57492 0q0 0.33728 0.0383 0.61324 0.046 0.27596 0.13798 0.47527 0.0996 0.1993 0.25296 0.31428 0.15331 0.10732 0.36795 0.10732 0.39861 0 0.59791-0.35261 0.19931-0.36029 0.19931-1.1575 0-0.32962-0.046-0.60558-0.0383-0.28363-0.13798-0.48293-0.092-0.19931-0.24529-0.30663-0.15332-0.11498-0.36795-0.11498-0.39094 0-0.59791 0.36028-0.19931 0.36028-0.19931 1.1498z"/>
+     <path d="m-512.08-331.18h0.4676v-0.75889l0.55192-0.17631v0.9352h0.82788v0.49826h-0.82788v2.2843q0 0.33729 0.0767 0.4906 0.0843 0.14565 0.26829 0.14565 0.15331 0 0.26063-0.0307 0.11499-0.0383 0.2453-0.092l0.10732 0.43693q-0.16864 0.0843-0.37561 0.13032-0.19931 0.0537-0.42161 0.0537-0.38328 0-0.55192-0.2453-0.16098-0.25296-0.16098-0.81255v-2.361h-0.4676z"/>
+     <path d="m-509.82-329.27q0-1.0348 0.35261-1.5178 0.36028-0.4906 1.0195-0.4906 0.70523 0 1.0348 0.49826 0.33728 0.49826 0.33728 1.5101 0 1.0425-0.36028 1.5254t-1.0118 0.48293q-0.70523 0-1.0425-0.49826-0.32961-0.49826-0.32961-1.5101zm0.57491 0q0 0.33728 0.0383 0.61324 0.046 0.27596 0.13798 0.47527 0.0996 0.1993 0.25296 0.31428 0.15331 0.10732 0.36795 0.10732 0.39861 0 0.59791-0.35261 0.19931-0.36029 0.19931-1.1575 0-0.32962-0.046-0.60558-0.0383-0.28363-0.13798-0.48293-0.092-0.19931-0.24529-0.30663-0.15331-0.11498-0.36795-0.11498-0.39094 0-0.59791 0.36028-0.19931 0.36028-0.19931 1.1498z"/>
+     <path d="m-504.23-327.54q-0.19164 0.14564-0.43694 0.21463-0.2453 0.069-0.51359 0.069-0.36795 0-0.62091-0.13798-0.25297-0.14564-0.41394-0.40627-0.15331-0.2683-0.22997-0.63624-0.069-0.37562-0.069-0.82788 0-0.98119 0.34495-1.4948 0.35262-0.51359 1.0042-0.51359 0.29896 0 0.51359 0.0537 0.21464 0.0537 0.36795 0.13798l-0.15331 0.48293q-0.30663-0.17631-0.66691-0.17631-0.41394 0-0.62857 0.36795-0.20697 0.36028-0.20697 1.1422 0 0.31428 0.046 0.59024t0.15331 0.48293q0.10732 0.19931 0.27596 0.32196 0.16864 0.11498 0.42161 0.11498 0.1993 0 0.36794-0.069 0.17631-0.069 0.28363-0.16098z"/>
+     <path d="m-503.92-329.27q0-1.0348 0.35262-1.5178 0.36028-0.4906 1.0195-0.4906 0.70524 0 1.0348 0.49826 0.33729 0.49826 0.33729 1.5101 0 1.0425-0.36028 1.5254t-1.0119 0.48293q-0.70523 0-1.0425-0.49826-0.32962-0.49826-0.32962-1.5101zm0.57492 0q0 0.33728 0.0383 0.61324 0.046 0.27596 0.13798 0.47527 0.0997 0.1993 0.25297 0.31428 0.15331 0.10732 0.36794 0.10732 0.39861 0 0.59792-0.35261 0.1993-0.36029 0.1993-1.1575 0-0.32962-0.046-0.60558-0.0383-0.28363-0.13798-0.48293-0.092-0.19931-0.2453-0.30663-0.15331-0.11498-0.36795-0.11498-0.39094 0-0.59791 0.36028-0.1993 0.36028-0.1993 1.1498z"/>
+     <path d="m-499.85-328.26q0 0.2683 0.069 0.38328 0.0767 0.11499 0.20697 0.11499 0.16097 0 0.37561-0.0843l0.0537 0.44461q-0.0997 0.0613-0.28363 0.0996-0.17631 0.0383-0.32195 0.0383-0.29129 0-0.47527-0.17631-0.1763-0.18397-0.1763-0.63624v-4.6377h0.55192z"/>
+    </g>
+   </g>
+  </g>
+  <path d="m-625.84-304.32h18.161v5.8389h-18.161z" fill="#999" stroke="#999" stroke-width=".053718"/>
+  <path d="m-625.84-296.58h18.161v5.8389h-18.161z" fill="#999" stroke="#999" stroke-width=".053718"/>
+  <g stroke-width=".062942" aria-label="lws_token_map_t">
+   <path d="m-625.93-287.66q0 0.0881 0.0227 0.12589 0.0252 0.0378 0.068 0.0378 0.0529 0 0.12336-0.0277l0.0176 0.14602q-0.0327 0.0201-0.0931 0.0327-0.0579 0.0126-0.10575 0.0126-0.0957 0-0.15609-0.0579-0.0579-0.0604-0.0579-0.20896v-1.5232h0.18127z"/>
+   <path d="m-624.86-288.62 0.22407 0.73516 0.0453 0.2417h5e-3l0.0378-0.24674 0.1712-0.73012h0.1712l-0.33485 1.2865h-0.10322l-0.25428-0.8258-0.0352-0.21148h-5e-3l-0.0352 0.214-0.24674 0.82328h-0.10322l-0.34492-1.2865h0.19386l0.19386 0.73264 0.0302 0.24422h5e-3l0.0453-0.24925 0.20644-0.72761z"/>
+   <path d="m-624.07-287.57q0.0504 0.0302 0.11833 0.0529 0.0705 0.0201 0.14351 0.0201 0.0831 0 0.14099-0.0403 0.0579-0.0428 0.0579-0.13596 0-0.078-0.0352-0.1284-0.0353-0.0503-0.0906-0.0906-0.0529-0.0403-0.11581-0.073-0.0629-0.0352-0.11833-0.0831-0.0529-0.0478-0.0881-0.1133-0.0353-0.0654-0.0353-0.16616 0-0.16113 0.0856-0.2417 0.0881-0.0831 0.24673-0.0831 0.10323 0 0.17876 0.0201 0.0755 0.0176 0.13092 0.0504l-0.0478 0.15106q-0.0478-0.0252-0.11078-0.0403-0.0629-0.0176-0.1284-0.0176-0.0906 0-0.13343 0.0378-0.0403 0.0378-0.0403 0.11833 0 0.063 0.0353 0.10826 0.0353 0.0428 0.0881 0.0806 0.0554 0.0353 0.11833 0.073 0.0629 0.0378 0.11581 0.0906 0.0554 0.0503 0.0906 0.12336 0.0353 0.0705 0.0353 0.17876 0 0.0705-0.0227 0.13343-0.0227 0.0629-0.0705 0.11078-0.0453 0.0453-0.11581 0.073-0.068 0.0277-0.16113 0.0277-0.11078 0-0.19134-0.0227-0.0806-0.0201-0.13596-0.0554z"/>
+   <path d="m-623.35-287.01h0.8409v0.16365h-0.8409z"/>
+   <path d="m-622.48-288.62h0.15357v-0.24925l0.18128-0.0579v0.30715h0.2719v0.16365h-0.2719v0.75026q0 0.11078 0.0252 0.16114 0.0277 0.0478 0.0881 0.0478 0.0503 0 0.0856-0.0101 0.0378-0.0126 0.0806-0.0302l0.0352 0.14351q-0.0554 0.0277-0.12336 0.0428-0.0655 0.0176-0.13847 0.0176-0.12589 0-0.18127-0.0806-0.0529-0.0831-0.0529-0.26687v-0.77544h-0.15357z"/>
+   <path d="m-621.74-287.99q0-0.33989 0.11582-0.4985 0.11833-0.16113 0.33485-0.16113 0.23162 0 0.33988 0.16365 0.11078 0.16365 0.11078 0.49598 0 0.3424-0.11833 0.50101-0.11833 0.15862-0.33233 0.15862-0.23163 0-0.34241-0.16365-0.10826-0.16365-0.10826-0.49598zm0.18883 0q0 0.11078 0.0126 0.20141 0.0151 0.0906 0.0453 0.1561 0.0327 0.0655 0.0831 0.10322 0.0504 0.0352 0.12085 0.0352 0.13091 0 0.19637-0.11581 0.0655-0.11833 0.0655-0.38017 0-0.10826-0.0151-0.1989-0.0126-0.0931-0.0453-0.15861-0.0302-0.0655-0.0806-0.10071-0.0503-0.0378-0.12084-0.0378-0.12841 0-0.19638 0.11833-0.0655 0.11833-0.0655 0.37765z"/>
+   <path d="m-620.32-287.93h-0.0931v0.56647h-0.18127v-1.7624h0.18127v1.0725l0.0831-0.0352 0.29457-0.53375h0.20897l-0.29709 0.50857-0.0881 0.0806 0.10323 0.0982 0.32478 0.57151h-0.21904z"/>
+   <path d="m-618.89-287.45q-0.0604 0.0554-0.15358 0.0856-0.0932 0.0302-0.19638 0.0302-0.11833 0-0.20644-0.0453-0.0856-0.0478-0.14351-0.13344-0.0554-0.0881-0.0831-0.20896-0.0252-0.12085-0.0252-0.27191 0-0.32226 0.11833-0.49095 0.11833-0.16868 0.33485-0.16868 0.0705 0 0.13847 0.0176 0.0705 0.0176 0.12588 0.0705 0.0554 0.0529 0.0881 0.14854 0.0353 0.0957 0.0353 0.24925 0 0.0428-5e-3 0.0931-3e-3 0.0478-8e-3 0.10071h-0.63949q0 0.10826 0.0176 0.19638 0.0176 0.0881 0.0554 0.15106 0.0378 0.0604 0.0957 0.0957 0.0604 0.0327 0.14854 0.0327 0.068 0 0.13344-0.0252 0.068-0.0252 0.10322-0.0604zm-0.14099-0.67473q5e-3 -0.18883-0.0529-0.27694-0.0579-0.0881-0.15861-0.0881-0.11582 0-0.18379 0.0881-0.068 0.0881-0.0806 0.27694z"/>
+   <path d="m-617.96-287.36v-0.76788q0-0.18883-0.0453-0.27191-0.0428-0.0856-0.1561-0.0856-0.10071 0-0.16616 0.0604-0.0655 0.0604-0.0957 0.14854v0.91643h-0.18127v-1.2588h0.13092l0.0327 0.13344h8e-3q0.0478-0.068 0.1284-0.11581 0.0831-0.0478 0.19638-0.0478 0.0806 0 0.14099 0.0227 0.0629 0.0227 0.10323 0.0781 0.0428 0.0529 0.0629 0.1435 0.0227 0.0906 0.0227 0.22911v0.81572z"/>
+   <path d="m-617.65-287.01h0.8409v0.16365h-0.8409z"/>
+   <path d="m-616.07-287.36v-0.74774q0-0.10071-8e-3 -0.1712-5e-3 -0.073-0.0252-0.11833-0.0201-0.0453-0.0554-0.0655-0.0353-0.0227-0.0932-0.0227-0.0856 0-0.14602 0.068-0.0579 0.0654-0.0806 0.15106v0.90635h-0.18127v-1.2588h0.1284l0.0327 0.13344h8e-3q0.0529-0.073 0.12589-0.11833 0.073-0.0453 0.1863-0.0453 0.0957 0 0.1561 0.0428 0.0629 0.0403 0.0982 0.14602 0.0453-0.0881 0.1284-0.13847 0.0856-0.0503 0.18631-0.0503 0.0831 0 0.14099 0.0227 0.0604 0.0201 0.0957 0.0755 0.0378 0.0529 0.0554 0.14351 0.0176 0.0881 0.0176 0.22407v0.82327h-0.18127v-0.80061q0-0.16365-0.0327-0.24422-0.0302-0.0806-0.14351-0.0806-0.0957 0-0.15358 0.0604-0.0554 0.0579-0.078 0.15862v0.90635z"/>
+   <path d="m-615.03-288.55q0.073-0.0453 0.17623-0.0705 0.10575-0.0252 0.22156-0.0252 0.10574 0 0.16868 0.0327 0.0655 0.0302 0.10071 0.0856 0.0378 0.0529 0.0478 0.12337 0.0126 0.068 0.0126 0.1435 0 0.15106-8e-3 0.29457-5e-3 0.14351-5e-3 0.27191 0 0.0957 5e-3 0.17875 8e-3 0.0806 0.0252 0.15358h-0.13847l-0.0428-0.14854h-0.0101q-0.0378 0.0655-0.11077 0.11329-0.073 0.0478-0.19638 0.0478-0.13595 0-0.22407-0.0932-0.0856-0.0957-0.0856-0.26183 0-0.10826 0.0352-0.18127 0.0378-0.073 0.10323-0.11833 0.068-0.0453 0.15861-0.063 0.0932-0.0201 0.20645-0.0201 0.0252 0 0.0503 0 0.0252 0 0.0529 3e-3 8e-3 -0.0781 8e-3 -0.13847 0-0.14351-0.0428-0.20141t-0.1561-0.0579q-0.0705 0-0.15358 0.0227-0.0831 0.0201-0.13847 0.0529zm0.54633 0.60928q-0.0252-3e-3 -0.0503-3e-3 -0.0252-3e-3 -0.0504-3e-3 -0.0604 0-0.11833 0.0101-0.0579 0.0101-0.10322 0.0353-0.0453 0.0252-0.073 0.068-0.0252 0.0428-0.0252 0.10826 0 0.1007 0.0478 0.15609 0.0504 0.0554 0.1284 0.0554 0.10574 0 0.16365-0.0503 0.0579-0.0504 0.0806-0.11078z"/>
+   <path d="m-614.01-288.62h0.1284l0.0277 0.13596h0.0101q0.0932-0.16617 0.29205-0.16617 0.1989 0 0.29708 0.14854 0.10071 0.14854 0.10071 0.48591 0 0.15861-0.0327 0.28702-0.0327 0.12588-0.0932 0.21651-0.0604 0.0881-0.14854 0.13596-0.0856 0.0453-0.19135 0.0453-0.073 0-0.11581-0.0101-0.0428-8e-3 -0.0932-0.0353v0.51864h-0.18127zm0.18127 1.0599q0.0352 0.0302 0.078 0.0478 0.0453 0.0176 0.11833 0.0176 0.13344 0 0.21149-0.13596 0.0781-0.13595 0.0781-0.38772 0-0.10574-0.0151-0.19134-0.0126-0.0856-0.0428-0.14602-0.0302-0.0629-0.0781-0.0957-0.0453-0.0353-0.11329-0.0353-0.18379 0-0.23666 0.22407z"/>
+   <path d="m-613.06-287.01h0.8409v0.16365h-0.8409z"/>
+   <path d="m-612.2-288.62h0.15358v-0.24925l0.18127-0.0579v0.30715h0.27191v0.16365h-0.27191v0.75026q0 0.11078 0.0252 0.16114 0.0277 0.0478 0.0881 0.0478 0.0503 0 0.0856-0.0101 0.0378-0.0126 0.0806-0.0302l0.0352 0.14351q-0.0554 0.0277-0.12336 0.0428-0.0655 0.0176-0.13847 0.0176-0.12589 0-0.18128-0.0806-0.0529-0.0831-0.0529-0.26687v-0.77544h-0.15358z"/>
+  </g>
+  <path d="m-608.92-301.69h9.2604" fill="none" marker-end="url(#Arrow1Send)" stroke="#000" stroke-width="1.565"/>
+  <g dominant-baseline="auto" stroke-width=".11071" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Specify transport- specific details">
+   <path d="m-626.08-282.52q0.0841 0.0576 0.2347 0.11071 0.155 0.0487 0.34985 0.0487 0.24799 0 0.40299-0.11957 0.15499-0.12399 0.15499-0.38527 0-0.17271-0.0886-0.30113-0.0886-0.12843-0.22142-0.23471-0.13285-0.11071-0.28785-0.21256-0.15056-0.10629-0.28342-0.23028-0.13285-0.12843-0.22142-0.29228-0.0886-0.16385-0.0886-0.39413 0-0.37198 0.22143-0.54912 0.22584-0.18157 0.58455-0.18157 0.22142 0 0.39413 0.0399t0.27899 0.10185l-0.10628 0.29228q-0.0797-0.0487-0.23028-0.0886-0.14614-0.0399-0.34099-0.0399-0.23914 0-0.35428 0.11957-0.11513 0.11514-0.11513 0.29228 0 0.15499 0.0886 0.27456t0.22143 0.22585q0.13285 0.10628 0.28341 0.21699 0.155 0.10628 0.28785 0.23914 0.13285 0.13285 0.22142 0.30113 0.0886 0.16828 0.0886 0.39856 0 0.3897-0.23028 0.61112-0.23027 0.22142-0.65097 0.22142-0.26571 0-0.43842-0.0487-0.16828-0.0487-0.27013-0.11071z"/>
+   <path d="m-624.19-284.33h0.22584l0.0487 0.23913h0.0177q0.16385-0.29227 0.5137-0.29227 0.34984 0 0.52255 0.26127 0.17714 0.26128 0.17714 0.85469 0 0.27899-0.0576 0.50484-0.0576 0.22142-0.16385 0.38084-0.10628 0.155-0.26128 0.23914-0.15057 0.0797-0.33656 0.0797-0.12842 0-0.20371-0.0177-0.0753-0.0133-0.16385-0.062v0.91225h-0.31884zm0.31884 1.8644q0.062 0.0532 0.13728 0.0841 0.0797 0.031 0.20814 0.031 0.23471 0 0.37199-0.23913 0.13728-0.23914 0.13728-0.68198 0-0.18599-0.0266-0.33656-0.0222-0.15056-0.0753-0.25685-0.0531-0.11071-0.13728-0.16828-0.0797-0.062-0.19928-0.062-0.32327 0-0.41627 0.39412z"/>
+   <path d="m-620.94-282.26q-0.10628 0.0974-0.27013 0.15057-0.16386 0.0531-0.34542 0.0531-0.20814 0-0.36313-0.0797-0.15057-0.0841-0.25242-0.23471-0.0974-0.15499-0.14614-0.36756-0.0443-0.21256-0.0443-0.47827 0-0.56683 0.20813-0.86354 0.20814-0.2967 0.58898-0.2967 0.124 0 0.24357 0.031 0.12399 0.031 0.22142 0.12399 0.0974 0.093 0.15499 0.26128 0.062 0.16828 0.062 0.43841 0 0.0753-9e-3 0.16386-4e-3 0.0841-0.0133 0.17713h-1.1248q0 0.19042 0.031 0.34542 0.031 0.15499 0.0974 0.2657 0.0664 0.10629 0.16828 0.16828 0.10628 0.0576 0.26128 0.0576 0.11957 0 0.23471-0.0443 0.11956-0.0443 0.18156-0.10628zm-0.24799-1.1868q9e-3 -0.33214-0.093-0.48713-0.10185-0.15499-0.27899-0.15499-0.20371 0-0.32327 0.15499-0.11957 0.15499-0.14171 0.48713z"/>
+   <path d="m-619.25-282.22q-0.11071 0.0841-0.25242 0.124t-0.2967 0.0399q-0.21257 0-0.3587-0.0797-0.14614-0.0841-0.23914-0.23471-0.0886-0.15499-0.13285-0.36756-0.0399-0.21699-0.0399-0.47827 0-0.56683 0.19928-0.86354 0.20371-0.2967 0.58013-0.2967 0.1727 0 0.2967 0.031t0.21256 0.0797l-0.0886 0.27899q-0.17714-0.10185-0.38528-0.10185-0.23913 0-0.36313 0.21256-0.11957 0.20814-0.11957 0.65983 0 0.18157 0.0266 0.34099 0.0266 0.15943 0.0886 0.27899 0.062 0.11514 0.15943 0.186 0.0974 0.0664 0.24356 0.0664 0.11514 0 0.21256-0.0399 0.10186-0.0399 0.16386-0.093z"/>
+   <path d="m-618.84-284.33h0.31884v2.2142h-0.31884zm-0.0576-0.67312q0-0.10628 0.0576-0.17271 0.062-0.0664 0.15942-0.0664 0.0974 0 0.15942 0.0664 0.0664 0.062 0.0664 0.17271 0 0.10628-0.0664 0.16828-0.062 0.0576-0.15942 0.0576-0.0974 0-0.15942-0.062-0.0576-0.062-0.0576-0.16385z"/>
+   <path d="m-618.13-284.33h0.27013v-0.124q0-0.41627 0.11957-0.60226 0.11957-0.186 0.40742-0.186 0.11514 0 0.20813 0.0133 0.093 0.0133 0.19042 0.0576l-0.0797 0.27456q-0.0797-0.0354-0.15056-0.0443-0.0664-0.0133-0.12843-0.0133-0.0886 0-0.13728 0.0354-0.0487 0.0354-0.0753 0.11071-0.0221 0.0753-0.031 0.19485-4e-3 0.11514-4e-3 0.28342h0.46056v0.28785h-0.46056v1.9264h-0.31885v-1.9264h-0.27013z"/>
+   <path d="m-616.25-282.89 0.093 0.42956h0.0221l0.0664-0.42956 0.33656-1.4304h0.32327l-0.52698 1.9884q-0.062 0.23914-0.124 0.44727-0.062 0.20814-0.13728 0.3587-0.0708 0.155-0.16385 0.23914-0.0886 0.0886-0.21257 0.0886-0.12399 0-0.21699-0.0399l0.0531-0.30113q0.062 0.0221 0.124 9e-3 0.062-0.0133 0.11514-0.0753 0.0576-0.062 0.10185-0.18599 0.0487-0.11957 0.0841-0.31442l-0.7174-2.2142h0.36313z"/>
+   <path d="m-626.31-279.9h0.27013v-0.43841l0.31885-0.10186v0.54027h0.47827v0.28785h-0.47827v1.3197q0 0.19485 0.0443 0.28342 0.0487 0.0841 0.15499 0.0841 0.0886 0 0.15057-0.0177 0.0664-0.0221 0.14171-0.0531l0.062 0.25242q-0.0974 0.0487-0.21699 0.0753-0.11514 0.031-0.24357 0.031-0.22142 0-0.31884-0.14171-0.093-0.14614-0.093-0.46942v-1.364h-0.27013z"/>
+   <path d="m-624.86-279.9h0.22585l0.0576 0.23471h0.0133q0.062-0.12843 0.15942-0.19928 0.10185-0.0753 0.24356-0.0753 0.10186 0 0.23028 0.0399l-0.062 0.32328q-0.11514-0.0399-0.2037-0.0399-0.14171 0-0.23028 0.0841-0.0886 0.0797-0.11514 0.21699v1.6297h-0.31885z"/>
+   <path d="m-623.68-279.76q0.12842-0.0797 0.30999-0.12399 0.18599-0.0443 0.3897-0.0443 0.18599 0 0.2967 0.0576 0.11514 0.0531 0.17714 0.15057 0.0664 0.093 0.0841 0.21699 0.0221 0.11957 0.0221 0.25242 0 0.26571-0.0133 0.51813-9e-3 0.25242-9e-3 0.47827 0 0.16828 9e-3 0.31442 0.0133 0.14171 0.0443 0.27013h-0.24356l-0.0753-0.26128h-0.0177q-0.0664 0.11514-0.19485 0.19928-0.12842 0.0841-0.34542 0.0841-0.23913 0-0.39413-0.16385-0.15056-0.16828-0.15056-0.46056 0-0.19042 0.062-0.31884 0.0664-0.12843 0.18157-0.20814 0.11957-0.0797 0.27899-0.11071 0.16385-0.0354 0.36313-0.0354 0.0443 0 0.0886 0t0.093 4e-3q0.0133-0.13728 0.0133-0.24356 0-0.25242-0.0753-0.35427-0.0753-0.10186-0.27456-0.10186-0.124 0-0.27014 0.0399-0.14613 0.0354-0.24356 0.093zm0.96097 1.0717q-0.0443-4e-3 -0.0886-4e-3 -0.0443-4e-3 -0.0886-4e-3 -0.10628 0-0.20814 0.0177-0.10185 0.0177-0.18156 0.062-0.0797 0.0443-0.12843 0.11956-0.0443 0.0753-0.0443 0.19043 0 0.17713 0.0841 0.27456 0.0886 0.0974 0.22585 0.0974 0.18599 0 0.28785-0.0886 0.10185-0.0886 0.14171-0.19485z"/>
+   <path d="m-620.75-277.68v-1.3507q0-0.33213-0.0797-0.47827-0.0753-0.15056-0.27456-0.15056-0.17714 0-0.29227 0.10628-0.11514 0.10628-0.16828 0.26128v1.6119h-0.31885v-2.2142h0.23028l0.0576 0.23471h0.0133q0.0841-0.11957 0.22585-0.20371 0.14614-0.0841 0.34542-0.0841 0.14171 0 0.24799 0.0399 0.11071 0.0399 0.18156 0.13728 0.0753 0.093 0.11072 0.25242 0.0399 0.15942 0.0399 0.40298v1.4348z"/>
+   <path d="m-619.97-278.05q0.0886 0.0531 0.20813 0.093 0.124 0.0354 0.25242 0.0354 0.14614 0 0.24799-0.0709 0.10186-0.0753 0.10186-0.23913 0-0.13728-0.062-0.22585-0.062-0.0886-0.15942-0.15943-0.093-0.0709-0.20371-0.12842-0.11071-0.062-0.20814-0.14614-0.093-0.0841-0.15499-0.19928-0.062-0.11514-0.062-0.29227 0-0.28342 0.15057-0.42513 0.15499-0.14614 0.43398-0.14614 0.18157 0 0.31442 0.0354 0.13285 0.031 0.23028 0.0886l-0.0841 0.2657q-0.0841-0.0443-0.19485-0.0709-0.11071-0.031-0.22585-0.031-0.15943 0-0.23471 0.0664-0.0709 0.0664-0.0709 0.20814 0 0.11071 0.062 0.19042 0.062 0.0753 0.155 0.14171 0.0974 0.062 0.20813 0.12843 0.11072 0.0664 0.20371 0.15942 0.0974 0.0886 0.15943 0.21699 0.062 0.124 0.062 0.31442 0 0.124-0.0399 0.23471t-0.124 0.19485q-0.0797 0.0797-0.20371 0.12842-0.11956 0.0487-0.28342 0.0487-0.19485 0-0.33656-0.0399-0.1417-0.0354-0.23913-0.0974z"/>
+   <path d="m-618.44-279.9h0.22584l0.0487 0.23914h0.0177q0.16385-0.29228 0.5137-0.29228 0.34984 0 0.52255 0.26128 0.17714 0.26127 0.17714 0.85468 0 0.27899-0.0576 0.50484-0.0576 0.22142-0.16385 0.38085-0.10628 0.15499-0.26128 0.23913-0.15057 0.0797-0.33656 0.0797-0.12842 0-0.20371-0.0177-0.0753-0.0133-0.16385-0.062v0.91226h-0.31884zm0.31884 1.8644q0.062 0.0531 0.13728 0.0841 0.0797 0.031 0.20814 0.031 0.23471 0 0.37199-0.23914 0.13728-0.23913 0.13728-0.68198 0-0.18599-0.0266-0.33656-0.0222-0.15056-0.0753-0.25684-0.0531-0.11071-0.13728-0.16828-0.0797-0.062-0.19928-0.062-0.32327 0-0.41627 0.39413z"/>
+   <path d="m-616.61-278.79q0-0.59784 0.2037-0.87683 0.20814-0.28342 0.58898-0.28342 0.40742 0 0.59784 0.28785 0.19485 0.28785 0.19485 0.8724 0 0.60226-0.20814 0.88125-0.20813 0.27899-0.58455 0.27899-0.40741 0-0.60226-0.28784-0.19042-0.28785-0.19042-0.8724zm0.33213 0q0 0.19485 0.0221 0.35427 0.0266 0.15942 0.0797 0.27456 0.0576 0.11514 0.14614 0.18157 0.0886 0.062 0.21256 0.062 0.23028 0 0.34542-0.20371 0.11514-0.20814 0.11514-0.66869 0-0.19043-0.0266-0.34985-0.0221-0.16385-0.0797-0.27899-0.0531-0.11514-0.14171-0.17714-0.0886-0.0664-0.21257-0.0664-0.22585 0-0.34541 0.20813-0.11514 0.20814-0.11514 0.66427z"/>
+   <path d="m-614.6-279.9h0.22585l0.0576 0.23471h0.0133q0.062-0.12843 0.15942-0.19928 0.10186-0.0753 0.24357-0.0753 0.10185 0 0.23028 0.0399l-0.062 0.32328q-0.11514-0.0399-0.20371-0.0399-0.14171 0-0.23028 0.0841-0.0886 0.0797-0.11514 0.21699v1.6297h-0.31884z"/>
+   <path d="m-613.5-279.9h0.27014v-0.43841l0.31885-0.10186v0.54027h0.47826v0.28785h-0.47826v1.3197q0 0.19485 0.0443 0.28342 0.0487 0.0841 0.15499 0.0841 0.0886 0 0.15057-0.0177 0.0664-0.0221 0.14171-0.0531l0.062 0.25242q-0.0974 0.0487-0.21699 0.0753-0.11514 0.031-0.24357 0.031-0.22142 0-0.31884-0.14171-0.093-0.14614-0.093-0.46942v-1.364h-0.27014z"/>
+   <path d="m-612.22-279.1h0.8724v0.30556h-0.8724z"/>
+   <path d="m-626.12-273.62q0.0886 0.0531 0.20814 0.093 0.12399 0.0354 0.25242 0.0354 0.14613 0 0.24799-0.0709 0.10185-0.0753 0.10185-0.23913 0-0.13728-0.062-0.22585-0.062-0.0886-0.15942-0.15942-0.093-0.0709-0.20371-0.12843-0.11071-0.062-0.20813-0.14614-0.093-0.0841-0.155-0.19927-0.062-0.11514-0.062-0.29228 0-0.28342 0.15057-0.42513 0.15499-0.14614 0.43399-0.14614 0.18156 0 0.31441 0.0354 0.13286 0.031 0.23028 0.0886l-0.0841 0.2657q-0.0841-0.0443-0.19485-0.0709-0.11071-0.031-0.22585-0.031-0.15942 0-0.23471 0.0664-0.0709 0.0664-0.0709 0.20813 0 0.11071 0.062 0.19042 0.062 0.0753 0.15499 0.14171 0.0974 0.062 0.20814 0.12843 0.11071 0.0664 0.20371 0.15942 0.0974 0.0886 0.15942 0.21699 0.062 0.124 0.062 0.31442 0 0.124-0.0399 0.23471t-0.12399 0.19485q-0.0797 0.0797-0.20371 0.12842-0.11957 0.0487-0.28342 0.0487-0.19485 0-0.33656-0.0399-0.14171-0.0354-0.23914-0.0974z"/>
+   <path d="m-624.59-275.47h0.22585l0.0487 0.23914h0.0177q0.16385-0.29228 0.51369-0.29228 0.34985 0 0.52256 0.26128 0.17713 0.26128 0.17713 0.85469 0 0.27899-0.0576 0.50484-0.0576 0.22142-0.16385 0.38084-0.10628 0.15499-0.26128 0.23913-0.15056 0.0797-0.33656 0.0797-0.12842 0-0.2037-0.0177-0.0753-0.0133-0.16385-0.062v0.91226h-0.31885zm0.31885 1.8644q0.062 0.0531 0.13728 0.0841 0.0797 0.031 0.20813 0.031 0.23471 0 0.37199-0.23914 0.13728-0.23913 0.13728-0.68197 0-0.186-0.0266-0.33656-0.0221-0.15057-0.0753-0.25685-0.0531-0.11071-0.13728-0.16828-0.0797-0.062-0.19928-0.062-0.32328 0-0.41627 0.39413z"/>
+   <path d="m-621.34-273.4q-0.10628 0.0974-0.27013 0.15057-0.16385 0.0531-0.34542 0.0531-0.20813 0-0.36313-0.0797-0.15056-0.0841-0.25242-0.2347-0.0974-0.155-0.14614-0.36756-0.0443-0.21257-0.0443-0.47827 0-0.56684 0.20814-0.86354 0.20813-0.29671 0.58898-0.29671 0.12399 0 0.24356 0.031 0.12399 0.031 0.22142 0.124 0.0974 0.093 0.15499 0.26127 0.062 0.16828 0.062 0.43842 0 0.0753-9e-3 0.16385-4e-3 0.0841-0.0133 0.17714h-1.1248q0 0.19042 0.031 0.34541 0.031 0.155 0.0974 0.26571 0.0664 0.10628 0.16828 0.16828 0.10628 0.0576 0.26127 0.0576 0.11957 0 0.23471-0.0443 0.11957-0.0443 0.18156-0.10628zm-0.24799-1.1868q9e-3 -0.33213-0.093-0.48712-0.10185-0.155-0.27899-0.155-0.2037 0-0.32327 0.155-0.11957 0.15499-0.14171 0.48712z"/>
+   <path d="m-619.65-273.36q-0.11071 0.0841-0.25242 0.124t-0.29671 0.0399q-0.21256 0-0.3587-0.0797-0.14614-0.0841-0.23913-0.2347-0.0886-0.155-0.13286-0.36756-0.0399-0.21699-0.0399-0.47827 0-0.56684 0.19928-0.86354 0.2037-0.29671 0.58012-0.29671 0.17271 0 0.2967 0.031 0.124 0.031 0.21257 0.0797l-0.0886 0.27899q-0.17714-0.10185-0.38527-0.10185-0.23914 0-0.36313 0.21256-0.11957 0.20814-0.11957 0.65984 0 0.18156 0.0266 0.34099 0.0266 0.15942 0.0886 0.27899 0.062 0.11514 0.15942 0.18599 0.0974 0.0664 0.24356 0.0664 0.11514 0 0.21257-0.0399 0.10185-0.0398 0.16385-0.093z"/>
+   <path d="m-619.24-275.47h0.31885v2.2142h-0.31885zm-0.0576-0.67312q0-0.10628 0.0576-0.1727 0.062-0.0664 0.15942-0.0664 0.0974 0 0.15943 0.0664 0.0664 0.062 0.0664 0.1727 0 0.10629-0.0664 0.16828-0.062 0.0576-0.15943 0.0576-0.0974 0-0.15942-0.062-0.0576-0.062-0.0576-0.16386z"/>
+   <path d="m-616.93-275.47v2.2142h-0.31885v-1.9264h-0.68641v1.9264h-0.31884v-1.9264h-0.27014v-0.28785h0.27014v-0.12399q0-0.41627 0.16828-0.60227 0.16828-0.19042 0.49155-0.19042 0.217 0 0.37642 0.0443 0.16385 0.0399 0.2657 0.093l-0.10185 0.26571q-0.10628-0.062-0.23913-0.0886-0.12843-0.0266-0.27014-0.0266-0.12399 0-0.19928 0.0443-0.0708 0.0399-0.11071 0.124-0.0399 0.0797-0.0531 0.19928-9e-3 0.11514-9e-3 0.26127z"/>
+   <path d="m-615.17-273.36q-0.11072 0.0841-0.25242 0.124-0.14171 0.0399-0.29671 0.0399-0.21256 0-0.3587-0.0797-0.14614-0.0841-0.23914-0.2347-0.0886-0.155-0.13285-0.36756-0.0399-0.21699-0.0399-0.47827 0-0.56684 0.19927-0.86354 0.20371-0.29671 0.58013-0.29671 0.17271 0 0.2967 0.031 0.124 0.031 0.21257 0.0797l-0.0886 0.27899q-0.17714-0.10185-0.38527-0.10185-0.23914 0-0.36314 0.21256-0.11956 0.20814-0.11956 0.65984 0 0.18156 0.0266 0.34099 0.0266 0.15942 0.0886 0.27899 0.062 0.11514 0.15942 0.18599 0.0974 0.0664 0.24356 0.0664 0.11514 0 0.21257-0.0399 0.10185-0.0398 0.16385-0.093z"/>
+   <path d="m-624.69-269.59q0 0.22585 4e-3 0.41184 4e-3 0.18157 0.031 0.3587h-0.217l-0.0708-0.2657h-0.0177q-0.062 0.13285-0.19485 0.22142-0.13286 0.0886-0.31885 0.0886-0.3587 0-0.53584-0.27899-0.17271-0.27899-0.17271-0.87683 0-0.56684 0.21257-0.85911 0.21699-0.29228 0.5934-0.29228 0.12843 0 0.20371 0.0177 0.0753 0.0133 0.16385 0.0487v-0.91226h0.31885zm-0.31885-1.1027q-0.062-0.0531-0.14171-0.0753-0.0753-0.0266-0.2037-0.0266-0.23471 0-0.36756 0.21256-0.12843 0.21257-0.12843 0.65541 0 0.19485 0.0222 0.35427 0.0266 0.155 0.0753 0.27014 0.0531 0.11514 0.13285 0.17713 0.0841 0.062 0.20371 0.062 0.31885 0 0.40741-0.37641z"/>
+   <path d="m-622.84-268.98q-0.10628 0.0974-0.27013 0.15057-0.16385 0.0531-0.34542 0.0531-0.20814 0-0.36313-0.0797-0.15057-0.0841-0.25242-0.23471-0.0974-0.15499-0.14614-0.36756-0.0443-0.21256-0.0443-0.47827 0-0.56684 0.20813-0.86354 0.20814-0.2967 0.58898-0.2967 0.124 0 0.24357 0.031 0.12399 0.031 0.22142 0.124 0.0974 0.093 0.15499 0.26128 0.062 0.16828 0.062 0.43841 0 0.0753-9e-3 0.16385-4e-3 0.0841-0.0133 0.17714h-1.1248q0 0.19042 0.031 0.34542 0.031 0.15499 0.0974 0.2657 0.0664 0.10628 0.16828 0.16828 0.10629 0.0576 0.26128 0.0576 0.11957 0 0.23471-0.0443 0.11956-0.0443 0.18156-0.10629zm-0.24799-1.1868q9e-3 -0.33213-0.093-0.48712-0.10185-0.155-0.27899-0.155-0.20371 0-0.32327 0.155-0.11957 0.15499-0.14171 0.48712z"/>
+   <path d="m-622.56-271.04h0.27013v-0.43842l0.31885-0.10185v0.54027h0.47827v0.28784h-0.47827v1.3197q0 0.19485 0.0443 0.28342 0.0487 0.0841 0.155 0.0841 0.0886 0 0.15056-0.0177 0.0664-0.0221 0.14171-0.0531l0.062 0.25242q-0.0974 0.0487-0.21699 0.0753-0.11514 0.031-0.24357 0.031-0.22142 0-0.31884-0.14171-0.093-0.14614-0.093-0.46941v-1.364h-0.27013z"/>
+   <path d="m-621.14-270.91q0.12843-0.0797 0.30999-0.124 0.18599-0.0443 0.3897-0.0443 0.186 0 0.29671 0.0576 0.11514 0.0531 0.17713 0.15056 0.0664 0.093 0.0841 0.217 0.0222 0.11956 0.0222 0.25242 0 0.2657-0.0133 0.51812-9e-3 0.25242-9e-3 0.47827 0 0.16828 9e-3 0.31442 0.0133 0.14171 0.0443 0.27013h-0.24356l-0.0753-0.26127h-0.0177q-0.0664 0.11514-0.19485 0.19928-0.12842 0.0841-0.34541 0.0841-0.23914 0-0.39413-0.16386-0.15057-0.16828-0.15057-0.46055 0-0.19042 0.062-0.31885 0.0664-0.12842 0.18156-0.20813 0.11957-0.0797 0.27899-0.11071 0.16386-0.0354 0.36314-0.0354 0.0443 0 0.0886 0t0.093 4e-3q0.0133-0.13728 0.0133-0.24357 0-0.25242-0.0753-0.35427t-0.27456-0.10185q-0.124 0-0.27013 0.0399-0.14614 0.0354-0.24357 0.093zm0.96097 1.0717q-0.0443-4e-3 -0.0886-4e-3 -0.0443-4e-3 -0.0886-4e-3 -0.10628 0-0.20813 0.0177-0.10186 0.0177-0.18157 0.062-0.0797 0.0443-0.12842 0.11957-0.0443 0.0753-0.0443 0.19042 0 0.17714 0.0841 0.27456 0.0886 0.0974 0.22585 0.0974 0.186 0 0.28785-0.0886t0.14171-0.19485z"/>
+   <path d="m-619.29-271.04h0.31885v2.2142h-0.31885zm-0.0576-0.67312q0-0.10629 0.0576-0.17271 0.062-0.0664 0.15943-0.0664 0.0974 0 0.15942 0.0664 0.0664 0.062 0.0664 0.17271 0 0.10628-0.0664 0.16828-0.062 0.0576-0.15942 0.0576-0.0974 0-0.15943-0.062-0.0576-0.062-0.0576-0.16385z"/>
+   <path d="m-618.05-269.35q0 0.15499 0.0399 0.22142 0.0443 0.0664 0.11956 0.0664 0.093 0 0.217-0.0487l0.031 0.25685q-0.0576 0.0354-0.16385 0.0576-0.10185 0.0221-0.18599 0.0221-0.16828 0-0.27456-0.10185-0.10186-0.10629-0.10186-0.36756v-2.6792h0.31885z"/>
+   <path d="m-617.39-269.19q0.0886 0.0531 0.20813 0.093 0.124 0.0354 0.25242 0.0354 0.14614 0 0.24799-0.0709 0.10186-0.0753 0.10186-0.23914 0-0.13728-0.062-0.22585-0.062-0.0886-0.15942-0.15942-0.093-0.0709-0.20371-0.12843-0.11071-0.062-0.20814-0.14613-0.093-0.0841-0.15499-0.19928-0.062-0.11514-0.062-0.29228 0-0.28342 0.15057-0.42513 0.15499-0.14613 0.43398-0.14613 0.18157 0 0.31442 0.0354 0.13285 0.031 0.23028 0.0886l-0.0841 0.26571q-0.0841-0.0443-0.19485-0.0709-0.11071-0.031-0.22585-0.031-0.15943 0-0.23471 0.0664-0.0708 0.0664-0.0708 0.20813 0 0.11072 0.062 0.19043 0.062 0.0753 0.155 0.14171 0.0974 0.062 0.20813 0.12842 0.11072 0.0664 0.20371 0.15942 0.0974 0.0886 0.15943 0.217 0.062 0.12399 0.062 0.31441 0 0.124-0.0399 0.23471t-0.124 0.19485q-0.0797 0.0797-0.20371 0.12843-0.11956 0.0487-0.28341 0.0487-0.19486 0-0.33656-0.0399-0.14171-0.0354-0.23914-0.0974z"/>
+  </g>
+  <path d="m-547.11-304.62h18.161v5.8389h-18.161z" fill="#999" stroke="#999" stroke-width=".053718"/>
+  <path d="m-547.11-296.87h18.161v5.8389h-18.161z" fill="#999" stroke="#999" stroke-width=".053718"/>
+  <g stroke-width=".062942" aria-label="lws_token_map_t">
+   <path d="m-543.41-287.95q0 0.0881 0.0227 0.12589 0.0252 0.0378 0.068 0.0378 0.0529 0 0.12336-0.0277l0.0176 0.14602q-0.0327 0.0201-0.0932 0.0327-0.0579 0.0126-0.10574 0.0126-0.0957 0-0.1561-0.0579-0.0579-0.0604-0.0579-0.20896v-1.5232h0.18127z"/>
+   <path d="m-542.35-288.91 0.22407 0.73516 0.0453 0.2417h5e-3l0.0378-0.24673 0.1712-0.73013h0.1712l-0.33485 1.2865h-0.10322l-0.25429-0.82579-0.0353-0.21149h-5e-3l-0.0353 0.214-0.24673 0.82328h-0.10322l-0.34492-1.2865h0.19386l0.19386 0.73264 0.0302 0.24422h5e-3l0.0453-0.24925 0.20645-0.72761z"/>
+   <path d="m-541.56-287.86q0.0503 0.0302 0.11833 0.0529 0.0705 0.0201 0.1435 0.0201 0.0831 0 0.14099-0.0403 0.0579-0.0428 0.0579-0.13596 0-0.0781-0.0353-0.1284-0.0352-0.0503-0.0906-0.0906-0.0529-0.0403-0.11582-0.073-0.0629-0.0352-0.11833-0.0831-0.0529-0.0478-0.0881-0.11329-0.0353-0.0655-0.0353-0.16617 0-0.16113 0.0856-0.2417 0.0881-0.0831 0.24673-0.0831 0.10322 0 0.17875 0.0201 0.0755 0.0176 0.13092 0.0504l-0.0478 0.15106q-0.0478-0.0252-0.11078-0.0403-0.0629-0.0176-0.1284-0.0176-0.0906 0-0.13344 0.0378-0.0403 0.0378-0.0403 0.11833 0 0.063 0.0352 0.10826 0.0353 0.0428 0.0881 0.0806 0.0554 0.0353 0.11833 0.073 0.0629 0.0378 0.11581 0.0906 0.0554 0.0503 0.0906 0.12336 0.0353 0.0705 0.0353 0.17876 0 0.0705-0.0227 0.13343-0.0227 0.063-0.0705 0.11078-0.0453 0.0453-0.11581 0.073-0.068 0.0277-0.16113 0.0277-0.11078 0-0.19135-0.0227-0.0806-0.0201-0.13595-0.0554z"/>
+   <path d="m-540.83-287.3h0.8409v0.16365h-0.8409z"/>
+   <path d="m-539.97-288.91h0.15358v-0.24925l0.18127-0.0579v0.30715h0.27191v0.16365h-0.27191v0.75027q0 0.11077 0.0252 0.16113 0.0277 0.0478 0.0881 0.0478 0.0503 0 0.0856-0.0101 0.0378-0.0126 0.0806-0.0302l0.0353 0.14351q-0.0554 0.0277-0.12337 0.0428-0.0654 0.0176-0.13847 0.0176-0.12588 0-0.18127-0.0806-0.0529-0.0831-0.0529-0.26687v-0.77544h-0.15358z"/>
+   <path d="m-539.23-288.28q0-0.33989 0.11581-0.4985 0.11833-0.16113 0.33485-0.16113 0.23163 0 0.33989 0.16365 0.11077 0.16365 0.11077 0.49598 0 0.3424-0.11833 0.50101-0.11833 0.15862-0.33233 0.15862-0.23162 0-0.3424-0.16365-0.10826-0.16365-0.10826-0.49598zm0.18882 0q0 0.11078 0.0126 0.20141 0.0151 0.0906 0.0453 0.1561 0.0327 0.0655 0.0831 0.10322 0.0504 0.0353 0.12085 0.0353 0.13092 0 0.19638-0.11581 0.0655-0.11833 0.0655-0.38017 0-0.10826-0.0151-0.1989-0.0126-0.0931-0.0453-0.15861-0.0302-0.0655-0.0806-0.10071-0.0503-0.0378-0.12085-0.0378-0.1284 0-0.19638 0.11833-0.0655 0.11833-0.0655 0.37765z"/>
+   <path d="m-537.81-288.22h-0.0932v0.56648h-0.18127v-1.7624h0.18127v1.0725l0.0831-0.0353 0.29457-0.53375h0.20896l-0.29708 0.50857-0.0881 0.0806 0.10322 0.0982 0.32478 0.57151h-0.21903z"/>
+   <path d="m-536.38-287.74q-0.0604 0.0554-0.15358 0.0856-0.0932 0.0302-0.19638 0.0302-0.11833 0-0.20645-0.0453-0.0856-0.0478-0.14351-0.13344-0.0554-0.0881-0.0831-0.20896-0.0252-0.12085-0.0252-0.27191 0-0.32226 0.11833-0.49095 0.11833-0.16868 0.33485-0.16868 0.0705 0 0.13847 0.0176 0.0705 0.0176 0.12588 0.0705 0.0554 0.0529 0.0881 0.14854 0.0353 0.0957 0.0353 0.24925 0 0.0428-5e-3 0.0931-3e-3 0.0478-8e-3 0.10071h-0.63949q0 0.10826 0.0176 0.19638 0.0176 0.0881 0.0554 0.15106 0.0378 0.0604 0.0957 0.0957 0.0604 0.0327 0.14855 0.0327 0.068 0 0.13343-0.0252 0.068-0.0252 0.10323-0.0604zm-0.14099-0.67473q5e-3 -0.18882-0.0529-0.27694-0.0579-0.0881-0.15862-0.0881-0.11581 0-0.18379 0.0881-0.068 0.0881-0.0806 0.27694z"/>
+   <path d="m-535.45-287.66v-0.76789q0-0.18883-0.0453-0.27191-0.0428-0.0856-0.15609-0.0856-0.10071 0-0.16617 0.0604-0.0655 0.0604-0.0957 0.14855v0.91643h-0.18127v-1.2588h0.13092l0.0327 0.13344h8e-3q0.0478-0.068 0.1284-0.11581 0.0831-0.0478 0.19638-0.0478 0.0806 0 0.14099 0.0227 0.0629 0.0227 0.10322 0.0781 0.0428 0.0529 0.0629 0.1435 0.0227 0.0906 0.0227 0.22911v0.81573z"/>
+   <path d="m-535.13-287.3h0.8409v0.16365h-0.8409z"/>
+   <path d="m-533.56-287.66v-0.74775q0-0.10071-8e-3 -0.1712-5e-3 -0.073-0.0252-0.11833-0.0201-0.0453-0.0554-0.0655-0.0353-0.0227-0.0932-0.0227-0.0856 0-0.14603 0.068-0.0579 0.0655-0.0806 0.15106v0.90636h-0.18127v-1.2588h0.1284l0.0327 0.13344h8e-3q0.0529-0.073 0.12588-0.11833 0.073-0.0453 0.18631-0.0453 0.0957 0 0.15609 0.0428 0.063 0.0403 0.0982 0.14603 0.0453-0.0881 0.1284-0.13848 0.0856-0.0504 0.18631-0.0504 0.0831 0 0.14099 0.0227 0.0604 0.0201 0.0957 0.0755 0.0378 0.0529 0.0554 0.14351 0.0176 0.0881 0.0176 0.22407v0.82328h-0.18127v-0.80062q0-0.16365-0.0327-0.24422-0.0302-0.0806-0.1435-0.0806-0.0957 0-0.15358 0.0604-0.0554 0.0579-0.078 0.15862v0.90636z"/>
+   <path d="m-532.52-288.84q0.073-0.0453 0.17624-0.0705 0.10574-0.0252 0.22155-0.0252 0.10575 0 0.16869 0.0327 0.0655 0.0302 0.1007 0.0856 0.0378 0.0529 0.0478 0.12337 0.0126 0.068 0.0126 0.1435 0 0.15106-8e-3 0.29457-5e-3 0.14351-5e-3 0.27191 0 0.0957 5e-3 0.17875 8e-3 0.0806 0.0252 0.15358h-0.13847l-0.0428-0.14854h-0.0101q-0.0378 0.0655-0.11078 0.11329-0.073 0.0478-0.19638 0.0478-0.13595 0-0.22407-0.0931-0.0856-0.0957-0.0856-0.26184 0-0.10826 0.0353-0.18127 0.0378-0.073 0.10322-0.11833 0.068-0.0453 0.15862-0.063 0.0932-0.0201 0.20644-0.0201 0.0252 0 0.0504 0t0.0529 3e-3q8e-3 -0.0781 8e-3 -0.13847 0-0.14351-0.0428-0.20141t-0.15609-0.0579q-0.0705 0-0.15358 0.0227-0.0831 0.0201-0.13847 0.0529zm0.54634 0.60928q-0.0252-3e-3 -0.0504-3e-3 -0.0252-3e-3 -0.0504-3e-3 -0.0604 0-0.11833 0.0101-0.0579 0.0101-0.10322 0.0353-0.0453 0.0252-0.073 0.068-0.0252 0.0428-0.0252 0.10826 0 0.1007 0.0478 0.15609 0.0504 0.0554 0.1284 0.0554 0.10575 0 0.16365-0.0504t0.0806-0.11078z"/>
+   <path d="m-531.5-288.91h0.1284l0.0277 0.13596h0.0101q0.0931-0.16617 0.29205-0.16617 0.19889 0 0.29708 0.14854 0.10071 0.14855 0.10071 0.48591 0 0.15862-0.0327 0.28702-0.0327 0.12588-0.0932 0.21652-0.0604 0.0881-0.14854 0.13595-0.0856 0.0453-0.19134 0.0453-0.073 0-0.11581-0.0101-0.0428-8e-3 -0.0932-0.0352v0.51864h-0.18127zm0.18127 1.0599q0.0353 0.0302 0.078 0.0478 0.0453 0.0176 0.11833 0.0176 0.13344 0 0.21148-0.13595 0.078-0.13596 0.078-0.38773 0-0.10574-0.0151-0.19134-0.0126-0.0856-0.0428-0.14602-0.0302-0.0629-0.078-0.0957-0.0453-0.0353-0.1133-0.0353-0.18379 0-0.23666 0.22407z"/>
+   <path d="m-530.55-287.3h0.8409v0.16365h-0.8409z"/>
+   <path d="m-529.69-288.91h0.15358v-0.24925l0.18127-0.0579v0.30715h0.27191v0.16365h-0.27191v0.75027q0 0.11077 0.0252 0.16113 0.0277 0.0478 0.0881 0.0478 0.0504 0 0.0856-0.0101 0.0378-0.0126 0.0806-0.0302l0.0353 0.14351q-0.0554 0.0277-0.12337 0.0428-0.0655 0.0176-0.13847 0.0176-0.12588 0-0.18127-0.0806-0.0529-0.0831-0.0529-0.26687v-0.77544h-0.15358z"/>
+  </g>
+  <path d="m-546.1-301.79h-9.2604" fill="none" marker-end="url(#Arrow1Send-7)" stroke="#000" stroke-width="1.565"/>
+  <g dominant-baseline="auto" stroke-width=".11071" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Specify protocol- specific details">
+   <path d="m-539.86-282.81q0.0841 0.0576 0.23471 0.11071 0.15499 0.0487 0.34984 0.0487 0.248 0 0.40299-0.11957 0.155-0.12399 0.155-0.38527 0-0.17271-0.0886-0.30113-0.0886-0.12843-0.22142-0.23471-0.13286-0.11071-0.28785-0.21256-0.15057-0.10629-0.28342-0.23028-0.13285-0.12843-0.22142-0.29228-0.0886-0.16385-0.0886-0.39413 0-0.37198 0.22142-0.54912 0.22585-0.18157 0.58455-0.18157 0.22142 0 0.39413 0.0399t0.27899 0.10185l-0.10628 0.29228q-0.0797-0.0487-0.23028-0.0886-0.14614-0.0399-0.34099-0.0399-0.23913 0-0.35427 0.11957-0.11514 0.11514-0.11514 0.29228 0 0.15499 0.0886 0.27456t0.22142 0.22585q0.13285 0.10628 0.28342 0.21699 0.15499 0.10628 0.28785 0.23914 0.13285 0.13285 0.22142 0.30113 0.0886 0.16828 0.0886 0.39856 0 0.3897-0.23028 0.61112t-0.65098 0.22142q-0.2657 0-0.43841-0.0487-0.16828-0.0487-0.27014-0.11071z"/>
+   <path d="m-537.96-284.62h0.22585l0.0487 0.23913h0.0177q0.16385-0.29227 0.51369-0.29227 0.34985 0 0.52256 0.26128 0.17713 0.26127 0.17713 0.85468 0 0.27899-0.0576 0.50484-0.0576 0.22142-0.16385 0.38085-0.10628 0.15499-0.26127 0.23913-0.15057 0.0797-0.33656 0.0797-0.12843 0-0.20371-0.0177-0.0753-0.0133-0.16385-0.062v0.91225h-0.31885zm0.31885 1.8644q0.062 0.0531 0.13728 0.0841 0.0797 0.031 0.20813 0.031 0.23471 0 0.37199-0.23913 0.13728-0.23914 0.13728-0.68198 0-0.18599-0.0266-0.33656-0.0221-0.15056-0.0753-0.25685-0.0531-0.11071-0.13728-0.16828-0.0797-0.062-0.19928-0.062-0.32328 0-0.41627 0.39413z"/>
+   <path d="m-534.71-282.55q-0.10628 0.0974-0.27013 0.15057-0.16385 0.0531-0.34542 0.0531-0.20813 0-0.36313-0.0797-0.15056-0.0841-0.25242-0.23471-0.0974-0.15499-0.14613-0.36756-0.0443-0.21256-0.0443-0.47827 0-0.56683 0.20814-0.86354 0.20813-0.2967 0.58898-0.2967 0.12399 0 0.24356 0.031 0.124 0.031 0.22142 0.12399 0.0974 0.093 0.155 0.26128 0.062 0.16828 0.062 0.43841 0 0.0753-9e-3 0.16386-4e-3 0.0841-0.0133 0.17713h-1.1248q0 0.19042 0.031 0.34542 0.031 0.15499 0.0974 0.2657 0.0664 0.10629 0.16828 0.16828 0.10628 0.0576 0.26127 0.0576 0.11957 0 0.23471-0.0443 0.11957-0.0443 0.18157-0.10628zm-0.24799-1.1868q9e-3 -0.33213-0.093-0.48713-0.10186-0.15499-0.27899-0.15499-0.20371 0-0.32328 0.15499-0.11957 0.155-0.14171 0.48713z"/>
+   <path d="m-533.02-282.51q-0.11071 0.0841-0.25242 0.124t-0.29671 0.0399q-0.21256 0-0.3587-0.0797-0.14614-0.0841-0.23913-0.23471-0.0886-0.15499-0.13286-0.36756-0.0399-0.21699-0.0399-0.47827 0-0.56683 0.19928-0.86354 0.2037-0.2967 0.58012-0.2967 0.17271 0 0.2967 0.031 0.124 0.031 0.21257 0.0797l-0.0886 0.27899q-0.17714-0.10185-0.38527-0.10185-0.23914 0-0.36313 0.21256-0.11957 0.20814-0.11957 0.65983 0 0.18157 0.0266 0.34099 0.0266 0.15943 0.0886 0.27899 0.062 0.11514 0.15942 0.186 0.0974 0.0664 0.24357 0.0664 0.11513 0 0.21256-0.0399 0.10185-0.0399 0.16385-0.093z"/>
+   <path d="m-532.62-284.62h0.31885v2.2142h-0.31885zm-0.0576-0.67312q0-0.10628 0.0576-0.17271 0.062-0.0664 0.15942-0.0664 0.0974 0 0.15943 0.0664 0.0664 0.062 0.0664 0.17271 0 0.10628-0.0664 0.16828-0.062 0.0576-0.15943 0.0576-0.0974 0-0.15942-0.062-0.0576-0.062-0.0576-0.16385z"/>
+   <path d="m-531.9-284.62h0.27013v-0.124q0-0.41627 0.11957-0.60226 0.11956-0.186 0.40741-0.186 0.11514 0 0.20814 0.0133t0.19042 0.0576l-0.0797 0.27456q-0.0797-0.0354-0.15057-0.0443-0.0664-0.0133-0.12842-0.0133-0.0886 0-0.13728 0.0354-0.0487 0.0354-0.0753 0.11071-0.0221 0.0753-0.031 0.19485-4e-3 0.11514-4e-3 0.28342h0.46055v0.28785h-0.46055v1.9264h-0.31885v-1.9264h-0.27013z"/>
+   <path d="m-530.02-283.19 0.093 0.42956h0.0221l0.0664-0.42956 0.33656-1.4304h0.32328l-0.52699 1.9884q-0.062 0.23914-0.12399 0.44727-0.062 0.20814-0.13728 0.3587-0.0709 0.155-0.16386 0.23914-0.0886 0.0886-0.21256 0.0886-0.124 0-0.21699-0.0399l0.0531-0.30113q0.062 0.0221 0.12399 9e-3 0.062-0.0133 0.11514-0.0753 0.0576-0.062 0.10186-0.18599 0.0487-0.11957 0.0841-0.31442l-0.71741-2.2142h0.36313z"/>
+   <path d="m-542.77-280.19h0.22585l0.0487 0.23914h0.0177q0.16385-0.29228 0.51369-0.29228 0.34985 0 0.52256 0.26128 0.17713 0.26127 0.17713 0.85468 0 0.27899-0.0576 0.50484-0.0576 0.22142-0.16385 0.38085-0.10628 0.15499-0.26127 0.23913-0.15057 0.0797-0.33656 0.0797-0.12843 0-0.20371-0.0177-0.0753-0.0133-0.16385-0.062v0.91226h-0.31885zm0.31885 1.8644q0.062 0.0531 0.13728 0.0841 0.0797 0.031 0.20813 0.031 0.23471 0 0.37199-0.23914 0.13728-0.23913 0.13728-0.68198 0-0.18599-0.0266-0.33656-0.0221-0.15056-0.0753-0.25684-0.0531-0.11071-0.13728-0.16828-0.0797-0.062-0.19928-0.062-0.32328 0-0.41627 0.39413z"/>
+   <path d="m-540.84-280.19h0.22585l0.0576 0.23471h0.0133q0.062-0.12843 0.15942-0.19928 0.10186-0.0753 0.24357-0.0753 0.10185 0 0.23027 0.0399l-0.062 0.32328q-0.11514-0.0399-0.20371-0.0399-0.14171 0-0.23028 0.0841-0.0886 0.0797-0.11514 0.21699v1.6297h-0.31884z"/>
+   <path d="m-539.73-279.08q0-0.59784 0.20371-0.87683 0.20813-0.28342 0.58898-0.28342 0.40741 0 0.59783 0.28785 0.19485 0.28785 0.19485 0.8724 0 0.60226-0.20813 0.88125-0.20814 0.27899-0.58455 0.27899-0.40742 0-0.60227-0.28784-0.19042-0.28785-0.19042-0.8724zm0.33213 0q0 0.19485 0.0221 0.35427 0.0266 0.15942 0.0797 0.27456 0.0576 0.11514 0.14614 0.18157 0.0886 0.062 0.21257 0.062 0.23027 0 0.34541-0.20371 0.11514-0.20814 0.11514-0.66869 0-0.19042-0.0266-0.34985-0.0221-0.16385-0.0797-0.27899-0.0531-0.11514-0.14171-0.17714-0.0886-0.0664-0.21256-0.0664-0.22585 0-0.34542 0.20813-0.11514 0.20814-0.11514 0.66427z"/>
+   <path d="m-537.94-280.19h0.27013v-0.43841l0.31885-0.10186v0.54027h0.47827v0.28785h-0.47827v1.3197q0 0.19485 0.0443 0.28342 0.0487 0.0841 0.155 0.0841 0.0886 0 0.15056-0.0177 0.0664-0.0221 0.14171-0.0531l0.062 0.25242q-0.0974 0.0487-0.21699 0.0753-0.11514 0.031-0.24356 0.031-0.22143 0-0.31885-0.14171-0.093-0.14614-0.093-0.46942v-1.364h-0.27013z"/>
+   <path d="m-536.63-279.08q0-0.59784 0.20371-0.87683 0.20814-0.28342 0.58898-0.28342 0.40742 0 0.59784 0.28785 0.19485 0.28785 0.19485 0.8724 0 0.60226-0.20814 0.88125-0.20813 0.27899-0.58455 0.27899-0.40741 0-0.60226-0.28784-0.19043-0.28785-0.19043-0.8724zm0.33214 0q0 0.19485 0.0221 0.35427 0.0266 0.15942 0.0797 0.27456 0.0576 0.11514 0.14614 0.18157 0.0886 0.062 0.21256 0.062 0.23028 0 0.34542-0.20371 0.11514-0.20814 0.11514-0.66869 0-0.19042-0.0266-0.34985-0.0222-0.16385-0.0797-0.27899-0.0531-0.11514-0.14171-0.17714-0.0886-0.0664-0.21257-0.0664-0.22585 0-0.34542 0.20813-0.11513 0.20814-0.11513 0.66427z"/>
+   <path d="m-533.4-278.08q-0.11071 0.0841-0.25242 0.124t-0.2967 0.0399q-0.21257 0-0.35871-0.0797-0.14613-0.0841-0.23913-0.2347-0.0886-0.155-0.13285-0.36756-0.0399-0.217-0.0399-0.47827 0-0.56684 0.19928-0.86355 0.20371-0.2967 0.58012-0.2967 0.17271 0 0.29671 0.031 0.12399 0.031 0.21256 0.0797l-0.0886 0.27899q-0.17713-0.10185-0.38527-0.10185-0.23913 0-0.36313 0.21256-0.11957 0.20814-0.11957 0.65984 0 0.18156 0.0266 0.34099 0.0266 0.15942 0.0886 0.27899 0.062 0.11514 0.15942 0.18599 0.0974 0.0664 0.24357 0.0664 0.11514 0 0.21256-0.0399 0.10186-0.0399 0.16385-0.093z"/>
+   <path d="m-533.23-279.08q0-0.59784 0.20371-0.87683 0.20814-0.28342 0.58898-0.28342 0.40742 0 0.59784 0.28785 0.19485 0.28785 0.19485 0.8724 0 0.60226-0.20814 0.88125-0.20813 0.27899-0.58455 0.27899-0.40741 0-0.60226-0.28784-0.19043-0.28785-0.19043-0.8724zm0.33213 0q0 0.19485 0.0222 0.35427 0.0266 0.15942 0.0797 0.27456 0.0576 0.11514 0.14614 0.18157 0.0886 0.062 0.21256 0.062 0.23028 0 0.34542-0.20371 0.11514-0.20814 0.11514-0.66869 0-0.19042-0.0266-0.34985-0.0222-0.16385-0.0797-0.27899-0.0531-0.11514-0.14171-0.17714-0.0886-0.0664-0.21256-0.0664-0.22585 0-0.34542 0.20813-0.11514 0.20814-0.11514 0.66427z"/>
+   <path d="m-530.88-278.5q0 0.155 0.0399 0.22142 0.0443 0.0664 0.11957 0.0664 0.093 0 0.21699-0.0487l0.031 0.25684q-0.0576 0.0354-0.16385 0.0576-0.10186 0.0222-0.186 0.0222-0.16828 0-0.27456-0.10186-0.10185-0.10628-0.10185-0.36756v-2.6792h0.31884z"/>
+   <path d="m-530.24-279.39h0.8724v0.30556h-0.8724z"/>
+   <path d="m-540.2-273.91q0.0886 0.0532 0.20814 0.093 0.12399 0.0354 0.25242 0.0354 0.14613 0 0.24799-0.0709 0.10185-0.0753 0.10185-0.23913 0-0.13728-0.062-0.22585-0.062-0.0886-0.15942-0.15942-0.093-0.0709-0.20371-0.12843-0.11071-0.062-0.20813-0.14614-0.093-0.0841-0.155-0.19927-0.062-0.11514-0.062-0.29228 0-0.28342 0.15057-0.42513 0.155-0.14614 0.43399-0.14614 0.18156 0 0.31441 0.0354 0.13286 0.031 0.23028 0.0886l-0.0841 0.2657q-0.0841-0.0443-0.19485-0.0709-0.11071-0.031-0.22585-0.031-0.15942 0-0.2347 0.0664-0.0709 0.0664-0.0709 0.20813 0 0.11071 0.062 0.19042 0.062 0.0753 0.15499 0.14171 0.0974 0.062 0.20814 0.12843 0.11071 0.0664 0.20371 0.15942 0.0974 0.0886 0.15942 0.21699 0.062 0.124 0.062 0.31442 0 0.124-0.0399 0.23471t-0.12399 0.19485q-0.0797 0.0797-0.20371 0.12842-0.11957 0.0487-0.28342 0.0487-0.19485 0-0.33656-0.0399-0.14171-0.0354-0.23913-0.0974z"/>
+   <path d="m-538.67-275.76h0.22585l0.0487 0.23914h0.0177q0.16385-0.29228 0.51369-0.29228 0.34985 0 0.52256 0.26128 0.17713 0.26128 0.17713 0.85469 0 0.27899-0.0576 0.50484-0.0576 0.22142-0.16385 0.38084-0.10628 0.15499-0.26127 0.23913-0.15057 0.0797-0.33656 0.0797-0.12843 0-0.20371-0.0177-0.0753-0.0133-0.16385-0.062v0.91226h-0.31885zm0.31885 1.8644q0.062 0.0531 0.13728 0.0841 0.0797 0.031 0.20813 0.031 0.23471 0 0.37199-0.23914 0.13728-0.23913 0.13728-0.68197 0-0.186-0.0266-0.33656-0.0221-0.15057-0.0753-0.25685-0.0531-0.11071-0.13728-0.16828-0.0797-0.062-0.19928-0.062-0.32328 0-0.41627 0.39413z"/>
+   <path d="m-535.41-273.7q-0.10628 0.0974-0.27013 0.15057-0.16385 0.0531-0.34542 0.0531-0.20813 0-0.36313-0.0797-0.15056-0.0841-0.25242-0.2347-0.0974-0.155-0.14614-0.36756-0.0443-0.21256-0.0443-0.47827 0-0.56684 0.20814-0.86354 0.20813-0.29671 0.58898-0.29671 0.12399 0 0.24356 0.031 0.124 0.031 0.22142 0.124 0.0974 0.093 0.155 0.26127 0.062 0.16828 0.062 0.43842 0 0.0753-9e-3 0.16385-4e-3 0.0841-0.0133 0.17714h-1.1248q0 0.19042 0.031 0.34541 0.031 0.155 0.0974 0.26571 0.0664 0.10628 0.16828 0.16828 0.10628 0.0576 0.26127 0.0576 0.11957 0 0.23471-0.0443 0.11957-0.0443 0.18157-0.10628zm-0.24799-1.1868q9e-3 -0.33213-0.093-0.48712-0.10186-0.155-0.27899-0.155-0.20371 0-0.32328 0.155-0.11957 0.15499-0.14171 0.48712z"/>
+   <path d="m-533.72-273.66q-0.11071 0.0841-0.25242 0.124t-0.29671 0.0399q-0.21256 0-0.3587-0.0797-0.14614-0.0841-0.23913-0.2347-0.0886-0.155-0.13286-0.36756-0.0399-0.21699-0.0399-0.47827 0-0.56684 0.19928-0.86354 0.2037-0.29671 0.58012-0.29671 0.17271 0 0.2967 0.031 0.124 0.031 0.21257 0.0797l-0.0886 0.27899q-0.17714-0.10185-0.38527-0.10185-0.23914 0-0.36313 0.21256-0.11957 0.20814-0.11957 0.65984 0 0.18157 0.0266 0.34099t0.0886 0.27899q0.062 0.11514 0.15942 0.18599 0.0974 0.0664 0.24357 0.0664 0.11513 0 0.21256-0.0399 0.10185-0.0399 0.16385-0.093z"/>
+   <path d="m-533.32-275.76h0.31885v2.2142h-0.31885zm-0.0576-0.67312q0-0.10628 0.0576-0.1727 0.062-0.0664 0.15942-0.0664 0.0974 0 0.15943 0.0664 0.0664 0.062 0.0664 0.1727 0 0.10629-0.0664 0.16828-0.062 0.0576-0.15943 0.0576-0.0974 0-0.15942-0.062-0.0576-0.062-0.0576-0.16386z"/>
+   <path d="m-531.01-275.76v2.2142h-0.31885v-1.9264h-0.6864v1.9264h-0.31885v-1.9264h-0.27013v-0.28785h0.27013v-0.12399q0-0.41627 0.16828-0.60227 0.16828-0.19042 0.49155-0.19042 0.217 0 0.37642 0.0443 0.16385 0.0399 0.2657 0.093l-0.10185 0.26571q-0.10628-0.062-0.23913-0.0886-0.12843-0.0266-0.27014-0.0266-0.12399 0-0.19928 0.0443-0.0709 0.0399-0.11071 0.124-0.0399 0.0797-0.0531 0.19928-9e-3 0.11514-9e-3 0.26127z"/>
+   <path d="m-529.25-273.66q-0.11071 0.0841-0.25242 0.124t-0.29671 0.0399q-0.21256 0-0.3587-0.0797-0.14614-0.0841-0.23913-0.2347-0.0886-0.155-0.13286-0.36756-0.0399-0.21699-0.0399-0.47827 0-0.56684 0.19928-0.86354 0.2037-0.29671 0.58012-0.29671 0.17271 0 0.2967 0.031 0.124 0.031 0.21257 0.0797l-0.0886 0.27899q-0.17714-0.10185-0.38527-0.10185-0.23914 0-0.36313 0.21256-0.11957 0.20814-0.11957 0.65984 0 0.18157 0.0266 0.34099t0.0886 0.27899q0.062 0.11514 0.15942 0.18599 0.0974 0.0664 0.24356 0.0664 0.11514 0 0.21257-0.0399 0.10185-0.0399 0.16385-0.093z"/>
+   <path d="m-537.73-269.88q0 0.22585 4e-3 0.41184 4e-3 0.18157 0.031 0.3587h-0.21699l-0.0709-0.2657h-0.0177q-0.062 0.13285-0.19485 0.22142-0.13285 0.0886-0.31885 0.0886-0.3587 0-0.53584-0.27899-0.17271-0.27899-0.17271-0.87683 0-0.56684 0.21257-0.85911 0.21699-0.29228 0.59341-0.29228 0.12842 0 0.2037 0.0177 0.0753 0.0133 0.16386 0.0487v-0.91226h0.31884zm-0.31884-1.1027q-0.062-0.0531-0.14171-0.0753-0.0753-0.0266-0.20371-0.0266-0.23471 0-0.36756 0.21256-0.12842 0.21257-0.12842 0.65541 0 0.19485 0.0221 0.35427 0.0266 0.155 0.0753 0.27014 0.0531 0.11514 0.13285 0.17713 0.0841 0.062 0.20371 0.062 0.31885 0 0.40742-0.37641z"/>
+   <path d="m-535.88-269.27q-0.10628 0.0974-0.27013 0.15057-0.16385 0.0531-0.34542 0.0531-0.20813 0-0.36313-0.0797-0.15056-0.0841-0.25242-0.23471-0.0974-0.15499-0.14614-0.36756-0.0443-0.21256-0.0443-0.47827 0-0.56684 0.20814-0.86354 0.20813-0.2967 0.58898-0.2967 0.12399 0 0.24356 0.031 0.124 0.031 0.22142 0.124 0.0974 0.093 0.155 0.26128 0.062 0.16828 0.062 0.43841 0 0.0753-9e-3 0.16385-4e-3 0.0841-0.0133 0.17714h-1.1248q0 0.19042 0.031 0.34542 0.031 0.15499 0.0974 0.2657 0.0664 0.10628 0.16828 0.16828 0.10628 0.0576 0.26127 0.0576 0.11957 0 0.23471-0.0443 0.11957-0.0443 0.18157-0.10629zm-0.24799-1.1868q9e-3 -0.33213-0.093-0.48712-0.10186-0.155-0.27899-0.155-0.20371 0-0.32328 0.155-0.11957 0.15499-0.14171 0.48712z"/>
+   <path d="m-535.6-271.33h0.27013v-0.43842l0.31885-0.10185v0.54027h0.47827v0.28784h-0.47827v1.3197q0 0.19485 0.0443 0.28342 0.0487 0.0841 0.155 0.0841 0.0886 0 0.15056-0.0177 0.0664-0.0221 0.14171-0.0531l0.062 0.25242q-0.0974 0.0487-0.21699 0.0753-0.11514 0.031-0.24356 0.031-0.22142 0-0.31885-0.14171-0.093-0.14614-0.093-0.46941v-1.364h-0.27013z"/>
+   <path d="m-534.18-271.2q0.12843-0.0797 0.30999-0.124 0.186-0.0443 0.38971-0.0443 0.18599 0 0.2967 0.0576 0.11514 0.0531 0.17714 0.15056 0.0664 0.093 0.0841 0.217 0.0221 0.11957 0.0221 0.25242 0 0.2657-0.0133 0.51812-9e-3 0.25242-9e-3 0.47827 0 0.16828 9e-3 0.31442 0.0133 0.14171 0.0443 0.27013h-0.24357l-0.0753-0.26127h-0.0177q-0.0664 0.11514-0.19485 0.19928-0.12843 0.0841-0.34542 0.0841-0.23914 0-0.39413-0.16385-0.15057-0.16828-0.15057-0.46056 0-0.19042 0.062-0.31885 0.0664-0.12842 0.18157-0.20813 0.11956-0.0797 0.27899-0.11071 0.16385-0.0354 0.36313-0.0354 0.0443 0 0.0886 0t0.093 4e-3q0.0133-0.13728 0.0133-0.24357 0-0.25242-0.0753-0.35427t-0.27457-0.10185q-0.12399 0-0.27013 0.0399-0.14614 0.0354-0.24356 0.093zm0.96097 1.0717q-0.0443-4e-3 -0.0886-4e-3 -0.0443-4e-3 -0.0886-4e-3 -0.10629 0-0.20814 0.0177t-0.18157 0.062q-0.0797 0.0443-0.12842 0.11957-0.0443 0.0753-0.0443 0.19042 0 0.17714 0.0841 0.27456 0.0886 0.0974 0.22584 0.0974 0.186 0 0.28785-0.0886 0.10186-0.0886 0.14171-0.19485z"/>
+   <path d="m-532.33-271.33h0.31884v2.2142h-0.31884zm-0.0576-0.67312q0-0.10629 0.0576-0.17271 0.062-0.0664 0.15942-0.0664 0.0974 0 0.15942 0.0664 0.0664 0.062 0.0664 0.17271 0 0.10628-0.0664 0.16828-0.062 0.0576-0.15942 0.0576-0.0974 0-0.15942-0.062-0.0576-0.062-0.0576-0.16385z"/>
+   <path d="m-531.08-269.64q0 0.15499 0.0399 0.22142 0.0443 0.0664 0.11957 0.0664 0.093 0 0.21699-0.0487l0.031 0.25685q-0.0576 0.0354-0.16385 0.0576-0.10186 0.0221-0.186 0.0221-0.16828 0-0.27456-0.10185-0.10185-0.10629-0.10185-0.36756v-2.6792h0.31884z"/>
+   <path d="m-530.43-269.48q0.0886 0.0531 0.20813 0.093 0.124 0.0354 0.25242 0.0354 0.14614 0 0.248-0.0709 0.10185-0.0753 0.10185-0.23914 0-0.13728-0.062-0.22585-0.062-0.0886-0.15942-0.15942-0.093-0.0709-0.20371-0.12843-0.11071-0.062-0.20813-0.14613-0.093-0.0841-0.155-0.19928-0.062-0.11514-0.062-0.29228 0-0.28342 0.15057-0.42513 0.15499-0.14613 0.43398-0.14613 0.18157 0 0.31442 0.0354 0.13285 0.031 0.23028 0.0886l-0.0841 0.26571q-0.0841-0.0443-0.19485-0.0709-0.11071-0.031-0.22585-0.031-0.15942 0-0.23471 0.0664-0.0708 0.0664-0.0708 0.20814 0 0.11071 0.062 0.19042 0.062 0.0753 0.15499 0.14171 0.0974 0.062 0.20814 0.12842 0.11071 0.0664 0.2037 0.15942 0.0974 0.0886 0.15943 0.217 0.062 0.12399 0.062 0.31441 0 0.124-0.0399 0.23471t-0.124 0.19485q-0.0797 0.0797-0.2037 0.12843-0.11957 0.0487-0.28342 0.0487-0.19485 0-0.33656-0.0399-0.14171-0.0354-0.23914-0.0974z"/>
+  </g>
+  <path d="m-597.58-290.35h40.632v13.607h-40.632z" fill="#b3b3b3" stroke-width=".12266"/>
+  <path d="m-597.58-275.14h40.632v13.607h-40.632z" fill="#b3b3b3" stroke-width=".12266"/>
+  <g>
+   <g dominant-baseline="auto" stroke-width=".17608" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="transport priv">
+    <path d="m-593.67-284.97h0.42965v-0.69729l0.50712-0.162v0.85929h0.76068v0.45782h-0.76068v2.0989q0 0.30991 0.0704 0.45078 0.0775 0.13382 0.24652 0.13382 0.14086 0 0.23947-0.0282 0.10565-0.0352 0.22539-0.0845l0.0986 0.40147q-0.15495 0.0775-0.34512 0.11973-0.18313 0.0493-0.38738 0.0493-0.35217 0-0.50712-0.22539-0.14791-0.23243-0.14791-0.74659v-2.1694h-0.42965z"/>
+    <path d="m-591.38-284.97h0.35921l0.0916 0.3733h0.0211q0.0986-0.20426 0.25356-0.31695 0.162-0.11974 0.38739-0.11974 0.16199 0 0.36625 0.0634l-0.0986 0.51416q-0.18312-0.0634-0.32399-0.0634-0.22539 0-0.36625 0.13383-0.14087 0.12678-0.18313 0.34512v2.592h-0.50712z"/>
+    <path d="m-589.49-284.76q0.20425-0.12678 0.49303-0.19721 0.29582-0.0704 0.61981-0.0704 0.29582 0 0.47191 0.0916 0.18312 0.0845 0.28173 0.23947 0.10565 0.14791 0.13382 0.34512 0.0352 0.19017 0.0352 0.40147 0 0.4226-0.0211 0.82407-0.0141 0.40148-0.0141 0.76069 0 0.26764 0.0141 0.50007 0.0211 0.22539 0.0704 0.42965h-0.38738l-0.11974-0.41556h-0.0282q-0.10565 0.18313-0.30991 0.31695-0.20425 0.13382-0.54938 0.13382-0.38034 0-0.62685-0.2606-0.23948-0.26765-0.23948-0.73251 0-0.30286 0.0986-0.50712 0.10565-0.20426 0.28878-0.33104 0.19017-0.12678 0.44373-0.17608 0.2606-0.0564 0.57755-0.0564 0.0704 0 0.14087 0 0.0704 0 0.14791 7e-3 0.0211-0.21835 0.0211-0.38739 0-0.40147-0.11974-0.56346-0.11974-0.162-0.43669-0.162-0.19721 0-0.42964 0.0634-0.23243 0.0564-0.38738 0.14791zm1.5284 1.7045q-0.0704-7e-3 -0.14087-7e-3 -0.0704-7e-3 -0.14086-7e-3 -0.16904 0-0.33104 0.0282t-0.28878 0.0986-0.20425 0.19017q-0.0704 0.11974-0.0704 0.30287 0 0.28173 0.13383 0.43668 0.14086 0.15496 0.35921 0.15496 0.29582 0 0.45781-0.14087 0.162-0.14087 0.22539-0.30991z"/>
+    <path d="m-584.83-281.45v-2.1482q0-0.52825-0.12678-0.76068-0.11973-0.23947-0.43668-0.23947-0.28174 0-0.46486 0.16904-0.18313 0.16904-0.26765 0.41555v2.5638h-0.50712v-3.5217h0.36625l0.0916 0.3733h0.0211q0.13382-0.19017 0.35921-0.324 0.23243-0.13382 0.54938-0.13382 0.22538 0 0.39442 0.0634 0.17609 0.0634 0.28878 0.21834 0.11974 0.14791 0.17608 0.40147 0.0634 0.25356 0.0634 0.64095v2.282z"/>
+    <path d="m-583.59-282.02q0.14087 0.0845 0.33104 0.14791 0.19721 0.0563 0.40147 0.0563 0.23243 0 0.39442-0.11269 0.162-0.11974 0.162-0.38034 0-0.21834-0.0986-0.35921t-0.25356-0.25356q-0.14791-0.1127-0.32399-0.20426-0.17608-0.0986-0.33104-0.23243-0.14791-0.13382-0.24651-0.31695t-0.0986-0.46486q0-0.45077 0.23947-0.67616 0.24652-0.23243 0.69025-0.23243 0.28878 0 0.50008 0.0564 0.2113 0.0493 0.36625 0.14086l-0.13382 0.4226q-0.13382-0.0704-0.30991-0.11269-0.17608-0.0493-0.35921-0.0493-0.25356 0-0.3733 0.10565-0.11269 0.10565-0.11269 0.33103 0 0.17609 0.0986 0.30287 0.0986 0.11973 0.24651 0.22538 0.15496 0.0986 0.33104 0.20426 0.17608 0.10565 0.324 0.25356 0.15495 0.14087 0.25356 0.34513 0.0986 0.19721 0.0986 0.50007 0 0.19722-0.0634 0.3733t-0.19721 0.30991q-0.12678 0.12678-0.324 0.20425-0.19017 0.0775-0.45077 0.0775-0.30991 0-0.53529-0.0634-0.22539-0.0564-0.38034-0.15495z"/>
+    <path d="m-581.16-284.97h0.35921l0.0775 0.38034h0.0282q0.2606-0.46486 0.81702-0.46486 0.55643 0 0.83112 0.41556 0.28173 0.41555 0.28173 1.3594 0 0.44373-0.0916 0.80294-0.0916 0.35217-0.26061 0.60573-0.16904 0.24652-0.41555 0.38034-0.23948 0.12678-0.5353 0.12678-0.20425 0-0.32399-0.0282-0.11974-0.0211-0.2606-0.0986v1.4509h-0.50712zm0.50712 2.9652q0.0986 0.0845 0.21834 0.13382 0.12678 0.0493 0.33104 0.0493 0.37329 0 0.59164-0.38034 0.21834-0.38034 0.21834-1.0847 0-0.29582-0.0423-0.5353-0.0352-0.23947-0.11974-0.40851-0.0845-0.17608-0.21834-0.26765-0.12678-0.0986-0.31695-0.0986-0.51416 0-0.66207 0.62685z"/>
+    <path d="m-578.24-283.21q0-0.95085 0.32399-1.3946 0.33104-0.45077 0.93676-0.45077 0.64799 0 0.95085 0.45782 0.30991 0.45781 0.30991 1.3875 0 0.9579-0.33104 1.4016-0.33103 0.44373-0.92972 0.44373-0.64798 0-0.95789-0.45782-0.30286-0.45781-0.30286-1.3875zm0.52825 0q0 0.30991 0.0352 0.56347 0.0423 0.25356 0.12678 0.43669 0.0916 0.18313 0.23243 0.28878 0.14087 0.0986 0.33808 0.0986 0.36626 0 0.54938-0.32399 0.18313-0.33104 0.18313-1.0636 0-0.30286-0.0423-0.55642-0.0352-0.2606-0.12678-0.44373-0.0845-0.18313-0.22539-0.28173-0.14086-0.10565-0.33808-0.10565-0.35921 0-0.54938 0.33103-0.18312 0.33104-0.18312 1.0565z"/>
+    <path d="m-575.05-284.97h0.35921l0.0916 0.3733h0.0211q0.0986-0.20426 0.25357-0.31695 0.16199-0.11974 0.38738-0.11974 0.162 0 0.36625 0.0634l-0.0986 0.51416q-0.18313-0.0634-0.324-0.0634-0.22538 0-0.36625 0.13383-0.14087 0.12678-0.18313 0.34512v2.592h-0.50712z"/>
+    <path d="m-573.31-284.97h0.42964v-0.69729l0.50712-0.162v0.85929h0.76068v0.45782h-0.76068v2.0989q0 0.30991 0.0704 0.45078 0.0775 0.13382 0.24652 0.13382 0.14087 0 0.23947-0.0282 0.10565-0.0352 0.22539-0.0845l0.0986 0.40147q-0.15496 0.0775-0.34513 0.11973-0.18312 0.0493-0.38738 0.0493-0.35217 0-0.50712-0.22539-0.14791-0.23243-0.14791-0.74659v-2.1694h-0.42964z"/>
+    <path d="m-569.52-284.97h0.35921l0.0775 0.38034h0.0282q0.26061-0.46486 0.81703-0.46486t0.83111 0.41556q0.28174 0.41555 0.28174 1.3594 0 0.44373-0.0916 0.80294-0.0916 0.35217-0.2606 0.60573-0.16904 0.24652-0.41556 0.38034-0.23947 0.12678-0.53529 0.12678-0.20426 0-0.32399-0.0282-0.11974-0.0211-0.26061-0.0986v1.4509h-0.50712zm0.50712 2.9652q0.0986 0.0845 0.21835 0.13382 0.12678 0.0493 0.33103 0.0493 0.3733 0 0.59164-0.38034 0.21835-0.38034 0.21835-1.0847 0-0.29582-0.0423-0.5353-0.0352-0.23947-0.11974-0.40851-0.0845-0.17608-0.21834-0.26765-0.12678-0.0986-0.31695-0.0986-0.51417 0-0.66208 0.62685z"/>
+    <path d="m-566.45-284.97h0.35921l0.0916 0.3733h0.0211q0.0986-0.20426 0.25356-0.31695 0.162-0.11974 0.38738-0.11974 0.162 0 0.36626 0.0634l-0.0986 0.51416q-0.18313-0.0634-0.32399-0.0634-0.22539 0-0.36626 0.13383-0.14086 0.12678-0.18312 0.34512v2.592h-0.50712z"/>
+    <path d="m-564.44-284.97h0.50712v3.5217h-0.50712zm-0.0916-1.0706q0-0.16904 0.0916-0.27469 0.0986-0.10565 0.25356-0.10565 0.15495 0 0.25356 0.10565 0.10565 0.0986 0.10565 0.27469 0 0.16904-0.10565 0.26765-0.0986 0.0916-0.25356 0.0916-0.15495 0-0.25356-0.0986-0.0916-0.0986-0.0916-0.26061z"/>
+    <path d="m-562.15-282.9 0.14086 0.69729h0.0141l0.12678-0.71138 0.61982-2.0496h0.53529l-1.2044 3.5992h-0.24652l-1.2255-3.5992h0.57755z"/>
+   </g>
+   <g dominant-baseline="auto" stroke-width=".17608" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="protocol priv">
+    <path d="m-592.31-270h0.35921l0.0775 0.38034h0.0282q0.26061-0.46486 0.81703-0.46486t0.83111 0.41556q0.28174 0.41555 0.28174 1.3594 0 0.44373-0.0916 0.80294-0.0916 0.35217-0.2606 0.60573-0.16904 0.24652-0.41556 0.38034-0.23947 0.12678-0.53529 0.12678-0.20426 0-0.32399-0.0282-0.11974-0.0211-0.26061-0.0986v1.4509h-0.50712zm0.50712 2.9652q0.0986 0.0845 0.21835 0.13382 0.12678 0.0493 0.33103 0.0493 0.3733 0 0.59164-0.38034 0.21835-0.38034 0.21835-1.0847 0-0.29582-0.0423-0.5353-0.0352-0.23947-0.11974-0.40851-0.0845-0.17608-0.21834-0.26765-0.12678-0.0986-0.31695-0.0986-0.51417 0-0.66208 0.62685z"/>
+    <path d="m-589.24-270h0.35921l0.0916 0.3733h0.0211q0.0986-0.20426 0.25356-0.31695 0.162-0.11974 0.38738-0.11974 0.162 0 0.36626 0.0634l-0.0986 0.51416q-0.18313-0.0634-0.32399-0.0634-0.22539 0-0.36626 0.13383-0.14086 0.12678-0.18312 0.34512v2.592h-0.50712z"/>
+    <path d="m-587.47-268.24q0-0.95085 0.32399-1.3946 0.33104-0.45077 0.93676-0.45077 0.64799 0 0.95085 0.45782 0.30991 0.45781 0.30991 1.3875 0 0.9579-0.33104 1.4016-0.33103 0.44373-0.92972 0.44373-0.64798 0-0.95789-0.45782-0.30286-0.45781-0.30286-1.3875zm0.52825 0q0 0.30991 0.0352 0.56347 0.0423 0.25356 0.12678 0.43669 0.0916 0.18313 0.23243 0.28878 0.14087 0.0986 0.33808 0.0986 0.36626 0 0.54938-0.32399 0.18313-0.33104 0.18313-1.0636 0-0.30286-0.0423-0.55642-0.0352-0.2606-0.12678-0.44373-0.0845-0.18313-0.22539-0.28173-0.14086-0.10565-0.33808-0.10565-0.35921 0-0.54938 0.33103-0.18312 0.33104-0.18312 1.0565z"/>
+    <path d="m-584.62-270h0.42965v-0.69729l0.50712-0.162v0.85929h0.76068v0.45782h-0.76068v2.0989q0 0.30991 0.0704 0.45078 0.0775 0.13382 0.24652 0.13382 0.14086 0 0.23947-0.0282 0.10565-0.0352 0.22539-0.0845l0.0986 0.40147q-0.15495 0.0775-0.34512 0.11973-0.18313 0.0493-0.38738 0.0493-0.35217 0-0.50712-0.22539-0.14791-0.23243-0.14791-0.74659v-2.1694h-0.42965z"/>
+    <path d="m-582.54-268.24q0-0.95085 0.32399-1.3946 0.33104-0.45077 0.93677-0.45077 0.64798 0 0.95085 0.45782 0.30991 0.45781 0.30991 1.3875 0 0.9579-0.33104 1.4016t-0.92972 0.44373q-0.64799 0-0.9579-0.45782-0.30286-0.45781-0.30286-1.3875zm0.52825 0q0 0.30991 0.0352 0.56347 0.0423 0.25356 0.12678 0.43669 0.0916 0.18313 0.23243 0.28878 0.14086 0.0986 0.33808 0.0986 0.36625 0 0.54938-0.32399 0.18312-0.33104 0.18312-1.0636 0-0.30286-0.0423-0.55642-0.0352-0.2606-0.12678-0.44373-0.0845-0.18313-0.22538-0.28173-0.14087-0.10565-0.33808-0.10565-0.35921 0-0.54938 0.33103-0.18313 0.33104-0.18313 1.0565z"/>
+    <path d="m-577.4-266.66q-0.17609 0.13382-0.40147 0.19721-0.22539 0.0634-0.47191 0.0634-0.33808 0-0.57051-0.12678-0.23243-0.13382-0.38034-0.3733-0.14086-0.24651-0.2113-0.58459-0.0634-0.34513-0.0634-0.76069 0-0.90154 0.31695-1.3734 0.324-0.4719 0.92268-0.4719 0.27469 0 0.4719 0.0493 0.19722 0.0493 0.33808 0.12678l-0.14086 0.44373q-0.28174-0.16199-0.61277-0.16199-0.38034 0-0.57756 0.33808-0.19017 0.33103-0.19017 1.0494 0 0.28878 0.0423 0.54234t0.14087 0.44373q0.0986 0.18313 0.25356 0.29582 0.15495 0.10565 0.38738 0.10565 0.18313 0 0.33808-0.0634 0.162-0.0634 0.26061-0.14791z"/>
+    <path d="m-577.12-268.24q0-0.95085 0.32399-1.3946 0.33104-0.45077 0.93677-0.45077 0.64798 0 0.95085 0.45782 0.30991 0.45781 0.30991 1.3875 0 0.9579-0.33104 1.4016t-0.92972 0.44373q-0.64799 0-0.9579-0.45782-0.30286-0.45781-0.30286-1.3875zm0.52825 0q0 0.30991 0.0352 0.56347 0.0423 0.25356 0.12678 0.43669 0.0916 0.18313 0.23243 0.28878 0.14086 0.0986 0.33808 0.0986 0.36625 0 0.54938-0.32399 0.18312-0.33104 0.18312-1.0636 0-0.30286-0.0423-0.55642-0.0352-0.2606-0.12678-0.44373-0.0845-0.18313-0.22538-0.28173-0.14087-0.10565-0.33808-0.10565-0.35921 0-0.54938 0.33103-0.18313 0.33104-0.18313 1.0565z"/>
+    <path d="m-573.39-267.32q0 0.24652 0.0634 0.35217 0.0704 0.10565 0.19017 0.10565 0.14791 0 0.34513-0.0775l0.0493 0.40852q-0.0916 0.0563-0.2606 0.0916-0.162 0.0352-0.29582 0.0352-0.26765 0-0.43669-0.162-0.162-0.16904-0.162-0.5846v-4.2612h0.50712z"/>
+    <path d="m-570.79-270h0.35921l0.0775 0.38034h0.0282q0.26061-0.46486 0.81703-0.46486t0.83111 0.41556q0.28174 0.41555 0.28174 1.3594 0 0.44373-0.0916 0.80294-0.0916 0.35217-0.2606 0.60573-0.16904 0.24652-0.41556 0.38034-0.23947 0.12678-0.53529 0.12678-0.20426 0-0.32399-0.0282-0.11974-0.0211-0.26061-0.0986v1.4509h-0.50712zm0.50712 2.9652q0.0986 0.0845 0.21835 0.13382 0.12678 0.0493 0.33103 0.0493 0.3733 0 0.59164-0.38034 0.21835-0.38034 0.21835-1.0847 0-0.29582-0.0423-0.5353-0.0352-0.23947-0.11974-0.40851-0.0845-0.17608-0.21834-0.26765-0.12678-0.0986-0.31695-0.0986-0.51417 0-0.66208 0.62685z"/>
+    <path d="m-567.72-270h0.35921l0.0916 0.3733h0.0211q0.0986-0.20426 0.25356-0.31695 0.162-0.11974 0.38738-0.11974 0.162 0 0.36626 0.0634l-0.0986 0.51416q-0.18313-0.0634-0.32399-0.0634-0.22539 0-0.36626 0.13383-0.14086 0.12678-0.18312 0.34512v2.592h-0.50712z"/>
+    <path d="m-565.71-270h0.50712v3.5217h-0.50712zm-0.0916-1.0706q0-0.16904 0.0916-0.27469 0.0986-0.10565 0.25356-0.10565 0.15495 0 0.25356 0.10565 0.10565 0.0986 0.10565 0.27469 0 0.16904-0.10565 0.26765-0.0986 0.0916-0.25356 0.0916-0.15495 0-0.25356-0.0986-0.0916-0.0986-0.0916-0.26061z"/>
+    <path d="m-563.42-267.94 0.14086 0.69729h0.0141l0.12678-0.71138 0.61981-2.0496h0.5353l-1.2044 3.5992h-0.24652l-1.2255-3.5992h0.57755z"/>
+   </g>
+   <path d="m-605.19-328.66h9.2604" fill="none" marker-end="url(#Arrow1Send-6)" stroke="#000" stroke-width="1.565"/>
+   <path d="m-550.11-328.66h-9.2604" fill="none" marker-end="url(#Arrow1Send-7-0)" stroke="#000" stroke-width="1.565"/>
+  </g>
+  <path d="m-582.59-329.2a5.3454 5.3454 0 0 1-5.3454 5.3454 5.3454 5.3454 0 0 1-5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454 5.3454z" fill="#d4aa00"/>
+  <g dominant-baseline="auto" stroke-width=".19242" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="at">
+   <path d="m-590.54-330.97q0.22321-0.13854 0.53878-0.21551 0.32328-0.077 0.67734-0.077 0.32327 0 0.51569 0.10006 0.20012 0.0924 0.30788 0.26169 0.11546 0.16164 0.14624 0.37716 0.0385 0.20781 0.0385 0.43872 0 0.46182-0.0231 0.90055-0.0154 0.43873-0.0154 0.83127 0 0.29249 0.0154 0.54649 0.0231 0.2463 0.077 0.46951h-0.42333l-0.13085-0.45412h-0.0308q-0.11546 0.20012-0.33867 0.34636-0.22321 0.14625-0.60036 0.14625-0.41564 0-0.68503-0.28479-0.2617-0.29249-0.2617-0.80049 0-0.33097 0.10776-0.55418 0.11545-0.22321 0.31557-0.36176 0.20782-0.13854 0.48491-0.19242 0.28479-0.0616 0.63116-0.0616 0.077 0 0.15393 0 0.077 0 0.16164 8e-3 0.0231-0.23861 0.0231-0.42333 0-0.43873-0.13085-0.61576t-0.47721-0.17703q-0.21551 0-0.46951 0.0693-0.254 0.0616-0.42334 0.16164zm1.6702 1.8627q-0.077-8e-3 -0.15394-8e-3 -0.077-8e-3 -0.15394-8e-3 -0.18473 0-0.36176 0.0308t-0.31557 0.10776q-0.13855 0.077-0.22322 0.20782-0.077 0.13085-0.077 0.33097 0 0.30788 0.14625 0.47721 0.15394 0.16933 0.39254 0.16933 0.32327 0 0.5003-0.15394 0.17704-0.15394 0.24631-0.33866z"/>
+   <path d="m-587.8-331.2h0.46951v-0.762l0.55418-0.17703v0.93903h0.83128v0.5003h-0.83128v2.2937q0 0.33867 0.077 0.49261 0.0847 0.14624 0.2694 0.14624 0.15394 0 0.26169-0.0308 0.11546-0.0385 0.24631-0.0924l0.10775 0.43873q-0.16933 0.0847-0.37715 0.13084-0.20012 0.0539-0.42333 0.0539-0.38485 0-0.55418-0.2463-0.16164-0.254-0.16164-0.81588v-2.3707h-0.46951z"/>
+  </g>
+  <path d="m-562.28-329.73a5.3454 5.3454 0 0 1-5.3454 5.3454 5.3454 5.3454 0 0 1-5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454 5.3454z" fill="#d4aa00"/>
+  <g dominant-baseline="auto" stroke-width=".19242" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="ap">
+   <path d="m-570.22-331.5q0.22321-0.13855 0.53878-0.21552 0.32328-0.077 0.67734-0.077 0.32327 0 0.51569 0.10006 0.20012 0.0924 0.30788 0.2617 0.11546 0.16163 0.14624 0.37715 0.0385 0.20782 0.0385 0.43873 0 0.46181-0.0231 0.90054-0.0154 0.43873-0.0154 0.83127 0 0.29249 0.0154 0.54649 0.0231 0.2463 0.077 0.46951h-0.42333l-0.13085-0.45412h-0.0308q-0.11546 0.20012-0.33867 0.34637-0.22321 0.14624-0.60036 0.14624-0.41564 0-0.68503-0.28479-0.2617-0.29248-0.2617-0.80048 0-0.33097 0.10776-0.55418 0.11545-0.22322 0.31557-0.36176 0.20782-0.13855 0.48491-0.19243 0.28479-0.0616 0.63116-0.0616 0.077 0 0.15393 0 0.077 0 0.16164 8e-3 0.0231-0.2386 0.0231-0.42333 0-0.43873-0.13085-0.61576t-0.47721-0.17703q-0.21551 0-0.46951 0.0693-0.254 0.0616-0.42334 0.16163zm1.6702 1.8627q-0.077-8e-3 -0.15394-8e-3 -0.077-8e-3 -0.15394-8e-3 -0.18473 0-0.36176 0.0308t-0.31557 0.10775q-0.13855 0.077-0.22322 0.20782-0.077 0.13085-0.077 0.33097 0 0.30788 0.14625 0.47721 0.15394 0.16934 0.39254 0.16934 0.32327 0 0.5003-0.15394 0.17704-0.15394 0.24631-0.33867z"/>
+   <path d="m-567.1-331.74h0.39255l0.0847 0.41563h0.0308q0.28479-0.508 0.89285-0.508t0.90825 0.45412q0.30787 0.45412 0.30787 1.4855 0 0.48491-0.10006 0.87745-0.10006 0.38485-0.28478 0.66194-0.18473 0.2694-0.45413 0.41564-0.26169 0.13854-0.58496 0.13854-0.22322 0-0.35407-0.0308-0.13084-0.0231-0.28478-0.10776v1.5856h-0.55419zm0.55419 3.2404q0.10775 0.0924 0.2386 0.14624 0.13855 0.0539 0.36176 0.0539 0.40794 0 0.64655-0.41564 0.2386-0.41563 0.2386-1.1853 0-0.32327-0.0462-0.58497-0.0385-0.26169-0.13085-0.44642-0.0924-0.19243-0.2386-0.29249-0.13855-0.10775-0.34637-0.10775-0.56188 0-0.72351 0.68503z"/>
+  </g>
+  <path d="m-583.93-315.84a5.3454 5.3454 0 0 1-5.3454 5.3454 5.3454 5.3454 0 0 1-5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454 5.3454z" fill="#d4aa00"/>
+  <g dominant-baseline="auto" stroke-width=".19242" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="ati">
+   <path d="m-591.87-317.61q0.22321-0.13855 0.53879-0.21552 0.32327-0.077 0.67733-0.077 0.32328 0 0.5157 0.10006 0.20012 0.0924 0.30788 0.2617 0.11545 0.16163 0.14624 0.37715 0.0385 0.20782 0.0385 0.43873 0 0.46181-0.0231 0.90054-0.0154 0.43873-0.0154 0.83127 0 0.29249 0.0154 0.54649 0.0231 0.2463 0.077 0.46951h-0.42333l-0.13085-0.45412h-0.0308q-0.11546 0.20012-0.33867 0.34637-0.22321 0.14624-0.60037 0.14624-0.41563 0-0.68503-0.28479-0.26169-0.29248-0.26169-0.80048 0-0.33097 0.10776-0.55418 0.11545-0.22322 0.31557-0.36176 0.20782-0.13855 0.48491-0.19243 0.28479-0.0616 0.63115-0.0616 0.077 0 0.15394 0 0.077 0 0.16164 8e-3 0.0231-0.2386 0.0231-0.42333 0-0.43873-0.13085-0.61576t-0.47721-0.17703q-0.21552 0-0.46952 0.0693-0.254 0.0616-0.42333 0.16163zm1.6702 1.8627q-0.077-8e-3 -0.15394-8e-3 -0.077-8e-3 -0.15393-8e-3 -0.18473 0-0.36176 0.0308t-0.31558 0.10775q-0.13854 0.077-0.22321 0.20782-0.077 0.13085-0.077 0.33097 0 0.30788 0.14624 0.47721 0.15394 0.16934 0.39255 0.16934 0.32327 0 0.5003-0.15394t0.2463-0.33867z"/>
+   <path d="m-589.14-317.84h0.46952v-0.762l0.55418-0.17703v0.93903h0.83127v0.5003h-0.83127v2.2937q0 0.33866 0.077 0.4926 0.0847 0.14624 0.26939 0.14624 0.15394 0 0.2617-0.0308 0.11545-0.0385 0.2463-0.0924l0.10776 0.43873q-0.16933 0.0847-0.37715 0.13085-0.20012 0.0539-0.42333 0.0539-0.38485 0-0.55419-0.24631-0.16163-0.254-0.16163-0.81587v-2.3707h-0.46952z"/>
+   <path d="m-586.54-317.84h0.55418v3.8485h-0.55418zm-0.10006-1.1699q0-0.18473 0.10006-0.30018 0.10776-0.11546 0.27709-0.11546t0.27709 0.11546q0.11545 0.10775 0.11545 0.30018 0 0.18472-0.11545 0.29248-0.10776 0.10006-0.27709 0.10006t-0.27709-0.10776q-0.10006-0.10775-0.10006-0.28478z"/>
+  </g>
+  <path d="m-564.69-317.17a5.3454 5.3454 0 0 1-5.3454 5.3454 5.3454 5.3454 0 0 1-5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454-5.3454 5.3454 5.3454 0 0 1 5.3454 5.3454z" fill="#d4aa00"/>
+  <g>
+   <g dominant-baseline="auto" stroke-width=".19242" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="api">
+    <path d="m-572.63-318.94q0.22321-0.13854 0.53879-0.21551 0.32327-0.077 0.67733-0.077 0.32327 0 0.5157 0.10006 0.20012 0.0924 0.30788 0.26169 0.11545 0.16164 0.14624 0.37716 0.0385 0.20781 0.0385 0.43872 0 0.46182-0.0231 0.90055-0.0154 0.43873-0.0154 0.83127 0 0.29249 0.0154 0.54649 0.0231 0.2463 0.077 0.46951h-0.42333l-0.13085-0.45412h-0.0308q-0.11545 0.20012-0.33866 0.34636-0.22322 0.14625-0.60037 0.14625-0.41563 0-0.68503-0.28479-0.26169-0.29249-0.26169-0.80049 0-0.33097 0.10775-0.55418 0.11546-0.22321 0.31558-0.36176 0.20782-0.13854 0.48491-0.19242 0.28479-0.0616 0.63115-0.0616 0.077 0 0.15394 0 0.077 0 0.16164 8e-3 0.0231-0.23861 0.0231-0.42333 0-0.43873-0.13085-0.61576t-0.47721-0.17703q-0.21552 0-0.46952 0.0693-0.254 0.0616-0.42333 0.16164zm1.6702 1.8627q-0.077-8e-3 -0.15394-8e-3 -0.077-8e-3 -0.15394-8e-3 -0.18472 0-0.36175 0.0308t-0.31558 0.10776q-0.13855 0.077-0.22321 0.20782-0.077 0.13085-0.077 0.33097 0 0.30788 0.14624 0.47721 0.15394 0.16933 0.39255 0.16933 0.32327 0 0.5003-0.15394t0.2463-0.33866z"/>
+    <path d="m-569.51-319.17h0.39254l0.0847 0.41564h0.0308q0.28479-0.508 0.89285-0.508t0.90824 0.45412q0.30788 0.45412 0.30788 1.4855 0 0.48491-0.10006 0.87746-0.10006 0.38485-0.28479 0.66194-0.18473 0.26939-0.45412 0.41563-0.2617 0.13855-0.58497 0.13855-0.22321 0-0.35406-0.0308-0.13085-0.0231-0.28479-0.10776v1.5856h-0.55418zm0.55418 3.2404q0.10776 0.0924 0.23861 0.14624 0.13854 0.0539 0.36175 0.0539 0.40794 0 0.64655-0.41564t0.23861-1.1853q0-0.32328-0.0462-0.58497-0.0385-0.2617-0.13084-0.44643-0.0924-0.19242-0.23861-0.29248-0.13855-0.10776-0.34636-0.10776-0.56188 0-0.72352 0.68503z"/>
+    <path d="m-566.06-319.17h0.55419v3.8485h-0.55419zm-0.10006-1.1699q0-0.18472 0.10006-0.30018 0.10776-0.11545 0.27709-0.11545 0.16934 0 0.2771 0.11545 0.11545 0.10776 0.11545 0.30018 0 0.18473-0.11545 0.29249-0.10776 0.10006-0.2771 0.10006-0.16933 0-0.27709-0.10776-0.10006-0.10776-0.10006-0.28479z"/>
+   </g>
+   <path d="m-589.42-311.83-5.9739 24.054" fill="none" marker-end="url(#Arrow1Send-70)" stroke="#000" stroke-width="1.565"/>
+   <path d="m-569.63-313.36-25.485 37.952" fill="none" marker-end="url(#Arrow1Send-70-8)" stroke="#000" stroke-width="1.565"/>
+  </g>
+ </g>
+</svg>
diff --git a/doc-assets/accept-flow-1.svg b/doc-assets/accept-flow-1.svg
new file mode 100644 (file)
index 0000000..1c70ca1
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="171.61mm" height="209.9mm" version="1.1" viewBox="0 0 171.61372 209.89664" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.068968" y="-.055005" width="1.1379" height="1.11" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="15.814675"/>
+               </filter>
+       </defs>
+       <g transform="translate(503.09 509.47)">
+               <rect transform="matrix(.27404 0 0 .27404 -943.39 -698.21)" x="1644.6" y="726.68" width="550.33" height="690.03" fill-opacity=".93307" filter="url(#a)"/>
+               <rect x="-496.75" y="-503.13" width="150.81" height="189.09" fill="#fff" fill-opacity=".99606"/>
+               <path d="m-478.17-461.61h23.075c3.3599 0.0747 5.5257 3.5038 5.4557 5.6978s-1.697 5.9145-5.3019 6.0545c-5.0352-0.0508-18.547 0.0709-23.582 0.0206-2.2582-0.0882-4.5671-3.0397-4.5533-6.2647 0.014-3.225 2.6009-5.4327 4.9065-5.5082z" fill="#fff" stroke="#000" stroke-width="1.2058"/>
+               <g>
+                       <rect x="-413.06" y="-406.97" width="1.4355" height="26.25"/>
+                       <rect transform="rotate(90)" x="-412.46" y="381.78" width="1.4355" height="72.606"/>
+                       <rect x="-466.58" y="-449.89" width="1.4355" height="100.28"/>
+               </g>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="-465.56778" y="-337.15268" dominant-baseline="auto" font-size="4.8637px" stroke-width=".30398" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-465.56778" y="-337.15268">http</tspan><tspan x="-465.56778" y="-331.07309">processing</tspan></text>
+                       <text x="-467.32571" y="-454.26376" dominant-baseline="auto" font-size="4.8637px" stroke-width=".30398" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-467.32571" y="-454.26376" stroke-width=".30398">Connection</tspan></text>
+                       <text x="-365.37674" y="-415.41953" dominant-baseline="auto" font-size="4.6404px" stroke-width=".10461" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-365.37674" y="-415.41953">Fallback</tspan><tspan x="-365.37674" y="-409.61908">role +</tspan><tspan x="-365.37674" y="-403.81863">protocol</tspan></text>
+               </g>
+               <path d="m-460.22-350.48h-11.894l6.2548 6.2549z" stroke="#000" stroke-width=".072506px"/>
+               <g>
+                       <path d="m-465.22-424.63-21.47 12.396 22.038 12.723 20.338-11.742z" fill="#fff" stroke="#000" stroke-width="1.0961"/>
+                       <text x="-464.97501" y="-413.11075" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.4092px" letter-spacing="0px" stroke-width=".21307" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-464.97501" y="-413.11075">Invalid Method</tspan><tspan x="-464.97501" y="-408.8493">in "http header"</tspan></text>
+                       <path d="m-412.77-423.89-21.47 12.396 22.038 12.723 20.748-12.767z" fill="#fff" stroke="#000" stroke-width="1.0961"/>
+                       <text x="-412.72952" y="-413.60532" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.4092px" letter-spacing="0px" stroke-width=".21307" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-412.72952" y="-413.60532">Fallback</tspan><tspan x="-412.72952" y="-409.34387">set and enabled</tspan><tspan x="-412.72952" y="-405.08243">on vhost?</tspan></text>
+               </g>
+               <path d="m-382.23-417.61v11.894l6.2549-6.2548z" stroke="#000" stroke-width=".072506px"/>
+               <path d="m-423.88-381.11h23.075c3.36 0.0746 5.5257 3.5038 5.4558 5.6978-0.07 2.194-1.697 5.9145-5.3019 6.0545-5.0352-0.0508-18.547 0.0709-23.582 0.0205-2.2583-0.0882-4.5671-3.0397-4.5533-6.2647 0.014-3.225 2.6009-5.4327 4.9065-5.5082z" fill="#fff" stroke="#000" stroke-width="1.2058"/>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="-413.17911" y="-373.47064" dominant-baseline="auto" font-size="4.8637px" stroke-width=".30398" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-413.17911" y="-373.47064" stroke-width=".30398">Error</tspan></text>
+                       <text x="-442.51761" y="-413.80447" dominant-baseline="auto" font-size="3.0936px" stroke-width=".072506" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-442.51761" y="-413.80447" stroke-width=".072506">Yes</tspan></text>
+                       <text x="-389.51834" y="-413.87546" dominant-baseline="auto" font-size="3.0936px" stroke-width=".072506" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-389.51834" y="-413.87546" stroke-width=".072506">Yes</tspan></text>
+               </g>
+               <g>
+                       <text x="-387.27786" y="-423.98447" dominant-baseline="auto" fill="#0000ff" font-family="'Open Sans'" font-size="2.3761px" letter-spacing="0px" stroke-width=".012375" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-387.27786" y="-423.98447">LWS_SERVER_OPTION_FALLBACK_TO_</tspan><tspan x="-387.27786" y="-421.01437">APPLY_LISTEN_ACCEPT_CONFIG</tspan></text>
+                       <text x="-420.85062" y="-484.34042" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="13.921px" letter-spacing="0px" stroke-width=".072506" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-420.85062" y="-484.34042" stroke-width=".072506">plain http port</tspan></text>
+                       <path d="m-435.14-458.75c1.9501-3.293 3.8701-6.6043 5.8395-9.8856 0.2398-0.54978 0.8983-0.73811 1.3075-0.10105 0.3564 0.71254 0.6543 1.2038 0.9661 1.8124 1.5622 2.7051 3.1244 5.4102 4.6866 8.1153 0.2615 0.57459 0.2121 1.1772-0.3114 1.4642-0.6619 0.10273-1.0636 0.0238-1.6073 0.0453-3.3541-0.0106-6.7088 0.0256-10.063 0.0135-0.5994 0.042-1.205-0.57336-0.9833-1.1331 0.055-0.11037 0.1104-0.22075 0.1656-0.3311z" fill="#f00"/>
+                       <path d="m-433.59-459.48c1.4834-2.5049 2.9439-5.0238 4.442-7.5197 0.1824-0.41821 0.6833-0.56147 0.9946-0.0769 0.2711 0.54201 0.4977 0.91574 0.7349 1.3787 1.1884 2.0577 2.3768 4.1154 3.565 6.1731 0.199 0.43709 0.1614 0.89546-0.2369 1.1138-0.5035 0.0781-0.809 0.0181-1.2225 0.0344-2.5515-8e-3 -5.1034 0.0195-7.655 0.0103-0.456 0.0319-0.9167-0.43614-0.7481-0.86193 0.042-0.084 0.084-0.16791 0.126-0.25186z" fill="#fff"/>
+                       <path d="m-429.31-461.54c-0.1016-1.1244-0.2033-2.2489-0.3049-3.3733 0-0.53087 0.5275-0.97852 1.05-0.80555 0.5428 0.0382 0.767 0.61271 0.6514 1.0873-0.192 1.0861-0.287 2.1887-0.5181 3.2682-0.1472 0.45794-0.879 0.39062-0.86-0.12605l-0.019-0.0506z"/>
+                       <circle cx="-428.86" cy="-459.79" r=".69563"/>
+                       <text x="-399.48111" y="-466.21732" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.0161px" letter-spacing="0px" stroke-width=".10282" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-399.48111" y="-466.21732">This flow happens</tspan><tspan x="-399.48111" y="-462.4472">before any Host: headers.</tspan><tspan x="-399.48111" y="-458.67709">Indeed there are no Host:</tspan><tspan x="-399.48111" y="-454.90701">headers if the connection</tspan><tspan x="-399.48111" y="-451.1369">is not actually http.</tspan><tspan x="-399.48111" y="-447.36679">Therefore it occurs on the</tspan><tspan x="-399.48111" y="-443.59668" font-weight="bold">first vhost that listens</tspan><tspan x="-399.48111" y="-439.82657" font-weight="bold">on the connection port.</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/accept-flow-2.svg b/doc-assets/accept-flow-2.svg
new file mode 100644 (file)
index 0000000..1a47cf9
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="182.1mm" height="225.6mm" version="1.1" viewBox="0 0 182.09999 225.60331" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.062619" y="-.049353" width="1.1252" height="1.0987" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="15.408275"/>
+               </filter>
+       </defs>
+       <g transform="translate(807.32 618)">
+               <rect transform="matrix(.27404 0 0 .27404 -1453.5 -811.64)" x="2395" y="743.62" width="590.55" height="749.3" fill-opacity=".99606" filter="url(#a)"/>
+               <rect x="-799.8" y="-610.76" width="161.83" height="205.34" fill="#fff" fill-opacity=".99606"/>
+               <g>
+                       <rect transform="rotate(90)" x="-460.09" y="677.79" width="1.4355" height="24.758"/>
+                       <rect transform="rotate(90)" x="-493.87" y="731.55" width="1.4355" height="21.272"/>
+                       <text x="-739.81171" y="-495.0983" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.0936px" letter-spacing="0px" stroke-width=".072506" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-739.81171" y="-495.0983" stroke-width=".072506">Yes</tspan></text>
+               </g>
+               <path d="m-777.65-577.12h23.075c3.3599 0.0747 5.5257 3.5038 5.4557 5.6978-0.07 2.1941-1.697 5.9145-5.3018 6.0545-5.0352-0.0509-18.547 0.0709-23.582 0.0206-2.2582-0.0882-4.5671-3.0397-4.5533-6.2647 0.014-3.225 2.6009-5.4327 4.9065-5.5082z" fill="#fff" stroke="#000" stroke-width="1.2058"/>
+               <g>
+                       <rect x="-712.54" y="-522.48" width="1.4355" height="26.25"/>
+                       <rect transform="rotate(90)" x="-527.97" y="677.63" width="1.4355" height="76.231"/>
+                       <rect x="-766.07" y="-565.39" width="1.4355" height="100.28"/>
+               </g>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="-765.05292" y="-452.6568" dominant-baseline="auto" font-size="4.8637px" stroke-width=".30398" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-765.05292" y="-452.6568">http</tspan><tspan x="-765.05292" y="-446.57721">processing</tspan></text>
+                       <text x="-766.81085" y="-569.76788" dominant-baseline="auto" font-size="4.8637px" stroke-width=".30398" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-766.81085" y="-569.76788" stroke-width=".30398">Connection</tspan></text>
+                       <text x="-660.09058" y="-531.22888" dominant-baseline="auto" font-size="4.6404px" stroke-width=".072506" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-660.09058" y="-531.22888">Issue http</tspan><tspan x="-660.09058" y="-525.42847">redirect to</tspan><tspan x="-660.09058" y="-519.62799">https://</tspan></text>
+               </g>
+               <path d="m-759.71-465.99h-11.894l6.2548 6.2548z" stroke="#000" stroke-width=".072506px"/>
+               <g>
+                       <path d="m-765.43-540.28-21.47 12.396 22.038 12.723 20.338-11.742z" fill="#fff" stroke="#000" stroke-width="1.0961"/>
+                       <text x="-764.46002" y="-528.61481" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.4092px" letter-spacing="0px" stroke-width=".21307" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-764.46002" y="-528.61481">TLS header</tspan><tspan x="-764.46002" y="-524.35339">looks bad?</tspan></text>
+                       <path d="m-712.26-539.4-21.47 12.396 22.038 12.723 20.748-12.767z" fill="#fff" stroke="#000" stroke-width="1.0961"/>
+                       <text x="-712.21466" y="-529.10938" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.4092px" letter-spacing="0px" stroke-width=".21307" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-712.21466" y="-529.10938">Redirect http</tspan><tspan x="-712.21466" y="-524.84796">to https enabled?</tspan></text>
+               </g>
+               <path d="m-678.95-533.2v11.894l6.2549-6.2548z" stroke="#000" stroke-width=".072506px"/>
+               <g>
+                       <text x="-739.98285" y="-528.75726" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.0936px" letter-spacing="0px" stroke-width=".072506" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-739.98285" y="-528.75726" stroke-width=".072506">Yes</tspan></text>
+                       <text x="-688.21381" y="-529.19232" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.0936px" letter-spacing="0px" stroke-width=".072506" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-688.21381" y="-529.19232" stroke-width=".072506">Yes</tspan></text>
+                       <rect x="-712.26" y="-488.52" width="1.4355" height="26.25"/>
+               </g>
+               <path d="m-711.97-505.44-21.47 12.396 22.038 12.723 20.748-12.767z" fill="#fff" stroke="#000" stroke-width="1.0961"/>
+               <g>
+                       <text x="-711.93158" y="-495.14944" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.4092px" letter-spacing="0px" stroke-width=".21307" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-711.93158" y="-495.14944">Allow http</tspan><tspan x="-711.93158" y="-490.888">on https enabled?</tspan></text>
+                       <rect transform="rotate(45.692)" x="-879.04" y="193.74" width="1.5071" height="18.707"/>
+                       <rect x="-712.38" y="-454.93" width="1.4355" height="26.25"/>
+               </g>
+               <g stroke="#000">
+                       <path d="m-712.09-471.85-21.47 12.396 22.038 12.723 20.748-12.767z" fill="#fff" stroke-width="1.0961"/>
+                       <path d="m-678.88-465.36v11.894l6.2548-6.2548z" stroke-width=".072506px"/>
+                       <path d="m-723.2-429.07h23.075c3.36 0.0746 5.5257 3.5038 5.4558 5.6978-0.07 2.194-1.697 5.9145-5.3019 6.0545-5.0352-0.0508-18.547 0.0709-23.582 0.0205-2.2582-0.0882-4.5671-3.0397-4.5533-6.2647 0.014-3.225 2.6009-5.4327 4.9065-5.5082z" fill="#fff" stroke-width="1.2058"/>
+               </g>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="-712.4975" y="-421.4249" dominant-baseline="auto" font-size="4.8637px" stroke-width=".30398" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-712.4975" y="-421.4249" stroke-width=".30398">Error</tspan></text>
+                       <text x="-688.83673" y="-461.82974" dominant-baseline="auto" font-size="3.0936px" stroke-width=".072506" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-688.83673" y="-461.82974" stroke-width=".072506">Yes</tspan></text>
+                       <text x="-711.2818" y="-462.36154" dominant-baseline="auto" font-size="3.4092px" stroke-width=".21307" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-711.2818" y="-462.36154">Fallback</tspan><tspan x="-711.2818" y="-458.1001">set and enabled</tspan><tspan x="-711.2818" y="-453.83865">on vhost?</tspan></text>
+               </g>
+               <g fill="#0000ff" font-family="'Open Sans'" font-size="2.3761px" letter-spacing="0px" stroke-width=".012375" text-anchor="middle" word-spacing="0px">
+                       <text x="-688.36298" y="-538.93268" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-688.36298" y="-538.93268">LWS_SERVER_OPTION_REDIRECT_</tspan><tspan x="-688.36298" y="-535.96259">HTTP_TO_HTTPS</tspan></text>
+                       <text x="-686.2973" y="-504.24808" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-686.2973" y="-504.24808">LWS_SERVER_OPTION_ALLOW_</tspan><tspan x="-686.2973" y="-501.27798">HTTP_ON_HTTPS_LISTENER</tspan></text>
+                       <text x="-683.63141" y="-471.23087" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-683.63141" y="-471.23087">LWS_SERVER_OPTION_FALLBACK_TO_</tspan><tspan x="-683.63141" y="-468.26077">APPLY_LISTEN_ACCEPT_CONFIG</tspan></text>
+               </g>
+               <g>
+                       <text x="-661.86536" y="-462.39395" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.6404px" letter-spacing="0px" stroke-width=".10461" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-661.86536" y="-462.39395">Fallback</tspan><tspan x="-661.86536" y="-456.59351">role +</tspan><tspan x="-661.86536" y="-450.79306">protocol</tspan></text>
+                       <text x="-721.37823" y="-592.37463" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="13.921px" letter-spacing="0px" stroke-width=".072506" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-721.37823" y="-592.37463" stroke-width=".072506">TLS port</tspan></text>
+                       <path d="m-732.59-572.25c1.9501-3.293 3.8701-6.6043 5.8395-9.8856 0.2398-0.54978 0.8982-0.73811 1.3074-0.10105 0.3564 0.71254 0.6543 1.2038 0.9662 1.8124 1.5622 2.7051 3.1244 5.4102 4.6866 8.1153 0.2615 0.57459 0.21211 1.1772-0.3115 1.4642-0.6618 0.10273-1.0635 0.0238-1.6072 0.0453-3.3542-0.0106-6.7089 0.0256-10.063 0.0135-0.5994 0.042-1.205-0.57336-0.9833-1.1331 0.055-0.11036 0.1104-0.22074 0.1656-0.33109z" fill="#f00"/>
+                       <path d="m-731.04-572.98c1.4834-2.5049 2.9439-5.0238 4.442-7.5197 0.1824-0.4182 0.6833-0.56147 0.99461-0.0769 0.2711 0.54201 0.4977 0.91573 0.73489 1.3787 1.1883 2.0577 2.3767 4.1154 3.565 6.1731 0.1989 0.43707 0.16139 0.89545-0.2369 1.1138-0.50351 0.0781-0.80901 0.0181-1.2226 0.0344-2.5514-8e-3 -5.1034 0.0195-7.6549 0.0103-0.45599 0.0319-0.9167-0.43614-0.74809-0.86194 0.042-0.084 0.084-0.16791 0.126-0.25185z" fill="#fff"/>
+                       <path d="m-726.75-575.05c-0.1016-1.1244-0.2033-2.2489-0.30501-3.3733 0-0.53089 0.5276-0.97852 1.0501-0.80555 0.5428 0.0382 0.767 0.6127 0.65131 1.0873-0.19201 1.0861-0.28691 2.1887-0.51801 3.2682-0.1473 0.45794-0.8791 0.39062-0.86-0.12607l-0.019-0.0506z"/>
+                       <circle cx="-726.3" cy="-573.3" r=".69563"/>
+                       <text x="-696.92792" y="-579.72461" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.0161px" letter-spacing="0px" stroke-width=".10282" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-696.92792" y="-579.72461">This flow happens</tspan><tspan x="-696.92792" y="-575.95453">before any vhost selection</tspan><tspan x="-696.92792" y="-572.18439">using SNI or Host: headers.</tspan><tspan x="-696.92792" y="-568.41431">Therefore it occurs on the</tspan><tspan x="-696.92792" y="-564.64417" font-weight="bold">first vhost that listens</tspan><tspan x="-696.92792" y="-560.87408"><tspan font-weight="bold" stroke-width=".10282">on the connection port</tspan>.</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/accept-flow-3.svg b/doc-assets/accept-flow-3.svg
new file mode 100644 (file)
index 0000000..ea51c3c
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="171.61mm" height="209.9mm" version="1.1" viewBox="0 0 171.61372 209.89664" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.068968" y="-.055005" width="1.1379" height="1.11" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="15.814675"/>
+               </filter>
+       </defs>
+       <g transform="translate(884.06 595.62)">
+               <rect transform="matrix(.27404 0 0 .27404 -1727.7 -789.44)" x="3116.4" y="745.24" width="550.33" height="690.03" fill-opacity=".99606" filter="url(#a)"/>
+               <rect x="-876.94" y="-588.5" width="150.81" height="189.09" fill="#fff" fill-opacity=".99606"/>
+               <path d="m-811.77-547.46h23.075c3.36 0.0747 5.5258 3.5038 5.4558 5.6978s-1.697 5.9145-5.3019 6.0545c-5.0352-0.0508-18.547 0.0709-23.582 0.0206-2.2582-0.0882-4.5671-3.0397-4.5533-6.2647 0.014-3.225 2.6009-5.4327 4.9065-5.5082z" fill="#fff" stroke="#000" stroke-width="1.2058"/>
+               <g>
+                       <rect x="-800.18" y="-535.73" width="1.4355" height="100.28"/>
+                       <text x="-800.92352" y="-540.10565" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.8637px" letter-spacing="0px" stroke-width=".30398" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-800.92352" y="-540.10565" stroke-width=".30398">Connection</tspan></text>
+                       <text x="-800.69403" y="-422.8768" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.7928px" letter-spacing="0px" stroke-width=".11233" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-800.69403" y="-422.8768">Specified</tspan><tspan x="-800.69403" y="-416.88583">role +</tspan><tspan x="-800.69403" y="-410.89487">protocol</tspan></text>
+               </g>
+               <g>
+                       <path d="m-793.82-436.33h-11.894l6.2548 6.2549z" stroke="#000" stroke-width=".072506px"/>
+                       <text x="-776.86151" y="-525.82227" dominant-baseline="auto" fill="#0000ff" font-family="'Open Sans'" font-size="2.3761px" letter-spacing="0px" stroke-width=".012375" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-776.86151" y="-525.82227">LWS_SERVER_OPTION_ADOPT_</tspan><tspan x="-776.86151" y="-522.85217">APPLY_LISTEN_ACCEPT_CONFIG</tspan></text>
+                       <text x="-803.2453" y="-567.64862" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="13.921px" letter-spacing="0px" stroke-width=".072506" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-803.2453" y="-567.64862" stroke-width=".072506">raw-only port</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/http-proxy-overview.svg b/doc-assets/http-proxy-overview.svg
new file mode 100644 (file)
index 0000000..fbfcf0c
--- /dev/null
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="243.74mm" height="145.08mm" version="1.1" viewBox="0 0 243.7415 145.08104" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(.2 0 0 .2 1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#00f" fill-rule="evenodd" stroke="#00f" stroke-width="1pt"/>
+               </marker>
+               <marker id="e" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#00f" fill-rule="evenodd" stroke="#00f" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(.2 0 0 .2 1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#803300" fill-rule="evenodd" stroke="#803300" stroke-width="1pt"/>
+               </marker>
+               <marker id="Arrow2Send" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#803300" fill-rule="evenodd" stroke="#803300" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <filter id="d" x="-.031351" y="-.045733" width="1.0627" height="1.0915" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.40133499"/>
+               </filter>
+               <filter id="c" x="-.040315" y="-.071659" width="1.0806" height="1.1433" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="3.7888347"/>
+               </filter>
+       </defs>
+       <g transform="translate(248.11 -145.08)">
+               <rect x="-239.02" y="154.18" width="225.56" height="126.89" filter="url(#c)"/>
+               <rect x="-240.52" y="152.68" width="225.56" height="126.89" fill="#fff"/>
+               <path d="m-203.49 176.31v89.373h102.61v-45.868h43.977v-43.504z" fill="#f2f2f2" stroke="#000" stroke-dasharray="1.09315409, 0.54657705" stroke-width=".54658"/>
+               <g>
+                       <rect x="-120.46" y="195.81" width="43.421" height="4.0194" fill="#b3b3b3"/>
+                       <rect x="-218.82" y="218.27" width="43.421" height="4.0194" fill="#b3b3b3"/>
+                       <rect x="-196.16" y="186.24" width="43.421" height="68.566" fill="#59f"/>
+                       <circle cx="-222.64" cy="220.19" r="5.6745" fill="#00f"/>
+                       <rect x="-109.82" y="181.86" width="43.421" height="32.392" fill="#3771c8"/>
+                       <circle cx="-121.21" cy="197.82" r="5.6745" fill="#2a7fff"/>
+                       <text x="-174.1732" y="218.8647" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="8.9165px" letter-spacing="0px" stroke-width=".16719" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-174.1732" y="218.8647">LWS</tspan><tspan x="-174.1732" y="230.01038">vhost</tspan></text>
+                       <text x="-223.58031" y="203.6944" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.8726px" letter-spacing="0px" stroke-width=".11011" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-223.58031" y="203.6944">listen</tspan><tspan x="-223.58031" y="211.03516">socket</tspan></text>
+                       <path d="m-131.62 192.98 4.0455 4.7126-3.7957 4.7683-0.1979-2.1869-16.596-0.2467 0.018 2.5528-4.7786-5.2543 5.072-4.7774 0.2007 2.5516 16.047-0.11407z" fill="#0b2822"/>
+                       <text x="-121.06414" y="209.12131" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.8726px" letter-spacing="0px" stroke-width=".11011" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-121.06414" y="209.12131">unix</tspan><tspan x="-121.06414" y="216.46207">socket</tspan></text>
+                       <text x="-87.778496" y="191.65094" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="8.9165px" letter-spacing="0px" stroke-width=".16719" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-87.778496" y="191.65094" style="line-height:1">local</tspan><tspan x="-87.778496" y="200.56747" style="line-height:1">server</tspan><tspan x="-87.778496" y="209.48402" style="line-height:1">process</tspan></text>
+                       <rect x="-79.284" y="243.21" width="43.421" height="4.0194" fill="#b3b3b3"/>
+                       <rect x="-68.645" y="229.26" width="43.421" height="32.392" fill="#a40"/>
+                       <circle cx="-80.035" cy="245.22" r="5.6745" fill="#d40000"/>
+                       <path d="m-132.06 240.62 4.0454 4.7125-3.7957 4.7683-0.1979-2.1869-16.596-0.2467 0.018 2.5528-4.7786-5.2543 5.072-4.7774 0.2007 2.5516 16.047-0.11407z" fill="#0b2822"/>
+                       <text x="-80.596794" y="229.09276" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.8726px" letter-spacing="0px" stroke-width=".11011" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-80.596794" y="229.09276">tcp</tspan><tspan x="-80.596794" y="236.43352">socket</tspan></text>
+                       <text x="-46.365471" y="238.81244" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="8.9165px" letter-spacing="0px" stroke-width=".16719" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-46.365471" y="238.81244" style="line-height:1">remote</tspan><tspan x="-46.365471" y="247.72897" style="line-height:1">server</tspan><tspan x="-46.365471" y="256.64551" style="line-height:1">process</tspan></text>
+                       <circle cx="-122.16" cy="245.41" r="5.6745" fill="#d40000"/>
+                       <text x="-122.71922" y="229.28714" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.8726px" letter-spacing="0px" stroke-width=".11011" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-122.71922" y="229.28714">tcp</tspan><tspan x="-122.71922" y="236.6279">socket</tspan></text>
+               </g>
+               <g transform="matrix(.70144 0 0 .70144 659.12 -5.3651)">
+                       <g transform="matrix(2.2577 0 0 2.2577 -1204.3 38.778)" stroke="#000">
+                               <path transform="matrix(.50721 0 0 .50721 25.042 69.595)" d="m51.659 134.03c-2.0851-0.12503-3.8134 1.7965-4.5661 3.9988-1.5249 1.1166-4.2394-0.53702-5.789 1.0267-2.0771 2.076-0.37705 6.0117 2.7601 4.8135-0.47278 1.5099-0.75873 4.5771 1.522 5.411 2.1312 1.3398 4.9927 3.0089 7.4051 1.2303 0.68399 2.3393 3.6453 2.132 5.8297 2.0845 2.7293-0.0797 2.925-3.1718 3.7481-5.0413 2.2446 0.29343 5.4772 1.759 7.3986-0.52837 2.0288-1.933 1.2298-5.1169-1.3224-6.0736 0.52211-1.5598 3.1656-3.1727 2.0583-5.5736-0.53035-2.6615-4.5547-2.3289-6.5126-1.6479-1.7563 0.39989-4.1576-2.7705-6.6758-2.069-2.0834 0.0156-3.7796 3.3888-5.856 2.3689z" filter="url(#d)" stroke-width=".26458px"/>
+                               <path d="m51.114 137.16c-1.0576-0.0634-1.9342 0.91121-2.316 2.0282-0.77345 0.56634-2.1503-0.27238-2.9362 0.52075-1.0535 1.053-0.19124 3.0492 1.4 2.4414-0.2398 0.76584-0.38483 2.3216 0.77195 2.7445 1.081 0.67957 2.5323 1.5261 3.756 0.62404 0.34693 1.1865 1.8489 1.0814 2.9569 1.0573 1.3843-0.0404 1.4836-1.6088 1.9011-2.557 1.1385 0.14883 2.7781 0.89221 3.7526-0.26799 1.029-0.98042 0.62376-2.5953-0.67075-3.0806 0.26481-0.79115 1.6056-1.6092 1.044-2.827-0.269-1.3499-2.3102-1.1812-3.3033-0.83582-0.89082 0.20282-2.1088-1.4052-3.386-1.0494-1.0567 8e-3 -1.917 1.7188-2.9702 1.2015z" fill="#fff" stroke-width=".1342px"/>
+                       </g>
+                       <path d="m-1100.2 353.84c0.228 1.444 0.092 4.1866 2.0938 4.1394 2.76-0.89355 1.614 2.3483 2.3553 3.8603 0.5369 1.7833 2.3894 2.688 4.0176 3.319 1.5065-0.33473 3.0524-0.44858 4.5433-0.9385 0.5968-0.53669 1.3677-2.4241 1.5086-0.46339-0.1379 1.9656 1.4359 3.7062 3.376 3.8508 2.0459 0.0805 4.1887-1.1964 4.5884-3.3087 0.3964-2.4423 2.9804-2.0561 4.7899-1.833 2.6111 0.46766 4.6695-1.5612 4.2784-3.8402-0.3292-0.62674-0.9799-1.5627-0.5541-2.005 0.6427 0.23344 0.7123 0.29712 1.5696 1.2172 1.3306 1.4687 0.7987 3.5646-0.7422 4.6677-1.9132 1.9453-4.8561 1.3242-7.2417 0.88611-1.5044 0.0134-1.8498 2.9308-3.0559 4.1574-1.4529 1.2982-3.6539 1.2866-5.4776 1.035-2.7618-1.0621-1.9219-1.6855-2.3917-2.6273-1.2717 1.4488-2.9824 1.4776-4.694 0.90067-2.2605-0.51622-4.4419-1.7616-5.4215-3.9148-1.1135-1.2559 0.8242-4.3474-1.6904-3.687-1.7758-0.10283-3.0036-1.5264-3.0656-3.1226-0.033-0.84101 0.3702-1.4645 0.8491-1.9336l0.3779-0.26718z" fill="#afdde9"/>
+                       <g fill="none" stroke="#59f" stroke-width=".55093">
+                               <path d="m-1069.5 356.24c-2.5321-0.9028-5.0309 0.33188-5.4271 2.346-0.5968 4.0032 2.9102 3.7736 3.9992 3.9031 2.2199-0.15795 1.9794-3.2719-0.014-3.6619-0.6858-0.10284-1.8955 0.42074-1.4254 1.2594"/>
+                               <path d="m-1086.8 348.65c-3.3207 1.5727-1.9903 6.9954 1.8119 7.8919 3.0317 0.70004 5.3028-0.94158 5.6392-4.0738-0.1383-6.049-9.7549-2.5459-4.6067 1.2101 1.8751 0.94881 2.5497-2.3454 0.3252-2.0759"/>
+                               <path d="m-1087.7 367.14c2.2642-1.2112 3.035-5.1802 0.073-7.0513-1.869-1.2364-6.7739-0.80354-6.8516 2.1143 0.1157 2.8118 3.5929 3.4314 4.8412 1.9362 1.5744-2.0279-1.2503-3.8211-1.8405-1.3674"/>
+                               <path d="m-1076.4 363.87c0.2792-1.3541-0.4647-2.8259-2.5736-3.0946-1.4728-3e-3 -2.5151 0.95206-2.6332 2.0977-0.1548 1.502 0.9309 2.3738 0.9309 2.3738"/>
+                               <path d="m-1093.8 353.05c-0.3522 1.2065-0.4239 2.9464 1.1236 4.3977"/>
+                               <path d="m-1097.4 359.62c1.8678-0.99722 1.6327-3.6828 0.2496-4.2174-2.9541-0.92357-3.4771 2.373-1.3424 2.4722 0.2963-0.0322 0.5545-0.30762 0.5025-0.61634"/>
+                               <path d="m-1074 348.26c-1.9706 0.77451-2.0477 3.4693-0.7355 4.1602 2.8274 1.259 3.7282-1.955 1.6192-2.3003-0.2978-3e-3 -0.5863 0.24139-0.5703 0.55411"/>
+                       </g>
+                       <path d="m-1101.3 354.58c-0.6475 2.0313 0.7931 4.6728 3.072 4.6389 1.8281-0.49677 1.1889 1.6649 1.119 2.7062 0.8812 3.1557 4.4277 4.7111 7.1449 5.2566 1.4448 0.1446 2.9844-0.48482 3.8006-1.7092 0.5901-0.10509-0.654 1.9656 0.4999 2.2368 1.2611 1.1762 3.4101 1.2493 5.652 0.60491 1.8613-1.405 2.555-2.3786 2.9221-4.5053 0.8977-1.29 2.5237 0.0997 3.7567-0.16084 2.1812 0.4726 4.7838-0.35024 5.8949-2.3697 0.2312-0.5232 0.665-2.9429 0.7386-1.175 0.022 2.5634-2.3736 4.9106-4.9665 4.6651-1.4911-0.0421-2.9084-0.55781-4.3657-0.81633-1.2792 0.43945-1.0322 2.1641-1.5243 3.2096-0.4097 1.0643-1.1567 2.1538-2.383 2.3143-1.4513 0.24363-2.8573 0.20223-4.3121 0.11811-1.3245-0.20318-2.7463-1.0595-3.4018-2.2784-1.7498 1.1785-4.0959 0.77328-5.9008-0.056-1.7345-0.83919-3.5068-1.6552-4.3939-3.6923-0.4388-1.223 0.7147-2.6254-0.046-3.7184-1.2026 0.0666-2.6355-0.0223-3.3212-1.2572-0.7584-1.0649-0.7256-2.5058-0.109-3.626l0.066-0.19171 0.057-0.19426z" fill="#006680" fill-opacity=".67769"/>
+                       <g>
+                               <path d="m-1071 355.62c0.7715-0.67861 1.5563-1.3423 2.3197-2.0302 0.4184-0.76588 0.8876-1.5114 1.2741-2.29 0.1021-0.31082 0.025-1.089 0.2771-1.1108 0.2615 0.46504 0.5434 1.0364 0.2761 1.5623-0.2828 0.66801-0.4644 1.3738-0.7501 2.0408-0.6032 0.71354-1.2189 1.4165-1.811 2.1394-0.5483 0.0223-1.0521-0.23533-1.5861-0.31144z" fill="#5297a9"/>
+                               <path d="m-1071.9 355.62 0.9681-0.78197 1.2661-0.85649 0.8193-0.67025 0.7447-1.1916 0.2608-1.2288-0.037-1.1916-0.4469-1.3033-0.1118-0.0746 0.7447 0.48409 0.3724 0.67029 0.1491 0.67025-0.1863 1.1171-0.5585 1.1171-0.7821 1.3033-1.266 1.0799-1.0427 0.85646z" fill="#afdde9"/>
+                               <path d="m-1090.2 355.92c2.3736 2.7392 6.2505 4.936 9.9141 3.5655 2.3013-0.91408 4.6853-3.1508 4.1134-5.859 0.032-1.1478-2.1104-3.2484-1.2883-1.03 0.5607 1.6037 0.068 3.2664-1.33 4.2378-1.1913 1.0196-2.8465 1.5004-4.4021 1.4868-2.4227-0.47191-4.7493-1.41-7.0071-2.4011z" fill="#87cdde"/>
+                       </g>
+               </g>
+               <g>
+                       <path d="m-76.411 259.82h-89.165l-22.488-18.786v-15.08l-9.1008-5.2544" fill="none" marker-end="url(#Arrow2Send)" marker-start="url(#b)" stroke="#803300" stroke-width="1.365"/>
+                       <text x="-210.98216" y="226.74814" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="2.9673px" letter-spacing="0px" stroke-width=".055637" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-210.98216" y="226.74814" stroke-width=".055637">h1/h2</tspan></text>
+                       <text x="-136.25352" y="257.5087" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="2.9673px" letter-spacing="0px" stroke-width=".055637" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-136.25352" y="257.5087" stroke-width=".055637">h1 proxy</tspan></text>
+                       <path d="m-116.93 181.6-52.123 1e-5 -18.784 16.141v15.08l-9.1008 5.2544" fill="none" marker-end="url(#e)" marker-start="url(#a)" stroke="#00f" stroke-width="1.365"/>
+               </g>
+               <g font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="-138.71373" y="185.71481" dominant-baseline="auto" fill="#000000" font-size="2.9673px" stroke-width=".055637" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-138.71373" y="185.71481" stroke-width=".055637">h1 proxy</tspan></text>
+                       <text x="-170.83958" y="194.2597" dominant-baseline="auto" fill="#ffffff" font-size="3.9794px" stroke-width=".074614" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-170.83958" y="194.2597" style="line-height:1">chosen</tspan><tspan x="-170.83958" y="198.23911" style="line-height:1">by URL</tspan></text>
+                       <text x="-170.03917" y="244.313" dominant-baseline="auto" fill="#ffffff" font-size="3.9794px" stroke-width=".074614" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-170.03917" y="244.313" style="line-height:1">chosen</tspan><tspan x="-170.03917" y="248.2924" style="line-height:1">by URL</tspan></text>
+                       <text x="-167.20567" y="166.98483" dominant-baseline="auto" fill="#000000" font-size="10.672px" stroke-width=".20009" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-167.20567" y="166.98483" stroke-width=".20009">lws http proxying overview</tspan></text>
+                       <text x="-177.77489" y="271.70822" dominant-baseline="auto" fill="#000000" font-size="5.3799px" stroke-width=".10087" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-177.77489" y="271.70822" stroke-width=".10087">Same physical server</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/lws-crypto-overview.svg b/doc-assets/lws-crypto-overview.svg
new file mode 100644 (file)
index 0000000..559c8c4
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="200.21mm" height="105.23mm" version="1.1" viewBox="0 0 200.21263 105.22723" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.036673" y="-.074724" width="1.0733" height="1.1494" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="2.8502899"/>
+               </filter>
+       </defs>
+       <g transform="translate(651.2 344.5)">
+               <g fill-opacity=".99606">
+                       <rect x="-644.35" y="-337.66" width="186.53" height="91.546" filter="url(#a)"/>
+                       <rect x="-645.81" y="-339.25" width="186.53" height="91.546" fill="#fff"/>
+                       <rect x="-633.9" y="-327.61" width="105.3" height="41.275" fill="#f6ffd5" stroke="#e9ddaf" stroke-dasharray="2.33000003, 2.33000003" stroke-linejoin="round" stroke-width="1.165"/>
+               </g>
+               <g fill-opacity=".99606">
+                       <rect x="-583.53" y="-312" width="5.4256" height="9.5415"/>
+                       <rect x="-554.9" y="-292.8" width="5.4256" height="13.996"/>
+                       <rect x="-610.37" y="-292.94" width="5.4256" height="13.996"/>
+               </g>
+               <g fill-opacity=".99606">
+                       <rect x="-632.82" y="-279.84" width="48.643" height="24.322" fill="#8a0"/>
+                       <rect x="-576.51" y="-280.03" width="48.643" height="24.322" fill="#a80"/>
+                       <rect x="-628.51" y="-303.48" width="94.328" height="12.348" fill="#008000"/>
+                       <rect x="-628.26" y="-323.5" width="94.063" height="12.348" fill="#00aad4"/>
+               </g>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="-608.50153" y="-268.05179" dominant-baseline="auto" font-size="7.7611px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-608.50153" y="-268.05179" stroke-width=".26458">OpenSSL</tspan></text>
+                       <text x="-552.70789" y="-264.67337" dominant-baseline="auto" font-size="7.7611px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-552.70789" y="-264.67337" stroke-width=".26458">mbedTLS</tspan></text>
+                       <text x="-608.09796" y="-261.13004" dominant-baseline="auto" font-size="4.8053px" stroke-width=".16382" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-608.09796" y="-261.13004" stroke-width=".16382">and derivitives</tspan></text>
+               </g>
+               <text x="-580.06641" y="-296.07693" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="5.936px" letter-spacing="0px" stroke-width=".20236" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-580.06641" y="-296.07693" fill="#ffffff" stroke-width=".20236">genhash, genrsa, genaes, genec</tspan></text>
+               <text x="-581.93494" y="-314.70865" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="7.7611px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-581.93494" y="-314.70865" fill="#ffffff" stroke-width=".26458">JOSE, JWS, JWK, JWE</tspan></text>
+               <g fill="#000000" font-family="'Open Sans'" font-size="4.3141px" letter-spacing="0px" stroke-width=".14707" text-anchor="middle" word-spacing="0px">
+                       <text x="-493.83551" y="-274.2438" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-493.83551" y="-274.2438">TLS-library-specific</tspan><tspan x="-493.83551" y="-268.8512">and cipher-specific</tspan><tspan x="-493.83551" y="-263.45856">keys using EVP</tspan><tspan x="-493.83551" y="-258.06595">or bignum</tspan></text>
+                       <text x="-493.41776" y="-301.00821" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-493.41776" y="-301.00821">TLS library-independent</tspan><tspan x="-493.41776" y="-295.6156">metadata +</tspan><tspan x="-493.41776" y="-290.22296">binary key elements</tspan></text>
+                       <text x="-493.22754" y="-319.46451" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-493.22754" y="-319.46451">JWK JSON key</tspan><tspan x="-493.22754" y="-314.0719">creation and parsing</tspan></text>
+               </g>
+               <g>
+                       <path d="m-492.48-306.99-1.2324 1.0579-1.247-0.9926 0.57189-0.052 0.0645-4.34h-0.66758l1.374-1.2497 1.2493 1.3264-0.66727 0.053 0.0298 4.1963z" fill="#0b2822"/>
+                       <path d="m-492.34-280.9-1.2324 1.0579-1.247-0.9926 0.57189-0.052 0.0645-4.34h-0.66758l1.374-1.2497 1.2493 1.3264-0.66727 0.053 0.0298 4.1963z" fill="#0b2822"/>
+                       <text x="-618.2807" y="-330.22464" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.0105px" letter-spacing="0px" stroke-width=".17081" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-618.2807" y="-330.22464" stroke-width=".17081">libwebsockets</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/lws-fts.svg b/doc-assets/lws-fts.svg
new file mode 100644 (file)
index 0000000..081adc7
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="187.45mm" height="114.8mm" version="1.1" viewBox="0 0 187.4528 114.80323" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="c" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="d" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="f" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="e" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="g" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <filter id="h" x="-.018221" y="-.030454" width="1.0364" height="1.0609" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="1.0113078"/>
+               </filter>
+       </defs>
+       <g transform="translate(223.75 183.73)">
+               <g>
+                       <rect transform="matrix(1.3577 0 0 1.3577 157.53 159.82)" x="-278.39" y="-250.61" width="133.21" height="79.7" filter="url(#h)"/>
+                       <rect x="-221.47" y="-181.71" width="180.86" height="108.21" fill="#fff"/>
+                       <rect x="-136.12" y="-131.41" width="66.299" height="13.971" fill="#c8c4b7"/>
+                       <circle transform="rotate(-90)" cx="99.407" cy="-188.19" r="13.209" fill="#5f8dd3" opacity=".742"/>
+                       <text x="-188.80128" y="-102.71894" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.1903px" letter-spacing="0px" stroke-width=".27805" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-188.80128" y="-102.71894">Original</tspan><tspan x="-188.80128" y="-96.231033">Text</tspan><tspan x="-188.80128" y="-89.743126">file</tspan></text>
+                       <circle transform="rotate(-90)" cx="128.11" cy="-199.63" r="13.209" fill="#5f8dd3" opacity=".742"/>
+                       <text x="-199.72415" y="-131.67715" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.1903px" letter-spacing="0px" stroke-width=".27805" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-199.72415" y="-131.67715">Original</tspan><tspan x="-199.72415" y="-125.18925">Text</tspan><tspan x="-199.72415" y="-118.70134">file</tspan></text>
+                       <circle transform="rotate(-90)" cx="156.82" cy="-184.64" r="13.209" fill="#5f8dd3" opacity=".742"/>
+                       <text x="-184.48293" y="-161.14342" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.1903px" letter-spacing="0px" stroke-width=".27805" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-184.48293" y="-161.14342">Original</tspan><tspan x="-184.48293" y="-154.65552">Text</tspan><tspan x="-184.48293" y="-148.1676">file</tspan></text>
+                       <circle transform="rotate(-90)" cx="126.08" cy="-154.16" r="23.116" fill="#217867"/>
+                       <text x="-154.524" y="-127.70548" dominant-baseline="auto" fill="#d7f4ee" font-family="'Open Sans'" font-size="13.246px" letter-spacing="0px" stroke-width=".70963" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-154.524" y="-127.70548">Index</tspan><tspan x="-154.524" y="-111.14747">File</tspan></text>
+               </g>
+               <g fill="none" stroke="#000" stroke-width="1.1745">
+                       <path d="m-177.53-107.03c6.6045-7.3666 6.6045-7.3666 6.6045-7.3666" marker-end="url(#g)"/>
+                       <path d="m-175.59-147.47 6.8585 7.3666" marker-end="url(#e)"/>
+                       <path d="m-186.9-127.65 11.939 0.63506" marker-end="url(#f)"/>
+               </g>
+               <g>
+                       <rect x="-113.26" y="-146.65" width="27.434" height="41.151" fill="#fca"/>
+                       <text x="-99.287437" y="-128.11143" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-99.287437" y="-128.11143">Search</tspan><tspan x="-99.287437" y="-119.72922">Action</tspan></text>
+                       <text x="-83.03019" y="-161.13399" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-83.03019" y="-161.13399" stroke-width=".35924">Keyword</tspan></text>
+                       <rect x="-79.982" y="-146.91" width="27.434" height="41.151" fill="#fca"/>
+                       <text x="-65.248817" y="-131.4137" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-65.248817" y="-131.4137">Auto-</tspan><tspan x="-65.248817" y="-123.03148">comp-</tspan><tspan x="-65.248817" y="-114.64926">lete</tspan></text>
+                       <text x="-82.307457" y="-85.980392" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-82.307457" y="-85.980392" stroke-width=".35924">Result lwsac</tspan></text>
+               </g>
+               <g fill="none" stroke="#000" stroke-width="1.1745">
+                       <path d="m-73.75-156.75 6.8585 7.3666" marker-end="url(#d)"/>
+                       <path d="m-89.77-156.75-6.8585 7.3666" marker-end="url(#a)"/>
+                       <path d="m-98.136-103.91 6.8585 7.3666" marker-end="url(#c)"/>
+                       <path d="m-65.384-104.42-6.8585 7.3666" marker-end="url(#b)"/>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/lws-overview.png b/doc-assets/lws-overview.png
new file mode 100644 (file)
index 0000000..64195d9
Binary files /dev/null and b/doc-assets/lws-overview.png differ
diff --git a/doc-assets/lws-relpol-1.svg b/doc-assets/lws-relpol-1.svg
new file mode 100644 (file)
index 0000000..6407390
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="117.55mm" height="43.682mm" version="1.1" viewBox="0 0 117.55421 43.682442" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <filter id="b" x="-.044975" y="-.14275" width="1.09" height="1.2855" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="24.569981"/>
+               </filter>
+       </defs>
+       <g transform="translate(534.27 246.45)">
+               <rect transform="matrix(.08226 0 0 .08226 -259.43 86.518)" x="-3282.1" y="-3988.8" width="1311.1" height="413.09" filter="url(#b)"/>
+               <rect x="-530.41" y="-243.08" width="107.85" height="33.981" fill="#fff"/>
+               <path d="m-518.44-222.12h87.407" fill="none" marker-end="url(#a)" stroke="#000" stroke-width=".8444"/>
+               <text x="-474.75528" y="-216.97371" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.1788px" letter-spacing="0px" stroke-width=".021765" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-474.75528" y="-216.97371" stroke-width=".021765">All new work is done only on master</tspan></text>
+               <rect x="-493.93" y="-234.24" width="37.485" height="5.7258" fill-opacity=".91732"/>
+               <text x="-476.569" y="-229.52711" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="6.5983px" letter-spacing="0px" stroke-width=".034366" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-476.569" y="-229.52711" fill="#ffffff" stroke-width=".034366">master</tspan></text>
+               <g fill-opacity=".91732">
+                       <circle cx="-450.55" cy="-231.43" r="2.8446"/>
+                       <circle cx="-443.98" cy="-231.4" r="2.8446" opacity=".742"/>
+                       <circle cx="-437.52" cy="-231.26" r="2.8446" opacity=".497"/>
+                       <circle cx="-513.47" cy="-231.74" r="2.8446" opacity=".522"/>
+                       <circle cx="-506.9" cy="-231.7" r="2.8446" opacity=".73"/>
+                       <circle cx="-500.44" cy="-231.56" r="2.8446"/>
+                       <circle cx="-493.87" cy="-231.43" r="2.8446"/>
+                       <circle cx="-457.11" cy="-231.42" r="2.8446"/>
+               </g>
+               <rect x="-527.7" y="-242.83" width="107.85" height="33.981" fill="none"/>
+       </g>
+</svg>
diff --git a/doc-assets/lws-relpol-2.svg b/doc-assets/lws-relpol-2.svg
new file mode 100644 (file)
index 0000000..397aabb
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="135.68mm" height="69.688mm" version="1.1" viewBox="0 0 135.68018 69.688057" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+       <defs>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <linearGradient id="b" x1="1000.2" x2="993.99" y1="928.57" y2="992.97" gradientTransform="matrix(-.19888 .028535 .028535 .19888 110.19 -89.467)" gradientUnits="userSpaceOnUse">
+                       <stop stop-color="#e9ddaf" offset="0"/>
+                       <stop stop-color="#504416" offset="1"/>
+               </linearGradient>
+               <filter id="d" x="-.060394" y="-.055053" width="1.1208" height="1.1101" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.44336333"/>
+               </filter>
+               <filter id="c" x="-.038095" y="-.079938" width="1.0762" height="1.1599" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="24.327513"/>
+               </filter>
+       </defs>
+       <g transform="translate(132.66 -92.741)">
+               <rect transform="matrix(.08226 0 0 .08226 160.24 368.6)" x="-3502.3" y="-3295.1" width="1532.6" height="730.39" filter="url(#c)"/>
+               <rect x="-129.09" y="96.313" width="126.07" height="60.082" fill="#fff"/>
+               <rect x="-96.346" y="137.61" width="62.355" height="5.7258" fill-opacity=".91732"/>
+               <path d="m-57.724 138.73 39.644-31.888 4.5554 5.4173-38.66 31.888z" fill="#00f" stroke="#000" stroke-width=".021765px"/>
+               <g transform="matrix(.08226 0 0 .08226 209.08 204.79)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <circle cx="-59.501" cy="132.08" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <path transform="matrix(-.75166 .10785 .10785 .75166 -2080.3 632.8)" d="m-2725.9-287.4c-0.4737 0.0104-0.7327 0.29435-0.9193 0.61443-2.45 4.2318-6.125 10.579-7.35 12.695-0.6344 1.3127 0.5828 2.8675 1.0263 3.5972 0.5853 0.7769 0.802 1.1542 1.8469 1.882 1.1717 0.46167 2.3507 0.56335 2.8066 0.53435s2.7265 0.26464 4.5434-1.3364c1.1317-2.0971 4.6213-8.6717 6.8848-12.866 0.2058-0.37197 0.6954-0.86583-0.1364-1.3663-2.8286-1.2472-5.6572-2.4941-8.4858-3.7414-0.077-0.0104-0.1488-0.0149-0.2165-0.0134zm-4.2607 15.885a1.1359 1.0667 0 0 1 1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1359 -1.0666 1.1359 1.0667 0 0 1 1.1359 -1.0666z" filter="url(#d)" stroke="#000" stroke-width=".26458px"/>
+               <path d="m-62.36 122.52c0.3571-0.0433 0.5825 0.14227 0.7573 0.36273 2.2979 2.9166 5.7448 7.2912 6.8937 8.7496 0.6185 0.91825-0.1287 2.2186-0.3834 2.8149-0.356 0.64707-0.4783 0.95366-1.1853 1.6135-0.8309 0.47338-1.7057 0.67692-2.0515 0.70427-0.3459 0.0273-2.0213 0.49302-3.5596-0.51443-1.0769-1.4542-4.409-6.0198-6.5628-8.9283-0.1947-0.25737-0.616-0.57542-0.045-1.0413 1.9917-1.2426 3.9833-2.4853 5.9749-3.7279 0.057-0.0161 0.1104-0.0267 0.1614-0.0332zm4.9157 11.48a0.81001 0.86256 81.835 0 0 -0.73879 0.92424 0.81001 0.86256 81.835 0 0 0.96889 0.67917 0.81001 0.86256 81.835 0 0 0.7387 -0.92418 0.81001 0.86256 81.835 0 0 -0.9688 -0.67923z" fill="url(#b)" stroke="#000" stroke-width=".20091px"/>
+               <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width=".20123">
+                       <path d="m-63.003 123.99 6.3983 8.4695"/>
+                       <path d="m-66.817 126.33 6.4415 8.7713"/>
+                       <path d="m-64.931 125.21 6.4288 8.5557"/>
+               </g>
+               <text x="-78.984398" y="142.32184" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="6.5983px" letter-spacing="0px" stroke-width=".034366" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-78.984398" y="142.32184" fill="#ffffff" stroke-width=".034366">master</tspan></text>
+               <g fill-opacity=".91732">
+                       <circle cx="-27.354" cy="140.42" r="2.8446"/>
+                       <circle cx="-20.787" cy="140.45" r="2.8446" opacity=".742"/>
+                       <circle cx="-14.325" cy="140.59" r="2.8446" opacity=".497"/>
+                       <circle cx="-115.88" cy="140.11" r="2.8446" opacity=".522"/>
+                       <circle cx="-109.32" cy="140.15" r="2.8446" opacity=".73"/>
+                       <circle cx="-102.86" cy="140.29" r="2.8446"/>
+                       <circle cx="-96.288" cy="140.42" r="2.8446"/>
+                       <circle cx="-33.921" cy="140.43" r="2.8446"/>
+               </g>
+               <g letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text transform="rotate(30.874)" x="13.613972" y="144.56674" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" stroke-width=".027864" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="13.613972" y="144.56674" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.0.0</tspan></text>
+                       <text transform="rotate(-39.355)" x="-104.55833" y="68.824959" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.5255px" stroke-width=".028779" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-104.55833" y="68.824959" fill="#000000" stroke-width=".028779">v3.0-stable</tspan></text>
+                       <text x="-86.001366" y="108.6248" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.1788px" stroke-width=".021765" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-86.001366" y="108.6248">When a release happens, master</tspan><tspan x="-86.001366" y="113.84833">is copied into a release-specific</tspan><tspan x="-86.001366" y="119.07185">-stable branch and tagged</tspan></text>
+               </g>
+               <path d="m-111.25 152.21h87.407" fill="none" marker-end="url(#a)" stroke="#000" stroke-width=".8444"/>
+       </g>
+</svg>
diff --git a/doc-assets/lws-relpol-3.svg b/doc-assets/lws-relpol-3.svg
new file mode 100644 (file)
index 0000000..a3ae305
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="164.43mm" height="92.039mm" version="1.1" viewBox="0 0 164.43303 92.038696" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+       <defs>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <linearGradient id="c" x1="1000.2" x2="993.99" y1="928.57" y2="992.97" gradientTransform="matrix(-.19888 .028535 .028535 .19888 73.514 -107.37)" gradientUnits="userSpaceOnUse">
+                       <stop stop-color="#e9ddaf" offset="0"/>
+                       <stop stop-color="#504416" offset="1"/>
+               </linearGradient>
+               <filter id="e" x="-.060394" y="-.055053" width="1.1208" height="1.1101" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.44336333"/>
+               </filter>
+               <filter id="d" x="-.042027" y="-.0804" width="1.0841" height="1.1608" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="32.290007"/>
+               </filter>
+       </defs>
+       <g transform="translate(167.26 -73.321)">
+               <rect transform="matrix(.08226 0 0 .08226 153.32 266.78)" x="-3819.6" y="-2274.3" width="1843.9" height="963.88" filter="url(#d)"/>
+               <rect x="-162.36" y="77.972" width="151.68" height="79.289" fill="#fff"/>
+               <rect x="-133.03" y="119.7" width="90.911" height="5.7258" fill-opacity=".91732"/>
+               <path d="m-94.403 120.82 39.644-31.888 4.5555 5.4173-38.66 31.888z" fill="#00f" stroke="#000" stroke-width=".021765px"/>
+               <g transform="matrix(.08226 0 0 .08226 172.4 186.89)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <circle cx="-96.18" cy="114.18" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <path transform="matrix(-.75166 .10785 .10785 .75166 -2117 614.9)" d="m-2725.9-287.4c-0.4737 0.0104-0.7327 0.29435-0.9193 0.61443-2.45 4.2318-6.125 10.579-7.35 12.695-0.6344 1.3127 0.5828 2.8675 1.0263 3.5972 0.5853 0.7769 0.802 1.1542 1.8469 1.882 1.1717 0.46167 2.3507 0.56335 2.8066 0.53435s2.7265 0.26464 4.5434-1.3364c1.1317-2.0971 4.6213-8.6717 6.8848-12.866 0.2058-0.37197 0.6954-0.86583-0.1364-1.3663-2.8286-1.2472-5.6572-2.4941-8.4858-3.7414-0.077-0.0104-0.1488-0.0149-0.2165-0.0134zm-4.2607 15.885a1.1359 1.0667 0 0 1 1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1359 -1.0666 1.1359 1.0667 0 0 1 1.1359 -1.0666z" filter="url(#e)" stroke="#000" stroke-width=".26458px"/>
+               <path d="m-99.039 104.62c0.3572-0.0433 0.5825 0.14222 0.7573 0.36268 2.2979 2.9166 5.7448 7.2913 6.8938 8.7496 0.6184 0.91831-0.1288 2.2186-0.3835 2.815-0.356 0.64707-0.4783 0.95367-1.1853 1.6135-0.8309 0.47333-1.7057 0.67687-2.0515 0.70422-0.3458 0.0273-2.0213 0.49307-3.5596-0.51442-1.077-1.4542-4.409-6.0198-6.5627-8.9282-0.1948-0.25742-0.61611-0.57547-0.045-1.0414 1.9917-1.2426 3.9833-2.4853 5.9749-3.7278 0.057-0.0161 0.1104-0.0268 0.1614-0.0332zm4.9158 11.48a0.81001 0.86256 81.835 0 0 -0.7388 0.92424 0.81001 0.86256 81.835 0 0 0.9688 0.67923 0.81001 0.86256 81.835 0 0 0.7387 -0.92424 0.81001 0.86256 81.835 0 0 -0.9687 -0.67923z" fill="url(#c)" stroke="#000" stroke-width=".20091px"/>
+               <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width=".20123">
+                       <path d="m-99.682 106.08 6.3983 8.4695"/>
+                       <path d="m-103.5 108.42 6.4415 8.7713"/>
+                       <path d="m-101.61 107.3 6.4288 8.5557"/>
+               </g>
+               <text x="-115.66345" y="124.41663" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="6.5983px" letter-spacing="0px" stroke-width=".034366" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-115.66345" y="124.41663" fill="#ffffff" stroke-width=".034366">master</tspan></text>
+               <g fill-opacity=".91732">
+                       <circle cx="-35.652" cy="122.51" r="2.8446"/>
+                       <circle cx="-29.085" cy="122.55" r="2.8446" opacity=".742"/>
+                       <circle cx="-22.623" cy="122.69" r="2.8446" opacity=".497"/>
+                       <circle cx="-152.56" cy="122.21" r="2.8446" opacity=".522"/>
+                       <circle cx="-146" cy="122.24" r="2.8446" opacity=".73"/>
+                       <circle cx="-139.53" cy="122.38" r="2.8446"/>
+                       <circle cx="-132.97" cy="122.52" r="2.8446"/>
+                       <circle cx="-42.219" cy="122.52" r="2.8446"/>
+               </g>
+               <g>
+                       <text transform="rotate(30.874)" x="-27.055706" y="148.02026" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" letter-spacing="0px" stroke-width=".027864" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-27.055706" y="148.02026" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.0.0</tspan></text>
+                       <text transform="rotate(-39.355)" x="-121.56568" y="31.720842" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.5255px" letter-spacing="0px" stroke-width=".028779" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-121.56568" y="31.720842" fill="#000000" stroke-width=".028779">v3.0-stable</tspan></text>
+                       <path d="m-147.93 134.3h118.75" fill="none" marker-end="url(#a)" stroke="#000" stroke-width=".8444"/>
+                       <text x="-91.00293" y="139.95488" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.1788px" letter-spacing="0px" stroke-width=".021765" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-91.00293" y="139.95488">Work continues on master, and if a fix is made</tspan><tspan x="-91.00293" y="145.17841">that is also relevant to the last release,</tspan><tspan x="-91.00293" y="150.40193">it is also backported on to the -stable branch</tspan></text>
+                       <path d="m-64.193 120.82c6.0493-5.3437 1.0792-10.607-3.5694-14.321" fill="none" marker-end="url(#b)" stroke="#37abc8" stroke-width=".8226"/>
+               </g>
+               <g>
+                       <path d="m-47.649 97.997c1.4407-2.4326 2.859-4.8788 4.3139-7.3028 0.17716-0.40617 0.6636-0.54529 0.96587-0.0746 0.26326 0.52636 0.48333 0.8893 0.71368 1.3389 1.1541 1.9984 2.3082 3.9968 3.4622 5.9951 0.19316 0.42448 0.15667 0.86961-0.23008 1.0817-0.48896 0.0759-0.78571 0.0177-1.1873 0.0337-2.4778-8e-3 -4.9561 0.0193-7.4341 0.01-0.44278 0.031-0.89016-0.42352-0.72643-0.83708 0.0407-0.0815 0.0816-0.16303 0.12232-0.24453z" fill="#f00"/>
+                       <path d="m-46.502 97.457c1.0958-1.8504 2.1748-3.7112 3.2815-5.5551 0.13473-0.30895 0.50479-0.41474 0.73477-0.0565 0.20022 0.4004 0.36765 0.6765 0.54289 1.0185 0.8779 1.5201 1.7557 3.0402 2.6336 4.5604 0.14699 0.32287 0.11916 0.66146-0.17502 0.82279-0.37193 0.0578-0.59763 0.0128-0.90321 0.0257-1.8848-6e-3 -3.77 0.0144-5.6549 8e-3 -0.33683 0.0235-0.67719-0.32217-0.55263-0.63674 0.031-0.0618 0.0618-0.12403 0.0931-0.18599z" fill="#fff"/>
+                       <path d="m-43.337 95.934c-0.0751-0.8306-0.1502-1.6613-0.22522-2.492 0-0.39216 0.3897-0.72284 0.77565-0.59506 0.40104 0.0284 0.56665 0.45257 0.4812 0.8032-0.14185 0.80235-0.21195 1.6168-0.38274 2.4144-0.10873 0.33827-0.64932 0.28856-0.6353-0.0932l-0.01389-0.0375z"/>
+                       <circle cx="-43.007" cy="97.222" r=".51388"/>
+                       <text x="-28.618044" y="94.997299" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="3.259px" letter-spacing="0px" stroke-width=".016974" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-28.618044" y="94.997299">New features</tspan><tspan x="-28.618044" y="99.071075">and API</tspan><tspan x="-28.618044" y="103.14484">changes are</tspan><tspan x="-28.618044" y="107.21862">not allowed for</tspan><tspan x="-28.618044" y="111.29239">backport</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/lws-relpol-4.svg b/doc-assets/lws-relpol-4.svg
new file mode 100644 (file)
index 0000000..a50150e
--- /dev/null
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="164.9mm" height="100.87mm" version="1.1" viewBox="0 0 164.89519 100.87298" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+       <defs>
+               <linearGradient id="e" x1="1000.2" x2="993.99" y1="928.57" y2="992.97" gradientTransform="matrix(-2.4177 .34689 .34689 2.4177 -1517.1 -4018.7)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
+               <linearGradient id="a">
+                       <stop stop-color="#e9ddaf" offset="0"/>
+                       <stop stop-color="#504416" offset="1"/>
+               </linearGradient>
+               <marker id="c" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="d" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <linearGradient id="f" x1="1000.2" x2="993.99" y1="928.57" y2="992.97" gradientTransform="matrix(-.19888 .028535 .028535 .19888 114.31 -54.733)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
+               <filter id="h" x="-.060394" y="-.055053" width="1.1208" height="1.1101" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.44336333"/>
+               </filter>
+               <filter id="g" x="-.043551" y="-.075357" width="1.0871" height="1.1507" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="33.460433"/>
+               </filter>
+       </defs>
+       <g transform="translate(128.75 -106.61)">
+               <rect transform="matrix(.08226 0 0 .08226 192.06 201.56)" x="-3819.6" y="-1074" width="1843.9" height="1065.7" filter="url(#g)"/>
+               <rect x="-123.62" y="111.49" width="151.68" height="87.661" fill="#fff"/>
+               <path d="m-20.36 149.04 26.893-21.631 3.0902 3.6749-26.225 21.631z" fill="#000080" stroke="#000" stroke-width=".014764px"/>
+               <rect x="-92.228" y="172.34" width="90.911" height="5.7258" fill-opacity=".91732"/>
+               <path d="m-53.606 173.46 39.644-31.888 4.5555 5.4172-38.66 31.888z" fill="#00f" stroke="#000" stroke-width=".021765px"/>
+               <g transform="matrix(.08226 0 0 .08226 213.19 239.53)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <circle cx="-55.383" cy="166.82" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <path transform="matrix(-.75166 .10785 .10785 .75166 -2076.2 667.54)" d="m-2725.9-287.4c-0.4737 0.0104-0.7327 0.29435-0.9193 0.61443-2.45 4.2318-6.125 10.579-7.35 12.695-0.6344 1.3127 0.5828 2.8675 1.0263 3.5972 0.5853 0.7769 0.802 1.1542 1.8469 1.882 1.1717 0.46167 2.3507 0.56335 2.8066 0.53435s2.7265 0.26464 4.5434-1.3364c1.1317-2.0971 4.6213-8.6717 6.8848-12.866 0.2058-0.37197 0.6954-0.86583-0.1364-1.3663-2.8286-1.2472-5.6572-2.4941-8.4858-3.7414-0.077-0.0104-0.1488-0.0149-0.2165-0.0134zm-4.2607 15.885a1.1359 1.0667 0 0 1 1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1359 -1.0666 1.1359 1.0667 0 0 1 1.1359 -1.0666z" filter="url(#h)" stroke="#000" stroke-width=".26458px"/>
+               <path d="m-58.242 157.26c0.3572-0.0434 0.5825 0.14222 0.7573 0.36267 2.298 2.9166 5.7448 7.2913 6.8938 8.7496 0.6184 0.9183-0.1288 2.2187-0.3834 2.815-0.3561 0.64707-0.4784 0.95367-1.1854 1.6135-0.8309 0.47338-1.7057 0.67687-2.0515 0.70426-0.3458 0.0273-2.0213 0.49302-3.5596-0.51442-1.0769-1.4542-4.409-6.0198-6.5627-8.9283-0.1948-0.25743-0.616-0.57542-0.045-1.0414 1.9918-1.2426 3.9833-2.4852 5.9749-3.7278 0.057-0.0161 0.1104-0.0268 0.1615-0.0332zm4.9158 11.48a0.81001 0.86256 81.835 0 0 -0.7389 0.92424 0.81001 0.86256 81.835 0 0 0.9689 0.67923 0.81001 0.86256 81.835 0 0 0.7387 -0.92424 0.81001 0.86256 81.835 0 0 -0.9687 -0.67923z" fill="url(#f)" stroke="#000" stroke-width=".20091px"/>
+               <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width=".20123">
+                       <path d="m-58.885 158.72 6.3982 8.4694"/>
+                       <path d="m-62.699 161.06 6.4416 8.7713"/>
+                       <path d="m-60.813 159.94 6.4288 8.5557"/>
+               </g>
+               <text x="-74.86644" y="177.05612" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="6.5983px" letter-spacing="0px" stroke-width=".034366" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-74.86644" y="177.05612" fill="#ffffff" stroke-width=".034366">master</tspan></text>
+               <g fill-opacity=".91732">
+                       <circle cx="5.1449" cy="175.15" r="2.8446"/>
+                       <circle cx="11.712" cy="175.19" r="2.8446" opacity=".742"/>
+                       <circle cx="18.174" cy="175.33" r="2.8446" opacity=".497"/>
+                       <circle cx="-111.77" cy="174.85" r="2.8446" opacity=".522"/>
+                       <circle cx="-105.2" cy="174.88" r="2.8446" opacity=".73"/>
+                       <circle cx="-98.738" cy="175.02" r="2.8446"/>
+                       <circle cx="-92.17" cy="175.16" r="2.8446"/>
+                       <circle cx="-1.422" cy="175.16" r="2.8446"/>
+               </g>
+               <text transform="rotate(30.874)" x="34.972202" y="172.26613" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" letter-spacing="0px" stroke-width=".027864" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="34.972202" y="172.26613" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.0.0</tspan></text>
+               <text transform="rotate(-39.355)" x="-123.39997" y="98.293953" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.5255px" letter-spacing="0px" stroke-width=".028779" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-123.39997" y="98.293953" fill="#000000" stroke-width=".028779">v3.0-stable</tspan></text>
+               <g fill="none">
+                       <path d="m-107.13 186.94h118.75" marker-end="url(#b)" stroke="#000" stroke-width=".8444"/>
+                       <path d="m-12.364 175.18c8.1388-4.7778 0.6439-14.481-7.2258-19.849" marker-end="url(#d)" stroke="#37abc8" stroke-width=".8226"/>
+                       <path d="m-22.502 175.27c6.0493-5.3436 1.0792-10.607-3.5694-14.321" marker-end="url(#c)" stroke="#37abc8" stroke-width=".8226"/>
+               </g>
+               <g transform="matrix(.050194 0 0 .050194 151.08 182.99)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <g fill="#000080" fill-opacity=".91732">
+                       <circle cx="8.2638" cy="129.27" r="2.4824"/>
+                       <circle cx="12.955" cy="125.83" r="2.4824" opacity=".764"/>
+                       <circle cx="17.553" cy="122.32" r="2.4824" opacity=".508"/>
+               </g>
+               <circle cx="-14.407" cy="137.61" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <g transform="matrix(.08226 0 0 .08226 279.87 246.65)">
+                       <circle cx="-3580" cy="-1325.4" r="68.849" fill="#fff" fill-opacity=".9375"/>
+                       <path d="m-3614.7-1441.7c4.3421-0.526 7.081 1.7291 9.2058 4.4092 27.935 35.456 69.837 88.636 83.805 106.36 7.518 11.163-1.5651 26.971-4.661 34.22-4.3288 7.8664-5.8149 11.594-14.409 19.614-10.101 5.7546-20.736 8.2287-24.94 8.5614-4.2039 0.3328-24.572 5.9937-43.273-6.2536-13.092-17.678-53.598-73.18-79.78-108.54-2.3674-3.1292-7.4889-6.9953-0.5444-12.659 24.212-15.106 48.423-30.212 72.634-45.318 0.6899-0.196 1.3417-0.3268 1.9623-0.4019zm59.758 139.56a9.847 10.486 81.835 0 0 -8.9811 11.236 9.847 10.486 81.835 0 0 11.778 8.2569 9.847 10.486 81.835 0 0 8.9802 -11.235 9.847 10.486 81.835 0 0 -11.777 -8.2571z" fill="url(#e)" stroke="#000" stroke-width="2.4424px"/>
+                       <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width="2.4463">
+                               <path d="m-3622.5-1423.9 77.78 102.96"/>
+                               <path d="m-3668.9-1395.4 78.307 106.63"/>
+                               <path d="m-3646-1409 78.152 104.01"/>
+                       </g>
+               </g>
+               <text transform="rotate(30.874)" x="55.154636" y="126.17037" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" letter-spacing="0px" stroke-width=".027864" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="55.154636" y="126.17037" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.0.1</tspan></text>
+               <text x="-75.360199" y="127.00266" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.1788px" letter-spacing="0px" stroke-width=".021765" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-75.360199" y="127.00266">Periodically new point releases on</tspan><tspan x="-75.360199" y="132.22618">the -stable branch are tagged out,</tspan><tspan x="-75.360199" y="137.44971">with backports that didn't</tspan><tspan x="-75.360199" y="142.67323">generate any problems on</tspan><tspan x="-75.360199" y="147.89676">-stable.</tspan></text>
+       </g>
+</svg>
diff --git a/doc-assets/lws-relpol-5.svg b/doc-assets/lws-relpol-5.svg
new file mode 100644 (file)
index 0000000..aab2649
--- /dev/null
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="243.61mm" height="119.5mm" version="1.1" viewBox="0 0 243.60584 119.50127" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+       <defs>
+               <marker id="c" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <linearGradient id="a" x1="1000.2" x2="993.99" y1="928.57" y2="992.97" gradientTransform="matrix(-2.4177 .34689 .34689 2.4177 -1517.1 -4018.7)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+               <linearGradient id="b">
+                       <stop stop-color="#e9ddaf" offset="0"/>
+                       <stop stop-color="#504416" offset="1"/>
+               </linearGradient>
+               <marker id="h" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="e" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="g" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="d" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#5fbcd3" fill-rule="evenodd" stroke="#5fbcd3" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="i" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="f" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="j" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#37abc8" fill-rule="evenodd" stroke="#37abc8" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <linearGradient id="k" x1="1000.2" x2="993.99" y1="928.57" y2="992.97" gradientTransform="matrix(-.19888 .028535 .028535 .19888 76.322 -70.376)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
+               <filter id="m" x="-.060394" y="-.055053" width="1.1208" height="1.1101" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.44336333"/>
+               </filter>
+               <filter id="l" x="-.040014" y="-.088963" width="1.08" height="1.1779" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="45.715489"/>
+               </filter>
+       </defs>
+       <g transform="translate(170.18 -89.083)">
+               <rect transform="matrix(.08226 0 0 .08226 224.59 64.781)" x="-4689.4" y="405.15" width="2742" height="1233.3" filter="url(#l)"/>
+               <rect x="-162.64" y="96.631" width="225.56" height="101.45" fill="#fff"/>
+               <g fill="#000080">
+                       <path d="m-2.7427 136.03 26.893-21.631 3.0902 3.6748-26.225 21.631z" stroke="#000" stroke-width=".014764px"/>
+                       <g fill-opacity=".91732">
+                               <circle cx="25.541" cy="116.31" r="2.4824"/>
+                               <circle cx="30.232" cy="112.88" r="2.4824" opacity=".764"/>
+                               <circle cx="34.831" cy="109.36" r="2.4824" opacity=".508"/>
+                       </g>
+                       <path d="m-60.266 133.89 26.893-21.631 3.0902 3.6748-26.225 21.631z" stroke="#000" stroke-width=".014764px"/>
+               </g>
+               <path d="m-30.959 157.14 39.644-31.888 4.5555 5.4172-38.66 31.888z" fill="#00f" stroke="#000" stroke-width=".021765px"/>
+               <rect x="-130.22" y="156.7" width="158.14" height="5.7258" fill-opacity=".91732"/>
+               <path d="m-91.596 157.82 39.644-31.888 4.5555 5.4173-38.66 31.888z" fill="#00f" stroke="#000" stroke-width=".021765px"/>
+               <g transform="matrix(.08226 0 0 .08226 175.2 223.88)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <circle cx="-93.373" cy="151.18" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <path transform="matrix(-.75166 .10785 .10785 .75166 -2114.2 651.9)" d="m-2725.9-287.4c-0.4737 0.0104-0.7327 0.29435-0.9193 0.61443-2.45 4.2318-6.125 10.579-7.35 12.695-0.6344 1.3127 0.5828 2.8675 1.0263 3.5972 0.5853 0.7769 0.802 1.1542 1.8469 1.882 1.1717 0.46167 2.3507 0.56335 2.8066 0.53435s2.7265 0.26464 4.5434-1.3364c1.1317-2.0971 4.6213-8.6717 6.8848-12.866 0.2058-0.37197 0.6954-0.86583-0.1364-1.3663-2.8286-1.2472-5.6572-2.4941-8.4858-3.7414-0.077-0.0104-0.1488-0.0149-0.2165-0.0134zm-4.2607 15.885a1.1359 1.0667 0 0 1 1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1358 1.0666 1.1359 1.0667 0 0 1 -1.1359 -1.0666 1.1359 1.0667 0 0 1 1.1359 -1.0666z" filter="url(#m)" stroke="#000" stroke-width=".26458px"/>
+               <path d="m-96.232 141.62c0.3572-0.0433 0.5825 0.14222 0.7573 0.36273 2.298 2.9166 5.7448 7.2912 6.8938 8.7496 0.6184 0.91824-0.1288 2.2186-0.3834 2.8149-0.3561 0.64707-0.4784 0.95368-1.1854 1.6135-0.8309 0.47339-1.7057 0.67693-2.0515 0.70428-0.3458 0.0273-2.0213 0.49301-3.5596-0.51443-1.0769-1.4542-4.409-6.0198-6.5627-8.9283-0.1948-0.25742-0.6161-0.57542-0.045-1.0413 1.9917-1.2426 3.9833-2.4853 5.975-3.7279 0.057-0.0161 0.1103-0.0268 0.1613-0.0332zm4.9157 11.48a0.81001 0.86256 81.835 0 0 -0.7387 0.92419 0.81001 0.86256 81.835 0 0 0.9688 0.67923 0.81001 0.86256 81.835 0 0 0.7388 -0.92419 0.81001 0.86256 81.835 0 0 -0.9689 -0.67923z" fill="url(#k)" stroke="#000" stroke-width=".20091px"/>
+               <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width=".20123">
+                       <path d="m-96.875 143.08 6.3983 8.4694"/>
+                       <path d="m-100.69 145.42 6.4416 8.7713"/>
+                       <path d="m-98.803 144.3 6.4288 8.5556"/>
+               </g>
+               <text x="-112.85628" y="161.41298" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans'" font-size="6.5983px" letter-spacing="0px" stroke-width=".034366" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-112.85628" y="161.41298" fill="#ffffff" stroke-width=".034366">master</tspan></text>
+               <g transform="matrix(.050194 0 0 .050194 110.83 167.89)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <g fill="#000080" fill-opacity=".91732">
+                       <circle cx="-31.982" cy="114.17" r="2.4824"/>
+                       <circle cx="-27.291" cy="110.73" r="2.4824" opacity=".764"/>
+                       <circle cx="-22.692" cy="107.21" r="2.4824" opacity=".508"/>
+               </g>
+               <g fill-opacity=".91732">
+                       <circle cx="34.315" cy="159.51" r="2.8446"/>
+                       <circle cx="40.882" cy="159.54" r="2.8446" opacity=".742"/>
+                       <circle cx="47.343" cy="159.68" r="2.8446" opacity=".497"/>
+                       <circle cx="-149.76" cy="159.2" r="2.8446" opacity=".522"/>
+                       <circle cx="-143.19" cy="159.24" r="2.8446" opacity=".73"/>
+                       <circle cx="-136.73" cy="159.38" r="2.8446"/>
+                       <circle cx="-130.16" cy="159.51" r="2.8446"/>
+                       <circle cx="27.748" cy="159.52" r="2.8446"/>
+               </g>
+               <g fill="none" stroke-width=".8226">
+                       <g stroke="#37abc8">
+                               <path d="m-77.701 160.21c4.6564-3.733 6.8252-5.6007 0.3483-9.2282" marker-end="url(#j)"/>
+                               <path d="m-66.2 159.88c6.0493-5.3436 1.0792-10.607-3.5694-14.321" marker-end="url(#f)"/>
+                               <path d="m-55.579 159.97c8.1387-4.7778 0.644-14.481-7.2258-19.849" marker-end="url(#i)"/>
+                       </g>
+                       <path d="m-16.933 159.57c13.314-11.163-7.6595-24.173-10.414-24.407-12.367-3.0226-15.261 13.773-28.19-1.2216" marker-end="url(#d)" stroke="#5fbcd3"/>
+               </g>
+               <text transform="rotate(.45095)" x="-28.319969" y="133.38049" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="2.9593px" letter-spacing="0px" stroke-width=".015413" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-28.319969" y="133.38049" stroke-width=".015413">backport</tspan></text>
+               <g transform="matrix(.08226 0 0 .08226 235.71 223.42)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <circle cx="-32.864" cy="150.71" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <g transform="matrix(.050194 0 0 .050194 171.34 167.42)" fill-opacity=".91732">
+                       <circle cx="-3207.7" cy="-780.43" r="82.55"/>
+                       <circle cx="-3208.3" cy="-781.05" r="52.917" fill="#fff"/>
+               </g>
+               <g fill="none" stroke="#37abc8" stroke-width=".8226">
+                       <path d="m-17.192 159.74c6.2096-1.0078 14.537-8.1352 8.9217-14.408l-0.7984-0.73691-0.8975-0.61239" marker-end="url(#g)"/>
+                       <path d="m-5.6906 159.41c10.402-2.2095 11.178-15.047 3.3082-20.415" marker-end="url(#e)"/>
+                       <path d="m4.6694 160.02c8.1387-4.7777 7.6957-20.575-0.1741-25.943" marker-end="url(#h)"/>
+               </g>
+               <g letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text transform="rotate(.45095)" x="20.183514" y="148.49408" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="2.9593px" stroke-width=".015413" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="20.183514" y="148.49408" stroke-width=".015413">backport</tspan></text>
+                       <text transform="rotate(30.874)" x="-5.6618776" y="178.33397" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" stroke-width=".027864" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-5.6618776" y="178.33397" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.0.0</tspan></text>
+                       <text transform="rotate(.45095)" x="-43.566673" y="155.62878" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="2.9593px" stroke-width=".015413" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-43.566673" y="155.62878" stroke-width=".015413">backport</tspan></text>
+               </g>
+               <g transform="matrix(.08226 0 0 .08226 261.22 259.75)">
+                       <circle cx="-3580" cy="-1325.4" r="68.849" fill="#fff" fill-opacity=".9375"/>
+                       <path d="m-3614.7-1441.7c4.3421-0.526 7.081 1.7291 9.2058 4.4092 27.935 35.456 69.837 88.636 83.805 106.36 7.518 11.163-1.5651 26.971-4.661 34.22-4.3288 7.8664-5.8149 11.594-14.409 19.614-10.101 5.7546-20.736 8.2287-24.94 8.5614-4.2039 0.3328-24.572 5.9937-43.273-6.2536-13.092-17.678-53.598-73.18-79.78-108.54-2.3674-3.1292-7.4889-6.9953-0.5444-12.659 24.212-15.106 48.423-30.212 72.634-45.318 0.6899-0.196 1.3417-0.3268 1.9623-0.4019zm59.758 139.56a9.847 10.486 81.835 0 0 -8.9811 11.236 9.847 10.486 81.835 0 0 11.778 8.2569 9.847 10.486 81.835 0 0 8.9802 -11.235 9.847 10.486 81.835 0 0 -11.777 -8.2571z" fill="url(#a)" stroke="#000" stroke-width="2.4424px"/>
+                       <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width="2.4463">
+                               <path d="m-3622.5-1423.9 77.78 102.96"/>
+                               <path d="m-3668.9-1395.4 78.307 106.63"/>
+                               <path d="m-3646-1409 78.152 104.01"/>
+                       </g>
+               </g>
+               <text transform="rotate(30.874)" x="45.627552" y="147.34514" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" letter-spacing="0px" stroke-width=".027864" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="45.627552" y="147.34514" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.1.0</tspan></text>
+               <circle cx="-54.652" cy="122.51" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <g transform="matrix(.08226 0 0 .08226 239.63 231.54)">
+                       <circle cx="-3580" cy="-1325.4" r="68.849" fill="#fff" fill-opacity=".9375"/>
+                       <path d="m-3614.7-1441.7c4.3421-0.526 7.081 1.7291 9.2058 4.4092 27.935 35.456 69.837 88.636 83.805 106.36 7.518 11.163-1.5651 26.971-4.661 34.22-4.3288 7.8664-5.8149 11.594-14.409 19.614-10.101 5.7546-20.736 8.2287-24.94 8.5614-4.2039 0.3328-24.572 5.9937-43.273-6.2536-13.092-17.678-53.598-73.18-79.78-108.54-2.3674-3.1292-7.4889-6.9953-0.5444-12.659 24.212-15.106 48.423-30.212 72.634-45.318 0.6899-0.196 1.3417-0.3268 1.9623-0.4019zm59.758 139.56a9.847 10.486 81.835 0 0 -8.9811 11.236 9.847 10.486 81.835 0 0 11.778 8.2569 9.847 10.486 81.835 0 0 8.9802 -11.235 9.847 10.486 81.835 0 0 -11.777 -8.2571z" fill="url(#a)" stroke="#000" stroke-width="2.4424px"/>
+                       <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width="2.4463">
+                               <path d="m-3622.5-1423.9 77.78 102.96"/>
+                               <path d="m-3668.9-1395.4 78.307 106.63"/>
+                               <path d="m-3646-1409 78.152 104.01"/>
+                       </g>
+               </g>
+               <text transform="rotate(30.874)" x="12.861007" y="133.85826" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" letter-spacing="0px" stroke-width=".027864" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="12.861007" y="133.85826" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.0.1</tspan></text>
+               <circle cx="5.1836" cy="122.51" r="5.6635" fill="#fff" fill-opacity=".9375"/>
+               <g transform="matrix(.08226 0 0 .08226 300.22 231.72)">
+                       <circle cx="-3580" cy="-1325.4" r="68.849" fill="#fff" fill-opacity=".9375"/>
+                       <path d="m-3614.7-1441.7c4.3421-0.526 7.081 1.7291 9.2058 4.4092 27.935 35.456 69.837 88.636 83.805 106.36 7.518 11.163-1.5651 26.971-4.661 34.22-4.3288 7.8664-5.8149 11.594-14.409 19.614-10.101 5.7546-20.736 8.2287-24.94 8.5614-4.2039 0.3328-24.572 5.9937-43.273-6.2536-13.092-17.678-53.598-73.18-79.78-108.54-2.3674-3.1292-7.4889-6.9953-0.5444-12.659 24.212-15.106 48.423-30.212 72.634-45.318 0.6899-0.196 1.3417-0.3268 1.9623-0.4019zm59.758 139.56a9.847 10.486 81.835 0 0 -8.9811 11.236 9.847 10.486 81.835 0 0 11.778 8.2569 9.847 10.486 81.835 0 0 8.9802 -11.235 9.847 10.486 81.835 0 0 -11.777 -8.2571z" fill="url(#a)" stroke="#000" stroke-width="2.4424px"/>
+                       <g fill="none" stroke="#540" stroke-opacity=".18182" stroke-width="2.4463">
+                               <path d="m-3622.5-1423.9 77.78 102.96"/>
+                               <path d="m-3668.9-1395.4 78.307 106.63"/>
+                               <path d="m-3646-1409 78.152 104.01"/>
+                       </g>
+               </g>
+               <text transform="rotate(30.874)" x="64.21843" y="103.15361" dominant-baseline="auto" fill="#ffff00" font-family="'Open Sans Condensed'" font-size="5.3499px" letter-spacing="0px" stroke-width=".027864" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="64.21843" y="103.15361" fill="#ffff00" font-family="'Open Sans Condensed'" stroke-width=".027864">v3.1.1</tspan></text>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text transform="rotate(-39.355)" x="-147.32625" y="62.8997" dominant-baseline="auto" font-size="5.5255px" stroke-width=".028779" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-147.32625" y="62.8997" fill="#000000" stroke-width=".028779">v3.0-stable</tspan></text>
+                       <text transform="rotate(-38.777)" x="-98.208199" y="101.82109" dominant-baseline="auto" font-size="5.865px" stroke-width=".030547" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-98.208199" y="101.82109" fill="#000000" stroke-width=".030547">v3.1-stable</tspan></text>
+                       <text transform="rotate(.45095)" x="-28.459866" y="130.57814" dominant-baseline="auto" font-size="2.9593px" stroke-width=".015413" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-28.459866" y="130.57814" stroke-width=".015413">critical</tspan></text>
+               </g>
+               <path d="m-133.22 172.47h161.77" fill="none" marker-end="url(#c)" stroke="#000" stroke-width=".8444"/>
+               <text x="-57.245564" y="180.3519" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="4.1788px" letter-spacing="0px" stroke-width=".021765" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-57.245564" y="180.3519">Occasionally fixes are added on master that  fix old, maybe</tspan><tspan x="-57.245564" y="185.57542">critical bugs that affect more than one release.  These</tspan><tspan x="-57.245564" y="190.79895">may be backported to serveral -stable trees.</tspan></text>
+       </g>
+</svg>
diff --git a/doc-assets/lws-smp-example.png b/doc-assets/lws-smp-example.png
new file mode 100644 (file)
index 0000000..8ea23fa
Binary files /dev/null and b/doc-assets/lws-smp-example.png differ
diff --git a/doc-assets/lws-smp-ov.png b/doc-assets/lws-smp-ov.png
new file mode 100644 (file)
index 0000000..5faf17f
Binary files /dev/null and b/doc-assets/lws-smp-ov.png differ
diff --git a/doc-assets/lws_dll.svg b/doc-assets/lws_dll.svg
new file mode 100644 (file)
index 0000000..d0681c5
--- /dev/null
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="168.92mm" height="91.174mm" version="1.1" viewBox="0 0 168.92 91.174" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="c" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#a80" fill-rule="evenodd" stroke="#a80" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="d" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill="#a80" fill-rule="evenodd" stroke="#a80" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="g" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="j" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="e" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="f" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="i" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="h" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <filter id="k" x="-.034384" y="-.067675" width="1.0688" height="1.1353" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="12.065"/>
+               </filter>
+       </defs>
+       <g transform="translate(641.6 -54.865)">
+               <rect transform="matrix(.18769 0 0 .18769 -587.78 290.31)" x="-257.78" y="-1225.5" width="842.13" height="427.87" filter="url(#k)"/>
+               <rect x="-637.02" y="59.307" width="158.06" height="80.305" fill="#fff"/>
+               <path d="m-591.76 83.208c19.337 4.4099 14.374 18.712 10.783 24.758" fill="none" marker-end="url(#h)" stroke="#000" stroke-width="1.5888"/>
+               <path d="m-606.65 83.917c-19.337 4.4099-14.374 18.712-10.783 24.758" fill="none" marker-end="url(#i)" stroke="#000" stroke-width="1.5888"/>
+               <g>
+                       <rect x="-577.19" y="114.93" width="6.6215" height="3.9628" fill="#f00"/>
+                       <text x="-573.70239" y="118.16439" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="3.2562px" letter-spacing="0px" stroke-width=".24422" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-573.70239" y="118.16439" fill="#ffffff" stroke-width=".24422">NULL</tspan></text>
+                       <rect x="-627.56" y="115.28" width="6.6215" height="3.9628" fill="#f00"/>
+                       <text x="-624.07019" y="118.519" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="3.2562px" letter-spacing="0px" stroke-width=".24422" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-624.07019" y="118.519" fill="#ffffff" stroke-width=".24422">NULL</tspan></text>
+               </g>
+               <path d="m-512.11 120.77c-3.7306 3.3935-9.0532 4.1311-13.833 3.1214" fill="none" marker-end="url(#f)" stroke="#000" stroke-width="1.5888"/>
+               <g>
+                       <rect x="-497.19" y="115.83" width="6.6215" height="3.9628" fill="#f00"/>
+                       <text x="-493.70251" y="119.06271" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="3.2562px" letter-spacing="0px" stroke-width=".24422" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-493.70251" y="119.06271" fill="#ffffff" stroke-width=".24422">NULL</tspan></text>
+                       <rect x="-548.05" y="115.15" width="6.6215" height="3.9628" fill="#f00"/>
+                       <circle cx="-612.22" cy="117.26" r="7.5197" fill="#ccc"/>
+                       <g>
+                               <circle cx="-616.73" cy="117.16" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-616.78351" y="118.60555" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-616.78351" y="118.60555" fill="#ffffff" font-weight="bold" stroke-width=".32595">prev</tspan></text>
+                               <circle cx="-606.9" cy="117.19" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-606.95825" y="118.64108" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-606.95825" y="118.64108" fill="#ffffff" font-weight="bold" stroke-width=".32595">next</tspan></text>
+                               <text x="-612.91266" y="130.14314" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-612.91266" y="130.14314" fill="#000000" font-weight="bold" stroke-width=".32595">lws_dll</tspan></text>
+                       </g>
+                       <circle cx="-506.47" cy="117.75" r="7.5197" fill="#2a7fff"/>
+                       <g>
+                               <circle cx="-510.98" cy="117.64" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-511.02988" y="119.08846" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-511.02988" y="119.08846" fill="#ffffff" font-weight="bold" stroke-width=".32595">prev</tspan></text>
+                               <circle cx="-501.15" cy="117.67" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-501.20462" y="119.12399" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-501.20462" y="119.12399" fill="#ffffff" font-weight="bold" stroke-width=".32595">next</tspan></text>
+                               <text x="-507.15903" y="130.62617" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-507.15903" y="130.62617" fill="#000000" font-weight="bold" stroke-width=".32595">lws_dll2</tspan></text>
+                       </g>
+                       <circle cx="-518.67" cy="84.947" r="7.5197" fill="#a80"/>
+                       <g>
+                               <circle cx="-523.17" cy="84.84" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-523.22449" y="86.289391" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-523.22449" y="86.289391" fill="#ffffff" font-weight="bold" stroke-width=".32595">head</tspan></text>
+                               <text x="-518.72443" y="70.31234" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-518.72443" y="70.31234" fill="#000000" font-weight="bold" stroke-width=".32595">lws_dll2_owner</tspan></text>
+                       </g>
+                       <circle cx="-518.46" cy="76.37" r="4.5456" fill="#786721"/>
+                       <text x="-518.44611" y="77.61422" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="3.5001px" letter-spacing="0px" stroke-width=".26251" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-518.44611" y="77.61422" fill="#ffffff" font-weight="bold" stroke-width=".26251">count</tspan></text>
+                       <circle cx="-532.76" cy="117.24" r="7.5197" fill="#2a7fff"/>
+                       <g>
+                               <circle cx="-537.26" cy="117.14" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-537.315" y="118.58688" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-537.315" y="118.58688" fill="#ffffff" font-weight="bold" stroke-width=".32595">prev</tspan></text>
+                               <circle cx="-527.44" cy="117.17" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                       </g>
+                       <g font-family="'Open Sans Condensed'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                               <text x="-527.48981" y="118.6224" dominant-baseline="auto" fill="#ffffff" font-size="4.346px" stroke-width=".32595" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-527.48981" y="118.6224" fill="#ffffff" font-weight="bold" stroke-width=".32595">next</tspan></text>
+                               <text x="-533.44415" y="130.12459" dominant-baseline="auto" fill="#000000" font-size="4.346px" stroke-width=".32595" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-533.44415" y="130.12459" fill="#000000" font-weight="bold" stroke-width=".32595">lws_dll2</tspan></text>
+                               <text x="-544.56732" y="118.38558" dominant-baseline="auto" fill="#ffffff" font-size="3.2562px" stroke-width=".24422" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-544.56732" y="118.38558" fill="#ffffff" stroke-width=".24422">NULL</tspan></text>
+                       </g>
+               </g>
+               <path d="m-526.89 114.31c3.7306-3.3935 9.0532-4.1311 13.833-3.1214" fill="none" marker-end="url(#e)" stroke="#000" stroke-width="1.5888"/>
+               <g>
+                       <circle cx="-532.24" cy="109.04" r="4.5456" fill="#a80"/>
+                       <text x="-532.22772" y="110.2806" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="3.5001px" letter-spacing="0px" stroke-width=".26251" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-532.22772" y="110.2806" fill="#ffffff" font-weight="bold" stroke-width=".26251">owner</tspan></text>
+                       <circle cx="-506.49" cy="109.25" r="4.5456" fill="#a80"/>
+                       <text x="-506.47632" y="110.49337" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="3.5001px" letter-spacing="0px" stroke-width=".26251" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-506.47632" y="110.49337" fill="#ffffff" font-weight="bold" stroke-width=".26251">owner</tspan></text>
+               </g>
+               <g stroke="#000">
+                       <path d="m-527.5 85.113c-19.337 4.4099-14.374 18.712-10.783 24.758" fill="none" marker-end="url(#j)" stroke-width="1.5888"/>
+                       <path d="m-511.31 85.478c19.337 4.4099 14.374 18.712 10.783 24.758" fill="none" marker-end="url(#g)" stroke-width="1.5888"/>
+                       <circle cx="-513.34" cy="84.876" r="4.5456" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+               </g>
+               <g>
+                       <text x="-513.39923" y="86.324791" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-513.39923" y="86.324791" fill="#ffffff" font-weight="bold" stroke-width=".32595">tail</tspan></text>
+                       <path d="m-532.42 105.53c1.9571-5.5217 5.719-6.0465 10.286-12.627" fill="none" marker-end="url(#d)" stroke="#a80" stroke-width="1.5888"/>
+                       <path d="m-506.07 105.39c-1.9571-5.5218-5.719-6.0466-10.286-12.628" fill="none" marker-end="url(#c)" stroke="#a80" stroke-width="1.5888"/>
+               </g>
+               <g>
+                       <circle cx="-586.46" cy="116.88" r="7.5197" fill="#ccc"/>
+                       <g>
+                               <circle cx="-590.97" cy="116.78" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-591.02338" y="118.22713" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-591.02338" y="118.22713" fill="#ffffff" font-weight="bold" stroke-width=".32595">prev</tspan></text>
+                               <circle cx="-581.14" cy="116.81" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                               <text x="-581.19806" y="118.26266" dominant-baseline="auto" fill="#ffffff" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-581.19806" y="118.26266" fill="#ffffff" font-weight="bold" stroke-width=".32595">next</tspan></text>
+                               <text x="-587.15247" y="129.76485" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-587.15247" y="129.76485" fill="#000000" font-weight="bold" stroke-width=".32595">lws_dll</tspan></text>
+                       </g>
+                       <circle cx="-599.4" cy="84.001" r="7.5197" fill="#ccc"/>
+                       <circle cx="-603.9" cy="83.895" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                       <circle cx="-594.08" cy="83.93" r="4.5456" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".62205" stroke-width=".1248"/>
+                       <g font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-anchor="middle" word-spacing="0px">
+                               <text x="-604.2782" y="85.308311" dominant-baseline="auto" fill="#ffffff" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-604.2782" y="85.308311" fill="#ffffff" font-weight="bold" stroke-width=".32595">next</tspan></text>
+                               <text x="-599.28552" y="71.560875" dominant-baseline="auto" fill="#000000" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-599.28552" y="71.560875" fill="#000000" font-weight="bold" stroke-width=".32595">lws_dll</tspan></text>
+                               <text x="-593.88544" y="85.272911" dominant-baseline="auto" fill="#ffffff" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-593.88544" y="85.272911" fill="#ffffff" font-weight="bold" stroke-width=".32595">prev</tspan></text>
+                       </g>
+               </g>
+               <g>
+                       <path d="m-605.77 112.84c3.7306-3.3936 9.0532-4.1312 13.833-3.1214" fill="none" marker-end="url(#b)" stroke="#000" stroke-width="1.5888"/>
+                       <path d="m-592.68 120.8c-3.7306 3.3935-9.0532 4.1311-13.833 3.1214" fill="none" marker-end="url(#a)" stroke="#000" stroke-width="1.5888"/>
+                       <text x="-620.91577" y="82.28817" dominant-baseline="auto" fill="#4d4d4d" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-620.91577" y="82.28817" fill="#4d4d4d" font-weight="bold" stroke-width=".32595">(head)</tspan></text>
+                       <text x="-577.56561" y="82.463707" dominant-baseline="auto" fill="#4d4d4d" font-family="'Open Sans Condensed'" font-size="4.346px" letter-spacing="0px" stroke-width=".32595" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-577.56561" y="82.463707" fill="#4d4d4d" font-weight="bold" stroke-width=".32595">(tail)</tspan></text>
+               </g>
+               <path d="m-559.27 67.394v66.684" fill="none" stroke="#000" stroke-dasharray="0.29795014, 0.29795014" stroke-width=".049658"/>
+       </g>
+</svg>
diff --git a/doc-assets/lws_sequencer.svg b/doc-assets/lws_sequencer.svg
new file mode 100644 (file)
index 0000000..c846ae9
--- /dev/null
@@ -0,0 +1,955 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="179.03mm" height="156.88mm" version="1.1" viewBox="0 0 179.03 156.88" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <defs>
+  <marker id="marker9266-0-2-5" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="marker34759" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="marker9266-0-2" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="marker9266-0" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <marker id="marker35789" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <clipPath id="clipPath32169">
+   <rect x="-54.933" y="230.95" width="79.112" height="39.021" fill="#b3b3b3"/>
+  </clipPath>
+  <linearGradient id="linearGradient11357-7" x2="1" gradientTransform="matrix(-636.8 1466.5 -15.797 -6.8597 1051 -923.88)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient11375-1" x2="1" gradientTransform="matrix(-636.8 1466.5 -15.797 -6.8598 1040.3 -928.53)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient11735-6" x2="1" gradientTransform="matrix(-636.8 1466.5 -7.592 -3.2967 1017.3 -938.51)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient11771-1" x2="1" gradientTransform="matrix(-636.8 1466.5 -15.797 -6.8598 1002.5 -944.94)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient11789-0" x2="1" gradientTransform="matrix(-636.8 1466.5 -7.5923 -3.2969 1006.6 -943.16)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient11825-1" x2="1" gradientTransform="matrix(-636.8 1466.5 -15.797 -6.8598 1029.6 -933.17)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient11843-3" x2="1" gradientTransform="matrix(-636.8 1466.5 -15.797 -6.8598 1018.9 -937.81)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient12131-2" x2="1" gradientTransform="matrix(-636.8 1466.5 -7.592 -3.2967 979.46 -954.93)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient12221-8" x2="1" gradientTransform="matrix(-636.8 1466.5 -15.797 -6.8598 991.77 -949.58)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient13921-1" x2="1" gradientTransform="matrix(-11.223 135.93 -98.154 -8.1044 502.25 289.31)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient13939-6" x2="1" gradientTransform="matrix(-9.7265 54.751 -55.467 -9.8538 543.81 291.93)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient13957-9" x2="1" gradientTransform="matrix(-5.6174 13.258 -6.2951 -2.6671 502.44 306.56)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient13975-8" x2="1" gradientTransform="matrix(-9.6681 22.819 -2.7255 -1.1548 481.47 310.7)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient13993-9" x2="1" gradientTransform="matrix(-9.1296 21.548 -2.3134 -.98017 530.55 301.83)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14011-4" x2="1" gradientTransform="matrix(-3.4589 8.1639 -8.1917 -3.4707 428.4 329.67)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14029-0" x2="1" gradientTransform="matrix(-1.8567 71.399 -5.2548 -.13665 500.06 265.73)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14047-1" x2="1" gradientTransform="matrix(-1.3053e-5 79.162 -4.2642 -7.0556e-7 498.32 282.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14065-8" x2="1" gradientTransform="matrix(-5.2906 12.487 -9.4796 -4.0164 497.3 319.25)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14083-3" x2="1" gradientTransform="matrix(8.1136 75.133 -5.4303 .58642 476.4 289.65)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14101-3" x2="1" gradientTransform="matrix(-1.8567 71.399 -3.9987 -.10399 529.88 266.51)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14119-8" x2="1" gradientTransform="matrix(-4.7822 11.287 -6.4102 -2.7159 500.9 321.02)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14137-5" x2="1" gradientTransform="matrix(76.07 77.87 -5.5448 5.4166 470.83 293.71)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14155-3" x2="1" gradientTransform="matrix(-1.3053e-5 79.161 -4.5952 -7.0556e-7 529.2 282.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14173-0" x2="1" gradientTransform="matrix(-21.83 -125.58 7.7055 -1.3395 435.69 438.02)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14191-5" x2="1" gradientTransform="matrix(-22.955 121.3 -6.924 -1.3103 431.76 257.51)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14209-6" x2="1" gradientTransform="matrix(-1.5169e-5 92.676 -10.559 -1.7639e-6 539.04 242.39)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14227-4" x2="1" gradientTransform="matrix(-1.8568 71.401 -11.17 -.29046 496.45 265.64)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14245-6" x2="1" gradientTransform="matrix(-10.538 24.872 -6.814 -2.887 498.23 322.7)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14263-8" x2="1" gradientTransform="matrix(-8.5818 20.255 -2.9111 -1.2334 532.31 316.6)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14281-5" x2="1" gradientTransform="matrix(-1.3053e-5 79.171 -9.6911 -1.4111e-6 493.87 282.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14299-9" x2="1" gradientTransform="matrix(2.8695 75.957 -8.3324 .31478 513.58 266.6)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14317-6" x2="1" gradientTransform="matrix(-5.4725 12.916 -10.664 -4.5181 420.06 346.72)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14335-3" x2="1" gradientTransform="matrix(-6.9203 56.037 -7.186 -.88742 502.74 282.75)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14353-1" x2="1" gradientTransform="matrix(-1.0936e-5 67.349 -4.6128 -7.0556e-7 496.96 290.5)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14371-1" x2="1" gradientTransform="matrix(-1.8567 71.4 -10.794 -.28069 541.23 266.8)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14389-0" x2="1" gradientTransform="matrix(-6.9202 56.037 -4.8661 -.60093 534.54 286.68)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14407-7" x2="1" gradientTransform="matrix(-7.4823 98.268 -10.279 -.78264 497.57 275.32)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14425-3" x2="1" gradientTransform="matrix(-1.0936e-5 67.346 -5.0159 -7.0556e-7 531.16 290.5)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14443-6" x2="1" gradientTransform="matrix(-24.981 162.38 -53.719 -8.2645 542.15 286.35)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14461-7" x2="1" gradientTransform="matrix(1.5192 85.248 -10.941 .19498 502.5 266.35)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14479-0" x2="1" gradientTransform="matrix(-1.5169e-5 92.665 -6.982 -1.0583e-6 509.23 242.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14497-2" x2="1" gradientTransform="matrix(-1.5169e-5 92.658 -7.2814 -1.0583e-6 521.52 242.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14515-7" x2="1" gradientTransform="matrix(1.5191 85.238 -11.644 .20752 530.49 265.86)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14533-4" x2="1" gradientTransform="matrix(-1.3053e-5 79.169 -10.771 -1.7639e-6 540.23 282.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14551-1" x2="1" gradientTransform="matrix(-80.31 110.24 -11.498 -8.3765 544.26 260.36)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14569-3" x2="1" gradientTransform="matrix(-6.9206 56.04 -9.831 -1.2141 520.91 285)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14587-5" x2="1" gradientTransform="matrix(-21.83 -125.58 13.737 -2.3879 418.91 440.94)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14605-7" x2="1" gradientTransform="matrix(-7.6229 17.992 -8.6558 -3.6673 518.12 308.23)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14623-8" x2="1" gradientTransform="matrix(-1.4464e-5 89.626 -12.383 -2.1167e-6 503.66 266.7)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14641-5" x2="1" gradientTransform="matrix(-1.8567 71.399 -8.0716 -.2099 510.83 266.01)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14659-8" x2="1" gradientTransform="matrix(-22.956 121.3 -11.204 -2.1202 425.84 256.39)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14677-7" x2="1" gradientTransform="matrix(-8.8194e-6 54.688 -7.6788 -1.4111e-6 508.71 283.07)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14695-9" x2="1" gradientTransform="matrix(-1.3053e-5 79.163 -11.515 -1.7639e-6 502.79 282.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14713-2" x2="1" gradientTransform="matrix(-20.618 48.663 -10.559 -4.4739 499.56 313.04)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14731-7" x2="1" gradientTransform="matrix(-1.4464e-5 89.625 -12.384 -2.1167e-6 532.4 266.7)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14749-8" x2="1" gradientTransform="matrix(-1.8567 71.398 -7.7998 -.20283 523.14 266.33)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14767-9" x2="1" gradientTransform="matrix(-40.171 125.24 -5.9737 -1.9161 498.07 291.41)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14785-6" x2="1" gradientTransform="matrix(54.015 81.697 -8.9165 5.8952 450.76 281.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14803-0" x2="1" gradientTransform="matrix(-3.1507 112.53 -4.4752 -.1253 500.08 270.03)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14821-3" x2="1" gradientTransform="matrix(-8.8194e-6 54.687 -7.7308 -1.4111e-6 521.82 283.07)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14839-5" x2="1" gradientTransform="matrix(-1.3053e-5 79.163 -12.187 -2.1167e-6 532.4 282.4)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14857-1" x2="1" gradientTransform="matrix(-21.942 130.98 -11.668 -1.9546 496.31 280.71)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14875-3" x2="1" gradientTransform="matrix(-24.306 79.331 -19.483 -5.9692 455.32 299.19)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14893-0" x2="1" gradientTransform="matrix(-40.172 125.24 -8.2904 -2.6592 496.95 291.05)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14911-5" x2="1" gradientTransform="matrix(-40.172 125.24 -12.987 -4.1656 453.6 277.15)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14929-6" x2="1" gradientTransform="matrix(-24.981 162.38 -92.236 -14.19 502.3 280.22)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14947-0" x2="1" gradientTransform="matrix(-17.329 86.872 -7.1895 -1.4342 495.63 298.01)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14965-1" x2="1" gradientTransform="matrix(-4.9506 109.82 -12.164 -.54836 430.76 287.49)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient14983-6" x2="1" gradientTransform="matrix(-17.328 86.866 -12.868 -2.567 452.47 289.41)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15001-7" x2="1" gradientTransform="matrix(-17.329 86.872 -13.628 -2.7186 478.52 294.6)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15019-6" x2="1" gradientTransform="matrix(8.1137 75.134 -11.902 1.2853 488.46 288.35)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15037-9" x2="1" gradientTransform="matrix(-19.129 99.247 -6.6693 -1.2855 506.13 297.54)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15055-5" x2="1" gradientTransform="matrix(39.637 -119.91 12.795 4.2295 460.64 391.33)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15073-2" x2="1" gradientTransform="matrix(-13.953 109.6 -5.0541 -.64344 500.6 296.65)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15091-2" x2="1" gradientTransform="matrix(-19.13 99.25 -17.602 -3.3927 432.7 283.39)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15109-5" x2="1" gradientTransform="matrix(-208 87.292 -6.3852 -15.215 563.7 306.81)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15127-3" x2="1" gradientTransform="matrix(54.01 81.691 -3.1178 2.0613 423.26 299.59)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15145-1" x2="1" gradientTransform="matrix(-3.1505 112.52 -14.011 -.39228 485.04 269.61)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15163-1" x2="1" gradientTransform="matrix(-3.1503 112.51 -16.554 -.46349 450.87 268.66)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15181-9" x2="1" gradientTransform="matrix(-4.951 109.83 -5.8077 -.26181 455.48 288.6)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15199-2" x2="1" gradientTransform="matrix(-13.953 109.6 -9.555 -1.2164 418.36 286.18)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15217-3" x2="1" gradientTransform="matrix(-142.91 -61.441 5.2383 -12.184 502.42 396.73)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15235-9" x2="1" gradientTransform="matrix(-24.306 79.331 -19.17 -5.8732 480.63 306.95)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15253-3" x2="1" gradientTransform="matrix(-40.174 125.25 -14.457 -4.6372 480.24 285.69)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15271-9" x2="1" gradientTransform="matrix(108.19 -41.016 8.1434 21.481 383.12 378.64)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15289-0" x2="1" gradientTransform="matrix(-19.13 99.249 -23.633 -4.5552 463.8 289.38)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15307-1" x2="1" gradientTransform="matrix(-171.09 82.304 -9.1941 -19.113 546.48 328.34)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15325-6" x2="1" gradientTransform="matrix(94.741 -111.17 10.193 8.6868 396.93 417.47)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15343-8" x2="1" gradientTransform="matrix(-13.954 109.61 -17.602 -2.2409 451.2 290.36)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15361-6" x2="1" gradientTransform="matrix(-142.9 -61.438 5.5636 -12.941 504.93 390.88)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15379-3" x2="1" gradientTransform="matrix(5.0712 -20.634 23.394 5.7496 498.22 303.38)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15397-8" x2="1" gradientTransform="matrix(-3.1476 12.59 -18.216 -4.5541 521.79 299.08)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15415-7" x2="1" gradientTransform="matrix(46.248 -48.949 29.351 27.732 415.4 328.26)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15433-5" x2="1" gradientTransform="matrix(-24.981 162.38 -44.4 -6.8308 478.29 276.53)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15451-4" x2="1" gradientTransform="matrix(-8.918 29.552 -33.772 -10.191 468.37 319.58)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15469-3" x2="1" gradientTransform="matrix(-24.981 162.38 -25.03 -3.8509 467.36 274.85)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15487-8" x2="1" gradientTransform="matrix(8.8828 -3.3035 2.4608 6.6169 441.41 321.12)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15505-3" x2="1" gradientTransform="matrix(-24.982 162.38 -11.779 -1.8121 519.91 282.93)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15523-5" x2="1" gradientTransform="matrix(3.7385 -1.1554 1.1691 3.7829 508.72 298.98)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15541-2" x2="1" gradientTransform="matrix(-19.13 99.25 -18.198 -3.5075 495.36 295.47)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient15559-0" x2="1" gradientTransform="matrix(-13.953 109.6 -15.2 -1.9351 486.83 294.9)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#8ca0b2" offset=".5"/>
+   <stop stop-color="#14212f" offset="1"/>
+  </linearGradient>
+  <marker id="marker9266" overflow="visible" orient="auto">
+   <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+  </marker>
+  <filter id="filter37725" x="-.031866" y="-.041367" width="1.0637" height="1.0827" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="4.3768498"/>
+  </filter>
+ </defs>
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g transform="translate(391.05 132.94)">
+  <path transform="matrix(.51055 0 0 .5706 -377.19 -236.69)" d="m-16.656 192.33h329.65v253.93h-329.65z" filter="url(#filter37725)"/>
+  <path d="m-386.36-127.7h168.3v144.9h-168.3z" fill="#fff"/>
+  <path d="m-342.59-102.46h87.058v73.507h-87.058z" fill="#ececec"/>
+  <path d="m-265.26-8.1289a14.859 16.607 0 0 1-14.859 16.607 14.859 16.607 0 0 1-14.859-16.607 14.859 16.607 0 0 1 14.859-16.607 14.859 16.607 0 0 1 14.859 16.607z" fill="#00f"/>
+  <path d="m-357.67-122.28h102.06v15.25h-102.06z" fill="#008000"/>
+  <path d="m-353.72-114.8h35.711v15.25h-35.711z" fill="#b3b3b3"/>
+  <path d="m-334.28-89.41h40.391v18.06h-40.391z" fill="#b3b3b3"/>
+  <g transform="scale(.94592 1.0572)" dominant-baseline="auto" fill="#fff" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="lws_context">
+   <path d="m-295.49-110.85q0 0.19993 0.0514 0.28561 0.0571 0.0857 0.15423 0.0857 0.11996 0 0.2799-0.0628l0.04 0.33132q-0.0743 0.0457-0.21136 0.0742-0.13138 0.0286-0.23991 0.0286-0.21707 0-0.35416-0.13139-0.13139-0.13709-0.13139-0.47411v-3.4559h0.41129z"/>
+   <path d="m-293.07-113.03 0.50839 1.668 0.10282 0.54838h0.0114l0.0857-0.55981 0.38843-1.6566h0.38844l-0.75974 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.2342l-0.78258-2.919h0.43984l0.43985 1.6623 0.0685 0.55409h0.0114l0.10282-0.56552 0.46841-1.6508z"/>
+   <path d="m-291.28-110.64q0.11424 0.0686 0.26848 0.11996 0.15994 0.0457 0.3256 0.0457 0.1885 0 0.31988-0.0914 0.13139-0.0971 0.13139-0.30846 0-0.17708-0.08-0.29132-0.08-0.11425-0.20564-0.20565-0.11996-0.0914-0.26276-0.16565-0.14281-0.08-0.26848-0.18851-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.04 0.29704 0.11424l-0.10853 0.34274q-0.10854-0.0571-0.25134-0.0914-0.14281-0.04-0.29133-0.04-0.20564 0-0.30275 0.0857-0.0914 0.0857-0.0914 0.26848 0 0.1428 0.08 0.24562 0.08 0.0971 0.19993 0.1828 0.12567 0.08 0.26848 0.16565 0.14281 0.0857 0.26277 0.20564 0.12567 0.11425 0.20564 0.27991 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.1428-0.15995 0.25134-0.10282 0.10282-0.26276 0.16565-0.15423 0.0628-0.36559 0.0628-0.25134 0-0.43413-0.0514-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-289.64-109.37h1.9079v0.3713h-1.9079z"/>
+   <path d="m-285.82-110.32q-0.14281 0.10854-0.3256 0.15995-0.18279 0.0514-0.38272 0.0514-0.27419 0-0.4627-0.10282-0.1885-0.10854-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61693 0-0.73117 0.25705-1.1139 0.26277-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.04 0.15995 0.04 0.27419 0.10282l-0.11424 0.35988q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.46841 0.27418-0.15423 0.26848-0.15423 0.85113 0 0.23421 0.0343 0.43985t0.11424 0.35987q0.08 0.14852 0.20564 0.23992 0.12567 0.0857 0.31418 0.0857 0.14852 0 0.27419-0.0514 0.13138-0.0514 0.21135-0.11996z"/>
+   <path d="m-285.59-111.6q0-0.77115 0.26277-1.131 0.26848-0.36558 0.75973-0.36558 0.52553 0 0.77116 0.3713 0.25134 0.37129 0.25134 1.1253 0 0.77687-0.26848 1.1368-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20565 0.10282 0.35417 0.0743 0.14851 0.1885 0.2342 0.11425 0.08 0.27419 0.08 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86256 0-0.24562-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0685-0.14852-0.18279-0.22849-0.11425-0.0857-0.27419-0.0857-0.29132 0-0.44556 0.26847-0.14852 0.26848-0.14852 0.85684z"/>
+   <path d="m-281.53-110.17v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19421-0.35416-0.19421-0.22849 0-0.37701 0.13709t-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30276h0.0171q0.10853-0.15424 0.29132-0.26277 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.0514 0.1428 0.0514 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51981v1.8508z"/>
+   <path d="m-280.77-113.03h0.34844v-0.56551l0.41129-0.13138v0.69689h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.0229 0.0857-0.0286 0.18279-0.0686l0.08 0.3256q-0.12567 0.0628-0.2799 0.0971-0.14852 0.04-0.31417 0.04-0.28562 0-0.41129-0.1828-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34844z"/>
+   <path d="m-277.25-110.37q-0.1371 0.12567-0.34845 0.19422-0.21136 0.0686-0.44556 0.0686-0.26848 0-0.46841-0.10282-0.19421-0.10854-0.32559-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.04 0.15995 0.04 0.28562 0.15995t0.19992 0.33702q0.08 0.21707 0.08 0.56552 0 0.0971-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556t0.12567 0.34273q0.0857 0.1371 0.21707 0.21707 0.13709 0.0743 0.33702 0.0743 0.15424 0 0.30275-0.0571 0.15424-0.0571 0.23421-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19992-0.35987-0.19992-0.26277 0-0.417 0.19992-0.15423 0.19993-0.18279 0.62835z"/>
+   <path d="m-276.12-111.63-0.75402-1.3938h0.49126l0.4227 0.81686 0.11425 0.31989 0.11996-0.31989 0.43413-0.81686h0.45127l-0.75973 1.371 0.80543 1.4852h-0.46841l-0.47983-0.89683-0.12567-0.34273-0.13138 0.34273-0.47983 0.89683h-0.45127z"/>
+   <path d="m-274.64-113.03h0.34844v-0.56551l0.41129-0.13138v0.69689h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.0229 0.0857-0.0286 0.18279-0.0686l0.08 0.3256q-0.12567 0.0628-0.2799 0.0971-0.14852 0.04-0.31417 0.04-0.28562 0-0.41129-0.1828-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34844z"/>
+  </g>
+  <g stroke-width=".14281">
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="per-thread">
+    <path d="m-373.02-106.11h0.29132l0.0628 0.30846h0.0229q0.21135-0.37701 0.66262-0.37701t0.67405 0.33702q0.22849 0.33703 0.22849 1.1025 0 0.35987-0.0743 0.6512-0.0743 0.28561-0.21135 0.49126-0.1371 0.19992-0.33703 0.30846-0.19421 0.10282-0.43413 0.10282-0.16566 0-0.26276-0.0229-0.0971-0.0171-0.21136-0.08v1.1767h-0.41128zm0.41128 2.4049q0.08 0.0686 0.17708 0.10854 0.10282 0.04 0.26848 0.04 0.30275 0 0.47983-0.30846t0.17708-0.87969q0-0.23992-0.0343-0.43413-0.0286-0.19422-0.0971-0.33132-0.0685-0.1428-0.17708-0.21706-0.10282-0.08-0.25705-0.08-0.417 0-0.53696 0.50839z"/>
+    <path d="m-368.83-103.45q-0.13709 0.12567-0.34845 0.19422-0.21135 0.0686-0.44556 0.0686-0.26847 0-0.4684-0.10282-0.19422-0.10854-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.04 0.15995 0.04 0.28562 0.15995 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.0971-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556t0.12567 0.34274q0.0857 0.13709 0.21706 0.21706 0.1371 0.0743 0.33703 0.0743 0.15423 0 0.30275-0.0571 0.15423-0.0571 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42843-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19992-0.1828 0.62835z"/>
+    <path d="m-368.18-106.11h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20565-0.25706 0.13138-0.0971 0.31417-0.0971 0.13138 0 0.29704 0.0514l-0.08 0.41699q-0.14852-0.0514-0.26277-0.0514-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14851 0.27991v2.1021h-0.41129z"/>
+    <path d="m-366.69-105.09h1.1253v0.39415h-1.1253z"/>
+    <path d="m-365.24-106.11h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61693v0.37129h-0.61693v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.0229 0.0857-0.0286 0.18279-0.0686l0.08 0.3256q-0.12567 0.0628-0.2799 0.0971-0.14852 0.04-0.31417 0.04-0.28562 0-0.41129-0.1828-0.11995-0.1885-0.11995-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-361.92-103.26v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.1428-0.15995 0.14281-0.21707 0.35988v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23991 0.17137-0.0971 0.42271-0.0971 0.1885 0 0.3256 0.0514 0.1428 0.0514 0.2342 0.17708 0.0914 0.12567 0.13709 0.33702 0.0457 0.20564 0.0457 0.51411v1.8451z"/>
+    <path d="m-360.87-106.11h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25706 0.13138-0.0971 0.31417-0.0971 0.13139 0 0.29704 0.0514l-0.08 0.41699q-0.14852-0.0514-0.26276-0.0514-0.1828 0-0.29704 0.10853-0.11425 0.10282-0.14852 0.27991v2.1021h-0.41129z"/>
+    <path d="m-357.6-103.45q-0.13709 0.12567-0.34845 0.19422-0.21135 0.0686-0.44555 0.0686-0.26848 0-0.46841-0.10282-0.19422-0.10854-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139t0.75973-0.38272q0.15995 0 0.31418 0.04 0.15994 0.04 0.28561 0.15995 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.0971-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556t0.12567 0.34274q0.0857 0.13709 0.21707 0.21706 0.13709 0.0743 0.33702 0.0743 0.15423 0 0.30275-0.0571 0.15423-0.0571 0.2342-0.1371zm-0.31988-1.5309q0.0114-0.42843-0.11996-0.62835-0.13138-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19992-0.18279 0.62835z"/>
+    <path d="m-356.99-105.94q0.16566-0.10282 0.39986-0.15994 0.23992-0.0571 0.50268-0.0571 0.23992 0 0.38273 0.0743 0.14851 0.0685 0.22849 0.19421 0.0857 0.11996 0.10853 0.27991 0.0286 0.15423 0.0286 0.32559 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21706 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31418l-0.0971-0.33703h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10854-0.44555 0.10854-0.30847 0-0.5084-0.21136-0.19421-0.21706-0.19421-0.59407 0-0.24563 0.08-0.41129 0.0857-0.16565 0.2342-0.26847 0.15423-0.10282 0.35988-0.14281 0.21135-0.0457 0.4684-0.0457 0.0571 0 0.11425 0 0.0571 0 0.11995 6e-3 0.0171-0.17708 0.0171-0.31417 0-0.3256-0.0971-0.45698-0.0971-0.13139-0.35416-0.13139-0.15994 0-0.34845 0.0514-0.1885 0.0457-0.31417 0.11995zm1.2396 1.3824q-0.0571-6e-3 -0.11425-6e-3 -0.0571-6e-3 -0.11424-6e-3 -0.1371 0-0.26848 0.0228-0.13138 0.0229-0.2342 0.08t-0.16566 0.15423q-0.0571 0.0971-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11425 0.12567 0.29133 0.12567 0.23991 0 0.37129-0.11425 0.13139-0.11424 0.1828-0.25134z"/>
+    <path d="m-352.87-104.24q0 0.29133 6e-3 0.53124 6e-3 0.23421 0.04 0.4627h-0.2799l-0.0914-0.34274h-0.0229q-0.08 0.17137-0.25134 0.28561-0.17137 0.11425-0.41128 0.11425-0.4627 0-0.69119-0.35987-0.22277-0.35988-0.22277-1.131 0-0.73118 0.27418-1.1082 0.27991-0.37701 0.76545-0.37701 0.16565 0 0.26276 0.0229 0.0971 0.0171 0.21136 0.0628v-1.1767h0.41128zm-0.41128-1.4224q-0.08-0.0685-0.1828-0.0971-0.0971-0.0343-0.26276-0.0343-0.30275 0-0.47412 0.27419-0.16565 0.27419-0.16565 0.84542 0 0.25134 0.0286 0.45698 0.0343 0.19993 0.0971 0.34845 0.0685 0.14852 0.17136 0.22849 0.10854 0.08 0.26277 0.08 0.41128 0 0.52553-0.48554z"/>
+   </g>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="event loop">
+    <path d="m-292.25-90.727q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21136-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.1371 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15424 0 0.30275-0.05712 0.15424-0.05712 0.23421-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-291.01-91.716 0.11425 0.56552h0.0114l0.10282-0.57694 0.50268-1.6623h0.43414l-0.9768 2.919h-0.19993l-0.99394-2.919h0.46841z"/>
+    <path d="m-287.77-90.727q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21136-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.1371 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15424 0 0.30275-0.05712 0.15424-0.05712 0.23421-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-285.66-90.533v-1.7422q0-0.42842-0.10282-0.61692-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.1371-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10854-0.15423 0.29133-0.26276 0.1885-0.10853 0.44556-0.10853 0.18279 0 0.31988 0.05141 0.14281 0.05141 0.23421 0.17708 0.0971 0.11996 0.1428 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-284.9-93.389h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61693v0.3713h-0.61693v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19421-0.02285 0.0857-0.02856 0.1828-0.06855l0.08 0.3256q-0.12567 0.06284-0.2799 0.09711-0.14852 0.03999-0.31418 0.03999-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-281.39-91.213q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11995 0 0.2799-0.06284l0.04 0.33131q-0.0743 0.0457-0.21135 0.07426-0.13138 0.02856-0.23992 0.02856-0.21706 0-0.35416-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41128z"/>
+    <path d="m-280.63-91.961q0-0.77116 0.26276-1.131 0.26848-0.36558 0.75973-0.36558 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367-0.26847 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24562-0.3713-0.24562-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.1828-0.22849-0.11424-0.08568-0.27419-0.08568-0.29132 0-0.44555 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-278.16-91.961q0-0.77116 0.26276-1.131 0.26848-0.36558 0.75973-0.36558 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367-0.26847 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24562-0.3713-0.24562-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10283-0.35987-0.0685-0.14852-0.18279-0.22849-0.11424-0.08568-0.27419-0.08568-0.29132 0-0.44555 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-275.57-93.389h0.29133l0.0628 0.30846h0.0228q0.21136-0.37701 0.66263-0.37701t0.67405 0.33702q0.22849 0.33702 0.22849 1.1025 0 0.35987-0.0743 0.6512-0.0743 0.28561-0.21136 0.49126-0.13709 0.19993-0.33702 0.30846-0.19422 0.10282-0.43413 0.10282-0.16566 0-0.26277-0.02285-0.0971-0.01714-0.21135-0.07997v1.1767h-0.41129zm0.41129 2.4049q0.08 0.06855 0.17708 0.10853 0.10282 0.03999 0.26848 0.03999 0.30275 0 0.47983-0.30846 0.17708-0.30846 0.17708-0.87969 0-0.23992-0.0343-0.43413-0.0286-0.19422-0.0971-0.33131-0.0685-0.14281-0.17708-0.21707-0.10282-0.07997-0.25705-0.07997-0.41699 0-0.53695 0.50839z"/>
+   </g>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="lws_sequencer">
+    <path d="m-350.73-80.685q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11996 0 0.2799-0.06283l0.04 0.33131q-0.0743 0.0457-0.21136 0.07426-0.13138 0.02856-0.23991 0.02856-0.21707 0-0.35416-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41128z"/>
+    <path d="m-348.31-82.861 0.50839 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38843-1.6566h0.38844l-0.75973 2.919h-0.23421l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.55981 1.8679h-0.2342l-0.78258-2.919h0.43984l0.43985 1.6623 0.0686 0.55409h0.0114l0.10282-0.56552 0.46841-1.6508z"/>
+    <path d="m-346.51-80.473q0.11425 0.06855 0.26848 0.11996 0.15994 0.0457 0.3256 0.0457 0.1885 0 0.31988-0.0914 0.13139-0.09711 0.13139-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11995-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11425l-0.10853 0.34274q-0.10854-0.05712-0.25134-0.0914-0.14281-0.03999-0.29133-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26847 0.16566 0.14281 0.08568 0.26277 0.20564 0.12567 0.11424 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15994 0.25134q-0.10282 0.10282-0.26277 0.16566-0.15423 0.06283-0.36558 0.06283-0.25134 0-0.43414-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+    <path d="m-344.88-79.205h1.9079v0.3713h-1.9079z"/>
+    <path d="m-342.68-80.473q0.11425 0.06855 0.26848 0.11996 0.15994 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.1428-0.07997-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.23421 0 0.40558 0.0457 0.17136 0.03999 0.29703 0.11425l-0.10853 0.34274q-0.10853-0.05712-0.25134-0.0914-0.14281-0.03999-0.29133-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11424 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15994 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15424 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.1828-0.0457-0.30847-0.12567z"/>
+    <path d="m-338.99-80.199q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.1371 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.23421-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-336.59-78.862h-0.41129v-1.3767h-0.0228q-0.0914 0.14281-0.2342 0.22278-0.13709 0.07997-0.35987 0.07997-0.45127 0-0.67405-0.35987-0.22278-0.36559-0.22278-1.1253 0-0.73688 0.29133-1.1139 0.29132-0.37701 0.84541-0.37701 0.23992 0 0.45698 0.05712 0.21707 0.05712 0.33132 0.11996zm-0.41129-3.5816q-0.15994-0.09711-0.45127-0.09711-0.29704 0-0.4684 0.27419-0.16566 0.27419-0.16566 0.8397 0 0.23992 0.0286 0.44556t0.0914 0.35987q0.0685 0.14852 0.17137 0.2342 0.10853 0.07997 0.26276 0.07997 0.21707 0 0.34274-0.12567t0.1885-0.36558z"/>
+    <path d="m-335.55-82.861v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11996 0 0.21135-0.0457 0.0971-0.05141 0.17137-0.13138 0.0743-0.07997 0.13138-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41128v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29133l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.2799 0.33131-0.18279 0.1371-0.45698 0.1371-0.18279 0-0.31989-0.0457-0.13709-0.0457-0.2342-0.16566-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+    <path d="m-331.74-80.199q-0.13709 0.12567-0.34844 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.1371 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-329.63-80.005v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.1371-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29703l0.0743 0.30275h0.0171q0.10853-0.15423 0.29133-0.26276 0.1885-0.10853 0.44555-0.10853 0.1828 0 0.31989 0.05141 0.14281 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-327.01-80.148q-0.14281 0.10853-0.3256 0.15994t-0.38272 0.05141q-0.27419 0-0.4627-0.10282-0.1885-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25705-1.1139 0.26277-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999 0.15995 0.03999 0.27419 0.10282l-0.11424 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984 0.0343 0.20564 0.11424 0.35987 0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31418 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21135-0.11996z"/>
+    <path d="m-324.95-80.199q-0.13709 0.12567-0.34844 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.1371 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.23421-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-324.3-82.861h0.29132l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.09711 0.31417-0.09711 0.13139 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26276-0.05141-0.1828 0-0.29704 0.10853-0.11425 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+   </g>
+  </g>
+  <path d="m-254-78.922h30.293v15.25h-30.293z" fill="#999"/>
+  <g transform="scale(.94592 1.0572)" dominant-baseline="auto" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="lws_vhost">
+   <path d="m-266.88-70.861q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11995 0 0.2799-0.06284l0.04 0.33131q-0.0743 0.0457-0.21135 0.07426-0.13138 0.02856-0.23992 0.02856-0.21706 0-0.35416-0.13138-0.13138-0.1371-0.13138-0.47412v-3.4559h0.41129z"/>
+   <path d="m-264.47-73.038 0.5084 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38843-1.6566h0.38844l-0.75974 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.2342l-0.78259-2.919h0.43985l0.43984 1.6623 0.0686 0.55409h0.0114l0.10282-0.56552 0.4684-1.6508z"/>
+   <path d="m-262.67-70.65q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11996-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.14281 0.08568 0.26276 0.20564 0.12567 0.11425 0.20565 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15995 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15423 0.06284-0.36559 0.06284-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-261.03-69.382h1.9079v0.3713h-1.9079z"/>
+   <path d="m-258.1-71.364 0.11425 0.56552h0.0114l0.10283-0.57694 0.50268-1.6623h0.43413l-0.9768 2.919h-0.19993l-0.99394-2.919h0.46841z"/>
+   <path d="m-255.1-70.182v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.14281-0.15994 0.14281-0.21707 0.35987v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23992 0.17137-0.09711 0.42271-0.09711 0.1885 0 0.3256 0.05141 0.14281 0.05141 0.2342 0.17708 0.0914 0.12567 0.1371 0.33702 0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+   <path d="m-254.18-71.61q0-0.77116 0.26277-1.131 0.26848-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367t-0.75402 0.35987q-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42843 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0685-0.14852-0.18279-0.22849-0.11424-0.08568-0.27419-0.08568-0.29132 0-0.44556 0.26848-0.14851 0.26848-0.14851 0.85684z"/>
+   <path d="m-251.64-70.65q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.1428-0.07997-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20565 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15995 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15423 0.06284-0.36559 0.06284-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-249.95-73.038h0.34845v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36558 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19421-0.02285 0.0857-0.02856 0.1828-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31418 0.03999-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+  </g>
+  <path d="m-289.99-53.117h17.739v15.25h-17.739z" fill="#b3b3b3"/>
+  <g transform="scale(.94592 1.0572)" dominant-baseline="auto" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="wsi">
+   <path d="m-304.13-47.763 0.5084 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38843-1.6566h0.38844l-0.75974 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.2342l-0.78259-2.919h0.43985l0.43984 1.6623 0.0685 0.55409h0.0114l0.10282-0.56552 0.4684-1.6508z"/>
+   <path d="m-302.33-45.375q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133t-0.20564-0.20564q-0.11996-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.14281 0.08568 0.26276 0.20564 0.12567 0.11424 0.20565 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15995 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15423 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-300.29-47.763h0.41128v2.8561h-0.41128zm-0.0743-0.86826q0-0.1371 0.0743-0.22278 0.08-0.08568 0.20564-0.08568 0.12567 0 0.20564 0.08568 0.0857 0.07997 0.0857 0.22278 0 0.13709-0.0857 0.21707-0.08 0.07426-0.20564 0.07426-0.12567 0-0.20564-0.07997-0.0743-0.07997-0.0743-0.21135z"/>
+  </g>
+  <path d="m-304.45-67.223c-9.1033 20.34-0.25477 39.141 20.741 26.536" fill="none" marker-end="url(#marker9266)" stroke="#000" stroke-dasharray="1.00661946, 1.00661946" stroke-width="1.0066"/>
+  <g transform="matrix(.51055 0 0 .5706 -305.23 -226.24)" clip-path="url(#clipPath32169)">
+   <g transform="matrix(.28118 0 0 .28118 -172.38 171.04)">
+    <path d="m508.9 300.55-8.6184 2.2398 8.6184 2.2398 8.6191-2.2398-8.6191-2.2398" fill="url(#linearGradient11357-7)"/>
+    <path d="m486.44 323.02-8.6187 2.2401 8.6187 2.2394 8.6191-2.2394-8.6191-2.2401" fill="url(#linearGradient11375-1)"/>
+    <path d="m461.73 325.26 2.2401 8.6191 2.2398-8.6191-2.2398-8.6187-2.2401 8.6187" fill="url(#linearGradient11735-6)"/>
+    <path d="m441.51 323.02-8.6184 2.2401 8.6184 2.2394 8.6191-2.2394-8.6191-2.2401" fill="url(#linearGradient11771-1)"/>
+    <path d="m439.27 347.72 2.2398 8.6187 2.2398-8.6187-2.2398-8.6191-2.2398 8.6191" fill="url(#linearGradient11789-0)"/>
+    <path d="m463.97 345.48-8.6187 2.2398 8.6187 2.2394 8.6191-2.2394-8.6191-2.2398" fill="url(#linearGradient11825-1)"/>
+    <path d="m441.51 367.95-8.6184 2.2398 8.6184 2.2398 8.6191-2.2398-8.6191-2.2398" fill="url(#linearGradient11843-3)"/>
+    <path d="m416.8 325.26 2.2401 8.6191 2.2398-8.6191-2.2398-8.6187-2.2401 8.6187" fill="url(#linearGradient12131-2)"/>
+    <path d="m419.04 345.48-8.6184 2.2398 8.6184 2.2394 8.6191-2.2394-8.6191-2.2398" fill="url(#linearGradient12221-8)"/>
+    <path d="m496.2 325.6-1.1352-2.9718-0.23354-0.043-0.88371-9.0135-1.1345-3.2318-5.0102-5.4543-8.4751 2.1654-2.3304-1.1324-0.66216-4.0499-1.1853-2.7326-9.1087-1.3797-5.0952 3.9751-9.7335 0.48119-1.8457-3.5553-10.056 2.3336-0.76764 4.1808-9.3772 3.991-5.9828-1.4192-8.4272 5.5316 0.47977 2.9556 1.1518 2.674-3.4844 3.8848-8.9213 1.9509-3.4087 7.6429 0.77996 3.4191 3.7976 7.1226-3.3158 2.3576 0.79442 3.15 6.8851 12.302 6.1288 7.4408 1.7872 1.893 9.1331-5.0839 0.77999 0.30869 4.6895 9.7973 2.105 2.0923 12.976 0.56338 4.0287-8.552 12.41-3.1591 6.2819 5.8833 11.521-6.7899 0.38136-2.6871-1.4111-7.5452 4.1247-4.4394 9.5934 0.31856 2.9065-8.7323 0.49283-3.1411-1.2425-13.301" fill="url(#linearGradient13921-1)"/>
+    <path d="m539.07 301.71-1.1871-5.485-8.7076-0.16264-0.81174-0.57538 0.12805-3.0371-0.60148-2.9259-8.7055-1.759-4.7082 3.2075-6.4696 0.37712-4.6122-2.6106-8.2169 2.7192-0.42051 2.8734 0.23918 2.7263-0.96732 0.82056-7.8765 0.9839-1.0171 5.2649-0.25012 3.3793 1.6602 12.531 0.96202 2.3816 5.232 0.35207 0.62689 5.8424 1.0664 2.5936 8.5707 3.2576 5.3344-4.3653 6.9286-0.0942 5.5442 4.3049 9.0685-3.5514 1.0096-2.7347 0.36195-6.0907 6.1436-0.66711 0.93733-2.5728 1.1899-13.418-0.45191-3.5655" fill="url(#linearGradient13939-6)"/>
+    <path d="m498.19 310.2 0.80433 2.6681-2.3096-3.6897-0.86572-2.7238 2.371 3.7454" fill="url(#linearGradient13957-9)"/>
+    <path d="m478.32 314.01-0.22225 2.8437-2.6286 4.6715 0.14993-2.7908 2.7009-4.7244" fill="url(#linearGradient13975-8)"/>
+    <path d="m527.96 305.12-0.79586 2.8914-2.4155 3.9853 0.71966-2.8056 2.4917-4.0711" fill="url(#linearGradient13993-9)"/>
+    <path d="m424.32 331.06 1.7064 2.388-3.9938-3.151-1.7776-2.4148 4.0651 3.1778" fill="url(#linearGradient14011-4)"/>
+    <path d="m497.22 297.86 0.97826 12.341-2.371-3.7454-1.0629-12.125 2.4557 3.5302" fill="url(#linearGradient14029-0)"/>
+    <path d="m497.61 294.84-0.39758 3.0208-2.4557-3.5302 0.42051-2.8734 2.4328 3.3828" fill="url(#linearGradient14047-1)"/>
+    <path d="m494.32 320.53 0.6604 2.3929-7.4651-0.50236-0.96202-2.3816 7.7668 0.49107" fill="url(#linearGradient14065-8)"/>
+    <path d="m476.34 301.87 1.9844 12.138-2.7009 4.7244-2.1897-12.284 2.9062-4.5787" fill="url(#linearGradient14083-3)"/>
+    <path d="m528.49 292.45-0.5334 12.671-2.4917 4.0711 0.41769-12.807 2.6074-3.9356" fill="url(#linearGradient14101-3)"/>
+    <path d="m496.98 323.64 0.89993 2.716-2.9005-3.4308-0.6604-2.3929 2.661 3.1076" fill="url(#linearGradient14119-8)"/>
+    <path d="m475.15 299.14 1.1853 2.7326-2.9062 4.5787-1.2054-2.8483 2.9263-4.463" fill="url(#linearGradient14137-5)"/>
+    <path d="m527.89 289.53 0.60148 2.9259-2.6074 3.9356-0.57679-3.067 2.5827-3.7945" fill="url(#linearGradient14155-3)"/>
+    <path d="m419.37 319.23 4.9551 11.836-4.0651-3.1778-5.0197-11.655 4.1296 2.9965" fill="url(#linearGradient14173-0)"/>
+    <path d="m418.85 316.17 0.51999 3.0632-4.1296-2.9965-0.47977-2.9556 4.0894 2.8889" fill="url(#linearGradient14191-5)"/>
+    <path d="m538.34 318.7-0.93733 2.5728-8.2106 0.89183 0.57467-2.5206 8.5732-0.94403" fill="url(#linearGradient14209-6)"/>
+    <path d="m493.05 307.84 1.2619 12.689-7.7668-0.49107-1.6602-12.531 8.165 0.33337" fill="url(#linearGradient14227-4)"/>
+    <path d="m495.21 326.49-0.49071 2.6843-7.1762 3.5754 0.26035-2.6049 7.4066-3.6548" fill="url(#linearGradient14245-6)"/>
+    <path d="m529.76 319.64-0.57467 2.5206-3.0706 3.6777 0.81597-2.8427 2.8293-3.3556" fill="url(#linearGradient14263-8)"/>
+    <path d="m493.17 304.41-0.115 3.4346-8.165-0.33337 0.25012-3.3793 8.0299 0.27799" fill="url(#linearGradient14281-5)"/>
+    <path d="m514.94 327.57 0.32597 2.4405-6.9286 0.0942 0.18274-2.4292 6.4198-0.10548" fill="url(#linearGradient14299-9)"/>
+    <path d="m416.14 347.69 1.8369 2.1466-8.1118 0.13264-2.0906-2.1223 8.3654-0.15698" fill="url(#linearGradient14317-6)"/>
+    <path d="m495.83 310.79 1.1458 12.848-2.661-3.1076-1.2619-12.689 2.7771 2.9485" fill="url(#linearGradient14335-3)"/>
+    <path d="m496.26 307.58-0.42475 3.2082-2.7771-2.9485 0.115-3.4346 3.0868 3.175" fill="url(#linearGradient14353-1)"/>
+    <path d="m539.52 305.28-1.1899 13.418-8.5732 0.94403 0.69709-13.297 9.066-1.0657" fill="url(#linearGradient14371-1)"/>
+    <path d="m530.46 306.34-0.69709 13.297-2.8293 3.3556 0.54646-13.36 2.9799-3.2918" fill="url(#linearGradient14389-0)"/>
+    <path d="m492.81 310.34 1.1345 3.2318-7.8535 3.6689-1.0968-3.3066 7.8158-3.5941" fill="url(#linearGradient14407-7)"/>
+    <path d="m530.17 302.78 0.29351 3.5662-2.9799 3.2918-0.62513-3.284 3.3115-3.574" fill="url(#linearGradient14425-3)"/>
+    <path d="m529.18 296.06 8.7076 0.16264 1.1871 5.485-8.9076 1.065-3.3115 3.574 4.1296 4.5985-9.8707 3.6368-6.0367-3.9783-7.5219 0.26952-5.8219 4.3106-9.2572-2.8956 3.7804-4.7103-3.0868-3.175-8.0299-0.27799 1.0171-5.2649 7.8765-0.9839 3.5814-3.0374-2.4328-3.3828 8.2169-2.7192 4.6122 2.6106 6.4696-0.37712 4.7082-3.2075 8.7055 1.759-2.5827 3.7945 3.8679 2.7439" fill="url(#linearGradient14443-6)"/>
+    <path d="m502.95 332.18 0.054 2.2864-8.5707-3.2576-1.0664-2.5936 9.5832 3.5648" fill="url(#linearGradient14461-7)"/>
+    <path d="m508.52 327.67-0.18274 2.4292-5.3344 4.3653-0.054-2.2864 5.5711-4.5082" fill="url(#linearGradient14479-0)"/>
+    <path d="m520.73 331.98 0.0878 2.3336-5.5442-4.3049-0.32597-2.4405 5.7824 4.4118" fill="url(#linearGradient14497-2)"/>
+    <path d="m530.89 328.03-1.0096 2.7347-9.0685 3.5514-0.0878-2.3336 10.166-3.9525" fill="url(#linearGradient14515-7)"/>
+    <path d="m539.07 301.71 0.45191 3.5655-9.066 1.0657-0.29351-3.5662 8.9076-1.065" fill="url(#linearGradient14533-4)"/>
+    <path d="m493.95 313.57 1.2668 12.913-7.4066 3.6548-1.7138-12.899 7.8535-3.6689" fill="url(#linearGradient14551-1)"/>
+    <path d="m514.79 314.27 0.15063 13.294-6.4198 0.10548-0.52387-13.185 6.7931-0.21484" fill="url(#linearGradient14569-3)"/>
+    <path d="m410.15 335.52 5.9891 12.171-8.3654 0.15698-6.4196-12.04 8.796-0.28752" fill="url(#linearGradient14587-5)"/>
+    <path d="m515.08 310.61-0.28292 3.6647-6.7931 0.21484-0.44591-3.61 7.5219-0.26952" fill="url(#linearGradient14605-7)"/>
+    <path d="m502.08 318.96 0.86783 13.221-9.5832-3.5648-1.3882-12.949 10.104 3.2921" fill="url(#linearGradient14623-8)"/>
+    <path d="m508 314.49 0.52387 13.185-5.5711 4.5082-0.86783-13.221 5.915-4.4718" fill="url(#linearGradient14641-5)"/>
+    <path d="m409.3 332.07 0.85019 3.4505-8.796 0.28752-0.77996-3.4191 8.7257-0.31891" fill="url(#linearGradient14659-8)"/>
+    <path d="m507.55 310.88 0.44591 3.61-5.915 4.4718-0.35278-3.7712 5.8219-4.3106" fill="url(#linearGradient14677-7)"/>
+    <path d="m501.73 315.19 0.35278 3.7712-10.104-3.2921 0.49354-3.3747 9.2572 2.8956" fill="url(#linearGradient14695-9)"/>
+    <path d="m486.09 317.24 1.7138 12.899 0.86431 6.7952-1.7424-13.105-0.83573-6.5892" fill="url(#linearGradient14713-2)"/>
+    <path d="m531.7 314.44-0.80716 13.588-10.166 3.9525 0.19721-13.522 10.776-4.0185" fill="url(#linearGradient14731-7)"/>
+    <path d="m520.92 318.46-0.19721 13.522-5.7824-4.4118-0.15063-13.294 6.1302 4.1843" fill="url(#linearGradient14749-8)"/>
+    <path d="m485 313.94 1.0968 3.3066 0.83573 6.5892-1.0806-2.8967-0.85196-6.9991" fill="url(#linearGradient14767-9)"/>
+    <path d="m494.24 348.44-0.19544 2.3361-9.5934-0.31856-0.15381-2.3178 9.9427 0.30022" fill="url(#linearGradient14785-6)"/>
+    <path d="m497.44 338.9-0.49283 3.1411-2.9065 8.7323 0.19544-2.3361 3.2039-9.5374" fill="url(#linearGradient14803-0)"/>
+    <path d="m521.11 314.59-0.18944 3.8707-6.1302-4.1843 0.28292-3.6647 6.0367 3.9783" fill="url(#linearGradient14821-3)"/>
+    <path d="m530.98 310.95 0.71579 3.489-10.776 4.0185 0.18944-3.8707 9.8707-3.6368" fill="url(#linearGradient14839-5)"/>
+    <path d="m482.15 334.87 2.1445 13.275-6.8809 7.3212-2.6854-13.277 7.4217-7.3187" fill="url(#linearGradient14857-1)"/>
+    <path d="m428.31 353.67 5.6755 12.727-8.8434-3.4579-6.0836-12.563 9.2516 3.2946" fill="url(#linearGradient14875-3)"/>
+    <path d="m481.3 331.09 0.8509 3.7733-7.4217 7.3187-1.5335-3.2865 8.1044-7.8056" fill="url(#linearGradient14893-0)"/>
+    <path d="m427.73 350.1 0.57608 3.5754-9.2516-3.2946-1.3349-3.7342 10.01 3.4533" fill="url(#linearGradient14911-5)"/>
+    <path d="m485 313.94 0.85196 6.9991 9.2156 1.6951-3.2875 8.5432-10.477-0.0808-8.1044 7.8056 4.4072 6.6626-12.848 6.9931-6.7179-5.6981-13.707 3.4011-4.7763 8.5979-14.21-0.28822 2.3904-8.4681-10.01-3.4533-10.22 5.2144-5.6694-6.5751 8.6048-6.1182-1.1409-7.1-8.7257 0.31891 3.4087-7.6429 8.9213-1.9509 5.9422-6.6255-4.0894-2.8889 8.4272-5.5316 5.9828 1.4192 9.3772-3.991 0.76764-4.1808 10.056-2.3336 1.8457 3.5553 9.7335-0.48119 5.0952-3.9751 9.1087 1.3797-2.9263 4.463 7.1042 3.4519 8.4751-2.1654 5.0102 5.4543-7.8158 3.5941" fill="url(#linearGradient14929-6)"/>
+    <path d="m484.3 348.14 0.15381 2.3178-7.2577 7.8119 0.22296-2.8085 6.8809-7.3212" fill="url(#linearGradient14947-0)"/>
+    <path d="m425.14 362.94 1.421 2.0433-9.1331 5.0839-1.7872-1.893 9.4992-5.2342" fill="url(#linearGradient14965-1)"/>
+    <path d="m433.99 366.4 1.9505 2.2959-9.373-3.7105-1.421-2.0433 8.8434 3.4579" fill="url(#linearGradient14983-6)"/>
+    <path d="m462.44 363.91 1.1144 2.1294-12.41 3.1591-0.49459-2.2962 11.79-2.9923" fill="url(#linearGradient15001-7)"/>
+    <path d="m492.72 335 1.5233 13.441-9.9427-0.30022-2.1445-13.275 10.564 0.1337" fill="url(#linearGradient15019-6)"/>
+    <path d="m496.2 325.6 1.2425 13.301-3.2039 9.5374-1.5233-13.441 3.4847-9.3976" fill="url(#linearGradient15037-9)"/>
+    <path d="m491.78 331.17 0.93804 3.8262-10.564-0.1337-0.8509-3.7733 10.477 0.0808" fill="url(#linearGradient15055-5)"/>
+    <path d="m495.07 322.63 1.1352 2.9718-3.4847 9.3976-0.93804-3.8262 3.2875-8.5432" fill="url(#linearGradient15073-2)"/>
+    <path d="m408.8 355.71 6.8481 12.47-6.1288-7.4408-6.8851-12.302 6.1658 7.2725" fill="url(#linearGradient15091-2)"/>
+    <path d="m419.06 350.38 6.0836 12.563-9.4992 5.2342-6.8481-12.47 10.264-5.3276" fill="url(#linearGradient15109-5)"/>
+    <path d="m468.92 369.93 0.91686 1.9872-6.2819-5.8833-1.1144-2.1294 6.4795 6.0254" fill="url(#linearGradient15127-3)"/>
+    <path d="m481.74 362.44-0.38136 2.6871-11.521 6.7899-0.91686-1.9872 12.819-7.4898" fill="url(#linearGradient15145-1)"/>
+    <path d="m446.44 375.68 0.67028 2.0666-12.976-0.56338-2.105-2.0923 14.41 0.58914" fill="url(#linearGradient15163-1)"/>
+    <path d="m450.65 366.9 0.49459 2.2962-4.0287 8.552-0.67028-2.0666 4.2044-8.7817" fill="url(#linearGradient15181-9)"/>
+    <path d="m407.5 351.86 1.2908 3.8474-6.1658-7.2725-0.79442-3.15 5.6694 6.5751" fill="url(#linearGradient15199-2)"/>
+    <path d="m417.72 346.65 1.3349 3.7342-10.264 5.3276-1.2908-3.8474 10.22-5.2144" fill="url(#linearGradient15217-3)"/>
+    <path d="m458.63 350.77 3.8104 13.133-11.79 2.9923-4.6447-12.976 12.624-3.1492" fill="url(#linearGradient15235-9)"/>
+    <path d="m458.04 346.86 0.58984 3.9186-12.624 3.1492-1.6725-3.6668 13.707-3.4011" fill="url(#linearGradient15253-3)"/>
+    <path d="m465.41 356.67 3.5056 13.258-6.4795-6.0254-3.8104-13.133 6.7843 5.8999" fill="url(#linearGradient15271-9)"/>
+    <path d="m441.28 362.74 5.1661 12.94-14.41-0.58914-6.096-12.736 15.34 0.38488" fill="url(#linearGradient15289-0)"/>
+    <path d="m446 353.92 4.6447 12.976-4.2044 8.7817-5.1661-12.94 4.7258-8.8173" fill="url(#linearGradient15307-1)"/>
+    <path d="m464.76 352.55 0.65617 4.1204-6.7843-5.8999-0.58984-3.9186 6.7179 5.6981" fill="url(#linearGradient15325-6)"/>
+    <path d="m439.55 358.85 1.723 3.8862-15.34-0.38488-0.59302-3.7895 14.21 0.28822" fill="url(#linearGradient15343-8)"/>
+    <path d="m444.33 350.26 1.6725 3.6668-4.7258 8.8173-1.723-3.8862 4.7763-8.5979" fill="url(#linearGradient15361-6)"/>
+    <path d="m522.23 299.69c0.2219 3.0783-4.476 5.9267-10.49 6.3585-6.0163 0.43215-11.071-1.7142-11.293-4.7925-0.22048-3.0808 4.475-5.927 10.49-6.3592 6.0138-0.43216 11.071 1.7131 11.292 4.7932" fill="url(#linearGradient15379-3)"/>
+    <path d="m519.78 301.18c0.17004 2.358-3.4272 4.5385-8.0335 4.8694-4.6062 0.33091-8.4772-1.3123-8.6473-3.6699-0.16969-2.359 3.4265-4.5388 8.0328-4.8701 4.6059-0.33091 8.4787 1.3123 8.648 3.6706" fill="url(#linearGradient15397-8)"/>
+    <path d="m471.26 316.95c2.2172 7.6101-6.7317 16.912-19.982 20.769-13.252 3.8559-25.789 0.81068-28.007-6.8023-2.2144-7.6165 6.7303-16.913 19.982-20.77 13.248-3.8559 25.789-0.81209 28.008 6.8037" fill="url(#linearGradient15415-7)"/>
+    <path d="m468 320.34c1.8552 6.3687-5.6324 14.151-16.719 17.378-11.088 3.2262-21.578 0.67804-23.434-5.6917-1.8528-6.3726 5.6307-14.151 16.719-17.379 11.084-3.2262 21.578-0.6798 23.434 5.6921" fill="url(#linearGradient15433-5)"/>
+    <path d="m464.21 320.95c1.4831 5.0666-4.5237 11.284-13.412 13.883-8.8896 2.5986-17.295 0.59655-18.779-4.4704-1.481-5.0701 4.523-11.284 13.412-13.883 8.8875-2.5978 17.296-0.59831 18.779 4.4708" fill="url(#linearGradient15451-4)"/>
+    <path d="m458.22 319.51c1.0167 3.4763-3.1037 7.7417-9.2022 9.5246-6.0992 1.7826-11.866 0.40958-12.884-3.067-1.0156-3.478 3.103-7.7414 9.2019-9.525 6.0971-1.7829 11.867-0.41028 12.884 3.0674" fill="url(#linearGradient15469-3)"/>
+    <path d="m451.64 321.44c0.41028 1.403-1.2527 3.1246-3.714 3.8442-2.462 0.71932-4.79 0.1651-5.2006-1.2375-0.40993-1.4041 1.2527-3.1249 3.7144-3.8449 2.4606-0.71966 4.7897-0.1658 5.2003 1.2383" fill="url(#linearGradient15487-8)"/>
+    <path d="m516.25 299.93c0.10266 1.4277-2.075 2.7488-4.862 2.9492-2.7887 0.20003-5.1315-0.7948-5.2345-2.2236-0.10265-1.4288 2.074-2.7485 4.862-2.9489 2.7873-0.20003 5.1315 0.79516 5.2345 2.2232" fill="url(#linearGradient15505-3)"/>
+    <path d="m513.24 300.14c0.0409 0.57573-0.83785 1.1088-1.9629 1.1899-1.1257 0.0808-2.0708-0.32103-2.1124-0.89747-0.0416-0.57644 0.83679-1.1091 1.9622-1.1903 1.1254-0.0804 2.0712 0.32067 2.1131 0.89782" fill="url(#linearGradient15523-5)"/>
+    <path d="m479.23 349.03 2.5086 13.414-12.819 7.4898-3.5056-13.258 13.816-7.645" fill="url(#linearGradient15541-2)"/>
+    <path d="m477.6 345.56 1.6242 3.4685-13.816 7.645-0.65617-4.1204 12.848-6.9931" fill="url(#linearGradient15559-0)"/>
+   </g>
+  </g>
+  <g transform="scale(.94592 1.0572)" dominant-baseline="auto" fill="#00f" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Sequencers exist in the event loop">
+   <path d="m-399.91-79.355q0.10854 0.07426 0.30275 0.14281 0.19993 0.06284 0.45127 0.06284 0.31989 0 0.51982-0.15423 0.19993-0.15994 0.19993-0.49697 0-0.22278-0.11425-0.38843-0.11424-0.16566-0.28561-0.30275-0.17137-0.14281-0.3713-0.27419-0.19422-0.13709-0.36558-0.29704-0.17137-0.16566-0.28562-0.37701-0.11424-0.21135-0.11424-0.50839 0-0.47983 0.28561-0.70832 0.29133-0.2342 0.75402-0.2342 0.28561 0 0.50839 0.05141t0.35988 0.13138l-0.1371 0.37701q-0.10282-0.06284-0.29704-0.11425-0.1885-0.05141-0.43984-0.05141-0.30846 0-0.45698 0.15423-0.14852 0.14852-0.14852 0.37701 0 0.19993 0.11424 0.35416 0.11425 0.15423 0.28562 0.29133t0.36558 0.2799q0.19993 0.1371 0.3713 0.30846t0.28561 0.38844q0.11425 0.21707 0.11425 0.5141 0 0.50268-0.29704 0.78829-0.29704 0.28561-0.8397 0.28561-0.34274 0-0.56552-0.06283-0.21706-0.06284-0.34845-0.14281z"/>
+   <path d="m-395.75-79.018q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19992 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.1371 0.07426 0.33702 0.07426 0.15424 0 0.30275-0.05712 0.15424-0.05712 0.23421-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+   <path d="m-393.35-77.681h-0.41129v-1.3767h-0.0228q-0.0914 0.14281-0.23421 0.22278-0.13709 0.07997-0.35987 0.07997-0.45127 0-0.67405-0.35987-0.22278-0.36558-0.22278-1.1253 0-0.73688 0.29133-1.1139 0.29132-0.37701 0.84541-0.37701 0.23992 0 0.45699 0.05712 0.21706 0.05712 0.33131 0.11996zm-0.41129-3.5816q-0.15994-0.09711-0.45127-0.09711-0.29703 0-0.4684 0.27419-0.16566 0.27419-0.16566 0.8397 0 0.23992 0.0286 0.44556 0.0286 0.20564 0.0914 0.35987 0.0686 0.14852 0.17137 0.2342 0.10853 0.07997 0.26276 0.07997 0.21707 0 0.34274-0.12567t0.1885-0.36559z"/>
+   <path d="m-392.32-81.68v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11996 0 0.21136-0.0457 0.0971-0.05141 0.17136-0.13138 0.0743-0.07997 0.13139-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41129v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29133l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.27991 0.33131-0.18279 0.13709-0.45698 0.13709-0.18279 0-0.31988-0.0457-0.1371-0.0457-0.23421-0.16566-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+   <path d="m-388.5-79.018q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.23421-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+   <path d="m-386.39-78.824v-1.7422q0-0.42842-0.10282-0.61692-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.1371-0.14852 0.13709-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10854-0.15423 0.29133-0.26276 0.1885-0.10853 0.44556-0.10853 0.18279 0 0.31988 0.05141 0.14281 0.05141 0.23421 0.17708 0.0971 0.11996 0.1428 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+   <path d="m-383.77-78.966q-0.14281 0.10853-0.3256 0.15994-0.18279 0.05141-0.38272 0.05141-0.27419 0-0.4627-0.10282-0.1885-0.10853-0.30846-0.30275-0.11424-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25706-1.1139 0.26276-0.38272 0.7483-0.38272 0.22278 0 0.38273 0.03999 0.15994 0.03999 0.27418 0.10282l-0.11424 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.4684 0.27419-0.15424 0.26848-0.15424 0.85113 0 0.2342 0.0343 0.43984t0.11424 0.35987q0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31418 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21135-0.11996z"/>
+   <path d="m-381.71-79.018q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.32559-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19992 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15424 0 0.30275-0.05712 0.15424-0.05712 0.23421-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+   <path d="m-381.06-81.68h0.29132l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.09711 0.31418-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26276-0.05141-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+   <path d="m-379.55-79.292q0.11424 0.06855 0.26848 0.11996 0.15994 0.0457 0.32559 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19992-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.14281 0.08568 0.26276 0.20564 0.12567 0.11425 0.20565 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15995 0.25134-0.10282 0.10282-0.26276 0.16566-0.15423 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-374.65-79.018q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+   <path d="m-373.52-80.286-0.75402-1.3938h0.49126l0.42271 0.81686 0.11424 0.31989 0.11996-0.31989 0.43413-0.81686h0.45127l-0.75973 1.3709 0.80543 1.4852h-0.46841l-0.47983-0.89683-0.12567-0.34274-0.13138 0.34274-0.47983 0.89683h-0.45127z"/>
+   <path d="m-371.68-81.68h0.41128v2.8561h-0.41128zm-0.0743-0.86827q0-0.13709 0.0743-0.22278 0.08-0.08568 0.20564-0.08568 0.12567 0 0.20564 0.08568 0.0857 0.07997 0.0857 0.22278 0 0.1371-0.0857 0.21707-0.08 0.07426-0.20564 0.07426-0.12567 0-0.20564-0.07997-0.0743-0.07997-0.0743-0.21135z"/>
+   <path d="m-370.55-79.292q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.1428-0.07997-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.23421 0 0.40558 0.0457 0.17136 0.03999 0.29703 0.11424l-0.10853 0.34274q-0.10853-0.05712-0.25134-0.0914-0.14281-0.03999-0.29132-0.03999-0.20565 0-0.30276 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15994 0.25134-0.10282 0.10282-0.26276 0.16566-0.15424 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-368.86-81.68h0.34845v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36558 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31418 0.03999-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+   <path d="m-399.84-74.539h0.41128v2.8561h-0.41128zm-0.0743-0.86827q0-0.13709 0.0743-0.22278 0.08-0.08569 0.20564-0.08569 0.12567 0 0.20564 0.08569 0.0857 0.07997 0.0857 0.22278 0 0.1371-0.0857 0.21707-0.08 0.07426-0.20564 0.07426-0.12567 0-0.20564-0.07997-0.0743-0.07997-0.0743-0.21135z"/>
+   <path d="m-397.2-71.683v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.2285 0-0.37701 0.1371-0.14852 0.13709-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29703l0.0743 0.30275h0.0171q0.10853-0.15423 0.29133-0.26276 0.1885-0.10853 0.44555-0.10853 0.1828 0 0.31989 0.05141 0.14281 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+   <path d="m-395.23-74.539h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36558 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06284-0.2799 0.09711-0.14852 0.03999-0.31417 0.03999-0.28562 0-0.41129-0.18279-0.11995-0.1885-0.11995-0.6055v-1.7594h-0.34845z"/>
+   <path d="m-391.91-71.683v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.14281-0.15995 0.14281-0.21707 0.35987v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23992 0.17137-0.09711 0.42271-0.09711 0.1885 0 0.3256 0.05141 0.1428 0.05141 0.2342 0.17708t0.13709 0.33702q0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+   <path d="m-389.15-71.877q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44555 0.06855-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+   <path d="m-385.58-71.877q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44555 0.06855-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+   <path d="m-384.34-72.866 0.11424 0.56552h0.0114l0.10282-0.57694 0.50268-1.6623h0.43413l-0.9768 2.919h-0.19993l-0.99393-2.919h0.4684z"/>
+   <path d="m-381.11-71.877q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44555 0.06855-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15994 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.1828 0.62835z"/>
+   <path d="m-379-71.683v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35417-0.19422-0.22849 0-0.37701 0.1371-0.14851 0.13709-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.05141 0.14281 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+   <path d="m-378.23-74.539h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61693v0.3713h-0.61693v1.7023q0 0.25134 0.0571 0.36558 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06284-0.2799 0.09711-0.14852 0.03999-0.31417 0.03999-0.28562 0-0.41129-0.18279-0.11995-0.1885-0.11995-0.6055v-1.7594h-0.34845z"/>
+   <path d="m-374.72-72.363q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11996 0 0.2799-0.06284l0.04 0.33131q-0.0743 0.0457-0.21135 0.07426-0.13139 0.02856-0.23992 0.02856-0.21707 0-0.35416-0.13138-0.13138-0.1371-0.13138-0.47412v-3.4559h0.41128z"/>
+   <path d="m-373.96-73.111q0-0.77116 0.26277-1.131 0.26847-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367t-0.75402 0.35987q-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10283 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0685-0.14852-0.18279-0.22849-0.11425-0.08568-0.27419-0.08568-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+   <path d="m-371.49-73.111q0-0.77116 0.26277-1.131 0.26847-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367t-0.75402 0.35987q-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11424 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14851-0.26848 0.14851-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.08568-0.27419-0.08568-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+   <path d="m-368.9-74.539h0.29133l0.0628 0.30846h0.0229q0.21135-0.37701 0.66262-0.37701t0.67405 0.33702q0.22849 0.33702 0.22849 1.1025 0 0.35987-0.0743 0.6512-0.0743 0.28561-0.21135 0.49126-0.1371 0.19993-0.33702 0.30846-0.19422 0.10282-0.43414 0.10282-0.16565 0-0.26276-0.02285-0.0971-0.01714-0.21136-0.07997v1.1767h-0.41128zm0.41128 2.4049q0.08 0.06855 0.17708 0.10853 0.10283 0.03998 0.26848 0.03998 0.30275 0 0.47983-0.30846 0.17708-0.30846 0.17708-0.87969 0-0.23992-0.0343-0.43413-0.0286-0.19422-0.0971-0.33131-0.0685-0.14281-0.17708-0.21707-0.10282-0.07997-0.25705-0.07997-0.417 0-0.53696 0.50839z"/>
+  </g>
+  <path d="m-312.92-79.119h17.739v15.25h-17.739z" fill="#0f0"/>
+  <g>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="callback">
+    <path d="m-328.31-69.645q-0.14281 0.10853-0.3256 0.15994-0.1828 0.05141-0.38273 0.05141-0.27418 0-0.46269-0.10282-0.1885-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25705-1.1139 0.26277-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999 0.15995 0.03999 0.27419 0.10282l-0.11424 0.35987q-0.2285-0.13138-0.49697-0.13138-0.30847 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984t0.11425 0.35987q0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31418 0.08568 0.14851 0 0.27418-0.05141 0.13139-0.05141 0.21136-0.11996z"/>
+    <path d="m-327.89-72.187q0.16566-0.10282 0.39986-0.15994 0.23992-0.05712 0.50268-0.05712 0.23992 0 0.38272 0.07426 0.14852 0.06855 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31418l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44555 0.10853-0.30847 0-0.5084-0.21135-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.0457 0.46841-0.0457 0.0571 0 0.11425 0 0.0571 0 0.11995 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698-0.0971-0.13138-0.35416-0.13138-0.15994 0-0.34845 0.05141-0.1885 0.0457-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11425-0.0057-0.0571-0.0057-0.11425-0.0057-0.13709 0-0.26847 0.02285t-0.23421 0.07997q-0.10282 0.05712-0.16565 0.15423-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11424 0.12567 0.29133 0.12567 0.23991 0 0.37129-0.11424 0.13139-0.11425 0.1828-0.25134z"/>
+    <path d="m-325.13-70.182q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11996 0 0.2799-0.06284l0.04 0.33131q-0.0743 0.0457-0.21136 0.07426-0.13138 0.02856-0.23991 0.02856-0.21707 0-0.35416-0.13138-0.13138-0.1371-0.13138-0.47412v-3.4559h0.41128z"/>
+    <path d="m-323.81-70.182q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11996 0 0.2799-0.06284l0.04 0.33131q-0.0743 0.0457-0.21136 0.07426-0.13138 0.02856-0.23991 0.02856-0.21707 0-0.35417-0.13138-0.13138-0.1371-0.13138-0.47412v-3.4559h0.41129z"/>
+    <path d="m-322.92-73.501h0.41128v1.3595h0.0171q0.2342-0.28561 0.62264-0.28561 0.43984 0 0.65691 0.34845 0.22278 0.34845 0.22278 1.1025 0 0.77116-0.29704 1.1482-0.29133 0.37701-0.82828 0.37701-0.26277 0-0.47983-0.05712-0.21707-0.06284-0.3256-0.14281zm0.41128 3.5816q0.08 0.0457 0.19422 0.07426 0.11996 0.02285 0.25134 0.02285 0.29704 0 0.46841-0.2799 0.17708-0.28561 0.17708-0.87398 0-0.24563-0.0343-0.43984-0.0286-0.19993-0.0971-0.34274-0.0628-0.14281-0.17136-0.21707-0.10283-0.07997-0.25134-0.07997-0.20565 0-0.34274 0.12567-0.13138 0.11996-0.19422 0.33131z"/>
+    <path d="m-320.47-72.187q0.16566-0.10282 0.39986-0.15994 0.23992-0.05712 0.50268-0.05712 0.23992 0 0.38272 0.07426 0.14852 0.06855 0.2285 0.19422 0.0857 0.11996 0.10853 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31418l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44555 0.10853-0.30847 0-0.5084-0.21135-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.0457 0.46841-0.0457 0.0571 0 0.11425 0 0.0571 0 0.11995 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698-0.0971-0.13138-0.35416-0.13138-0.15994 0-0.34845 0.05141-0.1885 0.0457-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11425-0.0057-0.0571-0.0057-0.11424-0.0057-0.1371 0-0.26848 0.02285t-0.2342 0.07997q-0.10283 0.05712-0.16566 0.15423-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11425 0.12567 0.29133 0.12567 0.23991 0 0.37129-0.11424 0.13139-0.11425 0.1828-0.25134z"/>
+    <path d="m-316.58-69.645q-0.14281 0.10853-0.3256 0.15994-0.18279 0.05141-0.38272 0.05141-0.27419 0-0.46269-0.10282-0.18851-0.10853-0.30847-0.30275-0.11424-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25706-1.1139 0.26276-0.38272 0.7483-0.38272 0.22278 0 0.38273 0.03999 0.15994 0.03999 0.27419 0.10282l-0.11425 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.4684 0.27419-0.15424 0.26848-0.15424 0.85113 0 0.2342 0.0343 0.43984t0.11424 0.35987q0.08 0.14852 0.20565 0.23992 0.12567 0.08568 0.31417 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21135-0.11996z"/>
+    <path d="m-315.5-70.788h-0.21136v1.2853h-0.41128v-3.9986h0.41128v2.4334l0.18851-0.07997 0.66833-1.211h0.47412l-0.67405 1.1539-0.19993 0.18279 0.23421 0.22278 0.73688 1.2967h-0.49697z"/>
+   </g>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" fill="#00f" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Sequencers may create one or more wsi and synchronize with events on them">
+    <path d="m-383.41-52.29q0.10854 0.07426 0.30275 0.14281 0.19993 0.06283 0.45127 0.06283 0.31989 0 0.51982-0.15423 0.19993-0.15994 0.19993-0.49697 0-0.22278-0.11425-0.38843-0.11424-0.16566-0.28561-0.30275-0.17137-0.14281-0.3713-0.27419-0.19421-0.13709-0.36558-0.29704-0.17137-0.16566-0.28562-0.37701-0.11424-0.21135-0.11424-0.50839 0-0.47983 0.28561-0.70832 0.29133-0.2342 0.75402-0.2342 0.28562 0 0.50839 0.05141 0.22278 0.05141 0.35988 0.13138l-0.1371 0.37701q-0.10282-0.06283-0.29704-0.11425-0.1885-0.05141-0.43984-0.05141-0.30846 0-0.45698 0.15423-0.14852 0.14852-0.14852 0.37701 0 0.19993 0.11424 0.35416 0.11425 0.15423 0.28562 0.29133 0.17137 0.13709 0.36558 0.2799 0.19993 0.1371 0.3713 0.30846t0.28561 0.38844q0.11425 0.21707 0.11425 0.5141 0 0.50268-0.29704 0.78829-0.29704 0.28561-0.8397 0.28561-0.34274 0-0.56552-0.06283-0.21706-0.06283-0.34845-0.14281z"/>
+    <path d="m-379.26-51.953q-0.1371 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-376.86-50.616h-0.41128v-1.3767h-0.0228q-0.0914 0.14281-0.23421 0.22278-0.13709 0.07997-0.35987 0.07997-0.45127 0-0.67405-0.35987-0.22278-0.36558-0.22278-1.1253 0-0.73688 0.29133-1.1139 0.29132-0.37701 0.84542-0.37701 0.23991 0 0.45698 0.05712 0.21706 0.05712 0.33131 0.11996zm-0.41128-3.5816q-0.15995-0.09711-0.45127-0.09711-0.29704 0-0.46841 0.27419-0.16566 0.27419-0.16566 0.8397 0 0.23992 0.0286 0.44556 0.0286 0.20564 0.0914 0.35987 0.0686 0.14852 0.17137 0.2342 0.10853 0.07997 0.26276 0.07997 0.21707 0 0.34274-0.12567t0.18851-0.36559z"/>
+    <path d="m-375.82-54.615v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11996 0 0.21136-0.0457 0.0971-0.05141 0.17137-0.13138 0.0743-0.07997 0.13138-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41129v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29133l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.2799 0.33131-0.1828 0.13709-0.45699 0.13709-0.18279 0-0.31988-0.0457-0.1371-0.0457-0.23421-0.16566-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+    <path d="m-372.01-51.953q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15424 0 0.30275-0.05712 0.15424-0.05712 0.23421-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-369.9-51.759v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.1371-0.14852 0.13709-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10854-0.15423 0.29133-0.26276 0.1885-0.10853 0.44556-0.10853 0.18279 0 0.31988 0.05141 0.14281 0.05141 0.23421 0.17708 0.0971 0.11996 0.1428 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-367.27-51.902q-0.14281 0.10853-0.3256 0.15994-0.18279 0.05141-0.38272 0.05141-0.27419 0-0.46269-0.10282-0.18851-0.10853-0.30847-0.30275-0.11424-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25706-1.1139 0.26276-0.38272 0.7483-0.38272 0.22278 0 0.38273 0.03999 0.15994 0.03999 0.27419 0.10282l-0.11425 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.4684 0.27419-0.15424 0.26848-0.15424 0.85113 0 0.2342 0.0343 0.43984t0.11424 0.35987q0.08 0.14852 0.20565 0.23992 0.12567 0.08568 0.31417 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21135-0.11996z"/>
+    <path d="m-365.21-51.953q-0.1371 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-364.57-54.615h0.29132l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13139-0.09711 0.31418-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14851-0.05141-0.26276-0.05141-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+    <path d="m-363.05-52.227q0.11424 0.06855 0.26848 0.11996 0.15994 0.0457 0.3256 0.0457 0.1885 0 0.31988-0.0914 0.13139-0.09711 0.13139-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10853 0.34274q-0.10854-0.05712-0.25134-0.0914-0.14281-0.03999-0.29133-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.14281 0.08568 0.26277 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15995 0.25134-0.10282 0.10282-0.26276 0.16566-0.15423 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+    <path d="m-358.52-51.759v-1.6965q0-0.22849-0.0171-0.38843-0.0114-0.16566-0.0571-0.26848t-0.12567-0.14852q-0.08-0.05141-0.21135-0.05141-0.19422 0-0.33131 0.15423-0.13138 0.14852-0.1828 0.34274v2.0564h-0.41128v-2.8561h0.29133l0.0743 0.30275h0.0171q0.11996-0.16566 0.28562-0.26848 0.16565-0.10282 0.42271-0.10282 0.21706 0 0.35416 0.09711 0.1428 0.0914 0.22277 0.33131 0.10283-0.19993 0.29133-0.31418 0.19422-0.11425 0.42271-0.11425 0.1885 0 0.31989 0.05141 0.13709 0.0457 0.21706 0.17137 0.0857 0.11996 0.12567 0.3256 0.04 0.19993 0.04 0.50839v1.8679h-0.41129v-1.8165q0-0.3713-0.0742-0.55409-0.0686-0.18279-0.3256-0.18279-0.21707 0-0.34845 0.1371-0.12567 0.13138-0.17708 0.35987v2.0564z"/>
+    <path d="m-356.17-54.444q0.16565-0.10282 0.39986-0.15994 0.23991-0.05712 0.50268-0.05712 0.23991 0 0.38272 0.07426 0.14852 0.06855 0.22849 0.19422 0.0857 0.11996 0.10853 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16566 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19422-0.21707-0.19422-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.23421-0.26848 0.15423-0.10282 0.35987-0.14281 0.21135-0.0457 0.46841-0.0457 0.0571 0 0.11424 0 0.0571 0 0.11996 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698-0.0971-0.13138-0.35416-0.13138-0.15995 0-0.34845 0.05141-0.18851 0.0457-0.31418 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11424-0.0057-0.0571-0.0057-0.11425-0.0057-0.13709 0-0.26848 0.02285-0.13138 0.02285-0.2342 0.07997t-0.16566 0.15423q-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11425 0.12567 0.29133 0.12567 0.23992 0 0.3713-0.11424 0.13138-0.11425 0.18279-0.25134z"/>
+    <path d="m-353.15-52.77 0.11996 0.55409h0.0286l0.0857-0.55409 0.43413-1.8451h0.41699l-0.67976 2.5648q-0.08 0.30846-0.15994 0.57694-0.08 0.26848-0.17708 0.46269-0.0914 0.19993-0.21135 0.30846-0.11425 0.11424-0.27419 0.11424-0.15995 0-0.2799-0.05141l0.0685-0.38844q0.08 0.02856 0.15995 0.01143 0.08-0.01714 0.14852-0.09711 0.0743-0.07997 0.13138-0.23992 0.0628-0.15423 0.10853-0.40557l-0.92539-2.8561h0.46841z"/>
+    <path d="m-349.08-51.902q-0.1428 0.10853-0.3256 0.15994-0.18279 0.05141-0.38272 0.05141-0.27419 0-0.46269-0.10282-0.18851-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25705-1.1139 0.26276-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999t0.27419 0.10282l-0.11425 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.4684 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984t0.11425 0.35987q0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31417 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21136-0.11996z"/>
+    <path d="m-348.62-54.615h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13139-0.09711 0.31418-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26277-0.05141-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+    <path d="m-345.35-51.953q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+    <path d="m-344.74-54.444q0.16566-0.10282 0.39986-0.15994 0.23991-0.05712 0.50268-0.05712 0.23991 0 0.38272 0.07426 0.14852 0.06855 0.22849 0.19422 0.0857 0.11996 0.10853 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16566 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19422-0.21707-0.19422-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.23421-0.26848 0.15423-0.10282 0.35987-0.14281 0.21135-0.0457 0.46841-0.0457 0.0571 0 0.11424 0 0.0571 0 0.11996 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698-0.0971-0.13138-0.35416-0.13138-0.15995 0-0.34845 0.05141-0.18851 0.0457-0.31418 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11424-0.0057-0.0571-0.0057-0.11425-0.0057-0.13709 0-0.26848 0.02285-0.13138 0.02285-0.2342 0.07997t-0.16565 0.15423q-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10854 0.35416 0.11424 0.12567 0.29132 0.12567 0.23992 0 0.3713-0.11424 0.13138-0.11425 0.18279-0.25134z"/>
+    <path d="m-342.71-54.615h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36558 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31417 0.03999-0.28562 0-0.41129-0.18279-0.11995-0.1885-0.11995-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-339.18-51.953q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+    <path d="m-383.54-46.047q0-0.77116 0.26277-1.131 0.26847-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367t-0.75402 0.35987q-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.08568-0.27419-0.08568-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-379.48-44.619v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.1371-0.14852 0.13709-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.05141 0.1428 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-376.73-44.813q-0.1371 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-374.99-46.047q0-0.77116 0.26276-1.131 0.26848-0.36559 0.75974-0.36559 0.52552 0 0.77115 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26847 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11424 0.07997 0.27419 0.07997 0.29703 0 0.44555-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.1828-0.22849-0.11424-0.08568-0.27418-0.08568-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-372.4-47.475h0.29132l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.09711 0.31418-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26276-0.05141-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+    <path d="m-368.28-44.619v-1.6965q0-0.22849-0.0171-0.38844-0.0114-0.16566-0.0571-0.26848-0.0457-0.10282-0.12567-0.14852-0.08-0.05141-0.21135-0.05141-0.19422 0-0.33132 0.15423-0.13138 0.14852-0.18279 0.34274v2.0564h-0.41128v-2.8561h0.29132l0.0743 0.30275h0.0171q0.11996-0.16566 0.28561-0.26848 0.16566-0.10282 0.42271-0.10282 0.21707 0 0.35416 0.09711 0.14281 0.0914 0.22278 0.33131 0.10282-0.19993 0.29133-0.31418 0.19421-0.11425 0.4227-0.11425 0.18851 0 0.31989 0.05141 0.1371 0.0457 0.21707 0.17137 0.0857 0.11996 0.12567 0.3256 0.04 0.19993 0.04 0.50839v1.8679h-0.41128v-1.8165q0-0.3713-0.0743-0.55409-0.0686-0.18279-0.3256-0.18279-0.21706 0-0.34845 0.1371-0.12567 0.13138-0.17708 0.35987v2.0564z"/>
+    <path d="m-366.03-46.047q0-0.77116 0.26277-1.131 0.26848-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367t-0.75402 0.35987q-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42843 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0685-0.14852-0.18279-0.22849-0.11424-0.08568-0.27419-0.08568-0.29132 0-0.44556 0.26848-0.14851 0.26848-0.14851 0.85684z"/>
+    <path d="m-363.44-47.475h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.09711 0.31417-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26277-0.05141-0.18279 0-0.29703 0.10853-0.11425 0.10282-0.14852 0.2799v2.1021h-0.41129z"/>
+    <path d="m-360.17-44.813q-0.13709 0.12567-0.34845 0.19422-0.21135 0.06855-0.44555 0.06855-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-356.78-47.475 0.50839 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38844-1.6566h0.38843l-0.75973 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.23421l-0.78258-2.919h0.43985l0.43984 1.6623 0.0686 0.55409h0.0114l0.10282-0.56552 0.46841-1.6508z"/>
+    <path d="m-354.98-45.087q0.11425 0.06855 0.26848 0.11996 0.15994 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.14281-0.07997-0.26848-0.1885-0.11995-0.10853-0.19992-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.23421 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11425l-0.10853 0.34274q-0.10853-0.05712-0.25134-0.0914-0.14281-0.03999-0.29133-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15994 0.25134q-0.10282 0.10282-0.26277 0.16566-0.15423 0.06283-0.36558 0.06283-0.25134 0-0.43413-0.05141-0.1828-0.0457-0.30847-0.12567z"/>
+    <path d="m-352.94-47.475h0.41129v2.8561h-0.41129zm-0.0743-0.86827q0-0.13709 0.0743-0.22278 0.08-0.08568 0.20564-0.08568 0.12567 0 0.20565 0.08568 0.0857 0.07997 0.0857 0.22278 0 0.1371-0.0857 0.21707-0.08 0.07426-0.20565 0.07426-0.12567 0-0.20564-0.07997-0.0743-0.07997-0.0743-0.21135z"/>
+    <path d="m-350.58-47.303q0.16565-0.10282 0.39985-0.15994 0.23992-0.05712 0.50268-0.05712 0.23992 0 0.38273 0.07426 0.14852 0.06855 0.22849 0.19422 0.0857 0.11996 0.10853 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61692 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31418l-0.0971-0.33702h-0.0228q-0.0857 0.14852-0.25134 0.25705-0.16566 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19422-0.21707-0.19422-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15424-0.10282 0.35988-0.14281 0.21135-0.0457 0.4684-0.0457 0.0571 0 0.11425 0 0.0571 0 0.11996 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698-0.0971-0.13138-0.35416-0.13138-0.15994 0-0.34844 0.05141-0.18851 0.0457-0.31418 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11425-0.0057-0.0571-0.0057-0.11424-0.0057-0.1371 0-0.26848 0.02285t-0.2342 0.07997-0.16566 0.15423q-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11425 0.12567 0.29133 0.12567 0.23991 0 0.3713-0.11424 0.13138-0.11425 0.18279-0.25134z"/>
+    <path d="m-346.8-44.619v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.1371-0.14852 0.13709-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0742 0.30275h0.0171q0.10853-0.15423 0.29133-0.26276 0.1885-0.10853 0.44555-0.10853 0.1828 0 0.31989 0.05141 0.14281 0.05141 0.23421 0.17708 0.0971 0.11996 0.1428 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-343.96-45.601q0 0.29133 6e-3 0.53124 6e-3 0.2342 0.04 0.46269h-0.2799l-0.0914-0.34274h-0.0229q-0.08 0.17137-0.25134 0.28561-0.17137 0.11424-0.41128 0.11424-0.4627 0-0.69119-0.35987-0.22278-0.35987-0.22278-1.131 0-0.73117 0.27419-1.1082 0.2799-0.37701 0.76545-0.37701 0.16565 0 0.26276 0.02285 0.0971 0.01714 0.21136 0.06283v-1.1767h0.41128zm-0.41128-1.4224q-0.08-0.06855-0.1828-0.09711-0.0971-0.03427-0.26276-0.03427-0.30275 0-0.47412 0.27419-0.16566 0.27419-0.16566 0.84542 0 0.25134 0.0286 0.45698 0.0343 0.19993 0.0971 0.34845 0.0686 0.14852 0.17137 0.22849 0.10854 0.07997 0.26277 0.07997 0.41128 0 0.52553-0.48554z"/>
+    <path d="m-383.46-37.947q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.1428-0.07997-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.23421 0 0.40558 0.0457 0.17136 0.03999 0.29703 0.11425l-0.10853 0.34274q-0.10853-0.05712-0.25134-0.0914-0.14281-0.03999-0.29132-0.03999-0.20565 0-0.30276 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11424 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15994 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15424 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+    <path d="m-380.77-38.489 0.11996 0.55409h0.0286l0.0857-0.55409 0.43413-1.8451h0.417l-0.67976 2.5648q-0.08 0.30846-0.15994 0.57694-0.08 0.26848-0.17708 0.46269-0.0914 0.19993-0.21136 0.30846-0.11424 0.11425-0.27419 0.11425-0.15994 0-0.2799-0.05141l0.0686-0.38843q0.08 0.02856 0.15994 0.01142 0.08-0.01714 0.14852-0.09711 0.0743-0.07997 0.13138-0.23992 0.0628-0.15423 0.10854-0.40557l-0.92539-2.8561h0.4684z"/>
+    <path d="m-377.86-37.478v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.1371-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29703l0.0743 0.30275h0.0171q0.10853-0.15423 0.29133-0.26276 0.1885-0.10853 0.44555-0.10853 0.1828 0 0.31989 0.05141 0.14281 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-375.24-37.621q-0.14281 0.10853-0.3256 0.15994-0.18279 0.05141-0.38272 0.05141-0.27419 0-0.4627-0.10282-0.1885-0.10853-0.30846-0.30275-0.11424-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61692 0-0.73117 0.25705-1.1139 0.26277-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999 0.15995 0.03999 0.27419 0.10282l-0.11424 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984 0.0343 0.20564 0.11424 0.35987 0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31418 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21135-0.11996z"/>
+    <path d="m-373.32-37.478v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.14281-0.15995 0.14281-0.21707 0.35987v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23992 0.17137-0.09711 0.42271-0.09711 0.1885 0 0.3256 0.05141 0.1428 0.05141 0.2342 0.17708t0.13709 0.33702q0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+    <path d="m-372.28-40.334h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20565-0.25705 0.13138-0.09711 0.31417-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26277-0.05141-0.18279 0-0.29703 0.10853-0.11425 0.10282-0.14852 0.2799v2.1021h-0.41129z"/>
+    <path d="m-370.84-38.906q0-0.77116 0.26277-1.131 0.26847-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367t-0.75402 0.35987q-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.08568-0.27419-0.08568-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-366.79-37.478v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.1371-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.05141 0.1428 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-365.67-40.334h0.41129v2.8561h-0.41129zm-0.0742-0.86826q0-0.1371 0.0742-0.22278 0.08-0.08568 0.20565-0.08568 0.12567 0 0.20564 0.08568 0.0857 0.07997 0.0857 0.22278 0 0.13709-0.0857 0.21707-0.08 0.07426-0.20564 0.07426-0.12567 0-0.20565-0.07997-0.0742-0.07997-0.0742-0.21135z"/>
+    <path d="m-364.65-37.849 1.0625-1.8679 0.19993-0.24563h-1.2624v-0.3713h1.668v0.3713l-1.0682 1.885-0.19422 0.22849h1.2624v0.3713h-1.668z"/>
+    <path d="m-360.81-37.672q-0.1371 0.12567-0.34845 0.19422-0.21136 0.06855-0.44556 0.06855-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.32559-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19992 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556t0.12567 0.34274q0.0857 0.1371 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15424 0 0.30275-0.05712 0.15424-0.05712 0.23421-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-357.42-40.334 0.5084 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38843-1.6566h0.38843l-0.75973 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.2342l-0.78259-2.919h0.43985l0.43984 1.6623 0.0686 0.55409h0.0114l0.10282-0.56552 0.4684-1.6508z"/>
+    <path d="m-355.51-40.334h0.41129v2.8561h-0.41129zm-0.0743-0.86826q0-0.1371 0.0743-0.22278 0.08-0.08568 0.20564-0.08568 0.12567 0 0.20565 0.08568 0.0857 0.07997 0.0857 0.22278 0 0.13709-0.0857 0.21707-0.08 0.07426-0.20565 0.07426-0.12567 0-0.20564-0.07997-0.0743-0.07997-0.0743-0.21135z"/>
+    <path d="m-354.62-40.334h0.34844v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31417 0.03999-0.28562 0-0.41129-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34844z"/>
+    <path d="m-351.3-37.478v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.14281-0.15995 0.14281-0.21707 0.35987v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23992 0.17137-0.09711 0.42271-0.09711 0.1885 0 0.3256 0.05141 0.1428 0.05141 0.2342 0.17708t0.13709 0.33702q0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+    <path d="m-347.33-37.672q-0.1371 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556t0.12567 0.34274q0.0857 0.1371 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+    <path d="m-346.09-38.661 0.11425 0.56552h0.0114l0.10283-0.57694 0.50268-1.6623h0.43413l-0.9768 2.919h-0.19993l-0.99394-2.919h0.46841z"/>
+    <path d="m-342.85-37.672q-0.1371 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61692 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556t0.12567 0.34274q0.0857 0.1371 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.1371zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-340.75-37.478v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.1371-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10854-0.15423 0.29133-0.26276 0.1885-0.10853 0.44556-0.10853 0.18279 0 0.31988 0.05141 0.14281 0.05141 0.23421 0.17708 0.0971 0.11996 0.1428 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-339.98-40.334h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61693v0.3713h-0.61693v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19421-0.02285 0.0857-0.02856 0.1828-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31418 0.03999-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-338.17-37.947q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11424-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.1428-0.07997-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36558 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11425l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20565 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11424 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15995 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15423 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+    <path d="m-383.54-31.766q0-0.77116 0.26277-1.131 0.26847-0.36558 0.75973-0.36558 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.07997 0.27419 0.07997 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.08568-0.27419-0.08568-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-379.48-30.338v-1.7422q0-0.42842-0.10282-0.61692-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.1371-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.05141 0.1428 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-377.5-33.194h0.34845v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31418 0.03999-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-374.18-30.338v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36559 0.14281-0.15994 0.14281-0.21706 0.35987v2.0507h-0.41129v-3.9986h0.41129v1.4109h0.0171q0.11425-0.14852 0.27991-0.23992 0.17136-0.09711 0.4227-0.09711 0.18851 0 0.3256 0.05141 0.14281 0.05141 0.23421 0.17708 0.0914 0.12567 0.13709 0.33702 0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+    <path d="m-371.43-30.532q-0.1371 0.12567-0.34845 0.19422-0.21135 0.06855-0.44556 0.06855-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.03999 0.15995 0.03999 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.1371 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.05712 0.15423-0.05712 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+    <path d="m-369.44-30.338v-1.6965q0-0.22849-0.0171-0.38844-0.0114-0.16566-0.0571-0.26848t-0.12567-0.14852q-0.08-0.05141-0.21135-0.05141-0.19422 0-0.33131 0.15423-0.13138 0.14852-0.1828 0.34274v2.0564h-0.41128v-2.8561h0.29133l0.0743 0.30275h0.0171q0.11996-0.16566 0.28562-0.26848 0.16565-0.10282 0.4227-0.10282 0.21707 0 0.35417 0.09711 0.1428 0.0914 0.22277 0.33131 0.10283-0.19993 0.29133-0.31418 0.19422-0.11424 0.42271-0.11424 0.1885 0 0.31989 0.05141 0.13709 0.0457 0.21706 0.17137 0.0857 0.11996 0.12567 0.3256 0.04 0.19993 0.04 0.50839v1.8679h-0.41129v-1.8165q0-0.3713-0.0743-0.55409-0.0685-0.18279-0.32559-0.18279-0.21707 0-0.34845 0.13709-0.12567 0.13138-0.17708 0.35987v2.0564z"/>
+   </g>
+   <path d="m-277.42-52.773c6.3682-12.725 27.017-14.019 27.017-14.019" fill="none" marker-end="url(#marker35789)" stroke="#000" stroke-width="1.0795"/>
+  </g>
+  <path d="m-253.39-51.772h30.293v15.25h-30.293z" fill="#999"/>
+  <g transform="scale(.94592 1.0572)" dominant-baseline="auto" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="lws_protocols">
+   <path d="m-266.23-45.179q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11995 0 0.2799-0.06283l0.04 0.33131q-0.0743 0.0457-0.21135 0.07426-0.13138 0.02856-0.23992 0.02856-0.21706 0-0.35416-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41128z"/>
+   <path d="m-263.82-47.356 0.5084 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38843-1.6566h0.38843l-0.75973 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.2342l-0.78259-2.919h0.43985l0.43984 1.6623 0.0686 0.55409h0.0114l0.10282-0.56552 0.4684-1.6508z"/>
+   <path d="m-262.02-44.968q0.11424 0.06855 0.26847 0.11996 0.15995 0.0457 0.3256 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.0914-0.26277-0.16566-0.1428-0.07997-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20565 0-0.30275 0.08569-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.1428 0.08568 0.26276 0.20564 0.12567 0.11424 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15995 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15423 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+   <path d="m-260.38-43.7h1.9079v0.3713h-1.9079z"/>
+   <path d="m-258.14-47.356h0.29132l0.0628 0.30846h0.0229q0.21135-0.37701 0.66262-0.37701t0.67405 0.33702q0.22849 0.33702 0.22849 1.1025 0 0.35987-0.0743 0.6512-0.0743 0.28561-0.21135 0.49126-0.1371 0.19993-0.33703 0.30846-0.19421 0.10282-0.43413 0.10282-0.16566 0-0.26276-0.02285-0.0971-0.01714-0.21136-0.07997v1.1767h-0.41128zm0.41128 2.4049q0.08 0.06855 0.17708 0.10853 0.10282 0.03999 0.26848 0.03999 0.30275 0 0.47983-0.30846t0.17708-0.87969q0-0.23992-0.0343-0.43413-0.0286-0.19422-0.0971-0.33131-0.0686-0.14281-0.17708-0.21707-0.10282-0.07997-0.25705-0.07997-0.417 0-0.53696 0.50839z"/>
+   <path d="m-255.65-47.356h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20565-0.25705 0.13138-0.09711 0.31417-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26277-0.05141-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+   <path d="m-254.21-45.928q0-0.77116 0.26276-1.131 0.26848-0.36558 0.75974-0.36558 0.52553 0 0.77115 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26847 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11424 0.07997 0.27419 0.07997 0.29703 0 0.44555-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0685-0.14852-0.18279-0.22849-0.11425-0.08569-0.27419-0.08569-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+   <path d="m-251.91-47.356h0.34845v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36558 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19422-0.02285 0.0857-0.02856 0.18279-0.06855l0.08 0.3256q-0.12567 0.06283-0.2799 0.09711-0.14852 0.03999-0.31418 0.03999-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+   <path d="m-250.22-45.928q0-0.77116 0.26276-1.131 0.26848-0.36558 0.75974-0.36558 0.52553 0 0.77115 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26847 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11424 0.07997 0.27419 0.07997 0.29703 0 0.44555-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.08569-0.27419-0.08569-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+   <path d="m-246.05-44.643q-0.14281 0.10853-0.3256 0.15994-0.1828 0.05141-0.38273 0.05141-0.27419 0-0.46269-0.10282-0.18851-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61693 0-0.73117 0.25705-1.1139 0.26276-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999t0.27419 0.10282l-0.11425 0.35987q-0.22849-0.13138-0.49696-0.13138-0.30847 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984t0.11425 0.35987q0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31417 0.08568 0.14852 0 0.27419-0.05141 0.13139-0.05141 0.21136-0.11996z"/>
+   <path d="m-245.82-45.928q0-0.77116 0.26276-1.131 0.26848-0.36558 0.75974-0.36558 0.52553 0 0.77115 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26847 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11424 0.07997 0.27419 0.07997 0.29704 0 0.44555-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.08569-0.27419-0.08569-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+   <path d="m-242.79-45.179q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15424 0.08568 0.11995 0 0.2799-0.06283l0.04 0.33131q-0.0743 0.0457-0.21135 0.07426-0.13138 0.02856-0.23992 0.02856-0.21706 0-0.35416-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41128z"/>
+   <path d="m-241.95-44.968q0.11424 0.06855 0.26848 0.11996 0.15994 0.0457 0.32559 0.0457 0.18851 0 0.31989-0.0914 0.13138-0.09711 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19992-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20564 0-0.30275 0.08569-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.14281 0.08568 0.26276 0.20564 0.12567 0.11424 0.20565 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15995 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15423 0.06283-0.36559 0.06283-0.25134 0-0.43413-0.05141-0.18279-0.0457-0.30846-0.12567z"/>
+  </g>
+  <path d="m-268.53-45.948h17.739v15.25h-17.739z" fill="#0f0"/>
+  <g>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="callback">
+    <path d="m-281.39-38.269q-0.14281 0.10853-0.3256 0.15994-0.1828 0.05141-0.38273 0.05141-0.27418 0-0.46269-0.10282-0.1885-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61693 0-0.73117 0.25705-1.1139 0.26277-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999 0.15995 0.03999 0.27419 0.10282l-0.11424 0.35987q-0.2285-0.13138-0.49697-0.13138-0.30847 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984 0.0343 0.20564 0.11425 0.35987 0.08 0.14852 0.20564 0.23992 0.12567 0.08568 0.31418 0.08568 0.14851 0 0.27418-0.05141 0.13139-0.05141 0.21136-0.11996z"/>
+    <path d="m-280.97-40.811q0.16566-0.10282 0.39986-0.15994 0.23992-0.05712 0.50268-0.05712 0.23992 0 0.38272 0.07426 0.14852 0.06855 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61692 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21136-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.0457 0.46841-0.0457 0.0571 0 0.11425 0 0.0571 0 0.11995 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15994 0-0.34845 0.05141-0.1885 0.0457-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11425-0.0057-0.0571-0.0057-0.11425-0.0057-0.13709 0-0.26847 0.02285t-0.23421 0.07997q-0.10282 0.05712-0.16565 0.15423-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11424 0.12567 0.29132 0.12567 0.23992 0 0.3713-0.11425 0.13138-0.11424 0.1828-0.25134z"/>
+    <path d="m-278.21-38.806q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11996 0 0.2799-0.06283l0.04 0.33131q-0.0743 0.0457-0.21136 0.07426-0.13138 0.02856-0.23991 0.02856-0.21707 0-0.35416-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41128z"/>
+    <path d="m-276.89-38.806q0 0.19993 0.0514 0.28561 0.0571 0.08568 0.15423 0.08568 0.11996 0 0.2799-0.06283l0.04 0.33131q-0.0743 0.0457-0.21136 0.07426-0.13138 0.02856-0.23991 0.02856-0.21707 0-0.35417-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41129z"/>
+    <path d="m-276-42.125h0.41128v1.3595h0.0171q0.2342-0.28561 0.62264-0.28561 0.43984 0 0.65691 0.34845 0.22278 0.34845 0.22278 1.1025 0 0.77116-0.29704 1.1482-0.29133 0.37701-0.82828 0.37701-0.26277 0-0.47983-0.05712-0.21707-0.06283-0.3256-0.14281zm0.41128 3.5816q0.08 0.0457 0.19422 0.07426 0.11996 0.02285 0.25134 0.02285 0.29704 0 0.46841-0.2799 0.17708-0.28561 0.17708-0.87398 0-0.24563-0.0343-0.43984-0.0286-0.19993-0.0971-0.34274-0.0628-0.14281-0.17136-0.21707-0.10283-0.07997-0.25134-0.07997-0.20565 0-0.34274 0.12567-0.13138 0.11996-0.19422 0.33131z"/>
+    <path d="m-273.55-40.811q0.16566-0.10282 0.39986-0.15994 0.23992-0.05712 0.50268-0.05712 0.23992 0 0.38272 0.07426 0.14852 0.06855 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61692 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31418l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44555 0.10853-0.30847 0-0.5084-0.21136-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.0457 0.46841-0.0457 0.0571 0 0.11425 0 0.0571 0 0.11995 0.0057 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15994 0-0.34845 0.05141-0.1885 0.0457-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.0057-0.11425-0.0057-0.0571-0.0057-0.11425-0.0057-0.13709 0-0.26847 0.02285t-0.23421 0.07997q-0.10282 0.05712-0.16565 0.15423-0.0571 0.09711-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11424 0.12567 0.29133 0.12567 0.23991 0 0.37129-0.11425 0.13139-0.11424 0.1828-0.25134z"/>
+    <path d="m-269.66-38.269q-0.14281 0.10853-0.3256 0.15994t-0.38272 0.05141q-0.27419 0-0.46269-0.10282-0.18851-0.10853-0.30847-0.30275-0.11424-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61693 0-0.73117 0.25706-1.1139 0.26276-0.38272 0.7483-0.38272 0.22278 0 0.38273 0.03999 0.15994 0.03999 0.27419 0.10282l-0.11425 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.4684 0.27419-0.15424 0.26848-0.15424 0.85113 0 0.2342 0.0343 0.43984 0.0343 0.20564 0.11424 0.35987 0.08 0.14852 0.20565 0.23992 0.12567 0.08568 0.31417 0.08568 0.14852 0 0.27419-0.05141 0.13138-0.05141 0.21135-0.11996z"/>
+    <path d="m-268.58-39.411h-0.21136v1.2853h-0.41128v-3.9986h0.41128v2.4334l0.18851-0.07997 0.66833-1.211h0.47412l-0.67405 1.1539-0.19993 0.18279 0.23421 0.22278 0.73688 1.2967h-0.49697z"/>
+   </g>
+   <path d="m-295.2-8.4861c-7.9454-5.7561-27.85-23.836-15.924-58.871" fill="none" marker-end="url(#marker9266-0)" stroke="#000" stroke-dasharray="1.00661947, 1.00661947" stroke-width="1.0066"/>
+   <path d="m-261.52-33.429c-2.542 4.1649 0.71012 7.868-7.2404 14.027" fill="none" marker-end="url(#marker9266-0-2)" stroke="#000" stroke-dasharray="1.00661948, 1.00661948" stroke-width="1.0066"/>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" fill="#fff" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="lws_sequencer event api">
+    <path d="m-309.23-9.797q0 0.19993 0.0514 0.28561 0.0571 0.085684 0.15423 0.085684 0.11996 0 0.2799-0.062835l0.04 0.33131q-0.0743 0.045698-0.21135 0.07426-0.13138 0.028561-0.23991 0.028561-0.21707 0-0.35417-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41129z"/>
+    <path d="m-306.82-11.973 0.5084 1.668 0.10282 0.54838h0.0114l0.0857-0.5598 0.38843-1.6566h0.38844l-0.75974 2.919h-0.2342l-0.57694-1.8736-0.08-0.47983h-0.0114l-0.08 0.48554-0.5598 1.8679h-0.2342l-0.78259-2.919h0.43985l0.43984 1.6623 0.0686 0.55409h0.0114l0.10282-0.56551 0.4684-1.6508z"/>
+    <path d="m-305.02-9.5857q0.11424 0.068547 0.26847 0.11996 0.15995 0.045698 0.3256 0.045698 0.18851 0 0.31989-0.091396 0.13138-0.097109 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10854 0.34274q-0.10853-0.05712-0.25134-0.0914-0.1428-0.03999-0.29132-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26848 0.16566 0.14281 0.08568 0.26276 0.20564 0.12567 0.11425 0.20565 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15995 0.25134-0.10282 0.10282-0.26276 0.16566-0.15423 0.062835-0.36559 0.062835-0.25134 0-0.43413-0.051411-0.18279-0.045698-0.30846-0.12567z"/>
+    <path d="m-303.38-8.3176h1.9079v0.3713h-1.9079z"/>
+    <path d="m-301.18-9.5857q0.11425 0.068547 0.26848 0.11996 0.15994 0.045698 0.3256 0.045698 0.1885 0 0.31988-0.091396 0.13139-0.097109 0.13139-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20565-0.20564-0.11995-0.0914-0.26276-0.16566-0.14281-0.07997-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.0457 0.17137 0.03999 0.29704 0.11424l-0.10853 0.34274q-0.10854-0.05712-0.25134-0.0914-0.14281-0.03999-0.29133-0.03999-0.20564 0-0.30275 0.08568-0.0914 0.08568-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.09711 0.19993 0.18279 0.12567 0.07997 0.26847 0.16566 0.14281 0.08568 0.26277 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15994 0.25134-0.10282 0.10282-0.26277 0.16566-0.15423 0.062835-0.36558 0.062835-0.25134 0-0.43414-0.051411-0.18279-0.045698-0.30846-0.12567z"/>
+    <path d="m-297.5-9.3115q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44555 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-295.1-7.9748h-0.41128v-1.3767h-0.0229q-0.0914 0.14281-0.2342 0.22278-0.1371 0.079972-0.35988 0.079972-0.45127 0-0.67404-0.35987-0.22278-0.36559-0.22278-1.1253 0-0.73688 0.29132-1.1139 0.29133-0.37701 0.84542-0.37701 0.23991 0 0.45698 0.05712t0.33131 0.11996zm-0.41128-3.5816q-0.15994-0.09711-0.45127-0.09711-0.29704 0-0.46841 0.27419-0.16565 0.27419-0.16565 0.8397 0 0.23992 0.0286 0.44556 0.0286 0.20564 0.0914 0.35987 0.0686 0.14852 0.17137 0.2342 0.10854 0.079972 0.26277 0.079972 0.21706 0 0.34273-0.12567t0.18851-0.36559z"/>
+    <path d="m-294.06-11.973v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11995 0 0.21135-0.045698 0.0971-0.051411 0.17137-0.13138 0.0743-0.079972 0.13138-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41128v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29132l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.2799 0.33131-0.18279 0.13709-0.45698 0.13709-0.1828 0-0.31989-0.045698t-0.2342-0.16566q-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+    <path d="m-290.24-9.3115q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44556 0.068547-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.1828 0.62835z"/>
+    <path d="m-288.14-9.1173v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.1371-0.14852 0.13709-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.05141 0.1428 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-285.51-9.2601q-0.14281 0.10853-0.3256 0.15994-0.1828 0.051411-0.38273 0.051411-0.27419 0-0.46269-0.10282-0.18851-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61693 0-0.73117 0.25705-1.1139 0.26276-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.03999t0.27419 0.10282l-0.11425 0.35987q-0.22849-0.13138-0.49696-0.13138-0.30847 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984 0.0343 0.20564 0.11425 0.35987 0.08 0.14852 0.20564 0.23992 0.12567 0.085684 0.31417 0.085684 0.14852 0 0.27419-0.05141 0.13139-0.05141 0.21136-0.11996z"/>
+    <path d="m-283.45-9.3115q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44555 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.03999 0.15994 0.03999 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.09711-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-282.8-11.973h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20565-0.25705 0.13138-0.09711 0.31417-0.09711 0.13138 0 0.29704 0.05141l-0.08 0.417q-0.14852-0.05141-0.26277-0.05141-0.18279 0-0.29703 0.10853-0.11425 0.10282-0.14852 0.2799v2.1021h-0.41129z"/>
+    <path d="m-307.96-2.1712q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44556 0.068547-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.039986 0.15995 0.039986 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+    <path d="m-306.72-3.1594 0.11424 0.56551h0.0114l0.10282-0.57694 0.50268-1.6623h0.43413l-0.9768 2.919h-0.19993l-0.99393-2.919h0.4684z"/>
+    <path d="m-303.49-2.1712q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44556 0.068547-0.26847 0-0.4684-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26847-0.38272 0.75973-0.38272 0.15994 0 0.31417 0.039986 0.15995 0.039986 0.28562 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26276 0-0.41699 0.19993-0.15424 0.19993-0.1828 0.62835z"/>
+    <path d="m-301.38-1.9769v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.13709-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.051411 0.1428 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-300.62-4.8331h0.34845v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11424 0 0.19422-0.022849 0.0857-0.028561 0.18279-0.068547l0.08 0.3256q-0.12567 0.062835-0.2799 0.097109-0.14852 0.039986-0.31418 0.039986-0.28561 0-0.41128-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-297.57-4.6617q0.16566-0.10282 0.39986-0.15994 0.23992-0.057123 0.50268-0.057123 0.23992 0 0.38272 0.07426 0.14852 0.068547 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19422-0.21707-0.19422-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.045698 0.46841-0.045698 0.0571 0 0.11424 0 0.0571 0 0.11996 0.00571 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15994 0-0.34845 0.05141-0.1885 0.045698-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.00571-0.11424-0.00571-0.0571-0.00571-0.11425-0.00571-0.13709 0-0.26847 0.022849-0.13139 0.022849-0.23421 0.079972t-0.16565 0.15423q-0.0571 0.097109-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11424 0.12567 0.29132 0.12567 0.23992 0 0.3713-0.11425 0.13138-0.11425 0.18279-0.25134z"/>
+    <path d="m-295.25-4.8331h0.29133l0.0628 0.30846h0.0229q0.21136-0.37701 0.66263-0.37701t0.67405 0.33702q0.22849 0.33702 0.22849 1.1025 0 0.35987-0.0743 0.6512-0.0743 0.28561-0.21136 0.49126-0.13709 0.19993-0.33702 0.30846-0.19422 0.10282-0.43413 0.10282-0.16566 0-0.26277-0.022849-0.0971-0.017137-0.21135-0.079972v1.1767h-0.41129zm0.41129 2.4049q0.08 0.068547 0.17708 0.10853 0.10282 0.039986 0.26847 0.039986 0.30276 0 0.47984-0.30846t0.17708-0.87969q0-0.23992-0.0343-0.43413-0.0286-0.19422-0.0971-0.33131-0.0685-0.14281-0.17708-0.21707-0.10282-0.079972-0.25705-0.079972-0.41699 0-0.53695 0.50839z"/>
+    <path d="m-292.7-4.8331h0.41129v2.8561h-0.41129zm-0.0743-0.86827q0-0.13709 0.0743-0.22278 0.08-0.085684 0.20564-0.085684 0.12567 0 0.20565 0.085684 0.0857 0.079972 0.0857 0.22278 0 0.13709-0.0857 0.21707-0.08 0.07426-0.20565 0.07426-0.12567 0-0.20564-0.079972-0.0743-0.079972-0.0743-0.21135z"/>
+   </g>
+  </g>
+  <path d="m-272.24-51.911c9.9557-4.1084 12.477-3.125 16.982 0.43134" fill="none" marker-end="url(#marker34759)" stroke="#000" stroke-width="1.0795"/>
+  <g>
+   <path d="m-333.15 3.8989c24.927-4.2464 23.412 6.1426 38.503-4.3056" fill="none" marker-end="url(#marker9266-0-2-5)" stroke="#000" stroke-dasharray="1.00661949, 1.00661949" stroke-width="1.0066"/>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" fill="#00f" stroke-width=".14281" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Other threads may also queue events on a sequencer">
+    <path d="m-356.69-8.3282q0-1.0168 0.3256-1.5423 0.3256-0.52553 0.99393-0.52553 0.35988 0 0.61122 0.14852 0.25134 0.14281 0.40557 0.41128 0.15994 0.26848 0.2342 0.6512 0.0743 0.38272 0.0743 0.85684 0 1.0168-0.33131 1.5423-0.3256 0.52553-0.99394 0.52553-0.35416 0-0.6055-0.14281-0.25134-0.14852-0.41128-0.417-0.15995-0.26848-0.23421-0.6512-0.0685-0.38272-0.0685-0.85684zm0.45127 0q0 0.33702 0.0457 0.63977 0.0514 0.30275 0.15424 0.53124 0.10282 0.22849 0.26847 0.36559 0.16566 0.13709 0.39986 0.13709 0.42842 0 0.6512-0.41128 0.22278-0.41128 0.22278-1.2624 0-0.33131-0.0514-0.63406-0.0457-0.30275-0.14852-0.53124-0.10282-0.2342-0.26848-0.3713-0.16565-0.13709-0.40557-0.13709-0.42271 0-0.64548 0.41128-0.22278 0.41128-0.22278 1.2624z"/>
+    <path d="m-353.75-9.185h0.34844v-0.56552l0.41129-0.13138v0.6969h0.61692v0.3713h-0.61692v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.022849 0.0857-0.028561 0.18279-0.068547l0.08 0.3256q-0.12567 0.062835-0.2799 0.097109-0.14852 0.039986-0.31417 0.039986-0.28562 0-0.41129-0.18279-0.11996-0.1885-0.11996-0.6055v-1.7594h-0.34844z"/>
+    <path d="m-350.43-6.3289v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.14281-0.15995 0.14281-0.21707 0.35987v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23992 0.17137-0.097109 0.42271-0.097109 0.1885 0 0.3256 0.05141 0.1428 0.05141 0.2342 0.17708 0.0914 0.12567 0.13709 0.33702 0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+    <path d="m-347.67-6.5231q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44555 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.18851-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15994 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21706 0.21707 0.1371 0.07426 0.33703 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11995-0.62835-0.13139-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.1828 0.62835z"/>
+    <path d="m-347.03-9.185h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.097109 0.31417-0.097109 0.13138 0 0.29704 0.051411l-0.08 0.417q-0.14852-0.051411-0.26277-0.051411-0.18279 0-0.29703 0.10853-0.11425 0.10282-0.14852 0.2799v2.1021h-0.41129z"/>
+    <path d="m-344.53-9.185h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61693v0.3713h-0.61693v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.022849 0.0857-0.028561 0.18279-0.068547l0.08 0.3256q-0.12567 0.062835-0.2799 0.097109-0.14852 0.039986-0.31417 0.039986-0.28562 0-0.41129-0.18279-0.11995-0.1885-0.11995-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-341.21-6.3289v-1.7365q0-0.39986-0.0971-0.6055-0.0914-0.21135-0.3713-0.21135-0.19993 0-0.36558 0.14281-0.15995 0.14281-0.21707 0.35987v2.0507h-0.41128v-3.9986h0.41128v1.4109h0.0171q0.11424-0.14852 0.2799-0.23992 0.17137-0.097109 0.42271-0.097109 0.1885 0 0.3256 0.05141 0.1428 0.05141 0.2342 0.17708 0.0914 0.12567 0.13709 0.33702 0.0457 0.20564 0.0457 0.5141v1.8451z"/>
+    <path d="m-340.16-9.185h0.29133l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.097109 0.31417-0.097109 0.13139 0 0.29704 0.051411l-0.08 0.417q-0.14852-0.051411-0.26276-0.051411-0.1828 0-0.29704 0.10853-0.11425 0.10282-0.14852 0.2799v2.1021h-0.41129z"/>
+    <path d="m-336.89-6.5231q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44555 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-336.28-9.0136q0.16566-0.10282 0.39986-0.15994 0.23992-0.057123 0.50268-0.057123 0.23992 0 0.38273 0.07426 0.14851 0.068547 0.22849 0.19422 0.0857 0.11996 0.10853 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31418l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44555 0.10853-0.30847 0-0.5084-0.21135-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35988-0.14281 0.21135-0.045698 0.4684-0.045698 0.0571 0 0.11425 0 0.0571 0 0.11995 0.00571 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15994 0-0.34845 0.05141-0.1885 0.045698-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.00571-0.11425-0.00571-0.0571-0.00571-0.11424-0.00571-0.1371 0-0.26848 0.022849t-0.2342 0.079972-0.16566 0.15423q-0.0571 0.097109-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11425 0.12567 0.29133 0.12567 0.23991 0 0.37129-0.11425 0.13139-0.11425 0.1828-0.25134z"/>
+    <path d="m-332.16-7.3114q0 0.29133 6e-3 0.53124 6e-3 0.2342 0.04 0.46269h-0.2799l-0.0914-0.34274h-0.0229q-0.08 0.17137-0.25134 0.28561-0.17137 0.11425-0.41128 0.11425-0.4627 0-0.69119-0.35987-0.22277-0.35987-0.22277-1.131 0-0.73117 0.27418-1.1082 0.27991-0.37701 0.76545-0.37701 0.16565 0 0.26276 0.022849 0.0971 0.017137 0.21136 0.062835v-1.1767h0.41128zm-0.41128-1.4224q-0.08-0.068547-0.1828-0.097109-0.0971-0.034274-0.26276-0.034274-0.30275 0-0.47412 0.27419-0.16565 0.27419-0.16565 0.84542 0 0.25134 0.0286 0.45698 0.0343 0.19993 0.0971 0.34845 0.0685 0.14852 0.17136 0.22849 0.10854 0.079972 0.26277 0.079972 0.41128 0 0.52553-0.48554z"/>
+    <path d="m-331.53-6.7973q0.11425 0.068547 0.26848 0.11996 0.15994 0.045698 0.3256 0.045698 0.1885 0 0.31989-0.091396 0.13138-0.097109 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20565-0.20564-0.11995-0.091396-0.26276-0.16566-0.14281-0.079972-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.2342 0 0.40557 0.045698 0.17137 0.039986 0.29704 0.11425l-0.10853 0.34274q-0.10854-0.057123-0.25134-0.091396-0.14281-0.039986-0.29133-0.039986-0.20564 0-0.30275 0.085684-0.0914 0.085684-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.097109 0.19993 0.18279 0.12567 0.079972 0.26847 0.16566 0.14281 0.085684 0.26277 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15994 0.25134-0.10282 0.10282-0.26277 0.16566-0.15423 0.062835-0.36558 0.062835-0.25134 0-0.43414-0.05141-0.18279-0.045698-0.30846-0.12567z"/>
+    <path d="m-327-6.3289v-1.6965q0-0.22849-0.0171-0.38843-0.0114-0.16566-0.0571-0.26848t-0.12567-0.14852q-0.08-0.051411-0.21136-0.051411-0.19422 0-0.33131 0.15423-0.13138 0.14852-0.18279 0.34274v2.0564h-0.41129v-2.8561h0.29133l0.0743 0.30275h0.0171q0.11995-0.16566 0.28561-0.26848t0.42271-0.10282q0.21706 0 0.35416 0.097109 0.14281 0.091396 0.22278 0.33131 0.10282-0.19993 0.29132-0.31417 0.19422-0.11425 0.42271-0.11425 0.18851 0 0.31989 0.05141 0.13709 0.045698 0.21706 0.17137 0.0857 0.11996 0.12567 0.3256 0.04 0.19993 0.04 0.50839v1.8679h-0.41128v-1.8165q0-0.3713-0.0743-0.55409-0.0685-0.18279-0.3256-0.18279-0.21707 0-0.34845 0.13709-0.12567 0.13138-0.17708 0.35987v2.0564z"/>
+    <path d="m-324.65-9.0136q0.16566-0.10282 0.39986-0.15994 0.23991-0.057123 0.50268-0.057123 0.23991 0 0.38272 0.07426 0.14852 0.068547 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16566 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19422-0.21707-0.19422-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.045698 0.46841-0.045698 0.0571 0 0.11424 0 0.0571 0 0.11996 0.00571 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15995 0-0.34845 0.05141-0.18851 0.045698-0.31418 0.11996zm1.2396 1.3824q-0.0571-0.00571-0.11424-0.00571-0.0571-0.00571-0.11425-0.00571-0.13709 0-0.26847 0.022849-0.13139 0.022849-0.23421 0.079972t-0.16565 0.15423q-0.0571 0.097109-0.0571 0.24563 0 0.22849 0.10854 0.35416 0.11424 0.12567 0.29132 0.12567 0.23992 0 0.3713-0.11425 0.13138-0.11425 0.18279-0.25134z"/>
+    <path d="m-321.63-7.3399 0.11996 0.55409h0.0286l0.0857-0.55409 0.43413-1.8451h0.417l-0.67977 2.5648q-0.08 0.30846-0.15994 0.57694-0.08 0.26848-0.17708 0.46269-0.0914 0.19993-0.21135 0.30846-0.11425 0.11425-0.27419 0.11425-0.15995 0-0.2799-0.051411l0.0685-0.38843q0.08 0.028561 0.15995 0.011424 0.08-0.017137 0.14852-0.097109 0.0743-0.079972 0.13138-0.23992 0.0628-0.15423 0.10853-0.40557l-0.92539-2.8561h0.46841z"/>
+    <path d="m-356.63-1.8733q0.16566-0.10282 0.39986-0.15994 0.23992-0.057123 0.50268-0.057123 0.23992 0 0.38272 0.07426 0.14852 0.068547 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.045698 0.46841-0.045698 0.0571 0 0.11425 0 0.0571 0 0.11995 0.005712 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15994 0-0.34845 0.051411-0.1885 0.045698-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.005712-0.11425-0.005712-0.0571-0.005712-0.11425-0.005712-0.13709 0-0.26847 0.022849t-0.23421 0.079972q-0.10282 0.057123-0.16565 0.15423-0.0571 0.097109-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11424 0.12567 0.29132 0.12567 0.23992 0 0.3713-0.11425 0.13138-0.11425 0.1828-0.25134z"/>
+    <path d="m-353.87 0.13172q0 0.19993 0.0514 0.28561 0.0571 0.085684 0.15423 0.085684 0.11996 0 0.2799-0.062835l0.04 0.33131q-0.0743 0.045698-0.21136 0.07426-0.13138 0.028561-0.23991 0.028561-0.21707 0-0.35416-0.13138-0.13138-0.13709-0.13138-0.47412v-3.4559h0.41128z"/>
+    <path d="m-353.03 0.34307q0.11425 0.068547 0.26848 0.11996 0.15994 0.045698 0.3256 0.045698 0.1885 0 0.31989-0.091396 0.13138-0.097109 0.13138-0.30846 0-0.17708-0.08-0.29133t-0.20564-0.20564q-0.11996-0.091396-0.26277-0.16566-0.14281-0.079972-0.26848-0.1885-0.11995-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.23421 0 0.40557 0.045698 0.17137 0.039986 0.29704 0.11425l-0.10853 0.34274q-0.10853-0.057123-0.25134-0.091396-0.14281-0.039986-0.29133-0.039986-0.20564 0-0.30275 0.085684-0.0914 0.085684-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.097109 0.19993 0.18279 0.12567 0.079972 0.26847 0.16566 0.14281 0.085684 0.26277 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15994 0.25134-0.10282 0.10282-0.26277 0.16566-0.15423 0.062835-0.36558 0.062835-0.25134 0-0.43413-0.05141-0.1828-0.045698-0.30847-0.12567z"/>
+    <path d="m-351.18-0.61659q0-0.77116 0.26277-1.131 0.26848-0.36559 0.75973-0.36559 0.52553 0 0.77116 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26848 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.1885 0.2342 0.11425 0.079972 0.27419 0.079972 0.29704 0 0.44556-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0685-0.14852-0.18279-0.22849-0.11425-0.085684-0.27419-0.085684-0.29132 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-345.57 1.9539h-0.41129v-1.3767h-0.0228q-0.0914 0.14281-0.23421 0.22278-0.13709 0.079972-0.35987 0.079972-0.45127 0-0.67405-0.35987-0.22278-0.36559-0.22278-1.1253 0-0.73688 0.29133-1.1139 0.29132-0.37701 0.84541-0.37701 0.23992 0 0.45699 0.057123 0.21706 0.057123 0.33131 0.11996zm-0.41129-3.5816q-0.15994-0.097109-0.45126-0.097109-0.29704 0-0.46841 0.27419-0.16566 0.27419-0.16566 0.8397 0 0.23992 0.0286 0.44556 0.0286 0.20564 0.0914 0.35987 0.0685 0.14852 0.17137 0.2342 0.10853 0.079972 0.26276 0.079972 0.21707 0 0.34274-0.12567t0.1885-0.36559z"/>
+    <path d="m-344.54-2.0447v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11996 0 0.21136-0.045698 0.0971-0.05141 0.17136-0.13138 0.0743-0.079972 0.13139-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41129v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29133l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.27991 0.33131-0.18279 0.13709-0.45698 0.13709-0.18279 0-0.31988-0.045698-0.1371-0.045698-0.23421-0.16566-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+    <path d="m-340.72 0.61726q-0.1371 0.12567-0.34845 0.19422-0.21136 0.068547-0.44556 0.068547-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.039986 0.15994 0.039986 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15424-0.057123 0.23421-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-339.7-2.0447v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11996 0 0.21135-0.045698 0.0971-0.05141 0.17137-0.13138 0.0743-0.079972 0.13138-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41128v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29133l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.2799 0.33131-0.18279 0.13709-0.45698 0.13709-0.18279 0-0.31989-0.045698-0.13709-0.045698-0.2342-0.16566-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+    <path d="m-335.89 0.61726q-0.13709 0.12567-0.34845 0.19422-0.21135 0.068547-0.44555 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35988-0.19993-0.26276 0-0.41699 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-332.32 0.61726q-0.13709 0.12567-0.34844 0.19422-0.21136 0.068547-0.44556 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.23421-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-331.08-0.37096 0.11425 0.56551h0.0114l0.10282-0.57694 0.50268-1.6623h0.43413l-0.9768 2.919h-0.19992l-0.99394-2.919h0.46841z"/>
+    <path d="m-327.85 0.61726q-0.13709 0.12567-0.34844 0.19422-0.21136 0.068547-0.44556 0.068547-0.26848 0-0.46841-0.10282-0.19422-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26848-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994 0.12567 0.11996 0.19993 0.33702 0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.2342-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-325.74 0.81148v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35417-0.19422-0.22849 0-0.37701 0.13709-0.14851 0.13709-0.21706 0.33702v2.0793h-0.41129v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10853-0.15423 0.29132-0.26276 0.18851-0.10853 0.44556-0.10853 0.18279 0 0.31989 0.051411 0.14281 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-324.98-2.0447h0.34845v-0.56552l0.41128-0.13138v0.6969h0.61693v0.3713h-0.61693v1.7023q0 0.25134 0.0571 0.36559 0.0628 0.10853 0.19993 0.10853 0.11425 0 0.19422-0.022849 0.0857-0.028561 0.18279-0.068547l0.08 0.3256q-0.12567 0.062835-0.2799 0.097109-0.14852 0.039986-0.31417 0.039986-0.28562 0-0.41129-0.18279-0.11995-0.1885-0.11995-0.6055v-1.7594h-0.34845z"/>
+    <path d="m-323.16 0.34307q0.11424 0.068547 0.26847 0.11996 0.15995 0.045698 0.3256 0.045698 0.18851 0 0.31989-0.091396 0.13138-0.097109 0.13138-0.30846 0-0.17708-0.08-0.29133t-0.20564-0.20564q-0.11996-0.091396-0.26276-0.16566-0.14281-0.079972-0.26848-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19421-0.54838 0.19993-0.1885 0.55981-0.1885 0.2342 0 0.40557 0.045698 0.17137 0.039986 0.29704 0.11425l-0.10854 0.34274q-0.10853-0.057123-0.25134-0.091396-0.1428-0.039986-0.29132-0.039986-0.20564 0-0.30275 0.085684-0.0914 0.085684-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.097109 0.19993 0.18279 0.12567 0.079972 0.26848 0.16566 0.14281 0.085684 0.26276 0.20564 0.12567 0.11425 0.20565 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275-0.0514 0.14281-0.15995 0.25134-0.10282 0.10282-0.26276 0.16566-0.15423 0.062835-0.36559 0.062835-0.25134 0-0.43413-0.05141-0.18279-0.045698-0.30846-0.12567z"/>
+    <path d="m-320.1-0.61659q0-0.77116 0.26276-1.131 0.26848-0.36559 0.75974-0.36559 0.52553 0 0.77115 0.3713 0.25134 0.3713 0.25134 1.1253 0 0.77687-0.26847 1.1367-0.26848 0.35987-0.75402 0.35987-0.52553 0-0.77687-0.3713-0.24563-0.3713-0.24563-1.1253zm0.42842 0q0 0.25134 0.0286 0.45698 0.0343 0.20564 0.10282 0.35416 0.0743 0.14852 0.18851 0.2342 0.11424 0.079972 0.27419 0.079972 0.29703 0 0.44555-0.26276 0.14852-0.26848 0.14852-0.86255 0-0.24563-0.0343-0.45127-0.0286-0.21135-0.10282-0.35987-0.0686-0.14852-0.18279-0.22849-0.11425-0.085684-0.27419-0.085684-0.29133 0-0.44556 0.26848-0.14852 0.26848-0.14852 0.85684z"/>
+    <path d="m-316.04 0.81148v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.13709-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29704l0.0743 0.30275h0.0171q0.10854-0.15423 0.29133-0.26276 0.1885-0.10853 0.44556-0.10853 0.18279 0 0.31988 0.051411 0.14281 0.05141 0.23421 0.17708 0.0971 0.11996 0.1428 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-356.63 5.2671q0.16566-0.10282 0.39986-0.15994 0.23992-0.057123 0.50268-0.057123 0.23992 0 0.38272 0.07426 0.14852 0.068547 0.22849 0.19422 0.0857 0.11996 0.10854 0.2799 0.0286 0.15423 0.0286 0.3256 0 0.34274-0.0171 0.66834-0.0114 0.3256-0.0114 0.61693 0 0.21707 0.0114 0.40557 0.0171 0.18279 0.0571 0.34845h-0.31417l-0.0971-0.33702h-0.0229q-0.0857 0.14852-0.25134 0.25705-0.16565 0.10853-0.44556 0.10853-0.30846 0-0.50839-0.21135-0.19421-0.21707-0.19421-0.59408 0-0.24563 0.08-0.41128 0.0857-0.16566 0.2342-0.26848 0.15423-0.10282 0.35987-0.14281 0.21136-0.045698 0.46841-0.045698 0.0571 0 0.11425 0 0.0571 0 0.11995 0.00571 0.0171-0.17708 0.0171-0.31418 0-0.3256-0.0971-0.45698t-0.35416-0.13138q-0.15994 0-0.34845 0.051411-0.1885 0.045698-0.31417 0.11996zm1.2396 1.3824q-0.0571-0.00571-0.11425-0.00571-0.0571-0.00571-0.11425-0.00571-0.13709 0-0.26847 0.022849t-0.23421 0.079972q-0.10282 0.057123-0.16565 0.15423-0.0571 0.097109-0.0571 0.24563 0 0.22849 0.10853 0.35416 0.11424 0.12567 0.29132 0.12567 0.23992 0 0.3713-0.11425 0.13138-0.11425 0.1828-0.25134z"/>
+    <path d="m-353.14 7.4834q0.11424 0.068547 0.26847 0.11996 0.15995 0.045698 0.3256 0.045698 0.18851 0 0.31989-0.091396 0.13138-0.097109 0.13138-0.30846 0-0.17708-0.08-0.29133-0.08-0.11425-0.20564-0.20564-0.11996-0.091396-0.26277-0.16566-0.1428-0.079972-0.26847-0.1885-0.11996-0.10853-0.19993-0.25705-0.08-0.14852-0.08-0.37701 0-0.36559 0.19422-0.54838 0.19993-0.1885 0.5598-0.1885 0.23421 0 0.40558 0.045698 0.17136 0.039986 0.29703 0.11425l-0.10853 0.34274q-0.10853-0.057123-0.25134-0.091396-0.14281-0.039986-0.29132-0.039986-0.20565 0-0.30276 0.085684-0.0914 0.085684-0.0914 0.26848 0 0.14281 0.08 0.24563 0.08 0.097109 0.19993 0.18279 0.12567 0.079972 0.26848 0.16566 0.1428 0.085684 0.26276 0.20564 0.12567 0.11425 0.20564 0.2799 0.08 0.15994 0.08 0.40557 0 0.15994-0.0514 0.30275t-0.15994 0.25134q-0.10282 0.10282-0.26276 0.16566-0.15424 0.062835-0.36559 0.062835-0.25134 0-0.43413-0.051411-0.18279-0.045698-0.30846-0.12567z"/>
+    <path d="m-349.46 7.7576q-0.1371 0.12567-0.34845 0.19422-0.21136 0.068547-0.44556 0.068547-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75974-0.38272 0.15994 0 0.31417 0.039986 0.15994 0.039986 0.28561 0.15994t0.19993 0.33702q0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15424-0.057123 0.23421-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-347.06 9.0943h-0.41129v-1.3767h-0.0229q-0.0914 0.14281-0.2342 0.22278-0.13709 0.079972-0.35987 0.079972-0.45127 0-0.67405-0.35987-0.22278-0.36559-0.22278-1.1253 0-0.73688 0.29133-1.1139 0.29132-0.37701 0.84541-0.37701 0.23992 0 0.45698 0.057123 0.21707 0.057123 0.33132 0.11996zm-0.41129-3.5816q-0.15994-0.097109-0.45127-0.097109-0.29703 0-0.4684 0.27419-0.16566 0.27419-0.16566 0.8397 0 0.23992 0.0286 0.44556t0.0914 0.35987q0.0686 0.14852 0.17137 0.2342 0.10853 0.079972 0.26276 0.079972 0.21707 0 0.34274-0.12567t0.1885-0.36559z"/>
+    <path d="m-346.02 5.0957v1.748q0 0.43413 0.0857 0.62264 0.0914 0.18279 0.3256 0.18279 0.11996 0 0.21136-0.045698 0.0971-0.051411 0.17136-0.13138 0.0743-0.079972 0.13139-0.18279 0.0571-0.10282 0.0914-0.21135v-1.9822h0.41129v2.045q0 0.20564 0.0114 0.42842 0.0171 0.21707 0.0457 0.38272h-0.29133l-0.10282-0.39986h-0.0171q-0.0971 0.1885-0.2799 0.33131-0.18279 0.13709-0.45698 0.13709-0.18279 0-0.31989-0.045698-0.13709-0.045698-0.2342-0.16566-0.0971-0.11996-0.14852-0.3256-0.0457-0.21135-0.0457-0.53695v-1.8508z"/>
+    <path d="m-342.21 7.7576q-0.13709 0.12567-0.34844 0.19422-0.21136 0.068547-0.44556 0.068547-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994t0.19993 0.33702q0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.23421-0.13709zm-0.31988-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-340.1 7.9518v-1.7422q0-0.42842-0.10282-0.61693-0.0971-0.19422-0.35416-0.19422-0.22849 0-0.37701 0.13709-0.14852 0.13709-0.21707 0.33702v2.0793h-0.41128v-2.8561h0.29703l0.0743 0.30275h0.0171q0.10853-0.15423 0.29133-0.26276 0.1885-0.10853 0.44555-0.10853 0.1828 0 0.31989 0.051411 0.14281 0.05141 0.2342 0.17708 0.0971 0.11996 0.14281 0.3256 0.0514 0.20564 0.0514 0.51982v1.8508z"/>
+    <path d="m-337.47 7.809q-0.14281 0.10853-0.3256 0.15994-0.18279 0.051411-0.38272 0.051411-0.27419 0-0.4627-0.10282-0.1885-0.10853-0.30846-0.30275-0.11425-0.19993-0.17137-0.47412-0.0514-0.2799-0.0514-0.61693 0-0.73117 0.25705-1.1139 0.26277-0.38272 0.74831-0.38272 0.22278 0 0.38272 0.039986 0.15995 0.039986 0.27419 0.10282l-0.11424 0.35987q-0.22849-0.13138-0.49697-0.13138-0.30846 0-0.46841 0.27419-0.15423 0.26848-0.15423 0.85113 0 0.2342 0.0343 0.43984 0.0343 0.20564 0.11424 0.35987 0.08 0.14852 0.20564 0.23992 0.12567 0.085684 0.31418 0.085684 0.14852 0 0.27419-0.051411 0.13138-0.051411 0.21135-0.11996z"/>
+    <path d="m-335.41 7.7576q-0.1371 0.12567-0.34845 0.19422-0.21136 0.068547-0.44556 0.068547-0.26848 0-0.46841-0.10282-0.19421-0.10853-0.3256-0.30275-0.12567-0.19993-0.1885-0.47412-0.0571-0.27419-0.0571-0.61693 0-0.73117 0.26847-1.1139 0.26848-0.38272 0.75973-0.38272 0.15995 0 0.31418 0.039986 0.15994 0.039986 0.28561 0.15994t0.19993 0.33702q0.08 0.21707 0.08 0.56552 0 0.097109-0.0114 0.21135-6e-3 0.10853-0.0171 0.22849h-1.4509q0 0.24563 0.04 0.44556 0.04 0.19993 0.12567 0.34274 0.0857 0.13709 0.21707 0.21707 0.13709 0.07426 0.33702 0.07426 0.15423 0 0.30275-0.057123 0.15423-0.057123 0.23421-0.13709zm-0.31989-1.5309q0.0114-0.42842-0.11996-0.62835-0.13138-0.19993-0.35987-0.19993-0.26277 0-0.417 0.19993-0.15423 0.19993-0.18279 0.62835z"/>
+    <path d="m-334.76 5.0957h0.29132l0.0743 0.30275h0.0171q0.08-0.16566 0.20564-0.25705 0.13138-0.097109 0.31418-0.097109 0.13138 0 0.29704 0.051411l-0.08 0.417q-0.14852-0.051411-0.26276-0.051411-0.18279 0-0.29704 0.10853-0.11424 0.10282-0.14852 0.2799v2.1021h-0.41128z"/>
+   </g>
+   <g transform="scale(.94592 1.0572)" dominant-baseline="auto" stroke-width=".20412" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="lws_sequencer">
+    <path d="m-273.25 9.9991q0 0.28576 0.0735 0.40823 0.0817 0.12247 0.22045 0.12247 0.17145 0 0.40006-0.08981l0.0572 0.47355q-0.10614 0.06532-0.3021 0.10614-0.18778 0.04082-0.34291 0.04082-0.31026 0-0.50621-0.18779-0.18779-0.19595-0.18779-0.67766v-4.9396h0.58786z"/>
+    <path d="m-269.8 6.8884 0.72666 2.3841 0.14696 0.78381h0.0163l0.12247-0.80014 0.5552-2.3677h0.55519l-1.0859 4.1721h-0.33475l-0.82463-2.678-0.1143-0.68583h-0.0163l-0.1143 0.694-0.80014 2.6698h-0.33475l-1.1186-4.1721h0.62868l0.62868 2.3759 0.098 0.79197h0.0163l0.14697-0.8083 0.6695-2.3596z"/>
+    <path d="m-267.23 10.301q0.1633 0.09798 0.38374 0.17146 0.22861 0.06532 0.46539 0.06532 0.26943 0 0.45722-0.13064 0.18778-0.1388 0.18778-0.44089 0-0.2531-0.1143-0.4164-0.11431-0.16329-0.29393-0.29393-0.17146-0.13063-0.37557-0.23677-0.20412-0.11431-0.38374-0.26943-0.17146-0.15513-0.28576-0.36741-0.11431-0.21228-0.11431-0.53887 0-0.52254 0.2776-0.78381 0.28576-0.26943 0.80014-0.26943 0.33475 0 0.57969 0.065317 0.24494 0.057152 0.42456 0.16329l-0.15513 0.48988q-0.15513-0.081646-0.35925-0.13063-0.20411-0.057153-0.41639-0.057153-0.29393 0-0.43273 0.12247-0.13063 0.12247-0.13063 0.38374 0 0.20412 0.1143 0.35108 0.11431 0.1388 0.28576 0.26127 0.17963 0.11431 0.38374 0.23677 0.20412 0.12247 0.37558 0.29393 0.17962 0.16329 0.29392 0.40007 0.11431 0.22861 0.11431 0.57969 0 0.22861-0.0735 0.43273-0.0735 0.20412-0.22861 0.35924-0.14697 0.14696-0.37558 0.23678-0.22044 0.08981-0.52253 0.08981-0.35925 0-0.62052-0.07348-0.26127-0.06532-0.44089-0.17962z"/>
+    <path d="m-264.89 12.114h2.727v0.5307h-2.727z"/>
+    <path d="m-261.74 10.301q0.16329 0.09798 0.38374 0.17146 0.22861 0.06532 0.46538 0.06532 0.26944 0 0.45722-0.13064 0.18779-0.1388 0.18779-0.44089 0-0.2531-0.11431-0.4164-0.1143-0.16329-0.29392-0.29393-0.17146-0.13063-0.37558-0.23677-0.20411-0.11431-0.38374-0.26943-0.17145-0.15513-0.28576-0.36741-0.1143-0.21228-0.1143-0.53887 0-0.52254 0.27759-0.78381 0.28577-0.26943 0.80014-0.26943 0.33475 0 0.57969 0.065317 0.24494 0.057152 0.42456 0.16329l-0.15513 0.48988q-0.15512-0.081646-0.35924-0.13063-0.20412-0.057153-0.4164-0.057153-0.29393 0-0.43272 0.12247-0.13064 0.12247-0.13064 0.38374 0 0.20412 0.11431 0.35108 0.1143 0.1388 0.28576 0.26127 0.17962 0.11431 0.38374 0.23677 0.20411 0.12247 0.37557 0.29393 0.17962 0.16329 0.29393 0.40007 0.1143 0.22861 0.1143 0.57969 0 0.22861-0.0735 0.43273-0.0735 0.20412-0.22861 0.35924-0.14696 0.14696-0.37557 0.23678-0.22045 0.08981-0.52254 0.08981-0.35924 0-0.62051-0.07348-0.26127-0.06532-0.44089-0.17962z"/>
+    <path d="m-256.48 10.693q-0.19595 0.17962-0.49804 0.2776-0.30209 0.09798-0.63684 0.09798-0.38374 0-0.6695-0.14696-0.2776-0.15513-0.46539-0.43273-0.17962-0.28576-0.26943-0.67767-0.0817-0.3919-0.0817-0.88178 0-1.0451 0.38374-1.5921 0.38374-0.54703 1.0859-0.54703 0.22861 0 0.44905 0.057152 0.22861 0.057153 0.40824 0.22861 0.17962 0.17146 0.28576 0.48171 0.1143 0.31026 0.1143 0.8083 0 0.1388-0.0163 0.30209-8e-3 0.15513-0.0245 0.32659h-2.0738q0 0.35108 0.0571 0.63684 0.0572 0.28576 0.17963 0.48988 0.12247 0.19595 0.31025 0.31026 0.19595 0.10614 0.48172 0.10614 0.22044 0 0.43272-0.08165 0.22045-0.08165 0.33475-0.19595zm-0.45722-2.1881q0.0163-0.61235-0.17145-0.89811-0.18779-0.28576-0.51438-0.28576-0.37557 0-0.59602 0.28576-0.22044 0.28576-0.26126 0.89811z"/>
+    <path d="m-253.05 12.604h-0.58785v-1.9677h-0.0327q-0.13064 0.20412-0.33475 0.31842-0.19595 0.1143-0.51438 0.1143-0.645 0-0.96342-0.51437-0.31843-0.52254-0.31843-1.6084 0-1.0532 0.4164-1.5921t1.2084-0.53887q0.34291 0 0.65317 0.081646 0.31026 0.081647 0.47355 0.17146zm-0.58785-5.1192q-0.22861-0.1388-0.64501-0.1388-0.42456 0-0.6695 0.3919-0.23678 0.3919-0.23678 1.2002 0 0.34292 0.0408 0.63684t0.13063 0.51437q0.098 0.21228 0.24494 0.33475 0.15513 0.1143 0.37557 0.1143 0.31026 0 0.48988-0.17962 0.17963-0.17962 0.26944-0.52254z"/>
+    <path d="m-251.57 6.8884v2.4984q0 0.62051 0.12247 0.88995 0.13064 0.26127 0.46539 0.26127 0.17146 0 0.30209-0.06532 0.1388-0.07348 0.24494-0.18779 0.10614-0.1143 0.18779-0.26127 0.0816-0.14696 0.13063-0.30209v-2.8331h0.58786v2.9229q0 0.29393 0.0163 0.61235 0.0245 0.31026 0.0653 0.54703h-0.41639l-0.14697-0.57152h-0.0245q-0.1388 0.26943-0.40007 0.47355-0.26127 0.19595-0.65317 0.19595-0.26127 0-0.45722-0.06532t-0.33475-0.23678q-0.1388-0.17146-0.21228-0.46538-0.0653-0.30209-0.0653-0.76748v-2.6453z"/>
+    <path d="m-246.11 10.693q-0.19595 0.17962-0.49804 0.2776-0.30209 0.09798-0.63684 0.09798-0.38374 0-0.66951-0.14696-0.27759-0.15513-0.46538-0.43273-0.17962-0.28576-0.26943-0.67767-0.0817-0.3919-0.0817-0.88178 0-1.0451 0.38374-1.5921 0.38374-0.54703 1.0859-0.54703 0.22861 0 0.44905 0.057152 0.22861 0.057153 0.40823 0.22861 0.17963 0.17146 0.28577 0.48171 0.1143 0.31026 0.1143 0.8083 0 0.1388-0.0163 0.30209-8e-3 0.15513-0.0245 0.32659h-2.0738q0 0.35108 0.0572 0.63684 0.0571 0.28576 0.17962 0.48988 0.12247 0.19595 0.31026 0.31026 0.19595 0.10614 0.48171 0.10614 0.22045 0 0.43273-0.08165 0.22045-0.08165 0.33475-0.19595zm-0.45722-2.1881q0.0163-0.61235-0.17146-0.89811-0.18778-0.28576-0.51437-0.28576-0.37557 0-0.59602 0.28576-0.22044 0.28576-0.26127 0.89811z"/>
+    <path d="m-243.1 10.971v-2.4902q0-0.61235-0.14696-0.88178-0.1388-0.2776-0.50621-0.2776-0.32659 0-0.53887 0.19595t-0.31025 0.48171v2.9719h-0.58786v-4.0823h0.42456l0.10614 0.43273h0.0245q0.15512-0.22045 0.41639-0.37557 0.26944-0.15513 0.63685-0.15513 0.26127 0 0.45722 0.073482 0.20411 0.073482 0.33475 0.2531 0.1388 0.17146 0.20411 0.46538 0.0735 0.29393 0.0735 0.74298v2.6453z"/>
+    <path d="m-239.35 10.767q-0.20412 0.15513-0.46539 0.22861-0.26127 0.07348-0.54703 0.07348-0.3919 0-0.66134-0.14696-0.26943-0.15513-0.44089-0.43273-0.16329-0.28576-0.24494-0.67767-0.0735-0.40007-0.0735-0.88178 0-1.0451 0.36741-1.5921 0.37557-0.54703 1.0696-0.54703 0.31842 0 0.54703 0.057152 0.22861 0.057153 0.3919 0.14696l-0.16329 0.51437q-0.32659-0.18779-0.71032-0.18779-0.4409 0-0.66951 0.3919-0.22044 0.38374-0.22044 1.2165 0 0.33475 0.049 0.62868t0.16329 0.51437q0.1143 0.21228 0.29393 0.34292 0.17962 0.12247 0.44905 0.12247 0.21228 0 0.39191-0.07348 0.18778-0.07348 0.30209-0.17146z"/>
+    <path d="m-236.4 10.693q-0.19595 0.17962-0.49804 0.2776-0.30209 0.09798-0.63685 0.09798-0.38373 0-0.6695-0.14696-0.27759-0.15513-0.46538-0.43273-0.17962-0.28576-0.26943-0.67767-0.0816-0.3919-0.0816-0.88178 0-1.0451 0.38374-1.5921 0.38374-0.54703 1.0859-0.54703 0.22861 0 0.44905 0.057152 0.22861 0.057153 0.40823 0.22861 0.17963 0.17146 0.28577 0.48171 0.1143 0.31026 0.1143 0.8083 0 0.1388-0.0163 0.30209-8e-3 0.15513-0.0245 0.32659h-2.0738q0 0.35108 0.0572 0.63684 0.0572 0.28576 0.17962 0.48988 0.12247 0.19595 0.31026 0.31026 0.19595 0.10614 0.48171 0.10614 0.22045 0 0.43273-0.08165 0.22045-0.08165 0.33475-0.19595zm-0.45722-2.1881q0.0163-0.61235-0.17146-0.89811-0.18778-0.28576-0.51437-0.28576-0.37557 0-0.59602 0.28576-0.22044 0.28576-0.26127 0.89811z"/>
+    <path d="m-235.48 6.8884h0.4164l0.10614 0.43273h0.0245q0.1143-0.23677 0.29393-0.36741 0.18778-0.1388 0.44905-0.1388 0.18779 0 0.42456 0.073482l-0.1143 0.59602q-0.21228-0.073482-0.37557-0.073482-0.26127 0-0.42457 0.15513-0.16329 0.14696-0.21228 0.40007v3.0046h-0.58785z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/doc-assets/lws_struct-overview.svg b/doc-assets/lws_struct-overview.svg
new file mode 100644 (file)
index 0000000..ba618c9
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="258.28mm" height="107.81mm" version="1.1" viewBox="0 0 258.28 107.81" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(.2 0 0 .2 1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="e" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(.2 0 0 .2 1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="f" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <marker id="c" overflow="visible" orient="auto">
+                       <path transform="matrix(.2 0 0 .2 1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="g" overflow="visible" orient="auto">
+                       <path transform="matrix(-.3 0 0 -.3 .69 0)" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width=".625"/>
+               </marker>
+               <filter id="d" x="-.03148" y="-.082687" width="1.063" height="1.1654" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="1.2468962"/>
+               </filter>
+       </defs>
+       <g transform="translate(751.29 58.975)">
+               <g>
+                       <rect transform="matrix(2.5561 0 0 2.5561 1169.1 91.771)" x="-748.3" y="-55.983" width="95.061" height="36.191" filter="url(#d)"/>
+                       <rect x="-744.61" y="-52.171" width="242.99" height="92.508" fill="#fff"/>
+                       <circle cx="-715.52" cy="-2.6399" r="13.375" fill="#d4aa00"/>
+                       <text x="-715.49457" y="-0.013140791" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="9.0174px" letter-spacing="0px" stroke-width=".6763" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-715.49457" y="-0.013140791" stroke-width=".6763">sqlite</tspan></text>
+                       <circle cx="-671.6" cy="-2.9116" r="13.375" fill="#b3b3b3"/>
+                       <text x="-671.56494" y="-0.28489438" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="9.0174px" letter-spacing="0px" stroke-width=".6763" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-671.56494" y="-0.28489438" stroke-width=".6763">C structs</tspan></text>
+                       <circle cx="-627.82" cy="-2.67" r="13.375" fill="#aad400"/>
+               </g>
+               <g fill="#000000" font-family="'Open Sans Condensed'" font-size="9.0174px" letter-spacing="0px" stroke-width=".6763" text-anchor="middle" word-spacing="0px">
+                       <text x="-627.78656" y="-0.043329135" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-627.78656" y="-0.043329135" stroke-width=".6763">JSON</tspan></text>
+                       <text x="-627.64801" y="21.166273" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-627.64801" y="21.166273" stroke-width=".6763">transit</tspan></text>
+                       <text x="-716.26086" y="21.211004" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-716.26086" y="21.211004" stroke-width=".6763">storage</tspan></text>
+                       <text x="-672.80682" y="20.817616" dominant-baseline="auto" text-align="center" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-672.80682" y="20.817616" stroke-width=".6763">processing</tspan></text>
+               </g>
+               <g fill="none" stroke="#000" stroke-width="1.7147">
+                       <path d="m-699.88-2.5175 13.015-0.1388" marker-end="url(#g)" marker-start="url(#c)" stroke-dasharray="3.42934834, 1.71467418"/>
+                       <path d="m-655.78-2.6715 13.015-0.1388" marker-end="url(#f)" marker-start="url(#b)"/>
+                       <path d="m-611.7-3.5168 13.015-0.1388" marker-end="url(#e)" marker-start="url(#a)"/>
+               </g>
+               <g transform="matrix(.98084 0 0 .98084 -621.3 237.27)" fill="#000080" opacity=".244">
+                       <path d="m41.881-252.66c-0.74224 2.5432-0.89347 6.2107 2.3684 9.2701"/>
+                       <path d="m53.529-227.18c6.6092-4.0094 3.4326-11.99-0.99884-13.439-1.8556-0.60663-5.8343 0.0294-7.9178 3.1773-0.58269 0.88034-2.1307 3.9439-0.48031 5.5996 2.9809 2.6693 5.6161 0.56537 5.3608-1.4101-0.21066-1.6304-2.5195-2.2246-3.5161-0.16091-0.25975-2.8413 3.6465-3.1988 4.5934-2.4826 3.041 2.3001 1.5823 6.4615 1.0276 7.1414-0.73173 0.89688-3.6971 3.6214-8.2393 0.56437-6.8298-5.655-0.13193-11.313 3.866-12.476 6.1028-2.0341 9.0102 1.5223 10.671 3.6144 3.6868 4.6452 1.9809 8.9627 5.3505 13.525 1.9595 3.128 7.9821 3.7261 11.158 0.60173 5.5288-6.1445 3.3332-13.229-1.2516-13.479-0.22801-0.0125-2.9101-0.63086-4.4241 2.3627-1.0649 2.794 4e-3 3.8072 0.81726 4.1078 2.9088 0.79976 4.5016-5.174 0.82228-3.1128 4.8236-4.2183 5.2182 0.96693 5.0018 2.2308-0.80165 5.564-9.3442 7.4818-10.819 2.8668-0.35205-0.62503-1.4338-5.0762 3.9067-8.1954 5.5765-3.6326 13.211 5.2134 17.982 5.8918 10.106 1.6835 14.307-6.6479 10.856-12.582-1.2554-1.7669-2.8684-3.5441-5.7068-3.7758-6.1109-0.17391-9.2141 3.4739-7.9593 8.2436 1.3959 2.9346 4.6564 2.9335 5.6551 2.6689 10.271-4.9733-4.2693-8.9086-0.87901-1.2245-4.8802-3.8972 0.63964-7.5993 4.4048-6.1991 0.96063 0.35724 5.1803 5.0648-0.05897 9.3556-1.1663 0.95517-4.5269 2.2051-8.0004 0.28109-3.3694-1.8664-3.5338-5.8701-2.477-9.261 1.0569-3.3909 5.3014-5.0232 8.3812-4.6644 6.7277 0.78392 8.7315 5.091 9.5955 7.5454 2.4366 6.9213-4.6408 13.804-8.902 13.4-4.2109 0.0282-8.4677 0.21333-12.52-6.2854 2.1202 3.9189 2.0498 12.14-3.7631 14.191-4.5313 1.8257-11.466 2.5989-16.992-3.7837-8.6613 4.8198-20.705 1.566-24.156-7.0006-0.92495-2.578-0.47339-6.8624 0.24474-9.4071-7.6693 1.5897-10.613-4.3062-9.6483-8.4011 0.91606-4.9175 6.1951-6.9841 9.8568-7.0428 1.5425-0.0247 4.6646-0.23374 6.5237 1.4445 0.91307 0.82426 0.56096-3.1566 4.8371-7.1831 1.0571-1.2067 4.9098-4.4309 6.8716-4.0876 2.9444 0.51523 6.0699-5.719 15.148-4.942 3.6186 0.30968 11.982 4.7173 15.123 5.2824 6.4081 1.1531 11.088-2.2 14.812 1.3207 2.1216 2.0055 4.2287 8.5906 1.9089 12.056-3.3431 5.2297 0.78824 7.9425 0.71094 11.137-0.41729 1.0853-2.8169-2.0166-3.8145-6.0592-0.12602-0.51067-1.1007-2.262-2.1876-2.0061 5.211-1.0111 1.7392-4.8923 3.8526-5.3799-1.3772 2.9802-4.6943 6.3834-11.832 6.001 2.3202-2.0058 7.0672-2.1803 9.3859-7.409 1.4893-3.3583-1.1162-6.6918-1.6409-7.4946-1.7761-2.7172-9.3712-0.53945-10.376 0.18939-1.5682 1.1375-2.5616 5.9494-0.43858 6.8512 0.54995 0.23361 1.794 0.56034 3.5295 0.0399 0.50862-0.15251 2.2807-1.4805 1.5209-3.1415-0.75989-1.661-4.1817-1.9196-3.2935 2.0787-2.4306-2.4398 0.42917-5.7946 3.2902-5.3916 0.79279 0.11167 2.0901 0.64339 2.7036 1.9892 0.7832 1.7182 0.04247 9.0743-6.3208 9.7095-2.5704-0.23817-4.933-1.0036-6.0081-4.2173-1.0894-3.2565-0.34649-6.6133 3.6075-8.8417-10.088-5.6172-13.564-5.3423-14.931-5.3234-3.5985 0.0499-11.415 2.2097-12.233 6.9656-2.0539 9.4145 6.0196 15.718 11.313 13.869 2.0435-0.71404 5.3178-3.0132 6.2069-5.7695 1.1253-2.7161 0.52067-9.2257-3.7502-9.8187-7.5674-1.0507-7.4178 5.1207-7.3467 5.7392 0.43169 3.7568 3.3857 3.0344 3.6417 2.0547 0.95883-3.6692-2.0573-0.28653-1.6915-1.0436 0.46602-0.96437 3.7458-4.2325 5.2541-0.60127 1.7856 4.2987-2.9558 7.0032-5.6537 6.3922-4.4526-1.0083-5.5626-5.3976-4.766-8.0601 2.1592-7.2172 11.062-5.6806 12.605-5.1024 5.4016 2.0239 6.2208 7.6173 5.5996 10.861-0.49155 2.5665-2.0196 9.0474-10.012 9.7885-4.1054 0.38065-9.5771-2.4666-11.216-5.3882-2.0253-3.6104-3.5546-6.4056-1.5183-12.344 0.49778-1.4517-0.80522-2.6114-2.1376-2.7358-1.8908-0.17651-7.8322 5.0017-8.3951 12.347-0.0049 0.0687 0.16434 3.8731 1.508 5.7369 1.0084 1.3987 1.5571 1.0966 3.0688 0.64569-0.50956 1.6452-2.9656 3.1932-5.2687 0.96907-1.1202-1.0819-1.4076-4.8291-2.1762-7.3776-0.52918-1.7545-1.3818-2.0825-1.8102-2.3238-10.206-1.1153-13.561 6.2269-9.7615 11.16 1.0884 1.2644 4.3886 2.667 7.0879-0.23624 0.9211-1.4945 1.569-5.634-2.188-6.2928-2.8126-0.35877-4.3231 2.5961-2.3307 4.1754 0.37764 0.29935 2.4183 0.24252 2.3959-1.3755 2.2889 2.8495-1.3845 4.3098-3.6108 2.018-1.7839-2.9588 1.0791-5.2149 3.1674-5.1311-0.18791-0.022 5.895-0.84785 4.9726 6.1419-4.9602 11.939 4.7226 21.559 15.224 15.13z"/>
+                       <path d="m49.624-251.08c4.0745-1.5884-4.5287-6.9179-2.0933 1.9722 3.3605 7.6759 16.386 13.929 25.972 10.856 6.2802-2.3279 6.422-8.3769 11.798-9.3185-1.4058 0.498-3.0762-0.99605-8.0373 3.492 2.7673-2.8522 0.46911-8.4801 0.5153-10.38 0.81305 6.522-3.3474 14.573-10.648 15.099-9.1475-0.20557-17.567-6.4219-18.317-10.157-1.3969-3.3934 1.9255-4.3416 0.80968-1.5631z"/>
+                       <path d="m65.991-266.93c-3.2975 0.0765-7.0461 1.7511-9.1722 4.3069-1.4452 1.8513-1.5312 4.3552-1.5178 6.6306 0.40978 2.9471 0.52755-2.4046 1.0549-3.2704 0.79541-3.0328-0.05729 1.3697 0.19279 2.5077-3e-3 1.4284 0.62634 4.1837 0.98556 4.6393-0.36808-2.9118-0.83127-5.9611-0.0089-8.831 1.1023-1.717-1.0043 5.3378-0.05502 2.4936 0.4507-2.5768 3.4798-4.9071 5.958-5.5887 1.9113-0.44792 3.8363 0.0423 5.7521 0.13584-1.5893-1.1774-4.5231-0.98014-5.3358-0.94935 1.9261-0.94969 4.1318-0.0705 6.1133 0.25724 0.92665 0.28009 6.1313 2.5542 3.7244 1.4918-2.7444-2.1779-4.7658-2.532-8.0506-2.962 2.403-0.17544 4.792 0.0365 7.8314 1.5548 1.0265 0.36413 3.0918 2.8212 3.2862 2.578-1.2943-1.7886-1.656-2.829-5.0375-4.2074-2.3484-0.68646-3.2712-0.65201-5.7213-0.78692z"/>
+                       <path d="m58.364-238.03c0.82342 0.96459 1.6468 1.9292 2.4703 2.8938 0.7972 4.0979 0.99109 8.6917 3.935 11.943 1.529 1.3148 3.8631 1.8335 5.5898 0.94221-1.6708 0.0763-3.301-0.33307-4.7173-1.19-1.0351-0.45373-2.6418-3.7051-0.93718-1.5735 1.7152 1.2122 3.8589 2.519 6.0318 1.776 1.6617-0.48301 3.0361-1.5936 3.9635-3.0436 0.658-0.86497-1.5494 1.0574-2.0538 1.3326-1.8192 1.5865-4.345 0.99565-6.3502 0.16388-1.5572-1.0506-3.0636-2.5037-3.4345-4.4401-0.53767-2.1605 0.98445-4.2317 2.4081-5.6982 1.6672-1.9048-2.3171 0.34388-2.1141 1.6664-1.0943 2.7953-0.80067-1.9094 0.29333-2.6286 1.0637-1.4519-1.4052-0.44253-1.242 0.76276-0.18207 0.78531-0.7779 2.569-0.75391 0.63336 0.26016-2.0878-1.6642-2.6196-3.0889-3.5399z"/>
+                       <path d="m28.156-247.06c0.92848-3.4966 5.0287-4.4196 8.1746-4.5432 2.7482-0.098 3.9609 2.5301 3.7359 4.9102 1.2078 1.8932 1.8939 5.2494 4.7221 5.2123 1.2565-0.038 4.8122-0.79982 1.7535 0.0858-1.4333 0.12222-3.256 0.27432-3.2963 0.71318-2.8124-0.78131-1.9305 3.4262-3.2886 4.988-0.84011 2.5666 0.06825 5.4727 1.5198 7.6219-2.1035-1.442-2.5373-4.3132-2.6564-6.6511-1.0429 0.61507 0.02588 3.9157-0.70499 1.3105-0.96281-3.0077 0.12061-6.1571 1.2057-8.9638-0.28831 1.356-1.0953 5.2215-0.54995 4.9051 0.17316-2.8227 2.8539-6.6846 0.33375-8.8214-0.47624-1.622-3.5311-2.5422-3.7271-2.9056 1.0824-0.19259 4.4689 1.1444 2.3851-0.89256-1.7961-1.5634-4.5102-0.71819-6.2136 0.55886-0.92245 1.0856-0.90091 1.8597-1.0907 0.0721-0.8937 0.11462-1.6812 1.692-2.3029 2.3998z"/>
+                       <path d="m45.236-235.2c1.4188-1.2781 3.3971-2.7545 5.3421-1.602 2.2217 0.66519 3.4478 3.1534 2.884 5.3536-0.35111 2.2009-1.6599 4.5037-3.921 5.1553 2.5987-0.48934 4.4214-2.8736 4.7078-5.4242 0.1482-0.77718 0.32924-3.0423 0.40115-1.0554 0.12287 1.4282 0.10405 3.0167-0.82412 4.1993 1.4662-1.0739 2.0707-2.9731 1.5762-4.6908-0.45478-0.93908-0.35002-2.0493 0.21589-0.60058 0.29431 0.77353 0.85771 2.5136 0.68046 0.59142 0.10183-2.1526-1.1589-3.992-2.5783-5.468-1.6545-1.5072-4.1397-1.0272-6.0679-0.40689-0.86995 0.3702-2.6266 1.5015-2.405 2.1681 1.5976-1.3604 3.7768-2.0397 5.8513-2.0223 1.1343 0.0526 1.9047 0.93003 0.30213 0.39937-1.4706-0.39468-4.1193 0.64258-4.5509 1.1895 1.2514-0.73692 2.9324-1.0773 4.2523-0.52456-2.0155 0.32148-4.9769-2e-3 -5.7173 2.462z"/>
+                       <path d="m86.737-261.88c2.7563-0.85852 6.3293-0.86997 7.8693 2.0772 2.368 3.7758-0.10823 8.4824-3.8369 10.086-1.0758 0.54064-4.9583 2.5338-2.4181 1.0435 1.7273-0.88976 5.7959-3.6731 5.9537-6.6596-1.5209 2.8566-5.3829 6.2444-5.4944 5.5818 2.2515-1.9622 5.7254-6.7355 4.4017-8.798-0.18086 2.3574-0.74912 3.2298-1.9174 4.6555 1.3091-2.3149 1.4959-5.674-1.6939-7.182-1.3411-0.35708-2.2776-0.17587-3.6745-0.07 3.191-1.0558 7.3728 0.49056 7.9763 1.8864-0.45062-2.8257-4.689-2.7438-7.1658-2.621z"/>
+                       <path d="m75.47-237.09c2.803 1.1663 5.3876 2.75 7.7894 4.5827 1.9392 1.5512 4.4316 2.1984 6.8793 1.8021 2.3819-0.15037 4.6177-1.2797 6.0384-3.2205 1.9103-1.8401 1.691-4.7936 1.1326-7.1714-0.72217-1.5912-1.5238-3.4197-3.1792-4.1842-2.4435-1.655-5.9834-0.92278-8.0631 1.0117-1.5923 0.92202-2.2122 4.5642-1.4908 5.0768-0.0885-1.1957 0.49061-4.0393 1.2515-3.838-0.37329 1.783-0.92585 4.5926 0.50945 5.5734-0.8281-2.4749-0.42119-5.6188 1.6709-7.3533 1.916-0.69165 1.3727 0.0971 0.05383 0.82066-1.0229 0.84309-0.97413 1.8974 0.2291 0.57914 1.6494-1.1529 5.0885-1.3778 6.0631 0.19933-0.90007-0.28761-2.911-1.116-1.022-0.0561 1.9922 0.71869 2.4199 2.8623 2.6766 4.5463 1.1651-3.0198-1.5306-6.5592-4.7208-6.464 2.0653-0.70343 4.5676 1.3093 5.1924 3.4456 0.99083 2.0816 0.44235 4.4699-0.78661 6.3162-0.28779 1.164-3.2359 3.5234-0.83706 1.7868 1.0288-0.62561 1.9327-3.8066 2.276-3.4672-0.62049 2.3505-2.1836 4.9718-4.8418 5.2318-1.685-1.3884-3.9146 1.1436-5.6327 0.0106 1.0791 1.0606 3.711-0.21206 4.0259 0.49175-2.1714 0.97677-4.6633 0.0758-6.5161-1.1913-2.1407-0.61396-3.3669-2.5051-3.7891-4.5991-0.80372-1.169 0.05018-4.2871 0.01116-4.1904-1.3934 1.8704-0.77804 4.5464 0.15394 6.3709-1.4526-1.2408-3.2863-1.6076-5.0744-2.1103z"/>
+                       <path d="m50.816-260.85c-4.7428 2.3195-7.1531 8.0201-6.1682 13.101 0.39021 3.199 4.0638 3.4365 5.7719 5.5891 1.5389 0.65182 2.9375 0.63553 0.71682-0.17495-1.5715-1.7161-4.12-3.0931-5.145-4.8013 1.783-0.2772 4.1569 3.8232 4.4273 3.0339-1.8386-2.4567-5.1454-4.5752-4.3129-8.1415-0.08179-1.1106 1.0497 5.0564 0.75955 1.2857-1.2383-2.9507 0.76703-6.6915 3.6364-3.2944 2.2856 2.3184-1.7431 5.7349 1.4032 7.6768 2.9709 2.6687 6.3811 5.045 10.377 5.8104-2.8244-1.2674-8.8144-4.4477-8.8341-5.6956 3.7202 2.8569 8.0143 5.1295 12.555 6.2946 1.903 0.41675 5.1739-0.28816 5.8167-1.6201-1.0633 0.93888-7.2765 1.5085-3.2054 0.5499 2.3524 0.0776 6.2515-3.239 5.786-3.926-3.7911 3.7293-9.9662 4.6807-14.649 2.0879-2.5619-1.4337-5.342-2.8518-7.136-5.2375-1.3113-2.369 3.214 2.47 0.93224-0.61762-1.1318-2.9407-2.6594-5.4934-4.4296-8.0667 0.12899-2.4985 1.7215-1.6882 1.3476 0.44799 1.3267 0.61265 1.9866 5.1062 2.2629 4.4024 3e-3 -2.2907-2.4535-5.7724-1.0966-7.2729 0.70538 5.5047 2.2634-4.336-0.81591-1.4309z"/>
+                       <path d="m64.434-258.85c0.50641-2.0361 2.6105-3.0205 4.9975-2.1045 2.2245 1.1612 3.1451 4.1428 2.485 6.4831-0.36288 1.6369-0.91876 2.8077-1.8397 4.0217 0.91276-2.6109 2.182-5.7102 0.3283-7.932 0.69965 1.4755 0.09798 4.4636-0.42959 6.0267-0.42961 1.2118-1.9816 2.8979-2.5725 3.3358 1.3169-1.5756 2.7659-3.31 2.7069-5.3954 0.55889-1.9106-0.70491-5.1142-3.0272-4.5658-1.0129 0.0956-3.4269 2.0908-3.4554 1.7915 1.0397-1.6217 3.2371-2.8233 5.0618-2.6442-0.69423-1.545-3.1889-0.0781-4.0742 0.84006z"/>
+               </g>
+               <text x="-696.14355" y="-27.217051" dominant-baseline="auto" fill="#000000" font-family="'Open Sans Condensed'" font-size="20.244px" letter-spacing="0px" stroke-width="1.5183" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-696.14355" y="-27.217051" stroke-width="1.5183">lws_struct</tspan></text>
+       </g>
+</svg>
diff --git a/doc-assets/lwsac.svg b/doc-assets/lwsac.svg
new file mode 100644 (file)
index 0000000..1a8ba87
--- /dev/null
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="187.8mm" height="80.752mm" version="1.1" viewBox="0 0 187.79663 80.752144" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.014418" y="-.034864" width="1.0288" height="1.0697" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="1.5466839"/>
+               </filter>
+       </defs>
+       <g transform="translate(-161.61 156.88)">
+               <g>
+                       <g stroke="#666" stroke-linejoin="round">
+                               <rect transform="matrix(.709 0 0 .709 167.83 -486.38)" x="-5.0609" y="468.46" width="257.45" height="106.47" ry="0" filter="url(#a)" stroke-width=".24551"/>
+                               <rect x="163.71" y="-154.85" width="182.53" height="75.489" ry="0" fill="#f2f2f2" stroke-width=".17406"/>
+                               <rect x="171.7" y="-140.36" width="30.952" height="50.082" fill="#808080" stroke-width=".40058"/>
+                               <rect x="172.68" y="-139.06" width="28.784" height="6.4996" fill="#a00" stroke-width=".11698"/>
+                               <rect x="172.74" y="-131.83" width="28.784" height="40.39" fill="#c4c8b7" stroke-width=".11698"/>
+                       </g>
+                       <text x="181.33067" y="-136.13423" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="181.33067" y="-136.13423" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac</tspan></text>
+                       <text x="184.53554" y="-92.850395" fill="#000000" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="184.53554" y="-92.850395" font-family="'Open Sans'" stroke-width=".39677">allocated area</tspan></text>
+                       <path d="m206.96-138.6 1.5352 0.74171-1.4119 0.76078-0.0812-0.34638-6.239-0.0174c0.0635-0.40246-0.0214-0.51522 0.0725-0.82588l6.136 5e-3z" fill="#2a7fff"/>
+                       <rect x="172" y="-150.67" width="26.156" height="4.4025" fill="#500" stroke="#666" stroke-linejoin="round" stroke-width=".091779"/>
+                       <text x="185.17738" y="-147.67433" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="185.17738" y="-147.67433" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac *head</tspan></text>
+                       <text x="198.1916" y="-137.38612" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="198.1916" y="-137.38612">next</tspan><tspan x="198.1916" y="-135.51024">head</tspan><tspan x="198.1916" y="-133.63435">curr</tspan></text>
+                       <path d="m174.55-138.71-0.74171 2.0619-0.76078-1.8964 0.34638-0.10906 0.0174-8.3796c0.40246 0.0853 0.51522-0.0288 0.82587 0.0974l-5e-3 8.2413z" fill="#2a7fff"/>
+                       <text x="188.84824" y="-135.46999" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="188.84824" y="-135.46999" text-align="start">ofs</tspan><tspan x="188.84824" y="-133.5941" text-align="start">alloc_size</tspan></text>
+                       <path d="m195.91-144.71-0.7417-2.0619-0.76078 1.8964 0.34637 0.10906-0.0412 9.1651c0.24984 0.0323 0.88518-0.0192 1.5658-0.0156 0.0711-0.0968 0.0691-0.46205 0.015-0.82619-0.45397 2e-3 -0.78488-5e-3 -0.69634-0.0411l-5e-3 -8.2413z" fill="#2a7fff"/>
+               </g>
+               <path d="m201.26-131.89-2.3876 2.1223-0.13265 35.947 2.7855 2.255" fill="none" stroke="#000" stroke-dasharray="0.5627649, 0.1875883" stroke-width=".18759"/>
+               <path d="m191.97-133.09 6.8975 7.6934" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <path d="m174.12-132.51-1.5352 0.7417 1.4119 0.76078 0.0812-0.34638 2.127 0.0157c-0.0635-0.40245 0.0214-0.54838-0.0725-0.85903l-2.024 5e-3z"/>
+               <path d="m176.09-131.63 5.7037 0.0664 6.2343-3.913 0.66323-0.0995" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <g>
+                       <rect x="220.5" y="-140.05" width="30.952" height="50.082" fill="#808080" stroke="#666" stroke-linejoin="round" stroke-width=".40058"/>
+                       <rect x="221.48" y="-138.74" width="28.784" height="6.4996" fill="#a00" stroke="#666" stroke-linejoin="round" stroke-width=".11698"/>
+                       <text x="226.27528" y="-118.77273" fill="#000000" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="226.27528" y="-118.77273" font-family="'Open Sans'" stroke-width=".18759">ptr aligned</tspan></text>
+                       <rect x="221.54" y="-131.51" width="28.784" height="40.39" fill="#c4c8b7" stroke="#666" stroke-linejoin="round" stroke-width=".11698"/>
+                       <text x="230.13133" y="-135.82219" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="230.13133" y="-135.82219" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac</tspan></text>
+                       <text x="233.3362" y="-92.538383" fill="#000000" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="233.3362" y="-92.538383" font-family="'Open Sans'" stroke-width=".39677">allocated area</tspan></text>
+                       <path d="m255.76-138.29 1.5352 0.74171-1.4119 0.76078-0.0812-0.34638-6.239-0.0174c0.0635-0.40246-0.0214-0.51522 0.0725-0.82588l6.136 5e-3z" fill="#2a7fff"/>
+                       <rect x="220.8" y="-150.36" width="26.156" height="4.4025" fill="#500" stroke="#666" stroke-linejoin="round" stroke-width=".091779"/>
+                       <text x="233.97806" y="-147.36229" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="233.97806" y="-147.36229" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac *head</tspan></text>
+                       <text x="246.99228" y="-137.07408" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="246.99228" y="-137.07408">next</tspan><tspan x="246.99228" y="-135.1982">head</tspan><tspan x="246.99228" y="-133.32231">curr</tspan></text>
+                       <path d="m223.35-138.4-0.74171 2.0619-0.76078-1.8964 0.34638-0.10905 0.0174-8.3796c0.40246 0.0853 0.51522-0.0288 0.82588 0.0974l-5e-3 8.2413z" fill="#2a7fff"/>
+                       <text x="237.64891" y="-135.15791" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="237.64891" y="-135.15791" text-align="start">ofs</tspan><tspan x="237.64891" y="-133.28203" text-align="start">alloc_size</tspan></text>
+                       <path d="m244.71-144.39-0.74171-2.0619-0.76078 1.8964 0.34638 0.10906-0.0412 9.1651c0.24984 0.0323 0.88518-0.0192 1.5658-0.0156 0.0711-0.0968 0.0691-0.46205 0.015-0.82618-0.45397 2e-3 -0.78488-5e-3 -0.69634-0.0411l-5e-3 -8.2413z" fill="#2a7fff"/>
+                       <rect x="221.98" y="-130.85" width="27.712" height="7.4313" fill="#ff8080" stroke="#666" stroke-linejoin="round" stroke-width=".14463"/>
+                       <rect x="221.97" y="-121.59" width="27.712" height="7.4313" fill="#ff8080" stroke="#666" stroke-linejoin="round" stroke-width=".14463"/>
+               </g>
+               <path d="m250.06-131.58-2.3876 2.1223-0.13265 35.947 2.7855 2.255" fill="none" stroke="#000" stroke-dasharray="0.56276491, 0.1875883" stroke-width=".18759"/>
+               <path d="m240.78-132.77 6.8975 7.6934" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <text x="235.9016" y="-126.22051" fill="#ffffff" font-family="'Open Sans'" font-size="2.5582px" letter-spacing="0px" stroke-width=".31977" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="235.9016" y="-126.22051" fill="#ffffff" font-family="'Open Sans'" stroke-width=".31977">lwsac_use area</tspan></text>
+               <path d="m223.49-112.72-1.5352 0.7417 1.4119 0.76079 0.0812-0.34638 2.127 0.0157c-0.0635-0.40245 0.0214-0.54838-0.0725-0.85903l-2.024 5e-3z"/>
+               <path d="m225.29-112.13 8.31 0.0664 3.0298-2.9076 0.20032-20.194 0.66323-0.0995" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <g>
+                       <text x="177.42035" y="-129.76131" fill="#000000" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="177.42035" y="-129.76131" font-family="'Open Sans'" stroke-width=".18759">ptr aligned</tspan></text>
+                       <text x="235.79298" y="-122.17254" fill="#000000" font-family="'Open Sans'" font-size="1.0364px" letter-spacing="0px" stroke-width=".12956" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="235.79298" y="-122.17254" font-family="'Open Sans'" stroke-width=".12956">alignment padding</tspan></text>
+                       <path d="m220.64-131.26 1.5352 0.7417-1.4119 0.76079-0.0812-0.34638-2.127 0.0157c0.0635-0.40246-0.0214-0.54838 0.0725-0.85903l2.024 5e-3z"/>
+                       <g font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                               <text x="235.89856" y="-116.96035" fill="#ffffff" font-size="2.5582px" stroke-width=".31977" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="235.89856" y="-116.96035" fill="#ffffff" font-family="'Open Sans'" stroke-width=".31977">lwsac_use area</tspan></text>
+                               <text x="226.27226" y="-109.51257" fill="#000000" font-size="1.5007px" stroke-width=".18759" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="226.27226" y="-109.51257" font-family="'Open Sans'" stroke-width=".18759">ptr aligned</tspan></text>
+                               <text x="235.78995" y="-112.91241" fill="#000000" font-size="1.0364px" stroke-width=".12956" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="235.78995" y="-112.91241" font-family="'Open Sans'" stroke-width=".12956">alignment padding</tspan></text>
+                       </g>
+                       <path d="m220.64-122 1.5352 0.7417-1.4119 0.76079-0.0812-0.34638-2.127 0.0157c0.0635-0.40245-0.0214-0.54838 0.0725-0.85903l2.024 5e-3z"/>
+                       <rect x="268.33" y="-140.45" width="30.952" height="50.082" fill="#808080" stroke="#666" stroke-linejoin="round" stroke-width=".40058"/>
+                       <rect x="269.31" y="-139.14" width="28.784" height="6.4996" fill="#a00" stroke="#666" stroke-linejoin="round" stroke-width=".11698"/>
+                       <text x="274.10483" y="-119.17068" fill="#000000" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="274.10483" y="-119.17068" font-family="'Open Sans'" stroke-width=".18759">ptr aligned</tspan></text>
+                       <rect x="269.37" y="-131.91" width="28.784" height="40.39" fill="#c4c8b7" stroke="#666" stroke-linejoin="round" stroke-width=".11698"/>
+                       <text x="277.96088" y="-136.22011" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="277.96088" y="-136.22011" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac</tspan></text>
+                       <text x="281.16574" y="-92.936333" fill="#000000" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="281.16574" y="-92.936333" font-family="'Open Sans'" stroke-width=".39677">allocated area</tspan></text>
+                       <rect x="302.47" y="-140.62" width="30.952" height="50.082" fill="#808080" stroke="#666" stroke-linejoin="round" stroke-width=".40058"/>
+                       <rect x="303.45" y="-139.31" width="28.784" height="6.4996" fill="#a00" stroke="#666" stroke-linejoin="round" stroke-width=".11698"/>
+                       <path d="m303.59-138.69 1.5352 0.7417-1.4119 0.76078-0.0812-0.34638-6.239-0.0174c0.0635-0.40245-0.0214-0.51522 0.0725-0.82587l6.136 5e-3z" fill="#2a7fff"/>
+                       <rect x="268.63" y="-150.76" width="26.156" height="4.4025" fill="#500" stroke="#666" stroke-linejoin="round" stroke-width=".091779"/>
+                       <text x="281.80762" y="-147.76024" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="281.80762" y="-147.76024" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac *head</tspan></text>
+                       <text x="294.82181" y="-137.47203" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="294.82181" y="-137.47203">next</tspan><tspan x="294.82181" y="-135.59615">head</tspan><tspan x="294.82181" y="-133.72026">curr</tspan></text>
+                       <path d="m271.18-138.8-0.74171 2.0619-0.76078-1.8964 0.34638-0.10906 0.0174-8.3796c0.40246 0.0853 0.51522-0.0288 0.82587 0.0974l-5e-3 8.2413z" fill="#2a7fff"/>
+                       <text x="285.47845" y="-135.55586" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="285.47845" y="-135.55586" text-align="start">ofs</tspan><tspan x="285.47845" y="-133.67998" text-align="start">alloc_size</tspan></text>
+                       <path d="m292.54-144.79-0.74171-2.0619-0.76078 1.8964 0.34638 0.10906-0.0412 9.1651c0.24984 0.0323 0.88517-0.0192 1.5658-0.0156 0.0711-0.0968 0.0691-0.46206 0.015-0.82619-0.45397 2e-3 -0.78487-5e-3 -0.69633-0.0411l-5e-3 -8.2413z" fill="#2a7fff"/>
+                       <rect x="269.81" y="-131.25" width="27.712" height="7.4313" fill="#ff8080" stroke="#666" stroke-linejoin="round" stroke-width=".14463"/>
+                       <rect x="269.8" y="-121.99" width="27.712" height="7.4313" fill="#ff8080" stroke="#666" stroke-linejoin="round" stroke-width=".14463"/>
+               </g>
+               <path d="m297.89-131.98-2.3876 2.1223-0.13264 35.947 2.7855 2.255" fill="none" stroke="#000" stroke-dasharray="0.56276492, 0.1875883" stroke-width=".18759"/>
+               <path d="m288.61-133.17 6.8975 7.6934" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <text x="283.73114" y="-126.61846" fill="#ffffff" font-family="'Open Sans'" font-size="2.5582px" letter-spacing="0px" stroke-width=".31977" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="283.73114" y="-126.61846" fill="#ffffff" font-family="'Open Sans'" stroke-width=".31977">lwsac_use area</tspan></text>
+               <path d="m271.31-113.11-1.5352 0.7417 1.4119 0.76079 0.0812-0.34638 2.127 0.0157c-0.0635-0.40246 0.0214-0.54838-0.0725-0.85904l-2.024 5e-3z"/>
+               <path d="m273.12-112.52 8.31 0.0664 3.0298-2.9076 0.20032-20.194 0.66323-0.0995" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <g>
+                       <text x="283.62253" y="-122.57049" fill="#000000" font-family="'Open Sans'" font-size="1.0364px" letter-spacing="0px" stroke-width=".12956" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="283.62253" y="-122.57049" font-family="'Open Sans'" stroke-width=".12956">alignment padding</tspan></text>
+                       <path d="m268.47-131.66 1.5352 0.74171-1.4119 0.76078-0.0812-0.34638-2.127 0.0157c0.0635-0.40245-0.0214-0.54838 0.0725-0.85903l2.024 5e-3z"/>
+                       <g font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                               <text x="283.72812" y="-117.3583" fill="#ffffff" font-size="2.5582px" stroke-width=".31977" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="283.72812" y="-117.3583" fill="#ffffff" font-family="'Open Sans'" stroke-width=".31977">lwsac_use area</tspan></text>
+                               <text x="274.10181" y="-109.91051" fill="#000000" font-size="1.5007px" stroke-width=".18759" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="274.10181" y="-109.91051" font-family="'Open Sans'" stroke-width=".18759">ptr aligned</tspan></text>
+                               <text x="283.61948" y="-113.31036" fill="#000000" font-size="1.0364px" stroke-width=".12956" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="283.61948" y="-113.31036" font-family="'Open Sans'" stroke-width=".12956">alignment padding</tspan></text>
+                       </g>
+                       <path d="m268.47-122.4 1.5352 0.7417-1.4119 0.76079-0.0812-0.34638-2.127 0.0157c0.0635-0.40246-0.0214-0.54838 0.0725-0.85904l2.024 5e-3z"/>
+                       <rect x="303.52" y="-132.08" width="28.784" height="40.39" fill="#c4c8b7" stroke="#666" stroke-linejoin="round" stroke-width=".11698"/>
+                       <text x="312.10593" y="-136.39232" fill="#ffffff" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="312.10593" y="-136.39232" fill="#ffffff" font-family="'Open Sans'" font-size="2.8222px" stroke-width=".39677">struct lwsac</tspan></text>
+                       <text x="315.31079" y="-93.108513" fill="#000000" font-family="'Open Sans'" font-size="3.1742px" letter-spacing="0px" stroke-width=".39677" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="315.31079" y="-93.108513" font-family="'Open Sans'" stroke-width=".39677">allocated area</tspan></text>
+                       <path d="m337.74-138.86 1.5352 0.74171-1.4119 0.76078-0.0812-0.34638-6.239-0.0174c0.0635-0.40245-0.0214-0.51522 0.0725-0.82587l6.136 5e-3z" fill="#2a7fff"/>
+                       <text x="328.96686" y="-137.64424" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="328.96686" y="-137.64424">next</tspan><tspan x="328.96686" y="-135.76836">head</tspan><tspan x="328.96686" y="-133.89247">curr</tspan></text>
+                       <text x="319.6235" y="-135.72807" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" stroke-width=".18759" text-align="center" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="319.6235" y="-135.72807" text-align="start">ofs</tspan><tspan x="319.6235" y="-133.85219" text-align="start">alloc_size</tspan></text>
+                       <rect x="304.15" y="-131.34" width="27.572" height="28.913" fill="#ff8080" stroke="#666" stroke-linejoin="round" stroke-width=".28455"/>
+               </g>
+               <path d="m332.04-132.15-2.3876 2.1223-0.13264 35.947 2.7855 2.255" fill="none" stroke="#000" stroke-dasharray="0.56276491, 0.1875883" stroke-width=".18759"/>
+               <path d="m322.75-133.34 6.8975 7.6934" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <path d="m292.68-145.32 0.74171-1.5352 0.76078 1.4119-0.34638 0.0812 0.0259 2.071 28.542 0.041 2.225 2.8639 0.0875 2.9524 0.64641 0.65493 1.5316 2e-3 0.0488 0.76904-2.1195-0.0297-1.142-1.1761-0.0569-2.9726-2.1165-2.253c-0.40246-0.0635-28.205 0.0547-28.516-0.0391l5e-3 -2.8532z" fill="#2a7fff"/>
+               <path d="m303.21-134.9 1.5352 0.74171-1.4119 0.76078-0.0812-0.34638-6.239-0.0174c0.0635-0.40245-0.0214-0.51522 0.0725-0.82587l6.136 5e-3z" fill="#2a7fff"/>
+               <path d="m307.26-100.45 1.4591 0.0664 3.06-3.7843-9e-3 -23.607 7.6934-8.0582" fill="none" stroke="#000" stroke-dasharray="0.37517659, 0.1875883" stroke-width=".18759"/>
+               <g>
+                       <path d="m305.59-100.91-1.5352 0.74171 1.4119 0.76078 0.0812-0.34638 2.127 0.01569c-0.0635-0.40245 0.0214-0.54838-0.0725-0.85903l-2.024 5e-3z"/>
+                       <g font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                               <text x="318.0058" y="-105.15496" fill="#ffffff" font-size="2.5582px" stroke-width=".31977" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="318.0058" y="-105.15496" fill="#ffffff" font-family="'Open Sans'" stroke-width=".31977">lwsac_use area</tspan></text>
+                               <text x="308.37949" y="-97.707207" fill="#000000" font-size="1.5007px" stroke-width=".18759" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="308.37949" y="-97.707207" font-family="'Open Sans'" stroke-width=".18759">ptr aligned</tspan></text>
+                               <text x="317.89719" y="-101.10705" fill="#000000" font-size="1.0364px" stroke-width=".12956" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="317.89719" y="-101.10705" font-family="'Open Sans'" stroke-width=".12956">alignment padding</tspan></text>
+                       </g>
+                       <path d="m302.48-132.08 1.5352 0.74171-1.4119 0.76078-0.0812-0.34638-2.127 0.0157c0.0635-0.40245-0.0214-0.54838 0.0725-0.85903l2.024 5e-3z"/>
+                       <g stroke-width=".18759">
+                               <rect x="204.83" y="-139.17" width="5.018" height="2.6731" ry="1.3366" fill="#808080" stroke="#666" stroke-linejoin="round"/>
+                               <text x="207.36176" y="-137.33116" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="207.36176" y="-137.33116" fill="#ffffff" font-family="'Open Sans'" stroke-width=".18759">NULL</tspan></text>
+                               <rect x="253.14" y="-139.13" width="5.018" height="2.6731" ry="1.3366" fill="#808080" stroke="#666" stroke-linejoin="round"/>
+                               <text x="255.6705" y="-137.29219" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="255.6705" y="-137.29219" fill="#ffffff" font-family="'Open Sans'" stroke-width=".18759">NULL</tspan></text>
+                               <rect x="336.04" y="-139.46" width="5.018" height="2.6731" ry="1.3366" fill="#808080" stroke="#666" stroke-linejoin="round"/>
+                               <text x="338.57361" y="-137.62383" fill="#ffffff" font-family="'Open Sans'" font-size="1.5007px" letter-spacing="0px" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="338.57361" y="-137.62383" fill="#ffffff" font-family="'Open Sans'" stroke-width=".18759">NULL</tspan></text>
+                       </g>
+               </g>
+               <g fill="#0000ff" font-family="'Open Sans'" font-size="3.2649px" letter-spacing="0px" stroke-width=".40811" text-anchor="middle" word-spacing="0px">
+                       <text x="187.11359" y="-84.657211" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="187.11359" y="-84.657211" fill="#0000ff" font-family="'Open Sans'" stroke-width=".40811">empty, generic lwsac</tspan></text>
+                       <text x="235.71112" y="-84.353561" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="235.71112" y="-84.353561" fill="#0000ff" font-family="'Open Sans'" stroke-width=".40811">lwsac with 2 "uses"</tspan></text>
+                       <text x="300.70877" y="-83.844711" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="300.70877" y="-83.844711" fill="#0000ff" font-family="'Open Sans'" stroke-width=".40811">lwsac with 2 "uses", 3rd requires a new one</tspan></text>
+               </g>
+       </g>
+</svg>
diff --git a/doc-assets/threadpool-states.svg b/doc-assets/threadpool-states.svg
new file mode 100644 (file)
index 0000000..024c03f
--- /dev/null
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="139.96mm" height="203.94mm" version="1.1" viewBox="0 0 139.95773 203.94206" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+       <defs>
+               <marker id="a" overflow="visible" orient="auto">
+                       <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="m" overflow="visible" orient="auto">
+                       <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/>
+               </marker>
+               <marker id="o" overflow="visible" orient="auto">
+                       <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/>
+               </marker>
+               <marker id="n" overflow="visible" orient="auto">
+                       <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/>
+               </marker>
+               <marker id="p" overflow="visible" orient="auto">
+                       <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/>
+               </marker>
+               <marker id="Arrow1Mend" overflow="visible" orient="auto">
+                       <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="e" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="f" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="g" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="h" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="b" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="i" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="c" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="j" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="d" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <marker id="k" overflow="visible" orient="auto">
+                       <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
+               </marker>
+               <filter id="l" x="-.028307" y="-.019087" width="1.0566" height="1.0382" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="1.5622839"/>
+               </filter>
+       </defs>
+       <metadata>
+               <rdf:RDF>
+                       <cc:Work rdf:about="">
+                               <dc:format>image/svg+xml</dc:format>
+                               <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+                               <dc:title/>
+                       </cc:Work>
+               </rdf:RDF>
+       </metadata>
+       <g transform="translate(-63.825 34.781)">
+               <g>
+                       <rect x="67.574" y="-31.031" width="132.46" height="196.44" filter="url(#l)"/>
+                       <rect x="67.013" y="-31.592" width="132.46" height="196.44" fill="#fff"/>
+                       <rect x="71.316" y="4.5158" width="60.617" height="156.03" fill="#e3e2db" opacity=".497"/>
+                       <circle cx="101.26" cy="21.51" r="9.1221" fill="#fca" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+                       <text x="101.37344" y="22.928638" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.37344" y="22.928638" font-family="'Open Sans'" stroke-width=".53093">queued</tspan></text>
+                       <circle cx="101.06" cy="43.549" r="9.1221" fill="#ff0" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+                       <text x="101.1737" y="44.968067" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.1737" y="44.968067" font-family="'Open Sans'" stroke-width=".53093">running</tspan></text>
+                       <circle cx="88.275" cy="111.47" r="9.1221" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+                       <text x="88.389488" y="112.88415" fill="#ffffff" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="88.389488" y="112.88415" fill="#ffffff" font-family="'Open Sans'" stroke-width=".53093">finished</tspan></text>
+                       <circle cx="115.44" cy="111.6" r="9.1221" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+                       <text x="115.55593" y="113.01734" fill="#ffffff" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="115.55593" y="113.01734" fill="#ffffff" font-family="'Open Sans'" stroke-width=".53093">stopped</tspan></text>
+                       <circle cx="101.19" cy="89.093" r="9.1221" fill="#00f" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+                       <text x="101.30688" y="90.511833" fill="#ffffff" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.30688" y="90.511833" fill="#ffffff" font-family="'Open Sans'" stroke-width=".53093">stopping</tspan></text>
+               </g>
+               <g fill="none" stroke="#000" stroke-width=".98901">
+                       <path d="m92.669 25.172c-5.6792 3.7073-5.9327 8.623-3.0086 12.864 0.59949 0.92797 1.3564 1.75 2.2096 2.45" marker-end="url(#k)"/>
+                       <path d="m92.794 46.725c-6.9332 3.511-5.3268 11.205-0.79902 15.314" marker-end="url(#d)"/>
+                       <path d="m107.31 95.998c1.5624 2.1075 1.7621 1.7662 4.5277 6.3921" marker-end="url(#j)"/>
+                       <path d="m96.922 73.892c-19.598 6.9719-17.845 24.544-15.314 29.697" marker-end="url(#c)"/>
+                       <path d="m105.58 74.072c3.4663 2.6148 2.6371 4.0982 2.0086 6.4967" marker-end="url(#i)"/>
+               </g>
+               <text x="83.188652" y="26.350147" fill="#000000" font-family="'Open Sans'" font-size="1.9194px" letter-spacing="0px" stroke-width=".23992" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="83.188652" y="26.350147">threadpool</tspan><tspan x="83.188652" y="28.74935">worker</tspan><tspan x="83.188652" y="31.148552">thread</tspan><tspan x="83.188652" y="33.547756">takes</tspan><tspan x="83.188652" y="35.946957">task</tspan></text>
+               <text x="81.767693" y="52.351551" fill="#000000" font-family="'Open Sans'" font-size="1.9194px" letter-spacing="0px" stroke-width=".23992" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="81.767693" y="52.351551">worker</tspan><tspan x="81.767693" y="54.750755">produces</tspan><tspan x="81.767693" y="57.149956">a buffer</tspan><tspan x="81.767693" y="59.54916">of output</tspan></text>
+               <path d="m109.63 63.431c6.9332-3.511 5.3268-11.205 0.79901-15.314" fill="none" marker-end="url(#b)" stroke="#000" stroke-width=".98901"/>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="85.033684" y="93.276115" font-size="1.9194px" stroke-width=".23992" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="85.033684" y="93.276115">nothing</tspan><tspan x="85.033684" y="95.675316">more</tspan><tspan x="85.033684" y="98.074524">to do</tspan></text>
+                       <text x="120.33315" y="51.474964" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="120.33315" y="51.474964">buffer</tspan><tspan x="120.33315" y="54.120796">sent on</tspan><tspan x="120.33315" y="56.766632">and more</tspan><tspan x="120.33315" y="59.412464">to do</tspan></text>
+                       <text x="113.87862" y="74.393372" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="113.87862" y="74.393372">problems</tspan><tspan x="113.87862" y="77.039207">sending</tspan></text>
+               </g>
+               <circle cx="101.19" cy="66.055" r="9.1221" fill="#f00" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+               <text x="101.30688" y="67.473564" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.30688" y="67.473564" font-family="'Open Sans'" stroke-width=".53093">sync</tspan></text>
+               <path d="m115.52 120.58c-1.0042 3.6297-3.8472 3.6305-6.9278 6.0658" fill="none" marker-end="url(#h)" stroke="#000" stroke-width=".98901"/>
+               <path d="m89.258 120.63c1.0042 3.6297 3.8472 3.6305 6.9278 6.0658" fill="none" marker-end="url(#g)" stroke="#000" stroke-width=".98901"/>
+               <text x="102.9891" y="153.14995" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="102.9891" y="153.14995" font-family="'Open Sans'" stroke-width=".53093">free task</tspan></text>
+               <path d="m103.43 141.5c-0.56428 2.168-0.208 2.0595-0.42484 6.8954" fill="none" marker-end="url(#f)" stroke="#000" stroke-width=".98901"/>
+               <circle cx="102.84" cy="133.7" r="9.1221" fill="#f00" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="102.95511" y="135.11937" font-size="4.2475px" stroke-width=".53093" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="102.95511" y="135.11937" font-family="'Open Sans'" stroke-width=".53093">sync</tspan></text>
+                       <text x="91.148003" y="3.3398941" font-size="3.8932px" stroke-width=".48665" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="91.148003" y="3.3398941" font-family="'Open Sans'" stroke-width=".48665">worker thread context</tspan></text>
+                       <text x="158.78787" y="-23.464428" font-size="3.8932px" stroke-width=".48665" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="158.78787" y="-23.464428" font-family="'Open Sans'" stroke-width=".48665">lws service thread context</tspan></text>
+               </g>
+               <rect x="135.67" y="-21.491" width="60.617" height="182.23" fill="#9dac93" opacity=".497"/>
+               <text x="87.216949" y="130.01646" fill="#000000" font-family="'Open Sans'" font-size="1.9194px" letter-spacing="0px" stroke-width=".23992" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="87.216949" y="130.01646">wait until</tspan><tspan x="87.216949" y="132.41566">the lws</tspan><tspan x="87.216949" y="134.81487">service</tspan><tspan x="87.216949" y="137.21407">thread</tspan><tspan x="87.216949" y="139.61328">knows the</tspan><tspan x="87.216949" y="142.01248">task is done</tspan></text>
+               <path d="m167.23 0.99672c-0.56428 2.168-0.208 2.0595-0.42484 6.8954" fill="none" marker-end="url(#e)" stroke="#000" stroke-width=".98901"/>
+               <g>
+                       <circle cx="166.68" cy="-6.6945" r="9.1221" fill="#cfa" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/>
+                       <text x="166.52914" y="-9.9058332" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="166.52914" y="-9.9058332" style="line-height:1">new</tspan><tspan x="166.52914" y="-5.6583772" style="line-height:1">wsi on</tspan><tspan x="166.52914" y="-1.4109211" style="line-height:1">mount</tspan></text>
+                       <rect x="153.45" y="8.1222" width="27.384" height="16.007" fill="#37c8ab" opacity=".497"/>
+                       <text x="167.22537" y="13.081109" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="167.22537" y="13.081109" style="line-height:1">protocol</tspan><tspan x="167.22537" y="17.328566" style="line-height:1">_HTTP</tspan><tspan x="167.22537" y="21.576021" style="line-height:1">callback</tspan></text>
+               </g>
+               <path d="m153.38 15.565-42.877 5.5222" fill="none" marker-end="url(#Arrow1Mend)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/>
+               <text transform="rotate(-6.3716)" x="129.80591" y="31.348564" fill="#0044aa" font-family="'Open Sans'" font-size="2.9008px" letter-spacing="0px" stroke-width=".3626" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="129.80591" y="31.348564" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">enqueue threadpool task</tspan></text>
+               <path d="m110.03 66.524 43.37-14.496" fill="none" marker-end="url(#p)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/>
+               <g>
+                       <rect x="153.23" y="43.846" width="27.384" height="16.007" fill="#37c8ab" opacity=".497"/>
+                       <text x="167.00159" y="51.049557" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="167.00159" y="51.049557" style="line-height:1">protocol</tspan><tspan x="167.00159" y="55.297012" style="line-height:1">WRITEABLE</tspan></text>
+                       <text transform="rotate(-17.805)" x="111.54491" y="95.327446" fill="#0044aa" font-family="'Open Sans'" font-size="2.9008px" letter-spacing="0px" stroke-width=".3626" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="111.54491" y="95.327446" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">cancel service</tspan></text>
+               </g>
+               <path d="m152.72 54.497-43.37 14.496" fill="none" marker-end="url(#n)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/>
+               <g>
+                       <rect x="153.67" y="114.19" width="27.384" height="16.007" fill="#37c8ab" opacity=".497"/>
+                       <text x="167.45035" y="121.39483" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="167.45035" y="121.39483" style="line-height:1">protocol</tspan><tspan x="167.45035" y="125.64229" style="line-height:1">WRITEABLE</tspan></text>
+                       <text transform="rotate(-17.805)" x="89.692116" y="162.38983" fill="#0044aa" font-family="'Open Sans'" font-size="2.9008px" letter-spacing="0px" stroke-width=".3626" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="89.692116" y="162.38983" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">cancel service</tspan></text>
+               </g>
+               <path d="m111.52 136.63 43.37-14.496" fill="none" marker-end="url(#o)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/>
+               <path d="m154.21 124.6-43.37 14.496" fill="none" marker-end="url(#m)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/>
+               <g font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text transform="rotate(-17.805)" x="107.68779" y="101.80786" fill="#0044aa" font-size="2.9008px" stroke-width=".3626" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="107.68779" y="101.80786" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">lws_threadpool_task_sync</tspan></text>
+                       <text transform="rotate(-17.805)" x="85.189613" y="168.89598" fill="#0044aa" font-size="2.9008px" stroke-width=".3626" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="85.189613" y="168.89598" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">lws_threadpool_task_status_wsi</tspan></text>
+                       <text x="132.67136" y="108.40496" fill="#000000" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="132.67136" y="108.40496">move to</tspan><tspan x="132.67136" y="111.0508">"done queue"</tspan><tspan x="132.67136" y="113.69662">idling</tspan><tspan x="132.67136" y="116.34246">worker thread</tspan></text>
+               </g>
+               <path d="m91.919 68.712c-21.074 4.8881-23.555 52.34-13.084 71.959 2.8493 5.3384 9.0772 11.691 14.142 11.914" fill="none" marker-end="url(#a)" stroke="#000" stroke-dasharray="0.265, 0.265" stroke-width=".265"/>
+               <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px">
+                       <text x="79.087029" y="66.727417" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="79.087029" y="66.727417">wsi has</tspan><tspan x="79.087029" y="69.373253">unexpect-</tspan><tspan x="79.087029" y="72.019081">edly gone</tspan></text>
+                       <text x="167.50217" y="64.309265" font-size="3.5963px" stroke-width=".44954" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="167.50217" y="64.309265">write the</tspan><tspan x="167.50217" y="68.804657">buffer on</tspan><tspan x="167.50217" y="73.300049">the wsi</tspan></text>
+                       <text x="167.53455" y="136.16283" font-size="3.5963px" stroke-width=".44954" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="167.53455" y="136.16283">acknowledge</tspan><tspan x="167.53455" y="140.65822">the task has</tspan><tspan x="167.53455" y="145.15361">ended</tspan></text>
+                       <text x="100.54455" y="-20.110546" font-size="7.4216px" stroke-width=".9277" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="100.54455" y="-20.110546" font-family="'Open Sans'" stroke-width=".9277">Threadpool</tspan></text>
+               </g>
+               <g transform="matrix(1.3705 0 0 1.3705 152.73 -89.601)">
+                       <path d="m-53.889 51.059c-0.2293-0.20876-0.45859-0.41752-0.68788-0.62628h-2.0842c-0.41073-0.4478-0.80598-0.91066-1.2269-1.3486-0.15635-0.18661-0.4411-0.26749-0.65538-0.13157-0.2532 0.13273-0.37597 0.41669-0.39448 0.69074-0.04758 0.31909 0.05354 0.68674 0.35175 0.85254 0.18832 0.10808 0.49386 0.04671 0.5437-0.19062 0.06699-0.21357-0.06488-0.51518-0.31343-0.50948-0.16072 3e-3 -0.26391 0.28316-0.07717 0.33226 0.08893 0.01617 0.10978-0.27326 0.17002-0.07939 0.0638 0.17507-0.16528 0.31938-0.28293 0.16402-0.213-0.21052-0.14192-0.60391 0.10406-0.75718 0.23529-0.07437 0.39199 0.16171 0.52824 0.31112 0.3873 0.42849 0.77336 0.85822 1.1601 1.2873 0.95481 0.0017 1.9097 0.0034 2.8645 0.0051z"/>
+                       <path d="m-56.225 50.077c0.07138-0.08712 0.14278-0.17424 0.21416-0.26135 0.10648 0.11858 0.21295 0.23715 0.31943 0.35573 0.19118 0.0012 0.38235 0.0025 0.57352 0.0036-0.2069-0.22868-0.41381-0.45737-0.62072-0.68605 0.10043-0.11374 0.20086-0.22747 0.30128-0.34121 0.19601 0.22868 0.39203 0.45737 0.58804 0.68605-0.0012-0.219-0.0025-0.43801-0.0036-0.65701-0.10043-0.11252-0.20085-0.22505-0.30128-0.33758 0.07198-0.10615 0.24907-0.21551 0.08668-0.31451-0.36406-0.40214-0.72811-0.80428-1.0922-1.2064-0.76543-0.0026-1.5317 0.01021-2.2966-0.0021-0.1821-0.01604-0.40898-0.07372-0.45071-0.28286-0.088-0.27195 0.21101-0.59285 0.48564-0.46528 0.18982 0.03674 0.11004 0.42003-0.05779 0.29297 0.17429-0.25602-0.31955-0.22697-0.17976 0.02451 0.09575 0.22004 0.44485 0.25635 0.58255 0.05617 0.14323-0.2308-0.05154-0.4937-0.2705-0.58497-0.21915-0.10466-0.49117-0.07771-0.67434 0.08567-0.27378 0.20864-0.40621 0.61643-0.22752 0.92795 0.104 0.233 0.33446 0.38219 0.58455 0.41121 0.33507 0.04187 0.67431 0.01608 1.0114 0.02326h1.3407c0.25409 0.28192 0.50818 0.56384 0.76228 0.84577-0.22868 0.25288-0.45736 0.50576-0.68605 0.75865-0.16572-0.21381-0.41577-0.37628-0.51666-0.63121-0.08161-0.29871 0.35413-0.53156 0.5618-0.30555 0.19943 0.11035-0.01178 0.46291-0.15577 0.26978 0.1166-0.03951 0.16904-0.22582-0.01437-0.21638-0.21499 0.08183-0.12303 0.40655 0.0643 0.46926 0.17871 0.08651 0.40261-0.07556 0.3799-0.27118 0.01826-0.28278-0.18842-0.5907-0.48713-0.60388-0.31967-0.06227-0.68039 0.12472-0.75854 0.45263-0.10766 0.28228 0.06955 0.56541 0.26324 0.75966 0.22723 0.24585 0.44911 0.49691 0.67408 0.74475z" fill="#f00"/>
+                       <path d="m-54.464 50.361c0.0017-0.54757 0.0034-1.0951 0.0051-1.6427-0.33832-0.38748-0.69457-0.75998-1.021-1.1574-0.10913-0.13496-0.19252-0.29467-0.18614-0.47289-0.0086-0.33136 0.19731-0.65612 0.50556-0.78212 0.23741-0.10513 0.52154-0.11525 0.754 0.0094 0.3243 0.16516 0.47791 0.61109 0.30157 0.93353-0.13519 0.19951-0.48216 0.19542-0.59404-0.0265-0.1079-0.14135-0.10151-0.43084 0.10373-0.47103 0.16696-0.0034 0.20764 0.24106 0.02397 0.25083-0.04085 0.15882 0.28306 0.12232 0.27255-0.04546 0.04138-0.23154-0.21514-0.42013-0.4314-0.37653-0.24357 0.02414-0.45758 0.28096-0.37332 0.52553 0.08871 0.24037 0.30285 0.40102 0.46218 0.59282 0.25613 0.27896 0.51148 0.55865 0.76757 0.83766-0.0017 0.61002-0.0034 1.22-0.0051 1.8301-0.19509-0.0017-0.3902-0.0035-0.58527-0.0051z"/>
+               </g>
+               <text x="70.290306" y="-13.629649" fill="#000000" font-family="'Open Sans'" font-size="2.8389px" letter-spacing="0px" stroke-width=".35487" text-align="center" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="70.290306" y="-13.629649" text-align="start">synchronization with the lws service thread</tspan><tspan x="70.290306" y="-10.08099" text-align="start">(syncs to the correct service thread for the wsi)</tspan></text>
+       </g>
+</svg>
diff --git a/doc-assets/threadpool.svg b/doc-assets/threadpool.svg
new file mode 100644 (file)
index 0000000..08a4f6a
--- /dev/null
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="117.73mm" height="119.98mm" version="1.1" viewBox="0 0 117.72776 119.97711" xmlns="http://www.w3.org/2000/svg"><defs><marker id="b" overflow="visible" orient="auto"><path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/></marker><marker id="c" overflow="visible" orient="auto"><path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/></marker><marker id="a" overflow="visible" orient="auto"><path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/></marker><filter id="d" x="-.052144" y="-.051067" width="1.1043" height="1.1021" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="2.3162866"/></filter></defs><g transform="translate(483.61 -108.01)"><g><rect x="-478.05" y="113.57" width="106.61" height="108.86" filter="url(#d)"/><rect x="-480.29" y="111.33" width="106.61" height="108.86" fill="#f9f9f9"/><rect x="-432.75" y="145.67" width="45.287" height="17.581" fill="#f00" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".12217"/><rect x="-432.85" y="168.43" width="45.212" height="45.685" fill="#00f" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".19677"/></g><g fill="#e7e0e6" stroke="#18161a" stroke-linejoin="round" stroke-width=".060447"><rect x="-431.73" y="146.79" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.79" y="152.38" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.89" y="169.63" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.96" y="175.21" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.02" y="180.87" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.09" y="186.45" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.16" y="191.85" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.22" y="197.43" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.79" y="157.73" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.29" y="202.83" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.35" y="208.41" width="43.364" height="4.4949" opacity=".497"/></g><text x="-425.05948" y="144.5038" fill="#000000" font-family="'Open Sans'" font-size="2.1167px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-425.05948" y="144.5038" stroke-width=".26458">Worker threads</tspan></text><text x="-427.40665" y="166.8611" fill="#000000" font-family="'Open Sans'" font-size="2.1167px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-427.40665" y="166.8611" stroke-width=".26458">Task queue</tspan></text><path d="m-386.3 172.95c7.3502-6.0322 8.1963-16.693-0.13231-20.373" fill="none" marker-end="url(#a)" stroke="#000" stroke-width=".965"/><rect x="-432.86" y="120.06" width="45.283" height="18.628" fill="#f60" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".12575"/><g fill="#e7e0e6" stroke="#18161a" stroke-linejoin="round" stroke-width=".060447"><rect x="-431.87" y="121.3" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.93" y="126.88" width="43.364" height="4.4949" opacity=".497"/><rect x="-432" y="132.54" width="43.364" height="4.4949" opacity=".497"/></g><text x="-426.85422" y="118.53297" fill="#000000" font-family="'Open Sans'" font-size="2.1167px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-426.85422" y="118.53297" stroke-width=".26458">Done queue</tspan></text><path d="m-386.6 149.21c7.3502-6.0322 8.1963-16.693-0.13231-20.373" fill="none" marker-end="url(#c)" stroke="#000" stroke-width=".965"/><g><path d="m-448.78 157.21-2.8648-3.3692 2.7188-3.3826 0.13363 1.5579 11.817 0.23143-4e-3 -1.8179 3.3851 3.7575-3.6277 3.3848-0.13429-1.8176-11.427 0.0273z" fill="#acf"/><text x="-442.99722" y="155.21205" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-442.99722" y="155.21205" stroke-width=".41538">SYNC</tspan></text><rect x="-470.06" y="121.56" width="16.576" height="50.864" fill="#e7e0e6" stroke="#18161a" stroke-linejoin="round" stroke-width=".12572"/><text x="-461.9382" y="144.28072" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-461.9382" y="144.28072">LWS</tspan><tspan x="-461.9382" y="148.43448">Service</tspan><tspan x="-461.9382" y="152.58824">Thread</tspan></text><path d="m-449.01 132.81-2.8648-3.3692 2.7188-3.3826 0.13363 1.5579 11.817 0.23143-4e-3 -1.8179 3.3851 3.7575-3.6277 3.3848-0.13429-1.8176-11.427 0.0273z" fill="#acf"/><text x="-443.23111" y="130.80745" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-443.23111" y="130.80745" stroke-width=".41538">reap</tspan></text><text x="-454.41257" y="178.39418" fill="#000000" font-family="'Open Sans'" font-size="1.9144px" letter-spacing="0px" stroke-width=".23929" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-454.41257" y="178.39418">All communication with tasks happens</tspan><tspan x="-454.41257" y="180.78712">in lws service thread context, via the</tspan><tspan x="-454.41257" y="183.18007">WRITEABLE callback</tspan></text><rect x="-473.25" y="191" width="31.563" height="12.803" fill="#e7e0e6" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".087035"/></g><g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" word-spacing="0px"><text x="-469.11374" y="189.37514" font-size="4.2914px" stroke-width=".53643" text-align="center" text-anchor="middle" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-469.11374" y="189.37514" stroke-width=".53643">task</tspan></text><g font-size="2.1167px" stroke-width=".26458"><g text-anchor="middle"><text x="-411.27023" y="124.3083" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.27023" y="124.3083" stroke-width=".26458">task</tspan></text><text x="-411.24612" y="129.95815" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.24612" y="129.95815" stroke-width=".26458">task</tspan></text><text x="-411.32364" y="135.46761" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.32364" y="135.46761" stroke-width=".26458">task</tspan></text><text x="-411.23813" y="149.70833" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.23813" y="149.70833" stroke-width=".26458">task</tspan></text><text x="-411.18332" y="155.39687" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.18332" y="155.39687" stroke-width=".26458">task</tspan></text><text x="-411.15262" y="160.68849" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.15262" y="160.68849" stroke-width=".26458">task</tspan></text><text x="-411.26883" y="172.54805" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.26883" y="172.54805" stroke-width=".26458">task</tspan></text><text x="-411.26883" y="178.22987" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.26883" y="178.22987" stroke-width=".26458">task</tspan></text></g><text x="-471.72641" y="194.02657" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-471.72641" y="194.02657"><tspan font-weight="bold" stroke-width=".26458">wsi</tspan> (may be detached)</tspan><tspan x="-471.72641" y="196.67239"><tspan font-weight="bold" stroke-width=".26458">task</tspan> function pointer</tspan><tspan x="-471.72641" y="199.31824"><tspan font-weight="bold" stroke-width=".26458">cleanup</tspan> function pointer</tspan><tspan x="-471.72641" y="201.96407"><tspan font-weight="bold" stroke-width=".26458">user</tspan> private pointer</tspan></text></g><text x="-452.33572" y="213.37485" font-size="5.4153px" stroke-width=".67691" text-align="center" text-anchor="middle" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-452.33572" y="213.37485" stroke-width=".67691">Threadpool</tspan></text></g><g transform="translate(-414.26 162.67)"><path d="m-53.889 51.059c-0.2293-0.20876-0.45859-0.41752-0.68788-0.62628h-2.0842c-0.41073-0.4478-0.80598-0.91066-1.2269-1.3486-0.15635-0.18661-0.4411-0.26749-0.65538-0.13157-0.2532 0.13273-0.37597 0.41669-0.39448 0.69074-0.04758 0.31909 0.05354 0.68674 0.35175 0.85254 0.18832 0.10808 0.49386 0.04671 0.5437-0.19062 0.06699-0.21357-0.06488-0.51518-0.31343-0.50948-0.16072 3e-3 -0.26391 0.28316-0.07717 0.33226 0.08893 0.01617 0.10978-0.27326 0.17002-0.07939 0.0638 0.17507-0.16528 0.31938-0.28293 0.16402-0.213-0.21052-0.14192-0.60391 0.10406-0.75718 0.23529-0.07437 0.39199 0.16171 0.52824 0.31112 0.3873 0.42849 0.77336 0.85822 1.1601 1.2873 0.95481 0.0017 1.9097 0.0034 2.8645 0.0051z"/><path d="m-56.225 50.077c0.07138-0.08712 0.14278-0.17424 0.21416-0.26135 0.10648 0.11858 0.21295 0.23715 0.31943 0.35573 0.19118 0.0012 0.38235 0.0025 0.57352 0.0036-0.2069-0.22868-0.41381-0.45737-0.62072-0.68605 0.10043-0.11374 0.20086-0.22747 0.30128-0.34121 0.19601 0.22868 0.39203 0.45737 0.58804 0.68605-0.0012-0.219-0.0025-0.43801-0.0036-0.65701-0.10043-0.11252-0.20085-0.22505-0.30128-0.33758 0.07198-0.10615 0.24907-0.21551 0.08668-0.31451-0.36406-0.40214-0.72811-0.80428-1.0922-1.2064-0.76543-0.0026-1.5317 0.01021-2.2966-0.0021-0.1821-0.01604-0.40898-0.07372-0.45071-0.28286-0.088-0.27195 0.21101-0.59285 0.48564-0.46528 0.18982 0.03674 0.11004 0.42003-0.05779 0.29297 0.17429-0.25602-0.31955-0.22697-0.17976 0.02451 0.09575 0.22004 0.44485 0.25635 0.58255 0.05617 0.14323-0.2308-0.05154-0.4937-0.2705-0.58497-0.21915-0.10466-0.49117-0.07771-0.67434 0.08567-0.27378 0.20864-0.40621 0.61643-0.22752 0.92795 0.104 0.233 0.33446 0.38219 0.58455 0.41121 0.33507 0.04187 0.67431 0.01608 1.0114 0.02326h1.3407c0.25409 0.28192 0.50818 0.56384 0.76228 0.84577-0.22868 0.25288-0.45736 0.50576-0.68605 0.75865-0.16572-0.21381-0.41577-0.37628-0.51666-0.63121-0.08161-0.29871 0.35413-0.53156 0.5618-0.30555 0.19943 0.11035-0.01178 0.46291-0.15577 0.26978 0.1166-0.03951 0.16904-0.22582-0.01437-0.21638-0.21499 0.08183-0.12303 0.40655 0.0643 0.46926 0.17871 0.08651 0.40261-0.07556 0.3799-0.27118 0.01826-0.28278-0.18842-0.5907-0.48713-0.60388-0.31967-0.06227-0.68039 0.12472-0.75854 0.45263-0.10766 0.28228 0.06955 0.56541 0.26324 0.75966 0.22723 0.24585 0.44911 0.49691 0.67408 0.74475z" fill="#f00"/><path d="m-54.464 50.361c0.0017-0.54757 0.0034-1.0951 0.0051-1.6427-0.33832-0.38748-0.69457-0.75998-1.021-1.1574-0.10913-0.13496-0.19252-0.29467-0.18614-0.47289-0.0086-0.33136 0.19731-0.65612 0.50556-0.78212 0.23741-0.10513 0.52154-0.11525 0.754 0.0094 0.3243 0.16516 0.47791 0.61109 0.30157 0.93353-0.13519 0.19951-0.48216 0.19542-0.59404-0.0265-0.1079-0.14135-0.10151-0.43084 0.10373-0.47103 0.16696-0.0034 0.20764 0.24106 0.02397 0.25083-0.04085 0.15882 0.28306 0.12232 0.27255-0.04546 0.04138-0.23154-0.21514-0.42013-0.4314-0.37653-0.24357 0.02414-0.45758 0.28096-0.37332 0.52553 0.08871 0.24037 0.30285 0.40102 0.46218 0.59282 0.25613 0.27896 0.51148 0.55865 0.76757 0.83766-0.0017 0.61002-0.0034 1.22-0.0051 1.8301-0.19509-0.0017-0.3902-0.0035-0.58527-0.0051z"/></g><path d="m-449.68 167.64 11.817 0.23143-4e-3 -1.8179 3.3851 3.7575-3.6277 3.3848-0.13429-1.8176-13.274 0.23777c0.0499-1.679 7e-3 -2.161 0.0144-4.0462z" fill="#acf"/><text x="-443.22256" y="170.56471" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-443.22256" y="170.56471" stroke-width=".41538">enqueue</tspan></text><g fill="none" stroke="#000"><path d="m-387.42 123.83c1.3484-0.56118 1.6984-1.3714 1.7386-3.1608" marker-end="url(#b)" stroke-width=".965"/><g stroke-width=".26458px"><path d="m-387.65 115.01 1.2568 1.7529"/><path d="m-383.25 120.07 3.4065 1.0253"/><path d="m-383.68 117.3 2.4143-1.2568"/><path d="m-387.19 119.94-1.9513 1.1245"/><path d="m-387.59 118.92-2.8112-0.52916"/><path d="m-384.84 116.57 0.8599-1.6867"/><path d="m-382.92 118.75 2.8773-0.26458"/><path d="m-384.35 120.84 1.3891 2.3151"/><path d="m-387.52 117.66-2.282-1.2237"/></g></g></g></svg>
diff --git a/doc-assets/wss2.png b/doc-assets/wss2.png
new file mode 100644 (file)
index 0000000..2aa7734
Binary files /dev/null and b/doc-assets/wss2.png differ
diff --git a/include/libwebsockets.h b/include/libwebsockets.h
new file mode 100644 (file)
index 0000000..9ca1c95
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+/** @file */
+
+#ifndef LIBWEBSOCKET_H_3060898B846849FF9F88F5DB59B5950C
+#define LIBWEBSOCKET_H_3060898B846849FF9F88F5DB59B5950C
+
+#ifdef __cplusplus
+#include <cstddef>
+#include <cstdarg>
+
+extern "C" {
+#else
+#include <stdarg.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "lws_config.h"
+
+/*
+ * CARE: everything using cmake defines needs to be below here
+ */
+
+#define LWS_US_PER_SEC 1000000
+#define LWS_MS_PER_SEC 1000
+#define LWS_US_PER_MS 1000
+#define LWS_NS_PER_US 1000
+
+#define LWS_KI (1024)
+#define LWS_MI (LWS_KI * 1024)
+#define LWS_GI (LWS_MI * 1024)
+#define LWS_TI ((uint64_t)LWS_GI * 1024)
+#define LWS_PI ((uint64_t)LWS_TI * 1024)
+
+#define LWS_US_TO_MS(x) ((x + (LWS_US_PER_MS / 2)) / LWS_US_PER_MS)
+
+#if defined(LWS_HAS_INTPTR_T)
+#include <stdint.h>
+#define lws_intptr_t intptr_t
+#else
+typedef unsigned long long lws_intptr_t;
+#endif
+
+#if defined(WIN32) || defined(_WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <stddef.h>
+#include <basetsd.h>
+#include <io.h>
+#ifndef _WIN32_WCE
+#include <fcntl.h>
+#else
+#define _O_RDONLY      0x0000
+#define O_RDONLY       _O_RDONLY
+#endif
+
+#define LWS_INLINE __inline
+#define LWS_VISIBLE
+#define LWS_WARN_UNUSED_RESULT
+#define LWS_WARN_DEPRECATED
+#define LWS_FORMAT(string_index)
+
+#if !defined(LWS_EXTERN)
+#ifdef LWS_DLL
+#ifdef LWS_INTERNAL
+#define LWS_EXTERN extern __declspec(dllexport)
+#else
+#define LWS_EXTERN extern __declspec(dllimport)
+#endif
+#else
+#define LWS_EXTERN
+#endif
+#endif
+
+#define LWS_INVALID_FILE INVALID_HANDLE_VALUE
+#define LWS_SOCK_INVALID (INVALID_SOCKET)
+#define LWS_O_RDONLY _O_RDONLY
+#define LWS_O_WRONLY _O_WRONLY
+#define LWS_O_CREAT _O_CREAT
+#define LWS_O_TRUNC _O_TRUNC
+
+#ifndef __func__
+#define __func__ __FUNCTION__
+#endif
+
+#else /* NOT WIN32 */
+#include <unistd.h>
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+#include <sys/capability.h>
+#endif
+
+#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__QNX__) || defined(__OpenBSD__)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#define LWS_INLINE inline
+#define LWS_O_RDONLY O_RDONLY
+#define LWS_O_WRONLY O_WRONLY
+#define LWS_O_CREAT O_CREAT
+#define LWS_O_TRUNC O_TRUNC
+
+#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_TA) && !defined(LWS_WITH_ESP32)
+#include <poll.h>
+#include <netdb.h>
+#define LWS_INVALID_FILE -1
+#define LWS_SOCK_INVALID (-1)
+#else
+#define getdtablesize() (30)
+#if defined(LWS_WITH_ESP32)
+#define LWS_INVALID_FILE NULL
+#define LWS_SOCK_INVALID (-1)
+#else
+#define LWS_INVALID_FILE NULL
+#define LWS_SOCK_INVALID (-1)
+#endif
+#endif
+
+#if defined(__GNUC__)
+
+/* warn_unused_result attribute only supported by GCC 3.4 or later */
+#if __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#define LWS_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+#define LWS_WARN_UNUSED_RESULT
+#endif
+
+#define LWS_VISIBLE __attribute__((visibility("default")))
+#define LWS_WARN_DEPRECATED __attribute__ ((deprecated))
+#define LWS_FORMAT(string_index) __attribute__ ((format(printf, string_index, string_index+1)))
+#else
+#define LWS_VISIBLE
+#define LWS_WARN_UNUSED_RESULT
+#define LWS_WARN_DEPRECATED
+#define LWS_FORMAT(string_index)
+#endif
+
+#if defined(__ANDROID__)
+#include <netinet/in.h>
+#include <unistd.h>
+#endif
+
+#endif
+
+#if defined(LWS_WITH_LIBEV)
+#include <ev.h>
+#endif /* LWS_WITH_LIBEV */
+#ifdef LWS_WITH_LIBUV
+#include <uv.h>
+#ifdef LWS_HAVE_UV_VERSION_H
+#include <uv-version.h>
+#endif
+#ifdef LWS_HAVE_NEW_UV_VERSION_H
+#include <uv/version.h>
+#endif
+#endif /* LWS_WITH_LIBUV */
+#if defined(LWS_WITH_LIBEVENT)
+#include <event2/event.h>
+#endif /* LWS_WITH_LIBEVENT */
+
+#ifndef LWS_EXTERN
+#define LWS_EXTERN extern
+#endif
+
+#ifdef _WIN32
+#define random rand
+#else
+#if !defined(LWS_PLAT_OPTEE)
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+#endif
+
+#if defined(LWS_WITH_TLS)
+
+#ifdef USE_WOLFSSL
+#ifdef USE_OLD_CYASSL
+#ifdef _WIN32
+/*
+ * Include user-controlled settings for windows from
+ * <wolfssl-root>/IDE/WIN/user_settings.h
+ */
+#include <IDE/WIN/user_settings.h>
+#include <cyassl/ctaocrypt/settings.h>
+#else
+#include <cyassl/options.h>
+#endif
+#include <cyassl/openssl/ssl.h>
+#include <cyassl/error-ssl.h>
+
+#else
+#ifdef _WIN32
+/*
+ * Include user-controlled settings for windows from
+ * <wolfssl-root>/IDE/WIN/user_settings.h
+ */
+#include <IDE/WIN/user_settings.h>
+#include <wolfssl/wolfcrypt/settings.h>
+#else
+#include <wolfssl/options.h>
+#endif
+#include <wolfssl/openssl/ssl.h>
+#include <wolfssl/error-ssl.h>
+#endif /* not USE_OLD_CYASSL */
+#else
+#if defined(LWS_WITH_MBEDTLS)
+#if defined(LWS_WITH_ESP32)
+/* this filepath is passed to us but without quotes or <> */
+#if !defined(LWS_AMAZON_RTOS)
+/* AMAZON RTOS has its own setting via MTK_MBEDTLS_CONFIG_FILE */
+#undef MBEDTLS_CONFIG_FILE
+#define MBEDTLS_CONFIG_FILE <mbedtls/esp_config.h>
+#endif
+#endif
+#include <mbedtls/ssl.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/ctr_drbg.h>
+#else
+#include <openssl/ssl.h>
+#if !defined(LWS_WITH_MBEDTLS)
+#include <openssl/err.h>
+#endif
+#endif
+#endif /* not USE_WOLFSSL */
+#endif
+
+/*
+ * Helpers for pthread mutex in user code... if lws is built for
+ * multiple service threads, these resolve to pthread mutex
+ * operations.  In the case LWS_MAX_SMP is 1 (the default), they
+ * are all NOPs and no pthread type or api is referenced.
+ */
+
+#if LWS_MAX_SMP > 1
+
+#include <pthread.h>
+
+#define lws_pthread_mutex(name) pthread_mutex_t name;
+
+static LWS_INLINE void
+lws_pthread_mutex_init(pthread_mutex_t *lock)
+{
+       pthread_mutex_init(lock, NULL);
+}
+
+static LWS_INLINE void
+lws_pthread_mutex_destroy(pthread_mutex_t *lock)
+{
+       pthread_mutex_destroy(lock);
+}
+
+static LWS_INLINE void
+lws_pthread_mutex_lock(pthread_mutex_t *lock)
+{
+       pthread_mutex_lock(lock);
+}
+
+static LWS_INLINE void
+lws_pthread_mutex_unlock(pthread_mutex_t *lock)
+{
+       pthread_mutex_unlock(lock);
+}
+
+#else
+#define lws_pthread_mutex(name)
+#define lws_pthread_mutex_init(_a)
+#define lws_pthread_mutex_destroy(_a)
+#define lws_pthread_mutex_lock(_a)
+#define lws_pthread_mutex_unlock(_a)
+#endif
+
+
+#define CONTEXT_PORT_NO_LISTEN -1
+#define CONTEXT_PORT_NO_LISTEN_SERVER -2
+
+#include <libwebsockets/lws-logs.h>
+
+
+#include <stddef.h>
+
+#ifndef lws_container_of
+#define lws_container_of(P,T,M)        ((T *)((char *)(P) - offsetof(T, M)))
+#endif
+
+struct lws;
+
+/* api change list for user code to test against */
+
+#define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_ARG
+
+/* the struct lws_protocols has the id field present */
+#define LWS_FEATURE_PROTOCOLS_HAS_ID_FIELD
+
+/* you can call lws_get_peer_write_allowance */
+#define LWS_FEATURE_PROTOCOLS_HAS_PEER_WRITE_ALLOWANCE
+
+/* extra parameter introduced in 917f43ab821 */
+#define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_LEN
+
+/* File operations stuff exists */
+#define LWS_FEATURE_FOPS
+
+
+#if defined(_WIN32)
+#if !defined(LWS_WIN32_HANDLE_TYPES)
+typedef SOCKET lws_sockfd_type;
+typedef HANDLE lws_filefd_type;
+#endif
+
+struct lws_pollfd {
+       lws_sockfd_type fd; /**< file descriptor */
+       SHORT events; /**< which events to respond to */
+       SHORT revents; /**< which events happened */
+};
+#define LWS_POLLHUP (FD_CLOSE)
+#define LWS_POLLIN (FD_READ | FD_ACCEPT)
+#define LWS_POLLOUT (FD_WRITE)
+#else
+
+
+#if defined(LWS_WITH_ESP32)
+#include <libwebsockets/lws-esp32.h>
+#else
+typedef int lws_sockfd_type;
+typedef int lws_filefd_type;
+#endif
+
+#if defined(LWS_PLAT_OPTEE)
+#include <time.h>
+struct timeval {
+       time_t          tv_sec;
+       unsigned int    tv_usec;
+};
+#if defined(LWS_WITH_NETWORK)
+// #include <poll.h>
+#define lws_pollfd pollfd
+
+struct timezone;
+
+int gettimeofday(struct timeval *tv, struct timezone *tz);
+
+    /* Internet address. */
+    struct in_addr {
+        uint32_t       s_addr;     /* address in network byte order */
+    };
+
+typedef unsigned short sa_family_t;
+typedef unsigned short in_port_t;
+typedef uint32_t socklen_t;
+
+#include <libwebsockets/lws-optee.h>
+
+#if !defined(TEE_SE_READER_NAME_MAX)
+           struct addrinfo {
+               int              ai_flags;
+               int              ai_family;
+               int              ai_socktype;
+               int              ai_protocol;
+               socklen_t        ai_addrlen;
+               struct sockaddr *ai_addr;
+               char            *ai_canonname;
+               struct addrinfo *ai_next;
+           };
+#endif
+
+ssize_t recv(int sockfd, void *buf, size_t len, int flags);
+ssize_t send(int sockfd, const void *buf, size_t len, int flags);
+ssize_t read(int fd, void *buf, size_t count);
+int getsockopt(int sockfd, int level, int optname,
+                      void *optval, socklen_t *optlen);
+       int setsockopt(int sockfd, int level, int optname,
+                      const void *optval, socklen_t optlen);
+int connect(int sockfd, const struct sockaddr *addr,
+                   socklen_t addrlen);
+
+extern int errno;
+
+uint16_t ntohs(uint16_t netshort);
+uint16_t htons(uint16_t hostshort);
+
+int bind(int sockfd, const struct sockaddr *addr,
+                socklen_t addrlen);
+
+
+#define  MSG_NOSIGNAL 0x4000
+#define        EAGAIN          11
+#define EINTR          4
+#define EWOULDBLOCK    EAGAIN
+#define        EADDRINUSE      98      
+#define INADDR_ANY     0
+#define AF_INET                2
+#define SHUT_WR 1
+#define AF_UNSPEC      0
+#define PF_UNSPEC      0
+#define SOCK_STREAM    1
+#define SOCK_DGRAM     2
+# define AI_PASSIVE    0x0001
+#define IPPROTO_UDP    17
+#define SOL_SOCKET     1
+#define SO_SNDBUF      7
+#define        EISCONN         106     
+#define        EALREADY        114
+#define        EINPROGRESS     115
+int shutdown(int sockfd, int how);
+int close(int fd);
+int atoi(const char *nptr);
+long long atoll(const char *nptr);
+
+int socket(int domain, int type, int protocol);
+       int getaddrinfo(const char *node, const char *service,
+                       const struct addrinfo *hints,
+                       struct addrinfo **res);
+
+       void freeaddrinfo(struct addrinfo *res);
+
+#if !defined(TEE_SE_READER_NAME_MAX)
+struct lws_pollfd
+{
+        int fd;                     /* File descriptor to poll.  */
+        short int events;           /* Types of events poller cares about.  */
+        short int revents;          /* Types of events that actually occurred.  */
+};
+#endif
+
+int poll(struct pollfd *fds, int nfds, int timeout);
+
+#define LWS_POLLHUP (0x18)
+#define LWS_POLLIN (1)
+#define LWS_POLLOUT (4)
+#else
+struct lws_pollfd;
+struct sockaddr_in;
+#endif
+#else
+#define lws_pollfd pollfd
+#define LWS_POLLHUP (POLLHUP | POLLERR)
+#define LWS_POLLIN (POLLIN)
+#define LWS_POLLOUT (POLLOUT)
+#endif
+#endif
+
+
+#if (defined(WIN32) || defined(_WIN32)) && !defined(__MINGW32__)
+/* ... */
+#define ssize_t SSIZE_T
+#endif
+
+#if defined(WIN32) && defined(LWS_HAVE__STAT32I64)
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+#if defined(LWS_HAVE_STDINT_H)
+#include <stdint.h>
+#else
+#if defined(WIN32) || defined(_WIN32)
+/* !!! >:-[  */
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int8 uint8_t;
+#else
+typedef unsigned int uint32_t;
+typedef unsigned short uint16_t;
+typedef unsigned char uint8_t;
+#endif
+#endif
+
+typedef int64_t lws_usec_t;
+typedef unsigned long long lws_filepos_t;
+typedef long long lws_fileofs_t;
+typedef uint32_t lws_fop_flags_t;
+
+#define lws_concat_temp(_t, _l) (_t + sizeof(_t) - _l)
+#define lws_concat_used(_t, _l) (sizeof(_t) - _l)
+
+/** struct lws_pollargs - argument structure for all external poll related calls
+ * passed in via 'in' */
+struct lws_pollargs {
+       lws_sockfd_type fd;     /**< applicable socket descriptor */
+       int events;             /**< the new event mask */
+       int prev_events;        /**< the previous event mask */
+};
+
+struct lws_extension; /* needed even with ws exts disabled for create context */
+struct lws_token_limits;
+struct lws_context;
+struct lws_tokens;
+struct lws_vhost;
+struct lws;
+
+#include <libwebsockets/lws-system.h>
+#include <libwebsockets/lws-ws-close.h>
+#include <libwebsockets/lws-callbacks.h>
+#include <libwebsockets/lws-ws-state.h>
+#include <libwebsockets/lws-ws-ext.h>
+#include <libwebsockets/lws-protocols-plugins.h>
+#include <libwebsockets/lws-plugin-generic-sessions.h>
+#include <libwebsockets/lws-context-vhost.h>
+#include <libwebsockets/lws-client.h>
+#include <libwebsockets/lws-http.h>
+#include <libwebsockets/lws-spa.h>
+#include <libwebsockets/lws-purify.h>
+#include <libwebsockets/lws-misc.h>
+#include <libwebsockets/lws-dsh.h>
+#include <libwebsockets/lws-timeout-timer.h>
+#include <libwebsockets/lws-service.h>
+#include <libwebsockets/lws-write.h>
+#include <libwebsockets/lws-writeable.h>
+#include <libwebsockets/lws-adopt.h>
+#include <libwebsockets/lws-network-helper.h>
+#include <libwebsockets/lws-ring.h>
+#include <libwebsockets/lws-sha1-base64.h>
+#include <libwebsockets/lws-x509.h>
+#include <libwebsockets/lws-cgi.h>
+#include <libwebsockets/lws-vfs.h>
+#include <libwebsockets/lws-lejp.h>
+#include <libwebsockets/lws-stats.h>
+#include <libwebsockets/lws-struct.h>
+#include <libwebsockets/lws-threadpool.h>
+#include <libwebsockets/lws-tokenize.h>
+#include <libwebsockets/lws-lwsac.h>
+#include <libwebsockets/lws-fts.h>
+#include <libwebsockets/lws-diskcache.h>
+#include <libwebsockets/lws-retry.h>
+#include <libwebsockets/lws-sequencer.h>
+
+#include <libwebsockets/abstract/abstract.h>
+
+#include <libwebsockets/lws-test-sequencer.h>
+
+#if defined(LWS_WITH_TLS)
+
+#if defined(LWS_WITH_MBEDTLS)
+#include <mbedtls/md5.h>
+#include <mbedtls/sha1.h>
+#include <mbedtls/sha256.h>
+#include <mbedtls/sha512.h>
+#endif
+
+#include <libwebsockets/lws-gencrypto.h>
+#include <libwebsockets/lws-genhash.h>
+#include <libwebsockets/lws-genrsa.h>
+#include <libwebsockets/lws-genaes.h>
+#include <libwebsockets/lws-genec.h>
+
+#include <libwebsockets/lws-jwk.h>
+#include <libwebsockets/lws-jose.h>
+#include <libwebsockets/lws-jws.h>
+#include <libwebsockets/lws-jwe.h>
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libwebsockets/abstract/abstract.h b/include/libwebsockets/abstract/abstract.h
new file mode 100644 (file)
index 0000000..d8f0228
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * libwebsockets - abstract top level header
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+/*
+ * These are used to optionally pass an array of index = C string, binary array,
+ * or ulong tokens to the abstract transport or protocol.  For example if it's
+ * raw socket transport, then the DNS address to connect to and the port are
+ * passed using these when the client created and bound to the transport.
+ */
+
+typedef struct lws_token_map {
+       union {
+               const char      *value;
+               uint8_t         *bvalue;
+               unsigned long   lvalue;
+       } u;
+       short                   name_index;  /* 0 here indicates end of array */
+       short                   length_or_zero;
+} lws_token_map_t;
+
+/*
+ * The indvidual protocols and transports define their own name_index-es which
+ * are meaningful to them.  Define index 0 globally as the end of an array of
+ * them, and separate the ones used for protocols and transport so we can
+ * sanity check they are at least in the correct category.
+ */
+
+enum {
+       LTMI_END_OF_ARRAY,
+
+       LTMI_PROTOCOL_BASE      = 2048,
+
+       LTMI_TRANSPORT_BASE     = 4096
+};
+
+struct lws_abs_transport;
+struct lws_abs_protocol;
+
+LWS_VISIBLE LWS_EXTERN const lws_token_map_t *
+lws_abs_get_token(const lws_token_map_t *token_map, short name_index);
+
+/*
+ * the combination of a protocol, transport, and token maps for each
+ */
+
+typedef void lws_abs_transport_inst_t;
+typedef void lws_abs_protocol_inst_t;
+
+typedef struct lws_abs {
+       void                            *user;
+       struct lws_vhost                *vh;
+
+       const struct lws_abs_protocol   *ap;
+       const lws_token_map_t           *ap_tokens;
+       const struct lws_abs_transport  *at;
+       const lws_token_map_t           *at_tokens;
+
+       lws_seq_t                       *seq;
+       void                            *opaque_user_data;
+
+       /*
+        * These are filled in by lws_abs_bind_and_create_instance() in the
+        * instance copy.  They do not need to be set when creating the struct
+        * for use by lws_abs_bind_and_create_instance()
+        */
+
+       struct lws_dll2                 abstract_instances;
+       lws_abs_transport_inst_t        *ati;
+       lws_abs_protocol_inst_t         *api;
+} lws_abs_t;
+
+/**
+ * lws_abs_bind_and_create_instance - use an abstract protocol and transport
+ *
+ * \param abs: the lws_abs_t describing the combination desired
+ *
+ * This instantiates an abstract protocol and abstract transport bound together.
+ * A single heap allocation is made for the combination and the protocol and
+ * transport creation ops are called on it.  The ap_tokens and at_tokens
+ * are consulted by the creation ops to decide the details of the protocol and
+ * transport for the instance.
+ */
+LWS_VISIBLE LWS_EXTERN lws_abs_t *
+lws_abs_bind_and_create_instance(const lws_abs_t *ai);
+
+/**
+ * lws_abs_destroy_instance() - destroys an instance
+ *
+ * \param ai: pointer to the ai pointer to destroy
+ *
+ * This is for destroying an instance created by
+ * lws_abs_bind_and_create_instance() above.
+ *
+ * Calls the protocol and transport destroy operations on the instance, then
+ * frees the combined allocation in one step.  The pointer ai is set to NULL.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_abs_destroy_instance(lws_abs_t **ai);
+
+/*
+ * bring in all the protocols and transports definitions
+ */
+
+#include <libwebsockets/abstract/protocols.h>
+#include <libwebsockets/abstract/transports.h>
diff --git a/include/libwebsockets/abstract/protocols.h b/include/libwebsockets/abstract/protocols.h
new file mode 100644 (file)
index 0000000..a6f802a
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * libwebsockets - abstract protocol definitions
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+typedef struct lws_abs_protocol {
+       const char      *name;
+       int             alloc;
+
+       int (*create)(const struct lws_abs *ai);
+       void (*destroy)(lws_abs_protocol_inst_t **d);
+
+       /* events the transport invokes (handled by abstract protocol) */
+
+       int (*accept)(lws_abs_protocol_inst_t *d);
+       int (*rx)(lws_abs_protocol_inst_t *d, uint8_t *buf, size_t len);
+       int (*writeable)(lws_abs_protocol_inst_t *d, size_t budget);
+       int (*closed)(lws_abs_protocol_inst_t *d);
+       int (*heartbeat)(lws_abs_protocol_inst_t *d);
+} lws_abs_protocol_t;
+
+/**
+ * lws_abs_protocol_get_by_name() - returns a pointer to the named protocol ops
+ *
+ * \param name: the name of the abstract protocol
+ *
+ * Returns a pointer to the named protocol ops struct if available, otherwise
+ * NULL.
+ */
+LWS_VISIBLE LWS_EXTERN const lws_abs_protocol_t *
+lws_abs_protocol_get_by_name(const char *name);
+
+/*
+ * bring in public api pieces from protocols
+ */
+
+#include <libwebsockets/abstract/protocols/smtp.h>
diff --git a/include/libwebsockets/abstract/protocols/smtp.h b/include/libwebsockets/abstract/protocols/smtp.h
new file mode 100644 (file)
index 0000000..5fb434e
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup smtp SMTP related functions
+ * ##SMTP related functions
+ * \ingroup lwsapi
+ *
+ * These apis let you communicate with a local SMTP server to send email from
+ * lws.  It handles all the SMTP sequencing and protocol actions.
+ *
+ * Your system should have postfix, sendmail or another MTA listening on port
+ * 25 and able to send email using the "mail" commandline app.  Usually distro
+ * MTAs are configured for this by default.
+ *
+ * It runs via its own libuv events if initialized (which requires giving it
+ * a libuv loop to attach to).
+ *
+ * It operates using three callbacks, on_next() queries if there is a new email
+ * to send, on_get_body() asks for the body of the email, and on_sent() is
+ * called after the email is successfully sent.
+ *
+ * To use it
+ *
+ *  - create an lws_email struct
+ *
+ *  - initialize data, loop, the email_* strings, max_content_size and
+ *    the callbacks
+ *
+ *  - call lws_email_init()
+ *
+ *  When you have at least one email to send, call lws_email_check() to
+ *  schedule starting to send it.
+ */
+//@{
+#if defined(LWS_WITH_SMTP)
+
+enum {
+       LTMI_PSMTP_V_HELO = LTMI_PROTOCOL_BASE,         /* u.value */
+       LTMI_PSMTP_LV_RETRY_INTERVAL,                   /* u.lvalue */
+       LTMI_PSMTP_LV_DELIVERY_TIMEOUT,                 /* u.lvalue */
+       LTMI_PSMTP_LV_EMAIL_QUEUE_MAX,                  /* u.lvalue */
+       LTMI_PSMTP_LV_MAX_CONTENT_SIZE,                 /* u.lvalue */
+};
+
+typedef struct lws_smtp_client lws_smtp_client_t;
+typedef struct lws_abs lws_abs_t;
+
+typedef struct lws_smtp_email {
+       struct lws_dll2 list;
+
+       void *data;
+       void *extra;
+
+       time_t added;
+       time_t last_try;
+
+       const char *email_from;
+       const char *email_to;
+       const char *payload;
+
+       int (*done)(struct lws_smtp_email *e, void *buf, size_t len);
+
+       int tries;
+} lws_smtp_email_t;
+
+
+/**
+ * lws_smtp_client_alloc_email_helper() - Allocates and inits an email object
+ *
+ * \param payload: the email payload string, with headers and terminating .
+ * \param payload_len: size in bytes of the payload string
+ * \param sender: the sender name and email
+ * \param recipient: the recipient name and email
+ *
+ * Allocates an email object and copies the payload, sender and recipient into
+ * it and initializes it.  Returns NULL if OOM, otherwise the allocated email
+ * object.
+ *
+ * Because it copies the arguments into an allocated buffer, the original
+ * arguments can be safely destroyed after calling this.
+ *
+ * The done() callback must free the email object.  It doesn't have to free any
+ * individual members.
+ */
+LWS_VISIBLE LWS_EXTERN lws_smtp_email_t *
+lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len,
+                                  const char *sender, const char *recipient,
+                                  const char *extra, size_t extra_len, void *data,
+                                  int (*done)(struct lws_smtp_email *e,
+                                              void *buf, size_t len));
+
+/**
+ * lws_smtp_client_add_email() - Add email to the list of ones being sent
+ *
+ * \param instance: smtp client + transport
+ * \param e: email to queue for sending on \p c
+ *
+ * Adds an email to the linked-list of emails to send
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e);
+
+/**
+ * lws_smtp_client_kick() - Request check for new email
+ *
+ * \param instance: instance to kick
+ *
+ * Gives smtp client a chance to move things on
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_smtp_client_kick(lws_abs_t *instance);
+
+#endif
+//@}
diff --git a/include/libwebsockets/abstract/transports.h b/include/libwebsockets/abstract/transports.h
new file mode 100644 (file)
index 0000000..e0aebc3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*
+ * Abstract transport ops
+ */
+
+typedef struct lws_abs_transport {
+       const char *name;
+       int alloc;
+
+       int (*create)(struct lws_abs *abs);
+       void (*destroy)(lws_abs_transport_inst_t **d);
+
+       /* events the abstract protocol invokes (handled by transport) */
+
+       int (*tx)(lws_abs_transport_inst_t *d, uint8_t *buf, size_t len);
+       int (*client_conn)(const lws_abs_t *abs);
+       int (*close)(lws_abs_transport_inst_t *d);
+       int (*ask_for_writeable)(lws_abs_transport_inst_t *d);
+       int (*set_timeout)(lws_abs_transport_inst_t *d, int reason, int secs);
+       int (*state)(lws_abs_transport_inst_t *d);
+} lws_abs_transport_t;
+
+/**
+ * lws_abs_protocol_get_by_name() - returns a pointer to the named protocol ops
+ *
+ * \param name: the name of the abstract protocol
+ *
+ * Returns a pointer to the named protocol ops struct if available, otherwise
+ * NULL.
+ */
+LWS_VISIBLE LWS_EXTERN const lws_abs_transport_t *
+lws_abs_transport_get_by_name(const char *name);
+
+/*
+ * bring in public api pieces from transports
+ */
+
+#include <libwebsockets/abstract/transports/raw-skt.h>
+#include <libwebsockets/abstract/transports/unit-test.h>
diff --git a/include/libwebsockets/abstract/transports/raw-skt.h b/include/libwebsockets/abstract/transports/raw-skt.h
new file mode 100644 (file)
index 0000000..f35ecaf
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * libwebsockets - raw-skt abstract transport
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+enum {
+       LTMI_PEER_V_DNS_ADDRESS = LTMI_TRANSPORT_BASE,  /* u.value */
+       LTMI_PEER_LV_PORT,                              /* u.lvalue */
+       LTMI_PEER_LV_TLS_FLAGS,                         /* u.lvalue */
+};
diff --git a/include/libwebsockets/abstract/transports/unit-test.h b/include/libwebsockets/abstract/transports/unit-test.h
new file mode 100644 (file)
index 0000000..1527ec8
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * libwebsockets include/libwebsockets/abstract/transports/unit-test.c
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * This is an abstract transport useful for unit testing abstract protocols.
+ *
+ * Instead of passing data anywhere, you give the transport a list of packets
+ * to deliver and packets you expect back from the abstract protocol it's
+ * bound to.
+ */
+
+enum {
+       LWS_AUT_EXPECT_TEST_END                                 = (1 << 0),
+       LWS_AUT_EXPECT_LOCAL_CLOSE                              = (1 << 1),
+       LWS_AUT_EXPECT_DO_REMOTE_CLOSE                          = (1 << 2),
+       LWS_AUT_EXPECT_TX /* expect this as tx from protocol */ = (1 << 3),
+       LWS_AUT_EXPECT_RX /* present this as rx to protocol */  = (1 << 4),
+       LWS_AUT_EXPECT_SHOULD_FAIL                              = (1 << 5),
+       LWS_AUT_EXPECT_SHOULD_TIMEOUT                           = (1 << 6),
+};
+
+typedef enum {
+       LPE_CONTINUE,
+       LPE_SUCCEEDED,
+       LPE_FAILED,
+       LPE_FAILED_UNEXPECTED_TIMEOUT,
+       LPE_FAILED_UNEXPECTED_PASS,
+       LPE_FAILED_UNEXPECTED_CLOSE,
+       LPE_SKIPPED,
+       LPE_CLOSING
+} lws_unit_test_packet_disposition;
+
+typedef int (*lws_unit_test_packet_test_cb)(const void *cb_user, int disposition);
+typedef int (*lws_unit_test_packet_cb)(lws_abs_t *instance);
+
+/* each step in the unit test */
+
+typedef struct lws_unit_test_packet {
+       void                            *buffer;
+       lws_unit_test_packet_cb         pre;
+       size_t                          len;
+
+       uint32_t                        flags;
+} lws_unit_test_packet_t;
+
+/* each unit test */
+
+typedef struct lws_unit_test {
+       const char *            name; /* NULL indicates end of test array */
+       lws_unit_test_packet_t *                expect_array;
+       int                     max_secs;
+} lws_unit_test_t;
+
+enum {
+       LTMI_PEER_V_EXPECT_TEST = LTMI_TRANSPORT_BASE,  /* u.value */
+       LTMI_PEER_V_EXPECT_RESULT_CB,                   /* u.value */
+       LTMI_PEER_V_EXPECT_RESULT_CB_ARG,               /* u.value */
+};
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_unit_test_result_name(int in);
+
diff --git a/include/libwebsockets/lws-adopt.h b/include/libwebsockets/lws-adopt.h
new file mode 100644 (file)
index 0000000..1f0cfb8
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup sock-adopt Socket adoption helpers
+ * ##Socket adoption helpers
+ *
+ * When integrating with an external app with its own event loop, these can
+ * be used to accept connections from someone else's listening socket.
+ *
+ * When using lws own event loop, these are not needed.
+ */
+///@{
+
+/**
+ * lws_adopt_socket() - adopt foreign socket as if listen socket accepted it
+ * for the default vhost of context.
+ *
+ * \param context: lws context
+ * \param accept_fd: fd of already-accepted socket to adopt
+ *
+ * Either returns new wsi bound to accept_fd, or closes accept_fd and
+ * returns NULL, having cleaned up any new wsi pieces.
+ *
+ * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
+ * to ws or just serve http.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd);
+/**
+ * lws_adopt_socket_vhost() - adopt foreign socket as if listen socket accepted
+ * it for vhost
+ *
+ * \param vh: lws vhost
+ * \param accept_fd: fd of already-accepted socket to adopt
+ *
+ * Either returns new wsi bound to accept_fd, or closes accept_fd and
+ * returns NULL, having cleaned up any new wsi pieces.
+ *
+ * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
+ * to ws or just serve http.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd);
+
+typedef enum {
+       LWS_ADOPT_RAW_FILE_DESC = 0,    /* convenience constant */
+       LWS_ADOPT_HTTP = 1,             /* flag: absent implies RAW */
+       LWS_ADOPT_SOCKET = 2,           /* flag: absent implies file descr */
+       LWS_ADOPT_ALLOW_SSL = 4,        /* flag: if set requires LWS_ADOPT_SOCKET */
+       LWS_ADOPT_FLAG_UDP = 16,        /* flag: socket is UDP */
+       LWS_ADOPT_FLAG_RAW_PROXY = 32,  /* flag: raw proxy */
+
+       LWS_ADOPT_RAW_SOCKET_UDP = LWS_ADOPT_SOCKET | LWS_ADOPT_FLAG_UDP,
+} lws_adoption_type;
+
+typedef union {
+       lws_sockfd_type sockfd;
+       lws_filefd_type filefd;
+} lws_sock_file_fd_type;
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+struct lws_udp {
+       struct sockaddr sa;
+       socklen_t salen;
+
+       struct sockaddr sa_pending;
+       socklen_t salen_pending;
+};
+#endif
+
+/**
+* lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor
+* if socket descriptor, should already have been accepted from listen socket
+*
+* \param vhost: lws vhost
+* \param type: OR-ed combinations of lws_adoption_type flags
+* \param fd: union with either .sockfd or .filefd set
+* \param vh_prot_name: NULL or vh protocol name to bind raw connection to
+* \param parent: NULL or struct lws to attach new_wsi to as a child
+*
+* Either returns new wsi bound to accept_fd, or closes accept_fd and
+* returns NULL, having cleaned up any new wsi pieces.
+*
+* If LWS_ADOPT_SOCKET is set, LWS adopts the socket in http serving mode, it's
+* ready to accept an upgrade to ws or just serve http.
+*
+* parent may be NULL, if given it should be an existing wsi that will become the
+* parent of the new wsi created by this call.
+*/
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
+                          lws_sock_file_fd_type fd, const char *vh_prot_name,
+                          struct lws *parent);
+
+/**
+ * lws_adopt_socket_readbuf() - adopt foreign socket and first rx as if listen socket accepted it
+ * for the default vhost of context.
+ * \param context:     lws context
+ * \param accept_fd:   fd of already-accepted socket to adopt
+ * \param readbuf:     NULL or pointer to data that must be drained before reading from
+ *             accept_fd
+ * \param len: The length of the data held at \param readbuf
+ *
+ * Either returns new wsi bound to accept_fd, or closes accept_fd and
+ * returns NULL, having cleaned up any new wsi pieces.
+ *
+ * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
+ * to ws or just serve http.
+ *
+ * If your external code did not already read from the socket, you can use
+ * lws_adopt_socket() instead.
+ *
+ * This api is guaranteed to use the data at \param readbuf first, before reading from
+ * the socket.
+ *
+ * readbuf is limited to the size of the ah rx buf, currently 2048 bytes.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd,
+                         const char *readbuf, size_t len);
+/**
+ * lws_adopt_socket_vhost_readbuf() - adopt foreign socket and first rx as if listen socket
+ * accepted it for vhost.
+ * \param vhost:       lws vhost
+ * \param accept_fd:   fd of already-accepted socket to adopt
+ * \param readbuf:     NULL or pointer to data that must be drained before
+ *                     reading from accept_fd
+ * \param len:         The length of the data held at \param readbuf
+ *
+ * Either returns new wsi bound to accept_fd, or closes accept_fd and
+ * returns NULL, having cleaned up any new wsi pieces.
+ *
+ * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
+ * to ws or just serve http.
+ *
+ * If your external code did not already read from the socket, you can use
+ * lws_adopt_socket() instead.
+ *
+ * This api is guaranteed to use the data at \param readbuf first, before reading from
+ * the socket.
+ *
+ * readbuf is limited to the size of the ah rx buf, currently 2048 bytes.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost,
+                              lws_sockfd_type accept_fd, const char *readbuf,
+                              size_t len);
+
+#define LWS_CAUDP_BIND 1
+
+/**
+ * lws_create_adopt_udp() - create, bind and adopt a UDP socket
+ *
+ * \param vhost:        lws vhost
+ * \param port:                 UDP port to bind to, -1 means unbound
+ * \param flags:        0 or LWS_CAUDP_NO_BIND
+ * \param protocol_name: Name of protocol on vhost to bind wsi to
+ * \param parent_wsi:   NULL or parent wsi new wsi will be a child of
+ *
+ * Either returns new wsi bound to accept_fd, or closes accept_fd and
+ * returns NULL, having cleaned up any new wsi pieces.
+ * */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags,
+                    const char *protocol_name, struct lws *parent_wsi);
+///@}
diff --git a/include/libwebsockets/lws-callbacks.h b/include/libwebsockets/lws-callbacks.h
new file mode 100644 (file)
index 0000000..7987404
--- /dev/null
@@ -0,0 +1,846 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup usercb User Callback
+ *
+ * ##User protocol callback
+ *
+ * The protocol callback is the primary way lws interacts with
+ * user code.  For one of a list of a few dozen reasons the callback gets
+ * called at some event to be handled.
+ *
+ * All of the events can be ignored, returning 0 is taken as "OK" and returning
+ * nonzero in most cases indicates that the connection should be closed.
+ */
+///@{
+
+struct lws_ssl_info {
+       int where;
+       int ret;
+};
+
+enum lws_cert_update_state {
+       LWS_CUS_IDLE,
+       LWS_CUS_STARTING,
+       LWS_CUS_SUCCESS,
+       LWS_CUS_FAILED,
+
+       LWS_CUS_CREATE_KEYS,
+       LWS_CUS_REG,
+       LWS_CUS_AUTH,
+       LWS_CUS_CHALLENGE,
+       LWS_CUS_CREATE_REQ,
+       LWS_CUS_REQ,
+       LWS_CUS_CONFIRM,
+       LWS_CUS_ISSUE,
+};
+
+enum {
+       LWS_TLS_REQ_ELEMENT_COUNTRY,
+       LWS_TLS_REQ_ELEMENT_STATE,
+       LWS_TLS_REQ_ELEMENT_LOCALITY,
+       LWS_TLS_REQ_ELEMENT_ORGANIZATION,
+       LWS_TLS_REQ_ELEMENT_COMMON_NAME,
+       LWS_TLS_REQ_ELEMENT_EMAIL,
+
+       LWS_TLS_REQ_ELEMENT_COUNT,
+
+       LWS_TLS_SET_DIR_URL = LWS_TLS_REQ_ELEMENT_COUNT,
+       LWS_TLS_SET_AUTH_PATH,
+       LWS_TLS_SET_CERT_PATH,
+       LWS_TLS_SET_KEY_PATH,
+
+       LWS_TLS_TOTAL_COUNT
+};
+
+struct lws_acme_cert_aging_args {
+       struct lws_vhost *vh;
+       const char *element_overrides[LWS_TLS_TOTAL_COUNT]; /* NULL = use pvo */
+};
+
+/*
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+/** enum lws_callback_reasons - reason you're getting a protocol callback */
+enum lws_callback_reasons {
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to wsi and protocol binding lifecycle -----
+        */
+
+       LWS_CALLBACK_PROTOCOL_INIT                              = 27,
+       /**< One-time call per protocol, per-vhost using it, so it can
+        * do initial setup / allocations etc */
+
+       LWS_CALLBACK_PROTOCOL_DESTROY                           = 28,
+       /**< One-time call per protocol, per-vhost using it, indicating
+        * this protocol won't get used at all after this callback, the
+        * vhost is getting destroyed.  Take the opportunity to
+        * deallocate everything that was allocated by the protocol. */
+
+       LWS_CALLBACK_WSI_CREATE                                 = 29,
+       /**< outermost (earliest) wsi create notification to protocols[0] */
+
+       LWS_CALLBACK_WSI_DESTROY                                = 30,
+       /**< outermost (latest) wsi destroy notification to protocols[0] */
+
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to Server TLS -----
+        */
+
+       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS     = 21,
+       /**< if configured for
+        * including OpenSSL support, this callback allows your user code
+        * to perform extra SSL_CTX_load_verify_locations() or similar
+        * calls to direct OpenSSL where to find certificates the client
+        * can use to confirm the remote server identity.  user is the
+        * OpenSSL SSL_CTX* */
+
+       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS     = 22,
+       /**< if configured for
+        * including OpenSSL support, this callback allows your user code
+        * to load extra certificates into the server which allow it to
+        * verify the validity of certificates returned by clients.  user
+        * is the server's OpenSSL SSL_CTX* and in is the lws_vhost */
+
+       LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION   = 23,
+       /**< if the libwebsockets vhost was created with the option
+        * LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT, then this
+        * callback is generated during OpenSSL verification of the cert
+        * sent from the client.  It is sent to protocol[0] callback as
+        * no protocol has been negotiated on the connection yet.
+        * Notice that the libwebsockets context and wsi are both NULL
+        * during this callback.  See
+        *  http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html
+        * to understand more detail about the OpenSSL callback that
+        * generates this libwebsockets callback and the meanings of the
+        * arguments passed.  In this callback, user is the x509_ctx,
+        * in is the ssl pointer and len is preverify_ok
+        * Notice that this callback maintains libwebsocket return
+        * conventions, return 0 to mean the cert is OK or 1 to fail it.
+        * This also means that if you don't handle this callback then
+        * the default callback action of returning 0 allows the client
+        * certificates. */
+
+       LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY       = 37,
+       /**< if configured for including OpenSSL support but no private key
+        * file has been specified (ssl_private_key_filepath is NULL), this is
+        * called to allow the user to set the private key directly via
+        * libopenssl and perform further operations if required; this might be
+        * useful in situations where the private key is not directly accessible
+        * by the OS, for example if it is stored on a smartcard.
+        * user is the server's OpenSSL SSL_CTX* */
+
+       LWS_CALLBACK_SSL_INFO                                   = 67,
+       /**< SSL connections only.  An event you registered an
+        * interest in at the vhost has occurred on a connection
+        * using the vhost.  in is a pointer to a
+        * struct lws_ssl_info containing information about the
+        * event*/
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to Client TLS -----
+        */
+
+       LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION = 58,
+       /**< Similar to LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION
+        * this callback is called during OpenSSL verification of the cert
+        * sent from the server to the client. It is sent to protocol[0]
+        * callback as no protocol has been negotiated on the connection yet.
+        * Notice that the wsi is set because lws_client_connect_via_info was
+        * successful.
+        *
+        * See http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html
+        * to understand more detail about the OpenSSL callback that
+        * generates this libwebsockets callback and the meanings of the
+        * arguments passed. In this callback, user is the x509_ctx,
+        * in is the ssl pointer and len is preverify_ok.
+        *
+        * THIS IS NOT RECOMMENDED BUT if a cert validation error shall be
+        * overruled and cert shall be accepted as ok,
+        * X509_STORE_CTX_set_error((X509_STORE_CTX*)user, X509_V_OK); must be
+        * called and return value must be 0 to mean the cert is OK;
+        * returning 1 will fail the cert in any case.
+        *
+        * This also means that if you don't handle this callback then
+        * the default callback action of returning 0 will not accept the
+        * certificate in case of a validation error decided by the SSL lib.
+        *
+        * This is expected and secure behaviour when validating certificates.
+        *
+        * Note: LCCSCF_ALLOW_SELFSIGNED and
+        * LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK still work without this
+        * callback being implemented.
+        */
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to HTTP Server  -----
+        */
+
+       LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED             = 19,
+       /**< A new client has been accepted by the ws server.  This
+        * callback allows setting any relevant property to it. Because this
+        * happens immediately after the instantiation of a new client,
+        * there's no websocket protocol selected yet so this callback is
+        * issued only to protocol 0. Only wsi is defined, pointing to the
+        * new client, and the return value is ignored. */
+
+       LWS_CALLBACK_HTTP                                       = 12,
+       /**< an http request has come from a client that is not
+        * asking to upgrade the connection to a websocket
+        * one.  This is a chance to serve http content,
+        * for example, to send a script to the client
+        * which will then open the websockets connection.
+        * in points to the URI path requested and
+        * lws_serve_http_file() makes it very
+        * simple to send back a file to the client.
+        * Normally after sending the file you are done
+        * with the http connection, since the rest of the
+        * activity will come by websockets from the script
+        * that was delivered by http, so you will want to
+        * return 1; to close and free up the connection. */
+
+       LWS_CALLBACK_HTTP_BODY                                  = 13,
+       /**< the next len bytes data from the http
+        * request body HTTP connection is now available in in. */
+
+       LWS_CALLBACK_HTTP_BODY_COMPLETION                       = 14,
+       /**< the expected amount of http request body has been delivered */
+
+       LWS_CALLBACK_HTTP_FILE_COMPLETION                       = 15,
+       /**< a file requested to be sent down http link has completed. */
+
+       LWS_CALLBACK_HTTP_WRITEABLE                             = 16,
+       /**< you can write more down the http protocol link now. */
+
+       LWS_CALLBACK_CLOSED_HTTP                                =  5,
+       /**< when a HTTP (non-websocket) session ends */
+
+       LWS_CALLBACK_FILTER_HTTP_CONNECTION                     = 18,
+       /**< called when the request has
+        * been received and parsed from the client, but the response is
+        * not sent yet.  Return non-zero to disallow the connection.
+        * user is a pointer to the connection user space allocation,
+        * in is the URI, eg, "/"
+        * In your handler you can use the public APIs
+        * lws_hdr_total_length() / lws_hdr_copy() to access all of the
+        * headers using the header enums lws_token_indexes from
+        * libwebsockets.h to check for and read the supported header
+        * presence and content before deciding to allow the http
+        * connection to proceed or to kill the connection. */
+
+       LWS_CALLBACK_ADD_HEADERS                                = 53,
+       /**< This gives your user code a chance to add headers to a server
+        * transaction bound to your protocol.  `in` points to a
+        * `struct lws_process_html_args` describing a buffer and length
+        * you can add headers into using the normal lws apis.
+        *
+        * (see LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to add headers to
+        * a client transaction)
+        *
+        * Only `args->p` and `args->len` are valid, and `args->p` should
+        * be moved on by the amount of bytes written, if any.  Eg
+        *
+        *      case LWS_CALLBACK_ADD_HEADERS:
+        *
+        *          struct lws_process_html_args *args =
+        *                      (struct lws_process_html_args *)in;
+        *
+        *          if (lws_add_http_header_by_name(wsi,
+        *                      (unsigned char *)"set-cookie:",
+        *                      (unsigned char *)cookie, cookie_len,
+        *                      (unsigned char **)&args->p,
+        *                      (unsigned char *)args->p + args->max_len))
+        *              return 1;
+        *
+        *          break;
+        */
+
+       LWS_CALLBACK_CHECK_ACCESS_RIGHTS                        = 51,
+       /**< This gives the user code a chance to forbid an http access.
+        * `in` points to a `struct lws_process_html_args`, which
+        * describes the URL, and a bit mask describing the type of
+        * authentication required.  If the callback returns nonzero,
+        * the transaction ends with HTTP_STATUS_UNAUTHORIZED. */
+
+       LWS_CALLBACK_PROCESS_HTML                               = 52,
+       /**< This gives your user code a chance to mangle outgoing
+        * HTML.  `in` points to a `struct lws_process_html_args`
+        * which describes the buffer containing outgoing HTML.
+        * The buffer may grow up to `.max_len` (currently +128
+        * bytes per buffer).
+        */
+
+       LWS_CALLBACK_HTTP_BIND_PROTOCOL                         = 49,
+       /**< By default, all HTTP handling is done in protocols[0].
+        * However you can bind different protocols (by name) to
+        * different parts of the URL space using callback mounts.  This
+        * callback occurs in the new protocol when a wsi is bound
+        * to that protocol.  Any protocol allocation related to the
+        * http transaction processing should be created then.
+        * These specific callbacks are necessary because with HTTP/1.1,
+        * a single connection may perform at series of different
+        * transactions at different URLs, thus the lifetime of the
+        * protocol bind is just for one transaction, not connection. */
+
+       LWS_CALLBACK_HTTP_DROP_PROTOCOL                         = 50,
+       /**< This is called when a transaction is unbound from a protocol.
+        * It indicates the connection completed its transaction and may
+        * do something different now.  Any protocol allocation related
+        * to the http transaction processing should be destroyed. */
+
+       LWS_CALLBACK_HTTP_CONFIRM_UPGRADE                       = 86,
+       /**< This is your chance to reject an HTTP upgrade action.  The
+        * name of the protocol being upgraded to is in 'in', and the ah
+        * is still bound to the wsi, so you can look at the headers.
+        *
+        * The default of returning 0 (ie, also if not handled) means the
+        * upgrade may proceed.  Return <0 to just hang up the connection,
+        * or >0 if you have rejected the connection by returning http headers
+        * and response code yourself.
+        *
+        * There is no need for you to call transaction_completed() as the
+        * caller will take care of it when it sees you returned >0.
+        */
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to HTTP Client  -----
+        */
+
+       LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP                    = 44,
+       /**< The HTTP client connection has succeeded, and is now
+        * connected to the server */
+
+       LWS_CALLBACK_CLOSED_CLIENT_HTTP                         = 45,
+       /**< The HTTP client connection is closing */
+
+       LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ                   = 48,
+       /**< This is generated by lws_http_client_read() used to drain
+        * incoming data.  In the case the incoming data was chunked, it will
+        * be split into multiple smaller callbacks for each chunk block,
+        * removing the chunk headers. If not chunked, it will appear all in
+        * one callback. */
+
+       LWS_CALLBACK_RECEIVE_CLIENT_HTTP                        = 46,
+       /**< This simply indicates data was received on the HTTP client
+        * connection.  It does NOT drain or provide the data.
+        * This exists to neatly allow a proxying type situation,
+        * where this incoming data will go out on another connection.
+        * If the outgoing connection stalls, we should stall processing
+        * the incoming data.  So a handler for this in that case should
+        * simply set a flag to indicate there is incoming data ready
+        * and ask for a writeable callback on the outgoing connection.
+        * In the writable callback he can check the flag and then get
+        * and drain the waiting incoming data using lws_http_client_read().
+        * This will use callbacks to LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ
+        * to get and drain the incoming data, where it should be sent
+        * back out on the outgoing connection. */
+       LWS_CALLBACK_COMPLETED_CLIENT_HTTP                      = 47,
+       /**< The client transaction completed... at the moment this
+        * is the same as closing since transaction pipelining on
+        * client side is not yet supported.  */
+
+       LWS_CALLBACK_CLIENT_HTTP_WRITEABLE                      = 57,
+       /**< when doing an HTTP type client connection, you can call
+        * lws_client_http_body_pending(wsi, 1) from
+        * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to get these callbacks
+        * sending the HTTP headers.
+        *
+        * From this callback, when you have sent everything, you should let
+        * lws know by calling lws_client_http_body_pending(wsi, 0)
+        */
+
+       LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL                  = 85,
+       LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL                  = 76,
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to Websocket Server -----
+        */
+
+       LWS_CALLBACK_ESTABLISHED                                =  0,
+       /**< (VH) after the server completes a handshake with an incoming
+        * client.  If you built the library with ssl support, in is a
+        * pointer to the ssl struct associated with the connection or NULL.
+        *
+        * b0 of len is set if the connection was made using ws-over-h2
+        */
+
+       LWS_CALLBACK_CLOSED                                     =  4,
+       /**< when the websocket session ends */
+
+       LWS_CALLBACK_SERVER_WRITEABLE                           = 11,
+       /**< See LWS_CALLBACK_CLIENT_WRITEABLE */
+
+       LWS_CALLBACK_RECEIVE                                    =  6,
+       /**< data has appeared for this server endpoint from a
+        * remote client, it can be found at *in and is
+        * len bytes long */
+
+       LWS_CALLBACK_RECEIVE_PONG                               =  7,
+       /**< servers receive PONG packets with this callback reason */
+
+       LWS_CALLBACK_WS_PEER_INITIATED_CLOSE                    = 38,
+       /**< The peer has sent an unsolicited Close WS packet.  in and
+        * len are the optional close code (first 2 bytes, network
+        * order) and the optional additional information which is not
+        * defined in the standard, and may be a string or non human-readable
+        * data.
+        * If you return 0 lws will echo the close and then close the
+        * connection.  If you return nonzero lws will just close the
+        * connection. */
+
+       LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION                 = 20,
+       /**< called when the handshake has
+        * been received and parsed from the client, but the response is
+        * not sent yet.  Return non-zero to disallow the connection.
+        * user is a pointer to the connection user space allocation,
+        * in is the requested protocol name
+        * In your handler you can use the public APIs
+        * lws_hdr_total_length() / lws_hdr_copy() to access all of the
+        * headers using the header enums lws_token_indexes from
+        * libwebsockets.h to check for and read the supported header
+        * presence and content before deciding to allow the handshake
+        * to proceed or to kill the connection. */
+
+       LWS_CALLBACK_CONFIRM_EXTENSION_OKAY                     = 25,
+       /**< When the server handshake code
+        * sees that it does support a requested extension, before
+        * accepting the extension by additing to the list sent back to
+        * the client it gives this callback just to check that it's okay
+        * to use that extension.  It calls back to the requested protocol
+        * and with in being the extension name, len is 0 and user is
+        * valid.  Note though at this time the ESTABLISHED callback hasn't
+        * happened yet so if you initialize user content there, user
+        * content during this callback might not be useful for anything. */
+
+       LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL                    = 77,
+       LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL                    = 78,
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to Websocket Client -----
+        */
+
+       LWS_CALLBACK_CLIENT_CONNECTION_ERROR                    =  1,
+       /**< the request client connection has been unable to complete a
+        * handshake with the remote server.  If in is non-NULL, you can
+        * find an error string of length len where it points to
+        *
+        * Diagnostic strings that may be returned include
+        *
+        *      "getaddrinfo (ipv6) failed"
+        *      "unknown address family"
+        *      "getaddrinfo (ipv4) failed"
+        *      "set socket opts failed"
+        *      "insert wsi failed"
+        *      "lws_ssl_client_connect1 failed"
+        *      "lws_ssl_client_connect2 failed"
+        *      "Peer hung up"
+        *      "read failed"
+        *      "HS: URI missing"
+        *      "HS: Redirect code but no Location"
+        *      "HS: URI did not parse"
+        *      "HS: Redirect failed"
+        *      "HS: Server did not return 200"
+        *      "HS: OOM"
+        *      "HS: disallowed by client filter"
+        *      "HS: disallowed at ESTABLISHED"
+        *      "HS: ACCEPT missing"
+        *      "HS: ws upgrade response not 101"
+        *      "HS: UPGRADE missing"
+        *      "HS: Upgrade to something other than websocket"
+        *      "HS: CONNECTION missing"
+        *      "HS: UPGRADE malformed"
+        *      "HS: PROTOCOL malformed"
+        *      "HS: Cannot match protocol"
+        *      "HS: EXT: list too big"
+        *      "HS: EXT: failed setting defaults"
+        *      "HS: EXT: failed parsing defaults"
+        *      "HS: EXT: failed parsing options"
+        *      "HS: EXT: Rejects server options"
+        *      "HS: EXT: unknown ext"
+        *      "HS: Accept hash wrong"
+        *      "HS: Rejected by filter cb"
+        *      "HS: OOM"
+        *      "HS: SO_SNDBUF failed"
+        *      "HS: Rejected at CLIENT_ESTABLISHED"
+        */
+
+       LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH                =  2,
+       /**< this is the last chance for the client user code to examine the
+        * http headers and decide to reject the connection.  If the
+        * content in the headers is interesting to the
+        * client (url, etc) it needs to copy it out at
+        * this point since it will be destroyed before
+        * the CLIENT_ESTABLISHED call */
+
+       LWS_CALLBACK_CLIENT_ESTABLISHED                         =  3,
+       /**< after your client connection completed the websocket upgrade
+        * handshake with the remote server */
+
+       LWS_CALLBACK_CLIENT_CLOSED                              = 75,
+       /**< when a client websocket session ends */
+
+       LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER             = 24,
+       /**< this callback happens
+        * when a client handshake is being compiled.  user is NULL,
+        * in is a char **, it's pointing to a char * which holds the
+        * next location in the header buffer where you can add
+        * headers, and len is the remaining space in the header buffer,
+        * which is typically some hundreds of bytes.  So, to add a canned
+        * cookie, your handler code might look similar to:
+        *
+        *      char **p = (char **)in, *end = (*p) + len;
+        *
+        *      if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_COOKIE,
+        *                      (unsigned char)"a=b", 3, p, end))
+        *              return -1;
+        *
+        * See LWS_CALLBACK_ADD_HEADERS for adding headers to server
+        * transactions.
+        */
+
+       LWS_CALLBACK_CLIENT_RECEIVE                             =  8,
+       /**< data has appeared from the server for the client connection, it
+        * can be found at *in and is len bytes long */
+
+       LWS_CALLBACK_CLIENT_RECEIVE_PONG                        =  9,
+       /**< clients receive PONG packets with this callback reason */
+
+       LWS_CALLBACK_CLIENT_WRITEABLE                           = 10,
+       /**<  If you call lws_callback_on_writable() on a connection, you will
+        * get one of these callbacks coming when the connection socket
+        * is able to accept another write packet without blocking.
+        * If it already was able to take another packet without blocking,
+        * you'll get this callback at the next call to the service loop
+        * function.  Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE
+        * and servers get LWS_CALLBACK_SERVER_WRITEABLE. */
+
+       LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED         = 26,
+       /**< When a ws client
+        * connection is being prepared to start a handshake to a server,
+        * each supported extension is checked with protocols[0] callback
+        * with this reason, giving the user code a chance to suppress the
+        * claim to support that extension by returning non-zero.  If
+        * unhandled, by default 0 will be returned and the extension
+        * support included in the header to the server.  Notice this
+        * callback comes to protocols[0]. */
+
+       LWS_CALLBACK_WS_EXT_DEFAULTS                            = 39,
+       /**< Gives client connections an opportunity to adjust negotiated
+        * extension defaults.  `user` is the extension name that was
+        * negotiated (eg, "permessage-deflate").  `in` points to a
+        * buffer and `len` is the buffer size.  The user callback can
+        * set the buffer to a string describing options the extension
+        * should parse.  Or just ignore for defaults. */
+
+
+       LWS_CALLBACK_FILTER_NETWORK_CONNECTION                  = 17,
+       /**< called when a client connects to
+        * the server at network level; the connection is accepted but then
+        * passed to this callback to decide whether to hang up immediately
+        * or not, based on the client IP.  in contains the connection
+        * socket's descriptor. Since the client connection information is
+        * not available yet, wsi still pointing to the main server socket.
+        * Return non-zero to terminate the connection before sending or
+        * receiving anything. Because this happens immediately after the
+        * network connection from the client, there's no websocket protocol
+        * selected yet so this callback is issued only to protocol 0. */
+
+       LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL                    = 79,
+       LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL                    = 80,
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to external poll loop integration  -----
+        */
+
+       LWS_CALLBACK_GET_THREAD_ID                              = 31,
+       /**< lws can accept callback when writable requests from other
+        * threads, if you implement this callback and return an opaque
+        * current thread ID integer. */
+
+       /* external poll() management support */
+       LWS_CALLBACK_ADD_POLL_FD                                = 32,
+       /**< lws normally deals with its poll() or other event loop
+        * internally, but in the case you are integrating with another
+        * server you will need to have lws sockets share a
+        * polling array with the other server.  This and the other
+        * POLL_FD related callbacks let you put your specialized
+        * poll array interface code in the callback for protocol 0, the
+        * first protocol you support, usually the HTTP protocol in the
+        * serving case.
+        * This callback happens when a socket needs to be
+        * added to the polling loop: in points to a struct
+        * lws_pollargs; the fd member of the struct is the file
+        * descriptor, and events contains the active events
+        *
+        * If you are using the internal lws polling / event loop
+        * you can just ignore these callbacks. */
+
+       LWS_CALLBACK_DEL_POLL_FD                                = 33,
+       /**< This callback happens when a socket descriptor
+        * needs to be removed from an external polling array.  in is
+        * again the struct lws_pollargs containing the fd member
+        * to be removed.  If you are using the internal polling
+        * loop, you can just ignore it. */
+
+       LWS_CALLBACK_CHANGE_MODE_POLL_FD                        = 34,
+       /**< This callback happens when lws wants to modify the events for
+        * a connection.
+        * in is the struct lws_pollargs with the fd to change.
+        * The new event mask is in events member and the old mask is in
+        * the prev_events member.
+        * If you are using the internal polling loop, you can just ignore
+        * it. */
+
+       LWS_CALLBACK_LOCK_POLL                                  = 35,
+       /**< These allow the external poll changes driven
+        * by lws to participate in an external thread locking
+        * scheme around the changes, so the whole thing is threadsafe.
+        * These are called around three activities in the library,
+        *      - inserting a new wsi in the wsi / fd table (len=1)
+        *      - deleting a wsi from the wsi / fd table (len=1)
+        *      - changing a wsi's POLLIN/OUT state (len=0)
+        * Locking and unlocking external synchronization objects when
+        * len == 1 allows external threads to be synchronized against
+        * wsi lifecycle changes if it acquires the same lock for the
+        * duration of wsi dereference from the other thread context. */
+
+       LWS_CALLBACK_UNLOCK_POLL                                = 36,
+       /**< See LWS_CALLBACK_LOCK_POLL, ignore if using lws internal poll */
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to CGI serving -----
+        */
+
+       LWS_CALLBACK_CGI                                        = 40,
+       /**< CGI: CGI IO events on stdin / out / err are sent here on
+        * protocols[0].  The provided `lws_callback_http_dummy()`
+        * handles this and the callback should be directed there if
+        * you use CGI. */
+
+       LWS_CALLBACK_CGI_TERMINATED                             = 41,
+       /**< CGI: The related CGI process ended, this is called before
+        * the wsi is closed.  Used to, eg, terminate chunking.
+        * The provided `lws_callback_http_dummy()`
+        * handles this and the callback should be directed there if
+        * you use CGI.  The child PID that terminated is in len. */
+
+       LWS_CALLBACK_CGI_STDIN_DATA                             = 42,
+       /**< CGI: Data is, to be sent to the CGI process stdin, eg from
+        * a POST body.  The provided `lws_callback_http_dummy()`
+        * handles this and the callback should be directed there if
+        * you use CGI. */
+
+       LWS_CALLBACK_CGI_STDIN_COMPLETED                        = 43,
+       /**< CGI: no more stdin is coming.  The provided
+        * `lws_callback_http_dummy()` handles this and the callback
+        * should be directed there if you use CGI. */
+
+       LWS_CALLBACK_CGI_PROCESS_ATTACH                         = 70,
+       /**< CGI: Sent when the CGI process is spawned for the wsi.  The
+        * len parameter is the PID of the child process */
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to Generic Sessions -----
+        */
+
+       LWS_CALLBACK_SESSION_INFO                               = 54,
+       /**< This is only generated by user code using generic sessions.
+        * It's used to get a `struct lws_session_info` filled in by
+        * generic sessions with information about the logged-in user.
+        * See the messageboard sample for an example of how to use. */
+
+       LWS_CALLBACK_GS_EVENT                                   = 55,
+       /**< Indicates an event happened to the Generic Sessions session.
+        * `in` contains a `struct lws_gs_event_args` describing the event. */
+
+       LWS_CALLBACK_HTTP_PMO                                   = 56,
+       /**< per-mount options for this connection, called before
+        * the normal LWS_CALLBACK_HTTP when the mount has per-mount
+        * options.
+        */
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to RAW PROXY -----
+        */
+
+       LWS_CALLBACK_RAW_PROXY_CLI_RX                           = 89,
+       /**< RAW mode client (outgoing) RX */
+
+       LWS_CALLBACK_RAW_PROXY_SRV_RX                           = 90,
+       /**< RAW mode server (listening) RX */
+
+       LWS_CALLBACK_RAW_PROXY_CLI_CLOSE                        = 91,
+       /**< RAW mode client (outgoing) is closing */
+
+       LWS_CALLBACK_RAW_PROXY_SRV_CLOSE                        = 92,
+       /**< RAW mode server (listening) is closing */
+
+       LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE                    = 93,
+       /**< RAW mode client (outgoing) may be written */
+
+       LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE                    = 94,
+       /**< RAW mode server (listening) may be written */
+
+       LWS_CALLBACK_RAW_PROXY_CLI_ADOPT                        = 95,
+       /**< RAW mode client (onward) accepted socket was adopted
+        *   (equivalent to 'wsi created') */
+
+       LWS_CALLBACK_RAW_PROXY_SRV_ADOPT                        = 96,
+       /**< RAW mode server (listening) accepted socket was adopted
+        *   (equivalent to 'wsi created') */
+
+       LWS_CALLBACK_RAW_PROXY_CLI_BIND_PROTOCOL                = 97,
+       LWS_CALLBACK_RAW_PROXY_SRV_BIND_PROTOCOL                = 98,
+       LWS_CALLBACK_RAW_PROXY_CLI_DROP_PROTOCOL                = 99,
+       LWS_CALLBACK_RAW_PROXY_SRV_DROP_PROTOCOL                = 100,
+
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to RAW sockets -----
+        */
+
+       LWS_CALLBACK_RAW_RX                                     = 59,
+       /**< RAW mode connection RX */
+
+       LWS_CALLBACK_RAW_CLOSE                                  = 60,
+       /**< RAW mode connection is closing */
+
+       LWS_CALLBACK_RAW_WRITEABLE                              = 61,
+       /**< RAW mode connection may be written */
+
+       LWS_CALLBACK_RAW_ADOPT                                  = 62,
+       /**< RAW mode connection was adopted (equivalent to 'wsi created') */
+
+       LWS_CALLBACK_RAW_CONNECTED                              = 101,
+       /**< outgoing client RAW mode connection was connected */
+
+       LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL                      = 81,
+       LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL                      = 82,
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to RAW file handles -----
+        */
+
+       LWS_CALLBACK_RAW_ADOPT_FILE                             = 63,
+       /**< RAW mode file was adopted (equivalent to 'wsi created') */
+
+       LWS_CALLBACK_RAW_RX_FILE                                = 64,
+       /**< This is the indication the RAW mode file has something to read.
+        *   This doesn't actually do the read of the file and len is always
+        *   0... your code should do the read having been informed there is
+        *   something to read now. */
+
+       LWS_CALLBACK_RAW_WRITEABLE_FILE                         = 65,
+       /**< RAW mode file is writeable */
+
+       LWS_CALLBACK_RAW_CLOSE_FILE                             = 66,
+       /**< RAW mode wsi that adopted a file is closing */
+
+       LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL                     = 83,
+       LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL                     = 84,
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to generic wsi events -----
+        */
+
+       LWS_CALLBACK_TIMER                                      = 73,
+       /**< When the time elapsed after a call to
+        * lws_set_timer_usecs(wsi, usecs) is up, the wsi will get one of
+        * these callbacks.  The deadline can be continuously extended into the
+        * future by later calls to lws_set_timer_usecs() before the deadline
+        * expires, or cancelled by lws_set_timer_usecs(wsi, -1);
+        */
+
+       LWS_CALLBACK_EVENT_WAIT_CANCELLED                       = 71,
+       /**< This is sent to every protocol of every vhost in response
+        * to lws_cancel_service() or lws_cancel_service_pt().  This
+        * callback is serialized in the lws event loop normally, even
+        * if the lws_cancel_service[_pt]() call was from a different
+        * thread. */
+
+       LWS_CALLBACK_CHILD_CLOSING                              = 69,
+       /**< Sent to parent to notify them a child is closing / being
+        * destroyed.  in is the child wsi.
+        */
+
+       /* ---------------------------------------------------------------------
+        * ----- Callbacks related to TLS certificate management -----
+        */
+
+       LWS_CALLBACK_VHOST_CERT_AGING                           = 72,
+       /**< When a vhost TLS cert has its expiry checked, this callback
+        * is broadcast to every protocol of every vhost in case the
+        * protocol wants to take some action with this information.
+        * \p in is a pointer to a struct lws_acme_cert_aging_args,
+        * and \p len is the number of days left before it expires, as
+        * a (ssize_t).  In the struct lws_acme_cert_aging_args, vh
+        * points to the vhost the cert aging information applies to,
+        * and element_overrides[] is an optional way to update information
+        * from the pvos... NULL in an index means use the information from
+        * from the pvo for the cert renewal, non-NULL in the array index
+        * means use that pointer instead for the index. */
+
+       LWS_CALLBACK_VHOST_CERT_UPDATE                          = 74,
+       /**< When a vhost TLS cert is being updated, progress is
+        * reported to the vhost in question here, including completion
+        * and failure.  in points to optional JSON, and len represents the
+        * connection state using enum lws_cert_update_state */
+
+
+       /****** add new things just above ---^ ******/
+
+       LWS_CALLBACK_USER = 1000,
+       /**<  user code can use any including above without fear of clashes */
+};
+
+
+
+/**
+ * typedef lws_callback_function() - User server actions
+ * \param wsi: Opaque websocket instance pointer
+ * \param reason:      The reason for the call
+ * \param user:        Pointer to per-session user data allocated by library
+ * \param in:          Pointer used for some callback reasons
+ * \param len: Length set for some callback reasons
+ *
+ *     This callback is the way the user controls what is served.  All the
+ *     protocol detail is hidden and handled by the library.
+ *
+ *     For each connection / session there is user data allocated that is
+ *     pointed to by "user".  You set the size of this user data area when
+ *     the library is initialized with lws_create_server.
+ */
+typedef int
+lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason,
+                   void *user, void *in, size_t len);
+
+#define LWS_CB_REASON_AUX_BF__CGI              1
+#define LWS_CB_REASON_AUX_BF__PROXY            2
+#define LWS_CB_REASON_AUX_BF__CGI_CHUNK_END    4
+#define LWS_CB_REASON_AUX_BF__CGI_HEADERS      8
+#define LWS_CB_REASON_AUX_BF__PROXY_TRANS_END  16
+#define LWS_CB_REASON_AUX_BF__PROXY_HEADERS    32
+///@}
diff --git a/include/libwebsockets/lws-cgi.h b/include/libwebsockets/lws-cgi.h
new file mode 100644 (file)
index 0000000..7a5eca2
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup cgi cgi handling
+ *
+ * ##CGI handling
+ *
+ * These functions allow low-level control over stdin/out/err of the cgi.
+ *
+ * However for most cases, binding the cgi to http in and out, the default
+ * lws implementation already does the right thing.
+ */
+
+enum lws_enum_stdinouterr {
+       LWS_STDIN = 0,
+       LWS_STDOUT = 1,
+       LWS_STDERR = 2,
+};
+
+enum lws_cgi_hdr_state {
+       LCHS_HEADER,
+       LCHS_CR1,
+       LCHS_LF1,
+       LCHS_CR2,
+       LCHS_LF2,
+       LHCS_RESPONSE,
+       LHCS_DUMP_HEADERS,
+       LHCS_PAYLOAD,
+       LCHS_SINGLE_0A,
+};
+
+struct lws_cgi_args {
+       struct lws **stdwsi; /**< get fd with lws_get_socket_fd() */
+       enum lws_enum_stdinouterr ch; /**< channel index */
+       unsigned char *data; /**< for messages with payload */
+       enum lws_cgi_hdr_state hdr_state; /**< track where we are in cgi headers */
+       int len; /**< length */
+};
+
+#ifdef LWS_WITH_CGI
+/**
+ * lws_cgi: spawn network-connected cgi process
+ *
+ * \param wsi: connection to own the process
+ * \param exec_array: array of "exec-name" "arg1" ... "argn" NULL
+ * \param script_uri_path_len: how many chars on the left of the uri are the
+ *        path to the cgi, or -1 to spawn without URL-related env vars
+ * \param timeout_secs: seconds script should be allowed to run
+ * \param mp_cgienv: pvo list with per-vhost cgi options to put in env
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_cgi(struct lws *wsi, const char * const *exec_array,
+       int script_uri_path_len, int timeout_secs,
+       const struct lws_protocol_vhost_options *mp_cgienv);
+
+/**
+ * lws_cgi_write_split_stdout_headers: write cgi output accounting for header part
+ *
+ * \param wsi: connection to own the process
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_cgi_write_split_stdout_headers(struct lws *wsi);
+
+/**
+ * lws_cgi_kill: terminate cgi process associated with wsi
+ *
+ * \param wsi: connection to own the process
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_cgi_kill(struct lws *wsi);
+
+/**
+ * lws_cgi_get_stdwsi: get wsi for stdin, stdout, or stderr
+ *
+ * \param wsi: parent wsi that has cgi
+ * \param ch: which of LWS_STDIN, LWS_STDOUT or LWS_STDERR
+ */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch);
+
+#endif
+///@}
+
diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h
new file mode 100644 (file)
index 0000000..d218e2b
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup client Client related functions
+ * ##Client releated functions
+ * \ingroup lwsapi
+ *
+ * */
+///@{
+
+/** enum lws_client_connect_ssl_connection_flags - flags that may be used
+ * with struct lws_client_connect_info ssl_connection member to control if
+ * and how SSL checks apply to the client connection being created
+ */
+
+enum lws_client_connect_ssl_connection_flags {
+       LCCSCF_USE_SSL                          = (1 << 0),
+       LCCSCF_ALLOW_SELFSIGNED                 = (1 << 1),
+       LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK  = (1 << 2),
+       LCCSCF_ALLOW_EXPIRED                    = (1 << 3),
+
+       LCCSCF_PIPELINE                         = (1 << 16),
+               /**< Serialize / pipeline multiple client connections
+                * on a single connection where possible.
+                *
+                * HTTP/1.0: possible if Keep-Alive: yes sent by server
+                * HTTP/1.1: always possible... uses pipelining
+                * HTTP/2:   always possible... uses parallel streams
+                * */
+};
+
+typedef struct lws_sequencer lws_seq_t;
+
+/** struct lws_client_connect_info - parameters to connect with when using
+ *                                 lws_client_connect_via_info() */
+
+struct lws_client_connect_info {
+       struct lws_context *context;
+       /**< lws context to create connection in */
+       const char *address;
+       /**< remote address to connect to */
+       int port;
+       /**< remote port to connect to */
+       int ssl_connection;
+       /**< 0, or a combination of LCCSCF_ flags */
+       const char *path;
+       /**< uri path */
+       const char *host;
+       /**< content of host header */
+       const char *origin;
+       /**< content of origin header */
+       const char *protocol;
+       /**< list of ws protocols we could accept */
+       int ietf_version_or_minus_one;
+       /**< deprecated: currently leave at 0 or -1 */
+       void *userdata;
+       /**< if non-NULL, use this as wsi user_data instead of malloc it */
+       const void *client_exts;
+       /**< UNUSED... provide in info.extensions at context creation time */
+       const char *method;
+       /**< if non-NULL, do this http method instead of ws[s] upgrade.
+        * use "GET" to be a simple http client connection.  "RAW" gets
+        * you a connected socket that lws itself will leave alone once
+        * connected. */
+       struct lws *parent_wsi;
+       /**< if another wsi is responsible for this connection, give it here.
+        * this is used to make sure if the parent closes so do any
+        * child connections first. */
+       const char *uri_replace_from;
+       /**< if non-NULL, when this string is found in URIs in
+        * text/html content-encoding, it's replaced with uri_replace_to */
+       const char *uri_replace_to;
+       /**< see uri_replace_from */
+       struct lws_vhost *vhost;
+       /**< vhost to bind to (used to determine related SSL_CTX) */
+       struct lws **pwsi;
+       /**< if not NULL, store the new wsi here early in the connection
+        * process.  Although we return the new wsi, the call to create the
+        * client connection does progress the connection somewhat and may
+        * meet an error that will result in the connection being scrubbed and
+        * NULL returned.  While the wsi exists though, he may process a
+        * callback like CLIENT_CONNECTION_ERROR with his wsi: this gives the
+        * user callback a way to identify which wsi it is that faced the error
+        * even before the new wsi is returned and even if ultimately no wsi
+        * is returned.
+        */
+       const char *iface;
+       /**< NULL to allow routing on any interface, or interface name or IP
+        * to bind the socket to */
+       const char *local_protocol_name;
+       /**< NULL: .protocol is used both to select the local protocol handler
+        *         to bind to and as the list of remote ws protocols we could
+        *         accept.
+        *   non-NULL: this protocol name is used to bind the connection to
+        *             the local protocol handler.  .protocol is used for the
+        *             list of remote ws protocols we could accept */
+       const char *alpn;
+       /**< NULL: allow lws default ALPN list, from vhost if present or from
+        *       list of roles built into lws
+        * non-NULL: require one from provided comma-separated list of alpn
+        *           tokens
+        */
+
+       lws_seq_t *seq;
+       /**< NULL, or an lws_seq_t that wants to be given messages about
+        * this wsi's lifecycle as it connects, errors or closes.
+        */
+
+       void *opaque_user_data;
+       /**< This data has no meaning to lws but is applied to the client wsi
+        *   and can be retrieved by user code with lws_get_opaque_user_data().
+        *   It's also provided with sequencer messages if the wsi is bound to
+        *   an lws_seq_t.
+        */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility
+        *
+        * The below is to ensure later library versions with new
+        * members added above will see 0 (default) even if the app
+        * was not built against the newer headers.
+        */
+
+       void *_unused[4]; /**< dummy */
+};
+
+/**
+ * lws_client_connect_via_info() - Connect to another websocket server
+ * \param ccinfo: pointer to lws_client_connect_info struct
+ *
+ *     This function creates a connection to a remote server using the
+ *     information provided in ccinfo.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_client_connect_via_info(const struct lws_client_connect_info *ccinfo);
+
+/**
+ * lws_init_vhost_client_ssl() - also enable client SSL on an existing vhost
+ *
+ * \param info: client ssl related info
+ * \param vhost: which vhost to initialize client ssl operations on
+ *
+ * You only need to call this if you plan on using SSL client connections on
+ * the vhost.  For non-SSL client connections, it's not necessary to call this.
+ *
+ * The following members of info are used during the call
+ *
+ *      - options must have LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT set,
+ *          otherwise the call does nothing
+ *      - provided_client_ssl_ctx must be NULL to get a generated client
+ *          ssl context, otherwise you can pass a prepared one in by setting it
+ *      - ssl_cipher_list may be NULL or set to the client valid cipher list
+ *      - ssl_ca_filepath may be NULL or client cert filepath
+ *      - ssl_cert_filepath may be NULL or client cert filepath
+ *      - ssl_private_key_filepath may be NULL or client cert private key
+ *
+ * You must create your vhost explicitly if you want to use this, so you have
+ * a pointer to the vhost.  Create the context first with the option flag
+ * LWS_SERVER_OPTION_EXPLICIT_VHOSTS and then call lws_create_vhost() with
+ * the same info struct.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_init_vhost_client_ssl(const struct lws_context_creation_info *info,
+                         struct lws_vhost *vhost);
+/**
+ * lws_http_client_read() - consume waiting received http client data
+ *
+ * \param wsi: client connection
+ * \param buf: pointer to buffer pointer - fill with pointer to your buffer
+ * \param len: pointer to chunk length - fill with max length of buffer
+ *
+ * This is called when the user code is notified client http data has arrived.
+ * The user code may choose to delay calling it to consume the data, for example
+ * waiting until an onward connection is writeable.
+ *
+ * For non-chunked connections, up to len bytes of buf are filled with the
+ * received content.  len is set to the actual amount filled before return.
+ *
+ * For chunked connections, the linear buffer content contains the chunking
+ * headers and it cannot be passed in one lump.  Instead, this function will
+ * call back LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ with in pointing to the
+ * chunk start and len set to the chunk length.  There will be as many calls
+ * as there are chunks or partial chunks in the buffer.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_http_client_read(struct lws *wsi, char **buf, int *len);
+
+/**
+ * lws_http_client_http_response() - get last HTTP response code
+ *
+ * \param wsi: client connection
+ *
+ * Returns the last server response code, eg, 200 for client http connections.
+ *
+ * You should capture this during the LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP
+ * callback, because after that the memory reserved for storing the related
+ * headers is freed and this value is lost.
+ */
+LWS_VISIBLE LWS_EXTERN unsigned int
+lws_http_client_http_response(struct lws *wsi);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_client_http_body_pending(struct lws *wsi, int something_left_to_send);
+
+/**
+ * lws_client_http_body_pending() - control if client connection neeeds to send body
+ *
+ * \param wsi: client connection
+ * \param something_left_to_send: nonzero if need to send more body, 0 (default)
+ *                             if nothing more to send
+ *
+ * If you will send payload data with your HTTP client connection, eg, for POST,
+ * when you set the related http headers in
+ * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER callback you should also call
+ * this API with something_left_to_send nonzero, and call
+ * lws_callback_on_writable(wsi);
+ *
+ * After sending the headers, lws will call your callback with
+ * LWS_CALLBACK_CLIENT_HTTP_WRITEABLE reason when writable.  You can send the
+ * next part of the http body payload, calling lws_callback_on_writable(wsi);
+ * if there is more to come, or lws_client_http_body_pending(wsi, 0); to
+ * let lws know the last part is sent and the connection can move on.
+ */
+
+///@}
diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h
new file mode 100644 (file)
index 0000000..de1248c
--- /dev/null
@@ -0,0 +1,1068 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup context-and-vhost context and vhost related functions
+ * ##Context and Vhost releated functions
+ * \ingroup lwsapi
+ *
+ *
+ *  LWS requires that there is one context, in which you may define multiple
+ *  vhosts.  Each vhost is a virtual host, with either its own listen port
+ *  or sharing an existing one.  Each vhost has its own SSL context that can
+ *  be set up individually or left disabled.
+ *
+ *  If you don't care about multiple "site" support, you can ignore it and
+ *  lws will create a single default vhost at context creation time.
+ */
+///@{
+
+/*
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+
+/** enum lws_context_options - context and vhost options */
+enum lws_context_options {
+       LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT     = (1 << 1) |
+                                                                 (1 << 12),
+       /**< (VH) Don't allow the connection unless the client has a
+        * client cert that we recognize; provides
+        * LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT */
+       LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME            = (1 << 2),
+       /**< (CTX) Don't try to get the server's hostname */
+       LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT             = (1 << 3) |
+                                                                 (1 << 12),
+       /**< (VH) Allow non-SSL (plaintext) connections on the same
+        * port as SSL is listening.  If combined with
+        * LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS it will try to
+        * force http connections on an https listener (eg, http://x.com:443) to
+        * redirect to an explicit https connection (eg, https://x.com)
+        */
+       LWS_SERVER_OPTION_LIBEV                                 = (1 << 4),
+       /**< (CTX) Use libev event loop */
+       LWS_SERVER_OPTION_DISABLE_IPV6                          = (1 << 5),
+       /**< (VH) Disable IPV6 support */
+       LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS                   = (1 << 6),
+       /**< (VH) Don't load OS CA certs, you will need to load your
+        * own CA cert(s) */
+       LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED                = (1 << 7),
+       /**< (VH) Accept connections with no valid Cert (eg, selfsigned) */
+       LWS_SERVER_OPTION_VALIDATE_UTF8                         = (1 << 8),
+       /**< (VH) Check UT-8 correctness */
+       LWS_SERVER_OPTION_SSL_ECDH                              = (1 << 9) |
+                                                                 (1 << 12),
+       /**< (VH)  initialize ECDH ciphers */
+       LWS_SERVER_OPTION_LIBUV                                 = (1 << 10),
+       /**< (CTX)  Use libuv event loop */
+       LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS                = (1 << 11) |
+                                                                 (1 << 12),
+       /**< (VH) Use an http redirect to force the client to ask for https.
+        * Notice if your http server issues the STS header and the client has
+        * ever seen that, the client will fail the http connection before it
+        * can actually do the redirect.
+        *
+        * Combine with LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS to handle, eg,
+        * http://x.com:443 -> https://x.com
+        *
+        * (deprecated: use mount redirection) */
+       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT                    = (1 << 12),
+       /**< (CTX) Initialize the SSL library at all */
+       LWS_SERVER_OPTION_EXPLICIT_VHOSTS                       = (1 << 13),
+       /**< (CTX) Only create the context when calling context
+        * create api, implies user code will create its own vhosts */
+       LWS_SERVER_OPTION_UNIX_SOCK                             = (1 << 14),
+       /**< (VH) Use Unix socket */
+       LWS_SERVER_OPTION_STS                                   = (1 << 15),
+       /**< (VH) Send Strict Transport Security header, making
+        * clients subsequently go to https even if user asked for http */
+       LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY                    = (1 << 16),
+       /**< (VH) Enable LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE to take effect */
+       LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE                     = (1 << 17),
+       /**< (VH) if set, only ipv6 allowed on the vhost */
+       LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN             = (1 << 18),
+       /**< (CTX) Libuv only: Do not spin on SIGSEGV / SIGFPE.  A segfault
+        * normally makes the lib spin so you can attach a debugger to it
+        * even if it happened without a debugger in place.  You can disable
+        * that by giving this option.
+        */
+       LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN                   = (1 << 19),
+       /**< For backwards-compatibility reasons, by default
+        * lws prepends "http://" to the origin you give in the client
+        * connection info struct.  If you give this flag when you create
+        * the context, only the string you give in the client connect
+        * info for .origin (if any) will be used directly.
+        */
+       LWS_SERVER_OPTION_FALLBACK_TO_RAW /* use below name */  = (1 << 20),
+       LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG= (1 << 20),
+       /**< (VH) if invalid http is coming in the first line, then abandon
+        * trying to treat the connection as http, and belatedly apply the
+        * .listen_accept_role / .listen_accept_protocol info struct members to
+        * the connection.  If they are NULL, for backwards-compatibility the
+        * connection is bound to "raw-skt" role, and in order of priority:
+        * 1) the vh protocol with a pvo named "raw", 2) the vh protocol with a
+        * pvo named "default", or 3) protocols[0].
+        *
+        * Must be combined with LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT
+        * to work with a socket listening with tls.
+        */
+
+       LWS_SERVER_OPTION_LIBEVENT                              = (1 << 21),
+       /**< (CTX) Use libevent event loop */
+
+       LWS_SERVER_OPTION_ONLY_RAW /* Use below name instead */ = (1 << 22),
+       LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG      = (1 << 22),
+       /**< (VH) All connections to this vhost / port are bound to the
+        * role and protocol given in .listen_accept_role /
+        * .listen_accept_protocol.
+        *
+        * If those explicit user-controlled names are NULL, for backwards-
+        * compatibility the connection is bound to "raw-skt" role, and in order
+        * of priority: 1) the vh protocol with a pvo named "raw", 2) the vh
+        * protocol with a pvo named "default", or 3) protocols[0].
+        *
+        * It's much preferred to specify the role + protocol using the
+        * .listen_accept_role and .listen_accept_protocol in the info struct.
+        */
+       LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE                    = (1 << 23),
+       /**< (VH) Set to allow multiple listen sockets on one interface +
+        * address + port.  The default is to strictly allow only one
+        * listen socket at a time.  This is automatically selected if you
+        * have multiple service threads.  Linux only.
+        */
+       LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX                  = (1 << 24),
+       /**< (VH) Force setting up the vhost SSL_CTX, even though the user
+        * code doesn't explicitly provide a cert in the info struct.  It
+        * implies the user code is going to provide a cert at the
+        * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS callback, which
+        * provides the vhost SSL_CTX * in the user parameter.
+        */
+       LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT                    = (1 << 25),
+       /**< (VH) You probably don't want this.  It forces this vhost to not
+        * call LWS_CALLBACK_PROTOCOL_INIT on its protocols.  It's used in the
+        * special case of a temporary vhost bound to a single protocol.
+        */
+       LWS_SERVER_OPTION_IGNORE_MISSING_CERT                   = (1 << 26),
+       /**< (VH) Don't fail if the vhost TLS cert or key are missing, just
+        * continue.  The vhost won't be able to serve anything, but if for
+        * example the ACME plugin was configured to fetch a cert, this lets
+        * you bootstrap your vhost from having no cert to start with.
+        */
+       LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK           = (1 << 27),
+       /**< (VH) On this vhost, if the connection is being upgraded, insist
+        * that there's a Host: header and that the contents match the vhost
+        * name + port (443 / 80 are assumed if no :port given based on if the
+        * connection is using TLS).
+        *
+        * By default, without this flag, on upgrade lws just checks that the
+        * Host: header was given without checking the contents... this is to
+        * allow lax hostname mappings like localhost / 127.0.0.1, and CNAME
+        * mappings like www.mysite.com / mysite.com
+        */
+       LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE  = (1 << 28),
+       /**< (VH) Send lws default HTTP headers recommended by Mozilla
+        * Observatory for security.  This is a helper option that sends canned
+        * headers on each http response enabling a VERY strict Content Security
+        * Policy.  The policy is so strict, for example it won't let the page
+        * run its own inline JS nor show images or take CSS from a different
+        * server.  In many cases your JS only comes from your server as do the
+        * image sources and CSS, so that is what you want... attackers hoping
+        * to inject JS into your DOM are completely out of luck since even if
+        * they succeed, it will be rejected for execution by the browser
+        * according to the strict CSP.  In other cases you have to deviate from
+        * the complete strictness, in which case don't use this flag: use the
+        * .headers member in the vhost init described in struct
+        * lws_context_creation_info instead to send the adapted headers
+        * yourself.
+        */
+
+       LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER          = (1 << 29),
+       /**< (VH) If you really want to allow HTTP connections on a tls
+        * listener, you can do it with this combined with
+        * LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT.  But this is allowing
+        * accidental loss of the security assurances provided by tls depending
+        * on the client using http when he meant https... it's not
+        * recommended.
+        */
+       LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND              = (1 << 30),
+       /**< (VH) When instantiating a new vhost and the specified port is
+        * already in use, a null value shall be return to signal the error.
+        */
+
+       LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW    = (1 << 31),
+       /**< (VH) Indicates the connections using this vhost should ignore
+        * h2 WINDOW_UPDATE from broken peers and fix them up */
+
+       /****** add new things just above ---^ ******/
+};
+
+#define lws_check_opt(c, f) (((c) & (f)) == (f))
+
+struct lws_plat_file_ops;
+
+/** struct lws_context_creation_info - parameters to create context and /or vhost with
+ *
+ * This is also used to create vhosts.... if LWS_SERVER_OPTION_EXPLICIT_VHOSTS
+ * is not given, then for backwards compatibility one vhost is created at
+ * context-creation time using the info from this struct.
+ *
+ * If LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, then no vhosts are created
+ * at the same time as the context, they are expected to be created afterwards.
+ */
+struct lws_context_creation_info {
+       int port;
+       /**< VHOST: Port to listen on. Use CONTEXT_PORT_NO_LISTEN to suppress
+        * listening for a client. Use CONTEXT_PORT_NO_LISTEN_SERVER if you are
+        * writing a server but you are using \ref sock-adopt instead of the
+        * built-in listener.
+        *
+        * You can also set port to 0, in which case the kernel will pick
+        * a random port that is not already in use.  You can find out what
+        * port the vhost is listening on using lws_get_vhost_listen_port() */
+       const char *iface;
+       /**< VHOST: NULL to bind the listen socket to all interfaces, or the
+        * interface name, eg, "eth2"
+        * If options specifies LWS_SERVER_OPTION_UNIX_SOCK, this member is
+        * the pathname of a UNIX domain socket. you can use the UNIX domain
+        * sockets in abstract namespace, by prepending an at symbol to the
+        * socket name. */
+       const struct lws_protocols *protocols;
+       /**< VHOST: Array of structures listing supported protocols and a
+        * protocol-specific callback for each one.  The list is ended with an
+        * entry that has a NULL callback pointer.  SEE ALSO .pprotocols below,
+        * which gives an alternative way to provide an array of pointers to
+        * protocol structs. */
+       const struct lws_extension *extensions;
+       /**< VHOST: NULL or array of lws_extension structs listing the
+        * extensions this context supports. */
+       const struct lws_token_limits *token_limits;
+       /**< CONTEXT: NULL or struct lws_token_limits pointer which is
+        * initialized with a token length limit for each possible WSI_TOKEN_ */
+       const char *ssl_private_key_password;
+       /**< VHOST: NULL or the passphrase needed for the private key. (For
+        * backwards compatibility, this can also be used to pass the client
+        * cert passphrase when setting up a vhost client SSL context, but it is
+        * preferred to use .client_ssl_private_key_password for that.) */
+       const char *ssl_cert_filepath;
+       /**< VHOST: If libwebsockets was compiled to use ssl, and you want
+        * to listen using SSL, set to the filepath to fetch the
+        * server cert from, otherwise NULL for unencrypted.  (For backwards
+        * compatibility, this can also be used to pass the client certificate
+        * when setting up a vhost client SSL context, but it is preferred to
+        * use .client_ssl_cert_filepath for that.)
+        *
+        * Notice you can alternatively set a single DER or PEM from a memory
+        * buffer as the vhost tls cert using \p server_ssl_cert_mem and
+        * \p server_ssl_cert_mem_len.
+        */
+       const char *ssl_private_key_filepath;
+       /**<  VHOST: filepath to private key if wanting SSL mode;
+        * if this is set to NULL but ssl_cert_filepath is set, the
+        * OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY callback is called
+        * to allow setting of the private key directly via openSSL
+        * library calls.   (For backwards compatibility, this can also be used
+        * to pass the client cert private key filepath when setting up a
+        * vhost client SSL context, but it is preferred to use
+        * .client_ssl_private_key_filepath for that.)
+        *
+        * Notice you can alternatively set a DER or PEM private key from a
+        * memory buffer as the vhost tls private key using
+        * \p server_ssl_private_key_mem and \p server_ssl_private_key_mem_len.
+        */
+       const char *ssl_ca_filepath;
+       /**< VHOST: CA certificate filepath or NULL.  (For backwards
+        * compatibility, this can also be used to pass the client CA
+        * filepath when setting up a vhost client SSL context,
+        * but it is preferred to use .client_ssl_ca_filepath for that.)
+        *
+        * Notice you can alternatively set a DER or PEM CA cert from a memory
+        * buffer using \p server_ssl_ca_mem and \p server_ssl_ca_mem_len.
+        */
+       const char *ssl_cipher_list;
+       /**< VHOST: List of valid ciphers to use ON TLS1.2 AND LOWER ONLY (eg,
+        * "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"
+        * or you can leave it as NULL to get "DEFAULT" (For backwards
+        * compatibility, this can also be used to pass the client cipher
+        * list when setting up a vhost client SSL context,
+        * but it is preferred to use .client_ssl_cipher_list for that.)
+        * SEE .tls1_3_plus_cipher_list and .client_tls_1_3_plus_cipher_list
+        * for the equivalent for tls1.3.
+        */
+       const char *http_proxy_address;
+       /**< VHOST: If non-NULL, attempts to proxy via the given address.
+        * If proxy auth is required, use format
+        * "username:password\@server:port" */
+       unsigned int http_proxy_port;
+       /**< VHOST: If http_proxy_address was non-NULL, uses this port */
+       int gid;
+       /**< CONTEXT: group id to change to after setting listen socket,
+        *   or -1. See also .username below. */
+       int uid;
+       /**< CONTEXT: user id to change to after setting listen socket,
+        *   or -1.  See also .groupname below. */
+       unsigned int options;
+       /**< VHOST + CONTEXT: 0, or LWS_SERVER_OPTION_... bitfields */
+       void *user;
+       /**< VHOST + CONTEXT: optional user pointer that will be associated
+        * with the context when creating the context (and can be retrieved by
+        * lws_context_user(context), or with the vhost when creating the vhost
+        * (and can be retrieved by lws_vhost_user(vhost)).  You will need to
+        * use LWS_SERVER_OPTION_EXPLICIT_VHOSTS and create the vhost separately
+        * if you care about giving the context and vhost different user pointer
+        * values.
+        */
+       int ka_time;
+       /**< CONTEXT: 0 for no TCP keepalive, otherwise apply this keepalive
+        * timeout to all libwebsocket sockets, client or server */
+       int ka_probes;
+       /**< CONTEXT: if ka_time was nonzero, after the timeout expires how many
+        * times to try to get a response from the peer before giving up
+        * and killing the connection */
+       int ka_interval;
+       /**< CONTEXT: if ka_time was nonzero, how long to wait before each ka_probes
+        * attempt */
+#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS)
+       SSL_CTX *provided_client_ssl_ctx;
+       /**< CONTEXT: If non-null, swap out libwebsockets ssl
+         * implementation for the one provided by provided_ssl_ctx.
+         * Libwebsockets no longer is responsible for freeing the context
+         * if this option is selected. */
+#else /* maintain structure layout either way */
+       void *provided_client_ssl_ctx; /**< dummy if ssl disabled */
+#endif
+
+       unsigned short max_http_header_data;
+       /**< CONTEXT: The max amount of header payload that can be handled
+        * in an http request (unrecognized header payload is dropped) */
+       unsigned short max_http_header_pool;
+       /**< CONTEXT: The max number of connections with http headers that
+        * can be processed simultaneously (the corresponding memory is
+        * allocated and deallocated dynamically as needed).  If the pool is
+        * fully busy new incoming connections must wait for accept until one
+        * becomes free. 0 = allow as many ah as number of availble fds for
+        * the process */
+
+       unsigned int count_threads;
+       /**< CONTEXT: how many contexts to create in an array, 0 = 1 */
+       unsigned int fd_limit_per_thread;
+       /**< CONTEXT: nonzero means restrict each service thread to this
+        * many fds, 0 means the default which is divide the process fd
+        * limit by the number of threads.
+        *
+        * Note if this is nonzero, and fd_limit_per_thread multiplied by the
+        * number of service threads is less than the process ulimit, then lws
+        * restricts internal lookup table allocation to the smaller size, and
+        * switches to a less efficient lookup scheme.  You should use this to
+        * trade off speed against memory usage if you know the lws context
+        * will only use a handful of fds.
+        *
+        * Bear in mind lws may use some fds internally, for example for the
+        * cancel pipe, so you may need to allow for some extras for normal
+        * operation.
+        */
+       unsigned int timeout_secs;
+       /**< VHOST: various processes involving network roundtrips in the
+        * library are protected from hanging forever by timeouts.  If
+        * nonzero, this member lets you set the timeout used in seconds.
+        * Otherwise a default timeout is used. */
+       const char *ecdh_curve;
+       /**< VHOST: if NULL, defaults to initializing server with
+        *   "prime256v1" */
+       const char *vhost_name;
+       /**< VHOST: name of vhost, must match external DNS name used to
+        * access the site, like "warmcat.com" as it's used to match
+        * Host: header and / or SNI name for SSL. */
+       const char * const *plugin_dirs;
+       /**< CONTEXT: NULL, or NULL-terminated array of directories to
+        * scan for lws protocol plugins at context creation time */
+       const struct lws_protocol_vhost_options *pvo;
+       /**< VHOST: pointer to optional linked list of per-vhost
+        * options made accessible to protocols */
+       int keepalive_timeout;
+       /**< VHOST: (default = 0 = 5s) seconds to allow remote
+        * client to hold on to an idle HTTP/1.1 connection */
+       const char *log_filepath;
+       /**< VHOST: filepath to append logs to... this is opened before
+        *              any dropping of initial privileges */
+       const struct lws_http_mount *mounts;
+       /**< VHOST: optional linked list of mounts for this vhost */
+       const char *server_string;
+       /**< CONTEXT: string used in HTTP headers to identify server
+ *             software, if NULL, "libwebsockets". */
+       unsigned int pt_serv_buf_size;
+       /**< CONTEXT: 0 = default of 4096.  This buffer is used by
+        * various service related features including file serving, it
+        * defines the max chunk of file that can be sent at once.
+        * At the risk of lws having to buffer failed large sends, it
+        * can be increased to, eg, 128KiB to improve throughput. */
+       unsigned int max_http_header_data2;
+       /**< CONTEXT: if max_http_header_data is 0 and this
+        * is nonzero, this will be used in place of the default.  It's
+        * like this for compatibility with the original short version,
+        * this is unsigned int length. */
+       long ssl_options_set;
+       /**< VHOST: Any bits set here will be set as server SSL options */
+       long ssl_options_clear;
+       /**< VHOST: Any bits set here will be cleared as server SSL options */
+       unsigned short ws_ping_pong_interval;
+       /**< CONTEXT: 0 for none, else interval in seconds between sending
+        * PINGs on idle websocket connections.  When the PING is sent,
+        * the PONG must come within the normal timeout_secs timeout period
+        * or the connection will be dropped.
+        * Any RX or TX traffic on the connection restarts the interval timer,
+        * so a connection which always sends or receives something at intervals
+        * less than the interval given here will never send PINGs / expect
+        * PONGs.  Conversely as soon as the ws connection is established, an
+        * idle connection will do the PING / PONG roundtrip as soon as
+        * ws_ping_pong_interval seconds has passed without traffic
+        */
+       const struct lws_protocol_vhost_options *headers;
+               /**< VHOST: pointer to optional linked list of per-vhost
+                * canned headers that are added to server responses */
+
+       const struct lws_protocol_vhost_options *reject_service_keywords;
+       /**< CONTEXT: Optional list of keywords and rejection codes + text.
+        *
+        * The keywords are checked for existing in the user agent string.
+        *
+        * Eg, "badrobot" "404 Not Found"
+        */
+       void *external_baggage_free_on_destroy;
+       /**< CONTEXT: NULL, or pointer to something externally malloc'd, that
+        * should be freed when the context is destroyed.  This allows you to
+        * automatically sync the freeing action to the context destruction
+        * action, so there is no need for an external free() if the context
+        * succeeded to create.
+        */
+
+       const char *client_ssl_private_key_password;
+       /**< VHOST: Client SSL context init: NULL or the passphrase needed
+        * for the private key */
+       const char *client_ssl_cert_filepath;
+       /**< VHOST: Client SSL context init: The certificate the client
+        * should present to the peer on connection */
+       const void *client_ssl_cert_mem;
+       /**< VHOST: Client SSL context init: client certificate memory buffer or
+        * NULL... use this to load client cert from memory instead of file */
+       unsigned int client_ssl_cert_mem_len;
+       /**< VHOST: Client SSL context init: length of client_ssl_cert_mem in
+        * bytes */
+       const char *client_ssl_private_key_filepath;
+       /**<  VHOST: Client SSL context init: filepath to client private key
+        * if this is set to NULL but client_ssl_cert_filepath is set, you
+        * can handle the LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
+        * callback of protocols[0] to allow setting of the private key directly
+        * via tls library calls */
+       const char *client_ssl_ca_filepath;
+       /**< VHOST: Client SSL context init: CA certificate filepath or NULL */
+       const void *client_ssl_ca_mem;
+       /**< VHOST: Client SSL context init: CA certificate memory buffer or
+        * NULL... use this to load CA cert from memory instead of file */
+       unsigned int client_ssl_ca_mem_len;
+       /**< VHOST: Client SSL context init: length of client_ssl_ca_mem in
+        * bytes */
+
+       const char *client_ssl_cipher_list;
+       /**< VHOST: Client SSL context init: List of valid ciphers to use (eg,
+       * "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"
+       * or you can leave it as NULL to get "DEFAULT" */
+
+       const struct lws_plat_file_ops *fops;
+       /**< CONTEXT: NULL, or pointer to an array of fops structs, terminated
+        * by a sentinel with NULL .open.
+        *
+        * If NULL, lws provides just the platform file operations struct for
+        * backwards compatibility.
+        */
+       int simultaneous_ssl_restriction;
+       /**< CONTEXT: 0 (no limit) or limit of simultaneous SSL sessions
+        * possible.*/
+       const char *socks_proxy_address;
+       /**< VHOST: If non-NULL, attempts to proxy via the given address.
+        * If proxy auth is required, use format
+        * "username:password\@server:port" */
+       unsigned int socks_proxy_port;
+       /**< VHOST: If socks_proxy_address was non-NULL, uses this port */
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+       cap_value_t caps[4];
+       /**< CONTEXT: array holding Linux capabilities you want to
+        * continue to be available to the server after it transitions
+        * to a noprivileged user.  Usually none are needed but for, eg,
+        * .bind_iface, CAP_NET_RAW is required.  This gives you a way
+        * to still have the capability but drop root.
+        */
+       char count_caps;
+       /**< CONTEXT: count of Linux capabilities in .caps[].  0 means
+        * no capabilities will be inherited from root (the default) */
+#endif
+       int bind_iface;
+       /**< VHOST: nonzero to strictly bind sockets to the interface name in
+        * .iface (eg, "eth2"), using SO_BIND_TO_DEVICE.
+        *
+        * Requires SO_BINDTODEVICE support from your OS and CAP_NET_RAW
+        * capability.
+        *
+        * Notice that common things like access network interface IP from
+        * your local machine use your lo / loopback interface and will be
+        * disallowed by this.
+        */
+       int ssl_info_event_mask;
+       /**< VHOST: mask of ssl events to be reported on LWS_CALLBACK_SSL_INFO
+        * callback for connections on this vhost.  The mask values are of
+        * the form SSL_CB_ALERT, defined in openssl/ssl.h.  The default of
+        * 0 means no info events will be reported.
+        */
+       unsigned int timeout_secs_ah_idle;
+       /**< VHOST: seconds to allow a client to hold an ah without using it.
+        * 0 defaults to 10s. */
+       unsigned short ip_limit_ah;
+       /**< CONTEXT: max number of ah a single IP may use simultaneously
+        *            0 is no limit. This is a soft limit: if the limit is
+        *            reached, connections from that IP will wait in the ah
+        *            waiting list and not be able to acquire an ah until
+        *            a connection belonging to the IP relinquishes one it
+        *            already has.
+        */
+       unsigned short ip_limit_wsi;
+       /**< 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 simultaneous wsi / accepted connections
+        *            given here.
+        */
+       uint32_t        http2_settings[7];
+       /**< 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.
+        */
+       const char *error_document_404;
+       /**< VHOST: If non-NULL, when asked to serve a non-existent file,
+        *          lws attempts to server this url path instead.  Eg,
+        *          "/404.html" */
+       const char *alpn;
+       /**< CONTEXT: If non-NULL, default list of advertised alpn, comma-
+        *            separated
+        *
+        *     VHOST: If non-NULL, per-vhost list of advertised alpn, comma-
+        *            separated
+        */
+       void **foreign_loops;
+       /**< CONTEXT: This is ignored if the context is not being started with
+        *              an event loop, ie, .options has a flag like
+        *              LWS_SERVER_OPTION_LIBUV.
+        *
+        *              NULL indicates lws should start its own even loop for
+        *              each service thread, and deal with closing the loops
+        *              when the context is destroyed.
+        *
+        *              Non-NULL means it points to an array of external
+        *              ("foreign") event loops that are to be used in turn for
+        *              each service thread.  In the default case of 1 service
+        *              thread, it can just point to one foreign event loop.
+        */
+       void (*signal_cb)(void *event_lib_handle, int signum);
+       /**< CONTEXT: NULL: default signal handling.  Otherwise this receives
+        *              the signal handler callback.  event_lib_handle is the
+        *              native event library signal handle, eg uv_signal_t *
+        *              for libuv.
+        */
+       struct lws_context **pcontext;
+       /**< CONTEXT: if non-NULL, at the end of context destroy processing,
+        * the pointer pointed to by pcontext is written with NULL.  You can
+        * use this to let foreign event loops know that lws context destruction
+        * is fully completed.
+        */
+       void (*finalize)(struct lws_vhost *vh, void *arg);
+       /**< VHOST: NULL, or pointer to function that will be called back
+        *          when the vhost is just about to be freed.  The arg parameter
+        *          will be set to whatever finalize_arg is below.
+        */
+       void *finalize_arg;
+       /**< VHOST: opaque pointer lws ignores but passes to the finalize
+        *          callback.  If you don't care, leave it NULL.
+        */
+       unsigned int max_http_header_pool2;
+       /**< CONTEXT: if max_http_header_pool is 0 and this
+        * is nonzero, this will be used in place of the default.  It's
+        * like this for compatibility with the original short version:
+        * this is unsigned int length. */
+
+       long ssl_client_options_set;
+       /**< VHOST: Any bits set here will be set as CLIENT SSL options */
+       long ssl_client_options_clear;
+       /**< VHOST: Any bits set here will be cleared as CLIENT SSL options */
+
+       const char *tls1_3_plus_cipher_list;
+       /**< VHOST: List of valid ciphers to use for incoming server connections
+        * ON TLS1.3 AND ABOVE (eg, "TLS_CHACHA20_POLY1305_SHA256" on this vhost
+        * or you can leave it as NULL to get "DEFAULT".
+        * SEE .client_tls_1_3_plus_cipher_list to do the same on the vhost
+        * client SSL_CTX.
+        */
+       const char *client_tls_1_3_plus_cipher_list;
+       /**< VHOST: List of valid ciphers to use for outgoing client connections
+        * ON TLS1.3 AND ABOVE on this vhost (eg,
+        * "TLS_CHACHA20_POLY1305_SHA256") or you can leave it as NULL to get
+        * "DEFAULT".
+        */
+       const char *listen_accept_role;
+       /**< VHOST: NULL for default, or force accepted incoming connections to
+        * bind to this role.  Uses the role names from their ops struct, eg,
+        * "raw-skt".
+        */
+       const char *listen_accept_protocol;
+       /**< VHOST: NULL for default, or force accepted incoming connections to
+        * bind to this vhost protocol name.
+        */
+       const struct lws_protocols **pprotocols;
+       /**< VHOST: NULL: use .protocols, otherwise ignore .protocols and use
+        * this array of pointers to protocols structs.  The end of the array
+        * is marked by a NULL pointer.
+        *
+        * This is preferred over .protocols, because it allows the protocol
+        * struct to be opaquely defined elsewhere, with just a pointer to it
+        * needed to create the context with it.  .protocols requires also
+        * the type of the user data to be known so its size can be given.
+        */
+
+       const void *server_ssl_cert_mem;
+       /**< VHOST: Alternative for \p ssl_cert_filepath that allows setting
+        * from memory instead of from a file.  At most one of
+        * \p ssl_cert_filepath or \p server_ssl_cert_mem should be non-NULL. */
+       unsigned int server_ssl_cert_mem_len;
+       /**< VHOST: Server SSL context init: length of server_ssl_cert_mem in
+        * bytes */
+       const void *server_ssl_private_key_mem;
+       /**<  VHOST: Alternative for \p ssl_private_key_filepath allowing
+        * init from a private key in memory instead of a file.  At most one
+        * of \p ssl_private_key_filepath or \p server_ssl_private_key_mem
+        * should be non-NULL. */
+       unsigned int server_ssl_private_key_mem_len;
+       /**< VHOST: length of \p server_ssl_private_key_mem in memory */
+       const void *server_ssl_ca_mem;
+       /**< VHOST: Alternative for \p ssl_ca_filepath allowing
+        * init from a CA cert in memory instead of a file.  At most one
+        * of \p ssl_ca_filepath or \p server_ssl_ca_mem should be non-NULL. */
+       unsigned int server_ssl_ca_mem_len;
+       /**< VHOST: length of \p server_ssl_ca_mem in memory */
+       const char *username; /**< CONTEXT: string username for post-init
+        * permissions.  Like .uid but takes a string username. */
+       const char *groupname; /**< CONTEXT: string groupname for post-init
+        * permissions.  Like .gid but takes a string groupname. */
+       const char *unix_socket_perms; /**< VHOST: if your vhost is listening
+        * on a unix socket, you can give a "username:groupname" string here
+        * to control the owner:group it's created with.  It's always created
+        * with 0660 mode. */
+       const lws_system_ops_t *system_ops;
+       /**< CONTEXT: hook up lws_system_ apis to system-specific
+        * implementations */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility
+        *
+        * The below is to ensure later library versions with new
+        * members added above will see 0 (default) even if the app
+        * was not built against the newer headers.
+        */
+
+       void *_unused[4]; /**< dummy */
+};
+
+/**
+ * lws_create_context() - Create the websocket handler
+ * \param info:        pointer to struct with parameters
+ *
+ *     This function creates the listening socket (if serving) and takes care
+ *     of all initialization in one step.
+ *
+ *     If option LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, no vhost is
+ *     created; you're expected to create your own vhosts afterwards using
+ *     lws_create_vhost().  Otherwise a vhost named "default" is also created
+ *     using the information in the vhost-related members, for compatibility.
+ *
+ *     After initialization, it returns a struct lws_context * that
+ *     represents this server.  After calling, user code needs to take care
+ *     of calling lws_service() with the context pointer to get the
+ *     server's sockets serviced.  This must be done in the same process
+ *     context as the initialization call.
+ *
+ *     The protocol callback functions are called for a handful of events
+ *     including http requests coming in, websocket connections becoming
+ *     established, and data arriving; it's also called periodically to allow
+ *     async transmission.
+ *
+ *     HTTP requests are sent always to the FIRST protocol in protocol, since
+ *     at that time websocket protocol has not been negotiated.  Other
+ *     protocols after the first one never see any HTTP callback activity.
+ *
+ *     The server created is a simple http server by default; part of the
+ *     websocket standard is upgrading this http connection to a websocket one.
+ *
+ *     This allows the same server to provide files like scripts and favicon /
+ *     images or whatever over http and dynamic data over websockets all in
+ *     one place; they're all handled in the user callback.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_context *
+lws_create_context(const struct lws_context_creation_info *info);
+
+
+/**
+ * lws_context_destroy() - Destroy the websocket context
+ * \param context:     Websocket context
+ *
+ *     This function closes any active connections and then frees the
+ *     context.  After calling this, any further use of the context is
+ *     undefined.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_context_destroy(struct lws_context *context);
+
+typedef int (*lws_reload_func)(void);
+
+/**
+ * lws_context_deprecate() - Deprecate the websocket context
+ *
+ * \param context:     Websocket context
+ * \param cb: Callback notified when old context listen sockets are closed
+ *
+ *     This function is used on an existing context before superceding it
+ *     with a new context.
+ *
+ *     It closes any listen sockets in the context, so new connections are
+ *     not possible.
+ *
+ *     And it marks the context to be deleted when the number of active
+ *     connections into it falls to zero.
+ *
+ *     This is aimed at allowing seamless configuration reloads.
+ *
+ *     The callback cb will be called after the listen sockets are actually
+ *     closed and may be reopened.  In the callback the new context should be
+ *     configured and created.  (With libuv, socket close happens async after
+ *     more loop events).
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_context_deprecate(struct lws_context *context, lws_reload_func cb);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_context_is_deprecated(struct lws_context *context);
+
+/**
+ * lws_set_proxy() - Setups proxy to lws_context.
+ * \param vhost:       pointer to struct lws_vhost you want set proxy for
+ * \param proxy: pointer to c string containing proxy in format address:port
+ *
+ * Returns 0 if proxy string was parsed and proxy was setup.
+ * Returns -1 if proxy is NULL or has incorrect format.
+ *
+ * This is only required if your OS does not provide the http_proxy
+ * environment variable (eg, OSX)
+ *
+ *   IMPORTANT! You should call this function right after creation of the
+ *   lws_context and before call to connect. If you call this
+ *   function after connect behavior is undefined.
+ *   This function will override proxy settings made on lws_context
+ *   creation with genenv() call.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_set_proxy(struct lws_vhost *vhost, const char *proxy);
+
+/**
+ * lws_set_socks() - Setup socks to lws_context.
+ * \param vhost:       pointer to struct lws_vhost you want set socks for
+ * \param socks: pointer to c string containing socks in format address:port
+ *
+ * Returns 0 if socks string was parsed and socks was setup.
+ * Returns -1 if socks is NULL or has incorrect format.
+ *
+ * This is only required if your OS does not provide the socks_proxy
+ * environment variable (eg, OSX)
+ *
+ *   IMPORTANT! You should call this function right after creation of the
+ *   lws_context and before call to connect. If you call this
+ *   function after connect behavior is undefined.
+ *   This function will override proxy settings made on lws_context
+ *   creation with genenv() call.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_set_socks(struct lws_vhost *vhost, const char *socks);
+
+struct lws_vhost;
+
+/**
+ * lws_create_vhost() - Create a vhost (virtual server context)
+ * \param context:     pointer to result of lws_create_context()
+ * \param info:                pointer to struct with parameters
+ *
+ * This function creates a virtual server (vhost) using the vhost-related
+ * members of the info struct.  You can create many vhosts inside one context
+ * if you created the context with the option LWS_SERVER_OPTION_EXPLICIT_VHOSTS
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_vhost *
+lws_create_vhost(struct lws_context *context,
+                const struct lws_context_creation_info *info);
+
+/**
+ * lws_vhost_destroy() - Destroy a vhost (virtual server context)
+ *
+ * \param vh:          pointer to result of lws_create_vhost()
+ *
+ * This function destroys a vhost.  Normally, if you just want to exit,
+ * then lws_destroy_context() will take care of everything.  If you want
+ * to destroy an individual vhost and all connections and allocations, you
+ * can do it with this.
+ *
+ * If the vhost has a listen sockets shared by other vhosts, it will be given
+ * to one of the vhosts sharing it rather than closed.
+ *
+ * The vhost close is staged according to the needs of the event loop, and if
+ * there are multiple service threads.  At the point the vhost itself if
+ * about to be freed, if you provided a finalize callback and optional arg at
+ * vhost creation time, it will be called just before the vhost is freed.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_vhost_destroy(struct lws_vhost *vh);
+
+/**
+ * lwsws_get_config_globals() - Parse a JSON server config file
+ * \param info:                pointer to struct with parameters
+ * \param d:           filepath of the config file
+ * \param config_strings: storage for the config strings extracted from JSON,
+ *                       the pointer is incremented as strings are stored
+ * \param len:         pointer to the remaining length left in config_strings
+ *                       the value is decremented as strings are stored
+ *
+ * This function prepares a n lws_context_creation_info struct with global
+ * settings from a file d.
+ *
+ * Requires CMake option LWS_WITH_LEJP_CONF to have been enabled
+ */
+LWS_VISIBLE LWS_EXTERN int
+lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
+                        char **config_strings, int *len);
+
+/**
+ * lwsws_get_config_vhosts() - Create vhosts from a JSON server config file
+ * \param context:     pointer to result of lws_create_context()
+ * \param info:                pointer to struct with parameters
+ * \param d:           filepath of the config file
+ * \param config_strings: storage for the config strings extracted from JSON,
+ *                       the pointer is incremented as strings are stored
+ * \param len:         pointer to the remaining length left in config_strings
+ *                       the value is decremented as strings are stored
+ *
+ * This function creates vhosts into a context according to the settings in
+ *JSON files found in directory d.
+ *
+ * Requires CMake option LWS_WITH_LEJP_CONF to have been enabled
+ */
+LWS_VISIBLE LWS_EXTERN int
+lwsws_get_config_vhosts(struct lws_context *context,
+                       struct lws_context_creation_info *info, const char *d,
+                       char **config_strings, int *len);
+
+/**
+ * lws_get_vhost() - return the vhost a wsi belongs to
+ *
+ * \param wsi: which connection
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_vhost *
+lws_get_vhost(struct lws *wsi);
+
+/**
+ * lws_get_vhost_name() - returns the name of a vhost
+ *
+ * \param vhost: which vhost
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_vhost_name(struct lws_vhost *vhost);
+
+/**
+ * lws_get_vhost_by_name() - returns the vhost with the requested name, or NULL
+ *
+ * \param name: vhost name we are looking for
+ *
+ * Returns NULL, or the vhost with the name \p name
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_vhost *
+lws_get_vhost_by_name(struct lws_context *context, const char *name);
+
+/**
+ * lws_get_vhost_port() - returns the port a vhost listens on, or -1
+ *
+ * \param vhost: which vhost
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_get_vhost_port(struct lws_vhost *vhost);
+
+/**
+ * lws_get_vhost_user() - returns the user pointer for the vhost
+ *
+ * \param vhost: which vhost
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lws_get_vhost_user(struct lws_vhost *vhost);
+
+/**
+ * lws_get_vhost_iface() - returns the binding for the vhost listen socket
+ *
+ * \param vhost: which vhost
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_vhost_iface(struct lws_vhost *vhost);
+
+/**
+ * lws_json_dump_vhost() - describe vhost state and stats in JSON
+ *
+ * \param vh: the vhost
+ * \param buf: buffer to fill with JSON
+ * \param len: max length of buf
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len);
+
+/**
+ * lws_json_dump_context() - describe context state and stats in JSON
+ *
+ * \param context: the context
+ * \param buf: buffer to fill with JSON
+ * \param len: max length of buf
+ * \param hide_vhosts: nonzero to not provide per-vhost mount etc information
+ *
+ * Generates a JSON description of vhost state into buf
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_json_dump_context(const struct lws_context *context, char *buf, int len,
+                     int hide_vhosts);
+
+/**
+ * lws_vhost_user() - get the user data associated with the vhost
+ * \param vhost: Websocket vhost
+ *
+ * This returns the optional user pointer that can be attached to
+ * a vhost when it was created.  Lws never dereferences this pointer, it only
+ * sets it when the vhost is created, and returns it using this api.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lws_vhost_user(struct lws_vhost *vhost);
+
+/**
+ * lws_context_user() - get the user data associated with the context
+ * \param context: Websocket context
+ *
+ * This returns the optional user allocation that can be attached to
+ * the context the sockets live in at context_create time.  It's a way
+ * to let all sockets serviced in the same context share data without
+ * using globals statics in the user code.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lws_context_user(struct lws_context *context);
+
+/*! \defgroup vhost-mounts Vhost mounts and options
+ * \ingroup context-and-vhost-creation
+ *
+ * ##Vhost mounts and options
+ */
+///@{
+/** struct lws_protocol_vhost_options - linked list of per-vhost protocol
+ *                                     name=value options
+ *
+ * This provides a general way to attach a linked-list of name=value pairs,
+ * which can also have an optional child link-list using the options member.
+ */
+struct lws_protocol_vhost_options {
+       const struct lws_protocol_vhost_options *next; /**< linked list */
+       const struct lws_protocol_vhost_options *options; /**< child linked-list of more options for this node */
+       const char *name; /**< name of name=value pair */
+       const char *value; /**< value of name=value pair */
+};
+
+/** enum lws_mount_protocols
+ * This specifies the mount protocol for a mountpoint, whether it is to be
+ * served from a filesystem, or it is a cgi etc.
+ */
+enum lws_mount_protocols {
+       LWSMPRO_HTTP            = 0, /**< http reverse proxy */
+       LWSMPRO_HTTPS           = 1, /**< https reverse proxy */
+       LWSMPRO_FILE            = 2, /**< serve from filesystem directory */
+       LWSMPRO_CGI             = 3, /**< pass to CGI to handle */
+       LWSMPRO_REDIR_HTTP      = 4, /**< redirect to http:// url */
+       LWSMPRO_REDIR_HTTPS     = 5, /**< redirect to https:// url */
+       LWSMPRO_CALLBACK        = 6, /**< hand by named protocol's callback */
+};
+
+/** struct lws_http_mount
+ *
+ * arguments for mounting something in a vhost's url namespace
+ */
+struct lws_http_mount {
+       const struct lws_http_mount *mount_next;
+       /**< pointer to next struct lws_http_mount */
+       const char *mountpoint;
+       /**< mountpoint in http pathspace, eg, "/" */
+       const char *origin;
+       /**< path to be mounted, eg, "/var/www/warmcat.com" */
+       const char *def;
+       /**< default target, eg, "index.html" */
+       const char *protocol;
+       /**<"protocol-name" to handle mount */
+
+       const struct lws_protocol_vhost_options *cgienv;
+       /**< optional linked-list of cgi options.  These are created
+        * as environment variables for the cgi process
+        */
+       const struct lws_protocol_vhost_options *extra_mimetypes;
+       /**< optional linked-list of mimetype mappings */
+       const struct lws_protocol_vhost_options *interpret;
+       /**< optional linked-list of files to be interpreted */
+
+       int cgi_timeout;
+       /**< seconds cgi is allowed to live, if cgi://mount type */
+       int cache_max_age;
+       /**< max-age for reuse of client cache of files, seconds */
+       unsigned int auth_mask;
+       /**< bits set here must be set for authorized client session */
+
+       unsigned int cache_reusable:1; /**< set if client cache may reuse this */
+       unsigned int cache_revalidate:1; /**< set if client cache should revalidate on use */
+       unsigned int cache_intermediaries:1; /**< set if intermediaries are allowed to cache */
+
+       unsigned char origin_protocol; /**< one of enum lws_mount_protocols */
+       unsigned char mountpoint_len; /**< length of mountpoint string */
+
+       const char *basic_auth_login_file;
+       /**<NULL, or filepath to use to check basic auth logins against */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility
+        *
+        * The below is to ensure later library versions with new
+        * members added above will see 0 (default) even if the app
+        * was not built against the newer headers.
+        */
+
+       void *_unused[2]; /**< dummy */
+};
+
+///@}
+///@}
diff --git a/include/libwebsockets/lws-dbus.h b/include/libwebsockets/lws-dbus.h
new file mode 100644 (file)
index 0000000..20de5a2
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * must be included manually as
+ *
+ *  #include <libwebsockets/lws-dbus.h>
+ *
+ * if dbus apis needed
+ */
+
+#if !defined(__LWS_DBUS_H__)
+#define __LWS_DBUS_H__
+
+#include <dbus/dbus.h>
+
+/* helper type to simplify implementing methods as individual functions */
+typedef DBusHandlerResult (*lws_dbus_message_handler)(DBusConnection *conn,
+                          DBusMessage *message, DBusMessage **reply, void *d);
+
+struct lws_dbus_ctx;
+typedef void (*lws_dbus_closing_t)(struct lws_dbus_ctx *ctx);
+
+struct lws_dbus_ctx {
+       struct lws_dll2_owner owner; /* dbusserver ctx: HEAD of accepted list */
+       struct lws_dll2 next; /* dbusserver ctx: HEAD of accepted list */
+       struct lws_vhost *vh; /* the vhost we logically bind to in lws */
+       int tsi;        /* the lws thread service index (0 if only one service
+                          thread as is the default */
+       DBusConnection *conn;
+       DBusServer *dbs;
+       DBusWatch *w[4];
+       DBusPendingCall *pc;
+
+       char hup;
+       char timeouts;
+
+       /* cb_closing callback will be called after the connection and this
+        * related ctx struct have effectively gone out of scope.
+        *
+        * The callback should close and clean up the connection and free the
+        * ctx.
+        */
+       lws_dbus_closing_t cb_closing;
+};
+
+/**
+ * lws_dbus_connection_setup() - bind dbus connection object to lws event loop
+ *
+ * \param ctx: additional information about the connection
+ * \param conn: the DBusConnection object to bind
+ *
+ * This configures a DBusConnection object to use lws for watchers and timeout
+ * operations.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_dbus_connection_setup(struct lws_dbus_ctx *ctx, DBusConnection *conn,
+                         lws_dbus_closing_t cb_closing);
+
+/**
+ * lws_dbus_server_listen() - bind dbus connection object to lws event loop
+ *
+ * \param ctx: additional information about the connection
+ * \param ads: the DBUS address to listen on, eg, "unix:abstract=mysocket"
+ * \param err: a DBusError object to take any extra error information
+ * \param new_conn: a callback function to prepare new accepted connections
+ *
+ * This creates a DBusServer and binds it to the lws event loop, and your
+ * callback to accept new connections.
+ */
+LWS_VISIBLE LWS_EXTERN DBusServer *
+lws_dbus_server_listen(struct lws_dbus_ctx *ctx, const char *ads,
+                      DBusError *err, DBusNewConnectionFunction new_conn);
+
+#endif
diff --git a/include/libwebsockets/lws-diskcache.h b/include/libwebsockets/lws-diskcache.h
new file mode 100644 (file)
index 0000000..eb63fdd
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * libwebsockets - disk cache helpers
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup diskcache LWS disk cache
+ * ## Disk cache API
+ *
+ * Lws provides helper apis useful if you need a disk cache containing hashed
+ * files and need to delete files from it on an LRU basis to keep it below some
+ * size limit.
+ *
+ * The API `lws_diskcache_prepare()` deals with creating the cache dir and
+ * 256 subdirs, which are used according to the first two chars of the hex
+ * hash of the cache file.
+ *
+ * `lws_diskcache_create()` and `lws_diskcache_destroy()` allocate and free
+ * an opaque struct that represents the disk cache.
+ *
+ * `lws_diskcache_trim()` should be called at eg, 1s intervals to perform the
+ * cache dir monitoring and LRU autodelete in the background lazily.  It can
+ * be done in its own thread or on a timer... it monitors the directories in a
+ * stateful way that stats one or more file in the cache per call, and keeps
+ * a list of the oldest files as it goes.  When it completes a scan, if the
+ * aggregate size is over the limit, it will delete oldest files first to try
+ * to keep it under the limit.
+ *
+ * The cache size monitoring is extremely efficient in time and memory even when
+ * the cache directory becomes huge.
+ *
+ * `lws_diskcache_query()` is used to determine if the file already exists in
+ * the cache, or if it must be created.  If it must be created, then the file
+ * is opened using a temp name that must be converted to a findable name with
+ * `lws_diskcache_finalize_name()` when the generation of the file contents are
+ * complete.  Aborted cached files that did not complete generation will be
+ * flushed by the LRU eventually.  If the file already exists, it is 'touched'
+ * to make it new again and the fd returned.
+ *
+ */
+///@{
+
+struct lws_diskcache_scan;
+
+/**
+ * lws_diskcache_create() - creates an opaque struct representing the disk cache
+ *
+ * \param cache_dir_base: The cache dir path, eg `/var/cache/mycache`
+ * \param cache_size_limit: maximum size on disk the cache is allowed to use
+ *
+ * This returns an opaque `struct lws_diskcache_scan *` which represents the
+ * disk cache, the trim scanning state and so on.  You should use
+ * `lws_diskcache_destroy()` to free it to destroy it.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_diskcache_scan *
+lws_diskcache_create(const char *cache_dir_base, uint64_t cache_size_limit);
+
+/**
+ * lws_diskcache_destroy() - destroys the pointer returned by ...create()
+ *
+ * \param lds: pointer to the pointer returned by lws_diskcache_create()
+ *
+ * Frees *lds and any allocations it did, and then sets *lds to NULL and
+ * returns.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_diskcache_destroy(struct lws_diskcache_scan **lds);
+
+/**
+ * lws_diskcache_prepare() - ensures the cache dir structure exists on disk
+ *
+ * \param cache_base_dir: The cache dir path, eg `/var/cache/mycache`
+ * \param mode: octal dir mode to enforce, like 0700
+ * \param uid: uid the cache dir should belong to
+ *
+ * This should be called while your app is still privileged.  It will create
+ * the cache directory structure on disk as necessary, enforce the given access
+ * mode on it and set the given uid as the owner.  It won't make any trouble
+ * if the cache already exists.
+ *
+ * Typically the mode is 0700 and the owner is the user that your application
+ * will transition to use when it drops root privileges.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_diskcache_prepare(const char *cache_base_dir, int mode, int uid);
+
+#define LWS_DISKCACHE_QUERY_NO_CACHE   0
+#define LWS_DISKCACHE_QUERY_EXISTS     1
+#define LWS_DISKCACHE_QUERY_CREATING   2
+#define LWS_DISKCACHE_QUERY_ONGOING    3 /* something else is creating it */
+
+/**
+ * lws_diskcache_query() - ensures the cache dir structure exists on disk
+ *
+ * \param lds: The opaque struct representing the disk cache
+ * \param is_bot: nonzero means the request is from a bot.  Don't create new cache contents if so.
+ * \param hash_hex: hex string representation of the cache object hash
+ * \param _fd: pointer to the fd to be set
+ * \param cache: destination string to take the cache filepath
+ * \param cache_len: length of the buffer at `cache`
+ * \param extant_cache_len: pointer to a size_t to take any extant cached file size
+ *
+ * This function is called when you want to find if the hashed name already
+ * exists in the cache.  The possibilities for the return value are
+ *
+ *  - LWS_DISKCACHE_QUERY_NO_CACHE: It's not in the cache and you can't create
+ *    it in the cache for whatever reason.
+ *  - LWS_DISKCACHE_QUERY_EXISTS: It exists in the cache.  It's open RDONLY and
+ *    *_fd has been set to the file descriptor.  *extant_cache_len has been set
+ *    to the size of the cached file in bytes.  cache has been set to the
+ *    full filepath of the cached file.  Closing _fd is your responsibility.
+ *  - LWS_DISKCACHE_QUERY_CREATING: It didn't exist, but a temp file has been
+ *    created in the cache and *_fd set to a file descriptor opened on it RDWR.
+ *    You should create the contents, and call `lws_diskcache_finalize_name()`
+ *    when it is done.  Closing _fd is your responsibility.
+ *  - LWS_DISKCACHE_QUERY_ONGOING: not returned by this api, but you may find it
+ *    desirable to make a wrapper function which can handle another asynchronous
+ *    process that is already creating the cached file.  This can be used to
+ *    indicate that situation externally... how to determine the same thing is
+ *    already being generated is out of scope of this api.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_diskcache_query(struct lws_diskcache_scan *lds, int is_bot,
+                   const char *hash_hex, int *_fd, char *cache, int cache_len,
+                   size_t *extant_cache_len);
+
+/**
+ * lws_diskcache_query() - ensures the cache dir structure exists on disk
+ *
+ * \param cache: The cache file temp name returned with LWS_DISKCACHE_QUERY_CREATING
+ *
+ * This renames the cache file you are creating to its final name.  It should
+ * be called on the temp name returned by `lws_diskcache_query()` if it gave a
+ * LWS_DISKCACHE_QUERY_CREATING return, after you have filled the cache file and
+ * closed it.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_diskcache_finalize_name(char *cache);
+
+/**
+ * lws_diskcache_trim() - performs one or more file checks in the cache for size management
+ *
+ * \param lds: The opaque object representing the cache
+ *
+ * This should be called periodically to statefully walk the cache on disk
+ * collecting the oldest files.  When it has visited every file, if the cache
+ * is oversize it will delete the oldest files until it's back under size again.
+ *
+ * Each time it's called, it will look at one or more dir in the cache.  If
+ * called when the cache is oversize, it increases the amount of work done each
+ * call until it is reduced again.  Typically it will take 256 calls before it
+ * deletes anything, so if called once per second, it will delete files once
+ * every 4 minutes.  Each call is very inexpensive both in memory and time.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_diskcache_trim(struct lws_diskcache_scan *lds);
+
+
+/**
+ * lws_diskcache_secs_to_idle() - see how long to idle before calling trim
+ *
+ * \param lds: The opaque object representing the cache
+ *
+ * If the cache is undersize, there's no need to monitor it immediately.  This
+ * suggests how long to "sleep" before calling `lws_diskcache_trim()` again.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_diskcache_secs_to_idle(struct lws_diskcache_scan *lds);
diff --git a/include/libwebsockets/lws-dsh.h b/include/libwebsockets/lws-dsh.h
new file mode 100644 (file)
index 0000000..342dfe7
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*
+ * lws_dsh (Disordered Shared Heap) is an opaque abstraction supporting a single
+ * linear buffer (overallocated at end of the lws_dsh_t) which may contain
+ * multiple kinds of packets that are retired out of order, and tracked by kind.
+ *
+ * Each kind of packet has an lws_dll2 list of its kind of packets and acts as
+ * a FIFO; packets of a particular type are always retired in order.  But there
+ * is no requirement about the order types are retired matching the original
+ * order they arrived.
+ * 
+ * Gaps are tracked as just another kind of "packet" list.
+ *
+ * "allocations" (including gaps) are prepended by an lws_dsh_object_t.
+ *
+ * dsh may themselves be on an lws_dll2_owner list, and under memory pressure
+ * allocate into other buffers on the list.
+ *
+ * All management structures exist inside the allocated buffer.
+ */
+
+typedef struct lws_dsh lws_dsh_t;
+
+/**
+ * lws_dsh_create() - Allocate a DSH buffer
+ *
+ * \param owner: the owning list this dsh belongs on, or NULL if standalone
+ * \param buffer_size: the allocation in bytes
+ * \param count_kinds: how many separately-tracked fifos use the buffer
+ *
+ * This makes a single heap allocation that includes internal tracking objects
+ * in the buffer.  Sub-allocated objects are bound to a "kind" index and
+ * managed via a FIFO for each kind.
+ *
+ * Every "kind" of allocation shares the same buffer space.
+ *
+ * Multiple buffers may be bound together in an lws_dll2 list, and if an
+ * allocation cannot be satisfied by the local buffer, space can be borrowed
+ * from other dsh in the same list (the local dsh FIFO tracks these "foreign"
+ * allocations as if they were local).
+ *
+ * Returns an opaque pointer to the dsh, or NULL if allocation failed.
+ */
+LWS_VISIBLE LWS_EXTERN lws_dsh_t *
+lws_dsh_create(lws_dll2_owner_t *owner, size_t buffer_size, int count_kinds);
+
+/**
+ * lws_dsh_destroy() - Destroy a DSH buffer
+ *
+ * \param pdsh: pointer to the dsh pointer
+ *
+ * Deallocates the DSH and sets *pdsh to NULL.
+ *
+ * Before destruction, any foreign buffer usage on the part of this dsh are
+ * individually freed.  All dsh on the same list are walked and checked if they
+ * have their own foreign allocations on the dsh buffer being destroyed.  If so,
+ * it attempts to migrate the allocation to a dsh that is not currently being
+ * destroyed.  If all else fails (basically the buffer memory is being shrunk)
+ * unmigratable objects are cleanly destroyed.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_dsh_destroy(lws_dsh_t **pdsh);
+
+/**
+ * lws_dsh_alloc_tail() - make a suballocation inside a dsh
+ *
+ * \param dsh: the dsh tracking the allocation
+ * \param kind: the kind of allocation
+ * \param src1: the first source data to copy
+ * \param size1: the size of the first source data
+ * \param src2: the second source data to copy (after the first), or NULL
+ * \param size2: the size of the second source data
+ *
+ * Allocates size1 + size2 bytes in a dsh (it prefers the given dsh but will
+ * borrow space from other dsh on the same list if necessary) and copies size1
+ * bytes into it from src1, followed by size2 bytes from src2 if src2 isn't
+ * NULL.  The actual suballocation is a bit larger because of alignment and a
+ * prepended management header.
+ *
+ * The suballocation is added to the kind-specific FIFO at the tail.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_dsh_alloc_tail(lws_dsh_t *dsh, int kind, const void *src1, size_t size1,
+                   const void *src2, size_t size2);
+
+/**
+ * lws_dsh_free() - free a suballocation from the dsh
+ *
+ * \param obj: a pointer to a void * that pointed to the allocated payload
+ *
+ * This returns the space used by \p obj in the dsh buffer to the free list
+ * of the dsh the allocation came from.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_dsh_free(void **obj);
+
+/**
+ * lws_dsh_get_head() - free a suballocation from the dsh
+ *
+ * \param dsh: the dsh tracking the allocation
+ * \param kind: the kind of allocation
+ * \param obj: pointer to a void * to be set to the payload
+ * \param size: set to the size of the allocation
+ *
+ * This gets the "next" object in the kind FIFO for the dsh, and returns 0 if
+ * any.  If none, returns nonzero.
+ *
+ * This is nondestructive of the fifo or the payload.  Use lws_dsh_free on
+ * obj to remove the entry from the kind fifo and return the payload to the
+ * free list.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_dsh_get_head(lws_dsh_t *dsh, int kind, void **obj, size_t *size);
+
+/**
+ * lws_dsh_describe() - DEBUG BUILDS ONLY dump the dsh to the logs
+ *
+ * \param dsh: the dsh to dump
+ * \param desc: text that appears at the top of the dump
+ *
+ * Useful information for debugging lws_dsh
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_dsh_describe(lws_dsh_t *dsh, const char *desc);
diff --git a/include/libwebsockets/lws-esp32.h b/include/libwebsockets/lws-esp32.h
new file mode 100644 (file)
index 0000000..0350586
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+typedef int lws_sockfd_type;
+typedef int lws_filefd_type;
+
+/*
+ * Later lwip (at least 2.1.12) already defines these in its own headers
+ * protected by the same test as used here... if POLLIN / POLLOUT already exist
+ * then assume no need to declare those and struct pollfd.
+ *
+ * Older lwip needs these declarations done here.
+ */
+
+#if !defined(POLLIN) && !defined(POLLOUT)
+
+struct pollfd {
+       lws_sockfd_type fd; /**< fd related to */
+       short events; /**< which POLL... events to respond to */
+       short revents; /**< which POLL... events occurred */
+};
+#define POLLIN         0x0001
+#define POLLPRI                0x0002
+#define POLLOUT                0x0004
+#define POLLERR                0x0008
+#define POLLHUP                0x0010
+#define POLLNVAL       0x0020
+
+#endif
+
+#if defined(LWS_AMAZON_RTOS)
+#include <FreeRTOS.h>
+#include <event_groups.h>
+#include <string.h>
+#include "timers.h"
+#else /* LWS_AMAZON_RTOS */
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <string.h>
+#include "esp_wifi.h"
+#include "esp_system.h"
+#include "esp_event.h"
+#include "esp_event_loop.h"
+#include "nvs.h"
+#include "driver/gpio.h"
+#include "esp_spi_flash.h"
+#include "freertos/timers.h"
+#endif /* LWS_AMAZON_RTOS */
+
+#if !defined(CONFIG_FREERTOS_HZ)
+#define CONFIG_FREERTOS_HZ 100
+#endif
+
+typedef TimerHandle_t uv_timer_t;
+typedef void uv_cb_t(uv_timer_t *);
+typedef void * uv_handle_t;
+
+struct timer_mapping {
+       uv_cb_t *cb;
+       uv_timer_t *t;
+};
+
+#define UV_VERSION_MAJOR 1
+
+#define lws_uv_getloop(a, b) (NULL)
+
+static LWS_INLINE void uv_timer_init(void *l, uv_timer_t *t)
+{
+       (void)l;
+       *t = NULL;
+}
+
+extern void esp32_uvtimer_cb(TimerHandle_t t);
+
+static LWS_INLINE void uv_timer_start(uv_timer_t *t, uv_cb_t *cb, int first, int rep)
+{
+       struct timer_mapping *tm = (struct timer_mapping *)malloc(sizeof(*tm));
+
+       if (!tm)
+               return;
+
+       tm->t = t;
+       tm->cb = cb;
+
+       *t = xTimerCreate("x", pdMS_TO_TICKS(first), !!rep, tm,
+                         (TimerCallbackFunction_t)esp32_uvtimer_cb);
+       xTimerStart(*t, 0);
+}
+
+static LWS_INLINE void uv_timer_stop(uv_timer_t *t)
+{
+       xTimerStop(*t, 0);
+}
+
+static LWS_INLINE void uv_close(uv_handle_t *h, void *v)
+{
+       free(pvTimerGetTimerID((uv_timer_t)h));
+       xTimerDelete(*(uv_timer_t *)h, 0);
+}
+
+
+#if !defined(LWS_AMAZON_RTOS)
+
+/* ESP32 helper declarations */
+
+#include <mdns.h>
+#include <esp_partition.h>
+
+#define LWS_PLUGIN_STATIC
+#define LWS_MAGIC_REBOOT_TYPE_ADS 0x50001ffc
+#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY 0xb00bcafe
+#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY 0xfaceb00b
+#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON 0xf0cedfac
+#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY_ERASE_OTA 0xfac0eeee
+
+/* user code provides these */
+
+extern void
+lws_esp32_identify_physical_device(void);
+
+/* lws-plat-esp32 provides these */
+
+typedef void (*lws_cb_scan_done)(uint16_t count, wifi_ap_record_t *recs, void *arg);
+
+enum genled_state {
+       LWSESP32_GENLED__INIT,
+       LWSESP32_GENLED__LOST_NETWORK,
+       LWSESP32_GENLED__NO_NETWORK,
+       LWSESP32_GENLED__CONN_AP,
+       LWSESP32_GENLED__GOT_IP,
+       LWSESP32_GENLED__OK,
+};
+
+struct lws_group_member {
+       struct lws_group_member *next;
+       uint64_t last_seen;
+       char model[16];
+       char role[16];
+       char host[32];
+       char mac[20];
+       int width, height;
+       struct ip4_addr addr;
+       struct ip6_addr addrv6;
+       uint8_t flags;
+};
+
+#define LWS_SYSTEM_GROUP_MEMBER_ADD            1
+#define LWS_SYSTEM_GROUP_MEMBER_CHANGE         2
+#define LWS_SYSTEM_GROUP_MEMBER_REMOVE         3
+
+#define LWS_GROUP_FLAG_SELF 1
+
+struct lws_esp32 {
+       char sta_ip[16];
+       char sta_mask[16];
+       char sta_gw[16];
+       char serial[16];
+       char opts[16];
+       char model[16];
+       char group[16];
+       char role[16];
+       char ssid[4][64];
+       char password[4][64];
+       char active_ssid[64];
+       char access_pw[16];
+       char hostname[32];
+       char mac[20];
+       char le_dns[64];
+       char le_email[64];
+               char region;
+               char inet;
+       char conn_ap;
+
+       enum genled_state genled;
+       uint64_t genled_t;
+
+       lws_cb_scan_done scan_consumer;
+       void *scan_consumer_arg;
+       struct lws_group_member *first;
+       int extant_group_members;
+
+       char acme;
+       char upload;
+
+       volatile char button_is_down;
+};
+
+struct lws_esp32_image {
+       uint32_t romfs;
+       uint32_t romfs_len;
+       uint32_t json;
+       uint32_t json_len;
+};
+
+extern struct lws_esp32 lws_esp32;
+struct lws_vhost;
+
+extern esp_err_t
+lws_esp32_event_passthru(void *ctx, system_event_t *event);
+extern void
+lws_esp32_wlan_config(void);
+extern void
+lws_esp32_wlan_start_ap(void);
+extern void
+lws_esp32_wlan_start_station(void);
+struct lws_context_creation_info;
+extern void
+lws_esp32_set_creation_defaults(struct lws_context_creation_info *info);
+extern struct lws_context *
+lws_esp32_init(struct lws_context_creation_info *, struct lws_vhost **pvh);
+extern int
+lws_esp32_wlan_nvs_get(int retry);
+extern esp_err_t
+lws_nvs_set_str(nvs_handle handle, const char* key, const char* value);
+extern void
+lws_esp32_restart_guided(uint32_t type);
+extern const esp_partition_t *
+lws_esp_ota_get_boot_partition(void);
+extern int
+lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i, char *json, int json_len);
+extern int
+lws_esp32_leds_network_indication(void);
+
+extern uint32_t lws_esp32_get_reboot_type(void);
+extern uint16_t lws_esp32_sine_interp(int n);
+
+/* required in external code by esp32 plat (may just return if no leds) */
+extern void lws_esp32_leds_timer_cb(TimerHandle_t th);
+
+#endif /* LWS_AMAZON_RTOS */
diff --git a/include/libwebsockets/lws-fts.h b/include/libwebsockets/lws-fts.h
new file mode 100644 (file)
index 0000000..29405bd
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * libwebsockets - fulltext search
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup search Search
+ *
+ * ##Full-text search
+ *
+ * Lws provides superfast indexing and fulltext searching from index files on
+ * storage.
+ */
+///@{
+
+struct lws_fts;
+struct lws_fts_file;
+
+/*
+ * Queries produce their results in an lwsac, using these public API types.
+ * The first thing in the lwsac is always a struct lws_fts_result (see below)
+ * containing heads for linked-lists of the other result types.
+ */
+
+/* one filepath's results */
+
+struct lws_fts_result_filepath {
+       struct lws_fts_result_filepath *next;
+       int matches;    /* logical number of matches */
+       int matches_length;     /* bytes in length table (may be zero) */
+       int lines_in_file;
+       int filepath_length;
+
+       /* - uint32_t line table follows (first for alignment) */
+       /* - filepath (of filepath_length) follows */
+};
+
+/* autocomplete result */
+
+struct lws_fts_result_autocomplete {
+       struct lws_fts_result_autocomplete *next;
+       int instances;
+       int agg_instances;
+       int ac_length;
+       char elided; /* children skipped in interest of antecedent children */
+       char has_children;
+
+       /* - autocomplete suggestion (of length ac_length) follows */
+};
+
+/*
+ * The results lwsac always starts with this.  If no results and / or no
+ * autocomplete the members may be NULL.  This implies the symbol nor any
+ * suffix on it exists in the trie file.
+ */
+struct lws_fts_result {
+       struct lws_fts_result_filepath *filepath_head;
+       struct lws_fts_result_autocomplete *autocomplete_head;
+       int duration_ms;
+       int effective_flags; /* the search flags that were used */
+};
+
+/*
+ * index creation functions
+ */
+
+/**
+ * lws_fts_create() - Create a new index file
+ *
+ * \param fd: The fd opened for write
+ *
+ * Inits a new index file, returning a struct lws_fts to represent it
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_fts *
+lws_fts_create(int fd);
+
+/**
+ * lws_fts_destroy() - Finalize a new index file / destroy the trie lwsac
+ *
+ * \param trie: The previously opened index being finalized
+ *
+ * Finalizes an index file that was being created, and frees the memory involved
+ * *trie is set to NULL afterwards.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_fts_destroy(struct lws_fts **trie);
+
+/**
+ * lws_fts_file_index() - Create a new entry in the trie file for an input path
+ *
+ * \param t: The previously opened index being written
+ * \param filepath: The filepath (which may be virtual) associated with this file
+ * \param filepath_len: The number of chars in the filepath
+ * \param priority: not used yet
+ *
+ * Returns an ordinal that represents this new filepath in the index file.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_fts_file_index(struct lws_fts *t, const char *filepath, int filepath_len,
+                  int priority);
+
+/**
+ * lws_fts_fill() - Process all or a bufferload of input file
+ *
+ * \param t: The previously opened index being written
+ * \param file_index: The ordinal representing this input filepath
+ * \param buf: A bufferload of data from the input file
+ * \param len: The number of bytes in buf
+ *
+ * Indexes a buffer of data from the input file.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_fts_fill(struct lws_fts *t, uint32_t file_index, const char *buf,
+            size_t len);
+
+/**
+ * lws_fts_serialize() - Store the in-memory trie into the index file
+ *
+ * \param t: The previously opened index being written
+ *
+ * The trie is held in memory where it can be added to... after all the input
+ * filepaths and data have been processed, this is called to serialize /
+ * write the trie data into the index file.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_fts_serialize(struct lws_fts *t);
+
+/*
+ * index search functions
+ */
+
+/**
+ * lws_fts_open() - Open an existing index file to search it
+ *
+ * \param filepath: The filepath to the index file to open
+ *
+ * Opening the index file returns an opaque struct lws_fts_file * that is
+ * used to perform other operations on it, or NULL if it can't be opened.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_fts_file *
+lws_fts_open(const char *filepath);
+
+#define LWSFTS_F_QUERY_AUTOCOMPLETE    (1 << 0)
+#define LWSFTS_F_QUERY_FILES           (1 << 1)
+#define LWSFTS_F_QUERY_FILE_LINES      (1 << 2)
+#define LWSFTS_F_QUERY_QUOTE_LINE      (1 << 3)
+
+struct lws_fts_search_params {
+       /* the actual search term */
+       const char *needle;
+        /* if non-NULL, FILE results for this filepath only */
+       const char *only_filepath;
+       /* will be set to the results lwsac */
+       struct lwsac *results_head;
+       /* combination of LWSFTS_F_QUERY_* flags */
+       int flags;
+       /* maximum number of autocomplete suggestions to return */
+       int max_autocomplete;
+       /* maximum number of filepaths to return */
+       int max_files;
+       /* maximum number of line number results to return per filepath */
+       int max_lines;
+};
+
+/**
+ * lws_fts_search() - Perform a search operation on an index
+ *
+ * \param jtf: The index file struct returned by lws_fts_open
+ * \param ftsp: The struct lws_fts_search_params filled in by the caller
+ *
+ * The caller should memset the ftsp struct to 0 to ensure members that may be
+ * introduced in later versions contain known values, then set the related
+ * members to describe the kind of search action required.
+ *
+ * ftsp->results_head is the results lwsac, or NULL.  It should be freed with
+ * lwsac_free() when the results are finished with.
+ *
+ * Returns a pointer into the results lwsac that is a struct lws_fts_result
+ * containing the head pointers into linked-lists of results for autocomplete
+ * and filepath data, along with some sundry information.  This does not need
+ * to be freed since freeing the lwsac will also remove this and everything it
+ * points to.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_fts_result *
+lws_fts_search(struct lws_fts_file *jtf, struct lws_fts_search_params *ftsp);
+
+/**
+ * lws_fts_close() - Close a previously-opened index file
+ *
+ * \param jtf: The pointer returned from the open
+ *
+ * Closes the file handle on the index and frees any allocations
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_fts_close(struct lws_fts_file *jtf);
+
+///@}
diff --git a/include/libwebsockets/lws-genaes.h b/include/libwebsockets/lws-genaes.h
new file mode 100644 (file)
index 0000000..eaf6c6c
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup generic AES
+ * ## Generic AES related functions
+ *
+ * Lws provides generic AES functions that abstract the ones
+ * provided by whatever tls library you are linking against.
+ *
+ * It lets you use the same code if you build against mbedtls or OpenSSL
+ * for example.
+ */
+///@{
+
+#if defined(LWS_WITH_MBEDTLS)
+#include <mbedtls/aes.h>
+#include <mbedtls/gcm.h>
+#endif
+
+enum enum_aes_modes {
+       LWS_GAESM_CBC,
+       LWS_GAESM_CFB128,
+       LWS_GAESM_CFB8,
+       LWS_GAESM_CTR,
+       LWS_GAESM_ECB,
+       LWS_GAESM_OFB,
+       LWS_GAESM_XTS,          /* care... requires double-length key */
+       LWS_GAESM_GCM,
+       LWS_GAESM_KW,
+};
+
+enum enum_aes_operation {
+       LWS_GAESO_ENC,
+       LWS_GAESO_DEC
+};
+
+enum enum_aes_padding {
+       LWS_GAESP_NO_PADDING,
+       LWS_GAESP_WITH_PADDING
+};
+
+/* include/libwebsockets/lws-jwk.h must be included before this */
+
+#define LWS_AES_BLOCKSIZE 128
+
+struct lws_genaes_ctx {
+#if defined(LWS_WITH_MBEDTLS)
+       union {
+               mbedtls_aes_context ctx;
+#if defined(MBEDTLS_CIPHER_MODE_XTS)
+               mbedtls_aes_xts_context ctx_xts;
+#endif
+               mbedtls_gcm_context ctx_gcm;
+       } u;
+#else
+       EVP_CIPHER_CTX *ctx;
+       const EVP_CIPHER *cipher;
+       ENGINE *engine;
+       char init;
+#endif
+       unsigned char tag[16];
+       struct lws_gencrypto_keyelem *k;
+       enum enum_aes_operation op;
+       enum enum_aes_modes mode;
+       enum enum_aes_padding padding;
+       int taglen;
+       char underway;
+};
+
+/** lws_genaes_create() - Create RSA public decrypt context
+ *
+ * \param ctx: your struct lws_genaes_ctx
+ * \param op: LWS_GAESO_ENC or LWS_GAESO_DEC
+ * \param mode: one of LWS_GAESM_
+ * \param el: struct prepared with key element data
+ * \param padding: 0 = no padding, 1 = padding
+ * \param engine: if openssl engine used, pass the pointer here
+ *
+ * Creates an RSA context with a public key associated with it, formed from
+ * the key elements in \p el.
+ *
+ * Returns 0 for OK or nonzero for error.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op,
+                 enum enum_aes_modes mode, struct lws_gencrypto_keyelem *el,
+                 enum enum_aes_padding padding, void *engine);
+
+/** lws_genaes_destroy() - Destroy genaes AES context
+ *
+ * \param ctx: your struct lws_genaes_ctx
+ * \param tag: NULL, or, GCM-only: buffer to receive tag
+ * \param tlen: 0, or, GCM-only: length of tag buffer
+ *
+ * Destroys any allocations related to \p ctx.
+ *
+ * For GCM only, up to tlen bytes of tag buffer will be set on exit.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genaes_destroy(struct lws_genaes_ctx *ctx, unsigned char *tag, size_t tlen);
+
+/** lws_genaes_crypt() - Encrypt or decrypt
+ *
+ * \param ctx: your struct lws_genaes_ctx
+ * \param in: input plaintext or ciphertext
+ * \param len: length of input (which is always length of output)
+ * \param out: output plaintext or ciphertext
+ * \param op: LWS_GAESO_ENC or LWS_GAESO_DEC
+ * \param iv_or_nonce_ctr_or_data_unit_16: NULL, iv, nonce_ctr16, or data_unit16
+ * \param stream_block_16: pointer to 16-byte stream block for CTR mode only
+ * \param nc_or_iv_off: NULL or pointer to nc, or iv_off
+ * \param taglen: length of tag
+ *
+ * Encrypts or decrypts using the AES mode set when the ctx was created.
+ * The last three arguments have different meanings depending on the mode:
+ *
+ *                           KW   CBC  CFB128 CFB8 CTR    ECB  OFB    XTS
+ * iv_or_nonce_ct.._unit_16 : iv   iv   iv     iv   nonce  NULL iv     dataunt
+ * stream_block_16         : NULL NULL NULL   NULL stream NULL NULL   NULL
+ * nc_or_iv_off                    : NULL NULL iv_off NULL nc_off NULL iv_off NULL
+ *
+ * For GCM:
+ *
+ * iv_or_nonce_ctr_or_data_unit_16 : iv
+ * stream_block_16                : pointer to tag
+ * nc_or_iv_off                           : set pointed-to size_t to iv length
+ * in                             : first call: additional data, subsequently
+ *                                :   input data
+ * len                            : first call: add data length, subsequently
+ *                                :   input / output length
+ *
+ * The length of the optional arg is always 16 if used, regardless of the mode.
+ *
+ * Returns 0 for OK or nonzero for error.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len,
+                uint8_t *out,
+                uint8_t *iv_or_nonce_ctr_or_data_unit_16,
+                uint8_t *stream_block_16,
+                size_t *nc_or_iv_off, int taglen);
+
+///@}
diff --git a/include/libwebsockets/lws-gencrypto.h b/include/libwebsockets/lws-gencrypto.h
new file mode 100644 (file)
index 0000000..d82fb60
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*
+ * These are gencrypto-level constants... they are used by both JOSE and direct
+ * gencrypto code.  However while JWK relies on these, using gencrypto apis has
+ * no dependency at all on any JOSE type.
+ */
+
+enum lws_gencrypto_kty {
+       LWS_GENCRYPTO_KTY_UNKNOWN,
+
+       LWS_GENCRYPTO_KTY_OCT,
+       LWS_GENCRYPTO_KTY_RSA,
+       LWS_GENCRYPTO_KTY_EC
+};
+
+/*
+ * Keytypes where the same element name is reused must all agree to put the
+ * same-named element at the same e[] index.  It's because when used with jwk,
+ * we parse and store in incoming key data, but we may not be informed of the
+ * definitive keytype until the end.
+ */
+
+enum lws_gencrypto_oct_tok {
+       LWS_GENCRYPTO_OCT_KEYEL_K, /* note... same offset as AES K */
+
+       LWS_GENCRYPTO_OCT_KEYEL_COUNT
+};
+
+enum lws_gencrypto_rsa_tok {
+       LWS_GENCRYPTO_RSA_KEYEL_E,
+       LWS_GENCRYPTO_RSA_KEYEL_N,
+       LWS_GENCRYPTO_RSA_KEYEL_D, /* note... same offset as EC D */
+       LWS_GENCRYPTO_RSA_KEYEL_P,
+       LWS_GENCRYPTO_RSA_KEYEL_Q,
+       LWS_GENCRYPTO_RSA_KEYEL_DP,
+       LWS_GENCRYPTO_RSA_KEYEL_DQ,
+       LWS_GENCRYPTO_RSA_KEYEL_QI,
+
+       LWS_GENCRYPTO_RSA_KEYEL_COUNT
+};
+
+enum lws_gencrypto_ec_tok {
+       LWS_GENCRYPTO_EC_KEYEL_CRV,
+       LWS_GENCRYPTO_EC_KEYEL_X,
+       /* note... same offset as RSA D */
+       LWS_GENCRYPTO_EC_KEYEL_D = LWS_GENCRYPTO_RSA_KEYEL_D,
+       LWS_GENCRYPTO_EC_KEYEL_Y,
+
+       LWS_GENCRYPTO_EC_KEYEL_COUNT
+};
+
+enum lws_gencrypto_aes_tok {
+       /* note... same offset as OCT K */
+       LWS_GENCRYPTO_AES_KEYEL_K = LWS_GENCRYPTO_OCT_KEYEL_K,
+
+       LWS_GENCRYPTO_AES_KEYEL_COUNT
+};
+
+/* largest number of key elements for any algorithm */
+#define LWS_GENCRYPTO_MAX_KEYEL_COUNT LWS_GENCRYPTO_RSA_KEYEL_COUNT
+
+/* this "stretchy" type holds individual key element data in binary form.
+ * It's typcially used in an array with the layout mapping the element index to
+ * the key element meaning defined by the enums above.  An array of these of
+ * length LWS_GENCRYPTO_MAX_KEYEL_COUNT can define key elements for any key
+ * type.
+ */
+
+struct lws_gencrypto_keyelem {
+       uint8_t *buf;
+       uint32_t len;
+};
+
+
+/**
+ * lws_gencrypto_bits_to_bytes() - returns rounded up bytes needed for bits
+ *
+ * \param bits
+ *
+ * Returns the number of bytes needed to store the given number of bits.  If
+ * a byte is partially used, the byte count is rounded up.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_gencrypto_bits_to_bytes(int bits);
+
+/**
+ * lws_base64_size() - returns estimated size of base64 encoding
+ *
+ * \param bytes
+ *
+ * Returns a slightly oversize estimate of the size of a base64 encoded version
+ * of the given amount of unencoded data.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_base64_size(int bytes);
diff --git a/include/libwebsockets/lws-genec.h b/include/libwebsockets/lws-genec.h
new file mode 100644 (file)
index 0000000..7db796e
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * libwebsockets - Generic Elliptic Curve Encryption
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+enum enum_genec_alg {
+       LEGENEC_UNKNOWN,
+
+       LEGENEC_ECDH,
+       LEGENEC_ECDSA
+};
+
+struct lws_genec_ctx {
+#if defined(LWS_WITH_MBEDTLS)
+       union {
+               mbedtls_ecdh_context *ctx_ecdh;
+               mbedtls_ecdsa_context *ctx_ecdsa;
+       } u;
+#else
+       EVP_PKEY_CTX *ctx[2];
+#endif
+       struct lws_context *context;
+       const struct lws_ec_curves *curve_table;
+       enum enum_genec_alg genec_alg;
+
+       char has_private;
+};
+
+#if defined(LWS_WITH_MBEDTLS)
+enum enum_lws_dh_side {
+       LDHS_OURS = MBEDTLS_ECDH_OURS,
+       LDHS_THEIRS = MBEDTLS_ECDH_THEIRS
+};
+#else
+enum enum_lws_dh_side {
+       LDHS_OURS,
+       LDHS_THEIRS
+};
+#endif
+
+struct lws_ec_curves {
+       const char *name;
+       int tls_lib_nid;
+       uint16_t key_bytes;
+};
+
+
+/* ECDH-specific apis */
+
+/** lws_genecdh_create() - Create a genecdh
+ *
+ * \param ctx: your genec context
+ * \param context: your lws_context (for RNG access)
+ * \param curve_table: NULL, enabling P-256, P-384 and P-521, or a replacement
+ *                    struct lws_ec_curves array, terminated by an entry with
+ *                    .name = NULL, of curves you want to whitelist
+ *
+ * Initializes a genecdh
+ */
+LWS_VISIBLE int
+lws_genecdh_create(struct lws_genec_ctx *ctx, struct lws_context *context,
+                  const struct lws_ec_curves *curve_table);
+
+/** lws_genecdh_set_key() - Apply an EC key to our or theirs side
+ *
+ * \param ctx: your genecdh context
+ * \param el: your key elements
+ * \param side: LDHS_OURS or LDHS_THEIRS
+ *
+ * Applies an EC key to one side or the other of an ECDH ctx
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdh_set_key(struct lws_genec_ctx *ctx, struct lws_gencrypto_keyelem *el,
+                   enum enum_lws_dh_side side);
+
+/** lws_genecdh_new_keypair() - Create a genec with a new public / private key
+ *
+ * \param ctx: your genec context
+ * \param side: LDHS_OURS or LDHS_THEIRS
+ * \param curve_name: an EC curve name, like "P-256"
+ * \param el: array pf LWS_GENCRYPTO_EC_KEYEL_COUNT key elems to take the new key
+ *
+ * Creates a genecdh with a newly minted EC public / private key
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdh_new_keypair(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side,
+                       const char *curve_name, struct lws_gencrypto_keyelem *el);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdh_compute_shared_secret(struct lws_genec_ctx *ctx, uint8_t *ss,
+                 int *ss_len);
+
+
+/* ECDSA-specific apis */
+
+/** lws_genecdsa_create() - Create a genecdsa and
+ *
+ * \param ctx: your genec context
+ * \param context: your lws_context (for RNG access)
+ * \param curve_table: NULL, enabling P-256, P-384 and P-521, or a replacement
+ *                    struct lws_ec_curves array, terminated by an entry with
+ *                    .name = NULL, of curves you want to whitelist
+ *
+ * Initializes a genecdh
+ */
+LWS_VISIBLE int
+lws_genecdsa_create(struct lws_genec_ctx *ctx, struct lws_context *context,
+                   const struct lws_ec_curves *curve_table);
+
+/** lws_genecdsa_new_keypair() - Create a genecdsa with a new public / private key
+ *
+ * \param ctx: your genec context
+ * \param curve_name: an EC curve name, like "P-256"
+ * \param el: array pf LWS_GENCRYPTO_EC_KEYEL_COUNT key elements to take the new key
+ *
+ * Creates a genecdsa with a newly minted EC public / private key
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_new_keypair(struct lws_genec_ctx *ctx, const char *curve_name,
+                        struct lws_gencrypto_keyelem *el);
+
+/** lws_genecdsa_set_key() - Apply an EC key to an ecdsa context
+ *
+ * \param ctx: your genecdsa context
+ * \param el: your key elements
+ *
+ * Applies an EC key to an ecdsa context
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_set_key(struct lws_genec_ctx *ctx,
+                    struct lws_gencrypto_keyelem *el);
+
+/** lws_genecdsa_hash_sig_verify_jws() - Verifies a JWS ECDSA signature on a given hash
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: unencrypted payload (usually a recomputed hash)
+ * \param hash_type: one of LWS_GENHASH_TYPE_
+ * \param keybits: number of bits in the crypto key
+ * \param sig: pointer to the signature we received with the payload
+ * \param sig_len: length of the signature we are checking in bytes
+ *
+ * This just looks at the signed hash... that's why there's no input length
+ * parameter, it's decided by the choice of hash.  It's up to you to confirm
+ * separately the actual payload matches the hash that was confirmed by this to
+ * be validly signed.
+ *
+ * Returns <0 for error, or 0 if signature matches the hash + key..
+ *
+ * The JWS ECDSA signature verification algorithm differs to generic ECDSA
+ * signatures and they're not interoperable.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sig_verify_jws(struct lws_genec_ctx *ctx, const uint8_t *in,
+                                enum lws_genhash_types hash_type, int keybits,
+                                const uint8_t *sig, size_t sig_len);
+
+/** lws_genecdsa_hash_sign_jws() - Creates a JWS ECDSA signature for a hash you provide
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: precomputed hash
+ * \param hash_type: one of LWS_GENHASH_TYPE_
+ * \param keybits: number of bits in the crypto key
+ * \param sig: pointer to buffer to take signature
+ * \param sig_len: length of the buffer (must be >= length of key N)
+ *
+ * Returns <0 for error, or 0 for success.
+ *
+ * This creates a JWS ECDSA signature for a hash you already computed and provide.
+ *
+ * The JWS ECDSA signature generation algorithm differs to generic ECDSA
+ * signatures and they're not interoperable.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in,
+                          enum lws_genhash_types hash_type, int keybits,
+                          uint8_t *sig, size_t sig_len);
+
+
+/* Apis that apply to both ECDH and ECDSA */
+
+LWS_VISIBLE LWS_EXTERN void
+lws_genec_destroy(struct lws_genec_ctx *ctx);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_genec_destroy_elements(struct lws_gencrypto_keyelem *el);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_genec_dump(struct lws_gencrypto_keyelem *el);
diff --git a/include/libwebsockets/lws-genhash.h b/include/libwebsockets/lws-genhash.h
new file mode 100644 (file)
index 0000000..ef05603
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup generic hash
+ * ## Generic Hash related functions
+ *
+ * Lws provides generic hash / digest accessors that abstract the ones
+ * provided by whatever tls library you are linking against.
+ *
+ * It lets you use the same code if you build against mbedtls or OpenSSL
+ * for example.
+ */
+///@{
+
+enum lws_genhash_types {
+       LWS_GENHASH_TYPE_UNKNOWN,
+       LWS_GENHASH_TYPE_MD5,
+       LWS_GENHASH_TYPE_SHA1,
+       LWS_GENHASH_TYPE_SHA256,
+       LWS_GENHASH_TYPE_SHA384,
+       LWS_GENHASH_TYPE_SHA512,
+};
+
+enum lws_genhmac_types {
+       LWS_GENHMAC_TYPE_UNKNOWN,
+       LWS_GENHMAC_TYPE_SHA256,
+       LWS_GENHMAC_TYPE_SHA384,
+       LWS_GENHMAC_TYPE_SHA512,
+};
+
+#define LWS_GENHASH_LARGEST 64
+
+struct lws_genhash_ctx {
+        uint8_t type;
+#if defined(LWS_WITH_MBEDTLS)
+        union {
+               mbedtls_md5_context md5;
+               mbedtls_sha1_context sha1;
+               mbedtls_sha256_context sha256;
+               mbedtls_sha512_context sha512; /* 384 also uses this */
+               const mbedtls_md_info_t *hmac;
+        } u;
+#else
+        const EVP_MD *evp_type;
+        EVP_MD_CTX *mdctx;
+#endif
+};
+
+struct lws_genhmac_ctx {
+        uint8_t type;
+#if defined(LWS_WITH_MBEDTLS)
+       const mbedtls_md_info_t *hmac;
+       mbedtls_md_context_t ctx;
+#else
+       const EVP_MD *evp_type;
+#if defined(LWS_HAVE_HMAC_CTX_new)
+        HMAC_CTX *ctx;
+#else
+        HMAC_CTX ctx;
+#endif
+#endif
+};
+
+/** lws_genhash_size() - get hash size in bytes
+ *
+ * \param type:        one of LWS_GENHASH_TYPE_...
+ *
+ * Returns number of bytes in this type of hash
+ */
+LWS_VISIBLE LWS_EXTERN size_t LWS_WARN_UNUSED_RESULT
+lws_genhash_size(enum lws_genhash_types type);
+
+/** lws_genhmac_size() - get hash size in bytes
+ *
+ * \param type:        one of LWS_GENHASH_TYPE_...
+ *
+ * Returns number of bytes in this type of hmac
+ */
+LWS_VISIBLE LWS_EXTERN size_t LWS_WARN_UNUSED_RESULT
+lws_genhmac_size(enum lws_genhmac_types type);
+
+/** lws_genhash_init() - prepare your struct lws_genhash_ctx for use
+ *
+ * \param ctx: your struct lws_genhash_ctx
+ * \param type:        one of LWS_GENHASH_TYPE_...
+ *
+ * Initializes the hash context for the type you requested
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type);
+
+/** lws_genhash_update() - digest len bytes of the buffer starting at in
+ *
+ * \param ctx: your struct lws_genhash_ctx
+ * \param in: start of the bytes to digest
+ * \param len: count of bytes to digest
+ *
+ * Updates the state of your hash context to reflect digesting len bytes from in
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len);
+
+/** lws_genhash_destroy() - copy out the result digest and destroy the ctx
+ *
+ * \param ctx: your struct lws_genhash_ctx
+ * \param result: NULL, or where to copy the result hash
+ *
+ * Finalizes the hash and copies out the digest.  Destroys any allocations such
+ * that ctx can safely go out of scope after calling this.
+ *
+ * NULL result is supported so that you can destroy the ctx cleanly on error
+ * conditions, where there is no valid result.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result);
+
+/** lws_genhmac_init() - prepare your struct lws_genhmac_ctx for use
+ *
+ * \param ctx: your struct lws_genhmac_ctx
+ * \param type:        one of LWS_GENHMAC_TYPE_...
+ * \param key: pointer to the start of the HMAC key
+ * \param key_len: length of the HMAC key
+ *
+ * Initializes the hash context for the type you requested
+ *
+ * If the return is nonzero, it failed and there is nothing needing to be
+ * destroyed.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type,
+                const uint8_t *key, size_t key_len);
+
+/** lws_genhmac_update() - digest len bytes of the buffer starting at in
+ *
+ * \param ctx: your struct lws_genhmac_ctx
+ * \param in: start of the bytes to digest
+ * \param len: count of bytes to digest
+ *
+ * Updates the state of your hash context to reflect digesting len bytes from in
+ *
+ * If the return is nonzero, it failed and needs destroying.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len);
+
+/** lws_genhmac_destroy() - copy out the result digest and destroy the ctx
+ *
+ * \param ctx: your struct lws_genhmac_ctx
+ * \param result: NULL, or where to copy the result hash
+ *
+ * Finalizes the hash and copies out the digest.  Destroys any allocations such
+ * that ctx can safely go out of scope after calling this.
+ *
+ * NULL result is supported so that you can destroy the ctx cleanly on error
+ * conditions, where there is no valid result.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result);
+///@}
diff --git a/include/libwebsockets/lws-genrsa.h b/include/libwebsockets/lws-genrsa.h
new file mode 100644 (file)
index 0000000..2434393
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup generic RSA
+ * ## Generic RSA related functions
+ *
+ * Lws provides generic RSA functions that abstract the ones
+ * provided by whatever OpenSSL library you are linking against.
+ *
+ * It lets you use the same code if you build against mbedtls or OpenSSL
+ * for example.
+ */
+///@{
+
+/* include/libwebsockets/lws-jwk.h must be included before this */
+
+enum enum_genrsa_mode {
+       LGRSAM_PKCS1_1_5,
+       LGRSAM_PKCS1_OAEP_PSS,
+
+       LGRSAM_COUNT
+};
+
+struct lws_genrsa_ctx {
+#if defined(LWS_WITH_MBEDTLS)
+       mbedtls_rsa_context *ctx;
+#else
+       BIGNUM *bn[LWS_GENCRYPTO_RSA_KEYEL_COUNT];
+       EVP_PKEY_CTX *ctx;
+       RSA *rsa;
+#endif
+       struct lws_context *context;
+       enum enum_genrsa_mode mode;
+};
+
+/** lws_genrsa_public_decrypt_create() - Create RSA public decrypt context
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param el: struct prepared with key element data
+ * \param context: lws_context for RNG
+ * \param mode: RSA mode, one of LGRSAM_ constants
+ * \param oaep_hashid: the lws genhash id for the hash used in MFG1 hash
+ *                     used in OAEP mode - normally, SHA1
+ *
+ * Creates an RSA context with a public key associated with it, formed from
+ * the key elements in \p el.
+ *
+ * Mode LGRSAM_PKCS1_1_5 is in widespread use but has weaknesses.  It's
+ * recommended to use LGRSAM_PKCS1_OAEP_PSS for new implementations.
+ *
+ * Returns 0 for OK or nonzero for error.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_gencrypto_keyelem *el,
+                 struct lws_context *context, enum enum_genrsa_mode mode,
+                 enum lws_genhash_types oaep_hashid);
+
+/** lws_genrsa_destroy_elements() - Free allocations in genrsa_elements
+ *
+ * \param el: your struct lws_gencrypto_keyelem
+ *
+ * This is a helper for user code making use of struct lws_gencrypto_keyelem
+ * where the elements are allocated on the heap, it frees any non-NULL
+ * buf element and sets the buf to NULL.
+ *
+ * NB: lws_genrsa_public_... apis do not need this as they take care of the key
+ * creation and destruction themselves.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_genrsa_destroy_elements(struct lws_gencrypto_keyelem *el);
+
+/** lws_genrsa_new_keypair() - Create new RSA keypair
+ *
+ * \param context: your struct lws_context (may be used for RNG)
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param mode: RSA mode, one of LGRSAM_ constants
+ * \param el: struct to get the new key element data allocated into it
+ * \param bits: key size, eg, 4096
+ *
+ * Creates a new RSA context and generates a new keypair into it, with \p bits
+ * bits.
+ *
+ * Returns 0 for OK or nonzero for error.
+ *
+ * Mode LGRSAM_PKCS1_1_5 is in widespread use but has weaknesses.  It's
+ * recommended to use LGRSAM_PKCS1_OAEP_PSS for new implementations.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx,
+                      enum enum_genrsa_mode mode, struct lws_gencrypto_keyelem *el,
+                      int bits);
+
+/** lws_genrsa_public_encrypt() - Perform RSA public key encryption
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: plaintext input
+ * \param in_len: length of plaintext input
+ * \param out: encrypted output
+ *
+ * Performs PKCS1 v1.5 Encryption
+ *
+ * Returns <0 for error, or length of decrypted data.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                         size_t in_len, uint8_t *out);
+
+/** lws_genrsa_private_encrypt() - Perform RSA private key encryption
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: plaintext input
+ * \param in_len: length of plaintext input
+ * \param out: encrypted output
+ *
+ * Performs PKCS1 v1.5 Encryption
+ *
+ * Returns <0 for error, or length of decrypted data.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_private_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          size_t in_len, uint8_t *out);
+
+/** lws_genrsa_public_decrypt() - Perform RSA public key decryption
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: encrypted input
+ * \param in_len: length of encrypted input
+ * \param out: decrypted output
+ * \param out_max: size of output buffer
+ *
+ * Performs PKCS1 v1.5 Decryption
+ *
+ * Returns <0 for error, or length of decrypted data.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                         size_t in_len, uint8_t *out, size_t out_max);
+
+/** lws_genrsa_private_decrypt() - Perform RSA private key decryption
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: encrypted input
+ * \param in_len: length of encrypted input
+ * \param out: decrypted output
+ * \param out_max: size of output buffer
+ *
+ * Performs PKCS1 v1.5 Decryption
+ *
+ * Returns <0 for error, or length of decrypted data.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_private_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          size_t in_len, uint8_t *out, size_t out_max);
+
+/** lws_genrsa_hash_sig_verify() - Verifies RSA signature on a given hash
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: input to be hashed
+ * \param hash_type: one of LWS_GENHASH_TYPE_
+ * \param sig: pointer to the signature we received with the payload
+ * \param sig_len: length of the signature we are checking in bytes
+ *
+ * Returns <0 for error, or 0 if signature matches the payload + key.
+ *
+ * This just looks at a hash... that's why there's no input length
+ * parameter, it's decided by the choice of hash.   It's up to you to confirm
+ * separately the actual payload matches the hash that was confirmed by this to
+ * be validly signed.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_hash_sig_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          enum lws_genhash_types hash_type,
+                          const uint8_t *sig, size_t sig_len);
+
+/** lws_genrsa_hash_sign() - Creates an ECDSA signature for a hash you provide
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param in: input to be hashed and signed
+ * \param hash_type: one of LWS_GENHASH_TYPE_
+ * \param sig: pointer to buffer to take signature
+ * \param sig_len: length of the buffer (must be >= length of key N)
+ *
+ * Returns <0 for error, or 0 for success.
+ *
+ * This creates an RSA signature for a hash you already computed and provide.
+ * You should have created the hash before calling this by iterating over the
+ * actual payload you need to confirm.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_hash_sign(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                    enum lws_genhash_types hash_type,
+                    uint8_t *sig, size_t sig_len);
+
+/** lws_genrsa_public_decrypt_destroy() - Destroy RSA public decrypt context
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ *
+ * Destroys any allocations related to \p ctx.
+ *
+ * This and related APIs operate identically with OpenSSL or mbedTLS backends.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_genrsa_destroy(struct lws_genrsa_ctx *ctx);
+
+/** lws_genrsa_render_pkey_asn1() - Exports public or private key to ASN1/DER
+ *
+ * \param ctx: your struct lws_genrsa_ctx
+ * \param _private: 0 = public part only, 1 = all parts of the key
+ * \param pkey_asn1: pointer to buffer to take the ASN1
+ * \param pkey_asn1_len: max size of the pkey_asn1_len
+ *
+ * Returns length of pkey_asn1 written, or -1 for error.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private,
+                           uint8_t *pkey_asn1, size_t pkey_asn1_len);
+///@}
diff --git a/include/libwebsockets/lws-http.h b/include/libwebsockets/lws-http.h
new file mode 100644 (file)
index 0000000..913e04c
--- /dev/null
@@ -0,0 +1,776 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/* minimal space for typical headers and CSP stuff */
+
+#define LWS_RECOMMENDED_MIN_HEADER_SPACE 2048
+
+/*! \defgroup http HTTP
+
+    Modules related to handling HTTP
+*/
+//@{
+
+/*! \defgroup httpft HTTP File transfer
+ * \ingroup http
+
+    APIs for sending local files in response to HTTP requests
+*/
+//@{
+
+/**
+ * lws_get_mimetype() - Determine mimetype to use from filename
+ *
+ * \param file:                filename
+ * \param m:           NULL, or mount context
+ *
+ * This uses a canned list of known filetypes first, if no match and m is
+ * non-NULL, then tries a list of per-mount file suffix to mimtype mappings.
+ *
+ * Returns either NULL or a pointer to the mimetype matching the file.
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_mimetype(const char *file, const struct lws_http_mount *m);
+
+/**
+ * lws_serve_http_file() - Send a file back to the client using http
+ * \param wsi:         Websocket instance (available from user callback)
+ * \param file:                The file to issue over http
+ * \param content_type:        The http content type, eg, text/html
+ * \param other_headers:       NULL or pointer to header string
+ * \param other_headers_len:   length of the other headers if non-NULL
+ *
+ *     This function is intended to be called from the callback in response
+ *     to http requests from the client.  It allows the callback to issue
+ *     local files down the http link in a single step.
+ *
+ *     Returning <0 indicates error and the wsi should be closed.  Returning
+ *     >0 indicates the file was completely sent and
+ *     lws_http_transaction_completed() called on the wsi (and close if != 0)
+ *     ==0 indicates the file transfer is started and needs more service later,
+ *     the wsi should be left alone.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
+                   const char *other_headers, int other_headers_len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_serve_http_file_fragment(struct lws *wsi);
+//@}
+
+
+enum http_status {
+       HTTP_STATUS_CONTINUE                                    = 100,
+
+       HTTP_STATUS_OK                                          = 200,
+       HTTP_STATUS_NO_CONTENT                                  = 204,
+       HTTP_STATUS_PARTIAL_CONTENT                             = 206,
+
+       HTTP_STATUS_MOVED_PERMANENTLY                           = 301,
+       HTTP_STATUS_FOUND                                       = 302,
+       HTTP_STATUS_SEE_OTHER                                   = 303,
+       HTTP_STATUS_NOT_MODIFIED                                = 304,
+
+       HTTP_STATUS_BAD_REQUEST                                 = 400,
+       HTTP_STATUS_UNAUTHORIZED,
+       HTTP_STATUS_PAYMENT_REQUIRED,
+       HTTP_STATUS_FORBIDDEN,
+       HTTP_STATUS_NOT_FOUND,
+       HTTP_STATUS_METHOD_NOT_ALLOWED,
+       HTTP_STATUS_NOT_ACCEPTABLE,
+       HTTP_STATUS_PROXY_AUTH_REQUIRED,
+       HTTP_STATUS_REQUEST_TIMEOUT,
+       HTTP_STATUS_CONFLICT,
+       HTTP_STATUS_GONE,
+       HTTP_STATUS_LENGTH_REQUIRED,
+       HTTP_STATUS_PRECONDITION_FAILED,
+       HTTP_STATUS_REQ_ENTITY_TOO_LARGE,
+       HTTP_STATUS_REQ_URI_TOO_LONG,
+       HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
+       HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE,
+       HTTP_STATUS_EXPECTATION_FAILED,
+
+       HTTP_STATUS_INTERNAL_SERVER_ERROR                       = 500,
+       HTTP_STATUS_NOT_IMPLEMENTED,
+       HTTP_STATUS_BAD_GATEWAY,
+       HTTP_STATUS_SERVICE_UNAVAILABLE,
+       HTTP_STATUS_GATEWAY_TIMEOUT,
+       HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
+};
+/*! \defgroup html-chunked-substitution HTML Chunked Substitution
+ * \ingroup http
+ *
+ * ##HTML chunked Substitution
+ *
+ * APIs for receiving chunks of text, replacing a set of variable names via
+ * a callback, and then prepending and appending HTML chunked encoding
+ * headers.
+ */
+//@{
+
+struct lws_process_html_args {
+       char *p; /**< pointer to the buffer containing the data */
+       int len; /**< length of the original data at p */
+       int max_len; /**< maximum length we can grow the data to */
+       int final; /**< set if this is the last chunk of the file */
+       int chunked; /**< 0 == unchunked, 1 == produce chunk headers
+                       (incompatible with HTTP/2) */
+};
+
+typedef const char *(*lws_process_html_state_cb)(void *data, int index);
+
+struct lws_process_html_state {
+       char *start; /**< pointer to start of match */
+       char swallow[16]; /**< matched character buffer */
+       int pos; /**< position in match */
+       void *data; /**< opaque pointer */
+       const char * const *vars; /**< list of variable names */
+       int count_vars; /**< count of variable names */
+
+       lws_process_html_state_cb replace;
+               /**< called on match to perform substitution */
+};
+
+/*! lws_chunked_html_process() - generic chunked substitution
+ * \param args: buffer to process using chunked encoding
+ * \param s: current processing state
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_chunked_html_process(struct lws_process_html_args *args,
+                        struct lws_process_html_state *s);
+//@}
+
+/** \defgroup HTTP-headers-read HTTP headers: read
+ * \ingroup http
+ *
+ * ##HTTP header releated functions
+ *
+ *  In lws the client http headers are temporarily stored in a pool, only for the
+ *  duration of the http part of the handshake.  It's because in most cases,
+ *  the header content is ignored for the whole rest of the connection lifetime
+ *  and would then just be taking up space needlessly.
+ *
+ *  During LWS_CALLBACK_HTTP when the URI path is delivered is the last time
+ *  the http headers are still allocated, you can use these apis then to
+ *  look at and copy out interesting header content (cookies, etc)
+ *
+ *  Notice that the header total length reported does not include a terminating
+ *  '\0', however you must allocate for it when using the _copy apis.  So the
+ *  length reported for a header containing "123" is 3, but you must provide
+ *  a buffer of length 4 so that "123\0" may be copied into it, or the copy
+ *  will fail with a nonzero return code.
+ *
+ *  In the special case of URL arguments, like ?x=1&y=2, the arguments are
+ *  stored in a token named for the method, eg,  WSI_TOKEN_GET_URI if it
+ *  was a GET or WSI_TOKEN_POST_URI if POST.  You can check the total
+ *  length to confirm the method.
+ *
+ *  For URL arguments, each argument is stored urldecoded in a "fragment", so
+ *  you can use the fragment-aware api lws_hdr_copy_fragment() to access each
+ *  argument in turn: the fragments contain urldecoded strings like x=1 or y=2.
+ *
+ *  As a convenience, lws has an api that will find the fragment with a
+ *  given name= part, lws_get_urlarg_by_name().
+ */
+///@{
+
+/** struct lws_tokens
+ * you need these to look at headers that have been parsed if using the
+ * LWS_CALLBACK_FILTER_CONNECTION callback.  If a header from the enum
+ * list below is absent, .token = NULL and len = 0.  Otherwise .token
+ * points to .len chars containing that header content.
+ */
+struct lws_tokens {
+       unsigned char *token; /**< pointer to start of the token */
+       int len; /**< length of the token's value */
+};
+
+/* enum lws_token_indexes
+ * these have to be kept in sync with lextable.h / minilex.c
+ *
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+enum lws_token_indexes {
+       WSI_TOKEN_GET_URI                                       =  0,
+       WSI_TOKEN_POST_URI                                      =  1,
+       WSI_TOKEN_OPTIONS_URI                                   =  2,
+       WSI_TOKEN_HOST                                          =  3,
+       WSI_TOKEN_CONNECTION                                    =  4,
+       WSI_TOKEN_UPGRADE                                       =  5,
+       WSI_TOKEN_ORIGIN                                        =  6,
+       WSI_TOKEN_DRAFT                                         =  7,
+       WSI_TOKEN_CHALLENGE                                     =  8,
+       WSI_TOKEN_EXTENSIONS                                    =  9,
+       WSI_TOKEN_KEY1                                          = 10,
+       WSI_TOKEN_KEY2                                          = 11,
+       WSI_TOKEN_PROTOCOL                                      = 12,
+       WSI_TOKEN_ACCEPT                                        = 13,
+       WSI_TOKEN_NONCE                                         = 14,
+       WSI_TOKEN_HTTP                                          = 15,
+       WSI_TOKEN_HTTP2_SETTINGS                                = 16,
+       WSI_TOKEN_HTTP_ACCEPT                                   = 17,
+       WSI_TOKEN_HTTP_AC_REQUEST_HEADERS                       = 18,
+       WSI_TOKEN_HTTP_IF_MODIFIED_SINCE                        = 19,
+       WSI_TOKEN_HTTP_IF_NONE_MATCH                            = 20,
+       WSI_TOKEN_HTTP_ACCEPT_ENCODING                          = 21,
+       WSI_TOKEN_HTTP_ACCEPT_LANGUAGE                          = 22,
+       WSI_TOKEN_HTTP_PRAGMA                                   = 23,
+       WSI_TOKEN_HTTP_CACHE_CONTROL                            = 24,
+       WSI_TOKEN_HTTP_AUTHORIZATION                            = 25,
+       WSI_TOKEN_HTTP_COOKIE                                   = 26,
+       WSI_TOKEN_HTTP_CONTENT_LENGTH                           = 27,
+       WSI_TOKEN_HTTP_CONTENT_TYPE                             = 28,
+       WSI_TOKEN_HTTP_DATE                                     = 29,
+       WSI_TOKEN_HTTP_RANGE                                    = 30,
+       WSI_TOKEN_HTTP_REFERER                                  = 31,
+       WSI_TOKEN_KEY                                           = 32,
+       WSI_TOKEN_VERSION                                       = 33,
+       WSI_TOKEN_SWORIGIN                                      = 34,
+
+       WSI_TOKEN_HTTP_COLON_AUTHORITY                          = 35,
+       WSI_TOKEN_HTTP_COLON_METHOD                             = 36,
+       WSI_TOKEN_HTTP_COLON_PATH                               = 37,
+       WSI_TOKEN_HTTP_COLON_SCHEME                             = 38,
+       WSI_TOKEN_HTTP_COLON_STATUS                             = 39,
+
+       WSI_TOKEN_HTTP_ACCEPT_CHARSET                           = 40,
+       WSI_TOKEN_HTTP_ACCEPT_RANGES                            = 41,
+       WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN              = 42,
+       WSI_TOKEN_HTTP_AGE                                      = 43,
+       WSI_TOKEN_HTTP_ALLOW                                    = 44,
+       WSI_TOKEN_HTTP_CONTENT_DISPOSITION                      = 45,
+       WSI_TOKEN_HTTP_CONTENT_ENCODING                         = 46,
+       WSI_TOKEN_HTTP_CONTENT_LANGUAGE                         = 47,
+       WSI_TOKEN_HTTP_CONTENT_LOCATION                         = 48,
+       WSI_TOKEN_HTTP_CONTENT_RANGE                            = 49,
+       WSI_TOKEN_HTTP_ETAG                                     = 50,
+       WSI_TOKEN_HTTP_EXPECT                                   = 51,
+       WSI_TOKEN_HTTP_EXPIRES                                  = 52,
+       WSI_TOKEN_HTTP_FROM                                     = 53,
+       WSI_TOKEN_HTTP_IF_MATCH                                 = 54,
+       WSI_TOKEN_HTTP_IF_RANGE                                 = 55,
+       WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE                      = 56,
+       WSI_TOKEN_HTTP_LAST_MODIFIED                            = 57,
+       WSI_TOKEN_HTTP_LINK                                     = 58,
+       WSI_TOKEN_HTTP_LOCATION                                 = 59,
+       WSI_TOKEN_HTTP_MAX_FORWARDS                             = 60,
+       WSI_TOKEN_HTTP_PROXY_AUTHENTICATE                       = 61,
+       WSI_TOKEN_HTTP_PROXY_AUTHORIZATION                      = 62,
+       WSI_TOKEN_HTTP_REFRESH                                  = 63,
+       WSI_TOKEN_HTTP_RETRY_AFTER                              = 64,
+       WSI_TOKEN_HTTP_SERVER                                   = 65,
+       WSI_TOKEN_HTTP_SET_COOKIE                               = 66,
+       WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY                = 67,
+       WSI_TOKEN_HTTP_TRANSFER_ENCODING                        = 68,
+       WSI_TOKEN_HTTP_USER_AGENT                               = 69,
+       WSI_TOKEN_HTTP_VARY                                     = 70,
+       WSI_TOKEN_HTTP_VIA                                      = 71,
+       WSI_TOKEN_HTTP_WWW_AUTHENTICATE                         = 72,
+
+       WSI_TOKEN_PATCH_URI                                     = 73,
+       WSI_TOKEN_PUT_URI                                       = 74,
+       WSI_TOKEN_DELETE_URI                                    = 75,
+
+       WSI_TOKEN_HTTP_URI_ARGS                                 = 76,
+       WSI_TOKEN_PROXY                                         = 77,
+       WSI_TOKEN_HTTP_X_REAL_IP                                = 78,
+       WSI_TOKEN_HTTP1_0                                       = 79,
+       WSI_TOKEN_X_FORWARDED_FOR                               = 80,
+       WSI_TOKEN_CONNECT                                       = 81,
+       WSI_TOKEN_HEAD_URI                                      = 82,
+       WSI_TOKEN_TE                                            = 83,
+       WSI_TOKEN_REPLAY_NONCE                                  = 84,
+       WSI_TOKEN_COLON_PROTOCOL                                = 85,
+       WSI_TOKEN_X_AUTH_TOKEN                                  = 86,
+
+       /****** add new things just above ---^ ******/
+
+       /* use token storage to stash these internally, not for
+        * user use */
+
+       _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
+       _WSI_TOKEN_CLIENT_PEER_ADDRESS,
+       _WSI_TOKEN_CLIENT_URI,
+       _WSI_TOKEN_CLIENT_HOST,
+       _WSI_TOKEN_CLIENT_ORIGIN,
+       _WSI_TOKEN_CLIENT_METHOD,
+       _WSI_TOKEN_CLIENT_IFACE,
+       _WSI_TOKEN_CLIENT_ALPN,
+
+       /* always last real token index*/
+       WSI_TOKEN_COUNT,
+
+       /* parser state additions, no storage associated */
+       WSI_TOKEN_NAME_PART,
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+       WSI_TOKEN_UNKNOWN_VALUE_PART,
+#endif
+       WSI_TOKEN_SKIPPING,
+       WSI_TOKEN_SKIPPING_SAW_CR,
+       WSI_PARSING_COMPLETE,
+       WSI_INIT_TOKEN_MUXURL,
+};
+
+struct lws_token_limits {
+       unsigned short token_limit[WSI_TOKEN_COUNT]; /**< max chars for this token */
+};
+
+/**
+ * lws_token_to_string() - returns a textual representation of a hdr token index
+ *
+ * \param token: token index
+ */
+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
+ *             terminating '\0'
+ *
+ * \param wsi: websocket connection
+ * \param h: which header index we are interested in
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h);
+
+/**
+ * lws_hdr_fragment_length: report length of a single fragment of a header
+ *             The returned length does not include the space for a
+ *             terminating '\0'
+ *
+ * \param wsi: websocket connection
+ * \param h: which header index we are interested in
+ * \param frag_idx: which fragment of h we want to get the length of
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h,
+                       int frag_idx);
+
+/**
+ * lws_hdr_copy() - copy all fragments of the given header to a buffer
+ *             The buffer length len must include space for an additional
+ *             terminating '\0', or it will fail returning -1.
+ *
+ * \param wsi: websocket connection
+ * \param dest: destination buffer
+ * \param len: length of destination buffer
+ * \param h: which header index we are interested in
+ *
+ * copies the whole, aggregated header, even if it was delivered in
+ * several actual headers piece by piece.  Returns -1 or length of the whole
+ * header.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_hdr_copy(struct lws *wsi, char *dest, int len, enum lws_token_indexes h);
+
+/**
+ * lws_hdr_copy_fragment() - copy a single fragment of the given header to a buffer
+ *             The buffer length len must include space for an additional
+ *             terminating '\0', or it will fail returning -1.
+ *             If the requested fragment index is not present, it fails
+ *             returning -1.
+ *
+ * \param wsi: websocket connection
+ * \param dest: destination buffer
+ * \param len: length of destination buffer
+ * \param h: which header index we are interested in
+ * \param frag_idx: which fragment of h we want to copy
+ *
+ * Normally this is only useful
+ * to parse URI arguments like ?x=1&y=2, token index WSI_TOKEN_HTTP_URI_ARGS
+ * fragment 0 will contain "x=1" and fragment 1 "y=2"
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_hdr_copy_fragment(struct lws *wsi, char *dest, int len,
+                     enum lws_token_indexes h, int frag_idx);
+
+/**
+ * lws_hdr_custom_length() - return length of a custom header
+ *
+ * \param wsi: websocket connection
+ * \param name: header string (including terminating :)
+ * \param nlen: length of name
+ *
+ * Lws knows about 100 common http headers, and parses them into indexes when
+ * it recognizes them.  When it meets a header that it doesn't know, it stores
+ * the name and value directly, and you can look them up using
+ * lws_hdr_custom_length() and lws_hdr_custom_copy().
+ *
+ * This api returns -1, or the length of the value part of the header if it
+ * exists.  Lws must be built with LWS_WITH_CUSTOM_HEADERS (on by default) to
+ * use this api.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen);
+
+/**
+ * lws_hdr_custom_copy() - copy value part of a custom header
+ *
+ * \param wsi: websocket connection
+ * \param dst: pointer to buffer to receive the copy
+ * \param len: number of bytes available at dst
+ * \param name: header string (including terminating :)
+ * \param nlen: length of name
+ *
+ * Lws knows about 100 common http headers, and parses them into indexes when
+ * it recognizes them.  When it meets a header that it doesn't know, it stores
+ * the name and value directly, and you can look them up using
+ * lws_hdr_custom_length() and lws_hdr_custom_copy().
+ *
+ * This api returns -1, or the length of the string it copied into dst if it
+ * was big enough to contain both the string and an extra terminating NUL. Lws
+ * must be built with LWS_WITH_CUSTOM_HEADERS (on by default) to use this api.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_hdr_custom_copy(struct lws *wsi, char *dst, int len, const char *name,
+                   int nlen);
+
+/**
+ * lws_get_urlarg_by_name() - return pointer to arg value if present
+ * \param wsi: the connection to check
+ * \param name: the arg name, like "token="
+ * \param buf: the buffer to receive the urlarg (including the name= part)
+ * \param len: the length of the buffer to receive the urlarg
+ *
+ *     Returns NULL if not found or a pointer inside buf to just after the
+ *     name= part.
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len);
+///@}
+
+/*! \defgroup HTTP-headers-create HTTP headers: create
+ *
+ * ## HTTP headers: Create
+ *
+ * These apis allow you to create HTTP response headers in a way compatible with
+ * both HTTP/1.x and HTTP/2.
+ *
+ * They each append to a buffer taking care about the buffer end, which is
+ * passed in as a pointer.  When data is written to the buffer, the current
+ * position p is updated accordingly.
+ *
+ * All of these apis are LWS_WARN_UNUSED_RESULT as they can run out of space
+ * and fail with nonzero return.
+ */
+///@{
+
+#define LWSAHH_CODE_MASK                       ((1 << 16) - 1)
+#define LWSAHH_FLAG_NO_SERVER_NAME             (1 << 30)
+
+/**
+ * lws_add_http_header_status() - add the HTTP response status code
+ *
+ * \param wsi: the connection to check
+ * \param code: an HTTP code like 200, 404 etc (see enum http_status)
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Adds the initial response code, so should be called first.
+ *
+ * Code may additionally take OR'd flags:
+ *
+ *    LWSAHH_FLAG_NO_SERVER_NAME:  don't apply server name header this time
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_add_http_header_status(struct lws *wsi,
+                          unsigned int code, unsigned char **p,
+                          unsigned char *end);
+/**
+ * lws_add_http_header_by_name() - append named header and value
+ *
+ * \param wsi: the connection to check
+ * \param name: the hdr name, like "my-header"
+ * \param value: the value after the = for this header
+ * \param length: the length of the value
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Appends name: value to the headers
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
+                           const unsigned char *value, int length,
+                           unsigned char **p, unsigned char *end);
+/**
+ * lws_add_http_header_by_token() - append given header and value
+ *
+ * \param wsi: the connection to check
+ * \param token: the token index for the hdr
+ * \param value: the value after the = for this header
+ * \param length: the length of the value
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Appends name=value to the headers, but is able to take advantage of better
+ * HTTP/2 coding mechanisms where possible.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
+                            const unsigned char *value, int length,
+                            unsigned char **p, unsigned char *end);
+/**
+ * lws_add_http_header_content_length() - append content-length helper
+ *
+ * \param wsi: the connection to check
+ * \param content_length: the content length to use
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Appends content-length: content_length to the headers
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_add_http_header_content_length(struct lws *wsi,
+                                  lws_filepos_t content_length,
+                                  unsigned char **p, unsigned char *end);
+/**
+ * lws_finalize_http_header() - terminate header block
+ *
+ * \param wsi: the connection to check
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Indicates no more headers will be added
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_finalize_http_header(struct lws *wsi, unsigned char **p,
+                        unsigned char *end);
+
+/**
+ * lws_finalize_write_http_header() - Helper finializing and writing http headers
+ *
+ * \param wsi: the connection to check
+ * \param start: pointer to the start of headers in the buffer, eg &buf[LWS_PRE]
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Terminates the headers correctly accoring to the protocol in use (h1 / h2)
+ * and writes the headers.  Returns nonzero for error.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_finalize_write_http_header(struct lws *wsi, unsigned char *start,
+                              unsigned char **p, unsigned char *end);
+
+#define LWS_ILLEGAL_HTTP_CONTENT_LEN ((lws_filepos_t)-1ll)
+
+/**
+ * lws_add_http_common_headers() - Helper preparing common http headers
+ *
+ * \param wsi: the connection to check
+ * \param code: an HTTP code like 200, 404 etc (see enum http_status)
+ * \param content_type: the content type, like "text/html"
+ * \param content_len: the content length, in bytes
+ * \param p: pointer to current position in buffer pointer
+ * \param end: pointer to end of buffer
+ *
+ * Adds the initial response code, so should be called first.
+ *
+ * Code may additionally take OR'd flags:
+ *
+ *    LWSAHH_FLAG_NO_SERVER_NAME:  don't apply server name header this time
+ *
+ * This helper just calls public apis to simplify adding headers that are
+ * commonly needed.  If it doesn't fit your case, or you want to add additional
+ * headers just call the public apis directly yourself for what you want.
+ *
+ * You can miss out the content length header by providing the constant
+ * LWS_ILLEGAL_HTTP_CONTENT_LEN for the content_len.
+ *
+ * It does not call lws_finalize_http_header(), to allow you to add further
+ * headers after calling this.  You will need to call that yourself at the end.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_add_http_common_headers(struct lws *wsi, unsigned int code,
+                           const char *content_type, lws_filepos_t content_len,
+                           unsigned char **p, unsigned char *end);
+
+/**
+ * lws_http_get_uri_and_method() - Get information on method and url
+ *
+ * \param wsi: the connection to get information on
+ * \param puri_ptr: points to pointer to set to url
+ * \param puri_len: points to int to set to uri length
+ *
+ * Returns -1 or method index
+ *
+ * GET      0
+ * POST     1
+ * OPTIONS  2
+ * PUT      3
+ * PATCH    4
+ * DELETE   5
+ * CONNECT  6
+ * HEAD     7
+ * :path    8
+ *
+ * If returns method, *puri_ptr is set to the method's URI string and *puri_len
+ * to its length
+ */
+
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len);
+
+///@}
+
+/*! \defgroup urlendec Urlencode and Urldecode
+ * \ingroup http
+ *
+ * ##HTML chunked Substitution
+ *
+ * APIs for receiving chunks of text, replacing a set of variable names via
+ * a callback, and then prepending and appending HTML chunked encoding
+ * headers.
+ */
+//@{
+
+/**
+ * lws_urlencode() - like strncpy but with urlencoding
+ *
+ * \param escaped: output buffer
+ * \param string: input buffer ('/0' terminated)
+ * \param len: output buffer max length
+ *
+ * Because urlencoding expands the output string, it's not
+ * possible to do it in-place, ie, with escaped == string
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_urlencode(char *escaped, const char *string, int len);
+
+/*
+ * URLDECODE 1 / 2
+ *
+ * This simple urldecode only operates until the first '\0' and requires the
+ * data to exist all at once
+ */
+/**
+ * lws_urldecode() - like strncpy but with urldecoding
+ *
+ * \param string: output buffer
+ * \param escaped: input buffer ('\0' terminated)
+ * \param len: output buffer max length
+ *
+ * This is only useful for '\0' terminated strings
+ *
+ * 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);
+///@}
+
+/**
+ * lws_return_http_status() - Return simple http status
+ * \param wsi:         Websocket instance (available from user callback)
+ * \param code:                Status index, eg, 404
+ * \param html_body:           User-readable HTML description < 1KB, or NULL
+ *
+ *     Helper to report HTTP errors back to the client cleanly and
+ *     consistently
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_return_http_status(struct lws *wsi, unsigned int code,
+                      const char *html_body);
+
+/**
+ * lws_http_redirect() - write http redirect out on wsi
+ *
+ * \param wsi: websocket connection
+ * \param code:        HTTP response code (eg, 301)
+ * \param loc: where to redirect to
+ * \param len: length of loc
+ * \param p:   pointer current position in buffer (updated as we write)
+ * \param end: pointer to end of buffer
+ *
+ * Returns amount written, or < 0 indicating fatal write failure.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len,
+                 unsigned char **p, unsigned char *end);
+
+/**
+ * lws_http_transaction_completed() - wait for new http transaction or close
+ * \param wsi: websocket connection
+ *
+ *     Returns 1 if the HTTP connection must close now
+ *     Returns 0 and resets connection to wait for new HTTP header /
+ *       transaction if possible
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_http_transaction_completed(struct lws *wsi);
+
+/**
+ * lws_http_headers_detach() - drop the associated headers storage and allow
+ *                             it to be reused by another connection
+ * \param wsi: http connection
+ *
+ * If the wsi has an ah headers struct attached, detach it.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_http_headers_detach(struct lws *wsi);
+
+/**
+ * lws_http_mark_sse() - called to indicate this http stream is now doing SSE
+ *
+ * \param wsi: http connection
+ *
+ * Cancel any timeout on the wsi, and for h2, mark the network connection as
+ * containing an immortal stream for the duration the SSE stream is open.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_http_mark_sse(struct lws *wsi);
+
+/**
+ * lws_http_compression_apply() - apply an http compression transform
+ *
+ * \param wsi: the wsi to apply the compression transform to
+ * \param name: NULL, or the name of the compression transform, eg, "deflate"
+ * \param p: pointer to pointer to headers buffer
+ * \param end: pointer to end of headers buffer
+ * \param decomp: 0 = add compressor to wsi, 1 = add decompressor
+ *
+ * This allows transparent compression of dynamically generated HTTP.  The
+ * requested compression (eg, "deflate") is only applied if the client headers
+ * indicated it was supported (and it has support in lws), otherwise it's a NOP.
+ *
+ * If the requested compression method is NULL, then the supported compression
+ * formats are tried, and for non-decompression (server) mode the first that's
+ * found on the client's accept-encoding header is chosen.
+ *
+ * NOTE: the compression transform, same as h2 support, relies on the user
+ * code using LWS_WRITE_HTTP and then LWS_WRITE_HTTP_FINAL on the last part
+ * written.  The internal lws fileserving code already does this.
+ *
+ * If the library was built without the cmake option
+ * LWS_WITH_HTTP_STREAM_COMPRESSION set, then a NOP is provided for this api,
+ * allowing user code to build either way and use compression if available.
+ */
+LWS_VISIBLE int
+lws_http_compression_apply(struct lws *wsi, const char *name,
+                          unsigned char **p, unsigned char *end, char decomp);
+///@}
+
diff --git a/include/libwebsockets/lws-jose.h b/include/libwebsockets/lws-jose.h
new file mode 100644 (file)
index 0000000..fc0fcc0
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+enum lws_jws_jose_hdr_indexes {
+       LJJHI_ALG,      /* REQUIRED */
+       LJJHI_JKU,      /* Optional: string */
+       LJJHI_JWK,      /* Optional: jwk JSON object: public key: */
+       LJJHI_KID,      /* Optional: string */
+       LJJHI_X5U,      /* Optional: string: url of public key cert / chain */
+       LJJHI_X5C,      /* Optional: base64 (NOT -url): actual cert */
+       LJJHI_X5T,      /* Optional: base64url: SHA-1 of actual cert */
+       LJJHI_X5T_S256, /* Optional: base64url: SHA-256 of actual cert */
+       LJJHI_TYP,      /* Optional: string: media type */
+       LJJHI_CTY,      /* Optional: string: content media type */
+       LJJHI_CRIT,     /* Optional for send, REQUIRED: array of strings:
+                        * mustn't contain standardized strings or null set */
+
+       LJJHI_RECIPS_HDR,
+       LJJHI_RECIPS_HDR_ALG,
+       LJJHI_RECIPS_HDR_KID,
+       LJJHI_RECIPS_EKEY,
+
+       LJJHI_ENC,      /* JWE only: Optional: string */
+       LJJHI_ZIP,      /* JWE only: Optional: string ("DEF" = deflate) */
+
+       LJJHI_EPK,      /* Additional arg for JWE ECDH:  ephemeral public key */
+       LJJHI_APU,      /* Additional arg for JWE ECDH:  base64url */
+       LJJHI_APV,      /* Additional arg for JWE ECDH:  base64url */
+       LJJHI_IV,       /* Additional arg for JWE AES:   base64url */
+       LJJHI_TAG,      /* Additional arg for JWE AES:   base64url */
+       LJJHI_P2S,      /* Additional arg for JWE PBES2: base64url: salt */
+       LJJHI_P2C,      /* Additional arg for JWE PBES2: integer: count */
+
+       LWS_COUNT_JOSE_HDR_ELEMENTS
+};
+
+enum lws_jose_algtype {
+       LWS_JOSE_ENCTYPE_NONE,
+
+       LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5,
+       LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP,
+       LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS,
+
+       LWS_JOSE_ENCTYPE_ECDSA,
+       LWS_JOSE_ENCTYPE_ECDHES,
+
+       LWS_JOSE_ENCTYPE_AES_CBC,
+       LWS_JOSE_ENCTYPE_AES_CFB128,
+       LWS_JOSE_ENCTYPE_AES_CFB8,
+       LWS_JOSE_ENCTYPE_AES_CTR,
+       LWS_JOSE_ENCTYPE_AES_ECB,
+       LWS_JOSE_ENCTYPE_AES_OFB,
+       LWS_JOSE_ENCTYPE_AES_XTS,       /* care: requires double-length key */
+       LWS_JOSE_ENCTYPE_AES_GCM,
+};
+
+/* there's a table of these defined in lws-gencrypto-common.c */
+
+struct lws_jose_jwe_alg {
+       enum lws_genhash_types hash_type;
+       enum lws_genhmac_types hmac_type;
+       enum lws_jose_algtype algtype_signing; /* the signing cipher */
+       enum lws_jose_algtype algtype_crypto; /* the encryption cipher */
+       const char *alg; /* the JWA enc alg name, eg "ES512" */
+       const char *curve_name; /* NULL, or, eg, "P-256" */
+       unsigned short keybits_min, keybits_fixed;
+       unsigned short ivbits;
+};
+
+/*
+ * For JWS, "JOSE header" is defined to be the union of...
+ *
+ * o  JWS Protected Header
+ * o  JWS Unprotected Header
+ *
+ * For JWE, the "JOSE header" is the union of...
+ *
+ * o  JWE Protected Header
+ * o  JWE Shared Unprotected Header
+ * o  JWE Per-Recipient Unprotected Header
+ */
+
+#define LWS_JWS_MAX_RECIPIENTS 3
+
+struct lws_jws_recpient {
+       /*
+        * JOSE per-recipient unprotected header... for JWS this contains
+        * protected / header / signature
+        */
+       struct lws_gencrypto_keyelem unprot[LWS_COUNT_JOSE_HDR_ELEMENTS];
+       struct lws_jwk jwk_ephemeral;   /* recipient ephemeral key if any */
+       struct lws_jwk jwk;             /* recipient "jwk" key if any */
+};
+
+struct lws_jose {
+       /* JOSE protected and unprotected header elements */
+       struct lws_gencrypto_keyelem e[LWS_COUNT_JOSE_HDR_ELEMENTS];
+
+       struct lws_jws_recpient recipient[LWS_JWS_MAX_RECIPIENTS];
+
+       /* information from the protected header part */
+       const struct lws_jose_jwe_alg *alg;
+       const struct lws_jose_jwe_alg *enc_alg;
+
+       int recipients; /* count of used recipient[] entries */
+};
+
+/**
+ * lws_jose_init() - prepare a struct lws_jose for use
+ *
+ * \param jose: the jose header struct to prepare
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_jose_init(struct lws_jose *jose);
+
+/**
+ * lws_jose_destroy() - retire a struct lws_jose from use
+ *
+ * \param jose: the jose header struct to destroy
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_jose_destroy(struct lws_jose *jose);
+
+/**
+ * lws_gencrypto_jws_alg_to_definition() - look up a jws alg name
+ *
+ * \param alg: the jws alg name
+ * \param jose: pointer to the pointer to the info struct to set on success
+ *
+ * Returns 0 if *jose set, else nonzero for failure
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_gencrypto_jws_alg_to_definition(const char *alg,
+                                   const struct lws_jose_jwe_alg **jose);
+
+/**
+ * lws_gencrypto_jwe_alg_to_definition() - look up a jwe alg name
+ *
+ * \param alg: the jwe alg name
+ * \param jose: pointer to the pointer to the info struct to set on success
+ *
+ * Returns 0 if *jose set, else nonzero for failure
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_gencrypto_jwe_alg_to_definition(const char *alg,
+                                   const struct lws_jose_jwe_alg **jose);
+
+/**
+ * lws_gencrypto_jwe_enc_to_definition() - look up a jwe enc name
+ *
+ * \param alg: the jwe enc name
+ * \param jose: pointer to the pointer to the info struct to set on success
+ *
+ * Returns 0 if *jose set, else nonzero for failure
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_gencrypto_jwe_enc_to_definition(const char *enc,
+                                   const struct lws_jose_jwe_alg **jose);
+
+/**
+ * lws_jws_parse_jose() - parse a JWS JOSE header
+ *
+ * \param jose: the jose struct to set to parsing results
+ * \param buf: the raw JOSE header
+ * \param len: the length of the raw JOSE header
+ * \param temp: parent-owned buffer to "allocate" elements into
+ * \param temp_len: amount of space available in temp
+ *
+ * returns the amount of temp used, or -1 for error
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_parse_jose(struct lws_jose *jose,
+                  const char *buf, int len, char *temp, int *temp_len);
+
+/**
+ * lws_jwe_parse_jose() - parse a JWE JOSE header
+ *
+ * \param jose: the jose struct to set to parsing results
+ * \param buf: the raw JOSE header
+ * \param len: the length of the raw JOSE header
+ * \param temp: parent-owned buffer to "allocate" elements into
+ * \param temp_len: amount of space available in temp
+ *
+ * returns the amount of temp used, or -1 for error
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_parse_jose(struct lws_jose *jose,
+                  const char *buf, int len, char *temp, int *temp_len);
+
diff --git a/include/libwebsockets/lws-jwe.h b/include/libwebsockets/lws-jwe.h
new file mode 100644 (file)
index 0000000..3798dee
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * libwebsockets - JSON Web Encryption
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ *
+ * JWE Compact Serialization consists of
+ *
+ *     BASE64URL(UTF8(JWE Protected Header)) || '.' ||
+ *     BASE64URL(JWE Encrypted Key)         || '.' ||
+ *     BASE64URL(JWE Initialization Vector)  || '.' ||
+ *     BASE64URL(JWE Ciphertext)            || '.' ||
+ *     BASE64URL(JWE Authentication Tag)
+ */
+
+#define LWS_JWE_RFC3394_OVERHEAD_BYTES 8
+#define LWS_JWE_AES_IV_BYTES 16
+
+#define LWS_JWE_LIMIT_RSA_KEY_BITS 4096
+#define LWS_JWE_LIMIT_AES_KEY_BITS (512 + 64) /* RFC3394 Key Wrap adds 64b */
+#define LWS_JWE_LIMIT_EC_KEY_BITS  528 /* 521 rounded to byte boundary */
+#define LWS_JWE_LIMIT_HASH_BITS    (LWS_GENHASH_LARGEST * 8)
+
+/* the largest key element for any cipher */
+#define LWS_JWE_LIMIT_KEY_ELEMENT_BYTES (LWS_JWE_LIMIT_RSA_KEY_BITS / 8)
+
+
+struct lws_jwe {
+       struct lws_jose jose;
+       struct lws_jws jws;
+       struct lws_jwk jwk;
+
+       /*
+        * We have to keep a copy of the CEK so we can reuse it with later
+        * key encryptions for the multiple recipient case.
+        */
+       uint8_t cek[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
+       unsigned int cek_valid:1;
+
+       int recip;
+};
+
+LWS_VISIBLE LWS_EXTERN void
+lws_jwe_init(struct lws_jwe *jwe, struct lws_context *context);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_jwe_destroy(struct lws_jwe *jwe);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_jwe_be64(uint64_t c, uint8_t *p8);
+
+/*
+ * JWE Compact Serialization consists of
+ *
+ *     BASE64URL(UTF8(JWE Protected Header)) || '.' ||
+ *     BASE64URL(JWE Encrypted Key)         || '.' ||
+ *     BASE64URL(JWE Initialization Vector)  || '.' ||
+ *     BASE64URL(JWE Ciphertext)            || '.' ||
+ *     BASE64URL(JWE Authentication Tag)
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_render_compact(struct lws_jwe *jwe, char *out, size_t out_len);
+
+LWS_VISIBLE int
+lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_json_parse(struct lws_jwe *jwe, const uint8_t *buf, int len,
+                  char *temp, int *temp_len);
+
+/**
+ * lws_jwe_auth_and_decrypt() - confirm and decrypt JWE
+ *
+ * \param jose: jose context
+ * \param jws: jws / jwe context... .map and .map_b64 must be filled already
+ *
+ * This is a high level JWE decrypt api that takes a jws with the maps
+ * already processed, and if the authentication passes, returns the decrypted
+ * plaintext in jws.map.buf[LJWE_CTXT] and its length in jws.map.len[LJWE_CTXT].
+ *
+ * In the jws, the following fields must have been set by the caller
+ *
+ * .context
+ * .jwk (the key encryption key)
+ * .map
+ * .map_b64
+ *
+ * Having the b64 and decoded maps filled externally makes it flexible where
+ * the data was picked from, eg, from a Complete JWE JSON serialization, a
+ * flattened one, or a Compact Serialization.
+ *
+ * Returns decrypt length, or -1 for failure.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_auth_and_decrypt(struct lws_jwe *jwe, char *temp, int *temp_len);
+
+/**
+ * lws_jwe_encrypt() - perform JWE encryption
+ *
+ * \param jose: the JOSE header information (encryption types, etc)
+ * \param jws: the JWE elements, pointer to jwk etc
+ * \param temp: parent-owned buffer to "allocate" elements into
+ * \param temp_len: amount of space available in temp
+ *
+ * May be called up to LWS_JWS_MAX_RECIPIENTS times to encrypt the same CEK
+ * multiple ways on the same JWE payload.
+ *
+ * returns the amount of temp used, or -1 for error.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_encrypt(struct lws_jwe *jwe, char *temp, int *temp_len);
+
+/**
+ * lws_jwe_create_packet() - add b64 sig to b64 hdr + payload
+ *
+ * \param jwe: the struct lws_jwe we are trying to render
+ * \param payload: unencoded payload JSON
+ * \param len: length of unencoded payload JSON
+ * \param nonce: Nonse string to include in protected header
+ * \param out: buffer to take signed packet
+ * \param out_len: size of \p out buffer
+ * \param conext: lws_context to get random from
+ *
+ * This creates a "flattened" JWS packet from the jwk and the plaintext
+ * payload, and signs it.  The packet is written into \p out.
+ *
+ * This does the whole packet assembly and signing, calling through to
+ * lws_jws_sign_from_b64() as part of the process.
+ *
+ * Returns the length written to \p out, or -1.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_create_packet(struct lws_jwe *jwe,
+                     const char *payload, size_t len, const char *nonce,
+                     char *out, size_t out_len, struct lws_context *context);
+
+
+/* only exposed because we have test vectors that need it */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwe_auth_and_decrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *enc_cek,
+                                       uint8_t *aad, int aad_len);
+
+/* only exposed because we have test vectors that need it */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwa_concat_kdf(struct lws_jwe *jwe, int direct,
+                  uint8_t *out, const uint8_t *shared_secret, int sslen);
diff --git a/include/libwebsockets/lws-jwk.h b/include/libwebsockets/lws-jwk.h
new file mode 100644 (file)
index 0000000..1725b99
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup jwk JSON Web Keys
+ * ## JSON Web Keys API
+ *
+ * Lws provides an API to parse JSON Web Keys into a struct lws_gencrypto_keyelem.
+ *
+ * "oct" and "RSA" type keys are supported.  For "oct" keys, they are held in
+ * the "e" member of the struct lws_gencrypto_keyelem.
+ *
+ * Keys elements are allocated on the heap.  You must destroy the allocations
+ * in the struct lws_gencrypto_keyelem by calling
+ * lws_genrsa_destroy_elements() when you are finished with it.
+ */
+///@{
+
+enum enum_jwk_meta_tok {
+       JWK_META_KTY,
+       JWK_META_KID,
+       JWK_META_USE,
+       JWK_META_KEY_OPS,
+       JWK_META_X5C,
+       JWK_META_ALG,
+
+       LWS_COUNT_JWK_ELEMENTS
+};
+
+struct lws_jwk {
+       /* key data elements */
+       struct lws_gencrypto_keyelem e[LWS_GENCRYPTO_MAX_KEYEL_COUNT];
+       /* generic meta key elements, like KID */
+       struct lws_gencrypto_keyelem meta[LWS_COUNT_JWK_ELEMENTS];
+       int kty;                        /**< one of LWS_JWK_ */
+       char private_key; /* nonzero = has private key elements */
+};
+
+typedef int (*lws_jwk_key_import_callback)(struct lws_jwk *s, void *user);
+
+struct lws_jwk_parse_state {
+       struct lws_jwk *jwk;
+       char b64[(((8192 / 8) * 4) / 3) + 1]; /* enough for 8Kb key */
+       lws_jwk_key_import_callback per_key_cb;
+       void *user;
+       int pos;
+       unsigned short possible;
+};
+
+/** lws_jwk_import() - Create a JSON Web key from the textual representation
+ *
+ * \param jwk: the JWK object to create
+ * \param cb: callback for each jwk-processed key, or NULL if importing a single
+ *           key with no parent "keys" JSON
+ * \param user: pointer to be passed to the callback, otherwise ignored by lws.
+ *             NULL if importing a single key with no parent "keys" JSON
+ * \param in: a single JWK JSON stanza in utf-8
+ * \param len: the length of the JWK JSON stanza in bytes
+ *
+ * Creates an lws_jwk struct filled with data from the JSON representation.
+ *
+ * There are two ways to use this... with some protocols a single jwk is
+ * delivered with no parent "keys": [] array.  If you call this with cb and
+ * user as NULL, then the input will be interpreted like that and the results
+ * placed in s.
+ *
+ * The second case is that you are dealing with a "keys":[] array with one or
+ * more keys in it.  In this case, the function iterates through the keys using
+ * s as a temporary jwk, and calls the user-provided callback for each key in
+ * turn while it return 0 (nonzero return from the callback terminates the
+ * iteration through any further keys).
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_import(struct lws_jwk *jwk, lws_jwk_key_import_callback cb, void *user,
+              const char *in, size_t len);
+
+/** lws_jwk_destroy() - Destroy a JSON Web key
+ *
+ * \param jwk: the JWK object to destroy
+ *
+ * All allocations in the lws_jwk are destroyed
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_jwk_destroy(struct lws_jwk *jwk);
+
+/** lws_jwk_dup_oct() - Set a jwk to a dup'd binary OCT key
+ *
+ * \param jwk: the JWK object to set
+ * \param key: the JWK object to destroy
+ * \param len: the JWK object to destroy
+ *
+ * Sets the kty to OCT, allocates len bytes for K and copies len bytes of key
+ * into the allocation.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_dup_oct(struct lws_jwk *jwk, const void *key, int len);
+
+/** lws_jwk_export() - Export a JSON Web key to a textual representation
+ *
+ * \param jwk: the JWK object to export
+ * \param _private: 0 = just export public parts, 1 = export everything
+ * \param p: the buffer to write the exported JWK to
+ * \param len: the length of the buffer \p p in bytes... reduced by used amount
+ *
+ * Returns length of the used part of the buffer if OK, or -1 for error.
+ *
+ * Serializes the content of the JWK into a char buffer.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_export(struct lws_jwk *jwk, int _private, char *p, int *len);
+
+/** lws_jwk_load() - Import a JSON Web key from a file
+ *
+ * \param jwk: the JWK object to load into
+ * \param filename: filename to load from
+ *
+ * Returns 0 for OK or -1 for failure
+ *
+ * There are two ways to use this... with some protocols a single jwk is
+ * delivered with no parent "keys": [] array.  If you call this with cb and
+ * user as NULL, then the input will be interpreted like that and the results
+ * placed in s.
+ *
+ * The second case is that you are dealing with a "keys":[] array with one or
+ * more keys in it.  In this case, the function iterates through the keys using
+ * s as a temporary jwk, and calls the user-provided callback for each key in
+ * turn while it return 0 (nonzero return from the callback terminates the
+ * iteration through any further keys, leaving the last one in s).
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_load(struct lws_jwk *jwk, const char *filename,
+            lws_jwk_key_import_callback cb, void *user);
+
+/** lws_jwk_save() - Export a JSON Web key to a file
+ *
+ * \param jwk: the JWK object to save from
+ * \param filename: filename to save to
+ *
+ * Returns 0 for OK or -1 for failure
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_save(struct lws_jwk *jwk, const char *filename);
+
+/** lws_jwk_rfc7638_fingerprint() - jwk to RFC7638 compliant fingerprint
+ *
+ * \param jwk: the JWK object to fingerprint
+ * \param digest32: buffer to take 32-byte digest
+ *
+ * Returns 0 for OK or -1 for failure
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_rfc7638_fingerprint(struct lws_jwk *jwk, char *digest32);
+
+/** lws_jwk_strdup_meta() - allocate a duplicated string meta element
+ *
+ * \param jwk: the JWK object to fingerprint
+ * \param idx: JWK_META_ element index
+ * \param in: string to copy
+ * \param len: length of string to copy
+ *
+ * Returns 0 for OK or -1 for failure
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_strdup_meta(struct lws_jwk *jwk, enum enum_jwk_meta_tok idx,
+                   const char *in, int len);
+
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jwk_dump(struct lws_jwk *jwk);
+
+/** lws_jwk_generate() - create a new key of given type and characteristics
+ *
+ * \param context: the struct lws_context used for RNG
+ * \param jwk: the JWK object to fingerprint
+ * \param kty: One of the LWS_GENCRYPTO_KTY_ key types
+ * \param bits: for OCT and RSA keys, the number of bits
+ * \param curve: for EC keys, the name of the curve
+ *
+ * Returns 0 for OK or -1 for failure
+ */
+LWS_VISIBLE int
+lws_jwk_generate(struct lws_context *context, struct lws_jwk *jwk,
+                enum lws_gencrypto_kty kty, int bits, const char *curve);
+
+///@}
diff --git a/include/libwebsockets/lws-jws.h b/include/libwebsockets/lws-jws.h
new file mode 100644 (file)
index 0000000..c814bbd
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup jws JSON Web Signature
+ * ## JSON Web Signature API
+ *
+ * Lws provides an API to check and create RFC7515 JSON Web Signatures
+ *
+ * SHA256/384/512 HMAC, and RSA 256/384/512 are supported.
+ *
+ * The API uses your TLS library crypto, but works exactly the same no matter
+ * what your TLS backend is.
+ */
+///@{
+
+/*
+ * The maps are built to work with both JWS (LJWS_) and JWE (LJWE_), and are
+ * sized to the slightly larger JWE case.
+ */
+
+enum enum_jws_sig_elements {
+
+       /* JWS block namespace */
+       LJWS_JOSE,
+       LJWS_PYLD,
+       LJWS_SIG,
+       LJWS_UHDR,
+
+       /* JWE block namespace */
+       LJWE_JOSE = 0,
+       LJWE_EKEY,
+       LJWE_IV,
+       LJWE_CTXT,
+       LJWE_ATAG,
+       LJWE_AAD,
+
+       LWS_JWS_MAX_COMPACT_BLOCKS
+};
+
+struct lws_jws_map {
+       const char *buf[LWS_JWS_MAX_COMPACT_BLOCKS];
+       uint32_t len[LWS_JWS_MAX_COMPACT_BLOCKS];
+};
+
+#define LWS_JWS_MAX_SIGS 3
+
+struct lws_jws {
+       struct lws_jwk *jwk; /* the struct lws_jwk containing the signing key */
+       struct lws_context *context; /* the lws context (used to get random) */
+       struct lws_jws_map map, map_b64;
+};
+
+/* jws EC signatures do not have ASN.1 in them, meaning they're incompatible
+ * with generic signatures.
+ */
+
+/**
+ * lws_jws_init() - initialize a jws for use
+ *
+ * \param jws: pointer to the jws to initialize
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_jws_init(struct lws_jws *jws, struct lws_jwk *jwk,
+            struct lws_context *context);
+
+/**
+ * lws_jws_destroy() - scrub a jws
+ *
+ * \param jws: pointer to the jws to destroy
+ *
+ * Call before the jws goes out of scope.
+ *
+ * Elements defined in the jws are zeroed.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_jws_destroy(struct lws_jws *jws);
+
+/**
+ * lws_jws_sig_confirm_compact() - check signature
+ *
+ * \param map: pointers and lengths for each of the unencoded JWS elements
+ * \param jwk: public key
+ * \param content: lws_context
+ *
+ * Confirms the signature on a JWS.  Use if you have non-b64 plain JWS elements
+ * in a map... it'll make a temp b64 version needed for comparison.  See below
+ * for other variants.
+ *
+ * Returns 0 on match.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_sig_confirm_compact(struct lws_jws_map *map, struct lws_jwk *jwk,
+                           struct lws_context *context,
+                           char *temp, int *temp_len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_sig_confirm_compact_b64_map(struct lws_jws_map *map_b64,
+                                   struct lws_jwk *jwk,
+                                   struct lws_context *context,
+                                   char *temp, int *temp_len);
+
+/**
+ * lws_jws_sig_confirm_compact_b64() - check signature on b64 compact JWS
+ *
+ * \param in: pointer to b64 jose.payload[.hdr].sig
+ * \param len: bytes available at \p in
+ * \param map: map to take decoded non-b64 content
+ * \param jwk: public key
+ * \param content: lws_context
+ *
+ * Confirms the signature on a JWS.  Use if you have you have b64 compact layout
+ * (jose.payload.hdr.sig) as an aggregated string... it'll make a temp plain
+ * version needed for comparison.
+ *
+ * Returns 0 on match.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_sig_confirm_compact_b64(const char *in, size_t len,
+                               struct lws_jws_map *map,
+                               struct lws_jwk *jwk,
+                               struct lws_context *context,
+                               char *temp, int *temp_len);
+
+/**
+ * lws_jws_sig_confirm() - check signature on plain + b64 JWS elements
+ *
+ * \param map_b64: pointers and lengths for each of the b64-encoded JWS elements
+ * \param map: pointers and lengths for each of the unencoded JWS elements
+ * \param jwk: public key
+ * \param content: lws_context
+ *
+ * Confirms the signature on a JWS.  Use if you have you already have both b64
+ * compact layout (jose.payload.hdr.sig) and decoded JWS elements in maps.
+ *
+ * If you had the b64 string and called lws_jws_compact_decode() on it, you
+ * will end up with both maps, and can use this api version, saving needlessly
+ * regenerating any temp map.
+ *
+ * Returns 0 on match.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_sig_confirm(struct lws_jws_map *map_b64, /* b64-encoded */
+                   struct lws_jws_map *map,    /* non-b64 */
+                   struct lws_jwk *jwk, struct lws_context *context);
+
+/**
+ * lws_jws_sign_from_b64() - add b64 sig to b64 hdr + payload
+ *
+ * \param jose: jose header information
+ * \param jws: information to include in the signature
+ * \param b64_sig: output buffer for b64 signature
+ * \param sig_len: size of \p b64_sig output buffer
+ *
+ * This adds a b64-coded JWS signature of the b64-encoded protected header
+ * and b64-encoded payload, at \p b64_sig.  The signature will be as large
+ * as the N element of the RSA key when the RSA key is used, eg, 512 bytes for
+ * a 4096-bit key, and then b64-encoding on top.
+ *
+ * In some special cases, there is only payload to sign and no header, in that
+ * case \p b64_hdr may be NULL, and only the payload will be hashed before
+ * signing.
+ *
+ * Returns the length of the encoded signature written to \p b64_sig, or -1.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_sign_from_b64(struct lws_jose *jose, struct lws_jws *jws, char *b64_sig,
+                       size_t sig_len);
+
+/**
+ * lws_jws_compact_decode() - converts and maps compact serialization b64 sections
+ *
+ * \param in: the incoming compact serialized b64
+ * \param len: the length of the incoming compact serialized b64
+ * \param map: pointer to the results structure
+ * \param map_b64: NULL, or pointer to a second results structure taking block
+ *                information about the undecoded b64
+ * \param out: buffer to hold decoded results
+ * \param out_len: size of out in bytes
+ *
+ * Returns number of sections (2 if "none", else 3), or -1 if illegal.
+ *
+ * map is set to point to the start and hold the length of each decoded block.
+ * If map_b64 is non-NULL, then it's set with information about the input b64
+ * blocks.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_compact_decode(const char *in, int len, struct lws_jws_map *map,
+               struct lws_jws_map *map_b64, char *out, int *out_len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_compact_encode(struct lws_jws_map *map_b64, /* b64-encoded */
+                      const struct lws_jws_map *map,   /* non-b64 */
+                      char *buf, int *out_len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_sig_confirm_json(const char *in, size_t len,
+                        struct lws_jws *jws, struct lws_jwk *jwk,
+                        struct lws_context *context,
+                        char *temp, int *temp_len);
+
+/**
+ * lws_jws_write_flattened_json() - create flattened JSON sig
+ *
+ * \param jws: information to include in the signature
+ * \param flattened: output buffer for JSON
+ * \param len: size of \p flattened output buffer
+ *
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_write_flattened_json(struct lws_jws *jws, char *flattened, size_t len);
+
+/**
+ * lws_jws_write_compact() - create flattened JSON sig
+ *
+ * \param jws: information to include in the signature
+ * \param compact: output buffer for compact format
+ * \param len: size of \p flattened output buffer
+ *
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_write_compact(struct lws_jws *jws, char *compact, size_t len);
+
+
+
+/*
+ * below apis are not normally needed if dealing with whole JWS... they're
+ * useful for creating from scratch
+ */
+
+
+/**
+ * lws_jws_dup_element() - allocate space for an element and copy data into it
+ *
+ * \param map: map to create the element in
+ * \param idx: index of element in the map to create
+ * \param temp: space to allocate in
+ * \param temp_len: available space at temp
+ * \param in: data to duplicate into element
+ * \param in_len: length of data to duplicate
+ * \param actual_alloc: 0 for same as in_len, else actual allocation size
+ *
+ * Copies in_len from in to temp, if temp_len is sufficient.
+ *
+ * Returns 0 or -1 if not enough space in temp / temp_len.
+ *
+ * Over-allocation can be acheived by setting actual_alloc to the real
+ * allocation desired... in_len will be copied into it.
+ *
+ * *temp_len is reduced by actual_alloc if successful.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_dup_element(struct lws_jws_map *map, int idx,
+                   char *temp, int *temp_len, const void *in, size_t in_len,
+                   size_t actual_alloc);
+
+/**
+ * lws_jws_randomize_element() - create an element and fill with random
+ *
+ * \param context: lws_context used for random
+ * \param map: map to create the element in
+ * \param idx: index of element in the map to create
+ * \param temp: space to allocate in
+ * \param temp_len: available space at temp
+ * \param random_len: length of data to fill with random
+ * \param actual_alloc: 0 for same as random_len, else actual allocation size
+ *
+ * Randomize random_len bytes at temp, if temp_len is sufficient.
+ *
+ * Returns 0 or -1 if not enough space in temp / temp_len.
+ *
+ * Over-allocation can be acheived by setting actual_alloc to the real
+ * allocation desired... the first random_len will be filled with random.
+ *
+ * *temp_len is reduced by actual_alloc if successful.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_randomize_element(struct lws_context *context,
+                         struct lws_jws_map *map,
+                         int idx, char *temp, int *temp_len, size_t random_len,
+                         size_t actual_alloc);
+
+/**
+ * lws_jws_alloc_element() - create an element and reserve space for content
+ *
+ * \param map: map to create the element in
+ * \param idx: index of element in the map to create
+ * \param temp: space to allocate in
+ * \param temp_len: available space at temp
+ * \param len: logical length of element
+ * \param actual_alloc: 0 for same as len, else actual allocation size
+ *
+ * Allocate len bytes at temp, if temp_len is sufficient.
+ *
+ * Returns 0 or -1 if not enough space in temp / temp_len.
+ *
+ * Over-allocation can be acheived by setting actual_alloc to the real
+ * allocation desired... the element logical length will be set to len.
+ *
+ * *temp_len is reduced by actual_alloc if successful.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_alloc_element(struct lws_jws_map *map, int idx, char *temp,
+                     int *temp_len, size_t len, size_t actual_alloc);
+
+/**
+ * lws_jws_encode_b64_element() - create an b64-encoded element
+ *
+ * \param map: map to create the element in
+ * \param idx: index of element in the map to create
+ * \param temp: space to allocate in
+ * \param temp_len: available space at temp
+ * \param in: pointer to unencoded input
+ * \param in_len: length of unencoded input
+ *
+ * Allocate len bytes at temp, if temp_len is sufficient.
+ *
+ * Returns 0 or -1 if not enough space in temp / temp_len.
+ *
+ * Over-allocation can be acheived by setting actual_alloc to the real
+ * allocation desired... the element logical length will be set to len.
+ *
+ * *temp_len is reduced by actual_alloc if successful.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_encode_b64_element(struct lws_jws_map *map, int idx,
+                          char *temp, int *temp_len, const void *in,
+                          size_t in_len);
+
+
+/**
+ * lws_jws_b64_compact_map() - find block starts and lengths in compact b64
+ *
+ * \param in: pointer to b64 jose.payload[.hdr].sig
+ * \param len: bytes available at \p in
+ * \param map: output struct with pointers and lengths for each JWS element
+ *
+ * Scans a jose.payload[.hdr].sig b64 string and notes where the blocks start
+ * and their length into \p map.
+ *
+ * Returns number of blocks if OK.  May return <0 if malformed.
+ * May not fill all map entries.
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_b64_compact_map(const char *in, int len, struct lws_jws_map *map);
+
+
+/**
+ * lws_jws_base64_enc() - encode input data into b64url data
+ *
+ * \param in: the incoming plaintext
+ * \param in_len: the length of the incoming plaintext in bytes
+ * \param out: the buffer to store the b64url encoded data to
+ * \param out_max: the length of \p out in bytes
+ *
+ * Returns either -1 if problems, or the number of bytes written to \p out.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max);
+
+/**
+ * lws_jws_encode_section() - encode input data into b64url data,
+ *                             prepending . if not first
+ *
+ * \param in: the incoming plaintext
+ * \param in_len: the length of the incoming plaintext in bytes
+ * \param first: nonzero if the first section
+ * \param out: the buffer to store the b64url encoded data to
+ * \param out_max: the length of \p out in bytes
+ *
+ * Returns either -1 if problems, or the number of bytes written to \p out.
+ * If the section is not the first one, '.' is prepended.
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_jws_encode_section(const char *in, size_t in_len, int first, char **p,
+                      char *end);
+///@}
similarity index 64%
rename from lib/lejp.h
rename to include/libwebsockets/lws-lejp.h
index 7bf7ba7..3fd37dd 100644 (file)
@@ -1,8 +1,37 @@
-#include "libwebsockets.h"
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup lejp JSON parser
+ * ##JSON parsing related functions
+ * \ingroup lwsapi
+ *
+ * LEJP is an extremely lightweight JSON stream parser included in lws.
+ */
+//@{
 struct lejp_ctx;
 
-#ifndef ARRAY_SIZE
-#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0]))
+#if !defined(LWS_ARRAY_SIZE)
+#define LWS_ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0]))
 #endif
 #define LEJP_FLAG_WS_KEEP 64
 #define LEJP_FLAG_WS_COMMENTLINE 32
@@ -77,7 +106,7 @@ enum lejp_callbacks {
        LEJPCB_ARRAY_END        = 15,
 
        LEJPCB_OBJECT_START     = 16,
-       LEJPCB_OBJECT_END       = 17
+       LEJPCB_OBJECT_END       = 17,
 };
 
 /**
@@ -140,10 +169,13 @@ enum lejp_callbacks {
  *
  *  LEJPCB_OBJECT_END: An object ended
  */
-LWS_EXTERN char _lejp_callback(struct lejp_ctx *ctx, char reason);
+LWS_EXTERN signed char _lejp_callback(struct lejp_ctx *ctx, char reason);
 
-typedef char (*lejp_callback)(struct lejp_ctx *ctx, char reason);
+typedef signed char (*lejp_callback)(struct lejp_ctx *ctx, char reason);
 
+#ifndef LEJP_MAX_PARSING_STACK_DEPTH
+#define LEJP_MAX_PARSING_STACK_DEPTH 5
+#endif
 #ifndef LEJP_MAX_DEPTH
 #define LEJP_MAX_DEPTH 12
 #endif
@@ -155,7 +187,7 @@ typedef char (*lejp_callback)(struct lejp_ctx *ctx, char reason);
 #endif
 #ifndef LEJP_STRING_CHUNK
 /* must be >= 30 to assemble floats */
-#define LEJP_STRING_CHUNK 255
+#define LEJP_STRING_CHUNK 254
 #endif
 
 enum num_flags {
@@ -172,51 +204,62 @@ struct _lejp_stack {
        char b; /* user bitfield */
 };
 
+struct _lejp_parsing_stack {
+       void *user;     /* private to the stack level */
+       signed char (*callback)(struct lejp_ctx *ctx, char reason);
+       const char * const *paths;
+       uint8_t count_paths;
+       uint8_t ppos;
+       uint8_t path_match;
+};
+
 struct lejp_ctx {
 
        /* sorted by type for most compact alignment
         *
         * pointers
         */
-
-       char (*callback)(struct lejp_ctx *ctx, char reason);
        void *user;
-       const char * const *paths;
 
        /* arrays */
 
+       struct _lejp_parsing_stack pst[LEJP_MAX_PARSING_STACK_DEPTH];
        struct _lejp_stack st[LEJP_MAX_DEPTH];
-       unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */
-       unsigned short wild[LEJP_MAX_INDEX_DEPTH]; /* index array */
+       uint16_t i[LEJP_MAX_INDEX_DEPTH]; /* index array */
+       uint16_t wild[LEJP_MAX_INDEX_DEPTH]; /* index array */
        char path[LEJP_MAX_PATH];
-       char buf[LEJP_STRING_CHUNK];
+       char buf[LEJP_STRING_CHUNK + 1];
+
+       /* size_t */
+
+       size_t path_stride; /* 0 means default ptr size, else stride */
 
        /* int */
 
-       unsigned int line;
+       uint32_t line;
 
        /* short */
 
-       unsigned short uni;
+       uint16_t uni;
 
        /* char */
 
-       unsigned char npos;
-       unsigned char dcount;
-       unsigned char f;
-       unsigned char sp; /* stack head */
-       unsigned char ipos; /* index stack depth */
-       unsigned char ppos;
-       unsigned char count_paths;
-       unsigned char path_match;
-       unsigned char path_match_len;
-       unsigned char wildcount;
+       uint8_t npos;
+       uint8_t dcount;
+       uint8_t f;
+       uint8_t sp; /* stack head */
+       uint8_t ipos; /* index stack depth */
+       uint8_t count_paths;
+       uint8_t path_match;
+       uint8_t path_match_len;
+       uint8_t wildcount;
+       uint8_t pst_sp; /* parsing stack head */
 };
 
 LWS_VISIBLE LWS_EXTERN void
 lejp_construct(struct lejp_ctx *ctx,
-              char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
-              const char * const *paths, unsigned char paths_count);
+              signed char (*callback)(struct lejp_ctx *ctx, char reason),
+              void *user, const char * const *paths, unsigned char paths_count);
 
 LWS_VISIBLE LWS_EXTERN void
 lejp_destruct(struct lejp_ctx *ctx);
@@ -226,7 +269,30 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len);
 
 LWS_VISIBLE LWS_EXTERN void
 lejp_change_callback(struct lejp_ctx *ctx,
-                      char (*callback)(struct lejp_ctx *ctx, char reason));
+                    signed char (*callback)(struct lejp_ctx *ctx, char reason));
+
+/*
+ * push the current paths / paths_count and lejp_cb to a stack in the ctx, and
+ * start using the new ones
+ */
+LWS_VISIBLE LWS_EXTERN int
+lejp_parser_push(struct lejp_ctx *ctx, void *user, const char * const *paths,
+                unsigned char paths_count, lejp_callback lejp_cb);
+
+/*
+ * pop the previously used paths / paths_count and lejp_cb, and continue
+ * parsing using those as before
+ */
+LWS_VISIBLE LWS_EXTERN int
+lejp_parser_pop(struct lejp_ctx *ctx);
+
+/* exported for use when reevaluating a path for use with a subcontext */
+LWS_VISIBLE LWS_EXTERN void
+lejp_check_path_match(struct lejp_ctx *ctx);
 
 LWS_VISIBLE LWS_EXTERN int
 lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len);
+
+LWS_VISIBLE LWS_EXTERN const char *
+lejp_error_to_string(int e);
+//@}
diff --git a/include/libwebsockets/lws-logs.h b/include/libwebsockets/lws-logs.h
new file mode 100644 (file)
index 0000000..d71f946
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup log Logging
+ *
+ * ##Logging
+ *
+ * Lws provides flexible and filterable logging facilities, which can be
+ * used inside lws and in user code.
+ *
+ * Log categories may be individually filtered bitwise, and directed to built-in
+ * sinks for syslog-compatible logging, or a user-defined function.
+ */
+///@{
+
+enum lws_log_levels {
+       LLL_ERR         = 1 << 0,
+       LLL_WARN        = 1 << 1,
+       LLL_NOTICE      = 1 << 2,
+       LLL_INFO        = 1 << 3,
+       LLL_DEBUG       = 1 << 4,
+       LLL_PARSER      = 1 << 5,
+       LLL_HEADER      = 1 << 6,
+       LLL_EXT         = 1 << 7,
+       LLL_CLIENT      = 1 << 8,
+       LLL_LATENCY     = 1 << 9,
+       LLL_USER        = 1 << 10,
+       LLL_THREAD      = 1 << 11,
+
+       LLL_COUNT       = 12 /* set to count of valid flags */
+};
+
+/**
+ * lwsl_timestamp: generate logging timestamp string
+ *
+ * \param level:       logging level
+ * \param p:           char * buffer to take timestamp
+ * \param len: length of p
+ *
+ * returns length written in p
+ */
+LWS_VISIBLE LWS_EXTERN int
+lwsl_timestamp(int level, char *p, int len);
+
+#if defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NETWORK)
+#define _lws_log(aaa, ...) SMSG(__VA_ARGS__)
+#else
+LWS_VISIBLE LWS_EXTERN void _lws_log(int filter, const char *format, ...) LWS_FORMAT(2);
+LWS_VISIBLE LWS_EXTERN void _lws_logv(int filter, const char *format, va_list vl);
+#endif
+
+/* these guys are unconditionally included */
+
+#define lwsl_err(...) _lws_log(LLL_ERR, __VA_ARGS__)
+#define lwsl_user(...) _lws_log(LLL_USER, __VA_ARGS__)
+
+#if !defined(LWS_WITH_NO_LOGS)
+/* notice and warn are usually included by being compiled in */
+#define lwsl_warn(...) _lws_log(LLL_WARN, __VA_ARGS__)
+#define lwsl_notice(...) _lws_log(LLL_NOTICE, __VA_ARGS__)
+#endif
+/*
+ *  weaker logging can be deselected by telling CMake to build in RELEASE mode
+ *  that gets rid of the overhead of checking while keeping _warn and _err
+ *  active
+ */
+
+#if defined(_DEBUG)
+#if defined(LWS_WITH_NO_LOGS)
+/* notice, warn and log are always compiled in */
+#define lwsl_warn(...) _lws_log(LLL_WARN, __VA_ARGS__)
+#define lwsl_notice(...) _lws_log(LLL_NOTICE, __VA_ARGS__)
+#endif
+#define lwsl_info(...) _lws_log(LLL_INFO, __VA_ARGS__)
+#define lwsl_debug(...) _lws_log(LLL_DEBUG, __VA_ARGS__)
+#define lwsl_parser(...) _lws_log(LLL_PARSER, __VA_ARGS__)
+#define lwsl_header(...)  _lws_log(LLL_HEADER, __VA_ARGS__)
+#define lwsl_ext(...)  _lws_log(LLL_EXT, __VA_ARGS__)
+#define lwsl_client(...) _lws_log(LLL_CLIENT, __VA_ARGS__)
+#define lwsl_latency(...) _lws_log(LLL_LATENCY, __VA_ARGS__)
+#define lwsl_thread(...) _lws_log(LLL_THREAD, __VA_ARGS__)
+
+#else /* no debug */
+#if defined(LWS_WITH_NO_LOGS)
+#define lwsl_warn(...) do {} while(0)
+#define lwsl_notice(...) do {} while(0)
+#endif
+#define lwsl_info(...) do {} while(0)
+#define lwsl_debug(...) do {} while(0)
+#define lwsl_parser(...) do {} while(0)
+#define lwsl_header(...) do {} while(0)
+#define lwsl_ext(...) do {} while(0)
+#define lwsl_client(...) do {} while(0)
+#define lwsl_latency(...) do {} while(0)
+#define lwsl_thread(...) do {} while(0)
+
+#endif
+
+
+#define lwsl_hexdump_err(...) lwsl_hexdump_level(LLL_ERR, __VA_ARGS__)
+#define lwsl_hexdump_warn(...) lwsl_hexdump_level(LLL_WARN, __VA_ARGS__)
+#define lwsl_hexdump_notice(...) lwsl_hexdump_level(LLL_NOTICE, __VA_ARGS__)
+#define lwsl_hexdump_info(...) lwsl_hexdump_level(LLL_INFO, __VA_ARGS__)
+#define lwsl_hexdump_debug(...) lwsl_hexdump_level(LLL_DEBUG, __VA_ARGS__)
+
+/**
+ * lwsl_hexdump_level() - helper to hexdump a buffer at a selected debug level
+ *
+ * \param level: one of LLL_ constants
+ * \param vbuf: buffer start to dump
+ * \param len: length of buffer to dump
+ *
+ * If \p level is visible, does a nice hexdump -C style dump of \p vbuf for
+ * \p len bytes.  This can be extremely convenient while debugging.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsl_hexdump_level(int level, const void *vbuf, size_t len);
+
+/**
+ * lwsl_hexdump() - helper to hexdump a buffer (DEBUG builds only)
+ *
+ * \param buf: buffer start to dump
+ * \param len: length of buffer to dump
+ *
+ * Calls through to lwsl_hexdump_level(LLL_DEBUG, ... for compatability.
+ * It's better to use lwsl_hexdump_level(level, ... directly so you can control
+ * the visibility.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsl_hexdump(const void *buf, size_t len);
+
+/**
+ * lws_is_be() - returns nonzero if the platform is Big Endian
+ */
+static LWS_INLINE int lws_is_be(void) {
+       const int probe = ~0xff;
+
+       return *(const char *)&probe;
+}
+
+/**
+ * lws_set_log_level() - Set the logging bitfield
+ * \param level:       OR together the LLL_ debug contexts you want output from
+ * \param log_emit_function:   NULL to leave it as it is, or a user-supplied
+ *                     function to perform log string emission instead of
+ *                     the default stderr one.
+ *
+ *     log level defaults to "err", "warn" and "notice" contexts enabled and
+ *     emission on stderr.  If stderr is a tty (according to isatty()) then
+ *     the output is coloured according to the log level using ANSI escapes.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_set_log_level(int level,
+                 void (*log_emit_function)(int level, const char *line));
+
+/**
+ * lwsl_emit_syslog() - helper log emit function writes to system log
+ *
+ * \param level: one of LLL_ log level indexes
+ * \param line: log string
+ *
+ * You use this by passing the function pointer to lws_set_log_level(), to set
+ * it as the log emit function, it is not called directly.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsl_emit_syslog(int level, const char *line);
+
+/**
+ * lwsl_emit_stderr() - helper log emit function writes to stderr
+ *
+ * \param level: one of LLL_ log level indexes
+ * \param line: log string
+ *
+ * You use this by passing the function pointer to lws_set_log_level(), to set
+ * it as the log emit function, it is not called directly.
+ *
+ * It prepends a system timestamp like [2018/11/13 07:41:57:3989]
+ *
+ * If stderr is a tty, then ansi colour codes are added.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsl_emit_stderr(int level, const char *line);
+
+/**
+ * lwsl_emit_stderr_notimestamp() - helper log emit function writes to stderr
+ *
+ * \param level: one of LLL_ log level indexes
+ * \param line: log string
+ *
+ * You use this by passing the function pointer to lws_set_log_level(), to set
+ * it as the log emit function, it is not called directly.
+ *
+ * If stderr is a tty, then ansi colour codes are added.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsl_emit_stderr_notimestamp(int level, const char *line);
+
+/**
+ * lwsl_visible() - returns true if the log level should be printed
+ *
+ * \param level: one of LLL_ log level indexes
+ *
+ * This is useful if you have to do work to generate the log content, you
+ * can skip the work if the log level used to print it is not actually
+ * enabled at runtime.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lwsl_visible(int level);
+
+///@}
diff --git a/include/libwebsockets/lws-lwsac.h b/include/libwebsockets/lws-lwsac.h
new file mode 100644 (file)
index 0000000..a45f420
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * libwebsockets - lws alloc chunk
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup log lwsac
+ *
+ * ##Allocated Chunks
+ *
+ * If you know you will be allocating a large, unknown number of same or
+ * differently sized objects, it's certainly possible to do it with libc
+ * malloc.  However the allocation cost in time and memory overhead can
+ * add up, and deallocation means walking the structure of every object and
+ * freeing them in turn.
+ *
+ * lwsac (LWS Allocated Chunks) allocates chunks intended to be larger
+ * than your objects (4000 bytes by default) which you linearly allocate from
+ * using lwsac_use().
+ *
+ * If your next request won't fit in the current chunk, a new chunk is added
+ * to the chain of chunks and the allocaton done from there.  If the request
+ * is larger than the chunk size, an oversize chunk is created to satisfy it.
+ *
+ * When you are finished with the allocations, you call lwsac_free() and
+ * free all the *chunks*.  So you may have thousands of objects in the chunks,
+ * but they are all destroyed with the chunks without having to deallocate them
+ * one by one pointlessly.
+ */
+///@{
+
+struct lwsac;
+typedef unsigned char * lwsac_cached_file_t;
+
+
+#define lws_list_ptr_container(P,T,M) ((T *)((char *)(P) - offsetof(T, M)))
+
+/*
+ * linked-list helper that's commonly useful to manage lists of things
+ * allocated using lwsac.
+ *
+ * These lists point to their corresponding "next" member in the target, NOT
+ * the original containing struct.  To get the containing struct, you must use
+ * lws_list_ptr_container() to convert.
+ *
+ * It's like that because it means we no longer have to have the next pointer
+ * at the start of the struct, and we can have the same struct on multiple
+ * linked-lists with everything held in the struct itself.
+ */
+typedef void * lws_list_ptr;
+
+/*
+ * optional sorting callback called by lws_list_ptr_insert() to sort the right
+ * things inside the opqaue struct being sorted / inserted on the list.
+ */
+typedef int (*lws_list_ptr_sort_func_t)(lws_list_ptr a, lws_list_ptr b);
+
+#define lws_list_ptr_advance(_lp) _lp = *((void **)_lp)
+
+/* sort may be NULL if you don't care about order */
+LWS_VISIBLE LWS_EXTERN void
+lws_list_ptr_insert(lws_list_ptr *phead, lws_list_ptr *add,
+                   lws_list_ptr_sort_func_t sort);
+
+
+/**
+ * lwsac_use - allocate / use some memory from a lwsac
+ *
+ * \param head: pointer to the lwsac list object
+ * \param ensure: the number of bytes we want to use
+ * \param chunk_size: 0, or the size of the chunk to (over)allocate if
+ *                     what we want won't fit in the current tail chunk.  If
+ *                     0, the default value of 4000 is used. If ensure is
+ *                     larger, it is used instead.
+ *
+ * This also serves to init the lwsac if *head is NULL.  Basically it does
+ * whatever is necessary to return you a pointer to ensure bytes of memory
+ * reserved for the caller.
+ *
+ * Returns NULL if OOM.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size);
+
+/**
+ * lwsac_use_zero - allocate / use some memory from a lwsac and zero it
+ *
+ * \param head: pointer to the lwsac list object
+ * \param ensure: the number of bytes we want to use
+ * \param chunk_size: 0, or the size of the chunk to (over)allocate if
+ *                     what we want won't fit in the current tail chunk.  If
+ *                     0, the default value of 4000 is used. If ensure is
+ *                     larger, it is used instead.
+ *
+ * This also serves to init the lwsac if *head is NULL.  Basically it does
+ * whatever is necessary to return you a pointer to ensure bytes of memory
+ * reserved for the caller.
+ *
+ * \p ensure bytes at the return address are zeroed if the allocation succeeded.
+ *
+ * Returns NULL if OOM.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lwsac_use_zero(struct lwsac **head, size_t ensure, size_t chunk_size);
+
+/**
+ * lwsac_use - allocate / use some memory from a lwsac
+ *
+ * \param head: pointer to the lwsac list object
+ * \param ensure: the number of bytes we want to use, which must be zeroed
+ * \param chunk_size: 0, or the size of the chunk to (over)allocate if
+ *                     what we want won't fit in the current tail chunk.  If
+ *                     0, the default value of 4000 is used. If ensure is
+ *                     larger, it is used instead.
+ *
+ * Same as lwsac_use(), but \p ensure bytes of memory at the return address
+ * are zero'd before returning.
+ *
+ * Returns NULL if OOM.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lwsac_use_zeroed(struct lwsac **head, size_t ensure, size_t chunk_size);
+
+/**
+ * lwsac_free - deallocate all chunks in the lwsac and set head NULL
+ *
+ * \param head: pointer to the lwsac list object
+ *
+ * This deallocates all chunks in the lwsac, then sets *head to NULL.  All
+ * lwsac_use() pointers are invalidated in one hit without individual frees.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsac_free(struct lwsac **head);
+
+/*
+ * Optional helpers useful for where consumers may need to defer destruction
+ * until all consumers are finished with the lwsac
+ */
+
+/**
+ * lwsac_detach() - destroy an lwsac unless somebody else is referencing it
+ *
+ * \param head: pointer to the lwsac list object
+ *
+ * The creator of the lwsac can all this instead of lwsac_free() when it itself
+ * has finished with the lwsac, but other code may be consuming it.
+ *
+ * If there are no other references, the lwsac is destroyed, *head is set to
+ * NULL and that's the end; however if something else has called
+ * lwsac_reference() on the lwsac, it simply returns.  When lws_unreference()
+ * is called and no references are left, it will be destroyed then.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsac_detach(struct lwsac **head);
+
+/**
+ * lwsac_reference() - increase the lwsac reference count
+ *
+ * \param head: pointer to the lwsac list object
+ *
+ * Increment the reference count on the lwsac to defer destruction.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsac_reference(struct lwsac *head);
+
+/**
+ * lwsac_reference() - increase the lwsac reference count
+ *
+ * \param head: pointer to the lwsac list object
+ *
+ * Decrement the reference count on the lwsac... if it reached 0 on a detached
+ * lwsac then the lwsac is immediately destroyed and *head set to NULL.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsac_unreference(struct lwsac **head);
+
+
+/* helpers to keep a file cached in memory */
+
+LWS_VISIBLE LWS_EXTERN void
+lwsac_use_cached_file_start(lwsac_cached_file_t cache);
+
+LWS_VISIBLE LWS_EXTERN void
+lwsac_use_cached_file_end(lwsac_cached_file_t *cache);
+
+LWS_VISIBLE LWS_EXTERN void
+lwsac_use_cached_file_detach(lwsac_cached_file_t *cache);
+
+LWS_VISIBLE LWS_EXTERN int
+lwsac_cached_file(const char *filepath, lwsac_cached_file_t *cache,
+                 size_t *len);
+
+/* more advanced helpers */
+
+LWS_VISIBLE LWS_EXTERN size_t
+lwsac_sizeof(void);
+
+LWS_VISIBLE LWS_EXTERN size_t
+lwsac_get_tail_pos(struct lwsac *lac);
+
+LWS_VISIBLE LWS_EXTERN struct lwsac *
+lwsac_get_next(struct lwsac *lac);
+
+LWS_VISIBLE LWS_EXTERN size_t
+lwsac_align(size_t length);
+
+LWS_VISIBLE LWS_EXTERN void
+lwsac_info(struct lwsac *head);
+
+LWS_VISIBLE LWS_EXTERN uint64_t
+lwsac_total_alloc(struct lwsac *head);
+
+///@}
diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h
new file mode 100644 (file)
index 0000000..e8bd90e
--- /dev/null
@@ -0,0 +1,926 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup misc Miscellaneous APIs
+* ##Miscellaneous APIs
+*
+* Various APIs outside of other categories
+*/
+///@{
+
+/**
+ * lws_start_foreach_ll(): linkedlist iterator helper start
+ *
+ * \param type: type of iteration, eg, struct xyz *
+ * \param it: iterator var name to create
+ * \param start: start of list
+ *
+ * This helper creates an iterator and starts a while (it) {
+ * loop.  The iterator runs through the linked list starting at start and
+ * ends when it gets a NULL.
+ * The while loop should be terminated using lws_start_foreach_ll().
+ */
+#define lws_start_foreach_ll(type, it, start)\
+{ \
+       type it = start; \
+       while (it) {
+
+/**
+ * lws_end_foreach_ll(): linkedlist iterator helper end
+ *
+ * \param it: same iterator var name given when starting
+ * \param nxt: member name in the iterator pointing to next list element
+ *
+ * This helper is the partner for lws_start_foreach_ll() that ends the
+ * while loop.
+ */
+
+#define lws_end_foreach_ll(it, nxt) \
+               it = it->nxt; \
+       } \
+}
+
+/**
+ * lws_start_foreach_ll_safe(): linkedlist iterator helper start safe against delete
+ *
+ * \param type: type of iteration, eg, struct xyz *
+ * \param it: iterator var name to create
+ * \param start: start of list
+ * \param nxt: member name in the iterator pointing to next list element
+ *
+ * This helper creates an iterator and starts a while (it) {
+ * loop.  The iterator runs through the linked list starting at start and
+ * ends when it gets a NULL.
+ * The while loop should be terminated using lws_end_foreach_ll_safe().
+ * Performs storage of next increment for situations where iterator can become invalidated
+ * during iteration.
+ */
+#define lws_start_foreach_ll_safe(type, it, start, nxt)\
+{ \
+       type it = start; \
+       while (it) { \
+               type next_##it = it->nxt;
+
+/**
+ * lws_end_foreach_ll_safe(): linkedlist iterator helper end (pre increment storage)
+ *
+ * \param it: same iterator var name given when starting
+ *
+ * This helper is the partner for lws_start_foreach_ll_safe() that ends the
+ * while loop. It uses the precreated next_ variable already stored during
+ * start.
+ */
+
+#define lws_end_foreach_ll_safe(it) \
+               it = next_##it; \
+       } \
+}
+
+/**
+ * lws_start_foreach_llp(): linkedlist pointer iterator helper start
+ *
+ * \param type: type of iteration, eg, struct xyz **
+ * \param it: iterator var name to create
+ * \param start: start of list
+ *
+ * This helper creates an iterator and starts a while (it) {
+ * loop.  The iterator runs through the linked list starting at the
+ * address of start and ends when it gets a NULL.
+ * The while loop should be terminated using lws_start_foreach_llp().
+ *
+ * This helper variant iterates using a pointer to the previous linked-list
+ * element.  That allows you to easily delete list members by rewriting the
+ * previous pointer to the element's next pointer.
+ */
+#define lws_start_foreach_llp(type, it, start)\
+{ \
+       type it = &(start); \
+       while (*(it)) {
+
+#define lws_start_foreach_llp_safe(type, it, start, nxt)\
+{ \
+       type it = &(start); \
+       type next; \
+       while (*(it)) { \
+               next = &((*(it))->nxt); \
+
+/**
+ * lws_end_foreach_llp(): linkedlist pointer iterator helper end
+ *
+ * \param it: same iterator var name given when starting
+ * \param nxt: member name in the iterator pointing to next list element
+ *
+ * This helper is the partner for lws_start_foreach_llp() that ends the
+ * while loop.
+ */
+
+#define lws_end_foreach_llp(it, nxt) \
+               it = &(*(it))->nxt; \
+       } \
+}
+
+#define lws_end_foreach_llp_safe(it) \
+               it = next; \
+       } \
+}
+
+#define lws_ll_fwd_insert(\
+       ___new_object,  /* pointer to new object */ \
+       ___m_list,      /* member for next list object ptr */ \
+       ___list_head    /* list head */ \
+               ) {\
+               ___new_object->___m_list = ___list_head; \
+               ___list_head = ___new_object; \
+       }
+
+#define lws_ll_fwd_remove(\
+       ___type,        /* type of listed object */ \
+       ___m_list,      /* member for next list object ptr */ \
+       ___target,      /* object to remove from list */ \
+       ___list_head    /* list head */ \
+       ) { \
+                lws_start_foreach_llp(___type **, ___ppss, ___list_head) { \
+                        if (*___ppss == ___target) { \
+                                *___ppss = ___target->___m_list; \
+                                break; \
+                        } \
+                } lws_end_foreach_llp(___ppss, ___m_list); \
+       }
+
+/*
+ * doubly linked-list
+ */
+
+#if defined (LWS_WITH_DEPRECATED_LWS_DLL)
+
+/*
+ * This is going away in v4.1.  You can set the cmake option above to keep it
+ * around temporarily.  Migrate your stuff to the more capable and robust
+ * lws_dll2 below
+ */
+
+struct lws_dll {
+       struct lws_dll *prev;
+       struct lws_dll *next;
+};
+
+/*
+ * these all point to the composed list objects... you have to use the
+ * lws_container_of() helper to recover the start of the containing struct
+ */
+
+#define lws_dll_add_front lws_dll_add_head
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll_add_head(struct lws_dll *d, struct lws_dll *phead);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll_add_tail(struct lws_dll *d, struct lws_dll *phead);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll_insert(struct lws_dll *d, struct lws_dll *target,
+              struct lws_dll *phead, int before);
+
+static LWS_INLINE struct lws_dll *
+lws_dll_get_head(struct lws_dll *phead) { return phead->next; }
+
+static LWS_INLINE struct lws_dll *
+lws_dll_get_tail(struct lws_dll *phead) { return phead->prev; }
+
+/*
+ * caution, this doesn't track the tail in the head struct.  Use
+ * lws_dll_remove_track_tail() instead of this if you want tail tracking.  Using
+ * this means you can't use lws_dll_add_tail() amd
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_dll_remove(struct lws_dll *d) LWS_WARN_DEPRECATED;
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll_remove_track_tail(struct lws_dll *d, struct lws_dll *phead);
+
+/* another way to do lws_start_foreach_dll_safe() on a list via a cb */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_dll_foreach_safe(struct lws_dll *phead, void *user,
+                    int (*cb)(struct lws_dll *d, void *user));
+
+#define lws_dll_is_detached(___dll, __head) \
+       (!(___dll)->prev && !(___dll)->next && (__head)->prev != (___dll))
+
+#endif
+
+/*
+ * lws_dll2_owner / lws_dll2 : more capable version of lws_dll.  Differences:
+ *
+ *  - there's an explicit lws_dll2_owner struct which holds head, tail and
+ *    count of members.
+ *
+ *  - list members all hold a pointer to their owner.  So user code does not
+ *    have to track anything about exactly what lws_dll2_owner list the object
+ *    is a member of.
+ *
+ *  - you can use lws_dll unless you want the member count or the ability to
+ *    not track exactly which list it's on.
+ *
+ *  - layout is compatible with lws_dll (but lws_dll apis will not update the
+ *    new stuff)
+ */
+
+
+struct lws_dll2;
+struct lws_dll2_owner;
+
+typedef struct lws_dll2 {
+       struct lws_dll2         *prev;
+       struct lws_dll2         *next;
+       struct lws_dll2_owner   *owner;
+} lws_dll2_t;
+
+typedef struct lws_dll2_owner {
+       struct lws_dll2         *tail;
+       struct lws_dll2         *head;
+
+       uint32_t                count;
+} lws_dll2_owner_t;
+
+static LWS_INLINE int
+lws_dll2_is_detached(const struct lws_dll2 *d) { return !d->owner; }
+
+static LWS_INLINE const struct lws_dll2_owner *
+lws_dll2_owner(const struct lws_dll2 *d) { return d->owner; }
+
+static LWS_INLINE struct lws_dll2 *
+lws_dll2_get_head(struct lws_dll2_owner *owner) { return owner->head; }
+
+static LWS_INLINE struct lws_dll2 *
+lws_dll2_get_tail(struct lws_dll2_owner *owner) { return owner->tail; }
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_add_head(struct lws_dll2 *d, struct lws_dll2_owner *owner);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_remove(struct lws_dll2 *d);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_dll2_foreach_safe(struct lws_dll2_owner *owner, void *user,
+                     int (*cb)(struct lws_dll2 *d, void *user));
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_clear(struct lws_dll2 *d);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_owner_clear(struct lws_dll2_owner *d);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_add_before(struct lws_dll2 *d, struct lws_dll2 *after);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_dll2_add_sorted(lws_dll2_t *d, lws_dll2_owner_t *own,
+                   int (*compare)(const lws_dll2_t *d, const lws_dll2_t *i));
+
+#if defined(_DEBUG)
+void
+lws_dll2_describe(struct lws_dll2_owner *owner, const char *desc);
+#else
+#define lws_dll2_describe(x, y)
+#endif
+
+/*
+ * these are safe against the current container object getting deleted,
+ * since the hold his next in a temp and go to that next.  ___tmp is
+ * the temp.
+ */
+
+#define lws_start_foreach_dll_safe(___type, ___it, ___tmp, ___start) \
+{ \
+       ___type ___it = ___start; \
+       while (___it) { \
+               ___type ___tmp = (___it)->next;
+
+#define lws_end_foreach_dll_safe(___it, ___tmp) \
+               ___it = ___tmp; \
+       } \
+}
+
+#define lws_start_foreach_dll(___type, ___it, ___start) \
+{ \
+       ___type ___it = ___start; \
+       while (___it) {
+
+#define lws_end_foreach_dll(___it) \
+               ___it = (___it)->next; \
+       } \
+}
+
+struct lws_buflist;
+
+/**
+ * lws_buflist_append_segment(): add buffer to buflist at head
+ *
+ * \param head: list head
+ * \param buf: buffer to stash
+ * \param len: length of buffer to stash
+ *
+ * Returns -1 on OOM, 1 if this was the first segment on the list, and 0 if
+ * it was a subsequent segment.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf,
+                          size_t len);
+/**
+ * lws_buflist_next_segment_len(): number of bytes left in current segment
+ *
+ * \param head: list head
+ * \param buf: if non-NULL, *buf is written with the address of the start of
+ *             the remaining data in the segment
+ *
+ * Returns the number of bytes left in the current segment.  0 indicates
+ * that the buflist is empty (there are no segments on the buflist).
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf);
+
+/**
+ * lws_buflist_use_segment(): remove len bytes from the current segment
+ *
+ * \param head: list head
+ * \param len: number of bytes to mark as used
+ *
+ * If len is less than the remaining length of the current segment, the position
+ * in the current segment is simply advanced and it returns.
+ *
+ * If len uses up the remaining length of the current segment, then the segment
+ * is deleted and the list head moves to the next segment if any.
+ *
+ * Returns the number of bytes left in the current segment.  0 indicates
+ * that the buflist is empty (there are no segments on the buflist).
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_buflist_use_segment(struct lws_buflist **head, size_t len);
+
+/**
+ * lws_buflist_destroy_all_segments(): free all segments on the list
+ *
+ * \param head: list head
+ *
+ * This frees everything on the list unconditionally.  *head is always
+ * NULL after this.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_buflist_destroy_all_segments(struct lws_buflist **head);
+
+void
+lws_buflist_describe(struct lws_buflist **head, void *id);
+
+/**
+ * lws_ptr_diff(): helper to report distance between pointers as an int
+ *
+ * \param head: the pointer with the larger address
+ * \param tail: the pointer with the smaller address
+ *
+ * This helper gives you an int representing the number of bytes further
+ * forward the first pointer is compared to the second pointer.
+ */
+#define lws_ptr_diff(head, tail) \
+                       ((int)((char *)(head) - (char *)(tail)))
+
+/**
+ * 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_strncpy(): strncpy that guarantees NUL on truncated copy
+ *
+ * \param dest: destination buffer
+ * \param src: source buffer
+ * \param size: bytes left in destination buffer
+ *
+ * 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 char *
+lws_strncpy(char *dest, const char *src, size_t size);
+
+/**
+ * lws_hex_to_byte_array(): convert hex string like 0123456789ab into byte data
+ *
+ * \param h: incoming NUL-terminated hex string
+ * \param dest: array to fill with binary decodes of hex pairs from h
+ * \param max: maximum number of bytes dest can hold, must be at least half
+ *             the size of strlen(h)
+ *
+ * This converts hex strings into an array of 8-bit representations, ie the
+ * input "abcd" produces two bytes of value 0xab and 0xcd.
+ *
+ * Returns number of bytes produced into \p dest, or -1 on error.
+ *
+ * Errors include non-hex chars and an odd count of hex chars in the input
+ * string.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_hex_to_byte_array(const char *h, uint8_t *dest, int max);
+
+/*
+ * lws_timingsafe_bcmp(): constant time memcmp
+ *
+ * \param a: first buffer
+ * \param b: second buffer
+ * \param len: count of bytes to compare
+ *
+ * Return 0 if the two buffers are the same, else nonzero.
+ *
+ * Always compares all of the buffer before returning, so it can't be used as
+ * a timing oracle.
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_timingsafe_bcmp(const void *a, const void *b, uint32_t len);
+
+/**
+ * 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
+ *
+ * Fills buf with len bytes of random.  Returns the number of bytes set, if
+ * not equal to len, then getting the random failed.
+ */
+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_set_wsi_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
+ *
+ * You may also refer to unix socket addresses, using a '+' at the start of
+ * the address.  In this case, the address should end with ':', which is
+ * treated as the separator between the address and path (the normal separator
+ * '/' is a valid part of the socket path).  Eg,
+ *
+ * http://+/var/run/mysocket:/my/path
+ *
+ * If the first character after the + is '@', it's interpreted by lws client
+ * processing as meaning to use linux abstract namespace sockets, the @ is
+ * replaced with a '\0' before use.
+ */
+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_cmdline_option():       simple commandline parser
+ *
+ * \param argc:                count of argument strings
+ * \param argv:                argument strings
+ * \param val:         string to find
+ *
+ * Returns NULL if the string \p val is not found in the arguments.
+ *
+ * If it is found, then it returns a pointer to the next character after \p val.
+ * So if \p val is "-d", then for the commandlines "myapp -d15" and
+ * "myapp -d 15", in both cases the return will point to the "15".
+ *
+ * In the case there is no argument, like "myapp -d", the return will
+ * either point to the '\\0' at the end of -d, or to the start of the
+ * next argument, ie, will be non-NULL.
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_cmdline_option(int argc, const char **argv, const char *val);
+
+/**
+ * lws_now_secs(): return seconds since 1970-1-1
+ */
+LWS_VISIBLE LWS_EXTERN unsigned long
+lws_now_secs(void);
+
+/**
+ * lws_now_usecs(): return useconds since 1970-1-1
+ */
+LWS_VISIBLE LWS_EXTERN lws_usec_t
+lws_now_usecs(void);
+
+/**
+ * lws_get_context - Allow getting 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_vhost_listen_port - Find out the port number a vhost is listening on
+ *
+ * In the case you passed 0 for the port number at context creation time, you
+ * can discover the port number that was actually chosen for the vhost using
+ * this api.
+ *
+ * \param vhost:       Vhost to get listen port from
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_get_vhost_listen_port(struct lws_vhost *vhost);
+
+/**
+ * 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_get_effective_uid_gid() - find out eventual uid and gid while still root
+ *
+ * \param context: lws context
+ * \param uid: pointer to uid result
+ * \param gid: pointer to gid result
+ *
+ * This helper allows you to find out what the uid and gid for the process will
+ * be set to after the privileges are dropped, beforehand.  So while still root,
+ * eg in LWS_CALLBACK_PROTOCOL_INIT, you can arrange things like cache dir
+ * and subdir creation / permissions down /var/cache dynamically.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_get_effective_uid_gid(struct lws_context *context, int *uid, int *gid);
+
+/**
+ * lws_get_udp() - get wsi's udp struct
+ *
+ * \param wsi: lws connection
+ *
+ * Returns NULL or pointer to the wsi's UDP-specific information
+ */
+LWS_VISIBLE LWS_EXTERN const struct lws_udp * LWS_WARN_UNUSED_RESULT
+lws_get_udp(const 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 void *
+lws_get_opaque_user_data(const struct lws *wsi);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_set_opaque_user_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);
+
+/**
+ * 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));
+
+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.
+ *
+ * If the output side of a server process becomes choked, this allows flow
+ * control for the input side.
+ *
+ * \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);
+
+/**
+ * lws_rx_flow_allow_all_protocol() - Allow all connections with this protocol to receive
+ *
+ * When the user server code realizes it can accept more input, it can
+ * call this to have the RX flow restriction removed from all connections using
+ * the given protocol.
+ * \param context:     lws_context
+ * \param protocol:    all connections using this protocol will be allowed to receive
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_rx_flow_allow_all_protocol(const struct lws_context *context,
+                              const struct lws_protocols *protocol);
+
+/**
+ * lws_remaining_packet_payload() - Bytes to come before "overall"
+ *                                           rx fragment is complete
+ * \param wsi:         Websocket instance (available from user callback)
+ *
+ * This tracks how many bytes are left in the current ws fragment, according
+ * to the ws length given in the fragment header.
+ *
+ * If the message was in a single fragment, and there is no compression, this
+ * is the same as "how much data is left to read for this message".
+ *
+ * However, if the message is being sent in multiple fragments, this will
+ * reflect the unread amount of the current **fragment**, not the message.  With
+ * ws, it is legal to not know the length of the message before it completes.
+ *
+ * Additionally if the message is sent via the negotiated permessage-deflate
+ * extension, this number only tells the amount of **compressed** data left to
+ * be read, since that is the only information available at the ws layer.
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_remaining_packet_payload(struct lws *wsi);
+
+#if defined(LWS_WITH_DIR)
+
+typedef enum {
+       LDOT_UNKNOWN,
+       LDOT_FILE,
+       LDOT_DIR,
+       LDOT_LINK,
+       LDOT_FIFO,
+       LDOTT_SOCKET,
+       LDOT_CHAR,
+       LDOT_BLOCK
+} lws_dir_obj_type_t;
+
+struct lws_dir_entry {
+       const char *name;
+       lws_dir_obj_type_t type;
+};
+
+typedef int
+lws_dir_callback_function(const char *dirpath, void *user,
+                         struct lws_dir_entry *lde);
+
+/**
+ * lws_dir() - get a callback for everything in a directory
+ *
+ * \param dirpath: the directory to scan
+ * \param user: pointer to give to callback
+ * \param cb: callback to receive information on each file or dir
+ *
+ * Calls \p cb (with \p user) for every object in dirpath.
+ *
+ * This wraps whether it's using POSIX apis, or libuv (as needed for windows,
+ * since it refuses to support POSIX apis for this).
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_dir(const char *dirpath, void *user, lws_dir_callback_function cb);
+#endif
+
+/**
+ * lws_get_allocated_heap() - if the platform supports it, returns amount of
+ *                             heap allocated by lws itself
+ *
+ * On glibc currently, this reports the total amount of current logical heap
+ * allocation, found by tracking the amount allocated by lws_malloc() and
+ * friends and accounting for freed allocations via lws_free().
+ *
+ * This is useful for confirming where processwide heap allocations actually
+ * come from... this number represents all lws internal allocations, for
+ * fd tables, wsi allocations, ah, etc combined.  It doesn't include allocations
+ * from user code, since lws_malloc() etc are not exported from the library.
+ *
+ * On other platforms, it always returns 0.
+ */
+size_t lws_get_allocated_heap(void);
+
+/**
+ * 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);
+
+/**
+ * lws_open() - platform-specific wrapper for open that prepares the fd
+ *
+ * \param file: the filepath to open
+ * \param oflag: option flags
+ * \param mode: optional mode of any created file
+ *
+ * This is a wrapper around platform open() that sets options on the fd
+ * according to lws policy.  Currently that is FD_CLOEXEC to stop the opened
+ * fd being available to any child process forked by user code.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_open(const char *__file, int __oflag, ...);
+
+struct lws_wifi_scan { /* generic wlan scan item */
+       struct lws_wifi_scan *next;
+       char ssid[32];
+       int32_t rssi; /* divide by .count to get db */
+       uint8_t bssid[6];
+       uint8_t count;
+       uint8_t channel;
+       uint8_t authmode;
+};
+
+#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS)
+/**
+ * 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
+
+LWS_VISIBLE LWS_EXTERN void
+lws_explicit_bzero(void *p, size_t len);
+
+typedef struct lws_humanize_unit {
+       const char *name; /* array ends with NULL name */
+       uint64_t factor;
+} lws_humanize_unit_t;
+
+LWS_VISIBLE LWS_EXTERN const lws_humanize_unit_t humanize_schema_si[];
+LWS_VISIBLE LWS_EXTERN const lws_humanize_unit_t humanize_schema_si_bytes[];
+LWS_VISIBLE LWS_EXTERN const lws_humanize_unit_t humanize_schema_us[];
+
+/**
+ * lws_humanize() - Convert possibly large number to himan-readable uints
+ *
+ * \param buf: result string buffer
+ * \param len: remaining length in \p buf
+ * \param value: the uint64_t value to represent
+ * \param schema: and array of scaling factors and units
+ *
+ * This produces a concise string representation of \p value, referening the
+ * schema \p schema of scaling factors and units to find the smallest way to
+ * render it.
+ *
+ * Three schema are exported from lws for general use, humanize_schema_si, which
+ * represents as, eg, "  22.130Gi" or " 128      "; humanize_schema_si_bytes
+ * which is the same but shows, eg, "  22.130GiB", and humanize_schema_us,
+ * which represents a count of us as a human-readable time like "  14.350min",
+ * or "  1.500d".
+ *
+ * You can produce your own schema.
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_humanize(char *buf, int len, uint64_t value,
+            const lws_humanize_unit_t *schema);
+
+///@}
diff --git a/include/libwebsockets/lws-network-helper.h b/include/libwebsockets/lws-network-helper.h
new file mode 100644 (file)
index 0000000..b3ad596
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup net Network related helper APIs
+ * ##Network related helper APIs
+ *
+ * These wrap miscellaneous useful network-related functions
+ */
+///@{
+
+/**
+ * lws_canonical_hostname() - returns this host's hostname
+ *
+ * This is typically used by client code to fill in the host parameter
+ * when making a client connection.  You can only call it after the context
+ * has been created.
+ *
+ * \param context:     Websocket context
+ */
+LWS_VISIBLE LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT
+lws_canonical_hostname(struct lws_context *context);
+
+/**
+ * lws_get_peer_addresses() - Get client address information
+ * \param wsi: Local struct lws associated with
+ * \param fd:          Connection socket descriptor
+ * \param name:        Buffer to take client address name
+ * \param name_len:    Length of client address name buffer
+ * \param rip: Buffer to take client address IP dotted quad
+ * \param rip_len:     Length of client address IP buffer
+ *
+ *     This function fills in name and rip with the name and IP of
+ *     the client connected with socket descriptor fd.  Names may be
+ *     truncated if there is not enough room.  If either cannot be
+ *     determined, they will be returned as valid zero-length strings.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name,
+                      int name_len, char *rip, int rip_len);
+
+/**
+ * lws_get_peer_simple() - Get client address information without RDNS
+ *
+ * \param wsi: Local struct lws associated with
+ * \param name:        Buffer to take client address name
+ * \param namelen:     Length of client address name buffer
+ *
+ * This provides a 123.123.123.123 type IP address in name from the
+ * peer that has connected to wsi
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_peer_simple(struct lws *wsi, char *name, int namelen);
+
+#define LWS_ITOSA_USABLE       0
+#define LWS_ITOSA_NOT_EXIST    -1
+#define LWS_ITOSA_NOT_USABLE   -2
+#define LWS_ITOSA_BUSY         -3 /* only returned by lws_socket_bind() on
+                                       EADDRINUSE */
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+/**
+ * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct
+ *
+ * \param ipv6:                Allow IPV6 addresses
+ * \param ifname:      Interface name or IP
+ * \param addr:                struct sockaddr_in * to be written
+ * \param addrlen:     Length of addr
+ *
+ * This converts a textual network interface name to a sockaddr usable by
+ * other network functions.
+ *
+ * If the network interface doesn't exist, it will return LWS_ITOSA_NOT_EXIST.
+ *
+ * If the network interface is not usable, eg ethernet cable is removed, it
+ * may logically exist but not have any IP address.  As such it will return
+ * LWS_ITOSA_NOT_USABLE.
+ *
+ * If the network interface exists and is usable, it will return
+ * LWS_ITOSA_USABLE.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
+                   size_t addrlen);
+#endif
+///@}
diff --git a/include/libwebsockets/lws-optee.h b/include/libwebsockets/lws-optee.h
new file mode 100644 (file)
index 0000000..9b73d30
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (C) 2019 Akira Tsukamoto <akira.tsukamoto@gmail.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+#ifndef __LWS_OPTEE_H
+#define __LWS_OPTEE_H
+
+/* 128-bit IP6 address */
+struct in6_addr {
+       union {
+               uint8_t   u6_addr8[16];
+               uint16_t  u6_addr16[8];
+               uint32_t  u6_addr32[4];
+       };
+};
+
+#define                _SS_MAXSIZE     128U
+#define                _SS_ALIGNSIZE   (sizeof(int64_t))
+#define                _SS_PAD1SIZE    (_SS_ALIGNSIZE - \
+                       sizeof(sa_family_t))
+#define                _SS_PAD2SIZE    (_SS_MAXSIZE - \
+                       sizeof(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE)
+
+struct sockaddr_storage {
+       sa_family_t     ss_family;      /* address family */
+       char            __ss_pad1[_SS_PAD1SIZE];
+       int64_t         __ss_align;     /* force desired struct alignment */
+       char            __ss_pad2[_SS_PAD2SIZE];
+};
+
+#define __SOCK_SIZE__  16              /* sizeof(struct sockaddr)      */
+struct sockaddr {
+       sa_family_t     sa_family;      /* address family */
+       uint8_t         sa_data[__SOCK_SIZE__   /* address value */
+                               - sizeof(sa_family_t)];
+};
+
+/* 16 bytes */
+struct sockaddr_in {
+       sa_family_t     sin_family;
+       in_port_t       sin_port;
+       struct in_addr  sin_addr;
+       uint8_t         sin_zero[__SOCK_SIZE__  /* padding until 16 bytes */
+                               - sizeof(sa_family_t)
+                               - sizeof(in_port_t)
+                               - sizeof(struct  in_addr)];
+};
+
+struct sockaddr_in6 {
+       sa_family_t     sin6_family;    /* AF_INET6 */
+       in_port_t       sin6_port;      /* Transport layer port # */
+       uint32_t        sin6_flowinfo;  /* IP6 flow information */
+       struct in6_addr sin6_addr;      /* IP6 address */
+       uint32_t        sin6_scope_id;  /* scope zone index */
+};
+
+#endif /* __LWS_OPTEE_H */
diff --git a/include/libwebsockets/lws-plugin-generic-sessions.h b/include/libwebsockets/lws-plugin-generic-sessions.h
new file mode 100644 (file)
index 0000000..caf27b7
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup generic-sessions plugin: generic-sessions
+ * \ingroup Protocols-and-Plugins
+ *
+ * ##Plugin Generic-sessions related
+ *
+ * generic-sessions plugin provides a reusable, generic session and login /
+ * register / forgot password framework including email verification.
+ */
+///@{
+
+#define LWSGS_EMAIL_CONTENT_SIZE 16384
+/**< Maximum size of email we might send */
+
+/* SHA-1 binary and hexified versions */
+/** typedef struct lwsgw_hash_bin */
+typedef struct { unsigned char bin[32]; /**< binary representation of hash */} lwsgw_hash_bin;
+/** typedef struct lwsgw_hash */
+typedef struct { char id[65]; /**< ascii hex representation of hash */ } lwsgw_hash;
+
+/** enum lwsgs_auth_bits */
+enum lwsgs_auth_bits {
+       LWSGS_AUTH_LOGGED_IN    = 1, /**< user is logged in as somebody */
+       LWSGS_AUTH_ADMIN        = 2, /**< logged in as the admin user */
+       LWSGS_AUTH_VERIFIED     = 4, /**< user has verified his email */
+       LWSGS_AUTH_FORGOT_FLOW  = 8, /**< just completed "forgot password" */
+};
+
+/** struct lws_session_info - information about user session status */
+struct lws_session_info {
+       char username[32]; /**< username logged in as, or empty string */
+       char email[100]; /**< email address associated with login, or empty string */
+       char ip[72]; /**< ip address session was started from */
+       unsigned int mask; /**< access rights mask associated with session
+                           * see enum lwsgs_auth_bits */
+       char session[42]; /**< session id string, usable as opaque uid when not logged in */
+};
+
+/** enum lws_gs_event */
+enum lws_gs_event {
+       LWSGSE_CREATED, /**< a new user was created */
+       LWSGSE_DELETED  /**< an existing user was deleted */
+};
+
+/** struct lws_gs_event_args */
+struct lws_gs_event_args {
+       enum lws_gs_event event; /**< which event happened */
+       const char *username; /**< which username the event happened to */
+       const char *email; /**< the email address of that user */
+};
+
+///@}
diff --git a/include/libwebsockets/lws-protocols-plugins.h b/include/libwebsockets/lws-protocols-plugins.h
new file mode 100644 (file)
index 0000000..d37041d
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup Protocols-and-Plugins Protocols and Plugins
+ * \ingroup lwsapi
+ *
+ * ##Protocol and protocol plugin -related apis
+ *
+ * Protocols bind ws protocol names to a custom callback specific to that
+ * protocol implementaion.
+ *
+ * A list of protocols can be passed in at context creation time, but it is
+ * also legal to leave that NULL and add the protocols and their callback code
+ * using plugins.
+ *
+ * Plugins are much preferable compared to cut and pasting code into an
+ * application each time, since they can be used standalone.
+ */
+///@{
+/** struct lws_protocols -     List of protocols and handlers client or server
+ *                                     supports. */
+
+struct lws_protocols {
+       const char *name;
+       /**< Protocol name that must match the one given in the client
+        * Javascript new WebSocket(url, 'protocol') name. */
+       lws_callback_function *callback;
+       /**< The service callback used for this protocol.  It allows the
+        * service action for an entire protocol to be encapsulated in
+        * the protocol-specific callback */
+       size_t per_session_data_size;
+       /**< Each new connection using this protocol gets
+        * this much memory allocated on connection establishment and
+        * freed on connection takedown.  A pointer to this per-connection
+        * allocation is passed into the callback in the 'user' parameter */
+       size_t rx_buffer_size;
+       /**< lws allocates this much space for rx data and informs callback
+        * when something came.  Due to rx flow control, the callback may not
+        * be able to consume it all without having to return to the event
+        * loop.  That is supported in lws.
+        *
+        * If .tx_packet_size is 0, this also controls how much may be sent at
+        * once for backwards compatibility.
+        */
+       unsigned int id;
+       /**< ignored by lws, but useful to contain user information bound
+        * to the selected protocol.  For example if this protocol was
+        * called "myprotocol-v2", you might set id to 2, and the user
+        * code that acts differently according to the version can do so by
+        * switch (wsi->protocol->id), user code might use some bits as
+        * capability flags based on selected protocol version, etc. */
+       void *user; /**< ignored by lws, but user code can pass a pointer
+                       here it can later access from the protocol callback */
+       size_t tx_packet_size;
+       /**< 0 indicates restrict send() size to .rx_buffer_size for backwards-
+        * compatibility.
+        * If greater than zero, a single send() is restricted to this amount
+        * and any remainder is buffered by lws and sent afterwards also in
+        * these size chunks.  Since that is expensive, it's preferable
+        * to restrict one fragment you are trying to send to match this
+        * size.
+        */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility */
+};
+
+/**
+ * lws_vhost_name_to_protocol() - get vhost's protocol object from its name
+ *
+ * \param vh: vhost to search
+ * \param name: protocol name
+ *
+ * Returns NULL or a pointer to the vhost's protocol of the requested name
+ */
+LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
+lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name);
+
+/**
+ * lws_get_protocol() - Returns a protocol pointer from a websocket
+ *                               connection.
+ * \param wsi: pointer to struct websocket you want to know the protocol of
+ *
+ *
+ *     Some apis can act on all live connections of a given protocol,
+ *     this is how you can get a pointer to the active protocol if needed.
+ */
+LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
+lws_get_protocol(struct lws *wsi);
+
+/** lws_protocol_get() -  deprecated: use lws_get_protocol */
+LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
+lws_protocol_get(struct lws *wsi) LWS_WARN_DEPRECATED;
+
+/**
+ * lws_protocol_vh_priv_zalloc() - Allocate and zero down a protocol's per-vhost
+ *                                storage
+ * \param vhost:       vhost the instance is related to
+ * \param prot:                protocol the instance is related to
+ * \param size:                bytes to allocate
+ *
+ * Protocols often find it useful to allocate a per-vhost struct, this is a
+ * helper to be called in the per-vhost init LWS_CALLBACK_PROTOCOL_INIT
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost,
+                           const struct lws_protocols *prot, int size);
+
+/**
+ * lws_protocol_vh_priv_get() - retreive a protocol's per-vhost storage
+ *
+ * \param vhost:       vhost the instance is related to
+ * \param prot:                protocol the instance is related to
+ *
+ * Recover a pointer to the allocated per-vhost storage for the protocol created
+ * by lws_protocol_vh_priv_zalloc() earlier
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lws_protocol_vh_priv_get(struct lws_vhost *vhost,
+                        const struct lws_protocols *prot);
+
+/**
+ * lws_adjust_protocol_psds - change a vhost protocol's per session data size
+ *
+ * \param wsi: a connection with the protocol to change
+ * \param new_size: the new size of the per session data size for the protocol
+ *
+ * Returns user_space for the wsi, after allocating
+ *
+ * This should not be used except to initalize a vhost protocol's per session
+ * data size one time, before any connections are accepted.
+ *
+ * Sometimes the protocol wraps another protocol and needs to discover and set
+ * its per session data size at runtime.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lws_adjust_protocol_psds(struct lws *wsi, size_t new_size);
+
+/**
+ * lws_finalize_startup() - drop initial process privileges
+ *
+ * \param context:     lws context
+ *
+ * This is called after the end of the vhost protocol initializations, but
+ * you may choose to call it earlier
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_finalize_startup(struct lws_context *context);
+
+/**
+ * lws_pvo_search() - helper to find a named pvo in a linked-list
+ *
+ * \param pvo: the first pvo in the linked-list
+ * \param name: the name of the pvo to return if found
+ *
+ * Returns NULL, or a pointer to the name pvo in the linked-list
+ */
+LWS_VISIBLE LWS_EXTERN const struct lws_protocol_vhost_options *
+lws_pvo_search(const struct lws_protocol_vhost_options *pvo, const char *name);
+
+/**
+ * lws_pvo_get_str() - retreive a string pvo value
+ *
+ * \param pvo: the first pvo in the linked-list
+ * \param name: the name of the pvo to return if found
+ * \param result: pointer to a const char * to get the result if any
+ *
+ * Returns 0 if found and *result set, or nonzero if not found
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_pvo_get_str(void *in, const char *name, const char **result);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_protocol_init(struct lws_context *context);
+
+#ifdef LWS_WITH_PLUGINS
+
+/* PLUGINS implies LIBUV */
+
+#define LWS_PLUGIN_API_MAGIC 180
+
+/** struct lws_plugin_capability - how a plugin introduces itself to lws */
+struct lws_plugin_capability {
+       unsigned int api_magic; /**< caller fills this in, plugin fills rest */
+       const struct lws_protocols *protocols; /**< array of supported protocols provided by plugin */
+       int count_protocols; /**< how many protocols */
+       const struct lws_extension *extensions; /**< array of extensions provided by plugin */
+       int count_extensions; /**< how many extensions */
+};
+
+typedef int (*lws_plugin_init_func)(struct lws_context *,
+                                   struct lws_plugin_capability *);
+typedef int (*lws_plugin_destroy_func)(struct lws_context *);
+
+/** struct lws_plugin */
+struct lws_plugin {
+       struct lws_plugin *list; /**< linked list */
+#if (UV_VERSION_MAJOR > 0)
+       uv_lib_t lib; /**< shared library pointer */
+#endif
+       void *l; /**< so we can compile on ancient libuv */
+       char name[64]; /**< name of the plugin */
+       struct lws_plugin_capability caps; /**< plugin capabilities */
+};
+
+#endif
+
+///@}
diff --git a/include/libwebsockets/lws-purify.h b/include/libwebsockets/lws-purify.h
new file mode 100644 (file)
index 0000000..0ae35ce
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+
+/*! \defgroup pur Sanitize / purify SQL and JSON helpers
+ *
+ * ##Sanitize / purify SQL and JSON helpers
+ *
+ * APIs for escaping untrusted JSON and SQL safely before use
+ */
+//@{
+
+/**
+ * lws_sql_purify() - like strncpy but with escaping for sql quotes
+ *
+ * \param escaped: output buffer
+ * \param string: input buffer ('/0' terminated)
+ * \param len: output buffer max length
+ *
+ * Because escaping expands the output string, it's not
+ * possible to do it in-place, ie, with escaped == string
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_sql_purify(char *escaped, const char *string, int len);
+
+/**
+ * lws_json_purify() - like strncpy but with escaping for json chars
+ *
+ * \param escaped: output buffer
+ * \param string: input buffer ('/0' terminated)
+ * \param len: output buffer max length
+ *
+ * Because escaping expands the output string, it's not
+ * possible to do it in-place, ie, with escaped == string
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_json_purify(char *escaped, const char *string, int len);
+
+/**
+ * lws_filename_purify_inplace() - replace scary filename chars with underscore
+ *
+ * \param filename: filename to be purified
+ *
+ * Replace scary characters in the filename (it should not be a path)
+ * with underscore, so it's safe to use.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_filename_purify_inplace(char *filename);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
+                       int len);
+LWS_VISIBLE LWS_EXTERN int
+lws_plat_write_file(const char *filename, void *buf, int len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_plat_read_file(const char *filename, void *buf, int len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_plat_recommended_rsa_bits(void);
+///@}
diff --git a/include/libwebsockets/lws-retry.h b/include/libwebsockets/lws-retry.h
new file mode 100644 (file)
index 0000000..fe058c5
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */ 
+
+/*
+ * Specifies backoff ranges using a pair of uint32_t in ms for the min, max.
+ *
+ * The actual backoff timing is picked randomly within the range.
+ */
+
+typedef struct lws_retry_range {
+       uint32_t                min_ms;
+       uint32_t                max_ms;
+} lws_retry_range_t;
+
+typedef struct lws_retry_bo {
+       const lws_retry_range_t *retry_ms_table;        /* backoff range pair */
+       uint16_t                retry_ms_table_count;      /* ranges in table */
+       uint16_t                conceal_count;      /* max retries to conceal */
+} lws_retry_bo_t;
+
+/**
+ * lws_retry_get_delay_ms() - get next delay from backoff table
+ *
+ * \param lws_context: the lws context (used for getting random)
+ * \param retry: the retry backoff table we are using
+ * \param ctry: pointer to the try counter
+ * \param conceal: pointer to flag set to nonzero if the try should be concealed
+ *                     in terms of creating an error
+ *
+ * Increments *\p try and retruns the number of ms that should elapse before the
+ * next connection retry, according to the backoff table \p retry. *\p conceal is
+ * set if the number of tries is less than the backoff table conceal_count, or
+ * is zero if it exceeded it.  This lets you conceal a certain number of retries
+ * before alerting the caller there is a problem.
+ */
+
+LWS_VISIBLE LWS_EXTERN unsigned int
+lws_retry_get_delay_ms(struct lws_context *context, const lws_retry_bo_t *retry,
+                       uint16_t *ctry, char *conceal);
+
diff --git a/include/libwebsockets/lws-ring.h b/include/libwebsockets/lws-ring.h
new file mode 100644 (file)
index 0000000..9a5ec2e
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \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
+ *
+ *  - lws_ring_create()
+ *  - lws_ring_destroy()
+ *
+ * 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()
+ *  - lws_ring_consume()
+ *
+ * You can find out how many whole elements are free or waiting using
+ *
+ *  - lws_ring_get_count_free_elements()
+ *  - lws_ring_get_count_waiting_elements()
+ *
+ * In addition there are special purpose optional byte-centric apis
+ *
+ *  - lws_ring_next_linear_insert_range()
+ *  - lws_ring_bump_head()
+ *
+ *  which let you, eg, read() directly into the ringbuffer without needing
+ *  an intermediate bounce buffer.
+ *
+ *  The accessors understand that the ring wraps, and optimizes insertion and
+ *  consumption into one or two memcpy()s depending on if the head or tail
+ *  wraps.
+ *
+ *  lws_ring only supports a single head, but optionally multiple tails with
+ *  an API to inform it when the "oldest" tail has moved on.  You can give
+ *  NULL where-ever an api asks for a tail pointer, and it will use an internal
+ *  single tail pointer for convenience.
+ *
+ *  The "oldest tail", which is the only tail if you give it NULL instead of
+ *  some other tail, is used to track which elements in the ringbuffer are
+ *  still unread by anyone.
+ *
+ *   - lws_ring_update_oldest_tail()
+ */
+///@{
+struct lws_ring;
+
+/**
+ * lws_ring_create(): create a new ringbuffer
+ *
+ * \param element_len: the size in bytes of one element in the ringbuffer
+ * \param count: the number of elements the ringbuffer can contain
+ * \param destroy_element: NULL, or callback to be called for each element
+ *                        that is removed from the ringbuffer due to the
+ *                        oldest tail moving beyond it
+ *
+ * Creates the ringbuffer and allocates the storage.  Returns the new
+ * lws_ring *, or NULL if the allocation failed.
+ *
+ * If non-NULL, destroy_element will get called back for every element that is
+ * retired from the ringbuffer after the oldest tail has gone past it, and for
+ * any element still left in the ringbuffer when it is destroyed.  It replaces
+ * all other element destruction code in your user code.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_ring *
+lws_ring_create(size_t element_len, size_t count,
+               void (*destroy_element)(void *element));
+
+/**
+ * lws_ring_destroy():  destroy a previously created ringbuffer
+ *
+ * \param ring: the struct lws_ring to destroy
+ *
+ * Destroys the ringbuffer allocation and the struct lws_ring itself.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_destroy(struct lws_ring *ring);
+
+/**
+ * lws_ring_get_count_free_elements():  return how many elements can fit
+ *                                   in the free space
+ *
+ * \param ring: the struct lws_ring to report on
+ *
+ * Returns how much room is left in the ringbuffer for whole element insertion.
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_get_count_free_elements(struct lws_ring *ring);
+
+/**
+ * lws_ring_get_count_waiting_elements():  return how many elements can be consumed
+ *
+ * \param ring: the struct lws_ring to report on
+ * \param tail: a pointer to the tail struct to use, or NULL for single tail
+ *
+ * Returns how many elements are waiting to be consumed from the perspective
+ * of the tail pointer given.
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_get_count_waiting_elements(struct lws_ring *ring, uint32_t *tail);
+
+/**
+ * lws_ring_insert():  attempt to insert up to max_count elements from src
+ *
+ * \param ring: the struct lws_ring to report on
+ * \param src: the array of elements to be inserted
+ * \param max_count: the number of available elements at src
+ *
+ * Attempts to insert as many of the elements at src as possible, up to the
+ * maximum max_count.  Returns the number of elements actually inserted.
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_insert(struct lws_ring *ring, const void *src, size_t max_count);
+
+/**
+ * lws_ring_consume():  attempt to copy out and remove up to max_count elements
+ *                     to src
+ *
+ * \param ring: the struct lws_ring to report on
+ * \param tail: a pointer to the tail struct to use, or NULL for single tail
+ * \param dest: the array of elements to be inserted. or NULL for no copy
+ * \param max_count: the number of available elements at src
+ *
+ * Attempts to copy out as many waiting elements as possible into dest, from
+ * the perspective of the given tail, up to max_count.  If dest is NULL, the
+ * copying out is not done but the elements are logically consumed as usual.
+ * NULL dest is useful in combination with lws_ring_get_element(), where you
+ * can use the element direct from the ringbuffer and then call this with NULL
+ * dest to logically consume it.
+ *
+ * Increments the tail position according to how many elements could be
+ * consumed.
+ *
+ * Returns the number of elements consumed.
+ */
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_consume(struct lws_ring *ring, uint32_t *tail, void *dest,
+                size_t max_count);
+
+/**
+ * lws_ring_get_element():  get a pointer to the next waiting element for tail
+ *
+ * \param ring: the struct lws_ring to report on
+ * \param tail: a pointer to the tail struct to use, or NULL for single tail
+ *
+ * Points to the next element that tail would consume, directly in the
+ * ringbuffer.  This lets you write() or otherwise use the element without
+ * having to copy it out somewhere first.
+ *
+ * After calling this, you must call lws_ring_consume(ring, &tail, NULL, 1)
+ * which will logically consume the element you used up and increment your
+ * tail (tail may also be NULL there if you use a single tail).
+ *
+ * Returns NULL if no waiting element, or a const void * pointing to it.
+ */
+LWS_VISIBLE LWS_EXTERN const void *
+lws_ring_get_element(struct lws_ring *ring, uint32_t *tail);
+
+/**
+ * lws_ring_update_oldest_tail():  free up elements older than tail for reuse
+ *
+ * \param ring: the struct lws_ring to report on
+ * \param tail: a pointer to the tail struct to use, or NULL for single tail
+ *
+ * If you are using multiple tails, you must use this API to inform the
+ * lws_ring when none of the tails still need elements in the fifo any more,
+ * by updating it when the "oldest" tail has moved on.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_update_oldest_tail(struct lws_ring *ring, uint32_t tail);
+
+/**
+ * lws_ring_get_oldest_tail():  get current oldest available data index
+ *
+ * \param ring: the struct lws_ring to report on
+ *
+ * If you are initializing a new ringbuffer consumer, you can set its tail to
+ * this to start it from the oldest ringbuffer entry still available.
+ */
+LWS_VISIBLE LWS_EXTERN uint32_t
+lws_ring_get_oldest_tail(struct lws_ring *ring);
+
+/**
+ * lws_ring_next_linear_insert_range():  used to write directly into the ring
+ *
+ * \param ring: the struct lws_ring to report on
+ * \param start: pointer to a void * set to the start of the next ringbuffer area
+ * \param bytes: pointer to a size_t set to the max length you may use from *start
+ *
+ * This provides a low-level, bytewise access directly into the ringbuffer
+ * allowing direct insertion of data without having to use a bounce buffer.
+ *
+ * The api reports the position and length of the next linear range that can
+ * be written in the ringbuffer, ie, up to the point it would wrap, and sets
+ * *start and *bytes accordingly.  You can then, eg, directly read() into
+ * *start for up to *bytes, and use lws_ring_bump_head() to update the lws_ring
+ * with what you have done.
+ *
+ * Returns nonzero if no insertion is currently possible.
+ */
+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
+ *
+ * \param ring: the struct lws_ring to operate on
+ * \param bytes: the number of bytes you inserted at the current head
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_bump_head(struct lws_ring *ring, size_t bytes);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_dump(struct lws_ring *ring, uint32_t *tail);
+
+/*
+ * This is a helper that combines the common pattern of needing to consume
+ * some ringbuffer elements, move the consumer tail on, and check if that
+ * has moved any ringbuffer elements out of scope, because it was the last
+ * consumer that had not already consumed them.
+ *
+ * Elements that go out of scope because the oldest tail is now after them
+ * get garbage-collected by calling the destroy_element callback on them
+ * defined when the ringbuffer was created.
+ */
+
+#define lws_ring_consume_and_update_oldest_tail(\
+               ___ring,    /* the lws_ring object */ \
+               ___type,    /* type of objects with tails */ \
+               ___ptail,   /* ptr to tail of obj with tail doing consuming */ \
+               ___count,   /* count of payload objects being consumed */ \
+               ___list_head,   /* head of list of objects with tails */ \
+               ___mtail,   /* member name of tail in ___type */ \
+               ___mlist  /* member name of next list member ptr in ___type */ \
+       ) { \
+               int ___n, ___m; \
+       \
+       ___n = lws_ring_get_oldest_tail(___ring) == *(___ptail); \
+       lws_ring_consume(___ring, ___ptail, NULL, ___count); \
+       if (___n) { \
+               uint32_t ___oldest; \
+               ___n = 0; \
+               ___oldest = *(___ptail); \
+               lws_start_foreach_llp(___type **, ___ppss, ___list_head) { \
+                       ___m = lws_ring_get_count_waiting_elements( \
+                                       ___ring, &(*___ppss)->tail); \
+                       if (___m >= ___n) { \
+                               ___n = ___m; \
+                               ___oldest = (*___ppss)->tail; \
+                       } \
+               } lws_end_foreach_llp(___ppss, ___mlist); \
+       \
+               lws_ring_update_oldest_tail(___ring, ___oldest); \
+       } \
+}
+
+/*
+ * This does the same as the lws_ring_consume_and_update_oldest_tail()
+ * helper, but for the simpler case there is only one consumer, so one
+ * tail, and that tail is always the oldest tail.
+ */
+
+#define lws_ring_consume_single_tail(\
+               ___ring,  /* the lws_ring object */ \
+               ___ptail, /* ptr to tail of obj with tail doing consuming */ \
+               ___count  /* count of payload objects being consumed */ \
+       ) { \
+       lws_ring_consume(___ring, ___ptail, NULL, ___count); \
+       lws_ring_update_oldest_tail(___ring, *(___ptail)); \
+}
+///@}
diff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h
new file mode 100644 (file)
index 0000000..f04b844
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ *
+ * lws_sequencer is intended to help implement sequences that:
+ *
+ *  - outlive a single connection lifetime,
+ *  - are not associated with a particular protocol,
+ *  - are not associated with a particular vhost,
+ *  - must receive and issue events inside the event loop
+ *
+ * lws_sequencer-s are bound to a pt (per-thread) which for the default case of
+ * one service thread is the same as binding to an lws_context.
+ */
+/*
+ * retry backoff table... retry n happens after .retry_ms_table[n] ms, with
+ * the last entry used if n is greater than the number of entries.
+ *
+ * The first .conceal_count retries are concealed, but after that the failures
+ * are reported.
+ */
+
+typedef enum {
+       LWSSEQ_CREATED,         /* sequencer created */
+       LWSSEQ_DESTROYED,       /* sequencer destroyed */
+       LWSSEQ_TIMED_OUT,       /* sequencer timeout */
+       LWSSEQ_HEARTBEAT,       /* 1Hz callback */
+
+       LWSSEQ_WSI_CONNECTED,   /* wsi we bound to us has connected */
+       LWSSEQ_WSI_CONN_FAIL,   /* wsi we bound to us has failed to connect */
+       LWSSEQ_WSI_CONN_CLOSE,  /* wsi we bound to us has closed */
+
+       LWSSEQ_USER_BASE = 100  /* define your events from here */
+} lws_seq_events_t;
+
+typedef enum lws_seq_cb_return {
+       LWSSEQ_RET_CONTINUE,
+       LWSSEQ_RET_DESTROY
+} lws_seq_cb_return_t;
+
+typedef struct lws_sequencer lws_seq_t; /* opaque */
+
+/*
+ * handler for this sequencer.  Return 0 if OK else nonzero to destroy the
+ * sequencer.  LWSSEQ_DESTROYED will be called back to the handler so it can
+ * close / destroy any private assets associated with the sequence.
+ *
+ * The callback may return either LWSSEQ_RET_CONTINUE for the sequencer to
+ * resume or LWSSEQ_RET_DESTROY to indicate the sequence is finished.
+ *
+ * Event indexes consist of some generic ones but mainly user-defined ones
+ * starting from LWSSEQ_USER_BASE.
+ */
+typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq,
+                            void *user, int event, void *data, void *aux);
+
+typedef struct lws_seq_info {
+       struct lws_context              *context;   /* lws_context for seq */
+       int                             tsi;        /* thread service idx */
+       size_t                          user_size;  /* size of user alloc */
+       void                            **puser;    /* place ptr to user */
+       lws_seq_event_cb                cb;         /* seq callback */
+       const char                      *name;      /* seq name */
+       const lws_retry_bo_t            *retry;     /* retry policy */
+} lws_seq_info_t;
+
+/**
+ * lws_seq_create() - create and bind sequencer to a pt
+ *
+ * \param info:        information about sequencer to create
+ *
+ * This binds an abstract sequencer to a per-thread (by default, the single
+ * event loop of an lws_context).  After the event loop starts, the sequencer
+ * will receive an LWSSEQ_CREATED event on its callback from the event loop
+ * context, where it can begin its sequence flow.
+ *
+ * Lws itself will only call the callback subsequently with LWSSEQ_DESTROYED
+ * when the sequencer is being destroyed.
+ *
+ * pt locking is used to protect the related data structures.
+ */
+LWS_VISIBLE LWS_EXTERN lws_seq_t *
+lws_seq_create(lws_seq_info_t *info);
+
+/**
+ * lws_seq_destroy() - destroy the sequencer
+ *
+ * \param seq: pointer to the the opaque sequencer pointer returned by
+ *            lws_seq_create()
+ *
+ * This proceeds to destroy the sequencer, calling LWSSEQ_DESTROYED and then
+ * freeing the sequencer object itself.  The pointed-to seq pointer will be
+ * set to NULL.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_seq_destroy(lws_seq_t **seq);
+
+/**
+ * lws_seq_queue_event() - queue an event on the given sequencer
+ *
+ * \param seq: the opaque sequencer pointer returned by lws_seq_create()
+ * \param e: the event index to queue
+ * \param data: associated opaque (to lws) data to provide the callback
+ * \param aux: second opaque data to provide the callback
+ *
+ * This queues the event on a given sequencer.  Queued events are delivered one
+ * per sequencer each subsequent time around the event loop, so the cb is called
+ * from the event loop thread context.
+ *
+ * Notice that because the events are delivered in order from the event loop,
+ * the scope of objects pointed to by \p data or \p aux may exceed the lifetime
+ * of the thing containing the pointed-to data.  So it's usually better to pass
+ * values here.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_seq_queue_event(lws_seq_t *seq, lws_seq_events_t e, void *data,
+                         void *aux);
+
+/**
+ * lws_seq_check_wsi() - check if wsi still extant
+ *
+ * \param seq: the sequencer interested in the wsi
+ * \param wsi: the wsi we want to confirm hasn't closed yet
+ *
+ * Check if wsi still extant, by peeking in the message queue for a
+ * LWSSEQ_WSI_CONN_CLOSE message about wsi.  (Doesn't need to do the same for
+ * CONN_FAIL since that will never have produced any messages prior to that).
+ *
+ * Use this to avoid trying to perform operations on wsi that have already
+ * closed but we didn't get to that message yet.
+ *
+ * Returns 0 if not closed yet or 1 if it has closed but we didn't process the
+ * close message yet.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_seq_check_wsi(lws_seq_t *seq, struct lws *wsi);
+
+#define LWSSEQTO_NONE 0
+
+/**
+ * lws_seq_timeout_us() - set a timeout by which the sequence must have
+ *                             completed by a different event or inform the
+ *                             sequencer
+ *
+ * \param seq: The sequencer to set the timeout on
+ * \param us: How many us in the future to fire the timeout
+ *             LWS_SET_TIMER_USEC_CANCEL = cancel any existing timeout
+ *
+ * This api allows the sequencer to ask to be informed if it has not completed
+ * or disabled its timeout after secs seconds.  Lws will send a LWSSEQ_TIMED_OUT
+ * event to the sequencer if the timeout expires.
+ *
+ * Typically the sequencer sets the timeout when starting a step, then waits to
+ * hear a queued event informing it the step completed or failed.  The timeout
+ * provides a way to deal with the case the step neither completed nor failed
+ * within the timeout period.
+ *
+ * Lws wsi timeouts are not really suitable for this since they are focused on
+ * short-term protocol timeout protection and may be set and reset many times
+ * in one transaction.  Wsi timeouts also enforce closure of the wsi when they
+ * trigger, sequencer timeouts have no side effect except to queue the
+ * LWSSEQ_TIMED_OUT message and leave it to the sequencer to decide how to
+ * react appropriately.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_seq_timeout_us(lws_seq_t *seq, lws_usec_t us);
+
+/**
+ * lws_seq_from_user(): get the lws_seq_t pointer from the user ptr
+ *
+ * \param u: the sequencer user allocation returned by lws_seq_create() or
+ *          provided in the sequencer callback
+ *
+ * This gets the lws_seq_t * from the sequencer user allocation pointer.
+ * Actually these are allocated at the same time in one step, with the user
+ * allocation immediately after the lws_seq_t, so lws can compute where
+ * the lws_seq_t is from having the user allocation pointer.  Since the
+ * size of the lws_seq_t is unknown to user code, this helper does it for
+ * you.
+ */
+LWS_VISIBLE LWS_EXTERN lws_seq_t *
+lws_seq_from_user(void *u);
+
+/**
+ * lws_seq_us_since_creation(): elapsed seconds since sequencer created
+ *
+ * \param seq: pointer to the lws_seq_t
+ *
+ * Returns the number of us elapsed since the lws_seq_t was
+ * created.  This is useful to calculate sequencer timeouts for the current
+ * step considering a global sequencer lifetime limit.
+ */
+LWS_VISIBLE LWS_EXTERN lws_usec_t
+lws_seq_us_since_creation(lws_seq_t *seq);
+
+/**
+ * lws_seq_name(): get the name of this sequencer
+ *
+ * \param seq: pointer to the lws_seq_t
+ *
+ * Returns the name given when the sequencer was created.  This is useful to
+ * annotate logging when then are multiple sequencers in play.
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_seq_name(lws_seq_t *seq);
+
+/**
+ * lws_seq_get_context(): get the lws_context sequencer was created on
+ *
+ * \param seq: pointer to the lws_seq_t
+ *
+ * Returns the lws_context.  Saves you having to store it if you have a seq
+ * pointer handy.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_context *
+lws_seq_get_context(lws_seq_t *seq);
diff --git a/include/libwebsockets/lws-service.h b/include/libwebsockets/lws-service.h
new file mode 100644 (file)
index 0000000..e1f3ac0
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup service Built-in service loop entry
+ *
+ * ##Built-in service loop entry
+ *
+ * If you're not using libev / libuv, these apis are needed to enter the poll()
+ * wait in lws and service any connections with pending events.
+ */
+///@{
+
+/**
+ * lws_service() - Service any pending websocket activity
+ * \param context:     Websocket context
+ * \param timeout_ms:  Set to 0; ignored; for backward compatibility
+ *
+ *     This function deals with any pending websocket traffic, for three
+ *     kinds of event.  It handles these events on both server and client
+ *     types of connection the same.
+ *
+ *     1) Accept new connections to our context's server
+ *
+ *     2) Call the receive callback for incoming frame data received by
+ *         server or client connections.
+ *
+ *  Since v4.0 internally the timeout wait is ignored, the lws scheduler is
+ *  smart enough to stay asleep until an event is queued.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_service(struct lws_context *context, int timeout_ms);
+
+/**
+ * lws_service_tsi() - Service any pending websocket activity
+ *
+ * \param context:     Websocket context
+ * \param timeout_ms:  Set to 0; ignored; for backwards compatibility
+ * \param tsi:         Thread service index, starting at 0
+ *
+ * Same as lws_service(), but for a specific thread service index.  Only needed
+ * if you are spawning multiple service threads.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi);
+
+/**
+ * lws_cancel_service_pt() - Cancel servicing of pending socket activity
+ *                             on one thread
+ * \param wsi: Cancel service on the thread this wsi is serviced by
+ *
+ * Same as lws_cancel_service(), but targets a single service thread, the one
+ * the wsi belongs to.  You probably want to use lws_cancel_service() instead.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_cancel_service_pt(struct lws *wsi);
+
+/**
+ * lws_cancel_service() - Cancel wait for new pending socket activity
+ * \param context:     Websocket context
+ *
+ * This function creates an immediate "synchronous interrupt" to the lws poll()
+ * wait or event loop.  As soon as possible in the serialzed service sequencing,
+ * a LWS_CALLBACK_EVENT_WAIT_CANCELLED callback is sent to every protocol on
+ * every vhost.
+ *
+ * lws_cancel_service() may be called from another thread while the context
+ * exists, and its effect will be immediately serialized.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_cancel_service(struct lws_context *context);
+
+/**
+ * lws_service_fd() - Service polled socket with something waiting
+ * \param context:     Websocket context
+ * \param pollfd:      The pollfd entry describing the socket fd and which events
+ *             happened, or NULL to tell lws to do only timeout servicing.
+ *
+ * This function takes a pollfd that has POLLIN or POLLOUT activity and
+ * services it according to the state of the associated
+ * struct lws.
+ *
+ * The one call deals with all "service" that might happen on a socket
+ * including listen accepts, http files as well as websocket protocol.
+ *
+ * If a pollfd says it has something, you can just pass it to
+ * lws_service_fd() whether it is a socket handled by lws or not.
+ * If it sees it is a lws socket, the traffic will be handled and
+ * pollfd->revents will be zeroed now.
+ *
+ * If the socket is foreign to lws, it leaves revents alone.  So you can
+ * see if you should service yourself by checking the pollfd revents
+ * after letting lws try to service it.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd);
+
+/**
+ * lws_service_fd_tsi() - Service polled socket in specific service thread
+ * \param context:     Websocket context
+ * \param pollfd:      The pollfd entry describing the socket fd and which events
+ *             happened.
+ * \param tsi: thread service index
+ *
+ * Same as lws_service_fd() but used with multiple service threads
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd,
+                  int tsi);
+
+/**
+ * lws_service_adjust_timeout() - Check for any connection needing forced service
+ * \param context:     Websocket context
+ * \param timeout_ms:  The original poll timeout value.  You can just set this
+ *                     to 1 if you don't really have a poll timeout.
+ * \param tsi: thread service index
+ *
+ * Under some conditions connections may need service even though there is no
+ * pending network action on them, this is "forced service".  For default
+ * poll() and libuv / libev, the library takes care of calling this and
+ * dealing with it for you.  But for external poll() integration, you need
+ * access to the apis.
+ *
+ * If anybody needs "forced service", returned timeout is zero.  In that case,
+ * you can call lws_service_tsi() with a timeout of -1 to only service
+ * guys who need forced service.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi);
+
+/* Backwards compatibility */
+#define lws_plat_service_tsi lws_service_tsi
+
+LWS_VISIBLE LWS_EXTERN int
+lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd);
+
+///@}
+
+/*! \defgroup uv libuv helpers
+ *
+ * ##libuv helpers
+ *
+ * APIs specific to libuv event loop itegration
+ */
+///@{
+#ifdef LWS_WITH_LIBUV
+/*
+ * Any direct libuv allocations in lws protocol handlers must participate in the
+ * lws reference counting scheme.  Two apis are provided:
+ *
+ * - lws_libuv_static_refcount_add(handle, context) to mark the handle with
+ *  a pointer to the context and increment the global uv object counter
+ *
+ * - lws_libuv_static_refcount_del() which should be used as the close callback
+ *   for your own libuv objects declared in the protocol scope.
+ *
+ * Using the apis allows lws to detach itself from a libuv loop completely
+ * cleanly and at the moment all of its libuv objects have completed close.
+ */
+
+LWS_VISIBLE LWS_EXTERN uv_loop_t *
+lws_uv_getloop(struct lws_context *context, int tsi);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_libuv_static_refcount_add(uv_handle_t *, struct lws_context *context);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_libuv_static_refcount_del(uv_handle_t *);
+
+#endif /* LWS_WITH_LIBUV */
+
+#if defined(LWS_WITH_ESP32)
+#define lws_libuv_static_refcount_add(_a, _b)
+#define lws_libuv_static_refcount_del NULL
+#endif
+///@}
diff --git a/include/libwebsockets/lws-sha1-base64.h b/include/libwebsockets/lws-sha1-base64.h
new file mode 100644 (file)
index 0000000..5a2bfdb
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup sha SHA and B64 helpers
+ * ##SHA and B64 helpers
+ *
+ * These provide SHA-1 and B64 helper apis
+ */
+///@{
+#ifdef LWS_SHA1_USE_OPENSSL_NAME
+#define lws_SHA1 SHA1
+#else
+/**
+ * lws_SHA1(): make a SHA-1 digest of a buffer
+ *
+ * \param d: incoming buffer
+ * \param n: length of incoming buffer
+ * \param md: buffer for message digest (must be >= 20 bytes)
+ *
+ * Reduces any size buffer into a 20-byte SHA-1 hash.
+ */
+LWS_VISIBLE LWS_EXTERN unsigned char *
+lws_SHA1(const unsigned char *d, size_t n, unsigned char *md);
+#endif
+/**
+ * lws_b64_encode_string(): encode a string into base 64
+ *
+ * \param in: incoming buffer
+ * \param in_len: length of incoming buffer
+ * \param out: result buffer
+ * \param out_size: length of result buffer
+ *
+ * Encodes a string using b64
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_b64_encode_string(const char *in, int in_len, char *out, int out_size);
+/**
+ * lws_b64_encode_string_url(): encode a string into base 64
+ *
+ * \param in: incoming buffer
+ * \param in_len: length of incoming buffer
+ * \param out: result buffer
+ * \param out_size: length of result buffer
+ *
+ * Encodes a string using b64 with the "URL" variant (+ -> -, and / -> _)
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_b64_encode_string_url(const char *in, int in_len, char *out, int out_size);
+/**
+ * lws_b64_decode_string(): decode a string from base 64
+ *
+ * \param in: incoming buffer
+ * \param out: result buffer
+ * \param out_size: length of result buffer
+ *
+ * Decodes a NUL-terminated string using b64
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_b64_decode_string(const char *in, char *out, int out_size);
+/**
+ * lws_b64_decode_string_len(): decode a string from base 64
+ *
+ * \param in: incoming buffer
+ * \param in_len: length of incoming buffer
+ * \param out: result buffer
+ * \param out_size: length of result buffer
+ *
+ * Decodes a range of chars using b64
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_b64_decode_string_len(const char *in, int in_len, char *out, int out_size);
+///@}
+
diff --git a/include/libwebsockets/lws-spa.h b/include/libwebsockets/lws-spa.h
new file mode 100644 (file)
index 0000000..b2f4553
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup form-parsing  Form Parsing
+ * \ingroup http
+ * ##POSTed form parsing functions
+ *
+ * These lws_spa (stateful post arguments) apis let you parse and urldecode
+ * POSTed form arguments, both using simple urlencoded and multipart transfer
+ * encoding.
+ *
+ * It's capable of handling file uploads as well a named input parsing,
+ * and the apis are the same for both form upload styles.
+ *
+ * You feed it a list of parameter names and it creates pointers to the
+ * urldecoded arguments: file upload parameters pass the file data in chunks to
+ * a user-supplied callback as they come.
+ *
+ * Since it's stateful, it handles the incoming data needing more than one
+ * POST_BODY callback and has no limit on uploaded file size.
+ */
+///@{
+
+/** enum lws_spa_fileupload_states */
+enum lws_spa_fileupload_states {
+       LWS_UFS_CONTENT,
+       /**< a chunk of file content has arrived */
+       LWS_UFS_FINAL_CONTENT,
+       /**< the last chunk (possibly zero length) of file content has arrived */
+       LWS_UFS_OPEN,
+       /**< a new file is starting to arrive */
+       LWS_UFS_CLOSE
+       /**< the file decode stuff is being destroyed */
+};
+
+/**
+ * lws_spa_fileupload_cb() - callback to receive file upload data
+ *
+ * \param data: opt_data pointer set in lws_spa_create
+ * \param name: name of the form field being uploaded
+ * \param filename: original filename from client
+ * \param buf: start of data to receive
+ * \param len: length of data to receive
+ * \param state: information about how this call relates to file
+ *
+ * Notice name and filename shouldn't be trusted, as they are passed from
+ * HTTP provided by the client.
+ */
+typedef int (*lws_spa_fileupload_cb)(void *data, const char *name,
+                                    const char *filename, char *buf, int len,
+                                    enum lws_spa_fileupload_states state);
+
+/** struct lws_spa - opaque urldecode parser capable of handling multipart
+ *                     and file uploads */
+struct lws_spa;
+
+/**
+ * lws_spa_create() - create urldecode parser
+ *
+ * \param wsi: lws connection (used to find Content Type)
+ * \param param_names: array of form parameter names, like "username"
+ * \param count_params: count of param_names
+ * \param max_storage: total amount of form parameter values we can store
+ * \param opt_cb: NULL, or callback to receive file upload data.
+ * \param opt_data: NULL, or user pointer provided to opt_cb.
+ *
+ * Creates a urldecode parser and initializes it.
+ *
+ * It's recommended to use the newer api, lws_spa_create_via_info()
+ * instead.
+ *
+ * opt_cb can be NULL if you just want normal name=value parsing, however
+ * if one or more entries in your form are bulk data (file transfer), you
+ * can provide this callback and filter on the name callback parameter to
+ * treat that urldecoded data separately.  The callback should return -1
+ * in case of fatal error, and 0 if OK.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_spa *
+lws_spa_create(struct lws *wsi, const char * const *param_names,
+              int count_params, int max_storage, lws_spa_fileupload_cb opt_cb,
+              void *opt_data);
+
+typedef struct lws_spa_create_info {
+       const char * const *param_names; /* array of form parameter names, like "username" */
+       int count_params; /* count of param_names */
+       int max_storage; /* total amount of form parameter values we can store */
+       lws_spa_fileupload_cb opt_cb; /* NULL, or callback to receive file upload data. */
+       void *opt_data; /* NULL, or user pointer provided to opt_cb. */
+       size_t param_names_stride; /* 0 if param_names is an array of char *.
+                                       Else stride to next char * */
+       struct lwsac **ac;      /* NULL, or pointer to lwsac * to contain all
+                                  related heap allocations */
+       size_t ac_chunk_size;   /* 0 for default, or ac chunk size */
+} lws_spa_create_info_t;
+
+/**
+ * lws_spa_create_via_info() - create urldecode parser
+ *
+ * \param wsi: lws connection (used to find Content Type)
+ * \param info: pointer to struct defining the arguments
+ *
+ * Creates a urldecode parser and initializes it.
+ *
+ * opt_cb can be NULL if you just want normal name=value parsing, however
+ * if one or more entries in your form are bulk data (file transfer), you
+ * can provide this callback and filter on the name callback parameter to
+ * treat that urldecoded data separately.  The callback should return -1
+ * in case of fatal error, and 0 if OK.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_spa *
+lws_spa_create_via_info(struct lws *wsi, const lws_spa_create_info_t *info);
+
+/**
+ * lws_spa_process() - parses a chunk of input data
+ *
+ * \param spa: the parser object previously created
+ * \param in: incoming, urlencoded data
+ * \param len: count of bytes valid at \param in
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_process(struct lws_spa *spa, const char *in, int len);
+
+/**
+ * lws_spa_finalize() - indicate incoming data completed
+ *
+ * \param spa: the parser object previously created
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_finalize(struct lws_spa *spa);
+
+/**
+ * lws_spa_get_length() - return length of parameter value
+ *
+ * \param spa: the parser object previously created
+ * \param n: parameter ordinal to return length of value for
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_get_length(struct lws_spa *spa, int n);
+
+/**
+ * lws_spa_get_string() - return pointer to parameter value
+ * \param spa: the parser object previously created
+ * \param n: parameter ordinal to return pointer to value for
+ */
+LWS_VISIBLE LWS_EXTERN const char *
+lws_spa_get_string(struct lws_spa *spa, int n);
+
+/**
+ * lws_spa_destroy() - destroy parser object
+ *
+ * \param spa: the parser object previously created
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_spa_destroy(struct lws_spa *spa);
+///@}
diff --git a/include/libwebsockets/lws-stats.h b/include/libwebsockets/lws-stats.h
new file mode 100644 (file)
index 0000000..58d02de
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*
+ * Stats are all uint64_t numbers that start at 0.
+ * Index names here have the convention
+ *
+ *  _C_ counter
+ *  _B_ byte count
+ *  _MS_ millisecond count
+ */
+
+enum {
+       LWSSTATS_C_CONNECTIONS, /**< count incoming connections */
+       LWSSTATS_C_API_CLOSE, /**< count calls to close api */
+       LWSSTATS_C_API_READ, /**< count calls to read from socket api */
+       LWSSTATS_C_API_LWS_WRITE, /**< count calls to lws_write API */
+       LWSSTATS_C_API_WRITE, /**< count calls to write API */
+       LWSSTATS_C_WRITE_PARTIALS, /**< count of partial writes */
+       LWSSTATS_C_WRITEABLE_CB_REQ, /**< count of writable callback requests */
+       LWSSTATS_C_WRITEABLE_CB_EFF_REQ, /**< count of effective writable callback requests */
+       LWSSTATS_C_WRITEABLE_CB, /**< count of writable callbacks */
+       LWSSTATS_C_SSL_CONNECTIONS_FAILED, /**< count of failed SSL connections */
+       LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, /**< count of accepted SSL connections */
+       LWSSTATS_C_SSL_ACCEPT_SPIN, /**< count of SSL_accept() attempts */
+       LWSSTATS_C_SSL_CONNS_HAD_RX, /**< count of accepted SSL conns that have had some RX */
+       LWSSTATS_C_TIMEOUTS, /**< count of timed-out connections */
+       LWSSTATS_C_SERVICE_ENTRY, /**< count of entries to lws service loop */
+       LWSSTATS_B_READ, /**< aggregate bytes read */
+       LWSSTATS_B_WRITE, /**< aggregate bytes written */
+       LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, /**< aggreate of size of accepted write data from new partials */
+       LWSSTATS_US_SSL_ACCEPT_LATENCY_AVG, /**< aggregate delay in accepting connection */
+       LWSSTATS_US_WRITABLE_DELAY_AVG, /**< aggregate delay between asking for writable and getting cb */
+       LWSSTATS_US_WORST_WRITABLE_DELAY, /**< single worst delay between asking for writable and getting cb */
+       LWSSTATS_US_SSL_RX_DELAY_AVG, /**< aggregate delay between ssl accept complete and first RX */
+       LWSSTATS_C_PEER_LIMIT_AH_DENIED, /**< number of times we would have given an ah but for the peer limit */
+       LWSSTATS_C_PEER_LIMIT_WSI_DENIED, /**< number of times we would have given a wsi but for the peer limit */
+       LWSSTATS_C_CONNS_CLIENT, /**< attempted client conns */
+       LWSSTATS_C_CONNS_CLIENT_FAILED, /**< failed client conns */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility
+        *
+        * UPDATE stat_names in stats.c in sync with this!
+        */
+       LWSSTATS_SIZE
+};
+
+#if defined(LWS_WITH_STATS)
+
+LWS_VISIBLE LWS_EXTERN uint64_t
+lws_stats_get(struct lws_context *context, int index);
+LWS_VISIBLE LWS_EXTERN void
+lws_stats_log_dump(struct lws_context *context);
+#else
+static LWS_INLINE uint64_t
+lws_stats_get(struct lws_context *context, int index) { (void)context; (void)index;  return 0; }
+static LWS_INLINE void
+lws_stats_log_dump(struct lws_context *context) { (void)context; }
+#endif
diff --git a/include/libwebsockets/lws-struct.h b/include/libwebsockets/lws-struct.h
new file mode 100644 (file)
index 0000000..0d02f59
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+#if defined(LWS_WITH_STRUCT_SQLITE3)
+#include <sqlite3.h>
+#endif
+
+typedef enum {
+       LSMT_SIGNED,
+       LSMT_UNSIGNED,
+       LSMT_BOOLEAN,
+       LSMT_STRING_CHAR_ARRAY,
+       LSMT_STRING_PTR,
+       LSMT_LIST,
+       LSMT_CHILD_PTR,
+       LSMT_SCHEMA,
+
+} lws_struct_map_type_eum;
+
+typedef struct lejp_collation {
+       struct lws_dll2 chunks;
+       int len;
+       char buf[LEJP_STRING_CHUNK + 1];
+} lejp_collation_t;
+
+typedef struct lws_struct_map {
+       const char *colname;
+       const struct lws_struct_map *child_map;
+       lejp_callback lejp_cb;
+       size_t ofs;                     /* child dll2; points to dll2_owner */
+       size_t aux;
+       size_t ofs_clist;
+       size_t child_map_size;
+       lws_struct_map_type_eum type;
+} lws_struct_map_t;
+
+typedef int (*lws_struct_args_cb)(void *obj, void *cb_arg);
+
+typedef struct lws_struct_args {
+       const lws_struct_map_t *map_st[LEJP_MAX_PARSING_STACK_DEPTH];
+       lws_struct_args_cb cb;
+       struct lwsac *ac;
+       void *cb_arg;
+       void *dest;
+
+       size_t dest_len;
+       size_t toplevel_dll2_ofs;
+       size_t map_entries_st[LEJP_MAX_PARSING_STACK_DEPTH];
+       size_t ac_block_size;
+       int subtype;
+
+       /*
+        * temp ac used to collate unknown possibly huge strings before final
+        * allocation and copy
+        */
+       struct lwsac *ac_chunks;
+       struct lws_dll2_owner chunks_owner;
+       size_t chunks_length;
+} lws_struct_args_t;
+
+#define LSM_SIGNED(type, name, qname) \
+       { \
+         qname, \
+         NULL, \
+         NULL, \
+         offsetof(type, name), \
+         sizeof ((type *)0)->name, \
+         0, \
+         0, \
+         LSMT_SIGNED \
+       }
+
+#define LSM_UNSIGNED(type, name, qname) \
+       { \
+         qname, \
+         NULL, \
+         NULL, \
+         offsetof(type, name), \
+         sizeof ((type *)0)->name, \
+         0, \
+         0, \
+         LSMT_UNSIGNED \
+       }
+
+#define LSM_BOOLEAN(type, name, qname) \
+       { \
+         qname, \
+         NULL, \
+         NULL, \
+         offsetof(type, name), \
+         sizeof ((type *)0)->name, \
+         0, \
+         0, \
+         LSMT_BOOLEAN \
+       }
+
+#define LSM_CARRAY(type, name, qname) \
+       { \
+         qname, \
+         NULL, \
+         NULL, \
+         offsetof(type, name), \
+         sizeof (((type *)0)->name), \
+         0, \
+         0, \
+         LSMT_STRING_CHAR_ARRAY \
+       }
+
+#define LSM_STRING_PTR(type, name, qname) \
+       { \
+         qname, \
+         NULL, \
+         NULL, \
+         offsetof(type, name), \
+         sizeof (((type *)0)->name), \
+         0, \
+         0, \
+         LSMT_STRING_PTR \
+       }
+
+#define LSM_LIST(ptype, pname, ctype, cname, lejp_cb, cmap, qname) \
+       { \
+         qname, \
+         cmap, \
+         lejp_cb, \
+         offsetof(ptype, pname), \
+         sizeof (ctype), \
+         offsetof(ctype, cname), \
+         LWS_ARRAY_SIZE(cmap), \
+         LSMT_LIST \
+       }
+
+#define LSM_CHILD_PTR(ptype, pname, ctype, lejp_cb, cmap, qname) \
+       { \
+         qname, \
+         cmap, \
+         lejp_cb, \
+         offsetof(ptype, pname), \
+         sizeof (ctype), \
+         0, \
+         LWS_ARRAY_SIZE(cmap), \
+         LSMT_CHILD_PTR \
+       }
+
+#define LSM_SCHEMA(ctype, lejp_cb, map, schema_name) \
+       { \
+         schema_name, \
+         map, \
+         lejp_cb, \
+         0, \
+         sizeof (ctype), \
+         0, \
+         LWS_ARRAY_SIZE(map), \
+         LSMT_SCHEMA \
+       }
+
+#define LSM_SCHEMA_DLL2(ctype, cdll2mem, lejp_cb, map, schema_name) \
+       { \
+         schema_name, \
+         map, \
+         lejp_cb, \
+         offsetof(ctype, cdll2mem), \
+         sizeof (ctype), \
+         0, \
+         LWS_ARRAY_SIZE(map), \
+         LSMT_SCHEMA \
+       }
+
+typedef struct lws_struct_serialize_st {
+       const struct lws_dll2 *dllpos;
+       const lws_struct_map_t *map;
+       const char *obj;
+       size_t map_entries;
+       size_t map_entry;
+       size_t size;
+       char subsequent;
+       char idt;
+} lws_struct_serialize_st_t;
+
+enum {
+       LSSERJ_FLAG_PRETTY = 1
+};
+
+typedef struct lws_struct_serialize {
+       lws_struct_serialize_st_t st[LEJP_MAX_PARSING_STACK_DEPTH];
+
+       size_t offset;
+       size_t remaining;
+
+       int sp;
+       int flags;
+} lws_struct_serialize_t;
+
+typedef enum {
+       LSJS_RESULT_CONTINUE,
+       LSJS_RESULT_FINISH,
+       LSJS_RESULT_ERROR
+} lws_struct_json_serialize_result_t;
+
+LWS_VISIBLE LWS_EXTERN int
+lws_struct_json_init_parse(struct lejp_ctx *ctx, lejp_callback cb,
+                          void *user);
+
+LWS_VISIBLE LWS_EXTERN signed char
+lws_struct_schema_only_lejp_cb(struct lejp_ctx *ctx, char reason);
+
+LWS_VISIBLE LWS_EXTERN signed char
+lws_struct_default_lejp_cb(struct lejp_ctx *ctx, char reason);
+
+LWS_VISIBLE LWS_EXTERN lws_struct_serialize_t *
+lws_struct_json_serialize_create(const lws_struct_map_t *map,
+                                size_t map_entries, int flags, void *ptoplevel);
+
+LWS_VISIBLE LWS_EXTERN void
+lws_struct_json_serialize_destroy(lws_struct_serialize_t **pjs);
+
+LWS_VISIBLE LWS_EXTERN lws_struct_json_serialize_result_t
+lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf,
+                         size_t len, size_t *written);
+
+#if defined(LWS_WITH_STRUCT_SQLITE3)
+
+LWS_VISIBLE LWS_EXTERN int
+lws_struct_sq3_deserialize(sqlite3 *pdb, const lws_struct_map_t *schema,
+                          lws_dll2_owner_t *o, struct lwsac **ac,
+                          uint64_t start, int limit);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_struct_sq3_open(struct lws_context *context, const char *sqlite3_path,
+                   sqlite3 **pdb);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_struct_sq3_close(sqlite3 **pdb);
+
+#endif
diff --git a/include/libwebsockets/lws-system.h b/include/libwebsockets/lws-system.h
new file mode 100644 (file)
index 0000000..6ad2f7a
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ *
+ * This provides a clean way to interface lws user code to be able to
+ * work unchanged on different systems for fetching common system information,
+ * and performing common system operations like reboot.
+ *
+ * An ops struct with the system-specific implementations is set at
+ * context creation time, and apis are provided that call through to
+ * those where they exist.
+ */
+
+typedef enum {
+       LWS_SYSI_HRS_DEVICE_MODEL = 1,
+       LWS_SYSI_HRS_DEVICE_SERIAL,
+       LWS_SYSI_HRS_FIRMWARE_VERSION,
+
+       LWS_SYSI_USER_BASE = 100
+} lws_system_item_t;
+
+typedef union {
+       const char      *hrs;   /* human readable string */
+       void            *data;
+       time_t          t;
+} lws_system_arg_t;
+
+typedef struct lws_system_ops {
+       int (*get_info)(lws_system_item_t i, lws_system_arg_t arg, size_t *len);
+       int (*reboot)(void);
+} lws_system_ops_t;
+
+/* wrappers handle NULL members or no ops struct set at all cleanly */
+
+/**
+ * lws_system_get_info() - get standardized system information
+ *
+ * \param context: the lws_context
+ * \param item: which information to fetch
+ * \param arg: where to place the result
+ * \param len: incoming: max length of result, outgoing: used length of result
+ *
+ * This queries a standardized information-fetching ops struct that can be
+ * applied to the context... the advantage is it allows you to get common items
+ * of information like a device serial number writing the code once, even if the
+ * actual serial number muse be fetched in wildly different ways depending on
+ * the exact platform it's running on.
+ *
+ * Set arg and *len on entry to be the result location and the max length that
+ * can be used there, on seccessful exit *len is set to the actual length and
+ * 0 is returned.  On error, 1 is returned.
+ */
+LWS_EXTERN LWS_VISIBLE int
+lws_system_get_info(struct lws_context *context, lws_system_item_t item,
+                   lws_system_arg_t arg, size_t *len);
+
+
+/**
+ * lws_system_reboot() - if provided, use the lws_system ops to reboot
+ *
+ * \param context: the lws_context
+ *
+ * If possible, the system will reboot.  Otherwise returns 1.
+ */
+LWS_EXTERN LWS_VISIBLE int
+lws_system_reboot(struct lws_context *context);
diff --git a/include/libwebsockets/lws-test-sequencer.h b/include/libwebsockets/lws-test-sequencer.h
new file mode 100644 (file)
index 0000000..45680b0
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ *
+ * lws_test_sequencer manages running an array of unit tests.
+ */
+
+typedef void (*lws_test_sequence_cb)(const void *cb_user);
+
+typedef struct lws_test_sequencer_args {
+       lws_abs_t               *abs; /* abstract protocol + unit test txport */
+       lws_unit_test_t *tests; /* array of lws_unit_test_t */
+       int                     *results; /* takes result dispositions */
+       int                     results_max; /* max space usable in results */
+       int                     *count_tests; /* count of done tests */
+       int                     *count_passes; /* count of passed tests */
+       lws_test_sequence_cb    cb; /* completion callback */
+       void                    *cb_user; /* opaque user ptr given to cb */
+} lws_test_sequencer_args_t;
+
+/**
+ * lws_abs_unit_test_sequencer() - helper to sequence multiple unit tests
+ *
+ * \param args: lws_test_sequencer_args_t prepared with arguments for the tests
+ *
+ * This helper sequences one or more unit tests to run and collects the results.
+ *
+ * The incoming abs should be set up for the abstract protocol you want to test
+ * and the lws unit-test transport.
+ *
+ * Results are one of
+ *
+ *     LPE_SUCCEEDED
+ *     LPE_FAILED
+ *     LPE_FAILED_UNEXPECTED_TIMEOUT
+ *     LPE_FAILED_UNEXPECTED_PASS
+ *     LPE_FAILED_UNEXPECTED_CLOSE
+ *
+ * The callback args->cb is called when the tests have been done.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_abs_unit_test_sequencer(const lws_test_sequencer_args_t *args);
diff --git a/include/libwebsockets/lws-threadpool.h b/include/libwebsockets/lws-threadpool.h
new file mode 100644 (file)
index 0000000..eb6c6e1
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup threadpool Threadpool related functions
+ * ##Threadpool
+ * \ingroup lwsapi
+ *
+ * This allows you to create one or more pool of threads which can run tasks
+ * associated with a wsi.  If the pool is busy, tasks wait on a queue.
+ *
+ * Tasks don't have to be atomic, if they will take more than a few tens of ms
+ * they should return back to the threadpool worker with a return of 0.  This
+ * will allow them to abort cleanly.
+ */
+//@{
+
+struct lws_threadpool;
+struct lws_threadpool_task;
+
+enum lws_threadpool_task_status {
+       LWS_TP_STATUS_QUEUED,
+       LWS_TP_STATUS_RUNNING,
+       LWS_TP_STATUS_SYNCING,
+       LWS_TP_STATUS_STOPPING,
+       LWS_TP_STATUS_FINISHED, /* lws_threadpool_task_status() frees task */
+       LWS_TP_STATUS_STOPPED, /* lws_threadpool_task_status() frees task */
+};
+
+enum lws_threadpool_task_return {
+       /** Still work to do, just confirming not being stopped */
+       LWS_TP_RETURN_CHECKING_IN,
+       /** Still work to do, enter cond_wait until service thread syncs.  This
+        * is used if you have filled your buffer(s) of data to the service
+        * thread and are blocked until the service thread completes sending at
+        * least one.
+        */
+       LWS_TP_RETURN_SYNC,
+       /** No more work to do... */
+       LWS_TP_RETURN_FINISHED,
+       /** Responding to request to stop */
+       LWS_TP_RETURN_STOPPED,
+
+       /* OR on to indicate this task wishes to outlive its wsi */
+       LWS_TP_RETURN_FLAG_OUTLIVE = 64
+};
+
+struct lws_threadpool_create_args {
+       int threads;
+       int max_queue_depth;
+};
+
+struct lws_threadpool_task_args {
+       struct lws *wsi;        /**< user must set to wsi task is bound to */
+       void *user;             /**< user may set (user-private pointer) */
+       const char *name;       /**< user may set to describe task */
+       char async_task;        /**< set to allow the task to shrug off the loss
+                                    of the associated wsi and continue to
+                                    completion */
+       enum lws_threadpool_task_return (*task)(void *user,
+                                       enum lws_threadpool_task_status s);
+       /**< user must set to actual task function */
+       void (*cleanup)(struct lws *wsi, void *user);
+       /**< socket lifecycle may end while task is not stoppable, so the task
+        * must be able to detach from any wsi and clean itself up when it does
+        * stop.  If NULL, no cleanup necessary, otherwise point to a user-
+        * supplied function that destroys the stuff in \p user.
+        *
+        * wsi may be NULL on entry, indicating the task got detached due to the
+        * wsi closing before.
+        */
+};
+
+/**
+ * lws_threadpool_create() - create a pool of worker threads
+ *
+ * \param context: the lws_context the threadpool will exist inside
+ * \param args: argument struct prepared by caller
+ * \param format: printf-type format for the task name
+ * \param ...: printf type args for the task name format
+ *
+ * Creates a pool of worker threads with \p threads and a queue of up to
+ * \p max_queue_depth waiting tasks if all the threads are busy.
+ *
+ * Returns NULL if OOM, or a struct lws_threadpool pointer that must be
+ * destroyed by lws_threadpool_destroy().
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_threadpool *
+lws_threadpool_create(struct lws_context *context,
+                     const struct lws_threadpool_create_args *args,
+                     const char *format, ...) LWS_FORMAT(3);
+
+/**
+ * lws_threadpool_finish() - Stop all pending and running tasks
+ *
+ * \param tp: the threadpool object
+ *
+ * Marks the threadpool as under destruction.  Removes everything from the
+ * pending queue and completes those tasks as LWS_TP_STATUS_STOPPED.
+ *
+ * Running tasks will also get LWS_TP_STATUS_STOPPED as soon as they
+ * "resurface".
+ *
+ * This doesn't reap tasks or free the threadpool, the reaping is done by the
+ * lws_threadpool_task_status() on the done task.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_threadpool_finish(struct lws_threadpool *tp);
+
+/**
+ * lws_threadpool_destroy() - Destroy a threadpool
+ *
+ * \param tp: the threadpool object
+ *
+ * Waits for all worker threads to stop, ends the threads and frees the tp.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_threadpool_destroy(struct lws_threadpool *tp);
+
+/**
+ * lws_threadpool_enqueue() - Queue the task and run it on a worker thread when possible
+ *
+ * \param tp: the threadpool to queue / run on
+ * \param args: information about what to run
+ * \param format: printf-type format for the task name
+ * \param ...: printf type args for the task name format
+ *
+ * This asks for a task to run ASAP on a worker thread in threadpool \p tp.
+ *
+ * The args defines the wsi, a user-private pointer, a timeout in secs and
+ * a pointer to the task function.
+ *
+ * Returns NULL or an opaque pointer to the queued (or running, or completed)
+ * task.
+ *
+ * Once a task is created and enqueued, it can only be destroyed by calling
+ * lws_threadpool_task_status() on it after it has reached the state
+ * LWS_TP_STATUS_FINISHED or LWS_TP_STATUS_STOPPED.
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_threadpool_task *
+lws_threadpool_enqueue(struct lws_threadpool *tp,
+                      const struct lws_threadpool_task_args *args,
+                      const char *format, ...) LWS_FORMAT(3);
+
+/**
+ * lws_threadpool_dequeue() - Dequeue or try to stop a running task
+ *
+ * \param wsi: the wsi whose current task we want to eliminate
+ *
+ * Returns 0 is the task was dequeued or already compeleted, or 1 if the task
+ * has been asked to stop asynchronously.
+ *
+ * This doesn't free the task.  It only shortcuts it to state
+ * LWS_TP_STATUS_STOPPED.  lws_threadpool_task_status() must be performed on
+ * the task separately once it is in LWS_TP_STATUS_STOPPED to free the task.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_threadpool_dequeue(struct lws *wsi);
+
+/**
+ * lws_threadpool_task_status() - Dequeue or try to stop a running task
+ *
+ * \param wsi: the wsi to query the current task of
+ * \param task: receives a pointer to the opaque task
+ * \param user: receives a void * pointer to the task user data
+ *
+ * This is the equivalent of posix waitpid()... it returns the status of the
+ * task, and if the task is in state LWS_TP_STATUS_FINISHED or
+ * LWS_TP_STATUS_STOPPED, frees \p task.  If in another state, the task
+ * continues to exist.
+ *
+ * This is designed to be called from the service thread.
+ *
+ * Its use is to make sure the service thread has seen the state of the task
+ * before deleting it.
+ */
+LWS_VISIBLE LWS_EXTERN enum lws_threadpool_task_status
+lws_threadpool_task_status_wsi(struct lws *wsi,
+                              struct lws_threadpool_task **task, void **user);
+
+/**
+ * lws_threadpool_task_sync() - Indicate to a stalled task it may continue
+ *
+ * \param task: the task to unblock
+ * \param stop: 0 = run after unblock, 1 = when he unblocks, stop him
+ *
+ * Inform the task that the service thread has finished with the shared data
+ * and that the task, if blocked in LWS_TP_RETURN_SYNC, may continue.
+ *
+ * If the lws service context determined that the task must be aborted, it
+ * should still call this but with stop = 1, causing the task to finish.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_threadpool_task_sync(struct lws_threadpool_task *task, int stop);
+
+/**
+ * lws_threadpool_dump() - dump the state of a threadpool to the log
+ *
+ * \param tp: The threadpool to dump
+ *
+ * This locks the threadpool and then dumps the pending queue, the worker
+ * threads and the done queue, together with time information for how long
+ * the tasks have been in their current state, how long they have occupied a
+ * thread, etc.
+ *
+ * This only does anything on lws builds with CMAKE_BUILD_TYPE=DEBUG, otherwise
+ * while it still exists, it's a NOP.
+ */
+
+LWS_VISIBLE LWS_EXTERN void
+lws_threadpool_dump(struct lws_threadpool *tp);
+//@}
diff --git a/include/libwebsockets/lws-timeout-timer.h b/include/libwebsockets/lws-timeout-timer.h
new file mode 100644 (file)
index 0000000..9a9fb43
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup timeout Connection timeouts
+
+    APIs related to setting connection timeouts
+*/
+//@{
+
+/*
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+enum pending_timeout {
+       NO_PENDING_TIMEOUT                                      =  0,
+       PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE                 =  1,
+       PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE               =  2,
+       PENDING_TIMEOUT_ESTABLISH_WITH_SERVER                   =  3,
+       PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE                =  4,
+       PENDING_TIMEOUT_AWAITING_PING                           =  5,
+       PENDING_TIMEOUT_CLOSE_ACK                               =  6,
+       PENDING_TIMEOUT_UNUSED1                                 =  7,
+       PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE                   =  8,
+       PENDING_TIMEOUT_SSL_ACCEPT                              =  9,
+       PENDING_TIMEOUT_HTTP_CONTENT                            = 10,
+       PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND                 = 11,
+       PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE                  = 12,
+       PENDING_TIMEOUT_SHUTDOWN_FLUSH                          = 13,
+       PENDING_TIMEOUT_CGI                                     = 14,
+       PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE                     = 15,
+       PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING                 = 16,
+       PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG                  = 17,
+       PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD                    = 18,
+       PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY           = 19,
+       PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY            = 20,
+       PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY               = 21,
+       PENDING_TIMEOUT_KILLED_BY_SSL_INFO                      = 22,
+       PENDING_TIMEOUT_KILLED_BY_PARENT                        = 23,
+       PENDING_TIMEOUT_CLOSE_SEND                              = 24,
+       PENDING_TIMEOUT_HOLDING_AH                              = 25,
+       PENDING_TIMEOUT_UDP_IDLE                                = 26,
+       PENDING_TIMEOUT_CLIENT_CONN_IDLE                        = 27,
+       PENDING_TIMEOUT_LAGGING                                 = 28,
+       PENDING_TIMEOUT_THREADPOOL                              = 29,
+       PENDING_TIMEOUT_THREADPOOL_TASK                         = 30,
+       PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE            = 31,
+       PENDING_TIMEOUT_USER_OK                                 = 32,
+
+       /****** add new things just above ---^ ******/
+
+       PENDING_TIMEOUT_USER_REASON_BASE                        = 1000
+};
+
+#define lws_time_in_microseconds lws_now_usecs
+
+#define LWS_TO_KILL_ASYNC -1
+/**< If LWS_TO_KILL_ASYNC is given as the timeout sec in a lws_set_timeout()
+ * call, then the connection is marked to be killed at the next timeout
+ * check.  This is how you should force-close the wsi being serviced if
+ * you are doing it outside the callback (where you should close by nonzero
+ * return).
+ */
+#define LWS_TO_KILL_SYNC -2
+/**< If LWS_TO_KILL_SYNC is given as the timeout sec in a lws_set_timeout()
+ * call, then the connection is closed before returning (which may delete
+ * the wsi).  This should only be used where the wsi being closed is not the
+ * wsi currently being serviced.
+ */
+/**
+ * lws_set_timeout() - marks the wsi as subject to a timeout some seconds hence
+ *
+ * \param wsi: Websocket connection instance
+ * \param reason:      timeout reason
+ * \param secs:        how many seconds.  You may set to LWS_TO_KILL_ASYNC to
+ *             force the connection to timeout at the next opportunity, or
+ *             LWS_TO_KILL_SYNC to close it synchronously if you know the
+ *             wsi is not the one currently being serviced.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs);
+
+/**
+ * lws_set_timeout_us() - marks the wsi as subject to a timeout some us hence
+ *
+ * \param wsi: Websocket connection instance
+ * \param reason:      timeout reason
+ * \param us:  0 removes the timeout, otherwise number of us to wait
+ *
+ * Higher-resolution version of lws_set_timeout().  Actual resolution depends
+ * on platform and load, usually ms.
+ */
+void
+lws_set_timeout_us(struct lws *wsi, enum pending_timeout reason, lws_usec_t us);
+
+#define LWS_SET_TIMER_USEC_CANCEL ((lws_usec_t)-1ll)
+#define LWS_USEC_PER_SEC (1000000ll)
+
+/**
+ * lws_set_timer_usecs() - schedules a callback on the wsi in the future
+ *
+ * \param wsi: Websocket connection instance
+ * \param usecs:  LWS_SET_TIMER_USEC_CANCEL removes any existing scheduled
+ *               callback, otherwise number of microseconds in the future
+ *               the callback will occur at.
+ *
+ * NOTE: event loop support for this:
+ *
+ *  default poll() loop:   yes
+ *  libuv event loop:      yes
+ *  libev:    not implemented (patch welcome)
+ *  libevent: not implemented (patch welcome)
+ *
+ * After the deadline expires, the wsi will get a callback of type
+ * LWS_CALLBACK_TIMER and the timer is exhausted.  The deadline may be
+ * continuously deferred by further calls to lws_set_timer_usecs() with a later
+ * deadline, or cancelled by lws_set_timer_usecs(wsi, -1).
+ *
+ * If the timer should repeat, lws_set_timer_usecs() must be called again from
+ * LWS_CALLBACK_TIMER.
+ *
+ * Accuracy depends on the platform and the load on the event loop or system...
+ * all that's guaranteed is the callback will come after the requested wait
+ * period.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs);
+
+/*
+ * lws_timed_callback_vh_protocol() - calls back a protocol on a vhost after
+ *                                     the specified delay in seconds
+ *
+ * \param vh:   the vhost to call back
+ * \param protocol: the protocol to call back
+ * \param reason: callback reason
+ * \param secs:        how many seconds in the future to do the callback.
+ *
+ * Callback the specified protocol with a fake wsi pointing to the specified
+ * vhost and protocol, with the specified reason, at the specified time in the
+ * future.
+ *
+ * Returns 0 if OK or 1 on OOM.
+ *
+ * In the multithreaded service case, the callback will occur in the same
+ * service thread context as the call to this api that requested it.  If it is
+ * called from a non-service thread, tsi 0 will handle it.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_timed_callback_vh_protocol(struct lws_vhost *vh,
+                              const struct lws_protocols *prot,
+                              int reason, int secs);
+
+/*
+ * lws_timed_callback_vh_protocol_us() - calls back a protocol on a vhost after
+ *                                      the specified delay in us
+ *
+ * \param vh:   the vhost to call back
+ * \param protocol: the protocol to call back
+ * \param reason: callback reason
+ * \param us:  how many us in the future to do the callback.
+ *
+ * Callback the specified protocol with a fake wsi pointing to the specified
+ * vhost and protocol, with the specified reason, at the specified time in the
+ * future.
+ *
+ * Returns 0 if OK or 1 on OOM.
+ *
+ * In the multithreaded service case, the callback will occur in the same
+ * service thread context as the call to this api that requested it.  If it is
+ * called from a non-service thread, tsi 0 will handle it.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_timed_callback_vh_protocol_us(struct lws_vhost *vh,
+                                 const struct lws_protocols *prot, int reason,
+                                 lws_usec_t us);
+
+
+typedef struct lws_sorted_usec_list lws_sorted_usec_list_t;
+typedef void (*sul_cb_t)(lws_sorted_usec_list_t *sul);
+
+typedef struct lws_sorted_usec_list {
+       struct lws_dll2 list;   /* simplify the code by keeping this at start */
+       sul_cb_t        cb;
+       lws_usec_t      us;
+} lws_sorted_usec_list_t;
+
+
+/*
+ * lws_sul_schedule() - schedule a callback
+ *
+ * \param context: the lws_context
+ * \param tsi: the thread service index (usually 0)
+ * \param sul: pointer to the sul element
+ * \param cb: the scheduled callback
+ * \param us: the delay before the callback arrives, or
+ *             LWS_SET_TIMER_USEC_CANCEL to cancel it.
+ *
+ * Generic callback-at-a-later time function.  The callback happens on the
+ * event loop thread context.
+ *
+ * Although the api has us resultion, the actual resolution depends on the
+ * platform and is commonly 1ms.
+ *
+ * This doesn't allocate and doesn't fail.
+ *
+ * You can call it again with another us value to change the delay.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_sul_schedule(struct lws_context *context, int tsi,
+                lws_sorted_usec_list_t *sul, sul_cb_t cb, lws_usec_t us);
+
+///@}
diff --git a/include/libwebsockets/lws-tokenize.h b/include/libwebsockets/lws-tokenize.h
new file mode 100644 (file)
index 0000000..0e14284
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/* Do not treat - as a terminal character, so "my-token" is one token */
+#define LWS_TOKENIZE_F_MINUS_NONTERM   (1 << 0)
+/* Separately report aggregate colon-delimited tokens */
+#define LWS_TOKENIZE_F_AGG_COLON       (1 << 1)
+/* Enforce sequencing for a simple token , token , token ... list */
+#define LWS_TOKENIZE_F_COMMA_SEP_LIST  (1 << 2)
+/* Allow more characters in the tokens and less delimiters... default is
+ * only alphanumeric + underscore in tokens */
+#define LWS_TOKENIZE_F_RFC7230_DELIMS  (1 << 3)
+/* Do not treat . as a terminal character, so "warmcat.com" is one token */
+#define LWS_TOKENIZE_F_DOT_NONTERM     (1 << 4)
+/* If something starts looking like a float, like 1.2, force to be string token.
+ * This lets you receive dotted-quads like 192.168.0.1 as string tokens, and
+ * avoids illegal float format detection like 1.myserver.com */
+#define LWS_TOKENIZE_F_NO_FLOATS       (1 << 5)
+/* Instead of LWS_TOKZE_INTEGER, report integers as any other string token */
+#define LWS_TOKENIZE_F_NO_INTEGERS     (1 << 6)
+
+typedef enum {
+
+       LWS_TOKZE_ERRS                  =  5, /* the number of errors defined */
+
+       LWS_TOKZE_ERR_BROKEN_UTF8       = -5,   /* malformed or partial utf8 */
+       LWS_TOKZE_ERR_UNTERM_STRING     = -4,   /* ended while we were in "" */
+       LWS_TOKZE_ERR_MALFORMED_FLOAT   = -3,   /* like 0..1 or 0.1.1 */
+       LWS_TOKZE_ERR_NUM_ON_LHS        = -2,   /* like 123= or 0.1= */
+       LWS_TOKZE_ERR_COMMA_LIST        = -1,   /* like ",tok", or, "tok,," */
+
+       LWS_TOKZE_ENDED = 0,            /* no more content */
+
+       /* Note: results have ordinal 1+, EOT is 0 and errors are < 0 */
+
+       LWS_TOKZE_DELIMITER,            /* a delimiter appeared */
+       LWS_TOKZE_TOKEN,                /* a token appeared */
+       LWS_TOKZE_INTEGER,              /* an integer appeared */
+       LWS_TOKZE_FLOAT,                /* a float appeared */
+       LWS_TOKZE_TOKEN_NAME_EQUALS,    /* token [whitespace] = */
+       LWS_TOKZE_TOKEN_NAME_COLON,     /* token [whitespace] : (only with
+                                          LWS_TOKENIZE_F_AGG_COLON flag) */
+       LWS_TOKZE_QUOTED_STRING,        /* "*", where * may have any char */
+
+} lws_tokenize_elem;
+
+/*
+ * helper enums to allow caller to enforce legal delimiter sequencing, eg
+ * disallow "token,,token", "token,", and ",token"
+ */
+
+enum lws_tokenize_delimiter_tracking {
+       LWSTZ_DT_NEED_FIRST_CONTENT,
+       LWSTZ_DT_NEED_DELIM,
+       LWSTZ_DT_NEED_NEXT_CONTENT,
+};
+
+struct lws_tokenize {
+       const char *start; /**< set to the start of the string to tokenize */
+       const char *token; /**< the start of an identified token or delimiter */
+       int len;        /**< set to the length of the string to tokenize */
+       int token_len;  /**< the length of the identied token or delimiter */
+
+       int flags;      /**< optional LWS_TOKENIZE_F_ flags, or 0 */
+       int delim;
+};
+
+/**
+ * lws_tokenize() - breaks down a string into tokens and delimiters in-place
+ *
+ * \param ts: the lws_tokenize struct to init
+ * \param start: the string to tokenize
+ * \param flags: LWS_TOKENIZE_F_ option flags
+ *
+ * This initializes the tokenize struct to point to the given string, and
+ * sets the length to 2GiB - 1 (so there must be a terminating NUL)... you can
+ * override this requirement by setting ts.len yourself before using it.
+ *
+ * .delim is also initialized to LWSTZ_DT_NEED_FIRST_CONTENT.
+ */
+
+LWS_VISIBLE LWS_EXTERN void
+lws_tokenize_init(struct lws_tokenize *ts, const char *start, int flags);
+
+/**
+ * lws_tokenize() - breaks down a string into tokens and delimiters in-place
+ *
+ * \param ts: the lws_tokenize struct with information and state on what to do
+ *
+ * The \p ts struct should have its start, len and flags members initialized to
+ * reflect the string to be tokenized and any options.
+ *
+ * Then `lws_tokenize()` may be called repeatedly on the struct, returning one
+ * of `lws_tokenize_elem` each time, and with the struct's `token` and
+ * `token_len` members set to describe the content of the delimiter or token
+ * payload each time.
+ *
+ * There are no allocations during the process.
+ *
+ * returns lws_tokenize_elem that was identified (LWS_TOKZE_ENDED means reached
+ * the end of the string).
+ */
+
+LWS_VISIBLE LWS_EXTERN lws_tokenize_elem
+lws_tokenize(struct lws_tokenize *ts);
+
+/**
+ * lws_tokenize_cstr() - copy token string to NUL-terminated buffer
+ *
+ * \param ts: pointer to lws_tokenize struct to operate on
+ * \param str: destination buffer
+ * \pparam max: bytes in destination buffer
+ *
+ * returns 0 if OK or nonzero if the string + NUL won't fit.
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_tokenize_cstr(struct lws_tokenize *ts, char *str, int max);
diff --git a/include/libwebsockets/lws-vfs.h b/include/libwebsockets/lws-vfs.h
new file mode 100644 (file)
index 0000000..00e2fda
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup fops file operation wrapping
+ *
+ * ##File operation wrapping
+ *
+ * Use these helper functions if you want to access a file from the perspective
+ * of a specific wsi, which is usually the case.  If you just want contextless
+ * file access, use the fops callbacks directly with NULL wsi instead of these
+ * helpers.
+ *
+ * If so, then it calls the platform handler or user overrides where present
+ * (as defined in info->fops)
+ *
+ * The advantage from all this is user code can be portable for file operations
+ * without having to deal with differences between platforms.
+ */
+//@{
+
+/** struct lws_plat_file_ops - Platform-specific file operations
+ *
+ * These provide platform-agnostic ways to deal with filesystem access in the
+ * library and in the user code.
+ */
+
+#if defined(LWS_WITH_ESP32)
+/* sdk preprocessor defs? compiler issue? gets confused with member names */
+#define LWS_FOP_OPEN           _open
+#define LWS_FOP_CLOSE          _close
+#define LWS_FOP_SEEK_CUR       _seek_cur
+#define LWS_FOP_READ           _read
+#define LWS_FOP_WRITE          _write
+#else
+#define LWS_FOP_OPEN           open
+#define LWS_FOP_CLOSE          close
+#define LWS_FOP_SEEK_CUR       seek_cur
+#define LWS_FOP_READ           read
+#define LWS_FOP_WRITE          write
+#endif
+
+#define LWS_FOP_FLAGS_MASK                ((1 << 23) - 1)
+#define LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP (1 << 24)
+#define LWS_FOP_FLAG_COMPR_IS_GZIP        (1 << 25)
+#define LWS_FOP_FLAG_MOD_TIME_VALID       (1 << 26)
+#define LWS_FOP_FLAG_VIRTUAL              (1 << 27)
+
+struct lws_plat_file_ops;
+
+struct lws_fop_fd {
+       lws_filefd_type                 fd;
+       /**< real file descriptor related to the file... */
+       const struct lws_plat_file_ops  *fops;
+       /**< fops that apply to this fop_fd */
+       void                            *filesystem_priv;
+       /**< ignored by lws; owned by the fops handlers */
+       lws_filepos_t                   pos;
+       /**< generic "position in file" */
+       lws_filepos_t                   len;
+       /**< generic "length of file" */
+       lws_fop_flags_t                 flags;
+       /**< copy of the returned flags */
+       uint32_t                        mod_time;
+       /**< optional "modification time of file", only valid if .open()
+        * set the LWS_FOP_FLAG_MOD_TIME_VALID flag */
+};
+typedef struct lws_fop_fd *lws_fop_fd_t;
+
+struct lws_fops_index {
+       const char *sig;        /* NULL or vfs signature, eg, ".zip/" */
+       uint8_t len;            /* length of above string */
+};
+
+struct lws_plat_file_ops {
+       lws_fop_fd_t (*LWS_FOP_OPEN)(const struct lws_plat_file_ops *fops,
+                                    const char *filename, const char *vpath,
+                                    lws_fop_flags_t *flags);
+       /**< Open file (always binary access if plat supports it)
+        * vpath may be NULL, or if the fops understands it, the point at which
+        * the filename's virtual part starts.
+        * *flags & LWS_FOP_FLAGS_MASK should be set to O_RDONLY or O_RDWR.
+        * If the file may be gzip-compressed,
+        * LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP is set.  If it actually is
+        * gzip-compressed, then the open handler should OR
+        * LWS_FOP_FLAG_COMPR_IS_GZIP on to *flags before returning.
+        */
+       int (*LWS_FOP_CLOSE)(lws_fop_fd_t *fop_fd);
+       /**< close file AND set the pointer to NULL */
+       lws_fileofs_t (*LWS_FOP_SEEK_CUR)(lws_fop_fd_t fop_fd,
+                                         lws_fileofs_t offset_from_cur_pos);
+       /**< seek from current position */
+       int (*LWS_FOP_READ)(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                           uint8_t *buf, lws_filepos_t len);
+       /**< Read from file, on exit *amount is set to amount actually read */
+       int (*LWS_FOP_WRITE)(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                            uint8_t *buf, lws_filepos_t len);
+       /**< Write to file, on exit *amount is set to amount actually written */
+
+       struct lws_fops_index fi[3];
+       /**< vfs path signatures implying use of this fops */
+
+       const struct lws_plat_file_ops *next;
+       /**< NULL or next fops in list */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility */
+};
+
+/**
+ * lws_get_fops() - get current file ops
+ *
+ * \param context: context
+ */
+LWS_VISIBLE LWS_EXTERN struct lws_plat_file_ops * LWS_WARN_UNUSED_RESULT
+lws_get_fops(struct lws_context *context);
+LWS_VISIBLE LWS_EXTERN void
+lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops);
+/**
+ * lws_vfs_tell() - get current file position
+ *
+ * \param fop_fd: fop_fd we are asking about
+ */
+LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT
+lws_vfs_tell(lws_fop_fd_t fop_fd);
+/**
+ * lws_vfs_get_length() - get current file total length in bytes
+ *
+ * \param fop_fd: fop_fd we are asking about
+ */
+LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT
+lws_vfs_get_length(lws_fop_fd_t fop_fd);
+/**
+ * lws_vfs_get_mod_time() - get time file last modified
+ *
+ * \param fop_fd: fop_fd we are asking about
+ */
+LWS_VISIBLE LWS_EXTERN uint32_t LWS_WARN_UNUSED_RESULT
+lws_vfs_get_mod_time(lws_fop_fd_t fop_fd);
+/**
+ * lws_vfs_file_seek_set() - seek relative to start of file
+ *
+ * \param fop_fd: fop_fd we are seeking in
+ * \param offset: offset from start of file
+ */
+LWS_VISIBLE LWS_EXTERN lws_fileofs_t
+lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+/**
+ * lws_vfs_file_seek_end() - seek relative to end of file
+ *
+ * \param fop_fd: fop_fd we are seeking in
+ * \param offset: offset from start of file
+ */
+LWS_VISIBLE LWS_EXTERN lws_fileofs_t
+lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+
+extern struct lws_plat_file_ops fops_zip;
+
+/**
+ * lws_plat_file_open() - open vfs filepath
+ *
+ * \param fops: file ops struct that applies to this descriptor
+ * \param vfs_path: filename to open
+ * \param flags: pointer to open flags
+ *
+ * The vfs_path is scanned for known fops signatures, and the open directed
+ * to any matching fops open.
+ *
+ * User code should use this api to perform vfs opens.
+ *
+ * returns semi-opaque handle
+ */
+LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT
+lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
+                 lws_fop_flags_t *flags);
+
+/**
+ * lws_plat_file_close() - close file
+ *
+ * \param fop_fd: file handle to close
+ */
+static LWS_INLINE int
+lws_vfs_file_close(lws_fop_fd_t *fop_fd)
+{
+       return (*fop_fd)->fops->LWS_FOP_CLOSE(fop_fd);
+}
+
+/**
+ * lws_plat_file_seek_cur() - close file
+ *
+ *
+ * \param fop_fd: file handle
+ * \param offset: position to seek to
+ */
+static LWS_INLINE lws_fileofs_t
+lws_vfs_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+       return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset);
+}
+/**
+ * lws_plat_file_read() - read from file
+ *
+ * \param fop_fd: file handle
+ * \param amount: how much to read (rewritten by call)
+ * \param buf: buffer to write to
+ * \param len: max length
+ */
+static LWS_INLINE int LWS_WARN_UNUSED_RESULT
+lws_vfs_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                  uint8_t *buf, lws_filepos_t len)
+{
+       return fop_fd->fops->LWS_FOP_READ(fop_fd, amount, buf, len);
+}
+/**
+ * lws_plat_file_write() - write from file
+ *
+ * \param fop_fd: file handle
+ * \param amount: how much to write (rewritten by call)
+ * \param buf: buffer to read from
+ * \param len: max length
+ */
+static LWS_INLINE int LWS_WARN_UNUSED_RESULT
+lws_vfs_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                   uint8_t *buf, lws_filepos_t len)
+{
+       return fop_fd->fops->LWS_FOP_WRITE(fop_fd, amount, buf, len);
+}
+
+/* these are the platform file operations implementations... they can
+ * be called directly and used in fops arrays
+ */
+
+LWS_VISIBLE LWS_EXTERN lws_fop_fd_t
+_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
+                   const char *vpath, lws_fop_flags_t *flags);
+LWS_VISIBLE LWS_EXTERN int
+_lws_plat_file_close(lws_fop_fd_t *fop_fd);
+LWS_VISIBLE LWS_EXTERN lws_fileofs_t
+_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
+LWS_VISIBLE LWS_EXTERN int
+_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                   uint8_t *buf, lws_filepos_t len);
+LWS_VISIBLE LWS_EXTERN int
+_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                    uint8_t *buf, lws_filepos_t len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_alloc_vfs_file(struct lws_context *context, const char *filename,
+                  uint8_t **buf, lws_filepos_t *amount);
+//@}
diff --git a/include/libwebsockets/lws-write.h b/include/libwebsockets/lws-write.h
new file mode 100644 (file)
index 0000000..f6e464d
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup sending-data Sending data
+
+    APIs related to writing data on a connection
+*/
+//@{
+#if !defined(LWS_SIZEOFPTR)
+#define LWS_SIZEOFPTR ((int)sizeof (void *))
+#endif
+
+#if defined(__x86_64__)
+#define _LWS_PAD_SIZE 16       /* Intel recommended for best performance */
+#else
+#define _LWS_PAD_SIZE LWS_SIZEOFPTR   /* Size of a pointer on the target arch */
+#endif
+#define _LWS_PAD(n) (((n) % _LWS_PAD_SIZE) ? \
+               ((n) + (_LWS_PAD_SIZE - ((n) % _LWS_PAD_SIZE))) : (n))
+/* last 2 is for lws-meta */
+#define LWS_PRE _LWS_PAD(4 + 10 + 2)
+/* used prior to 1.7 and retained for backward compatibility */
+#define LWS_SEND_BUFFER_PRE_PADDING LWS_PRE
+#define LWS_SEND_BUFFER_POST_PADDING 0
+
+#define LWS_WRITE_RAW LWS_WRITE_HTTP
+
+/*
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+enum lws_write_protocol {
+       LWS_WRITE_TEXT                                          = 0,
+       /**< Send a ws TEXT message,the pointer must have LWS_PRE valid
+        * memory behind it.
+        *
+        * The receiver expects only valid utf-8 in the payload */
+       LWS_WRITE_BINARY                                        = 1,
+       /**< Send a ws BINARY message, the pointer must have LWS_PRE valid
+        * memory behind it.
+        *
+        * Any sequence of bytes is valid */
+       LWS_WRITE_CONTINUATION                                  = 2,
+       /**< Continue a previous ws message, the pointer must have LWS_PRE valid
+        * memory behind it */
+       LWS_WRITE_HTTP                                          = 3,
+       /**< Send HTTP content */
+
+       /* LWS_WRITE_CLOSE is handled by lws_close_reason() */
+       LWS_WRITE_PING                                          = 5,
+       LWS_WRITE_PONG                                          = 6,
+
+       /* Same as write_http but we know this write ends the transaction */
+       LWS_WRITE_HTTP_FINAL                                    = 7,
+
+       /* HTTP2 */
+
+       LWS_WRITE_HTTP_HEADERS                                  = 8,
+       /**< Send http headers (http2 encodes this payload and LWS_WRITE_HTTP
+        * payload differently, http 1.x links also handle this correctly. so
+        * 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 ---^ ******/
+
+       /* flags */
+
+       LWS_WRITE_BUFLIST = 0x20,
+       /**< Don't actually write it... stick it on the output buflist and
+        *   write it as soon as possible.  Useful if you learn you have to
+        *   write something, have the data to write to hand but the timing is
+        *   unrelated as to whether the connection is writable or not, and were
+        *   otherwise going to have to allocate a temp buffer and write it
+        *   later anyway */
+
+       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
+        * decode the content if used */
+};
+
+/* used with LWS_CALLBACK_CHILD_WRITE_VIA_PARENT */
+
+struct lws_write_passthru {
+       struct lws *wsi;
+       unsigned char *buf;
+       size_t len;
+       enum lws_write_protocol wp;
+};
+
+
+/**
+ * lws_write() - Apply protocol then write data to client
+ *
+ * \param wsi: Websocket instance (available from user callback)
+ * \param buf: The data to send.  For data being sent on a websocket
+ *             connection (ie, not default http), this buffer MUST have
+ *             LWS_PRE bytes valid BEFORE the pointer.
+ *             This is so the protocol header data can be added in-situ.
+ * \param len: Count of the data bytes in the payload starting from buf
+ * \param protocol:    Use LWS_WRITE_HTTP to reply to an http connection, and one
+ *             of LWS_WRITE_BINARY or LWS_WRITE_TEXT to send appropriate
+ *             data on a websockets connection.  Remember to allow the extra
+ *             bytes before and after buf if LWS_WRITE_BINARY or LWS_WRITE_TEXT
+ *             are used.
+ *
+ *     This function provides the way to issue data back to the client
+ *     for both http and websocket protocols.
+ *
+ * IMPORTANT NOTICE!
+ *
+ * When sending with websocket protocol
+ *
+ * LWS_WRITE_TEXT,
+ * LWS_WRITE_BINARY,
+ * LWS_WRITE_CONTINUATION,
+ * LWS_WRITE_PING,
+ * LWS_WRITE_PONG,
+ *
+ * or sending on http/2,
+ *
+ * the send buffer has to have LWS_PRE bytes valid BEFORE the buffer pointer you
+ * pass to lws_write().  Since you'll probably want to use http/2 before too
+ * long, it's wise to just always do this with lws_write buffers... LWS_PRE is
+ * typically 16 bytes it's not going to hurt usually.
+ *
+ * start of alloc       ptr passed to lws_write      end of allocation
+ *       |                         |                         |
+ *       v  <-- LWS_PRE bytes -->  v                         v
+ *       [----------------  allocated memory  ---------------]
+ *              (for lws use)      [====== user buffer ======]
+ *
+ * This allows us to add protocol info before and after the data, and send as
+ * one packet on the network without payload copying, for maximum efficiency.
+ *
+ * So for example you need this kind of code to use lws_write with a
+ * 128-byte payload
+ *
+ *   char buf[LWS_PRE + 128];
+ *
+ *   // fill your part of the buffer... for example here it's all zeros
+ *   memset(&buf[LWS_PRE], 0, 128);
+ *
+ *   lws_write(wsi, &buf[LWS_PRE], 128, LWS_WRITE_TEXT);
+ *
+ * LWS_PRE is at least the frame nonce + 2 header + 8 length
+ * LWS_SEND_BUFFER_POST_PADDING is deprecated, it's now 0 and can be left off.
+ * The example apps no longer use it.
+ *
+ * Pad LWS_PRE to the CPU word size, so that word references
+ * to the address immediately after the padding won't cause an unaligned access
+ * error. Sometimes for performance reasons the recommended padding is even
+ * larger than sizeof(void *).
+ *
+ *     In the case of sending using websocket protocol, be sure to allocate
+ *     valid storage before and after buf as explained above.  This scheme
+ *     allows maximum efficiency of sending data and protocol in a single
+ *     packet while not burdening the user code with any protocol knowledge.
+ *
+ *     Return may be -1 for a fatal error needing connection close, or the
+ *     number of bytes sent.
+ *
+ * Truncated Writes
+ * ================
+ *
+ * The OS may not accept everything you asked to write on the connection.
+ *
+ * Posix defines POLLOUT indication from poll() to show that the connection
+ * will accept more write data, but it doesn't specifiy how much.  It may just
+ * accept one byte of whatever you wanted to send.
+ *
+ * LWS will buffer the remainder automatically, and send it out autonomously.
+ *
+ * During that time, WRITABLE callbacks will be suppressed.
+ *
+ * This is to handle corner cases where unexpectedly the OS refuses what we
+ * usually expect it to accept.  You should try to send in chunks that are
+ * almost always accepted in order to avoid the inefficiency of the buffering.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_write(struct lws *wsi, unsigned char *buf, size_t len,
+         enum lws_write_protocol protocol);
+
+/* helper for case where buffer may be const */
+#define lws_write_http(wsi, buf, len) \
+       lws_write(wsi, (unsigned char *)(buf), len, LWS_WRITE_HTTP)
+
+/**
+ * lws_write_ws_flags() - Helper for multi-frame ws message flags
+ *
+ * \param initial: the lws_write flag to use for the start fragment, eg,
+ *                LWS_WRITE_TEXT
+ * \param is_start: nonzero if this is the first fragment of the message
+ * \param is_end: nonzero if this is the last fragment of the message
+ *
+ * Returns the correct LWS_WRITE_ flag to use for each fragment of a message
+ * in turn.
+ */
+static LWS_INLINE int
+lws_write_ws_flags(int initial, int is_start, int is_end)
+{
+       int r;
+
+       if (is_start)
+               r = initial;
+       else
+               r = LWS_WRITE_CONTINUATION;
+
+       if (!is_end)
+               r |= LWS_WRITE_NO_FIN;
+
+       return r;
+}
+
+/**
+ * lws_raw_transaction_completed() - Helper for flushing before close
+ *
+ * \param wsi: the struct lws to operate on
+ *
+ * Returns -1 if the wsi can close now.  However if there is buffered, unsent
+ * data, the wsi is marked as to be closed when the output buffer data is
+ * drained, and it returns 0.
+ *
+ * For raw cases where the transaction completed without failure,
+ * `return lws_raw_transaction_completed(wsi)` should better be used than
+ * return -1.
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_raw_transaction_completed(struct lws *wsi);
+
+///@}
diff --git a/include/libwebsockets/lws-writeable.h b/include/libwebsockets/lws-writeable.h
new file mode 100644 (file)
index 0000000..dd5659c
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \defgroup callback-when-writeable Callback when writeable
+ *
+ * ##Callback When Writeable
+ *
+ * lws can only write data on a connection when it is able to accept more
+ * data without blocking.
+ *
+ * So a basic requirement is we should only use the lws_write() apis when the
+ * connection we want to write on says that he can accept more data.
+ *
+ * When lws cannot complete your send at the time, it will buffer the data
+ * and send it in the background, suppressing any further WRITEABLE callbacks
+ * on that connection until it completes.  So it is important to write new
+ * things in a new writeable callback.
+ *
+ * These apis reflect the various ways we can indicate we would like to be
+ * called back when one or more connections is writeable.
+ */
+///@{
+
+/**
+ * lws_callback_on_writable() - Request a callback when this socket
+ *                                      becomes able to be written to without
+ *                                      blocking
+ *
+ * \param wsi: Websocket connection instance to get callback for
+ *
+ * - Which:  only this wsi
+ * - When:   when the individual connection becomes writeable
+ * - What: LWS_CALLBACK_*_WRITEABLE
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_on_writable(struct lws *wsi);
+
+/**
+ * lws_callback_on_writable_all_protocol() - Request a callback for all
+ *                     connections using the given protocol when it
+ *                     becomes possible to write to each socket without
+ *                     blocking in turn.
+ *
+ * \param context:     lws_context
+ * \param protocol:    Protocol whose connections will get callbacks
+ *
+ * - Which:  connections using this protocol on ANY VHOST
+ * - When:   when the individual connection becomes writeable
+ * - What: LWS_CALLBACK_*_WRITEABLE
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_on_writable_all_protocol(const struct lws_context *context,
+                                     const struct lws_protocols *protocol);
+
+/**
+ * lws_callback_on_writable_all_protocol_vhost() - Request a callback for
+ *                     all connections on same vhost using the given protocol
+ *                     when it becomes possible to write to each socket without
+ *                     blocking in turn.
+ *
+ * \param vhost:       Only consider connections on this lws_vhost
+ * \param protocol:    Protocol whose connections will get callbacks
+ *
+ * - Which:  connections using this protocol on GIVEN VHOST ONLY
+ * - When:   when the individual connection becomes writeable
+ * - What: LWS_CALLBACK_*_WRITEABLE
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
+                                     const struct lws_protocols *protocol);
+
+/**
+ * lws_callback_all_protocol() - Callback all connections using
+ *                             the given protocol with the given reason
+ *
+ * \param context:     lws_context
+ * \param protocol:    Protocol whose connections will get callbacks
+ * \param reason:      Callback reason index
+ *
+ * - Which:  connections using this protocol on ALL VHOSTS
+ * - When:   before returning
+ * - What:   reason
+ *
+ * This isn't normally what you want... normally any update of connection-
+ * specific information can wait until a network-related callback like rx,
+ * writable, or close.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_all_protocol(struct lws_context *context,
+                         const struct lws_protocols *protocol, int reason);
+
+/**
+ * lws_callback_all_protocol_vhost() - Callback all connections using
+ *                     the given protocol with the given reason.  This is
+ *                     deprecated since v2.4: use lws_callback_all_protocol_vhost_args
+ *
+ * \param vh:          Vhost whose connections will get callbacks
+ * \param protocol:    Which protocol to match.  NULL means all.
+ * \param reason:      Callback reason index
+ *
+ * - Which:  connections using this protocol on GIVEN VHOST ONLY
+ * - When:   now
+ * - What:   reason
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_all_protocol_vhost(struct lws_vhost *vh,
+                               const struct lws_protocols *protocol,
+                               int reason)
+LWS_WARN_DEPRECATED;
+
+/**
+ * lws_callback_all_protocol_vhost_args() - Callback all connections using
+ *                     the given protocol with the given reason and args
+ *
+ * \param vh:          Vhost whose connections will get callbacks
+ * \param protocol:    Which protocol to match.  NULL means all.
+ * \param reason:      Callback reason index
+ * \param argp:                Callback "in" parameter
+ * \param len:         Callback "len" parameter
+ *
+ * - Which:  connections using this protocol on GIVEN VHOST ONLY
+ * - When:   now
+ * - What:   reason
+ */
+LWS_VISIBLE int
+lws_callback_all_protocol_vhost_args(struct lws_vhost *vh,
+                                    const struct lws_protocols *protocol,
+                                    int reason, void *argp, size_t len);
+
+/**
+ * lws_callback_vhost_protocols() - Callback all protocols enabled on a vhost
+ *                                     with the given reason
+ *
+ * \param wsi: wsi whose vhost will get callbacks
+ * \param reason:      Callback reason index
+ * \param in:          in argument to callback
+ * \param len: len argument to callback
+ *
+ * - Which:  connections using this protocol on same VHOST as wsi ONLY
+ * - When:   now
+ * - What:   reason
+ *
+ * This is deprecated since v2.5, use lws_callback_vhost_protocols_vhost()
+ * which takes the pointer to the vhost directly without using or needing the
+ * wsi.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len)
+LWS_WARN_DEPRECATED;
+
+/**
+ * lws_callback_vhost_protocols_vhost() - Callback all protocols enabled on a vhost
+ *                                     with the given reason
+ *
+ * \param vh:          vhost that will get callbacks
+ * \param reason:      Callback reason index
+ * \param in:          in argument to callback
+ * \param len:         len argument to callback
+ *
+ * - Which:  connections using this protocol on same VHOST as wsi ONLY
+ * - When:   now
+ * - What:   reason
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_vhost_protocols_vhost(struct lws_vhost *vh, int reason, void *in,
+                                  size_t len);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len);
+
+/**
+ * lws_get_socket_fd() - returns the socket file descriptor
+ *
+ * This is needed to use sendto() on UDP raw sockets
+ *
+ * \param wsi: Websocket connection instance
+ */
+LWS_VISIBLE LWS_EXTERN lws_sockfd_type
+lws_get_socket_fd(struct lws *wsi);
+
+/**
+ * lws_get_peer_write_allowance() - get the amount of data writeable to peer
+ *                                     if known
+ *
+ * \param wsi: Websocket connection instance
+ *
+ * if the protocol does not have any guidance, returns -1.  Currently only
+ * http2 connections get send window information from this API.  But your code
+ * should use it so it can work properly with any protocol.
+ *
+ * If nonzero return is the amount of payload data the peer or intermediary has
+ * reported it has buffer space for.  That has NO relationship with the amount
+ * of buffer space your OS can accept on this connection for a write action.
+ *
+ * This number represents the maximum you could send to the peer or intermediary
+ * on this connection right now without the protocol complaining.
+ *
+ * lws manages accounting for send window updates and payload writes
+ * automatically, so this number reflects the situation at the peer or
+ * intermediary dynamically.
+ */
+LWS_VISIBLE LWS_EXTERN lws_fileofs_t
+lws_get_peer_write_allowance(struct lws *wsi);
+///@}
diff --git a/include/libwebsockets/lws-ws-close.h b/include/libwebsockets/lws-ws-close.h
new file mode 100644 (file)
index 0000000..9721e29
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup wsclose Websocket Close
+ *
+ * ##Websocket close frame control
+ *
+ * When we close a ws connection, we can send a reason code and a short
+ * UTF-8 description back with the close packet.
+ */
+///@{
+
+/*
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+/** enum lws_close_status - RFC6455 close status codes */
+enum lws_close_status {
+       LWS_CLOSE_STATUS_NOSTATUS                               =    0,
+       LWS_CLOSE_STATUS_NORMAL                                 = 1000,
+       /**< 1000 indicates a normal closure, meaning that the purpose for
+      which the connection was established has been fulfilled. */
+       LWS_CLOSE_STATUS_GOINGAWAY                              = 1001,
+       /**< 1001 indicates that an endpoint is "going away", such as a server
+      going down or a browser having navigated away from a page. */
+       LWS_CLOSE_STATUS_PROTOCOL_ERR                           = 1002,
+       /**< 1002 indicates that an endpoint is terminating the connection due
+      to a protocol error. */
+       LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE                    = 1003,
+       /**< 1003 indicates that an endpoint is terminating the connection
+      because it has received a type of data it cannot accept (e.g., an
+      endpoint that understands only text data MAY send this if it
+      receives a binary message). */
+       LWS_CLOSE_STATUS_RESERVED                               = 1004,
+       /**< Reserved.  The specific meaning might be defined in the future. */
+       LWS_CLOSE_STATUS_NO_STATUS                              = 1005,
+       /**< 1005 is a reserved value and MUST NOT be set as a status code in a
+      Close control frame by an endpoint.  It is designated for use in
+      applications expecting a status code to indicate that no status
+      code was actually present. */
+       LWS_CLOSE_STATUS_ABNORMAL_CLOSE                         = 1006,
+       /**< 1006 is a reserved value and MUST NOT be set as a status code in a
+      Close control frame by an endpoint.  It is designated for use in
+      applications expecting a status code to indicate that the
+      connection was closed abnormally, e.g., without sending or
+      receiving a Close control frame. */
+       LWS_CLOSE_STATUS_INVALID_PAYLOAD                        = 1007,
+       /**< 1007 indicates that an endpoint is terminating the connection
+      because it has received data within a message that was not
+      consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
+      data within a text message). */
+       LWS_CLOSE_STATUS_POLICY_VIOLATION                       = 1008,
+       /**< 1008 indicates that an endpoint is terminating the connection
+      because it has received a message that violates its policy.  This
+      is a generic status code that can be returned when there is no
+      other more suitable status code (e.g., 1003 or 1009) or if there
+      is a need to hide specific details about the policy. */
+       LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE                      = 1009,
+       /**< 1009 indicates that an endpoint is terminating the connection
+      because it has received a message that is too big for it to
+      process. */
+       LWS_CLOSE_STATUS_EXTENSION_REQUIRED                     = 1010,
+       /**< 1010 indicates that an endpoint (client) is terminating the
+      connection because it has expected the server to negotiate one or
+      more extension, but the server didn't return them in the response
+      message of the WebSocket handshake.  The list of extensions that
+      are needed SHOULD appear in the /reason/ part of the Close frame.
+      Note that this status code is not used by the server, because it
+      can fail the WebSocket handshake instead */
+       LWS_CLOSE_STATUS_UNEXPECTED_CONDITION                   = 1011,
+       /**< 1011 indicates that a server is terminating the connection because
+      it encountered an unexpected condition that prevented it from
+      fulfilling the request. */
+       LWS_CLOSE_STATUS_TLS_FAILURE                            = 1015,
+       /**< 1015 is a reserved value and MUST NOT be set as a status code in a
+      Close control frame by an endpoint.  It is designated for use in
+      applications expecting a status code to indicate that the
+      connection was closed due to a failure to perform a TLS handshake
+      (e.g., the server certificate can't be verified). */
+
+       LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE                = 2000,
+
+       /****** add new things just above ---^ ******/
+
+       LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY               = 9999,
+};
+
+/**
+ * lws_close_reason - Set reason and aux data to send with Close packet
+ *             If you are going to return nonzero from the callback
+ *             requesting the connection to close, you can optionally
+ *             call this to set the reason the peer will be told if
+ *             possible.
+ *
+ * \param wsi: The websocket connection to set the close reason on
+ * \param status:      A valid close status from websocket standard
+ * \param buf: NULL or buffer containing up to 124 bytes of auxiliary data
+ * \param len: Length of data in \param buf to send
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_close_reason(struct lws *wsi, enum lws_close_status status,
+                unsigned char *buf, size_t len);
+
+///@}
diff --git a/include/libwebsockets/lws-ws-ext.h b/include/libwebsockets/lws-ws-ext.h
new file mode 100644 (file)
index 0000000..3face4f
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/*! \defgroup extensions Extension related functions
+ * ##Extension releated functions
+ *
+ *  Ws defines optional extensions, lws provides the ability to implement these
+ *  in user code if so desired.
+ *
+ *  We provide one extensions permessage-deflate.
+ */
+///@{
+
+/*
+ * NOTE: These public enums are part of the abi.  If you want to add one,
+ * add it at where specified so existing users are unaffected.
+ */
+enum lws_extension_callback_reasons {
+       LWS_EXT_CB_CONSTRUCT                            =  4,
+       LWS_EXT_CB_CLIENT_CONSTRUCT                     =  5,
+       LWS_EXT_CB_DESTROY                              =  8,
+       LWS_EXT_CB_PACKET_TX_PRESEND                    = 12,
+       LWS_EXT_CB_PAYLOAD_TX                           = 21,
+       LWS_EXT_CB_PAYLOAD_RX                           = 22,
+       LWS_EXT_CB_OPTION_DEFAULT                       = 23,
+       LWS_EXT_CB_OPTION_SET                           = 24,
+       LWS_EXT_CB_OPTION_CONFIRM                       = 25,
+       LWS_EXT_CB_NAMED_OPTION_SET                     = 26,
+
+       /****** add new things just above ---^ ******/
+};
+
+/** enum lws_ext_options_types */
+enum lws_ext_options_types {
+       EXTARG_NONE, /**< does not take an argument */
+       EXTARG_DEC,  /**< requires a decimal argument */
+       EXTARG_OPT_DEC /**< may have an optional decimal argument */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility */
+};
+
+/** struct lws_ext_options -   Option arguments to the extension.  These are
+ *                             used in the negotiation at ws upgrade time.
+ *                             The helper function lws_ext_parse_options()
+ *                             uses these to generate callbacks */
+struct lws_ext_options {
+       const char *name; /**< Option name, eg, "server_no_context_takeover" */
+       enum lws_ext_options_types type; /**< What kind of args the option can take */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility */
+};
+
+/** struct lws_ext_option_arg */
+struct lws_ext_option_arg {
+       const char *option_name; /**< may be NULL, option_index used then */
+       int option_index; /**< argument ordinal to use if option_name missing */
+       const char *start; /**< value */
+       int len; /**< length of value */
+};
+
+/**
+ * typedef lws_extension_callback_function() - Hooks to allow extensions to operate
+ * \param context:     Websockets context
+ * \param ext: This extension
+ * \param wsi: Opaque websocket instance pointer
+ * \param reason:      The reason for the call
+ * \param user:        Pointer to ptr to per-session user data allocated by library
+ * \param in:          Pointer used for some callback reasons
+ * \param len: Length set for some callback reasons
+ *
+ *     Each extension that is active on a particular connection receives
+ *     callbacks during the connection lifetime to allow the extension to
+ *     operate on websocket data and manage itself.
+ *
+ *     Libwebsockets takes care of allocating and freeing "user" memory for
+ *     each active extension on each connection.  That is what is pointed to
+ *     by the user parameter.
+ *
+ *     LWS_EXT_CB_CONSTRUCT:  called when the server has decided to
+ *             select this extension from the list provided by the client,
+ *             just before the server will send back the handshake accepting
+ *             the connection with this extension active.  This gives the
+ *             extension a chance to initialize its connection context found
+ *             in user.
+ *
+ *     LWS_EXT_CB_CLIENT_CONSTRUCT: same as LWS_EXT_CB_CONSTRUCT
+ *             but called when client is instantiating this extension.  Some
+ *             extensions will work the same on client and server side and then
+ *             you can just merge handlers for both CONSTRUCTS.
+ *
+ *     LWS_EXT_CB_DESTROY:  called when the connection the extension was
+ *             being used on is about to be closed and deallocated.  It's the
+ *             last chance for the extension to deallocate anything it has
+ *             allocated in the user data (pointed to by user) before the
+ *             user data is deleted.  This same callback is used whether you
+ *             are in client or server instantiation context.
+ *
+ *     LWS_EXT_CB_PACKET_TX_PRESEND: this works the same way as
+ *             LWS_EXT_CB_PACKET_RX_PREPARSE above, except it gives the
+ *             extension a chance to change websocket data just before it will
+ *             be sent out.  Using the same lws_token pointer scheme in in,
+ *             the extension can change the buffer and the length to be
+ *             transmitted how it likes.  Again if it wants to grow the
+ *             buffer safely, it should copy the data into its own buffer and
+ *             set the lws_tokens token pointer to it.
+ *
+ *     LWS_EXT_CB_ARGS_VALIDATE:
+ */
+typedef int
+lws_extension_callback_function(struct lws_context *context,
+                             const struct lws_extension *ext, struct lws *wsi,
+                             enum lws_extension_callback_reasons reason,
+                             void *user, void *in, size_t len);
+
+/** struct lws_extension -     An extension we support */
+struct lws_extension {
+       const char *name; /**< Formal extension name, eg, "permessage-deflate" */
+       lws_extension_callback_function *callback; /**< Service callback */
+       const char *client_offer; /**< String containing exts and options client offers */
+
+       /* Add new things just above here ---^
+        * This is part of the ABI, don't needlessly break compatibility */
+};
+
+/**
+ * lws_set_extension_option(): set extension option if possible
+ *
+ * \param wsi: websocket connection
+ * \param ext_name:    name of ext, like "permessage-deflate"
+ * \param opt_name:    name of option, like "rx_buf_size"
+ * \param opt_val:     value to set option to
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_set_extension_option(struct lws *wsi, const char *ext_name,
+                        const char *opt_name, const char *opt_val);
+
+/**
+ * lws_ext_parse_options() - deal with parsing negotiated extension options
+ *
+ * \param ext: related extension struct
+ * \param wsi: websocket connection
+ * \param ext_user: per-connection extension private data
+ * \param opts: list of supported options
+ * \param o: option string to parse
+ * \param len: length
+ */
+LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi,
+                     void *ext_user, const struct lws_ext_options *opts,
+                     const char *o, int len);
+
+/** lws_extension_callback_pm_deflate() - extension for RFC7692
+ *
+ * \param context:     lws context
+ * \param ext: related lws_extension struct
+ * \param wsi: websocket connection
+ * \param reason:      incoming callback reason
+ * \param user:        per-connection extension private data
+ * \param in:  pointer parameter
+ * \param len: length parameter
+ *
+ * Built-in callback implementing RFC7692 permessage-deflate
+ */
+LWS_EXTERN int
+lws_extension_callback_pm_deflate(struct lws_context *context,
+                                 const struct lws_extension *ext,
+                                 struct lws *wsi,
+                                 enum lws_extension_callback_reasons reason,
+                                 void *user, void *in, size_t len);
+
+/*
+ * The internal exts are part of the public abi
+ * If we add more extensions, publish the callback here  ------v
+ */
+///@}
diff --git a/include/libwebsockets/lws-ws-state.h b/include/libwebsockets/lws-ws-state.h
new file mode 100644 (file)
index 0000000..3f65724
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+/** \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);
+///@}
diff --git a/include/libwebsockets/lws-x509.h b/include/libwebsockets/lws-x509.h
new file mode 100644 (file)
index 0000000..8b4ec9b
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * included from libwebsockets.h
+ */
+
+enum lws_tls_cert_info {
+       LWS_TLS_CERT_INFO_VALIDITY_FROM,
+       /**< fills .time with the time_t the cert validity started from */
+       LWS_TLS_CERT_INFO_VALIDITY_TO,
+       /**< fills .time with the time_t the cert validity ends at */
+       LWS_TLS_CERT_INFO_COMMON_NAME,
+       /**< fills up to len bytes of .ns.name with the cert common name */
+       LWS_TLS_CERT_INFO_ISSUER_NAME,
+       /**< fills up to len bytes of .ns.name with the cert issuer name */
+       LWS_TLS_CERT_INFO_USAGE,
+       /**< fills verified with a bitfield asserting the valid uses */
+       LWS_TLS_CERT_INFO_VERIFIED,
+       /**< fills .verified with a bool representing peer cert validity,
+        *   call returns -1 if no cert */
+       LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY,
+       /**< the certificate's public key, as an opaque bytestream.  These
+        * opaque bytestreams can only be compared with each other using the
+        * same tls backend, ie, OpenSSL or mbedTLS.  The different backends
+        * produce different, incompatible representations for the same cert.
+        */
+};
+
+union lws_tls_cert_info_results {
+       unsigned int verified;
+       time_t time;
+       unsigned int usage;
+       struct {
+               int len;
+               /* KEEP LAST... notice the [64] is only there because
+                * name[] is not allowed in a union.  The actual length of
+                * name[] is arbitrary and is passed into the api using the
+                * len parameter.  Eg
+                *
+                * char big[1024];
+                * union lws_tls_cert_info_results *buf =
+                *      (union lws_tls_cert_info_results *)big;
+                *
+                * lws_tls_peer_cert_info(wsi, type, buf, sizeof(big) -
+                *                        sizeof(*buf) + sizeof(buf->ns.name));
+                */
+               char name[64];
+       } ns;
+};
+
+struct lws_x509_cert;
+struct lws_jwk;
+
+/**
+ * lws_x509_create() - Allocate an lws_x509_cert object
+ *
+ * \param x509: pointer to lws_x509_cert pointer to be set to allocated object
+ *
+ * Allocates an lws_x509_cert object and set *x509 to point to it.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_x509_create(struct lws_x509_cert **x509);
+
+/**
+ * lws_x509_parse_from_pem() - Read one or more x509 certs in PEM format from memory
+ *
+ * \param x509: pointer to lws_x509_cert object
+ * \param pem: pointer to PEM format content
+ * \param len: length of PEM format content
+ *
+ * Parses PEM certificates in memory into a native x509 representation for the
+ * TLS library.  If there are multiple PEM certs concatenated, they are all
+ * read into the same object and exist as a "chain".
+ *
+ * IMPORTANT for compatibility with mbedtls, the last used byte of \p pem
+ * must be '\0' and the \p len must include it.
+ *
+ * Returns 0 if all went OK.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len);
+
+/**
+ * lws_x509_verify() - Validate signing relationship between one or more certs
+ *                    and a trusted CA cert
+ *
+ * \param x509: pointer to lws_x509_cert object, may contain multiple
+ * \param trusted: a single, trusted cert object that we are checking for
+ * \param common_name: NULL, or required CN (Common Name) of \p x509
+ *
+ * Returns 0 if the cert or certs in \p x509 represent a complete chain that is
+ * ultimately signed by the cert in \p trusted.  Returns nonzero if that's not
+ * the case.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted,
+               const char *common_name);
+
+/**
+ * lws_x509_public_to_jwk() - Copy the public key out of a cert and into a JWK
+ *
+ * \param jwk: pointer to the jwk to initialize and set to the public key
+ * \param x509: pointer to lws_x509_cert object that has the public key
+ * \param curves: NULL to disallow EC, else a comma-separated list of valid
+ *               curves using the JWA naming, eg, "P-256,P-384,P-521".
+ * \param rsabits: minimum number of RSA bits required in the cert if RSA
+ *
+ * Returns 0 if JWK was set to the certificate public key correctly and the
+ * curve / the RSA key size was acceptable.  Automatically produces an RSA or
+ * EC JWK depending on what the cert had.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509,
+                      const char *curves, int rsabits);
+
+/**
+ * lws_x509_jwk_privkey_pem() - Copy a private key PEM into a jwk that has the
+ *                             public part already
+ *
+ * \param jwk: pointer to the jwk to initialize and set to the public key
+ * \param pem: pointer to PEM private key in memory
+ * \param len: length of PEM private key in memory
+ * \param passphrase: NULL or passphrase needed to decrypt private key
+ *
+ * IMPORTANT for compatibility with mbedtls, the last used byte of \p pem
+ * must be '\0' and the \p len must include it.
+ *
+ * Returns 0 if the private key was successfully added to the JWK, else
+ * nonzero if failed.
+ *
+ * The PEM image in memory is zeroed down on both successful and failed exits.
+ * The caller should take care to zero down passphrase if used.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len,
+                        const char *passphrase);
+
+/**
+ * lws_x509_destroy() - Destroy a previously allocated lws_x509_cert object
+ *
+ * \param x509: pointer to lws_x509_cert pointer
+ *
+ * Deallocates an lws_x509_cert object and sets its pointer to NULL.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_x509_destroy(struct lws_x509_cert **x509);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type,
+             union lws_tls_cert_info_results *buf, size_t len);
+
+/**
+ * lws_tls_peer_cert_info() - get information from the peer's TLS cert
+ *
+ * \param wsi: the connection to query
+ * \param type: one of LWS_TLS_CERT_INFO_
+ * \param buf: pointer to union to take result
+ * \param len: when result is a string, the true length of buf->ns.name[]
+ *
+ * lws_tls_peer_cert_info() lets you get hold of information from the peer
+ * certificate.
+ *
+ * Return 0 if there is a result in \p buf, or -1 indicating there was no cert
+ * or another problem.
+ *
+ * This function works the same no matter if the TLS backend is OpenSSL or
+ * mbedTLS.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
+                      union lws_tls_cert_info_results *buf, size_t len);
+
+/**
+ * lws_tls_vhost_cert_info() - get information from the vhost's own TLS cert
+ *
+ * \param vhost: the vhost to query
+ * \param type: one of LWS_TLS_CERT_INFO_
+ * \param buf: pointer to union to take result
+ * \param len: when result is a string, the true length of buf->ns.name[]
+ *
+ * lws_tls_vhost_cert_info() lets you get hold of information from the vhost
+ * certificate.
+ *
+ * Return 0 if there is a result in \p buf, or -1 indicating there was no cert
+ * or another problem.
+ *
+ * This function works the same no matter if the TLS backend is OpenSSL or
+ * mbedTLS.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
+                       union lws_tls_cert_info_results *buf, size_t len);
+
+/**
+ * lws_tls_acme_sni_cert_create() - creates a temp selfsigned cert
+ *                                 and attaches to a vhost
+ *
+ * \param vhost: the vhost to acquire the selfsigned cert
+ * \param san_a: SAN written into the certificate
+ * \param san_b: second SAN written into the certificate
+ *
+ *
+ * Returns 0 if created and attached to the vhost.  Returns -1 if problems and
+ * frees all allocations before returning.
+ *
+ * On success, any allocations are destroyed at vhost destruction automatically.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a,
+                            const char *san_b);
+
+/**
+ * lws_tls_acme_sni_csr_create() - creates a CSR and related private key PEM
+ *
+ * \param context: lws_context used for random
+ * \param elements: array of LWS_TLS_REQ_ELEMENT_COUNT const char *
+ * \param csr: buffer that will get the b64URL(ASN-1 CSR)
+ * \param csr_len: max length of the csr buffer
+ * \param privkey_pem: pointer to pointer allocated to hold the privkey_pem
+ * \param privkey_len: pointer to size_t set to the length of the privkey_pem
+ *
+ * Creates a CSR according to the information in \p elements, and a private
+ * RSA key used to sign the CSR.
+ *
+ * The outputs are the b64URL(ASN-1 CSR) into csr, and the PEM private key into
+ * privkey_pem.
+ *
+ * Notice that \p elements points to an array of const char *s pointing to the
+ * information listed in the enum above.  If an entry is NULL or an empty
+ * string, the element is set to "none" in the CSR.
+ *
+ * Returns 0 on success or nonzero for failure.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[],
+                           uint8_t *csr, size_t csr_len, char **privkey_pem,
+                           size_t *privkey_len);
+
+/**
+ * lws_tls_cert_updated() - update every vhost using the given cert path
+ *
+ * \param context: our lws_context
+ * \param certpath: the filepath to the certificate
+ * \param keypath: the filepath to the private key of the certificate
+ * \param mem_cert: copy of the cert in memory
+ * \param len_mem_cert: length of the copy of the cert in memory
+ * \param mem_privkey: copy of the private key in memory
+ * \param len_mem_privkey: length of the copy of the private key in memory
+ *
+ * Checks every vhost to see if it is the using certificate described by the
+ * the given filepaths.  If so, it attempts to update the vhost ssl_ctx to use
+ * the new certificate.
+ *
+ * Returns 0 on success or nonzero for failure.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_cert_updated(struct lws_context *context, const char *certpath,
+                    const char *keypath,
+                    const char *mem_cert, size_t len_mem_cert,
+                    const char *mem_privkey, size_t len_mem_privkey);
+
diff --git a/lib/.gitignore b/lib/.gitignore
deleted file mode 100644 (file)
index dbed3ff..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#Ignore build files
-Makefile
-*.o
-*.lo
-*.la
-.libs
-.deps
-
diff --git a/lib/README.md b/lib/README.md
new file mode 100644 (file)
index 0000000..2c0c900
--- /dev/null
@@ -0,0 +1,15 @@
+## Library sources layout
+
+Code that goes in the libwebsockets library itself lives down ./lib
+
+Path|Sources
+---|---
+lib/core|Core lws code related to generic fd and wsi servicing and management
+lib/event-libs|Code containing optional event-lib specific adaptations
+lib/jose|JOSE / JWS / JWK / JWE implementations
+lib/misc|Code for various mostly optional miscellaneous features
+lib/plat|Platform-specific adaptation code
+lib/roles|Code for specific optional wsi roles, eg, http/1, h2, ws, raw, etc
+lib/tls|Code supporting the various TLS libraries
+libwebsockets.h|Public API header for the whole of lws
+
diff --git a/lib/abstract/README.md b/lib/abstract/README.md
new file mode 100644 (file)
index 0000000..865b698
--- /dev/null
@@ -0,0 +1,170 @@
+# Abstract protocols and transports
+
+## Overview
+
+Until now protocol implementations in lws have been done directly
+to the network-related apis inside lws.
+
+In an effort to separate out completely network implementation
+details from protocol specification, lws now supports
+"abstract protocols" and "abstract transports".
+
+![lws_abstract overview](/doc-assets/abstract-overview.svg)
+
+The concept is that the implementation is split into two separate
+chunks of code hidden behind "ops" structs... the "abstract protocol"
+implementation is responsible for the logical protocol operation
+and reads and writes only memory buffers.
+
+The "abstract transport" implementation is responsible for sending
+and receiving buffers on some kind of transport, and again is hidden
+behind a standardized ops struct.
+
+In the system, both the abstract protocols and transports are
+found by their name.
+
+An actual "connection" is created by calling a generic api
+`lws_abs_bind_and_create_instance()` to instantiate the
+combination of a protocol and a transport.
+
+This makes it possible to confidently offer the same protocol on
+completely different transports, eg, like serial, or to wire
+up the protocol implementation to a test jig sending canned
+test vectors and confirming the response at buffer level, without
+any network.  The abstract protocol itself has no relationship
+to the transport at all and is completely unchanged by changes
+to the transport.
+
+In addition, generic tokens to control settings in both the
+protocol and the transport are passed in at instantiation-time,
+eg, controlling the IP address targeted by the transport.
+
+lws SMTP client support has been rewritten to use the new scheme,
+and lws provides a raw socket transport built-in.
+
+## Public API
+
+The public api for defining abstract protocols and transports is
+found at
+
+ - [abstract.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/abstract.h)
+ - [protocols.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/protocols.h)
+ - [transports.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/abstract/transports.h)
+
+### `lws_abs_t`
+
+The main structure that defines the abstraction is `lws_abs_t`,
+this is a name and then pointers to the protocol and transport,
+optional tokens to control both the protocol and transport,
+and pointers to private allocations for both the
+protocol and transport when instantiated.
+
+The transport is selected using
+
+```
+LWS_VISIBLE LWS_EXTERN const lws_abs_transport_t *
+lws_abs_transport_get_by_name(const char *name);
+```
+
+and similarly the protocol by
+
+```
+LWS_VISIBLE LWS_EXTERN const lws_abs_protocol_t *
+lws_abs_protocol_get_by_name(const char *name);
+```
+
+At the moment only "`raw-skt`" is defined as an lws built-in, athough
+you can also create your own mock transport the same way for creating
+test jigs.
+
+|transport op|meaning|
+|---|---|
+|`tx()`|transmit a buffer|
+|`client_conn()`|start a connection to a peer|
+|`close()`|request to close the connection to a peer|
+|`ask_for_writeable()`|request a `writeable()` callback when tx can be used|
+|`set_timeout()`|set a timeout that will close the connection if reached|
+|`state()`|check if the connection is established and can carry traffic|
+
+These are called by the protocol to get things done and make queries
+through the abstract transport.
+
+|protocol op|meaning|
+|---|---|
+|`accept()`|The peer has accepted the transport connection|
+|`rx()`|The peer has sent us some payload|
+|`writeable()`|The connection to the peer can take more tx|
+|`closed()`|The connection to the peer has closed|
+|`heartbeat()`|Called periodically even when no network events|
+
+These are called by the transport to inform the protocol of events
+and traffic.
+
+### Instantiation
+
+The user fills an lws_abs_t and passes a pointer to it to
+`lws_abs_bind_and_create_instance()` to create an instantiation
+of the protocol + transport.
+
+### `lws_token_map_t`
+
+The abstract protocol has no idea about a network or network addresses
+or ports or whatever... it may not even be hooked up to one.
+
+If the transport it is bound to wants things like that, they are passed
+in using an array of `lws_token_map_t` at instantiation time.
+
+For example this is passed to the raw socket protocol in the smtp client
+minimal example to control where it would connect to:
+
+```
+static const lws_token_map_t smtp_abs_tokens[] = {
+{
+       .u = { .value = "127.0.0.1" },
+       .name_index = LTMI_PEER_DNS_ADDRESS,
+}, {
+       .u = { .lvalue = 25l },
+       .name_index = LTMI_PEER_PORT,
+}};
+```
+
+## Steps for adding new abstract protocols
+
+ - add the public header in `./include/libwebsockets/abstract/protocols/`
+ - add a directory under `./lib/abstract/protocols/`
+ - add your protocol sources in the new directory
+ - in CMakeLists.txt:
+   - add an `LWS_WITH_xxx` for your protocol
+   - search for "using any abstract protocol" and add your `LWS_WITH_xxx` to
+     the if so it also sets `LWS_WITH_ABSTRACT` if any set
+   - add a clause to append your source to SOURCES if `LWS_WITH_xxx` enabled
+ - add your `lws_abs_protocol` to the list `available_abs_protocols` in
+   `./lib/abstract/abstract.c`
+
+## Steps for adding new abstract transports
+
+ - add the public header in `./include/libwebsockets/abstract/transports/`
+ - add your transport sources under `./lib/abstract/transports/`
+ - in CMakeLists.txt append your transport sources to SOURCES if `LWS_WITH_ABSTRACT`
+   and any other cmake conditionals
+ - add an extern for your transport `lws_protocols` in `./lib/core-net/private.h`
+ - add your transport `lws_protocols` to `available_abstract_protocols` in
+   `./lib/core-net/vhost.c`
+ - add your `lws_abs_transport` to the list `available_abs_transports` in
+   `./lib/abstract/abstract.c`
+
+# Protocol testing
+
+## unit tests
+
+lws features an abstract transport designed to facilitate unit testing.  This
+contains an lws_sequencer that performs the steps of tests involving sending the
+protocol test vector buffers and confirming the response of the protocol matches
+the test vectors.
+
+## test-sequencer
+
+test-sequencer is a helper that sequences running an array of unit tests and
+collects the statistics and gives a PASS / FAIL result.
+
+See the SMTP client api test for an example of how to use.
diff --git a/lib/abstract/abstract.c b/lib/abstract/abstract.c
new file mode 100644 (file)
index 0000000..0f52869
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+#include <abstract/private.h>
+
+extern const lws_abs_transport_t lws_abs_transport_cli_raw_skt,
+                                lws_abs_transport_cli_unit_test;
+#if defined(LWS_WITH_SMTP)
+extern const lws_abs_protocol_t lws_abs_protocol_smtp;
+#endif
+
+static const lws_abs_transport_t * const available_abs_transports[] = {
+       &lws_abs_transport_cli_raw_skt,
+       &lws_abs_transport_cli_unit_test,
+};
+
+/* HACK: microsoft compiler can't handle zero length array definition */
+#if defined(LWS_WITH_SMTP)
+static const lws_abs_protocol_t * const available_abs_protocols[] = {
+#if defined(LWS_WITH_SMTP)
+       &lws_abs_protocol_smtp,
+#endif
+};
+#endif
+
+const lws_abs_transport_t *
+lws_abs_transport_get_by_name(const char *name)
+{
+       int n;
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(available_abs_transports); n++)
+               if (!strcmp(name, available_abs_transports[n]->name))
+                       return available_abs_transports[n];
+
+       lwsl_err("%s: cannot find '%s'\n", __func__, name);
+
+       return NULL;
+}
+
+const lws_abs_protocol_t *
+lws_abs_protocol_get_by_name(const char *name)
+{
+#if defined(LWS_WITH_SMTP)
+       int n;
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(available_abs_protocols); n++)
+               if (!strcmp(name, available_abs_protocols[n]->name))
+                       return available_abs_protocols[n];
+#endif
+       lwsl_err("%s: cannot find '%s'\n", __func__, name);
+
+       return NULL;
+}
+
+const lws_token_map_t *
+lws_abs_get_token(const lws_token_map_t *token_map, short name_index)
+{
+       if (!token_map)
+               return NULL;
+
+       do {
+               if (token_map->name_index == name_index)
+                       return token_map;
+               token_map++;
+       } while (token_map->name_index);
+
+       return NULL;
+}
+
+void
+lws_abs_destroy_instance(lws_abs_t **ai)
+{
+       lws_abs_t *a = *ai;
+
+       if (a->api)
+               a->ap->destroy(&a->api);
+       if (a->ati)
+               a->at->destroy(&a->ati);
+
+       lws_dll2_remove(&a->abstract_instances);
+
+       *ai = NULL;
+       free(a);
+}
+
+lws_abs_t *
+lws_abs_bind_and_create_instance(const lws_abs_t *abs)
+{
+       size_t size = sizeof(lws_abs_t) + abs->ap->alloc + abs->at->alloc;
+       lws_abs_t *ai;
+
+       /*
+        * since we know we will allocate the lws_abs_t, the protocol's
+        * instance allocation, and the transport's instance allocation,
+        * we merge it into a single heap allocation
+        */
+       ai = lws_malloc(size, "abs inst");
+       if (!ai)
+               return NULL;
+
+       *ai = *abs;
+       ai->ati = NULL;
+
+       ai->api = (char *)ai + sizeof(lws_abs_t);
+       if (ai->ap->create(ai)) {
+               ai->api = NULL;
+               goto bail;
+       }
+
+       ai->ati = (char *)ai->api + abs->ap->alloc;
+       if (ai->at->create(ai)) {
+               ai->ati = NULL;
+               goto bail;
+       }
+
+       /* add us to the vhost's dll2 of instances */
+
+       lws_dll2_clear(&ai->abstract_instances);
+       lws_dll2_add_head(&ai->abstract_instances,
+                         &ai->vh->abstract_instances_owner);
+
+       return ai;
+
+bail:
+       lws_abs_destroy_instance(&ai);
+
+       return NULL;
+}
diff --git a/lib/abstract/private.h b/lib/abstract/private.h
new file mode 100644 (file)
index 0000000..a9b1ca2
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+
+
+
diff --git a/lib/abstract/protocols/smtp/smtp.c b/lib/abstract/protocols/smtp/smtp.c
new file mode 100644 (file)
index 0000000..668ab19
--- /dev/null
@@ -0,0 +1,449 @@
+/*
+ * Abstract SMTP support for libwebsockets
+ *
+ * Copyright (C) 2016-2019 Andy Green <andy@warmcat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "abstract/private.h"
+
+/** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */
+typedef enum lwsgs_smtp_states {
+       LGSSMTP_IDLE,           /**< awaiting new email */
+       LGSSMTP_CONNECTING,     /**< opening tcp connection to MTA */
+       LGSSMTP_CONNECTED,      /**< tcp connection to MTA is connected */
+       LGSSMTP_SENT_HELO,      /**< sent the HELO */
+       LGSSMTP_SENT_FROM,      /**< sent FROM */
+       LGSSMTP_SENT_TO,        /**< sent TO */
+       LGSSMTP_SENT_DATA,      /**< sent DATA request */
+       LGSSMTP_SENT_BODY,      /**< sent the email body */
+       LGSSMTP_SENT_QUIT,      /**< sent the session quit */
+} lwsgs_smtp_states_t;
+
+/** struct lws_email - abstract context for performing SMTP operations */
+typedef struct lws_smtp_client {
+       struct lws_dll2_owner pending_owner;
+
+       const struct lws_abs *abs;
+
+       const char *helo;
+
+       lwsgs_smtp_states_t estate;
+       time_t email_connect_started;
+
+       time_t retry_interval;
+       time_t delivery_timeout;
+
+       size_t email_queue_max;
+       size_t max_content_size;
+
+       unsigned char send_pending:1;
+} lws_smtp_client_t;
+
+static const short retcodes[] = {
+       0,      /* idle */
+       0,      /* connecting */
+       220,    /* connected */
+       250,    /* helo */
+       250,    /* from */
+       250,    /* to */
+       354,    /* data */
+       250,    /* body */
+       221,    /* quit */
+};
+
+static void
+lws_smtp_client_state_transition(lws_smtp_client_t *c, lwsgs_smtp_states_t s)
+{
+       lwsl_debug("%s: cli %p: state %d -> %d\n", __func__, c, c->estate, s);
+       c->estate = s;
+}
+
+static void
+lws_smtp_client_kick_internal(lws_smtp_client_t *c)
+{
+       lws_smtp_email_t *e;
+       lws_dll2_t *d;
+       char buf[64];
+       int n;
+
+       if (c->estate != LGSSMTP_IDLE)
+               return;
+
+       /* is there something to do? */
+
+again:
+       d = lws_dll2_get_head(&c->pending_owner);
+       if (!d)
+               return;
+
+       e = lws_container_of(d, lws_smtp_email_t, list);
+
+       /* do we need to time out this guy? */
+
+       if ((time_t)lws_now_secs() - e->added > (time_t)c->delivery_timeout) {
+               lwsl_err("%s: timing out email\n", __func__);
+               lws_dll2_remove(&e->list);
+               n = lws_snprintf(buf, sizeof(buf), "0 Timed out retrying send");
+               e->done(e, buf, n);
+
+               if (lws_dll2_get_head(&c->pending_owner))
+                       goto again;
+
+               return;
+       }
+
+       /* is it time for his retry yet? */
+
+       if (e->last_try &&
+           (time_t)lws_now_secs() - e->last_try < (time_t)c->retry_interval) {
+               /* no... send him to the tail */
+               lws_dll2_remove(&e->list);
+               lws_dll2_add_tail(&e->list, &c->pending_owner);
+               return;
+       }
+
+       /* ask the transport if we have a connection to the server ongoing */
+
+       if (c->abs->at->state(c->abs->ati)) {
+               /*
+                * there's a connection, it could be still trying to connect
+                * or established
+                */
+               c->abs->at->ask_for_writeable(c->abs->ati);
+
+               return;
+       }
+
+       /* there's no existing connection */
+
+       lws_smtp_client_state_transition(c, LGSSMTP_CONNECTING);
+
+       if (c->abs->at->client_conn(c->abs)) {
+               lwsl_err("%s: failed to connect\n", __func__);
+
+               return;
+       }
+
+       e->tries++;
+       e->last_try = lws_now_secs();
+}
+
+/*
+ * we became connected
+ */
+
+static int
+lws_smtp_client_abs_accept(lws_abs_protocol_inst_t *api)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)api;
+
+       lws_smtp_client_state_transition(c, LGSSMTP_CONNECTED);
+
+       return 0;
+}
+
+static int
+lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)api;
+       lws_smtp_email_t *e;
+       lws_dll2_t *pd2;
+       int n;
+
+       pd2 = lws_dll2_get_head(&c->pending_owner);
+       if (!pd2)
+               return 0;
+
+       e = lws_container_of(pd2, lws_smtp_email_t, list);
+       if (!e)
+               return 0;
+
+       n = atoi((char *)buf);
+       if (n != retcodes[c->estate]) {
+               lwsl_notice("%s: bad response from server: %d (state %d) %.*s\n",
+                               __func__, n, c->estate, (int)len, buf);
+
+               lws_dll2_remove(&e->list);
+               lws_dll2_add_tail(&e->list, &c->pending_owner);
+               lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
+               lws_smtp_client_kick_internal(c);
+
+               return 0;
+       }
+
+       if (c->estate == LGSSMTP_SENT_QUIT) {
+               lwsl_debug("%s: done\n", __func__);
+               lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
+
+               lws_dll2_remove(&e->list);
+               if (e->done && e->done(e, "sent OK", 7))
+                       return 1;
+
+               return 1;
+       }
+
+       c->send_pending = 1;
+       c->abs->at->ask_for_writeable(c->abs->ati);
+
+       return 0;
+}
+
+static int
+lws_smtp_client_abs_writeable(lws_abs_protocol_inst_t *api, size_t budget)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)api;
+       char b[256 + LWS_PRE], *p = b + LWS_PRE;
+       lws_smtp_email_t *e;
+       lws_dll2_t *pd2;
+       int n;
+
+       pd2 = lws_dll2_get_head(&c->pending_owner);
+       if (!pd2)
+               return 0;
+
+       e = lws_container_of(pd2, lws_smtp_email_t, list);
+       if (!e)
+               return 0;
+
+
+       if (!c->send_pending)
+               return 0;
+
+       c->send_pending = 0;
+
+       lwsl_debug("%s: writing response for state %d\n", __func__, c->estate);
+
+       switch (c->estate) {
+       case LGSSMTP_CONNECTED:
+               n = lws_snprintf(p, sizeof(b) - LWS_PRE, "HELO %s\n", c->helo);
+               lws_smtp_client_state_transition(c, LGSSMTP_SENT_HELO);
+               break;
+       case LGSSMTP_SENT_HELO:
+               n = lws_snprintf(p, sizeof(b) - LWS_PRE, "MAIL FROM: <%s>\n",
+                                e->email_from);
+               lws_smtp_client_state_transition(c, LGSSMTP_SENT_FROM);
+               break;
+       case LGSSMTP_SENT_FROM:
+               n = lws_snprintf(p, sizeof(b) - LWS_PRE,
+                                "RCPT TO: <%s>\n", e->email_to);
+               lws_smtp_client_state_transition(c, LGSSMTP_SENT_TO);
+               break;
+       case LGSSMTP_SENT_TO:
+               n = lws_snprintf(p, sizeof(b) - LWS_PRE, "DATA\n");
+               lws_smtp_client_state_transition(c, LGSSMTP_SENT_DATA);
+               break;
+       case LGSSMTP_SENT_DATA:
+               p = (char *)e->payload;
+               n = strlen(e->payload);
+               lws_smtp_client_state_transition(c, LGSSMTP_SENT_BODY);
+               break;
+       case LGSSMTP_SENT_BODY:
+               n = lws_snprintf(p, sizeof(b) - LWS_PRE, "quit\n");
+               lws_smtp_client_state_transition(c, LGSSMTP_SENT_QUIT);
+               break;
+       case LGSSMTP_SENT_QUIT:
+               return 0;
+
+       default:
+               return 0;
+       }
+
+       //puts(p);
+       c->abs->at->tx(c->abs->ati, (uint8_t *)p, n);
+
+       return 0;
+}
+
+static int
+lws_smtp_client_abs_closed(lws_abs_protocol_inst_t *api)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)api;
+
+       if (c)
+               lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
+
+       return 0;
+}
+
+static int
+lws_smtp_client_abs_heartbeat(lws_abs_protocol_inst_t *api)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)api;
+
+       lws_smtp_client_kick_internal(c);
+
+       return 0;
+}
+
+lws_smtp_email_t *
+lws_smtp_client_alloc_email_helper(const char *payload, size_t payload_len,
+                                  const char *sender, const char *recipient,
+                                  const char *extra, size_t extra_len, void *data,
+                                  int (*done)(struct lws_smtp_email *e,
+                                              void *buf, size_t len))
+{
+       size_t ls = strlen(sender), lr = strlen(recipient);
+       lws_smtp_email_t *em;
+       char *p;
+
+       em = malloc(sizeof(*em) + payload_len + ls + lr + extra_len + 4);
+       if (!em) {
+               lwsl_err("OOM\n");
+               return NULL;
+       }
+
+       p = (char *)&em[1];
+
+       memset(em, 0, sizeof(*em));
+
+       em->data = data;
+       em->done = done;
+
+       em->email_from = p;
+       memcpy(p, sender, ls + 1);
+       p += ls + 1;
+       em->email_to = p;
+       memcpy(p, recipient, lr + 1);
+       p += lr + 1;
+       em->payload = p;
+       memcpy(p, payload, payload_len + 1);
+       p += payload_len + 1;
+
+       if (extra) {
+               em->extra = p;
+               memcpy(p, extra, extra_len + 1);
+       }
+
+       return em;
+}
+
+int
+lws_smtp_client_add_email(lws_abs_t *instance, lws_smtp_email_t *e)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
+
+       if (c->pending_owner.count > c->email_queue_max) {
+               lwsl_err("%s: email queue at limit of %d\n", __func__,
+                               (int)c->email_queue_max);
+
+               return 1;
+       }
+
+       e->added = lws_now_secs();
+       e->last_try = 0;
+       e->tries = 0;
+
+       lws_dll2_clear(&e->list);
+       lws_dll2_add_tail(&e->list, &c->pending_owner);
+
+       lws_smtp_client_kick_internal(c);
+
+       return 0;
+}
+
+void
+lws_smtp_client_kick(lws_abs_t *instance)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)instance->api;
+
+       lws_smtp_client_kick_internal(c);
+}
+static int
+lws_smtp_client_create(const lws_abs_t *ai)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)ai->api;
+       const lws_token_map_t *tm;
+
+       memset(c, 0, sizeof(*c));
+
+       c->abs = ai;
+
+       tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_V_HELO);
+       if (!tm) {
+               lwsl_err("%s: LTMI_PSMTP_V_HELO is required\n", __func__);
+
+               return 1;
+       }
+       c->helo = tm->u.value;
+
+       c->email_queue_max      = 8;
+       c->retry_interval       = 15 * 60;
+       c->delivery_timeout     = 12 * 60 * 60;
+
+       tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_EMAIL_QUEUE_MAX);
+       if (tm)
+               c->email_queue_max = tm->u.lvalue;
+       tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_RETRY_INTERVAL);
+       if (tm)
+               c->retry_interval = tm->u.lvalue;
+       tm = lws_abs_get_token(ai->ap_tokens, LTMI_PSMTP_LV_DELIVERY_TIMEOUT);
+       if (tm)
+               c->delivery_timeout = tm->u.lvalue;
+
+       lws_smtp_client_state_transition(c, LGSSMTP_IDLE);
+
+       return 0;
+}
+
+static int
+cleanup(struct lws_dll2 *d, void *user)
+{
+       lws_smtp_email_t *e;
+
+       e = lws_container_of(d, lws_smtp_email_t, list);
+       if (e->done && e->done(e, "destroying", 10))
+               return 1;
+
+       return 0;
+}
+
+static void
+lws_smtp_client_destroy(lws_abs_protocol_inst_t **_c)
+{
+       lws_smtp_client_t *c = (lws_smtp_client_t *)*_c;
+
+       if (!c)
+               return;
+
+       lws_dll2_foreach_safe(&c->pending_owner, NULL, cleanup);
+
+       /*
+        * We don't free anything because the abstract layer combined our
+        * allocation with that of the instance, and it will free the whole
+        * thing after this.
+        */
+
+       *_c = NULL;
+}
+
+/* events the transport invokes (handled by abstract protocol) */
+
+const lws_abs_protocol_t lws_abs_protocol_smtp = {
+       .name           = "smtp",
+       .alloc          = sizeof(lws_smtp_client_t),
+
+       .create         = lws_smtp_client_create,
+       .destroy        = lws_smtp_client_destroy,
+
+       .accept         = lws_smtp_client_abs_accept,
+       .rx             = lws_smtp_client_abs_rx,
+       .writeable      = lws_smtp_client_abs_writeable,
+       .closed         = lws_smtp_client_abs_closed,
+       .heartbeat      = lws_smtp_client_abs_heartbeat,
+};
diff --git a/lib/abstract/test-sequencer.c b/lib/abstract/test-sequencer.c
new file mode 100644 (file)
index 0000000..2872aef
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * libwebsockets lib/abstract/test-sequencer.c
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * A helper for running multiple unit tests against abstract protocols.
+ *
+ * An lws_seq_t is used to base its actions in the event loop and manage
+ * the sequencing of multiple tests.  A new abstract connection is instantiated
+ * for each test using te
+ */
+
+#include <core/private.h>
+
+struct lws_seq_test_sequencer {
+       lws_abs_t                       original_abs;
+
+       lws_test_sequencer_args_t       args;
+
+       struct lws_context              *context;
+       struct lws_vhost                *vhost;
+       lws_seq_t                       *unit_test_seq;
+
+       /* holds the per-test token for the unit-test transport to consume */
+       lws_token_map_t                 uttt[4];
+
+       lws_abs_t                       *instance;
+
+       int                             state;
+};
+
+/* sequencer messages specific to this sequencer */
+
+enum {
+       SEQ_MSG_PASS = LWSSEQ_USER_BASE,
+       SEQ_MSG_FAIL,
+       SEQ_MSG_FAIL_TIMEOUT,
+};
+
+/*
+ * We get called back when the unit test transport has decided if the test
+ * passed or failed.  We get the priv, and report to the sequencer message queue
+ * what the result was.
+ */
+
+static int
+unit_test_result_cb(const void *cb_user, int disposition)
+{
+       const struct lws_seq_test_sequencer *s =
+                       (const struct lws_seq_test_sequencer *)cb_user;
+       int r;
+
+       lwsl_debug("%s: disp %d\n", __func__, disposition);
+
+       switch (disposition) {
+       case LPE_FAILED_UNEXPECTED_PASS:
+       case LPE_FAILED_UNEXPECTED_CLOSE:
+       case LPE_FAILED:
+               r = SEQ_MSG_FAIL;
+               break;
+
+       case LPE_FAILED_UNEXPECTED_TIMEOUT:
+               r = SEQ_MSG_FAIL_TIMEOUT;
+               break;
+
+       case LPE_SUCCEEDED:
+               r = SEQ_MSG_PASS;
+               break;
+
+       default:
+               assert(0);
+               return -1;
+       }
+
+       lws_seq_queue_event(s->unit_test_seq, r, NULL, NULL);
+
+       ((struct lws_seq_test_sequencer *)s)->instance = NULL;
+
+       return 0;
+}
+
+/*
+ * We receive the unit test result callback's messages via the message queue.
+ *
+ * We log the results and always move on to the next test until there are no
+ * more tests.
+ */
+
+static lws_seq_cb_return_t
+test_sequencer_cb(struct lws_sequencer *seq, void *user, int event, void *data,
+                 void *aux)
+{
+       struct lws_seq_test_sequencer *s =
+                               (struct lws_seq_test_sequencer *)user;
+       lws_unit_test_packet_t *exp = (lws_unit_test_packet_t *)
+                                       s->args.tests[s->state].expect_array;
+       lws_abs_t test_abs;
+
+       switch ((int)event) {
+       case LWSSEQ_CREATED: /* our sequencer just got started */
+               lwsl_notice("%s: %s: created\n", __func__,
+                           lws_seq_name(seq));
+               s->state = 0;  /* first thing we'll do is the first url */
+               goto step;
+
+       case LWSSEQ_DESTROYED:
+               /*
+                * We are going down... if we have a child unit test sequencer
+                * still around inform and destroy it
+                */
+               if (s->instance) {
+                       s->instance->at->close(s->instance);
+                       s->instance = NULL;
+               }
+               break;
+
+       case SEQ_MSG_FAIL_TIMEOUT: /* current step timed out */
+               if (exp->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT) {
+                       lwsl_user("%s: test %d got expected timeout\n",
+                                 __func__, s->state);
+
+                       goto pass;
+               }
+               lwsl_user("%s: seq timed out at step %d\n", __func__, s->state);
+
+               s->args.results[s->state] = LPE_FAILED_UNEXPECTED_TIMEOUT;
+               goto done; /* always move on to the next test */
+
+       case SEQ_MSG_FAIL:
+               if (exp->flags & LWS_AUT_EXPECT_SHOULD_FAIL) {
+                       /*
+                        * in this case, we expected to fail like this, it's OK
+                        */
+                       lwsl_user("%s: test %d failed as expected\n",
+                                 __func__, s->state);
+
+                       goto pass; /* always move on to the next test */
+               }
+
+               lwsl_user("%s: seq failed at step %d\n", __func__, s->state);
+
+               s->args.results[s->state] = LPE_FAILED;
+               goto done; /* always move on to the next test */
+
+       case SEQ_MSG_PASS:
+               if (exp->flags & (LWS_AUT_EXPECT_SHOULD_FAIL |
+                                 LWS_AUT_EXPECT_SHOULD_TIMEOUT)) {
+                       /*
+                        * In these specific cases, done would be a failure,
+                        * we expected to timeout or fail
+                        */
+                       lwsl_user("%s: seq failed at step %d\n", __func__,
+                                 s->state);
+
+                       s->args.results[s->state] = LPE_FAILED_UNEXPECTED_PASS;
+
+                       goto done; /* always move on to the next test */
+               }
+               lwsl_info("%s: seq done test %d\n", __func__, s->state);
+pass:
+               (*s->args.count_passes)++;
+               s->args.results[s->state] = LPE_SUCCEEDED;
+
+done:
+               lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE);
+               s->state++;
+step:
+               if (!s->args.tests[s->state].name) {
+                       /* the sequence has completed */
+                       lwsl_user("%s: sequence completed OK\n", __func__);
+
+                       if (s->args.cb)
+                               s->args.cb(s->args.cb_user);
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               lwsl_info("%s: starting test %d\n", __func__, s->state);
+
+               if (s->state >= s->args.results_max) {
+                       lwsl_err("%s: results array is too small\n", __func__);
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               test_abs = s->original_abs;
+               s->uttt[0].name_index = LTMI_PEER_V_EXPECT_TEST;
+               s->uttt[0].u.value = (void *)&s->args.tests[s->state];
+               s->uttt[1].name_index = LTMI_PEER_V_EXPECT_RESULT_CB;
+               s->uttt[1].u.value = (void *)unit_test_result_cb;
+               s->uttt[2].name_index = LTMI_PEER_V_EXPECT_RESULT_CB_ARG;
+               s->uttt[2].u.value = (void *)s;
+               /* give the unit test transport the test tokens */
+               test_abs.at_tokens = s->uttt;
+
+               s->instance = lws_abs_bind_and_create_instance(&test_abs);
+               if (!s->instance) {
+                       lwsl_notice("%s: failed to create step %d unit test\n",
+                                   __func__, s->state);
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               (*s->args.count_tests)++;
+               break;
+
+       default:
+               break;
+       }
+
+       return LWSSEQ_RET_CONTINUE;
+}
+
+
+/*
+ * Creates an lws_sequencer to manage the test sequence
+ */
+
+int
+lws_abs_unit_test_sequencer(const lws_test_sequencer_args_t *args)
+{
+       struct lws_seq_test_sequencer *s;
+       lws_seq_t *seq;
+       lws_seq_info_t i;
+
+       memset(&i, 0, sizeof(i));
+       i.context = args->abs->vh->context;
+       i.user_size = sizeof(struct lws_seq_test_sequencer);
+       i.puser = (void **)&s;
+       i.cb = test_sequencer_cb;
+       i.name = "test-seq";
+
+       /*
+        * Create a sequencer in the event loop to manage the tests
+        */
+
+       seq = lws_seq_create(&i);
+       if (!seq) {
+               lwsl_err("%s: unable to create sequencer\n", __func__);
+               return 1;
+       }
+
+       /*
+        * Take a copy of the original lws_abs_t we were passed so we can use
+        * it as the basis of the lws_abs_t we create the individual tests with
+        */
+       s->original_abs = *args->abs;
+
+       s->args = *args;
+
+       s->context = args->abs->vh->context;
+       s->vhost = args->abs->vh;
+       s->unit_test_seq = seq;
+
+       *s->args.count_tests = 0;
+       *s->args.count_passes = 0;
+
+       return 0;
+}
diff --git a/lib/abstract/transports/raw-skt.c b/lib/abstract/transports/raw-skt.c
new file mode 100644 (file)
index 0000000..74f08ab
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * libwebsockets lib/abstract/transports/raw-skt.c
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "abstract/private.h"
+
+typedef struct lws_abstxp_raw_skt_priv {
+       struct lws_abs *abs;
+       struct lws *wsi;
+
+       lws_dll2_t same_abs_transport_list;
+
+       uint8_t established:1;
+       uint8_t connecting:1;
+} abs_raw_skt_priv_t;
+
+struct vhd {
+       lws_dll2_owner_t owner;
+};
+
+static int
+heartbeat_cb(struct lws_dll2 *d, void *user)
+{
+       abs_raw_skt_priv_t *priv = lws_container_of(d, abs_raw_skt_priv_t,
+                                                   same_abs_transport_list);
+
+       if (priv->abs->ap->heartbeat)
+               priv->abs->ap->heartbeat(priv->abs->api);
+
+       return 0;
+}
+
+static int
+callback_abs_client_raw_skt(struct lws *wsi, enum lws_callback_reasons reason,
+                           void *user, void *in, size_t len)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)user;
+       struct vhd *vhd = (struct vhd *)
+               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                        lws_get_protocol(wsi));
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi), sizeof(struct vhd));
+               if (!vhd)
+                       return 1;
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                              lws_get_protocol(wsi),
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_USER:
+               /*
+                * This comes at 1Hz without a wsi context, so there is no
+                * valid priv.  We need to track the live abstract objects that
+                * are using our abstract protocol, and pass the heartbeat
+                * through to the ones that care.
+                */
+               if (!vhd)
+                       break;
+
+               lws_dll2_foreach_safe(&vhd->owner, NULL, heartbeat_cb);
+
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                              lws_get_protocol(wsi),
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+        case LWS_CALLBACK_RAW_CONNECTED:
+               lwsl_debug("LWS_CALLBACK_RAW_CONNECTED\n");
+               priv->connecting = 0;
+               priv->established = 1;
+               if (priv->abs->ap->accept)
+                       priv->abs->ap->accept(priv->abs->api);
+               if (wsi->seq)
+                       /*
+                        * we are bound to a sequencer who wants to know about
+                        * our lifecycle events
+                        */
+
+                       lws_seq_queue_event(wsi->seq, LWSSEQ_WSI_CONNECTED,
+                                                 wsi, NULL);
+                break;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_user("CONNECTION_ERROR\n");
+               if (in)
+                       lwsl_user("   %s\n", (const char *)in);
+
+               if (wsi->seq)
+                       /*
+                        * we are bound to a sequencer who wants to know about
+                        * our lifecycle events
+                        */
+
+                       lws_seq_queue_event(wsi->seq, LWSSEQ_WSI_CONN_FAIL,
+                                           wsi, NULL);
+
+               goto close_path;
+
+               /* fallthru */
+       case LWS_CALLBACK_RAW_CLOSE:
+               if (!user)
+                       break;
+
+               if (wsi->seq)
+                       /*
+                        * we are bound to a sequencer who wants to know about
+                        * our lifecycle events
+                        */
+
+                       lws_seq_queue_event(wsi->seq, LWSSEQ_WSI_CONN_CLOSE,
+                                           wsi, NULL);
+
+close_path:
+               lwsl_debug("LWS_CALLBACK_RAW_CLOSE\n");
+               priv->established = 0;
+               priv->connecting = 0;
+               if (priv->abs && priv->abs->ap->closed)
+                       priv->abs->ap->closed(priv->abs->api);
+               lws_set_wsi_user(wsi, NULL);
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               lwsl_debug("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
+               return !!priv->abs->ap->rx(priv->abs->api, in, len);
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_RAW_WRITEABLE\n");
+               priv->abs->ap->writeable(priv->abs->api,
+                               lws_get_peer_write_allowance(priv->wsi));
+               break;
+
+       case LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL:
+               lws_dll2_add_tail(&priv->same_abs_transport_list, &vhd->owner);
+               break;
+
+       case LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL:
+               lws_dll2_remove(&priv->same_abs_transport_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static int
+lws_atcrs_close(lws_abs_transport_inst_t *ati)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati;
+       struct lws *wsi = priv->wsi;
+
+       if (!priv->wsi)
+               return 0;
+
+       if (!lws_raw_transaction_completed(priv->wsi))
+               return 0;
+
+       priv->wsi = NULL;
+       lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC);
+
+       /* priv is destroyed in the CLOSE callback */
+
+       return 0;
+}
+
+
+const struct lws_protocols protocol_abs_client_raw_skt = {
+       "lws-abs-cli-raw-skt", callback_abs_client_raw_skt,
+       0, 1024, 1024, NULL, 0
+};
+
+static int
+lws_atcrs_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati;
+
+       if (!priv->wsi) {
+               lwsl_err("%s: NULL priv->wsi\n", __func__);
+               return 1;
+       }
+
+       lwsl_debug("%s: priv %p, wsi %p, ro %p\n", __func__,
+                       priv, priv->wsi, priv->wsi->role_ops);
+
+       if (lws_write(priv->wsi, buf, len, LWS_WRITE_RAW) < 0)
+               lws_atcrs_close(ati);
+
+       return 0;
+}
+
+#if !defined(LWS_WITHOUT_CLIENT)
+static int
+lws_atcrs_client_conn(const lws_abs_t *abs)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)abs->ati;
+       struct lws_client_connect_info i;
+       const lws_token_map_t *tm;
+
+       if (priv->connecting)
+               return 0;
+
+       if (priv->established) {
+               lws_set_timeout(priv->wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5);
+
+               return 0;
+       }
+
+       memset(&i, 0, sizeof(i));
+
+       /* address and port are passed-in using the abstract transport tokens */
+
+       tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_DNS_ADDRESS);
+       if (!tm) {
+               lwsl_notice("%s: raw_skt needs LTMI_PEER_V_DNS_ADDRESS\n",
+                           __func__);
+
+               return 1;
+       }
+       i.address = tm->u.value;
+
+       tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_LV_PORT);
+       if (!tm) {
+               lwsl_notice("%s: raw_skt needs LTMI_PEER_LV_PORT\n", __func__);
+
+               return 1;
+       }
+       i.port = tm->u.lvalue;
+
+       /* optional */
+       i.ssl_connection = 0;
+       tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_LV_TLS_FLAGS);
+       if (tm)
+               i.ssl_connection = tm->u.lvalue;
+
+
+       lwsl_debug("%s: raw_skt priv %p connecting to %s:%u %p\n",
+                  __func__, priv, i.address, i.port, abs->vh->context);
+
+       i.path = "";
+       i.method = "RAW";
+       i.vhost = abs->vh;
+       i.userdata = priv;
+       i.host = i.address;
+       i.pwsi = &priv->wsi;
+       i.origin = i.address;
+       i.context = abs->vh->context;
+       i.local_protocol_name = "lws-abs-cli-raw-skt";
+       i.seq = abs->seq;
+       i.opaque_user_data = abs->opaque_user_data;
+
+       priv->wsi = lws_client_connect_via_info(&i);
+       if (!priv->wsi)
+               return 1;
+
+       priv->connecting = 1;
+
+       return 0;
+}
+#endif
+
+static int
+lws_atcrs_ask_for_writeable(lws_abs_transport_inst_t *ati)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati;
+
+       if (!priv->wsi || !priv->established)
+               return 1;
+
+       lws_callback_on_writable(priv->wsi);
+
+       return 0;
+}
+
+static int
+lws_atcrs_create(struct lws_abs *ai)
+{
+       abs_raw_skt_priv_t *at = (abs_raw_skt_priv_t *)ai->ati;
+
+       memset(at, 0, sizeof(*at));
+       at->abs = ai;
+
+       return 0;
+}
+
+static void
+lws_atcrs_destroy(lws_abs_transport_inst_t **pati)
+{
+       /*
+        * We don't free anything because the abstract layer combined our
+        * allocation with that of the instance, and it will free the whole
+        * thing after this.
+        */
+       *pati = NULL;
+}
+
+static int
+lws_atcrs_set_timeout(lws_abs_transport_inst_t *ati, int reason, int secs)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati;
+
+       lws_set_timeout(priv->wsi, reason, secs);
+
+       return 0;
+}
+
+static int
+lws_atcrs_state(lws_abs_transport_inst_t *ati)
+{
+       abs_raw_skt_priv_t *priv = (abs_raw_skt_priv_t *)ati;
+
+       if (!priv || !priv->wsi || (!priv->established && !priv->connecting))
+               return 0;
+
+       return 1;
+}
+
+const lws_abs_transport_t lws_abs_transport_cli_raw_skt = {
+       .name                   = "raw_skt",
+       .alloc                  = sizeof(abs_raw_skt_priv_t),
+
+       .create                 = lws_atcrs_create,
+       .destroy                = lws_atcrs_destroy,
+
+       .tx                     = lws_atcrs_tx,
+#if defined(LWS_WITHOUT_CLIENT)
+       .client_conn            = NULL,
+#else
+       .client_conn            = lws_atcrs_client_conn,
+#endif
+       .close                  = lws_atcrs_close,
+       .ask_for_writeable      = lws_atcrs_ask_for_writeable,
+       .set_timeout            = lws_atcrs_set_timeout,
+       .state                  = lws_atcrs_state,
+};
diff --git a/lib/abstract/transports/unit-test.c b/lib/abstract/transports/unit-test.c
new file mode 100644 (file)
index 0000000..c891861
--- /dev/null
@@ -0,0 +1,531 @@
+/*
+ * libwebsockets lib/abstract/transports/unit-test.c
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * An abstract transport that is useful for unit testing an abstract protocol.
+ * It doesn't actually connect to anything, but checks the protocol's response
+ * to provided canned packets from an array of test vectors.
+ */
+
+#include "core/private.h"
+#include "abstract/private.h"
+
+/* this is the transport priv instantiated at abs->ati */
+
+typedef struct lws_abstxp_unit_test_priv {
+       char                                    note[128];
+       struct lws_abs                          *abs;
+
+       lws_seq_t                               *seq;
+       lws_unit_test_t                         *current_test;
+       lws_unit_test_packet_t                  *expect;
+       lws_unit_test_packet_test_cb            result_cb;
+       const void                              *result_cb_arg;
+
+       lws_unit_test_packet_disposition        disposition;
+       /* synthesized protocol timeout */
+       time_t                                  timeout;
+
+       uint8_t                                 established:1;
+       uint8_t                                 connecting:1;
+} abs_unit_test_priv_t;
+
+typedef struct seq_priv {
+       lws_abs_t *ai;
+} seq_priv_t;
+
+enum {
+       UTSEQ_MSG_WRITEABLE = LWSSEQ_USER_BASE,
+       UTSEQ_MSG_CLOSING,
+       UTSEQ_MSG_TIMEOUT,
+       UTSEQ_MSG_CONNECTING,
+       UTSEQ_MSG_POST_TX_KICK,
+       UTSEQ_MSG_DISPOSITION_KNOWN
+};
+
+/*
+ * A definitive result has appeared for the current test
+ */
+
+static lws_unit_test_packet_disposition
+lws_unit_test_packet_dispose(abs_unit_test_priv_t *priv,
+                           lws_unit_test_packet_disposition disp,
+                           const char *note)
+{
+       assert(priv->disposition == LPE_CONTINUE);
+
+       lwsl_info("%s: %d\n", __func__, disp);
+
+       if (note)
+               lws_strncpy(priv->note, note, sizeof(priv->note));
+
+       priv->disposition = disp;
+
+       lws_seq_queue_event(priv->seq, UTSEQ_MSG_DISPOSITION_KNOWN,
+                                 NULL, NULL);
+
+       return disp;
+}
+
+/*
+ * start on the next step of the test
+ */
+
+lws_unit_test_packet_disposition
+process_expect(abs_unit_test_priv_t *priv)
+{
+       assert(priv->disposition == LPE_CONTINUE);
+
+       while (priv->expect->flags & LWS_AUT_EXPECT_RX &&
+              priv->disposition == LPE_CONTINUE) {
+               int f = priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE, s;
+
+               if (priv->expect->pre)
+                       priv->expect->pre(priv->abs);
+
+               lwsl_info("%s: rx()\n", __func__);
+               lwsl_hexdump_debug(priv->expect->buffer, priv->expect->len);
+               s = priv->abs->ap->rx(priv->abs->api, priv->expect->buffer,
+                                                       priv->expect->len);
+
+               if (!!f != !!s) {
+                       lwsl_notice("%s: expected rx return %d, got %d\n",
+                                       __func__, !!f, s);
+
+                       return lws_unit_test_packet_dispose(priv, LPE_FAILED,
+                                                 "rx unexpected return");
+               }
+
+               if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) {
+                       lws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL);
+                       break;
+               }
+
+               priv->expect++;
+       }
+
+       return LPE_CONTINUE;
+}
+
+static lws_seq_cb_return_t
+unit_test_sequencer_cb(struct lws_sequencer *seq, void *user, int event,
+                      void *data, void *aux)
+{
+       seq_priv_t *s = (seq_priv_t *)user;
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)s->ai->ati;
+       time_t now;
+
+       switch ((int)event) {
+       case LWSSEQ_CREATED: /* our sequencer just got started */
+               lwsl_notice("%s: %s: created\n", __func__,
+                           lws_seq_name(seq));
+               if (s->ai->at->client_conn(s->ai)) {
+                       lwsl_notice("%s: %s: abstract client conn failed\n",
+                                       __func__, lws_seq_name(seq));
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               break;
+
+       case LWSSEQ_DESTROYED:
+               /*
+                * This sequencer is about to be destroyed.  If we have any
+                * other assets in play, detach them from us.
+                */
+
+               if (priv->abs)
+                       lws_abs_destroy_instance(&priv->abs);
+
+               break;
+
+       case LWSSEQ_HEARTBEAT:
+
+               /* synthesize a wsi-style timeout */
+
+               if (!priv->timeout)
+                       goto ph;
+
+               time(&now);
+
+               if (now <= priv->timeout)
+                       goto ph;
+
+               if (priv->expect->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT) {
+                       lwsl_user("%s: test got expected timeout\n",
+                                 __func__);
+                       lws_unit_test_packet_dispose(priv,
+                                       LPE_FAILED_UNEXPECTED_TIMEOUT, NULL);
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               lwsl_user("%s: seq timed out\n", __func__);
+
+ph:
+               if (priv->abs->ap->heartbeat)
+                       priv->abs->ap->heartbeat(priv->abs->api);
+               break;
+
+       case UTSEQ_MSG_DISPOSITION_KNOWN:
+
+               lwsl_info("%s: %s: DISPOSITION_KNOWN %s: %s\n", __func__,
+                         priv->abs->ap->name,
+                         priv->current_test->name,
+                         priv->disposition == LPE_SUCCEEDED ? "OK" : "FAIL");
+
+               /*
+                * if the test has a callback, call it back to let it
+                * know the result
+                */
+               if (priv->result_cb)
+                       priv->result_cb(priv->result_cb_arg, priv->disposition);
+
+               return LWSSEQ_RET_DESTROY;
+
+        case UTSEQ_MSG_CONNECTING:
+               lwsl_debug("UTSEQ_MSG_CONNECTING\n");
+
+               if (priv->abs->ap->accept)
+                       priv->abs->ap->accept(priv->abs->api);
+
+               priv->established = 1;
+
+               /* fallthru */
+
+        case UTSEQ_MSG_POST_TX_KICK:
+               if (priv->disposition)
+                       break;
+
+               if (process_expect(priv) != LPE_CONTINUE) {
+                       lwsl_notice("%s: UTSEQ_MSG_POST_TX_KICK failed\n",
+                                __func__);
+                       return LWSSEQ_RET_DESTROY;
+               }
+               break;
+
+       case UTSEQ_MSG_WRITEABLE:
+               /*
+                * inform the protocol our transport is writeable now
+                */
+               priv->abs->ap->writeable(priv->abs->api, 1024);
+               break;
+
+       case UTSEQ_MSG_CLOSING:
+
+               if (!(priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE)) {
+                       lwsl_user("%s: got unexpected close\n", __func__);
+
+                       lws_unit_test_packet_dispose(priv,
+                                       LPE_FAILED_UNEXPECTED_CLOSE, NULL);
+                       goto done;
+               }
+
+               /* tell the abstract protocol we are closing on them */
+
+               if (priv->abs && priv->abs->ap->closed)
+                       priv->abs->ap->closed(priv->abs->api);
+
+               goto done;
+
+       case UTSEQ_MSG_TIMEOUT: /* current step timed out */
+
+               s->ai->at->close(s->ai->ati);
+
+               if (!(priv->expect->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT)) {
+                       lwsl_user("%s: got unexpected timeout\n", __func__);
+
+                       lws_unit_test_packet_dispose(priv,
+                                       LPE_FAILED_UNEXPECTED_TIMEOUT, NULL);
+                       return LWSSEQ_RET_DESTROY;
+               }
+               goto done;
+
+done:
+               lws_seq_timeout_us(lws_seq_from_user(s),
+                                        LWSSEQTO_NONE);
+               priv->expect++;
+               if (!priv->expect->buffer) {
+                       /* the sequence has completed */
+                       lwsl_user("%s: sequence completed OK\n", __func__);
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return LWSSEQ_RET_CONTINUE;
+}
+
+static int
+lws_atcut_close(lws_abs_transport_inst_t *ati)
+{
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati;
+
+       lwsl_notice("%s\n", __func__);
+
+       lws_seq_queue_event(priv->seq, UTSEQ_MSG_CLOSING, NULL, NULL);
+
+       return 0;
+}
+
+static int
+lws_atcut_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len)
+{
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati;
+
+       assert(priv->disposition == LPE_CONTINUE);
+
+       lwsl_info("%s: received tx\n", __func__);
+
+       if (priv->expect->pre)
+               priv->expect->pre(priv->abs);
+
+       if (!(priv->expect->flags & LWS_AUT_EXPECT_TX)) {
+               lwsl_notice("%s: unexpected tx\n", __func__);
+               lwsl_hexdump_notice(buf, len);
+               lws_unit_test_packet_dispose(priv, LPE_FAILED, "unexpected tx");
+
+               return 1;
+       }
+
+       if (len != priv->expect->len) {
+               lwsl_notice("%s: unexpected tx len %zu, expected %zu\n",
+                               __func__, len, priv->expect->len);
+               lws_unit_test_packet_dispose(priv, LPE_FAILED,
+                                            "tx len mismatch");
+
+               return 1;
+       }
+
+       if (memcmp(buf, priv->expect->buffer, len)) {
+               lwsl_notice("%s: tx mismatch (exp / actual)\n", __func__);
+               lwsl_hexdump_debug(priv->expect->buffer, len);
+               lwsl_hexdump_debug(buf, len);
+               lws_unit_test_packet_dispose(priv, LPE_FAILED,
+                                            "tx data mismatch");
+
+               return 1;
+       }
+
+       if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) {
+               lws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL);
+
+               return 1;
+       }
+
+       priv->expect++;
+
+       lws_seq_queue_event(priv->seq, UTSEQ_MSG_POST_TX_KICK, NULL, NULL);
+
+       return 0;
+}
+
+#if !defined(LWS_WITHOUT_CLIENT)
+static int
+lws_atcut_client_conn(const lws_abs_t *abs)
+{
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)abs->ati;
+       const lws_token_map_t *tm;
+
+       if (priv->established) {
+               lwsl_err("%s: already established\n", __func__);
+               return 1;
+       }
+
+       /* set up the test start pieces... the array of test expects... */
+
+       tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_TEST);
+       if (!tm) {
+               lwsl_notice("%s: unit_test needs LTMI_PEER_V_EXPECT_TEST\n",
+                           __func__);
+
+               return 1;
+       }
+       priv->current_test = (lws_unit_test_t *)tm->u.value;
+
+       /* ... and the callback to deliver the result to */
+       tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_RESULT_CB);
+       if (tm)
+               priv->result_cb = (lws_unit_test_packet_test_cb)tm->u.value;
+       else
+               priv->result_cb = NULL;
+
+       /* ... and the arg to deliver it with */
+       tm = lws_abs_get_token(abs->at_tokens,
+                              LTMI_PEER_V_EXPECT_RESULT_CB_ARG);
+       if (tm)
+               priv->result_cb_arg = tm->u.value;
+
+       priv->expect = priv->current_test->expect_array;
+       priv->disposition = LPE_CONTINUE;
+       priv->note[0] = '\0';
+
+       lws_seq_timeout_us(priv->seq, priv->current_test->max_secs *
+                                           LWS_US_PER_SEC);
+
+       lwsl_notice("%s: %s: test '%s': start\n", __func__, abs->ap->name,
+                   priv->current_test->name);
+
+       lws_seq_queue_event(priv->seq, UTSEQ_MSG_CONNECTING, NULL, NULL);
+
+       return 0;
+}
+#endif
+
+static int
+lws_atcut_ask_for_writeable(lws_abs_transport_inst_t *ati)
+{
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati;
+
+       if (!priv->established)
+               return 1;
+
+       /*
+        * Queue a writeable event... this won't be handled by teh sequencer
+        * until we have returned to the event loop, just like a real
+        * callback_on_writable()
+        */
+       lws_seq_queue_event(priv->seq, UTSEQ_MSG_WRITEABLE, NULL, NULL);
+
+       return 0;
+}
+
+/*
+ * An abstract protocol + transport has been instantiated
+ */
+
+static int
+lws_atcut_create(lws_abs_t *ai)
+{
+       abs_unit_test_priv_t *priv;
+       lws_seq_t *seq;
+       lws_seq_info_t i;
+       seq_priv_t *s;
+
+       memset(&i, 0, sizeof(i));
+       i.context = ai->vh->context;
+       i.user_size = sizeof(*s);
+       i.puser = (void **)&s;
+       i.cb = unit_test_sequencer_cb;
+       i.name = "unit-test-seq";
+
+       /*
+        * Create the sequencer for the steps in a single unit test
+        */
+
+       seq = lws_seq_create(&i);
+       if (!seq) {
+               lwsl_err("%s: unable to create sequencer\n", __func__);
+
+               return 1;
+       }
+
+       priv = ai->ati;
+       memset(s, 0, sizeof(*s));
+       memset(priv, 0, sizeof(*priv));
+
+       /* the sequencer priv just points to the lws_abs_t */
+       s->ai = ai;
+       priv->abs = ai;
+       priv->seq = seq;
+
+       return 0;
+}
+
+static void
+lws_atcut_destroy(lws_abs_transport_inst_t **pati)
+{
+       /*
+        * We don't free anything because the abstract layer combined our
+        * allocation with that of the instance, and it will free the whole
+        * thing after this.
+        */
+       *pati = NULL;
+}
+
+static int
+lws_atcut_set_timeout(lws_abs_transport_inst_t *ati, int reason, int secs)
+{
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati;
+       time_t now;
+
+       time(&now);
+
+       if (secs)
+               priv->timeout = now + secs;
+       else
+               priv->timeout = 0;
+
+       return 0;
+}
+
+static int
+lws_atcut_state(lws_abs_transport_inst_t *ati)
+{
+       abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati;
+
+       if (!priv || (!priv->established && !priv->connecting))
+               return 0;
+
+       return 1;
+}
+
+static const char *dnames[] = {
+       "INCOMPLETE",
+       "PASS",
+       "FAIL",
+       "FAIL(TIMEOUT)",
+       "FAIL(UNEXPECTED PASS)",
+       "FAIL(UNEXPECTED CLOSE)",
+       "SKIPPED"
+       "?",
+       "?"
+};
+
+
+const char *
+lws_unit_test_result_name(int in)
+{
+       if (in < 0 || in > (int)LWS_ARRAY_SIZE(dnames))
+               return "unknown";
+
+       return dnames[in];
+}
+
+const lws_abs_transport_t lws_abs_transport_cli_unit_test = {
+       .name                   = "unit_test",
+       .alloc                  = sizeof(abs_unit_test_priv_t),
+
+       .create                 = lws_atcut_create,
+       .destroy                = lws_atcut_destroy,
+
+       .tx                     = lws_atcut_tx,
+#if defined(LWS_WITHOUT_CLIENT)
+       .client_conn            = NULL,
+#else
+       .client_conn            = lws_atcut_client_conn,
+#endif
+       .close                  = lws_atcut_close,
+       .ask_for_writeable      = lws_atcut_ask_for_writeable,
+       .set_timeout            = lws_atcut_set_timeout,
+       .state                  = lws_atcut_state,
+};
diff --git a/lib/alloc.c b/lib/alloc.c
deleted file mode 100644 (file)
index 34e7a8a..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-#include "private-libwebsockets.h"
-
-#if defined(LWS_PLAT_OPTEE)
-
-#define TEE_USER_MEM_HINT_NO_FILL_ZERO       0x80000000
-
-void *__attribute__((weak))
-       TEE_Malloc(uint32_t size, uint32_t hint)
-{
-       return NULL;
-}
-void *__attribute__((weak))
-       TEE_Realloc(void *buffer, uint32_t newSize)
-{
-       return NULL;
-}
-void __attribute__((weak))
-       TEE_Free(void *buffer)
-{
-}
-
-void *lws_realloc(void *ptr, size_t size)
-{
-       return TEE_Realloc(ptr, size);
-}
-
-void *lws_malloc(size_t size)
-{
-       return TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO);
-}
-
-void lws_free(void *p)
-{
-       TEE_Free(p);
-}
-
-void *lws_zalloc(size_t size)
-{
-       void *ptr = TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO);
-       if (ptr)
-               memset(ptr, 0, size);
-       return ptr;
-}
-
-void lws_set_allocator(void *(*cb)(void *ptr, size_t size))
-{
-       (void)cb;
-}
-#else
-
-static void *_realloc(void *ptr, size_t size)
-{
-       if (size)
-#if defined(LWS_PLAT_OPTEE)
-               return (void *)TEE_Realloc(ptr, size);
-#else
-               return (void *)realloc(ptr, size);
-#endif
-       else if (ptr)
-               free(ptr);
-       return NULL;
-}
-
-void *(*_lws_realloc)(void *ptr, size_t size) = _realloc;
-
-void *lws_realloc(void *ptr, size_t size)
-{
-       return _lws_realloc(ptr, size);
-}
-
-void *lws_zalloc(size_t size)
-{
-       void *ptr = _lws_realloc(NULL, size);
-       if (ptr)
-               memset(ptr, 0, size);
-       return ptr;
-}
-
-void lws_set_allocator(void *(*cb)(void *ptr, size_t size))
-{
-       _lws_realloc = cb;
-}
-#endif
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
deleted file mode 100644 (file)
index 6ae27d2..0000000
+++ /dev/null
@@ -1,1045 +0,0 @@
-#include "private-libwebsockets.h"
-
-static int
-lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result)
-{
-       struct addrinfo hints;
-
-       memset(&hints, 0, sizeof(hints));
-       *result = NULL;
-
-#ifdef LWS_USE_IPV6
-       if (wsi->ipv6) {
-
-#if !defined(__ANDROID__)
-               hints.ai_family = AF_INET6;
-               hints.ai_flags = AI_V4MAPPED;
-#endif
-       } else
-#endif
-       {
-               hints.ai_family = PF_UNSPEC;
-               hints.ai_socktype = SOCK_STREAM;
-               hints.ai_flags = AI_CANONNAME;
-       }
-
-       return getaddrinfo(ads, NULL, &hints, result);
-}
-
-struct lws *
-lws_client_connect_2(struct lws *wsi)
-{
-       sockaddr46 sa46;
-       struct addrinfo *result;
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       struct lws_pollfd pfd;
-       const char *cce = "", *iface;
-       int n, plen = 0, port;
-       const char *ads;
-#ifdef LWS_USE_IPV6
-       char ipv6only = lws_check_opt(wsi->vhost->options,
-                       LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY |
-                       LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
-
-#if defined(__ANDROID__)
-       ipv6only = 0;
-#endif
-#endif
-
-       lwsl_client("%s\n", __func__);
-
-       if (!wsi->u.hdr.ah) {
-               cce = "ah was NULL at cc2";
-               lwsl_err("%s\n", cce);
-               goto oom4;
-       }
-
-       /*
-        * start off allowing ipv6 on connection if vhost allows it
-        */
-       wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost);
-
-       /* Decide what it is we need to connect to:
-        *
-        * Priority 1: connect to http proxy */
-
-       if (wsi->vhost->http_proxy_port) {
-               plen = sprintf((char *)pt->serv_buf,
-                       "CONNECT %s:%u HTTP/1.0\x0d\x0a"
-                       "User-agent: libwebsockets\x0d\x0a",
-                       lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS),
-                       wsi->c_port);
-
-               if (wsi->vhost->proxy_basic_auth_token[0])
-                       plen += sprintf((char *)pt->serv_buf + plen,
-                                       "Proxy-authorization: basic %s\x0d\x0a",
-                                       wsi->vhost->proxy_basic_auth_token);
-
-               plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a");
-               ads = wsi->vhost->http_proxy_address;
-               port = wsi->vhost->http_proxy_port;
-
-#if defined(LWS_WITH_SOCKS5)
-
-       /* Priority 2: Connect to SOCK5 Proxy */
-
-       } else if (wsi->vhost->socks_proxy_port) {
-               socks_generate_msg(wsi, SOCKS_MSG_GREETING, (size_t *)&plen);
-               lwsl_client("%s\n", "Sending SOCKS Greeting.");
-
-               ads = wsi->vhost->socks_proxy_address;
-               port = wsi->vhost->socks_proxy_port;
-#endif
-       } else {
-
-               /* Priority 3: Connect directly */
-
-               ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
-               port = wsi->c_port;
-       }
-
-       /*
-        * prepare the actual connection
-        * to whatever we decided to connect to
-        */
-
-       lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads);
-
-       n = lws_getaddrinfo46(wsi, ads, &result);
-
-#ifdef LWS_USE_IPV6
-       if (wsi->ipv6) {
-
-               if (n) {
-                       /* lws_getaddrinfo46 failed, there is no usable result */
-                       lwsl_notice("%s: lws_getaddrinfo46 failed %d\n",
-                                       __func__, n);
-                       cce = "ipv6 lws_getaddrinfo46 failed";
-                       goto oom4;
-               }
-
-               memset(&sa46, 0, sizeof(sa46));
-
-               sa46.sa6.sin6_family = AF_INET6;
-               switch (result->ai_family) {
-               case AF_INET:
-                       if (ipv6only)
-                               break;
-                       /* map IPv4 to IPv6 */
-                       bzero((char *)&sa46.sa6.sin6_addr,
-                                               sizeof(sa46.sa6.sin6_addr));
-                       sa46.sa6.sin6_addr.s6_addr[10] = 0xff;
-                       sa46.sa6.sin6_addr.s6_addr[11] = 0xff;
-                       memcpy(&sa46.sa6.sin6_addr.s6_addr[12],
-                               &((struct sockaddr_in *)result->ai_addr)->sin_addr,
-                                                       sizeof(struct in_addr));
-                       lwsl_notice("uplevelling AF_INET to AF_INET6\n");
-                       break;
-
-               case AF_INET6:
-                       memcpy(&sa46.sa6.sin6_addr,
-                         &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr,
-                                               sizeof(struct in6_addr));
-                       sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id;
-                       sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo;
-                       break;
-               default:
-                       lwsl_err("Unknown address family\n");
-                       freeaddrinfo(result);
-                       cce = "unknown address family";
-                       goto oom4;
-               }
-       } else
-#endif /* use ipv6 */
-
-       /* use ipv4 */
-       {
-               void *p = NULL;
-
-               if (!n) {
-                       struct addrinfo *res = result;
-
-                       /* pick the first AF_INET (IPv4) result */
-
-                       while (!p && res) {
-                               switch (res->ai_family) {
-                               case AF_INET:
-                                       p = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
-                                       break;
-                               }
-
-                               res = res->ai_next;
-                       }
-#if defined(LWS_FALLBACK_GETHOSTBYNAME)
-               } else if (n == EAI_SYSTEM) {
-                       struct hostent *host;
-
-                       lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n");
-                       host = gethostbyname(ads);
-                       if (host) {
-                               p = host->h_addr;
-                       } else {
-                               lwsl_err("gethostbyname failed\n");
-                               cce = "gethostbyname (ipv4) failed";
-                               goto oom4;
-                       }
-#endif
-               } else {
-                       lwsl_err("getaddrinfo failed\n");
-                       cce = "getaddrinfo failed";
-                       goto oom4;
-               }
-
-               if (!p) {
-                       if (result)
-                               freeaddrinfo(result);
-                       lwsl_err("Couldn't identify address\n");
-                       cce = "unable to lookup address";
-                       goto oom4;
-               }
-
-               sa46.sa4.sin_family = AF_INET;
-               sa46.sa4.sin_addr = *((struct in_addr *)p);
-               bzero(&sa46.sa4.sin_zero, 8);
-       }
-
-       if (result)
-               freeaddrinfo(result);
-
-       /* now we decided on ipv4 or ipv6, set the port */
-
-       if (!lws_socket_is_valid(wsi->desc.sockfd)) {
-
-#if defined(LWS_USE_LIBUV)
-               if (LWS_LIBUV_ENABLED(context))
-                       if (lws_libuv_check_watcher_active(wsi)) {
-                               lwsl_warn("Waiting for libuv watcher to close\n");
-                               cce = "waiting for libuv watcher to close";
-                               goto oom4;
-                       }
-#endif
-
-#ifdef LWS_USE_IPV6
-               if (wsi->ipv6)
-                       wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0);
-               else
-#endif
-                       wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
-               if (!lws_socket_is_valid(wsi->desc.sockfd)) {
-                       lwsl_warn("Unable to open socket\n");
-                       cce = "unable to open socket";
-                       goto oom4;
-               }
-
-               if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) {
-                       lwsl_err("Failed to set wsi socket options\n");
-                       compatible_close(wsi->desc.sockfd);
-                       cce = "set socket opts failed";
-                       goto oom4;
-               }
-
-               wsi->mode = LWSCM_WSCL_WAITING_CONNECT;
-
-               lws_libev_accept(wsi, wsi->desc);
-               lws_libuv_accept(wsi, wsi->desc);
-               lws_libevent_accept(wsi, wsi->desc);
-
-               if (insert_wsi_socket_into_fds(context, wsi)) {
-                       compatible_close(wsi->desc.sockfd);
-                       cce = "insert wsi failed";
-                       goto oom4;
-               }
-
-               lws_change_pollfd(wsi, 0, LWS_POLLIN);
-
-               /*
-                * past here, we can't simply free the structs as error
-                * handling as oom4 does.  We have to run the whole close flow.
-                */
-
-               if (!wsi->protocol)
-                       wsi->protocol = &wsi->vhost->protocols[0];
-
-               wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE,
-                                       wsi->user_space, NULL, 0);
-
-               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
-                               AWAITING_TIMEOUT);
-
-               iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
-
-               if (iface) {
-                       n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface);
-                       if (n < 0) {
-                               cce = "unable to bind socket";
-                               goto failed;
-                       }
-               }
-       }
-
-#ifdef LWS_USE_IPV6
-       if (wsi->ipv6) {
-               sa46.sa6.sin6_port = htons(port);
-               n = sizeof(struct sockaddr_in6);
-       } else
-#endif
-       {
-               sa46.sa4.sin_port = htons(port);
-               n = sizeof(struct sockaddr);
-       }
-
-       if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 ||
-           LWS_ERRNO == LWS_EISCONN) {
-               if (LWS_ERRNO == LWS_EALREADY ||
-                   LWS_ERRNO == LWS_EINPROGRESS ||
-                   LWS_ERRNO == LWS_EWOULDBLOCK
-#ifdef _WIN32
-                       || LWS_ERRNO == WSAEINVAL
-#endif
-               ) {
-                       lwsl_client("nonblocking connect retry (errno = %d)\n",
-                                   LWS_ERRNO);
-
-                       if (lws_plat_check_connection_error(wsi)) {
-                               cce = "socket connect failed";
-                               goto failed;
-                       }
-
-                       /*
-                        * must do specifically a POLLOUT poll to hear
-                        * about the connect completion
-                        */
-                       if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) {
-                               cce = "POLLOUT set failed";
-                               goto failed;
-                       }
-
-                       return wsi;
-               }
-
-               if (LWS_ERRNO != LWS_EISCONN) {
-                       lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO);
-                       cce = "connect failed";
-                       goto failed;
-               }
-       }
-
-       lwsl_client("connected\n");
-
-       /* we are connected to server, or proxy */
-
-       /* http proxy */
-       if (wsi->vhost->http_proxy_port) {
-
-               /*
-                * OK from now on we talk via the proxy, so connect to that
-                *
-                * (will overwrite existing pointer,
-                * leaving old string/frag there but unreferenced)
-                */
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
-                                         wsi->vhost->http_proxy_address))
-                       goto failed;
-               wsi->c_port = wsi->vhost->http_proxy_port;
-
-               n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen,
-                        MSG_NOSIGNAL);
-               if (n < 0) {
-                       lwsl_debug("ERROR writing to proxy socket\n");
-                       cce = "proxy write failed";
-                       goto failed;
-               }
-
-               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
-                               AWAITING_TIMEOUT);
-
-               wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY;
-
-               return wsi;
-       }
-#if defined(LWS_WITH_SOCKS5)
-       /* socks proxy */
-       else if (wsi->vhost->socks_proxy_port) {
-               n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen,
-                        MSG_NOSIGNAL);
-               if (n < 0) {
-                       lwsl_debug("ERROR writing greeting to socks proxy"
-                               "socket.\n");
-                       cce = "socks write failed";
-                       goto failed;
-               }
-
-               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY,
-                               AWAITING_TIMEOUT);
-
-               wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY;
-
-               return wsi;
-       }
-#endif
-
-       /*
-        * provoke service to issue the handshake directly
-        * we need to do it this way because in the proxy case, this is the
-        * next state and executed only if and when we get a good proxy
-        * response inside the state machine... but notice in SSL case this
-        * may not have sent anything yet with 0 return, and won't until some
-        * many retries from main loop.  To stop that becoming endless,
-        * cover with a timeout.
-        */
-
-       lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
-                       AWAITING_TIMEOUT);
-
-       wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE;
-       pfd.fd = wsi->desc.sockfd;
-       pfd.events = LWS_POLLIN;
-       pfd.revents = LWS_POLLIN;
-
-       n = lws_service_fd(context, &pfd);
-       if (n < 0) {
-               cce = "first service failed";
-               goto failed;
-       }
-       if (n) /* returns 1 on failure after closing wsi */
-               return NULL;
-
-       return wsi;
-
-oom4:
-       /* we're closing, losing some rx is OK */
-       lws_header_table_force_to_detachable_state(wsi);
-
-       if (wsi->mode == LWSCM_HTTP_CLIENT ||
-           wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED ||
-           wsi->mode == LWSCM_WSCL_WAITING_CONNECT) {
-               wsi->vhost->protocols[0].callback(wsi,
-                       LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
-                       wsi->user_space, (void *)cce, strlen(cce));
-               wsi->already_did_cce = 1;
-       }
-       /* take care that we might be inserted in fds already */
-       if (wsi->position_in_fds_table != -1)
-               goto failed1;
-       lws_remove_from_timeout_list(wsi);
-       lws_header_table_detach(wsi, 0);
-       lws_free(wsi);
-
-       return NULL;
-
-failed:
-       wsi->vhost->protocols[0].callback(wsi,
-               LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
-               wsi->user_space, (void *)cce, strlen(cce));
-       wsi->already_did_cce = 1;
-failed1:
-       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-
-       return NULL;
-}
-
-/**
- * lws_client_reset() - retarget a connected wsi to start over with a new connection (ie, redirect)
- *                     this only works if still in HTTP, ie, not upgraded yet
- * wsi:                connection to reset
- * address:    network address of the new server
- * port:       port to connect to
- * path:       uri path to connect to on the new server
- * host:       host header to send to the new server
- */
-LWS_VISIBLE struct lws *
-lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
-                const char *path, const char *host)
-{
-       char origin[300] = "", protocol[300] = "", method[32] = "", iface[16] = "", *p;
-       struct lws *wsi = *pwsi;
-
-       if (wsi->redirects == 3) {
-               lwsl_err("%s: Too many redirects\n", __func__);
-               return NULL;
-       }
-       wsi->redirects++;
-
-       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN);
-       if (p)
-               strncpy(origin, p, sizeof(origin) - 1);
-
-       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
-       if (p)
-               strncpy(protocol, p, sizeof(protocol) - 1);
-
-       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
-       if (p)
-               strncpy(method, p, sizeof(method) - 1);
-
-       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
-       if (p)
-               strncpy(method, p, sizeof(iface) - 1);
-
-       lwsl_notice("redirect ads='%s', port=%d, path='%s', ssl = %d\n",
-                  address, port, path, ssl);
-
-       /* close the connection by hand */
-
-#ifdef LWS_OPENSSL_SUPPORT
-       lws_ssl_close(wsi);
-#endif
-
-#ifdef LWS_USE_LIBUV
-       if (LWS_LIBUV_ENABLED(wsi->context)) {
-               lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi);
-               /*
-                * libuv has to do his own close handle processing asynchronously
-                * but once it starts we can do everything else synchronously,
-                * including trash wsi->desc.sockfd since it took a copy.
-                *
-                * When it completes it will call compatible_close()
-                */
-               lws_libuv_closehandle_manually(wsi);
-       } else
-#else
-       compatible_close(wsi->desc.sockfd);
-#endif
-
-       remove_wsi_socket_from_fds(wsi);
-
-#ifdef LWS_OPENSSL_SUPPORT
-       wsi->use_ssl = ssl;
-#else
-       if (ssl) {
-               lwsl_err("%s: not configured for ssl\n", __func__);
-               return NULL;
-       }
-#endif
-
-       wsi->desc.sockfd = LWS_SOCK_INVALID;
-       wsi->state = LWSS_CLIENT_UNCONNECTED;
-       wsi->protocol = NULL;
-       wsi->pending_timeout = NO_PENDING_TIMEOUT;
-       wsi->c_port = port;
-       wsi->hdr_parsing_completed = 0;
-       _lws_header_table_reset(wsi->u.hdr.ah);
-
-       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address))
-               return NULL;
-
-       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host))
-               return NULL;
-
-       if (origin[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN,
-                                         origin))
-                       return NULL;
-       if (protocol[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
-                                         protocol))
-                       return NULL;
-       if (method[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD,
-                                         method))
-                       return NULL;
-
-       if (iface[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE,
-                                         iface))
-                       return NULL;
-
-       origin[0] = '/';
-       strncpy(&origin[1], path, sizeof(origin) - 2);
-       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, origin))
-               return NULL;
-
-       *pwsi = lws_client_connect_2(wsi);
-
-       return *pwsi;
-}
-
-#ifdef LWS_WITH_HTTP_PROXY
-static hubbub_error
-html_parser_cb(const hubbub_token *token, void *pw)
-{
-       struct lws_rewrite *r = (struct lws_rewrite *)pw;
-       char buf[1024], *start = buf + LWS_PRE, *p = start,
-            *end = &buf[sizeof(buf) - 1];
-       size_t i;
-
-       switch (token->type) {
-       case HUBBUB_TOKEN_DOCTYPE:
-
-               p += lws_snprintf(p, end - p, "<!DOCTYPE %.*s %s ",
-                               (int) token->data.doctype.name.len,
-                               token->data.doctype.name.ptr,
-                               token->data.doctype.force_quirks ?
-                                               "(force-quirks) " : "");
-
-               if (token->data.doctype.public_missing)
-                       lwsl_debug("\tpublic: missing\n");
-               else
-                       p += lws_snprintf(p, end - p, "PUBLIC \"%.*s\"\n",
-                               (int) token->data.doctype.public_id.len,
-                               token->data.doctype.public_id.ptr);
-
-               if (token->data.doctype.system_missing)
-                       lwsl_debug("\tsystem: missing\n");
-               else
-                       p += lws_snprintf(p, end - p, " \"%.*s\">\n",
-                               (int) token->data.doctype.system_id.len,
-                               token->data.doctype.system_id.ptr);
-
-               break;
-       case HUBBUB_TOKEN_START_TAG:
-               p += lws_snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len,
-                               token->data.tag.name.ptr);
-
-/*                             (token->data.tag.self_closing) ?
-                                               "(self-closing) " : "",
-                               (token->data.tag.n_attributes > 0) ?
-                                               "attributes:" : "");
-*/
-               for (i = 0; i < token->data.tag.n_attributes; i++) {
-                       if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) ||
-                           !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) ||
-                           !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) {
-                               const char *pp = (const char *)token->data.tag.attributes[i].value.ptr;
-                               int plen = (int) token->data.tag.attributes[i].value.len;
-
-                               if (strncmp(pp, "http:", 5) && strncmp(pp, "https:", 6)) {
-
-                                       if (!hstrcmp(&token->data.tag.attributes[i].value,
-                                                    r->from, r->from_len)) {
-                                               pp += r->from_len;
-                                               plen -= r->from_len;
-                                       }
-                                       p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"",
-                                              (int) token->data.tag.attributes[i].name.len,
-                                              token->data.tag.attributes[i].name.ptr,
-                                              r->to, plen, pp);
-                                       continue;
-                               }
-                       }
-
-                       p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"",
-                               (int) token->data.tag.attributes[i].name.len,
-                               token->data.tag.attributes[i].name.ptr,
-                               (int) token->data.tag.attributes[i].value.len,
-                               token->data.tag.attributes[i].value.ptr);
-               }
-               p += lws_snprintf(p, end - p, ">");
-               break;
-       case HUBBUB_TOKEN_END_TAG:
-               p += lws_snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len,
-                               token->data.tag.name.ptr);
-/*
-                               (token->data.tag.self_closing) ?
-                                               "(self-closing) " : "",
-                               (token->data.tag.n_attributes > 0) ?
-                                               "attributes:" : "");
-*/
-               for (i = 0; i < token->data.tag.n_attributes; i++) {
-                       p += lws_snprintf(p, end - p, " %.*s='%.*s'\n",
-                               (int) token->data.tag.attributes[i].name.len,
-                               token->data.tag.attributes[i].name.ptr,
-                               (int) token->data.tag.attributes[i].value.len,
-                               token->data.tag.attributes[i].value.ptr);
-               }
-               p += lws_snprintf(p, end - p, ">");
-               break;
-       case HUBBUB_TOKEN_COMMENT:
-               p += lws_snprintf(p, end - p, "<!-- %.*s -->\n",
-                               (int) token->data.comment.len,
-                               token->data.comment.ptr);
-               break;
-       case HUBBUB_TOKEN_CHARACTER:
-               if (token->data.character.len == 1) {
-                       if (*token->data.character.ptr == '<') {
-                               p += lws_snprintf(p, end - p, "&lt;");
-                               break;
-                       }
-                       if (*token->data.character.ptr == '>') {
-                               p += lws_snprintf(p, end - p, "&gt;");
-                               break;
-                       }
-                       if (*token->data.character.ptr == '&') {
-                               p += lws_snprintf(p, end - p, "&amp;");
-                               break;
-                       }
-               }
-
-               p += lws_snprintf(p, end - p, "%.*s", (int) token->data.character.len,
-                               token->data.character.ptr);
-               break;
-       case HUBBUB_TOKEN_EOF:
-               p += lws_snprintf(p, end - p, "\n");
-               break;
-       }
-
-       if (user_callback_handle_rxflow(r->wsi->protocol->callback,
-                       r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
-                       r->wsi->user_space, start, p - start))
-               return -1;
-
-       return HUBBUB_OK;
-}
-#endif
-
-LWS_VISIBLE struct lws *
-lws_client_connect_via_info(struct lws_client_connect_info *i)
-{
-       struct lws *wsi;
-       int v = SPEC_LATEST_SUPPORTED;
-       const struct lws_protocols *p;
-
-       if (i->context->requested_kill)
-               return NULL;
-
-       if (!i->context->protocol_init_done)
-               lws_protocol_init(i->context);
-
-       wsi = lws_zalloc(sizeof(struct lws));
-       if (wsi == NULL)
-               goto bail;
-
-       wsi->context = i->context;
-       /* assert the mode and union status (hdr) clearly */
-       lws_union_transition(wsi, LWSCM_HTTP_CLIENT);
-       wsi->desc.sockfd = LWS_SOCK_INVALID;
-
-       /* 1) fill up the wsi with stuff from the connect_info as far as it
-        * can go.  It's because not only is our connection async, we might
-        * not even be able to get ahold of an ah at this point.
-        */
-
-       /* -1 means just use latest supported */
-       if (i->ietf_version_or_minus_one != -1 && i->ietf_version_or_minus_one)
-               v = i->ietf_version_or_minus_one;
-
-       wsi->ietf_spec_revision = v;
-       wsi->user_space = NULL;
-       wsi->state = LWSS_CLIENT_UNCONNECTED;
-       wsi->pending_timeout = NO_PENDING_TIMEOUT;
-       wsi->position_in_fds_table = -1;
-       wsi->c_port = i->port;
-       wsi->vhost = i->vhost;
-       if (!wsi->vhost)
-               wsi->vhost = i->context->vhost_list;
-
-       wsi->protocol = &wsi->vhost->protocols[0];
-
-       /* for http[s] connection, allow protocol selection by name */
-
-       if (i->method && i->vhost && i->protocol) {
-               p = lws_vhost_name_to_protocol(i->vhost, i->protocol);
-               if (p)
-                       wsi->protocol = p;
-       }
-
-       if (wsi && !wsi->user_space && i->userdata) {
-               wsi->user_space_externally_allocated = 1;
-               wsi->user_space = i->userdata;
-       } else
-               /* if we stay in http, we can assign the user space now,
-                * otherwise do it after the protocol negotiated
-                */
-               if (i->method)
-                       if (lws_ensure_user_space(wsi))
-                               goto bail;
-
-#ifdef LWS_OPENSSL_SUPPORT
-       wsi->use_ssl = i->ssl_connection;
-#else
-       if (i->ssl_connection) {
-               lwsl_err("libwebsockets not configured for ssl\n");
-               goto bail;
-       }
-#endif
-
-       /* 2) stash the things from connect_info that we can't process without
-        * an ah.  Because if no ah, we will go on the ah waiting list and
-        * process those things later (after the connect_info and maybe the
-        * things pointed to have gone out of scope.
-        */
-
-       wsi->u.hdr.stash = lws_malloc(sizeof(*wsi->u.hdr.stash));
-       if (!wsi->u.hdr.stash) {
-               lwsl_err("%s: OOM\n", __func__);
-               goto bail;
-       }
-
-       wsi->u.hdr.stash->origin[0] = '\0';
-       wsi->u.hdr.stash->protocol[0] = '\0';
-       wsi->u.hdr.stash->method[0] = '\0';
-       wsi->u.hdr.stash->iface[0] = '\0';
-
-       strncpy(wsi->u.hdr.stash->address, i->address,
-               sizeof(wsi->u.hdr.stash->address) - 1);
-       strncpy(wsi->u.hdr.stash->path, i->path,
-               sizeof(wsi->u.hdr.stash->path) - 1);
-       strncpy(wsi->u.hdr.stash->host, i->host,
-               sizeof(wsi->u.hdr.stash->host) - 1);
-       if (i->origin)
-               strncpy(wsi->u.hdr.stash->origin, i->origin,
-                       sizeof(wsi->u.hdr.stash->origin) - 1);
-       if (i->protocol)
-               strncpy(wsi->u.hdr.stash->protocol, i->protocol,
-                       sizeof(wsi->u.hdr.stash->protocol) - 1);
-       if (i->method)
-               strncpy(wsi->u.hdr.stash->method, i->method,
-                       sizeof(wsi->u.hdr.stash->method) - 1);
-       if (i->iface)
-               strncpy(wsi->u.hdr.stash->iface, i->iface,
-                       sizeof(wsi->u.hdr.stash->iface) - 1);
-
-       wsi->u.hdr.stash->address[sizeof(wsi->u.hdr.stash->address) - 1] = '\0';
-       wsi->u.hdr.stash->path[sizeof(wsi->u.hdr.stash->path) - 1] = '\0';
-       wsi->u.hdr.stash->host[sizeof(wsi->u.hdr.stash->host) - 1] = '\0';
-       wsi->u.hdr.stash->origin[sizeof(wsi->u.hdr.stash->origin) - 1] = '\0';
-       wsi->u.hdr.stash->protocol[sizeof(wsi->u.hdr.stash->protocol) - 1] = '\0';
-       wsi->u.hdr.stash->method[sizeof(wsi->u.hdr.stash->method) - 1] = '\0';
-       wsi->u.hdr.stash->iface[sizeof(wsi->u.hdr.stash->iface) - 1] = '\0';
-
-       if (i->pwsi)
-               *i->pwsi = wsi;
-
-       /* if we went on the waiting list, no probs just return the wsi
-        * when we get the ah, now or later, he will call
-        * lws_client_connect_via_info2() below.
-        */
-       if (lws_header_table_attach(wsi, 0) < 0) {
-               /*
-                * if we failed here, the connection is already closed
-                * and freed.
-                */
-               goto bail1;
-       }
-
-       if (i->parent_wsi) {
-               lwsl_info("%s: created child %p of parent %p\n", __func__,
-                               wsi, i->parent_wsi);
-               wsi->parent = i->parent_wsi;
-               wsi->sibling_list = i->parent_wsi->child_list;
-               i->parent_wsi->child_list = wsi;
-       }
-#ifdef LWS_WITH_HTTP_PROXY
-       if (i->uri_replace_to)
-               wsi->rw = lws_rewrite_create(wsi, html_parser_cb,
-                                            i->uri_replace_from,
-                                            i->uri_replace_to);
-#endif
-
-       return wsi;
-
-bail:
-       lws_free(wsi);
-
-bail1:
-       if (i->pwsi)
-               *i->pwsi = NULL;
-
-       return NULL;
-}
-
-struct lws *
-lws_client_connect_via_info2(struct lws *wsi)
-{
-       struct client_info_stash *stash = wsi->u.hdr.stash;
-
-       if (!stash)
-               return wsi;
-
-       /*
-        * we're not necessarily in a position to action these right away,
-        * stash them... we only need during connect phase so u.hdr is fine
-        */
-       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
-                                 stash->address))
-               goto bail1;
-
-       /* these only need u.hdr lifetime as well */
-
-       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path))
-               goto bail1;
-
-       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host))
-               goto bail1;
-
-       if (stash->origin[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN,
-                                         stash->origin))
-                       goto bail1;
-       /*
-        * this is a list of protocols we tell the server we're okay with
-        * stash it for later when we compare server response with it
-        */
-       if (stash->protocol[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
-                                         stash->protocol))
-                       goto bail1;
-       if (stash->method[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD,
-                                         stash->method))
-                       goto bail1;
-       if (stash->iface[0])
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE,
-                                         stash->iface))
-                       goto bail1;
-
-#if defined(LWS_WITH_SOCKS5)
-       if (!wsi->vhost->socks_proxy_port)
-               lws_free_set_NULL(wsi->u.hdr.stash);
-#endif
-
-       /*
-        * Check with each extension if it is able to route and proxy this
-        * connection for us.  For example, an extension like x-google-mux
-        * can handle this and then we don't need an actual socket for this
-        * connection.
-        */
-
-       if (lws_ext_cb_all_exts(wsi->context, wsi,
-                               LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION,
-                               (void *)stash->address,
-                               wsi->c_port) > 0) {
-               lwsl_client("lws_client_connect: ext handling conn\n");
-
-               lws_set_timeout(wsi,
-                       PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE,
-                               AWAITING_TIMEOUT);
-
-               wsi->mode = LWSCM_WSCL_WAITING_EXTENSION_CONNECT;
-               return wsi;
-       }
-       lwsl_client("lws_client_connect: direct conn\n");
-       wsi->context->count_wsi_allocated++;
-
-       return lws_client_connect_2(wsi);
-
-bail1:
-#if defined(LWS_WITH_SOCKS5)
-       if (!wsi->vhost->socks_proxy_port)
-               lws_free_set_NULL(wsi->u.hdr.stash);
-#endif
-
-       return NULL;
-}
-
-LWS_VISIBLE struct lws *
-lws_client_connect_extended(struct lws_context *context, const char *address,
-                           int port, int ssl_connection, const char *path,
-                           const char *host, const char *origin,
-                           const char *protocol, int ietf_version_or_minus_one,
-                           void *userdata)
-{
-       struct lws_client_connect_info i;
-
-       memset(&i, 0, sizeof(i));
-
-       i.context = context;
-       i.address = address;
-       i.port = port;
-       i.ssl_connection = ssl_connection;
-       i.path = path;
-       i.host = host;
-       i.origin = origin;
-       i.protocol = protocol;
-       i.ietf_version_or_minus_one = ietf_version_or_minus_one;
-       i.userdata = userdata;
-
-       return lws_client_connect_via_info(&i);
-}
-
-LWS_VISIBLE struct lws *
-lws_client_connect(struct lws_context *context, const char *address,
-                           int port, int ssl_connection, const char *path,
-                           const char *host, const char *origin,
-                           const char *protocol, int ietf_version_or_minus_one)
-{
-       struct lws_client_connect_info i;
-
-       memset(&i, 0, sizeof(i));
-
-       i.context = context;
-       i.address = address;
-       i.port = port;
-       i.ssl_connection = ssl_connection;
-       i.path = path;
-       i.host = host;
-       i.origin = origin;
-       i.protocol = protocol;
-       i.ietf_version_or_minus_one = ietf_version_or_minus_one;
-       i.userdata = NULL;
-
-       return lws_client_connect_via_info(&i);
-}
-
-#if defined(LWS_WITH_SOCKS5)
-void socks_generate_msg(struct lws *wsi, enum socks_msg_type type,
-                       size_t *msg_len)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       size_t len = 0;
-
-       if (type == SOCKS_MSG_GREETING) {
-               /* socks version, version 5 only */
-               pt->serv_buf[len++] = SOCKS_VERSION_5;
-               /* number of methods */
-               pt->serv_buf[len++] = 2;
-               /* username password method */
-               pt->serv_buf[len++] = SOCKS_AUTH_USERNAME_PASSWORD;
-               /* no authentication method */
-               pt->serv_buf[len++] = SOCKS_AUTH_NO_AUTH;
-       }
-       else if (type == SOCKS_MSG_USERNAME_PASSWORD) {
-               size_t user_len = 0;
-               size_t passwd_len = 0;
-
-               user_len = strlen(wsi->vhost->socks_user);
-               passwd_len = strlen(wsi->vhost->socks_password);
-
-               /* the subnegotiation version */
-               pt->serv_buf[len++] = SOCKS_SUBNEGOTIATION_VERSION_1;
-               /* length of the user name */
-               pt->serv_buf[len++] = user_len;
-               /* user name */
-               strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user,
-                       context->pt_serv_buf_size - len);
-               len += user_len;
-               /* length of the password */
-               pt->serv_buf[len++] = passwd_len;
-               /* password */
-               strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password,
-                       context->pt_serv_buf_size - len);
-               len += passwd_len;
-       }
-       else if (type == SOCKS_MSG_CONNECT) {
-               size_t len_index = 0;
-               short net_num = 0;
-               char *net_buf = (char*)&net_num;
-
-               /* socks version */
-               pt->serv_buf[len++] = SOCKS_VERSION_5;
-               /* socks command */
-               pt->serv_buf[len++] = SOCKS_COMMAND_CONNECT;
-               /* reserved */
-               pt->serv_buf[len++] = 0;
-               /* address type */
-               pt->serv_buf[len++] = SOCKS_ATYP_DOMAINNAME;
-               len_index = len;
-               len++;
-               /* the address we tell SOCKS proxy to connect to */
-               strncpy((char *)&(pt->serv_buf[len]), wsi->u.hdr.stash->address,
-                       context->pt_serv_buf_size - len);
-               len += strlen(wsi->u.hdr.stash->address);
-               net_num = htons((short)wsi->c_port);
-               /* the port we tell SOCKS proxy to connect to */
-               pt->serv_buf[len++] = net_buf[0];
-               pt->serv_buf[len++] = net_buf[1];
-               /* the length of the address, excluding port */
-               pt->serv_buf[len_index] = strlen(wsi->u.hdr.stash->address);
-       }
-
-       *msg_len = len;
-}
-#endif
diff --git a/lib/client-parser.c b/lib/client-parser.c
deleted file mode 100644 (file)
index f99592e..0000000
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-/*
- * parsers.c: lws_rx_sm() needs to be roughly kept in
- *   sync with changes here, esp related to ext draining
- */
-
-int lws_client_rx_sm(struct lws *wsi, unsigned char c)
-{
-       int callback_action = LWS_CALLBACK_CLIENT_RECEIVE;
-       int handled, n, m, rx_draining_ext = 0;
-       unsigned short close_code;
-       struct lws_tokens eff_buf;
-       unsigned char *pp;
-
-       if (wsi->u.ws.rx_draining_ext) {
-               assert(!c);
-               eff_buf.token = NULL;
-               eff_buf.token_len = 0;
-               lws_remove_wsi_from_draining_ext_list(wsi);
-               rx_draining_ext = 1;
-               lwsl_debug("%s: doing draining flow\n", __func__);
-
-               goto drain_extension;
-       }
-
-       if (wsi->socket_is_permanently_unusable)
-               return -1;
-
-       switch (wsi->lws_rx_parse_state) {
-       case LWS_RXPS_NEW:
-               /* control frames (PING) may interrupt checkable sequences */
-               wsi->u.ws.defeat_check_utf8 = 0;
-
-               switch (wsi->ietf_spec_revision) {
-               case 13:
-                       wsi->u.ws.opcode = c & 0xf;
-                       /* revisit if an extension wants them... */
-                       switch (wsi->u.ws.opcode) {
-                       case LWSWSOPC_TEXT_FRAME:
-                               wsi->u.ws.rsv_first_msg = (c & 0x70);
-                               wsi->u.ws.continuation_possible = 1;
-                               wsi->u.ws.check_utf8 = lws_check_opt(
-                                       wsi->context->options,
-                                       LWS_SERVER_OPTION_VALIDATE_UTF8);
-                               wsi->u.ws.utf8 = 0;
-                               break;
-                       case LWSWSOPC_BINARY_FRAME:
-                               wsi->u.ws.rsv_first_msg = (c & 0x70);
-                               wsi->u.ws.check_utf8 = 0;
-                               wsi->u.ws.continuation_possible = 1;
-                               break;
-                       case LWSWSOPC_CONTINUATION:
-                               if (!wsi->u.ws.continuation_possible) {
-                                       lwsl_info("disordered continuation\n");
-                                       return -1;
-                               }
-                               break;
-                       case LWSWSOPC_CLOSE:
-                               wsi->u.ws.check_utf8 = 0;
-                               wsi->u.ws.utf8 = 0;
-                               break;
-                       case 3:
-                       case 4:
-                       case 5:
-                       case 6:
-                       case 7:
-                       case 0xb:
-                       case 0xc:
-                       case 0xd:
-                       case 0xe:
-                       case 0xf:
-                               lwsl_info("illegal opcode\n");
-                               return -1;
-                       default:
-                               wsi->u.ws.defeat_check_utf8 = 1;
-                               break;
-                       }
-                       wsi->u.ws.rsv = (c & 0x70);
-                       /* revisit if an extension wants them... */
-                       if (
-#ifndef LWS_NO_EXTENSIONS
-                               !wsi->count_act_ext &&
-#endif
-                               wsi->u.ws.rsv) {
-                               lwsl_info("illegal rsv bits set\n");
-                               return -1;
-                       }
-                       wsi->u.ws.final = !!((c >> 7) & 1);
-                       lwsl_ext("%s:    This RX frame Final %d\n", __func__, wsi->u.ws.final);
-
-                       if (wsi->u.ws.owed_a_fin &&
-                           (wsi->u.ws.opcode == LWSWSOPC_TEXT_FRAME ||
-                            wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME)) {
-                               lwsl_info("hey you owed us a FIN\n");
-                               return -1;
-                       }
-                       if ((!(wsi->u.ws.opcode & 8)) && wsi->u.ws.final) {
-                               wsi->u.ws.continuation_possible = 0;
-                               wsi->u.ws.owed_a_fin = 0;
-                       }
-
-                       if ((wsi->u.ws.opcode & 8) && !wsi->u.ws.final) {
-                               lwsl_info("control message cannot be fragmented\n");
-                               return -1;
-                       }
-                       if (!wsi->u.ws.final)
-                               wsi->u.ws.owed_a_fin = 1;
-
-                       switch (wsi->u.ws.opcode) {
-                       case LWSWSOPC_TEXT_FRAME:
-                       case LWSWSOPC_BINARY_FRAME:
-                               wsi->u.ws.frame_is_binary = wsi->u.ws.opcode ==
-                                                LWSWSOPC_BINARY_FRAME;
-                               break;
-                       }
-                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
-                       break;
-
-               default:
-                       lwsl_err("unknown spec version %02d\n",
-                                                      wsi->ietf_spec_revision);
-                       break;
-               }
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN:
-
-               wsi->u.ws.this_frame_masked = !!(c & 0x80);
-
-               switch (c & 0x7f) {
-               case 126:
-                       /* control frames are not allowed to have big lengths */
-                       if (wsi->u.ws.opcode & 8)
-                               goto illegal_ctl_length;
-                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
-                       break;
-               case 127:
-                       /* control frames are not allowed to have big lengths */
-                       if (wsi->u.ws.opcode & 8)
-                               goto illegal_ctl_length;
-                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
-                       break;
-               default:
-                       wsi->u.ws.rx_packet_length = c;
-                       if (wsi->u.ws.this_frame_masked)
-                               wsi->lws_rx_parse_state =
-                                               LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-                       else {
-                               if (c)
-                                       wsi->lws_rx_parse_state =
-                                       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-                               else {
-                                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                                       goto spill;
-                               }
-                       }
-                       break;
-               }
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN16_2:
-               wsi->u.ws.rx_packet_length = c << 8;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN16_1:
-               wsi->u.ws.rx_packet_length |= c;
-               if (wsi->u.ws.this_frame_masked)
-                       wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-               else {
-                       if (wsi->u.ws.rx_packet_length)
-                               wsi->lws_rx_parse_state =
-                                       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-                       else {
-                               wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                               goto spill;
-                       }
-               }
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_8:
-               if (c & 0x80) {
-                       lwsl_warn("b63 of length must be zero\n");
-                       /* kill the connection */
-                       return -1;
-               }
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length = ((size_t)c) << 56;
-#else
-               wsi->u.ws.rx_packet_length = 0;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_7:
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 48;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_6:
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 40;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_5:
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 32;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_4:
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 24;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_3:
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 16;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_2:
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 8;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_1:
-               wsi->u.ws.rx_packet_length |= (size_t)c;
-               if (wsi->u.ws.this_frame_masked)
-                       wsi->lws_rx_parse_state =
-                                       LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-               else {
-                       if (wsi->u.ws.rx_packet_length)
-                               wsi->lws_rx_parse_state =
-                                       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-                       else {
-                               wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                               goto spill;
-                       }
-               }
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
-               wsi->u.ws.mask[0] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
-               wsi->u.ws.mask[1] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
-               wsi->u.ws.mask[2] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
-               wsi->u.ws.mask[3] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-
-               if (wsi->u.ws.rx_packet_length)
-                       wsi->lws_rx_parse_state =
-                                       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-               else {
-                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                       goto spill;
-               }
-               break;
-
-       case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
-
-               assert(wsi->u.ws.rx_ubuf);
-
-               if (wsi->u.ws.rx_draining_ext)
-                       goto drain_extension;
-
-               if (wsi->u.ws.this_frame_masked && !wsi->u.ws.all_zero_nonce)
-                       c ^= wsi->u.ws.mask[(wsi->u.ws.mask_idx++) & 3];
-
-               wsi->u.ws.rx_ubuf[LWS_PRE + (wsi->u.ws.rx_ubuf_head++)] = c;
-
-               if (--wsi->u.ws.rx_packet_length == 0) {
-                       /* spill because we have the whole frame */
-                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                       goto spill;
-               }
-
-               /*
-                * if there's no protocol max frame size given, we are
-                * supposed to default to context->pt_serv_buf_size
-                */
-               if (!wsi->protocol->rx_buffer_size &&
-                   wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size)
-                       break;
-
-               if (wsi->protocol->rx_buffer_size &&
-                   wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size)
-                       break;
-
-               /* spill because we filled our rx buffer */
-spill:
-
-               handled = 0;
-
-               /*
-                * is this frame a control packet we should take care of at this
-                * layer?  If so service it and hide it from the user callback
-                */
-
-               switch (wsi->u.ws.opcode) {
-               case LWSWSOPC_CLOSE:
-                       pp = (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE];
-                       if (lws_check_opt(wsi->context->options,
-                                         LWS_SERVER_OPTION_VALIDATE_UTF8) &&
-                           wsi->u.ws.rx_ubuf_head > 2 &&
-                           lws_check_utf8(&wsi->u.ws.utf8, pp + 2,
-                                          wsi->u.ws.rx_ubuf_head - 2))
-                               goto utf8_fail;
-
-                       /* is this an acknowledgement of our close? */
-                       if (wsi->state == LWSS_AWAITING_CLOSE_ACK) {
-                               /*
-                                * fine he has told us he is closing too, let's
-                                * finish our close
-                                */
-                               lwsl_parser("seen server's close ack\n");
-                               return -1;
-                       }
-
-                       lwsl_parser("client sees server close len = %d\n",
-                                                wsi->u.ws.rx_ubuf_head);
-                       if (wsi->u.ws.rx_ubuf_head >= 2) {
-                               close_code = (pp[0] << 8) | pp[1];
-                               if (close_code < 1000 ||
-                                   close_code == 1004 ||
-                                   close_code == 1005 ||
-                                   close_code == 1006 ||
-                                   close_code == 1012 ||
-                                   close_code == 1013 ||
-                                   close_code == 1014 ||
-                                   close_code == 1015 ||
-                                   (close_code >= 1016 && close_code < 3000)
-                               ) {
-                                       pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
-                                       pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
-                               }
-                       }
-                       if (user_callback_handle_rxflow(
-                                       wsi->protocol->callback, wsi,
-                                       LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
-                                       wsi->user_space, pp,
-                                       wsi->u.ws.rx_ubuf_head))
-                               return -1;
-
-                       if (lws_partial_buffered(wsi))
-                               /*
-                                * if we're in the middle of something,
-                                * we can't do a normal close response and
-                                * have to just close our end.
-                                */
-                               wsi->socket_is_permanently_unusable = 1;
-                       else
-                               /*
-                                * parrot the close packet payload back
-                                * we do not care about how it went, we are closing
-                                * immediately afterwards
-                                */
-                               lws_write(wsi, (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE],
-                                         wsi->u.ws.rx_ubuf_head,
-                                         LWS_WRITE_CLOSE);
-                       wsi->state = LWSS_RETURNED_CLOSE_ALREADY;
-                       /* close the connection */
-                       return -1;
-
-               case LWSWSOPC_PING:
-                       lwsl_info("received %d byte ping, sending pong\n",
-                                 wsi->u.ws.rx_ubuf_head);
-
-                       /* he set a close reason on this guy, ignore PING */
-                       if (wsi->u.ws.close_in_ping_buffer_len)
-                               goto ping_drop;
-
-                       if (wsi->u.ws.ping_pending_flag) {
-                               /*
-                                * there is already a pending ping payload
-                                * we should just log and drop
-                                */
-                               lwsl_parser("DROP PING since one pending\n");
-                               goto ping_drop;
-                       }
-
-                       /* control packets can only be < 128 bytes long */
-                       if (wsi->u.ws.rx_ubuf_head > 128 - 3) {
-                               lwsl_parser("DROP PING payload too large\n");
-                               goto ping_drop;
-                       }
-
-                       /* stash the pong payload */
-                       memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE,
-                              &wsi->u.ws.rx_ubuf[LWS_PRE],
-                               wsi->u.ws.rx_ubuf_head);
-
-                       wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head;
-                       wsi->u.ws.ping_pending_flag = 1;
-
-                       /* get it sent as soon as possible */
-                       lws_callback_on_writable(wsi);
-ping_drop:
-                       wsi->u.ws.rx_ubuf_head = 0;
-                       handled = 1;
-                       break;
-
-               case LWSWSOPC_PONG:
-                       lwsl_info("client receied pong\n");
-                       lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE],
-                                    wsi->u.ws.rx_ubuf_head);
-
-                       if (wsi->pending_timeout == PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) {
-                               lwsl_info("received expected PONG on wsi %p\n", wsi);
-                               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-                       }
-
-                       /* issue it */
-                       callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG;
-                       break;
-
-               case LWSWSOPC_CONTINUATION:
-               case LWSWSOPC_TEXT_FRAME:
-               case LWSWSOPC_BINARY_FRAME:
-                       break;
-
-               default:
-
-                       lwsl_parser("Reserved opc 0x%2X\n", wsi->u.ws.opcode);
-
-                       /*
-                        * It's something special we can't understand here.
-                        * Pass the payload up to the extension's parsing
-                        * state machine.
-                        */
-
-                       eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE];
-                       eff_buf.token_len = wsi->u.ws.rx_ubuf_head;
-
-                       if (lws_ext_cb_active(wsi,
-                               LWS_EXT_CB_EXTENDED_PAYLOAD_RX,
-                                       &eff_buf, 0) <= 0) { /* not handle or fail */
-
-                               lwsl_ext("Unhandled ext opc 0x%x\n", wsi->u.ws.opcode);
-                               wsi->u.ws.rx_ubuf_head = 0;
-
-                               return 0;
-                       }
-                       handled = 1;
-                       break;
-               }
-
-               /*
-                * No it's real payload, pass it up to the user callback.
-                * It's nicely buffered with the pre-padding taken care of
-                * so it can be sent straight out again using lws_write
-                */
-               if (handled)
-                       goto already_done;
-
-               eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE];
-               eff_buf.token_len = wsi->u.ws.rx_ubuf_head;
-
-drain_extension:
-               lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len);
-
-               n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0);
-               lwsl_ext("Ext RX returned %d\n", n);
-               if (n < 0) {
-                       wsi->socket_is_permanently_unusable = 1;
-                       return -1;
-               }
-
-               lwsl_ext("post inflate eff_buf len %d\n", eff_buf.token_len);
-
-               if (rx_draining_ext && !eff_buf.token_len) {
-                       lwsl_err("   --- ignoring zero drain result, ending drain\n");
-                       goto already_done;
-               }
-
-               if (wsi->u.ws.check_utf8 && !wsi->u.ws.defeat_check_utf8) {
-                       if (lws_check_utf8(&wsi->u.ws.utf8,
-                                          (unsigned char *)eff_buf.token,
-                                          eff_buf.token_len))
-                               goto utf8_fail;
-
-                       /* we are ending partway through utf-8 character? */
-                       if (!wsi->u.ws.rx_packet_length && wsi->u.ws.final &&
-                           wsi->u.ws.utf8 && !n) {
-                               lwsl_info("FINAL utf8 error\n");
-utf8_fail:                     lwsl_info("utf8 error\n");
-                               return -1;
-                       }
-               }
-
-               if (eff_buf.token_len < 0 &&
-                   callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG)
-                       goto already_done;
-
-               if (!eff_buf.token)
-                       goto already_done;
-
-               eff_buf.token[eff_buf.token_len] = '\0';
-
-               if (!wsi->protocol->callback)
-                       goto already_done;
-
-               if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG)
-                       lwsl_info("Client doing pong callback\n");
-
-               if (n && eff_buf.token_len)
-                       /* extension had more... main loop will come back
-                        * we want callback to be done with this set, if so,
-                        * because lws_is_final() hides it was final until the
-                        * last chunk
-                        */
-                       lws_add_wsi_to_draining_ext_list(wsi);
-               else
-                       lws_remove_wsi_from_draining_ext_list(wsi);
-
-               if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
-                   wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION ||
-                   wsi->state == LWSS_AWAITING_CLOSE_ACK)
-                       goto already_done;
-
-               m = wsi->protocol->callback(wsi,
-                       (enum lws_callback_reasons)callback_action,
-                       wsi->user_space, eff_buf.token, eff_buf.token_len);
-
-               /* if user code wants to close, let caller know */
-               if (m)
-                       return 1;
-
-already_done:
-               wsi->u.ws.rx_ubuf_head = 0;
-               break;
-       default:
-               lwsl_err("client rx illegal state\n");
-               return 1;
-       }
-
-       return 0;
-
-illegal_ctl_length:
-       lwsl_warn("Control frame asking for extended length is illegal\n");
-       /* kill the connection */
-       return -1;
-}
-
-
diff --git a/lib/client.c b/lib/client.c
deleted file mode 100755 (executable)
index 3c8986c..0000000
+++ /dev/null
@@ -1,1402 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-int
-lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
-{
-       int m;
-
-       switch (wsi->mode) {
-       case LWSCM_WSCL_WAITING_PROXY_REPLY:
-       case LWSCM_WSCL_ISSUE_HANDSHAKE:
-       case LWSCM_WSCL_WAITING_SERVER_REPLY:
-       case LWSCM_WSCL_WAITING_EXTENSION_CONNECT:
-       case LWSCM_WS_CLIENT:
-               while (len) {
-                       /*
-                        * we were accepting input but now we stopped doing so
-                        */
-                       if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) {
-                               lwsl_debug("%s: caching %ld\n", __func__, (long)len);
-                               lws_rxflow_cache(wsi, *buf, 0, len);
-                               return 0;
-                       }
-                       if (wsi->u.ws.rx_draining_ext) {
-#if !defined(LWS_NO_CLIENT)
-                               if (wsi->mode == LWSCM_WS_CLIENT)
-                                       m = lws_client_rx_sm(wsi, 0);
-                               else
-#endif
-                                       m = lws_rx_sm(wsi, 0);
-                               if (m < 0)
-                                       return -1;
-                               continue;
-                       }
-                       /* account for what we're using in rxflow buffer */
-                       if (wsi->rxflow_buffer)
-                               wsi->rxflow_pos++;
-
-                       if (lws_client_rx_sm(wsi, *(*buf)++)) {
-                               lwsl_debug("client_rx_sm exited\n");
-                               return -1;
-                       }
-                       len--;
-               }
-               lwsl_debug("%s: finished with %ld\n", __func__, (long)len);
-               return 0;
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_client_http_body_pending(struct lws *wsi, int something_left_to_send)
-{
-       wsi->client_http_body_pending = !!something_left_to_send;
-}
-
-int
-lws_client_socket_service(struct lws_context *context, struct lws *wsi,
-                         struct lws_pollfd *pollfd)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       char *p = (char *)&pt->serv_buf[0];
-       const char *cce = NULL;
-       unsigned char c;
-       char *sb = p;
-       int n = 0, len = 0;
-#if defined(LWS_WITH_SOCKS5)
-       char conn_mode = 0, pending_timeout = 0;
-#endif
-
-       switch (wsi->mode) {
-
-       case LWSCM_WSCL_WAITING_CONNECT:
-
-               /*
-                * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
-                * timeout protection set in client-handshake.c
-                */
-
-               if (!lws_client_connect_2(wsi)) {
-                       /* closed */
-                       lwsl_client("closed\n");
-                       return -1;
-               }
-
-               /* either still pending connection, or changed mode */
-               return 0;
-
-#if defined(LWS_WITH_SOCKS5)
-       /* SOCKS Greeting Reply */
-       case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY:
-
-               /* handle proxy hung up on us */
-
-               if (pollfd->revents & LWS_POLLHUP) {
-
-                       lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
-                                 (void *)wsi, pollfd->fd);
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return 0;
-               }
-
-               n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
-               if (n < 0) {
-                       if (LWS_ERRNO == LWS_EAGAIN) {
-                               lwsl_debug("SOCKS read returned EAGAIN..."
-                                       "retrying\n");
-                               return 0;
-                       }
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR reading from SOCKS socket\n");
-                       return 0;
-               }
-
-               /* processing greeting reply */
-               if (pt->serv_buf[0] == SOCKS_VERSION_5
-                       && pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH)
-               {
-                       lwsl_client("%s\n", "SOCKS greeting reply received "
-                               "- No Authentication Method");
-                       socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len);
-
-                       conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY;
-                       pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
-                       lwsl_client("%s\n", "Sending SOCKS connect command");
-               }
-               else if (pt->serv_buf[0] == SOCKS_VERSION_5
-                               && pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD)
-               {
-                       lwsl_client("%s\n", "SOCKS greeting reply received "
-                               "- User Name Password Method");
-                       socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD,
-                               (size_t *)&len);
-
-                       conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY;
-                       pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY;
-                       lwsl_client("%s\n", "Sending SOCKS user/password");
-               }
-               else
-               {
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR SOCKS greeting reply failed, method "
-                               "code: %d\n", pt->serv_buf[1]);
-                       return 0;
-               }
-
-               n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
-                        MSG_NOSIGNAL);
-               if (n < 0) {
-                       lwsl_debug("ERROR writing socks command to socks proxy "
-                               "socket\n");
-                       return 0;
-               }
-
-               lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
-               wsi->mode = conn_mode;
-
-               break;
-       /* SOCKS auth Reply */
-       case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY:
-
-               /* handle proxy hung up on us */
-
-               if (pollfd->revents & LWS_POLLHUP) {
-
-                       lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
-                                 (void *)wsi, pollfd->fd);
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return 0;
-               }
-
-               n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
-               if (n < 0) {
-                       if (LWS_ERRNO == LWS_EAGAIN) {
-                               lwsl_debug("SOCKS read returned EAGAIN... "
-                                       "retrying\n");
-                               return 0;
-                       }
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR reading from socks socket\n");
-                       return 0;
-               }
-
-               /* processing auth reply */
-               if (pt->serv_buf[0] == SOCKS_SUBNEGOTIATION_VERSION_1
-                       && pt->serv_buf[1] == SOCKS_SUBNEGOTIATION_STATUS_SUCCESS)
-               {
-                       lwsl_client("%s\n", "SOCKS password reply recieved - "
-                               "successful");
-                       socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len);
-
-                       conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY;
-                       pending_timeout =
-                               PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
-                       lwsl_client("%s\n", "Sending SOCKS connect command");
-               }
-               else
-               {
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR : SOCKS user/password reply failed, "
-                               "error code: %d\n", pt->serv_buf[1]);
-                       return 0;
-               }
-
-               n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
-                        MSG_NOSIGNAL);
-               if (n < 0) {
-                       lwsl_debug("ERROR writing connect command to SOCKS "
-                               "socket\n");
-                       return 0;
-               }
-
-               lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
-               wsi->mode = conn_mode;
-
-               break;
-
-       /* SOCKS connect command Reply */
-       case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY:
-
-               /* handle proxy hung up on us */
-
-               if (pollfd->revents & LWS_POLLHUP) {
-
-                       lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
-                                 (void *)wsi, pollfd->fd);
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return 0;
-               }
-
-               n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
-               if (n < 0) {
-                       if (LWS_ERRNO == LWS_EAGAIN) {
-                               lwsl_debug("SOCKS read returned EAGAIN... "
-                                       "retrying\n");
-                               return 0;
-                       }
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR reading from socks socket\n");
-                       return 0;
-               }
-
-               /* processing connect reply */
-               if (pt->serv_buf[0] == SOCKS_VERSION_5
-                       && pt->serv_buf[1] == SOCKS_REQUEST_REPLY_SUCCESS)
-               {
-                       lwsl_client("%s\n", "SOCKS connect reply recieved - "
-                               "successful");
-               }
-               else
-               {
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR SOCKS connect reply failed, error "
-                               "code: %d\n", pt->serv_buf[1]);
-                       return 0;
-               }
-
-               /* free stash since we are done with it */
-               lws_free_set_NULL(wsi->u.hdr.stash);
-
-               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
-                       wsi->vhost->socks_proxy_address))
-                       goto bail3;
-               wsi->c_port = wsi->vhost->socks_proxy_port;
-
-               /* clear his proxy connection timeout */
-
-               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-
-               goto start_ws_hanshake;
-#endif
-       case LWSCM_WSCL_WAITING_PROXY_REPLY:
-
-               /* handle proxy hung up on us */
-
-               if (pollfd->revents & LWS_POLLHUP) {
-
-                       lwsl_warn("Proxy connection %p (fd=%d) dead\n",
-                                 (void *)wsi, pollfd->fd);
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return 0;
-               }
-
-               n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
-               if (n < 0) {
-                       if (LWS_ERRNO == LWS_EAGAIN) {
-                               lwsl_debug("Proxy read returned EAGAIN... retrying\n");
-                               return 0;
-                       }
-
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR reading from proxy socket\n");
-                       return 0;
-               }
-
-               pt->serv_buf[13] = '\0';
-               if (strcmp(sb, "HTTP/1.0 200 ") &&
-                   strcmp(sb, "HTTP/1.1 200 ")) {
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       lwsl_err("ERROR proxy: %s\n", sb);
-                       return 0;
-               }
-
-               /* clear his proxy connection timeout */
-
-               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-
-               /* fallthru */
-
-       case LWSCM_WSCL_ISSUE_HANDSHAKE:
-
-               /*
-                * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
-                * timeout protection set in client-handshake.c
-                *
-                * take care of our lws_callback_on_writable
-                * happening at a time when there's no real connection yet
-                */
-#if defined(LWS_WITH_SOCKS5)
-start_ws_hanshake:
-#endif
-               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0))
-                       return -1;
-
-#ifdef LWS_OPENSSL_SUPPORT
-               /* we can retry this... just cook the SSL BIO the first time */
-
-               if (wsi->use_ssl && !wsi->ssl) {
-                       if (lws_ssl_client_bio_create(wsi))
-                               return -1;
-               }
-
-               if (wsi->use_ssl) {
-                       n = lws_ssl_client_connect1(wsi);
-                       if (!n)
-                               return 0;
-                       if (n < 0) {
-                               cce = "lws_ssl_client_connect1 failed";
-                               goto bail3;
-                       }
-               } else
-                       wsi->ssl = NULL;
-
-               /* fallthru */
-
-       case LWSCM_WSCL_WAITING_SSL:
-
-               if (wsi->use_ssl) {
-                       n = lws_ssl_client_connect2(wsi);
-                       if (!n)
-                               return 0;
-                       if (n < 0) {
-                               cce = "lws_ssl_client_connect2 failed";
-                               goto bail3;
-                       }
-               } else
-                       wsi->ssl = NULL;
-#endif
-
-               wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2;
-               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
-                               context->timeout_secs);
-
-               /* fallthru */
-
-       case LWSCM_WSCL_ISSUE_HANDSHAKE2:
-               p = lws_generate_client_handshake(wsi, p);
-               if (p == NULL) {
-                       if (wsi->mode == LWSCM_RAW)
-                               return 0;
-
-                       lwsl_err("Failed to generate handshake for client\n");
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return 0;
-               }
-
-               /* send our request to the server */
-
-               lws_latency_pre(context, wsi);
-
-               n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb);
-               lws_latency(context, wsi, "send lws_issue_raw", n,
-                           n == p - sb);
-               switch (n) {
-               case LWS_SSL_CAPABLE_ERROR:
-                       lwsl_debug("ERROR writing to client socket\n");
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return 0;
-               case LWS_SSL_CAPABLE_MORE_SERVICE:
-                       lws_callback_on_writable(wsi);
-                       break;
-               }
-
-               if (wsi->client_http_body_pending) {
-                       wsi->mode = LWSCM_WSCL_ISSUE_HTTP_BODY;
-                       lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
-                                       context->timeout_secs);
-                       /* user code must ask for writable callback */
-                       break;
-               }
-
-               goto client_http_body_sent;
-
-       case LWSCM_WSCL_ISSUE_HTTP_BODY:
-               if (wsi->client_http_body_pending) {
-                       lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
-                                       context->timeout_secs);
-                       /* user code must ask for writable callback */
-                       break;
-               }
-client_http_body_sent:
-               wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
-               wsi->u.hdr.lextable_pos = 0;
-               wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY;
-               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
-                               context->timeout_secs);
-               break;
-
-       case LWSCM_WSCL_WAITING_SERVER_REPLY:
-
-               /* handle server hung up on us */
-
-               if (pollfd->revents & LWS_POLLHUP) {
-
-                       lwsl_debug("Server connection %p (fd=%d) dead\n",
-                               (void *)wsi, pollfd->fd);
-                       cce = "Peer hung up";
-                       goto bail3;
-               }
-
-               if (!(pollfd->revents & LWS_POLLIN))
-                       break;
-
-               /* 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)
-                * coalesce both handshake response and websocket traffic
-                * in one packet, since at that point the connection is
-                * definitively ready from browser pov.
-                */
-               len = 1;
-               while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE &&
-                      len > 0) {
-                       n = lws_ssl_capable_read(wsi, &c, 1);
-                       lws_latency(context, wsi, "send lws_issue_raw", n,
-                                   n == 1);
-                       switch (n) {
-                       case 0:
-                       case LWS_SSL_CAPABLE_ERROR:
-                               cce = "read failed";
-                               goto bail3;
-                       case LWS_SSL_CAPABLE_MORE_SERVICE:
-                               return 0;
-                       }
-
-                       if (lws_parse(wsi, c)) {
-                               lwsl_warn("problems parsing header\n");
-                               goto bail3;
-                       }
-               }
-
-               /*
-                * hs may also be coming in multiple packets, there is a 5-sec
-                * 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;
-
-               /*
-                * otherwise deal with the handshake.  If there's any
-                * 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:
-               lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n");
-               if (cce)
-                       lwsl_info("reason: %s\n", cce);
-               wsi->protocol->callback(wsi,
-                       LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
-                       wsi->user_space, (void *)cce, cce ? strlen(cce) : 0);
-               wsi->already_did_cce = 1;
-               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-               return -1;
-
-       case LWSCM_WSCL_WAITING_EXTENSION_CONNECT:
-               lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n");
-               break;
-
-       case LWSCM_WSCL_PENDING_CANDIDATE_CHILD:
-               lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n");
-               break;
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-/*
- * In-place str to lower case
- */
-
-static void
-strtolower(char *s)
-{
-       while (*s) {
-#ifdef LWS_PLAT_OPTEE
-               int tolower_optee(int c);
-               *s = tolower_optee((int)*s);
-#else
-               *s = tolower((int)*s);
-#endif
-               s++;
-       }
-}
-
-int LWS_WARN_UNUSED_RESULT
-lws_http_transaction_completed_client(struct lws *wsi)
-{
-       lwsl_debug("%s: wsi %p\n", __func__, wsi);
-       /* if we can't go back to accept new headers, drop the connection */
-       if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) {
-               lwsl_info("%s: %p: close connection\n", __func__, wsi);
-               return 1;
-       }
-
-       /* we don't support chained client connections yet */
-       return 1;
-#if 0
-       /* 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->hdr_parsing_completed = 0;
-
-       /* He asked for it to stay alive indefinitely */
-       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-
-       /*
-        * As client, nothing new is going to come until we ask for it
-        * we can drop the ah, if any
-        */
-       if (wsi->u.hdr.ah) {
-               lws_header_table_force_to_detachable_state(wsi);
-               lws_header_table_detach(wsi, 0);
-       }
-
-       /* If we're (re)starting on headers, need other implied init */
-       wsi->u.hdr.ues = URIES_IDLE;
-
-       lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi);
-
-       return 0;
-#endif
-}
-
-LWS_VISIBLE LWS_EXTERN unsigned int
-lws_http_client_http_response(struct lws *wsi)
-{
-       if (!wsi->u.http.ah)
-               return 0;
-
-       return wsi->u.http.ah->http_response;
-}
-
-int
-lws_client_interpret_server_handshake(struct lws *wsi)
-{
-       int n, len, okay = 0, port = 0, ssl = 0;
-       int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR;
-       struct lws_context *context = wsi->context;
-       const char *pc, *prot, *ads = NULL, *path, *cce = NULL;
-       struct allocated_headers *ah = NULL;
-       char *p, *q;
-       char new_path[300];
-#ifndef LWS_NO_EXTENSIONS
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       char *sb = (char *)&pt->serv_buf[0];
-       const struct lws_ext_options *opts;
-       const struct lws_extension *ext;
-       char ext_name[128];
-       const char *c, *a;
-       char ignore;
-       int more = 1;
-       void *v;
-#endif
-       if (wsi->u.hdr.stash)
-               lws_free_set_NULL(wsi->u.hdr.stash);
-
-       ah = wsi->u.hdr.ah;
-       if (!wsi->do_ws) {
-               /* we are being an http client...
-                */
-               lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED);
-               wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED;
-               wsi->u.http.ah = ah;
-               ah->http_response = 0;
-       }
-
-       /*
-        * well, what the server sent looked reasonable for syntax.
-        * Now let's confirm it sent all the necessary headers
-        *
-        * http (non-ws) client will expect something like this
-        *
-        * HTTP/1.0.200
-        * server:.libwebsockets
-        * content-type:.text/html
-        * content-length:.17703
-        * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000
-        *
-        *
-        *
-        */
-
-       wsi->u.http.connection_type = HTTP_CONNECTION_KEEP_ALIVE;
-       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
-       if (wsi->do_ws && !p) {
-               lwsl_info("no URI\n");
-               cce = "HS: URI missing";
-               goto bail3;
-       }
-       if (!p) {
-               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0);
-               wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;
-       }
-       if (!p) {
-               cce = "HS: URI missing";
-               lwsl_info("no URI\n");
-               goto bail3;
-       }
-       n = atoi(p);
-       if (ah)
-               ah->http_response = n;
-
-       if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) {
-               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION);
-               if (!p) {
-                       cce = "HS: Redirect code but no Location";
-                       goto bail3;
-               }
-
-               /* Relative reference absolute path */
-               if (p[0] == '/')
-               {
-#ifdef LWS_OPENSSL_SUPPORT
-                       ssl = wsi->use_ssl;
-#endif
-                       ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
-                       port = wsi->c_port;
-                       path = p + 1; /* +1 as lws_client_reset expects leading / to be omitted */
-               }
-               /* Absolute (Full) URI */
-               else if (strchr(p, ':'))
-               {
-                       if (lws_parse_uri(p, &prot, &ads, &port, &path)) {
-                               cce = "HS: URI did not parse";
-                               goto bail3;
-                       }
-
-                       if (!strcmp(prot, "wss") || !strcmp(prot, "https"))
-                               ssl = 1;
-               }
-               /* Relative reference relative path */
-               else
-               {
-                       /* This doesn't try to calculate an absolute path, that will be left to the server */
-#ifdef LWS_OPENSSL_SUPPORT
-                       ssl = wsi->use_ssl;
-#endif
-                       ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
-                       port = wsi->c_port;
-                       path = new_path + 1; /* +1 as lws_client_reset expects leading / to be omitted */
-                       strncpy(new_path, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), sizeof(new_path));
-                       new_path[sizeof(new_path) - 1] = '\0';
-                       q = strrchr(new_path, '/');
-                       if (q)
-                       {
-                               strncpy(q + 1, p, sizeof(new_path) - (q - new_path) - 1);
-                               new_path[sizeof(new_path) - 1] = '\0';
-                       }
-                       else
-                       {
-                               path = p;
-                       }
-               }
-
-#ifdef LWS_OPENSSL_SUPPORT
-               if (wsi->use_ssl && !ssl) {
-                       cce = "HS: Redirect attempted SSL downgrade";
-                       goto bail3;
-               }
-#endif
-
-               if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) {
-                       /* there are two ways to fail out with NULL return...
-                        * simple, early problem where the wsi is intact, or
-                        * we went through with the reconnect attempt and the
-                        * wsi is already closed.  In the latter case, the wsi
-                        * has beet set to NULL additionally.
-                        */
-                       lwsl_err("Redirect failed\n");
-                       cce = "HS: Redirect failed";
-                       if (wsi)
-                               goto bail3;
-
-                       return 1;
-               }
-               return 0;
-       }
-
-       if (!wsi->do_ws) {
-               if (n != 200 && n != 201 && n != 304 && n != 401) {
-                       lwsl_notice("Connection failed with code %d\n", n);
-                       cce = "HS: Server unrecognized response code";
-                       goto bail2;
-               }
-
-#ifdef LWS_WITH_HTTP_PROXY
-               wsi->perform_rewrite = 0;
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
-                       if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE),
-                                    "text/html", 9))
-                               wsi->perform_rewrite = 1;
-               }
-#endif
-
-               /* allocate the per-connection user memory (if any) */
-               if (lws_ensure_user_space(wsi)) {
-                       lwsl_err("Problem allocating wsi user mem\n");
-                       cce = "HS: OOM";
-                       goto bail2;
-               }
-
-               /* he may choose to send us stuff in chunked transfer-coding */
-               wsi->chunked = 0;
-               wsi->chunk_remaining = 0; /* ie, next thing is chunk size */
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) {
-                       wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi,
-                                              WSI_TOKEN_HTTP_TRANSFER_ENCODING),
-                                       "chunked");
-                       /* first thing is hex, after payload there is crlf */
-                       wsi->chunk_parser = ELCP_HEX;
-               }
-
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
-                       wsi->u.http.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;
-               } else /* can't do 1.1 without a content length or chunked */
-                       if (!wsi->chunked)
-                               wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;
-
-               /*
-                * we seem to be good to go, give client last chance to check
-                * headers and OK it
-                */
-               if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
-                                           wsi->user_space, NULL, 0)) {
-
-                       cce = "HS: disallowed by client filter";
-                       goto bail2;
-               }
-
-               /* clear his proxy connection timeout */
-               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-
-               wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
-
-               /* call him back to inform him he is up */
-               if (wsi->protocol->callback(wsi,
-                                           LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP,
-                                           wsi->user_space, NULL, 0)) {
-                       cce = "HS: disallowed at ESTABLISHED";
-                       goto bail3;
-               }
-
-               /* free up his parsing allocations */
-               lws_header_table_detach(wsi, 0);
-
-               lwsl_notice("%s: client connection up\n", __func__);
-
-               return 0;
-       }
-
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
-               lwsl_info("no ACCEPT\n");
-               cce = "HS: ACCEPT missing";
-               goto bail3;
-       }
-
-       if (p && strncmp(p, "101", 3)) {
-               lwsl_warn(
-                      "lws_client_handshake: got bad HTTP response '%s'\n", p);
-               cce = "HS: ws upgrade response not 101";
-               goto bail3;
-       }
-
-       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
-       if (!p) {
-               lwsl_info("no UPGRADE\n");
-               cce = "HS: UPGRADE missing";
-               goto bail3;
-       }
-       strtolower(p);
-       if (strcmp(p, "websocket")) {
-               lwsl_warn(
-                     "lws_client_handshake: got bad Upgrade header '%s'\n", p);
-               cce = "HS: Upgrade to something other than websocket";
-               goto bail3;
-       }
-
-       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION);
-       if (!p) {
-               lwsl_info("no Connection hdr\n");
-               cce = "HS: CONNECTION missing";
-               goto bail3;
-       }
-       strtolower(p);
-       if (strcmp(p, "upgrade")) {
-               lwsl_warn("lws_client_int_s_hs: bad header %s\n", p);
-               cce = "HS: UPGRADE malformed";
-               goto bail3;
-       }
-
-       pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
-       if (!pc) {
-               lwsl_parser("lws_client_int_s_hs: no protocol list\n");
-       } else
-               lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc);
-
-       /*
-        * confirm the protocol the server wants to talk was in the list
-        * of protocols we offered
-        */
-
-       len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
-       if (!len) {
-               lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n");
-               /*
-                * no protocol name to work from,
-                * default to first protocol
-                */
-               n = 0;
-               wsi->protocol = &wsi->vhost->protocols[0];
-               goto check_extensions;
-       }
-
-       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
-       len = strlen(p);
-
-       while (pc && *pc && !okay) {
-               if (!strncmp(pc, p, len) &&
-                   (pc[len] == ',' || pc[len] == '\0')) {
-                       okay = 1;
-                       continue;
-               }
-               while (*pc && *pc++ != ',')
-                       ;
-               while (*pc && *pc == ' ')
-                       pc++;
-       }
-
-       if (!okay) {
-               lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p);
-               cce = "HS: PROTOCOL malformed";
-               goto bail2;
-       }
-
-       /*
-        * identify the selected protocol struct and set it
-        */
-       n = 0;
-       wsi->protocol = NULL;
-       while (wsi->vhost->protocols[n].callback && !wsi->protocol) {
-               if (strcmp(p, wsi->vhost->protocols[n].name) == 0) {
-                       wsi->protocol = &wsi->vhost->protocols[n];
-                       break;
-               }
-               n++;
-       }
-
-       if (wsi->protocol == NULL) {
-               lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p);
-               cce = "HS: Cannot match protocol";
-               goto bail2;
-       }
-
-check_extensions:
-       /*
-        * stitch protocol choice into the vh protocol linked list
-        * We always insert ourselves at the start of the list
-        *
-        * X <-> B
-        * X <-> pAn <-> pB
-        */
-       //lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n",
-       //              __func__,
-       //              wsi->vhost->same_vh_protocol_list[n],
-       //              wsi->same_vh_protocol_prev);
-       wsi->same_vh_protocol_prev = /* guy who points to us */
-               &wsi->vhost->same_vh_protocol_list[n];
-       wsi->same_vh_protocol_next = /* old first guy is our next */
-                       wsi->vhost->same_vh_protocol_list[n];
-       /* we become the new first guy */
-       wsi->vhost->same_vh_protocol_list[n] = wsi;
-
-       if (wsi->same_vh_protocol_next)
-               /* old first guy points back to us now */
-               wsi->same_vh_protocol_next->same_vh_protocol_prev =
-                               &wsi->same_vh_protocol_next;
-
-#ifndef LWS_NO_EXTENSIONS
-       /* instantiate the accepted extensions */
-
-       if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
-               lwsl_ext("no client extensions allowed by server\n");
-               goto check_accept;
-       }
-
-       /*
-        * break down the list of server accepted extensions
-        * and go through matching them or identifying bogons
-        */
-
-       if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) {
-               lwsl_warn("ext list from server failed to copy\n");
-               cce = "HS: EXT: list too big";
-               goto bail2;
-       }
-
-       c = sb;
-       n = 0;
-       ignore = 0;
-       a = NULL;
-       while (more) {
-
-               if (*c && (*c != ',' && *c != '\t')) {
-                       if (*c == ';') {
-                               ignore = 1;
-                               if (!a)
-                                       a = c + 1;
-                       }
-                       if (ignore || *c == ' ') {
-                               c++;
-                               continue;
-                       }
-
-                       ext_name[n] = *c++;
-                       if (n < sizeof(ext_name) - 1)
-                               n++;
-                       continue;
-               }
-               ext_name[n] = '\0';
-               ignore = 0;
-               if (!*c)
-                       more = 0;
-               else {
-                       c++;
-                       if (!n)
-                               continue;
-               }
-
-               /* check we actually support it */
-
-               lwsl_notice("checking client ext %s\n", ext_name);
-
-               n = 0;
-               ext = wsi->vhost->extensions;
-               while (ext && ext->callback) {
-                       if (strcmp(ext_name, ext->name)) {
-                               ext++;
-                               continue;
-                       }
-
-                       n = 1;
-                       lwsl_notice("instantiating client ext %s\n", ext_name);
-
-                       /* instantiate the extension on this conn */
-
-                       wsi->active_extensions[wsi->count_act_ext] = ext;
-
-                       /* allow him to construct his ext instance */
-
-                       if (ext->callback(lws_get_context(wsi), ext, wsi,
-                                     LWS_EXT_CB_CLIENT_CONSTRUCT,
-                                     (void *)&wsi->act_ext_user[wsi->count_act_ext],
-                                     (void *)&opts, 0)) {
-                               lwsl_notice(" ext %s failed construction\n", ext_name);
-                               ext++;
-                               continue;
-                       }
-
-                       /*
-                        * allow the user code to override ext defaults if it
-                        * wants to
-                        */
-                       ext_name[0] = '\0';
-                       if (user_callback_handle_rxflow(wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
-                                       (char *)ext->name, ext_name,
-                                       sizeof(ext_name))) {
-                               cce = "HS: EXT: failed setting defaults";
-                               goto bail2;
-                       }
-
-                       if (ext_name[0] &&
-                           lws_ext_parse_options(ext, wsi, wsi->act_ext_user[
-                                                 wsi->count_act_ext], opts, ext_name,
-                                                 strlen(ext_name))) {
-                               lwsl_err("%s: unable to parse user defaults '%s'",
-                                        __func__, ext_name);
-                               cce = "HS: EXT: failed parsing defaults";
-                               goto bail2;
-                       }
-
-                       /*
-                        * give the extension the server options
-                        */
-                       if (a && lws_ext_parse_options(ext, wsi,
-                                       wsi->act_ext_user[wsi->count_act_ext],
-                                       opts, a, c - a)) {
-                               lwsl_err("%s: unable to parse remote def '%s'",
-                                        __func__, a);
-                               cce = "HS: EXT: failed parsing options";
-                               goto bail2;
-                       }
-
-                       if (ext->callback(lws_get_context(wsi), ext, wsi,
-                                       LWS_EXT_CB_OPTION_CONFIRM,
-                                     wsi->act_ext_user[wsi->count_act_ext],
-                                     NULL, 0)) {
-                               lwsl_err("%s: ext %s rejects server options %s",
-                                        __func__, ext->name, a);
-                               cce = "HS: EXT: Rejects server options";
-                               goto bail2;
-                       }
-
-                       wsi->count_act_ext++;
-
-                       ext++;
-               }
-
-               if (n == 0) {
-                       lwsl_warn("Unknown ext '%s'!\n", ext_name);
-                       cce = "HS: EXT: unknown ext";
-                       goto bail2;
-               }
-
-               a = NULL;
-               n = 0;
-       }
-
-check_accept:
-#endif
-
-       /*
-        * Confirm his accept token is the one we precomputed
-        */
-
-       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
-       if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) {
-               lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p,
-                                 wsi->u.hdr.ah->initial_handshake_hash_base64);
-               cce = "HS: Accept hash wrong";
-               goto bail2;
-       }
-
-       /* allocate the per-connection user memory (if any) */
-       if (lws_ensure_user_space(wsi)) {
-               lwsl_err("Problem allocating wsi user mem\n");
-               cce = "HS: OOM";
-               goto bail2;
-       }
-
-       /*
-        * we seem to be good to go, give client last chance to check
-        * headers and OK it
-        */
-       if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
-                                   wsi->user_space, NULL, 0)) {
-               cce = "HS: Rejected by filter cb";
-               goto bail2;
-       }
-
-       /* clear his proxy connection timeout */
-       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-
-       /* free up his parsing allocations */
-       lws_header_table_detach(wsi, 0);
-
-       lws_union_transition(wsi, LWSCM_WS_CLIENT);
-       wsi->state = LWSS_ESTABLISHED;
-       lws_restart_ws_ping_pong_timer(wsi);
-
-       wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
-
-       /*
-        * create the frame buffer for this connection according to the
-        * size mentioned in the protocol definition.  If 0 there, then
-        * use a big default for compatibility
-        */
-       n = wsi->protocol->rx_buffer_size;
-       if (!n)
-               n = context->pt_serv_buf_size;
-       n += LWS_PRE;
-       wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */);
-       if (!wsi->u.ws.rx_ubuf) {
-               lwsl_err("Out of Mem allocating rx buffer %d\n", n);
-               cce = "HS: OOM";
-               goto bail2;
-       }
-       wsi->u.ws.rx_ubuf_alloc = n;
-       lwsl_info("Allocating client RX buffer %d\n", n);
-
-#if !defined(LWS_WITH_ESP32)
-       if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n,
-                      sizeof n)) {
-               lwsl_warn("Failed to set SNDBUF to %d", n);
-               cce = "HS: SO_SNDBUF failed";
-               goto bail3;
-       }
-#endif
-
-       lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name);
-
-       /* call him back to inform him he is up */
-
-       if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
-                                   wsi->user_space, NULL, 0)) {
-               cce = "HS: Rejected at CLIENT_ESTABLISHED";
-               goto bail3;
-       }
-#ifndef LWS_NO_EXTENSIONS
-       /*
-        * inform all extensions, not just active ones since they
-        * already know
-        */
-       ext = wsi->vhost->extensions;
-
-       while (ext && ext->callback) {
-               v = NULL;
-               for (n = 0; n < wsi->count_act_ext; n++)
-                       if (wsi->active_extensions[n] == ext)
-                               v = wsi->act_ext_user[n];
-
-               ext->callback(context, ext, wsi,
-                         LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0);
-               ext++;
-       }
-#endif
-
-       return 0;
-
-bail3:
-       close_reason = LWS_CLOSE_STATUS_NOSTATUS;
-
-bail2:
-       if (wsi->protocol)
-               wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
-                               wsi->user_space, (void *)cce,
-                               (unsigned int)strlen(cce));
-       wsi->already_did_cce = 1;
-
-       lwsl_info("closing connection due to bail2 connection error\n");
-
-       /* closing will free up his parsing allocations */
-       lws_close_free_wsi(wsi, close_reason);
-
-       return 1;
-}
-
-
-char *
-lws_generate_client_handshake(struct lws *wsi, char *pkt)
-{
-       char buf[128], hash[20], key_b64[40], *p = pkt;
-       struct lws_context *context = wsi->context;
-       const char *meth;
-       int n;
-#ifndef LWS_NO_EXTENSIONS
-       const struct lws_extension *ext;
-       int ext_count = 0;
-#endif
-       const char *pp = lws_hdr_simple_ptr(wsi,
-                               _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
-
-       meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
-       if (!meth) {
-               meth = "GET";
-               wsi->do_ws = 1;
-       } else {
-               wsi->do_ws = 0;
-       }
-
-       if (!strcmp(meth, "RAW")) {
-               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-               lwsl_notice("client transition to raw\n");
-
-               if (pp) {
-                       const struct lws_protocols *pr;
-
-                       pr = lws_vhost_name_to_protocol(wsi->vhost, pp);
-
-                       if (!pr) {
-                               lwsl_err("protocol %s not enabled on vhost\n",
-                                        pp);
-                               return NULL;
-                       }
-
-                       lws_bind_protocol(wsi, pr);
-               }
-
-               if ((wsi->protocol->callback)(wsi,
-                               LWS_CALLBACK_RAW_ADOPT,
-                               wsi->user_space, NULL, 0))
-                       return NULL;
-
-               lws_header_table_force_to_detachable_state(wsi);
-               lws_union_transition(wsi, LWSCM_RAW);
-               lws_header_table_detach(wsi, 1);
-
-               return NULL;
-       }
-
-       if (wsi->do_ws) {
-               /*
-                * create the random key
-                */
-               n = lws_get_random(context, hash, 16);
-               if (n != 16) {
-                       lwsl_err("Unable to read from random dev %s\n",
-                                SYSTEM_RANDOM_FILEPATH);
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       return NULL;
-               }
-
-               lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64));
-       }
-
-       /*
-        * 04 example client handshake
-        *
-        * GET /chat HTTP/1.1
-        * Host: server.example.com
-        * Upgrade: websocket
-        * Connection: Upgrade
-        * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
-        * Sec-WebSocket-Origin: http://example.com
-        * Sec-WebSocket-Protocol: chat, superchat
-        * Sec-WebSocket-Version: 4
-        */
-
-       p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth,
-                    lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI));
-
-       p += sprintf(p, "Pragma: no-cache\x0d\x0a"
-                       "Cache-Control: no-cache\x0d\x0a");
-
-       p += sprintf(p, "Host: %s\x0d\x0a",
-                    lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST));
-
-       if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) {
-               if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN))
-                       p += sprintf(p, "Origin: %s\x0d\x0a",
-                                    lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN));
-               else
-                       p += sprintf(p, "Origin: http://%s\x0d\x0a",
-                                    lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN));
-       }
-
-       if (wsi->do_ws) {
-               p += sprintf(p, "Upgrade: websocket\x0d\x0a"
-                               "Connection: Upgrade\x0d\x0a"
-                               "Sec-WebSocket-Key: ");
-               strcpy(p, key_b64);
-               p += strlen(key_b64);
-               p += sprintf(p, "\x0d\x0a");
-               if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS))
-                       p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a",
-                            lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS));
-
-               /* tell the server what extensions we could support */
-
-#ifndef LWS_NO_EXTENSIONS
-               ext = wsi->vhost->extensions;
-               while (ext && ext->callback) {
-                       n = lws_ext_cb_all_exts(context, wsi,
-                                  LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION,
-                                  (char *)ext->name, 0);
-                       if (n) { /* an extension vetos us */
-                               lwsl_ext("ext %s vetoed\n", (char *)ext->name);
-                               ext++;
-                               continue;
-                       }
-                       n = wsi->vhost->protocols[0].callback(wsi,
-                               LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
-                                       wsi->user_space, (char *)ext->name, 0);
-
-                       /*
-                        * zero return from callback means
-                        * go ahead and allow the extension,
-                        * it's what we get if the callback is
-                        * unhandled
-                        */
-
-                       if (n) {
-                               ext++;
-                               continue;
-                       }
-
-                       /* apply it */
-
-                       if (ext_count)
-                               *p++ = ',';
-                       else
-                               p += sprintf(p, "Sec-WebSocket-Extensions: ");
-                       p += sprintf(p, "%s", ext->client_offer);
-                       ext_count++;
-
-                       ext++;
-               }
-               if (ext_count)
-                       p += sprintf(p, "\x0d\x0a");
-#endif
-
-               if (wsi->ietf_spec_revision)
-                       p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a",
-                                    wsi->ietf_spec_revision);
-
-               /* prepare the expected server accept response */
-
-               key_b64[39] = '\0'; /* enforce composed length below buf sizeof */
-               n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64);
-
-               lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash);
-
-               lws_b64_encode_string(hash, 20,
-                                     wsi->u.hdr.ah->initial_handshake_hash_base64,
-                                     sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64));
-       }
-
-       /* give userland a chance to append, eg, cookies */
-
-       wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
-                               wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12);
-
-       p += sprintf(p, "\x0d\x0a");
-
-       return p;
-}
-
diff --git a/lib/context.c b/lib/context.c
deleted file mode 100644 (file)
index 37219cd..0000000
+++ /dev/null
@@ -1,1458 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-#ifndef LWS_BUILD_HASH
-#define LWS_BUILD_HASH "unknown-build-hash"
-#endif
-
-static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH;
-
-/**
- * lws_get_library_version: get version and git hash library built from
- *
- *     returns a const char * to a string like "1.1 178d78c"
- *     representing the library version followed by the git head hash it
- *     was built from
- */
-LWS_VISIBLE const char *
-lws_get_library_version(void)
-{
-       return library_version;
-}
-
-static const char * const mount_protocols[] = {
-       "http://",
-       "https://",
-       "file://",
-       "cgi://",
-       ">http://",
-       ">https://",
-       "callback://"
-};
-
-LWS_VISIBLE void *
-lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot,
-                           int size)
-{
-       int n = 0;
-
-       /* allocate the vh priv array only on demand */
-       if (!vhost->protocol_vh_privs) {
-               vhost->protocol_vh_privs = (void **)lws_zalloc(
-                               vhost->count_protocols * sizeof(void *));
-               if (!vhost->protocol_vh_privs)
-                       return NULL;
-       }
-
-       while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
-               n++;
-
-       if (n == vhost->count_protocols) {
-               n = 0;
-               while (n < vhost->count_protocols &&
-                      strcmp(vhost->protocols[n].name, prot->name))
-                       n++;
-
-               if (n == vhost->count_protocols)
-                       return NULL;
-       }
-
-       vhost->protocol_vh_privs[n] = lws_zalloc(size);
-       return vhost->protocol_vh_privs[n];
-}
-
-LWS_VISIBLE void *
-lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot)
-{
-       int n = 0;
-
-       if (!vhost->protocol_vh_privs)
-               return NULL;
-
-       while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
-               n++;
-
-       if (n == vhost->count_protocols) {
-               n = 0;
-               while (n < vhost->count_protocols &&
-                      strcmp(vhost->protocols[n].name, prot->name))
-                       n++;
-
-               if (n == vhost->count_protocols) {
-                       lwsl_err("%s: unknown protocol %p\n", __func__, prot);
-                       return NULL;
-               }
-       }
-
-       return vhost->protocol_vh_privs[n];
-}
-
-static const struct lws_protocol_vhost_options *
-lws_vhost_protocol_options(struct lws_vhost *vh, const char *name)
-{
-       const struct lws_protocol_vhost_options *pvo = vh->pvo;
-
-       while (pvo) {
-               // lwsl_notice("%s: '%s' '%s'\n", __func__, pvo->name, name);
-               if (!strcmp(pvo->name, name))
-                       return pvo;
-               pvo = pvo->next;
-       }
-
-       return NULL;
-}
-
-/*
- * inform every vhost that hasn't already done it, that
- * his protocols are initializing
- */
-LWS_VISIBLE int
-lws_protocol_init(struct lws_context *context)
-{
-       struct lws_vhost *vh = context->vhost_list;
-       const struct lws_protocol_vhost_options *pvo, *pvo1;
-       struct lws wsi;
-       int n;
-
-       memset(&wsi, 0, sizeof(wsi));
-       wsi.context = context;
-
-       lwsl_info("%s\n", __func__);
-
-       while (vh) {
-               wsi.vhost = vh;
-
-               /* only do the protocol init once for a given vhost */
-               if (vh->created_vhost_protocols)
-                       goto next;
-
-               /* initialize supported protocols on this vhost */
-
-               for (n = 0; n < vh->count_protocols; n++) {
-                       wsi.protocol = &vh->protocols[n];
-                       if (!vh->protocols[n].name)
-                               continue;
-                       pvo = lws_vhost_protocol_options(vh,
-                                                        vh->protocols[n].name);
-                       if (pvo) {
-                               /*
-                                * linked list of options specific to
-                                * vh + protocol
-                                */
-                               pvo1 = pvo;
-                               pvo = pvo1->options;
-
-                               while (pvo) {
-                                       lwsl_notice("    vh %s prot %s opt %s\n",
-                                                       vh->name,
-                                                       vh->protocols[n].name,
-                                                       pvo->name);
-
-                                       if (!strcmp(pvo->name, "default")) {
-                                               lwsl_notice("Setting default "
-                                                  "protocol for vh %s to %s\n",
-                                                  vh->name,
-                                                  vh->protocols[n].name);
-                                               vh->default_protocol_index = n;
-                                       }
-                                       if (!strcmp(pvo->name, "raw")) {
-                                               lwsl_notice("Setting raw "
-                                                  "protocol for vh %s to %s\n",
-                                                  vh->name,
-                                                  vh->protocols[n].name);
-                                               vh->raw_protocol_index = n;
-                                       }
-                                       pvo = pvo->next;
-                               }
-
-                               pvo = pvo1->options;
-                       }
-
-                       /*
-                        * inform all the protocols that they are doing their one-time
-                        * initialization if they want to.
-                        *
-                        * NOTE the wsi is all zeros except for the context, vh and
-                        * protocol ptrs so lws_get_context(wsi) etc can work
-                        */
-                       if (vh->protocols[n].callback(&wsi,
-                               LWS_CALLBACK_PROTOCOL_INIT, NULL,
-                               (void *)pvo, 0))
-                               return 1;
-               }
-
-               vh->created_vhost_protocols = 1;
-next:
-               vh = vh->vhost_next;
-       }
-
-       if (!context->protocol_init_done)
-               lws_finalize_startup(context);
-
-       context->protocol_init_done = 1;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
-                   void *user, void *in, size_t len)
-{
-#ifdef LWS_WITH_CGI
-       struct lws_cgi_args *args;
-#endif
-#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY)
-       char buf[512];
-       int n;
-#endif
-
-
-       switch (reason) {
-       case LWS_CALLBACK_HTTP:
-#ifndef LWS_NO_SERVER
-               if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
-                       return -1;
-
-               if (lws_http_transaction_completed(wsi))
-#endif
-                       return -1;
-               break;
-#if !defined(LWS_NO_SERVER)
-       case LWS_CALLBACK_HTTP_FILE_COMPLETION:
-               if (lws_http_transaction_completed(wsi))
-                       return -1;
-               break;
-#endif
-
-       case LWS_CALLBACK_HTTP_WRITEABLE:
-#ifdef LWS_WITH_CGI
-               if (wsi->reason_bf & 1) {
-                       if (lws_cgi_write_split_stdout_headers(wsi) < 0)
-                               return -1;
-
-                       if (wsi->reason_bf & 8)
-                               wsi->reason_bf &= ~8;
-                       else
-                               wsi->reason_bf &= ~1;
-                       break;
-               }
-#endif
-#if defined(LWS_WITH_HTTP_PROXY)
-               if (wsi->reason_bf & 2) {
-                       char *px = buf + LWS_PRE;
-                       int lenx = sizeof(buf) - LWS_PRE;
-                       /*
-                        * our sink is writeable and our source has something
-                        * to read.  So read a lump of source material of
-                        * suitable size to send or what's available, whichever
-                        * is the smaller.
-                        */
-
-
-                       wsi->reason_bf &= ~2;
-                       if (!lws_get_child(wsi))
-                               break;
-                       if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0)
-                               return -1;
-                       break;
-               }
-#endif
-               break;
-
-#if defined(LWS_WITH_HTTP_PROXY)
-       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
-               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi);
-               assert(lws_get_parent(wsi));
-               if (!lws_get_parent(wsi))
-                       break;
-               lws_get_parent(wsi)->reason_bf |= 2;
-               lws_callback_on_writable(lws_get_parent(wsi));
-               break;
-
-       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
-               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", (int)len);
-               assert(lws_get_parent(wsi));
-               n = lws_write(lws_get_parent(wsi), (unsigned char *)in,
-                               len, LWS_WRITE_HTTP);
-               if (n < 0)
-                       return -1;
-               break;
-
-       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
-               unsigned char *p, *end;
-               char ctype[64], ctlen = 0;
-
-               //lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n");
-       
-               p = (unsigned char *)buf + LWS_PRE;
-               end = p + sizeof(buf) - LWS_PRE;
-
-               if (lws_add_http_header_status(lws_get_parent(wsi), HTTP_STATUS_OK, &p, end))
-                       return 1;
-               if (lws_add_http_header_by_token(lws_get_parent(wsi),
-                               WSI_TOKEN_HTTP_SERVER,
-                               (unsigned char *)"libwebsockets",
-                               13, &p, end))
-                       return 1;
-
-               ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE);
-               if (ctlen > 0) {
-                       if (lws_add_http_header_by_token(lws_get_parent(wsi),
-                               WSI_TOKEN_HTTP_CONTENT_TYPE,
-                               (unsigned char *)ctype, ctlen, &p, end))
-                               return 1;
-               }
-#if 0
-               if (lws_add_http_header_content_length(lws_get_parent(wsi),
-                                                      file_len, &p, end))
-                       return 1;
-#endif
-               if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
-                       return 1;
-
-               *p = '\0';
-//             lwsl_info("%s\n", buf + LWS_PRE);
-
-               n = lws_write(lws_get_parent(wsi), (unsigned char *)buf + LWS_PRE,
-                             p - ((unsigned char *)buf + LWS_PRE),
-                             LWS_WRITE_HTTP_HEADERS);
-               if (n < 0)
-                       return -1;
-
-               break; }
-
-#endif
-
-#ifdef LWS_WITH_CGI
-       /* CGI IO events (POLLIN/OUT) appear here, our default policy is:
-        *
-        *  - POST data goes on subprocess stdin
-        *  - subprocess stdout goes on http via writeable callback
-        *  - subprocess stderr goes to the logs
-        */
-       case LWS_CALLBACK_CGI:
-               args = (struct lws_cgi_args *)in;
-               switch (args->ch) { /* which of stdin/out/err ? */
-               case LWS_STDIN:
-                       /* TBD stdin rx flow control */
-                       break;
-               case LWS_STDOUT:
-                       wsi->reason_bf |= 1;
-                       /* when writing to MASTER would not block */
-                       lws_callback_on_writable(wsi);
-                       break;
-               case LWS_STDERR:
-                       n = read(lws_get_socket_fd(args->stdwsi[LWS_STDERR]),
-                                                  buf, sizeof(buf) - 2);
-                       if (n > 0) {
-                               if (buf[n - 1] != '\n')
-                                       buf[n++] = '\n';
-                               buf[n] = '\0';
-                               lwsl_notice("CGI-stderr: %s\n", buf);
-                       }
-                       break;
-               }
-               break;
-
-       case LWS_CALLBACK_CGI_TERMINATED:
-               return -1;
-
-       case LWS_CALLBACK_CGI_STDIN_DATA:  /* POST body for stdin */
-               args = (struct lws_cgi_args *)in;
-               args->data[args->len] = '\0';
-               n = write(lws_get_socket_fd(args->stdwsi[LWS_STDIN]),
-                         args->data, args->len);
-               if (n < args->len)
-                       lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
-                                   "sent %d only %d went", n, args->len);
-               return n;
-#endif
-
-       case LWS_CALLBACK_SSL_INFO:
-               {
-                       struct lws_ssl_info *si = in;
-
-                       (void)si;
-                       lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n",
-                                       si->where, si->ret);
-               }
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-/* list of supported protocols and callbacks */
-
-static const struct lws_protocols protocols_dummy[] = {
-       /* first protocol must always be HTTP handler */
-
-       {
-               "http-only",            /* name */
-               lws_callback_http_dummy,                /* callback */
-               0,      /* per_session_data_size */
-               0,                      /* max frame size / rx buffer */
-               0, NULL, 0
-       },
-       /*
-        * the other protocols are provided by lws plugins
-        */
-       { NULL, NULL, 0, 0, 0, NULL, 0} /* terminator */
-};
-
-#ifdef LWS_PLAT_OPTEE
-#undef LWS_HAVE_GETENV
-#endif
-
-LWS_VISIBLE struct lws_vhost *
-lws_create_vhost(struct lws_context *context,
-                struct lws_context_creation_info *info)
-{
-       struct lws_vhost *vh = lws_zalloc(sizeof(*vh)),
-                        **vh1 = &context->vhost_list;
-       const struct lws_http_mount *mounts;
-       const struct lws_protocol_vhost_options *pvo;
-#ifdef LWS_WITH_PLUGINS
-       struct lws_plugin *plugin = context->plugin_list;
-#endif
-       struct lws_protocols *lwsp;
-       int m, f = !info->pvo;
-#ifdef LWS_HAVE_GETENV
-       char *p;
-#endif
-       int n;
-
-       if (!vh)
-               return NULL;
-
-       if (!info->protocols)
-               info->protocols = &protocols_dummy[0];
-
-       vh->context = context;
-       if (!info->vhost_name)
-               vh->name = "default";
-       else
-               vh->name = info->vhost_name;
-
-       vh->iface = info->iface;
-#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
-       vh->bind_iface = info->bind_iface;
-#endif
-
-       for (vh->count_protocols = 0;
-            info->protocols[vh->count_protocols].callback;
-            vh->count_protocols++)
-               ;
-
-       vh->options = info->options;
-       vh->pvo = info->pvo;
-       vh->headers = info->headers;
-       vh->ssl_info_event_mask = info->ssl_info_event_mask;
-       if (info->keepalive_timeout)
-               vh->keepalive_timeout = info->keepalive_timeout;
-       else
-               vh->keepalive_timeout = 5;
-
-       if (info->timeout_secs_ah_idle)
-               vh->timeout_secs_ah_idle = info->timeout_secs_ah_idle;
-       else
-               vh->timeout_secs_ah_idle = 10;
-
-       /*
-        * give the vhost a unified list of protocols including the
-        * ones that came from plugins
-        */
-       lwsp = lws_zalloc(sizeof(struct lws_protocols) *
-                                  (vh->count_protocols +
-                                  context->plugin_protocol_count + 1));
-       if (!lwsp) {
-               lwsl_err("OOM\n");
-               return NULL;
-       }
-
-       m = vh->count_protocols;
-       memcpy(lwsp, info->protocols, sizeof(struct lws_protocols) * m);
-
-       /* for compatibility, all protocols enabled on vhost if only
-        * the default vhost exists.  Otherwise only vhosts who ask
-        * for a protocol get it enabled.
-        */
-
-       if (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)
-               f = 0;
-       (void)f;
-#ifdef LWS_WITH_PLUGINS
-       if (plugin) {
-
-               while (plugin) {
-                       for (n = 0; n < plugin->caps.count_protocols; n++) {
-                               /*
-                                * for compatibility's sake, no pvo implies
-                                * allow all protocols
-                                */
-                               if (f || lws_vhost_protocol_options(vh,
-                                   plugin->caps.protocols[n].name)) {
-                                       memcpy(&lwsp[m],
-                                              &plugin->caps.protocols[n],
-                                              sizeof(struct lws_protocols));
-                                       m++;
-                                       vh->count_protocols++;
-                               }
-                       }
-                       plugin = plugin->list;
-               }
-       }
-#endif
-
-       if (
-#ifdef LWS_WITH_PLUGINS
-           (context->plugin_list) ||
-#endif
-           context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)
-               vh->protocols = lwsp;
-       else {
-               vh->protocols = info->protocols;
-               lws_free(lwsp);
-       }
-
-       vh->same_vh_protocol_list = (struct lws **)
-                       lws_zalloc(sizeof(struct lws *) * vh->count_protocols);
-
-       vh->mount_list = info->mounts;
-
-#ifdef LWS_USE_UNIX_SOCK
-       if (LWS_UNIX_SOCK_ENABLED(context)) {
-               lwsl_notice("Creating Vhost '%s' path \"%s\", %d protocols\n",
-                               vh->name, info->iface, vh->count_protocols);
-       } else
-#endif
-       lwsl_notice("Creating Vhost '%s' port %d, %d protocols, IPv6 %s\n",
-                       vh->name, info->port, vh->count_protocols, LWS_IPV6_ENABLED(vh) ? "on" : "off");
-
-       mounts = info->mounts;
-       while (mounts) {
-               (void)mount_protocols[0];
-               lwsl_notice("   mounting %s%s to %s\n",
-                               mount_protocols[mounts->origin_protocol],
-                               mounts->origin, mounts->mountpoint);
-
-               /* convert interpreter protocol names to pointers */
-               pvo = mounts->interpret;
-               while (pvo) {
-                       for (n = 0; n < vh->count_protocols; n++)
-                               if (!strcmp(pvo->value, vh->protocols[n].name)) {
-                                       ((struct lws_protocol_vhost_options *)pvo)->value =
-                                                       (const char *)(lws_intptr_t)n;
-                                       break;
-                               }
-                       if (n == vh->count_protocols)
-                               lwsl_err("ignoring unknown interpret protocol %s\n", pvo->value);
-                       pvo = pvo->next;
-               }
-
-               mounts = mounts->mount_next;
-       }
-
-#ifndef LWS_NO_EXTENSIONS
-#ifdef LWS_WITH_PLUGINS
-       if (context->plugin_extension_count) {
-
-               m = 0;
-               while (info->extensions && info->extensions[m].callback)
-                       m++;
-
-               /*
-                * give the vhost a unified list of extensions including the
-                * ones that came from plugins
-                */
-               vh->extensions = lws_zalloc(sizeof(struct lws_extension) *
-                                          (m +
-                                          context->plugin_extension_count + 1));
-               if (!vh->extensions)
-                       return NULL;
-
-               memcpy((struct lws_extension *)vh->extensions, info->extensions,
-                      sizeof(struct lws_extension) * m);
-               plugin = context->plugin_list;
-               while (plugin) {
-                       memcpy((struct lws_extension *)&vh->extensions[m],
-                               plugin->caps.extensions,
-                              sizeof(struct lws_extension) *
-                              plugin->caps.count_extensions);
-                       m += plugin->caps.count_extensions;
-                       plugin = plugin->list;
-               }
-       } else
-#endif
-               vh->extensions = info->extensions;
-#endif
-
-       vh->listen_port = info->port;
-#if !defined(LWS_WITH_ESP8266)
-       vh->http_proxy_port = 0;
-       vh->http_proxy_address[0] = '\0';
-#if defined(LWS_WITH_SOCKS5)
-       vh->socks_proxy_port = 0;
-       vh->socks_proxy_address[0] = '\0';
-#endif
-
-       /* either use proxy from info, or try get it from env var */
-
-       /* http proxy */
-       if (info->http_proxy_address) {
-               /* override for backwards compatibility */
-               if (info->http_proxy_port)
-                       vh->http_proxy_port = info->http_proxy_port;
-               lws_set_proxy(vh, info->http_proxy_address);
-       } else {
-#ifdef LWS_HAVE_GETENV
-               p = getenv("http_proxy");
-               if (p)
-                       lws_set_proxy(vh, p);
-#endif
-       }
-#if defined(LWS_WITH_SOCKS5)
-       /* socks proxy */
-       if (info->socks_proxy_address) {
-               /* override for backwards compatibility */
-               if (info->socks_proxy_port)
-                       vh->socks_proxy_port = info->socks_proxy_port;
-               lws_set_socks(vh, info->socks_proxy_address);
-       } else {
-#ifdef LWS_HAVE_GETENV
-               p = getenv("socks_proxy");
-               if (p)
-                       lws_set_socks(vh, p);
-#endif
-       }
-#endif
-#endif
-
-       vh->ka_time = info->ka_time;
-       vh->ka_interval = info->ka_interval;
-       vh->ka_probes = info->ka_probes;
-
-       if (vh->options & LWS_SERVER_OPTION_STS)
-               lwsl_notice("   STS enabled\n");
-
-#ifdef LWS_WITH_ACCESS_LOG
-       if (info->log_filepath) {
-               vh->log_fd = open(info->log_filepath, O_CREAT | O_APPEND | O_RDWR, 0600);
-               if (vh->log_fd == (int)LWS_INVALID_FILE) {
-                       lwsl_err("unable to open log filepath %s\n",
-                                info->log_filepath);
-                       goto bail;
-               }
-#ifndef WIN32
-               if (context->uid != -1)
-                       if (chown(info->log_filepath, context->uid,
-                                 context->gid) == -1)
-                               lwsl_err("unable to chown log file %s\n",
-                                               info->log_filepath);
-#endif
-       } else
-               vh->log_fd = (int)LWS_INVALID_FILE;
-#endif
-       if (lws_context_init_server_ssl(info, vh))
-               goto bail;
-       if (lws_context_init_client_ssl(info, vh))
-               goto bail;
-       if (lws_context_init_server(info, vh)) {
-               lwsl_err("init server failed\n");
-               goto bail;
-       }
-
-       while (1) {
-               if (!(*vh1)) {
-                       *vh1 = vh;
-                       break;
-               }
-               vh1 = &(*vh1)->vhost_next;
-       };
-       /* for the case we are adding a vhost much later, after server init */
-
-       if (context->protocol_init_done)
-               lws_protocol_init(context);
-
-       return vh;
-
-bail:
-       lws_free(vh);
-
-       return NULL;
-}
-
-LWS_VISIBLE int
-lws_init_vhost_client_ssl(const struct lws_context_creation_info *info,
-                         struct lws_vhost *vhost)
-{
-       struct lws_context_creation_info i;
-
-       memcpy(&i, info, sizeof(i));
-       i.port = CONTEXT_PORT_NO_LISTEN;
-
-       return lws_context_init_client_ssl(&i, vhost);
-}
-
-LWS_VISIBLE struct lws_context *
-lws_create_context(struct lws_context_creation_info *info)
-{
-       struct lws_context *context = NULL;
-       struct lws_plat_file_ops *prev;
-#ifndef LWS_NO_DAEMONIZE
-       int pid_daemon = get_daemonize_pid();
-#endif
-       int n, m;
-#if defined(__ANDROID__)
-       struct rlimit rt;
-#endif
-
-       lwsl_notice("Initial logging level %d\n", log_level);
-       lwsl_notice("Libwebsockets version: %s\n", library_version);
-#if defined(GCC_VER)
-       lwsl_notice("Compiled with  %s\n", GCC_VER);
-#endif
-#if LWS_POSIX
-#ifdef LWS_USE_IPV6
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_IPV6))
-               lwsl_notice("IPV6 compiled in and enabled\n");
-       else
-               lwsl_notice("IPV6 compiled in but disabled\n");
-#else
-       lwsl_notice("IPV6 not compiled in\n");
-#endif
-#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_ESP32)
-       lws_feature_status_libev(info);
-       lws_feature_status_libuv(info);
-#endif
-#endif
-       lwsl_info(" LWS_DEF_HEADER_LEN    : %u\n", LWS_DEF_HEADER_LEN);
-       lwsl_info(" LWS_MAX_PROTOCOLS     : %u\n", LWS_MAX_PROTOCOLS);
-       lwsl_info(" LWS_MAX_SMP           : %u\n", LWS_MAX_SMP);
-       lwsl_info(" SPEC_LATEST_SUPPORTED : %u\n", SPEC_LATEST_SUPPORTED);
-       lwsl_info(" sizeof (*info)        : %ld\n", (long)sizeof(*info));
-#if defined(LWS_WITH_STATS)
-       lwsl_notice(" LWS_WITH_STATS        : on\n");
-#endif
-#if LWS_POSIX
-       lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH);
-#endif
-       if (lws_plat_context_early_init())
-               return NULL;
-
-       context = lws_zalloc(sizeof(struct lws_context));
-       if (!context) {
-               lwsl_err("No memory for websocket context\n");
-               return NULL;
-       }
-       if (info->pt_serv_buf_size)
-               context->pt_serv_buf_size = info->pt_serv_buf_size;
-       else
-               context->pt_serv_buf_size = 4096;
-
-       /* default to just the platform fops implementation */
-
-       context->fops_platform.LWS_FOP_OPEN     = _lws_plat_file_open;
-       context->fops_platform.LWS_FOP_CLOSE    = _lws_plat_file_close;
-       context->fops_platform.LWS_FOP_SEEK_CUR = _lws_plat_file_seek_cur;
-       context->fops_platform.LWS_FOP_READ     = _lws_plat_file_read;
-       context->fops_platform.LWS_FOP_WRITE    = _lws_plat_file_write;
-       context->fops_platform.fi[0].sig        = NULL;
-
-       /*
-        *  arrange a linear linked-list of fops starting from context->fops
-        *
-        * platform fops
-        * [ -> fops_zip (copied into context so .next settable) ]
-        * [ -> info->fops ]
-        */
-
-       context->fops = &context->fops_platform;
-       prev = (struct lws_plat_file_ops *)context->fops;
-
-#if defined(LWS_WITH_ZIP_FOPS)
-       /* make a soft copy so we can set .next */
-       context->fops_zip = fops_zip;
-       prev->next = &context->fops_zip;
-       prev = (struct lws_plat_file_ops *)prev->next;
-#endif
-
-       /* if user provided fops, tack them on the end of the list */
-       if (info->fops)
-               prev->next = info->fops;
-
-       context->reject_service_keywords = info->reject_service_keywords;
-       if (info->external_baggage_free_on_destroy)
-               context->external_baggage_free_on_destroy =
-                       info->external_baggage_free_on_destroy;
-
-       context->time_up = time(NULL);
-
-       context->simultaneous_ssl_restriction = info->simultaneous_ssl_restriction;
-
-#ifndef LWS_NO_DAEMONIZE
-       if (pid_daemon) {
-               context->started_with_parent = pid_daemon;
-               lwsl_notice(" Started with daemon pid %d\n", pid_daemon);
-       }
-#endif
-#if defined(__ANDROID__)
-               n = getrlimit ( RLIMIT_NOFILE,&rt);
-               if (-1 == n) {
-                       lwsl_err("Get RLIMIT_NOFILE failed!\n");
-                       return NULL;
-               }
-               context->max_fds = rt.rlim_cur;
-#else
-               context->max_fds = getdtablesize();
-#endif
-
-       if (info->count_threads)
-               context->count_threads = info->count_threads;
-       else
-               context->count_threads = 1;
-
-       if (context->count_threads > LWS_MAX_SMP)
-               context->count_threads = LWS_MAX_SMP;
-
-       context->token_limits = info->token_limits;
-
-       context->options = info->options;
-
-       if (info->timeout_secs)
-               context->timeout_secs = info->timeout_secs;
-       else
-               context->timeout_secs = AWAITING_TIMEOUT;
-
-       context->ws_ping_pong_interval = info->ws_ping_pong_interval;
-
-       lwsl_info(" default timeout (secs): %u\n", context->timeout_secs);
-
-       if (info->max_http_header_data)
-               context->max_http_header_data = info->max_http_header_data;
-       else
-               if (info->max_http_header_data2)
-                       context->max_http_header_data =
-                                       info->max_http_header_data2;
-               else
-                       context->max_http_header_data = LWS_DEF_HEADER_LEN;
-       if (info->max_http_header_pool)
-               context->max_http_header_pool = info->max_http_header_pool;
-       else
-               context->max_http_header_pool = LWS_DEF_HEADER_POOL;
-
-       /*
-        * Allocate the per-thread storage for scratchpad buffers,
-        * and header data pool
-        */
-       for (n = 0; n < context->count_threads; n++) {
-               context->pt[n].serv_buf = lws_zalloc(context->pt_serv_buf_size);
-               if (!context->pt[n].serv_buf) {
-                       lwsl_err("OOM\n");
-                       return NULL;
-               }
-
-#ifdef LWS_USE_LIBUV
-               context->pt[n].context = context;
-#endif
-               context->pt[n].tid = n;
-               context->pt[n].http_header_data = lws_malloc(context->max_http_header_data *
-                                                      context->max_http_header_pool);
-               if (!context->pt[n].http_header_data)
-                       goto bail;
-
-               context->pt[n].ah_pool = lws_zalloc(sizeof(struct allocated_headers) *
-                                             context->max_http_header_pool);
-               for (m = 0; m < context->max_http_header_pool; m++)
-                       context->pt[n].ah_pool[m].data =
-                               (char *)context->pt[n].http_header_data +
-                               (m * context->max_http_header_data);
-               if (!context->pt[n].ah_pool)
-                       goto bail;
-
-               lws_pt_mutex_init(&context->pt[n]);
-       }
-
-       if (info->fd_limit_per_thread)
-               context->fd_limit_per_thread = info->fd_limit_per_thread;
-       else
-               context->fd_limit_per_thread = context->max_fds /
-                                              context->count_threads;
-
-       lwsl_notice(" Threads: %d each %d fds\n", context->count_threads,
-                   context->fd_limit_per_thread);
-
-       if (!info->ka_interval && info->ka_time > 0) {
-               lwsl_err("info->ka_interval can't be 0 if ka_time used\n");
-               return NULL;
-       }
-
-#ifdef LWS_USE_LIBEV
-       /* (Issue #264) In order to *avoid breaking backwards compatibility*, we
-        * enable libev mediated SIGINT handling with a default handler of
-        * lws_sigint_cb. The handler can be overridden or disabled
-        * by invoking lws_sigint_cfg after creating the context, but
-        * before invoking lws_initloop:
-        */
-       context->use_ev_sigint = 1;
-       context->lws_ev_sigint_cb = &lws_ev_sigint_cb;
-#endif /* LWS_USE_LIBEV */
-#ifdef LWS_USE_LIBUV
-       /* (Issue #264) In order to *avoid breaking backwards compatibility*, we
-        * enable libev mediated SIGINT handling with a default handler of
-        * lws_sigint_cb. The handler can be overridden or disabled
-        * by invoking lws_sigint_cfg after creating the context, but
-        * before invoking lws_initloop:
-        */
-       context->use_ev_sigint = 1;
-       context->lws_uv_sigint_cb = &lws_uv_sigint_cb;
-#endif
-#ifdef LWS_USE_LIBEVENT
-       /* (Issue #264) In order to *avoid breaking backwards compatibility*, we
-        * enable libev mediated SIGINT handling with a default handler of
-        * lws_sigint_cb. The handler can be overridden or disabled
-        * by invoking lws_sigint_cfg after creating the context, but
-        * before invoking lws_initloop:
-        */
-       context->use_ev_sigint = 1;
-       context->lws_event_sigint_cb = &lws_event_sigint_cb;
-#endif /* LWS_USE_LIBEVENT */
-
-       lwsl_info(" mem: context:         %5lu bytes (%ld ctx + (%ld thr x %d))\n",
-                 (long)sizeof(struct lws_context) +
-                 (context->count_threads * context->pt_serv_buf_size),
-                 (long)sizeof(struct lws_context),
-                 (long)context->count_threads,
-                 context->pt_serv_buf_size);
-
-       lwsl_info(" mem: http hdr rsvd:   %5lu bytes (%u thr x (%u + %lu) x %u))\n",
-                   (long)(context->max_http_header_data +
-                    sizeof(struct allocated_headers)) *
-                   context->max_http_header_pool * context->count_threads,
-                   context->count_threads,
-                   context->max_http_header_data,
-                   (long)sizeof(struct allocated_headers),
-                   context->max_http_header_pool);
-       n = sizeof(struct lws_pollfd) * context->count_threads *
-           context->fd_limit_per_thread;
-       context->pt[0].fds = lws_zalloc(n);
-       if (context->pt[0].fds == NULL) {
-               lwsl_err("OOM allocating %d fds\n", context->max_fds);
-               goto bail;
-       }
-       lwsl_info(" mem: pollfd map:      %5u\n", n);
-
-       if (info->server_string) {
-               context->server_string = info->server_string;
-               context->server_string_len = (short)
-                               strlen(context->server_string);
-       }
-
-#if LWS_MAX_SMP > 1
-       /* each thread serves his own chunk of fds */
-       for (n = 1; n < (int)info->count_threads; n++)
-               context->pt[n].fds = context->pt[n - 1].fds +
-                                    context->fd_limit_per_thread;
-#endif
-
-       if (lws_plat_init(context, info))
-               goto bail;
-
-       lws_context_init_ssl_library(info);
-
-       context->user_space = info->user;
-
-       /*
-        * if he's not saying he'll make his own vhosts later then act
-        * compatibly and make a default vhost using the data in the info
-        */
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
-               if (!lws_create_vhost(context, info)) {
-                       lwsl_err("Failed to create default vhost\n");
-                       return NULL;
-               }
-
-       lws_context_init_extensions(info, context);
-
-       lwsl_notice(" mem: per-conn:        %5lu bytes + protocol rx buf\n",
-                   (unsigned long)sizeof(struct lws));
-
-       strcpy(context->canonical_hostname, "unknown");
-       lws_server_get_canonical_hostname(context, info);
-
-       context->uid = info->uid;
-       context->gid = info->gid;
-
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-       memcpy(context->caps, info->caps, sizeof(context->caps));
-       context->count_caps = info->count_caps;
-#endif
-
-       /*
-        * drop any root privs for this process
-        * to listen on port < 1023 we would have needed root, but now we are
-        * listening, we don't want the power for anything else
-        */
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
-               lws_plat_drop_app_privileges(info);
-
-       /*
-        * give all extensions a chance to create any per-context
-        * allocations they need
-        */
-       if (info->port != CONTEXT_PORT_NO_LISTEN) {
-               if (lws_ext_cb_all_exts(context, NULL,
-                       LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT, NULL, 0) < 0)
-                       goto bail;
-       } else
-               if (lws_ext_cb_all_exts(context, NULL,
-                       LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT, NULL, 0) < 0)
-                       goto bail;
-
-       return context;
-
-bail:
-       lws_context_destroy(context);
-       return NULL;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_context_deprecate(struct lws_context *context, lws_reload_func cb)
-{
-       struct lws_vhost *vh = context->vhost_list, *vh1;
-       struct lws *wsi;
-
-       /*
-        * "deprecation" means disable the context from accepting any new
-        * connections and free up listen sockets to be used by a replacement
-        * context.
-        *
-        * Otherwise the deprecated context remains operational, until its
-        * number of connected sockets falls to zero, when it is deleted.
-        */
-
-       /* for each vhost, close his listen socket */
-
-       while (vh) {
-               wsi = vh->lserv_wsi;
-               if (wsi) {
-                       wsi->socket_is_permanently_unusable = 1;
-                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-                       wsi->context->deprecation_pending_listen_close_count++;
-                       /*
-                        * other vhosts can share the listen port, they
-                        * point to the same wsi.  So zap those too.
-                        */
-                       vh1 = context->vhost_list;
-                       while (vh1) {
-                               if (vh1->lserv_wsi == wsi)
-                                       vh1->lserv_wsi = NULL;
-                               vh1 = vh1->vhost_next;
-                       }
-               }
-               vh = vh->vhost_next;
-       }
-
-       context->deprecated = 1;
-       context->deprecation_cb = cb;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_context_is_deprecated(struct lws_context *context)
-{
-       return context->deprecated;
-}
-
-LWS_VISIBLE void
-lws_context_destroy2(struct lws_context *context);
-
-
-static void
-lws_vhost_destroy1(struct lws_vhost *vh)
-{
-       const struct lws_protocols *protocol = NULL;
-       struct lws_context_per_thread *pt;
-       int n, m = vh->context->count_threads;
-       struct lws_context *context = vh->context;
-       struct lws wsi;
-
-       lwsl_notice("%s\n", __func__);
-
-       if (vh->being_destroyed)
-               return;
-
-       vh->being_destroyed = 1;
-
-       /*
-        * Are there other vhosts that are piggybacking on our listen socket?
-        * If so we need to hand the listen socket off to one of the others
-        * so it will remain open.  If not, leave it attached to the closing
-        * vhost and it will get closed.
-        */
-
-       if (vh->lserv_wsi)
-               lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) {
-                       if (v != vh &&
-                           !v->being_destroyed &&
-                           v->listen_port == vh->listen_port &&
-                           ((!v->iface && !vh->iface) ||
-                           (v->iface && vh->iface &&
-                           !strcmp(v->iface, vh->iface)))) {
-                               /*
-                                * this can only be a listen wsi, which is
-                                * restricted... it has no protocol or other
-                                * bindings or states.  So we can simply
-                                * swap it to a vhost that has the same
-                                * iface + port, but is not closing.
-                                */
-                               assert(v->lserv_wsi == NULL);
-                               v->lserv_wsi = vh->lserv_wsi;
-                               vh->lserv_wsi = NULL;
-                               v->lserv_wsi->vhost = v;
-
-                               lwsl_notice("%s: listen skt from %s to %s\n",
-                                           __func__, vh->name, v->name);
-                               break;
-                       }
-               } lws_end_foreach_ll(v, vhost_next);
-
-       /*
-        * Forcibly close every wsi assoicated with this vhost.  That will
-        * include the listen socket if it is still associated with the closing
-        * vhost.
-        */
-
-       while (m--) {
-               pt = &context->pt[m];
-
-               for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
-                       struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
-                       if (!wsi)
-                               continue;
-                       if (wsi->vhost != vh)
-                               continue;
-
-                       lws_close_free_wsi(wsi,
-                               LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
-                               /* no protocol close */);
-                       n--;
-               }
-       }
-
-       /*
-        * let the protocols destroy the per-vhost protocol objects
-        */
-
-       memset(&wsi, 0, sizeof(wsi));
-       wsi.context = vh->context;
-       wsi.vhost = vh;
-       protocol = vh->protocols;
-       if (protocol) {
-               n = 0;
-               while (n < vh->count_protocols) {
-                       wsi.protocol = protocol;
-                       protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY,
-                                          NULL, NULL, 0);
-                       protocol++;
-                       n++;
-               }
-       }
-
-       /*
-        * remove vhost from context list of vhosts
-        */
-
-       lws_start_foreach_llp(struct lws_vhost **, pv, context->vhost_list) {
-               if (*pv == vh) {
-                       *pv = vh->vhost_next;
-                       break;
-               }
-       } lws_end_foreach_llp(pv, vhost_next);
-
-       /* add ourselves to the pending destruction list */
-
-       vh->vhost_next = vh->context->vhost_pending_destruction_list;
-       vh->context->vhost_pending_destruction_list = vh;
-}
-
-static void
-lws_vhost_destroy2(struct lws_vhost *vh)
-{
-       const struct lws_protocols *protocol = NULL;
-       struct lws_context *context = vh->context;
-       struct lws_deferred_free *df;
-       int n;
-
-       lwsl_notice("%s: %p\n", __func__, vh);
-
-       /* if we are still on deferred free list, remove ourselves */
-
-       lws_start_foreach_llp(struct lws_deferred_free **, pdf, context->deferred_free_list) {
-               if ((*pdf)->payload == vh) {
-                       df = *pdf;
-                       *pdf = df->next;
-                       lws_free(df);
-                       break;
-               }
-       } lws_end_foreach_llp(pdf, next);
-
-       /* remove ourselves from the pending destruction list */
-
-       lws_start_foreach_llp(struct lws_vhost **, pv, context->vhost_pending_destruction_list) {
-               if ((*pv) == vh) {
-                       *pv = (*pv)->vhost_next;
-                       break;
-               }
-       } lws_end_foreach_llp(pv, vhost_next);
-
-       /*
-        * Free all the allocations associated with the vhost
-        */
-
-       protocol = vh->protocols;
-       if (protocol) {
-               n = 0;
-               while (n < vh->count_protocols) {
-                       if (vh->protocol_vh_privs &&
-                           vh->protocol_vh_privs[n]) {
-                               lws_free(vh->protocol_vh_privs[n]);
-                               vh->protocol_vh_privs[n] = NULL;
-                       }
-                       protocol++;
-                       n++;
-               }
-       }
-       if (vh->protocol_vh_privs)
-               lws_free(vh->protocol_vh_privs);
-       lws_ssl_SSL_CTX_destroy(vh);
-       lws_free(vh->same_vh_protocol_list);
-#ifdef LWS_WITH_PLUGINS
-       if (LWS_LIBUV_ENABLED(context)) {
-               if (context->plugin_list)
-                       lws_free((void *)vh->protocols);
-       } else
-#endif
-       {
-               if (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)
-                       lws_free((void *)vh->protocols);
-       }
-
-#ifdef LWS_WITH_PLUGINS
-#ifndef LWS_NO_EXTENSIONS
-       if (context->plugin_extension_count)
-               lws_free((void *)vh->extensions);
-#endif
-#endif
-#ifdef LWS_WITH_ACCESS_LOG
-       if (vh->log_fd != (int)LWS_INVALID_FILE)
-               close(vh->log_fd);
-#endif
-
-       /*
-        * although async event callbacks may still come for wsi handles with
-        * pending close in the case of asycn event library like libuv,
-        * they do not refer to the vhost.  So it's safe to free.
-        */
-
-       lwsl_notice("  %s: Freeing vhost %p\n", __func__, vh);
-
-       memset(vh, 0, sizeof(*vh));
-       free(vh);
-}
-
-int
-lws_check_deferred_free(struct lws_context *context, int force)
-{
-       struct lws_deferred_free *df;
-       time_t now = lws_now_secs();
-
-       lws_start_foreach_llp(struct lws_deferred_free **, pdf, context->deferred_free_list) {
-               if (now > (*pdf)->deadline || force) {
-                       df = *pdf;
-                       *pdf = df->next;
-                       /* finalize vh destruction */
-                       lwsl_notice("doing deferred vh %p destroy\n", df->payload);
-                       lws_vhost_destroy2(df->payload);
-                       lws_free(df);
-                       continue; /* after deletion we already point to next */
-               }
-       } lws_end_foreach_llp(pdf, next);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_vhost_destroy(struct lws_vhost *vh)
-{
-       struct lws_deferred_free *df = malloc(sizeof(*df));
-
-       if (!df)
-               return;
-
-       lws_vhost_destroy1(vh);
-
-       /* part 2 is deferred to allow all the handle closes to complete */
-
-       df->next = vh->context->deferred_free_list;
-       df->deadline = lws_now_secs() + 5;
-       df->payload = vh;
-       vh->context->deferred_free_list = df;
-}
-
-LWS_VISIBLE void
-lws_context_destroy(struct lws_context *context)
-{
-       struct lws_context_per_thread *pt;
-       struct lws_vhost *vh = NULL;
-       struct lws wsi;
-       int n, m;
-
-       if (!context) {
-               lwsl_notice("%s: ctx %p\n", __func__, context);
-               return;
-       }
-       if (context->being_destroyed1) {
-               lwsl_notice("%s: ctx %p: already being destroyed\n", __func__, context);
-               return;
-       }
-
-       lwsl_notice("%s: ctx %p\n", __func__, context);
-
-       m = context->count_threads;
-       context->being_destroyed = 1;
-       context->being_destroyed1 = 1;
-
-       memset(&wsi, 0, sizeof(wsi));
-       wsi.context = context;
-
-#ifdef LWS_LATENCY
-       if (context->worst_latency_info[0])
-               lwsl_notice("Worst latency: %s\n", context->worst_latency_info);
-#endif
-
-       while (m--) {
-               pt = &context->pt[m];
-
-               for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
-                       struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
-                       if (!wsi)
-                               continue;
-
-                       lws_close_free_wsi(wsi,
-                               LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
-                               /* no protocol close */);
-                       n--;
-               }
-               lws_pt_mutex_destroy(pt);
-       }
-
-       /*
-        * give all extensions a chance to clean up any per-context
-        * allocations they might have made
-        */
-
-       n = lws_ext_cb_all_exts(context, NULL,
-                               LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0);
-
-       n = lws_ext_cb_all_exts(context, NULL,
-                               LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0);
-
-       /*
-        * inform all the protocols that they are done and will have no more
-        * callbacks.
-        *
-        * We can't free things until after the event loop shuts down.
-        */
-       if (context->protocol_init_done)
-               vh = context->vhost_list;
-       while (vh) {
-               lws_vhost_destroy1(vh);
-               vh = vh->vhost_next;
-       }
-
-       for (n = 0; n < context->count_threads; n++) {
-               pt = &context->pt[n];
-
-               lws_libev_destroyloop(context, n);
-               lws_libuv_destroyloop(context, n);
-               lws_libevent_destroyloop(context, n);
-
-               lws_free_set_NULL(context->pt[n].serv_buf);
-               if (pt->ah_pool)
-                       lws_free(pt->ah_pool);
-               if (pt->http_header_data)
-                       lws_free(pt->http_header_data);
-       }
-       lws_plat_context_early_destroy(context);
-
-       if (context->pt[0].fds)
-               lws_free_set_NULL(context->pt[0].fds);
-
-       if (!LWS_LIBUV_ENABLED(context))
-               lws_context_destroy2(context);
-}
-
-/*
- * call the second one after the event loop has been shut down cleanly
- */
-
-LWS_VISIBLE void
-lws_context_destroy2(struct lws_context *context)
-{
-       struct lws_vhost *vh = NULL, *vh1;
-
-       lwsl_notice("%s: ctx %p\n", __func__, context);
-
-       /*
-        * free all the per-vhost allocations
-        */
-
-       vh = context->vhost_list;
-       while (vh) {
-               vh1 = vh->vhost_next;
-               lws_vhost_destroy2(vh);
-               vh = vh1;
-       }
-
-       /* remove ourselves from the pending destruction list */
-
-       while (context->vhost_pending_destruction_list)
-               /* removes itself from list */
-               lws_vhost_destroy2(context->vhost_pending_destruction_list);
-
-
-       lws_stats_log_dump(context);
-
-       lws_ssl_context_destroy(context);
-       lws_plat_context_late_destroy(context);
-
-       if (context->external_baggage_free_on_destroy)
-               free(context->external_baggage_free_on_destroy);
-
-       lws_check_deferred_free(context, 1);
-
-       lws_free(context);
-}
diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c
new file mode 100644 (file)
index 0000000..9b84af2
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+
+static int
+lws_get_idlest_tsi(struct lws_context *context)
+{
+       unsigned int lowest = ~0;
+       int n = 0, hit = -1;
+
+       for (; n < context->count_threads; n++) {
+               if ((unsigned int)context->pt[n].fds_count !=
+                   context->fd_limit_per_thread - 1 &&
+                   (unsigned int)context->pt[n].fds_count < lowest) {
+                       lowest = context->pt[n].fds_count;
+                       hit = n;
+               }
+       }
+
+       return hit;
+}
+
+struct lws *
+lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi)
+{
+       struct lws *new_wsi;
+       int n = fixed_tsi;
+
+       if (n < 0)
+               n = lws_get_idlest_tsi(vhost->context);
+
+       if (n < 0) {
+               lwsl_err("no space for new conn\n");
+               return NULL;
+       }
+
+       new_wsi = lws_zalloc(sizeof(struct lws), "new server wsi");
+       if (new_wsi == NULL) {
+               lwsl_err("Out of memory for new connection\n");
+               return NULL;
+       }
+
+       new_wsi->wsistate |= LWSIFR_SERVER;
+       new_wsi->tsi = n;
+       lwsl_debug("new wsi %p joining vhost %s, tsi %d\n", new_wsi,
+                  vhost->name, new_wsi->tsi);
+
+       lws_vhost_bind_wsi(vhost, new_wsi);
+       new_wsi->context = vhost->context;
+       new_wsi->pending_timeout = NO_PENDING_TIMEOUT;
+       new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
+
+       /* initialize the instance struct */
+
+       lwsi_set_state(new_wsi, LRS_UNCONNECTED);
+       new_wsi->hdr_parsing_completed = 0;
+
+#ifdef LWS_WITH_TLS
+       new_wsi->tls.use_ssl = LWS_SSL_ENABLED(vhost);
+#endif
+
+       /*
+        * these can only be set once the protocol is known
+        * 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
+        */
+       new_wsi->protocol = vhost->protocols;
+       new_wsi->user_space = NULL;
+       new_wsi->desc.sockfd = LWS_SOCK_INVALID;
+       new_wsi->position_in_fds_table = LWS_NO_FDS_POS;
+
+       vhost->context->count_wsi_allocated++;
+
+       /*
+        * outermost create notification for wsi
+        * no user_space because no protocol selection
+        */
+       vhost->protocols[0].callback(new_wsi, LWS_CALLBACK_WSI_CREATE, NULL,
+                                    NULL, 0);
+
+       return new_wsi;
+}
+
+
+/* if not a socket, it's a raw, non-ssl file descriptor */
+
+LWS_VISIBLE struct lws *
+lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
+                          lws_sock_file_fd_type fd, const char *vh_prot_name,
+                          struct lws *parent)
+{
+       struct lws_context *context = vh->context;
+       struct lws_context_per_thread *pt;
+       struct lws *new_wsi;
+       int n;
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       struct lws_peer *peer = NULL;
+
+       if (type & LWS_ADOPT_SOCKET) {
+               peer = lws_get_or_create_peer(vh, fd.sockfd);
+
+               if (peer && context->ip_limit_wsi &&
+                   peer->count_wsi >= context->ip_limit_wsi) {
+                       lwsl_notice("Peer reached wsi limit %d\n",
+                                       context->ip_limit_wsi);
+                       lws_stats_bump(&context->pt[0],
+                                             LWSSTATS_C_PEER_LIMIT_WSI_DENIED,
+                                             1);
+                       return NULL;
+               }
+       }
+#endif
+
+       /*
+        * Notice that in SMP case, the wsi may be being created on an
+        * entirely different pt / tsi for load balancing.  In that case as
+        * we initialize it, it may become "live" concurrently unexpectedly...
+        */
+
+       n = -1;
+       if (parent)
+               n = parent->tsi;
+       new_wsi = lws_create_new_server_wsi(vh, n);
+       if (!new_wsi) {
+               if (type & LWS_ADOPT_SOCKET)
+                       compatible_close(fd.sockfd);
+               return NULL;
+       }
+#if defined(LWS_WITH_PEER_LIMITS)
+       if (peer)
+               lws_peer_add_wsi(context, peer, new_wsi);
+#endif
+       pt = &context->pt[(int)new_wsi->tsi];
+       lws_stats_bump(pt, LWSSTATS_C_CONNECTIONS, 1);
+
+       if (parent) {
+               new_wsi->parent = parent;
+               new_wsi->sibling_list = parent->child_list;
+               parent->child_list = new_wsi;
+       }
+
+       /* enforce that every fd is nonblocking */
+
+       if (type & LWS_ADOPT_SOCKET) {
+               if (lws_plat_set_nonblocking(fd.sockfd)) {
+                       lwsl_err("%s: unable to set sockfd nonblocking\n",
+                                __func__);
+                       goto bail;
+               }
+       }
+#if !defined(WIN32)
+       else
+               if (lws_plat_set_nonblocking(fd.filefd)) {
+                       lwsl_err("%s: unable to set filefd nonblocking\n",
+                                __func__);
+                       goto bail;
+               }
+#endif
+
+       new_wsi->desc = fd;
+
+       if (vh_prot_name) {
+               new_wsi->protocol = lws_vhost_name_to_protocol(new_wsi->vhost,
+                                                              vh_prot_name);
+               if (!new_wsi->protocol) {
+                       lwsl_err("Protocol %s not enabled on vhost %s\n",
+                                vh_prot_name, new_wsi->vhost->name);
+                       goto bail;
+               }
+               if (lws_ensure_user_space(new_wsi)) {
+                      lwsl_notice("OOM trying to get user_space\n");
+                       goto bail;
+               }
+       }
+
+       if (!LWS_SSL_ENABLED(new_wsi->vhost) || !(type & LWS_ADOPT_SOCKET))
+               type &= ~LWS_ADOPT_ALLOW_SSL;
+
+       if (lws_role_call_adoption_bind(new_wsi, type, vh_prot_name)) {
+               lwsl_err("Unable to find a role that can adopt descriptor type 0x%x\n", type);
+               goto bail;
+       }
+
+       /*
+        * A new connection was accepted. Give the user a chance to
+        * set properties of the newly created wsi. There's no protocol
+        * selected yet so we issue this to the vhosts's default protocol,
+        * itself by default protocols[0]
+        */
+       new_wsi->wsistate |= LWSIFR_SERVER;
+       n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
+       if (new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)])
+               n = new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)];
+
+#if !defined(LWS_AMAZON_RTOS)
+       if (context->event_loop_ops->accept)
+               if (context->event_loop_ops->accept(new_wsi))
+                       goto fail;
+#endif
+
+#if LWS_MAX_SMP > 1
+       /*
+        * Caution: after this point the wsi is live on its service thread
+        * which may be concurrent to this.  We mark the wsi as still undergoing
+        * init in another pt so the assigned pt leaves it alone.
+        */
+       new_wsi->undergoing_init_from_other_pt = 1;
+#endif
+
+       if (!(type & LWS_ADOPT_ALLOW_SSL)) {
+               lws_pt_lock(pt, __func__);
+               if (__insert_wsi_socket_into_fds(context, new_wsi)) {
+                       lws_pt_unlock(pt);
+                       lwsl_err("%s: fail inserting socket\n", __func__);
+                       goto fail;
+               }
+               lws_pt_unlock(pt);
+       }
+#if !defined(LWS_WITHOUT_SERVER)
+        else
+               if (lws_server_socket_service_ssl(new_wsi, fd.sockfd)) {
+                       lwsl_info("%s: fail ssl negotiation\n", __func__);
+                       goto fail;
+               }
+#endif
+
+       /*
+        *  by deferring callback to this point, after insertion to fds,
+        * lws_callback_on_writable() can work from the callback
+        */
+       if ((new_wsi->protocol->callback)(new_wsi, n, new_wsi->user_space,
+                                         NULL, 0))
+               goto fail;
+
+       /* role may need to do something after all adoption completed */
+
+       lws_role_call_adoption_bind(new_wsi, type | _LWS_ADOPT_FINISH,
+                                   vh_prot_name);
+
+#if LWS_MAX_SMP > 1
+       /* its actual pt can service it now */
+
+       new_wsi->undergoing_init_from_other_pt = 0;
+#endif
+
+       lws_cancel_service_pt(new_wsi);
+
+       return new_wsi;
+
+fail:
+       if (type & LWS_ADOPT_SOCKET)
+               lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                  "adopt skt fail");
+
+       return NULL;
+
+bail:
+       lwsl_notice("%s: exiting on bail\n", __func__);
+       if (parent)
+               parent->child_list = new_wsi->sibling_list;
+       if (new_wsi->user_space)
+               lws_free(new_wsi->user_space);
+
+       vh->context->count_wsi_allocated--;
+
+       lws_vhost_unbind_wsi(new_wsi);
+       lws_free(new_wsi);
+
+       compatible_close(fd.sockfd);
+
+       return NULL;
+}
+
+LWS_VISIBLE struct lws *
+lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd)
+{
+       lws_sock_file_fd_type fd;
+
+       fd.sockfd = accept_fd;
+       return lws_adopt_descriptor_vhost(vh, LWS_ADOPT_SOCKET |
+                       LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL, fd, NULL, NULL);
+}
+
+LWS_VISIBLE struct lws *
+lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
+{
+       return lws_adopt_socket_vhost(context->vhost_list, accept_fd);
+}
+
+/* Common read-buffer adoption for lws_adopt_*_readbuf */
+static struct lws*
+adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len)
+{
+       struct lws_context_per_thread *pt;
+       struct lws_pollfd *pfd;
+       int n;
+
+       if (!wsi)
+               return NULL;
+
+       if (!readbuf || len == 0)
+               return wsi;
+
+       if (wsi->position_in_fds_table == LWS_NO_FDS_POS)
+               return wsi;
+
+       pt = &wsi->context->pt[(int)wsi->tsi];
+
+       n = lws_buflist_append_segment(&wsi->buflist, (const uint8_t *)readbuf,
+                                      len);
+       if (n < 0)
+               goto bail;
+       if (n)
+               lws_dll2_add_head(&wsi->dll_buflist, &pt->dll_buflist_owner);
+
+       /*
+        * we can't process the initial read data until we can attach an ah.
+        *
+        * if one is available, get it and place the data in his ah rxbuf...
+        * wsi with ah that have pending rxbuf get auto-POLLIN service.
+        *
+        * no autoservice because we didn't get a chance to attach the
+        * readbuf data to wsi or ah yet, and we will do it next if we get
+        * the ah.
+        */
+       if (wsi->http.ah || !lws_header_table_attach(wsi, 0)) {
+
+               lwsl_notice("%s: calling service on readbuf ah\n", __func__);
+
+               /*
+                * unlike a normal connect, we have the headers already
+                * (or the first part of them anyway).
+                * libuv won't come back and service us without a network
+                * event, so we need to do the header service right here.
+                */
+               pfd = &pt->fds[wsi->position_in_fds_table];
+               pfd->revents |= LWS_POLLIN;
+               lwsl_err("%s: calling service\n", __func__);
+               if (lws_service_fd_tsi(wsi->context, pfd, wsi->tsi))
+                       /* service closed us */
+                       return NULL;
+
+               return wsi;
+       }
+       lwsl_err("%s: deferring handling ah\n", __func__);
+
+       return wsi;
+
+bail:
+       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                          "adopt skt readbuf fail");
+
+       return NULL;
+}
+
+LWS_EXTERN struct lws *
+lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags,
+                    const char *protocol_name, struct lws *parent_wsi)
+{
+#if !defined(LWS_PLAT_OPTEE)
+       lws_sock_file_fd_type sock;
+       struct addrinfo h, *r, *rp;
+       struct lws *wsi = NULL;
+       char buf[16];
+       int n;
+
+       memset(&h, 0, sizeof(h));
+       h.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
+       h.ai_socktype = SOCK_DGRAM;
+       h.ai_protocol = IPPROTO_UDP;
+       h.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+       h.ai_flags |= AI_ADDRCONFIG;
+#endif
+
+       lws_snprintf(buf, sizeof(buf), "%u", port);
+       n = getaddrinfo(NULL, buf, &h, &r);
+       if (n) {
+#ifndef LWS_WITH_ESP32
+               lwsl_info("%s: getaddrinfo error: %s\n", __func__, gai_strerror(n));
+#else
+        lwsl_info("%s: getaddrinfo error: %s\n", __func__, strerror(n));
+#endif
+               goto bail;
+       }
+
+       for (rp = r; rp; rp = rp->ai_next) {
+               sock.sockfd = socket(rp->ai_family, rp->ai_socktype,
+                                    rp->ai_protocol);
+               if (sock.sockfd != LWS_SOCK_INVALID)
+                       break;
+       }
+       if (!rp) {
+               lwsl_err("%s: unable to create INET socket\n", __func__);
+               goto bail1;
+       }
+
+       if ((flags & LWS_CAUDP_BIND) && bind(sock.sockfd, rp->ai_addr,
+#if defined(_WIN32)
+                           (int)rp->ai_addrlen
+#else
+                           rp->ai_addrlen
+#endif
+          ) == -1) {
+               lwsl_err("%s: bind failed\n", __func__);
+               goto bail2;
+       }
+
+       wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_RAW_SOCKET_UDP, sock,
+                                       protocol_name, parent_wsi);
+       if (!wsi)
+               lwsl_err("%s: udp adoption failed\n", __func__);
+
+bail2:
+       if (!wsi)
+               compatible_close((int)sock.sockfd);
+bail1:
+       freeaddrinfo(r);
+
+bail:
+       return wsi;
+#else
+       return NULL;
+#endif
+}
+
+LWS_VISIBLE struct lws *
+lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd,
+                        const char *readbuf, size_t len)
+{
+        return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd),
+                                   readbuf, len);
+}
+
+LWS_VISIBLE struct lws *
+lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost,
+                              lws_sockfd_type accept_fd,
+                              const char *readbuf, size_t len)
+{
+        return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd),
+                                   readbuf, len);
+}
diff --git a/lib/core-net/client.c b/lib/core-net/client.c
new file mode 100644 (file)
index 0000000..873478b
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+
+LWS_VISIBLE int
+lws_set_proxy(struct lws_vhost *vhost, const char *proxy)
+{
+       char authstring[96];
+       int brackets = 0;
+       char *p;
+
+       if (!proxy)
+               return -1;
+
+       /* we have to deal with a possible redundant leading http:// */
+       if (!strncmp(proxy, "http://", 7))
+               proxy += 7;
+
+       p = strrchr(proxy, '@');
+       if (p) { /* auth is around */
+
+               if ((unsigned int)(p - proxy) > sizeof(authstring) - 1)
+                       goto auth_too_long;
+
+               lws_strncpy(authstring, proxy, p - proxy + 1);
+               // null termination not needed on input
+               if (lws_b64_encode_string(authstring, lws_ptr_diff(p, proxy),
+                               vhost->proxy_basic_auth_token,
+                   sizeof vhost->proxy_basic_auth_token) < 0)
+                       goto auth_too_long;
+
+               lwsl_info(" Proxy auth in use\n");
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+               proxy = p + 1;
+#endif
+       } else
+               vhost->proxy_basic_auth_token[0] = '\0';
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+
+#if defined(LWS_WITH_IPV6)
+       /*
+        * isolating the address / port is complicated by IPv6 overloading
+        * the meaning of : in the address.  The convention to solve it is to
+        * put [] around the ipv6 address part, eg, "[::1]:443".  This must be
+        * parsed to "::1" as the address and the port as 443.
+        *
+        * IPv4 addresses like myproxy:443 continue to be parsed as normal.
+        */
+
+       if (proxy[0] == '[')
+               brackets = 1;
+#endif
+
+       lws_strncpy(vhost->http.http_proxy_address, proxy + brackets,
+                   sizeof(vhost->http.http_proxy_address));
+
+       p = vhost->http.http_proxy_address;
+
+#if defined(LWS_WITH_IPV6)
+       if (brackets) {
+               /* original is IPv6 format "[::1]:443" */
+
+               p = strchr(vhost->http.http_proxy_address, ']');
+               if (!p) {
+                       lwsl_err("%s: malformed proxy '%s'\n", __func__, proxy);
+
+                       return -1;
+               }
+               *p++ = '\0';
+       }
+#endif
+
+       p = strchr(p, ':');
+       if (!p && !vhost->http.http_proxy_port) {
+               lwsl_err("http_proxy needs to be ads:port\n");
+
+               return -1;
+       }
+       if (p) {
+               *p = '\0';
+               vhost->http.http_proxy_port = atoi(p + 1);
+       }
+
+       lwsl_info(" Proxy %s:%u\n", vhost->http.http_proxy_address,
+                 vhost->http.http_proxy_port);
+#endif
+
+       return 0;
+
+auth_too_long:
+       lwsl_err("proxy auth too long\n");
+
+       return -1;
+}
+
diff --git a/lib/core-net/close.c b/lib/core-net/close.c
new file mode 100644 (file)
index 0000000..f561e94
--- /dev/null
@@ -0,0 +1,565 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+
+void
+__lws_free_wsi(struct lws *wsi)
+{
+       if (!wsi)
+               return;
+
+       /*
+        * Protocol user data may be allocated either internally by lws
+        * or by specified the user. We should only free what we allocated.
+        */
+       if (wsi->protocol && wsi->protocol->per_session_data_size &&
+           wsi->user_space && !wsi->user_space_externally_allocated)
+               lws_free(wsi->user_space);
+
+       lws_buflist_destroy_all_segments(&wsi->buflist);
+       lws_buflist_destroy_all_segments(&wsi->buflist_out);
+       lws_free_set_NULL(wsi->udp);
+
+       if (wsi->vhost && wsi->vhost->lserv_wsi == wsi)
+               wsi->vhost->lserv_wsi = NULL;
+#if !defined(LWS_NO_CLIENT)
+       if (wsi->vhost)
+               lws_dll2_remove(&wsi->dll_cli_active_conns);
+#endif
+       wsi->context->count_wsi_allocated--;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       __lws_header_table_detach(wsi, 0);
+#endif
+       __lws_same_vh_protocol_remove(wsi);
+#if !defined(LWS_NO_CLIENT)
+       lws_client_stash_destroy(wsi);
+       lws_free_set_NULL(wsi->cli_hostname_copy);
+#endif
+
+       if (wsi->role_ops->destroy_role)
+               wsi->role_ops->destroy_role(wsi);
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       lws_peer_track_wsi_close(wsi->context, wsi->peer);
+       wsi->peer = NULL;
+#endif
+
+       /* since we will destroy the wsi, make absolutely sure now */
+
+#if defined(LWS_WITH_OPENSSL)
+       __lws_ssl_remove_wsi_from_buffered_list(wsi);
+#endif
+       __lws_wsi_remove_from_sul(wsi);
+
+       if (wsi->context->event_loop_ops->destroy_wsi)
+               wsi->context->event_loop_ops->destroy_wsi(wsi);
+
+       lws_vhost_unbind_wsi(wsi);
+
+       lwsl_debug("%s: %p, remaining wsi %d\n", __func__, wsi,
+                       wsi->context->count_wsi_allocated);
+
+       lws_free(wsi);
+}
+
+
+void
+lws_remove_child_from_any_parent(struct lws *wsi)
+{
+       struct lws **pwsi;
+       int seen = 0;
+
+       if (!wsi->parent)
+               return;
+
+       /* detach ourselves from parent's child list */
+       pwsi = &wsi->parent->child_list;
+       while (*pwsi) {
+               if (*pwsi == wsi) {
+                       lwsl_info("%s: detach %p from parent %p\n", __func__,
+                                 wsi, wsi->parent);
+
+                       if (wsi->parent->protocol)
+                               wsi->parent->protocol->callback(wsi,
+                                               LWS_CALLBACK_CHILD_CLOSING,
+                                              wsi->parent->user_space, wsi, 0);
+
+                       *pwsi = wsi->sibling_list;
+                       seen = 1;
+                       break;
+               }
+               pwsi = &(*pwsi)->sibling_list;
+       }
+       if (!seen)
+               lwsl_err("%s: failed to detach from parent\n", __func__);
+
+       wsi->parent = NULL;
+}
+
+#if !defined(LWS_NO_CLIENT)
+static int
+lws_close_trans_q_leader(struct lws_dll2 *d, void *user)
+{
+       struct lws *w = lws_container_of(d, struct lws, dll2_cli_txn_queue);
+
+       __lws_close_free_wsi(w, -1, "trans q leader closing");
+
+       return 0;
+}
+
+void
+lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len)
+{
+       if (wsi->already_did_cce)
+               return;
+
+       wsi->already_did_cce = 1;
+       lws_stats_bump(&wsi->context->pt[(int)wsi->tsi],
+                      LWSSTATS_C_CONNS_CLIENT_FAILED, 1);
+
+       if (!wsi->protocol)
+               return;
+
+       wsi->protocol->callback(wsi,
+                               LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
+                               wsi->user_space, arg, len);
+}
+#endif
+
+void
+__lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason,
+                    const char *caller)
+{
+       struct lws_context_per_thread *pt;
+       struct lws *wsi1, *wsi2;
+       struct lws_context *context;
+#if !defined(LWS_NO_CLIENT)
+       long rl = (long)(int)reason;
+#endif
+       int n;
+
+       lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller);
+
+       if (!wsi)
+               return;
+
+       lws_access_log(wsi);
+
+       context = wsi->context;
+       pt = &context->pt[(int)wsi->tsi];
+       lws_stats_bump(pt, LWSSTATS_C_API_CLOSE, 1);
+
+#if !defined(LWS_NO_CLIENT)
+
+       lws_free_set_NULL(wsi->cli_hostname_copy);
+
+       /*
+        * if we have wsi in our transaction queue, if we are closing we
+        * must go through and close all those first
+        */
+       if (wsi->vhost) {
+
+               /* we are no longer an active client connection that can piggyback */
+               lws_dll2_remove(&wsi->dll_cli_active_conns);
+
+               if (rl != -1l)
+                       lws_vhost_lock(wsi->vhost);
+
+               lws_dll2_foreach_safe(&wsi->dll2_cli_txn_queue_owner, NULL,
+                                     lws_close_trans_q_leader);
+
+               /*
+                * !!! If we are closing, but we have pending pipelined
+                * transaction results we already sent headers for, that's going
+                * to destroy sync for HTTP/1 and leave H2 stream with no live
+                * swsi.`
+                *
+                * However this is normal if we are being closed because the
+                * transaction queue leader is closing.
+                */
+               lws_dll2_remove(&wsi->dll2_cli_txn_queue);
+               if (rl != -1l)
+                       lws_vhost_unlock(wsi->vhost);
+       }
+#endif
+
+       /* if we have children, close them first */
+       if (wsi->child_list) {
+               wsi2 = wsi->child_list;
+               while (wsi2) {
+                       wsi1 = wsi2->sibling_list;
+                       wsi2->parent = NULL;
+                       /* stop it doing shutdown processing */
+                       wsi2->socket_is_permanently_unusable = 1;
+                       __lws_close_free_wsi(wsi2, reason,
+                                            "general child recurse");
+                       wsi2 = wsi1;
+               }
+               wsi->child_list = NULL;
+       }
+
+       if (wsi->role_ops == &role_ops_raw_file) {
+               lws_remove_child_from_any_parent(wsi);
+               __remove_wsi_socket_from_fds(wsi);
+               if (wsi->protocol)
+                       wsi->protocol->callback(wsi, wsi->role_ops->close_cb[0],
+                                       wsi->user_space, NULL, 0);
+               goto async_close;
+       }
+
+       wsi->wsistate_pre_close = wsi->wsistate;
+
+#ifdef LWS_WITH_CGI
+       if (wsi->role_ops == &role_ops_cgi) {
+
+               // lwsl_debug("%s: closing stdwsi index %d\n", __func__, (int)wsi->cgi_channel);
+
+               /* we are not a network connection, but a handler for CGI io */
+               if (wsi->parent && wsi->parent->http.cgi) {
+
+                       if (wsi->parent->child_list == wsi && !wsi->sibling_list)
+                               lws_cgi_remove_and_kill(wsi->parent);
+
+                       /* end the binding between us and master */
+                       wsi->parent->http.cgi->stdwsi[(int)wsi->cgi_channel] =
+                                                                       NULL;
+               }
+               wsi->socket_is_permanently_unusable = 1;
+
+               goto just_kill_connection;
+       }
+
+       if (wsi->http.cgi)
+               lws_cgi_remove_and_kill(wsi);
+#endif
+
+#if !defined(LWS_NO_CLIENT)
+       lws_client_stash_destroy(wsi);
+#endif
+
+       if (wsi->role_ops == &role_ops_raw_skt) {
+               wsi->socket_is_permanently_unusable = 1;
+               goto just_kill_connection;
+       }
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (lwsi_role_http(wsi) && lwsi_role_server(wsi) &&
+           wsi->http.fop_fd != NULL)
+               lws_vfs_file_close(&wsi->http.fop_fd);
+#endif
+
+       if (lwsi_state(wsi) == LRS_DEAD_SOCKET)
+               return;
+
+       if (wsi->socket_is_permanently_unusable ||
+           reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY ||
+           lwsi_state(wsi) == LRS_SHUTDOWN)
+               goto just_kill_connection;
+
+       switch (lwsi_state_PRE_CLOSE(wsi)) {
+       case LRS_DEAD_SOCKET:
+               return;
+
+       /* we tried the polite way... */
+       case LRS_WAITING_TO_SEND_CLOSE:
+       case LRS_AWAITING_CLOSE_ACK:
+       case LRS_RETURNED_CLOSE:
+               goto just_kill_connection;
+
+       case LRS_FLUSHING_BEFORE_CLOSE:
+               if (lws_has_buffered_out(wsi)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+                   || wsi->http.comp_ctx.buflist_comp ||
+                   wsi->http.comp_ctx.may_have_more
+#endif
+                ) {
+                       lws_callback_on_writable(wsi);
+                       return;
+               }
+               lwsl_info("%p: end LRS_FLUSHING_BEFORE_CLOSE\n", wsi);
+               goto just_kill_connection;
+       default:
+               if (lws_has_buffered_out(wsi)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+                               || wsi->http.comp_ctx.buflist_comp ||
+                   wsi->http.comp_ctx.may_have_more
+#endif
+               ) {
+                       lwsl_info("%p: LRS_FLUSHING_BEFORE_CLOSE\n", wsi);
+                       lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE);
+                       __lws_set_timeout(wsi,
+                               PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5);
+                       return;
+               }
+               break;
+       }
+
+       if (lwsi_state(wsi) == LRS_WAITING_CONNECT ||
+           lwsi_state(wsi) == LRS_H1C_ISSUE_HANDSHAKE)
+               goto just_kill_connection;
+
+       if (!wsi->told_user_closed && wsi->user_space && wsi->protocol &&
+           wsi->protocol_bind_balance) {
+               wsi->protocol->callback(wsi,
+                               wsi->role_ops->protocol_unbind_cb[
+                                      !!lwsi_role_server(wsi)],
+                                      wsi->user_space, (void *)__func__, 0);
+               wsi->protocol_bind_balance = 0;
+       }
+
+       /*
+        * signal we are closing, lws_write will
+        * add any necessary version-specific stuff.  If the write fails,
+        * no worries we are closing anyway.  If we didn't initiate this
+        * close, then our state has been changed to
+        * LRS_RETURNED_CLOSE and we will skip this.
+        *
+        * Likewise if it's a second call to close this connection after we
+        * sent the close indication to the peer already, we are in state
+        * LRS_AWAITING_CLOSE_ACK and will skip doing this a second time.
+        */
+
+       if (wsi->role_ops->close_via_role_protocol &&
+           wsi->role_ops->close_via_role_protocol(wsi, reason))
+               return;
+
+just_kill_connection:
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       if (wsi->http.buflist_post_body)
+               lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body);
+#endif
+
+       if (wsi->role_ops->close_kill_connection)
+               wsi->role_ops->close_kill_connection(wsi, reason);
+
+       n = 0;
+
+       if (!wsi->told_user_closed && wsi->user_space &&
+           wsi->protocol_bind_balance && wsi->protocol) {
+               lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi,
+                          wsi->protocol ? wsi->protocol->name: "NULL");
+               if (wsi->protocol)
+                       wsi->protocol->callback(wsi,
+                               wsi->role_ops->protocol_unbind_cb[
+                                      !!lwsi_role_server(wsi)],
+                                      wsi->user_space, (void *)__func__, 0);
+               wsi->protocol_bind_balance = 0;
+       }
+
+#if !defined(LWS_NO_CLIENT)
+       if ((lwsi_state(wsi) == LRS_WAITING_SERVER_REPLY ||
+            lwsi_state(wsi) == LRS_WAITING_CONNECT) &&
+            !wsi->already_did_cce && wsi->protocol)
+               lws_inform_client_conn_fail(wsi,
+                               (void *)"closed before established", 24);
+#endif
+
+       /*
+        * Testing with ab shows that we have to stage the socket close when
+        * the system is under stress... shutdown any further TX, change the
+        * state to one that won't emit anything more, and wait with a timeout
+        * for the POLLIN to show a zero-size rx before coming back and doing
+        * the actual close.
+        */
+       if (wsi->role_ops != &role_ops_raw_skt && !lwsi_role_client(wsi) &&
+           lwsi_state(wsi) != LRS_SHUTDOWN &&
+           lwsi_state(wsi) != LRS_UNCONNECTED &&
+           reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY &&
+           !wsi->socket_is_permanently_unusable) {
+
+#if defined(LWS_WITH_TLS)
+               if (lws_is_ssl(wsi) && wsi->tls.ssl) {
+                       n = 0;
+                       switch (__lws_tls_shutdown(wsi)) {
+                       case LWS_SSL_CAPABLE_DONE:
+                       case LWS_SSL_CAPABLE_ERROR:
+                       case LWS_SSL_CAPABLE_MORE_SERVICE_READ:
+                       case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE:
+                       case LWS_SSL_CAPABLE_MORE_SERVICE:
+                               break;
+                       }
+               } else
+#endif
+               {
+                       lwsl_info("%s: shutdown conn: %p (sk %d, state 0x%x)\n",
+                                 __func__, wsi, (int)(long)wsi->desc.sockfd,
+                                 lwsi_state(wsi));
+                       if (!wsi->socket_is_permanently_unusable &&
+                           lws_socket_is_valid(wsi->desc.sockfd)) {
+                               wsi->socket_is_permanently_unusable = 1;
+                               n = shutdown(wsi->desc.sockfd, SHUT_WR);
+                       }
+               }
+               if (n)
+                       lwsl_debug("closing: shutdown (state 0x%x) ret %d\n",
+                                  lwsi_state(wsi), LWS_ERRNO);
+
+               /*
+                * This causes problems on WINCE / ESP32 with disconnection
+                * when the events are half closing connection
+                */
+#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP32)
+               /* libuv: no event available to guarantee completion */
+               if (!wsi->socket_is_permanently_unusable &&
+                   lws_socket_is_valid(wsi->desc.sockfd) &&
+                   lwsi_state(wsi) != LRS_SHUTDOWN &&
+                   context->event_loop_ops->periodic_events_available) {
+                       __lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN);
+                       lwsi_set_state(wsi, LRS_SHUTDOWN);
+                       __lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH,
+                                         context->timeout_secs);
+
+                       return;
+               }
+#endif
+       }
+
+       lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__,
+                  wsi, wsi->desc.sockfd);
+
+#ifdef LWS_WITH_HUBBUB
+       if (wsi->http.rw) {
+               lws_rewrite_destroy(wsi->http.rw);
+               wsi->http.rw = NULL;
+       }
+#endif
+
+       if (wsi->http.pending_return_headers)
+               lws_free_set_NULL(wsi->http.pending_return_headers);
+
+       /*
+        * we won't be servicing or receiving anything further from this guy
+        * delete socket from the internal poll list if still present
+        */
+       __lws_ssl_remove_wsi_from_buffered_list(wsi);
+       __lws_wsi_remove_from_sul(wsi);
+
+       //if (wsi->told_event_loop_closed) // cgi std close case (dummy-callback)
+       //      return;
+
+       // lwsl_notice("%s: wsi %p, fd %d\n", __func__, wsi, wsi->desc.sockfd);
+
+       /* checking return redundant since we anyway close */
+       if (wsi->desc.sockfd != LWS_SOCK_INVALID)
+               __remove_wsi_socket_from_fds(wsi);
+       else
+               __lws_same_vh_protocol_remove(wsi);
+
+       lwsi_set_state(wsi, LRS_DEAD_SOCKET);
+       lws_buflist_destroy_all_segments(&wsi->buflist);
+       lws_dll2_remove(&wsi->dll_buflist);
+
+       if (wsi->role_ops->close_role)
+           wsi->role_ops->close_role(pt, wsi);
+
+       /* tell the user it's all over for this guy */
+
+       if ((lwsi_state_est_PRE_CLOSE(wsi) ||
+           /* raw skt adopted but didn't complete tls hs should CLOSE */
+           (wsi->role_ops == &role_ops_raw_skt && !lwsi_role_client(wsi)) ||
+            lwsi_state_PRE_CLOSE(wsi) == LRS_WAITING_SERVER_REPLY) &&
+           !wsi->told_user_closed &&
+           wsi->role_ops->close_cb[lwsi_role_server(wsi)]) {
+               const struct lws_protocols *pro = wsi->protocol;
+
+               if (!wsi->protocol && wsi->vhost && wsi->vhost->protocols)
+                       pro = &wsi->vhost->protocols[0];
+
+               if (pro && (!wsi->upgraded_to_http2 || !lwsi_role_client(wsi)))
+                       /*
+                        * The network wsi for a client h2 connection shouldn't
+                        * call back for its role: the child stream connections
+                        * own the role.  Otherwise h2 will call back closed
+                        * one too many times as the children do it and then
+                        * the closing network stream.
+                        */
+                       pro->callback(wsi,
+                             wsi->role_ops->close_cb[lwsi_role_server(wsi)],
+                             wsi->user_space, NULL, 0);
+               wsi->told_user_closed = 1;
+       }
+
+async_close:
+       lws_remove_child_from_any_parent(wsi);
+       wsi->socket_is_permanently_unusable = 1;
+
+       if (wsi->context->event_loop_ops->wsi_logical_close)
+               if (wsi->context->event_loop_ops->wsi_logical_close(wsi))
+                       return;
+
+       __lws_close_free_wsi_final(wsi);
+}
+
+void
+__lws_close_free_wsi_final(struct lws *wsi)
+{
+       int n;
+
+       if (!wsi->shadow &&
+           lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) {
+               lwsl_debug("%s: wsi %p: fd %d\n", __func__, wsi, wsi->desc.sockfd);
+               n = compatible_close(wsi->desc.sockfd);
+               if (n)
+                       lwsl_debug("closing: close ret %d\n", LWS_ERRNO);
+
+               wsi->desc.sockfd = LWS_SOCK_INVALID;
+       }
+
+       /* outermost destroy notification for wsi (user_space still intact) */
+       if (wsi->vhost)
+               wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY,
+                                                 wsi->user_space, NULL, 0);
+
+#ifdef LWS_WITH_CGI
+       if (wsi->http.cgi) {
+
+               for (n = 0; n < 3; n++) {
+                       if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] == 0)
+                               lwsl_err("ZERO FD IN CGI CLOSE");
+
+                       if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] >= 0) {
+                               close(wsi->http.cgi->pipe_fds[n][!!(n == 0)]);
+                               wsi->http.cgi->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID;
+                       }
+               }
+
+               lws_free_set_NULL(wsi->http.cgi);
+       }
+#endif
+
+       __lws_free_wsi(wsi);
+}
+
+
+void
+lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_pt_lock(pt, __func__);
+       __lws_close_free_wsi(wsi, reason, caller);
+       lws_pt_unlock(pt);
+}
+
+
diff --git a/lib/core-net/connect.c b/lib/core-net/connect.c
new file mode 100644 (file)
index 0000000..604bebb
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+void
+lws_client_stash_destroy(struct lws *wsi)
+{
+       if (!wsi || !wsi->stash)
+               return;
+
+       lws_free_set_NULL(wsi->stash->address);
+       lws_free_set_NULL(wsi->stash->path);
+       lws_free_set_NULL(wsi->stash->host);
+       lws_free_set_NULL(wsi->stash->origin);
+       lws_free_set_NULL(wsi->stash->protocol);
+       lws_free_set_NULL(wsi->stash->method);
+       lws_free_set_NULL(wsi->stash->iface);
+       lws_free_set_NULL(wsi->stash->alpn);
+
+       lws_free_set_NULL(wsi->stash);
+}
+
+LWS_VISIBLE struct lws *
+lws_client_connect_via_info(const struct lws_client_connect_info *i)
+{
+       struct lws *wsi, *safe = NULL;
+       const struct lws_protocols *p;
+       const char *local = i->protocol;
+       int tid = 0;
+#if LWS_MAX_SMP > 1
+       int n;
+#endif
+
+       if (i->context->requested_kill)
+               return NULL;
+
+       if (!i->context->protocol_init_done)
+               if (lws_protocol_init(i->context))
+                       return NULL;
+
+       /*
+        * If we have .local_protocol_name, use it to select the local protocol
+        * handler to bind to.  Otherwise use .protocol if http[s].
+        */
+       if (i->local_protocol_name)
+               local = i->local_protocol_name;
+
+       lws_stats_bump(&i->context->pt[tid], LWSSTATS_C_CONNS_CLIENT, 1);
+
+       /* PHASE 1: create a bare wsi */
+
+       wsi = lws_zalloc(sizeof(struct lws), "client wsi");
+       if (wsi == NULL)
+               goto bail;
+
+       wsi->context = i->context;
+       wsi->desc.sockfd = LWS_SOCK_INVALID;
+       wsi->seq = i->seq;
+
+       wsi->vhost = NULL;
+       if (!i->vhost)
+               lws_vhost_bind_wsi(i->context->vhost_list, wsi);
+       else
+               lws_vhost_bind_wsi(i->vhost, wsi);
+
+       if (!wsi->vhost) {
+               lwsl_err("%s: No vhost in the context\n", __func__);
+
+               goto bail;
+       }
+
+#if LWS_MAX_SMP > 1
+       tid = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_GET_THREAD_ID,
+                                               NULL, NULL, 0);
+#endif
+
+       /*
+        * PHASE 2: if SMP, bind the client to whatever tsi the current thread
+        * represents
+        */
+
+#if LWS_MAX_SMP > 1
+       lws_context_lock(i->context, "client find tsi");
+
+       for (n = 0; n < i->context->count_threads; n++)
+               if (i->context->pt[n].service_tid == tid) {
+                       lwsl_info("%s: client binds to caller tsi %d\n",
+                                 __func__, n);
+                       wsi->tsi = n;
+                       break;
+               }
+
+       /*
+        * this binding is sort of provisional, since when we try to insert
+        * into the pt fds, there may be no space and it will fail
+        */
+
+       lws_context_unlock(i->context);
+#endif
+
+       /*
+        * PHASE 3: Choose an initial role for the wsi and do role-specific init
+        *
+        * Note the initial role may not reflect the final role, eg,
+        * we may want ws, but first we have to go through h1 to get that
+        */
+
+       if (lws_role_call_client_bind(wsi, i) < 0) {
+               lwsl_err("%s: unable to bind to role\n", __func__);
+
+               goto bail;
+       }
+       lwsl_info("%s: role binding to %s\n", __func__, wsi->role_ops->name);
+
+       /*
+        * PHASE 4: fill up the wsi with stuff from the connect_info as far as
+        * it can go.  It's uncertain because not only is our connection
+        * going to complete asynchronously, we might have bound to h1 and not
+        * even be able to get ahold of an ah immediately.
+        */
+
+       wsi->user_space = NULL;
+       wsi->pending_timeout = NO_PENDING_TIMEOUT;
+       wsi->position_in_fds_table = LWS_NO_FDS_POS;
+       wsi->c_port = i->port;
+
+       wsi->protocol = &wsi->vhost->protocols[0];
+       wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE);
+
+       /*
+        * PHASE 5: handle external user_space now, generic alloc is done in
+        * role finalization
+        */
+
+       if (!wsi->user_space && i->userdata) {
+               wsi->user_space_externally_allocated = 1;
+               wsi->user_space = i->userdata;
+       }
+
+       if (local) {
+               lwsl_info("%s: protocol binding to %s\n", __func__, local);
+               p = lws_vhost_name_to_protocol(wsi->vhost, local);
+               if (p)
+                       lws_bind_protocol(wsi, p, __func__);
+       }
+
+       /*
+        * PHASE 5: handle external user_space now, generic alloc is done in
+        * role finalization
+        */
+
+       if (!wsi->user_space && i->userdata) {
+               wsi->user_space_externally_allocated = 1;
+               wsi->user_space = i->userdata;
+       }
+
+#if defined(LWS_WITH_TLS)
+       wsi->tls.use_ssl = i->ssl_connection;
+#else
+       if (i->ssl_connection & LCCSCF_USE_SSL) {
+               lwsl_err("%s: lws not configured for tls\n", __func__);
+               goto bail;
+       }
+#endif
+
+       /*
+        * PHASE 6: stash the things from connect_info that we can't process
+        * right now, eg, if http binding, without an ah.  If h1 and no ah, we
+        * will go on the ah waiting list and process those things later (after
+        * the connect_info and maybe the things pointed to have gone out of
+        * scope)
+        *
+        * However these things are stashed in a generic way at this point,
+        * with no relationship to http or ah
+        */
+
+       wsi->stash = lws_zalloc(sizeof(*wsi->stash), "client stash");
+       if (!wsi->stash) {
+               lwsl_err("%s: OOM\n", __func__);
+               goto bail1;
+       }
+
+       wsi->stash->address = lws_strdup(i->address);
+       wsi->stash->path = lws_strdup(i->path);
+       wsi->stash->host = lws_strdup(i->host);
+       wsi->stash->opaque_user_data = i->opaque_user_data;
+
+       if (!wsi->stash->address || !wsi->stash->path || !wsi->stash->host)
+               goto bail1;
+
+       if (i->origin) {
+               wsi->stash->origin = lws_strdup(i->origin);
+               if (!wsi->stash->origin)
+                       goto bail1;
+       }
+       if (i->protocol) {
+               wsi->stash->protocol = lws_strdup(i->protocol);
+               if (!wsi->stash->protocol)
+                       goto bail1;
+       }
+       if (i->method) {
+               wsi->stash->method = lws_strdup(i->method);
+               if (!wsi->stash->method)
+                       goto bail1;
+       }
+       if (i->iface) {
+               wsi->stash->iface = lws_strdup(i->iface);
+               if (!wsi->stash->iface)
+                       goto bail1;
+       }
+       if (i->alpn) {
+               wsi->stash->alpn = lws_strdup(i->alpn);
+               if (!wsi->stash->alpn)
+                       goto bail1;
+       }
+
+       /*
+        * at this point user callbacks like
+        * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER will be interested to
+        * know the parent... eg for proxying we can grab extra headers from
+        * the parent's incoming ah and add them to the child client handshake
+        */
+
+       if (i->parent_wsi) {
+               lwsl_info("%s: created child %p of parent %p\n", __func__,
+                         wsi, i->parent_wsi);
+               wsi->parent = i->parent_wsi;
+               safe = wsi->sibling_list = i->parent_wsi->child_list;
+               i->parent_wsi->child_list = wsi;
+       }
+
+       /*
+        * PHASE 7: Do any role-specific finalization processing.  We can still
+        * see important info things via wsi->stash
+        */
+
+       if (wsi->role_ops->client_bind) {
+               int n = wsi->role_ops->client_bind(wsi, NULL);
+
+               if (n && i->parent_wsi) {
+                       /* unpick from parent */
+
+                       i->parent_wsi->child_list = safe;
+               }
+
+               if (n < 0)
+                       /* we didn't survive, wsi is freed */
+                       goto bail2;
+
+               if (n)
+                       /* something else failed, wsi needs freeing */
+                       goto bail;
+       }
+
+       /* let the caller's optional wsi storage have the wsi we created */
+
+       if (i->pwsi)
+               *i->pwsi = wsi;
+
+       /* PHASE 8: notify protocol with role-specific connected callback */
+
+       lwsl_debug("%s: wsi %p: cb %d to %s %s\n", __func__,
+                       wsi, wsi->role_ops->adoption_cb[0],
+                       wsi->role_ops->name, wsi->protocol->name);
+
+       wsi->protocol->callback(wsi,
+                       wsi->role_ops->adoption_cb[0],
+                       wsi->user_space, NULL, 0);
+
+#if defined(LWS_WITH_HUBBUB)
+       if (i->uri_replace_to)
+               wsi->http.rw = lws_rewrite_create(wsi, html_parser_cb,
+                                            i->uri_replace_from,
+                                            i->uri_replace_to);
+#endif
+
+       if (i->method && !strcmp(i->method, "RAW"))
+               lws_http_client_connect_via_info2(wsi);
+
+       return wsi;
+
+bail1:
+       lws_client_stash_destroy(wsi);
+
+bail:
+       lws_free(wsi);
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+bail2:
+#endif
+       if (i->pwsi)
+               *i->pwsi = NULL;
+
+       lws_stats_bump(&i->context->pt[tid], LWSSTATS_C_CONNS_CLIENT_FAILED, 1);
+
+       return NULL;
+}
diff --git a/lib/core-net/dummy-callback.c b/lib/core-net/dummy-callback.c
new file mode 100644 (file)
index 0000000..bf227eb
--- /dev/null
@@ -0,0 +1,824 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#if defined(LWS_WITH_HTTP_PROXY)
+static int
+proxy_header(struct lws *wsi, struct lws *par, unsigned char *temp,
+            int temp_len, int index, unsigned char **p, unsigned char *end)
+{
+       int n = lws_hdr_total_length(par, index);
+
+       if (n < 1) {
+               lwsl_debug("%s: no index %d:\n", __func__, index);
+               return 0;
+       }
+
+       if (lws_hdr_copy(par, (char *)temp, temp_len, index) < 0)
+               return -1;
+
+       lwsl_debug("%s: index %d: %s\n", __func__, index, (char *)temp);
+
+       if (lws_add_http_header_by_token(wsi, index, temp, n, p, end))
+               return -1;
+
+       return 0;
+}
+
+static int
+stream_close(struct lws *wsi)
+{
+       char buf[LWS_PRE + 6], *out = buf + LWS_PRE;
+
+       if (wsi->http.did_stream_close)
+               return 0;
+
+       wsi->http.did_stream_close = 1;
+
+       if (wsi->http2_substream) {
+               if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0,
+                             LWS_WRITE_HTTP_FINAL) < 0) {
+                       lwsl_info("%s: COMPL_CLIENT_HTTP: h2 fin wr failed\n",
+                                 __func__);
+
+                       return -1;
+               }
+       } else {
+               *out++ = '0';
+               *out++ = '\x0d';
+               *out++ = '\x0a';
+               *out++ = '\x0d';
+               *out++ = '\x0a';
+
+               if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 5,
+                             LWS_WRITE_HTTP_FINAL) < 0) {
+                       lwsl_err("%s: COMPL_CLIENT_HTTP: "
+                                "h2 final write failed\n", __func__);
+
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+#endif
+
+struct lws_proxy_pkt {
+       struct lws_dll2 pkt_list;
+       size_t len;
+       char binary;
+       char first;
+       char final;
+
+       /* data follows */
+};
+
+#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS)
+int
+lws_callback_ws_proxy(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct lws_proxy_pkt *pkt;
+       struct lws_dll2 *dll;
+
+       switch (reason) {
+
+       /* h1 ws proxying... child / client / onward */
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               if (!wsi->h1_ws_proxied || !wsi->parent)
+                       break;
+
+               lws_process_ws_upgrade2(wsi->parent);
+
+#if defined(LWS_WITH_HTTP2)
+               if (wsi->parent->http2_substream)
+                       lwsl_info("%s: proxied h2 -> h1 ws established\n", __func__);
+#endif
+               break;
+
+       case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
+               return 1;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               lwsl_user("%s: client closed: parent %p\n", __func__, wsi->parent);
+               if (wsi->parent)
+                       lws_set_timeout(wsi->parent,
+                               PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE,
+                               LWS_TO_KILL_ASYNC);
+               break;
+
+       case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+       {
+               unsigned char **p = (unsigned char **)in, *end = (*p) + len,
+                                   tmp[128];
+
+               proxy_header(wsi, wsi->parent, tmp, sizeof(tmp),
+                             WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end);
+
+               proxy_header(wsi, wsi->parent, tmp, sizeof(tmp),
+                             WSI_TOKEN_HTTP_COOKIE, p, end);
+
+               proxy_header(wsi, wsi->parent, tmp, sizeof(tmp),
+                             WSI_TOKEN_HTTP_SET_COOKIE, p, end);
+               break;
+       }
+
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+               wsi->parent->ws->proxy_buffered += len;
+               if (wsi->parent->ws->proxy_buffered > 10 * 1024 * 1024) {
+                       lwsl_err("%s: proxied ws connection excessive buffering: dropping\n",
+                                       __func__);
+                       return -1;
+               }
+               pkt = lws_malloc(sizeof(*pkt) + LWS_PRE + len, __func__);
+               if (!pkt)
+                       return -1;
+
+               pkt->pkt_list.prev = pkt->pkt_list.next = NULL;
+               pkt->len = len;
+               pkt->first = lws_is_first_fragment(wsi);
+               pkt->final = lws_is_final_fragment(wsi);
+               pkt->binary = lws_frame_is_binary(wsi);
+
+               memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len);
+
+               lws_dll2_add_tail(&pkt->pkt_list, &wsi->parent->ws->proxy_owner);
+               lws_callback_on_writable(wsi->parent);
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+               dll = lws_dll2_get_tail(&wsi->ws->proxy_owner);
+               if (!dll)
+                       break;
+
+               pkt = (struct lws_proxy_pkt *)dll;
+               if (lws_write(wsi, ((unsigned char *)&pkt[1]) +
+                             LWS_PRE, pkt->len, lws_write_ws_flags(
+                               pkt->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+                                       pkt->first, pkt->final)) < 0)
+                       return -1;
+
+               wsi->parent->ws->proxy_buffered -= pkt->len;
+
+               lws_dll2_remove(dll);
+               lws_free(pkt);
+
+               if (lws_dll2_get_tail(&wsi->ws->proxy_owner))
+                       lws_callback_on_writable(wsi);
+               break;
+
+       /* h1 ws proxying... parent / server / incoming */
+
+       case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
+               return 1;
+
+       case LWS_CALLBACK_CLOSED:
+               lwsl_user("%s: closed\n", __func__);
+               return -1;
+
+       case LWS_CALLBACK_RECEIVE:
+               pkt = lws_malloc(sizeof(*pkt) + LWS_PRE + len, __func__);
+               if (!pkt)
+                       return -1;
+
+               pkt->pkt_list.prev = pkt->pkt_list.next = NULL;
+               pkt->len = len;
+               pkt->first = lws_is_first_fragment(wsi);
+               pkt->final = lws_is_final_fragment(wsi);
+               pkt->binary = lws_frame_is_binary(wsi);
+
+               memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len);
+
+               lws_dll2_add_tail(&pkt->pkt_list, &wsi->child_list->ws->proxy_owner);
+               lws_callback_on_writable(wsi->child_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               dll = lws_dll2_get_tail(&wsi->ws->proxy_owner);
+               if (!dll)
+                       break;
+
+               pkt = (struct lws_proxy_pkt *)dll;
+               if (lws_write(wsi, ((unsigned char *)&pkt[1]) +
+                             LWS_PRE, pkt->len, lws_write_ws_flags(
+                               pkt->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+                                       pkt->first, pkt->final)) < 0)
+                       return -1;
+
+               lws_dll2_remove(dll);
+               lws_free(pkt);
+
+               if (lws_dll2_get_tail(&wsi->ws->proxy_owner))
+                       lws_callback_on_writable(wsi);
+               break;
+
+       default:
+               return 0;
+       }
+
+       return 0;
+}
+
+const struct lws_protocols lws_ws_proxy = {
+               "lws-ws-proxy",
+               lws_callback_ws_proxy,
+               0,
+               8192,
+               8192, NULL, 0
+};
+
+#endif
+
+LWS_VISIBLE int
+lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct lws_ssl_info *si;
+#ifdef LWS_WITH_CGI
+       struct lws_cgi_args *args;
+#endif
+#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY)
+       char buf[8192];
+       int n;
+#endif
+#if defined(LWS_WITH_HTTP_PROXY)
+       unsigned char **p, *end;
+       struct lws *parent;
+#endif
+
+       switch (reason) {
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       case LWS_CALLBACK_HTTP:
+#ifndef LWS_NO_SERVER
+               if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
+                       return -1;
+
+               if (lws_http_transaction_completed(wsi))
+#endif
+                       return -1;
+               break;
+#if !defined(LWS_NO_SERVER)
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+#if defined(LWS_WITH_HTTP_PROXY)
+               if (wsi->child_list) {
+                       lwsl_user("%s: LWS_CALLBACK_HTTP_BODY_COMPLETION: %d\n", __func__, (int)len);
+                       break;
+               }
+#endif
+               /* fallthru */
+       case LWS_CALLBACK_HTTP_FILE_COMPLETION:
+               if (lws_http_transaction_completed(wsi))
+                       return -1;
+               break;
+#endif
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       case LWS_CALLBACK_HTTP_BODY:
+               if (wsi->child_list) {
+                       lwsl_user("%s: LWS_CALLBACK_HTTP_BODY: stashing %d\n", __func__, (int)len);
+                       if (lws_buflist_append_segment(&wsi->http.buflist_post_body, in, len) < 0)
+                               return -1;
+                       lws_callback_on_writable(wsi->child_list);
+               }
+               break;
+#endif
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               // lwsl_err("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
+#ifdef LWS_WITH_CGI
+               if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS |
+                                     LWS_CB_REASON_AUX_BF__CGI)) {
+                       n = lws_cgi_write_split_stdout_headers(wsi);
+                       if (n < 0) {
+                               lwsl_debug("AUX_BF__CGI forcing close\n");
+                               return -1;
+                       }
+                       if (!n)
+                               lws_rx_flow_control(
+                                       wsi->http.cgi->stdwsi[LWS_STDOUT], 1);
+
+                       if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS)
+                               wsi->reason_bf &=
+                                       ~LWS_CB_REASON_AUX_BF__CGI_HEADERS;
+                       else
+                               wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI;
+
+                       if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over)
+                               return -1;
+                       break;
+               }
+
+               if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) {
+                       if (!wsi->http2_substream) {
+                               memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5);
+                               lwsl_debug("writing chunk term 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 */
+                       if (lws_http_transaction_completed(wsi))
+                               return -1;
+                       return 0;
+               }
+#endif
+#if defined(LWS_WITH_HTTP_PROXY)
+
+               if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) {
+
+                       wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS;
+
+                       n = LWS_WRITE_HTTP_HEADERS;
+                       if (!wsi->http.prh_content_length)
+                               n |= LWS_WRITE_H2_STREAM_END;
+
+                       lwsl_debug("%s: %p: issuing proxy headers: clen %d\n",
+                                   __func__, wsi, (int)wsi->http.prh_content_length);
+                       n = lws_write(wsi, wsi->http.pending_return_headers +
+                                          LWS_PRE,
+                                     wsi->http.pending_return_headers_len, n);
+
+                       lws_free_set_NULL(wsi->http.pending_return_headers);
+
+                       if (n < 0) {
+                               lwsl_err("%s: EST_CLIENT_HTTP: write failed\n",
+                                        __func__);
+                               return -1;
+                       }
+
+                       lws_callback_on_writable(wsi);
+                       break;
+               }
+
+               if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) {
+                       char *px = buf + LWS_PRE;
+                       int lenx = sizeof(buf) - LWS_PRE - 32;
+
+                       /*
+                        * our sink is writeable and our source has something
+                        * to read.  So read a lump of source material of
+                        * suitable size to send or what's available, whichever
+                        * is the smaller.
+                        */
+                       wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY;
+                       if (!lws_get_child(wsi))
+                               break;
+
+                       /* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */
+                       if (lws_http_client_read(lws_get_child(wsi), &px,
+                                                &lenx) < 0) {
+                               lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: "
+                                          "client closed\n", __func__);
+
+                               stream_close(wsi);
+
+                               return -1;
+                       }
+                       break;
+               }
+
+               if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) {
+                       lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n",
+                                  __func__);
+
+                       wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;
+
+                       if (stream_close(wsi))
+                               return -1;
+
+                       if (lws_http_transaction_completed(wsi))
+                               return -1;
+               }
+#endif
+               break;
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               assert(lws_get_parent(wsi));
+               if (!lws_get_parent(wsi))
+                       break;
+               lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY;
+               lws_callback_on_writable(lws_get_parent(wsi));
+               break;
+
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: {
+               char *out = buf + LWS_PRE;
+
+               assert(lws_get_parent(wsi));
+
+               if (wsi->http.proxy_parent_chunked) {
+
+                       if (len > sizeof(buf) - LWS_PRE - 16) {
+                               lwsl_err("oversize buf %d %d\n", (int)len,
+                                               (int)sizeof(buf) - LWS_PRE - 16);
+                               return -1;
+                       }
+
+                       /*
+                        * this only needs dealing with on http/1.1 to allow
+                        * pipelining
+                        */
+                       n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len);
+                       out += n;
+                       memcpy(out, in, len);
+                       out += len;
+                       *out++ = '\x0d';
+                       *out++ = '\x0a';
+
+                       n = lws_write(lws_get_parent(wsi),
+                                     (unsigned char *)buf + LWS_PRE,
+                                     len + n + 2, LWS_WRITE_HTTP);
+               } else
+                       n = lws_write(lws_get_parent(wsi), (unsigned char *)in,
+                                     len, LWS_WRITE_HTTP);
+               if (n < 0)
+                       return -1;
+               break; }
+
+       /* h1 http proxying... */
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
+               unsigned char *start, *p, *end;
+
+               /*
+                * We want to proxy these headers, but we are being called
+                * at the point the onward client was established, which is
+                * unrelated to the state or writability of our proxy
+                * connection.
+                *
+                * Therefore produce the headers using the onward client ah
+                * while we have it, and stick them on the output buflist to be
+                * written on the proxy connection as soon as convenient.
+                */
+
+               parent = lws_get_parent(wsi);
+
+               if (!parent)
+                       return 0;
+
+               start = p = (unsigned char *)buf + LWS_PRE;
+               end = p + sizeof(buf) - LWS_PRE - 256;
+
+               if (lws_add_http_header_status(lws_get_parent(wsi),
+                               lws_http_client_http_response(wsi), &p, end))
+                       return 1;
+
+               /*
+                * copy these headers from the client connection to the parent
+                */
+
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_ETAG, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_SET_COOKIE, &p, end);
+               proxy_header(parent, wsi, end, 256,
+                            WSI_TOKEN_HTTP_LOCATION, &p, end);
+
+               if (!parent->http2_substream)
+                       if (lws_add_http_header_by_token(parent,
+                               WSI_TOKEN_CONNECTION, (unsigned char *)"close",
+                               5, &p, end))
+                       return -1;
+
+               /*
+                * We proxy using h1 only atm, and strip any chunking so it
+                * can go back out on h2 just fine.
+                *
+                * However if we are actually going out on h1, we need to add
+                * our own chunking since we still don't know the size.
+                */
+
+               if (!parent->http2_substream &&
+                   !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
+                       lwsl_debug("downstream parent chunked\n");
+                       if (lws_add_http_header_by_token(parent,
+                                       WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+                                       (unsigned char *)"chunked", 7, &p, end))
+                               return -1;
+
+                       wsi->http.proxy_parent_chunked = 1;
+               }
+
+               if (lws_finalize_http_header(parent, &p, end))
+                       return 1;
+
+               parent->http.prh_content_length = -1;
+               if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
+                       parent->http.prh_content_length = atoll(
+                               lws_hdr_simple_ptr(wsi,
+                                               WSI_TOKEN_HTTP_CONTENT_LENGTH));
+
+               parent->http.pending_return_headers_len = lws_ptr_diff(p, start);
+               parent->http.pending_return_headers =
+                       lws_malloc(parent->http.pending_return_headers_len +
+                                   LWS_PRE, "return proxy headers");
+               if (!parent->http.pending_return_headers)
+                       return -1;
+
+               memcpy(parent->http.pending_return_headers + LWS_PRE, start,
+                      parent->http.pending_return_headers_len);
+
+               parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS;
+
+               lwsl_debug("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: "
+                          "prepared %d headers (len %d)\n", __func__,
+                          lws_http_client_http_response(wsi),
+                          (int)parent->http.prh_content_length);
+
+               /*
+                * so at this point, the onward client connection can bear
+                * traffic.  We might be doing a POST and have pending cached
+                * inbound stuff to send, it can go now.
+                */
+
+               lws_callback_on_writable(parent);
+
+               break; }
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n",
+                                       __func__, wsi, lws_get_parent(wsi));
+               if (!lws_get_parent(wsi))
+                       break;
+               lws_get_parent(wsi)->reason_bf |=
+                               LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;
+               lws_callback_on_writable(lws_get_parent(wsi));
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               if (!lws_get_parent(wsi))
+                       break;
+               lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__);
+               lws_set_timeout(lws_get_parent(wsi),
+                               PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE,
+                               LWS_TO_KILL_ASYNC);
+               break;
+
+       case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+               parent = lws_get_parent(wsi);
+               if (!parent)
+                       break;
+
+               p = (unsigned char **)in;
+               end = (*p) + len;
+
+               /*
+                * copy these headers from the parent request to the client
+                * connection's request
+                */
+
+               proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
+                               WSI_TOKEN_HTTP_ETAG, p, end);
+               proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
+                               WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end);
+               proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
+                               WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end);
+               proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
+                               WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end);
+               proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
+                               WSI_TOKEN_HTTP_CACHE_CONTROL, p, end);
+               proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
+                               WSI_TOKEN_HTTP_COOKIE, p, end);
+
+               buf[0] = '\0';
+               lws_get_peer_simple(parent, buf, sizeof(buf));
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR,
+                               (unsigned char *)buf, (int)strlen(buf), p, end))
+                       return -1;
+
+               break;
+#endif
+
+#ifdef LWS_WITH_CGI
+       /* CGI IO events (POLLIN/OUT) appear here, our default policy is:
+        *
+        *  - POST data goes on subprocess stdin
+        *  - subprocess stdout goes on http via writeable callback
+        *  - subprocess stderr goes to the logs
+        */
+       case LWS_CALLBACK_CGI:
+               args = (struct lws_cgi_args *)in;
+               switch (args->ch) { /* which of stdin/out/err ? */
+               case LWS_STDIN:
+                       /* TBD stdin rx flow control */
+                       break;
+               case LWS_STDOUT:
+                       /* quench POLLIN on STDOUT until MASTER got writeable */
+                       lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0);
+                       wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI;
+                       /* when writing to MASTER would not block */
+                       lws_callback_on_writable(wsi);
+                       break;
+               case LWS_STDERR:
+                       n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]);
+                       if (n < 0)
+                               break;
+                       n = read(n, buf, sizeof(buf) - 2);
+                       if (n > 0) {
+                               if (buf[n - 1] != '\n')
+                                       buf[n++] = '\n';
+                               buf[n] = '\0';
+                               lwsl_notice("CGI-stderr: %s\n", buf);
+                       }
+                       break;
+               }
+               break;
+
+       case LWS_CALLBACK_CGI_TERMINATED:
+               lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n",
+                               wsi->http.cgi->explicitly_chunked,
+                               (uint64_t)wsi->http.cgi->content_length);
+               if (!wsi->http.cgi->explicitly_chunked &&
+                   !wsi->http.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;
+                       lws_callback_on_writable(wsi);
+                       lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3);
+                       break;
+               }
+               if (lws_http_transaction_completed(wsi))
+                       return -1;
+               return 0;
+
+       case LWS_CALLBACK_CGI_STDIN_DATA:  /* POST body for stdin */
+               args = (struct lws_cgi_args *)in;
+               args->data[args->len] = '\0';
+               if (!args->stdwsi[LWS_STDIN])
+                       return -1;
+               n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]);
+               if (n < 0)
+                       return -1;
+
+#if defined(LWS_WITH_ZLIB)
+               if (wsi->http.cgi->gzip_inflate) {
+                       /* gzip handling */
+
+                       if (!wsi->http.cgi->gzip_init) {
+                               lwsl_info("inflating gzip\n");
+
+                               memset(&wsi->http.cgi->inflate, 0,
+                                      sizeof(wsi->http.cgi->inflate));
+
+                               if (inflateInit2(&wsi->http.cgi->inflate,
+                                                16 + 15) != Z_OK) {
+                                       lwsl_err("%s: iniflateInit failed\n",
+                                                __func__);
+                                       return -1;
+                               }
+
+                               wsi->http.cgi->gzip_init = 1;
+                       }
+
+                       wsi->http.cgi->inflate.next_in = args->data;
+                       wsi->http.cgi->inflate.avail_in = args->len;
+
+                       do {
+
+                               wsi->http.cgi->inflate.next_out =
+                                               wsi->http.cgi->inflate_buf;
+                               wsi->http.cgi->inflate.avail_out =
+                                       sizeof(wsi->http.cgi->inflate_buf);
+
+                               n = inflate(&wsi->http.cgi->inflate,
+                                           Z_SYNC_FLUSH);
+
+                               switch (n) {
+                               case Z_NEED_DICT:
+                               case Z_STREAM_ERROR:
+                               case Z_DATA_ERROR:
+                               case Z_MEM_ERROR:
+                                       inflateEnd(&wsi->http.cgi->inflate);
+                                       wsi->http.cgi->gzip_init = 0;
+                                       lwsl_err("zlib error inflate %d\n", n);
+                                       return -1;
+                               }
+
+                               if (wsi->http.cgi->inflate.avail_out !=
+                                          sizeof(wsi->http.cgi->inflate_buf)) {
+                                       int written;
+
+                                       written = write(args->stdwsi[LWS_STDIN]->desc.filefd,
+                                               wsi->http.cgi->inflate_buf,
+                                               sizeof(wsi->http.cgi->inflate_buf) -
+                                               wsi->http.cgi->inflate.avail_out);
+
+                                       if (written != (int)(
+                                               sizeof(wsi->http.cgi->inflate_buf) -
+                                               wsi->http.cgi->inflate.avail_out)) {
+                                               lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
+                                                       "sent %d only %d went", n, args->len);
+                                       }
+
+                                       if (n == Z_STREAM_END) {
+                                               lwsl_err("gzip inflate end\n");
+                                               inflateEnd(&wsi->http.cgi->inflate);
+                                               wsi->http.cgi->gzip_init = 0;
+                                               break;
+                                       }
+
+                               } else
+                                       break;
+
+                               if (wsi->http.cgi->inflate.avail_out)
+                                       break;
+
+                       } while (1);
+
+                       return args->len;
+               }
+#endif /* WITH_ZLIB */
+
+               n = write(n, args->data, args->len);
+//             lwsl_hexdump_notice(args->data, args->len);
+               if (n < args->len)
+                       lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
+                                   "sent %d only %d went", n, args->len);
+
+               if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] &&
+                   args->stdwsi[LWS_STDIN]->desc.filefd > 0) {
+                       wsi->http.cgi->post_in_expected -= n;
+                       if (!wsi->http.cgi->post_in_expected) {
+                               struct lws *siwsi = args->stdwsi[LWS_STDIN];
+
+                               lwsl_debug("%s: expected POST in end: "
+                                          "closing stdin wsi %p, fd %d\n",
+                                          __func__, siwsi, siwsi->desc.sockfd);
+
+                               __remove_wsi_socket_from_fds(siwsi);
+                               lwsi_set_state(siwsi, LRS_DEAD_SOCKET);
+                               siwsi->socket_is_permanently_unusable = 1;
+//                             lws_remove_child_from_any_parent(siwsi);
+                               if (wsi->context->event_loop_ops->
+                                                       close_handle_manually) {
+
+                                       wsi->context->event_loop_ops->
+                                               close_handle_manually(siwsi);
+                                       siwsi->told_event_loop_closed = 1;
+                               } else {
+                                       compatible_close(siwsi->desc.sockfd);
+                                       __lws_free_wsi(siwsi);
+                               }
+                               wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1;
+
+//                             args->stdwsi[LWS_STDIN] = NULL;
+                       }
+               }
+
+               return n;
+#endif /* WITH_CGI */
+#endif /* ROLE_ H1 / H2 */
+       case LWS_CALLBACK_SSL_INFO:
+               si = in;
+
+               (void)si;
+               lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n",
+                           si->where, si->ret);
+               break;
+
+#if LWS_MAX_SMP > 1
+       case LWS_CALLBACK_GET_THREAD_ID:
+               return (int)(unsigned long long)pthread_self();
+#endif
+
+       default:
+               break;
+       }
+
+       return 0;
+}
diff --git a/lib/core-net/lws-dsh.c b/lib/core-net/lws-dsh.c
new file mode 100644 (file)
index 0000000..7e28dad
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+struct lws_dsh_search {
+       size_t          required;
+       int             kind;
+       lws_dsh_obj_t   *best;
+       lws_dsh_t       *dsh;
+
+       lws_dsh_t       *already_checked;
+       lws_dsh_t       *this_dsh;
+};
+
+static int
+_lws_dsh_alloc_tail(lws_dsh_t *dsh, int kind, const void *src1, size_t size1,
+                   const void *src2, size_t size2, lws_dll2_t *replace);
+
+static size_t
+lws_dsh_align(size_t length)
+{
+       size_t align = sizeof(int *);
+
+       if (length & (align - 1))
+               length += align - (length & (align - 1));
+
+       return length;
+}
+
+lws_dsh_t *
+lws_dsh_create(lws_dll2_owner_t *owner, size_t buf_len, int count_kinds)
+{
+       size_t oha_len = sizeof(lws_dsh_obj_head_t) * ++count_kinds;
+       lws_dsh_obj_t *obj;
+       lws_dsh_t *dsh;
+       int n;
+
+       assert(buf_len);
+       assert(count_kinds > 1);
+
+       dsh = lws_malloc(sizeof(lws_dsh_t) + buf_len + oha_len, __func__);
+       if (!dsh)
+               return NULL;
+
+       /* set convenience pointers to the overallocated parts */
+
+       dsh->oha = (lws_dsh_obj_head_t *)&dsh[1];
+       dsh->buf = ((uint8_t *)dsh->oha) + oha_len;
+       dsh->count_kinds = count_kinds;
+       dsh->buffer_size = buf_len;
+       dsh->being_destroyed = 0;
+
+       /* clear down the obj heads array */
+
+       memset(dsh->oha, 0, oha_len);
+       for (n = 0; n < count_kinds; n++)
+               dsh->oha[n].kind = n;
+
+       /* initially the whole buffer is on the free kind (0) list */
+
+       obj = (lws_dsh_obj_t *)dsh->buf;
+       memset(obj, 0, sizeof(*obj));
+       obj->asize = buf_len - sizeof(*obj);
+
+       lws_dll2_add_head(&obj->list, &dsh->oha[0].owner);
+
+       dsh->locally_free = obj->asize;
+       dsh->locally_in_use = 0;
+
+       lws_dll2_clear(&dsh->list);
+       if (owner)
+               lws_dll2_add_head(&dsh->list, owner);
+
+       // lws_dsh_describe(dsh, "post-init");
+
+       return dsh;
+}
+
+static int
+search_best_free(struct lws_dll2 *d, void *user)
+{
+       struct lws_dsh_search *s = (struct lws_dsh_search *)user;
+       lws_dsh_obj_t *obj = lws_container_of(d, lws_dsh_obj_t, list);
+
+       lwsl_debug("%s: obj %p, asize %zu (req %zu)\n", __func__, obj,
+                       obj->asize, s->required);
+
+       if (obj->asize >= s->required &&
+           (!s->best || obj->asize < s->best->asize)) {
+               s->best = obj;
+               s->dsh = s->this_dsh;
+       }
+
+       return 0;
+}
+
+static int
+try_foreign(struct lws_dll2 *d, void *user)
+{
+       struct lws_dsh_search *s = (struct lws_dsh_search *)user;
+       lws_dsh_t *dsh1 = lws_container_of(d, lws_dsh_t, list);
+
+       if (dsh1 == s->already_checked)
+               return 0;
+
+       if (dsh1->being_destroyed)
+               return 0;
+
+       if (dsh1->count_kinds < s->kind + 1)
+               return 0;
+
+       lwsl_debug("%s: actual try_foreign: dsh %p (free list size %d)\n",
+                       __func__, dsh1, dsh1->oha[0].owner.count);
+
+       s->this_dsh = dsh1;
+       if (lws_dll2_foreach_safe(&dsh1->oha[0].owner, s, search_best_free))
+               return 1;
+
+       return 0;
+}
+
+static int
+free_foreign(struct lws_dll2 *d, void *user)
+{
+       lws_dsh_obj_t *obj = lws_container_of(d, lws_dsh_obj_t, list);
+       lws_dsh_t *dsh = (lws_dsh_t *)user;
+       void *p = (void *)&obj[1];
+
+       if (obj->dsh != dsh)
+               lws_dsh_free(&p);
+
+       return 0;
+}
+
+static int
+evict2(struct lws_dll2 *d, void *user)
+{
+       lws_dsh_obj_t *obj = lws_container_of(d, lws_dsh_obj_t, list);
+       lws_dsh_t *dsh = (lws_dsh_t *)user;
+       void *p;
+
+       if (obj->dsh != dsh)
+               return 0;
+
+       /*
+        * If we are here, it means obj is a live object that is allocated on
+        * the dsh being destroyed, from a different dsh.  We need to migrate
+        * the object to a dsh that isn't being destroyed.
+        */
+
+       lwsl_debug("%s: migrating object size %zu\n", __func__, obj->size);
+
+       if (_lws_dsh_alloc_tail(dsh, 0, (void *)&obj[1], obj->size, NULL, 0, &obj->list)) {
+               lwsl_notice("%s: failed to migrate object\n", __func__);
+               /*
+                * only thing we can do is drop the logical object
+                */
+               p = (uint8_t *)&obj[1];
+               lws_dsh_free(&p);
+       }
+
+       return 0;
+}
+
+static int
+evict1(struct lws_dll2 *d, void *user)
+{
+       lws_dsh_t *dsh1 = lws_container_of(d, lws_dsh_t, list);
+       lws_dsh_t *dsh = (lws_dsh_t *)user;
+       int n;
+
+       if (dsh1->being_destroyed)
+               return 0;
+
+       /*
+        * For every dsh that's not being destroyed, send every object to
+        * evict2 for checking.
+        */
+
+       lwsl_debug("%s: checking dsh %p\n", __func__, dsh1);
+
+       for (n = 1; n < dsh1->count_kinds; n++) {
+               lws_dll2_describe(&dsh1->oha[n].owner, "check dsh1");
+               lws_dll2_foreach_safe(&dsh1->oha[n].owner, dsh, evict2);
+       }
+
+       return 0;
+}
+
+void
+lws_dsh_destroy(lws_dsh_t **pdsh)
+{
+       lws_dsh_t *dsh = *pdsh;
+       int n;
+
+       if (!dsh)
+               return;
+
+       lwsl_debug("%s: destroying dsh %p\n", __func__, dsh);
+
+       dsh->being_destroyed = 1;
+
+       /* we need to explicitly free any of our allocations in foreign dsh */
+
+       for (n = 1; n < dsh->count_kinds; n++)
+               lws_dll2_foreach_safe(&dsh->oha[n].owner, dsh, free_foreign);
+
+       /*
+        * We need to have anybody else with allocations in us evict them
+        * and make a copy in a buffer that isn't being destroyed
+        */
+
+       if (dsh->list.owner)
+               lws_dll2_foreach_safe(dsh->list.owner, dsh, evict1);
+
+       lws_dll2_remove(&dsh->list);
+
+       /* everything else is in one heap allocation */
+
+       lws_free_set_NULL(*pdsh);
+}
+
+static int
+_lws_dsh_alloc_tail(lws_dsh_t *dsh, int kind, const void *src1, size_t size1,
+                   const void *src2, size_t size2, lws_dll2_t *replace)
+{
+       size_t asize = sizeof(lws_dsh_obj_t) + lws_dsh_align(size1 + size2);
+       struct lws_dsh_search s;
+
+       assert(kind >= 0);
+       kind++;
+       assert(!dsh || kind < dsh->count_kinds);
+
+       /*
+        * Search our free list looking for the smallest guy who will fit
+        * what we want to allocate
+        */
+       s.required = asize;
+       s.kind = kind;
+       s.best = NULL;
+       s.already_checked = NULL;
+       s.this_dsh = dsh;
+
+       if (dsh && !dsh->being_destroyed)
+               lws_dll2_foreach_safe(&dsh->oha[0].owner, &s, search_best_free);
+
+       if (!s.best) {
+               /*
+                * Let's see if any other buffer has room
+                */
+               s.already_checked = dsh;
+
+               if (dsh->list.owner)
+                       lws_dll2_foreach_safe(dsh->list.owner, &s, try_foreign);
+
+               if (!s.best) {
+                       lwsl_notice("%s: no buffer has space\n", __func__);
+
+                       return 1;
+               }
+       }
+
+       /* anything coming out of here must be aligned */
+       assert(!(((unsigned long)s.best) & (sizeof(int *) - 1)));
+
+       if (s.best->asize < asize + (2 * sizeof(*s.best))) {
+               /*
+                * Exact fit, or close enough we can't / don't want to have to
+                * track the little bit of free area that would be left.
+                *
+                * Move the object from the free list to the oha of the
+                * desired kind
+                */
+               lws_dll2_remove(&s.best->list);
+               s.best->dsh = s.dsh;
+               s.best->size = size1 + size2;
+               memcpy(&s.best[1], src1, size1);
+               if (src2)
+                       memcpy((uint8_t *)&s.best[1] + size1, src2, size2);
+
+               if (replace) {
+                       s.best->list.prev = replace->prev;
+                       s.best->list.next = replace->next;
+                       s.best->list.owner = replace->owner;
+                       if (replace->prev)
+                               replace->prev->next = &s.best->list;
+                       if (replace->next)
+                               replace->next->prev = &s.best->list;
+               } else
+                       lws_dll2_add_tail(&s.best->list, &dsh->oha[kind].owner);
+
+               assert(s.dsh->locally_free >= s.best->asize);
+               s.dsh->locally_free -= s.best->asize;
+               s.dsh->locally_in_use += s.best->asize;
+               assert(s.dsh->locally_in_use <= s.dsh->buffer_size);
+       } else {
+               lws_dsh_obj_t *obj;
+
+               /*
+                * Free area was oversize enough that we need to split it.
+                *
+                * Leave the first part of the free area where it is and
+                * reduce its extent by our asize.  Use the latter part of
+                * the original free area as the allocation.
+                */
+               lwsl_debug("%s: splitting... free reduce %zu -> %zu\n",
+                               __func__, s.best->asize, s.best->asize - asize);
+
+               s.best->asize -= asize;
+
+               /* latter part becomes new object */
+
+               obj = (lws_dsh_obj_t *)(((uint8_t *)s.best) + s.best->asize);
+
+               lws_dll2_clear(&obj->list);
+               obj->dsh = s.dsh;
+               obj->size = size1 + size2;
+               obj->asize = asize;
+
+               memcpy(&obj[1], src1, size1);
+               if (src2)
+                       memcpy((uint8_t *)&obj[1] + size1, src2, size2);
+
+               if (replace) {
+                       s.best->list.prev = replace->prev;
+                       s.best->list.next = replace->next;
+                       s.best->list.owner = replace->owner;
+                       if (replace->prev)
+                               replace->prev->next = &s.best->list;
+                       if (replace->next)
+                               replace->next->prev = &s.best->list;
+               } else
+                       lws_dll2_add_tail(&obj->list, &dsh->oha[kind].owner);
+
+               assert(s.dsh->locally_free >= asize);
+               s.dsh->locally_free -= asize;
+               s.dsh->locally_in_use += asize;
+               assert(s.dsh->locally_in_use <= s.dsh->buffer_size);
+       }
+
+       // lws_dsh_describe(dsh, "post-alloc");
+
+       return 0;
+}
+
+int
+lws_dsh_alloc_tail(lws_dsh_t *dsh, int kind, const void *src1, size_t size1,
+                  const void *src2, size_t size2)
+{
+       return _lws_dsh_alloc_tail(dsh, kind, src1, size1, src2, size2, NULL);
+}
+
+static int
+buf_compare(const lws_dll2_t *d, const lws_dll2_t *i)
+{
+       return (int)lws_ptr_diff(d, i);
+}
+
+void
+lws_dsh_free(void **pobj)
+{
+       lws_dsh_obj_t *_o = (lws_dsh_obj_t *)((uint8_t *)(*pobj) - sizeof(*_o)),
+                       *_o2;
+       lws_dsh_t *dsh = _o->dsh;
+
+       /* anything coming out of here must be aligned */
+       assert(!(((unsigned long)_o) & (sizeof(int *) - 1)));
+
+       /*
+        * Remove the object from its list and place on the free list of the
+        * dsh the buffer space belongs to
+        */
+
+       lws_dll2_remove(&_o->list);
+       *pobj = NULL;
+
+       assert(dsh->locally_in_use >= _o->asize);
+       dsh->locally_free += _o->asize;
+       dsh->locally_in_use -= _o->asize;
+       assert(dsh->locally_in_use <= dsh->buffer_size);
+
+       /*
+        * The free space list is sorted in buffer address order, so detecting
+        * coalescing opportunities is cheap.  Because the free list should be
+        * continuously tending to reduce by coalescing, the sorting should not
+        * be expensive to maintain.
+        */
+       _o->size = 0; /* not meaningful when on free list */
+       lws_dll2_add_sorted(&_o->list, &_o->dsh->oha[0].owner, buf_compare);
+
+       /* First check for already-free block at the end we can subsume.
+        * Because the free list is sorted, if there is such a guy he is
+        * already our list.next */
+
+       _o2 = (lws_dsh_obj_t *)_o->list.next;
+       if (_o2 && (uint8_t *)_o + _o->asize == (uint8_t *)_o2) {
+               /*
+                * since we are freeing _obj, we can coalesce with a
+                * free area immediately ahead of it
+                *
+                *  [ _o (being freed) ][ _o2 (free) ]  -> [ larger _o ]
+                */
+               _o->asize += _o2->asize;
+
+               /* guy next to us was absorbed into us */
+               lws_dll2_remove(&_o2->list);
+       }
+
+       /* Then check if we can be subsumed by a free block behind us.
+        * Because the free list is sorted, if there is such a guy he is
+        * already our list.prev */
+
+       _o2 = (lws_dsh_obj_t *)_o->list.prev;
+       if (_o2 && (uint8_t *)_o2 + _o2->asize == (uint8_t *)_o) {
+               /*
+                * since we are freeing obj, we can coalesce it with
+                * the previous free area that abuts it
+                *
+                *  [ _o2 (free) ][ _o (being freed) ] -> [ larger _o2 ]
+                */
+               _o2->asize += _o->asize;
+
+               /* we were absorbed! */
+               lws_dll2_remove(&_o->list);
+       }
+
+       // lws_dsh_describe(dsh, "post-alloc");
+}
+
+int
+lws_dsh_get_head(lws_dsh_t *dsh, int kind, void **obj, size_t *size)
+{
+       lws_dsh_obj_t *_obj = (lws_dsh_obj_t *)
+                       lws_dll2_get_head(&dsh->oha[kind + 1].owner);
+
+       if (!_obj) {
+               *obj = 0;
+               *size = 0;
+
+               return 1;       /* there is no head */
+       }
+
+       *obj = (void *)(&_obj[1]);
+       *size = _obj->size;
+
+       /* anything coming out of here must be aligned */
+       assert(!(((unsigned long)(*obj)) & (sizeof(int *) - 1)));
+
+       return 0;       /* we returned the head */
+}
+
+#if defined(_DEBUG)
+
+static int
+describe_kind(struct lws_dll2 *d, void *user)
+{
+       lws_dsh_obj_t *obj = lws_container_of(d, lws_dsh_obj_t, list);
+
+       lwsl_info("    _obj %p - %p, dsh %p, size %zu, asize %zu\n",
+                       obj, (uint8_t *)obj + obj->asize,
+                       obj->dsh, obj->size, obj->asize);
+
+       return 0;
+}
+
+void
+lws_dsh_describe(lws_dsh_t *dsh, const char *desc)
+{
+       int n = 0;
+
+       lwsl_info("%s: dsh %p, bufsize %zu, kinds %d, lf: %zu, liu: %zu, %s\n",
+                   __func__, dsh, dsh->buffer_size, dsh->count_kinds,
+                   dsh->locally_free, dsh->locally_in_use, desc);
+
+       for (n = 0; n < dsh->count_kinds; n++) {
+               lwsl_info("  Kind %d:\n", n);
+               lws_dll2_foreach_safe(&dsh->oha[n].owner, dsh, describe_kind);
+       }
+}
+#endif
diff --git a/lib/core-net/network.c b/lib/core-net/network.c
new file mode 100644 (file)
index 0000000..8c3fe1a
--- /dev/null
@@ -0,0 +1,519 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+static int
+interface_to_sa(struct lws_vhost *vh, const char *ifname,
+               struct sockaddr_in *addr, size_t addrlen, int allow_ipv6)
+{
+       int ipv6 = 0;
+#ifdef LWS_WITH_IPV6
+       if (allow_ipv6)
+               ipv6 = LWS_IPV6_ENABLED(vh);
+#endif
+       (void)vh;
+
+       return lws_interface_to_sa(ipv6, ifname, addr, addrlen);
+}
+#endif
+
+#ifndef LWS_PLAT_OPTEE
+static int
+lws_get_addresses(struct lws_vhost *vh, void *ads, char *name,
+                 int name_len, char *rip, int rip_len)
+{
+       struct addrinfo ai, *res;
+       struct sockaddr_in addr4;
+
+       rip[0] = '\0';
+       name[0] = '\0';
+       addr4.sin_family = AF_UNSPEC;
+
+#ifdef LWS_WITH_IPV6
+       if (LWS_IPV6_ENABLED(vh)) {
+               if (!lws_plat_inet_ntop(AF_INET6,
+                                       &((struct sockaddr_in6 *)ads)->sin6_addr,
+                                       rip, rip_len)) {
+                       lwsl_err("inet_ntop: %s", strerror(LWS_ERRNO));
+                       return -1;
+               }
+
+               // Strip off the IPv4 to IPv6 header if one exists
+               if (strncmp(rip, "::ffff:", 7) == 0)
+                       memmove(rip, rip + 7, strlen(rip) - 6);
+
+               getnameinfo((struct sockaddr *)ads, sizeof(struct sockaddr_in6),
+                           name, name_len, NULL, 0, 0);
+
+               return 0;
+       } else
+#endif
+       {
+               struct addrinfo *result;
+
+               memset(&ai, 0, sizeof ai);
+               ai.ai_family = PF_UNSPEC;
+               ai.ai_socktype = SOCK_STREAM;
+#if !defined(LWS_WITH_ESP32)
+               if (getnameinfo((struct sockaddr *)ads,
+                               sizeof(struct sockaddr_in),
+                               name, name_len, NULL, 0, 0))
+                       return -1;
+#endif
+
+               if (getaddrinfo(name, NULL, &ai, &result))
+                       return -1;
+
+               res = result;
+               while (addr4.sin_family == AF_UNSPEC && res) {
+                       switch (res->ai_family) {
+                       case AF_INET:
+                               addr4.sin_addr =
+                                ((struct sockaddr_in *)res->ai_addr)->sin_addr;
+                               addr4.sin_family = AF_INET;
+                               break;
+                       }
+
+                       res = res->ai_next;
+               }
+               freeaddrinfo(result);
+       }
+
+       if (addr4.sin_family == AF_UNSPEC)
+               return -1;
+
+       if (lws_plat_inet_ntop(AF_INET, &addr4.sin_addr, rip, rip_len) == NULL)
+               return -1;
+
+       return 0;
+}
+
+
+LWS_VISIBLE const char *
+lws_get_peer_simple(struct lws *wsi, char *name, int namelen)
+{
+       socklen_t len, olen;
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 sin6;
+#endif
+       struct sockaddr_in sin4;
+       int af = AF_INET;
+       void *p, *q;
+
+       wsi = lws_get_network_wsi(wsi);
+
+#ifdef LWS_WITH_IPV6
+       if (LWS_IPV6_ENABLED(wsi->vhost)) {
+               len = sizeof(sin6);
+               p = &sin6;
+               af = AF_INET6;
+               q = &sin6.sin6_addr;
+       } else
+#endif
+       {
+               len = sizeof(sin4);
+               p = &sin4;
+               q = &sin4.sin_addr;
+       }
+
+       olen = len;
+       if (getpeername(wsi->desc.sockfd, p, &len) < 0 || len > olen) {
+               lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO));
+               return NULL;
+       }
+
+       return lws_plat_inet_ntop(af, q, name, namelen);
+}
+#endif
+
+LWS_VISIBLE void
+lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name,
+                      int name_len, char *rip, int rip_len)
+{
+#ifndef LWS_PLAT_OPTEE
+       socklen_t len;
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 sin6;
+#endif
+       struct sockaddr_in sin4;
+       struct lws_context *context = wsi->context;
+       int ret = -1;
+       void *p;
+
+       rip[0] = '\0';
+       name[0] = '\0';
+
+       lws_latency_pre(context, wsi);
+
+#ifdef LWS_WITH_IPV6
+       if (LWS_IPV6_ENABLED(wsi->vhost)) {
+               len = sizeof(sin6);
+               p = &sin6;
+       } else
+#endif
+       {
+               len = sizeof(sin4);
+               p = &sin4;
+       }
+
+       if (getpeername(fd, p, &len) < 0) {
+               lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO));
+               goto bail;
+       }
+
+       ret = lws_get_addresses(wsi->vhost, p, name, name_len, rip, rip_len);
+
+bail:
+       lws_latency(context, wsi, "lws_get_peer_addresses", ret, 1);
+#endif
+       (void)wsi;
+       (void)fd;
+       (void)name;
+       (void)name_len;
+       (void)rip;
+       (void)rip_len;
+}
+
+
+
+/* note: this returns a random port, or one of these <= 0 return codes:
+ *
+ * LWS_ITOSA_USABLE:     the interface is usable, returned if so and sockfd invalid
+ * LWS_ITOSA_NOT_EXIST:  the requested iface does not even exist
+ * LWS_ITOSA_NOT_USABLE: the requested iface exists but is not usable (eg, no IP)
+ * LWS_ITOSA_BUSY:       the port at the requested iface + port is already in use
+ */
+
+LWS_EXTERN int
+lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
+               const char *iface, int ipv6_allowed)
+{
+#ifdef LWS_WITH_UNIX_SOCK
+       struct sockaddr_un serv_unix;
+#endif
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 serv_addr6;
+#endif
+       struct sockaddr_in serv_addr4;
+#ifndef LWS_PLAT_OPTEE
+       socklen_t len = sizeof(struct sockaddr_storage);
+#endif
+       int n;
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+       int m;
+#endif
+       struct sockaddr_storage sin;
+       struct sockaddr *v;
+
+       memset(&sin, 0, sizeof(sin));
+
+#if defined(LWS_WITH_UNIX_SOCK)
+       if (LWS_UNIX_SOCK_ENABLED(vhost)) {
+               v = (struct sockaddr *)&serv_unix;
+               n = sizeof(struct sockaddr_un);
+               memset(&serv_unix, 0, sizeof(serv_unix));
+               serv_unix.sun_family = AF_UNIX;
+               if (!iface)
+                       return LWS_ITOSA_NOT_EXIST;
+               if (sizeof(serv_unix.sun_path) <= strlen(iface)) {
+                       lwsl_err("\"%s\" too long for UNIX domain socket\n",
+                                iface);
+                       return LWS_ITOSA_NOT_EXIST;
+               }
+               strcpy(serv_unix.sun_path, iface);
+               if (serv_unix.sun_path[0] == '@')
+                       serv_unix.sun_path[0] = '\0';
+               else
+                       unlink(serv_unix.sun_path);
+
+       } else
+#endif
+#if defined(LWS_WITH_IPV6) && !defined(LWS_WITH_ESP32)
+       if (ipv6_allowed && LWS_IPV6_ENABLED(vhost)) {
+               v = (struct sockaddr *)&serv_addr6;
+               n = sizeof(struct sockaddr_in6);
+               memset(&serv_addr6, 0, sizeof(serv_addr6));
+               if (iface) {
+                       m = interface_to_sa(vhost, iface,
+                                   (struct sockaddr_in *)v, n, 1);
+                       if (m == LWS_ITOSA_NOT_USABLE) {
+                               lwsl_info("%s: netif %s: Not usable\n",
+                                        __func__, iface);
+                               return m;
+                       }
+                       if (m == LWS_ITOSA_NOT_EXIST) {
+                               lwsl_info("%s: netif %s: Does not exist\n",
+                                        __func__, iface);
+                               return m;
+                       }
+                       serv_addr6.sin6_scope_id = lws_get_addr_scope(iface);
+               }
+
+               serv_addr6.sin6_family = AF_INET6;
+               serv_addr6.sin6_port = htons(port);
+       } else
+#endif
+       {
+               v = (struct sockaddr *)&serv_addr4;
+               n = sizeof(serv_addr4);
+               memset(&serv_addr4, 0, sizeof(serv_addr4));
+               serv_addr4.sin_addr.s_addr = INADDR_ANY;
+               serv_addr4.sin_family = AF_INET;
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+               if (iface) {
+                   m = interface_to_sa(vhost, iface,
+                                   (struct sockaddr_in *)v, n, 0);
+                       if (m == LWS_ITOSA_NOT_USABLE) {
+                               lwsl_info("%s: netif %s: Not usable\n",
+                                        __func__, iface);
+                               return m;
+                       }
+                       if (m == LWS_ITOSA_NOT_EXIST) {
+                               lwsl_info("%s: netif %s: Does not exist\n",
+                                        __func__, iface);
+                               return m;
+                       }
+               }
+#endif
+               serv_addr4.sin_port = htons(port);
+       } /* ipv4 */
+
+       /* just checking for the interface extant */
+       if (sockfd == LWS_SOCK_INVALID)
+               return LWS_ITOSA_USABLE;
+
+       n = bind(sockfd, v, n);
+#ifdef LWS_WITH_UNIX_SOCK
+       if (n < 0 && LWS_UNIX_SOCK_ENABLED(vhost)) {
+               lwsl_err("ERROR on binding fd %d to \"%s\" (%d %d)\n",
+                        sockfd, iface, n, LWS_ERRNO);
+               return LWS_ITOSA_NOT_EXIST;
+       } else
+#endif
+       if (n < 0) {
+               lwsl_err("ERROR on binding fd %d to port %d (%d %d)\n",
+                        sockfd, port, n, LWS_ERRNO);
+
+               /* if something already listening, tell caller to fail permanently */
+
+               if (LWS_ERRNO == LWS_EADDRINUSE)
+                       return LWS_ITOSA_BUSY;
+
+               /* otherwise ask caller to retry later */
+
+               return LWS_ITOSA_NOT_EXIST;
+       }
+
+#if defined(LWS_WITH_UNIX_SOCK)
+       if (LWS_UNIX_SOCK_ENABLED(vhost)) {
+               uid_t uid = vhost->context->uid;
+               gid_t gid = vhost->context->gid;
+
+               if (vhost->unix_socket_perms) {
+                       if (lws_plat_user_colon_group_to_ids(
+                               vhost->unix_socket_perms, &uid, &gid)) {
+                               lwsl_err("%s: Failed to translate %s\n",
+                                         __func__, vhost->unix_socket_perms);
+                               return LWS_ITOSA_NOT_EXIST;
+                       }
+               }
+               if (uid && gid) {
+                       if (chown(serv_unix.sun_path, uid, gid)) {
+                               lwsl_err("%s: failed to set %s perms %u:%u\n",
+                                        __func__, serv_unix.sun_path,
+                                        (unsigned int)uid, (unsigned int)gid);
+
+                               return LWS_ITOSA_NOT_EXIST;
+                       }
+                       lwsl_notice("%s: vh %s unix skt %s perms %u:%u\n",
+                                   __func__, vhost->name, serv_unix.sun_path,
+                                   (unsigned int)uid, (unsigned int)gid);
+
+                       if (chmod(serv_unix.sun_path, 0660)) {
+                               lwsl_err("%s: failed to set %s to 0600 mode\n",
+                                        __func__, serv_unix.sun_path);
+
+                               return LWS_ITOSA_NOT_EXIST;
+                       }
+               }
+       }
+#endif
+
+#ifndef LWS_PLAT_OPTEE
+       if (getsockname(sockfd, (struct sockaddr *)&sin, &len) == -1)
+               lwsl_warn("getsockname: %s\n", strerror(LWS_ERRNO));
+       else
+#endif
+#if defined(LWS_WITH_IPV6)
+               port = (sin.ss_family == AF_INET6) ?
+                       ntohs(((struct sockaddr_in6 *) &sin)->sin6_port) :
+                       ntohs(((struct sockaddr_in *) &sin)->sin_port);
+#else
+               {
+                       struct sockaddr_in sain;
+                       memcpy(&sain, &sin, sizeof(sain));
+                       port = ntohs(sain.sin_port);
+               }
+#endif
+
+       return port;
+}
+
+static const lws_retry_range_t default_bo = { 3000, 7000 };
+
+unsigned int
+lws_retry_get_delay_ms(struct lws_context *context,
+                      const lws_retry_bo_t *retry, uint16_t *ctry, char *conceal)
+{
+       const lws_retry_range_t *r = &default_bo;
+       unsigned int ms;
+       uint16_t ra;
+
+       if (conceal)
+               *conceal = 0;
+
+       if (retry) {
+               if (*ctry < retry->retry_ms_table_count)
+                       r = &retry->retry_ms_table[*ctry];
+               else
+                       r = &retry->retry_ms_table[
+                               retry->retry_ms_table_count - 1];
+       }
+
+       ms = r->min_ms;
+       if (lws_get_random(context, &ra, sizeof(ra)) == sizeof(ra))
+               ms += ((r->max_ms - ms) * ra) / 65535;
+
+       if (*ctry < 0xffff)
+               (*ctry)++;
+
+       if (retry && conceal)
+               *conceal = (int)*ctry <= retry->conceal_count;
+
+       return ms;
+}
+
+#if defined(LWS_WITH_IPV6)
+LWS_EXTERN unsigned long
+lws_get_addr_scope(const char *ipaddr)
+{
+       unsigned long scope = 0;
+
+#ifndef WIN32
+       struct ifaddrs *addrs, *addr;
+       char ip[NI_MAXHOST];
+       unsigned int i;
+
+       getifaddrs(&addrs);
+       for (addr = addrs; addr; addr = addr->ifa_next) {
+               if (!addr->ifa_addr ||
+                       addr->ifa_addr->sa_family != AF_INET6)
+                       continue;
+
+               getnameinfo(addr->ifa_addr,
+                               sizeof(struct sockaddr_in6),
+                               ip, sizeof(ip),
+                               NULL, 0, NI_NUMERICHOST);
+
+               i = 0;
+               while (ip[i])
+                       if (ip[i++] == '%') {
+                               ip[i - 1] = '\0';
+                               break;
+                       }
+
+               if (!strcmp(ip, ipaddr)) {
+                       scope = if_nametoindex(addr->ifa_name);
+                       break;
+               }
+       }
+       freeifaddrs(addrs);
+#else
+       PIP_ADAPTER_ADDRESSES adapter, addrs = NULL;
+       PIP_ADAPTER_UNICAST_ADDRESS addr;
+       ULONG size = 0;
+       DWORD ret;
+       struct sockaddr_in6 *sockaddr;
+       char ip[NI_MAXHOST];
+       unsigned int i;
+       int found = 0;
+
+       for (i = 0; i < 5; i++)
+       {
+               ret = GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX,
+                                          NULL, addrs, &size);
+               if ((ret == NO_ERROR) || (ret == ERROR_NO_DATA)) {
+                       break;
+               } else if (ret == ERROR_BUFFER_OVERFLOW)
+               {
+                       if (addrs)
+                               free(addrs);
+                       addrs = (IP_ADAPTER_ADDRESSES *)malloc(size);
+               } else
+               {
+                       if (addrs)
+                       {
+                               free(addrs);
+                               addrs = NULL;
+                       }
+                       lwsl_err("Failed to get IPv6 address table (%d)", ret);
+                       break;
+               }
+       }
+
+       if ((ret == NO_ERROR) && (addrs)) {
+               adapter = addrs;
+               while (adapter && !found) {
+                       addr = adapter->FirstUnicastAddress;
+                       while (addr && !found) {
+                               if (addr->Address.lpSockaddr->sa_family ==
+                                   AF_INET6) {
+                                       sockaddr = (struct sockaddr_in6 *)
+                                               (addr->Address.lpSockaddr);
+
+                                       lws_plat_inet_ntop(sockaddr->sin6_family,
+                                                       &sockaddr->sin6_addr,
+                                                       ip, sizeof(ip));
+
+                                       if (!strcmp(ip, ipaddr)) {
+                                               scope = sockaddr->sin6_scope_id;
+                                               found = 1;
+                                               break;
+                                       }
+                               }
+                               addr = addr->Next;
+                       }
+                       adapter = adapter->Next;
+               }
+       }
+       if (addrs)
+               free(addrs);
+#endif
+
+       return scope;
+}
+#endif
+
+
+
diff --git a/lib/core-net/output.c b/lib/core-net/output.c
new file mode 100644 (file)
index 0000000..bcbc6d5
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * notice this returns number of bytes consumed, or -1
+ */
+int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
+{
+       struct lws_context *context = lws_get_context(wsi);
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       size_t real_len = len;
+       unsigned int n, m;
+
+       // lwsl_notice("%s: len %d\n", __func__, (int)len);
+       // lwsl_hexdump_level(LLL_NOTICE, buf, len);
+
+       /*
+        * Detect if we got called twice without going through the
+        * event loop to handle pending.  Since that guarantees extending any
+        * existing buflist_out it's inefficient.
+        */
+       if (0 && buf && wsi->could_have_pending) {
+               lwsl_hexdump_level(LLL_INFO, buf, len);
+               lwsl_info("** %p: vh: %s, prot: %s, role %s: "
+                         "Inefficient back-to-back write of %lu detected...\n",
+                         wsi, wsi->vhost ? wsi->vhost->name : "no vhost",
+                         wsi->protocol->name, wsi->role_ops->name,
+                         (unsigned long)len);
+       }
+
+       lws_stats_bump(pt, LWSSTATS_C_API_WRITE, 1);
+
+       /* just ignore sends after we cleared the truncation buffer */
+       if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE &&
+           !lws_has_buffered_out(wsi)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+           && !wsi->http.comp_ctx.may_have_more
+#endif
+           )
+               return (int)len;
+
+       if (buf && lws_has_buffered_out(wsi)) {
+               lwsl_info("** %p: vh: %s, prot: %s, incr buflist_out by %lu\n",
+                         wsi, wsi->vhost ? wsi->vhost->name : "no vhost",
+                         wsi->protocol->name, (unsigned long)len);
+
+               /*
+                * already buflist ahead of this, add it on the tail of the
+                * buflist, then ignore it for now and act like we're flushing
+                * the buflist...
+                */
+
+               if (lws_buflist_append_segment(&wsi->buflist_out, buf, len))
+                       return -1;
+
+               buf = NULL;
+               len = 0;
+       }
+
+       if (wsi->buflist_out) {
+               /* we have to drain the earliest buflist_out stuff first */
+
+               len = lws_buflist_next_segment_len(&wsi->buflist_out, &buf);
+               real_len = len;
+
+               lwsl_debug("%s: draining %d\n", __func__, (int)len);
+       }
+
+       if (!len || !buf)
+               return 0;
+
+       if (!wsi->http2_substream && !lws_socket_is_valid(wsi->desc.sockfd))
+               lwsl_warn("** error invalid sock but expected to send\n");
+
+       /* limit sending */
+       if (wsi->protocol->tx_packet_size)
+               n = (int)wsi->protocol->tx_packet_size;
+       else {
+               n = (int)wsi->protocol->rx_buffer_size;
+               if (!n)
+                       n = context->pt_serv_buf_size;
+       }
+       n += LWS_PRE + 4;
+       if (n > len)
+               n = (int)len;
+
+       /* nope, send it on the socket directly */
+       lws_latency_pre(context, wsi);
+       m = lws_ssl_capable_write(wsi, buf, n);
+       lws_latency(context, wsi, "send lws_issue_raw", n, n == m);
+
+       lwsl_info("%s: ssl_capable_write (%d) says %d\n", __func__, n, m);
+
+       /* something got written, it can have been truncated now */
+       wsi->could_have_pending = 1;
+
+       switch (m) {
+       case LWS_SSL_CAPABLE_ERROR:
+               /* we're going to close, let close know sends aren't possible */
+               wsi->socket_is_permanently_unusable = 1;
+               return -1;
+       case LWS_SSL_CAPABLE_MORE_SERVICE:
+               /*
+                * nothing got sent, not fatal.  Retry the whole thing later,
+                * ie, implying treat it was a truncated send so it gets
+                * retried
+                */
+               m = 0;
+               break;
+       }
+
+       if ((int)m < 0)
+               m = 0;
+
+       /*
+        * we were sending this from buflist_out?  Then not sending everything
+        * is a small matter of advancing ourselves only by the amount we did
+        * send in the buflist.
+        */
+       if (lws_has_buffered_out(wsi)) {
+               if (m) {
+                       lwsl_info("%p partial adv %d (vs %ld)\n", wsi, m,
+                                       (long)real_len);
+                       lws_buflist_use_segment(&wsi->buflist_out, m);
+               }
+
+               if (!lws_has_buffered_out(wsi)) {
+                       lwsl_info("%s: wsi %p: buflist_out flushed\n",
+                                 __func__, wsi);
+
+                       m = (int)real_len;
+                       if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) {
+                               lwsl_info("*%p signalling to close now\n", wsi);
+                               return -1; /* retry closing now */
+                       }
+
+                       if (wsi->close_when_buffered_out_drained) {
+                               wsi->close_when_buffered_out_drained = 0;
+                               return -1;
+                       }
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+#if !defined(LWS_WITHOUT_SERVER)
+                       if (wsi->http.deferred_transaction_completed) {
+                               lwsl_notice("%s: partial completed, doing "
+                                           "deferred transaction completed\n",
+                                           __func__);
+                               wsi->http.deferred_transaction_completed = 0;
+                               return lws_http_transaction_completed(wsi) ?
+                                                       -1 : (int)real_len;
+                       }
+#endif
+#endif
+               }
+               /* always callback on writeable */
+               lws_callback_on_writable(wsi);
+
+               return m;
+       }
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.comp_ctx.may_have_more)
+               lws_callback_on_writable(wsi);
+#endif
+
+       if (m == real_len)
+               /* what we just sent went out cleanly */
+               return m;
+
+       /*
+        * We were not able to send everything... and we were not sending from
+        * an existing buflist_out.  So we are starting a fresh buflist_out, by
+        * buffering the unsent remainder on it.
+        * (it will get first priority next time the socket is writable).
+        */
+       lwsl_debug("%p new partial sent %d from %lu total\n", wsi, m,
+                   (unsigned long)real_len);
+
+       if (lws_buflist_append_segment(&wsi->buflist_out, buf + m,
+                                      real_len - m) < 0)
+               return -1;
+
+       lws_stats_bump(pt, LWSSTATS_C_WRITE_PARTIALS, 1);
+       lws_stats_bump(pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, m);
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+       if (lws_wsi_is_udp(wsi)) {
+               /* stash original destination for fulfilling UDP partials */
+               wsi->udp->sa_pending = wsi->udp->sa;
+               wsi->udp->salen_pending = wsi->udp->salen;
+       }
+#endif
+
+       /* since something buffered, force it to get another chance to send */
+       lws_callback_on_writable(wsi);
+
+       return (int)real_len;
+}
+
+LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len,
+                         enum lws_write_protocol wp)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_stats_bump(pt, LWSSTATS_C_API_LWS_WRITE, 1);
+
+       if ((int)len < 0) {
+               lwsl_err("%s: suspicious len int %d, ulong %lu\n", __func__,
+                               (int)len, (unsigned long)len);
+               return -1;
+       }
+
+       lws_stats_bump(pt, LWSSTATS_B_WRITE, len);
+
+#ifdef LWS_WITH_ACCESS_LOG
+       wsi->http.access_log.sent += len;
+#endif
+       if (wsi->vhost)
+               wsi->vhost->conn_stats.tx += len;
+
+       assert(wsi->role_ops);
+       if (!wsi->role_ops->write_role_protocol)
+               return lws_issue_raw(wsi, buf, len);
+
+       return wsi->role_ops->write_role_protocol(wsi, buf, len, &wp);
+}
+
+LWS_VISIBLE int
+lws_ssl_capable_read_no_ssl(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;
+
+       lws_stats_bump(pt, LWSSTATS_C_API_READ, 1);
+
+       errno = 0;
+       if (lws_wsi_is_udp(wsi)) {
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+               wsi->udp->salen = sizeof(wsi->udp->sa);
+               n = recvfrom(wsi->desc.sockfd, (char *)buf, len, 0,
+                            &wsi->udp->sa, &wsi->udp->salen);
+#endif
+       } else
+               n = recv(wsi->desc.sockfd, (char *)buf, len, 0);
+
+       if (n >= 0) {
+
+               if (!n && wsi->unix_skt)
+                       return LWS_SSL_CAPABLE_ERROR;
+
+               /*
+                * See https://libwebsockets.org/
+                * pipermail/libwebsockets/2019-March/007857.html
+                */
+               if (!n)
+                       return LWS_SSL_CAPABLE_ERROR;
+
+               if (wsi->vhost)
+                       wsi->vhost->conn_stats.rx += n;
+               lws_stats_bump(pt, LWSSTATS_B_READ, n);
+
+               return n;
+       }
+
+       if (LWS_ERRNO == LWS_EAGAIN ||
+           LWS_ERRNO == LWS_EWOULDBLOCK ||
+           LWS_ERRNO == LWS_EINTR)
+               return LWS_SSL_CAPABLE_MORE_SERVICE;
+
+       lwsl_info("error on reading from skt : %d\n", LWS_ERRNO);
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+LWS_VISIBLE int
+lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
+{
+       int n = 0;
+#if defined(LWS_PLAT_OPTEE)
+       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
+#endif
+
+       if (lws_wsi_is_udp(wsi)) {
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+               if (lws_has_buffered_out(wsi))
+                       n = sendto(wsi->desc.sockfd, (const char *)buf,
+                                  len, 0, &wsi->udp->sa_pending,
+                                  wsi->udp->salen_pending);
+               else
+                       n = sendto(wsi->desc.sockfd, (const char *)buf,
+                                  len, 0, &wsi->udp->sa, wsi->udp->salen);
+#endif
+       } else
+               n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL);
+//     lwsl_info("%s: sent len %d result %d", __func__, len, n);
+       if (n >= 0)
+               return n;
+
+       if (LWS_ERRNO == LWS_EAGAIN ||
+           LWS_ERRNO == LWS_EWOULDBLOCK ||
+           LWS_ERRNO == LWS_EINTR) {
+               if (LWS_ERRNO == LWS_EWOULDBLOCK) {
+                       lws_set_blocking_send(wsi);
+               }
+
+               return LWS_SSL_CAPABLE_MORE_SERVICE;
+       }
+
+       lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n",
+                  len, wsi->desc.sockfd, n, LWS_ERRNO);
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+LWS_VISIBLE int
+lws_ssl_pending_no_ssl(struct lws *wsi)
+{
+       (void)wsi;
+#if defined(LWS_WITH_ESP32)
+       return 100;
+#else
+       return 0;
+#endif
+}
diff --git a/lib/core-net/pollfd.c b/lib/core-net/pollfd.c
new file mode 100644 (file)
index 0000000..725861c
--- /dev/null
@@ -0,0 +1,639 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa)
+{
+#if !defined(LWS_WITH_LIBUV) && !defined(LWS_WITH_LIBEV) && !defined(LWS_WITH_LIBEVENT)
+       volatile struct lws_context_per_thread *vpt;
+#endif
+       struct lws_context_per_thread *pt;
+       struct lws_context *context;
+       int ret = 0, pa_events;
+       struct lws_pollfd *pfd;
+       int sampled_tid, tid;
+
+       if (!wsi)
+               return 0;
+
+       assert(wsi->position_in_fds_table == LWS_NO_FDS_POS ||
+              wsi->position_in_fds_table >= 0);
+
+       if (wsi->position_in_fds_table == LWS_NO_FDS_POS)
+               return 0;
+
+       if (((volatile struct lws *)wsi)->handling_pollout &&
+           !_and && _or == LWS_POLLOUT) {
+               /*
+                * Happening alongside service thread handling POLLOUT.
+                * The danger is when he is finished, he will disable POLLOUT,
+                * countermanding what we changed here.
+                *
+                * Instead of changing the fds, inform the service thread
+                * what happened, and ask it to leave POLLOUT active on exit
+                */
+               ((volatile struct lws *)wsi)->leave_pollout_active = 1;
+               /*
+                * by definition service thread is not in poll wait, so no need
+                * to cancel service
+                */
+
+               lwsl_debug("%s: using leave_pollout_active\n", __func__);
+
+               return 0;
+       }
+
+       context = wsi->context;
+       pt = &context->pt[(int)wsi->tsi];
+
+       assert(wsi->position_in_fds_table < (int)pt->fds_count);
+
+#if !defined(LWS_WITH_LIBUV) && \
+    !defined(LWS_WITH_LIBEV) && \
+    !defined(LWS_WITH_LIBEVENT)
+       /*
+        * This only applies when we use the default poll() event loop.
+        *
+        * BSD can revert pa->events at any time, when the kernel decides to
+        * exit from poll().  We can't protect against it using locking.
+        *
+        * Therefore we must check first if the service thread is in poll()
+        * wait; if so, we know we must be being called from a foreign thread,
+        * and we must keep a strictly ordered list of changes we made instead
+        * of trying to apply them, since when poll() exits, which may happen
+        * at any time it would revert our changes.
+        *
+        * The plat code will apply them when it leaves the poll() wait
+        * before doing anything else.
+        */
+
+       vpt = (volatile struct lws_context_per_thread *)pt;
+
+       vpt->foreign_spinlock = 1;
+       lws_memory_barrier();
+
+       if (vpt->inside_poll) {
+               struct lws_foreign_thread_pollfd *ftp, **ftp1;
+               /*
+                * We are certainly a foreign thread trying to change events
+                * while the service thread is in the poll() wait.
+                *
+                * Create a list of changes to be applied after poll() exit,
+                * instead of trying to apply them now.
+                */
+               ftp = lws_malloc(sizeof(*ftp), "ftp");
+               if (!ftp) {
+                       vpt->foreign_spinlock = 0;
+                       lws_memory_barrier();
+                       ret = -1;
+                       goto bail;
+               }
+
+               ftp->_and = _and;
+               ftp->_or = _or;
+               ftp->fd_index = wsi->position_in_fds_table;
+               ftp->next = NULL;
+
+               lws_pt_lock(pt, __func__);
+
+               /* place at END of list to maintain order */
+               ftp1 = (struct lws_foreign_thread_pollfd **)
+                                               &vpt->foreign_pfd_list;
+               while (*ftp1)
+                       ftp1 = &((*ftp1)->next);
+
+               *ftp1 = ftp;
+               vpt->foreign_spinlock = 0;
+               lws_memory_barrier();
+
+               lws_pt_unlock(pt);
+
+               lws_cancel_service_pt(wsi);
+
+               return 0;
+       }
+
+       vpt->foreign_spinlock = 0;
+       lws_memory_barrier();
+#endif
+
+       pfd = &pt->fds[wsi->position_in_fds_table];
+       pa->fd = wsi->desc.sockfd;
+       lwsl_debug("%s: wsi %p: fd %d events %d -> %d\n", __func__, wsi,
+                  pa->fd, pfd->events, (pfd->events & ~_and) | _or);
+       pa->prev_events = pfd->events;
+       pa->events = pfd->events = (pfd->events & ~_and) | _or;
+
+       if (wsi->http2_substream)
+               return 0;
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi,
+                                             LWS_CALLBACK_CHANGE_MODE_POLL_FD,
+                                             wsi->user_space, (void *)pa, 0)) {
+               ret = -1;
+               goto bail;
+       }
+#endif
+
+       if (context->event_loop_ops->io) {
+               if (_and & LWS_POLLIN)
+                       context->event_loop_ops->io(wsi,
+                                       LWS_EV_STOP | LWS_EV_READ);
+
+               if (_or & LWS_POLLIN)
+                       context->event_loop_ops->io(wsi,
+                                       LWS_EV_START | LWS_EV_READ);
+
+               if (_and & LWS_POLLOUT)
+                       context->event_loop_ops->io(wsi,
+                                       LWS_EV_STOP | LWS_EV_WRITE);
+
+               if (_or & LWS_POLLOUT)
+                       context->event_loop_ops->io(wsi,
+                                       LWS_EV_START | LWS_EV_WRITE);
+       }
+
+       /*
+        * if we changed something in this pollfd...
+        *   ... and we're running in a different thread context
+        *     than the service thread...
+        *       ... and the service thread is waiting ...
+        *         then cancel it to force a restart with our changed events
+        */
+       pa_events = pa->prev_events != pa->events;
+
+       if (pa_events) {
+               if (lws_plat_change_pollfd(context, wsi, pfd)) {
+                       lwsl_info("%s failed\n", __func__);
+                       ret = -1;
+                       goto bail;
+               }
+               sampled_tid = pt->service_tid;
+               if (sampled_tid && wsi->vhost) {
+                       tid = wsi->vhost->protocols[0].callback(wsi,
+                                    LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
+                       if (tid == -1) {
+                               ret = -1;
+                               goto bail;
+                       }
+                       if (tid != sampled_tid)
+                               lws_cancel_service_pt(wsi);
+               }
+       }
+
+bail:
+       return ret;
+}
+
+#ifndef LWS_NO_SERVER
+/*
+ * Enable or disable listen sockets on this pt globally...
+ * it's modulated according to the pt having space for a new accept.
+ */
+static void
+lws_accept_modulation(struct lws_context *context,
+                     struct lws_context_per_thread *pt, int allow)
+{
+       struct lws_vhost *vh = context->vhost_list;
+       struct lws_pollargs pa1;
+
+       while (vh) {
+               if (vh->lserv_wsi) {
+                       if (allow)
+                               _lws_change_pollfd(vh->lserv_wsi,
+                                          0, LWS_POLLIN, &pa1);
+                       else
+                               _lws_change_pollfd(vh->lserv_wsi,
+                                          LWS_POLLIN, 0, &pa1);
+               }
+               vh = vh->vhost_next;
+       }
+}
+#endif
+
+#if defined(_DEBUG)
+void
+__dump_fds(struct lws_context_per_thread *pt, const char *s)
+{
+       unsigned int n;
+
+       lwsl_warn("%s: fds_count %u, %s\n", __func__, pt->fds_count, s);
+
+       for (n = 0; n < pt->fds_count; n++) {
+               struct lws *wsi = wsi_from_fd(pt->context, pt->fds[n].fd);
+
+               lwsl_warn("  %d: fd %d, wsi %p, pos_in_fds: %d\n",
+                       n + 1, pt->fds[n].fd, wsi,
+                       wsi ? wsi->position_in_fds_table : -1);
+       }
+}
+#else
+#define __dump_fds(x, y)
+#endif
+
+int
+__insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
+{
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       struct lws_pollargs pa = { wsi->desc.sockfd, LWS_POLLIN, 0 };
+#endif
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       int ret = 0;
+
+//     __dump_fds(pt, "pre insert");
+
+       lwsl_debug("%s: %p: tsi=%d, sock=%d, pos-in-fds=%d\n",
+                 __func__, wsi, wsi->tsi, wsi->desc.sockfd, pt->fds_count);
+
+       if ((unsigned int)pt->fds_count >= context->fd_limit_per_thread) {
+               lwsl_err("Too many fds (%d vs %d)\n", context->max_fds,
+                               context->fd_limit_per_thread    );
+               return 1;
+       }
+
+#if !defined(_WIN32)
+       if (!wsi->context->max_fds_unrelated_to_ulimit &&
+           wsi->desc.sockfd - lws_plat_socket_offset() >= context->max_fds) {
+               lwsl_err("Socket fd %d is too high (%d) offset %d\n",
+                        wsi->desc.sockfd, context->max_fds,
+                        lws_plat_socket_offset());
+               return 1;
+       }
+#endif
+
+       assert(wsi);
+       assert(wsi->event_pipe || wsi->vhost);
+       assert(lws_socket_is_valid(wsi->desc.sockfd));
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
+                                          wsi->user_space, (void *) &pa, 1))
+               return -1;
+#endif
+
+       if (insert_wsi(context, wsi))
+               return -1;
+       pt->count_conns++;
+       wsi->position_in_fds_table = pt->fds_count;
+
+       pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd;
+       pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN;
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       pa.events = pt->fds[pt->fds_count].events;
+#endif
+
+       lws_plat_insert_socket_into_fds(context, wsi);
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+
+       /* external POLL support via protocol 0 */
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD,
+                                          wsi->user_space, (void *) &pa, 0))
+               ret =  -1;
+#endif
+#ifndef LWS_NO_SERVER
+       /* if no more room, defeat accepts on this thread */
+       if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1)
+               lws_accept_modulation(context, pt, 0);
+#endif
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
+                                          wsi->user_space, (void *)&pa, 1))
+               ret = -1;
+#endif
+
+//     __dump_fds(pt, "post insert");
+
+       return ret;
+}
+
+int
+__remove_wsi_socket_from_fds(struct lws *wsi)
+{
+       struct lws_context *context = wsi->context;
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       struct lws_pollargs pa = { wsi->desc.sockfd, 0, 0 };
+#endif
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws *end_wsi;
+       int v, m, ret = 0;
+
+//     __dump_fds(pt, "pre remove");
+
+#if !defined(_WIN32)
+       if (!wsi->context->max_fds_unrelated_to_ulimit &&
+           wsi->desc.sockfd - lws_plat_socket_offset() > context->max_fds) {
+               lwsl_err("fd %d too high (%d)\n", wsi->desc.sockfd,
+                        context->max_fds);
+
+               return 1;
+       }
+#endif
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       if (wsi->vhost && wsi->vhost->protocols &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
+                                          wsi->user_space, (void *)&pa, 1))
+               return -1;
+#endif
+
+       lws_same_vh_protocol_remove(wsi);
+
+       /* the guy who is to be deleted's slot index in pt->fds */
+       m = wsi->position_in_fds_table;
+       
+       /* these are the only valid possibilities for position_in_fds_table */
+       assert(m == LWS_NO_FDS_POS || (m >= 0 &&
+                                      (unsigned int)m < pt->fds_count));
+
+       if (context->event_loop_ops->io)
+               context->event_loop_ops->io(wsi,
+                                 LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE |
+                                 LWS_EV_PREPARE_DELETION);
+/*
+       lwsl_notice("%s: wsi=%p, skt=%d, fds pos=%d, end guy pos=%d, endfd=%d\n",
+                 __func__, wsi, wsi->desc.sockfd, wsi->position_in_fds_table,
+                 pt->fds_count, pt->fds[pt->fds_count - 1].fd); */
+
+       if (m != LWS_NO_FDS_POS) {
+               char fixup = 0;
+
+               assert(pt->fds_count && (unsigned int)m != pt->fds_count);
+
+               /* deletion guy's lws_lookup entry needs nuking */
+               delete_from_fd(context, wsi->desc.sockfd);
+
+               if ((unsigned int)m != pt->fds_count - 1) {
+                       /* have the last guy take up the now vacant slot */
+                       pt->fds[m] = pt->fds[pt->fds_count - 1];
+                       fixup = 1;
+               }
+
+               pt->fds[pt->fds_count - 1].fd = -1;
+
+               /* this decrements pt->fds_count */
+               lws_plat_delete_socket_from_fds(context, wsi, m);
+               pt->count_conns--;
+               if (fixup) {
+                       v = (int) pt->fds[m].fd;
+                       /* old end guy's "position in fds table" is now the
+                        * deletion guy's old one */
+                       end_wsi = wsi_from_fd(context, v);
+                       if (!end_wsi) {
+                               lwsl_err("no wsi for fd %d pos %d, "
+                                        "pt->fds_count=%d\n",
+                                        (int)pt->fds[m].fd, m, pt->fds_count);
+                               assert(0);
+                       } else
+                               end_wsi->position_in_fds_table = m;
+               }
+
+               /* removed wsi has no position any more */
+               wsi->position_in_fds_table = LWS_NO_FDS_POS;
+       }
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       /* remove also from external POLL support via protocol 0 */
+       if (lws_socket_is_valid(wsi->desc.sockfd) && wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD,
+                                             wsi->user_space, (void *) &pa, 0))
+               ret = -1;
+#endif
+
+#ifndef LWS_NO_SERVER
+       if (!context->being_destroyed &&
+           /* if this made some room, accept connects on this thread */
+           (unsigned int)pt->fds_count < context->fd_limit_per_thread - 1)
+               lws_accept_modulation(context, pt, 1);
+#endif
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
+                                             wsi->user_space, (void *) &pa, 1))
+               ret = -1;
+#endif
+
+//     __dump_fds(pt, "post remove");
+
+       return ret;
+}
+
+int
+__lws_change_pollfd(struct lws *wsi, int _and, int _or)
+{
+       struct lws_context *context;
+       struct lws_pollargs pa;
+       int ret = 0;
+
+       if (!wsi || (!wsi->protocol && !wsi->event_pipe) ||
+           wsi->position_in_fds_table == LWS_NO_FDS_POS)
+               return 0;
+
+       context = lws_get_context(wsi);
+       if (!context)
+               return 1;
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
+                                             wsi->user_space, (void *) &pa, 0))
+               return -1;
+#endif
+
+       ret = _lws_change_pollfd(wsi, _and, _or, &pa);
+
+#if defined(LWS_WITH_EXTERNAL_POLL)
+       if (wsi->vhost &&
+           wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
+                                          wsi->user_space, (void *) &pa, 0))
+               ret = -1;
+#endif
+
+       return ret;
+}
+
+int
+lws_change_pollfd(struct lws *wsi, int _and, int _or)
+{
+       struct lws_context_per_thread *pt;
+       int ret = 0;
+
+       pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_pt_lock(pt, __func__);
+       ret = __lws_change_pollfd(wsi, _and, _or);
+       lws_pt_unlock(pt);
+
+       return ret;
+}
+
+LWS_VISIBLE int
+lws_callback_on_writable(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt;
+
+       if (lwsi_state(wsi) == LRS_SHUTDOWN)
+               return 0;
+
+       if (wsi->socket_is_permanently_unusable)
+               return 0;
+
+       pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB_REQ, 1);
+#if defined(LWS_WITH_STATS)
+       if (!wsi->active_writable_req_us) {
+               wsi->active_writable_req_us = lws_now_usecs();
+               lws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1);
+       }
+#endif
+
+
+       if (wsi->role_ops->callback_on_writable) {
+               if (wsi->role_ops->callback_on_writable(wsi))
+                       return 1;
+               wsi = lws_get_network_wsi(wsi);
+       }
+
+       if (wsi->position_in_fds_table == LWS_NO_FDS_POS) {
+               lwsl_debug("%s: failed to find socket %d\n", __func__,
+                          wsi->desc.sockfd);
+               return -1;
+       }
+
+       if (__lws_change_pollfd(wsi, 0, LWS_POLLOUT))
+               return -1;
+
+       return 1;
+}
+
+
+/*
+ * stitch protocol choice into the vh protocol linked list
+ * We always insert ourselves at the start of the list
+ *
+ * X <-> B
+ * X <-> pAn <-> pB
+ *
+ * Illegal to attach more than once without detach inbetween
+ */
+void
+lws_same_vh_protocol_insert(struct lws *wsi, int n)
+{
+       lws_vhost_lock(wsi->vhost);
+
+       lws_dll2_remove(&wsi->same_vh_protocol);
+       lws_dll2_add_head(&wsi->same_vh_protocol,
+                         &wsi->vhost->same_vh_protocol_owner[n]);
+
+       wsi->bound_vhost_index = n;
+
+       lws_vhost_unlock(wsi->vhost);
+}
+
+void
+__lws_same_vh_protocol_remove(struct lws *wsi)
+{
+       if (wsi->vhost && wsi->vhost->same_vh_protocol_owner)
+               lws_dll2_remove(&wsi->same_vh_protocol);
+}
+
+void
+lws_same_vh_protocol_remove(struct lws *wsi)
+{
+       if (!wsi->vhost)
+               return;
+
+       lws_vhost_lock(wsi->vhost);
+
+       __lws_same_vh_protocol_remove(wsi);
+
+       lws_vhost_unlock(wsi->vhost);
+}
+
+
+LWS_VISIBLE int
+lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
+                                          const struct lws_protocols *protocol)
+{
+       struct lws *wsi;
+       int n;
+
+       if (protocol < vhost->protocols ||
+           protocol >= (vhost->protocols + vhost->count_protocols)) {
+               lwsl_err("%s: protocol %p is not from vhost %p (%p - %p)\n",
+                       __func__, protocol, vhost->protocols, vhost,
+                       (vhost->protocols + vhost->count_protocols));
+
+               return -1;
+       }
+
+       n = (int)(protocol - vhost->protocols);
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
+                       lws_dll2_get_head(&vhost->same_vh_protocol_owner[n])) {
+               wsi = lws_container_of(d, struct lws, same_vh_protocol);
+
+               assert(wsi->protocol == protocol);
+               lws_callback_on_writable(wsi);
+
+       } lws_end_foreach_dll_safe(d, d1);
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_callback_on_writable_all_protocol(const struct lws_context *context,
+                                     const struct lws_protocols *protocol)
+{
+       struct lws_vhost *vhost;
+       int n;
+
+       if (!context)
+               return 0;
+
+       vhost = context->vhost_list;
+
+       while (vhost) {
+               for (n = 0; n < vhost->count_protocols; n++)
+                       if (protocol->callback ==
+                            vhost->protocols[n].callback &&
+                           !strcmp(protocol->name, vhost->protocols[n].name))
+                               break;
+               if (n != vhost->count_protocols)
+                       lws_callback_on_writable_all_protocol_vhost(
+                               vhost, &vhost->protocols[n]);
+
+               vhost = vhost->vhost_next;
+       }
+
+       return 0;
+}
diff --git a/lib/core-net/private.h b/lib/core-net/private.h
new file mode 100644 (file)
index 0000000..7d5910e
--- /dev/null
@@ -0,0 +1,1166 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined(__LWS_CORE_NET_PRIVATE_H__)
+#define __LWS_CORE_NET_PRIVATE_H__
+
+#if !defined(_POSIX_C_SOURCE)
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include "roles/private.h"
+
+#ifdef LWS_WITH_IPV6
+#if defined(WIN32) || defined(_WIN32)
+#include <iphlpapi.h>
+#else
+#include <net/if.h>
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * All lws_tls...() functions must return this type, converting the
+ * native backend result and doing the extra work to determine which one
+ * as needed.
+ *
+ * Native TLS backend return codes are NOT ALLOWED outside the backend.
+ *
+ * Non-SSL mode also uses these types.
+ */
+enum lws_ssl_capable_status {
+       LWS_SSL_CAPABLE_ERROR                   = -1, /* it failed */
+       LWS_SSL_CAPABLE_DONE                    = 0,  /* it succeeded */
+       LWS_SSL_CAPABLE_MORE_SERVICE_READ       = -2, /* retry WANT_READ */
+       LWS_SSL_CAPABLE_MORE_SERVICE_WRITE      = -3, /* retry WANT_WRITE */
+       LWS_SSL_CAPABLE_MORE_SERVICE            = -4, /* general retry */
+};
+
+
+/*
+ *
+ *  ------ roles ------
+ *
+ */
+
+/* null-terminated array of pointers to roles lws built with */
+extern const struct lws_role_ops *available_roles[];
+
+#define LWS_FOR_EVERY_AVAILABLE_ROLE_START(xx) { \
+               const struct lws_role_ops **ppxx = available_roles; \
+               while (*ppxx) { \
+                       const struct lws_role_ops *xx = *ppxx++;
+
+#define LWS_FOR_EVERY_AVAILABLE_ROLE_END }}
+
+/*
+ *
+ *  ------ event_loop ops ------
+ *
+ */
+
+/* enums of socks version */
+enum socks_version {
+       SOCKS_VERSION_4 = 4,
+       SOCKS_VERSION_5 = 5
+};
+
+/* enums of subnegotiation version */
+enum socks_subnegotiation_version {
+       SOCKS_SUBNEGOTIATION_VERSION_1 = 1,
+};
+
+/* enums of socks commands */
+enum socks_command {
+       SOCKS_COMMAND_CONNECT = 1,
+       SOCKS_COMMAND_BIND = 2,
+       SOCKS_COMMAND_UDP_ASSOCIATE = 3
+};
+
+/* enums of socks address type */
+enum socks_atyp {
+       SOCKS_ATYP_IPV4 = 1,
+       SOCKS_ATYP_DOMAINNAME = 3,
+       SOCKS_ATYP_IPV6 = 4
+};
+
+/* enums of socks authentication methods */
+enum socks_auth_method {
+       SOCKS_AUTH_NO_AUTH = 0,
+       SOCKS_AUTH_GSSAPI = 1,
+       SOCKS_AUTH_USERNAME_PASSWORD = 2
+};
+
+/* enums of subnegotiation status */
+enum socks_subnegotiation_status {
+       SOCKS_SUBNEGOTIATION_STATUS_SUCCESS = 0,
+};
+
+/* enums of socks request reply */
+enum socks_request_reply {
+       SOCKS_REQUEST_REPLY_SUCCESS = 0,
+       SOCKS_REQUEST_REPLY_FAILURE_GENERAL = 1,
+       SOCKS_REQUEST_REPLY_CONNECTION_NOT_ALLOWED = 2,
+       SOCKS_REQUEST_REPLY_NETWORK_UNREACHABLE = 3,
+       SOCKS_REQUEST_REPLY_HOST_UNREACHABLE = 4,
+       SOCKS_REQUEST_REPLY_CONNECTION_REFUSED = 5,
+       SOCKS_REQUEST_REPLY_TTL_EXPIRED = 6,
+       SOCKS_REQUEST_REPLY_COMMAND_NOT_SUPPORTED = 7,
+       SOCKS_REQUEST_REPLY_ATYP_NOT_SUPPORTED = 8
+};
+
+/* enums used to generate socks messages */
+enum socks_msg_type {
+       /* greeting */
+       SOCKS_MSG_GREETING,
+       /* credential, user name and password */
+       SOCKS_MSG_USERNAME_PASSWORD,
+       /* connect command */
+       SOCKS_MSG_CONNECT
+};
+
+enum {
+       LWS_RXFLOW_ALLOW = (1 << 0),
+       LWS_RXFLOW_PENDING_CHANGE = (1 << 1),
+};
+
+enum lws_parser_return {
+       LPR_OK          = 0,
+       LPR_FAIL        = -1,
+       LPR_DO_FALLBACK = 2,
+       LPR_FORBIDDEN   = -2
+};
+
+enum pmd_return {
+       PMDR_UNKNOWN,
+       PMDR_DID_NOTHING,
+       PMDR_HAS_PENDING,
+       PMDR_EMPTY_NONFINAL,
+       PMDR_EMPTY_FINAL,
+
+       PMDR_FAILED = -1
+};
+
+typedef union {
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 sa6;
+#endif
+       struct sockaddr_in sa4;
+} sockaddr46;
+
+
+#if defined(LWS_WITH_PEER_LIMITS)
+struct lws_peer {
+       struct lws_peer *next;
+       struct lws_peer *peer_wait_list;
+
+       time_t time_created;
+       time_t time_closed_all;
+
+       uint8_t addr[32];
+       uint32_t hash;
+       uint32_t count_wsi;
+       uint32_t total_wsi;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       struct lws_peer_role_http http;
+#endif
+
+       uint8_t af;
+};
+#endif
+
+enum {
+       LWS_EV_READ = (1 << 0),
+       LWS_EV_WRITE = (1 << 1),
+       LWS_EV_START = (1 << 2),
+       LWS_EV_STOP = (1 << 3),
+
+       LWS_EV_PREPARE_DELETION = (1u << 31),
+};
+
+#ifdef LWS_WITH_IPV6
+#define LWS_IPV6_ENABLED(vh) \
+       (!lws_check_opt(vh->context->options, LWS_SERVER_OPTION_DISABLE_IPV6) && \
+        !lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_IPV6))
+#else
+#define LWS_IPV6_ENABLED(context) (0)
+#endif
+
+#ifdef LWS_WITH_UNIX_SOCK
+#define LWS_UNIX_SOCK_ENABLED(vhost) \
+       (vhost->options & LWS_SERVER_OPTION_UNIX_SOCK)
+#else
+#define LWS_UNIX_SOCK_ENABLED(vhost) (0)
+#endif
+
+enum uri_path_states {
+       URIPS_IDLE,
+       URIPS_SEEN_SLASH,
+       URIPS_SEEN_SLASH_DOT,
+       URIPS_SEEN_SLASH_DOT_DOT,
+};
+
+enum uri_esc_states {
+       URIES_IDLE,
+       URIES_SEEN_PERCENT,
+       URIES_SEEN_PERCENT_H1,
+};
+
+
+#ifndef LWS_NO_CLIENT
+struct client_info_stash {
+       char *address;
+       char *path;
+       char *host;
+       char *origin;
+       char *protocol;
+       char *method;
+       char *iface;
+       char *alpn;
+       void *opaque_user_data; /* not allocated or freed by lws */
+};
+#endif
+
+#define lws_wsi_is_udp(___wsi) (!!___wsi->udp)
+
+#define LWS_H2_FRAME_HEADER_LENGTH 9
+
+int
+__lws_sul_insert(lws_dll2_owner_t *own, lws_sorted_usec_list_t *sul,
+                lws_usec_t us);
+
+lws_usec_t
+__lws_sul_check(lws_dll2_owner_t *own, lws_usec_t usnow);
+
+struct lws_timed_vh_protocol {
+       struct lws_timed_vh_protocol    *next;
+       lws_sorted_usec_list_t          sul;
+       const struct lws_protocols      *protocol;
+       struct lws_vhost *vhost; /* only used for pending processing */
+       int                             reason;
+       int                             tsi_req;
+};
+
+/*
+ * lws_dsh
+*/
+
+typedef struct lws_dsh_obj_head {
+       lws_dll2_owner_t                owner;
+       int                             kind;
+} lws_dsh_obj_head_t;
+
+typedef struct lws_dsh lws_dsh_t;
+
+typedef struct lws_dsh_obj {
+       lws_dll2_t                      list;   /* must be first */
+       lws_dsh_t                       *dsh;   /* invalid when on free list */
+       size_t                          size;   /* invalid when on free list */
+       size_t                          asize;
+} lws_dsh_obj_t;
+
+typedef struct lws_dsh {
+       lws_dll2_t                      list;
+       uint8_t                         *buf;
+       lws_dsh_obj_head_t              *oha;   /* array of object heads/kind */
+       size_t                          buffer_size;
+       size_t                          locally_in_use;
+       size_t                          locally_free;
+       int                             count_kinds;
+       uint8_t                         being_destroyed;
+       /*
+        * Overallocations at create:
+        *
+        *  - the buffer itself
+        *  - the object heads array
+        */
+} lws_dsh_t;
+
+/*
+ * so we can have n connections being serviced simultaneously,
+ * these things need to be isolated per-thread.
+ */
+
+struct lws_context_per_thread {
+#if LWS_MAX_SMP > 1
+       pthread_mutex_t lock_stats;
+       struct lws_mutex_refcount mr;
+       pthread_t self;
+#endif
+       struct lws_dll2_owner dll_buflist_owner;  /* guys with pending rxflow */
+       struct lws_dll2_owner seq_owner;           /* list of lws_sequencer-s */
+
+       struct lws_dll2_owner pt_sul_owner;
+
+#if defined (LWS_WITH_SEQUENCER)
+       lws_sorted_usec_list_t sul_seq_heartbeat;
+#endif
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       lws_sorted_usec_list_t sul_ah_lifecheck;
+#endif
+#if defined(LWS_WITH_TLS) && !defined(LWS_NO_SERVER)
+       lws_sorted_usec_list_t sul_tls;
+#endif
+#if defined(LWS_PLAT_UNIX)
+       lws_sorted_usec_list_t sul_plat;
+#endif
+#if defined(LWS_WITH_STATS)
+       uint64_t lws_stats[LWSSTATS_SIZE];
+       int updated;
+       lws_sorted_usec_list_t sul_stats;
+#endif
+#if defined(LWS_WITH_PEER_LIMITS)
+       lws_sorted_usec_list_t sul_peer_limits;
+#endif
+
+#if defined(LWS_WITH_TLS)
+       struct lws_pt_tls tls;
+#endif
+       struct lws *fake_wsi;   /* used for callbacks where there's no wsi */
+
+       struct lws_context *context;
+
+       /*
+        * usable by anything in the service code, but only if the scope
+        * does not last longer than the service action (since next service
+        * of any socket can likewise use it and overwrite)
+        */
+       unsigned char *serv_buf;
+
+       struct lws_pollfd *fds;
+       volatile struct lws_foreign_thread_pollfd * volatile foreign_pfd_list;
+#ifdef _WIN32
+       WSAEVENT events;
+       CRITICAL_SECTION interrupt_lock;
+#endif
+       lws_sockfd_type dummy_pipe_fds[2];
+       struct lws *pipe_wsi;
+
+       /* --- role based members --- */
+
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+       struct lws_pt_role_ws ws;
+#endif
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       struct lws_pt_role_http http;
+#endif
+#if defined(LWS_ROLE_DBUS)
+       struct lws_pt_role_dbus dbus;
+#endif
+
+       /* --- event library based members --- */
+
+#if defined(LWS_WITH_LIBEV)
+       struct lws_pt_eventlibs_libev ev;
+#endif
+#if defined(LWS_WITH_LIBUV)
+       struct lws_pt_eventlibs_libuv uv;
+#endif
+#if defined(LWS_WITH_LIBEVENT)
+       struct lws_pt_eventlibs_libevent event;
+#endif
+
+#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || \
+    defined(LWS_WITH_LIBEVENT)
+       struct lws_signal_watcher w_sigint;
+#endif
+
+       /* --- */
+
+       unsigned long count_conns;
+       unsigned int fds_count;
+
+       /*
+        * set to the Thread ID that's doing the service loop just before entry
+        * to poll indicates service thread likely idling in poll()
+        * volatile because other threads may check it as part of processing
+        * for pollfd event change.
+        */
+       volatile int service_tid;
+       int service_tid_detected;
+
+       volatile unsigned char inside_poll;
+       volatile unsigned char foreign_spinlock;
+
+       unsigned char tid;
+
+       unsigned char inside_service:1;
+       unsigned char event_loop_foreign:1;
+       unsigned char event_loop_destroy_processing_done:1;
+#ifdef _WIN32
+       unsigned char interrupt_requested:1;
+#endif
+};
+
+struct lws_conn_stats {
+       unsigned long long rx, tx;
+       unsigned long h1_conn, h1_trans, h2_trans, ws_upg, h2_alpn, h2_subs,
+                     h2_upg, rejected;
+};
+
+/*
+ * virtual host -related context information
+ *   vhostwide SSL context
+ *   vhostwide proxy
+ *
+ * hierarchy:
+ *
+ * context -> vhost -> wsi
+ *
+ * incoming connection non-SSL vhost binding:
+ *
+ *    listen socket -> wsi -> select vhost after first headers
+ *
+ * incoming connection SSL vhost binding:
+ *
+ *    SSL SNI -> wsi -> bind after SSL negotiation
+ */
+
+
+struct lws_vhost {
+#if !defined(LWS_WITHOUT_CLIENT)
+       char proxy_basic_auth_token[128];
+#endif
+#if LWS_MAX_SMP > 1
+       pthread_mutex_t lock;
+       char close_flow_vs_tsi[LWS_MAX_SMP];
+#endif
+
+#if defined(LWS_ROLE_H2)
+       struct lws_vhost_role_h2 h2;
+#endif
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       struct lws_vhost_role_http http;
+#endif
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+       struct lws_vhost_role_ws ws;
+#endif
+
+#if defined(LWS_WITH_SOCKS5)
+       char socks_proxy_address[128];
+       char socks_user[96];
+       char socks_password[96];
+#endif
+#if defined(LWS_WITH_LIBEV)
+       struct lws_io_watcher w_accept;
+#endif
+       struct lws_conn_stats conn_stats;
+       struct lws_context *context;
+       struct lws_vhost *vhost_next;
+
+       struct lws *lserv_wsi;
+       const char *name;
+       const char *iface;
+       const char *listen_accept_role;
+       const char *listen_accept_protocol;
+       const char *unix_socket_perms;
+
+       void (*finalize)(struct lws_vhost *vh, void *arg);
+       void *finalize_arg;
+
+#if !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
+       int bind_iface;
+#endif
+       const struct lws_protocols *protocols;
+       void **protocol_vh_privs;
+       const struct lws_protocol_vhost_options *pvo;
+       const struct lws_protocol_vhost_options *headers;
+       struct lws_dll2_owner *same_vh_protocol_owner;
+       struct lws_vhost *no_listener_vhost_list;
+       struct lws_dll2_owner abstract_instances_owner;
+
+#if !defined(LWS_NO_CLIENT)
+       struct lws_dll2_owner dll_cli_active_conns_owner;
+#endif
+
+#if defined(LWS_WITH_TLS)
+       struct lws_vhost_tls tls;
+#endif
+
+       struct lws_timed_vh_protocol *timed_vh_protocol_list;
+       void *user;
+
+       int listen_port;
+
+#if defined(LWS_WITH_SOCKS5)
+       unsigned int socks_proxy_port;
+#endif
+       unsigned int options;
+       int count_protocols;
+       int ka_time;
+       int ka_probes;
+       int ka_interval;
+       int keepalive_timeout;
+       int timeout_secs_ah_idle;
+
+       int count_bound_wsi;
+
+#ifdef LWS_WITH_ACCESS_LOG
+       int log_fd;
+#endif
+
+       unsigned int allocated_vhost_protocols:1;
+       unsigned int created_vhost_protocols:1;
+       unsigned int being_destroyed:1;
+
+       unsigned char default_protocol_index;
+       unsigned char raw_protocol_index;
+};
+
+void
+__lws_vhost_destroy2(struct lws_vhost *vh);
+
+struct lws {
+       /* structs */
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       struct _lws_http_mode_related http;
+#endif
+#if defined(LWS_ROLE_H2)
+       struct _lws_h2_related h2;
+#endif
+#if defined(LWS_ROLE_WS)
+       struct _lws_websocket_related *ws; /* allocated if we upgrade to ws */
+       lws_sorted_usec_list_t sul_ping;
+#endif
+#if defined(LWS_ROLE_DBUS)
+       struct _lws_dbus_mode_related dbus;
+#endif
+
+
+       const struct lws_role_ops *role_ops;
+       lws_wsi_state_t wsistate;
+       lws_wsi_state_t wsistate_pre_close;
+
+       /* lifetime members */
+
+#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || \
+    defined(LWS_WITH_LIBEVENT)
+       struct lws_io_watcher w_read;
+#endif
+#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBEVENT)
+       struct lws_io_watcher w_write;
+#endif
+
+       lws_sorted_usec_list_t sul_timeout;
+       lws_sorted_usec_list_t sul_hrtimer;
+
+       /* pointers */
+
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       struct lws *parent; /* points to parent, if any */
+       struct lws *child_list; /* points to first child */
+       struct lws *sibling_list; /* subsequent children at same level */
+
+       const struct lws_protocols *protocol;
+       struct lws_dll2 same_vh_protocol;
+
+       lws_seq_t *seq; /* associated sequencer if any */
+
+       struct lws_dll2 dll_buflist; /* guys with pending rxflow */
+
+#if defined(LWS_WITH_THREADPOOL)
+       struct lws_threadpool_task *tp_task;
+#endif
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       struct lws_peer *peer;
+#endif
+
+       struct lws_udp *udp;
+#ifndef LWS_NO_CLIENT
+       struct client_info_stash *stash;
+       char *cli_hostname_copy;
+       struct lws_dll2 dll_cli_active_conns;
+       struct lws_dll2_owner dll2_cli_txn_queue_owner;
+       struct lws_dll2 dll2_cli_txn_queue;
+#endif
+       void *user_space;
+       void *opaque_parent_data;
+       void *opaque_user_data;
+
+       struct lws_buflist *buflist;            /* input-side buflist */
+       struct lws_buflist *buflist_out;        /* output-side buflist */
+
+#if defined(LWS_WITH_TLS)
+       struct lws_lws_tls tls;
+#endif
+
+       lws_sock_file_fd_type desc; /* .filefd / .sockfd */
+#if defined(LWS_WITH_STATS)
+       uint64_t active_writable_req_us;
+#if defined(LWS_WITH_TLS)
+       uint64_t accept_start_us;
+#endif
+#endif
+
+#ifdef LWS_LATENCY
+       unsigned long action_start;
+       unsigned long latency_start;
+#endif
+
+       /* ints */
+#define LWS_NO_FDS_POS (-1)
+       int position_in_fds_table;
+
+#ifndef LWS_NO_CLIENT
+       int chunk_remaining;
+#endif
+       unsigned int cache_secs;
+
+       unsigned int hdr_parsing_completed:1;
+       unsigned int http2_substream:1;
+       unsigned int upgraded_to_http2:1;
+       unsigned int h2_stream_carries_ws:1;
+       unsigned int h2_stream_carries_sse:1;
+       unsigned int seen_nonpseudoheader:1;
+       unsigned int listener:1;
+       unsigned int user_space_externally_allocated:1;
+       unsigned int socket_is_permanently_unusable:1;
+       unsigned int rxflow_change_to:2;
+       unsigned int conn_stat_done:1;
+       unsigned int cache_reuse:1;
+       unsigned int cache_revalidate:1;
+       unsigned int cache_intermediaries:1;
+       unsigned int favoured_pollin:1;
+       unsigned int sending_chunked:1;
+       unsigned int interpreting:1;
+       unsigned int already_did_cce:1;
+       unsigned int told_user_closed:1;
+       unsigned int told_event_loop_closed:1;
+       unsigned int waiting_to_send_close_frame:1;
+       unsigned int close_needs_ack:1;
+       unsigned int ipv6: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;
+       unsigned int event_pipe:1;
+       unsigned int handling_404:1;
+       unsigned int protocol_bind_balance:1;
+       unsigned int unix_skt:1;
+       unsigned int close_when_buffered_out_drained:1;
+       unsigned int h1_ws_proxied;
+       unsigned int proxied_ws_parent;
+
+       unsigned int could_have_pending:1; /* detect back-to-back writes */
+       unsigned int outer_will_close:1;
+       unsigned int shadow:1; /* we do not control fd lifecycle at all */
+
+#ifdef LWS_WITH_ACCESS_LOG
+       unsigned int access_log_pending:1;
+#endif
+#ifndef LWS_NO_CLIENT
+       unsigned int do_ws:1; /* whether we are doing http or ws flow */
+       unsigned int chunked:1; /* if the clientside connection is chunked */
+       unsigned int client_rx_avail:1;
+       unsigned int client_http_body_pending:1;
+       unsigned int transaction_from_pipeline_queue:1;
+       unsigned int keepalive_active:1;
+       unsigned int keepalive_rejected:1;
+       unsigned int client_pipeline:1;
+       unsigned int client_h2_alpn:1;
+       unsigned int client_h2_substream:1;
+#endif
+
+#ifdef _WIN32
+       unsigned int sock_send_blocking:1;
+#endif
+
+#ifndef LWS_NO_CLIENT
+       unsigned short c_port;
+#endif
+
+       /* chars */
+
+       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 tsi; /* thread service index we belong to */
+       char protocol_interpret_idx;
+       char redirects;
+       uint8_t rxflow_bitmap;
+       uint8_t bound_vhost_index;
+#ifdef LWS_WITH_CGI
+       char cgi_channel; /* which of stdin/out/err */
+       char hdr_state;
+#endif
+#ifndef LWS_NO_CLIENT
+       char chunk_parser; /* enum lws_chunk_parser */
+#endif
+#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT)
+       char reason_bf; /* internal writeable callback reason bitfield */
+#endif
+#if defined(LWS_WITH_STATS) && defined(LWS_WITH_TLS)
+       char seen_rx;
+#endif
+       uint8_t immortal_substream_count;
+       /* volatile to make sure code is aware other thread can change */
+       volatile char handling_pollout;
+       volatile char leave_pollout_active;
+#if LWS_MAX_SMP > 1
+       volatile char undergoing_init_from_other_pt;
+#endif
+
+};
+
+#define lws_is_flowcontrolled(w) (!!(wsi->rxflow_bitmap))
+
+void
+lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt);
+
+const struct lws_role_ops *
+lws_role_by_name(const char *name);
+
+LWS_EXTERN int
+lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
+               const char *iface, int ipv6_allowed);
+
+#if defined(LWS_WITH_IPV6)
+LWS_EXTERN unsigned long
+lws_get_addr_scope(const char *ipaddr);
+#endif
+
+LWS_EXTERN void
+lws_close_free_wsi(struct lws *wsi, enum lws_close_status, const char *caller);
+LWS_EXTERN void
+__lws_close_free_wsi(struct lws *wsi, enum lws_close_status, const char *caller);
+
+LWS_EXTERN void
+__lws_free_wsi(struct lws *wsi);
+
+#if LWS_MAX_SMP > 1
+
+static LWS_INLINE void
+lws_pt_mutex_init(struct lws_context_per_thread *pt)
+{
+       lws_mutex_refcount_init(&pt->mr);
+       pthread_mutex_init(&pt->lock_stats, NULL);
+}
+
+static LWS_INLINE void
+lws_pt_mutex_destroy(struct lws_context_per_thread *pt)
+{
+       pthread_mutex_destroy(&pt->lock_stats);
+       lws_mutex_refcount_destroy(&pt->mr);
+}
+
+#define lws_pt_lock(pt, reason) lws_mutex_refcount_lock(&pt->mr, reason)
+#define lws_pt_unlock(pt) lws_mutex_refcount_unlock(&pt->mr)
+
+static LWS_INLINE void
+lws_pt_stats_lock(struct lws_context_per_thread *pt)
+{
+       pthread_mutex_lock(&pt->lock_stats);
+}
+
+static LWS_INLINE void
+lws_pt_stats_unlock(struct lws_context_per_thread *pt)
+{
+       pthread_mutex_unlock(&pt->lock_stats);
+}
+#endif
+
+/*
+ * EXTENSIONS
+ */
+
+#if defined(LWS_WITHOUT_EXTENSIONS)
+#define lws_any_extension_handled(_a, _b, _c, _d) (0)
+#define lws_ext_cb_active(_a, _b, _c, _d) (0)
+#define lws_ext_cb_all_exts(_a, _b, _c, _d, _e) (0)
+#define lws_issue_raw_ext_access lws_issue_raw
+#define lws_context_init_extensions(_a, _b)
+#endif
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_client_interpret_server_handshake(struct lws *wsi);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len);
+
+LWS_EXTERN void
+lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state,
+                   const struct lws_role_ops *ops);
+
+int
+lws_http_to_fallback(struct lws *wsi, unsigned char *buf, size_t len);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+user_callback_handle_rxflow(lws_callback_function, struct lws *wsi,
+                           enum lws_callback_reasons reason, void *user,
+                           void *in, size_t len);
+
+LWS_EXTERN int
+lws_plat_set_nonblocking(int fd);
+
+LWS_EXTERN int
+lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd,
+                           int unix_skt);
+
+LWS_EXTERN int
+lws_plat_check_connection_error(struct lws *wsi);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_header_table_attach(struct lws *wsi, int autoservice);
+
+LWS_EXTERN int
+lws_header_table_detach(struct lws *wsi, int autoservice);
+LWS_EXTERN int
+__lws_header_table_detach(struct lws *wsi, int autoservice);
+
+LWS_EXTERN void
+lws_header_table_reset(struct lws *wsi, int autoservice);
+
+void
+__lws_header_table_reset(struct lws *wsi, int autoservice);
+
+LWS_EXTERN char * LWS_WARN_UNUSED_RESULT
+lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ensure_user_space(struct lws *wsi);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_change_pollfd(struct lws *wsi, int _and, int _or);
+
+#ifndef LWS_NO_SERVER
+ int _lws_vhost_init_server(const struct lws_context_creation_info *info,
+                             struct lws_vhost *vhost);
+ LWS_EXTERN struct lws_vhost *
+ lws_select_vhost(struct lws_context *context, int port, const char *servername);
+ LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+ lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len);
+ LWS_EXTERN void
+ lws_server_get_canonical_hostname(struct lws_context *context,
+                                  const struct lws_context_creation_info *info);
+#else
+ #define _lws_vhost_init_server(_a, _b) (0)
+ #define lws_parse_ws(_a, _b, _c) (0)
+ #define lws_server_get_canonical_hostname(_a, _b)
+#endif
+
+LWS_EXTERN int
+__remove_wsi_socket_from_fds(struct lws *wsi);
+
+enum {
+       LWSRXFC_ERROR = -1,
+       LWSRXFC_CACHED = 0,
+       LWSRXFC_ADDITIONAL = 1,
+       LWSRXFC_TRIMMED = 2,
+};
+
+LWS_EXTERN int
+lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len);
+
+LWS_EXTERN int
+lws_service_flag_pending(struct lws_context *context, int tsi);
+
+LWS_EXTERN void
+lws_client_stash_destroy(struct lws *wsi);
+
+static LWS_INLINE int
+lws_has_buffered_out(struct lws *wsi) { return !!wsi->buflist_out; }
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ws_client_rx_sm(struct lws *wsi, unsigned char c);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_parse(struct lws *wsi, unsigned char *buf, int *len);
+
+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);
+
+LWS_EXTERN void
+__lws_close_free_wsi_final(struct lws *wsi);
+LWS_EXTERN void
+lws_libuv_closehandle(struct lws *wsi);
+LWS_EXTERN int
+lws_libuv_check_watcher_active(struct lws *wsi);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_plat_plugins_init(struct lws_context * context, const char * const *d);
+
+LWS_VISIBLE LWS_EXTERN int
+lws_plat_plugins_destroy(struct lws_context * context);
+
+LWS_EXTERN void
+lws_restart_ws_ping_pong_timer(struct lws *wsi);
+
+struct lws *
+lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd);
+
+void
+lws_vhost_bind_wsi(struct lws_vhost *vh, struct lws *wsi);
+void
+lws_vhost_unbind_wsi(struct lws *wsi);
+
+void
+__lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs);
+int
+__lws_change_pollfd(struct lws *wsi, int _and, int _or);
+
+
+int
+lws_callback_as_writeable(struct lws *wsi);
+
+int
+lws_role_call_client_bind(struct lws *wsi,
+                         const struct lws_client_connect_info *i);
+void
+lws_remove_child_from_any_parent(struct lws *wsi);
+
+char *
+lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1);
+int
+lws_client_ws_upgrade(struct lws *wsi, const char **cce);
+int
+lws_create_client_ws_object(const struct lws_client_connect_info *i,
+                           struct lws *wsi);
+int
+lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len);
+int
+lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn);
+int
+lws_tls_server_conn_alpn(struct lws *wsi);
+
+int
+lws_ws_client_rx_sm_block(struct lws *wsi, unsigned char **buf, size_t len);
+void
+lws_destroy_event_pipe(struct lws *wsi);
+
+/* socks */
+int
+socks_generate_msg(struct lws *wsi, enum socks_msg_type type, ssize_t *msg_len);
+
+
+void
+lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs);
+
+LWS_EXTERN int
+__lws_timed_callback_remove(struct lws_vhost *vh, struct lws_timed_vh_protocol *p);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+__insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len);
+
+LWS_EXTERN lws_usec_t
+__lws_seq_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow);
+
+LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
+lws_client_connect_2(struct lws *wsi);
+
+LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT
+lws_client_reset(struct lws **wsi, int ssl, const char *address, int port,
+                const char *path, const char *host);
+
+LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
+lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi);
+
+LWS_EXTERN char * LWS_WARN_UNUSED_RESULT
+lws_generate_client_handshake(struct lws *wsi, char *pkt);
+
+LWS_EXTERN int
+lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd);
+
+LWS_EXTERN struct lws *
+lws_http_client_connect_via_info2(struct lws *wsi);
+
+
+#ifndef LWS_NO_CLIENT
+LWS_EXTERN int lws_client_socket_service(struct lws *wsi,
+                                        struct lws_pollfd *pollfd,
+                                        struct lws *wsi_conn);
+LWS_EXTERN struct lws *
+lws_client_wsi_effective(struct lws *wsi);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_http_transaction_completed_client(struct lws *wsi);
+#if !defined(LWS_WITH_TLS)
+       #define lws_context_init_client_ssl(_a, _b) (0)
+#endif
+LWS_EXTERN void
+lws_decode_ssl_error(void);
+#else
+#define lws_context_init_client_ssl(_a, _b) (0)
+#endif
+
+LWS_EXTERN int
+__lws_rx_flow_control(struct lws *wsi);
+
+LWS_EXTERN int
+_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa);
+
+#ifndef LWS_NO_SERVER
+LWS_EXTERN int
+lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len);
+#else
+#define lws_server_socket_service(_b, _c) (0)
+#define lws_handshake_server(_a, _b, _c) (0)
+#endif
+
+#ifdef LWS_WITH_ACCESS_LOG
+LWS_EXTERN int
+lws_access_log(struct lws *wsi);
+LWS_EXTERN void
+lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int len, int meth);
+#else
+#define lws_access_log(_a)
+#endif
+
+LWS_EXTERN int
+lws_cgi_kill_terminated(struct lws_context_per_thread *pt);
+
+LWS_EXTERN void
+lws_cgi_remove_and_kill(struct lws *wsi);
+
+LWS_EXTERN void
+lws_plat_delete_socket_from_fds(struct lws_context *context,
+                               struct lws *wsi, int m);
+LWS_EXTERN void
+lws_plat_insert_socket_into_fds(struct lws_context *context,
+                               struct lws *wsi);
+
+LWS_EXTERN int
+lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi,
+                      struct lws_pollfd *pfd);
+
+
+int
+lws_plat_pipe_create(struct lws *wsi);
+int
+lws_plat_pipe_signal(struct lws *wsi);
+void
+lws_plat_pipe_close(struct lws *wsi);
+
+LWS_EXTERN void
+lws_add_wsi_to_draining_ext_list(struct lws *wsi);
+LWS_EXTERN void
+lws_remove_wsi_from_draining_ext_list(struct lws *wsi);
+LWS_EXTERN int
+lws_poll_listen_fd(struct lws_pollfd *fd);
+LWS_EXTERN int
+lws_plat_service(struct lws_context *context, int timeout_ms);
+LWS_EXTERN LWS_VISIBLE int
+_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi);
+
+LWS_EXTERN int
+lws_pthread_self_to_tsi(struct lws_context *context);
+LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT
+lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_plat_inet_pton(int af, const char *src, void *dst);
+
+LWS_EXTERN void
+lws_same_vh_protocol_remove(struct lws *wsi);
+LWS_EXTERN void
+__lws_same_vh_protocol_remove(struct lws *wsi);
+LWS_EXTERN void
+lws_same_vh_protocol_insert(struct lws *wsi, int n);
+
+void
+lws_seq_destroy_all_on_pt(struct lws_context_per_thread *pt);
+
+LWS_EXTERN int
+lws_broadcast(struct lws_context_per_thread *pt, int reason, void *in, size_t len);
+
+#if defined(LWS_WITH_STATS)
+ void
+ lws_stats_bump(struct lws_context_per_thread *pt, int i, uint64_t bump);
+ void
+ lws_stats_max(struct lws_context_per_thread *pt, int index, uint64_t val);
+#else
+ static LWS_INLINE uint64_t lws_stats_bump(
+               struct lws_context_per_thread *pt, int index, uint64_t bump) {
+       (void)pt; (void)index; (void)bump; return 0; }
+ static LWS_INLINE uint64_t lws_stats_max(
+               struct lws_context_per_thread *pt, int index, uint64_t val) {
+       (void)pt; (void)index; (void)val; return 0; }
+#endif
+
+
+
+#if defined(LWS_WITH_PEER_LIMITS)
+void
+lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer);
+int
+lws_peer_confirm_ah_attach_ok(struct lws_context *context,
+                             struct lws_peer *peer);
+void
+lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer);
+void
+lws_peer_cull_peer_wait_list(struct lws_context *context);
+struct lws_peer *
+lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd);
+void
+lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer,
+                struct lws *wsi);
+void
+lws_peer_dump_from_wsi(struct lws *wsi);
+#endif
+
+#ifdef LWS_WITH_HUBBUB
+hubbub_error
+html_parser_cb(const hubbub_token *token, void *pw);
+#endif
+
+int
+lws_threadpool_tsi_context(struct lws_context *context, int tsi);
+
+void
+__lws_wsi_remove_from_sul(struct lws *wsi);
+
+int
+lws_seq_pt_init(struct lws_context_per_thread *pt);
+
+int
+lws_buflist_aware_read(struct lws_context_per_thread *pt, struct lws *wsi,
+                      struct lws_tokens *ebuf);
+int
+lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used,
+                         int buffered);
+
+extern const struct lws_protocols protocol_abs_client_raw_skt,
+                                 protocol_abs_client_unit_test;
+
+void
+lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/lib/core-net/sequencer.c b/lib/core-net/sequencer.c
new file mode 100644 (file)
index 0000000..fbfd752
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * libwebsockets - lib/core-net/sequencer.c
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * per pending event
+ */
+typedef struct lws_seq_event {
+       struct lws_dll2                 seq_event_list;
+
+       void                            *data;
+       void                            *aux;
+       lws_seq_events_t                e;
+} lws_seq_event_t;
+
+/*
+ * per sequencer
+ */
+typedef struct lws_sequencer {
+       struct lws_dll2                 seq_list;
+
+       lws_sorted_usec_list_t          sul_timeout;
+       lws_sorted_usec_list_t          sul_pending;
+
+       struct lws_dll2_owner           seq_event_owner;
+       struct lws_context_per_thread   *pt;
+       lws_seq_event_cb                cb;
+       const char                      *name;
+       const lws_retry_bo_t            *retry;
+
+       lws_usec_t                      time_created;
+       lws_usec_t                      timeout; /* 0 or time we timeout */
+
+       char                            going_down;
+} lws_seq_t;
+
+#define QUEUE_SANITY_LIMIT 10
+
+static void
+lws_sul_seq_heartbeat_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws_context_per_thread *pt = lws_container_of(sul,
+                       struct lws_context_per_thread, sul_seq_heartbeat);
+
+       /* send every sequencer a heartbeat message... it can ignore it */
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+                                  lws_dll2_get_head(&pt->seq_owner)) {
+               lws_seq_t *s = lws_container_of(p, lws_seq_t, seq_list);
+
+               /* queue the message to inform the sequencer */
+               lws_seq_queue_event(s, LWSSEQ_HEARTBEAT, NULL, NULL);
+
+       } lws_end_foreach_dll_safe(p, tp);
+
+       /* schedule the next one */
+
+       __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_seq_heartbeat,
+                        LWS_US_PER_SEC);
+}
+
+int
+lws_seq_pt_init(struct lws_context_per_thread *pt)
+{
+       pt->sul_seq_heartbeat.cb = lws_sul_seq_heartbeat_cb;
+
+       /* schedule the first heartbeat */
+       __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_seq_heartbeat,
+                        LWS_US_PER_SEC);
+
+       return 0;
+}
+
+lws_seq_t *
+lws_seq_create(lws_seq_info_t *i)
+{
+       struct lws_context_per_thread *pt = &i->context->pt[i->tsi];
+       lws_seq_t *seq = lws_zalloc(sizeof(*seq) + i->user_size, __func__);
+
+       if (!seq)
+               return NULL;
+
+       seq->cb = i->cb;
+       seq->pt = pt;
+       seq->name = i->name;
+       seq->retry = i->retry;
+
+       *i->puser = (void *)&seq[1];
+
+       /* add the sequencer to the pt */
+
+       lws_pt_lock(pt, __func__); /* ---------------------------------- pt { */
+
+       lws_dll2_add_tail(&seq->seq_list, &pt->seq_owner);
+
+       lws_pt_unlock(pt); /* } pt ------------------------------------------ */
+
+       seq->time_created = lws_now_usecs();
+
+       /* try to queue the creation cb */
+
+       if (lws_seq_queue_event(seq, LWSSEQ_CREATED, NULL, NULL)) {
+               lws_dll2_remove(&seq->seq_list);
+               lws_free(seq);
+
+               return NULL;
+       }
+
+       return seq;
+}
+
+static int
+seq_ev_destroy(struct lws_dll2 *d, void *user)
+{
+       lws_seq_event_t *seqe = lws_container_of(d, lws_seq_event_t,
+                                                seq_event_list);
+
+       lws_dll2_remove(&seqe->seq_event_list);
+       lws_free(seqe);
+
+       return 0;
+}
+
+void
+lws_seq_destroy(lws_seq_t **pseq)
+{
+       lws_seq_t *seq = *pseq;
+
+       /* defeat another thread racing to add events while we are destroying */
+       seq->going_down = 1;
+
+       seq->cb(seq, (void *)&seq[1], LWSSEQ_DESTROYED, NULL, NULL);
+
+       lws_pt_lock(seq->pt, __func__); /* -------------------------- pt { */
+
+       lws_dll2_remove(&seq->seq_list);
+       lws_dll2_remove(&seq->sul_timeout.list);
+       lws_dll2_remove(&seq->sul_pending.list);
+       /* remove and destroy any pending events */
+       lws_dll2_foreach_safe(&seq->seq_event_owner, NULL, seq_ev_destroy);
+
+       lws_pt_unlock(seq->pt); /* } pt ---------------------------------- */
+
+
+       lws_free_set_NULL(seq);
+}
+
+void
+lws_seq_destroy_all_on_pt(struct lws_context_per_thread *pt)
+{
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+                                  pt->seq_owner.head) {
+               lws_seq_t *s = lws_container_of(p, lws_seq_t,
+                                                     seq_list);
+
+               lws_seq_destroy(&s);
+
+       } lws_end_foreach_dll_safe(p, tp);
+}
+
+static void
+lws_seq_sul_pending_cb(lws_sorted_usec_list_t *sul)
+{
+       lws_seq_t *seq = lws_container_of(sul, lws_seq_t, sul_pending);
+       lws_seq_event_t *seqe;
+       struct lws_dll2 *dh;
+       int n;
+
+       if (!seq->seq_event_owner.count)
+               return;
+
+       /* events are only added at tail, so no race possible yet... */
+
+       dh = lws_dll2_get_head(&seq->seq_event_owner);
+       seqe = lws_container_of(dh, lws_seq_event_t, seq_event_list);
+
+       n = seq->cb(seq, (void *)&seq[1], seqe->e, seqe->data, seqe->aux);
+
+       /* ... have to lock here though, because we will change the list */
+
+       lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
+
+       /* detach event from sequencer event list and free it */
+       lws_dll2_remove(&seqe->seq_event_list);
+       lws_free(seqe);
+       lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
+
+       if (n) {
+               lwsl_info("%s: destroying seq '%s' by request\n", __func__,
+                               seq->name);
+               lws_seq_destroy(&seq);
+       }
+}
+
+int
+lws_seq_queue_event(lws_seq_t *seq, lws_seq_events_t e, void *data, void *aux)
+{
+       lws_seq_event_t *seqe;
+
+       if (!seq || seq->going_down)
+               return 1;
+
+       seqe = lws_zalloc(sizeof(*seqe), __func__);
+       if (!seqe)
+               return 1;
+
+       seqe->e = e;
+       seqe->data = data;
+       seqe->aux = aux;
+
+       // lwsl_notice("%s: seq %s: event %d\n", __func__, seq->name, e);
+
+       lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
+
+       if (seq->seq_event_owner.count > QUEUE_SANITY_LIMIT) {
+               lwsl_err("%s: more than %d events queued\n", __func__,
+                        QUEUE_SANITY_LIMIT);
+       }
+
+       lws_dll2_add_tail(&seqe->seq_event_list, &seq->seq_event_owner);
+
+       seq->sul_pending.cb = lws_seq_sul_pending_cb;
+       __lws_sul_insert(&seq->pt->pt_sul_owner, &seq->sul_pending, 1);
+
+       lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
+
+       return 0;
+}
+
+/*
+ * Check if wsi still extant, by peeking in the message queue for a
+ * LWSSEQ_WSI_CONN_CLOSE message about wsi.  (Doesn't need to do the same for
+ * CONN_FAIL since that will never have produced any messages prior to that).
+ *
+ * Use this to avoid trying to perform operations on wsi that have already
+ * closed but we didn't get to that message yet.
+ *
+ * Returns 0 if not closed yet or 1 if it has closed but we didn't process the
+ * close message yet.
+ */
+
+int
+lws_seq_check_wsi(lws_seq_t *seq, struct lws *wsi)
+{
+       lws_seq_event_t *seqe;
+       struct lws_dll2 *dh;
+
+       lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
+
+       dh = lws_dll2_get_head(&seq->seq_event_owner);
+       while (dh) {
+               seqe = lws_container_of(dh, lws_seq_event_t, seq_event_list);
+
+               if (seqe->e == LWSSEQ_WSI_CONN_CLOSE && seqe->data == wsi)
+                       break;
+
+               dh = dh->next;
+       }
+
+       lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
+
+       return !!dh;
+}
+
+
+static void
+lws_seq_sul_timeout_cb(lws_sorted_usec_list_t *sul)
+{
+       lws_seq_t *s = lws_container_of(sul, lws_seq_t, sul_timeout);
+
+       lws_seq_queue_event(s, LWSSEQ_TIMED_OUT, NULL, NULL);
+}
+
+/* set us to LWS_SET_TIMER_USEC_CANCEL to remove timeout */
+
+int
+lws_seq_timeout_us(lws_seq_t *seq, lws_usec_t us)
+{
+       seq->sul_timeout.cb = lws_seq_sul_timeout_cb;
+       /* list is always at the very top of the sul */
+       return __lws_sul_insert(&seq->pt->pt_sul_owner,
+                       (lws_sorted_usec_list_t *)&seq->sul_timeout.list, us);
+}
+
+lws_seq_t *
+lws_seq_from_user(void *u)
+{
+       return &((lws_seq_t *)u)[-1];
+}
+
+const char *
+lws_seq_name(lws_seq_t *seq)
+{
+       return seq->name;
+}
+
+lws_usec_t
+lws_seq_us_since_creation(lws_seq_t *seq)
+{
+       return lws_now_usecs() - seq->time_created;
+}
+
+struct lws_context *
+lws_seq_get_context(lws_seq_t *seq)
+{
+       return seq->pt->context;
+}
+
diff --git a/lib/core-net/server.c b/lib/core-net/server.c
new file mode 100644 (file)
index 0000000..e64c034
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#if defined(LWS_WITH_SERVER_STATUS)
+
+void
+lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs)
+{
+       const struct lws_vhost *vh = ctx->vhost_list;
+
+       while (vh) {
+
+               cs->rx += vh->conn_stats.rx;
+               cs->tx += vh->conn_stats.tx;
+               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->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;
+       }
+}
+
+LWS_EXTERN int
+lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len)
+{
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       static const char * const prots[] = {
+               "http://",
+               "https://",
+               "file://",
+               "cgi://",
+               ">http://",
+               ">https://",
+               "callback://"
+       };
+#endif
+       char *orig = buf, *end = buf + len - 1, first = 1;
+       int n = 0;
+
+       if (len < 100)
+               return 0;
+
+       buf += lws_snprintf(buf, end - buf,
+                       "{\n \"name\":\"%s\",\n"
+                       " \"port\":\"%d\",\n"
+                       " \"use_ssl\":\"%d\",\n"
+                       " \"sts\":\"%d\",\n"
+                       " \"rx\":\"%llu\",\n"
+                       " \"tx\":\"%llu\",\n"
+                       " \"h1_conn\":\"%lu\",\n"
+                       " \"h1_trans\":\"%lu\",\n"
+                       " \"h2_trans\":\"%lu\",\n"
+                       " \"ws_upg\":\"%lu\",\n"
+                       " \"rejected\":\"%lu\",\n"
+                       " \"h2_upg\":\"%lu\",\n"
+                       " \"h2_alpn\":\"%lu\",\n"
+                       " \"h2_subs\":\"%lu\""
+                       ,
+                       vh->name, vh->listen_port,
+#if defined(LWS_WITH_TLS)
+                       vh->tls.use_ssl & LCCSCF_USE_SSL,
+#else
+                       0,
+#endif
+                       !!(vh->options & LWS_SERVER_OPTION_STS),
+                       vh->conn_stats.rx, vh->conn_stats.tx,
+                       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.h2_upg,
+                       vh->conn_stats.h2_alpn,
+                       vh->conn_stats.h2_subs
+       );
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (vh->http.mount_list) {
+               const struct lws_http_mount *m = vh->http.mount_list;
+
+               buf += lws_snprintf(buf, end - buf, ",\n \"mounts\":[");
+               while (m) {
+                       if (!first)
+                               buf += lws_snprintf(buf, end - buf, ",");
+                       buf += lws_snprintf(buf, end - buf,
+                                       "\n  {\n   \"mountpoint\":\"%s\",\n"
+                                       "  \"origin\":\"%s%s\",\n"
+                                       "  \"cache_max_age\":\"%d\",\n"
+                                       "  \"cache_reuse\":\"%d\",\n"
+                                       "  \"cache_revalidate\":\"%d\",\n"
+                                       "  \"cache_intermediaries\":\"%d\"\n"
+                                       ,
+                                       m->mountpoint,
+                                       prots[m->origin_protocol],
+                                       m->origin,
+                                       m->cache_max_age,
+                                       m->cache_reusable,
+                                       m->cache_revalidate,
+                                       m->cache_intermediaries);
+                       if (m->def)
+                               buf += lws_snprintf(buf, end - buf,
+                                               ",\n  \"default\":\"%s\"",
+                                               m->def);
+                       buf += lws_snprintf(buf, end - buf, "\n  }");
+                       first = 0;
+                       m = m->mount_next;
+               }
+               buf += lws_snprintf(buf, end - buf, "\n ]");
+       }
+#endif
+       if (vh->protocols) {
+               n = 0;
+               first = 1;
+
+               buf += lws_snprintf(buf, end - buf, ",\n \"ws-protocols\":[");
+               while (n < vh->count_protocols) {
+                       if (!first)
+                               buf += lws_snprintf(buf, end - buf, ",");
+                       buf += lws_snprintf(buf, end - buf,
+                                       "\n  {\n   \"%s\":{\n"
+                                       "    \"status\":\"ok\"\n   }\n  }"
+                                       ,
+                                       vh->protocols[n].name);
+                       first = 0;
+                       n++;
+               }
+               buf += lws_snprintf(buf, end - buf, "\n ]");
+       }
+
+       buf += lws_snprintf(buf, end - buf, "\n}");
+
+       return buf - orig;
+}
+
+
+LWS_EXTERN LWS_VISIBLE int
+lws_json_dump_context(const struct lws_context *context, char *buf, int len,
+               int hide_vhosts)
+{
+       char *orig = buf, *end = buf + len - 1, first = 1;
+       const struct lws_vhost *vh = context->vhost_list;
+       const struct lws_context_per_thread *pt;
+       int n, listening = 0, cgi_count = 0, fd;
+       struct lws_conn_stats cs;
+       double d = 0;
+#ifdef LWS_WITH_CGI
+       struct lws_cgi * const *pcgi;
+#endif
+
+#ifdef LWS_WITH_LIBUV
+       uv_uptime(&d);
+#endif
+
+       buf += lws_snprintf(buf, end - buf, "{ "
+                           "\"version\":\"%s\",\n"
+                           "\"uptime\":\"%ld\",\n",
+                           lws_get_library_version(),
+                           (long)d);
+
+#ifdef LWS_HAVE_GETLOADAVG
+       {
+               double d[3];
+               int m;
+
+               m = getloadavg(d, 3);
+               for (n = 0; n < m; n++) {
+                       buf += lws_snprintf(buf, end - buf,
+                               "\"l%d\":\"%.2f\",\n",
+                               n + 1, d[n]);
+               }
+       }
+#endif
+
+       fd = lws_open("/proc/self/statm", LWS_O_RDONLY);
+       if (fd >= 0) {
+               char contents[96], pure[96];
+               n = read(fd, contents, sizeof(contents) - 1);
+               if (n > 0) {
+                       contents[n] = '\0';
+                       if (contents[n - 1] == '\n')
+                               contents[--n] = '\0';
+                       lws_json_purify(pure, contents, sizeof(pure));
+
+                       buf += lws_snprintf(buf, end - buf,
+                                         "\"statm\": \"%s\",\n", pure);
+               }
+               close(fd);
+       }
+
+       buf += lws_snprintf(buf, end - buf, "\"heap\":%lld,\n\"contexts\":[\n",
+                               (long long)lws_get_allocated_heap());
+
+       buf += lws_snprintf(buf, end - buf, "{ "
+                               "\"context_uptime\":\"%llu\",\n"
+                               "\"cgi_spawned\":\"%d\",\n"
+                               "\"pt_fd_max\":\"%d\",\n"
+                               "\"ah_pool_max\":\"%d\",\n"
+                               "\"deprecated\":\"%d\",\n"
+                               "\"wsi_alive\":\"%d\",\n",
+                               (unsigned long long)(lws_now_usecs() - context->time_up),
+                               context->count_cgi_spawned,
+                               context->fd_limit_per_thread,
+                               context->max_http_header_pool,
+                               context->deprecated,
+                               context->count_wsi_allocated);
+
+       buf += lws_snprintf(buf, end - buf, "\"pt\":[\n ");
+       for (n = 0; n < context->count_threads; n++) {
+               pt = &context->pt[n];
+               if (n)
+                       buf += lws_snprintf(buf, end - buf, ",");
+               buf += lws_snprintf(buf, end - buf,
+                               "\n  {\n"
+                               "    \"fds_count\":\"%d\",\n"
+                               "    \"ah_pool_inuse\":\"%d\",\n"
+                               "    \"ah_wait_list\":\"%d\"\n"
+                               "    }",
+                               pt->fds_count,
+                               pt->http.ah_count_in_use,
+                               pt->http.ah_wait_list_length);
+       }
+
+       buf += lws_snprintf(buf, end - buf, "]");
+
+       buf += lws_snprintf(buf, end - buf, ", \"vhosts\":[\n ");
+
+       first = 1;
+       vh = context->vhost_list;
+       listening = 0;
+       cs = context->conn_stats;
+       lws_sum_stats(context, &cs);
+       while (vh) {
+
+               if (!hide_vhosts) {
+                       if (!first)
+                               if(buf != end)
+                                       *buf++ = ',';
+                       buf += lws_json_dump_vhost(vh, buf, end - buf);
+                       first = 0;
+               }
+               if (vh->lserv_wsi)
+                       listening++;
+               vh = vh->vhost_next;
+       }
+
+       buf += lws_snprintf(buf, end - buf,
+                       "],\n\"listen_wsi\":\"%d\",\n"
+                       " \"rx\":\"%llu\",\n"
+                       " \"tx\":\"%llu\",\n"
+                       " \"h1_conn\":\"%lu\",\n"
+                       " \"h1_trans\":\"%lu\",\n"
+                       " \"h2_trans\":\"%lu\",\n"
+                       " \"ws_upg\":\"%lu\",\n"
+                       " \"rejected\":\"%lu\",\n"
+                       " \"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++) {
+               pt = &context->pt[n];
+               pcgi = &pt->http.cgi_list;
+
+               while (*pcgi) {
+                       pcgi = &(*pcgi)->cgi_list;
+
+                       cgi_count++;
+               }
+       }
+#endif
+       buf += lws_snprintf(buf, end - buf, ",\n \"cgi_alive\":\"%d\"\n ",
+                       cgi_count);
+
+       buf += lws_snprintf(buf, end - buf, "}");
+
+
+       buf += lws_snprintf(buf, end - buf, "]}\n ");
+
+       return buf - orig;
+}
+
+#endif
diff --git a/lib/core-net/service.c b/lib/core-net/service.c
new file mode 100644 (file)
index 0000000..05c14e9
--- /dev/null
@@ -0,0 +1,708 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+lws_callback_as_writeable(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int n, m;
+
+       lws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB, 1);
+#if defined(LWS_WITH_STATS)
+       if (wsi->active_writable_req_us) {
+               uint64_t ul = lws_now_usecs() -
+                             wsi->active_writable_req_us;
+
+               lws_stats_bump(pt, LWSSTATS_US_WRITABLE_DELAY_AVG, ul);
+               lws_stats_max(pt, LWSSTATS_US_WORST_WRITABLE_DELAY, ul);
+               wsi->active_writable_req_us = 0;
+       }
+#endif
+
+       n = wsi->role_ops->writeable_cb[lwsi_role_server(wsi)];
+
+       m = user_callback_handle_rxflow(wsi->protocol->callback,
+                                       wsi, (enum lws_callback_reasons) n,
+                                       wsi->user_space, NULL, 0);
+
+       return m;
+}
+
+LWS_VISIBLE int
+lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
+{
+       volatile struct lws *vwsi = (volatile struct lws *)wsi;
+       int n;
+
+       // lwsl_notice("%s: %p\n", __func__, wsi);
+
+       vwsi->leave_pollout_active = 0;
+       vwsi->handling_pollout = 1;
+       /*
+        * if another thread wants POLLOUT on us, from here on while
+        * handling_pollout is set, he will only set leave_pollout_active.
+        * If we are going to disable POLLOUT, we will check that first.
+        */
+       wsi->could_have_pending = 0; /* clear back-to-back write detection */
+
+       /*
+        * user callback is lowest priority to get these notifications
+        * actually, since other pending things cannot be disordered
+        *
+        * Priority 1: pending truncated sends are incomplete ws fragments
+        *             If anything else sent first the protocol would be
+        *             corrupted.
+        *
+        *             These are post- any compression transform
+        */
+
+       if (lws_has_buffered_out(wsi)) {
+               //lwsl_notice("%s: completing partial\n", __func__);
+               if (lws_issue_raw(wsi, NULL, 0) < 0) {
+                       lwsl_info("%s signalling to close\n", __func__);
+                       goto bail_die;
+               }
+               /* leave POLLOUT active either way */
+               goto bail_ok;
+       } else
+               if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) {
+                       wsi->socket_is_permanently_unusable = 1;
+                       goto bail_die; /* retry closing now */
+               }
+
+       /* Priority 2: pre- compression transform */
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.comp_ctx.buflist_comp ||
+           wsi->http.comp_ctx.may_have_more) {
+               enum lws_write_protocol wp = LWS_WRITE_HTTP;
+
+               lwsl_info("%s: completing comp partial (buflist_comp %p, may %d)\n",
+                               __func__, wsi->http.comp_ctx.buflist_comp,
+                               wsi->http.comp_ctx.may_have_more
+                               );
+
+               if (wsi->role_ops->write_role_protocol(wsi, NULL, 0, &wp) < 0) {
+                       lwsl_info("%s signalling to close\n", __func__);
+                       goto bail_die;
+               }
+               lws_callback_on_writable(wsi);
+
+               goto bail_ok;
+       }
+#endif
+
+#ifdef LWS_WITH_CGI
+       /*
+        * A cgi master's wire protocol remains h1 or h2.  He is just getting
+        * his data from his child cgis.
+        */
+       if (wsi->http.cgi) {
+               /* also one shot */
+               if (pollfd)
+                       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+                               lwsl_info("failed at set pollfd\n");
+                               return 1;
+                       }
+               goto user_service_go_again;
+       }
+#endif
+
+       /* if we got here, we should have wire protocol ops set on the wsi */
+       assert(wsi->role_ops);
+
+       if (!wsi->role_ops->handle_POLLOUT)
+               goto bail_ok;
+
+       switch ((wsi->role_ops->handle_POLLOUT)(wsi)) {
+       case LWS_HP_RET_BAIL_OK:
+               goto bail_ok;
+       case LWS_HP_RET_BAIL_DIE:
+               goto bail_die;
+       case LWS_HP_RET_USER_SERVICE:
+               break;
+       default:
+               assert(0);
+       }
+
+       /* one shot */
+
+       if (pollfd) {
+               int eff = vwsi->leave_pollout_active;
+
+               if (!eff) {
+                       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+                               lwsl_info("failed at set pollfd\n");
+                               goto bail_die;
+                       }
+               }
+
+               vwsi->handling_pollout = 0;
+
+               /* cannot get leave_pollout_active set after the above */
+               if (!eff && wsi->leave_pollout_active) {
+                       /*
+                        * got set inbetween sampling eff and clearing
+                        * handling_pollout, force POLLOUT on
+                        */
+                       lwsl_debug("leave_pollout_active\n");
+                       if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) {
+                               lwsl_info("failed at set pollfd\n");
+                               goto bail_die;
+                       }
+               }
+
+               vwsi->leave_pollout_active = 0;
+       }
+
+       if (lwsi_role_client(wsi) && !wsi->hdr_parsing_completed &&
+            lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS &&
+            lwsi_state(wsi) != LRS_ISSUE_HTTP_BODY)
+               goto bail_ok;
+
+
+#ifdef LWS_WITH_CGI
+user_service_go_again:
+#endif
+
+       if (wsi->role_ops->perform_user_POLLOUT) {
+               if (wsi->role_ops->perform_user_POLLOUT(wsi) == -1)
+                       goto bail_die;
+               else
+                       goto bail_ok;
+       }
+       
+       lwsl_debug("%s: %p: non mux: wsistate 0x%lx, ops %s\n", __func__, wsi,
+                  (unsigned long)wsi->wsistate, wsi->role_ops->name);
+
+       vwsi = (volatile struct lws *)wsi;
+       vwsi->leave_pollout_active = 0;
+
+       n = lws_callback_as_writeable(wsi);
+       vwsi->handling_pollout = 0;
+
+       if (vwsi->leave_pollout_active)
+               if (lws_change_pollfd(wsi, 0, LWS_POLLOUT))
+                       goto bail_die;
+
+       return n;
+
+       /*
+        * since these don't disable the POLLOUT, they are always doing the
+        * right thing for leave_pollout_active whether it was set or not.
+        */
+
+bail_ok:
+       vwsi->handling_pollout = 0;
+       vwsi->leave_pollout_active = 0;
+
+       return 0;
+
+bail_die:
+       vwsi->handling_pollout = 0;
+       vwsi->leave_pollout_active = 0;
+
+       return -1;
+}
+
+int
+lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       uint8_t *buffered;
+       size_t blen;
+       int ret = LWSRXFC_CACHED, m;
+
+       /* his RX is flowcontrolled, don't send remaining now */
+       blen = lws_buflist_next_segment_len(&wsi->buflist, &buffered);
+       if (blen) {
+               if (buf >= buffered && buf + len <= buffered + blen &&
+                   blen != (size_t)len) {
+                       /*
+                        * rxflow while we were spilling prev rxflow
+                        *
+                        * len indicates how much was unused, then... so trim
+                        * the head buflist to match that situation
+                        */
+
+                       lws_buflist_use_segment(&wsi->buflist, blen - len);
+                       lwsl_debug("%s: trim existing rxflow %d -> %d\n",
+                                       __func__, (int)blen, (int)len);
+
+                       return LWSRXFC_TRIMMED;
+               }
+               ret = LWSRXFC_ADDITIONAL;
+       }
+
+       /* a new rxflow, buffer it and warn caller */
+
+       m = lws_buflist_append_segment(&wsi->buflist, buf + n, len - n);
+
+       if (m < 0)
+               return LWSRXFC_ERROR;
+       if (m) {
+               lwsl_debug("%s: added %p to rxflow list\n", __func__, wsi);
+               lws_dll2_add_head(&wsi->dll_buflist, &pt->dll_buflist_owner);
+       }
+
+       return ret;
+}
+
+/* this is used by the platform service code to stop us waiting for network
+ * activity in poll() when we have something that already needs service
+ */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+
+       /*
+        * Figure out if we really want to wait in poll()... we only need to
+        * wait if really nothing already to do and we have to wait for
+        * something from network
+        */
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+       /* 1) if we know we are draining rx ext, do not wait in poll */
+       if (pt->ws.rx_draining_ext_list)
+               return 0;
+#endif
+
+#if defined(LWS_WITH_TLS)
+       /* 2) if we know we have non-network pending data,
+        *    do not wait in poll */
+
+       if (pt->context->tls_ops &&
+           pt->context->tls_ops->fake_POLLIN_for_buffered &&
+           pt->context->tls_ops->fake_POLLIN_for_buffered(pt))
+                       return 0;
+#endif
+
+       /*
+        * 4) If there is any wsi with rxflow buffered and in a state to process
+        *    it, we should not wait in poll
+        */
+
+       lws_start_foreach_dll(struct lws_dll2 *, d, pt->dll_buflist_owner.head) {
+               struct lws *wsi = lws_container_of(d, struct lws, dll_buflist);
+
+               if (!lws_is_flowcontrolled(wsi) &&
+                    lwsi_state(wsi) != LRS_DEFERRING_ACTION)
+                       return 0;
+
+       /*
+        * 5) If any guys with http compression to spill, we shouldn't wait in
+        *    poll but hurry along and service them
+        */
+
+       } lws_end_foreach_dll(d);
+
+       return timeout_ms;
+}
+
+/*
+ * POLLIN said there is something... we must read it, and either use it; or
+ * if other material already in the buflist append it and return the buflist
+ * head material.
+ */
+int
+lws_buflist_aware_read(struct lws_context_per_thread *pt, struct lws *wsi,
+                      struct lws_tokens *ebuf)
+{
+       int n, prior = (int)lws_buflist_next_segment_len(&wsi->buflist, NULL);
+
+       ebuf->token = pt->serv_buf;
+       ebuf->len = lws_ssl_capable_read(wsi, pt->serv_buf,
+                                        wsi->context->pt_serv_buf_size);
+
+       if (ebuf->len == LWS_SSL_CAPABLE_MORE_SERVICE && prior)
+               goto get_from_buflist;
+
+       if (ebuf->len <= 0)
+               return 0;
+
+       /* nothing in buflist already?  Then just use what we read */
+
+       if (!prior)
+               return 0;
+
+       /* stash what we read */
+
+       n = lws_buflist_append_segment(&wsi->buflist, ebuf->token,
+                                      ebuf->len);
+       if (n < 0)
+               return -1;
+       if (n) {
+               lwsl_debug("%s: added %p to rxflow list\n", __func__, wsi);
+               lws_dll2_add_head(&wsi->dll_buflist, &pt->dll_buflist_owner);
+       }
+
+       /* get the first buflist guy in line */
+
+get_from_buflist:
+
+       ebuf->len = (int)lws_buflist_next_segment_len(&wsi->buflist,
+                                                     &ebuf->token);
+
+       return 1; /* came from buflist */
+}
+
+int
+lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used,
+                         int buffered)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int m;
+
+       /* it's in the buflist; we didn't use any */
+
+       if (!used && buffered)
+               return 0;
+
+       if (used && buffered) {
+               m = lws_buflist_use_segment(&wsi->buflist, used);
+               lwsl_info("%s: draining rxflow: used %d, next %d\n",
+                           __func__, used, m);
+               if (m)
+                       return 0;
+
+               lwsl_info("%s: removed %p from dll_buflist\n", __func__, wsi);
+               lws_dll2_remove(&wsi->dll_buflist);
+
+               return 0;
+       }
+
+       /* any remainder goes on the buflist */
+
+       if (used != ebuf->len) {
+               m = lws_buflist_append_segment(&wsi->buflist,
+                                              ebuf->token + used,
+                                              ebuf->len - used);
+               if (m < 0)
+                       return 1; /* OOM */
+               if (m) {
+                       lwsl_debug("%s: added %p to rxflow list\n",
+                                  __func__, wsi);
+                       lws_dll2_add_head(&wsi->dll_buflist,
+                                        &pt->dll_buflist_owner);
+               }
+       }
+
+       return 0;
+}
+
+void
+lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt)
+{
+       struct lws_pollfd pfd;
+
+       if (!pt->dll_buflist_owner.head)
+               return;
+
+       /*
+        * service all guys with pending rxflow that reached a state they can
+        * accept the pending data
+        */
+
+       lws_pt_lock(pt, __func__);
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
+                                  pt->dll_buflist_owner.head) {
+               struct lws *wsi = lws_container_of(d, struct lws, dll_buflist);
+
+               pfd.events = LWS_POLLIN;
+               pfd.revents = LWS_POLLIN;
+               pfd.fd = -1;
+
+               lwsl_debug("%s: rxflow processing: %p fc=%d, 0x%lx\n", __func__,
+                          wsi, lws_is_flowcontrolled(wsi),
+                          (unsigned long)wsi->wsistate);
+
+               if (!lws_is_flowcontrolled(wsi) &&
+                   lwsi_state(wsi) != LRS_DEFERRING_ACTION &&
+                   (wsi->role_ops->handle_POLLIN)(pt, wsi, &pfd) ==
+                                                  LWS_HPI_RET_PLEASE_CLOSE_ME)
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "close_and_handled");
+
+       } lws_end_foreach_dll_safe(d, d1);
+
+       lws_pt_unlock(pt);
+}
+
+/*
+ * guys that need POLLIN service again without waiting for network action
+ * can force POLLIN here if not flowcontrolled, so they will get service.
+ *
+ * Return nonzero if anybody got their POLLIN faked
+ */
+int
+lws_service_flag_pending(struct lws_context *context, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       int forced = 0;
+
+       lws_pt_lock(pt, __func__);
+
+       /*
+        * 1) If there is any wsi with a buflist and in a state to process
+        *    it, we should not wait in poll
+        */
+
+       lws_start_foreach_dll(struct lws_dll2 *, d, pt->dll_buflist_owner.head) {
+               struct lws *wsi = lws_container_of(d, struct lws, dll_buflist);
+
+               if (!lws_is_flowcontrolled(wsi) &&
+                    lwsi_state(wsi) != LRS_DEFERRING_ACTION) {
+                       forced = 1;
+                       break;
+               }
+       } lws_end_foreach_dll(d);
+
+#if defined(LWS_ROLE_WS)
+       forced |= role_ops_ws.service_flag_pending(context, tsi);
+#endif
+
+#if defined(LWS_WITH_TLS)
+       /*
+        * 2) For all guys with buffered SSL read data already saved up, if they
+        * are not flowcontrolled, fake their POLLIN status so they'll get
+        * service to use up the buffered incoming data, even though their
+        * network socket may have nothing
+        */
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
+                       lws_dll2_get_head(&pt->tls.dll_pending_tls_owner)) {
+               struct lws *wsi = lws_container_of(p, struct lws,
+                                                  tls.dll_pending_tls);
+
+               pt->fds[wsi->position_in_fds_table].revents |=
+                       pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
+               if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) {
+                       forced = 1;
+                       /*
+                        * he's going to get serviced now, take him off the
+                        * list of guys with buffered SSL.  If he still has some
+                        * at the end of the service, he'll get put back on the
+                        * list then.
+                        */
+                       __lws_ssl_remove_wsi_from_buffered_list(wsi);
+               }
+
+       } lws_end_foreach_dll_safe(p, p1);
+#endif
+
+       lws_pt_unlock(pt);
+
+       return forced;
+}
+
+LWS_VISIBLE int
+lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd,
+                  int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       struct lws *wsi;
+
+       if (!context || context->being_destroyed1 )
+               return -1;
+
+       if (!pollfd) {
+               /*
+                * calling with NULL pollfd for periodic background processing
+                * is no longer needed and is now illegal.
+                */
+               assert(pollfd);
+               return -1;
+       }
+       assert(lws_socket_is_valid(pollfd->fd));
+
+       /* no, here to service a socket descriptor */
+       wsi = wsi_from_fd(context, pollfd->fd);
+       if (!wsi)
+               /* not lws connection ... leave revents alone and return */
+               return 0;
+
+#if LWS_MAX_SMP > 1
+       if (wsi->undergoing_init_from_other_pt)
+               /*
+                * Temporary situation that other service thread is initializing
+                * this wsi right now for use on our service thread.
+                */
+               return 0;
+#endif
+
+       /*
+        * so that caller can tell we handled, past here we need to
+        * zero down pollfd->revents after handling
+        */
+
+       /* handle session socket closed */
+
+       if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) &&
+           (pollfd->revents & LWS_POLLHUP)) {
+               wsi->socket_is_permanently_unusable = 1;
+               lwsl_debug("Session Socket %p (fd=%d) dead\n",
+                          (void *)wsi, pollfd->fd);
+
+               goto close_and_handled;
+       }
+
+#ifdef _WIN32
+       if (pollfd->revents & LWS_POLLOUT)
+               wsi->sock_send_blocking = FALSE;
+#endif
+
+       if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) &&
+           (pollfd->revents & LWS_POLLHUP)) {
+               lwsl_debug("pollhup\n");
+               wsi->socket_is_permanently_unusable = 1;
+               goto close_and_handled;
+       }
+
+#if defined(LWS_WITH_TLS)
+       if (lwsi_state(wsi) == LRS_SHUTDOWN &&
+           lws_is_ssl(wsi) && wsi->tls.ssl) {
+               switch (__lws_tls_shutdown(wsi)) {
+               case LWS_SSL_CAPABLE_DONE:
+               case LWS_SSL_CAPABLE_ERROR:
+                       goto close_and_handled;
+
+               case LWS_SSL_CAPABLE_MORE_SERVICE_READ:
+               case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE:
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       goto handled;
+               }
+       }
+#endif
+       wsi->could_have_pending = 0; /* clear back-to-back write detection */
+
+       /* okay, what we came here to do... */
+
+       /* if we got here, we should have wire protocol ops set on the wsi */
+       assert(wsi->role_ops);
+
+       // lwsl_notice("%s: %s: wsistate 0x%x\n", __func__, wsi->role_ops->name,
+       //          wsi->wsistate);
+
+       switch ((wsi->role_ops->handle_POLLIN)(pt, wsi, pollfd)) {
+       case LWS_HPI_RET_WSI_ALREADY_DIED:
+               return 1;
+       case LWS_HPI_RET_HANDLED:
+               break;
+       case LWS_HPI_RET_PLEASE_CLOSE_ME:
+close_and_handled:
+               lwsl_debug("%p: Close and handled\n", wsi);
+               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                  "close_and_handled");
+#if defined(_DEBUG) && defined(LWS_WITH_LIBUV)
+               /*
+                * confirm close has no problem being called again while
+                * it waits for libuv service to complete the first async
+                * close
+                */
+               if (context->event_loop_ops == &event_loop_ops_uv)
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "close_and_handled uv repeat test");
+#endif
+               /*
+                * pollfd may point to something else after the close
+                * due to pollfd swapping scheme on delete on some platforms
+                * we can't clear revents now because it'd be the wrong guy's
+                * revents
+                */
+               return 1;
+       default:
+               assert(0);
+       }
+#if defined(LWS_WITH_TLS)
+handled:
+#endif
+       pollfd->revents = 0;
+
+       if (!context->protocol_init_done)
+               if (lws_protocol_init(context)) {
+                       lwsl_err("%s: lws_protocol_init failed\n", __func__);
+                       return -1;
+               }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd)
+{
+       return lws_service_fd_tsi(context, pollfd, 0);
+}
+
+LWS_VISIBLE int
+lws_service(struct lws_context *context, int timeout_ms)
+{
+       struct lws_context_per_thread *pt = &context->pt[0];
+       int n;
+
+       if (!context)
+               return 1;
+
+       pt->inside_service = 1;
+
+       if (context->event_loop_ops->run_pt) {
+               /* we are configured for an event loop */
+               context->event_loop_ops->run_pt(context, 0);
+
+               pt->inside_service = 0;
+
+               return 1;
+       }
+       n = lws_plat_service(context, timeout_ms);
+
+       pt->inside_service = 0;
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       int n;
+
+       pt->inside_service = 1;
+#if LWS_MAX_SMP > 1
+       pt->self = pthread_self();
+#endif
+
+       if (context->event_loop_ops->run_pt) {
+               /* we are configured for an event loop */
+               context->event_loop_ops->run_pt(context, tsi);
+
+               pt->inside_service = 0;
+
+               return 1;
+       }
+
+       n = _lws_plat_service_tsi(context, timeout_ms, tsi);
+
+       pt->inside_service = 0;
+
+       return n;
+}
diff --git a/lib/core-net/sorted-usec-list.c b/lib/core-net/sorted-usec-list.c
new file mode 100644 (file)
index 0000000..12348f6
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static int
+sul_compare(const lws_dll2_t *d, const lws_dll2_t *i)
+{
+       return ((lws_sorted_usec_list_t *)d)->us -
+                       ((lws_sorted_usec_list_t *)i)->us;
+}
+
+int
+__lws_sul_insert(lws_dll2_owner_t *own, lws_sorted_usec_list_t *sul,
+                lws_usec_t us)
+{
+       lws_dll2_remove(&sul->list);
+
+       if (us == LWS_SET_TIMER_USEC_CANCEL) {
+               /* we are clearing the timeout */
+               sul->us = 0;
+
+               return 0;
+       }
+
+       sul->us = lws_now_usecs() + us;
+       assert(sul->cb);
+
+       /*
+        * we sort the pt's list of sequencers with pending timeouts, so it's
+        * cheap to check it every second
+        */
+
+       lws_dll2_add_sorted(&sul->list, own, sul_compare);
+
+       // lws_dll2_describe(own, "post-tail-insert");
+
+       return 0;
+}
+
+void
+lws_sul_schedule(struct lws_context *context, int tsi,
+                lws_sorted_usec_list_t *sul, sul_cb_t cb, lws_usec_t us)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+
+       sul->cb = cb;
+
+       __lws_sul_insert(&pt->pt_sul_owner, sul, us);
+}
+
+lws_usec_t
+__lws_sul_check(lws_dll2_owner_t *own, lws_usec_t usnow)
+{
+       lws_usec_t future_us = 0;
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+                                  lws_dll2_get_head(own)) {
+               /* .list is always first member in lws_sorted_usec_list_t */
+               lws_sorted_usec_list_t *sul = (lws_sorted_usec_list_t *)p;
+
+               assert(sul->us); /* shouldn't be on the list otherwise */
+               if (sul->us <= usnow) {
+                       /* seq has timed out... remove him from timeout list */
+                       lws_dll2_remove(&sul->list);
+                       sul->us = 0;
+                       sul->cb(sul);
+               } else {
+                       /*
+                        * No need to look further if we met one later than now:
+                        * the list is sorted in ascending time order
+                        */
+                       future_us = sul->us - usnow;
+
+                       break;
+               }
+
+       } lws_end_foreach_dll_safe(p, tp);
+
+       return future_us;
+}
diff --git a/lib/core-net/stats.c b/lib/core-net/stats.c
new file mode 100644 (file)
index 0000000..e87fc2e
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+
+#if defined(LWS_WITH_STATS)
+
+LWS_VISIBLE LWS_EXTERN uint64_t
+lws_stats_get(struct lws_context *context, int index)
+{
+       struct lws_context_per_thread *pt = &context->pt[0];
+
+       if (index >= LWSSTATS_SIZE)
+               return 0;
+
+       return pt->lws_stats[index];
+}
+
+static const char * stat_names[] = {
+       "C_CONNECTIONS",
+       "C_API_CLOSE",
+       "C_API_READ",
+       "C_API_LWS_WRITE",
+       "C_API_WRITE",
+       "C_WRITE_PARTIALS",
+       "C_WRITEABLE_CB_REQ",
+       "C_WRITEABLE_CB_EFF_REQ",
+       "C_WRITEABLE_CB",
+       "C_SSL_CONNECTIONS_FAILED",
+       "C_SSL_CONNECTIONS_ACCEPTED",
+       "C_SSL_CONNECTIONS_ACCEPT_SPIN",
+       "C_SSL_CONNS_HAD_RX",
+       "C_TIMEOUTS",
+       "C_SERVICE_ENTRY",
+       "B_READ",
+       "B_WRITE",
+       "B_PARTIALS_ACCEPTED_PARTS",
+       "US_SSL_ACCEPT_LATENCY_AVG",
+       "US_WRITABLE_DELAY_AVG",
+       "US_WORST_WRITABLE_DELAY",
+       "US_SSL_RX_DELAY_AVG",
+       "C_PEER_LIMIT_AH_DENIED",
+       "C_PEER_LIMIT_WSI_DENIED",
+       "C_CONNECTIONS_CLIENT",
+       "C_CONNECTIONS_CLIENT_FAILED",
+};
+
+static int
+quantify(struct lws_context *context, int tsi, char *p, int len, int idx,
+        uint64_t *sum)
+{
+       const lws_humanize_unit_t *schema = humanize_schema_si;
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       uint64_t u, u1;
+
+       lws_pt_stats_lock(pt);
+       u = pt->lws_stats[idx];
+
+       /* it's supposed to be an average? */
+
+       switch (idx) {
+       case LWSSTATS_US_SSL_ACCEPT_LATENCY_AVG:
+               u1 = pt->lws_stats[LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED];
+               if (u1)
+                       u = u / u1;
+               break;
+       case LWSSTATS_US_SSL_RX_DELAY_AVG:
+               u1 = pt->lws_stats[LWSSTATS_C_SSL_CONNS_HAD_RX];
+               if (u1)
+                       u = u / u1;
+               break;
+       case LWSSTATS_US_WRITABLE_DELAY_AVG:
+               u1 = pt->lws_stats[LWSSTATS_C_WRITEABLE_CB];
+               if (u1)
+                       u = u / u1;
+               break;
+       }
+       lws_pt_stats_unlock(pt);
+
+       *sum += u;
+
+       switch (stat_names[idx][0]) {
+       case 'U':
+               schema = humanize_schema_us;
+               break;
+       case 'B':
+               schema = humanize_schema_si_bytes;
+               break;
+       }
+
+       return lws_humanize(p, len, u, schema);
+}
+
+
+LWS_VISIBLE LWS_EXTERN void
+lws_stats_log_dump(struct lws_context *context)
+{
+       struct lws_vhost *v = context->vhost_list;
+       uint64_t summary[LWSSTATS_SIZE];
+       char bufline[128], *p, *end = bufline + sizeof(bufline) - 1;
+       int n, m;
+
+       if (!context->updated)
+               return;
+
+       context->updated = 0;
+       memset(summary, 0, sizeof(summary));
+
+       lwsl_notice("\n");
+       lwsl_notice("LWS internal statistics dump ----->\n");
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(stat_names); n++) {
+               uint64_t u = 0;
+
+               /* if it's all zeroes, don't report it */
+
+               for (m = 0; m < context->count_threads; m++) {
+                       struct lws_context_per_thread *pt = &context->pt[m];
+
+                       u |= pt->lws_stats[n];
+               }
+               if (!u)
+                       continue;
+
+               p = bufline;
+               p += lws_snprintf(p, lws_ptr_diff(end, p), "%28s: ",
+                                 stat_names[n]);
+
+               for (m = 0; m < context->count_threads; m++)
+                       quantify(context, m, p, lws_ptr_diff(end, p), n, &summary[n]);
+
+               lwsl_notice("%s\n", bufline);
+       }
+
+       lwsl_notice("Simultaneous SSL restriction:  %8d/%d\n",
+                       context->simultaneous_ssl,
+                       context->simultaneous_ssl_restriction);
+
+       lwsl_notice("Live wsi:                      %8d\n",
+                       context->count_wsi_allocated);
+
+       context->updated = 1;
+
+       while (v) {
+               if (v->lserv_wsi &&
+                   v->lserv_wsi->position_in_fds_table != LWS_NO_FDS_POS) {
+
+                       struct lws_context_per_thread *pt =
+                                       &context->pt[(int)v->lserv_wsi->tsi];
+                       struct lws_pollfd *pfd;
+
+                       pfd = &pt->fds[v->lserv_wsi->position_in_fds_table];
+
+                       lwsl_notice("  Listen port %d actual POLLIN:   %d\n",
+                                   v->listen_port,
+                                   (int)pfd->events & LWS_POLLIN);
+               }
+
+               v = v->vhost_next;
+       }
+
+       for (n = 0; n < context->count_threads; n++) {
+               struct lws_context_per_thread *pt = &context->pt[n];
+               struct lws *wl;
+               int m = 0;
+
+               lwsl_notice("PT %d\n", n + 1);
+
+               lws_pt_lock(pt, __func__);
+
+               lwsl_notice("  AH in use / max:                  %d / %d\n",
+                               pt->http.ah_count_in_use,
+                               context->max_http_header_pool);
+
+               wl = pt->http.ah_wait_list;
+               while (wl) {
+                       m++;
+                       wl = wl->http.ah_wait_list;
+               }
+
+               lwsl_notice("  AH wait list count / actual:      %d / %d\n",
+                               pt->http.ah_wait_list_length, m);
+
+               lws_pt_unlock(pt);
+       }
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       m = 0;
+       for (n = 0; n < (int)context->pl_hash_elements; n++) {
+               lws_start_foreach_llp(struct lws_peer **, peer,
+                                     context->pl_hash_table[n]) {
+                       m++;
+               } lws_end_foreach_llp(peer, next);
+       }
+
+       lwsl_notice(" Peers: total active %d\n", m);
+       if (m > 10) {
+               m = 10;
+               lwsl_notice("  (showing 10 peers only)\n");
+       }
+
+       if (m) {
+               for (n = 0; n < (int)context->pl_hash_elements; n++) {
+                       char buf[72];
+
+                       lws_start_foreach_llp(struct lws_peer **, peer,
+                                             context->pl_hash_table[n]) {
+                               struct lws_peer *df = *peer;
+
+                               if (!lws_plat_inet_ntop(df->af, df->addr, buf,
+                                                       sizeof(buf) - 1))
+                                       strcpy(buf, "unknown");
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+                               lwsl_notice("  peer %s: count wsi: %d, count ah: %d\n",
+                                           buf, df->count_wsi,
+                                           df->http.count_ah);
+#else
+                               lwsl_notice("  peer %s: count wsi: %d\n",
+                                           buf, df->count_wsi);
+#endif
+
+                               if (!--m)
+                                       break;
+                       } lws_end_foreach_llp(peer, next);
+               }
+       }
+#endif
+
+       lwsl_notice("\n");
+}
+
+void
+lws_stats_bump(struct lws_context_per_thread *pt, int i, uint64_t bump)
+{
+       lws_pt_stats_lock(pt);
+       pt->lws_stats[i] += bump;
+       if (i != LWSSTATS_C_SERVICE_ENTRY) {
+               pt->updated = 1;
+               pt->context->updated = 1;
+       }
+       lws_pt_stats_unlock(pt);
+}
+
+void
+lws_stats_max(struct lws_context_per_thread *pt, int index, uint64_t val)
+{
+       lws_pt_stats_lock(pt);
+       if (val > pt->lws_stats[index]) {
+               pt->lws_stats[index] = val;
+               pt->updated = 1;
+               pt->context->updated = 1;
+       }
+       lws_pt_stats_unlock(pt);
+}
+
+#endif
+
+
diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c
new file mode 100644 (file)
index 0000000..69fe26a
--- /dev/null
@@ -0,0 +1,1318 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+const struct lws_role_ops *available_roles[] = {
+#if defined(LWS_ROLE_H2)
+       &role_ops_h2,
+#endif
+#if defined(LWS_ROLE_H1)
+       &role_ops_h1,
+#endif
+#if defined(LWS_ROLE_WS)
+       &role_ops_ws,
+#endif
+#if defined(LWS_ROLE_DBUS)
+       &role_ops_dbus,
+#endif
+#if defined(LWS_ROLE_RAW_PROXY)
+       &role_ops_raw_proxy,
+#endif
+       NULL
+};
+
+const struct lws_event_loop_ops *available_event_libs[] = {
+#if defined(LWS_WITH_POLL)
+       &event_loop_ops_poll,
+#endif
+#if defined(LWS_WITH_LIBUV)
+       &event_loop_ops_uv,
+#endif
+#if defined(LWS_WITH_LIBEVENT)
+       &event_loop_ops_event,
+#endif
+#if defined(LWS_WITH_LIBEV)
+       &event_loop_ops_ev,
+#endif
+       NULL
+};
+
+#if defined(LWS_WITH_ABSTRACT)
+const struct lws_protocols *available_abstract_protocols[] = {
+#if defined(LWS_ROLE_RAW)
+       &protocol_abs_client_raw_skt,
+#endif
+       NULL
+};
+#endif
+
+static const char * const mount_protocols[] = {
+       "http://",
+       "https://",
+       "file://",
+       "cgi://",
+       ">http://",
+       ">https://",
+       "callback://"
+};
+
+const struct lws_role_ops *
+lws_role_by_name(const char *name)
+{
+       LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+               if (!strcmp(ar->name, name))
+                       return ar;
+       LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
+       if (!strcmp(name, role_ops_raw_skt.name))
+               return &role_ops_raw_skt;
+
+       if (!strcmp(name, role_ops_raw_file.name))
+               return &role_ops_raw_file;
+
+       return NULL;
+}
+
+int
+lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn)
+{
+#if defined(LWS_WITH_TLS)
+       if (!alpn)
+               return 0;
+
+       lwsl_info("%s: '%s'\n", __func__, alpn);
+
+       LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+               if (ar->alpn && !strcmp(ar->alpn, alpn) && ar->alpn_negotiated)
+                       return ar->alpn_negotiated(wsi, alpn);
+       LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+#endif
+       return 0;
+}
+
+//#if !defined(LWS_WITHOUT_SERVER)
+int
+lws_role_call_adoption_bind(struct lws *wsi, int type, const char *prot)
+{
+       int n;
+
+       /*
+        * if the vhost is told to bind accepted sockets to a given role,
+        * then look it up by name and try to bind to the specific role.
+        */
+       if (lws_check_opt(wsi->vhost->options,
+                         LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG) &&
+           wsi->vhost->listen_accept_role) {
+               const struct lws_role_ops *role =
+                       lws_role_by_name(wsi->vhost->listen_accept_role);
+
+               if (!prot)
+                       prot = wsi->vhost->listen_accept_protocol;
+
+               if (!role)
+                       lwsl_err("%s: can't find role '%s'\n", __func__,
+                                 wsi->vhost->listen_accept_role);
+
+               if (role && role->adoption_bind) {
+                       n = role->adoption_bind(wsi, type, prot);
+                       if (n < 0)
+                               return -1;
+                       if (n) /* did the bind */
+                               return 0;
+               }
+
+               if (type & _LWS_ADOPT_FINISH) {
+                       lwsl_debug("%s: leaving bound to role %s\n", __func__,
+                                  wsi->role_ops->name);
+                       return 0;
+               }
+
+
+               lwsl_warn("%s: adoption bind to role '%s', "
+                         "protocol '%s', type 0x%x, failed\n", __func__,
+                         wsi->vhost->listen_accept_role, prot, type);
+       }
+
+       /*
+        * Otherwise ask each of the roles in order of preference if they
+        * want to bind to this accepted socket
+        */
+
+       LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+               if (ar->adoption_bind && ar->adoption_bind(wsi, type, prot))
+                       return 0;
+       LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
+       /* fall back to raw socket role if, eg, h1 not configured */
+
+       if (role_ops_raw_skt.adoption_bind &&
+           role_ops_raw_skt.adoption_bind(wsi, type, prot))
+               return 0;
+
+       /* fall back to raw file role if, eg, h1 not configured */
+
+       if (role_ops_raw_file.adoption_bind &&
+           role_ops_raw_file.adoption_bind(wsi, type, prot))
+               return 0;
+
+       return 1;
+}
+//#endif
+
+#if !defined(LWS_WITHOUT_CLIENT)
+int
+lws_role_call_client_bind(struct lws *wsi,
+                         const struct lws_client_connect_info *i)
+{
+       LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+               if (ar->client_bind) {
+                       int m = ar->client_bind(wsi, i);
+                       if (m < 0)
+                               return m;
+                       if (m)
+                               return 0;
+               }
+       LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
+       /* fall back to raw socket role if, eg, h1 not configured */
+
+       if (role_ops_raw_skt.client_bind &&
+           role_ops_raw_skt.client_bind(wsi, i))
+               return 0;
+
+       return 1;
+}
+#endif
+
+LWS_VISIBLE void *
+lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost,
+                           const struct lws_protocols *prot, int size)
+{
+       int n = 0;
+
+       /* allocate the vh priv array only on demand */
+       if (!vhost->protocol_vh_privs) {
+               vhost->protocol_vh_privs = (void **)lws_zalloc(
+                               vhost->count_protocols * sizeof(void *),
+                               "protocol_vh_privs");
+               if (!vhost->protocol_vh_privs)
+                       return NULL;
+       }
+
+       while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
+               n++;
+
+       if (n == vhost->count_protocols) {
+               n = 0;
+               while (n < vhost->count_protocols &&
+                      strcmp(vhost->protocols[n].name, prot->name))
+                       n++;
+
+               if (n == vhost->count_protocols)
+                       return NULL;
+       }
+
+       vhost->protocol_vh_privs[n] = lws_zalloc(size, "vh priv");
+       return vhost->protocol_vh_privs[n];
+}
+
+LWS_VISIBLE void *
+lws_protocol_vh_priv_get(struct lws_vhost *vhost,
+                        const struct lws_protocols *prot)
+{
+       int n = 0;
+
+       if (!vhost || !vhost->protocol_vh_privs || !prot)
+               return NULL;
+
+       while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
+               n++;
+
+       if (n == vhost->count_protocols) {
+               n = 0;
+               while (n < vhost->count_protocols &&
+                      strcmp(vhost->protocols[n].name, prot->name))
+                       n++;
+
+               if (n == vhost->count_protocols) {
+                       lwsl_err("%s: unknown protocol %p\n", __func__, prot);
+                       return NULL;
+               }
+       }
+
+       return vhost->protocol_vh_privs[n];
+}
+
+const struct lws_protocol_vhost_options *
+lws_vhost_protocol_options(struct lws_vhost *vh, const char *name)
+{
+       const struct lws_protocol_vhost_options *pvo = vh->pvo;
+
+       if (!name)
+               return NULL;
+
+       while (pvo) {
+               if (!strcmp(pvo->name, name))
+                       return pvo;
+               pvo = pvo->next;
+       }
+
+       return NULL;
+}
+
+/*
+ * inform every vhost that hasn't already done it, that
+ * his protocols are initializing
+ */
+LWS_VISIBLE int
+lws_protocol_init(struct lws_context *context)
+{
+       struct lws_vhost *vh = context->vhost_list;
+       const struct lws_protocol_vhost_options *pvo, *pvo1;
+       struct lws wsi;
+       int n, any = 0;
+
+       if (context->doing_protocol_init)
+               return 0;
+
+       context->doing_protocol_init = 1;
+
+       memset(&wsi, 0, sizeof(wsi));
+       wsi.context = context;
+
+       lwsl_info("%s\n", __func__);
+
+       while (vh) {
+               wsi.vhost = vh;
+
+               /* only do the protocol init once for a given vhost */
+               if (vh->created_vhost_protocols ||
+                   (vh->options & LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT))
+                       goto next;
+
+               /* initialize supported protocols on this vhost */
+
+               for (n = 0; n < vh->count_protocols; n++) {
+                       wsi.protocol = &vh->protocols[n];
+                       if (!vh->protocols[n].name)
+                               continue;
+                       pvo = lws_vhost_protocol_options(vh,
+                                                        vh->protocols[n].name);
+                       if (pvo) {
+                               /*
+                                * linked list of options specific to
+                                * vh + protocol
+                                */
+                               pvo1 = pvo;
+                               pvo = pvo1->options;
+
+                               while (pvo) {
+                                       lwsl_debug(
+                                               "    vhost \"%s\", "
+                                               "protocol \"%s\", "
+                                               "option \"%s\"\n",
+                                                       vh->name,
+                                                       vh->protocols[n].name,
+                                                       pvo->name);
+
+                                       if (!strcmp(pvo->name, "default")) {
+                                               lwsl_info("Setting default "
+                                                  "protocol for vh %s to %s\n",
+                                                  vh->name,
+                                                  vh->protocols[n].name);
+                                               vh->default_protocol_index = n;
+                                       }
+                                       if (!strcmp(pvo->name, "raw")) {
+                                               lwsl_info("Setting raw "
+                                                  "protocol for vh %s to %s\n",
+                                                  vh->name,
+                                                  vh->protocols[n].name);
+                                               vh->raw_protocol_index = n;
+                                       }
+                                       pvo = pvo->next;
+                               }
+
+                               pvo = pvo1->options;
+                       }
+
+#if defined(LWS_WITH_TLS)
+                       any |= !!vh->tls.ssl_ctx;
+#endif
+
+                       /*
+                        * inform all the protocols that they are doing their
+                        * one-time initialization if they want to.
+                        *
+                        * NOTE the wsi is all zeros except for the context, vh
+                        * + protocol ptrs so lws_get_context(wsi) etc can work
+                        */
+                       if (vh->protocols[n].callback(&wsi,
+                                       LWS_CALLBACK_PROTOCOL_INIT, NULL,
+                                       (void *)pvo, 0)) {
+                               if (vh->protocol_vh_privs[n]) {
+                                       lws_free(vh->protocol_vh_privs[n]);
+                                       vh->protocol_vh_privs[n] = NULL;
+                               }
+                               lwsl_err("%s: protocol %s failed init\n",
+                                        __func__, vh->protocols[n].name);
+
+                               return 1;
+                       }
+               }
+
+               vh->created_vhost_protocols = 1;
+next:
+               vh = vh->vhost_next;
+       }
+
+       context->doing_protocol_init = 0;
+
+       if (!context->protocol_init_done && lws_finalize_startup(context))
+               return 1;
+
+       context->protocol_init_done = 1;
+
+       if (any)
+               lws_tls_check_all_cert_lifetimes(context);
+
+       return 0;
+}
+
+
+/* list of supported protocols and callbacks */
+
+static const struct lws_protocols protocols_dummy[] = {
+       /* first protocol must always be HTTP handler */
+
+       {
+               "http-only",                    /* name */
+               lws_callback_http_dummy,        /* callback */
+               0,                              /* per_session_data_size */
+               0,                              /* rx_buffer_size */
+               0,                              /* id */
+               NULL,                           /* user */
+               0                               /* tx_packet_size */
+       },
+       /*
+        * the other protocols are provided by lws plugins
+        */
+       { NULL, NULL, 0, 0, 0, NULL, 0} /* terminator */
+};
+
+
+#ifdef LWS_PLAT_OPTEE
+#undef LWS_HAVE_GETENV
+#endif
+
+LWS_VISIBLE struct lws_vhost *
+lws_create_vhost(struct lws_context *context,
+                const struct lws_context_creation_info *info)
+{
+       struct lws_vhost *vh = lws_zalloc(sizeof(*vh), "create vhost"),
+                        **vh1 = &context->vhost_list;
+       const struct lws_http_mount *mounts;
+       const struct lws_protocols *pcols = info->protocols;
+#ifdef LWS_WITH_PLUGINS
+       struct lws_plugin *plugin = context->plugin_list;
+#endif
+       struct lws_protocols *lwsp;
+       int m, f = !info->pvo, fx = 0, abs_pcol_count = 0;
+       char buf[96];
+#if !defined(LWS_WITHOUT_CLIENT) && defined(LWS_HAVE_GETENV)
+       char *p;
+#endif
+       int n;
+
+       if (!vh)
+               return NULL;
+
+#if LWS_MAX_SMP > 1
+       pthread_mutex_init(&vh->lock, NULL);
+#endif
+
+       if (!pcols && !info->pprotocols)
+               pcols = &protocols_dummy[0];
+
+       vh->context = context;
+       if (!info->vhost_name)
+               vh->name = "default";
+       else
+               vh->name = info->vhost_name;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       vh->http.error_document_404 = info->error_document_404;
+#endif
+
+       if (info->options & LWS_SERVER_OPTION_ONLY_RAW)
+               lwsl_info("%s set to only support RAW\n", vh->name);
+
+       vh->iface = info->iface;
+#if !defined(LWS_WITH_ESP32) && \
+    !defined(OPTEE_TA) && !defined(WIN32)
+       vh->bind_iface = info->bind_iface;
+#endif
+
+       /*
+        * let's figure out how many protocols the user is handing us, using the
+        * old or new way depending on what he gave us
+        */
+
+       if (!pcols)
+               for (vh->count_protocols = 0;
+                       info->pprotocols[vh->count_protocols];
+                       vh->count_protocols++)
+                       ;
+       else
+               for (vh->count_protocols = 0;
+                       pcols[vh->count_protocols].callback;
+                       vh->count_protocols++)
+                               ;
+
+       vh->options = info->options;
+       vh->pvo = info->pvo;
+       vh->headers = info->headers;
+       vh->user = info->user;
+       vh->finalize = info->finalize;
+       vh->finalize_arg = info->finalize_arg;
+       vh->listen_accept_role = info->listen_accept_role;
+       vh->listen_accept_protocol = info->listen_accept_protocol;
+       vh->unix_socket_perms = info->unix_socket_perms;
+
+       LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+               if (ar->init_vhost)
+                       if (ar->init_vhost(vh, info))
+                               return NULL;
+       LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
+
+       if (info->keepalive_timeout)
+               vh->keepalive_timeout = info->keepalive_timeout;
+       else
+               vh->keepalive_timeout = 5;
+
+       if (info->timeout_secs_ah_idle)
+               vh->timeout_secs_ah_idle = info->timeout_secs_ah_idle;
+       else
+               vh->timeout_secs_ah_idle = 10;
+
+#if defined(LWS_WITH_TLS)
+
+       vh->tls.alpn = info->alpn;
+       vh->tls.ssl_info_event_mask = info->ssl_info_event_mask;
+
+       if (info->ecdh_curve)
+               lws_strncpy(vh->tls.ecdh_curve, info->ecdh_curve,
+                           sizeof(vh->tls.ecdh_curve));
+
+       /* carefully allocate and take a copy of cert + key paths if present */
+       n = 0;
+       if (info->ssl_cert_filepath)
+               n += (int)strlen(info->ssl_cert_filepath) + 1;
+       if (info->ssl_private_key_filepath)
+               n += (int)strlen(info->ssl_private_key_filepath) + 1;
+
+       if (n) {
+               vh->tls.key_path = vh->tls.alloc_cert_path =
+                                       lws_malloc(n, "vh paths");
+               if (info->ssl_cert_filepath) {
+                       n = (int)strlen(info->ssl_cert_filepath) + 1;
+                       memcpy(vh->tls.alloc_cert_path,
+                              info->ssl_cert_filepath, n);
+                       vh->tls.key_path += n;
+               }
+               if (info->ssl_private_key_filepath)
+                       memcpy(vh->tls.key_path, info->ssl_private_key_filepath,
+                              strlen(info->ssl_private_key_filepath) + 1);
+       }
+#endif
+
+#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS)
+       fx = 1;
+#endif
+#if defined(LWS_WITH_ABSTRACT)
+       abs_pcol_count = (int)LWS_ARRAY_SIZE(available_abstract_protocols) - 1;
+#endif
+
+       /*
+        * give the vhost a unified list of protocols including:
+        *
+        * - internal, abstracted ones
+        * - the ones that came from plugins
+        * - his user protocols
+        */
+       lwsp = lws_zalloc(sizeof(struct lws_protocols) *
+                               (vh->count_protocols +
+                                  abs_pcol_count +
+                                  context->plugin_protocol_count +
+                                  fx + 1),
+                         "vhost-specific plugin table");
+       if (!lwsp) {
+               lwsl_err("OOM\n");
+               return NULL;
+       }
+
+       /*
+        * 1: user protocols (from pprotocols or protocols)
+        */
+
+       m = vh->count_protocols;
+       if (!pcols) {
+               for (n = 0; n < m; n++)
+                       memcpy(&lwsp[n], info->pprotocols[n], sizeof(lwsp[0]));
+       } else
+               memcpy(lwsp, pcols, sizeof(struct lws_protocols) * m);
+
+       /*
+        * 2: abstract protocols
+        */
+#if defined(LWS_WITH_ABSTRACT)
+       for (n = 0; n < abs_pcol_count; n++) {
+               memcpy(&lwsp[m++], available_abstract_protocols[n],
+                      sizeof(*lwsp));
+               vh->count_protocols++;
+       }
+#endif
+
+       /*
+        * 3: For compatibility, all protocols enabled on vhost if only
+        * the default vhost exists.  Otherwise only vhosts who ask
+        * for a protocol get it enabled.
+        */
+
+       if (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)
+               f = 0;
+       (void)f;
+#ifdef LWS_WITH_PLUGINS
+       if (plugin) {
+               while (plugin) {
+                       for (n = 0; n < plugin->caps.count_protocols; n++) {
+                               /*
+                                * for compatibility's sake, no pvo implies
+                                * allow all protocols
+                                */
+                               if (f || lws_vhost_protocol_options(vh,
+                                   plugin->caps.protocols[n].name)) {
+                                       memcpy(&lwsp[m],
+                                              &plugin->caps.protocols[n],
+                                              sizeof(struct lws_protocols));
+                                       m++;
+                                       vh->count_protocols++;
+                               }
+                       }
+                       plugin = plugin->list;
+               }
+       }
+#endif
+
+#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS)
+       memcpy(&lwsp[m++], &lws_ws_proxy, sizeof(*lwsp));
+       vh->count_protocols++;
+#endif
+
+       vh->protocols = lwsp;
+       vh->allocated_vhost_protocols = 1;
+
+       vh->same_vh_protocol_owner = (struct lws_dll2_owner *)
+                       lws_zalloc(sizeof(struct lws_dll2_owner) *
+                                  vh->count_protocols, "same vh list");
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       vh->http.mount_list = info->mounts;
+#endif
+
+#ifdef LWS_WITH_UNIX_SOCK
+       if (LWS_UNIX_SOCK_ENABLED(vh)) {
+               lwsl_info("Creating Vhost '%s' path \"%s\", %d protocols\n",
+                               vh->name, vh->iface, vh->count_protocols);
+       } else
+#endif
+       {
+               switch(info->port) {
+               case CONTEXT_PORT_NO_LISTEN:
+                       strcpy(buf, "(serving disabled)");
+                       break;
+               case CONTEXT_PORT_NO_LISTEN_SERVER:
+                       strcpy(buf, "(no listener)");
+                       break;
+               default:
+                       lws_snprintf(buf, sizeof(buf), "port %u", info->port);
+                       break;
+               }
+               lwsl_info("Creating Vhost '%s' %s, %d protocols, IPv6 %s\n",
+                           vh->name, buf, vh->count_protocols,
+                           LWS_IPV6_ENABLED(vh) ? "on" : "off");
+       }
+       mounts = info->mounts;
+       while (mounts) {
+               (void)mount_protocols[0];
+               lwsl_info("   mounting %s%s to %s\n",
+                         mount_protocols[mounts->origin_protocol],
+                         mounts->origin, mounts->mountpoint);
+
+               mounts = mounts->mount_next;
+       }
+
+       vh->listen_port = info->port;
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       vh->http.http_proxy_port = 0;
+       vh->http.http_proxy_address[0] = '\0';
+#endif
+#if defined(LWS_WITH_SOCKS5)
+       vh->socks_proxy_port = 0;
+       vh->socks_proxy_address[0] = '\0';
+#endif
+
+#if !defined(LWS_WITHOUT_CLIENT)
+       /* either use proxy from info, or try get it from env var */
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       /* http proxy */
+       if (info->http_proxy_address) {
+               /* override for backwards compatibility */
+               if (info->http_proxy_port)
+                       vh->http.http_proxy_port = info->http_proxy_port;
+               lws_set_proxy(vh, info->http_proxy_address);
+       } else
+#endif
+       {
+#ifdef LWS_HAVE_GETENV
+               p = getenv("http_proxy");
+               if (p) {
+                       lws_strncpy(buf, p, sizeof(buf));
+
+                       lws_set_proxy(vh, buf);
+               }
+#endif
+       }
+#endif
+#if defined(LWS_WITH_SOCKS5)
+       /* socks proxy */
+       if (info->socks_proxy_address) {
+               /* override for backwards compatibility */
+               if (info->socks_proxy_port)
+                       vh->socks_proxy_port = info->socks_proxy_port;
+               lws_set_socks(vh, info->socks_proxy_address);
+       } else {
+#ifdef LWS_HAVE_GETENV
+               p = getenv("socks_proxy");
+               if (p && strlen(p) > 0 && strlen(p) < 95)
+                       lws_set_socks(vh, p);
+#endif
+       }
+#endif
+
+       vh->ka_time = info->ka_time;
+       vh->ka_interval = info->ka_interval;
+       vh->ka_probes = info->ka_probes;
+
+       if (vh->options & LWS_SERVER_OPTION_STS)
+               lwsl_notice("   STS enabled\n");
+
+#ifdef LWS_WITH_ACCESS_LOG
+       if (info->log_filepath) {
+               vh->log_fd = lws_open(info->log_filepath,
+                                 O_CREAT | O_APPEND | O_RDWR, 0600);
+               if (vh->log_fd == (int)LWS_INVALID_FILE) {
+                       lwsl_err("unable to open log filepath %s\n",
+                                info->log_filepath);
+                       goto bail;
+               }
+#ifndef WIN32
+               if (context->uid != -1)
+                       if (chown(info->log_filepath, context->uid,
+                                 context->gid) == -1)
+                               lwsl_err("unable to chown log file %s\n",
+                                               info->log_filepath);
+#endif
+       } else
+               vh->log_fd = (int)LWS_INVALID_FILE;
+#endif
+       if (lws_context_init_server_ssl(info, vh)) {
+               lwsl_err("%s: lws_context_init_server_ssl failed\n", __func__);
+               goto bail1;
+       }
+       if (lws_context_init_client_ssl(info, vh)) {
+               lwsl_err("%s: lws_context_init_client_ssl failed\n", __func__);
+               goto bail1;
+       }
+       lws_context_lock(context, "create_vhost");
+       n = _lws_vhost_init_server(info, vh);
+       lws_context_unlock(context);
+       if (n < 0) {
+               lwsl_err("init server failed\n");
+               goto bail1;
+       }
+
+       while (1) {
+               if (!(*vh1)) {
+                       *vh1 = vh;
+                       break;
+               }
+               vh1 = &(*vh1)->vhost_next;
+       };
+
+       /* for the case we are adding a vhost much later, after server init */
+
+       if (context->protocol_init_done)
+               if (lws_protocol_init(context)) {
+                       lwsl_err("%s: lws_protocol_init failed\n", __func__);
+                       goto bail1;
+               }
+
+       return vh;
+
+bail1:
+       lws_vhost_destroy(vh);
+
+       return NULL;
+
+#ifdef LWS_WITH_ACCESS_LOG
+bail:
+       lws_free(vh);
+#endif
+
+       return NULL;
+}
+
+LWS_VISIBLE int
+lws_init_vhost_client_ssl(const struct lws_context_creation_info *info,
+                         struct lws_vhost *vhost)
+{
+       struct lws_context_creation_info i;
+
+       memcpy(&i, info, sizeof(i));
+       i.port = CONTEXT_PORT_NO_LISTEN;
+
+       return lws_context_init_client_ssl(&i, vhost);
+}
+
+LWS_VISIBLE void
+lws_cancel_service_pt(struct lws *wsi)
+{
+       lws_plat_pipe_signal(wsi);
+}
+
+LWS_VISIBLE void
+lws_cancel_service(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt = &context->pt[0];
+       short m = context->count_threads;
+
+       if (context->being_destroyed1)
+               return;
+
+       lwsl_info("%s\n", __func__);
+
+       while (m--) {
+               if (pt->pipe_wsi)
+                       lws_plat_pipe_signal(pt->pipe_wsi);
+               pt++;
+       }
+}
+
+int
+lws_create_event_pipes(struct lws_context *context)
+{
+       struct lws *wsi;
+       int n;
+
+       /*
+        * Create the pt event pipes... these are unique in that they are
+        * not bound to a vhost or protocol (both are NULL)
+        */
+
+       for (n = 0; n < context->count_threads; n++) {
+               if (context->pt[n].pipe_wsi)
+                       continue;
+
+               wsi = lws_zalloc(sizeof(*wsi), "event pipe wsi");
+               if (!wsi) {
+                       lwsl_err("%s: Out of mem\n", __func__);
+                       return 1;
+               }
+               wsi->context = context;
+               lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_pipe);
+               wsi->protocol = NULL;
+               wsi->tsi = n;
+               wsi->vhost = NULL;
+               wsi->event_pipe = 1;
+               wsi->desc.sockfd = LWS_SOCK_INVALID;
+               context->pt[n].pipe_wsi = wsi;
+               context->count_wsi_allocated++;
+
+               if (lws_plat_pipe_create(wsi))
+                       /*
+                        * platform code returns 0 if it actually created pipes
+                        * and initialized pt->dummy_pipe_fds[].  If it used
+                        * some other mechanism outside of signaling in the
+                        * normal event loop, we skip treating the pipe as
+                        * related to dummy_pipe_fds[], adding it to the fds,
+                        * etc.
+                        */
+                       continue;
+
+               wsi->desc.sockfd = context->pt[n].dummy_pipe_fds[0];
+               lwsl_debug("event pipe fd %d\n", wsi->desc.sockfd);
+
+#if !defined(LWS_AMAZON_RTOS)
+               if (context->event_loop_ops->accept)
+                       if (context->event_loop_ops->accept(wsi))
+                               return 1;
+#endif
+
+               if (__insert_wsi_socket_into_fds(context, wsi))
+                       return 1;
+       }
+
+       return 0;
+}
+
+void
+lws_destroy_event_pipe(struct lws *wsi)
+{
+       lwsl_info("%s\n", __func__);
+       __remove_wsi_socket_from_fds(wsi);
+
+       if (wsi->context->event_loop_ops->wsi_logical_close) {
+               wsi->context->event_loop_ops->wsi_logical_close(wsi);
+               lws_plat_pipe_close(wsi);
+               return;
+       }
+
+       if (wsi->context->event_loop_ops->destroy_wsi)
+               wsi->context->event_loop_ops->destroy_wsi(wsi);
+       lws_plat_pipe_close(wsi);
+       wsi->context->count_wsi_allocated--;
+       lws_free(wsi);
+}
+
+
+void
+lws_vhost_destroy1(struct lws_vhost *vh)
+{
+       struct lws_context *context = vh->context;
+
+       lwsl_info("%s\n", __func__);
+
+       lws_context_lock(context, "vhost destroy 1"); /* ---------- context { */
+
+       if (vh->being_destroyed)
+               goto out;
+
+       lws_vhost_lock(vh); /* -------------- vh { */
+
+       vh->being_destroyed = 1;
+#if defined(LWS_WITH_NETWORK)
+       /*
+        * PHASE 1: take down or reassign any listen wsi
+        *
+        * Are there other vhosts that are piggybacking on our listen socket?
+        * If so we need to hand the listen socket off to one of the others
+        * so it will remain open.
+        *
+        * If not, leave it attached to the closing vhost, the vh being marked
+        * being_destroyed will defeat any service and it will get closed in
+        * later phases.
+        */
+
+       if (vh->lserv_wsi)
+               lws_start_foreach_ll(struct lws_vhost *, v,
+                                    context->vhost_list) {
+                       if (v != vh &&
+                           !v->being_destroyed &&
+                           v->listen_port == vh->listen_port &&
+                           ((!v->iface && !vh->iface) ||
+                           (v->iface && vh->iface &&
+                           !strcmp(v->iface, vh->iface)))) {
+                               /*
+                                * this can only be a listen wsi, which is
+                                * restricted... it has no protocol or other
+                                * bindings or states.  So we can simply
+                                * swap it to a vhost that has the same
+                                * iface + port, but is not closing.
+                                */
+                               assert(v->lserv_wsi == NULL);
+                               v->lserv_wsi = vh->lserv_wsi;
+
+                               lwsl_notice("%s: listen skt from %s to %s\n",
+                                           __func__, vh->name, v->name);
+
+                               if (v->lserv_wsi) {
+                                       lws_vhost_unbind_wsi(vh->lserv_wsi);
+                                       lws_vhost_bind_wsi(v, v->lserv_wsi);
+                               }
+
+                               break;
+                       }
+               } lws_end_foreach_ll(v, vhost_next);
+
+#endif
+
+       lws_vhost_unlock(vh); /* } vh -------------- */
+
+       /*
+        * lws_check_deferred_free() will notice there is a vhost that is
+        * marked for destruction during the next 1s, for all tsi.
+        *
+        * It will start closing all wsi on this vhost.  When the last wsi
+        * is closed, it will trigger lws_vhost_destroy2()
+        */
+
+out:
+       lws_context_unlock(context); /* --------------------------- context { */
+}
+
+#if defined(LWS_WITH_ABSTRACT)
+static int
+destroy_ais(struct lws_dll2 *d, void *user)
+{
+       lws_abs_t *ai = lws_container_of(d, lws_abs_t, abstract_instances);
+
+       lws_abs_destroy_instance(&ai);
+
+       return 0;
+}
+#endif
+
+void
+__lws_vhost_destroy2(struct lws_vhost *vh)
+{
+       const struct lws_protocols *protocol = NULL;
+       struct lws_context *context = vh->context;
+       struct lws_deferred_free *df;
+       struct lws wsi;
+       int n;
+
+       /*
+        * destroy any pending timed events
+        */
+
+       while (vh->timed_vh_protocol_list)
+               __lws_timed_callback_remove(vh, vh->timed_vh_protocol_list);
+
+       /*
+        * let the protocols destroy the per-vhost protocol objects
+        */
+
+       memset(&wsi, 0, sizeof(wsi));
+       wsi.context = vh->context;
+       wsi.vhost = vh; /* not a real bound wsi */
+       protocol = vh->protocols;
+       if (protocol && vh->created_vhost_protocols) {
+               n = 0;
+               while (n < vh->count_protocols) {
+                       wsi.protocol = protocol;
+
+                       if (protocol->callback)
+                               protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY,
+                                          NULL, NULL, 0);
+                       protocol++;
+                       n++;
+               }
+       }
+
+       /*
+        * remove vhost from context list of vhosts
+        */
+
+       lws_start_foreach_llp(struct lws_vhost **, pv, context->vhost_list) {
+               if (*pv == vh) {
+                       *pv = vh->vhost_next;
+                       break;
+               }
+       } lws_end_foreach_llp(pv, vhost_next);
+
+       /* add ourselves to the pending destruction list */
+
+       vh->vhost_next = vh->context->vhost_pending_destruction_list;
+       vh->context->vhost_pending_destruction_list = vh;
+
+       lwsl_info("%s: %p\n", __func__, vh);
+
+       /* if we are still on deferred free list, remove ourselves */
+
+       lws_start_foreach_llp(struct lws_deferred_free **, pdf,
+                             context->deferred_free_list) {
+               if ((*pdf)->payload == vh) {
+                       df = *pdf;
+                       *pdf = df->next;
+                       lws_free(df);
+                       break;
+               }
+       } lws_end_foreach_llp(pdf, next);
+
+       /* remove ourselves from the pending destruction list */
+
+       lws_start_foreach_llp(struct lws_vhost **, pv,
+                             context->vhost_pending_destruction_list) {
+               if ((*pv) == vh) {
+                       *pv = (*pv)->vhost_next;
+                       break;
+               }
+       } lws_end_foreach_llp(pv, vhost_next);
+
+       /*
+        * Free all the allocations associated with the vhost
+        */
+
+       protocol = vh->protocols;
+       if (protocol) {
+               n = 0;
+               while (n < vh->count_protocols) {
+                       if (vh->protocol_vh_privs &&
+                           vh->protocol_vh_privs[n]) {
+                               lws_free(vh->protocol_vh_privs[n]);
+                               vh->protocol_vh_privs[n] = NULL;
+                       }
+                       protocol++;
+                       n++;
+               }
+       }
+       if (vh->protocol_vh_privs)
+               lws_free(vh->protocol_vh_privs);
+       lws_ssl_SSL_CTX_destroy(vh);
+       lws_free(vh->same_vh_protocol_owner);
+
+       if (context->plugin_list ||
+           (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) ||
+           vh->allocated_vhost_protocols)
+               lws_free((void *)vh->protocols);
+#if defined(LWS_WITH_NETWORK)
+       LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+               if (ar->destroy_vhost)
+                       ar->destroy_vhost(vh);
+       LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+#endif
+
+#ifdef LWS_WITH_ACCESS_LOG
+       if (vh->log_fd != (int)LWS_INVALID_FILE)
+               close(vh->log_fd);
+#endif
+
+#if defined (LWS_WITH_TLS)
+       lws_free_set_NULL(vh->tls.alloc_cert_path);
+#endif
+
+#if LWS_MAX_SMP > 1
+       pthread_mutex_destroy(&vh->lock);
+#endif
+
+#if defined(LWS_WITH_UNIX_SOCK)
+       if (LWS_UNIX_SOCK_ENABLED(vh)) {
+               n = unlink(vh->iface);
+               if (n)
+                       lwsl_info("Closing unix socket %s: errno %d\n",
+                                 vh->iface, errno);
+       }
+#endif
+       /*
+        * although async event callbacks may still come for wsi handles with
+        * pending close in the case of asycn event library like libuv,
+        * they do not refer to the vhost.  So it's safe to free.
+        */
+
+       if (vh->finalize)
+               vh->finalize(vh, vh->finalize_arg);
+
+#if defined(LWS_WITH_ABSTRACT)
+       /*
+        * abstract instances
+        */
+
+       lws_dll2_foreach_safe(&vh->abstract_instances_owner, NULL, destroy_ais);
+#endif
+
+       lwsl_info("  %s: Freeing vhost %p\n", __func__, vh);
+
+       memset(vh, 0, sizeof(*vh));
+       lws_free(vh);
+}
+
+/*
+ * each service thread calls this once a second or so
+ */
+
+int
+lws_check_deferred_free(struct lws_context *context, int tsi, int force)
+{
+       struct lws_context_per_thread *pt;
+       int n;
+
+       /*
+        * If we see a vhost is being destroyed, forcibly close every wsi on
+        * this tsi associated with this vhost.  That will include the listen
+        * socket if it is still associated with the closing vhost.
+        *
+        * For SMP, we do this once per tsi per destroyed vhost.  The reference
+        * counting on the vhost as the bound wsi close will notice that there
+        * are no bound wsi left, that vhost destruction can complete,
+        * and perform it.  It doesn't matter which service thread does that
+        * because there is nothing left using the vhost to conflict.
+        */
+
+       lws_context_lock(context, "check deferred free"); /* ------ context { */
+
+       lws_start_foreach_ll_safe(struct lws_vhost *, v, context->vhost_list, vhost_next) {
+               if (v->being_destroyed
+#if LWS_MAX_SMP > 1
+                       && !v->close_flow_vs_tsi[tsi]
+#endif
+               ) {
+
+                       pt = &context->pt[tsi];
+
+                       lws_pt_lock(pt, "vhost removal"); /* -------------- pt { */
+
+#if LWS_MAX_SMP > 1
+                       v->close_flow_vs_tsi[tsi] = 1;
+#endif
+
+                       for (n = 0; (unsigned int)n < pt->fds_count; n++) {
+                               struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
+                               if (!wsi)
+                                       continue;
+                               if (wsi->vhost != v)
+                                       continue;
+
+                               __lws_close_free_wsi(wsi,
+                                       LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY,
+                                       "vh destroy"
+                                       /* no protocol close */);
+                               n--;
+                       }
+
+                       lws_pt_unlock(pt); /* } pt -------------- */
+               }
+       } lws_end_foreach_ll_safe(v);
+
+
+       lws_context_unlock(context); /* } context ------------------- */
+
+       return 0;
+}
+
+
+LWS_VISIBLE void
+lws_vhost_destroy(struct lws_vhost *vh)
+{
+       struct lws_deferred_free *df = lws_malloc(sizeof(*df), "deferred free");
+       struct lws_context *context = vh->context;
+
+       if (!df)
+               return;
+
+       lws_context_lock(context, __func__); /* ------ context { */
+
+       lws_vhost_destroy1(vh);
+
+       if (!vh->count_bound_wsi) {
+               /*
+                * After listen handoff, there are already no wsi bound to this
+                * vhost by any pt: nothing can be servicing any wsi belonging
+                * to it any more.
+                *
+                * Finalize the vh destruction immediately
+                */
+               __lws_vhost_destroy2(vh);
+               lws_free(df);
+
+               goto out;
+       }
+
+       /* part 2 is deferred to allow all the handle closes to complete */
+
+       df->next = vh->context->deferred_free_list;
+       df->deadline = lws_now_secs();
+       df->payload = vh;
+       vh->context->deferred_free_list = df;
+
+out:
+       lws_context_unlock(context); /* } context ------------------- */
+}
+
+
+LWS_EXTERN void *
+lws_vhost_user(struct lws_vhost *vhost)
+{
+       return vhost->user;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_get_vhost_listen_port(struct lws_vhost *vhost)
+{
+       return vhost->listen_port;
+}
+
+
+LWS_VISIBLE LWS_EXTERN void
+lws_context_deprecate(struct lws_context *context, lws_reload_func cb)
+{
+       struct lws_vhost *vh = context->vhost_list, *vh1;
+
+       /*
+        * "deprecation" means disable the context from accepting any new
+        * connections and free up listen sockets to be used by a replacement
+        * context.
+        *
+        * Otherwise the deprecated context remains operational, until its
+        * number of connected sockets falls to zero, when it is deleted.
+        */
+
+       /* for each vhost, close his listen socket */
+
+       while (vh) {
+               struct lws *wsi = vh->lserv_wsi;
+
+               if (wsi) {
+                       wsi->socket_is_permanently_unusable = 1;
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "ctx deprecate");
+                       wsi->context->deprecation_pending_listen_close_count++;
+                       /*
+                        * other vhosts can share the listen port, they
+                        * point to the same wsi.  So zap those too.
+                        */
+                       vh1 = context->vhost_list;
+                       while (vh1) {
+                               if (vh1->lserv_wsi == wsi)
+                                       vh1->lserv_wsi = NULL;
+                               vh1 = vh1->vhost_next;
+                       }
+               }
+               vh = vh->vhost_next;
+       }
+
+       context->deprecated = 1;
+       context->deprecation_cb = cb;
+}
+
+#if defined(LWS_WITH_NETWORK)
+struct lws_vhost *
+lws_get_vhost_by_name(struct lws_context *context, const char *name)
+{
+       lws_start_foreach_ll(struct lws_vhost *, v,
+                            context->vhost_list) {
+               if (!strcmp(v->name, name))
+                       return v;
+
+       } lws_end_foreach_ll(v, vhost_next);
+
+       return NULL;
+}
+#endif
diff --git a/lib/core-net/wsi-timeout.c b/lib/core-net/wsi-timeout.c
new file mode 100644 (file)
index 0000000..88d0cb3
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+void
+__lws_wsi_remove_from_sul(struct lws *wsi)
+{
+       //struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       //lwsl_notice("%s: wsi %p, to %p, hr %p\n", __func__, wsi,
+       //              &wsi->sul_timeout.list, &wsi->sul_hrtimer.list);
+
+       // lws_dll2_describe(&pt->pt_sul_owner, "pre-remove");
+       lws_dll2_remove(&wsi->sul_timeout.list);
+       lws_dll2_remove(&wsi->sul_hrtimer.list);
+       // lws_dll2_describe(&pt->pt_sul_owner, "post-remove");
+}
+
+/*
+ * hrtimer
+ */
+
+static void
+lws_sul_hrtimer_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws *wsi = lws_container_of(sul, struct lws, sul_hrtimer);
+
+       if (wsi->protocol &&
+           wsi->protocol->callback(wsi, LWS_CALLBACK_TIMER,
+                                   wsi->user_space, NULL, 0))
+               __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                    "hrtimer cb errored");
+}
+
+void
+__lws_set_timer_usecs(struct lws *wsi, lws_usec_t us)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       wsi->sul_hrtimer.cb = lws_sul_hrtimer_cb;
+       __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_hrtimer, us);
+}
+
+LWS_VISIBLE void
+lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs)
+{
+       __lws_set_timer_usecs(wsi, usecs);
+}
+
+/*
+ * wsi timeout
+ */
+
+static void
+lws_sul_wsitimeout_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws *wsi = lws_container_of(sul, struct lws, sul_timeout);
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       if (wsi->pending_timeout != PENDING_TIMEOUT_USER_OK)
+               lws_stats_bump(pt, LWSSTATS_C_TIMEOUTS, 1);
+
+       /* no need to log normal idle keepalive timeout */
+//             if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE)
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (wsi->pending_timeout != PENDING_TIMEOUT_USER_OK)
+               lwsl_info("wsi %p: TIMEDOUT WAITING on %d "
+                         "(did hdr %d, ah %p, wl %d)\n",
+                         (void *)wsi, wsi->pending_timeout,
+                         wsi->hdr_parsing_completed, wsi->http.ah,
+                         pt->http.ah_wait_list_length);
+#if defined(LWS_WITH_CGI)
+       if (wsi->http.cgi)
+               lwsl_notice("CGI timeout: %s\n", wsi->http.cgi->summary);
+#endif
+#else
+       if (wsi->pending_timeout != PENDING_TIMEOUT_USER_OK)
+               lwsl_info("wsi %p: TIMEDOUT WAITING on %d ", (void *)wsi,
+                               wsi->pending_timeout);
+#endif
+       /* cgi timeout */
+       if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE)
+               /*
+                * Since he failed a timeout, he already had a chance to
+                * do something and was unable to... that includes
+                * situations like half closed connections.  So process
+                * this "failed timeout" close as a violent death and
+                * don't try to do protocol cleanup like flush partials.
+                */
+               wsi->socket_is_permanently_unusable = 1;
+#if !defined(LWS_NO_CLIENT)
+       if (lwsi_state(wsi) == LRS_WAITING_SSL)
+               lws_inform_client_conn_fail(wsi,
+                       (void *)"Timed out waiting SSL", 21);
+#endif
+
+       __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "timeout");
+}
+
+void
+__lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       wsi->sul_timeout.cb = lws_sul_wsitimeout_cb;
+       __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_timeout,
+                        ((lws_usec_t)secs) * LWS_US_PER_SEC);
+
+       lwsl_debug("%s: %p: %d secs, reason %d\n", __func__, wsi, secs, reason);
+
+       wsi->pending_timeout = reason;
+}
+
+void
+lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_pt_lock(pt, __func__);
+       lws_dll2_remove(&wsi->sul_timeout.list);
+       lws_pt_unlock(pt);
+
+       if (!secs)
+               return;
+
+       if (secs == LWS_TO_KILL_SYNC) {
+               lwsl_debug("synchronously killing %p\n", wsi);
+               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                  "to sync kill");
+               return;
+       }
+
+       if (secs == LWS_TO_KILL_ASYNC)
+               secs = 0;
+
+       lws_pt_lock(pt, __func__);
+       __lws_set_timeout(wsi, reason, secs);
+       lws_pt_unlock(pt);
+}
+
+void
+lws_set_timeout_us(struct lws *wsi, enum pending_timeout reason, lws_usec_t us)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_pt_lock(pt, __func__);
+       lws_dll2_remove(&wsi->sul_timeout.list);
+       lws_pt_unlock(pt);
+
+       if (!us)
+               return;
+
+       lws_pt_lock(pt, __func__);
+       __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_timeout, us);
+
+       lwsl_debug("%s: %p: %llu us, reason %d\n", __func__, wsi,
+                  (unsigned long long)us, reason);
+
+       wsi->pending_timeout = reason;
+       lws_pt_unlock(pt);
+}
+
+/* requires context + vh lock */
+
+int
+__lws_timed_callback_remove(struct lws_vhost *vh, struct lws_timed_vh_protocol *p)
+{
+       lws_start_foreach_llp(struct lws_timed_vh_protocol **, pt,
+                             vh->timed_vh_protocol_list) {
+               if (*pt == p) {
+                       *pt = p->next;
+                       lws_dll2_remove(&p->sul.list);
+                       lws_free(p);
+
+                       return 0;
+               }
+       } lws_end_foreach_llp(pt, next);
+
+       return 1;
+}
+
+void
+lws_sul_timed_callback_vh_protocol_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws_timed_vh_protocol *tvp = lws_container_of(sul,
+                                       struct lws_timed_vh_protocol, sul);
+       struct lws_context_per_thread *pt =
+                               &tvp->vhost->context->pt[tvp->tsi_req];
+
+       pt->fake_wsi->context = tvp->vhost->context;
+
+       pt->fake_wsi->vhost = tvp->vhost; /* not a real bound wsi */
+       pt->fake_wsi->protocol = tvp->protocol;
+
+       lwsl_debug("%s: timed cb: vh %s, protocol %s, reason %d\n", __func__,
+                  tvp->vhost->name, tvp->protocol->name, tvp->reason);
+
+       tvp->protocol->callback(pt->fake_wsi, tvp->reason, NULL, NULL, 0);
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_timed_callback_vh_protocol_us(struct lws_vhost *vh,
+                                 const struct lws_protocols *prot, int reason,
+                                 lws_usec_t us)
+{
+       struct lws_timed_vh_protocol *p = (struct lws_timed_vh_protocol *)
+                       lws_malloc(sizeof(*p), "timed_vh");
+
+       if (!p)
+               return 1;
+
+       memset(p, 0, sizeof(*p));
+
+       p->tsi_req = lws_pthread_self_to_tsi(vh->context);
+       if (p->tsi_req < 0) /* not called from a service thread --> tsi 0 */
+               p->tsi_req = 0;
+
+       lws_context_lock(vh->context, __func__); /* context ----------------- */
+
+       p->protocol = prot;
+       p->reason = reason;
+       p->vhost = vh;
+
+       p->sul.cb = lws_sul_timed_callback_vh_protocol_cb;
+       /* list is always at the very top of the sul */
+       __lws_sul_insert(&vh->context->pt[p->tsi_req].pt_sul_owner,
+                        (lws_sorted_usec_list_t *)&p->sul.list, us);
+
+       // lwsl_notice("%s: %s.%s %d\n", __func__, vh->name, prot->name, secs);
+
+       lws_vhost_lock(vh); /* vhost ---------------------------------------- */
+       p->next = vh->timed_vh_protocol_list;
+       vh->timed_vh_protocol_list = p;
+       lws_vhost_unlock(vh); /* -------------------------------------- vhost */
+
+       lws_context_unlock(vh->context); /* ------------------------- context */
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_timed_callback_vh_protocol(struct lws_vhost *vh,
+                              const struct lws_protocols *prot, int reason,
+                              int secs)
+{
+       return lws_timed_callback_vh_protocol_us(vh, prot, reason,
+                                       ((lws_usec_t)secs) * LWS_US_PER_SEC);
+}
diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c
new file mode 100644 (file)
index 0000000..3968f62
--- /dev/null
@@ -0,0 +1,887 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#if defined (_DEBUG)
+void lwsi_set_role(struct lws *wsi, lws_wsi_state_t role)
+{
+       wsi->wsistate = (wsi->wsistate & (~LWSI_ROLE_MASK)) | role;
+
+       lwsl_debug("lwsi_set_role(%p, 0x%lx)\n", wsi,
+                                       (unsigned long)wsi->wsistate);
+}
+
+void lwsi_set_state(struct lws *wsi, lws_wsi_state_t lrs)
+{
+       wsi->wsistate = (wsi->wsistate & (~LRS_MASK)) | lrs;
+
+       lwsl_debug("lwsi_set_state(%p, 0x%lx)\n", wsi,
+                                       (unsigned long)wsi->wsistate);
+}
+#endif
+
+
+void
+lws_vhost_bind_wsi(struct lws_vhost *vh, struct lws *wsi)
+{
+       if (wsi->vhost == vh)
+               return;
+       lws_context_lock(vh->context, __func__); /* ---------- context { */
+       wsi->vhost = vh;
+       vh->count_bound_wsi++;
+       lws_context_unlock(vh->context); /* } context ---------- */
+       lwsl_info("%s: vh %s: count_bound_wsi %d\n",
+                   __func__, vh->name, vh->count_bound_wsi);
+       assert(wsi->vhost->count_bound_wsi > 0);
+}
+
+void
+lws_vhost_unbind_wsi(struct lws *wsi)
+{
+       if (!wsi->vhost)
+               return;
+
+       lws_context_lock(wsi->context, __func__); /* ---------- context { */
+
+       assert(wsi->vhost->count_bound_wsi > 0);
+       wsi->vhost->count_bound_wsi--;
+       lwsl_info("%s: vh %s: count_bound_wsi %d\n", __func__,
+                 wsi->vhost->name, wsi->vhost->count_bound_wsi);
+
+       if (!wsi->vhost->count_bound_wsi &&
+           wsi->vhost->being_destroyed) {
+               /*
+                * We have closed all wsi that were bound to this vhost
+                * by any pt: nothing can be servicing any wsi belonging
+                * to it any more.
+                *
+                * Finalize the vh destruction
+                */
+               __lws_vhost_destroy2(wsi->vhost);
+       }
+       wsi->vhost = NULL;
+
+       lws_context_unlock(wsi->context); /* } context ---------- */
+}
+
+LWS_VISIBLE struct lws *
+lws_get_network_wsi(struct lws *wsi)
+{
+       if (!wsi)
+               return NULL;
+
+#if defined(LWS_WITH_HTTP2)
+       if (!wsi->http2_substream
+#if !defined(LWS_NO_CLIENT)
+                       && !wsi->client_h2_substream
+#endif
+       )
+               return wsi;
+
+       while (wsi->h2.parent_wsi)
+               wsi = wsi->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)
+{
+       int n;
+
+       for (n = 0; n < vh->count_protocols; n++)
+               if (vh->protocols[n].name && !strcmp(name, vh->protocols[n].name))
+                       return &vh->protocols[n];
+
+       return NULL;
+}
+
+LWS_VISIBLE int
+lws_callback_all_protocol(struct lws_context *context,
+                         const struct lws_protocols *protocol, int reason)
+{
+       struct lws_context_per_thread *pt = &context->pt[0];
+       unsigned int n, m = context->count_threads;
+       struct lws *wsi;
+
+       while (m--) {
+               for (n = 0; n < pt->fds_count; n++) {
+                       wsi = wsi_from_fd(context, pt->fds[n].fd);
+                       if (!wsi)
+                               continue;
+                       if (wsi->protocol == protocol)
+                               protocol->callback(wsi, reason, wsi->user_space,
+                                                  NULL, 0);
+               }
+               pt++;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_callback_all_protocol_vhost_args(struct lws_vhost *vh,
+                         const struct lws_protocols *protocol, int reason,
+                         void *argp, size_t len)
+{
+       struct lws_context *context = vh->context;
+       struct lws_context_per_thread *pt = &context->pt[0];
+       unsigned int n, m = context->count_threads;
+       struct lws *wsi;
+
+       while (m--) {
+               for (n = 0; n < pt->fds_count; n++) {
+                       wsi = wsi_from_fd(context, pt->fds[n].fd);
+                       if (!wsi)
+                               continue;
+                       if (wsi->vhost == vh && (wsi->protocol == protocol ||
+                                                !protocol))
+                               wsi->protocol->callback(wsi, reason,
+                                               wsi->user_space, argp, len);
+               }
+               pt++;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_callback_all_protocol_vhost(struct lws_vhost *vh,
+                         const struct lws_protocols *protocol, int reason)
+{
+       return lws_callback_all_protocol_vhost_args(vh, protocol, reason, NULL, 0);
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len)
+{
+       int n;
+
+       for (n = 0; n < wsi->vhost->count_protocols; n++)
+               if (wsi->vhost->protocols[n].callback(wsi, reason, NULL, in, len))
+                       return 1;
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_callback_vhost_protocols_vhost(struct lws_vhost *vh, int reason, void *in,
+                                  size_t len)
+{
+       int n;
+       struct lws *wsi = lws_zalloc(sizeof(*wsi), "fake wsi");
+
+       if (!wsi)
+               return 1;
+
+       wsi->context = vh->context;
+       lws_vhost_bind_wsi(vh, wsi);
+
+       for (n = 0; n < wsi->vhost->count_protocols; n++) {
+               wsi->protocol = &vh->protocols[n];
+               if (wsi->protocol->callback(wsi, reason, NULL, in, len)) {
+                       lws_free(wsi);
+                       return 1;
+               }
+       }
+
+       lws_free(wsi);
+
+       return 0;
+}
+
+
+LWS_VISIBLE int
+lws_rx_flow_control(struct lws *wsi, int _enable)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int en = _enable;
+
+       // h2 ignores rx flow control atm
+       if (lwsi_role_h2(wsi) || wsi->http2_substream ||
+           lwsi_role_h2_ENCAPSULATION(wsi))
+               return 0; // !!!
+
+       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;
+       }
+
+       lws_pt_lock(pt, __func__);
+
+       /* 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)
+               goto skip;
+
+       wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE |
+                               (!wsi->rxflow_bitmap);
+
+       lwsl_info("%s: %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) {
+               en = __lws_rx_flow_control(wsi);
+               lws_pt_unlock(pt);
+
+               return en;
+       }
+
+skip:
+       lws_pt_unlock(pt);
+
+       return 0;
+}
+
+LWS_VISIBLE void
+lws_rx_flow_allow_all_protocol(const struct lws_context *context,
+                              const struct lws_protocols *protocol)
+{
+       const struct lws_context_per_thread *pt = &context->pt[0];
+       struct lws *wsi;
+       unsigned int n, m = context->count_threads;
+
+       while (m--) {
+               for (n = 0; n < pt->fds_count; n++) {
+                       wsi = wsi_from_fd(context, pt->fds[n].fd);
+                       if (!wsi)
+                               continue;
+                       if (wsi->protocol == protocol)
+                               lws_rx_flow_control(wsi, LWS_RXFLOW_ALLOW);
+               }
+               pt++;
+       }
+}
+
+int user_callback_handle_rxflow(lws_callback_function callback_function,
+                               struct lws *wsi,
+                               enum lws_callback_reasons reason, void *user,
+                               void *in, size_t len)
+{
+       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);
+
+       return n;
+}
+
+LWS_EXTERN int
+__lws_rx_flow_control(struct lws *wsi)
+{
+       struct lws *wsic = wsi->child_list;
+
+       // h2 ignores rx flow control atm
+       if (lwsi_role_h2(wsi) || wsi->http2_substream ||
+           lwsi_role_h2_ENCAPSULATION(wsi))
+               return 0; // !!!
+
+       /* if he has children, do those if they were changed */
+       while (wsic) {
+               if (wsic->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)
+                       __lws_rx_flow_control(wsic);
+
+               wsic = wsic->sibling_list;
+       }
+
+       /* there is no pending change */
+       if (!(wsi->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE))
+               return 0;
+
+       /* stuff is still buffered, not ready to really accept new input */
+       if (lws_buflist_next_segment_len(&wsi->buflist, NULL)) {
+               /* get ourselves called back to deal with stashed buffer */
+               lws_callback_on_writable(wsi);
+               // return 0;
+       }
+
+       /* now the pending is cleared, we can change rxflow state */
+
+       wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE;
+
+       lwsl_info("rxflow: wsi %p change_to %d\n", wsi,
+                 wsi->rxflow_change_to & LWS_RXFLOW_ALLOW);
+
+       /* adjust the pollfd for this wsi */
+
+       if (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW) {
+               lwsl_info("%s: reenable POLLIN\n", __func__);
+               // lws_buflist_describe(&wsi->buflist, NULL);
+               if (__lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
+                       lwsl_info("%s: fail\n", __func__);
+                       return -1;
+               }
+       } else
+               if (__lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                       return -1;
+
+       return 0;
+}
+
+
+LWS_VISIBLE const struct lws_protocols *
+lws_get_protocol(struct lws *wsi)
+{
+       return wsi->protocol;
+}
+
+
+int
+lws_ensure_user_space(struct lws *wsi)
+{
+       if (!wsi->protocol)
+               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) {
+                       lwsl_err("%s: OOM\n", __func__);
+                       return 1;
+               }
+       } else
+               lwsl_debug("%s: %p protocol pss %lu, user_space=%p\n", __func__,
+                          wsi, (long)wsi->protocol->per_session_data_size,
+                          wsi->user_space);
+       return 0;
+}
+
+LWS_VISIBLE void *
+lws_adjust_protocol_psds(struct lws *wsi, size_t new_size)
+{
+       ((struct lws_protocols *)lws_get_protocol(wsi))->per_session_data_size =
+               new_size;
+
+       if (lws_ensure_user_space(wsi))
+                       return NULL;
+
+       return wsi->user_space;
+}
+
+
+
+LWS_VISIBLE int
+lws_is_ssl(struct lws *wsi)
+{
+#if defined(LWS_WITH_TLS)
+       return wsi->tls.use_ssl & LCCSCF_USE_SSL;
+#else
+       (void)wsi;
+       return 0;
+#endif
+}
+
+#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS)
+LWS_VISIBLE lws_tls_conn*
+lws_get_ssl(struct lws *wsi)
+{
+       return wsi->tls.ssl;
+}
+#endif
+
+LWS_VISIBLE int
+lws_partial_buffered(struct lws *wsi)
+{
+       return lws_has_buffered_out(wsi);
+}
+
+LWS_VISIBLE lws_fileofs_t
+lws_get_peer_write_allowance(struct lws *wsi)
+{
+       if (!wsi->role_ops->tx_credit)
+               return -1;
+       return wsi->role_ops->tx_credit(wsi);
+}
+
+LWS_VISIBLE void
+lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state,
+                   const struct lws_role_ops *ops)
+{
+#if defined(_DEBUG)
+       const char *name = "(unset)";
+#endif
+       wsi->wsistate = role | state;
+       if (ops)
+               wsi->role_ops = ops;
+#if defined(_DEBUG)
+       if (wsi->role_ops)
+               name = wsi->role_ops->name;
+       lwsl_debug("%s: %p: wsistate 0x%lx, ops %s\n", __func__, wsi,
+                  (unsigned long)wsi->wsistate, name);
+#endif
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_parse_uri(char *p, const char **prot, const char **ads, int *port,
+             const char **path)
+{
+       const char *end;
+       char unix_skt = 0;
+
+       /* cut up the location into address, port and path */
+       *prot = p;
+       while (*p && (*p != ':' || p[1] != '/' || p[2] != '/'))
+               p++;
+       if (!*p) {
+               end = p;
+               p = (char *)*prot;
+               *prot = end;
+       } else {
+               *p = '\0';
+               p += 3;
+       }
+       if (*p == '+') /* unix skt */
+               unix_skt = 1;
+
+       *ads = p;
+       if (!strcmp(*prot, "http") || !strcmp(*prot, "ws"))
+               *port = 80;
+       else if (!strcmp(*prot, "https") || !strcmp(*prot, "wss"))
+               *port = 443;
+
+       if (*p == '[') {
+               ++(*ads);
+               while (*p && *p != ']')
+                       p++;
+               if (*p)
+                       *p++ = '\0';
+       } else
+               while (*p && *p != ':' && (unix_skt || *p != '/'))
+                       p++;
+
+       if (*p == ':') {
+               *p++ = '\0';
+               *port = atoi(p);
+               while (*p && *p != '/')
+                       p++;
+       }
+       *path = "/";
+       if (*p) {
+               *p++ = '\0';
+               if (*p)
+                       *path = p;
+       }
+
+       return 0;
+}
+
+/* ... */
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len)
+{
+       int n = 0, sl = (int)strlen(name);
+
+       while (lws_hdr_copy_fragment(wsi, buf, len,
+                         WSI_TOKEN_HTTP_URI_ARGS, n) >= 0) {
+
+               if (!strncmp(buf, name, sl))
+                       return buf + sl;
+
+               n++;
+       }
+
+       return NULL;
+}
+
+
+#if defined(LWS_WITHOUT_EXTENSIONS)
+
+/* we need to provide dummy callbacks for internal exts
+ * so user code runs when faced with a lib compiled with
+ * extensions disabled.
+ */
+
+LWS_VISIBLE int
+lws_extension_callback_pm_deflate(struct lws_context *context,
+                                  const struct lws_extension *ext,
+                                  struct lws *wsi,
+                                  enum lws_extension_callback_reasons reason,
+                                  void *user, void *in, size_t len)
+{
+       (void)context;
+       (void)ext;
+       (void)wsi;
+       (void)reason;
+       (void)user;
+       (void)in;
+       (void)len;
+
+       return 0;
+}
+
+LWS_EXTERN int
+lws_set_extension_option(struct lws *wsi, const char *ext_name,
+                        const char *opt_name, const char *opt_val)
+{
+       return -1;
+}
+#endif
+
+LWS_VISIBLE LWS_EXTERN int
+lws_is_cgi(struct lws *wsi) {
+#ifdef LWS_WITH_CGI
+       return !!wsi->http.cgi;
+#else
+       return 0;
+#endif
+}
+
+const struct lws_protocol_vhost_options *
+lws_pvo_search(const struct lws_protocol_vhost_options *pvo, const char *name)
+{
+       while (pvo) {
+               if (!strcmp(pvo->name, name))
+                       break;
+
+               pvo = pvo->next;
+       }
+
+       return pvo;
+}
+
+int
+lws_pvo_get_str(void *in, const char *name, const char **result)
+{
+       const struct lws_protocol_vhost_options *pv =
+               lws_pvo_search((const struct lws_protocol_vhost_options *)in,
+                               name);
+
+       if (!pv)
+               return 1;
+
+       *result = (const char *)pv->value;
+
+       return 0;
+}
+
+int
+lws_broadcast(struct lws_context_per_thread *pt, int reason, void *in, size_t len)
+{
+       struct lws_vhost *v = pt->context->vhost_list;
+       int n, ret = 0;
+
+       pt->fake_wsi->context = pt->context;
+
+       while (v) {
+               const struct lws_protocols *p = v->protocols;
+               pt->fake_wsi->vhost = v; /* not a real bound wsi */
+
+               for (n = 0; n < v->count_protocols; n++) {
+                       pt->fake_wsi->protocol = p;
+                       if (p->callback &&
+                           p->callback(pt->fake_wsi, reason, NULL, in, len))
+                               ret |= 1;
+                       p++;
+               }
+               v = v->vhost_next;
+       }
+
+       return ret;
+}
+
+LWS_VISIBLE LWS_EXTERN void *
+lws_wsi_user(struct lws *wsi)
+{
+       return wsi->user_space;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_set_wsi_user(struct lws *wsi, void *data)
+{
+       if (wsi->user_space_externally_allocated)
+               wsi->user_space = data;
+       else
+               lwsl_err("%s: Cannot set internally-allocated user_space\n",
+                        __func__);
+}
+
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_get_parent(const struct lws *wsi)
+{
+       return wsi->parent;
+}
+
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_get_child(const struct lws *wsi)
+{
+       return wsi->child_list;
+}
+
+LWS_VISIBLE LWS_EXTERN void *
+lws_get_opaque_parent_data(const struct lws *wsi)
+{
+       return wsi->opaque_parent_data;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_set_opaque_parent_data(struct lws *wsi, void *data)
+{
+       wsi->opaque_parent_data = data;
+}
+
+LWS_VISIBLE LWS_EXTERN void *
+lws_get_opaque_user_data(const struct lws *wsi)
+{
+       return wsi->opaque_user_data;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_set_opaque_user_data(struct lws *wsi, void *data)
+{
+       wsi->opaque_user_data = data;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_get_child_pending_on_writable(const struct lws *wsi)
+{
+       return wsi->parent_pending_cb_on_writable;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_clear_child_pending_on_writable(struct lws *wsi)
+{
+       wsi->parent_pending_cb_on_writable = 0;
+}
+
+
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_vhost_name(struct lws_vhost *vhost)
+{
+       return vhost->name;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_get_vhost_port(struct lws_vhost *vhost)
+{
+       return vhost->listen_port;
+}
+
+LWS_VISIBLE LWS_EXTERN void *
+lws_get_vhost_user(struct lws_vhost *vhost)
+{
+       return vhost->user;
+}
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_vhost_iface(struct lws_vhost *vhost)
+{
+       return vhost->iface;
+}
+
+LWS_VISIBLE lws_sockfd_type
+lws_get_socket_fd(struct lws *wsi)
+{
+       if (!wsi)
+               return -1;
+       return wsi->desc.sockfd;
+}
+
+
+LWS_VISIBLE struct lws_vhost *
+lws_vhost_get(struct lws *wsi)
+{
+       return wsi->vhost;
+}
+
+LWS_VISIBLE struct lws_vhost *
+lws_get_vhost(struct lws *wsi)
+{
+       return wsi->vhost;
+}
+
+LWS_VISIBLE const struct lws_protocols *
+lws_protocol_get(struct lws *wsi)
+{
+       return wsi->protocol;
+}
+
+LWS_VISIBLE const struct lws_udp *
+lws_get_udp(const struct lws *wsi)
+{
+       return wsi->udp;
+}
+
+LWS_VISIBLE LWS_EXTERN struct lws_context *
+lws_get_context(const struct lws *wsi)
+{
+       return wsi->context;
+}
+
+#ifdef LWS_LATENCY
+void
+lws_latency(struct lws_context *context, struct lws *wsi, const char *action,
+           int ret, int completed)
+{
+       unsigned long long u;
+       char buf[256];
+
+       u = lws_now_usecs();
+
+       if (!action) {
+               wsi->latency_start = u;
+               if (!wsi->action_start)
+                       wsi->action_start = u;
+               return;
+       }
+       if (completed) {
+               if (wsi->action_start == wsi->latency_start)
+                       sprintf(buf,
+                         "Completion first try lat %lluus: %p: ret %d: %s\n",
+                                       u - wsi->latency_start,
+                                                     (void *)wsi, ret, action);
+               else
+                       sprintf(buf,
+                         "Completion %lluus: lat %lluus: %p: ret %d: %s\n",
+                               u - wsi->action_start,
+                                       u - wsi->latency_start,
+                                                     (void *)wsi, ret, action);
+               wsi->action_start = 0;
+       } else
+               sprintf(buf, "lat %lluus: %p: ret %d: %s\n",
+                             u - wsi->latency_start, (void *)wsi, ret, action);
+
+       if (u - wsi->latency_start > context->worst_latency) {
+               context->worst_latency = u - wsi->latency_start;
+               strcpy(context->worst_latency_info, buf);
+       }
+       lwsl_latency("%s", buf);
+}
+#endif
+
+LWS_VISIBLE int LWS_WARN_UNUSED_RESULT
+lws_raw_transaction_completed(struct lws *wsi)
+{
+       if (lws_has_buffered_out(wsi)) {
+               /*
+                * ...so he tried to send something large, but it went out
+                * as a partial, but he immediately called us to say he wants
+                * to close the connection.
+                *
+                * Defer the close until the last part of the partial is sent.
+                *
+                */
+               lwsl_debug("%s: %p: deferring due to partial\n", __func__, wsi);
+               wsi->close_when_buffered_out_drained = 1;
+               lws_callback_on_writable(wsi);
+
+               return 0;
+       }
+
+       return -1;
+}
+
+int
+lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p,
+                 const char *reason)
+{
+//     if (wsi->protocol == p)
+//             return 0;
+       const struct lws_protocols *vp = wsi->vhost->protocols, *vpo;
+
+       if (wsi->protocol && wsi->protocol_bind_balance) {
+               wsi->protocol->callback(wsi,
+                      wsi->role_ops->protocol_unbind_cb[!!lwsi_role_server(wsi)],
+                                       wsi->user_space, (void *)reason, 0);
+               wsi->protocol_bind_balance = 0;
+       }
+       if (!wsi->user_space_externally_allocated)
+               lws_free_set_NULL(wsi->user_space);
+
+       lws_same_vh_protocol_remove(wsi);
+
+       wsi->protocol = p;
+       if (!p)
+               return 0;
+
+       if (lws_ensure_user_space(wsi))
+               return 1;
+
+       if (p > vp && p < &vp[wsi->vhost->count_protocols])
+               lws_same_vh_protocol_insert(wsi, (int)(p - vp));
+       else {
+               int n = wsi->vhost->count_protocols;
+               int hit = 0;
+
+               vpo = vp;
+
+               while (n--) {
+                       if (p->name && vp->name && !strcmp(p->name, vp->name)) {
+                               hit = 1;
+                               lws_same_vh_protocol_insert(wsi, (int)(vp - vpo));
+                               break;
+                       }
+                       vp++;
+               }
+               if (!hit)
+                       lwsl_err("%s: %p is not in vhost '%s' protocols list\n",
+                                __func__, p, wsi->vhost->name);
+       }
+
+       if (wsi->protocol->callback(wsi, wsi->role_ops->protocol_bind_cb[
+                                   !!lwsi_role_server(wsi)],
+                                   wsi->user_space, NULL, 0))
+               return 1;
+
+       wsi->protocol_bind_balance = 1;
+
+       return 0;
+}
+
+int
+lws_http_mark_sse(struct lws *wsi)
+{
+       lws_http_headers_detach(wsi);
+       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+       if (wsi->http2_substream) {
+               struct lws *nwsi = lws_get_network_wsi(wsi);
+
+               wsi->h2_stream_carries_sse = 1;
+               nwsi->immortal_substream_count++;
+               if (nwsi->immortal_substream_count == 1)
+                       lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0);
+       }
+
+       return 0;
+}
diff --git a/lib/core/alloc.c b/lib/core/alloc.c
new file mode 100644 (file)
index 0000000..09d8882
--- /dev/null
@@ -0,0 +1,156 @@
+#include "core/private.h"
+
+#if defined(LWS_HAVE_MALLOC_USABLE_SIZE)
+
+#include <malloc.h>
+
+/* the heap is processwide */
+static size_t allocated;
+#endif
+
+#if defined(LWS_PLAT_OPTEE)
+
+#define TEE_USER_MEM_HINT_NO_FILL_ZERO       0x80000000
+#if defined (LWS_WITH_NETWORK)
+
+/* normal TA apis */
+
+void *__attribute__((weak))
+       TEE_Malloc(uint32_t size, uint32_t hint)
+{
+       return NULL;
+}
+void *__attribute__((weak))
+       TEE_Realloc(void *buffer, uint32_t newSize)
+{
+       return NULL;
+}
+void __attribute__((weak))
+       TEE_Free(void *buffer)
+{
+}
+#else
+
+/* in-OP-TEE core apis */
+
+void *
+       TEE_Malloc(uint32_t size, uint32_t hint)
+{
+       return malloc(size);
+}
+void *
+       TEE_Realloc(void *buffer, uint32_t newSize)
+{
+       return realloc(buffer, newSize);
+}
+void
+       TEE_Free(void *buffer)
+{
+       free(buffer);
+}
+
+#endif
+
+void *lws_realloc(void *ptr, size_t size, const char *reason)
+{
+       return TEE_Realloc(ptr, size);
+}
+
+void *lws_malloc(size_t size, const char *reason)
+{
+       return TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO);
+}
+
+void lws_free(void *p)
+{
+       TEE_Free(p);
+}
+
+void *lws_zalloc(size_t size, const char *reason)
+{
+       void *ptr = TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO);
+       if (ptr)
+               memset(ptr, 0, size);
+       return ptr;
+}
+
+void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason))
+{
+       (void)cb;
+}
+#else
+
+static void *
+_realloc(void *ptr, size_t size, const char *reason)
+{
+       void *v;
+
+       if (size) {
+#if defined(LWS_WITH_ESP32)
+               lwsl_notice("%s: size %lu: %s (free heap %d)\n", __func__,
+#if defined(LWS_AMAZON_RTOS)
+                           (unsigned long)size, reason, (unsigned int)xPortGetFreeHeapSize() - (int)size);
+#else
+                           (unsigned long)size, reason, (unsigned int)esp_get_free_heap_size() - (int)size);
+#endif
+#else
+               lwsl_debug("%s: size %lu: %s\n", __func__,
+                          (unsigned long)size, reason);
+#endif
+
+#if defined(LWS_HAVE_MALLOC_USABLE_SIZE)
+               if (ptr)
+                       allocated -= malloc_usable_size(ptr);
+#endif
+
+#if defined(LWS_PLAT_OPTEE)
+               v = (void *)TEE_Realloc(ptr, size);
+#else
+               v = (void *)realloc(ptr, size);
+#endif
+#if defined(LWS_HAVE_MALLOC_USABLE_SIZE)
+               allocated += malloc_usable_size(v);
+#endif
+               return v;
+       }
+       if (ptr) {
+#if defined(LWS_HAVE_MALLOC_USABLE_SIZE)
+               allocated -= malloc_usable_size(ptr);
+#endif
+               free(ptr);
+       }
+
+       return NULL;
+}
+
+void *(*_lws_realloc)(void *ptr, size_t size, const char *reason) = _realloc;
+
+void *lws_realloc(void *ptr, size_t size, const char *reason)
+{
+       return _lws_realloc(ptr, size, reason);
+}
+
+void *lws_zalloc(size_t size, const char *reason)
+{
+       void *ptr = _lws_realloc(NULL, size, reason);
+
+       if (ptr)
+               memset(ptr, 0, size);
+
+       return ptr;
+}
+
+void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason))
+{
+       _lws_realloc = cb;
+}
+
+size_t lws_get_allocated_heap(void)
+{
+#if defined(LWS_HAVE_MALLOC_USABLE_SIZE)
+       return allocated;
+#else
+       return 0;
+#endif
+}
+#endif
diff --git a/lib/core/buflist.c b/lib/core/buflist.c
new file mode 100644 (file)
index 0000000..75c156f
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#ifdef LWS_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+/* lws_buflist */
+
+int
+lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf,
+                          size_t len)
+{
+       struct lws_buflist *nbuf;
+       int first = !*head;
+       void *p = *head;
+       int sanity = 1024;
+
+       assert(buf);
+       assert(len);
+
+       /* append at the tail */
+       while (*head) {
+               if (!--sanity) {
+                       lwsl_err("%s: buflist reached sanity limit\n", __func__);
+                       return -1;
+               }
+               if (*head == (*head)->next) {
+                       lwsl_err("%s: corrupt list points to self\n", __func__);
+                       return -1;
+               }
+               head = &((*head)->next);
+       }
+
+       (void)p;
+       lwsl_info("%s: len %u first %d %p\n", __func__, (unsigned int)len,
+                                             first, p);
+
+       nbuf = (struct lws_buflist *)lws_malloc(sizeof(**head) + len, __func__);
+       if (!nbuf) {
+               lwsl_err("%s: OOM\n", __func__);
+               return -1;
+       }
+
+       nbuf->len = len;
+       nbuf->pos = 0;
+       nbuf->next = NULL;
+
+       p = (void *)nbuf->buf;
+       memcpy(p, buf, len);
+
+       *head = nbuf;
+
+       return first; /* returns 1 if first segment just created */
+}
+
+static int
+lws_buflist_destroy_segment(struct lws_buflist **head)
+{
+       struct lws_buflist *old = *head;
+
+       assert(*head);
+       *head = old->next;
+       old->next = NULL;
+       lws_free(old);
+
+       return !*head; /* returns 1 if last segment just destroyed */
+}
+
+void
+lws_buflist_destroy_all_segments(struct lws_buflist **head)
+{
+       struct lws_buflist *p = *head, *p1;
+
+       while (p) {
+               p1 = p->next;
+               p->next = NULL;
+               lws_free(p);
+               p = p1;
+       }
+
+       *head = NULL;
+}
+
+size_t
+lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf)
+{
+       if (!*head) {
+               if (buf)
+                       *buf = NULL;
+
+               return 0;
+       }
+
+       if (!(*head)->len && (*head)->next)
+               lws_buflist_destroy_segment(head);
+
+       if (!*head) {
+               if (buf)
+                       *buf = NULL;
+
+               return 0;
+       }
+
+       assert((*head)->pos < (*head)->len);
+
+       if (buf)
+               *buf = (*head)->buf + (*head)->pos;
+
+       return (*head)->len - (*head)->pos;
+}
+
+int
+lws_buflist_use_segment(struct lws_buflist **head, size_t len)
+{
+       assert(*head);
+       assert(len);
+       assert((*head)->pos + len <= (*head)->len);
+
+       (*head)->pos += len;
+       if ((*head)->pos == (*head)->len)
+               lws_buflist_destroy_segment(head);
+
+       if (!*head)
+               return 0;
+
+       return (int)((*head)->len - (*head)->pos);
+}
+
+void
+lws_buflist_describe(struct lws_buflist **head, void *id)
+{
+       struct lws_buflist *old;
+       int n = 0;
+
+       if (*head == NULL)
+               lwsl_notice("%p: buflist empty\n", id);
+
+       while (*head) {
+               lwsl_notice("%p: %d: %llu / %llu (%llu left)\n", id, n,
+                           (unsigned long long)(*head)->pos,
+                           (unsigned long long)(*head)->len,
+                           (unsigned long long)(*head)->len - (*head)->pos);
+               old = *head;
+               head = &((*head)->next);
+               if (*head == old) {
+                       lwsl_err("%s: next points to self\n", __func__);
+                       break;
+               }
+               n++;
+       }
+}
diff --git a/lib/core/context.c b/lib/core/context.c
new file mode 100644 (file)
index 0000000..f4887b9
--- /dev/null
@@ -0,0 +1,871 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#ifndef LWS_BUILD_HASH
+#define LWS_BUILD_HASH "unknown-build-hash"
+#endif
+
+
+static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH;
+
+/**
+ * lws_get_library_version: get version and git hash library built from
+ *
+ *     returns a const char * to a string like "1.1 178d78c"
+ *     representing the library version followed by the git head hash it
+ *     was built from
+ */
+LWS_VISIBLE const char *
+lws_get_library_version(void)
+{
+       return library_version;
+}
+
+#if defined(LWS_WITH_STATS)
+static void
+lws_sul_stats_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws_context_per_thread *pt = lws_container_of(sul,
+                       struct lws_context_per_thread, sul_stats);
+
+       lws_stats_log_dump(pt->context);
+
+       __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_stats, 10 * LWS_US_PER_SEC);
+}
+#endif
+#if defined(LWS_WITH_PEER_LIMITS)
+static void
+lws_sul_peer_limits_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws_context_per_thread *pt = lws_container_of(sul,
+                       struct lws_context_per_thread, sul_peer_limits);
+
+       lws_peer_cull_peer_wait_list(pt->context);
+
+       __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_peer_limits, 10 * LWS_US_PER_SEC);
+}
+#endif
+
+
+LWS_VISIBLE struct lws_context *
+lws_create_context(const struct lws_context_creation_info *info)
+{
+       struct lws_context *context = NULL;
+       struct lws_plat_file_ops *prev;
+#ifndef LWS_NO_DAEMONIZE
+       pid_t pid_daemon = get_daemonize_pid();
+#endif
+#if defined(LWS_WITH_NETWORK)
+       int n;
+#endif
+#if defined(__ANDROID__)
+       struct rlimit rt;
+#endif
+
+       lwsl_info("Initial logging level %d\n", log_level);
+       lwsl_info("Libwebsockets version: %s\n", library_version);
+
+#ifdef LWS_WITH_IPV6
+       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_IPV6))
+               lwsl_info("IPV6 compiled in and enabled\n");
+       else
+               lwsl_info("IPV6 compiled in but disabled\n");
+#else
+       lwsl_info("IPV6 not compiled in\n");
+#endif
+
+       lwsl_info(" LWS_DEF_HEADER_LEN    : %u\n", LWS_DEF_HEADER_LEN);
+       lwsl_info(" LWS_MAX_PROTOCOLS     : %u\n", LWS_MAX_PROTOCOLS);
+       lwsl_info(" LWS_MAX_SMP           : %u\n", LWS_MAX_SMP);
+       lwsl_info(" sizeof (*info)        : %ld\n", (long)sizeof(*info));
+#if defined(LWS_WITH_STATS)
+       lwsl_info(" LWS_WITH_STATS        : on\n");
+#endif
+       lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH);
+#if defined(LWS_WITH_HTTP2)
+       lwsl_info(" HTTP2 support         : available\n");
+#else
+       lwsl_info(" HTTP2 support         : not configured\n");
+#endif
+       if (lws_plat_context_early_init())
+               return NULL;
+
+       context = lws_zalloc(sizeof(struct lws_context), "context");
+       if (!context) {
+               lwsl_err("No memory for websocket context\n");
+               return NULL;
+       }
+
+       context->uid = info->uid;
+       context->gid = info->gid;
+       context->username = info->username;
+       context->groupname = info->groupname;
+       context->system_ops = info->system_ops;
+
+       /* if he gave us names, set the uid / gid */
+       if (lws_plat_drop_app_privileges(context, 0))
+               goto bail;
+
+lwsl_info("context created\n");
+#if defined(LWS_WITH_TLS) && defined(LWS_WITH_NETWORK)
+#if defined(LWS_WITH_MBEDTLS)
+       context->tls_ops = &tls_ops_mbedtls;
+#else
+       context->tls_ops = &tls_ops_openssl;
+#endif
+#endif
+
+       if (info->pt_serv_buf_size)
+               context->pt_serv_buf_size = info->pt_serv_buf_size;
+       else
+               context->pt_serv_buf_size = 4096;
+
+#if defined(LWS_ROLE_H2)
+       role_ops_h2.init_context(context, info);
+#endif
+
+#if LWS_MAX_SMP > 1
+       lws_mutex_refcount_init(&context->mr);
+#endif
+
+#if defined(LWS_WITH_ESP32)
+       context->last_free_heap = esp_get_free_heap_size();
+#endif
+
+       /* default to just the platform fops implementation */
+
+       context->fops_platform.LWS_FOP_OPEN     = _lws_plat_file_open;
+       context->fops_platform.LWS_FOP_CLOSE    = _lws_plat_file_close;
+       context->fops_platform.LWS_FOP_SEEK_CUR = _lws_plat_file_seek_cur;
+       context->fops_platform.LWS_FOP_READ     = _lws_plat_file_read;
+       context->fops_platform.LWS_FOP_WRITE    = _lws_plat_file_write;
+       context->fops_platform.fi[0].sig        = NULL;
+
+       /*
+        *  arrange a linear linked-list of fops starting from context->fops
+        *
+        * platform fops
+        * [ -> fops_zip (copied into context so .next settable) ]
+        * [ -> info->fops ]
+        */
+
+       context->fops = &context->fops_platform;
+       prev = (struct lws_plat_file_ops *)context->fops;
+
+#if defined(LWS_WITH_ZIP_FOPS)
+       /* make a soft copy so we can set .next */
+       context->fops_zip = fops_zip;
+       prev->next = &context->fops_zip;
+       prev = (struct lws_plat_file_ops *)prev->next;
+#endif
+
+       /* if user provided fops, tack them on the end of the list */
+       if (info->fops)
+               prev->next = info->fops;
+
+       context->reject_service_keywords = info->reject_service_keywords;
+       if (info->external_baggage_free_on_destroy)
+               context->external_baggage_free_on_destroy =
+                       info->external_baggage_free_on_destroy;
+#if defined(LWS_WITH_NETWORK)
+       context->time_up = lws_now_usecs();
+#endif
+       context->pcontext_finalize = info->pcontext;
+
+       context->simultaneous_ssl_restriction =
+                       info->simultaneous_ssl_restriction;
+
+       context->options = info->options;
+
+#ifndef LWS_NO_DAEMONIZE
+       if (pid_daemon) {
+               context->started_with_parent = pid_daemon;
+               lwsl_info(" Started with daemon pid %u\n", (unsigned int)pid_daemon);
+       }
+#endif
+#if defined(__ANDROID__)
+               n = getrlimit(RLIMIT_NOFILE, &rt);
+               if (n == -1) {
+                       lwsl_err("Get RLIMIT_NOFILE failed!\n");
+
+                       return NULL;
+               }
+               context->max_fds = rt.rlim_cur;
+#else
+#if defined(WIN32) || defined(_WIN32) || defined(LWS_AMAZON_RTOS)
+               context->max_fds = getdtablesize();
+#else
+               context->max_fds = sysconf(_SC_OPEN_MAX);
+#endif
+#endif
+
+               if (context->max_fds < 0) {
+                       lwsl_err("%s: problem getting process max files\n",
+                                __func__);
+
+                       return NULL;
+               }
+
+       if (info->count_threads)
+               context->count_threads = info->count_threads;
+       else
+               context->count_threads = 1;
+
+       if (context->count_threads > LWS_MAX_SMP)
+               context->count_threads = LWS_MAX_SMP;
+
+       /*
+        * deal with any max_fds override, if it's reducing (setting it to
+        * more than ulimit -n is meaningless).  The platform init will
+        * figure out what if this is something it can deal with.
+        */
+       if (info->fd_limit_per_thread) {
+               int mf = info->fd_limit_per_thread * context->count_threads;
+
+               if (mf < context->max_fds) {
+                       context->max_fds_unrelated_to_ulimit = 1;
+                       context->max_fds = mf;
+               }
+       }
+
+       context->token_limits = info->token_limits;
+
+#if defined(LWS_WITH_NETWORK)
+
+       /*
+        * set the context event loops ops struct
+        *
+        * after this, all event_loop actions use the generic ops
+        */
+
+#if defined(LWS_WITH_POLL)
+       context->event_loop_ops = &event_loop_ops_poll;
+#endif
+
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
+#if defined(LWS_WITH_LIBUV)
+               context->event_loop_ops = &event_loop_ops_uv;
+#else
+               goto fail_event_libs;
+#endif
+
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV))
+#if defined(LWS_WITH_LIBEV)
+               context->event_loop_ops = &event_loop_ops_ev;
+#else
+               goto fail_event_libs;
+#endif
+
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT))
+#if defined(LWS_WITH_LIBEVENT)
+               context->event_loop_ops = &event_loop_ops_event;
+#else
+               goto fail_event_libs;
+#endif
+
+       if (!context->event_loop_ops)
+               goto fail_event_libs;
+
+       lwsl_info("Using event loop: %s\n", context->event_loop_ops->name);
+#endif
+
+#if defined(LWS_WITH_TLS) && defined(LWS_WITH_NETWORK)
+       time(&context->tls.last_cert_check_s);
+       if (info->alpn)
+               context->tls.alpn_default = info->alpn;
+       else {
+               char *p = context->tls.alpn_discovered, first = 1;
+
+               LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) {
+                       if (ar->alpn) {
+                               if (!first)
+                                       *p++ = ',';
+                               p += lws_snprintf(p,
+                                       context->tls.alpn_discovered +
+                                       sizeof(context->tls.alpn_discovered) -
+                                       2 - p, "%s", ar->alpn);
+                               first = 0;
+                       }
+               } LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
+               context->tls.alpn_default = context->tls.alpn_discovered;
+       }
+
+       lwsl_info("Default ALPN advertisment: %s\n", context->tls.alpn_default);
+#endif
+
+       if (info->timeout_secs)
+               context->timeout_secs = info->timeout_secs;
+       else
+               context->timeout_secs = AWAITING_TIMEOUT;
+
+       context->ws_ping_pong_interval = info->ws_ping_pong_interval;
+
+       lwsl_info(" default timeout (secs): %u\n", context->timeout_secs);
+
+       if (info->max_http_header_data)
+               context->max_http_header_data = info->max_http_header_data;
+       else
+               if (info->max_http_header_data2)
+                       context->max_http_header_data =
+                                       info->max_http_header_data2;
+               else
+                       context->max_http_header_data = LWS_DEF_HEADER_LEN;
+
+       if (info->max_http_header_pool)
+               context->max_http_header_pool = info->max_http_header_pool;
+       else
+               if (info->max_http_header_pool2)
+                       context->max_http_header_pool =
+                                       info->max_http_header_pool2;
+               else
+                       context->max_http_header_pool = context->max_fds;
+
+       if (info->fd_limit_per_thread)
+               context->fd_limit_per_thread = info->fd_limit_per_thread;
+       else
+               context->fd_limit_per_thread = context->max_fds /
+                                              context->count_threads;
+
+#if defined(LWS_WITH_NETWORK)
+       /*
+        * Allocate the per-thread storage for scratchpad buffers,
+        * and header data pool
+        */
+       for (n = 0; n < context->count_threads; n++) {
+               context->pt[n].serv_buf = lws_malloc(
+                               context->pt_serv_buf_size + sizeof(struct lws),
+                                                    "pt_serv_buf");
+               if (!context->pt[n].serv_buf) {
+                       lwsl_err("OOM\n");
+                       return NULL;
+               }
+
+               context->pt[n].context = context;
+               context->pt[n].tid = n;
+
+               /*
+                * We overallocated for a fakewsi (can't compose it in the
+                * pt because size isn't known at that time).  point to it
+                * and zero it down.  Fakewsis are needed to make callbacks work
+                * when the source of the callback is not actually from a wsi
+                * context.
+                */
+               context->pt[n].fake_wsi = (struct lws *)(context->pt[n].serv_buf +
+                                               context->pt_serv_buf_size);
+
+               memset(context->pt[n].fake_wsi, 0, sizeof(struct lws));
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+               context->pt[n].http.ah_list = NULL;
+               context->pt[n].http.ah_pool_length = 0;
+#endif
+               lws_pt_mutex_init(&context->pt[n]);
+#if defined(LWS_WITH_SEQUENCER)
+               lws_seq_pt_init(&context->pt[n]);
+#endif
+       }
+
+       lwsl_info(" Threads: %d each %d fds\n", context->count_threads,
+                   context->fd_limit_per_thread);
+
+       if (!info->ka_interval && info->ka_time > 0) {
+               lwsl_err("info->ka_interval can't be 0 if ka_time used\n");
+               return NULL;
+       }
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       /* scale the peer hash table according to the max fds for the process,
+        * so that the max list depth averages 16.  Eg, 1024 fd -> 64,
+        * 102400 fd -> 6400
+        */
+
+       context->pl_hash_elements =
+               (context->count_threads * context->fd_limit_per_thread) / 16;
+       context->pl_hash_table = lws_zalloc(sizeof(struct lws_peer *) *
+                       context->pl_hash_elements, "peer limits hash table");
+
+       context->ip_limit_ah = info->ip_limit_ah;
+       context->ip_limit_wsi = info->ip_limit_wsi;
+#endif
+
+       lwsl_info(" mem: context:         %5lu B (%ld ctx + (%ld thr x %d))\n",
+                 (long)sizeof(struct lws_context) +
+                 (context->count_threads * context->pt_serv_buf_size),
+                 (long)sizeof(struct lws_context),
+                 (long)context->count_threads,
+                 context->pt_serv_buf_size);
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       lwsl_info(" mem: http hdr size:   (%u + %lu), max count %u\n",
+                   context->max_http_header_data,
+                   (long)sizeof(struct allocated_headers),
+                   context->max_http_header_pool);
+#endif
+
+       /*
+        * fds table contains pollfd structs for as many pollfds as we can
+        * handle... spread across as many service threads as we have going
+        */
+       n = sizeof(struct lws_pollfd) * context->count_threads *
+           context->fd_limit_per_thread;
+       context->pt[0].fds = lws_zalloc(n, "fds table");
+       if (context->pt[0].fds == NULL) {
+               lwsl_err("OOM allocating %d fds\n", context->max_fds);
+               goto bail;
+       }
+       lwsl_info(" mem: pollfd map:      %5u B\n", n);
+#endif
+       if (info->server_string) {
+               context->server_string = info->server_string;
+               context->server_string_len = (short)
+                               strlen(context->server_string);
+       }
+
+#if LWS_MAX_SMP > 1
+       /* each thread serves his own chunk of fds */
+       for (n = 1; n < (int)context->count_threads; n++)
+               context->pt[n].fds = context->pt[n - 1].fds +
+                                    context->fd_limit_per_thread;
+#endif
+
+       if (lws_plat_init(context, info))
+               goto bail;
+
+#if defined(LWS_WITH_NETWORK)
+       if (context->event_loop_ops->init_context)
+               if (context->event_loop_ops->init_context(context, info))
+                       goto bail;
+
+
+       if (context->event_loop_ops->init_pt)
+               for (n = 0; n < context->count_threads; n++) {
+                       void *lp = NULL;
+
+                       if (info->foreign_loops)
+                               lp = info->foreign_loops[n];
+
+                       if (context->event_loop_ops->init_pt(context, lp, n))
+                               goto bail;
+               }
+
+#if !defined(LWS_AMAZON_RTOS)
+       if (lws_create_event_pipes(context))
+               goto bail;
+#endif
+#endif
+
+       lws_context_init_ssl_library(info);
+
+       context->user_space = info->user;
+#if defined(LWS_WITH_NETWORK)
+       /*
+        * if he's not saying he'll make his own vhosts later then act
+        * compatibly and make a default vhost using the data in the info
+        */
+       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
+               if (!lws_create_vhost(context, info)) {
+                       lwsl_err("Failed to create default vhost\n");
+                       for (n = 0; n < context->count_threads; n++)
+                               lws_free_set_NULL(context->pt[n].serv_buf);
+#if defined(LWS_WITH_PEER_LIMITS)
+                       lws_free_set_NULL(context->pl_hash_table);
+#endif
+                       lws_free_set_NULL(context->pt[0].fds);
+                       lws_plat_context_late_destroy(context);
+                       lws_free_set_NULL(context);
+                       return NULL;
+               }
+
+       lws_context_init_extensions(info, context);
+
+       lwsl_info(" mem: per-conn:        %5lu bytes + protocol rx buf\n",
+                   (unsigned long)sizeof(struct lws));
+#endif
+       strcpy(context->canonical_hostname, "unknown");
+#if defined(LWS_WITH_NETWORK)
+       lws_server_get_canonical_hostname(context, info);
+#endif
+
+#if defined(LWS_WITH_STATS)
+       context->pt[0].sul_stats.cb = lws_sul_stats_cb;
+       __lws_sul_insert(&context->pt[0].pt_sul_owner, &context->pt[0].sul_stats,
+                        10 * LWS_US_PER_SEC);
+#endif
+#if defined(LWS_WITH_PEER_LIMITS)
+       context->pt[0].sul_peer_limits.cb = lws_sul_peer_limits_cb;
+       __lws_sul_insert(&context->pt[0].pt_sul_owner,
+                        &context->pt[0].sul_peer_limits, 10 * LWS_US_PER_SEC);
+#endif
+
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+       memcpy(context->caps, info->caps, sizeof(context->caps));
+       context->count_caps = info->count_caps;
+#endif
+
+       /*
+        * drop any root privs for this process
+        * to listen on port < 1023 we would have needed root, but now we are
+        * listening, we don't want the power for anything else
+        */
+       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
+               if (lws_plat_drop_app_privileges(context, 1))
+                       goto bail;
+
+#if defined(LWS_WITH_NETWORK)
+       /* expedite post-context init (eg, protocols) */
+       lws_cancel_service(context);
+#endif
+
+       return context;
+
+bail:
+       lws_context_destroy(context);
+
+       return NULL;
+
+#if defined(LWS_WITH_NETWORK)
+fail_event_libs:
+       lwsl_err("Requested event library support not configured, available:\n");
+       {
+               extern const struct lws_event_loop_ops *available_event_libs[];
+               const struct lws_event_loop_ops **elops = available_event_libs;
+
+               while (*elops) {
+                       lwsl_err("  - %s\n", (*elops)->name);
+                       elops++;
+               }
+       }
+#endif
+       lws_free(context);
+
+       return NULL;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_context_is_deprecated(struct lws_context *context)
+{
+       return context->deprecated;
+}
+
+/*
+ * When using an event loop, the context destruction is in three separate
+ * parts.  This is to cover both internal and foreign event loops cleanly.
+ *
+ *  - lws_context_destroy() simply starts a soft close of all wsi and
+ *     related allocations.  The event loop continues.
+ *
+ *     As the closes complete in the event loop, reference counting is used
+ *     to determine when everything is closed.  It then calls
+ *     lws_context_destroy2().
+ *
+ *  - lws_context_destroy2() cleans up the rest of the higher-level logical
+ *     lws pieces like vhosts.  If the loop was foreign, it then proceeds to
+ *     lws_context_destroy3().  If it the loop is internal, it stops the
+ *     internal loops and waits for lws_context_destroy() to be called again
+ *     outside the event loop (since we cannot destroy the loop from
+ *     within the loop).  That will cause lws_context_destroy3() to run
+ *     directly.
+ *
+ *  - lws_context_destroy3() destroys any internal event loops and then
+ *     destroys the context itself, setting what was info.pcontext to NULL.
+ */
+
+/*
+ * destroy the actual context itself
+ */
+
+static void
+lws_context_destroy3(struct lws_context *context)
+{
+       struct lws_context **pcontext_finalize = context->pcontext_finalize;
+#if defined(LWS_WITH_NETWORK)
+       int n;
+
+       lwsl_debug("%s\n", __func__);
+
+       for (n = 0; n < context->count_threads; n++) {
+               struct lws_context_per_thread *pt = &context->pt[n];
+               (void)pt;
+#if defined(LWS_WITH_SEQUENCER)
+               lws_seq_destroy_all_on_pt(pt);
+#endif
+
+               if (context->event_loop_ops->destroy_pt)
+                       context->event_loop_ops->destroy_pt(context, n);
+
+               lws_free_set_NULL(context->pt[n].serv_buf);
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+               while (pt->http.ah_list)
+                       _lws_destroy_ah(pt, pt->http.ah_list);
+#endif
+       }
+
+       if (context->pt[0].fds)
+               lws_free_set_NULL(context->pt[0].fds);
+#endif
+       lws_context_deinit_ssl_library(context);
+
+       lws_free(context);
+       lwsl_info("%s: ctx %p freed\n", __func__, context);
+
+       if (pcontext_finalize)
+               *pcontext_finalize = NULL;
+}
+
+/*
+ * really start destroying things
+ */
+
+void
+lws_context_destroy2(struct lws_context *context)
+{
+#if defined(LWS_WITH_NETWORK)
+       struct lws_vhost *vh = NULL, *vh1;
+#endif
+#if defined(LWS_WITH_PEER_LIMITS)
+       uint32_t nu;
+#endif
+
+       lwsl_info("%s: ctx %p\n", __func__, context);
+
+       lws_context_lock(context, "context destroy 2"); /* ------ context { */
+
+       context->being_destroyed2 = 1;
+#if defined(LWS_WITH_NETWORK)
+       /*
+        * free all the per-vhost allocations
+        */
+
+       vh = context->vhost_list;
+       while (vh) {
+               vh1 = vh->vhost_next;
+               __lws_vhost_destroy2(vh);
+               vh = vh1;
+       }
+
+       lwsl_debug("%p: post vh listl\n", __func__);
+
+       /* remove ourselves from the pending destruction list */
+
+       while (context->vhost_pending_destruction_list)
+               /* removes itself from list */
+               __lws_vhost_destroy2(context->vhost_pending_destruction_list);
+#endif
+
+       lwsl_debug("%p: post pdl\n", __func__);
+
+       lws_stats_log_dump(context);
+#if defined(LWS_WITH_NETWORK)
+       lws_ssl_context_destroy(context);
+#endif
+       lws_plat_context_late_destroy(context);
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       for (nu = 0; nu < context->pl_hash_elements; nu++)      {
+               lws_start_foreach_llp(struct lws_peer **, peer,
+                                     context->pl_hash_table[nu]) {
+                       struct lws_peer *df = *peer;
+                       *peer = df->next;
+                       lws_free(df);
+                       continue;
+               } lws_end_foreach_llp(peer, next);
+       }
+       lws_free(context->pl_hash_table);
+#endif
+
+       lwsl_debug("%p: baggage\n", __func__);
+
+       if (context->external_baggage_free_on_destroy)
+               free(context->external_baggage_free_on_destroy);
+
+#if defined(LWS_WITH_NETWORK)
+       lws_check_deferred_free(context, 0, 1);
+#endif
+
+#if LWS_MAX_SMP > 1
+       lws_mutex_refcount_destroy(&context->mr);
+#endif
+#if defined(LWS_WITH_NETWORK)
+       if (context->event_loop_ops->destroy_context2)
+               if (context->event_loop_ops->destroy_context2(context)) {
+                       lws_context_unlock(context); /* } context ----------- */
+                       context->finalize_destroy_after_internal_loops_stopped = 1;
+                       return;
+               }
+
+       lwsl_debug("%p: post dc2\n", __func__);
+
+       if (!context->pt[0].event_loop_foreign) {
+               int n;
+               for (n = 0; n < context->count_threads; n++)
+                       if (context->pt[n].inside_service) {
+                               lwsl_debug("%p: bailing as inside service\n", __func__);
+                               lws_context_unlock(context); /* } context --- */
+                               return;
+                       }
+       }
+#endif
+       lws_context_unlock(context); /* } context ------------------- */
+
+       lws_context_destroy3(context);
+}
+
+/*
+ * Begin the context takedown
+ */
+
+LWS_VISIBLE void
+lws_context_destroy(struct lws_context *context)
+{
+#if defined(LWS_WITH_NETWORK)
+       volatile struct lws_foreign_thread_pollfd *ftp, *next;
+       volatile struct lws_context_per_thread *vpt;
+       struct lws_vhost *vh = NULL;
+       struct lws wsi;
+       int n, m;
+#endif
+
+       if (!context)
+               return;
+#if defined(LWS_WITH_NETWORK)
+       if (context->finalize_destroy_after_internal_loops_stopped) {
+               if (context->event_loop_ops->destroy_context2)
+                       context->event_loop_ops->destroy_context2(context);
+               lws_context_destroy3(context);
+
+               return;
+       }
+#endif
+       if (context->being_destroyed1) {
+               if (!context->being_destroyed2) {
+                       lws_context_destroy2(context);
+
+                       return;
+               }
+               lwsl_info("%s: ctx %p: already being destroyed\n",
+                           __func__, context);
+
+               lws_context_destroy3(context);
+               return;
+       }
+
+       lwsl_info("%s: ctx %p\n", __func__, context);
+
+       context->being_destroyed = 1;
+       context->being_destroyed1 = 1;
+       context->requested_kill = 1;
+
+#if defined(LWS_WITH_NETWORK)
+       m = context->count_threads;
+       memset(&wsi, 0, sizeof(wsi));
+       wsi.context = context;
+
+#ifdef LWS_LATENCY
+       if (context->worst_latency_info[0])
+               lwsl_notice("Worst latency: %s\n", context->worst_latency_info);
+#endif
+
+       while (m--) {
+               struct lws_context_per_thread *pt = &context->pt[m];
+               vpt = (volatile struct lws_context_per_thread *)pt;
+
+               ftp = vpt->foreign_pfd_list;
+               while (ftp) {
+                       next = ftp->next;
+                       lws_free((void *)ftp);
+                       ftp = next;
+               }
+               vpt->foreign_pfd_list = NULL;
+
+               for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
+                       struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
+                       if (!wsi)
+                               continue;
+
+                       if (wsi->event_pipe)
+                               lws_destroy_event_pipe(wsi);
+                       else
+                               lws_close_free_wsi(wsi,
+                                       LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY,
+                                       "ctx destroy"
+                                       /* no protocol close */);
+                       n--;
+               }
+               lws_pt_mutex_destroy(pt);
+       }
+
+       /*
+        * inform all the protocols that they are done and will have no more
+        * callbacks.
+        *
+        * We can't free things until after the event loop shuts down.
+        */
+       if (context->protocol_init_done)
+               vh = context->vhost_list;
+       while (vh) {
+               struct lws_vhost *vhn = vh->vhost_next;
+               lws_vhost_destroy1(vh);
+               vh = vhn;
+       }
+#endif
+
+       lws_plat_context_early_destroy(context);
+
+#if defined(LWS_WITH_NETWORK)
+
+       /*
+        * We face two different needs depending if foreign loop or not.
+        *
+        * 1) If foreign loop, we really want to advance the destroy_context()
+        *    past here, and block only for libuv-style async close completion.
+        *
+        * 2a) If poll, and we exited by ourselves and are calling a final
+        *     destroy_context() outside of any service already, we want to
+        *     advance all the way in one step.
+        *
+        * 2b) If poll, and we are reacting to a SIGINT, service thread(s) may
+        *     be in poll wait or servicing.  We can't advance the
+        *     destroy_context() to the point it's freeing things; we have to
+        *     leave that for the final destroy_context() after the service
+        *     thread(s) are finished calling for service.
+        */
+
+       if (context->event_loop_ops->destroy_context1) {
+               context->event_loop_ops->destroy_context1(context);
+
+               return;
+       }
+#endif
+
+#if defined(LWS_WITH_ESP32)
+#if defined(LWS_AMAZON_RTOS)
+       context->last_free_heap = xPortGetFreeHeapSize();
+#else
+       context->last_free_heap = esp_get_free_heap_size();
+#endif
+#endif
+
+       lws_context_destroy2(context);
+}
+
diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c
new file mode 100644 (file)
index 0000000..c425cdc
--- /dev/null
@@ -0,0 +1,955 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#ifdef LWS_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+signed char char_to_hex(const char c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+
+       return -1;
+}
+
+int
+lws_hex_to_byte_array(const char *h, uint8_t *dest, int max)
+{
+       uint8_t *odest = dest;
+
+       while (max-- && *h) {
+               int t = char_to_hex(*h++), t1;
+
+               if (!*h || t < 0)
+                       return -1;
+
+               t1 = char_to_hex(*h++);
+               if (t1 < 0)
+                       return -1;
+
+               *dest++ = (t << 4) | t1;
+       }
+
+       if (max < 0)
+               return -1;
+
+       return dest - odest;
+}
+
+
+#if !defined(LWS_PLAT_OPTEE)
+
+#if !defined(LWS_AMAZON_RTOS)
+int lws_open(const char *__file, int __oflag, ...)
+{
+       va_list ap;
+       int n;
+
+       va_start(ap, __oflag);
+       if (((__oflag & O_CREAT) == O_CREAT)
+#if defined(O_TMPFILE)
+               || ((__oflag & O_TMPFILE) == O_TMPFILE)
+#endif
+       )
+               /* last arg is really a mode_t.  But windows... */
+               n = open(__file, __oflag, va_arg(ap, uint32_t));
+       else
+               n = open(__file, __oflag);
+       va_end(ap);
+
+       if (n != -1 && lws_plat_apply_FD_CLOEXEC(n)) {
+               close(n);
+
+               return -1;
+       }
+
+       return n;
+}
+#endif
+#endif
+
+int
+lws_pthread_self_to_tsi(struct lws_context *context)
+{
+#if LWS_MAX_SMP > 1
+       pthread_t ps = pthread_self();
+       struct lws_context_per_thread *pt = &context->pt[0];
+       int n;
+
+       for (n = 0; n < context->count_threads; n++) {
+               if (pthread_equal(ps, pt->self))
+                       return n;
+               pt++;
+       }
+
+       return -1;
+#else
+       return 0;
+#endif
+}
+
+LWS_EXTERN void *
+lws_context_user(struct lws_context *context)
+{
+       return context->user_space;
+}
+
+LWS_VISIBLE void
+lws_explicit_bzero(void *p, size_t len)
+{
+       volatile uint8_t *vp = p;
+
+       while (len--)
+               *vp++ = 0;
+}
+
+#if !(defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NETWORK))
+
+/**
+ * lws_now_secs() - seconds since 1970-1-1
+ *
+ */
+LWS_VISIBLE LWS_EXTERN unsigned long
+lws_now_secs(void)
+{
+       struct timeval tv;
+
+       gettimeofday(&tv, NULL);
+
+       return tv.tv_sec;
+}
+
+#endif
+LWS_VISIBLE extern const char *
+lws_canonical_hostname(struct lws_context *context)
+{
+       return (const char *)context->canonical_hostname;
+}
+
+#if defined(LWS_WITH_SOCKS5)
+LWS_VISIBLE int
+lws_set_socks(struct lws_vhost *vhost, const char *socks)
+{
+       char *p_at, *p_colon;
+       char user[96];
+       char password[96];
+
+       if (!socks)
+               return -1;
+
+       vhost->socks_user[0] = '\0';
+       vhost->socks_password[0] = '\0';
+
+       p_at = strrchr(socks, '@');
+       if (p_at) { /* auth is around */
+               if ((unsigned int)(p_at - socks) > (sizeof(user)
+                       + sizeof(password) - 2)) {
+                       lwsl_err("Socks auth too long\n");
+                       goto bail;
+               }
+
+               p_colon = strchr(socks, ':');
+               if (p_colon) {
+                       if ((unsigned int)(p_colon - socks) > (sizeof(user)
+                               - 1) ) {
+                               lwsl_err("Socks user too long\n");
+                               goto bail;
+                       }
+                       if ((unsigned int)(p_at - p_colon) > (sizeof(password)
+                               - 1) ) {
+                               lwsl_err("Socks password too long\n");
+                               goto bail;
+                       }
+
+                       lws_strncpy(vhost->socks_user, socks, p_colon - socks + 1);
+                       lws_strncpy(vhost->socks_password, p_colon + 1,
+                               p_at - (p_colon + 1) + 1);
+               }
+
+               lwsl_info(" Socks auth, user: %s, password: %s\n",
+                       vhost->socks_user, vhost->socks_password );
+
+               socks = p_at + 1;
+       }
+
+       lws_strncpy(vhost->socks_proxy_address, socks,
+                   sizeof(vhost->socks_proxy_address));
+
+       p_colon = strchr(vhost->socks_proxy_address, ':');
+       if (!p_colon && !vhost->socks_proxy_port) {
+               lwsl_err("socks_proxy needs to be address:port\n");
+               return -1;
+       } else {
+               if (p_colon) {
+                       *p_colon = '\0';
+                       vhost->socks_proxy_port = atoi(p_colon + 1);
+               }
+       }
+
+       lwsl_info(" Socks %s:%u\n", vhost->socks_proxy_address,
+                       vhost->socks_proxy_port);
+
+       return 0;
+
+bail:
+       return -1;
+}
+#endif
+
+
+
+LWS_VISIBLE LWS_EXTERN int
+lws_get_count_threads(struct lws_context *context)
+{
+       return context->count_threads;
+}
+
+static const unsigned char e0f4[] = {
+       0xa0 | ((2 - 1) << 2) | 1, /* e0 */
+       0x80 | ((4 - 1) << 2) | 1, /* e1 */
+       0x80 | ((4 - 1) << 2) | 1, /* e2 */
+       0x80 | ((4 - 1) << 2) | 1, /* e3 */
+       0x80 | ((4 - 1) << 2) | 1, /* e4 */
+       0x80 | ((4 - 1) << 2) | 1, /* e5 */
+       0x80 | ((4 - 1) << 2) | 1, /* e6 */
+       0x80 | ((4 - 1) << 2) | 1, /* e7 */
+       0x80 | ((4 - 1) << 2) | 1, /* e8 */
+       0x80 | ((4 - 1) << 2) | 1, /* e9 */
+       0x80 | ((4 - 1) << 2) | 1, /* ea */
+       0x80 | ((4 - 1) << 2) | 1, /* eb */
+       0x80 | ((4 - 1) << 2) | 1, /* ec */
+       0x80 | ((2 - 1) << 2) | 1, /* ed */
+       0x80 | ((4 - 1) << 2) | 1, /* ee */
+       0x80 | ((4 - 1) << 2) | 1, /* ef */
+       0x90 | ((3 - 1) << 2) | 2, /* f0 */
+       0x80 | ((4 - 1) << 2) | 2, /* f1 */
+       0x80 | ((4 - 1) << 2) | 2, /* f2 */
+       0x80 | ((4 - 1) << 2) | 2, /* f3 */
+       0x80 | ((1 - 1) << 2) | 2, /* f4 */
+
+       0,                         /* s0 */
+       0x80 | ((4 - 1) << 2) | 0, /* s2 */
+       0x80 | ((4 - 1) << 2) | 1, /* s3 */
+};
+
+LWS_EXTERN int
+lws_check_byte_utf8(unsigned char state, unsigned char c)
+{
+       unsigned char s = state;
+
+       if (!s) {
+               if (c >= 0x80) {
+                       if (c < 0xc2 || c > 0xf4)
+                               return -1;
+                       if (c < 0xe0)
+                               return 0x80 | ((4 - 1) << 2);
+                       else
+                               return e0f4[c - 0xe0];
+               }
+
+               return s;
+       }
+       if (c < (s & 0xf0) || c >= (s & 0xf0) + 0x10 + ((s << 2) & 0x30))
+               return -1;
+
+       return e0f4[21 + (s & 3)];
+}
+
+LWS_EXTERN int
+lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len)
+{
+       unsigned char s = *state;
+
+       while (len--) {
+               unsigned char c = *buf++;
+
+               if (!s) {
+                       if (c >= 0x80) {
+                               if (c < 0xc2 || c > 0xf4)
+                                       return 1;
+                               if (c < 0xe0)
+                                       s = 0x80 | ((4 - 1) << 2);
+                               else
+                                       s = e0f4[c - 0xe0];
+                       }
+               } else {
+                       if (c < (s & 0xf0) ||
+                           c >= (s & 0xf0) + 0x10 + ((s << 2) & 0x30))
+                               return 1;
+                       s = e0f4[21 + (s & 3)];
+               }
+       }
+
+       *state = s;
+
+       return 0;
+}
+
+
+char *
+lws_strdup(const char *s)
+{
+       char *d = lws_malloc(strlen(s) + 1, "strdup");
+
+       if (d)
+               strcpy(d, s);
+
+       return d;
+}
+
+static const char *hex = "0123456789ABCDEF";
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_sql_purify(char *escaped, const char *string, int len)
+{
+       const char *p = string;
+       char *q = escaped;
+
+       while (*p && len-- > 2) {
+               if (*p == '\'') {
+                       *q++ = '\'';
+                       *q++ = '\'';
+                       len --;
+                       p++;
+               } else
+                       *q++ = *p++;
+       }
+       *q = '\0';
+
+       return escaped;
+}
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_json_purify(char *escaped, const char *string, int len)
+{
+       const char *p = string;
+       char *q = escaped;
+
+       if (!p) {
+               escaped[0] = '\0';
+               return escaped;
+       }
+
+       while (*p && len-- > 6) {
+               if (*p == '\t') {
+                       p++;
+                       *q++ = '\\';
+                       *q++ = 't';
+                       continue;
+               }
+
+               if (*p == '\n') {
+                       p++;
+                       *q++ = '\\';
+                       *q++ = 'n';
+                       continue;
+               }
+
+               if (*p == '\r') {
+                       p++;
+                       *q++ = '\\';
+                       *q++ = 'r';
+                       continue;
+               }
+
+               if (*p == '\"' || *p == '\\' || *p < 0x20) {
+                       *q++ = '\\';
+                       *q++ = 'u';
+                       *q++ = '0';
+                       *q++ = '0';
+                       *q++ = hex[((*p) >> 4) & 15];
+                       *q++ = hex[(*p) & 15];
+                       len -= 5;
+                       p++;
+               } else
+                       *q++ = *p++;
+       }
+       *q = '\0';
+
+       return escaped;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_filename_purify_inplace(char *filename)
+{
+       while (*filename) {
+
+               if (*filename == '.' && filename[1] == '.') {
+                       *filename = '_';
+                       filename[1] = '_';
+               }
+
+               if (*filename == ':' ||
+                   *filename == '\\' ||
+                   *filename == '$' ||
+                   *filename == '%')
+                       *filename = '_';
+
+               filename++;
+       }
+}
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_urlencode(char *escaped, const char *string, int len)
+{
+       const char *p = string;
+       char *q = escaped;
+
+       while (*p && len-- > 3) {
+               if (*p == ' ') {
+                       *q++ = '+';
+                       p++;
+                       continue;
+               }
+               if ((*p >= '0' && *p <= '9') ||
+                   (*p >= 'A' && *p <= 'Z') ||
+                   (*p >= 'a' && *p <= 'z')) {
+                       *q++ = *p++;
+                       continue;
+               }
+               *q++ = '%';
+               *q++ = hex[(*p >> 4) & 0xf];
+               *q++ = hex[*p & 0xf];
+
+               len -= 2;
+               p++;
+       }
+       *q = '\0';
+
+       return escaped;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_urldecode(char *string, const char *escaped, int len)
+{
+       int state = 0, n;
+       char sum = 0;
+
+       while (*escaped && len) {
+               switch (state) {
+               case 0:
+                       if (*escaped == '%') {
+                               state++;
+                               escaped++;
+                               continue;
+                       }
+                       if (*escaped == '+') {
+                               escaped++;
+                               *string++ = ' ';
+                               len--;
+                               continue;
+                       }
+                       *string++ = *escaped++;
+                       len--;
+                       break;
+               case 1:
+                       n = char_to_hex(*escaped);
+                       if (n < 0)
+                               return -1;
+                       escaped++;
+                       sum = n << 4;
+                       state++;
+                       break;
+
+               case 2:
+                       n = char_to_hex(*escaped);
+                       if (n < 0)
+                               return -1;
+                       escaped++;
+                       *string++ = sum | n;
+                       len--;
+                       state = 0;
+                       break;
+               }
+
+       }
+       *string = '\0';
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_finalize_startup(struct lws_context *context)
+{
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
+               if (lws_plat_drop_app_privileges(context, 1))
+                       return 1;
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_get_effective_uid_gid(struct lws_context *context, int *uid, int *gid)
+{
+       *uid = context->uid;
+       *gid = context->gid;
+}
+
+int
+lws_snprintf(char *str, size_t size, const char *format, ...)
+{
+       va_list ap;
+       int n;
+
+       if (!size)
+               return 0;
+
+       va_start(ap, format);
+       n = vsnprintf(str, size, format, ap);
+       va_end(ap);
+
+       if (n >= (int)size)
+               return (int)size;
+
+       return n;
+}
+
+char *
+lws_strncpy(char *dest, const char *src, size_t size)
+{
+       strncpy(dest, src, size - 1);
+       dest[size - 1] = '\0';
+
+       return dest;
+}
+
+int
+lws_timingsafe_bcmp(const void *a, const void *b, uint32_t len)
+{
+       const uint8_t *pa = a, *pb = b;
+       uint8_t sum = 0;
+
+       while (len--)
+               sum |= (*pa++ ^ *pb++);
+
+       return sum;
+}
+
+
+typedef enum {
+       LWS_TOKZS_LEADING_WHITESPACE,
+       LWS_TOKZS_QUOTED_STRING,
+       LWS_TOKZS_TOKEN,
+       LWS_TOKZS_TOKEN_POST_TERMINAL
+} lws_tokenize_state;
+
+#if defined(LWS_AMAZON_RTOS)
+lws_tokenize_elem
+#else
+int
+#endif
+lws_tokenize(struct lws_tokenize *ts)
+{
+       const char *rfc7230_delims = "(),/:;<=>?@[\\]{}";
+       lws_tokenize_state state = LWS_TOKZS_LEADING_WHITESPACE;
+       char c, flo = 0, d_minus = '-', d_dot = '.', s_minus = '\0',
+            s_dot = '\0';
+       signed char num = ts->flags & LWS_TOKENIZE_F_NO_INTEGERS ? 0 : -1;
+       int utf8 = 0;
+
+       /* for speed, compute the effect of the flags outside the loop */
+
+       if (ts->flags & LWS_TOKENIZE_F_MINUS_NONTERM) {
+               d_minus = '\0';
+               s_minus = '-';
+       }
+       if (ts->flags & LWS_TOKENIZE_F_DOT_NONTERM) {
+               d_dot = '\0';
+               s_dot = '.';
+       }
+
+       ts->token = NULL;
+       ts->token_len = 0;
+
+       while (ts->len) {
+               c = *ts->start++;
+               ts->len--;
+
+               utf8 = lws_check_byte_utf8((unsigned char)utf8, c);
+               if (utf8 < 0)
+                       return LWS_TOKZE_ERR_BROKEN_UTF8;
+
+               if (!c)
+                       break;
+
+               /* whitespace */
+
+               if (c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
+                   c == '\f') {
+                       switch (state) {
+                       case LWS_TOKZS_LEADING_WHITESPACE:
+                       case LWS_TOKZS_TOKEN_POST_TERMINAL:
+                               continue;
+                       case LWS_TOKZS_QUOTED_STRING:
+                               ts->token_len++;
+                               continue;
+                       case LWS_TOKZS_TOKEN:
+                               /* we want to scan forward to look for = */
+
+                               state = LWS_TOKZS_TOKEN_POST_TERMINAL;
+                               continue;
+                       }
+               }
+
+               /* quoted string */
+
+               if (c == '\"') {
+                       if (state == LWS_TOKZS_QUOTED_STRING)
+                               return LWS_TOKZE_QUOTED_STRING;
+
+                       /* starting a quoted string */
+
+                       if (ts->flags & LWS_TOKENIZE_F_COMMA_SEP_LIST) {
+                               if (ts->delim == LWSTZ_DT_NEED_DELIM)
+                                       return LWS_TOKZE_ERR_COMMA_LIST;
+                               ts->delim = LWSTZ_DT_NEED_DELIM;
+                       }
+
+                       state = LWS_TOKZS_QUOTED_STRING;
+                       ts->token = ts->start;
+                       ts->token_len = 0;
+
+                       continue;
+               }
+
+               /* token= aggregation */
+
+               if (c == '=' && (state == LWS_TOKZS_TOKEN_POST_TERMINAL ||
+                                state == LWS_TOKZS_TOKEN)) {
+                       if (num == 1)
+                               return LWS_TOKZE_ERR_NUM_ON_LHS;
+                       /* swallow the = */
+                       return LWS_TOKZE_TOKEN_NAME_EQUALS;
+               }
+
+               /* optional token: aggregation */
+
+               if ((ts->flags & LWS_TOKENIZE_F_AGG_COLON) && c == ':' &&
+                   (state == LWS_TOKZS_TOKEN_POST_TERMINAL ||
+                    state == LWS_TOKZS_TOKEN))
+                       /* swallow the : */
+                       return LWS_TOKZE_TOKEN_NAME_COLON;
+
+               /* aggregate . in a number as a float */
+
+               if (c == '.' && !(ts->flags & LWS_TOKENIZE_F_NO_FLOATS) &&
+                   state == LWS_TOKZS_TOKEN && num == 1) {
+                       if (flo)
+                               return LWS_TOKZE_ERR_MALFORMED_FLOAT;
+                       flo = 1;
+                       ts->token_len++;
+                       continue;
+               }
+
+               /*
+                * Delimiter... by default anything that:
+                *
+                *  - isn't matched earlier, or
+                *  - is [A-Z, a-z, 0-9, _], and
+                *  - is not a partial utf8 char
+                *
+                * is a "delimiter", it marks the end of a token and is itself
+                * reported as a single LWS_TOKZE_DELIMITER each time.
+                *
+                * However with LWS_TOKENIZE_F_RFC7230_DELIMS flag, tokens may
+                * contain any noncontrol character that isn't defined in
+                * rfc7230_delims, and only characters listed there are treated
+                * as delimiters.
+                */
+
+               if (!utf8 &&
+                    ((ts->flags & LWS_TOKENIZE_F_RFC7230_DELIMS &&
+                    strchr(rfc7230_delims, c) && c > 32) ||
+                   ((!(ts->flags & LWS_TOKENIZE_F_RFC7230_DELIMS) &&
+                    (c < '0' || c > '9') && (c < 'A' || c > 'Z') &&
+                    (c < 'a' || c > 'z') && c != '_') &&
+                    c != s_minus && c != s_dot) ||
+                   c == d_minus || c == d_dot
+                   )) {
+                       switch (state) {
+                       case LWS_TOKZS_LEADING_WHITESPACE:
+                               if (ts->flags & LWS_TOKENIZE_F_COMMA_SEP_LIST) {
+                                       if (c != ',' ||
+                                           ts->delim != LWSTZ_DT_NEED_DELIM)
+                                               return LWS_TOKZE_ERR_COMMA_LIST;
+                                       ts->delim = LWSTZ_DT_NEED_NEXT_CONTENT;
+                               }
+
+                               ts->token = ts->start - 1;
+                               ts->token_len = 1;
+                               return LWS_TOKZE_DELIMITER;
+
+                       case LWS_TOKZS_QUOTED_STRING:
+                               ts->token_len++;
+                               continue;
+
+                       case LWS_TOKZS_TOKEN_POST_TERMINAL:
+                       case LWS_TOKZS_TOKEN:
+                               /* report the delimiter next time */
+                               ts->start--;
+                               ts->len++;
+                               goto token_or_numeric;
+                       }
+               }
+
+               /* anything that's not whitespace or delimiter is payload */
+
+               switch (state) {
+               case LWS_TOKZS_LEADING_WHITESPACE:
+
+                       if (ts->flags & LWS_TOKENIZE_F_COMMA_SEP_LIST) {
+                               if (ts->delim == LWSTZ_DT_NEED_DELIM)
+                                       return LWS_TOKZE_ERR_COMMA_LIST;
+                               ts->delim = LWSTZ_DT_NEED_DELIM;
+                       }
+
+                       state = LWS_TOKZS_TOKEN;
+                       ts->token = ts->start - 1;
+                       ts->token_len = 1;
+                       goto checknum;
+
+               case LWS_TOKZS_QUOTED_STRING:
+               case LWS_TOKZS_TOKEN:
+                       ts->token_len++;
+checknum:
+                       if (!(ts->flags & LWS_TOKENIZE_F_NO_INTEGERS)) {
+                               if (c < '0' || c > '9')
+                                       num = 0;
+                               else
+                                       if (num < 0)
+                                               num = 1;
+                       }
+                       continue;
+
+               case LWS_TOKZS_TOKEN_POST_TERMINAL:
+                       /* report the new token next time */
+                       ts->start--;
+                       ts->len++;
+                       goto token_or_numeric;
+               }
+       }
+
+       /* we ran out of content */
+
+       if (utf8) /* ended partway through a multibyte char */
+               return LWS_TOKZE_ERR_BROKEN_UTF8;
+
+       if (state == LWS_TOKZS_QUOTED_STRING)
+               return LWS_TOKZE_ERR_UNTERM_STRING;
+
+       if (state != LWS_TOKZS_TOKEN_POST_TERMINAL &&
+           state != LWS_TOKZS_TOKEN) {
+               if ((ts->flags & LWS_TOKENIZE_F_COMMA_SEP_LIST) &&
+                    ts->delim == LWSTZ_DT_NEED_NEXT_CONTENT)
+                       return LWS_TOKZE_ERR_COMMA_LIST;
+
+               return LWS_TOKZE_ENDED;
+       }
+
+       /* report the pending token */
+
+token_or_numeric:
+
+       if (num != 1)
+               return LWS_TOKZE_TOKEN;
+       if (flo)
+               return LWS_TOKZE_FLOAT;
+
+       return LWS_TOKZE_INTEGER;
+}
+
+
+LWS_VISIBLE LWS_EXTERN int
+lws_tokenize_cstr(struct lws_tokenize *ts, char *str, int max)
+{
+       if (ts->token_len + 1 >= max)
+               return 1;
+
+       memcpy(str, ts->token, ts->token_len);
+       str[ts->token_len] = '\0';
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_tokenize_init(struct lws_tokenize *ts, const char *start, int flags)
+{
+       ts->start = start;
+       ts->len = 0x7fffffff;
+       ts->flags = flags;
+       ts->delim = LWSTZ_DT_NEED_FIRST_CONTENT;
+}
+
+#if LWS_MAX_SMP > 1
+
+void
+lws_mutex_refcount_init(struct lws_mutex_refcount *mr)
+{
+       pthread_mutex_init(&mr->lock, NULL);
+       mr->last_lock_reason = NULL;
+       mr->lock_depth = 0;
+       mr->metadata = 0;
+       mr->lock_owner = 0;
+}
+
+void
+lws_mutex_refcount_destroy(struct lws_mutex_refcount *mr)
+{
+       pthread_mutex_destroy(&mr->lock);
+}
+
+void
+lws_mutex_refcount_lock(struct lws_mutex_refcount *mr, const char *reason)
+{
+       /* if true, this sequence is atomic because our thread has the lock
+        *
+        *  - if true, only guy who can race to make it untrue is our thread,
+        *    and we are here.
+        *
+        *  - if false, only guy who could race to make it true is our thread,
+        *    and we are here
+        *
+        *  - it can be false and change to a different tid that is also false
+        */
+       if (mr->lock_owner == pthread_self()) {
+               /* atomic because we only change it if we own the lock */
+               mr->lock_depth++;
+               return;
+       }
+
+       pthread_mutex_lock(&mr->lock);
+       /* atomic because only we can have the lock */
+       mr->last_lock_reason = reason;
+       mr->lock_owner = pthread_self();
+       mr->lock_depth = 1;
+       //lwsl_notice("tid %d: lock %s\n", mr->tid, reason);
+}
+
+void
+lws_mutex_refcount_unlock(struct lws_mutex_refcount *mr)
+{
+       if (--mr->lock_depth)
+               /* atomic because only thread that has the lock can unlock */
+               return;
+
+       mr->last_lock_reason = "free";
+       mr->lock_owner = 0;
+       //lwsl_notice("tid %d: unlock %s\n", mr->tid, mr->last_lock_reason);
+       pthread_mutex_unlock(&mr->lock);
+}
+
+#endif /* SMP */
+
+
+const char *
+lws_cmdline_option(int argc, const char **argv, const char *val)
+{
+       int n = (int)strlen(val), c = argc;
+
+       while (--c > 0) {
+
+               if (!strncmp(argv[c], val, n)) {
+                       if (!*(argv[c] + n) && c < argc - 1) {
+                               /* coverity treats unchecked argv as "tainted" */
+                               if (!argv[c + 1] || strlen(argv[c + 1]) > 1024)
+                                       return NULL;
+                               return argv[c + 1];
+                       }
+
+                       return argv[c] + n;
+               }
+       }
+
+       return NULL;
+}
+
+
+const lws_humanize_unit_t humanize_schema_si[] = {
+       { "Pi ", LWS_PI }, { "Ti ", LWS_TI }, { "Gi ", LWS_GI },
+       { "Mi ", LWS_MI }, { "Ki ", LWS_KI }, { "   ", 1 },
+       { NULL, 0 }
+};
+const lws_humanize_unit_t humanize_schema_si_bytes[] = {
+       { "PiB", LWS_PI }, { "TiB", LWS_TI }, { "GiB", LWS_GI },
+       { "MiB", LWS_MI }, { "KiB", LWS_KI }, { "B  ", 1 },
+       { NULL, 0 }
+};
+const lws_humanize_unit_t humanize_schema_us[] = {
+       { "y  ",  (uint64_t)365 * 24 * 3600 * LWS_US_PER_SEC },
+       { "d  ",  (uint64_t)24 * 3600 * LWS_US_PER_SEC },
+       { "hr ", (uint64_t)3600 * LWS_US_PER_SEC },
+       { "min", 60 * LWS_US_PER_SEC },
+       { "s  ", LWS_US_PER_SEC },
+       { "ms ", LWS_US_PER_MS },
+       { "us ", 1 },
+       { NULL, 0 }
+};
+
+int
+lws_humanize(char *p, int len, uint64_t v, const lws_humanize_unit_t *schema)
+{
+       do {
+               if (v >= schema->factor || schema->factor == 1) {
+                       if (schema->factor == 1)
+                               return lws_snprintf(p, len,
+                                       " %4"PRIu64"%s    ",
+                                       v / schema->factor, schema->name);
+
+                       return lws_snprintf(p, len, " %4"PRIu64".%03"PRIu64"%s",
+                               v / schema->factor,
+                               (v % schema->factor) / (schema->factor / 1000),
+                               schema->name);
+               }
+               schema++;
+       } while (schema->name);
+
+       assert(0);
+
+       return 0;
+}
+
+int
+lws_system_get_info(struct lws_context *context, lws_system_item_t item,
+                   lws_system_arg_t arg, size_t *len)
+{
+       if (!context->system_ops || !context->system_ops->get_info)
+               return 1;
+
+       return context->system_ops->get_info(item, arg, len);
+}
+
+int
+lws_system_reboot(struct lws_context *context)
+{
+       if (!context->system_ops || !context->system_ops->reboot)
+               return 1;
+
+       return context->system_ops->reboot();
+}
diff --git a/lib/core/logs.c b/lib/core/logs.c
new file mode 100644 (file)
index 0000000..dbece69
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#ifdef LWS_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#if defined(LWS_PLAT_OPTEE)
+void lwsl_emit_optee(int level, const char *line);
+#endif
+
+int log_level = LLL_ERR | LLL_WARN | LLL_NOTICE;
+static void (*lwsl_emit)(int level, const char *line)
+#ifndef LWS_PLAT_OPTEE
+       = lwsl_emit_stderr
+#else
+       = lwsl_emit_optee;
+#endif
+       ;
+#ifndef LWS_PLAT_OPTEE
+static const char * const log_level_names[] = {
+       "E",
+       "W",
+       "N",
+       "I",
+       "D",
+       "P",
+       "H",
+       "EXT",
+       "C",
+       "L",
+       "U",
+       "T",
+       "?",
+       "?"
+};
+#endif
+
+LWS_VISIBLE int
+lwsl_timestamp(int level, char *p, int len)
+{
+#ifndef LWS_PLAT_OPTEE
+#ifndef _WIN32_WCE
+       time_t o_now = time(NULL);
+#endif
+       unsigned long long now;
+       struct tm *ptm = NULL;
+#ifndef WIN32
+       struct tm tm;
+#endif
+       int n;
+
+#ifndef _WIN32_WCE
+#ifdef WIN32
+       ptm = localtime(&o_now);
+#else
+       if (localtime_r(&o_now, &tm))
+               ptm = &tm;
+#endif
+#endif
+       p[0] = '\0';
+       for (n = 0; n < LLL_COUNT; n++) {
+               if (level != (1 << n))
+                       continue;
+               now = lws_now_usecs() / 100;
+               if (ptm)
+                       n = lws_snprintf(p, len,
+                               "[%04d/%02d/%02d %02d:%02d:%02d:%04d] %s: ",
+                               ptm->tm_year + 1900,
+                               ptm->tm_mon + 1,
+                               ptm->tm_mday,
+                               ptm->tm_hour,
+                               ptm->tm_min,
+                               ptm->tm_sec,
+                               (int)(now % 10000), log_level_names[n]);
+               else
+                       n = lws_snprintf(p, len, "[%llu:%04d] %s: ",
+                                       (unsigned long long) now / 10000,
+                                       (int)(now % 10000), log_level_names[n]);
+               return n;
+       }
+#else
+       p[0] = '\0';
+#endif
+
+       return 0;
+}
+
+#ifndef LWS_PLAT_OPTEE
+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 */
+       "[33m", /* LLL_HEADER */
+       "[33m", /* LLL_EXT */
+       "[33m", /* LLL_CLIENT */
+       "[33;1m", /* LLL_LATENCY */
+       "[30;1m", /* LLL_USER */
+       "[31m", /* LLL_THREAD */
+};
+
+static char tty;
+
+LWS_VISIBLE void
+lwsl_emit_stderr(int level, const char *line)
+{
+       char buf[50];
+       int n, m = LWS_ARRAY_SIZE(colours) - 1;
+
+       if (!tty)
+               tty = isatty(2) | 2;
+       lwsl_timestamp(level, buf, sizeof(buf));
+
+       if (tty == 3) {
+               n = 1 << (LWS_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);
+}
+
+LWS_VISIBLE void
+lwsl_emit_stderr_notimestamp(int level, const char *line)
+{
+       int n, m = LWS_ARRAY_SIZE(colours) - 1;
+
+       if (!tty)
+               tty = isatty(2) | 2;
+
+       if (tty == 3) {
+               n = 1 << (LWS_ARRAY_SIZE(colours) - 1);
+               while (n) {
+                       if (level & n)
+                               break;
+                       m--;
+                       n >>= 1;
+               }
+               fprintf(stderr, "%c%s%s%c[0m", 27, colours[m], line, 27);
+       } else
+               fprintf(stderr, "%s", line);
+}
+
+#endif
+
+#if !(defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_NETWORK))
+LWS_VISIBLE void _lws_logv(int filter, const char *format, va_list vl)
+{
+       static char buf[256];
+       int n;
+
+       if (!(log_level & filter))
+               return;
+
+       n = vsnprintf(buf, sizeof(buf) - 1, format, vl);
+       (void)n;
+       /* vnsprintf returns what it would have written, even if truncated */
+       if (n > (int)sizeof(buf) - 1) {
+               n = sizeof(buf) - 5;
+               buf[n++] = '.';
+               buf[n++] = '.';
+               buf[n++] = '.';
+               buf[n++] = '\n';
+               buf[n] = '\0';
+       }
+       if (n > 0)
+               buf[n] = '\0';
+       lwsl_emit(filter, buf);
+}
+
+LWS_VISIBLE void _lws_log(int filter, const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+       _lws_logv(filter, format, ap);
+       va_end(ap);
+}
+#endif
+LWS_VISIBLE void lws_set_log_level(int level,
+                                  void (*func)(int level, const char *line))
+{
+       log_level = level;
+       if (func)
+               lwsl_emit = func;
+}
+
+LWS_VISIBLE int lwsl_visible(int level)
+{
+       return log_level & level;
+}
+
+LWS_VISIBLE void
+lwsl_hexdump_level(int hexdump_level, const void *vbuf, size_t len)
+{
+       unsigned char *buf = (unsigned char *)vbuf;
+       unsigned int n;
+
+       if (!lwsl_visible(hexdump_level))
+               return;
+
+       if (!len) {
+               _lws_log(hexdump_level, "(hexdump: zero length)\n");
+               return;
+       }
+
+       if (!vbuf) {
+               _lws_log(hexdump_level, "(hexdump: trying to dump %d at NULL)\n",
+                                       (int)len);
+               return;
+       }
+
+       _lws_log(hexdump_level, "\n");
+
+       for (n = 0; n < len;) {
+               unsigned int start = n, m;
+               char line[80], *p = line;
+
+               p += lws_snprintf(p, 10, "%04X: ", start);
+
+               for (m = 0; m < 16 && n < len; m++)
+                       p += lws_snprintf(p, 5, "%02X ", buf[n++]);
+               while (m++ < 16)
+                       p += lws_snprintf(p, 5, "   ");
+
+               p += lws_snprintf(p, 6, "   ");
+
+               for (m = 0; m < 16 && (start + m) < len; m++) {
+                       if (buf[start + m] >= ' ' && buf[start + m] < 127)
+                               *p++ = buf[start + m];
+                       else
+                               *p++ = '.';
+               }
+               while (m++ < 16)
+                       *p++ = ' ';
+
+               *p++ = '\n';
+               *p = '\0';
+               _lws_log(hexdump_level, "%s", line);
+               (void)line;
+       }
+
+       _lws_log(hexdump_level, "\n");
+}
+
+LWS_VISIBLE void
+lwsl_hexdump(const void *vbuf, size_t len)
+{
+#if defined(_DEBUG)
+       lwsl_hexdump_level(LLL_DEBUG, vbuf, len);
+#endif
+}
diff --git a/lib/core/lws_dll.c b/lib/core/lws_dll.c
new file mode 100644 (file)
index 0000000..bbb9c2b
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#ifdef LWS_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+
+void
+lws_dll_add_head(struct lws_dll *d, struct lws_dll *phead)
+{
+       if (!lws_dll_is_detached(d, phead)) {
+               assert(0); /* only wholly detached things can be added */
+               return;
+       }
+
+       /* our next guy is current first guy, if any */
+       if (phead->next != d)
+               d->next = phead->next;
+
+       /* if there is a next guy, set his prev ptr to our next ptr */
+       if (d->next)
+               d->next->prev = d;
+       /* there is nobody previous to us, we are the head */
+       d->prev = NULL;
+
+       /* set the first guy to be us */
+       phead->next = d;
+
+       /* if there was nothing on the list before, we are also now the tail */
+       if (!phead->prev)
+               phead->prev = d;
+
+       assert(d->prev != d);
+       assert(d->next != d);
+}
+
+void
+lws_dll_add_tail(struct lws_dll *d, struct lws_dll *phead)
+{
+       if (!lws_dll_is_detached(d, phead)) {
+               assert(0); /* only wholly detached things can be added */
+               return;
+       }
+
+       /* our previous guy is current last guy */
+       d->prev = phead->prev;
+       /* if there is a prev guy, set his next ptr to our prev ptr */
+       if (d->prev)
+               d->prev->next = d;
+       /* our next ptr is NULL */
+       d->next = NULL;
+       /* set the last guy to be us */
+       phead->prev = d;
+
+       /* list head is also us if we're the first */
+       if (!phead->next)
+               phead->next = d;
+
+       assert(d->prev != d);
+       assert(d->next != d);
+}
+
+void
+lws_dll_insert(struct lws_dll *n, struct lws_dll *target,
+              struct lws_dll *phead, int before)
+{
+       if (!lws_dll_is_detached(n, phead)) {
+               assert(0); /* only wholly detached things can be inserted */
+               return;
+       }
+       if (!target) {
+               /*
+                * the case where there's no target identified degenerates to
+                * a simple add at head or tail
+                */
+               if (before) {
+                       lws_dll_add_head(n, phead);
+                       return;
+               }
+               lws_dll_add_tail(n, phead);
+               return;
+       }
+
+       /*
+        * in the case there's a target "cursor", we have to do the work to
+        * stitch the new guy in appropriately
+        */
+
+       if (before) {
+               /*
+                *  we go before dd
+                *  DDp <-> DD <-> DDn   -->   DDp <-> us <-> DD <-> DDn
+                */
+               /* we point forward to dd */
+               n->next = target;
+               /* we point back to what dd used to point back to */
+               n->prev = target->prev;
+               /* DDp points forward to us now */
+               if (target->prev)
+                       target->prev->next = n;
+               /* DD points back to us now */
+               target->prev = n;
+
+               /* if target was the head, we are now the head */
+               if (phead->next == target)
+                       phead->next = n;
+
+               /* since we are before another guy, we cannot become the tail */
+
+       } else {
+               /*
+                *  we go after dd
+                *  DDp <-> DD <-> DDn   -->   DDp <-> DD <-> us <-> DDn
+                */
+               /* we point forward to what dd used to point forward to */
+               n->next = target->next;
+               /* we point back to dd */
+               n->prev = target;
+               /* DDn points back to us */
+               if (target->next)
+                       target->next->prev = n;
+               /* DD points forward to us */
+               target->next = n;
+
+               /* if target was the tail, we are now the tail */
+               if (phead->prev == target)
+                       phead->prev = n;
+
+               /* since we go after another guy, we cannot become the head */
+       }
+}
+
+/* situation is:
+ *
+ *  HEAD: struct lws_dll * = &entry1
+ *
+ *  Entry 1: struct lws_dll  .pprev = &HEAD , .next = Entry 2
+ *  Entry 2: struct lws_dll  .pprev = &entry1 , .next = &entry2
+ *  Entry 3: struct lws_dll  .pprev = &entry2 , .next = NULL
+ *
+ *  Delete Entry1:
+ *
+ *   - HEAD = &entry2
+ *   - Entry2: .pprev = &HEAD, .next = &entry3
+ *   - Entry3: .pprev = &entry2, .next = NULL
+ *
+ *  Delete Entry2:
+ *
+ *   - HEAD = &entry1
+ *   - Entry1: .pprev = &HEAD, .next = &entry3
+ *   - Entry3: .pprev = &entry1, .next = NULL
+ *
+ *  Delete Entry3:
+ *
+ *   - HEAD = &entry1
+ *   - Entry1: .pprev = &HEAD, .next = &entry2
+ *   - Entry2: .pprev = &entry1, .next = NULL
+ *
+ */
+
+void
+lws_dll_remove(struct lws_dll *d)
+{
+       if (!d->prev && !d->next)
+               return;
+
+       /*
+        *  remove us
+        *
+        *  USp <-> us <-> USn  -->  USp <-> USn
+        */
+
+       /* if we have a next guy, set his prev to our prev */
+       if (d->next)
+               d->next->prev = d->prev;
+
+       /* set our prev guy to our next guy instead of us */
+       if (d->prev)
+               d->prev->next = d->next;
+
+       /* we're out of the list, we should not point anywhere any more */
+       d->prev = NULL;
+       d->next = NULL;
+}
+
+void
+lws_dll_remove_track_tail(struct lws_dll *d, struct lws_dll *phead)
+{
+       if (lws_dll_is_detached(d, phead)) {
+               assert(phead->prev != d);
+               assert(phead->next != d);
+               return;
+       }
+
+       /* if we have a next guy, set his prev to our prev */
+       if (d->next)
+               d->next->prev = d->prev;
+
+       /* if we have a previous guy, set his next to our next */
+       if (d->prev)
+               d->prev->next = d->next;
+
+       if (phead->prev == d)
+               phead->prev = d->prev;
+
+       if (phead->next == d)
+               phead->next = d->next;
+
+       /* we're out of the list, we should not point anywhere any more */
+       d->prev = NULL;
+       d->next = NULL;
+}
+
+
+int
+lws_dll_foreach_safe(struct lws_dll *phead, void *user,
+                    int (*cb)(struct lws_dll *d, void *user))
+{
+       lws_start_foreach_dll_safe(struct lws_dll *, p, tp, phead->next) {
+               if (cb(p, user))
+                       return 1;
+       } lws_end_foreach_dll_safe(p, tp);
+
+       return 0;
+}
diff --git a/lib/core/lws_dll2.c b/lib/core/lws_dll2.c
new file mode 100644 (file)
index 0000000..f3b47f9
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#ifdef LWS_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+int
+lws_dll2_foreach_safe(struct lws_dll2_owner *owner, void *user,
+                     int (*cb)(struct lws_dll2 *d, void *user))
+{
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp, owner->head) {
+               if (cb(p, user))
+                       return 1;
+       } lws_end_foreach_dll_safe(p, tp);
+
+       return 0;
+}
+
+void
+lws_dll2_add_head(struct lws_dll2 *d, struct lws_dll2_owner *owner)
+{
+       if (!lws_dll2_is_detached(d)) {
+               assert(0); /* only wholly detached things can be added */
+               return;
+       }
+
+       /* our next guy is current first guy, if any */
+       if (owner->head != d)
+               d->next = owner->head;
+
+       /* if there is a next guy, set his prev ptr to our next ptr */
+       if (d->next)
+               d->next->prev = d;
+       /* there is nobody previous to us, we are the head */
+       d->prev = NULL;
+
+       /* set the first guy to be us */
+       owner->head = d;
+
+       if (!owner->tail)
+               owner->tail = d;
+
+       d->owner = owner;
+       owner->count++;
+}
+
+/*
+ * add us to the list that 'after' is in, just before him
+ */
+
+void
+lws_dll2_add_before(struct lws_dll2 *d, struct lws_dll2 *after)
+{
+       lws_dll2_owner_t *owner = after->owner;
+
+       if (!lws_dll2_is_detached(d)) {
+               assert(0); /* only wholly detached things can be added */
+               return;
+       }
+
+       if (lws_dll2_is_detached(after)) {
+               assert(0); /* can't add after something detached */
+               return;
+       }
+
+       d->owner = owner;
+
+       /* we need to point forward to after */
+
+       d->next = after;
+
+       /* we need to point back to after->prev */
+
+       d->prev = after->prev;
+
+       /* guy that used to point to after, needs to point to us */
+
+       if (after->prev)
+               after->prev->next = d;
+       else
+               owner->head = d;
+
+       /* then after needs to point back to us */
+
+       after->prev = d;
+
+       owner->count++;
+}
+
+void
+lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner)
+{
+       if (!lws_dll2_is_detached(d)) {
+               assert(0); /* only wholly detached things can be added */
+               return;
+       }
+
+       /* our previous guy is current last guy */
+       d->prev = owner->tail;
+       /* if there is a prev guy, set his next ptr to our prev ptr */
+       if (d->prev)
+               d->prev->next = d;
+       /* our next ptr is NULL */
+       d->next = NULL;
+       /* set the last guy to be us */
+       owner->tail = d;
+
+       /* list head is also us if we're the first */
+       if (!owner->head)
+               owner->head = d;
+
+       d->owner = owner;
+       owner->count++;
+}
+
+void
+lws_dll2_remove(struct lws_dll2 *d)
+{
+       if (lws_dll2_is_detached(d))
+               return;
+
+       /* if we have a next guy, set his prev to our prev */
+       if (d->next)
+               d->next->prev = d->prev;
+
+       /* if we have a previous guy, set his next to our next */
+       if (d->prev)
+               d->prev->next = d->next;
+
+       /* if we have phead, track the tail and head if it points to us... */
+
+       if (d->owner->tail == d)
+               d->owner->tail = d->prev;
+
+       if (d->owner->head == d)
+               d->owner->head = d->next;
+
+       d->owner->count--;
+
+       /* we're out of the list, we should not point anywhere any more */
+       d->owner = NULL;
+       d->prev = NULL;
+       d->next = NULL;
+}
+
+void
+lws_dll2_clear(struct lws_dll2 *d)
+{
+       d->owner = NULL;
+       d->prev = NULL;
+       d->next = NULL;
+}
+
+void
+lws_dll2_owner_clear(struct lws_dll2_owner *d)
+{
+       d->head = NULL;
+       d->tail = NULL;
+       d->count = 0;
+}
+
+void
+lws_dll2_add_sorted(lws_dll2_t *d, lws_dll2_owner_t *own,
+                   int (*compare)(const lws_dll2_t *d, const lws_dll2_t *i))
+{
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+                                  lws_dll2_get_head(own)) {
+               assert(p != d);
+
+               if (compare(p, d) >= 0) {
+                       /* drop us in before this guy */
+                       lws_dll2_add_before(d, p);
+
+                       // lws_dll2_describe(own, "post-insert");
+
+                       return;
+               }
+       } lws_end_foreach_dll_safe(p, tp);
+
+       /*
+        * Either nobody on the list yet to compare him to, or he's the
+        * furthest away timeout... stick him at the tail end
+        */
+
+       lws_dll2_add_tail(d, own);
+}
+
+#if defined(_DEBUG)
+
+void
+lws_dll2_describe(lws_dll2_owner_t *owner, const char *desc)
+{
+       int n = 1;
+
+       lwsl_info("%s: %s: owner %p: count %d, head %p, tail %p\n",
+                   __func__, desc, owner, owner->count, owner->head, owner->tail);
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+                                  lws_dll2_get_head(owner)) {
+               lwsl_info("%s:    %d: %p: owner %p, prev %p, next %p\n",
+                           __func__, n++, p, p->owner, p->prev, p->next);
+       } lws_end_foreach_dll_safe(p, tp);
+}
+
+#endif
diff --git a/lib/core/private.h b/lib/core/private.h
new file mode 100644 (file)
index 0000000..196b43a
--- /dev/null
@@ -0,0 +1,621 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "lws_config.h"
+#include "lws_config_private.h"
+
+#if defined(LWS_WITH_CGI) && defined(LWS_HAVE_VFORK) && \
+    !defined(NO_GNU_SOURCE_THIS_TIME)
+ #define  _GNU_SOURCE
+#endif
+
+/*
+#if !defined(_POSIX_C_SOURCE)
+#define _POSIX_C_SOURCE 200112L
+#endif
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#ifdef LWS_HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#include <assert.h>
+
+#ifdef LWS_HAVE_SYS_TYPES_H
+ #include <sys/types.h>
+#endif
+#if defined(LWS_HAVE_SYS_STAT_H) && !defined(LWS_PLAT_OPTEE)
+ #include <sys/stat.h>
+#endif
+
+#if LWS_MAX_SMP > 1
+ #include <pthread.h>
+#endif
+
+#ifndef LWS_DEF_HEADER_LEN
+#define LWS_DEF_HEADER_LEN 4096
+#endif
+#ifndef LWS_DEF_HEADER_POOL
+#define LWS_DEF_HEADER_POOL 4
+#endif
+#ifndef LWS_MAX_PROTOCOLS
+#define LWS_MAX_PROTOCOLS 5
+#endif
+#ifndef LWS_MAX_EXTENSIONS_ACTIVE
+#define LWS_MAX_EXTENSIONS_ACTIVE 1
+#endif
+#ifndef LWS_MAX_EXT_OFFERS
+#define LWS_MAX_EXT_OFFERS 8
+#endif
+#ifndef SPEC_LATEST_SUPPORTED
+#define SPEC_LATEST_SUPPORTED 13
+#endif
+#ifndef AWAITING_TIMEOUT
+#define AWAITING_TIMEOUT 20
+#endif
+#ifndef CIPHERS_LIST_STRING
+#define CIPHERS_LIST_STRING "DEFAULT"
+#endif
+#ifndef LWS_SOMAXCONN
+#define LWS_SOMAXCONN SOMAXCONN
+#endif
+
+#define MAX_WEBSOCKET_04_KEY_LEN 128
+
+#ifndef SYSTEM_RANDOM_FILEPATH
+#define SYSTEM_RANDOM_FILEPATH "/dev/urandom"
+#endif
+
+#define LWS_H2_RX_SCRATCH_SIZE 512
+
+#define lws_socket_is_valid(x) (x != LWS_SOCK_INVALID)
+
+#ifndef LWS_HAVE_STRERROR
+ #define strerror(x) ""
+#endif
+
+
+ /*
+  *
+  *  ------ private platform defines ------
+  *
+  */
+
+#if defined(LWS_WITH_ESP32)
+ #include "plat/esp32/private.h"
+#else
+ #if defined(WIN32) || defined(_WIN32)
+  #include "plat/windows/private.h"
+ #else
+  #if defined(LWS_PLAT_OPTEE)
+   #include "plat/optee/private.h"
+  #else
+   #include "plat/unix/private.h"
+  #endif
+ #endif
+#endif
+
+ /*
+  *
+  *  ------ public api ------
+  *
+  */
+
+#include "libwebsockets.h"
+
+#include "tls/private.h"
+
+#if defined(WIN32) || defined(_WIN32)
+        // Visual studio older than 2015 and WIN_CE has only _stricmp
+       #if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_WIN32_WCE)
+       #define strcasecmp _stricmp
+       #define strncasecmp _strnicmp
+       #elif !defined(__MINGW32__)
+       #define strcasecmp stricmp
+       #define strncasecmp strnicmp
+       #endif
+       #define getdtablesize() 30000
+#endif
+
+#ifndef LWS_ARRAY_SIZE
+#define LWS_ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#if defined(__clang__)
+#define lws_memory_barrier() __sync_synchronize()
+#elif defined(__GNUC__)
+#define lws_memory_barrier() __sync_synchronize()
+#else
+#define lws_memory_barrier()
+#endif
+
+
+struct lws_ring {
+       void *buf;
+       void (*destroy_element)(void *element);
+       uint32_t buflen;
+       uint32_t element_len;
+       uint32_t head;
+       uint32_t oldest_tail;
+};
+
+struct lws_protocols;
+struct lws;
+
+#if defined(LWS_WITH_NETWORK)
+#include "event-libs/private.h"
+
+
+struct lws_io_watcher {
+#ifdef LWS_WITH_LIBEV
+       struct lws_io_watcher_libev ev;
+#endif
+#ifdef LWS_WITH_LIBUV
+       struct lws_io_watcher_libuv uv;
+#endif
+#ifdef LWS_WITH_LIBEVENT
+       struct lws_io_watcher_libevent event;
+#endif
+       struct lws_context *context;
+
+       uint8_t actual_events;
+};
+
+struct lws_signal_watcher {
+#ifdef LWS_WITH_LIBEV
+       struct lws_signal_watcher_libev ev;
+#endif
+#ifdef LWS_WITH_LIBUV
+       struct lws_signal_watcher_libuv uv;
+#endif
+#ifdef LWS_WITH_LIBEVENT
+       struct lws_signal_watcher_libevent event;
+#endif
+       struct lws_context *context;
+};
+
+struct lws_foreign_thread_pollfd {
+       struct lws_foreign_thread_pollfd *next;
+       int fd_index;
+       int _and;
+       int _or;
+};
+#endif
+
+#if LWS_MAX_SMP > 1
+
+struct lws_mutex_refcount {
+       pthread_mutex_t lock;
+       pthread_t lock_owner;
+       const char *last_lock_reason;
+       char lock_depth;
+       char metadata;
+};
+
+void
+lws_mutex_refcount_init(struct lws_mutex_refcount *mr);
+
+void
+lws_mutex_refcount_destroy(struct lws_mutex_refcount *mr);
+
+void
+lws_mutex_refcount_lock(struct lws_mutex_refcount *mr, const char *reason);
+
+void
+lws_mutex_refcount_unlock(struct lws_mutex_refcount *mr);
+#endif
+
+#if defined(LWS_WITH_NETWORK)
+#include "core-net/private.h"
+#endif
+
+struct lws_deferred_free
+{
+       struct lws_deferred_free *next;
+       time_t deadline;
+       void *payload;
+};
+
+/*
+ * the rest is managed per-context, that includes
+ *
+ *  - processwide single fd -> wsi lookup
+ *  - contextwide headers pool
+ */
+
+struct lws_context {
+       time_t last_ws_ping_pong_check_s;
+       lws_usec_t time_up; /* monotonic */
+       const struct lws_plat_file_ops *fops;
+       struct lws_plat_file_ops fops_platform;
+       struct lws_context **pcontext_finalize;
+
+       const struct lws_tls_ops *tls_ops;
+
+       const char *username, *groupname;
+
+#if defined(LWS_WITH_HTTP2)
+       struct http2_settings set;
+#endif
+#if defined(LWS_WITH_ZIP_FOPS)
+       struct lws_plat_file_ops fops_zip;
+#endif
+#if defined(LWS_WITH_NETWORK)
+       struct lws_context_per_thread pt[LWS_MAX_SMP];
+       struct lws_conn_stats conn_stats;
+       struct lws_vhost *vhost_list;
+       struct lws_vhost *no_listener_vhost_list;
+       struct lws_vhost *vhost_pending_destruction_list;
+       struct lws_plugin *plugin_list;
+#ifdef _WIN32
+/* different implementation between unix and windows */
+       struct lws_fd_hashtable fd_hashtable[FD_HASHTABLE_MODULUS];
+#else
+       struct lws **lws_lookup;
+
+#endif
+#endif
+#if LWS_MAX_SMP > 1
+       struct lws_mutex_refcount mr;
+#endif
+
+#if defined(LWS_AMAZON_RTOS)
+       mbedtls_entropy_context mec;
+       mbedtls_ctr_drbg_context mcdc;
+#endif
+
+       struct lws_deferred_free *deferred_free_list;
+
+#if defined(LWS_WITH_THREADPOOL)
+       struct lws_threadpool *tp_list_head;
+#endif
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       struct lws_peer **pl_hash_table;
+       struct lws_peer *peer_wait_list;
+       time_t next_cull;
+#endif
+
+       const lws_system_ops_t *system_ops;
+       void *external_baggage_free_on_destroy;
+       const struct lws_token_limits *token_limits;
+       void *user_space;
+       const struct lws_protocol_vhost_options *reject_service_keywords;
+       lws_reload_func deprecation_cb;
+       void (*eventlib_signal_cb)(void *event_lib_handle, int signum);
+
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+       cap_value_t caps[4];
+       char count_caps;
+#endif
+
+#if defined(LWS_WITH_NETWORK)
+#if defined(LWS_WITH_LIBEV)
+       struct lws_context_eventlibs_libev ev;
+#endif
+#if defined(LWS_WITH_LIBUV)
+       struct lws_context_eventlibs_libuv uv;
+#endif
+#if defined(LWS_WITH_LIBEVENT)
+       struct lws_context_eventlibs_libevent event;
+#endif
+       struct lws_event_loop_ops *event_loop_ops;
+#endif
+
+#if defined(LWS_WITH_TLS) && defined(LWS_WITH_NETWORK)
+       struct lws_context_tls tls;
+#endif
+
+       char canonical_hostname[128];
+       const char *server_string;
+
+#ifdef LWS_LATENCY
+       unsigned long worst_latency;
+       char worst_latency_info[256];
+#endif
+
+#if defined(LWS_WITH_ESP32)
+       unsigned long time_last_state_dump;
+       uint32_t last_free_heap;
+#endif
+
+       int max_fds;
+       int count_event_loop_static_asset_handles;
+#if !defined(LWS_NO_DAEMONIZE)
+       pid_t started_with_parent;
+#endif
+       int uid, gid;
+
+       int fd_random;
+
+       int count_wsi_allocated;
+       int count_cgi_spawned;
+       unsigned int options;
+       unsigned int fd_limit_per_thread;
+       unsigned int timeout_secs;
+       unsigned int pt_serv_buf_size;
+       int max_http_header_data;
+       int max_http_header_pool;
+       int simultaneous_ssl_restriction;
+       int simultaneous_ssl;
+#if defined(LWS_WITH_PEER_LIMITS)
+       uint32_t pl_hash_elements;      /* protected by context->lock */
+       uint32_t count_peers;           /* protected by context->lock */
+       unsigned short ip_limit_ah;
+       unsigned short ip_limit_wsi;
+#endif
+       unsigned int deprecated:1;
+       unsigned int being_destroyed:1;
+       unsigned int being_destroyed1:1;
+       unsigned int being_destroyed2:1;
+       unsigned int requested_kill:1;
+       unsigned int protocol_init_done:1;
+       unsigned int doing_protocol_init:1;
+       unsigned int done_protocol_destroy_cb:1;
+       unsigned int finalize_destroy_after_internal_loops_stopped:1;
+       unsigned int max_fds_unrelated_to_ulimit:1;
+
+       short count_threads;
+       short plugin_protocol_count;
+       short plugin_extension_count;
+       short server_string_len;
+       unsigned short ws_ping_pong_interval;
+       unsigned short deprecation_pending_listen_close_count;
+
+       uint8_t max_fi;
+
+#if defined(LWS_WITH_STATS)
+       uint8_t updated;
+#endif
+};
+
+int
+lws_check_deferred_free(struct lws_context *context, int tsi, int force);
+
+#define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x]
+#define lws_get_vh_protocol(vh, x) vh->protocols[x]
+
+int
+lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max);
+
+void
+lws_vhost_destroy1(struct lws_vhost *vh);
+
+
+#if defined(LWS_WITH_ESP32)
+LWS_EXTERN int
+lws_find_string_in_file(const char *filename, const char *str, int stringlen);
+#endif
+
+
+signed char char_to_hex(const char c);
+
+
+struct lws_buflist {
+       struct lws_buflist *next;
+
+       size_t len;
+       size_t pos;
+
+       uint8_t buf[1]; /* true length of this is set by the oversize malloc */
+};
+
+
+LWS_EXTERN char *
+lws_strdup(const char *s);
+
+LWS_EXTERN int log_level;
+
+
+
+#ifndef LWS_LATENCY
+static LWS_INLINE void
+lws_latency(struct lws_context *context, struct lws *wsi, const char *action,
+           int ret, int completion) {
+       do {
+               (void)context; (void)wsi; (void)action; (void)ret;
+               (void)completion;
+       } while (0);
+}
+static LWS_INLINE void
+lws_latency_pre(struct lws_context *context, struct lws *wsi) {
+       do { (void)context; (void)wsi; } while (0);
+}
+#else
+#define lws_latency_pre(_context, _wsi) lws_latency(_context, _wsi, NULL, 0, 0)
+extern void
+lws_latency(struct lws_context *context, struct lws *wsi, const char *action,
+           int ret, int completion);
+#endif
+
+
+LWS_EXTERN int
+lws_b64_selftest(void);
+
+
+
+
+
+#ifndef LWS_NO_DAEMONIZE
+ LWS_EXTERN int get_daemonize_pid();
+#else
+ #define get_daemonize_pid() (0)
+#endif
+
+LWS_EXTERN void lwsl_emit_stderr(int level, const char *line);
+
+#if !defined(LWS_WITH_TLS)
+ #define LWS_SSL_ENABLED(context) (0)
+ #define lws_context_init_server_ssl(_a, _b) (0)
+ #define lws_ssl_destroy(_a)
+ #define lws_context_init_alpn(_a)
+ #define lws_ssl_capable_read lws_ssl_capable_read_no_ssl
+ #define lws_ssl_capable_write lws_ssl_capable_write_no_ssl
+ #define lws_ssl_pending lws_ssl_pending_no_ssl
+ #define lws_server_socket_service_ssl(_b, _c) (0)
+ #define lws_ssl_close(_a) (0)
+ #define lws_ssl_context_destroy(_a)
+ #define lws_ssl_SSL_CTX_destroy(_a)
+ #define lws_ssl_remove_wsi_from_buffered_list(_a)
+ #define __lws_ssl_remove_wsi_from_buffered_list(_a)
+ #define lws_context_init_ssl_library(_a)
+ #define lws_context_deinit_ssl_library(_a)
+ #define lws_tls_check_all_cert_lifetimes(_a)
+ #define lws_tls_acme_sni_cert_destroy(_a)
+#endif
+
+
+
+#if LWS_MAX_SMP > 1
+#define lws_context_lock(c, reason) lws_mutex_refcount_lock(&c->mr, reason)
+#define lws_context_unlock(c) lws_mutex_refcount_unlock(&c->mr)
+
+static LWS_INLINE void
+lws_vhost_lock(struct lws_vhost *vhost)
+{
+       pthread_mutex_lock(&vhost->lock);
+}
+
+static LWS_INLINE void
+lws_vhost_unlock(struct lws_vhost *vhost)
+{
+       pthread_mutex_unlock(&vhost->lock);
+}
+
+
+#else
+#define lws_pt_mutex_init(_a) (void)(_a)
+#define lws_pt_mutex_destroy(_a) (void)(_a)
+#define lws_pt_lock(_a, b) (void)(_a)
+#define lws_pt_unlock(_a) (void)(_a)
+#define lws_context_lock(_a, _b) (void)(_a)
+#define lws_context_unlock(_a) (void)(_a)
+#define lws_vhost_lock(_a) (void)(_a)
+#define lws_vhost_unlock(_a) (void)(_a)
+#define lws_pt_stats_lock(_a) (void)(_a)
+#define lws_pt_stats_unlock(_a) (void)(_a)
+#endif
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len);
+
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ssl_pending_no_ssl(struct lws *wsi);
+
+int
+lws_tls_check_cert_lifetime(struct lws_vhost *vhost);
+
+int lws_jws_selftest(void);
+int lws_jwe_selftest(void);
+
+int
+lws_protocol_init(struct lws_context *context);
+
+int
+lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p,
+                 const char *reason);
+
+const struct lws_protocol_vhost_options *
+lws_vhost_protocol_options(struct lws_vhost *vh, const char *name);
+
+const struct lws_http_mount *
+lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len);
+
+/*
+ * custom allocator
+ */
+LWS_EXTERN void *
+lws_realloc(void *ptr, size_t size, const char *reason);
+
+LWS_EXTERN void * LWS_WARN_UNUSED_RESULT
+lws_zalloc(size_t size, const char *reason);
+
+#ifdef LWS_PLAT_OPTEE
+void *lws_malloc(size_t size, const char *reason);
+void lws_free(void *p);
+#define lws_free_set_NULL(P)    do { lws_free(P); (P) = NULL; } while(0)
+#else
+#define lws_malloc(S, R)       lws_realloc(NULL, S, R)
+#define lws_free(P)    lws_realloc(P, 0, "lws_free")
+#define lws_free_set_NULL(P)   do { lws_realloc(P, 0, "free"); (P) = NULL; } while(0)
+#endif
+
+int
+lws_create_event_pipes(struct lws_context *context);
+
+int
+lws_plat_apply_FD_CLOEXEC(int n);
+
+const struct lws_plat_file_ops *
+lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path,
+                   const char **vpath);
+
+/* lws_plat_ */
+
+LWS_EXTERN int
+lws_plat_context_early_init(void);
+LWS_EXTERN void
+lws_plat_context_early_destroy(struct lws_context *context);
+LWS_EXTERN void
+lws_plat_context_late_destroy(struct lws_context *context);
+
+LWS_EXTERN int
+lws_plat_init(struct lws_context *context,
+             const struct lws_context_creation_info *info);
+LWS_EXTERN int
+lws_plat_drop_app_privileges(struct lws_context *context, int actually_drop);
+
+#if defined(LWS_WITH_UNIX_SOCK)
+int
+lws_plat_user_colon_group_to_ids(const char *u_colon_g, uid_t *puid, gid_t *pgid);
+#endif
+
+LWS_EXTERN int
+lws_check_byte_utf8(unsigned char state, unsigned char c);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len);
+LWS_EXTERN int alloc_file(struct lws_context *context, const char *filename,
+                         uint8_t **buf, lws_filepos_t *amount);
+
+void
+lws_context_destroy2(struct lws_context *context);
+
+
+#ifdef __cplusplus
+};
+#endif
diff --git a/lib/core/vfs.c b/lib/core/vfs.c
new file mode 100644 (file)
index 0000000..0f37d90
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+
+LWS_VISIBLE LWS_EXTERN void
+lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops)
+{
+       context->fops = fops;
+}
+
+LWS_VISIBLE LWS_EXTERN lws_filepos_t
+lws_vfs_tell(lws_fop_fd_t fop_fd)
+{
+       return fop_fd->pos;
+}
+
+LWS_VISIBLE LWS_EXTERN lws_filepos_t
+lws_vfs_get_length(lws_fop_fd_t fop_fd)
+{
+       return fop_fd->len;
+}
+
+LWS_VISIBLE LWS_EXTERN uint32_t
+lws_vfs_get_mod_time(lws_fop_fd_t fop_fd)
+{
+       return fop_fd->mod_time;
+}
+
+LWS_VISIBLE lws_fileofs_t
+lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+       lws_fileofs_t ofs;
+
+       ofs = fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset - fop_fd->pos);
+
+       return ofs;
+}
+
+
+LWS_VISIBLE lws_fileofs_t
+lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+       return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, fop_fd->len +
+                                             fop_fd->pos + offset);
+}
+
+
+const struct lws_plat_file_ops *
+lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path,
+                   const char **vpath)
+{
+       const struct lws_plat_file_ops *pf;
+       const char *p = vfs_path;
+       int n;
+
+       *vpath = NULL;
+
+       /* no non-platform fops, just use that */
+
+       if (!fops->next)
+               return fops;
+
+       /*
+        *  scan the vfs path looking for indications we are to be
+        * handled by a specific fops
+        */
+
+       while (p && *p) {
+               if (*p != '/') {
+                       p++;
+                       continue;
+               }
+               /* the first one is always platform fops, so skip */
+               pf = fops->next;
+               while (pf) {
+                       n = 0;
+                       while (n < (int)LWS_ARRAY_SIZE(pf->fi) && pf->fi[n].sig) {
+                               if (p >= vfs_path + pf->fi[n].len)
+                                       if (!strncmp(p - (pf->fi[n].len - 1),
+                                                    pf->fi[n].sig,
+                                                    pf->fi[n].len - 1)) {
+                                               *vpath = p + 1;
+                                               return pf;
+                                       }
+
+                               n++;
+                       }
+                       pf = pf->next;
+               }
+               p++;
+       }
+
+       return fops;
+}
+
+LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT
+lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
+                 lws_fop_flags_t *flags)
+{
+       const char *vpath = "";
+       const struct lws_plat_file_ops *selected;
+
+       selected = lws_vfs_select_fops(fops, vfs_path, &vpath);
+
+       return selected->LWS_FOP_OPEN(fops, vfs_path, vpath, flags);
+}
+
+
+LWS_VISIBLE struct lws_plat_file_ops *
+lws_get_fops(struct lws_context *context)
+{
+       return (struct lws_plat_file_ops *)context->fops;
+}
+
diff --git a/lib/event-libs/README.md b/lib/event-libs/README.md
new file mode 100644 (file)
index 0000000..ccfbb7c
--- /dev/null
@@ -0,0 +1,124 @@
+## Information for new event lib implementers
+
+### Introduction
+
+By default lws has built-in support for POSIX poll() as the event loop.
+
+However either to get access to epoll() or other platform specific better
+poll waits, or to integrate with existing applications already using a
+specific event loop, it can be desirable for lws to use another external
+event library, like libuv, libevent or libev.
+
+### Code placement
+
+The code specific to the event library should live in `./lib/event-libs/**lib name**`
+
+### Allowing control over enabling event libs
+
+All event libs should add a cmake define `LWS_WITH_**lib name**` and make its build
+dependent on it in CMakeLists.txt.  Export the cmakedefine in `./cmake/lws_config.h.in`
+as well so user builds can understand if the event lib is available in the lws build it is
+trying to bind to.
+
+If the event lib is disabled in cmake, nothing in its directory is built or referenced.
+
+### Event loop ops struct
+
+The event lib support is defined by `struct lws_event_loop_ops` in `lib/event-libs/private.h`,
+each event lib support instantiates one of these and fills in the appropriate ops
+callbacks to perform its job.  By convention that lives in
+`./lib/event-libs/**lib name**/**lib_name**.c`.
+
+### Private event lib declarations
+
+Truly private declarations for the event lib can go in the event-libs directory as you like.
+However when the declarations must be accessible to other things in lws build, eg,
+the event lib support adds members to `struct lws` when enabled, they should be in the
+event lib supporr directory in a file `private.h`.
+
+Search for "bring in event libs private declarations" in `./lib/core/private.h
+and add your private event lib support file there following the style used for the other
+event libs, eg,
+
+```
+#if defined(LWS_WITH_LIBUV)
+ #include "event-libs/libuv/private.h"
+#endif
+```
+
+If the event lib support is disabled at cmake, nothing from its private.h should be used anywhere.
+
+### Integrating event lib assets to lws
+
+If your event lib needs special storage in lws objects, that's no problem.  But to keep
+things sane, there are some rules.
+
+ - declare a "container struct" in your private.h for everything, eg, the libuv event
+   lib support need to add its own assets in the perthread struct, it declares in its private.h
+
+```
+struct lws_pt_eventlibs_libuv {
+       uv_loop_t *io_loop;
+       uv_signal_t signals[8];
+       uv_timer_t timeout_watcher;
+       uv_timer_t hrtimer;
+       uv_idle_t idle;
+};
+```
+
+ - add your event lib content in one place in the related lws struct, protected by `#if defined(LWS_WITH_**lib name**)`,
+   eg, again for LWS_WITH_LIBUV
+
+```
+struct lws_context_per_thread {
+
+...
+
+#if defined(LWS_WITH_LIBUV)
+       struct lws_pt_eventlibs_libuv uv;
+#endif
+
+...
+```
+
+### Adding to lws available event libs list
+
+Edit the NULL-terminated array `available_event_libs` at the top of `./lib/context.c` to include
+a pointer to your new event lib support's ops struct, following the style already there.
+
+```
+const struct lws_event_loop_ops *available_event_libs[] = {
+#if defined(LWS_WITH_POLL)
+       &event_loop_ops_poll,
+#endif
+#if defined(LWS_WITH_LIBUV)
+       &event_loop_ops_uv,
+#endif
+...
+```
+
+This is used to provide a list of avilable configured backends.
+
+### Enabling event lib adoption
+
+You need to add a `LWS_SERVER_OPTION...` flag as necessary in `./lib/libwebsockets.h`
+`enum lws_context_options`, and follow the existing code in `lws_create_context()`
+to convert the flag into binding your ops struct to the context.
+
+### Implementation of the event lib bindings
+
+Study eg libuv implementation, using the available ops in the struct lws_event_loop_ops
+as a guide.
+
+### Destruction
+
+Ending the event loop is generally a bit tricky, because if the event loop is internal
+to the lws context, you cannot destroy it while the event loop is running.
+
+Don't add special exports... we tried that, it's a huge mess.  The same user code should be able
+work with any of the event loops including poll.
+
+The solution we found was hide the different processing necessary for the different cases in
+lws_destroy_context().  To help with that there are ops available at two different places in
+the context destroy processing.
+
diff --git a/lib/event-libs/libev/libev.c b/lib/event-libs/libev/libev.c
new file mode 100644 (file)
index 0000000..4b85c19
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static void
+lws_ev_hrtimer_cb(struct ev_loop *loop, struct ev_timer *watcher, int revents)
+{
+       struct lws_context_per_thread *pt =
+                       (struct lws_context_per_thread *)watcher->data;
+       lws_usec_t us;
+
+       lws_pt_lock(pt, __func__);
+       us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+       if (us) {
+               ev_timer_set(&pt->ev.hrtimer, ((float)us) / 1000000.0, 0);
+               ev_timer_start(pt->ev.io_loop, &pt->ev.hrtimer);
+       }
+       lws_pt_unlock(pt);
+}
+
+static void
+lws_ev_idle_cb(struct ev_loop *loop, struct ev_idle *handle, int revents)
+{
+       struct lws_context_per_thread *pt = lws_container_of(handle,
+                                       struct lws_context_per_thread, ev.idle);
+       lws_usec_t us;
+
+       lws_service_do_ripe_rxflow(pt);
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) {
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(pt->context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(pt->context, 1, pt->tid))
+                       /* yes... come back again later */
+               return;
+       }
+
+       /* account for hrtimer */
+
+       lws_pt_lock(pt, __func__);
+       us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+       if (us) {
+               ev_timer_set(&pt->ev.hrtimer, ((float)us) / 1000000.0, 0);
+               ev_timer_start(pt->ev.io_loop, &pt->ev.hrtimer);
+       }
+       lws_pt_unlock(pt);
+
+       /* there is nobody who needs service forcing, shut down idle */
+       ev_idle_stop(loop, handle);
+}
+
+static void
+lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
+{
+       struct lws_context_per_thread *pt;
+       struct lws_io_watcher *lws_io = lws_container_of(watcher,
+                                       struct lws_io_watcher, ev.watcher);
+       struct lws_context *context = lws_io->context;
+       struct lws_pollfd eventfd;
+       struct lws *wsi;
+
+       if (revents & EV_ERROR)
+               return;
+
+       eventfd.fd = watcher->fd;
+       eventfd.events = 0;
+       eventfd.revents = EV_NONE;
+
+       if (revents & EV_READ) {
+               eventfd.events |= LWS_POLLIN;
+               eventfd.revents |= LWS_POLLIN;
+       }
+       if (revents & EV_WRITE) {
+               eventfd.events |= LWS_POLLOUT;
+               eventfd.revents |= LWS_POLLOUT;
+       }
+
+       wsi = wsi_from_fd(context, watcher->fd);
+       pt = &context->pt[(int)wsi->tsi];
+
+       lws_service_fd_tsi(context, &eventfd, (int)wsi->tsi);
+
+       ev_idle_start(pt->ev.io_loop, &pt->ev.idle);
+}
+
+LWS_VISIBLE void
+lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents)
+{
+       struct lws_context *context = watcher->data;
+
+       if (context->eventlib_signal_cb) {
+               context->eventlib_signal_cb((void *)watcher, watcher->signum);
+
+               return;
+       }
+       ev_break(loop, EVBREAK_ALL);
+}
+
+static int
+elops_init_pt_ev(struct lws_context *context, void *_loop, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       struct ev_signal *w_sigint = &context->pt[tsi].w_sigint.ev.watcher;
+       struct lws_vhost *vh = context->vhost_list;
+       const char *backend_name;
+       struct ev_loop *loop = (struct ev_loop *)_loop;
+       int status = 0;
+       int backend;
+
+       lwsl_info("%s: loop %p\n", __func__, _loop);
+
+       if (!loop)
+               loop = ev_loop_new(0);
+       else
+               context->pt[tsi].event_loop_foreign = 1;
+
+       if (!loop) {
+               lwsl_err("%s: creating event base failed\n", __func__);
+
+               return -1;
+       }
+
+       pt->ev.io_loop = loop;
+
+       /*
+        * Initialize the accept w_accept with all the listening sockets
+        * and register a callback for read operations
+        */
+       while (vh) {
+               if (vh->lserv_wsi) {
+                       vh->lserv_wsi->w_read.context = context;
+                       vh->w_accept.context = context;
+
+                       ev_io_init(&vh->w_accept.ev.watcher, lws_accept_cb,
+                                  vh->lserv_wsi->desc.sockfd, EV_READ);
+                       ev_io_start(loop, &vh->w_accept.ev.watcher);
+
+               }
+               vh = vh->vhost_next;
+       }
+
+       /* Register the signal watcher unless it's a foreign loop */
+       if (!context->pt[tsi].event_loop_foreign) {
+               ev_signal_init(w_sigint, lws_ev_sigint_cb, SIGINT);
+               w_sigint->data = context;
+               ev_signal_start(loop, w_sigint);
+       }
+
+       backend = ev_backend(loop);
+       switch (backend) {
+       case EVBACKEND_SELECT:
+               backend_name = "select";
+               break;
+       case EVBACKEND_POLL:
+               backend_name = "poll";
+               break;
+       case EVBACKEND_EPOLL:
+               backend_name = "epoll";
+               break;
+       case EVBACKEND_KQUEUE:
+               backend_name = "kqueue";
+               break;
+       case EVBACKEND_DEVPOLL:
+               backend_name = "/dev/poll";
+               break;
+       case EVBACKEND_PORT:
+               backend_name = "Solaris 10 \"port\"";
+               break;
+       default:
+               backend_name = "Unknown libev backend";
+               break;
+       }
+
+       lwsl_info(" libev backend: %s\n", backend_name);
+       (void)backend_name;
+
+       ev_timer_init(&pt->ev.hrtimer, lws_ev_hrtimer_cb, 0, 0);
+       pt->ev.hrtimer.data = pt;
+
+       ev_idle_init(&pt->ev.idle, lws_ev_idle_cb);
+
+       return status;
+}
+
+static void
+elops_destroy_pt_ev(struct lws_context *context, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       struct lws_vhost *vh = context->vhost_list;
+
+       while (vh) {
+               if (vh->lserv_wsi)
+                       ev_io_stop(pt->ev.io_loop, &vh->w_accept.ev.watcher);
+               vh = vh->vhost_next;
+       }
+
+       /* static assets */
+
+       ev_timer_stop(pt->ev.io_loop, &pt->ev.hrtimer);
+       ev_idle_stop(pt->ev.io_loop, &pt->ev.idle);
+
+       if (!pt->event_loop_foreign) {
+               ev_signal_stop(pt->ev.io_loop, &pt->w_sigint.ev.watcher);
+
+               ev_loop_destroy(pt->ev.io_loop);
+       }
+}
+
+static int
+elops_init_context_ev(struct lws_context *context,
+                     const struct lws_context_creation_info *info)
+{
+       int n;
+
+       context->eventlib_signal_cb = info->signal_cb;
+
+       for (n = 0; n < context->count_threads; n++)
+               context->pt[n].w_sigint.context = context;
+
+       return 0;
+}
+
+static int
+elops_accept_ev(struct lws *wsi)
+{
+       int fd;
+
+       if (wsi->role_ops->file_handle)
+               fd = wsi->desc.filefd;
+       else
+               fd = wsi->desc.sockfd;
+
+       wsi->w_read.context = wsi->context;
+       wsi->w_write.context = wsi->context;
+
+       ev_io_init(&wsi->w_read.ev.watcher, lws_accept_cb, fd, EV_READ);
+       ev_io_init(&wsi->w_write.ev.watcher, lws_accept_cb, fd, EV_WRITE);
+
+       return 0;
+}
+
+static void
+elops_io_ev(struct lws *wsi, int flags)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       if (!pt->ev.io_loop)
+               return;
+
+       assert((flags & (LWS_EV_START | LWS_EV_STOP)) &&
+              (flags & (LWS_EV_READ | LWS_EV_WRITE)));
+
+       if (flags & LWS_EV_START) {
+               if (flags & LWS_EV_WRITE)
+                       ev_io_start(pt->ev.io_loop, &wsi->w_write.ev.watcher);
+               if (flags & LWS_EV_READ)
+                       ev_io_start(pt->ev.io_loop, &wsi->w_read.ev.watcher);
+       } else {
+               if (flags & LWS_EV_WRITE)
+                       ev_io_stop(pt->ev.io_loop, &wsi->w_write.ev.watcher);
+               if (flags & LWS_EV_READ)
+                       ev_io_stop(pt->ev.io_loop, &wsi->w_read.ev.watcher);
+       }
+}
+
+static void
+elops_run_pt_ev(struct lws_context *context, int tsi)
+{
+       if (context->pt[tsi].ev.io_loop)
+               ev_run(context->pt[tsi].ev.io_loop, 0);
+}
+
+static int
+elops_destroy_context2_ev(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt;
+       int n, m;
+
+       lwsl_debug("%s\n", __func__);
+
+       for (n = 0; n < context->count_threads; n++) {
+               int budget = 1000;
+
+               pt = &context->pt[n];
+
+               /* only for internal loops... */
+
+               if (pt->event_loop_foreign || !pt->ev.io_loop)
+                       continue;
+
+               if (!context->finalize_destroy_after_internal_loops_stopped) {
+                       ev_break(pt->ev.io_loop, EVBREAK_ONE);
+                       continue;
+               }
+               while (budget-- &&
+                      (m = ev_run(pt->ev.io_loop, 0)))
+                       ;
+
+               ev_loop_destroy(pt->ev.io_loop);
+       }
+
+       return 0;
+}
+
+static int
+elops_init_vhost_listen_wsi_ev(struct lws *wsi)
+{
+       int fd;
+
+       if (!wsi) {
+               assert(0);
+               return 0;
+       }
+
+       wsi->w_read.context = wsi->context;
+       wsi->w_write.context = wsi->context;
+
+       if (wsi->role_ops->file_handle)
+               fd = wsi->desc.filefd;
+       else
+               fd = wsi->desc.sockfd;
+
+       ev_io_init(&wsi->w_read.ev.watcher, lws_accept_cb, fd, EV_READ);
+       ev_io_init(&wsi->w_write.ev.watcher, lws_accept_cb, fd, EV_WRITE);
+
+       elops_io_ev(wsi, LWS_EV_START | LWS_EV_READ);
+
+       return 0;
+}
+
+static void
+elops_destroy_wsi_ev(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       ev_io_stop(pt->ev.io_loop, &wsi->w_read.ev.watcher);
+       ev_io_stop(pt->ev.io_loop, &wsi->w_write.ev.watcher);
+}
+
+struct lws_event_loop_ops event_loop_ops_ev = {
+       /* name */                      "libev",
+       /* init_context */              elops_init_context_ev,
+       /* destroy_context1 */          NULL,
+       /* destroy_context2 */          elops_destroy_context2_ev,
+       /* init_vhost_listen_wsi */     elops_init_vhost_listen_wsi_ev,
+       /* init_pt */                   elops_init_pt_ev,
+       /* wsi_logical_close */         NULL,
+       /* check_client_connect_ok */   NULL,
+       /* close_handle_manually */     NULL,
+       /* accept */                    elops_accept_ev,
+       /* io */                        elops_io_ev,
+       /* run_pt */                    elops_run_pt_ev,
+       /* destroy_pt */                elops_destroy_pt_ev,
+       /* destroy wsi */               elops_destroy_wsi_ev,
+
+       /* periodic_events_available */ 0,
+};
diff --git a/lib/event-libs/libev/private.h b/lib/event-libs/libev/private.h
new file mode 100644 (file)
index 0000000..9359f34
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_WITH_LIBEV
+ */
+
+#include <ev.h>
+
+#define LWS_EV_REFCOUNT_STATIC_HANDLE_NEW(_x, _ctx) \
+               { (_x)->data = _ctx; \
+               _ctx->count_event_loop_static_asset_handles++; }
+#define LWS_EV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(_x) \
+                       ((struct lws_context *)(_x)->data)))
+#define LWS_EV_REFCOUNT_STATIC_HANDLE_DESTROYED(_x) \
+               (--(LWS_UV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(_x)-> \
+                               count_event_loop_static_asset_handles))
+
+struct lws_pt_eventlibs_libev {
+       struct ev_loop *io_loop;
+       struct ev_timer hrtimer;
+       struct ev_idle idle;
+};
+
+struct lws_io_watcher_libev {
+       ev_io watcher;
+};
+
+struct lws_signal_watcher_libev {
+       ev_signal watcher;
+};
+
+struct lws_context_eventlibs_libev {
+       int placeholder;
+};
+
+extern struct lws_event_loop_ops event_loop_ops_ev;
diff --git a/lib/event-libs/libevent/libevent.c b/lib/event-libs/libevent/libevent.c
new file mode 100644 (file)
index 0000000..cc89c75
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static void
+lws_event_hrtimer_cb(int fd, short event, void *p)
+{
+       struct lws_context_per_thread *pt = (struct lws_context_per_thread *)p;
+       struct timeval tv;
+       lws_usec_t us;
+
+       lws_pt_lock(pt, __func__);
+       us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+       if (us) {
+               tv.tv_sec = us / LWS_US_PER_SEC;
+               tv.tv_usec = us - (tv.tv_sec * LWS_US_PER_SEC);
+               evtimer_add(pt->event.hrtimer, &tv);
+       }
+       lws_pt_unlock(pt);
+}
+
+static void
+lws_event_idle_timer_cb(int fd, short event, void *p)
+{
+       struct lws_context_per_thread *pt = (struct lws_context_per_thread *)p;
+       struct timeval tv;
+       lws_usec_t us;
+
+       lws_service_do_ripe_rxflow(pt);
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) {
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(pt->context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) {
+                       /* yes... come back again later */
+
+                       tv.tv_sec = 0;
+                       tv.tv_usec = 1000;
+                       evtimer_add(pt->event.idle_timer, &tv);
+
+                       return;
+               }
+       }
+
+       lwsl_debug("%s: wait\n", __func__);
+
+       /* account for hrtimer */
+
+       lws_pt_lock(pt, __func__);
+       us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+       if (us) {
+               tv.tv_sec = us / LWS_US_PER_SEC;
+               tv.tv_usec = us - (tv.tv_sec * LWS_US_PER_SEC);
+               evtimer_add(pt->event.hrtimer, &tv);
+       }
+       lws_pt_unlock(pt);
+}
+
+static void
+lws_event_cb(evutil_socket_t sock_fd, short revents, void *ctx)
+{
+       struct lws_io_watcher *lws_io = (struct lws_io_watcher *)ctx;
+       struct lws_context *context = lws_io->context;
+       struct lws_context_per_thread *pt;
+       struct lws_pollfd eventfd;
+       struct timeval tv;
+       struct lws *wsi;
+
+       if (revents & EV_TIMEOUT)
+               return;
+
+       /* !!! EV_CLOSED doesn't exist in libevent2 */
+#if LIBEVENT_VERSION_NUMBER < 0x02000000
+       if (revents & EV_CLOSED) {
+               event_del(lws_io->event.watcher);
+               event_free(lws_io->event.watcher);
+               return;
+       }
+#endif
+
+       eventfd.fd = sock_fd;
+       eventfd.events = 0;
+       eventfd.revents = 0;
+       if (revents & EV_READ) {
+               eventfd.events |= LWS_POLLIN;
+               eventfd.revents |= LWS_POLLIN;
+       }
+       if (revents & EV_WRITE) {
+               eventfd.events |= LWS_POLLOUT;
+               eventfd.revents |= LWS_POLLOUT;
+       }
+
+       wsi = wsi_from_fd(context, sock_fd);
+       if (!wsi) {
+               return;
+       }
+       pt = &context->pt[(int)wsi->tsi];
+
+       lws_service_fd_tsi(context, &eventfd, wsi->tsi);
+
+       /* set the idle timer for 1ms ahead */
+
+       tv.tv_sec = 0;
+       tv.tv_usec = 1000;
+       evtimer_add(pt->event.idle_timer, &tv);
+}
+
+LWS_VISIBLE void
+lws_event_sigint_cb(evutil_socket_t sock_fd, short revents, void *ctx)
+{
+       struct lws_context_per_thread *pt = ctx;
+       struct event *signal = (struct event *)ctx;
+
+       if (pt->context->eventlib_signal_cb) {
+               pt->context->eventlib_signal_cb((void *)(lws_intptr_t)sock_fd,
+                                               event_get_signal(signal));
+
+               return;
+       }
+       if (!pt->event_loop_foreign)
+               event_base_loopbreak(pt->event.io_loop);
+}
+
+
+static int
+elops_init_pt_event(struct lws_context *context, void *_loop, int tsi)
+{
+       struct lws_vhost *vh = context->vhost_list;
+       struct event_base *loop = (struct event_base *)_loop;
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+
+       lwsl_info("%s: loop %p\n", __func__, _loop);
+
+       if (!loop)
+               loop = event_base_new();
+       else
+               context->pt[tsi].event_loop_foreign = 1;
+
+       if (!loop) {
+               lwsl_err("%s: creating event base failed\n", __func__);
+
+               return -1;
+       }
+
+       pt->event.io_loop = loop;
+
+       /*
+       * Initialize all events with the listening sockets
+       * and register a callback for read operations
+       */
+
+       while (vh) {
+               if (vh->lserv_wsi) {
+                       vh->lserv_wsi->w_read.context = context;
+                       vh->lserv_wsi->w_read.event.watcher = event_new(
+                                       loop, vh->lserv_wsi->desc.sockfd,
+                                       (EV_READ | EV_PERSIST), lws_event_cb,
+                                       &vh->lserv_wsi->w_read);
+                       event_add(vh->lserv_wsi->w_read.event.watcher, NULL);
+               }
+               vh = vh->vhost_next;
+       }
+
+       /* static event loop objects */
+
+       pt->event.hrtimer = event_new(loop, -1, EV_PERSIST,
+                                     lws_event_hrtimer_cb, pt);
+
+       pt->event.idle_timer = event_new(loop, -1, 0,
+                                        lws_event_idle_timer_cb, pt);
+
+       /* Register the signal watcher unless it's a foreign loop */
+
+       if (pt->event_loop_foreign)
+               return 0;
+
+       pt->w_sigint.event.watcher = evsignal_new(loop, SIGINT,
+                                                 lws_event_sigint_cb, pt);
+       event_add(pt->w_sigint.event.watcher, NULL);
+
+       return 0;
+}
+
+static int
+elops_init_context_event(struct lws_context *context,
+                        const struct lws_context_creation_info *info)
+{
+       int n;
+
+       context->eventlib_signal_cb = info->signal_cb;
+
+       for (n = 0; n < context->count_threads; n++)
+               context->pt[n].w_sigint.context = context;
+
+       return 0;
+}
+
+static int
+elops_accept_event(struct lws *wsi)
+{
+       struct lws_context *context = lws_get_context(wsi);
+       struct lws_context_per_thread *pt;
+       int fd;
+
+       wsi->w_read.context = context;
+       wsi->w_write.context = context;
+
+       // Initialize the event
+       pt = &context->pt[(int)wsi->tsi];
+
+       if (wsi->role_ops->file_handle)
+               fd = wsi->desc.filefd;
+       else
+               fd = wsi->desc.sockfd;
+
+       wsi->w_read.event.watcher = event_new(pt->event.io_loop, fd,
+                       (EV_READ | EV_PERSIST), lws_event_cb, &wsi->w_read);
+       wsi->w_write.event.watcher = event_new(pt->event.io_loop, fd,
+                       (EV_WRITE | EV_PERSIST), lws_event_cb, &wsi->w_write);
+
+       return 0;
+}
+
+static void
+elops_io_event(struct lws *wsi, int flags)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       if (!pt->event.io_loop || wsi->context->being_destroyed)
+               return;
+
+       assert((flags & (LWS_EV_START | LWS_EV_STOP)) &&
+              (flags & (LWS_EV_READ | LWS_EV_WRITE)));
+
+       if (flags & LWS_EV_START) {
+               if (flags & LWS_EV_WRITE)
+                       event_add(wsi->w_write.event.watcher, NULL);
+
+               if (flags & LWS_EV_READ)
+                       event_add(wsi->w_read.event.watcher, NULL);
+       } else {
+               if (flags & LWS_EV_WRITE)
+                       event_del(wsi->w_write.event.watcher);
+
+               if (flags & LWS_EV_READ)
+                       event_del(wsi->w_read.event.watcher);
+       }
+}
+
+static void
+elops_run_pt_event(struct lws_context *context, int tsi)
+{
+       /* Run / Dispatch the event_base loop */
+       if (context->pt[tsi].event.io_loop)
+               event_base_dispatch(context->pt[tsi].event.io_loop);
+}
+
+static void
+elops_destroy_pt_event(struct lws_context *context, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       struct lws_vhost *vh = context->vhost_list;
+
+       lwsl_info("%s\n", __func__);
+
+       if (!pt->event.io_loop)
+               return;
+
+       /*
+        * Free all events with the listening sockets
+        */
+       while (vh) {
+               if (vh->lserv_wsi) {
+                       event_free(vh->lserv_wsi->w_read.event.watcher);
+                       vh->lserv_wsi->w_read.event.watcher = NULL;
+                       event_free(vh->lserv_wsi->w_write.event.watcher);
+                       vh->lserv_wsi->w_write.event.watcher = NULL;
+               }
+               vh = vh->vhost_next;
+       }
+
+       event_free(pt->event.hrtimer);
+       event_free(pt->event.idle_timer);
+
+       if (!pt->event_loop_foreign) {
+               event_del(pt->w_sigint.event.watcher);
+               event_free(pt->w_sigint.event.watcher);
+
+               event_base_free(pt->event.io_loop);
+       }
+}
+
+static void
+elops_destroy_wsi_event(struct lws *wsi)
+{
+       if (!wsi)
+               return;
+
+       if (wsi->w_read.event.watcher)
+               event_free(wsi->w_read.event.watcher);
+
+       if (wsi->w_write.event.watcher)
+               event_free(wsi->w_write.event.watcher);
+}
+
+static int
+elops_init_vhost_listen_wsi_event(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt;
+       int fd;
+
+       if (!wsi) {
+               assert(0);
+               return 0;
+       }
+
+       wsi->w_read.context = wsi->context;
+       wsi->w_write.context = wsi->context;
+
+       pt = &wsi->context->pt[(int)wsi->tsi];
+
+       if (wsi->role_ops->file_handle)
+               fd = wsi->desc.filefd;
+       else
+               fd = wsi->desc.sockfd;
+
+       wsi->w_read.event.watcher = event_new(pt->event.io_loop, fd,
+                                             (EV_READ | EV_PERSIST),
+                                             lws_event_cb, &wsi->w_read);
+       wsi->w_write.event.watcher = event_new(pt->event.io_loop, fd,
+                                              (EV_WRITE | EV_PERSIST),
+                                              lws_event_cb, &wsi->w_write);
+
+       elops_io_event(wsi, LWS_EV_START | LWS_EV_READ);
+
+       return 0;
+}
+
+static int
+elops_destroy_context2_event(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt;
+       int n, m;
+
+       lwsl_debug("%s: in\n", __func__);
+
+       for (n = 0; n < context->count_threads; n++) {
+               int budget = 1000;
+
+               pt = &context->pt[n];
+
+               /* only for internal loops... */
+
+               if (pt->event_loop_foreign || !pt->event.io_loop)
+                       continue;
+
+               if (!context->finalize_destroy_after_internal_loops_stopped) {
+                       event_base_loopexit(pt->event.io_loop, NULL);
+                       continue;
+               }
+               while (budget-- &&
+                      (m = event_base_loop(pt->event.io_loop, EVLOOP_NONBLOCK)))
+                       ;
+#if 0
+               if (m) {
+                       lwsl_err("%s: tsi %d: NOT everything closed\n",
+                                __func__, n);
+                       event_base_dump_events(pt->event.io_loop, stderr);
+               } else
+                       lwsl_debug("%s: %d: everything closed OK\n", __func__, n);
+#endif
+               event_base_free(pt->event.io_loop);
+
+       }
+
+       lwsl_debug("%s: out\n", __func__);
+
+       return 0;
+}
+
+struct lws_event_loop_ops event_loop_ops_event = {
+       /* name */                      "libevent",
+       /* init_context */              elops_init_context_event,
+       /* destroy_context1 */          NULL,
+       /* destroy_context2 */          elops_destroy_context2_event,
+       /* init_vhost_listen_wsi */     elops_init_vhost_listen_wsi_event,
+       /* init_pt */                   elops_init_pt_event,
+       /* wsi_logical_close */         NULL,
+       /* check_client_connect_ok */   NULL,
+       /* close_handle_manually */     NULL,
+       /* accept */                    elops_accept_event,
+       /* io */                        elops_io_event,
+       /* run_pt */                    elops_run_pt_event,
+       /* destroy_pt */                elops_destroy_pt_event,
+       /* destroy wsi */               elops_destroy_wsi_event,
+
+       /* periodic_events_available */ 0,
+};
diff --git a/lib/event-libs/libevent/private.h b/lib/event-libs/libevent/private.h
new file mode 100644 (file)
index 0000000..04fbbdf
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_WITH_LIBEVENT
+ */
+
+#include <event2/event.h>
+
+struct lws_pt_eventlibs_libevent {
+       struct event_base *io_loop;
+       struct event *hrtimer;
+       struct event *idle_timer;
+};
+
+struct lws_io_watcher_libevent {
+       struct event *watcher;
+};
+
+struct lws_signal_watcher_libevent {
+       struct event *watcher;
+};
+
+struct lws_context_eventlibs_libevent {
+       int placeholder;
+};
+
+extern struct lws_event_loop_ops event_loop_ops_event;
diff --git a/lib/event-libs/libuv/libuv.c b/lib/event-libs/libuv/libuv.c
new file mode 100644 (file)
index 0000000..58c2056
--- /dev/null
@@ -0,0 +1,974 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static void
+lws_uv_sultimer_cb(uv_timer_t *timer
+#if UV_VERSION_MAJOR == 0
+               , int status
+#endif
+)
+{
+       struct lws_context_per_thread *pt = lws_container_of(timer,
+                               struct lws_context_per_thread, uv.sultimer);
+       lws_usec_t us;
+
+       lws_pt_lock(pt, __func__);
+       us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+       if (us)
+               uv_timer_start(&pt->uv.sultimer, lws_uv_sultimer_cb,
+                              LWS_US_TO_MS(us), 0);
+       lws_pt_unlock(pt);
+}
+
+static void
+lws_uv_idle(uv_idle_t *handle
+#if UV_VERSION_MAJOR == 0
+               , int status
+#endif
+)
+{
+       struct lws_context_per_thread *pt = lws_container_of(handle,
+                                       struct lws_context_per_thread, uv.idle);
+       lws_usec_t us;
+
+       lws_service_do_ripe_rxflow(pt);
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) {
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(pt->context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(pt->context, 1, pt->tid))
+                       /* yes... come back again later */
+               return;
+       }
+
+       /* account for sultimer */
+
+       lws_pt_lock(pt, __func__);
+       us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+       if (us)
+               uv_timer_start(&pt->uv.sultimer, lws_uv_sultimer_cb,
+                              LWS_US_TO_MS(us), 0);
+       lws_pt_unlock(pt);
+
+       /* there is nobody who needs service forcing, shut down idle */
+       uv_idle_stop(handle);
+}
+
+static void
+lws_io_cb(uv_poll_t *watcher, int status, int revents)
+{
+       struct lws *wsi = (struct lws *)((uv_handle_t *)watcher)->data;
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws_pollfd eventfd;
+
+#if defined(WIN32) || defined(_WIN32)
+       eventfd.fd = watcher->socket;
+#else
+       eventfd.fd = watcher->io_watcher.fd;
+#endif
+       eventfd.events = 0;
+       eventfd.revents = 0;
+
+       if (status < 0) {
+               /*
+                * At this point status will be an UV error, like UV_EBADF,
+                * we treat all errors as LWS_POLLHUP
+                *
+                * You might want to return; instead of servicing the fd in
+                * some cases */
+               if (status == UV_EAGAIN)
+                       return;
+
+               eventfd.events |= LWS_POLLHUP;
+               eventfd.revents |= LWS_POLLHUP;
+       } else {
+               if (revents & UV_READABLE) {
+                       eventfd.events |= LWS_POLLIN;
+                       eventfd.revents |= LWS_POLLIN;
+               }
+               if (revents & UV_WRITABLE) {
+                       eventfd.events |= LWS_POLLOUT;
+                       eventfd.revents |= LWS_POLLOUT;
+               }
+       }
+       lws_service_fd_tsi(context, &eventfd, wsi->tsi);
+
+       uv_idle_start(&pt->uv.idle, lws_uv_idle);
+}
+
+/*
+ * This does not actually stop the event loop.  The reason is we have to pass
+ * libuv handle closures through its event loop.  So this tries to close all
+ * wsi, and set a flag; when all the wsi closures are finalized then we
+ * actually stop the libuv event loops.
+ */
+static void
+lws_libuv_stop(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt;
+       int n, m;
+
+       lwsl_err("%s\n", __func__);
+
+       if (context->requested_kill) {
+               lwsl_err("%s: ignoring\n", __func__);
+               return;
+       }
+
+       context->requested_kill = 1;
+
+       m = context->count_threads;
+       context->being_destroyed = 1;
+
+       /*
+        * Phase 1: start the close of every dynamic uv handle
+        */
+
+       while (m--) {
+               pt = &context->pt[m];
+
+               if (pt->pipe_wsi) {
+                       uv_poll_stop(pt->pipe_wsi->w_read.uv.pwatcher);
+                       lws_destroy_event_pipe(pt->pipe_wsi);
+                       pt->pipe_wsi = NULL;
+               }
+
+               for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
+                       struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
+
+                       if (!wsi)
+                               continue;
+                       lws_close_free_wsi(wsi,
+                               LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY,
+                               __func__ /* no protocol close */);
+                       n--;
+               }
+       }
+
+       lwsl_info("%s: started closing all wsi\n", __func__);
+
+       /* we cannot have completed... there are at least the cancel pipes */
+}
+
+static void
+lws_uv_signal_handler(uv_signal_t *watcher, int signum)
+{
+       struct lws_context *context = watcher->data;
+
+       if (context->eventlib_signal_cb) {
+               context->eventlib_signal_cb((void *)watcher, signum);
+
+               return;
+       }
+
+       lwsl_err("internal signal handler caught signal %d\n", signum);
+       lws_libuv_stop(watcher->data);
+}
+
+static const int sigs[] = { SIGINT, SIGTERM, SIGSEGV, SIGFPE, SIGHUP };
+
+/*
+ * Closing Phase 2: Close callback for a static UV asset
+ */
+
+static void
+lws_uv_close_cb_sa(uv_handle_t *handle)
+{
+       struct lws_context *context =
+                       LWS_UV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(handle);
+       int n;
+
+       lwsl_info("%s: sa left %d: dyn left: %d\n", __func__,
+                   context->count_event_loop_static_asset_handles,
+                   context->count_wsi_allocated);
+
+       /* any static assets left? */
+
+       if (LWS_UV_REFCOUNT_STATIC_HANDLE_DESTROYED(handle) ||
+           context->count_wsi_allocated)
+               return;
+
+       /*
+        * That's it... all wsi were down, and now every
+        * static asset lws had a UV handle for is down.
+        *
+        * Stop the loop so we can get out of here.
+        */
+
+       for (n = 0; n < context->count_threads; n++) {
+               struct lws_context_per_thread *pt = &context->pt[n];
+
+               if (pt->uv.io_loop && !pt->event_loop_foreign)
+                       uv_stop(pt->uv.io_loop);
+       }
+
+       if (!context->pt[0].event_loop_foreign) {
+               lwsl_info("%s: calling lws_context_destroy2\n", __func__);
+               lws_context_destroy2(context);
+       }
+
+       lwsl_info("%s: all done\n", __func__);
+}
+
+/*
+ * These must be called by protocols that want to use libuv objects directly...
+ *
+ * .... when the libuv object is created...
+ */
+
+LWS_VISIBLE void
+lws_libuv_static_refcount_add(uv_handle_t *h, struct lws_context *context)
+{
+       LWS_UV_REFCOUNT_STATIC_HANDLE_NEW(h, context);
+}
+
+/*
+ * ... and in the close callback when the object is closed.
+ */
+
+LWS_VISIBLE void
+lws_libuv_static_refcount_del(uv_handle_t *h)
+{
+       lws_uv_close_cb_sa(h);
+}
+
+
+static void lws_uv_close_cb(uv_handle_t *handle)
+{
+}
+
+static void lws_uv_walk_cb(uv_handle_t *handle, void *arg)
+{
+       if (!uv_is_closing(handle))
+               uv_close(handle, lws_uv_close_cb);
+}
+
+LWS_VISIBLE void
+lws_close_all_handles_in_loop(uv_loop_t *loop)
+{
+       uv_walk(loop, lws_uv_walk_cb, NULL);
+}
+
+
+LWS_VISIBLE void
+lws_libuv_stop_without_kill(const struct lws_context *context, int tsi)
+{
+       if (context->pt[tsi].uv.io_loop)
+               uv_stop(context->pt[tsi].uv.io_loop);
+}
+
+
+
+LWS_VISIBLE uv_loop_t *
+lws_uv_getloop(struct lws_context *context, int tsi)
+{
+       if (context->pt[tsi].uv.io_loop)
+               return context->pt[tsi].uv.io_loop;
+
+       return NULL;
+}
+
+int
+lws_libuv_check_watcher_active(struct lws *wsi)
+{
+       uv_handle_t *h = (uv_handle_t *)wsi->w_read.uv.pwatcher;
+
+       if (!h)
+               return 0;
+
+       return uv_is_active(h);
+}
+
+
+#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
+
+int
+lws_uv_plugins_init(struct lws_context *context, const char * const *d)
+{
+       struct lws_plugin_capability lcaps;
+       struct lws_plugin *plugin;
+       lws_plugin_init_func initfunc;
+       int m, ret = 0;
+       void *v;
+       uv_dirent_t dent;
+       uv_fs_t req;
+       char path[256];
+       uv_lib_t lib;
+       int pofs = 0;
+
+#if  defined(__MINGW32__) || !defined(WIN32)
+       pofs = 3;
+#endif
+
+       lib.errmsg = NULL;
+       lib.handle = NULL;
+
+       uv_loop_init(&context->uv.loop);
+
+       lwsl_notice("  Plugins:\n");
+
+       while (d && *d) {
+
+               lwsl_notice("  Scanning %s\n", *d);
+               m =uv_fs_scandir(&context->uv.loop, &req, *d, 0, NULL);
+               if (m < 1) {
+                       lwsl_err("Scandir on %s failed\n", *d);
+                       return 1;
+               }
+
+               while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
+                       if (strlen(dent.name) < 7)
+                               continue;
+
+                       lwsl_notice("   %s\n", dent.name);
+
+                       lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d,
+                                    dent.name);
+                       if (uv_dlopen(path, &lib)) {
+                               uv_dlerror(&lib);
+                               lwsl_err("Error loading DSO: %s\n", lib.errmsg);
+                               uv_dlclose(&lib);
+                               goto bail;
+                       }
+
+                       /* we could open it, can we get his init function? */
+
+#if !defined(WIN32) && !defined(__MINGW32__)
+                       m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
+                                    dent.name + pofs /* snip lib... */);
+                       path[m - 3] = '\0'; /* snip the .so */
+#else
+                       m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
+                                    dent.name + pofs);
+                       path[m - 4] = '\0'; /* snip the .dll */
+#endif
+                       if (uv_dlsym(&lib, path, &v)) {
+                               uv_dlerror(&lib);
+                               lwsl_err("Failed to get %s on %s: %s", path,
+                                               dent.name, lib.errmsg);
+                               uv_dlclose(&lib);
+                               goto bail;
+                       }
+                       initfunc = (lws_plugin_init_func)v;
+                       lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
+                       m = initfunc(context, &lcaps);
+                       if (m) {
+                               lwsl_err("Init %s failed %d\n", dent.name, m);
+                               goto skip;
+                       }
+
+                       plugin = lws_malloc(sizeof(*plugin), "plugin");
+                       if (!plugin) {
+                               uv_dlclose(&lib);
+                               lwsl_err("OOM\n");
+                               goto bail;
+                       }
+                       plugin->list = context->plugin_list;
+                       context->plugin_list = plugin;
+                       lws_strncpy(plugin->name, dent.name, sizeof(plugin->name));
+                       plugin->lib = lib;
+                       plugin->caps = lcaps;
+                       context->plugin_protocol_count += lcaps.count_protocols;
+                       context->plugin_extension_count += lcaps.count_extensions;
+
+                       continue;
+
+skip:
+                       uv_dlclose(&lib);
+               }
+bail:
+               uv_fs_req_cleanup(&req);
+               d++;
+       }
+
+       return ret;
+}
+
+int
+lws_uv_plugins_destroy(struct lws_context *context)
+{
+       struct lws_plugin *plugin = context->plugin_list, *p;
+       lws_plugin_destroy_func func;
+       char path[256];
+       int pofs = 0;
+       void *v;
+       int m;
+
+#if  defined(__MINGW32__) || !defined(WIN32)
+       pofs = 3;
+#endif
+
+       if (!plugin)
+               return 0;
+
+       while (plugin) {
+               p = plugin;
+
+#if !defined(WIN32) && !defined(__MINGW32__)
+               m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s",
+                                plugin->name + pofs);
+               path[m - 3] = '\0';
+#else
+               m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s",
+                                plugin->name + pofs);
+               path[m - 4] = '\0';
+#endif
+
+               if (uv_dlsym(&plugin->lib, path, &v)) {
+                       uv_dlerror(&plugin->lib);
+                       lwsl_err("Failed to get %s on %s: %s", path,
+                                       plugin->name, plugin->lib.errmsg);
+               } else {
+                       func = (lws_plugin_destroy_func)v;
+                       m = func(context);
+                       if (m)
+                               lwsl_err("Destroying %s failed %d\n",
+                                               plugin->name, m);
+               }
+
+               uv_dlclose(&p->lib);
+               plugin = p->list;
+               p->list = NULL;
+               free(p);
+       }
+
+       context->plugin_list = NULL;
+
+       while (uv_loop_close(&context->uv.loop))
+               ;
+
+       return 0;
+}
+
+#endif
+
+static int
+elops_init_context_uv(struct lws_context *context,
+                     const struct lws_context_creation_info *info)
+{
+       int n;
+
+       context->eventlib_signal_cb = info->signal_cb;
+
+       for (n = 0; n < context->count_threads; n++)
+               context->pt[n].w_sigint.context = context;
+
+       return 0;
+}
+
+static int
+elops_destroy_context1_uv(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt;
+       int n, m = 0;
+
+       for (n = 0; n < context->count_threads; n++) {
+               int budget = 10000;
+               pt = &context->pt[n];
+
+               /* only for internal loops... */
+
+               if (!pt->event_loop_foreign) {
+
+                       while (budget-- && (m = uv_run(pt->uv.io_loop,
+                                                 UV_RUN_NOWAIT)))
+                                       ;
+                       if (m)
+                               lwsl_err("%s: tsi %d: not all closed\n",
+                                        __func__, n);
+
+               }
+       }
+
+       /* call destroy2 if internal loop */
+       return !context->pt[0].event_loop_foreign;
+}
+
+static int
+elops_destroy_context2_uv(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt;
+       int n, internal = 0;
+
+       for (n = 0; n < context->count_threads; n++) {
+               pt = &context->pt[n];
+
+               /* only for internal loops... */
+
+               if (!pt->event_loop_foreign && pt->uv.io_loop) {
+                       internal = 1;
+                       if (!context->finalize_destroy_after_internal_loops_stopped)
+                               uv_stop(pt->uv.io_loop);
+                       else {
+#if UV_VERSION_MAJOR > 0
+                               uv_loop_close(pt->uv.io_loop);
+#endif
+                               lws_free_set_NULL(pt->uv.io_loop);
+                       }
+               }
+       }
+
+       return internal;
+}
+
+static int
+elops_wsi_logical_close_uv(struct lws *wsi)
+{
+       if (!lws_socket_is_valid(wsi->desc.sockfd))
+               return 0;
+
+       if (wsi->listener || wsi->event_pipe) {
+               lwsl_debug("%s: %p: %d %d stop listener / pipe poll\n",
+                          __func__, wsi, wsi->listener, wsi->event_pipe);
+               if (wsi->w_read.uv.pwatcher)
+                       uv_poll_stop(wsi->w_read.uv.pwatcher);
+       }
+       lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi);
+       /*
+        * libuv has to do his own close handle processing asynchronously
+        */
+       lws_libuv_closehandle(wsi);
+
+       return 1; /* do not complete the wsi close, uv close cb will do it */
+}
+
+static int
+elops_check_client_connect_ok_uv(struct lws *wsi)
+{
+       if (lws_libuv_check_watcher_active(wsi)) {
+               lwsl_warn("Waiting for libuv watcher to close\n");
+               return 1;
+       }
+
+       return 0;
+}
+
+static void
+lws_libuv_closewsi_m(uv_handle_t* handle)
+{
+       lws_sockfd_type sockfd = (lws_sockfd_type)(lws_intptr_t)handle->data;
+       lwsl_debug("%s: sockfd %d\n", __func__, sockfd);
+       compatible_close(sockfd);
+       lws_free(handle);
+}
+
+static void
+elops_close_handle_manually_uv(struct lws *wsi)
+{
+       uv_handle_t *h = (uv_handle_t *)wsi->w_read.uv.pwatcher;
+
+       lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi);
+
+       /*
+        * the "manual" variant only closes the handle itself and the
+        * related fd.  handle->data is the fd.
+        */
+       h->data = (void *)(lws_intptr_t)wsi->desc.sockfd;
+
+       /*
+        * We take responsibility to close / destroy these now.
+        * Remove any trace from the wsi.
+        */
+
+       wsi->desc.sockfd = LWS_SOCK_INVALID;
+       wsi->w_read.uv.pwatcher = NULL;
+       wsi->told_event_loop_closed = 1;
+
+       uv_close(h, lws_libuv_closewsi_m);
+}
+
+static int
+elops_accept_uv(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       wsi->w_read.context = wsi->context;
+
+       wsi->w_read.uv.pwatcher =
+               lws_malloc(sizeof(*wsi->w_read.uv.pwatcher), "uvh");
+       if (!wsi->w_read.uv.pwatcher)
+               return -1;
+
+       if (wsi->role_ops->file_handle)
+               uv_poll_init(pt->uv.io_loop, wsi->w_read.uv.pwatcher,
+                            (int)(long long)wsi->desc.filefd);
+       else
+               uv_poll_init_socket(pt->uv.io_loop,
+                                   wsi->w_read.uv.pwatcher,
+                                   wsi->desc.sockfd);
+
+       ((uv_handle_t *)wsi->w_read.uv.pwatcher)->data = (void *)wsi;
+
+       return 0;
+}
+
+static void
+elops_io_uv(struct lws *wsi, int flags)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws_io_watcher *w = &wsi->w_read;
+       int current_events = w->actual_events & (UV_READABLE | UV_WRITABLE);
+
+       lwsl_debug("%s: %p: %d\n", __func__, wsi, flags);
+
+       /* w->context is set after the loop is initialized */
+
+       if (!pt->uv.io_loop || !w->context) {
+               lwsl_info("%s: no io loop yet\n", __func__);
+               return;
+       }
+
+       if (!((flags & (LWS_EV_START | LWS_EV_STOP)) &&
+             (flags & (LWS_EV_READ | LWS_EV_WRITE)))) {
+               lwsl_err("%s: assert: flags %d", __func__, flags);
+               assert(0);
+       }
+
+       if (!w->uv.pwatcher || wsi->told_event_loop_closed) {
+               lwsl_err("%s: no watcher\n", __func__);
+
+               return;
+       }
+
+       if (flags & LWS_EV_START) {
+               if (flags & LWS_EV_WRITE)
+                       current_events |= UV_WRITABLE;
+
+               if (flags & LWS_EV_READ)
+                       current_events |= UV_READABLE;
+
+               uv_poll_start(w->uv.pwatcher, current_events, lws_io_cb);
+       } else {
+               if (flags & LWS_EV_WRITE)
+                       current_events &= ~UV_WRITABLE;
+
+               if (flags & LWS_EV_READ)
+                       current_events &= ~UV_READABLE;
+
+               if (!(current_events & (UV_READABLE | UV_WRITABLE)))
+                       uv_poll_stop(w->uv.pwatcher);
+               else
+                       uv_poll_start(w->uv.pwatcher, current_events,
+                                     lws_io_cb);
+       }
+
+       w->actual_events = current_events;
+}
+
+static int
+elops_init_vhost_listen_wsi_uv(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt;
+       int n;
+
+       if (!wsi)
+               return 0;
+       if (wsi->w_read.context)
+               return 0;
+
+       pt = &wsi->context->pt[(int)wsi->tsi];
+       if (!pt->uv.io_loop)
+               return 0;
+
+       wsi->w_read.context = wsi->context;
+
+       wsi->w_read.uv.pwatcher =
+               lws_malloc(sizeof(*wsi->w_read.uv.pwatcher), "uvh");
+       if (!wsi->w_read.uv.pwatcher)
+               return -1;
+
+       n = uv_poll_init_socket(pt->uv.io_loop, wsi->w_read.uv.pwatcher,
+                                  wsi->desc.sockfd);
+       if (n) {
+               lwsl_err("uv_poll_init failed %d, sockfd=%p\n", n,
+                               (void *)(lws_intptr_t)wsi->desc.sockfd);
+
+               return -1;
+       }
+
+       ((uv_handle_t *)wsi->w_read.uv.pwatcher)->data = (void *)wsi;
+
+       elops_io_uv(wsi, LWS_EV_START | LWS_EV_READ);
+
+       return 0;
+}
+
+static void
+elops_run_pt_uv(struct lws_context *context, int tsi)
+{
+       if (context->pt[tsi].uv.io_loop)
+               uv_run(context->pt[tsi].uv.io_loop, 0);
+}
+
+static void
+elops_destroy_pt_uv(struct lws_context *context, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       int m, ns;
+
+       lwsl_info("%s: %d\n", __func__, tsi);
+
+       if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
+               return;
+
+       if (!pt->uv.io_loop)
+               return;
+
+       if (pt->event_loop_destroy_processing_done)
+               return;
+
+       pt->event_loop_destroy_processing_done = 1;
+
+       if (!pt->event_loop_foreign) {
+               uv_signal_stop(&pt->w_sigint.uv.watcher);
+
+               ns = LWS_ARRAY_SIZE(sigs);
+               if (lws_check_opt(context->options,
+                                 LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN))
+                       ns = 2;
+
+               for (m = 0; m < ns; m++) {
+                       uv_signal_stop(&pt->uv.signals[m]);
+                       uv_close((uv_handle_t *)&pt->uv.signals[m],
+                                lws_uv_close_cb_sa);
+               }
+       } else
+               lwsl_debug("%s: not closing pt signals\n", __func__);
+
+       uv_timer_stop(&pt->uv.sultimer);
+       uv_close((uv_handle_t *)&pt->uv.sultimer, lws_uv_close_cb_sa);
+
+       uv_idle_stop(&pt->uv.idle);
+       uv_close((uv_handle_t *)&pt->uv.idle, lws_uv_close_cb_sa);
+}
+
+/*
+ * This needs to be called after vhosts have been defined.
+ *
+ * If later, after server start, another vhost is added, this must be
+ * called again to bind the vhost
+ */
+
+LWS_VISIBLE int
+elops_init_pt_uv(struct lws_context *context, void *_loop, int tsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       struct lws_vhost *vh = context->vhost_list;
+       int status = 0, n, ns, first = 1;
+       uv_loop_t *loop = (uv_loop_t *)_loop;
+
+       if (!pt->uv.io_loop) {
+               if (!loop) {
+                       loop = lws_malloc(sizeof(*loop), "libuv loop");
+                       if (!loop) {
+                               lwsl_err("OOM\n");
+                               return -1;
+                       }
+       #if UV_VERSION_MAJOR > 0
+                       uv_loop_init(loop);
+       #else
+                       lwsl_err("This libuv is too old to work...\n");
+                       return 1;
+       #endif
+                       pt->event_loop_foreign = 0;
+               } else {
+                       lwsl_notice(" Using foreign event loop...\n");
+                       pt->event_loop_foreign = 1;
+               }
+
+               pt->uv.io_loop = loop;
+               uv_idle_init(loop, &pt->uv.idle);
+               LWS_UV_REFCOUNT_STATIC_HANDLE_NEW(&pt->uv.idle, context);
+
+
+               ns = LWS_ARRAY_SIZE(sigs);
+               if (lws_check_opt(context->options,
+                                 LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN))
+                       ns = 2;
+
+               if (!pt->event_loop_foreign) {
+                       assert(ns <= (int)LWS_ARRAY_SIZE(pt->uv.signals));
+                       for (n = 0; n < ns; n++) {
+                               uv_signal_init(loop, &pt->uv.signals[n]);
+                               LWS_UV_REFCOUNT_STATIC_HANDLE_NEW(&pt->uv.signals[n],
+                                                                 context);
+                               pt->uv.signals[n].data = pt->context;
+                               uv_signal_start(&pt->uv.signals[n],
+                                               lws_uv_signal_handler, sigs[n]);
+                       }
+               }
+       } else
+               first = 0;
+
+       /*
+        * Initialize the accept wsi read watcher with all the listening sockets
+        * and register a callback for read operations
+        *
+        * We have to do it here because the uv loop(s) are not
+        * initialized until after context creation.
+        */
+       while (vh) {
+               if (elops_init_vhost_listen_wsi_uv(vh->lserv_wsi) == -1)
+                       return -1;
+               vh = vh->vhost_next;
+       }
+
+       if (!first)
+               return status;
+
+       uv_timer_init(pt->uv.io_loop, &pt->uv.sultimer);
+       LWS_UV_REFCOUNT_STATIC_HANDLE_NEW(&pt->uv.sultimer, context);
+
+       return status;
+}
+
+static void
+lws_libuv_closewsi(uv_handle_t* handle)
+{
+       struct lws *wsi = (struct lws *)handle->data;
+       struct lws_context *context = lws_get_context(wsi);
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+#if !defined(LWS_WITHOUT_SERVER)
+       int lspd = 0;
+#endif
+
+       lwsl_info("%s: %p\n", __func__, wsi);
+
+       /*
+        * We get called back here for every wsi that closes
+        */
+
+#if !defined(LWS_WITHOUT_SERVER)
+       if (wsi->role_ops == &role_ops_listen && wsi->context->deprecated) {
+               lspd = 1;
+               context->deprecation_pending_listen_close_count--;
+               if (!context->deprecation_pending_listen_close_count)
+                       lspd = 2;
+       }
+#endif
+
+       lws_pt_lock(pt, __func__);
+       __lws_close_free_wsi_final(wsi);
+       lws_pt_unlock(pt);
+
+       /* it's our job to close the handle finally */
+       lws_free(handle);
+
+#if !defined(LWS_WITHOUT_SERVER)
+       if (lspd == 2 && context->deprecation_cb) {
+               lwsl_notice("calling deprecation callback\n");
+               context->deprecation_cb();
+       }
+#endif
+
+       lwsl_info("%s: sa left %d: dyn left: %d (rk %d)\n", __func__,
+                   context->count_event_loop_static_asset_handles,
+                   context->count_wsi_allocated, context->requested_kill);
+
+       /*
+        * eventually, we closed all the wsi...
+        */
+
+       if (context->requested_kill && !context->count_wsi_allocated) {
+               struct lws_vhost *vh = context->vhost_list;
+               int m;
+
+               /*
+                * Start Closing Phase 2: close of static handles
+                */
+
+               lwsl_info("%s: all lws dynamic handles down, closing static\n",
+                           __func__);
+
+               for (m = 0; m < context->count_threads; m++)
+                       elops_destroy_pt_uv(context, m);
+
+               /* protocols may have initialized libuv objects */
+
+               while (vh) {
+                       lws_vhost_destroy1(vh);
+                       vh = vh->vhost_next;
+               }
+
+               if (!context->count_event_loop_static_asset_handles &&
+                   context->pt[0].event_loop_foreign) {
+                       lwsl_info("%s: call lws_context_destroy2\n", __func__);
+                       lws_context_destroy2(context);
+               }
+       }
+}
+
+void
+lws_libuv_closehandle(struct lws *wsi)
+{
+       uv_handle_t* handle;
+
+       if (!wsi->w_read.uv.pwatcher)
+               return;
+
+       if (wsi->told_event_loop_closed) {
+       //      assert(0);
+               return;
+       }
+
+       lwsl_debug("%s: %p\n", __func__, wsi);
+
+       wsi->told_event_loop_closed = 1;
+
+       /*
+        * The normal close path attaches the related wsi as the
+        * handle->data.
+        */
+
+       handle = (uv_handle_t *)wsi->w_read.uv.pwatcher;
+
+       /* ensure we can only do this once */
+
+       wsi->w_read.uv.pwatcher = NULL;
+
+       uv_close(handle, lws_libuv_closewsi);
+}
+
+struct lws_event_loop_ops event_loop_ops_uv = {
+       /* name */                      "libuv",
+       /* init_context */              elops_init_context_uv,
+       /* destroy_context1 */          elops_destroy_context1_uv,
+       /* destroy_context2 */          elops_destroy_context2_uv,
+       /* init_vhost_listen_wsi */     elops_init_vhost_listen_wsi_uv,
+       /* init_pt */                   elops_init_pt_uv,
+       /* wsi_logical_close */         elops_wsi_logical_close_uv,
+       /* check_client_connect_ok */   elops_check_client_connect_ok_uv,
+       /* close_handle_manually */     elops_close_handle_manually_uv,
+       /* accept */                    elops_accept_uv,
+       /* io */                        elops_io_uv,
+       /* run_pt */                    elops_run_pt_uv,
+       /* destroy_pt */                elops_destroy_pt_uv,
+       /* destroy wsi */               NULL,
+
+       /* periodic_events_available */ 0,
+};
diff --git a/lib/event-libs/libuv/private.h b/lib/event-libs/libuv/private.h
new file mode 100644 (file)
index 0000000..815f0f5
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_WITH_LIBUV
+ */
+
+#include <uv.h>
+
+/*
+ * libuv's async destroy cb means that asking to close something doesn't mean
+ * you can destroy it or parent things until after the close completes.
+ *
+ * So we must reference-count creation and close completions with libuv.
+ *
+ * All "static" (per-pt or per-context) uv handles must
+ *
+ *  - have their .data set to point to the context
+ *
+ *  - contribute to context->uv_count_static_asset_handles
+ *    counting
+ */
+#define LWS_UV_REFCOUNT_STATIC_HANDLE_NEW(_x, _ctx) \
+               { uv_handle_t *_uht = (uv_handle_t *)(_x); _uht->data = _ctx; \
+               _ctx->count_event_loop_static_asset_handles++; }
+#define LWS_UV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(_x) \
+               ((struct lws_context *)((uv_handle_t *)((_x)->data)))
+#define LWS_UV_REFCOUNT_STATIC_HANDLE_DESTROYED(_x) \
+               (--(LWS_UV_REFCOUNT_STATIC_HANDLE_TO_CONTEXT(_x)-> \
+                               count_event_loop_static_asset_handles))
+
+struct lws_pt_eventlibs_libuv {
+       uv_loop_t *io_loop;
+       uv_signal_t signals[8];
+       uv_timer_t sultimer;
+       uv_idle_t idle;
+};
+
+struct lws_context_eventlibs_libuv {
+       uv_loop_t loop;
+};
+
+struct lws_io_watcher_libuv {
+       uv_poll_t *pwatcher;
+};
+
+struct lws_signal_watcher_libuv {
+       uv_signal_t watcher;
+};
+
+extern struct lws_event_loop_ops event_loop_ops_uv;
+
+uv_loop_t *
+lws_uv_getloop(struct lws_context *context, int tsi);
+
+int
+lws_uv_plugins_init(struct lws_context *context, const char * const *d);
+
+int
+lws_uv_plugins_destroy(struct lws_context *context);
diff --git a/lib/event-libs/poll/poll.c b/lib/event-libs/poll/poll.c
new file mode 100644 (file)
index 0000000..6fb312f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_WS
+ */
+
+#include <core/private.h>
+
+struct lws_event_loop_ops event_loop_ops_poll = {
+       /* name */                      "poll",
+       /* init_context */              NULL,
+       /* destroy_context1 */          NULL,
+       /* destroy_context2 */          NULL,
+       /* init_vhost_listen_wsi */     NULL,
+       /* init_pt */                   NULL,
+       /* wsi_logical_close */         NULL,
+       /* check_client_connect_ok */   NULL,
+       /* close_handle_manually */     NULL,
+       /* accept */                    NULL,
+       /* io */                        NULL,
+       /* run */                       NULL,
+       /* destroy_pt */                NULL,
+       /* destroy wsi */               NULL,
+
+       /* periodic_events_available */ 1,
+};
diff --git a/lib/event-libs/poll/private.h b/lib/event-libs/poll/private.h
new file mode 100644 (file)
index 0000000..ca313eb
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ */
+
+extern struct lws_event_loop_ops event_loop_ops_poll;
diff --git a/lib/event-libs/private.h b/lib/event-libs/private.h
new file mode 100644 (file)
index 0000000..58bca94
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h
+ */
+
+struct lws_event_loop_ops {
+       const char *name;
+       /* event loop-specific context init during context creation */
+       int (*init_context)(struct lws_context *context,
+                           const struct lws_context_creation_info *info);
+       /* called during lws_destroy_context */
+       int (*destroy_context1)(struct lws_context *context);
+       /* called during lws_destroy_context2 */
+       int (*destroy_context2)(struct lws_context *context);
+       /* init vhost listening wsi */
+       int (*init_vhost_listen_wsi)(struct lws *wsi);
+       /* init the event loop for a pt */
+       int (*init_pt)(struct lws_context *context, void *_loop, int tsi);
+       /* called at end of first phase of close_free_wsi()  */
+       int (*wsi_logical_close)(struct lws *wsi);
+       /* return nonzero if client connect not allowed  */
+       int (*check_client_connect_ok)(struct lws *wsi);
+       /* close handle manually  */
+       void (*close_handle_manually)(struct lws *wsi);
+       /* event loop accept processing  */
+       int (*accept)(struct lws *wsi);
+       /* control wsi active events  */
+       void (*io)(struct lws *wsi, int flags);
+       /* run the event loop for a pt */
+       void (*run_pt)(struct lws_context *context, int tsi);
+       /* called before pt is destroyed */
+       void (*destroy_pt)(struct lws_context *context, int tsi);
+       /* called just before wsi is freed  */
+       void (*destroy_wsi)(struct lws *wsi);
+
+       unsigned int periodic_events_available:1;
+};
+
+/* bring in event libs private declarations */
+
+#if defined(LWS_WITH_POLL)
+#include "event-libs/poll/private.h"
+#endif
+
+#if defined(LWS_WITH_LIBUV)
+#include "event-libs/libuv/private.h"
+#endif
+
+#if defined(LWS_WITH_LIBEVENT)
+#include "event-libs/libevent/private.h"
+#endif
+
+#if defined(LWS_WITH_LIBEV)
+#include "event-libs/libev/private.h"
+#endif
+
diff --git a/lib/extension-permessage-deflate.c b/lib/extension-permessage-deflate.c
deleted file mode 100644 (file)
index f027e1f..0000000
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- * ./lib/extension-permessage-deflate.c
- *
- *  Copyright (C) 2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-#include "extension-permessage-deflate.h"
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-#define LWS_ZLIB_MEMLEVEL 8
-
-const struct lws_ext_options lws_ext_pm_deflate_options[] = {
-       /* public RFC7692 settings */
-       { "server_no_context_takeover", EXTARG_NONE },
-       { "client_no_context_takeover", EXTARG_NONE },
-       { "server_max_window_bits",     EXTARG_OPT_DEC },
-       { "client_max_window_bits",     EXTARG_OPT_DEC },
-       /* ones only user code can set */
-       { "rx_buf_size",                EXTARG_DEC },
-       { "tx_buf_size",                EXTARG_DEC },
-       { "compression_level",          EXTARG_DEC },
-       { "mem_level",                  EXTARG_DEC },
-       { NULL, 0 }, /* sentinel */
-};
-
-static void
-lws_extension_pmdeflate_restrict_args(struct lws *wsi,
-                                     struct lws_ext_pm_deflate_priv *priv)
-{
-       int n, extra;
-
-       /* cap the RX buf at the nearest power of 2 to protocol rx buf */
-
-       n = wsi->context->pt_serv_buf_size;
-       if (wsi->protocol->rx_buffer_size)
-               n =  wsi->protocol->rx_buffer_size;
-
-       extra = 7;
-       while (n >= 1 << (extra + 1))
-               extra++;
-
-       if (extra < priv->args[PMD_RX_BUF_PWR2]) {
-               priv->args[PMD_RX_BUF_PWR2] = extra;
-               lwsl_err(" Capping pmd rx to %d\n", 1 << extra);
-       }
-}
-
-LWS_VISIBLE int
-lws_extension_callback_pm_deflate(struct lws_context *context,
-                                 const struct lws_extension *ext,
-                                 struct lws *wsi,
-                                 enum lws_extension_callback_reasons reason,
-                                 void *user, void *in, size_t len)
-{
-       struct lws_ext_pm_deflate_priv *priv =
-                                    (struct lws_ext_pm_deflate_priv *)user;
-       struct lws_tokens *eff_buf = (struct lws_tokens *)in;
-       static unsigned char trail[] = { 0, 0, 0xff, 0xff };
-       int n, ret = 0, was_fin = 0, extra;
-       struct lws_ext_option_arg *oa;
-
-       switch (reason) {
-       case LWS_EXT_CB_NAMED_OPTION_SET:
-               oa = in;
-               if (!oa->option_name)
-                       break;
-               for (n = 0; n < ARRAY_SIZE(lws_ext_pm_deflate_options); n++)
-                       if (!strcmp(lws_ext_pm_deflate_options[n].name, oa->option_name))
-                               break;
-
-               if (n == ARRAY_SIZE(lws_ext_pm_deflate_options))
-                       break;
-               oa->option_index = n;
-
-               /* fallthru */
-
-       case LWS_EXT_CB_OPTION_SET:
-               oa = in;
-               lwsl_notice("%s: option set: idx %d, %s, len %d\n", __func__,
-                         oa->option_index, oa->start, oa->len);
-               if (oa->start)
-                       priv->args[oa->option_index] = atoi(oa->start);
-               else
-                       priv->args[oa->option_index] = 1;
-
-               if (priv->args[PMD_CLIENT_MAX_WINDOW_BITS] == 8)
-                       priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 9;
-
-               lws_extension_pmdeflate_restrict_args(wsi, priv);
-               break;
-
-       case LWS_EXT_CB_OPTION_CONFIRM:
-               if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 ||
-                   priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 ||
-                   priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 ||
-                   priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15)
-                       return -1;
-               break;
-
-       case LWS_EXT_CB_CLIENT_CONSTRUCT:
-       case LWS_EXT_CB_CONSTRUCT:
-
-               n = context->pt_serv_buf_size;
-               if (wsi->protocol->rx_buffer_size)
-                       n =  wsi->protocol->rx_buffer_size;
-
-               if (n < 128) {
-                       lwsl_err(" permessage-deflate requires the protocol (%s) to have an RX buffer >= 128\n",
-                                       wsi->protocol->name);
-                       return -1;
-               }
-
-               /* fill in **user */
-               priv = lws_zalloc(sizeof(*priv));
-               *((void **)user) = priv;
-               lwsl_ext("%s: LWS_EXT_CB_*CONSTRUCT\n", __func__);
-               memset(priv, 0, sizeof(*priv));
-
-               /* fill in pointer to options list */
-               if (in)
-                       *((const struct lws_ext_options **)in) =
-                                       lws_ext_pm_deflate_options;
-
-               /* fallthru */
-
-       case LWS_EXT_CB_OPTION_DEFAULT:
-
-               /* set the public, RFC7692 defaults... */
-
-               priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0,
-               priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0;
-               priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15;
-               priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15;
-
-               /* ...and the ones the user code can override */
-
-               priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */
-               priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */
-               priv->args[PMD_COMP_LEVEL] = 1;
-               priv->args[PMD_MEM_LEVEL] = 8;
-
-               lws_extension_pmdeflate_restrict_args(wsi, priv);
-               break;
-
-       case LWS_EXT_CB_DESTROY:
-               lwsl_ext("%s: LWS_EXT_CB_DESTROY\n", __func__);
-               lws_free(priv->buf_rx_inflated);
-               lws_free(priv->buf_tx_deflated);
-               if (priv->rx_init)
-                       (void)inflateEnd(&priv->rx);
-               if (priv->tx_init)
-                       (void)deflateEnd(&priv->tx);
-               lws_free(priv);
-               return ret;
-
-       case LWS_EXT_CB_PAYLOAD_RX:
-               lwsl_ext(" %s: LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d\n",
-                        __func__, eff_buf->token_len, priv->rx.avail_in);
-               if (!(wsi->u.ws.rsv_first_msg & 0x40))
-                       return 0;
-
-#if 0
-               for (n = 0; n < eff_buf->token_len; n++) {
-                       printf("%02X ", (unsigned char)eff_buf->token[n]);
-                       if ((n & 15) == 15)
-                               printf("\n");
-               }
-               printf("\n");
-#endif
-               if (!priv->rx_init)
-                       if (inflateInit2(&priv->rx, -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) {
-                               lwsl_err("%s: iniflateInit failed\n", __func__);
-                               return -1;
-                       }
-               priv->rx_init = 1;
-               if (!priv->buf_rx_inflated)
-                       priv->buf_rx_inflated = lws_malloc(LWS_PRE + 7 + 5 +
-                                           (1 << priv->args[PMD_RX_BUF_PWR2]));
-               if (!priv->buf_rx_inflated) {
-                       lwsl_err("%s: OOM\n", __func__);
-                       return -1;
-               }
-
-               /*
-                * We have to leave the input stream alone if we didn't
-                * finish with it yet.  The input stream is held in the wsi
-                * rx buffer by the caller, so this assumption is safe while
-                * we block new rx while draining the existing rx
-                */
-               if (!priv->rx.avail_in && eff_buf->token && eff_buf->token_len) {
-                       priv->rx.next_in = (unsigned char *)eff_buf->token;
-                       priv->rx.avail_in = eff_buf->token_len;
-               }
-               priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE;
-               eff_buf->token = (char *)priv->rx.next_out;
-               priv->rx.avail_out = 1 << priv->args[PMD_RX_BUF_PWR2];
-
-               if (priv->rx_held_valid) {
-                       lwsl_ext("-- RX piling on held byte --\n");
-                       *(priv->rx.next_out++) = priv->rx_held;
-                       priv->rx.avail_out--;
-                       priv->rx_held_valid = 0;
-               }
-
-               /* if...
-                *
-                *  - he has no remaining input content for this message, and
-                *  - and this is the final fragment, and
-                *  - we used everything that could be drained on the input side
-                *
-                * ...then put back the 00 00 FF FF the sender stripped as our
-                * input to zlib
-                */
-               if (!priv->rx.avail_in && wsi->u.ws.final &&
-                   !wsi->u.ws.rx_packet_length) {
-                       lwsl_ext("RX APPEND_TRAILER-DO\n");
-                       was_fin = 1;
-                       priv->rx.next_in = trail;
-                       priv->rx.avail_in = sizeof(trail);
-               }
-
-               n = inflate(&priv->rx, Z_NO_FLUSH);
-               lwsl_ext("inflate ret %d, avi %d, avo %d, wsifinal %d\n", n,
-                        priv->rx.avail_in, priv->rx.avail_out, wsi->u.ws.final);
-               switch (n) {
-               case Z_NEED_DICT:
-               case Z_STREAM_ERROR:
-               case Z_DATA_ERROR:
-               case Z_MEM_ERROR:
-                       lwsl_info("zlib error inflate %d: %s\n",
-                                 n, priv->rx.msg);
-                       return -1;
-               }
-               /*
-                * If we did not already send in the 00 00 FF FF, and he's
-                * out of input, he did not EXACTLY fill the output buffer
-                * (which is ambiguous and we will force it to go around
-                * again by withholding a byte), and he's otherwise working on
-                * being a FIN fragment, then do the FIN message processing
-                * of faking up the 00 00 FF FF that the sender stripped.
-                */
-               if (!priv->rx.avail_in && wsi->u.ws.final &&
-                   !wsi->u.ws.rx_packet_length && !was_fin &&
-                   priv->rx.avail_out /* ambiguous as to if it is the end */
-               ) {
-                       lwsl_ext("RX APPEND_TRAILER-DO\n");
-                       was_fin = 1;
-                       priv->rx.next_in = trail;
-                       priv->rx.avail_in = sizeof(trail);
-                       n = inflate(&priv->rx, Z_SYNC_FLUSH);
-                       lwsl_ext("RX trailer inf returned %d, avi %d, avo %d\n", n,
-                                priv->rx.avail_in, priv->rx.avail_out);
-                       switch (n) {
-                       case Z_NEED_DICT:
-                       case Z_STREAM_ERROR:
-                       case Z_DATA_ERROR:
-                       case Z_MEM_ERROR:
-                               lwsl_info("zlib error inflate %d: %s\n",
-                                         n, priv->rx.msg);
-                               return -1;
-                       }
-               }
-               /*
-                * we must announce in our returncode now if there is more
-                * output to be expected from inflate, so we can decide to
-                * set the FIN bit on this bufferload or not.  However zlib
-                * is ambiguous when we exactly filled the inflate buffer.  It
-                * does not give us a clue as to whether we should understand
-                * that to mean he ended on a buffer boundary, or if there is
-                * more in the pipeline.
-                *
-                * So to work around that safely, if it used all output space
-                * exactly, we ALWAYS say there is more coming and we withhold
-                * the last byte of the buffer to guarantee that is true.
-                *
-                * That still leaves us at least one byte to finish with a FIN
-                * on, even if actually nothing more is coming from the next
-                * inflate action itself.
-                */
-               if (!priv->rx.avail_out) { /* he used all available out buf */
-                       lwsl_ext("-- rx grabbing held --\n");
-                       /* snip the last byte and hold it for next time */
-                       priv->rx_held = *(--priv->rx.next_out);
-                       priv->rx_held_valid = 1;
-               }
-
-               eff_buf->token_len = (char *)priv->rx.next_out - eff_buf->token;
-               priv->count_rx_between_fin += eff_buf->token_len;
-
-               lwsl_ext("  %s: RX leaving with new effbuff len %d, "
-                        "ret %d, rx.avail_in=%d, TOTAL RX since FIN %lu\n",
-                        __func__, eff_buf->token_len, priv->rx_held_valid,
-                        priv->rx.avail_in,
-                        (unsigned long)priv->count_rx_between_fin);
-
-               if (was_fin) {
-                       priv->count_rx_between_fin = 0;
-                       if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) {
-                               (void)inflateEnd(&priv->rx);
-                               priv->rx_init = 0;
-                       }
-               }
-#if 0
-               for (n = 0; n < eff_buf->token_len; n++)
-                       putchar(eff_buf->token[n]);
-               puts("\n");
-#endif
-
-               return priv->rx_held_valid;
-
-       case LWS_EXT_CB_PAYLOAD_TX:
-
-               if (!priv->tx_init) {
-                       n = deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL],
-                                        Z_DEFLATED,
-                                        -priv->args[PMD_SERVER_MAX_WINDOW_BITS +
-                                                    (wsi->vhost->listen_port <= 0)],
-                                        priv->args[PMD_MEM_LEVEL],
-                                        Z_DEFAULT_STRATEGY);
-                       if (n != Z_OK) {
-                               lwsl_ext("inflateInit2 failed %d\n", n);
-                               return 1;
-                       }
-               }
-               priv->tx_init = 1;
-               if (!priv->buf_tx_deflated)
-                       priv->buf_tx_deflated = lws_malloc(LWS_PRE + 7 + 5 +
-                                           (1 << priv->args[PMD_TX_BUF_PWR2]));
-               if (!priv->buf_tx_deflated) {
-                       lwsl_err("%s: OOM\n", __func__);
-                       return -1;
-               }
-
-               if (eff_buf->token) {
-                       lwsl_ext("%s: TX: eff_buf length %d\n", __func__,
-                                eff_buf->token_len);
-                       priv->tx.next_in = (unsigned char *)eff_buf->token;
-                       priv->tx.avail_in = eff_buf->token_len;
-               }
-
-#if 0
-               for (n = 0; n < eff_buf->token_len; n++) {
-                       printf("%02X ", (unsigned char)eff_buf->token[n]);
-                       if ((n & 15) == 15)
-                               printf("\n");
-               }
-               printf("\n");
-#endif
-
-               priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5;
-               eff_buf->token = (char *)priv->tx.next_out;
-               priv->tx.avail_out = 1 << priv->args[PMD_TX_BUF_PWR2];
-
-               n = deflate(&priv->tx, Z_SYNC_FLUSH);
-               if (n == Z_STREAM_ERROR) {
-                       lwsl_ext("%s: Z_STREAM_ERROR\n", __func__);
-                       return -1;
-               }
-
-               if (priv->tx_held_valid) {
-                       priv->tx_held_valid = 0;
-                       if (priv->tx.avail_out == 1 << priv->args[PMD_TX_BUF_PWR2])
-                               /*
-                                * we can get a situation he took something in
-                                * but did not generate anything out, at the end
-                                * of a message (eg, next thing he sends is 80
-                                * 00, a zero length FIN, like Authobahn can
-                                * send).
-                                * If we have come back as a FIN, we must not
-                                * place the pending trailer 00 00 FF FF, just
-                                * the 1 byte of live data
-                                */
-                               *(--eff_buf->token) = priv->tx_held[0];
-                       else {
-                               /* he generated data, prepend whole pending */
-                               eff_buf->token -= 5;
-                               for (n = 0; n < 5; n++)
-                                       eff_buf->token[n] = priv->tx_held[n];
-
-                       }
-               }
-               priv->compressed_out = 1;
-               eff_buf->token_len = (int)(priv->tx.next_out -
-                                          (unsigned char *)eff_buf->token);
-
-               /*
-                * we must announce in our returncode now if there is more
-                * output to be expected from inflate, so we can decide to
-                * set the FIN bit on this bufferload or not.  However zlib
-                * is ambiguous when we exactly filled the inflate buffer.  It
-                * does not give us a clue as to whether we should understand
-                * that to mean he ended on a buffer boundary, or if there is
-                * more in the pipeline.
-                *
-                * Worse, the guy providing the stuff we are sending may not
-                * know until after that this was, actually, the last chunk,
-                * that can happen even if we did not fill the output buf, ie
-                * he may send after this a zero-length FIN fragment.
-                *
-                * This is super difficult because we must snip the last 4
-                * bytes in the case this is the last compressed output of the
-                * message.  The only way to deal with it is defer sending the
-                * last 5 bytes of each frame until the next one, when we will
-                * be in a position to understand if that has a FIN or not.
-                */
-
-               extra = !!(len & LWS_WRITE_NO_FIN) || !priv->tx.avail_out;
-
-               if (eff_buf->token_len >= 4 + extra) {
-                       lwsl_ext("tx held %d\n", 4 + extra);
-                       priv->tx_held_valid = extra;
-                       for (n = 3 + extra; n >= 0; n--)
-                               priv->tx_held[n] = *(--priv->tx.next_out);
-                       eff_buf->token_len -= 4 + extra;
-               }
-               lwsl_ext("  TX rewritten with new effbuff len %d, ret %d\n",
-                        eff_buf->token_len, !priv->tx.avail_out);
-
-               return !priv->tx.avail_out; /* 1 == have more tx pending */
-
-       case LWS_EXT_CB_PACKET_TX_PRESEND:
-               if (!priv->compressed_out)
-                       break;
-               priv->compressed_out = 0;
-
-               if ((*(eff_buf->token) & 0x80) &&
-                   priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) {
-                       lwsl_debug("PMD_CLIENT_NO_CONTEXT_TAKEOVER\n");
-                       (void)deflateEnd(&priv->tx);
-                       priv->tx_init = 0;
-               }
-
-               n = *(eff_buf->token) & 15;
-               /* set RSV1, but not on CONTINUATION */
-               if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME)
-                       *eff_buf->token |= 0x40;
-#if 0
-               for (n = 0; n < eff_buf->token_len; n++) {
-                       printf("%02X ", (unsigned char)eff_buf->token[n]);
-                       if ((n & 15) == 15)
-                               puts("\n");
-               }
-               puts("\n");
-#endif
-               lwsl_ext("%s: tx opcode 0x%02X\n", __func__,
-                        (unsigned char)*eff_buf->token);
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
diff --git a/lib/handshake.c b/lib/handshake.c
deleted file mode 100644 (file)
index 5e897e2..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-/*
- * -04 of the protocol (actually the 80th version) has a radically different
- * handshake.  The 04 spec gives the following idea
- *
- *    The handshake from the client looks as follows:
- *
- *      GET /chat HTTP/1.1
- *      Host: server.example.com
- *      Upgrade: websocket
- *      Connection: Upgrade
- *      Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
- *      Sec-WebSocket-Origin: http://example.com
- *      Sec-WebSocket-Protocol: chat, superchat
- *     Sec-WebSocket-Version: 4
- *
- *  The handshake from the server looks as follows:
- *
- *       HTTP/1.1 101 Switching Protocols
- *       Upgrade: websocket
- *       Connection: Upgrade
- *       Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
- *       Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
- *       Sec-WebSocket-Protocol: chat
- */
-
-#ifndef min
-#define min(a, b) ((a) < (b) ? (a) : (b))
-#endif
-
-/*
- * We have to take care about parsing because the headers may be split
- * into multiple fragments.  They may contain unknown headers with arbitrary
- * argument lengths.  So, we parse using a single-character at a time state
- * machine that is completely independent of packet size.
- *
- * Returns <0 for error or length of chars consumed from buf (up to len)
- */
-
-LWS_VISIBLE int
-lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
-{
-       unsigned char *last_char, *oldbuf = buf;
-       lws_filepos_t body_chunk_len;
-       size_t n;
-
-       lwsl_debug("%s: incoming len %d  state %d\n", __func__, (int)len, wsi->state);
-
-       switch (wsi->state) {
-#ifdef LWS_USE_HTTP2
-       case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
-       case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
-       case LWSS_HTTP2_ESTABLISHED:
-               n = 0;
-               while (n < len) {
-                       /*
-                        * we were accepting input but now we stopped doing so
-                        */
-                       if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) {
-                               lws_rxflow_cache(wsi, buf, n, len);
-
-                               return 1;
-                       }
-
-                       /* account for what we're using in rxflow buffer */
-                       if (wsi->rxflow_buffer)
-                               wsi->rxflow_pos++;
-                       if (lws_http2_parser(wsi, buf[n++])) {
-                               lwsl_debug("%s: http2_parser bailed\n", __func__);
-                               goto bail;
-                       }
-               }
-               break;
-#endif
-
-       case LWSS_HTTP_ISSUING_FILE:
-               return 0;
-
-       case LWSS_CLIENT_HTTP_ESTABLISHED:
-               break;
-
-       case LWSS_HTTP:
-               wsi->hdr_parsing_completed = 0;
-               /* fallthru */
-
-       case LWSS_HTTP_HEADERS:
-               if (!wsi->u.hdr.ah) {
-                       lwsl_err("%s: LWSS_HTTP_HEADERS: NULL ah\n", __func__);
-                       assert(0);
-               }
-               lwsl_parser("issuing %d bytes to parser\n", (int)len);
-
-               if (lws_handshake_client(wsi, &buf, (size_t)len))
-                       goto bail;
-
-               last_char = buf;
-               if (lws_handshake_server(wsi, &buf, (size_t)len))
-                       /* Handshake indicates this session is done. */
-                       goto bail;
-
-               /* we might have transitioned to RAW */
-               if (wsi->mode == LWSCM_RAW)
-                        /* we gave the read buffer to RAW handler already */
-                       goto read_ok;
-
-               /*
-                * It's possible that we've exhausted our data already, or
-                * rx flow control has stopped us dealing with this early,
-                * but lws_handshake_server doesn't update len for us.
-                * Figure out how much was read, so that we can proceed
-                * appropriately:
-                */
-               len -= (buf - last_char);
-               lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len);
-
-               if (!wsi->hdr_parsing_completed)
-                       /* More header content on the way */
-                       goto read_ok;
-
-               switch (wsi->state) {
-                       case LWSS_HTTP:
-                       case LWSS_HTTP_HEADERS:
-                               goto read_ok;
-                       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)
-                                       goto http_postbody;
-
-                               /* there is no POST content */
-                               goto postbody_completion;
-                       default:
-                               break;
-               }
-               break;
-
-       case LWSS_HTTP_BODY:
-http_postbody:
-               while (len && wsi->u.http.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;
-                       len -= body_chunk_len;
-#ifdef LWS_WITH_CGI
-                       if (wsi->cgi) {
-                               struct lws_cgi_args args;
-
-                               args.ch = LWS_STDIN;
-                               args.stdwsi = &wsi->cgi->stdwsi[0];
-                               args.data = buf;
-                               args.len = body_chunk_len;
-
-                               /* returns how much used */
-                               n = user_callback_handle_rxflow(
-                                       wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_CGI_STDIN_DATA,
-                                       wsi->user_space,
-                                       (void *)&args, 0);
-                               if ((int)n < 0)
-                                       goto bail;
-                       } else {
-#endif
-                               n = wsi->protocol->callback(wsi,
-                                       LWS_CALLBACK_HTTP_BODY, wsi->user_space,
-                                       buf, (size_t)body_chunk_len);
-                               if (n)
-                                       goto bail;
-                               n = (size_t)body_chunk_len;
-#ifdef LWS_WITH_CGI
-                       }
-#endif
-                       buf += n;
-
-                       if (wsi->u.http.content_remain)  {
-                               lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
-                                               wsi->context->timeout_secs);
-                               break;
-                       }
-                       /* he sent all the content in time */
-postbody_completion:
-#ifdef LWS_WITH_CGI
-                       /* if we're running a cgi, we can't let him off the hook just because he sent his POST data */
-                       if (wsi->cgi)
-                               lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, wsi->context->timeout_secs);
-                       else
-#endif
-                       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-#ifdef LWS_WITH_CGI
-                       if (!wsi->cgi)
-#endif
-                       {
-                               n = wsi->protocol->callback(wsi,
-                                       LWS_CALLBACK_HTTP_BODY_COMPLETION,
-                                       wsi->user_space, NULL, 0);
-                               if (n)
-                                       goto bail;
-                       }
-
-                       break;
-               }
-               break;
-
-       case LWSS_ESTABLISHED:
-       case LWSS_AWAITING_CLOSE_ACK:
-       case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION:
-       case LWSS_SHUTDOWN:
-               if (lws_handshake_client(wsi, &buf, (size_t)len))
-                       goto bail;
-               switch (wsi->mode) {
-               case LWSCM_WS_SERVING:
-
-                       if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) {
-                               lwsl_info("interpret_incoming_packet has bailed\n");
-                               goto bail;
-                       }
-                       break;
-               }
-               break;
-       default:
-               lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state);
-               break;
-       }
-
-read_ok:
-       /* Nothing more to do for now */
-       lwsl_info("%s: read_ok, used %ld\n", __func__, (long)(buf - oldbuf));
-
-       return buf - oldbuf;
-
-bail:
-       //lwsl_notice("closing connection at lws_read bail:\n");
-       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-
-       return -1;
-}
diff --git a/lib/header.c b/lib/header.c
deleted file mode 100644 (file)
index 6744c67..0000000
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-#include "lextable-strings.h"
-
-
-const unsigned char *lws_token_to_string(enum lws_token_indexes token)
-{
-       if ((unsigned int)token >= ARRAY_SIZE(set))
-               return NULL;
-
-       return (unsigned char *)set[token];
-}
-
-int
-lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
-                           const unsigned char *value, int length,
-                           unsigned char **p, unsigned char *end)
-{
-#ifdef LWS_USE_HTTP2
-       if (wsi->mode == LWSCM_HTTP2_SERVING)
-               return lws_add_http2_header_by_name(wsi, name,
-                                                   value, length, p, end);
-#else
-       (void)wsi;
-#endif
-       if (name) {
-               while (*p < end && *name)
-                       *((*p)++) = *name++;
-               if (*p == end)
-                       return 1;
-               *((*p)++) = ' ';
-       }
-       if (*p + length + 3 >= end)
-               return 1;
-
-       memcpy(*p, value, length);
-       *p += length;
-       *((*p)++) = '\x0d';
-       *((*p)++) = '\x0a';
-
-       return 0;
-}
-
-int lws_finalize_http_header(struct lws *wsi, unsigned char **p,
-                            unsigned char *end)
-{
-#ifdef LWS_USE_HTTP2
-       if (wsi->mode == LWSCM_HTTP2_SERVING)
-               return 0;
-#else
-       (void)wsi;
-#endif
-       if ((lws_intptr_t)(end - *p) < 3)
-               return 1;
-       *((*p)++) = '\x0d';
-       *((*p)++) = '\x0a';
-
-       return 0;
-}
-
-int
-lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
-                            const unsigned char *value, int length,
-                            unsigned char **p, unsigned char *end)
-{
-       const unsigned char *name;
-#ifdef LWS_USE_HTTP2
-       if (wsi->mode == LWSCM_HTTP2_SERVING)
-               return lws_add_http2_header_by_token(wsi, token, value, length, p, end);
-#endif
-       name = lws_token_to_string(token);
-       if (!name)
-               return 1;
-       return lws_add_http_header_by_name(wsi, name, value, length, p, end);
-}
-
-int lws_add_http_header_content_length(struct lws *wsi,
-                                      lws_filepos_t content_length,
-                                      unsigned char **p, unsigned char *end)
-{
-       char b[24];
-       int n;
-
-       n = sprintf(b, "%llu", (unsigned long long)content_length);
-       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;
-
-       return 0;
-}
-
-STORE_IN_ROM static const char * const err400[] = {
-       "Bad Request",
-       "Unauthorized",
-       "Payment Required",
-       "Forbidden",
-       "Not Found",
-       "Method Not Allowed",
-       "Not Acceptable",
-       "Proxy Auth Required",
-       "Request Timeout",
-       "Conflict",
-       "Gone",
-       "Length Required",
-       "Precondition Failed",
-       "Request Entity Too Large",
-       "Request URI too Long",
-       "Unsupported Media Type",
-       "Requested Range Not Satisfiable",
-       "Expectation Failed"
-};
-
-STORE_IN_ROM static const char * const err500[] = {
-       "Internal Server Error",
-       "Not Implemented",
-       "Bad Gateway",
-       "Service Unavailable",
-       "Gateway Timeout",
-       "HTTP Version Not Supported"
-};
-
-int
-lws_add_http_header_status(struct lws *wsi, unsigned int _code,
-                          unsigned char **p, unsigned char *end)
-{
-       STORE_IN_ROM static const char * const hver[] = {
-               "HTTP/1.0", "HTTP/1.1", "HTTP/2"
-       };
-       const struct lws_protocol_vhost_options *headers;
-       unsigned int code = _code & LWSAHH_CODE_MASK;
-       const char *description = "", *p1;
-       unsigned char code_and_desc[60];
-       int n;
-
-#ifdef LWS_WITH_ACCESS_LOG
-       wsi->access_log.response = code;
-#endif
-
-#ifdef LWS_USE_HTTP2
-       if (wsi->mode == LWSCM_HTTP2_SERVING)
-               return lws_add_http2_header_status(wsi, code, p, end);
-#endif
-       if (code >= 400 && code < (400 + ARRAY_SIZE(err400)))
-               description = err400[code - 400];
-       if (code >= 500 && code < (500 + ARRAY_SIZE(err500)))
-               description = err500[code - 500];
-
-       if (code == 200)
-               description = "OK";
-
-       if (code == 304)
-               description = "Not Modified";
-       else
-               if (code >= 300 && code < 400)
-                       description = "Redirect";
-
-       if (wsi->u.http.request_version < ARRAY_SIZE(hver))
-               p1 = hver[wsi->u.http.request_version];
-       else
-               p1 = hver[0];
-
-       n = sprintf((char *)code_and_desc, "%s %u %s", p1, code, description);
-
-       if (lws_add_http_header_by_name(wsi, NULL, code_and_desc, n, p, end))
-               return 1;
-
-       headers = wsi->vhost->headers;
-       while (headers) {
-               if (lws_add_http_header_by_name(wsi,
-                               (const unsigned char *)headers->name,
-                               (unsigned char *)headers->value,
-                               strlen(headers->value), p, end))
-                       return 1;
-
-               headers = headers->next;
-       }
-
-       if (wsi->context->server_string &&
-           !(_code & LWSAHH_FLAG_NO_SERVER_NAME))
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
-                               (unsigned char *)wsi->context->server_string,
-                               wsi->context->server_string_len, p, end))
-                       return 1;
-
-       if (wsi->vhost->options & LWS_SERVER_OPTION_STS)
-               if (lws_add_http_header_by_name(wsi, (unsigned char *)
-                               "Strict-Transport-Security:",
-                               (unsigned char *)"max-age=15768000 ; "
-                               "includeSubDomains", 36, p, end))
-                       return 1;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_return_http_status(struct lws *wsi, unsigned int code,
-                      const char *html_body)
-{
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       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;
-       char slen[20];
-
-       if (!html_body)
-               html_body = "";
-
-       if (lws_add_http_header_status(wsi, code, &p, end))
-               return 1;
-
-       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
-                                        (unsigned char *)"text/html", 9,
-                                        &p, end))
-               return 1;
-
-       len = 35 + strlen(html_body) + sprintf(slen, "%d", code);
-       n = sprintf(slen, "%d", len);
-
-       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH,
-                                        (unsigned char *)slen, n,
-                                        &p, end))
-               return 1;
-
-       if (lws_finalize_http_header(wsi, &p, end))
-               return 1;
-
-#if defined(LWS_USE_HTTP2)
-       {
-               unsigned char *body = p + 512;
-
-               m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
-               if (m != (int)(p - start))
-                       return 1;
-
-               len = sprintf((char *)body, "<html><body><h1>%u</h1>%s</body></html>",
-                     code, html_body);
-
-               n = len;
-               m = lws_write(wsi, body, len, LWS_WRITE_HTTP);
-       }
-#else
-       p += lws_snprintf((char *)p, end - p - 1,
-                         "<html><body><h1>%u</h1>%s</body></html>",
-                         code, html_body);
-
-       n = (int)(p - start);
-       m = lws_write(wsi, start, n, LWS_WRITE_HTTP);
-       if (m != n)
-               return 1;
-#endif
-
-       return m != n;
-}
-
-LWS_VISIBLE int
-lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len,
-                 unsigned char **p, unsigned char *end)
-{
-       unsigned char *start = *p;
-       int n;
-
-       if (lws_add_http_header_status(wsi, code, p, end))
-               return -1;
-
-       if (lws_add_http_header_by_token(wsi,
-                       WSI_TOKEN_HTTP_LOCATION,
-                       loc, len, p, end))
-               return -1;
-       /*
-        * if we're going with http/1.1 and keepalive,
-        * we have to give fake content metadata so the
-        * client knows we completed the transaction and
-        * it can do the redirect...
-        */
-       if (lws_add_http_header_by_token(wsi,
-                       WSI_TOKEN_HTTP_CONTENT_TYPE,
-                       (unsigned char *)"text/html", 9,
-                       p, end))
-               return -1;
-       if (lws_add_http_header_by_token(wsi,
-                       WSI_TOKEN_HTTP_CONTENT_LENGTH,
-                       (unsigned char *)"0", 1, p, end))
-               return -1;
-
-       if (lws_finalize_http_header(wsi, p, end))
-               return -1;
-
-       n = lws_write(wsi, start, *p - start,
-                       LWS_WRITE_HTTP_HEADERS);
-
-       return n;
-}
diff --git a/lib/hpack.c b/lib/hpack.c
deleted file mode 100644 (file)
index a205cbc..0000000
+++ /dev/null
@@ -1,704 +0,0 @@
-/*
- * lib/hpack.c
- *
- * Copyright (C) 2014 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-/*
- * Official static header table for HPACK
- *        +-------+-----------------------------+---------------+
-          | 1     | :authority                  |               |
-          | 2     | :method                     | GET           |
-          | 3     | :method                     | POST          |
-          | 4     | :path                       | /             |
-          | 5     | :path                       | /index.html   |
-          | 6     | :scheme                     | http          |
-          | 7     | :scheme                     | https         |
-          | 8     | :status                     | 200           |
-          | 9     | :status                     | 204           |
-          | 10    | :status                     | 206           |
-          | 11    | :status                     | 304           |
-          | 12    | :status                     | 400           |
-          | 13    | :status                     | 404           |
-          | 14    | :status                     | 500           |
-          | 15    | accept-charset              |               |
-          | 16    | accept-encoding             | gzip, deflate |
-          | 17    | accept-language             |               |
-          | 18    | accept-ranges               |               |
-          | 19    | accept                      |               |
-          | 20    | access-control-allow-origin |               |
-          | 21    | age                         |               |
-          | 22    | allow                       |               |
-          | 23    | authorization               |               |
-          | 24    | cache-control               |               |
-          | 25    | content-disposition         |               |
-          | 26    | content-encoding            |               |
-          | 27    | content-language            |               |
-          | 28    | content-length              |               |
-          | 29    | content-location            |               |
-          | 30    | content-range               |               |
-          | 31    | content-type                |               |
-          | 32    | cookie                      |               |
-          | 33    | date                        |               |
-          | 34    | etag                        |               |
-          | 35    | expect                      |               |
-          | 36    | expires                     |               |
-          | 37    | from                        |               |
-          | 38    | host                        |               |
-          | 39    | if-match                    |               |
-          | 40    | if-modified-since           |               |
-          | 41    | if-none-match               |               |
-          | 42    | if-range                    |               |
-          | 43    | if-unmodified-since         |               |
-          | 44    | last-modified               |               |
-          | 45    | link                        |               |
-          | 46    | location                    |               |
-          | 47    | max-forwards                |               |
-          | 48    | proxy-authenticate          |               |
-          | 49    | proxy-authorization         |               |
-          | 50    | range                       |               |
-          | 51    | referer                     |               |
-          | 52    | refresh                     |               |
-          | 53    | retry-after                 |               |
-          | 54    | server                      |               |
-          | 55    | set-cookie                  |               |
-          | 56    | strict-transport-security   |               |
-          | 57    | transfer-encoding           |               |
-          | 58    | user-agent                  |               |
-          | 59    | vary                        |               |
-          | 60    | via                         |               |
-          | 61    | www-authenticate            |               |
-          +-------+-----------------------------+---------------+
-*/
-
-static const unsigned char static_token[] = {
-       0,
-       WSI_TOKEN_HTTP_COLON_AUTHORITY,
-       WSI_TOKEN_HTTP_COLON_METHOD,
-       WSI_TOKEN_HTTP_COLON_METHOD,
-       WSI_TOKEN_HTTP_COLON_PATH,
-       WSI_TOKEN_HTTP_COLON_PATH,
-       WSI_TOKEN_HTTP_COLON_SCHEME,
-       WSI_TOKEN_HTTP_COLON_SCHEME,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_COLON_STATUS,
-       WSI_TOKEN_HTTP_ACCEPT_CHARSET,
-       WSI_TOKEN_HTTP_ACCEPT_ENCODING,
-       WSI_TOKEN_HTTP_ACCEPT_LANGUAGE,
-       WSI_TOKEN_HTTP_ACCEPT_RANGES,
-       WSI_TOKEN_HTTP_ACCEPT,
-       WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN,
-       WSI_TOKEN_HTTP_AGE,
-       WSI_TOKEN_HTTP_ALLOW,
-       WSI_TOKEN_HTTP_AUTHORIZATION,
-       WSI_TOKEN_HTTP_CACHE_CONTROL,
-       WSI_TOKEN_HTTP_CONTENT_DISPOSITION,
-       WSI_TOKEN_HTTP_CONTENT_ENCODING,
-       WSI_TOKEN_HTTP_CONTENT_LANGUAGE,
-       WSI_TOKEN_HTTP_CONTENT_LENGTH,
-       WSI_TOKEN_HTTP_CONTENT_LOCATION,
-       WSI_TOKEN_HTTP_CONTENT_RANGE,
-       WSI_TOKEN_HTTP_CONTENT_TYPE,
-       WSI_TOKEN_HTTP_COOKIE,
-       WSI_TOKEN_HTTP_DATE,
-       WSI_TOKEN_HTTP_ETAG,
-       WSI_TOKEN_HTTP_EXPECT,
-       WSI_TOKEN_HTTP_EXPIRES,
-       WSI_TOKEN_HTTP_FROM,
-       WSI_TOKEN_HOST,
-       WSI_TOKEN_HTTP_IF_MATCH,
-       WSI_TOKEN_HTTP_IF_MODIFIED_SINCE,
-       WSI_TOKEN_HTTP_IF_NONE_MATCH,
-       WSI_TOKEN_HTTP_IF_RANGE,
-       WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE,
-       WSI_TOKEN_HTTP_LAST_MODIFIED,
-       WSI_TOKEN_HTTP_LINK,
-       WSI_TOKEN_HTTP_LOCATION,
-       WSI_TOKEN_HTTP_MAX_FORWARDS,
-       WSI_TOKEN_HTTP_PROXY_AUTHENTICATE,
-       WSI_TOKEN_HTTP_PROXY_AUTHORIZATION,
-       WSI_TOKEN_HTTP_RANGE,
-       WSI_TOKEN_HTTP_REFERER,
-       WSI_TOKEN_HTTP_REFRESH,
-       WSI_TOKEN_HTTP_RETRY_AFTER,
-       WSI_TOKEN_HTTP_SERVER,
-       WSI_TOKEN_HTTP_SET_COOKIE,
-       WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY,
-       WSI_TOKEN_HTTP_TRANSFER_ENCODING,
-       WSI_TOKEN_HTTP_USER_AGENT,
-       WSI_TOKEN_HTTP_VARY,
-       WSI_TOKEN_HTTP_VIA,
-       WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
-};
-
-/* some of the entries imply values as well as header names */
-
-static const char * const http2_canned[] = {
-       "",
-       "",
-       "GET",
-       "POST",
-       "/",
-       "/index.html",
-       "http",
-       "https",
-       "200",
-       "204",
-       "206",
-       "304",
-       "400",
-       "404",
-       "500",
-       "",
-       "gzip, deflate"
-};
-
-/* see minihuf.c */
-
-#include "huftable.h"
-
-static int huftable_decode(int pos, char c)
-{
-       int q = pos + !!c;
-
-       if (lextable_terms[q >> 3] & (1 << (q & 7))) /* terminal */
-               return lextable[q] | 0x8000;
-
-       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;
-
-       if (!hdr_token_idx) {
-               lwsl_err("%s: zero hdr_token_idx\n", __func__);
-               return 1;
-       }
-
-       if (ah->nfrag >= ARRAY_SIZE(ah->frag_index)) {
-               lwsl_err("%s: frag index %d too big\n", __func__, ah->nfrag);
-               return 1;
-       }
-
-       ah->frags[ah->nfrag].offset = ah->pos;
-       ah->frags[ah->nfrag].len = 0;
-       ah->frags[ah->nfrag].nfrag = 0;
-
-       ah->frag_index[hdr_token_idx] = ah->nfrag;
-
-       return 0;
-}
-
-static int lws_frag_append(struct lws *wsi, unsigned char c)
-{
-       struct allocated_headers * ah = wsi->u.http2.http.ah;
-
-       ah->data[ah->pos++] = c;
-       ah->frags[ah->nfrag].len++;
-
-       return ah->pos >= wsi->context->max_http_header_data;
-}
-
-static int lws_frag_end(struct lws *wsi)
-{
-       if (lws_frag_append(wsi, 0))
-               return 1;
-
-       wsi->u.http2.http.ah->nfrag++;
-       return 0;
-}
-
-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);
-}
-
-static int
-lws_token_from_index(struct lws *wsi, int index, char **arg, int *len)
-{
-       struct hpack_dynamic_table *dyn;
-
-       /* dynamic table only belongs to network wsi */
-
-       wsi = lws_http2_get_network_wsi(wsi);
-
-       dyn = wsi->u.http2.hpack_dyn_table;
-
-       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;
-}
-
-static int
-lws_hpack_add_dynamic_header(struct lws *wsi, int token, char *arg, int len)
-{
-       struct hpack_dynamic_table *dyn;
-       int ret = 1;
-
-       wsi = lws_http2_get_network_wsi(wsi);
-       dyn = wsi->u.http2.hpack_dyn_table;
-
-       if (!dyn) {
-               dyn = lws_zalloc(sizeof(*dyn));
-               if (!dyn)
-                       return 1;
-               wsi->u.http2.hpack_dyn_table = dyn;
-
-               dyn->args = lws_malloc(1024);
-               if (!dyn->args)
-                       goto bail1;
-               dyn->args_length = 1024;
-               dyn->entries = lws_malloc(sizeof(dyn->entries[0]) * 20);
-               if (!dyn->entries)
-                       goto bail2;
-               dyn->num_entries = 20;
-       }
-
-       if (dyn->next == dyn->num_entries)
-               return 1;
-
-       if (dyn->args_length - dyn->pos < len)
-               return 1;
-
-       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;
-
-       lwsl_info("%s: added dynamic hdr %d, token %d (%s), len %d\n",
-                 __func__, dyn->next, token, lws_token_to_string(token), len);
-
-       dyn->pos += len;
-       dyn->next++;
-
-       return 0;
-
-bail2:
-       lws_free(dyn->args);
-bail1:
-       lws_free(dyn);
-       wsi->u.http2.hpack_dyn_table = NULL;
-
-       return ret;
-}
-
-static int lws_write_indexed_hdr(struct lws *wsi, int idx)
-{
-       const char *p;
-       int tok = lws_token_from_index(wsi, idx, NULL, 0);
-
-       lwsl_info("writing indexed hdr %d (tok %d '%s')\n", idx, tok,
-                 lws_token_to_string(tok));
-
-       if (lws_frag_start(wsi, tok))
-               return 1;
-
-       if (idx < ARRAY_SIZE(http2_canned)) {
-               p = http2_canned[idx];
-               while (*p)
-                       if (lws_frag_append(wsi, *p++))
-                               return 1;
-       }
-       if (lws_frag_end(wsi))
-               return 1;
-
-       lws_dump_header(wsi, tok);
-
-       return 0;
-}
-
-int lws_hpack_interpret(struct lws *wsi, unsigned char c)
-{
-       unsigned int prev;
-       unsigned char c1;
-       int n;
-
-       lwsl_debug("   state %d\n", wsi->u.http2.hpack);
-
-       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;
-
-       case HPKS_TYPE:
-
-               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 */
-                       /* 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;
-                       if ((c & 0x7f) == 0x7f) {
-                               wsi->u.http2.hpack_len = c & 0x7f;
-                               wsi->u.http2.hpack_m = 0;
-                               wsi->u.http2.hpack = HPKS_IDX_EXT;
-                               break;
-                       }
-                       lwsl_debug("HKPS_TYPE: %d\n", c & 0x7f);
-                       if (lws_write_indexed_hdr(wsi, c & 0x7f))
-                               return 1;
-                       /* stay at same state */
-                       break;
-               }
-               if (c & 0x40) { /* literal header incr idx */
-                       /*
-                        * [possibly-extended hdr idx (6) | new literal hdr name]
-                        * H + possibly-extended 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;
-                               break;
-                       }
-                       /* indexed name */
-                       wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_6_VALUE_INCR;
-                       if ((c & 0x3f) == 0x3f) {
-                               wsi->u.http2.hpack_len = c & 0x3f;
-                               wsi->u.http2.hpack_m = 0;
-                               wsi->u.http2.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;
-                       break;
-               }
-               switch(c & 0xf0) {
-               case 0x10: /* literal header never index */
-               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;
-                               break;
-                       }
-                       //lwsl_debug("indexed\n");
-                       /* indexed name */
-                       wsi->u.http2.hpack_type = HPKT_INDEXED_HDR_4_VALUE;
-                       wsi->u.http2.header_index = 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;
-                               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;
-                       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;
-                               break;
-                       }
-                       lws_hpack_update_table_size(wsi, c & 0x1f);
-                       /* stay at HPKS_TYPE state */
-                       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;
-                       }
-               }
-               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;
-                       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;
-
-               break;
-
-       case HPKS_DATA:
-               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);
-                               c <<= 1;
-                               if (wsi->u.http2.hpack_pos == 0xffff)
-                                       return 1;
-                               if (!(wsi->u.http2.hpack_pos & 0x8000))
-                                       continue;
-                               c1 = wsi->u.http2.hpack_pos & 0x7fff;
-                               wsi->u.http2.hpack_pos = 0;
-
-                               if (!c1 && prev == HUFTABLE_0x100_PREV)
-                                       ; /* EOT */
-                       } else {
-                               n = 8;
-                               c1 = c;
-                       }
-                       if (wsi->u.http2.value) { /* value */
-                               if (wsi->u.http2.header_index)
-                                       if (lws_frag_append(wsi, c1))
-                                               return 1;
-                       } else { /* name */
-                               if (lws_parse(wsi, c1))
-                                       return 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;
-                       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;
-                       }
-               }
-               break;
-       case HKPS_OPT_DISCARD_PADDING:
-               lwsl_info("eating padding %x\n", c);
-               if (! --wsi->u.http2.padding)
-                       wsi->u.http2.hpack = HPKS_TYPE;
-               break;
-       }
-
-       return 0;
-}
-
-static int lws_http2_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;
-
-       num -= mask;
-       while (num >= 128) {
-               *((*p)++) = 0x80 | (num & 0x7f);
-               if (*p >= end)
-                       return 1;
-               num >>= 7;
-       }
-
-       return 0;
-}
-
-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);
-
-       len = strlen((char *)name);
-       if (len)
-               if (name[len - 1] == ':')
-                       len--;
-
-       if (end - *p < len + length + 8)
-               return 1;
-
-       *((*p)++) = 0; /* not indexed, literal name */
-
-       **p = 0; /* non-HUF */
-       if (lws_http2_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))
-               return 1;
-
-       memcpy(*p, value, length);
-       *p += length;
-
-       return 0;
-}
-
-int lws_add_http2_header_by_token(struct lws *wsi, enum lws_token_indexes token,
-                                 const unsigned char *value, int length,
-                                 unsigned char **p, unsigned char *end)
-{
-       const unsigned char *name;
-
-       name = lws_token_to_string(token);
-       if (!name)
-               return 1;
-
-       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)
-{
-       unsigned char status[10];
-       int n;
-
-       wsi->u.http2.send_END_STREAM = !!(code >= 400);
-
-       n = sprintf((char *)status, "%u", code);
-       if (lws_add_http2_header_by_token(wsi, WSI_TOKEN_HTTP_COLON_STATUS,
-                                         status, n, p, end))
-
-               return 1;
-
-       return 0;
-}
diff --git a/lib/http2.c b/lib/http2.c
deleted file mode 100644 (file)
index 0bde700..0000000
+++ /dev/null
@@ -1,536 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-
-#include "private-libwebsockets.h"
-
-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,
-}};
-
-
-void lws_http2_init(struct http2_settings *settings)
-{
-       memcpy(settings, lws_http2_default_settings.setting, sizeof(*settings));
-}
-
-struct lws *
-lws_http2_wsi_from_id(struct lws *wsi, unsigned int sid)
-{
-       do {
-               if (wsi->u.http2.my_stream_id == sid)
-                       return wsi;
-
-               wsi = wsi->u.http2.next_child_wsi;
-       } while (wsi);
-
-       return NULL;
-}
-
-struct lws *
-lws_create_server_child_wsi(struct lws_vhost *vhost, 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);
-
-       return NULL;
-}
-
-int lws_remove_server_child_wsi(struct lws_context *context, struct lws *wsi)
-{
-       struct lws **w = &wsi->u.http2.parent_wsi;
-       do {
-               if (*w == wsi) {
-                       *w = wsi->u.http2.next_child_wsi;
-                       (wsi->u.http2.parent_wsi)->u.http2.child_count--;
-                       return 0;
-               }
-
-               w = &((*w)->u.http2.next_child_wsi);
-       } while (*w);
-
-       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)
-{
-       unsigned int a, b;
-
-       if (!len)
-               return 0;
-
-       if (len < LWS_HTTP2_SETTINGS_LENGTH)
-               return 1;
-
-       while (len >= LWS_HTTP2_SETTINGS_LENGTH) {
-               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);
-               }
-               len -= LWS_HTTP2_SETTINGS_LENGTH;
-               buf += LWS_HTTP2_SETTINGS_LENGTH;
-       }
-
-       if (len)
-               return 1;
-
-       return 0;
-}
-
-struct lws *lws_http2_get_network_wsi(struct lws *wsi)
-{
-       while (wsi->u.http2.parent_wsi)
-               wsi = wsi->u.http2.parent_wsi;
-
-       return wsi;
-}
-
-int lws_http2_frame_write(struct lws *wsi, int type, int flags,
-                         unsigned int sid, unsigned int len, unsigned char *buf)
-{
-       struct lws *wsi_eff = lws_http2_get_network_wsi(wsi);
-       unsigned char *p = &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH];
-       int n;
-
-       *p++ = len >> 16;
-       *p++ = len >> 8;
-       *p++ = len;
-       *p++ = type;
-       *p++ = flags;
-       *p++ = sid >> 24;
-       *p++ = sid >> 16;
-       *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);
-
-       if (type == LWS_HTTP2_FRAME_TYPE_DATA) {
-               if (wsi->u.http2.tx_credit < 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;
-       }
-
-       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;
-
-       return n;
-}
-
-static void lws_http2_settings_write(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];
-}
-
-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)
-{
-       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;
-       int n, m = 0;
-
-       lwsl_debug("%s: %p: %d\n", __func__, wsi, wsi->pps);
-
-       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);
-                       }
-               n = lws_http2_frame_write(wsi, LWS_HTTP2_FRAME_TYPE_SETTINGS,
-                                         0, LWS_HTTP2_STREAM_ID_MASTER, m,
-                                         &settings[LWS_PRE]);
-               if (n != m) {
-                       lwsl_info("send %d %d\n", n, m);
-                       return 1;
-               }
-               break;
-       case LWS_PPS_HTTP2_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]);
-               if (n) {
-                       lwsl_err("ack tells %d\n", n);
-                       return 1;
-               }
-               /* 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");
-                               break;
-                       }
-
-                       /*
-                        * we need to treat the headers from this upgrade
-                        * as the first job.  These need to get
-                        * shifted to stream ID 1
-                        */
-                       lwsl_info("%s: setting up sid 1\n", __func__);
-
-                       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;
-                       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;
-                       /* demanded by HTTP2 */
-                       swsi->u.http2.END_STREAM = 1;
-                       lwsl_info("servicing initial http request\n");
-                       return lws_http_action(swsi);
-               }
-               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]);
-               if (n != 8) {
-                       lwsl_info("send %d %d\n", n, m);
-                       return 1;
-               }
-               break;
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-struct lws * lws_http2_get_nth_child(struct lws *wsi, int n)
-{
-       do {
-               wsi = wsi->u.http2.next_child_wsi;
-               if (!wsi)
-                       return NULL;
-       } while (n--);
-
-       return wsi;
-}
diff --git a/lib/jose/README.md b/lib/jose/README.md
new file mode 100644 (file)
index 0000000..b9fd538
--- /dev/null
@@ -0,0 +1,79 @@
+# JOSE support
+
+JOSE is a set of web standards aimed at encapsulating crypto
+operations flexibly inside JSON objects.
+
+Lws provides lightweight apis to performs operations on JWK, JWS and JWE
+independent of the tls backend in use.  The JSON parsing is handled by the lws
+lejp stream parser.
+
+|Part|RFC|Function|
+|---|---|---|
+|JWS|[RFC7515](https://tools.ietf.org/html/rfc7515)|JSON Web Signatures|
+|JWE|[RFC7516](https://tools.ietf.org/html/rfc7516)|JSON Web Encryption|
+|JWK|[RFC7517](https://tools.ietf.org/html/rfc7517)|JSON Web Keys|
+|JWA|[RFC7518](https://tools.ietf.org/html/rfc7518)|JSON Web Algorithms|
+
+JWA is a set of recommendations for which combinations of algorithms
+are deemed desirable and secure, which implies what must be done for
+useful implementations of JWS, JWE and JWK.
+
+## Supported algorithms
+
+### Supported keys
+
+ - All RFC7517 / JWK forms: octet, RSA and EC
+
+ - singleton and keys[] arrays of keys supported
+
+### Symmetric ciphers
+
+ - All common AES varaiants: CBC, CFB128, CFB8, CTR, EVB, OFB, KW and XTS
+
+### Asymmetric ciphers
+
+ - RSA
+
+ - EC (P-256, P-384 and P-521 JWA curves)
+
+### Payload auth and crypt
+
+ - AES_128_CBC_HMAC_SHA_256
+ - AES_192_CBC_HMAC_SHA_384
+ - AES_256_CBC_HMAC_SHA_512
+ - AES_128_GCM
+
+For the required and recommended asymmetric algorithms, support currently
+looks like this
+
+|JWK kty|JWA|lws|
+|---|---|---|
+|EC|Recommended+|yes|
+|RSA|Required|yes|
+|oct|Required|yes|
+
+|JWE alg|JWA|lws|
+|---|---|---|
+|RSA1_5|Recommended-|yes|
+|RSA-OAEP|Recommended+|no|
+|ECDH-ES|Recommended+|no|
+
+|JWS alg|JWA|lws|
+|---|---|---|
+|HS256|Required|yes|
+|RS256|Recommended+|yes|
+|ES256|Recommended|yes|
+
+## Minimal Example tools
+
+[JWK](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwk)
+
+[JWS](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jws)
+
+[JWE](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwe)
+
+## API tests
+
+See `./minimal-examples/api-tests/api-test-jose/` for example test code.
+The tests are built and confirmed during CI.
+
diff --git a/lib/jose/jwe/enc/aescbc.c b/lib/jose/jwe/enc/aescbc.c
new file mode 100644 (file)
index 0000000..54cabc7
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * JWE code for payload encrypt / decrypt using aescbc
+ *
+ */
+#include "core/private.h"
+#include "jose/jwe/private.h"
+
+int
+lws_jwe_encrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *cek,
+                      uint8_t *aad, int aad_len)
+{
+       int n, hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type);
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_gencrypto_keyelem el;
+       struct lws_genhmac_ctx hmacctx;
+       struct lws_genaes_ctx aesctx;
+       uint8_t al[8];
+
+       /* Caller must have prepared space for the results */
+
+       if (jwe->jws.map.len[LJWE_ATAG] != (unsigned int)hlen / 2) {
+               lwsl_notice("%s: expected tag len %d, got %d\n", __func__,
+                           hlen / 2, jwe->jws.map.len[LJWE_ATAG]);
+               return -1;
+       }
+
+       if (jwe->jws.map.len[LJWE_IV] != 16) {
+               lwsl_notice("expected iv len %d, got %d\n", 16,
+                               jwe->jws.map.len[LJWE_IV]);
+               return -1;
+       }
+
+       /* first create the authentication hmac */
+
+       /* JWA Section 5.2.2.1
+        *
+        * 1.  The secondary keys MAC_KEY and ENC_KEY are generated from the
+        *     input key K as follows.  Each of these two keys is an octet
+        *     string.
+        *
+        *       MAC_KEY consists of the initial MAC_KEY_LEN octets of K, in
+        *        order.
+        *       ENC_KEY consists of the final ENC_KEY_LEN octets of K, in
+        *        order.
+        */
+
+       /*
+        *    2.  The IV used is a 128-bit value generated randomly or
+        *        pseudorandomly for use in the cipher.
+        */
+       lws_get_random(jwe->jws.context, (void *)jwe->jws.map.buf[LJWE_IV], 16);
+
+       /*
+        *  3.  The plaintext is CBC encrypted using PKCS #7 padding using
+        *      ENC_KEY as the key and the IV.  We denote the ciphertext output
+        *      from this step as E.
+        */
+
+       /* second half is the AES ENC_KEY */
+       el.buf = cek + (hlen / 2);
+       el.len = hlen / 2;
+
+       if (lws_genaes_create(&aesctx, LWS_GAESO_ENC, LWS_GAESM_CBC, &el,
+                             LWS_GAESP_NO_PADDING, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+
+               return -1;
+       }
+
+       /*
+        * the plaintext gets delivered to us in LJWE_CTXT, this replaces
+        * the plaintext there with the same amount of ciphertext
+        */
+       n = lws_genaes_crypt(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            jwe->jws.map.len[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_IV],
+                            NULL, NULL, 16);
+       lws_genaes_destroy(&aesctx, NULL, 0);
+       if (n) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               return -1;
+       }
+
+       /*
+        * 4.  The octet string AL is equal to the number of bits in the
+        *     Additional Authenticated Data A expressed as a 64-bit unsigned
+        *     big-endian integer.
+        */
+       lws_jwe_be64(aad_len * 8, al);
+
+       /* first half of the CEK is the MAC key */
+       if (lws_genhmac_init(&hmacctx, jwe->jose.enc_alg->hmac_type,
+                               cek, hlen / 2))
+               return -1;
+
+       /*
+        *    5.  A message Authentication Tag T is computed by applying HMAC
+        *    [RFC2104] to the following data, in order:
+        *
+        *     - the Additional Authenticated Data A,
+        *     - the Initialization Vector IV,
+        *     - the ciphertext E computed in the previous step, and
+        *     - the octet string AL defined above.
+        *
+        *    The string MAC_KEY is used as the MAC key.  We denote the output
+        *    of the MAC computed in this step as M.  The first T_LEN octets of
+        *    M are used as T.
+        */
+
+       if (lws_genhmac_update(&hmacctx, aad, aad_len) ||
+           lws_genhmac_update(&hmacctx, jwe->jws.map.buf[LJWE_IV],
+                              LWS_JWE_AES_IV_BYTES) ||
+           /* since we encrypted it, this is the ciphertext */
+           lws_genhmac_update(&hmacctx,
+                              (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                                         jwe->jws.map.len[LJWE_CTXT]) ||
+           lws_genhmac_update(&hmacctx, al, 8)) {
+               lwsl_err("%s: hmac computation failed\n", __func__);
+               lws_genhmac_destroy(&hmacctx, NULL);
+               return -1;
+       }
+
+       if (lws_genhmac_destroy(&hmacctx, digest)) {
+               lwsl_err("%s: problem destroying hmac\n", __func__);
+               return -1;
+       }
+
+       /* create tag */
+       memcpy((void *)jwe->jws.map.buf[LJWE_ATAG], digest, hlen / 2);
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
+
+int
+lws_jwe_auth_and_decrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *enc_cek,
+                               uint8_t *aad, int aad_len)
+{
+       int n, hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type);
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_gencrypto_keyelem el;
+       struct lws_genhmac_ctx hmacctx;
+       struct lws_genaes_ctx aesctx;
+       uint8_t al[8];
+
+       /* Some sanity checks on what came in */
+
+       if (jwe->jws.map.len[LJWE_ATAG] != (unsigned int)hlen / 2) {
+               lwsl_notice("%s: expected tag len %d, got %d\n", __func__,
+                               hlen / 2, jwe->jws.map.len[LJWE_ATAG]);
+               return -1;
+       }
+
+       if (jwe->jws.map.len[LJWE_IV] != 16) {
+               lwsl_notice("expected iv len %d, got %d\n", 16,
+                               jwe->jws.map.len[LJWE_IV]);
+               return -1;
+       }
+
+       /* Prepare to check authentication
+        *
+        * AAD is the b64 JOSE header.
+        *
+        * The octet string AL, which is the number of bits in AAD expressed as
+        * a big-endian 64-bit unsigned integer is:
+        *
+        * [0, 0, 0, 0, 0, 0, 1, 152]
+        *
+        * Concatenate the AAD, the Initialization Vector, the ciphertext, and
+        * the AL value.
+        *
+        */
+
+       lws_jwe_be64(aad_len * 8, al);
+
+       /* first half of enc_cek is the MAC key */
+       if (lws_genhmac_init(&hmacctx, jwe->jose.enc_alg->hmac_type, enc_cek,
+                            hlen / 2)) {
+               lwsl_err("%s: lws_genhmac_init fail\n", __func__);
+               return -1;
+       }
+
+       if (lws_genhmac_update(&hmacctx, aad, aad_len) ||
+           lws_genhmac_update(&hmacctx, (uint8_t *)jwe->jws.map.buf[LJWE_IV],
+                                        jwe->jws.map.len[LJWE_IV]) ||
+           lws_genhmac_update(&hmacctx, (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                                        jwe->jws.map.len[LJWE_CTXT]) ||
+           lws_genhmac_update(&hmacctx, al, 8)) {
+               lwsl_err("%s: hmac computation failed\n", __func__);
+               lws_genhmac_destroy(&hmacctx, NULL);
+               return -1;
+       }
+
+       if (lws_genhmac_destroy(&hmacctx, digest)) {
+               lwsl_err("%s: problem destroying hmac\n", __func__);
+               return -1;
+       }
+
+       /* first half of digest is the auth tag */
+
+       if (lws_timingsafe_bcmp(digest, jwe->jws.map.buf[LJWE_ATAG], hlen / 2)) {
+               lwsl_err("%s: auth failed: hmac tag (%d) != ATAG (%d)\n",
+                        __func__, hlen / 2, jwe->jws.map.len[LJWE_ATAG]);
+               lwsl_hexdump_notice(jwe->jws.map.buf[LJWE_ATAG], hlen / 2);
+               lwsl_hexdump_notice(digest, hlen / 2);
+               return -1;
+       }
+
+       /* second half of enc cek is the CEK KEY */
+       el.buf = enc_cek + (hlen / 2);
+       el.len = hlen / 2;
+
+       if (lws_genaes_create(&aesctx, LWS_GAESO_DEC, LWS_GAESM_CBC,
+                             &el, LWS_GAESP_NO_PADDING, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+
+               return -1;
+       }
+
+       n = lws_genaes_crypt(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            jwe->jws.map.len[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_IV], NULL, NULL, 16);
+       n |= lws_genaes_destroy(&aesctx, NULL, 0);
+       if (n) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               return -1;
+       }
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
+
diff --git a/lib/jose/jwe/enc/aesgcm.c b/lib/jose/jwe/enc/aesgcm.c
new file mode 100644 (file)
index 0000000..4e93878
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * JWE code related to aes gcm
+ *
+ */
+#include "core/private.h"
+#include "jose/jwe/private.h"
+
+/*
+ * NOTICE this is AESGCM content encryption, it's not AES GCM key wrapping
+ *
+ *
+ * This section defines the specifics of performing authenticated
+ * encryption with AES in Galois/Counter Mode (GCM) ([AES] and
+ * [NIST.800-38D]).
+ *
+ * The CEK is used as the encryption key.
+ *
+ * Use of an IV of size 96 bits is REQUIRED with this algorithm.
+ *
+ * The requested size of the Authentication Tag output MUST be 128 bits,
+ * regardless of the key size.
+ *
+ * For decrypt: decrypt the KEK, then decrypt the payload
+ *
+ * For encrypt: encrypt the payload, then encrypt the KEK
+ */
+
+/*
+ * encrypting... enc_cek is unencrypted
+ */
+
+int
+lws_jwe_encrypt_gcm(struct lws_jwe *jwe,
+                   uint8_t *enc_cek, uint8_t *aad, int aad_len)
+{
+       struct lws_gencrypto_keyelem el;
+       struct lws_genaes_ctx aesctx;
+       size_t ivs = LWS_AESGCM_IV;
+       int n;
+
+       /* Some sanity checks on what came in */
+
+       /* MUST be 128-bit for all sizes */
+       if (jwe->jws.map.len[LJWE_ATAG] != LWS_AESGCM_TAG) {
+               lwsl_notice("%s: AESGCM tag size must be 128b, got %d\n",
+                               __func__, jwe->jws.map.len[LJWE_ATAG]);
+               return -1;
+       }
+
+       if (jwe->jws.map.len[LJWE_IV] != LWS_AESGCM_IV) { /* MUST be 96-bit */
+               lwsl_notice("%s: AESGCM IV must be 128b, got %d\n", __func__,
+                               jwe->jws.map.len[LJWE_IV]);
+               return -1;
+       }
+
+       /* EKEY is directly the CEK KEY */
+       el.buf = enc_cek;
+       el.len = jwe->jose.enc_alg->keybits_fixed / 8;
+
+       if (lws_genaes_create(&aesctx, LWS_GAESO_ENC, LWS_GAESM_GCM,
+                             &el, LWS_GAESP_NO_PADDING, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+
+               return -1;
+       }
+
+       /* aad */
+
+       n = lws_genaes_crypt(&aesctx, aad, aad_len, NULL,
+                            (uint8_t *)jwe->jws.map.buf[LJWE_IV],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_ATAG], &ivs,
+                            LWS_AESGCM_TAG);
+       if (n) {
+               lwsl_err("%s: lws_genaes_crypt aad failed\n", __func__);
+               return -1;
+       }
+
+       /* payload */
+       n = lws_genaes_crypt(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            jwe->jws.map.len[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_IV],
+                            NULL, &ivs,
+                            LWS_AESGCM_TAG);
+
+       n |= lws_genaes_destroy(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_ATAG],
+                               LWS_AESGCM_TAG);
+       if (n) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               return -1;
+       }
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
+
+int
+lws_jwe_auth_and_decrypt_gcm(struct lws_jwe *jwe,
+                            uint8_t *enc_cek, uint8_t *aad, int aad_len)
+{
+       struct lws_gencrypto_keyelem el;
+       struct lws_genaes_ctx aesctx;
+       size_t ivs = LWS_AESGCM_IV;
+       uint8_t tag[LWS_AESGCM_TAG];
+       int n;
+
+       /* Some sanity checks on what came in */
+
+       /* Tag MUST be 128-bit for all sizes */
+       if (jwe->jws.map.len[LJWE_ATAG] != LWS_AESGCM_TAG) {
+               lwsl_notice("%s: AESGCM tag size must be 128b, got %d\n",
+                               __func__, jwe->jws.map.len[LJWE_ATAG]);
+               return -1;
+       }
+
+       if (jwe->jws.map.len[LJWE_IV] != LWS_AESGCM_IV) { /* MUST be 96-bit */
+               lwsl_notice("%s: AESGCM IV must be 128b, got %d\n", __func__,
+                               jwe->jws.map.len[LJWE_IV]);
+               return -1;
+       }
+
+       /* EKEY is directly the CEK KEY */
+       el.buf = enc_cek;
+       el.len = jwe->jose.enc_alg->keybits_fixed / 8;
+
+       if (lws_genaes_create(&aesctx, LWS_GAESO_DEC, LWS_GAESM_GCM,
+                             &el, LWS_GAESP_NO_PADDING, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+
+               return -1;
+       }
+
+       n = lws_genaes_crypt(&aesctx, aad, aad_len,
+                            NULL,
+                            (uint8_t *)jwe->jws.map.buf[LJWE_IV],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_ATAG], &ivs, 16);
+       if (n) {
+               lwsl_err("%s: lws_genaes_crypt aad failed\n", __func__);
+               return -1;
+       }
+       n = lws_genaes_crypt(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            jwe->jws.map.len[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_CTXT],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_IV],
+                            (uint8_t *)jwe->jws.map.buf[LJWE_ATAG], &ivs, 16);
+
+       n |= lws_genaes_destroy(&aesctx, tag, sizeof(tag));
+       if (n) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               return -1;
+       }
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
diff --git a/lib/jose/jwe/enc/aeskw.c b/lib/jose/jwe/enc/aeskw.c
new file mode 100644 (file)
index 0000000..7d0b5a7
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * JWE code related to aeskw cbc
+ *
+ */
+#include "core/private.h"
+#include "jose/jwe/private.h"
+
+
+/*
+ * RFC3394 Key Wrap uses a 128-bit key, and bloats what it is wrapping by
+ * one 8-byte block.  So, if you had a 32 byte plaintext CEK to wrap, after
+ * wrapping it becomes a 40 byte wrapped, enciphered, key.
+ *
+ * The CEK comes in from and goes out in LJWE_EKEY.  So LJWE_EKEY length
+ * increases by 8 from calling this.
+ */
+
+int
+lws_jwe_encrypt_aeskw_cbc_hs(struct lws_jwe *jwe, char *temp, int *temp_len)
+{
+       struct lws_genaes_ctx aesctx;
+       /* we are wrapping a key, so size for the worst case after wrap */
+       uint8_t enc_cek[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES +
+                       LWS_JWE_RFC3394_OVERHEAD_BYTES];
+       int n, m, hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type),
+                        ot = *temp_len;
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_OCT) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       /* create a b64 version of the JOSE header, needed for hashing */
+
+       if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
+                                      temp + (ot - *temp_len), temp_len,
+                                      jwe->jws.map.buf[LJWE_JOSE],
+                                      jwe->jws.map.len[LJWE_JOSE]))
+               return -1;
+
+       /* Allocate temp space for ATAG and IV */
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_ATAG, temp + (ot - *temp_len),
+                                 temp_len, hlen / 2, 0))
+               return -1;
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_IV, temp + (ot - *temp_len),
+                                 temp_len, LWS_JWE_AES_IV_BYTES, 0))
+               return -1;
+
+       /* 1) Encrypt the payload...  */
+
+       /* the CEK is 256-bit in the example encrypted with a 128-bit key */
+
+       n = lws_jwe_encrypt_cbc_hs(jwe, (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                                  (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                                  jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt_cbc_hs failed\n", __func__);
+               return -1;
+       }
+
+       /* 2) Encrypt the JWE Encrypted Key: RFC3394 Key Wrap uses 64 bit blocks
+        *    and 128-bit input key*/
+
+       if (lws_genaes_create(&aesctx, LWS_GAESO_ENC, LWS_GAESM_KW,
+                             jwe->jws.jwk->e, 1, NULL)) {
+
+               lwsl_notice("%s: lws_genaes_create\n", __func__);
+               return -1;
+       }
+
+       /* tag size is determined by enc cipher key length */
+
+       n = lws_genaes_crypt(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                            jwe->jws.map.len[LJWE_EKEY], enc_cek, NULL, NULL, NULL,
+                            lws_gencrypto_bits_to_bytes(
+                                            jwe->jose.enc_alg->keybits_fixed));
+       m = lws_genaes_destroy(&aesctx, NULL, 0);
+       if (n < 0) {
+               lwsl_err("%s: encrypt cek fail\n", __func__);
+               return -1;
+       }
+       if (m < 0) {
+               lwsl_err("%s: lws_genaes_destroy fail\n", __func__);
+               return -1;
+       }
+
+       jwe->jws.map.len[LJWE_EKEY] += LWS_JWE_RFC3394_OVERHEAD_BYTES;
+       memcpy((uint8_t *)jwe->jws.map.buf[LJWE_EKEY], enc_cek,
+              jwe->jws.map.len[LJWE_EKEY]);
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
+
+
+int
+lws_jwe_auth_and_decrypt_aeskw_cbc_hs(struct lws_jwe *jwe)
+{
+       struct lws_genaes_ctx aesctx;
+       uint8_t enc_cek[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES +
+                       LWS_JWE_RFC3394_OVERHEAD_BYTES];
+       int n, m;
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_OCT) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       /* the CEK is 256-bit in the example encrypted with a 128-bit key */
+
+       if (jwe->jws.map.len[LJWE_EKEY] > sizeof(enc_cek))
+               return -1;
+
+       /* 1) Decrypt the JWE Encrypted Key to get the raw MAC / CEK */
+
+       if (lws_genaes_create(&aesctx, LWS_GAESO_DEC, LWS_GAESM_KW,
+                             jwe->jws.jwk->e, 1, NULL)) {
+
+               lwsl_notice("%s: lws_genaes_create\n", __func__);
+               return -1;
+       }
+
+       /*
+        * Decrypt the CEK into enc_cek
+        * tag size is determined by enc cipher key length */
+
+       n = lws_genaes_crypt(&aesctx, (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                            jwe->jws.map.len[LJWE_EKEY], enc_cek, NULL, NULL, NULL,
+                            lws_gencrypto_bits_to_bytes(
+                                            jwe->jose.enc_alg->keybits_fixed));
+       m = lws_genaes_destroy(&aesctx, NULL, 0);
+       if (n < 0) {
+               lwsl_err("%s: decrypt CEK fail\n", __func__);
+               return -1;
+       }
+       if (m < 0) {
+               lwsl_err("%s: lws_genaes_destroy fail\n", __func__);
+               return -1;
+       }
+
+       /* 2) Decrypt the payload */
+
+       n = lws_jwe_auth_and_decrypt_cbc_hs(jwe, enc_cek,
+                            (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                            jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt_cbc_hs failed\n",
+                               __func__);
+               return -1;
+       }
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
+
+
diff --git a/lib/jose/jwe/jwe-ecdh-es-aeskw.c b/lib/jose/jwe/jwe-ecdh-es-aeskw.c
new file mode 100644 (file)
index 0000000..4be1a56
--- /dev/null
@@ -0,0 +1,615 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * JWE code related to ecdh-es + Concat KDF and aes kw
+ *
+ */
+#include "core/private.h"
+#include "jose/jwe/private.h"
+
+/*
+ * From RFC7518 JWA
+ *
+ * 4.6.  Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static
+ *    (ECDH-ES)
+ *
+ * This section defines the specifics of key agreement with Elliptic
+ * Curve Diffie-Hellman Ephemeral Static [RFC6090], in combination with
+ * the Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A].  The
+ * key agreement result can be used in one of two ways:
+ *
+ * 1.  directly as the Content Encryption Key (CEK) for the "enc"
+ *     algorithm, in the Direct Key Agreement mode, or
+ *
+ * 2.  as a symmetric key used to wrap the CEK with the "A128KW",
+ *     "A192KW", or "A256KW" algorithms, in the Key Agreement with Key
+ *     Wrapping mode.
+ *
+ * A new ephemeral public key value MUST be generated for each key
+ * agreement operation.
+ *
+ * In Direct Key Agreement mode, the output of the Concat KDF MUST be a
+ * key of the same length as that used by the "enc" algorithm.  In this
+ * case, the empty octet sequence is used as the JWE Encrypted Key
+ * value.  The "alg" (algorithm) Header Parameter value "ECDH-ES" is
+ * used in the Direct Key Agreement mode.
+ *
+ * In Key Agreement with Key Wrapping mode, the output of the Concat KDF
+ * MUST be a key of the length needed for the specified key wrapping
+ * algorithm.  In this case, the JWE Encrypted Key is the CEK wrapped
+ * with the agreed-upon key.
+ *
+ * The following "alg" (algorithm) Header Parameter values are used to
+ * indicate that the JWE Encrypted Key is the result of encrypting the
+ * CEK using the result of the key agreement algorithm as the key
+ * encryption key for the corresponding key wrapping algorithm:
+ *
+ * +-----------------+-------------------------------------------------+
+ * | "alg" Param     | Key Management Algorithm                        |
+ * | Value           |                                                 |
+ * +-----------------+-------------------------------------------------+
+ * | ECDH-ES+A128KW  | ECDH-ES using Concat KDF and CEK wrapped with   |
+ * |                 | "A128KW"                                        |
+ * | ECDH-ES+A192KW  | ECDH-ES using Concat KDF and CEK wrapped with   |
+ * |                 | "A192KW"                                        |
+ * | ECDH-ES+A256KW  | ECDH-ES using Concat KDF and CEK wrapped with   |
+ * |                 | "A256KW"                                        |
+ * +-----------------+-------------------------------------------------+
+ *
+ * 4.6.1.  Header Parameters Used for ECDH Key Agreement
+ *
+ * The following Header Parameter names are used for key agreement as
+ * defined below.
+ *
+ * 4.6.1.1.  "epk" (Ephemeral Public Key) Header Parameter
+ *
+ * The "epk" (ephemeral public key) value created by the originator for
+ * the use in key agreement algorithms.  This key is represented as a
+ * JSON Web Key [JWK] public key value.  It MUST contain only public key
+ * parameters and SHOULD contain only the minimum JWK parameters
+ * necessary to represent the key; other JWK parameters included can be
+ * checked for consistency and honored, or they can be ignored.  This
+ * Header Parameter MUST be present and MUST be understood and processed
+ * by implementations when these algorithms are used.
+ *
+ * 4.6.1.2.  "apu" (Agreement PartyUInfo) Header Parameter
+ *
+ * The "apu" (agreement PartyUInfo) value for key agreement algorithms
+ * using it (such as "ECDH-ES"), represented as a base64url-encoded
+ * string.  When used, the PartyUInfo value contains information about
+ * the producer.  Use of this Header Parameter is OPTIONAL.  This Header
+ * Parameter MUST be understood and processed by implementations when
+ * these algorithms are used.
+ *
+ * 4.6.1.3.  "apv" (Agreement PartyVInfo) Header Parameter
+ *
+ * The "apv" (agreement PartyVInfo) value for key agreement algorithms
+ * using it (such as "ECDH-ES"), represented as a base64url encoded
+ * string.  When used, the PartyVInfo value contains information about
+ * the recipient.  Use of this Header Parameter is OPTIONAL.  This
+ * Header Parameter MUST be understood and processed by implementations
+ * when these algorithms are used.
+ *
+ * 4.6.2.  Key Derivation for ECDH Key Agreement
+ *
+ * The key derivation process derives the agreed-upon key from the
+ * shared secret Z established through the ECDH algorithm, per
+ * Section 6.2.2.2 of [NIST.800-56A].
+ *
+ * Key derivation is performed using the Concat KDF, as defined in
+ * Section 5.8.1 of [NIST.800-56A], where the Digest Method is SHA-256.
+ * The Concat KDF parameters are set as follows:
+ *
+ * Z
+ *    This is set to the representation of the shared secret Z as an
+ *    octet sequence.
+ *
+ * keydatalen
+ *    This is set to the number of bits in the desired output key.  For
+ *    "ECDH-ES", this is length of the key used by the "enc" algorithm.
+ *    For "ECDH-ES+A128KW", "ECDH-ES+A192KW", and "ECDH-ES+A256KW", this
+ *    is 128, 192, and 256, respectively.
+ *
+ * AlgorithmID
+ *    The AlgorithmID value is of the form Datalen || Data, where Data
+ *    is a variable-length string of zero or more octets, and Datalen is
+ *    a fixed-length, big-endian 32-bit counter that indicates the
+ *    length (in octets) of Data.  In the Direct Key Agreement case,
+ *    Data is set to the octets of the ASCII representation of the "enc"
+ *    Header Parameter value.  In the Key Agreement with Key Wrapping
+ *    case, Data is set to the octets of the ASCII representation of the
+ *    "alg" (algorithm) Header Parameter value.
+ *
+ * PartyUInfo
+ *    The PartyUInfo value is of the form Datalen || Data, where Data is
+ *    a variable-length string of zero or more octets, and Datalen is a
+ *    fixed-length, big-endian 32-bit counter that indicates the length
+ *    (in octets) of Data.  If an "apu" (agreement PartyUInfo) Header
+ *    Parameter is present, Data is set to the result of base64url
+ *    decoding the "apu" value and Datalen is set to the number of
+ *    octets in Data.  Otherwise, Datalen is set to 0 and Data is set to
+ *    the empty octet sequence.
+ *
+ * PartyVInfo
+ *    The PartyVInfo value is of the form Datalen || Data, where Data is
+ *    a variable-length string of zero or more octets, and Datalen is a
+ *    fixed-length, big-endian 32-bit counter that indicates the length
+ *    (in octets) of Data.  If an "apv" (agreement PartyVInfo) Header
+ *    Parameter is present, Data is set to the result of base64url
+ *    decoding the "apv" value and Datalen is set to the number of
+ *    octets in Data.  Otherwise, Datalen is set to 0 and Data is set to
+ *    the empty octet sequence.
+ *
+ * SuppPubInfo
+ *    This is set to the keydatalen represented as a 32-bit big-endian
+ *    integer.
+ *
+ * SuppPrivInfo
+ *    This is set to the empty octet sequence.
+ *
+ * Applications need to specify how the "apu" and "apv" Header
+ * Parameters are used for that application.  The "apu" and "apv" values
+ * MUST be distinct, when used.  Applications wishing to conform to
+ * [NIST.800-56A] need to provide values that meet the requirements of
+ * that document, e.g., by using values that identify the producer and
+ * consumer.  Alternatively, applications MAY conduct key derivation in
+ * a manner similar to "Diffie-Hellman Key Agreement Method" [RFC2631]:
+ * in that case, the "apu" parameter MAY either be omitted or represent
+ * a random 512-bit value (analogous to PartyAInfo in Ephemeral-Static
+ * mode in RFC 2631) and the "apv" parameter SHOULD NOT be present.
+ *
+ */
+
+
+/*
+ * - ECDH-ES[-variant] comes in the jose "alg" and just covers key agreement.
+ *   The "enc" action is completely separate and handled elsewhere.  However
+ *   the key size throughout is determined by the needs of the "enc" action.
+ *
+ * - The jwe->jws.jwk is the PEER - the encryption consumer's - public key.
+ *
+ * - The public part of the ephemeral key comes out in jose.jwk_ephemeral
+ *
+ * - Return shared secret length or < 0 for error
+ *
+ * - Unwrapped CEK in EKEY.  If any, wrapped CEK in "wrapped".
+ *
+ * - Caller responsibility to cleanse EKEY.
+ */
+
+static int
+lws_jwe_encrypt_ecdh(struct lws_jwe *jwe, char *temp, int *temp_len,
+                    uint8_t *cek)
+{
+       uint8_t shared_secret[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES],
+               derived[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
+       int m, n, ret = -1, ot = *temp_len, ss_len = sizeof(shared_secret),
+         //  kw_hlen = lws_genhash_size(jwe->jose.alg->hash_type),
+           enc_hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type),
+           ekbytes = 32; //jwe->jose.alg->keybits_fixed / 8;
+       struct lws_genec_ctx ecctx;
+       struct lws_jwk *ephem = &jwe->jose.recipient[jwe->recip].jwk_ephemeral;
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_EC) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       ephem->kty = LWS_GENCRYPTO_KTY_EC;
+       ephem->private_key = 1;
+
+       /* Generate jose.jwk_ephemeral on the peer public key curve */
+
+       if (lws_genecdh_create(&ecctx, jwe->jws.context, NULL))
+               goto bail;
+
+       /* ephemeral context gets random key on same curve as recip pubkey */
+       if (lws_genecdh_new_keypair(&ecctx, LDHS_OURS, (const char *)
+                               jwe->jws.jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
+                               ephem->e))
+               goto bail;
+
+       /* peer context gets js->jwk key */
+       if (lws_genecdh_set_key(&ecctx, jwe->jws.jwk->e, LDHS_THEIRS)) {
+               lwsl_err("%s: setting peer pubkey failed\n", __func__);
+               goto bail;
+       }
+
+       /* combine our ephemeral key and the peer pubkey to get the secret */
+
+       if (lws_genecdh_compute_shared_secret(&ecctx, shared_secret, &ss_len)) {
+               lwsl_notice("%s: lws_genecdh_compute_shared_secret failed\n",
+                               __func__);
+
+               goto bail;
+       }
+
+       /*
+        * The private part of the ephemeral key is finished with...
+        * cleanse and free it.  We need to keep the public part around so we
+        * can publish it with the JWE as "epk".
+        */
+
+       lws_explicit_bzero(ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].buf,
+                          ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].len);
+       lws_free_set_NULL(ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].buf);
+       ephem->e[LWS_GENCRYPTO_EC_KEYEL_D].len = 0;
+       ephem->private_key = 0;
+
+       /*
+        * Derive the CEK from the shared secret... amount of bytes written to
+        * derived matches bitcount in jwe->jose.enc_alg->keybits_fixed
+        *
+        * In Direct Key Agreement mode, the output of the Concat KDF MUST be a
+        * key of the same length as that used by the "enc" algorithm.
+        */
+
+       if (lws_jwa_concat_kdf(jwe,
+                       jwe->jose.alg->algtype_crypto == LWS_JOSE_ENCTYPE_NONE,
+                       derived, shared_secret, ss_len)) {
+               lwsl_notice("%s: lws_jwa_concat_kdf failed\n", __func__);
+
+               goto bail;
+       }
+
+       /* in P-521 case, we get a 66-byte shared secret for a 64-byte key */
+       if (ss_len < enc_hlen) {
+               lwsl_err("%s: concat KDF bad derived key len %d\n", __func__,
+                        ss_len);
+               goto bail;
+       }
+
+       /*
+        * For "ECDH-ES", that was it, and we use what we just wrapped in
+        * wrapped as the CEK without publishing it.
+        *
+        * For "ECDH-ES-AES[128,192,256]KW", we generate a new, random CEK and
+        * then wrap it using the key we just wrapped, and make the wrapped
+        * version available in EKEY.
+        */
+
+       if (jwe->jose.alg->algtype_crypto != LWS_JOSE_ENCTYPE_NONE) {
+               struct lws_gencrypto_keyelem el;
+               struct lws_genaes_ctx aesctx;
+
+               /* generate the actual CEK in cek */
+
+               if (lws_get_random(jwe->jws.context, cek, enc_hlen) != enc_hlen) {
+                       lwsl_err("Problem getting random\n");
+                       goto bail;
+               }
+
+               /* wrap with the derived key */
+
+               el.buf = derived;
+               el.len = enc_hlen / 2;
+
+               if (lws_genaes_create(&aesctx, LWS_GAESO_ENC, LWS_GAESM_KW, &el,
+                                       1, NULL)) {
+
+                       lwsl_notice("%s: lws_genaes_create\n", __func__);
+                       goto bail;
+               }
+
+               /* wrap CEK into EKEY */
+
+               n = lws_genaes_crypt(&aesctx, cek, enc_hlen,
+                                    (void *)jwe->jws.map.buf[LJWE_EKEY],
+                                    NULL, NULL, NULL, 0);
+               m = lws_genaes_destroy(&aesctx, NULL, 0);
+               if (n < 0) {
+                       lwsl_err("%s: encrypt cek fail\n", __func__);
+                       goto bail;
+               }
+               if (m < 0) {
+                       lwsl_err("%s: lws_genaes_destroy fail\n", __func__);
+                       goto bail;
+               }
+
+               jwe->jws.map.len[LJWE_EKEY] = enc_hlen + 8;
+
+               /* Wrapped CEK is in EKEY. Random CEK is in cek. */
+
+       } else /* direct derived CEK is in cek */
+               memcpy(cek, derived, enc_hlen);
+
+       /* rewrite the protected JOSE header to have the epk pieces */
+
+       jwe->jws.map.buf[LJWE_JOSE] = temp + (ot - *temp_len);
+
+       m = n = lws_snprintf(temp + (ot - *temp_len), *temp_len,
+                            "{\"alg\":\"%s\", \"enc\":\"%s\", \"epk\":",
+                            jwe->jose.alg->alg, jwe->jose.enc_alg->alg);
+       *temp_len -= n;
+
+       n = lws_jwk_export(ephem, 0, temp + (ot - *temp_len), temp_len);
+       if (n < 0) {
+               lwsl_err("%s: ephemeral export failed\n", __func__);
+               goto bail;
+       }
+       m += n;
+
+       n = lws_snprintf(temp + (ot - *temp_len), *temp_len, "}");
+       *temp_len -= n + 1;
+       m += n;
+       jwe->jws.map.len[LJWE_JOSE] = m;
+
+       /* create a b64 version of the JOSE header, needed later for AAD */
+
+       if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
+                                      temp + (ot - *temp_len), temp_len,
+                                      jwe->jws.map.buf[LJWE_JOSE],
+                                      jwe->jws.map.len[LJWE_JOSE]))
+               return -1;
+
+       ret = enc_hlen;
+
+bail:
+       lws_genec_destroy(&ecctx);
+
+       /* cleanse the shared secret (watch out for cek at parent too) */
+       lws_explicit_bzero(shared_secret, ekbytes);
+       lws_explicit_bzero(derived, ekbytes);
+
+       return ret;
+}
+
+int
+lws_jwe_encrypt_ecdh_cbc_hs(struct lws_jwe *jwe, char *temp, int *temp_len)
+{
+       int ss_len, // kw_hlen = lws_genhash_size(jwe->jose.alg->hash_type),
+           enc_hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type);
+       uint8_t cek[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
+       int ekbytes = jwe->jose.alg->keybits_fixed / 8;
+       int n, ot = *temp_len, ret = -1;
+
+       /* if we will produce an EKEY, make space for it */
+
+       if (jwe->jose.alg->algtype_crypto != LWS_JOSE_ENCTYPE_NONE) {
+               if (lws_jws_alloc_element(&jwe->jws.map, LJWE_EKEY,
+                                         temp + (ot - *temp_len), temp_len,
+                                         enc_hlen + 8, 0))
+                       goto bail;
+       }
+
+       /* decrypt the CEK */
+
+       ss_len = lws_jwe_encrypt_ecdh(jwe, temp + (ot - *temp_len), temp_len, cek);
+       if (ss_len < 0) {
+               lwsl_err("%s: lws_jwe_encrypt_ecdh failed\n", __func__);
+               return -1;
+       }
+
+       /* cek contains the unwrapped CEK.  EKEY may contain wrapped CEK */
+
+       /* make space for the payload encryption pieces */
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_ATAG,
+                                 temp + (ot - *temp_len),
+                                 temp_len, enc_hlen / 2, 0))
+               goto bail;
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_IV,
+                                 temp + (ot - *temp_len),
+                                 temp_len, LWS_JWE_AES_IV_BYTES, 0))
+               goto bail;
+
+       /* Perform the authenticated encryption on CTXT...
+        * ...the AAD is b64u(protected JOSE header) */
+
+       n = lws_jwe_encrypt_cbc_hs(jwe, cek,
+                                  (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                                  jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_notice("%s: lws_jwe_encrypt_cbc_hs failed\n", __func__);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       /* if fail or direct CEK, cleanse and remove EKEY */
+       if (ret || jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_NONE) {
+               if (jwe->jws.map.len[LJWE_EKEY])
+                       lws_explicit_bzero((void *)jwe->jws.map.buf[LJWE_EKEY],
+                                          jwe->jws.map.len[LJWE_EKEY]);
+               jwe->jws.map.len[LJWE_EKEY] = 0;
+       }
+
+       lws_explicit_bzero(cek, ekbytes);
+
+       return ret;
+}
+
+/*
+ * jwe->jws.jwk is recipient private key
+ *
+ * If kw mode, then EKEY is the wrapped CEK
+ *
+ *
+ */
+
+static int
+lws_jwe_auth_and_decrypt_ecdh(struct lws_jwe *jwe)
+{
+       uint8_t shared_secret[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES],
+               derived[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
+       int ekbytes = jwe->jose.enc_alg->keybits_fixed / 8,
+                     enc_hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type);
+       struct lws_genec_ctx ecctx;
+       int n, ret = -1, ss_len = sizeof(shared_secret);
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_EC) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       if (jwe->jose.recipient[jwe->recip].jwk_ephemeral.kty !=
+                       LWS_GENCRYPTO_KTY_EC) {
+               lwsl_err("%s: missing epk\n", __func__);
+
+               return -1;
+       }
+
+       /*
+        * Recompute the shared secret...
+        *
+        * - direct:  it's the CEK
+        *
+        * - aeskw: apply it as AES keywrap to EKEY to get the CEK
+        */
+
+       /* Generate jose.jwk_ephemeral on the peer public key curve */
+
+       if (lws_genecdh_create(&ecctx, jwe->jws.context, NULL))
+               goto bail;
+
+       /* Load our private key into our side of the ecdh context */
+
+       if (lws_genecdh_set_key(&ecctx, jwe->jws.jwk->e, LDHS_OURS)) {
+               lwsl_err("%s: setting our private key failed\n", __func__);
+               goto bail;
+       }
+
+       /* Import the ephemeral public key into the peer side */
+       if (lws_genecdh_set_key(&ecctx,
+                       jwe->jose.recipient[jwe->recip].jwk_ephemeral.e,
+                       LDHS_THEIRS)) {
+               lwsl_err("%s: setting epk pubkey failed\n", __func__);
+               goto bail;
+       }
+
+       /* combine their ephemeral key and our private key to get the secret */
+
+       if (lws_genecdh_compute_shared_secret(&ecctx, shared_secret, &ss_len)) {
+               lwsl_notice("%s: lws_genecdh_compute_shared_secret failed\n",
+                               __func__);
+
+               goto bail;
+       }
+
+       lws_genec_destroy(&ecctx);
+
+       if (ss_len < enc_hlen) {
+               lwsl_err("%s: ss_len %d ekbytes %d\n", __func__, ss_len, enc_hlen);
+               goto bail;
+       }
+
+       /*
+        * Derive the CEK from the shared secret... amount of bytes written to
+        * cek[] matches bitcount in jwe->jose.enc_alg->keybits_fixed
+        */
+
+       if (lws_jwa_concat_kdf(jwe,
+                       jwe->jose.alg->algtype_crypto == LWS_JOSE_ENCTYPE_NONE,
+                       derived, shared_secret, ss_len)) {
+               lwsl_notice("%s: lws_jwa_concat_kdf failed\n", __func__);
+
+               goto bail;
+       }
+
+       /*
+        * "ECDH-ES": derived is the CEK
+        * "ECDH-ES-AES[128,192,256]KW": wrapped key is in EKEY,
+        *                               "derived" contains KEK
+        */
+
+       if (jwe->jose.alg->algtype_crypto != LWS_JOSE_ENCTYPE_NONE) {
+               struct lws_gencrypto_keyelem el;
+               struct lws_genaes_ctx aesctx;
+               int m;
+
+               /* Confirm space for EKEY */
+
+               if (jwe->jws.map.len[LJWE_EKEY] < (unsigned int)enc_hlen) {
+                       lwsl_err("%s: missing EKEY\n", __func__);
+
+                       goto bail;
+               }
+
+               /* unwrap with the KEK we derived */
+
+               el.buf = derived;
+               el.len = enc_hlen / 2;
+
+               if (lws_genaes_create(&aesctx, LWS_GAESO_DEC, LWS_GAESM_KW,
+                                     &el, 1, NULL)) {
+
+                       lwsl_notice("%s: lws_genaes_create\n", __func__);
+                       goto bail;
+               }
+
+               /* decrypt the EKEY to end up with CEK in "shared_secret" */
+
+               n = lws_genaes_crypt(&aesctx,
+                                    (const uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                                    jwe->jws.map.len[LJWE_EKEY],
+                                    (uint8_t *)shared_secret,
+                                    NULL, NULL, NULL, 0);
+               m = lws_genaes_destroy(&aesctx, NULL, 0);
+               if (n < 0) {
+                       lwsl_err("%s: decrypt cek fail\n", __func__);
+                       goto bail;
+               }
+               if (m < 0) {
+                       lwsl_err("%s: lws_genaes_destroy fail\n", __func__);
+                       goto bail;
+               }
+       } else
+               memcpy(shared_secret, derived, enc_hlen);
+
+       /* either way, the recovered CEK is in shared_secret */
+
+       if (lws_jwe_auth_and_decrypt_cbc_hs(jwe, shared_secret,
+                       (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                       jwe->jws.map_b64.len[LJWE_JOSE]) < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt_cbc_hs fail\n", __func__);
+               goto bail;
+       }
+
+       /* if all went well, then CTXT is now the plaintext */
+       ret = 0;
+
+bail:
+       /* cleanse wrapped on stack that contained the CEK / wrapped key */
+       lws_explicit_bzero(derived, ekbytes);
+       /* cleanse the shared secret */
+       lws_explicit_bzero(shared_secret, ekbytes);
+
+       return ret;
+}
+
+int
+lws_jwe_auth_and_decrypt_ecdh_cbc_hs(struct lws_jwe *jwe,
+                                    char *temp, int *temp_len)
+{
+       /* create a b64 version of the JOSE header, needed later for AAD */
+
+       if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
+                                      temp, temp_len,
+                                      jwe->jws.map.buf[LJWE_JOSE],
+                                      jwe->jws.map.len[LJWE_JOSE]))
+               return -1;
+
+       return lws_jwe_auth_and_decrypt_ecdh(jwe);
+}
diff --git a/lib/jose/jwe/jwe-rsa-aescbc.c b/lib/jose/jwe/jwe-rsa-aescbc.c
new file mode 100644 (file)
index 0000000..7f2f21e
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * JWE code related to rsa + aescbc
+ *
+ */
+#include "core/private.h"
+#include "jose/jwe/private.h"
+
+/*
+ * Requirements on entry:
+ *
+ *  - jwe->jws.map LJWE_JOSE contains the ASCII JOSE header
+ *  - jwe->jws.map LJWE_EKEY contains cek of enc_alg hmac length
+ *  - jwe->jws.map LJWE_CTXT contains the plaintext
+ *
+ * On successful exit:
+ *
+ *  - jwe->jws.map LJWE_ATAG contains the tag
+ *  - jwe->jws.map LJWE_IV contains the new random IV that was used
+ *  - jwe->jws.map LJWE_EKEY contains the encrypted CEK
+ *  - jwe->jws.map LJWE_CTXT contains the ciphertext
+ *
+ *  Return the amount of temp used, or -1
+ */
+
+int
+lws_jwe_encrypt_rsa_aes_cbc_hs(struct lws_jwe *jwe,
+                              char *temp, int *temp_len)
+{
+       int n, hlen = lws_genhmac_size(jwe->jose.enc_alg->hmac_type), ot = *temp_len;
+       char ekey[LWS_GENHASH_LARGEST];
+       struct lws_genrsa_ctx rsactx;
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_RSA) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       /*
+        * Notice that the unencrypted EKEY coming in is smaller than the
+        * RSA-encrypted EKEY going out, which is going to be the RSA key size
+        *
+        * Create a b64 version of the JOSE header, needed as aad
+        */
+       if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
+                                      temp + (ot - *temp_len), temp_len,
+                                      jwe->jws.map.buf[LJWE_JOSE],
+                                      jwe->jws.map.len[LJWE_JOSE]))
+               return -1;
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_ATAG, temp + (ot - *temp_len),
+                                 temp_len, hlen / 2, 0))
+               return -1;
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_IV, temp + (ot - *temp_len),
+                                 temp_len, LWS_JWE_AES_IV_BYTES, 0))
+               return -1;
+
+       /*
+        * Without changing the unencrypted CEK in EKEY, reallocate enough
+        * space to write the RSA-encrypted version in-situ.
+        */
+       if (lws_jws_dup_element(&jwe->jws.map, LJWE_EKEY, temp + (ot - *temp_len),
+                               temp_len, jwe->jws.map.buf[LJWE_EKEY],
+                               jwe->jws.map.len[LJWE_EKEY],
+                               jwe->jws.jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len))
+               return -1;
+
+       /* Encrypt using the raw CEK (treated as MAC KEY | ENC KEY) */
+
+       n = lws_jwe_encrypt_cbc_hs(jwe, (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                                    (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                                    jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt_cbc_hs failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genrsa_create(&rsactx, jwe->jws.jwk->e, jwe->jws.context,
+                       !strcmp(jwe->jose.alg->alg,   "RSA-OAEP") ?
+                                       LGRSAM_PKCS1_OAEP_PSS : LGRSAM_PKCS1_1_5,
+                                       LWS_GENHASH_TYPE_UNKNOWN)) {
+               lwsl_notice("%s: lws_genrsa_create\n",
+                           __func__);
+               return -1;
+       }
+
+       /* encrypt the CEK using RSA, mbedtls can't handle both in and out are
+        * the EKEY, so copy the unencrypted ekey out temporarily */
+
+       memcpy(ekey, jwe->jws.map.buf[LJWE_EKEY], hlen);
+
+       n = lws_genrsa_public_encrypt(&rsactx, (uint8_t *)ekey, hlen,
+                                     (uint8_t *)jwe->jws.map.buf[LJWE_EKEY]);
+       lws_genrsa_destroy(&rsactx);
+       lws_explicit_bzero(ekey, hlen); /* cleanse the temp CEK copy */
+       if (n < 0) {
+               lwsl_err("%s: encrypt cek fail\n", __func__);
+               return -1;
+       }
+       jwe->jws.map.len[LJWE_EKEY] = n; /* update to encrypted EKEY size */
+
+       /*
+        * We end up with IV, ATAG, set, EKEY encrypted and CTXT is ciphertext,
+        * and b64u version of ATAG in map_b64.
+        */
+
+       return 0;
+}
+
+int
+lws_jwe_auth_and_decrypt_rsa_aes_cbc_hs(struct lws_jwe *jwe)
+{
+       int n;
+       struct lws_genrsa_ctx rsactx;
+       uint8_t enc_cek[512];
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_RSA) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       if (jwe->jws.map.len[LJWE_EKEY] < 40) {
+               lwsl_err("%s: EKEY length too short %d\n", __func__,
+                               jwe->jws.map.len[LJWE_EKEY]);
+
+               return -1;
+       }
+
+       /* Decrypt the JWE Encrypted Key to get the raw MAC || CEK */
+
+       if (lws_genrsa_create(&rsactx, jwe->jws.jwk->e, jwe->jws.context,
+                       !strcmp(jwe->jose.alg->alg,   "RSA-OAEP") ?
+                               LGRSAM_PKCS1_OAEP_PSS : LGRSAM_PKCS1_1_5,
+                               LWS_GENHASH_TYPE_UNKNOWN)) {
+               lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                           __func__);
+               return -1;
+       }
+
+       n = lws_genrsa_private_decrypt(&rsactx,
+                                      (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                                      jwe->jws.map.len[LJWE_EKEY], enc_cek,
+                                      sizeof(enc_cek));
+       lws_genrsa_destroy(&rsactx);
+       if (n < 0) {
+               lwsl_err("%s: decrypt cek fail: \n", __func__);
+               return -1;
+       }
+
+       n = lws_jwe_auth_and_decrypt_cbc_hs(jwe, enc_cek,
+                            (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                            jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt_cbc_hs failed\n",
+                        __func__);
+               return -1;
+       }
+
+#if defined(LWS_WITH_MBEDTLS) && defined(LWS_PLAT_OPTEE)
+
+       /* strip padding */
+
+       n = jwe->jws.map.buf[LJWE_CTXT][jwe->jws.map.len[LJWE_CTXT] - 1];
+       if (n > 16) {
+               lwsl_err("%s: n == %d, plen %d\n", __func__, n,
+                               (int)jwe->jws.map.len[LJWE_CTXT]);
+               return -1;
+       }
+       jwe->jws.map.len[LJWE_CTXT] -= n;
+#endif
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
diff --git a/lib/jose/jwe/jwe-rsa-aesgcm.c b/lib/jose/jwe/jwe-rsa-aesgcm.c
new file mode 100644 (file)
index 0000000..42eb0b2
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * JWE code related to aes gcm
+ *
+ */
+#include "core/private.h"
+#include "jose/jwe/private.h"
+
+#define LWS_AESGCM_IV 12
+
+
+int
+lws_jwe_encrypt_rsa_aes_gcm(struct lws_jwe *jwe, char *temp, int *temp_len)
+{
+       int ekbytes = jwe->jose.enc_alg->keybits_fixed / 8;
+       struct lws_genrsa_ctx rsactx;
+       int n, ret = -1, ot = *temp_len;
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_RSA) {
+               lwsl_err("%s: wrong kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       /* create the IV + CEK */
+
+       if (lws_jws_randomize_element(jwe->jws.context, &jwe->jws.map, LJWE_IV,
+                                     temp + (ot - *temp_len), temp_len,
+                                     LWS_AESGCM_IV, 0))
+               return -1;
+
+       if (lws_jws_alloc_element(&jwe->jws.map, LJWE_ATAG,
+                                 temp + (ot - *temp_len),
+                                 temp_len, LWS_AESGCM_TAG, 0))
+               return -1;
+
+       /* create a b64 version of the JOSE header, needed as aad */
+
+       if (lws_jws_encode_b64_element(&jwe->jws.map_b64, LJWE_JOSE,
+                                      temp + (ot - *temp_len), temp_len,
+                                      jwe->jws.map.buf[LJWE_JOSE],
+                                      jwe->jws.map.len[LJWE_JOSE]))
+               return -1;
+
+       /*
+        * If none already, create a new, random CEK in the JWE (so it can be
+        * reused for other recipients on same payload).  If it already exists,
+        * just reuse it.  It will be cleansed in the JWE destroy.
+        */
+       if (!jwe->cek_valid) {
+               if (lws_get_random(jwe->jws.context, jwe->cek, ekbytes) !=
+                                                              ekbytes) {
+                       lwsl_err("%s: Problem getting random\n", __func__);
+                       return -1;
+               }
+               jwe->cek_valid = 1;
+       }
+
+       if (lws_jws_dup_element(&jwe->jws.map, LJWE_EKEY,
+                               temp + (ot - *temp_len), temp_len,
+                               jwe->cek, ekbytes, 0))
+               return -1;
+
+       /* encrypt the payload */
+
+       n = lws_jwe_encrypt_gcm(jwe, (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                               (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                               jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt_gcm failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* Encrypt the CEK into EKEY to make the JWE Encrypted Key */
+
+       if (lws_genrsa_create(&rsactx, jwe->jws.jwk->e, jwe->jws.context,
+                       !strcmp(jwe->jose.alg->alg,   "RSA-OAEP") ?
+                               LGRSAM_PKCS1_OAEP_PSS : LGRSAM_PKCS1_1_5,
+                       LWS_GENHASH_TYPE_SHA1 /* !!! */)) {
+               lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                           __func__);
+               goto bail;
+       }
+
+       n = lws_genrsa_public_encrypt(&rsactx, jwe->cek, ekbytes,
+                                     (uint8_t *)jwe->jws.map.buf[LJWE_EKEY]);
+       lws_genrsa_destroy(&rsactx);
+       if (n < 0) {
+               lwsl_err("%s: encrypt cek fail: \n", __func__);
+               goto bail;
+       }
+
+       /* set the EKEY length to the actual enciphered length */
+       jwe->jws.map.len[LJWE_EKEY] = n;
+
+       ret = jwe->jws.map.len[LJWE_CTXT];
+
+bail:
+
+       return ret;
+}
+
+int
+lws_jwe_auth_and_decrypt_rsa_aes_gcm(struct lws_jwe *jwe)
+{
+       int n;
+       struct lws_genrsa_ctx rsactx;
+       uint8_t enc_cek[LWS_JWE_LIMIT_KEY_ELEMENT_BYTES];
+
+       if (jwe->jws.jwk->kty != LWS_GENCRYPTO_KTY_RSA) {
+               lwsl_err("%s: unexpected kty %d\n", __func__, jwe->jws.jwk->kty);
+
+               return -1;
+       }
+
+       if (jwe->jws.map.len[LJWE_EKEY] < 32) {
+               lwsl_err("%s: EKEY length too short %d\n", __func__,
+                               jwe->jws.map.len[LJWE_EKEY]);
+
+               return -1;
+       }
+
+       /* Decrypt the JWE Encrypted Key to get the direct CEK */
+
+       if (lws_genrsa_create(&rsactx, jwe->jws.jwk->e, jwe->jws.context,
+                       !strcmp(jwe->jose.alg->alg,   "RSA-OAEP") ?
+                               LGRSAM_PKCS1_OAEP_PSS : LGRSAM_PKCS1_1_5,
+                       LWS_GENHASH_TYPE_SHA1 /* !!! */)) {
+               lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                           __func__);
+               return -1;
+       }
+
+       n = lws_genrsa_private_decrypt(&rsactx,
+                                      (uint8_t *)jwe->jws.map.buf[LJWE_EKEY],
+                                      jwe->jws.map.len[LJWE_EKEY], enc_cek,
+                                      sizeof(enc_cek));
+       lws_genrsa_destroy(&rsactx);
+       if (n < 0) {
+               lwsl_err("%s: decrypt cek fail: \n", __func__);
+               return -1;
+       }
+
+       n = lws_jwe_auth_and_decrypt_gcm(jwe, enc_cek,
+                       (uint8_t *)jwe->jws.map_b64.buf[LJWE_JOSE],
+                               jwe->jws.map_b64.len[LJWE_JOSE]);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt_gcm_hs failed\n",
+                        __func__);
+               return -1;
+       }
+
+#if defined(LWS_WITH_MBEDTLS) && defined(LWS_PLAT_OPTEE)
+       /* strip padding */
+
+       n = jwe->jws.map.buf[LJWE_CTXT][jwe->jws.map.len[LJWE_CTXT] - 1];
+       if (n > 16)
+               return -1;
+       jwe->jws.map.len[LJWE_CTXT] -= n;
+#endif
+
+       return jwe->jws.map.len[LJWE_CTXT];
+}
diff --git a/lib/jose/jwe/jwe.c b/lib/jose/jwe/jwe.c
new file mode 100644 (file)
index 0000000..bb8446e
--- /dev/null
@@ -0,0 +1,788 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * This supports RFC7516 JSON Web Encryption
+ *
+ */
+#include "core/private.h"
+#include "jose/private.h"
+#include "jose/jwe/private.h"
+
+/*
+ * Currently only support flattened or compact (implicitly single signature)
+ */
+
+static const char * const jwe_json[] = {
+       "protected",
+       "iv",
+       "ciphertext",
+       "tag",
+       "encrypted_key"
+};
+
+enum enum_jwe_complete_tokens {
+       LWS_EJCT_PROTECTED,
+       LWS_EJCT_IV,
+       LWS_EJCT_CIPHERTEXT,
+       LWS_EJCT_TAG,
+       LWS_EJCT_RECIP_ENC_KEY,
+};
+
+/* parse a JWS complete or flattened JSON object */
+
+struct jwe_cb_args {
+       struct lws_jws *jws;
+
+       char *temp;
+       int *temp_len;
+};
+
+static signed char
+lws_jwe_json_cb(struct lejp_ctx *ctx, char reason)
+{
+       struct jwe_cb_args *args = (struct jwe_cb_args *)ctx->user;
+       int n, m;
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       switch (ctx->path_match - 1) {
+
+       /* strings */
+
+       case LWS_EJCT_PROTECTED:  /* base64u: JOSE: must contain 'alg' */
+               m = LJWS_JOSE;
+               goto append_string;
+       case LWS_EJCT_IV:       /* base64u */
+               m = LJWE_IV;
+               goto append_string;
+       case LWS_EJCT_CIPHERTEXT:  /* base64u */
+               m = LJWE_CTXT;
+               goto append_string;
+       case LWS_EJCT_TAG:  /* base64u */
+               m = LJWE_ATAG;
+               goto append_string;
+       case LWS_EJCT_RECIP_ENC_KEY:  /* base64u */
+               m = LJWE_EKEY;
+               goto append_string;
+
+       default:
+               return -1;
+       }
+
+       return 0;
+
+append_string:
+
+       if (*args->temp_len < ctx->npos) {
+               lwsl_err("%s: out of parsing space\n", __func__);
+               return -1;
+       }
+
+       /*
+        * We keep both b64u and decoded in temp mapped using map / map_b64,
+        * the jws signature is actually over the b64 content not the plaintext,
+        * and we can't do it until we see the protected alg.
+        */
+
+       if (!args->jws->map_b64.buf[m]) {
+               args->jws->map_b64.buf[m] = args->temp;
+               args->jws->map_b64.len[m] = 0;
+       }
+
+       memcpy(args->temp, ctx->buf, ctx->npos);
+       args->temp += ctx->npos;
+       *args->temp_len -= ctx->npos;
+       args->jws->map_b64.len[m] += ctx->npos;
+
+       if (reason == LEJPCB_VAL_STR_END) {
+               args->jws->map.buf[m] = args->temp;
+
+               n = lws_b64_decode_string_len(
+                       (const char *)args->jws->map_b64.buf[m],
+                       args->jws->map_b64.len[m],
+                       (char *)args->temp, *args->temp_len);
+               if (n < 0) {
+                       lwsl_err("%s: b64 decode failed\n", __func__);
+                       return -1;
+               }
+
+               args->temp += n;
+               *args->temp_len -= n;
+               args->jws->map.len[m] = n;
+       }
+
+       return 0;
+}
+
+int
+lws_jwe_json_parse(struct lws_jwe *jwe, const uint8_t *buf, int len,
+                  char *temp, int *temp_len)
+{
+       struct jwe_cb_args args;
+       struct lejp_ctx jctx;
+       int m = 0;
+
+       args.jws = &jwe->jws;
+       args.temp = temp;
+       args.temp_len = temp_len;
+
+       lejp_construct(&jctx, lws_jwe_json_cb, &args, jwe_json,
+                      LWS_ARRAY_SIZE(jwe_json));
+
+       m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, len);
+       lejp_destruct(&jctx);
+       if (m < 0) {
+               lwsl_notice("%s: parse returned %d\n", __func__, m);
+               return -1;
+       }
+
+       return 0;
+}
+
+void
+lws_jwe_init(struct lws_jwe *jwe, struct lws_context *context)
+{
+       lws_jose_init(&jwe->jose);
+       lws_jws_init(&jwe->jws, &jwe->jwk, context);
+       memset(&jwe->jwk, 0, sizeof(jwe->jwk));
+       jwe->recip = 0;
+       jwe->cek_valid = 0;
+}
+
+void
+lws_jwe_destroy(struct lws_jwe *jwe)
+{
+       lws_jws_destroy(&jwe->jws);
+       lws_jose_destroy(&jwe->jose);
+       lws_jwk_destroy(&jwe->jwk);
+       /* cleanse the CEK we held on to in case of further encryptions of it */
+       lws_explicit_bzero(jwe->cek, sizeof(jwe->cek));
+       jwe->cek_valid = 0;
+}
+
+static uint8_t *
+be32(uint32_t i, uint32_t *p32)
+{
+       uint8_t *p = (uint8_t *)p32;
+
+       *p++ = (i >> 24) & 0xff;
+       *p++ = (i >> 16) & 0xff;
+       *p++ = (i >> 8) & 0xff;
+       *p++ = i & 0xff;
+
+       return (uint8_t *)p32;
+}
+
+/*
+ * The key derivation process derives the agreed-upon key from the
+ * shared secret Z established through the ECDH algorithm, per
+ * Section 6.2.2.2 of [NIST.800-56A].
+ *
+ *
+ * Key derivation is performed using the Concat KDF, as defined in
+ * Section 5.8.1 of [NIST.800-56A], where the Digest Method is SHA-256.
+ *
+ * out must be prepared to take at least 32 bytes or the encrypted key size,
+ * whichever is larger.
+ */
+
+int
+lws_jwa_concat_kdf(struct lws_jwe *jwe, int direct, uint8_t *out,
+                  const uint8_t *shared_secret, int sslen)
+{
+       int hlen = lws_genhash_size(LWS_GENHASH_TYPE_SHA256), aidlen;
+       struct lws_genhash_ctx hash_ctx;
+       uint32_t ctr = 1, t;
+       const char *aid;
+
+       if (!jwe->jose.enc_alg || !jwe->jose.alg)
+               return -1;
+
+       /*
+        * Hash
+        *
+        * AlgorithmID || PartyUInfo || PartyVInfo
+        *      {|| SuppPubInfo }{|| SuppPrivInfo }
+        *
+        * AlgorithmID
+        *
+        * The AlgorithmID value is of the form Datalen || Data, where Data
+        * is a variable-length string of zero or more octets, and Datalen is
+        * a fixed-length, big-endian 32-bit counter that indicates the
+        * length (in octets) of Data.  In the Direct Key Agreement case,
+        * Data is set to the octets of the ASCII representation of the "enc"
+        * Header Parameter value.  In the Key Agreement with Key Wrapping
+        * case, Data is set to the octets of the ASCII representation of the
+        * "alg" (algorithm) Header Parameter value.
+        */
+
+       aid = direct ? jwe->jose.enc_alg->alg : jwe->jose.alg->alg;
+       aidlen = strlen(aid);
+
+       /*
+        *   PartyUInfo (PartyVInfo is the same deal)
+        *
+        *    The PartyUInfo value is of the form Datalen || Data, where Data is
+        *    a variable-length string of zero or more octets, and Datalen is a
+        *    fixed-length, big-endian 32-bit counter that indicates the length
+        *    (in octets) of Data.  If an "apu" (agreement PartyUInfo) Header
+        *    Parameter is present, Data is set to the result of base64url
+        *    decoding the "apu" value and Datalen is set to the number of
+        *    octets in Data.  Otherwise, Datalen is set to 0 and Data is set to
+        *    the empty octet sequence
+        *
+        *   SuppPubInfo
+        *
+        *    This is set to the keydatalen represented as a 32-bit big-endian
+        *    integer.
+        *
+        *   keydatalen
+        *
+        *    This is set to the number of bits in the desired output key.  For
+        *    "ECDH-ES", this is length of the key used by the "enc" algorithm.
+        *    For "ECDH-ES+A128KW", "ECDH-ES+A192KW", and "ECDH-ES+A256KW", this
+        *    is 128, 192, and 256, respectively.
+        *
+        *    Compute Hash i = H(counter || Z || OtherInfo).
+        *
+        *    We must iteratively hash over key material that's larger than
+        *    one hash output size (256b for SHA-256)
+        */
+
+       while (ctr <= (uint32_t)((jwe->jose.enc_alg->keybits_fixed + (hlen - 1)) / hlen)) {
+
+               /*
+                * Key derivation is performed using the Concat KDF, as defined
+                * in Section 5.8.1 of [NIST.800-56A], where the Digest Method
+                * is SHA-256.
+                */
+
+               if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))
+                       return -1;
+
+               if (/* counter */
+                   lws_genhash_update(&hash_ctx, be32(ctr++, &t), 4) ||
+                   /* Z */
+                   lws_genhash_update(&hash_ctx, shared_secret, sslen) ||
+                   /* other info */
+                   lws_genhash_update(&hash_ctx, be32(strlen(aid), &t), 4) ||
+                   lws_genhash_update(&hash_ctx, aid, aidlen) ||
+                   lws_genhash_update(&hash_ctx,
+                                      be32(jwe->jose.e[LJJHI_APU].len, &t), 4) ||
+                   lws_genhash_update(&hash_ctx, jwe->jose.e[LJJHI_APU].buf,
+                                                 jwe->jose.e[LJJHI_APU].len) ||
+                   lws_genhash_update(&hash_ctx,
+                                      be32(jwe->jose.e[LJJHI_APV].len, &t), 4) ||
+                   lws_genhash_update(&hash_ctx, jwe->jose.e[LJJHI_APV].buf,
+                                                 jwe->jose.e[LJJHI_APV].len) ||
+                   lws_genhash_update(&hash_ctx,
+                                      be32(jwe->jose.enc_alg->keybits_fixed, &t),
+                                           4) ||
+                   lws_genhash_destroy(&hash_ctx, out)) {
+                       lwsl_err("%s: fail\n", __func__);
+                       lws_genhash_destroy(&hash_ctx, NULL);
+
+                       return -1;
+               }
+
+               out += hlen;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE void
+lws_jwe_be64(uint64_t c, uint8_t *p8)
+{
+       int n;
+
+       for (n = 56; n >= 0; n -= 8)
+               *p8++ = (uint8_t)((c >> n) & 0xff);
+}
+
+LWS_VISIBLE int
+lws_jwe_auth_and_decrypt(struct lws_jwe *jwe, char *temp, int *temp_len)
+{
+       int valid_aescbc_hmac, valid_aesgcm;
+
+       if (lws_jwe_parse_jose(&jwe->jose, jwe->jws.map.buf[LJWS_JOSE],
+                              jwe->jws.map.len[LJWS_JOSE],
+                              temp, temp_len) < 0) {
+               lwsl_err("%s: JOSE parse '%.*s' failed\n", __func__,
+                               jwe->jws.map.len[LJWS_JOSE],
+                               jwe->jws.map.buf[LJWS_JOSE]);
+               return -1;
+       }
+
+       if (!jwe->jose.alg) {
+               lwsl_err("%s: no jose.alg: %.*s\n", __func__,
+                               jwe->jws.map.len[LJWS_JOSE],
+                               jwe->jws.map.buf[LJWS_JOSE]);
+
+               return -1;
+       }
+
+       valid_aescbc_hmac = jwe->jose.enc_alg &&
+               jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_CBC &&
+               (jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA256 ||
+                jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA384 ||
+                jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA512);
+
+       valid_aesgcm = jwe->jose.enc_alg &&
+               jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_GCM;
+
+       if ((jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5 ||
+            jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP)) {
+               /* RSA + AESCBC */
+               if (valid_aescbc_hmac)
+                       return lws_jwe_auth_and_decrypt_rsa_aes_cbc_hs(jwe);
+               /* RSA + AESGCM */
+               if (valid_aesgcm)
+                       return lws_jwe_auth_and_decrypt_rsa_aes_gcm(jwe);
+       }
+
+       /* AESKW */
+
+       if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_AES_ECB &&
+           valid_aescbc_hmac)
+               return lws_jwe_auth_and_decrypt_aeskw_cbc_hs(jwe);
+
+       /* ECDH-ES + AESKW */
+
+       if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_ECDHES &&
+           valid_aescbc_hmac)
+               return lws_jwe_auth_and_decrypt_ecdh_cbc_hs(jwe,
+                                                           temp, temp_len);
+
+       lwsl_err("%s: unknown cipher alg combo %s / %s\n", __func__,
+                       jwe->jose.alg->alg, jwe->jose.enc_alg ?
+                                       jwe->jose.enc_alg->alg : "NULL");
+
+       return -1;
+}
+LWS_VISIBLE int
+lws_jwe_encrypt(struct lws_jwe *jwe, char *temp, int *temp_len)
+{
+       int valid_aescbc_hmac, valid_aesgcm, ot = *temp_len, ret = -1;
+
+       if (jwe->jose.recipients >= (int)LWS_ARRAY_SIZE(jwe->jose.recipient)) {
+               lwsl_err("%s: max recipients reached\n", __func__);
+
+               return -1;
+       }
+
+       valid_aesgcm = jwe->jose.enc_alg &&
+               jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_GCM;
+
+       if (lws_jwe_parse_jose(&jwe->jose, jwe->jws.map.buf[LJWS_JOSE],
+                              jwe->jws.map.len[LJWS_JOSE], temp, temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+               goto bail;
+       }
+
+       temp += ot - *temp_len;
+
+       valid_aescbc_hmac = jwe->jose.enc_alg &&
+               jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_CBC &&
+               (jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA256 ||
+                jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA384 ||
+                jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA512);
+
+       if ((jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5 ||
+            jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP)) {
+               /* RSA + AESCBC */
+               if (valid_aescbc_hmac) {
+                       ret = lws_jwe_encrypt_rsa_aes_cbc_hs(jwe, temp, temp_len);
+                       goto bail;
+               }
+               /* RSA + AESGCM */
+               if (valid_aesgcm) {
+                       ret = lws_jwe_encrypt_rsa_aes_gcm(jwe, temp, temp_len);
+                       goto bail;
+               }
+       }
+
+       /* AESKW */
+
+       if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_AES_ECB &&
+           valid_aescbc_hmac) {
+               ret = lws_jwe_encrypt_aeskw_cbc_hs(jwe, temp, temp_len);
+               goto bail;
+       }
+
+       /* ECDH-ES + AESKW */
+
+       if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_ECDHES &&
+           valid_aescbc_hmac) {
+               ret = lws_jwe_encrypt_ecdh_cbc_hs(jwe, temp, temp_len);
+               goto bail;
+       }
+
+       lwsl_err("%s: unknown cipher alg combo %s / %s\n", __func__,
+                       jwe->jose.alg->alg, jwe->jose.enc_alg ?
+                                       jwe->jose.enc_alg->alg : "NULL");
+
+bail:
+       if (ret)
+               memset(&jwe->jose.recipient[jwe->jose.recipients], 0,
+                       sizeof(jwe->jose.recipient[0]));
+       else
+               jwe->jose.recipients++;
+
+       return ret;
+}
+
+/*
+ * JWE Compact Serialization consists of
+ *
+ *     BASE64URL(UTF8(JWE Protected Header)) || '.' ||
+ *     BASE64URL(JWE Encrypted Key)         || '.' ||
+ *     BASE64URL(JWE Initialization Vector)  || '.' ||
+ *     BASE64URL(JWE Ciphertext)            || '.' ||
+ *     BASE64URL(JWE Authentication Tag)
+ *
+ *
+ * In the JWE Compact Serialization, no JWE Shared Unprotected Header or
+ * JWE Per-Recipient Unprotected Header are used.  In this case, the
+ * JOSE Header and the JWE Protected Header are the same.
+ *
+ * Therefore:
+ *
+ *  - Everything needed in the header part must go in the protected header
+ *    (it's the only part emitted).  We expect the caller did this.
+ *
+ *  - You can't emit Compact representation if there are multiple recipients
+ */
+
+LWS_VISIBLE int
+lws_jwe_render_compact(struct lws_jwe *jwe, char *out, size_t out_len)
+{
+       size_t orig = out_len;
+       int n;
+
+       if (jwe->jose.recipients > 1) {
+               lwsl_notice("%s: can't issue compact representation for"
+                           " multiple recipients (%d)\n", __func__,
+                           jwe->jose.recipients);
+
+               return -1;
+       }
+
+       n = lws_jws_base64_enc(jwe->jws.map.buf[LJWS_JOSE],
+                              jwe->jws.map.len[LJWS_JOSE], out, out_len);
+       if (n < 0 || (int)out_len == n) {
+               lwsl_info("%s: unable to encode JOSE\n", __func__);
+               return n;
+       }
+
+       out += n;
+       *out++ = '.';
+       out_len -= n + 1;
+
+       n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_EKEY],
+                              jwe->jws.map.len[LJWE_EKEY], out, out_len);
+       if (n < 0 || (int)out_len == n) {
+               lwsl_info("%s: unable to encode EKEY\n", __func__);
+               return n;
+       }
+
+       out += n;
+       *out++ = '.';
+       out_len -= n + 1;
+       n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_IV],
+                              jwe->jws.map.len[LJWE_IV], out, out_len);
+       if (n < 0 || (int)out_len == n) {
+               lwsl_info("%s: unable to encode IV\n", __func__);
+               return n;
+       }
+
+       out += n;
+       *out++ = '.';
+       out_len -= n + 1;
+
+       n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_CTXT],
+                              jwe->jws.map.len[LJWE_CTXT], out, out_len);
+       if (n < 0 || (int)out_len == n) {
+               lwsl_info("%s: unable to encode CTXT\n", __func__);
+               return n;
+       }
+
+       out += n;
+       *out++ = '.';
+       out_len -= n + 1;
+       n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_ATAG],
+                              jwe->jws.map.len[LJWE_ATAG], out, out_len);
+       if (n < 0 || (int)out_len == n) {
+               lwsl_info("%s: unable to encode ATAG\n", __func__);
+               return n;
+       }
+
+       out += n;
+       *out++ = '\0';
+       out_len -= n;
+
+       return orig - out_len;
+}
+
+LWS_VISIBLE int
+lws_jwe_create_packet(struct lws_jwe *jwe, const char *payload, size_t len,
+                     const char *nonce, char *out, size_t out_len,
+                     struct lws_context *context)
+{
+       char *buf, *start, *p, *end, *p1, *end1;
+       struct lws_jws jws;
+       int n, m;
+
+       lws_jws_init(&jws, &jwe->jwk, context);
+
+       /*
+        * This buffer is local to the function, the actual output is prepared
+        * into out.  Only the plaintext protected header
+        * (which contains the public key, 512 bytes for 4096b) goes in
+        * here temporarily.
+        */
+       n = LWS_PRE + 2048;
+       buf = malloc(n);
+       if (!buf) {
+               lwsl_notice("%s: malloc %d failed\n", __func__, n);
+               return -1;
+       }
+
+       p = start = buf + LWS_PRE;
+       end = buf + n - LWS_PRE - 1;
+
+       /*
+        * temporary JWS protected header plaintext
+        */
+
+       if (!jwe->jose.alg || !jwe->jose.alg->alg)
+               goto bail;
+
+       p += lws_snprintf(p, end - p, "{\"alg\":\"%s\",\"jwk\":",
+                         jwe->jose.alg->alg);
+       m = end - p;
+       n = lws_jwk_export(&jwe->jwk, 0, p, &m);
+       if (n < 0) {
+               lwsl_notice("failed to export jwk\n");
+
+               goto bail;
+       }
+       p += n;
+       p += lws_snprintf(p, end - p, ",\"nonce\":\"%s\"}", nonce);
+
+       /*
+        * prepare the signed outer JSON with all the parts in
+        */
+
+       p1 = out;
+       end1 = out + out_len - 1;
+
+       p1 += lws_snprintf(p1, end1 - p1, "{\"protected\":\"");
+       jws.map_b64.buf[LJWS_JOSE] = p1;
+       n = lws_jws_base64_enc(start, p - start, p1, end1 - p1);
+       if (n < 0) {
+               lwsl_notice("%s: failed to encode protected\n", __func__);
+               goto bail;
+       }
+       jws.map_b64.len[LJWS_JOSE] = n;
+       p1 += n;
+
+       p1 += lws_snprintf(p1, end1 - p1, "\",\"payload\":\"");
+       jws.map_b64.buf[LJWS_PYLD] = p1;
+       n = lws_jws_base64_enc(payload, len, p1, end1 - p1);
+       if (n < 0) {
+               lwsl_notice("%s: failed to encode payload\n", __func__);
+               goto bail;
+       }
+       jws.map_b64.len[LJWS_PYLD] = n;
+       p1 += n;
+
+       p1 += lws_snprintf(p1, end1 - p1, "\",\"header\":\"");
+       jws.map_b64.buf[LJWS_UHDR] = p1;
+       n = lws_jws_base64_enc(payload, len, p1, end1 - p1);
+       if (n < 0) {
+               lwsl_notice("%s: failed to encode payload\n", __func__);
+               goto bail;
+       }
+       jws.map_b64.len[LJWS_UHDR] = n;
+
+       p1 += n;
+       p1 += lws_snprintf(p1, end1 - p1, "\",\"signature\":\"");
+
+       /*
+        * taking the b64 protected header and the b64 payload, sign them
+        * and place the signature into the packet
+        */
+       n = lws_jws_sign_from_b64(&jwe->jose, &jws, p1, end1 - p1);
+       if (n < 0) {
+               lwsl_notice("sig gen failed\n");
+
+               goto bail;
+       }
+       jws.map_b64.buf[LJWS_SIG] = p1;
+       jws.map_b64.len[LJWS_SIG] = n;
+
+       p1 += n;
+       p1 += lws_snprintf(p1, end1 - p1, "\"}");
+
+       free(buf);
+
+       return p1 - out;
+
+bail:
+       lws_jws_destroy(&jws);
+       free(buf);
+
+       return -1;
+}
+
+static const char *protected_en[] = {
+       "encrypted_key", "aad", "iv", "ciphertext", "tag"
+};
+
+static int protected_idx[] = {
+       LJWE_EKEY, LJWE_AAD, LJWE_IV, LJWE_CTXT, LJWE_ATAG
+};
+
+/*
+ * The complete JWE may look something like this:
+ *
+ *  {
+ *    "protected":
+ *     "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
+ *    "unprotected":
+ *     {"jku":"https://server.example.com/keys.jwks"},
+ *    "recipients":[
+ *     {"header":
+ *       {"alg":"RSA1_5","kid":"2011-04-29"},
+ *      "encrypted_key":
+ *       "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-
+ *        kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKx
+ *        GHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3
+ *        YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPh
+ *        cCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPg
+ *        wCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"},
+ *     {"header":
+ *       {"alg":"A128KW","kid":"7"},
+ *      "encrypted_key":
+ *       "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ"}],
+ *    "iv":
+ *     "AxY8DCtDaGlsbGljb3RoZQ",
+ *    "ciphertext":
+ *     "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",
+ *    "tag":
+ *     "Mz-VPPyU4RlcuYv1IwIvzw"
+ *   }
+ *
+ *  The flattened JWE ends up like this
+ *
+ *   {
+ *    "protected": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
+ *    "unprotected": {"jku":"https://server.example.com/keys.jwks"},
+ *    "header": {"alg":"A128KW","kid":"7"},
+ *    "encrypted_key": "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ",
+ *    "iv": "AxY8DCtDaGlsbGljb3RoZQ",
+ *    "ciphertext": "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",
+ *    "tag": "Mz-VPPyU4RlcuYv1IwIvzw"
+ *   }
+ *
+ *    {
+ *      "protected":"<integrity-protected header contents>",
+ *      "unprotected":<non-integrity-protected header contents>,
+ *      "header":<more non-integrity-protected header contents>,
+ *      "encrypted_key":"<encrypted key contents>",
+ *      "aad":"<additional authenticated data contents>",
+ *      "iv":"<initialization vector contents>",
+ *      "ciphertext":"<ciphertext contents>",
+ *      "tag":"<authentication tag contents>"
+ *     }
+ */
+
+LWS_VISIBLE int
+lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len)
+{
+       char buf[3072], *p1, *end1, protected[128];
+       int m, n, jlen, plen;
+
+       jlen = lws_jose_render(&jwe->jose, jwe->jws.jwk, buf, sizeof(buf));
+       if (jlen < 0) {
+               lwsl_err("%s: lws_jose_render failed\n", __func__);
+
+               return -1;
+       }
+
+       /*
+        * prepare the JWE JSON with all the parts in
+        */
+
+       p1 = out;
+       end1 = out + out_len - 1;
+
+       /*
+        * The protected header is b64url encoding of the JOSE header part
+        */
+
+       plen = lws_snprintf(protected, sizeof(protected),
+                           "{\"alg\":\"%s\",\"enc\":\"%s\"}",
+                           jwe->jose.alg->alg, jwe->jose.enc_alg->alg);
+
+       p1 += lws_snprintf(p1, end1 - p1, "{\"protected\":\"");
+       jwe->jws.map_b64.buf[LJWS_JOSE] = p1;
+       n = lws_jws_base64_enc(protected, plen, p1, end1 - p1);
+       if (n < 0) {
+               lwsl_notice("%s: failed to encode protected\n", __func__);
+               goto bail;
+       }
+       jwe->jws.map_b64.len[LJWS_JOSE] = n;
+       p1 += n;
+
+       /* unprotected not supported atm */
+
+       p1 += lws_snprintf(p1, end1 - p1, "\",\n\"header\":%.*s", jlen, buf);
+
+       for (m = 0; m < (int)LWS_ARRAY_SIZE(protected_en); m++)
+               if (jwe->jws.map.buf[protected_idx[m]]) {
+                       p1 += lws_snprintf(p1, end1 - p1, ",\n\"%s\":\"",
+                                          protected_en[m]);
+                       //jwe->jws.map_b64.buf[protected_idx[m]] = p1;
+                       n = lws_jws_base64_enc(jwe->jws.map.buf[protected_idx[m]],
+                                              jwe->jws.map.len[protected_idx[m]],
+                                              p1, end1 - p1);
+                       if (n < 0) {
+                               lwsl_notice("%s: failed to encode %s\n",
+                                           __func__, protected_en[m]);
+                               goto bail;
+                       }
+                       //jwe->jws.map_b64.len[protected_idx[m]] = n;
+                       p1 += n;
+                       p1 += lws_snprintf(p1, end1 - p1, "\"");
+               }
+
+       p1 += lws_snprintf(p1, end1 - p1, "\n}\n");
+
+       return p1 - out;
+
+bail:
+       lws_jws_destroy(&jwe->jws);
+
+       return -1;
+}
diff --git a/lib/jose/jwe/private.h b/lib/jose/jwe/private.h
new file mode 100644 (file)
index 0000000..f64a51f
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * libwebsockets - JSON Web Encryption support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ */
+#define LWS_AESGCM_IV 12
+#define LWS_AESGCM_TAG 16
+
+/* jwe-rsa-aescbc.c */
+
+int
+lws_jwe_auth_and_decrypt_rsa_aes_cbc_hs(struct lws_jwe *jwe);
+
+
+int
+lws_jwe_encrypt_rsa_aes_cbc_hs(struct lws_jwe *jwe,
+                              char *temp, int *temp_len);
+
+int
+lws_jwe_auth_and_decrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *enc_cek,
+                               uint8_t *aad, int aad_len);
+
+
+/* jws-rsa-aesgcm.c */
+
+int
+lws_jwe_auth_and_decrypt_gcm(struct lws_jwe *jwe, uint8_t *enc_cek,
+                            uint8_t *aad, int aad_len);
+
+int
+lws_jwe_auth_and_decrypt_rsa_aes_gcm(struct lws_jwe *jwe);
+
+int
+lws_jwe_encrypt_gcm(struct lws_jwe *jwe,
+                   uint8_t *enc_cek, uint8_t *aad, int aad_len);
+
+int
+lws_jwe_encrypt_rsa_aes_gcm(struct lws_jwe *jwe,
+                           char *temp, int *temp_len);
+
+
+
+
+/* jwe-rsa-aeskw.c */
+
+int
+lws_jwe_encrypt_aeskw_cbc_hs(struct lws_jwe *jwe,
+                            char *temp, int *temp_len);
+
+int
+lws_jwe_auth_and_decrypt_aeskw_cbc_hs(struct lws_jwe *jwe);
+
+/* aescbc.c */
+
+int
+lws_jwe_auth_and_decrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *enc_cek,
+                               uint8_t *aad, int aad_len);
+
+int
+lws_jwe_encrypt_cbc_hs(struct lws_jwe *jwe,
+                      uint8_t *cek, uint8_t *aad, int aad_len);
+
+int
+lws_jwe_auth_and_decrypt_ecdh_cbc_hs(struct lws_jwe *jwe,
+               char *temp, int *temp_len);
+
+int
+lws_jwe_encrypt_ecdh_cbc_hs(struct lws_jwe *jwe,
+                            char *temp, int *temp_len);
diff --git a/lib/jose/jwk/jwk.c b/lib/jose/jwk/jwk.c
new file mode 100644 (file)
index 0000000..35f4723
--- /dev/null
@@ -0,0 +1,903 @@
+/*
+ * libwebsockets - JSON Web Key support
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "jose/private.h"
+
+#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
+#include <fcntl.h>
+#endif
+
+static const char * const kty_names[] = {
+       "unknown",      /* LWS_GENCRYPTO_KTY_UNKNOWN */
+       "oct",          /* LWS_GENCRYPTO_KTY_OCT */
+       "RSA",          /* LWS_GENCRYPTO_KTY_RSA */
+       "EC"            /* LWS_GENCRYPTO_KTY_EC */
+};
+
+/*
+ * These are the entire legal token set for names in jwk.
+ *
+ * The first version is used to parse a detached single jwk that don't have any
+ * parent JSON context.  The second version is used to parse full jwk objects
+ * that has a "keys": [ ] array containing the keys.
+ */
+
+static const char * const jwk_tok[] = {
+       "keys[]",                       /* dummy */
+       "e", "n", "d", "p", "q", "dp", "dq", "qi", /* RSA */
+       "kty",                          /* generic */
+       "k",                            /* symmetric key data */
+       "crv", "x", "y",                /* EC (also "D") */
+       "kid",                          /* generic */
+       "use"                           /* mutually exclusive with "key_ops" */,
+       "key_ops"                       /* mutually exclusive with "use" */,
+       "x5c",                          /* generic */
+       "alg"                           /* generic */
+}, * const jwk_outer_tok[] = {
+       "keys[]",
+       "keys[].e", "keys[].n", "keys[].d", "keys[].p", "keys[].q", "keys[].dp",
+       "keys[].dq", "keys[].qi",
+
+       "keys[].kty", "keys[].k",               /* generic */
+       "keys[].crv", "keys[].x", "keys[].y",   /* EC (also "D") */
+       "keys[].kid", "keys[].use"      /* mutually exclusive with "key_ops" */,
+       "keys[].key_ops",               /* mutually exclusive with "use" */
+       "keys[].x5c", "keys[].alg"
+};
+
+/* information about each token declared above */
+
+#define F_M    (1 <<  9)       /* Mandatory for key type */
+#define F_B64  (1 << 10)       /* Base64 coded octets */
+#define F_B64U (1 << 11)       /* Base64 Url coded octets */
+#define F_META (1 << 12)       /* JWK key metainformation */
+#define F_RSA  (1 << 13)       /* RSA key */
+#define F_EC   (1 << 14)       /* Elliptic curve key */
+#define F_OCT  (1 << 15)       /* octet key */
+
+static unsigned short tok_map[] = {
+       F_RSA | F_EC | F_OCT | F_META |          0xff,
+       F_RSA |                         F_B64U | F_M | LWS_GENCRYPTO_RSA_KEYEL_E,
+       F_RSA |                         F_B64U | F_M | LWS_GENCRYPTO_RSA_KEYEL_N,
+       F_RSA | F_EC |                  F_B64U |       LWS_GENCRYPTO_RSA_KEYEL_D,
+       F_RSA |                         F_B64U |       LWS_GENCRYPTO_RSA_KEYEL_P,
+       F_RSA |                         F_B64U |       LWS_GENCRYPTO_RSA_KEYEL_Q,
+       F_RSA |                         F_B64U |       LWS_GENCRYPTO_RSA_KEYEL_DP,
+       F_RSA |                         F_B64U |       LWS_GENCRYPTO_RSA_KEYEL_DQ,
+       F_RSA |                         F_B64U |       LWS_GENCRYPTO_RSA_KEYEL_QI,
+
+       F_RSA | F_EC | F_OCT | F_META |          F_M | JWK_META_KTY,
+                      F_OCT |          F_B64U | F_M | LWS_GENCRYPTO_OCT_KEYEL_K,
+
+               F_EC |                           F_M | LWS_GENCRYPTO_EC_KEYEL_CRV,
+               F_EC |                  F_B64U | F_M | LWS_GENCRYPTO_EC_KEYEL_X,
+               F_EC |                  F_B64U | F_M | LWS_GENCRYPTO_EC_KEYEL_Y,
+
+       F_RSA | F_EC | F_OCT | F_META |                JWK_META_KID,
+       F_RSA | F_EC | F_OCT | F_META |                JWK_META_USE,
+
+       F_RSA | F_EC | F_OCT | F_META |                JWK_META_KEY_OPS,
+       F_RSA | F_EC | F_OCT | F_META | F_B64 |        JWK_META_X5C,
+       F_RSA | F_EC | F_OCT | F_META |                JWK_META_ALG,
+};
+
+static const char *meta_names[] = {
+       "kty", "kid", "use", "key_ops", "x5c", "alg"
+};
+
+struct lexico {
+       const char *name;
+       int idx;
+       char meta;
+} lexico_ec[] =  {
+       { "alg",        JWK_META_ALG,                   1 },
+       { "crv",        LWS_GENCRYPTO_EC_KEYEL_CRV,     0 },
+       { "d",          LWS_GENCRYPTO_EC_KEYEL_D,       2 | 0 },
+       { "key_ops",    JWK_META_KEY_OPS,               1 },
+       { "kid",        JWK_META_KID,                   1 },
+       { "kty",        JWK_META_KTY,                   1 },
+       { "use",        JWK_META_USE,                   1 },
+       { "x",          LWS_GENCRYPTO_EC_KEYEL_X,       0 },
+       { "x5c",        JWK_META_X5C,                   1 },
+       { "y",          LWS_GENCRYPTO_EC_KEYEL_Y,       0 }
+}, lexico_oct[] =  {
+       { "alg",        JWK_META_ALG,                   1 },
+       { "k",          LWS_GENCRYPTO_OCT_KEYEL_K,      0 },
+       { "key_ops",    JWK_META_KEY_OPS,               1 },
+       { "kid",        JWK_META_KID,                   1 },
+       { "kty",        JWK_META_KTY,                   1 },
+       { "use",        JWK_META_USE,                   1 },
+       { "x5c",        JWK_META_X5C,                   1 }
+}, lexico_rsa[] =  {
+       { "alg",        JWK_META_ALG,                   1 },
+       { "d",          LWS_GENCRYPTO_RSA_KEYEL_D,      2 | 0 },
+       { "dp",         LWS_GENCRYPTO_RSA_KEYEL_DP,     2 | 0 },
+       { "dq",         LWS_GENCRYPTO_RSA_KEYEL_DQ,     2 | 0 },
+       { "e",          LWS_GENCRYPTO_RSA_KEYEL_E,      0 },
+       { "key_ops",    JWK_META_KEY_OPS,               1 },
+       { "kid",        JWK_META_KID,                   1 },
+       { "kty",        JWK_META_KTY,                   1 },
+       { "n",          LWS_GENCRYPTO_RSA_KEYEL_N,      0 },
+       { "p",          LWS_GENCRYPTO_RSA_KEYEL_P,      2 | 0 },
+       { "q",          LWS_GENCRYPTO_RSA_KEYEL_Q,      2 | 0 },
+       { "qi",         LWS_GENCRYPTO_RSA_KEYEL_QI,     2 | 0 },
+       { "use",        JWK_META_USE,                   1 },
+       { "x5c",        JWK_META_X5C,                   1 }
+};
+
+static const char meta_b64[] = { 0, 0, 0, 0, 1, 0 };
+
+static const char *oct_names[] = {
+       "k"
+};
+static const char oct_b64[] = { 1 };
+
+static const char *rsa_names[] = {
+       "e", "n", "d", "p", "q", "dp", "dq", "qi"
+};
+static const char rsa_b64[] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+
+static const char *ec_names[] = {
+       "crv", "x", "d", "y",
+};
+static const char ec_b64[] = { 0, 1, 1, 1 };
+
+LWS_VISIBLE int
+lws_jwk_dump(struct lws_jwk *jwk)
+{
+       const char **enames, *b64;
+       int elems;
+       int n;
+
+       (void)enames;
+       (void)meta_names;
+
+       switch (jwk->kty) {
+       default:
+       case LWS_GENCRYPTO_KTY_UNKNOWN:
+               lwsl_err("%s: jwk %p: unknown type\n", __func__, jwk);
+
+               return 1;
+       case LWS_GENCRYPTO_KTY_OCT:
+               elems = LWS_GENCRYPTO_OCT_KEYEL_COUNT;
+               enames = oct_names;
+               b64 = oct_b64;
+               break;
+       case LWS_GENCRYPTO_KTY_RSA:
+               elems = LWS_GENCRYPTO_RSA_KEYEL_COUNT;
+               enames = rsa_names;
+               b64 = rsa_b64;
+               break;
+       case LWS_GENCRYPTO_KTY_EC:
+               elems = LWS_GENCRYPTO_EC_KEYEL_COUNT;
+               enames = ec_names;
+               b64 = ec_b64;
+               break;
+       }
+
+       lwsl_info("%s: jwk %p\n", __func__, jwk);
+
+       for (n = 0; n < LWS_COUNT_JWK_ELEMENTS; n++) {
+               if (jwk->meta[n].buf && meta_b64[n]) {
+                       lwsl_info("  meta: %s\n", meta_names[n]);
+                       lwsl_hexdump_info(jwk->meta[n].buf, jwk->meta[n].len);
+               }
+               if (jwk->meta[n].buf && !meta_b64[n])
+                       lwsl_info("  meta: %s: '%s'\n", meta_names[n],
+                                       jwk->meta[n].buf);
+       }
+
+       for (n = 0; n < elems; n++) {
+               if (jwk->e[n].buf && b64[n]) {
+                       lwsl_info("  e: %s\n", enames[n]);
+                       lwsl_hexdump_info(jwk->e[n].buf, jwk->e[n].len);
+               }
+               if (jwk->e[n].buf && !b64[n])
+                       lwsl_info("  e: %s: '%s'\n", enames[n], jwk->e[n].buf);
+       }
+
+       return 0;
+}
+
+static int
+_lws_jwk_set_el_jwk(struct lws_gencrypto_keyelem *e, char *in, int len)
+{
+       e->buf = lws_malloc(len + 1, "jwk");
+       if (!e->buf)
+               return -1;
+
+       memcpy(e->buf, in, len);
+       e->buf[len] = '\0';
+       e->len = len;
+
+       return 0;
+}
+
+static int
+_lws_jwk_set_el_jwk_b64(struct lws_gencrypto_keyelem *e, char *in, int len)
+{
+       int dec_size = lws_base64_size(len), n;
+
+       e->buf = lws_malloc(dec_size, "jwk");
+       if (!e->buf)
+               return -1;
+
+       /* same decoder accepts both url or original styles */
+
+       n = lws_b64_decode_string_len(in, len, (char *)e->buf, dec_size - 1);
+       if (n < 0)
+               return -1;
+       e->len = n;
+
+       return 0;
+}
+
+static int
+_lws_jwk_set_el_jwk_b64u(struct lws_gencrypto_keyelem *e, char *in, int len)
+{
+       int dec_size = lws_base64_size(len), n;
+
+       e->buf = lws_malloc(dec_size, "jwk");
+       if (!e->buf)
+               return -1;
+
+       /* same decoder accepts both url or original styles */
+
+       n = lws_b64_decode_string_len(in, len, (char *)e->buf, dec_size - 1);
+       if (n < 0)
+               return -1;
+       e->len = n;
+
+       return 0;
+}
+
+void
+lws_jwk_destroy_elements(struct lws_gencrypto_keyelem *el, int m)
+{
+       int n;
+
+       for (n = 0; n < m; n++)
+               if (el[n].buf) {
+                       /* wipe all key material when it goes out of scope */
+                       lws_explicit_bzero(el[n].buf, el[n].len);
+                       lws_free_set_NULL(el[n].buf);
+                       el[n].len = 0;
+               }
+}
+
+LWS_VISIBLE void
+lws_jwk_destroy(struct lws_jwk *jwk)
+{
+       lws_jwk_destroy_elements(jwk->e, LWS_ARRAY_SIZE(jwk->e));
+       lws_jwk_destroy_elements(jwk->meta, LWS_ARRAY_SIZE(jwk->meta));
+}
+
+static signed char
+cb_jwk(struct lejp_ctx *ctx, char reason)
+{
+       struct lws_jwk_parse_state *jps = (struct lws_jwk_parse_state *)ctx->user;
+       struct lws_jwk *jwk = jps->jwk;
+       unsigned int idx, poss, n;
+
+       if (reason == LEJPCB_VAL_STR_START)
+               jps->pos = 0;
+
+       if (reason == LEJPCB_OBJECT_START && ctx->path_match == 0 + 1)
+               /*
+                * new keys[] member is starting
+                *
+                * Until we see some JSON names, it could be anything...
+                * there is no requirement for kty to be given first and eg,
+                * ACME specifies the keys must be ordered in lexographic
+                * order - where kty is not first.
+                */
+               jps->possible = F_RSA | F_EC | F_OCT;
+
+       if (reason == LEJPCB_OBJECT_END && ctx->path_match == 0 + 1) {
+               /* we completed parsing a key */
+               if (jps->per_key_cb && jps->possible) {
+                       if (jps->per_key_cb(jps->jwk, jps->user)) {
+
+                               lwsl_notice("%s: user cb halts import\n",
+                                           __func__);
+
+                               return -2;
+                       }
+
+                       /* clear it down */
+                       lws_jwk_destroy(jps->jwk);
+                       jps->possible = 0;
+               }
+       }
+
+       if (reason == LEJPCB_COMPLETE) {
+
+               /*
+                * Now we saw the whole jwk and know the key type, let'jwk insist
+                * that as a whole, it must be consistent and complete.
+                *
+                * The tracking of ->possible bits from even before we know the
+                * kty already makes certain we cannot have key element members
+                * defined that are inconsistent with the key type.
+                */
+
+               for (n = 0; n < LWS_ARRAY_SIZE(tok_map); n++)
+                       /*
+                        * All mandataory elements for the key type
+                        * must be present
+                        */
+                       if ((tok_map[n] & jps->possible) && (
+                           ((tok_map[n] & (F_M | F_META)) == (F_M | F_META) &&
+                            !jwk->meta[tok_map[n] & 0xff].buf) ||
+                           ((tok_map[n] & (F_M | F_META)) == F_M &&
+                            !jwk->e[tok_map[n] & 0xff].buf))) {
+                               lwsl_notice("%s: missing %s\n", __func__,
+                                           jwk_tok[n]);
+                                       return -3;
+                               }
+
+               /*
+                * When the key may be public or public + private, ensure the
+                * intra-key members related to that are consistent.
+                *
+                * Only RSA keys need extra care, since EC keys are already
+                * confirmed by making CRV, X and Y mandatory and only D
+                * (the singular private part) optional.  For RSA, N and E are
+                * also already known to be present using mandatory checking.
+                */
+
+               /*
+                * If a private key, it must have all D, P and Q.  Public key
+                * must have none of them.
+                */
+               if (jwk->kty == LWS_GENCRYPTO_KTY_RSA &&
+                   !(((!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf) &&
+                     (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf) &&
+                     (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf)) ||
+                     (jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf &&
+                      jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf &&
+                      jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf))
+                     ) {
+                       lwsl_notice("%s: RSA requires D, P and Q for private\n",
+                                   __func__);
+                       return -3;
+               }
+
+               /*
+                * If the precomputed private key terms appear, they must all
+                * appear together.
+                */
+               if (jwk->kty == LWS_GENCRYPTO_KTY_RSA &&
+                   !(((!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf) &&
+                     (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf) &&
+                     (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf)) ||
+                     (jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DP].buf &&
+                      jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf &&
+                      jwk->e[LWS_GENCRYPTO_RSA_KEYEL_QI].buf))
+                     ) {
+                       lwsl_notice("%s: RSA DP, DQ, QI must all appear "
+                                   "or none\n", __func__);
+                       return -3;
+               }
+
+               /*
+                * The precomputed private key terms must not appear without
+                * the private key itself also appearing.
+                */
+               if (jwk->kty == LWS_GENCRYPTO_KTY_RSA &&
+                   !jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf &&
+                    jwk->e[LWS_GENCRYPTO_RSA_KEYEL_DQ].buf) {
+                       lwsl_notice("%s: RSA DP, DQ, QI can appear only with "
+                                   "private key\n", __func__);
+                       return -3;
+               }
+
+               if ((jwk->kty == LWS_GENCRYPTO_KTY_RSA ||
+                    jwk->kty == LWS_GENCRYPTO_KTY_EC) &&
+                   jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf)
+               jwk->private_key = 1;
+       }
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       if (ctx->path_match == 0 + 1)
+               return 0;
+
+       idx = tok_map[ctx->path_match - 1];
+       if ((idx & 0xff) == 0xff)
+               return 0;
+
+       switch (idx) {
+       /* note: kty is not necessarily first... we have to keep track of
+        * what could match given which element names have already been
+        * seen.  Once kty comes, we confirm it'jwk still possible (ie, it'jwk
+        * not trying to tell us that it'jwk RSA now when we saw a "crv"
+        * earlier) and then reduce the possibilities to just the one that
+        * kty told. */
+       case F_RSA | F_EC | F_OCT | F_META | F_M | JWK_META_KTY:
+
+               if (ctx->npos == 3 && !strncmp(ctx->buf, "oct", 3)) {
+                       if (!(jps->possible & F_OCT))
+                               goto elements_mismatch;
+                       jwk->kty = LWS_GENCRYPTO_KTY_OCT;
+                       jps->possible = F_OCT;
+                       goto cont;
+               }
+               if (ctx->npos == 3 && !strncmp(ctx->buf, "RSA", 3)) {
+                       if (!(jps->possible & F_RSA))
+                               goto elements_mismatch;
+                       jwk->kty = LWS_GENCRYPTO_KTY_RSA;
+                       jps->possible = F_RSA;
+                       goto cont;
+               }
+               if (ctx->npos == 2 && !strncmp(ctx->buf, "EC", 2)) {
+                       if (!(jps->possible & F_EC))
+                               goto elements_mismatch;
+                       jwk->kty = LWS_GENCRYPTO_KTY_EC;
+                       jps->possible = F_EC;
+                       goto cont;
+               }
+               lwsl_err("%s: Unknown KTY '%.*s'\n", __func__, ctx->npos,
+                         ctx->buf);
+               return -1;
+
+       default:
+cont:
+               if (jps->pos + ctx->npos >= (int)sizeof(jps->b64))
+                       goto bail;
+
+               memcpy(jps->b64 + jps->pos, ctx->buf, ctx->npos);
+               jps->pos += ctx->npos;
+
+               if (reason == LEJPCB_VAL_STR_CHUNK)
+                       return 0;
+
+               /* chunking has been collated */
+
+               poss = idx & (F_RSA | F_EC | F_OCT);
+               jps->possible &= poss;
+               if (!jps->possible)
+                       goto elements_mismatch;
+
+               if (idx & F_META) {
+                       if (_lws_jwk_set_el_jwk(&jwk->meta[idx & 0x7f],
+                                               jps->b64, jps->pos) < 0)
+                               goto bail;
+
+                       break;
+               }
+
+               if (idx & F_B64U) {
+                       /* key data... do the base64 decode as needed */
+                       if (_lws_jwk_set_el_jwk_b64u(&jwk->e[idx & 0x7f],
+                                                    jps->b64, jps->pos) < 0)
+                               goto bail;
+
+                       if (jwk->e[idx & 0x7f].len >
+                                       LWS_JWE_LIMIT_KEY_ELEMENT_BYTES) {
+                               lwsl_notice("%s: oversize keydata\n", __func__);
+                               goto bail;
+                       }
+
+                       return 0;
+               }
+
+               if (idx & F_B64) {
+
+                       /* cert data... do non-urlcoded base64 decode */
+                       if (_lws_jwk_set_el_jwk_b64(&jwk->e[idx & 0x7f],
+                                                   jps->b64, jps->pos) < 0)
+                               goto bail;
+                       return 0;
+               }
+
+                       if (_lws_jwk_set_el_jwk(&jwk->e[idx & 0x7f],
+                                               jps->b64, jps->pos) < 0)
+                               goto bail;
+               break;
+       }
+
+       return 0;
+
+elements_mismatch:
+       lwsl_err("%s: jwk elements mismatch\n", __func__);
+
+bail:
+       lwsl_err("%s: element failed\n", __func__);
+
+       return -1;
+}
+
+void
+lws_jwk_init_jps(struct lejp_ctx *jctx, struct lws_jwk_parse_state *jps,
+                struct lws_jwk *jwk, lws_jwk_key_import_callback cb,
+                void *user)
+{
+       if (jwk)
+               memset(jwk, 0, sizeof(*jwk));
+
+       jps->jwk = jwk;
+       jps->possible = F_RSA | F_EC | F_OCT;
+       jps->per_key_cb = cb;
+       jps->user = user;
+       jps->pos = 0;
+
+       lejp_construct(jctx, cb_jwk, jps, cb ? jwk_outer_tok: jwk_tok,
+                      LWS_ARRAY_SIZE(jwk_tok));
+}
+
+LWS_VISIBLE int
+lws_jwk_dup_oct(struct lws_jwk *jwk, const void *key, int len)
+{
+       jwk->e[LWS_GENCRYPTO_KTY_OCT].buf = lws_malloc(len, __func__);
+       if (!jwk->e[LWS_GENCRYPTO_KTY_OCT].buf)
+               return -1;
+
+       jwk->kty = LWS_GENCRYPTO_KTY_OCT;
+       jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].len = len;
+
+       memcpy(jwk->e[LWS_GENCRYPTO_KTY_OCT].buf, key, len);
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jwk_generate(struct lws_context *context, struct lws_jwk *jwk,
+                enum lws_gencrypto_kty kty, int bits, const char *curve)
+{
+       int n;
+
+       memset(jwk, 0, sizeof(*jwk));
+
+       jwk->kty = kty;
+       jwk->private_key = 1;
+
+       switch (kty) {
+       case LWS_GENCRYPTO_KTY_RSA:
+       {
+               struct lws_genrsa_ctx ctx;
+
+               lwsl_notice("%s: generating %d bit RSA key\n", __func__, bits);
+               n = lws_genrsa_new_keypair(context, &ctx, LGRSAM_PKCS1_1_5,
+                                           jwk->e, bits);
+               lws_genrsa_destroy(&ctx);
+               if (n) {
+                       lwsl_err("%s: problem generating RSA key\n", __func__);
+                       return 1;
+               }
+       }
+               break;
+       case LWS_GENCRYPTO_KTY_OCT:
+               n = lws_gencrypto_bits_to_bytes(bits);
+               jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].buf = lws_malloc(n, "oct");
+               jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].len = n;
+               if (lws_get_random(context,
+                                jwk->e[LWS_GENCRYPTO_OCT_KEYEL_K].buf, n) != n) {
+                       lwsl_err("%s: problem getting random\n", __func__);
+                       return 1;
+               }
+               break;
+       case LWS_GENCRYPTO_KTY_EC:
+       {
+               struct lws_genec_ctx ctx;
+
+               if (!curve) {
+                       lwsl_err("%s: must have a named curve\n", __func__);
+
+                       return 1;
+               }
+
+               if (lws_genecdsa_create(&ctx, context, NULL))
+                       return 1;
+
+               lwsl_notice("%s: generating ECDSA key on curve %s\n", __func__,
+                               curve);
+
+               n = lws_genecdsa_new_keypair(&ctx, curve, jwk->e);
+               lws_genec_destroy(&ctx);
+               if (n) {
+                       lwsl_err("%s: problem generating ECDSA key\n", __func__);
+                       return 1;
+               }
+       }
+               break;
+
+       case LWS_GENCRYPTO_KTY_UNKNOWN:
+       default:
+               lwsl_err("%s: unknown kty\n", __func__);
+               return 1;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jwk_import(struct lws_jwk *jwk, lws_jwk_key_import_callback cb, void *user,
+              const char *in, size_t len)
+{
+       struct lejp_ctx jctx;
+       struct lws_jwk_parse_state jps;
+       int m;
+
+       lws_jwk_init_jps(&jctx, &jps, jwk, cb, user);
+
+       m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)in, len);
+       lejp_destruct(&jctx);
+
+       if (m < 0) {
+               lwsl_notice("%s: parse got %d\n", __func__, m);
+               lws_jwk_destroy(jwk);
+               return -1;
+       }
+
+       switch (jwk->kty) {
+       case LWS_GENCRYPTO_KTY_UNKNOWN:
+               lwsl_notice("%s: missing or unknown kyt\n", __func__);
+               lws_jwk_destroy(jwk);
+               return -1;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+
+LWS_VISIBLE int
+lws_jwk_export(struct lws_jwk *jwk, int private, char *p, int *len)
+{
+       char *start = p, *end = &p[*len - 1];
+       int n, m, limit, first = 1, asym = 0;
+       struct lexico *l;
+
+       /* RFC7638 lexicographic order requires
+        *  RSA: e -> kty -> n
+        *  oct: k -> kty
+        *
+        * ie, meta and key data elements appear interleaved in name alpha order
+        */
+
+       p += lws_snprintf(p, end - p, "{");
+
+       switch (jwk->kty) {
+       case LWS_GENCRYPTO_KTY_OCT:
+               l = lexico_oct;
+               limit = LWS_ARRAY_SIZE(lexico_oct);
+               break;
+       case LWS_GENCRYPTO_KTY_RSA:
+               l = lexico_rsa;
+               limit = LWS_ARRAY_SIZE(lexico_rsa);
+               asym = 1;
+               break;
+       case LWS_GENCRYPTO_KTY_EC:
+               l = lexico_ec;
+               limit = LWS_ARRAY_SIZE(lexico_ec);
+               asym = 1;
+               break;
+       default:
+               return -1;
+       }
+
+       for (n = 0; n < limit; n++) {
+               const char *q, *q_end;
+               char tok[12];
+               int pos = 0, f = 1;
+
+               if ((l->meta & 1) && (jwk->meta[l->idx].buf ||
+                                     l->idx == (int)JWK_META_KTY)) {
+
+                       switch (l->idx) {
+                       case JWK_META_KTY:
+                               if (!first)
+                                       *p++ = ',';
+                               first = 0;
+                               p += lws_snprintf(p, end - p, "\"%s\":\"%s\"",
+                                                 l->name, kty_names[jwk->kty]);
+                               break;
+                       case JWK_META_KEY_OPS:
+                               if (!first)
+                                       *p++ = ',';
+                               first = 0;
+                               q = (const char *)jwk->meta[l->idx].buf;
+                               q_end = q + jwk->meta[l->idx].len;
+
+                               p += lws_snprintf(p, end - p,
+                                                 "\"%s\":[", l->name);
+                               /*
+                                * For the public version, usages that
+                                * require the private part must be
+                                * snipped
+                                */
+
+                               while (q < q_end) {
+                                       if (*q != ' ' && pos < (int)sizeof(tok) - 1) {
+                                               tok[pos++] = *q++;
+                                               if (q != q_end)
+                                                       continue;
+                                       }
+                                       tok[pos] = '\0';
+                                       pos = 0;
+                                       if (private || !asym ||
+                                           (strcmp(tok, "sign") &&
+                                           strcmp(tok, "encrypt"))) {
+                                               if (!f)
+                                                       *p++ = ',';
+                                               f = 0;
+                                               p += lws_snprintf(p, end - p,
+                                                       "\"%s\"", tok);
+                                       }
+                                       q++;
+                               }
+
+                               *p++ = ']';
+
+                               break;
+
+                       default:
+                               /* both sig and enc require asym private key */
+                               if (!private && asym && l->idx == (int)JWK_META_USE)
+                                       break;
+                               if (!first)
+                                       *p++ = ',';
+                               first = 0;
+                               p += lws_snprintf(p, end - p, "\"%s\":\"%.*s\"",
+                                                 l->name, jwk->meta[l->idx].len,
+                                                 jwk->meta[l->idx].buf);
+                               break;
+                       }
+               }
+
+               if ((!(l->meta & 1)) && jwk->e[l->idx].buf &&
+                   (private || !(l->meta & 2))) {
+                       if (!first)
+                               *p++ = ',';
+                       first = 0;
+
+                       p += lws_snprintf(p, end - p, "\"%s\":\"", l->name);
+
+                       if (jwk->kty == LWS_GENCRYPTO_KTY_EC &&
+                           l->idx == (int)LWS_GENCRYPTO_EC_KEYEL_CRV)
+                               m = lws_snprintf(p, end - p, "%.*s",
+                                       jwk->e[l->idx].len,
+                                       (const char *)jwk->e[l->idx].buf);
+                       else
+                               m = lws_jws_base64_enc(
+                                       (const char *)jwk->e[l->idx].buf,
+                                       jwk->e[l->idx].len, p, end - p - 4);
+                       if (m < 0) {
+                               lwsl_notice("%s: enc failed\n", __func__);
+                               return -1;
+                       }
+                       p += m;
+                       p += lws_snprintf(p, end - p, "\"");
+               }
+
+               l++;
+       }
+
+       p += lws_snprintf(p, end - p, "}\n");
+
+       *len -= p - start;
+
+       return p - start;
+}
+
+LWS_VISIBLE int
+lws_jwk_rfc7638_fingerprint(struct lws_jwk *jwk, char *digest32)
+{
+       struct lws_genhash_ctx hash_ctx;
+       int tmpsize = 2536, n;
+       char *tmp;
+
+       tmp = lws_malloc(tmpsize, "rfc7638 tmp");
+
+       n = lws_jwk_export(jwk, 0, tmp, &tmpsize);
+       if (n < 0)
+               goto bail;
+
+       if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))
+               goto bail;
+
+       if (lws_genhash_update(&hash_ctx, tmp, n)) {
+               lws_genhash_destroy(&hash_ctx, NULL);
+
+               goto bail;
+       }
+       lws_free(tmp);
+
+       if (lws_genhash_destroy(&hash_ctx, digest32))
+               return -1;
+
+       return 0;
+
+bail:
+       lws_free(tmp);
+
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_jwk_strdup_meta(struct lws_jwk *jwk, enum enum_jwk_meta_tok idx,
+                   const char *in, int len)
+{
+       jwk->meta[idx].buf = lws_malloc(len, __func__);
+       if (!jwk->meta[idx].buf)
+               return 1;
+       jwk->meta[idx].len = len;
+       memcpy(jwk->meta[idx].buf, in, len);
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jwk_load(struct lws_jwk *jwk, const char *filename,
+            lws_jwk_key_import_callback cb, void *user)
+{
+       int buflen = 4096;
+       char *buf = lws_malloc(buflen, "jwk-load");
+       int n;
+
+       if (!buf)
+               return -1;
+
+       n = lws_plat_read_file(filename, buf, buflen);
+       if (n < 0)
+               goto bail;
+
+       n = lws_jwk_import(jwk, cb, user, buf, n);
+       lws_free(buf);
+
+       return n;
+bail:
+       lws_free(buf);
+
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_jwk_save(struct lws_jwk *jwk, const char *filename)
+{
+       int buflen = 4096;
+       char *buf = lws_malloc(buflen, "jwk-save");
+       int n, m;
+
+       if (!buf)
+               return -1;
+
+       n = lws_jwk_export(jwk, 1, buf, &buflen);
+       if (n < 0)
+               goto bail;
+
+       m = lws_plat_write_file(filename, buf, n);
+
+       lws_free(buf);
+       if (m)
+               return -1;
+
+       return 0;
+
+bail:
+       lws_free(buf);
+
+       return -1;
+}
diff --git a/lib/jose/jws/jose.c b/lib/jose/jws/jose.c
new file mode 100644 (file)
index 0000000..627fd23
--- /dev/null
@@ -0,0 +1,602 @@
+/*
+ * libwebsockets - JSON Web Signature support
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * JOSE is actually specified as part of JWS RFC7515.  JWE references RFC7515
+ * to specify its JOSE JSON object.  So it lives in ./lib/jose/jws/jose.c.
+ */
+
+#include "core/private.h"
+#include "jose/private.h"
+
+#include <stdint.h>
+
+static const char * const jws_jose[] = {
+       "alg", /* REQUIRED */
+       "jku",
+       "jwk",
+       "kid",
+       "x5u",
+       "x5c",
+       "x5t",
+       "x5t#S256",
+       "typ",
+       "cty",
+       "crit",
+
+       /* valid for JWE only below here */
+
+       "recipients[].header",
+       "recipients[].header.alg",
+       "recipients[].header.kid",
+       "recipients[].encrypted_key",
+
+       "enc",
+       "zip", /* ("DEF" = deflate) */
+
+       "epk", /* valid for JWE ECDH only */
+       "apu", /* valid for JWE ECDH only */
+       "apv", /* valid for JWE ECDH only */
+       "iv",  /* valid for JWE AES only */
+       "tag", /* valid for JWE AES only */
+       "p2s", /* valid for JWE PBES2 only */
+       "p2c"  /* valid for JWE PBES2 only */
+};
+
+struct jose_cb_args {
+       struct lws_jose *jose;
+
+       struct lejp_ctx jwk_jctx; /* fake lejp context used to parse epk */
+       struct lws_jwk_parse_state jps; /* fake jwk parse state */
+
+       char *temp;
+       int *temp_len;
+
+       unsigned int is_jwe;
+       unsigned int recipients_array;
+
+       int recip;
+};
+
+/*
+ * JWE A.4.7 Complete JWE JSON Serialization example
+ *
+ * LEJPCB_CONSTRUCTED
+ *  LEJPCB_START
+ *   LEJPCB_OBJECT_START
+ *
+ *    protected LEJPCB_PAIR_NAME
+ *    protected LEJPCB_VAL_STR_START
+ *    protected LEJPCB_VAL_STR_END
+ *
+ *    unprotected LEJPCB_PAIR_NAME
+ *    unprotected LEJPCB_OBJECT_START
+ *     unprotected.jku LEJPCB_PAIR_NAME
+ *     unprotected.jku LEJPCB_VAL_STR_START
+ *     unprotected.jku LEJPCB_VAL_STR_END
+ *    unprotected.jku LEJPCB_OBJECT_END
+ *
+ *    recipients LEJPCB_PAIR_NAME
+ *    recipients[] LEJPCB_ARRAY_START
+ *
+ *     recipients[] LEJPCB_OBJECT_START
+ *      recipients[].header LEJPCB_PAIR_NAME
+ *      recipients[].header LEJPCB_OBJECT_START
+ *       recipients[].header.alg LEJPCB_PAIR_NAME
+ *       recipients[].header.alg LEJPCB_VAL_STR_START
+ *       recipients[].header.alg LEJPCB_VAL_STR_END
+ *       recipients[].header.kid LEJPCB_PAIR_NAME
+ *       recipients[].header.kid LEJPCB_VAL_STR_START
+ *       recipients[].header.kid LEJPCB_VAL_STR_END
+ *      recipients[] LEJPCB_OBJECT_END
+ *      recipients[].encrypted_key LEJPCB_PAIR_NAME
+ *      recipients[].encrypted_key LEJPCB_VAL_STR_START
+ *      recipients[].encrypted_key LEJPCB_VAL_STR_CHUNK
+ *      recipients[].encrypted_key LEJPCB_VAL_STR_END
+ *     recipients[] LEJPCB_OBJECT_END (ctx->sp = 1)
+ *
+ *     recipients[] LEJPCB_OBJECT_START
+ *      recipients[].header LEJPCB_PAIR_NAME
+ *      recipients[].header LEJPCB_OBJECT_START
+ *       recipients[].header.alg LEJPCB_PAIR_NAME
+ *       recipients[].header.alg LEJPCB_VAL_STR_START
+ *       recipients[].header.alg LEJPCB_VAL_STR_END
+ *       recipients[].header.kid LEJPCB_PAIR_NAME
+ *       recipients[].header.kid LEJPCB_VAL_STR_START
+ *       recipients[].header.kid LEJPCB_VAL_STR_END
+ *      recipients[] LEJPCB_OBJECT_END
+ *      recipients[].encrypted_key LEJPCB_PAIR_NAME
+ *      recipients[].encrypted_key LEJPCB_VAL_STR_START
+ *      recipients[].encrypted_key LEJPCB_VAL_STR_END
+ *     recipients[] LEJPCB_OBJECT_END (ctx->sp = 1)
+ *
+ *    recipients[] LEJPCB_ARRAY_END
+ *
+ *    iv LEJPCB_PAIR_NAME
+ *    iv LEJPCB_VAL_STR_START
+ *    iv LEJPCB_VAL_STR_END
+ *    ciphertext LEJPCB_PAIR_NAME
+ *    ciphertext LEJPCB_VAL_STR_START
+ *    ciphertext LEJPCB_VAL_STR_END
+ *    tag LEJPCB_PAIR_NAME
+ *    tag LEJPCB_VAL_STR_START
+ *    tag LEJPCB_VAL_STR_END
+ *
+ *   tag LEJPCB_OBJECT_END
+ *  tag LEJPCB_COMPLETE
+ * tag LEJPCB_DESTRUCTED
+ *
+ */
+
+/*
+ * RFC7516 7.2.2
+ *
+ * Note that when using the flattened syntax, just as when using the
+ * general syntax, any unprotected Header Parameter values can reside in
+ * either the "unprotected" member or the "header" member, or in both.
+ */
+
+static signed char
+lws_jws_jose_cb(struct lejp_ctx *ctx, char reason)
+{
+       struct jose_cb_args *args = (struct jose_cb_args *)ctx->user;
+       int n; //, dest;
+
+       /*
+        * In JOSE JSON, the element "epk" contains a fully-formed JWK.
+        *
+        * For JOSE paths beginning "epk.", we pass them through to a JWK
+        * LEJP subcontext to parse using the JWK parser directly.
+        */
+
+       if (args->is_jwe && !strncmp(ctx->path, "epk.", 4)) {
+               memcpy(args->jwk_jctx.path, ctx->path + 4,
+                      sizeof(ctx->path) - 4);
+               memcpy(args->jwk_jctx.buf, ctx->buf, ctx->npos);
+               args->jwk_jctx.npos = ctx->npos;
+
+               if (!ctx->path_match)
+                       args->jwk_jctx.path_match = 0;
+               lejp_check_path_match(&args->jwk_jctx);
+
+               if (args->jwk_jctx.path_match)
+                       args->jwk_jctx.pst[args->jwk_jctx.pst_sp].
+                               callback(&args->jwk_jctx, reason);
+       }
+
+       // lwsl_notice("%s: %s %d (%d)\n", __func__, ctx->path, reason, ctx->sp);
+
+       /* at the end of each recipients[] entry, bump recipients count */
+
+       if (args->is_jwe && reason == LEJPCB_OBJECT_END && ctx->sp == 1 &&
+           !strcmp(ctx->path, "recipients[]"))
+               args->jose->recipients++;
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       //dest = ctx->path_match - 1;
+
+       switch (ctx->path_match - 1) {
+
+       /* strings */
+
+       case LJJHI_ALG: /* REQUIRED */
+
+               /*
+                * look up whether we support this alg and point the caller at
+                * its definition if so
+                */
+
+               if (!args->is_jwe &&
+                   lws_gencrypto_jws_alg_to_definition(ctx->buf,
+                                                       &args->jose->alg)) {
+                       lwsl_notice("%s: unknown alg '%s'\n", __func__,
+                                   ctx->buf);
+
+                       return -1;
+               }
+
+               if (args->is_jwe &&
+                   lws_gencrypto_jwe_alg_to_definition(ctx->buf,
+                                                       &args->jose->alg)) {
+                       lwsl_notice("%s: unknown JWE alg '%s'\n", __func__,
+                                   ctx->buf);
+
+                       return -1;
+               }
+
+               return 0;
+
+       case LJJHI_TYP: /* Optional: string: media type */
+               if (strcmp(ctx->buf, "JWT"))
+                       return -1;
+               break;
+
+       case LJJHI_JKU: /* Optional: string */
+       case LJJHI_KID: /* Optional: string */
+       case LJJHI_X5U: /* Optional: string: url of public key cert / chain */
+       case LJJHI_CTY: /* Optional: string: content media type */
+
+       /* base64 */
+
+       case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */
+
+       /* base64-url */
+
+       case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */
+       case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of actual cert */
+
+       /* array of strings */
+
+       case LJJHI_CRIT: /* Optional for send, REQUIRED: array of strings:
+                         * mustn't contain standardized strings or null set */
+               break;
+
+       /* jwk child */
+
+       case LJJHI_JWK: /* Optional: jwk JSON object: public key: */
+
+       /* past here, JWE only */
+
+       case LJJHI_RECIPS_HDR:
+               if (!args->is_jwe) {
+                       lwsl_info("%s: recipients in jws\n", __func__);
+                       return -1;
+               }
+               args->recipients_array = 1;
+               break;
+
+       case LJJHI_RECIPS_HDR_ALG:
+       case LJJHI_RECIPS_HDR_KID:
+               break;
+
+       case LJJHI_RECIPS_EKEY:
+               if (!args->is_jwe) {
+                       lwsl_info("%s: recipients in jws\n", __func__);
+                       return -1;
+               }
+               args->recipients_array = 1;
+               //dest = ;
+               goto append_string;
+
+       case LJJHI_ENC: /* JWE only: Mandatory: string */
+               if (!args->is_jwe) {
+                       lwsl_info("%s: enc in jws\n", __func__);
+                       return -1;
+               }
+               if (lws_gencrypto_jwe_enc_to_definition(ctx->buf,
+                                                       &args->jose->enc_alg)) {
+                       lwsl_notice("%s: unknown enc '%s'\n", __func__,
+                                   ctx->buf);
+
+                       return -1;
+               }
+               break;
+
+       case LJJHI_ZIP: /* JWE only: Optional: string ("DEF" = deflate) */
+               if (!args->is_jwe)
+                       return -1;
+               goto append_string;
+
+       case LJJHI_EPK: /* Additional arg for JWE ECDH */
+               if (!args->is_jwe)
+                       return -1;
+               /* Ephemeral key... this JSON subsection is actually a JWK */
+               lwsl_err("LJJHI_EPK\n");
+               break;
+
+       case LJJHI_APU: /* Additional arg for JWE ECDH */
+               if (!args->is_jwe)
+                       return -1;
+               /* Agreement Party U */
+               goto append_string;
+
+       case LJJHI_APV: /* Additional arg for JWE ECDH */
+               if (!args->is_jwe)
+                       return -1;
+               /* Agreement Party V */
+               goto append_string;
+
+       case LJJHI_IV:  /* Additional arg for JWE AES */
+               if (!args->is_jwe)
+                       return -1;
+               goto append_string;
+
+       case LJJHI_TAG: /* Additional arg for JWE AES */
+               if (!args->is_jwe)
+                       return -1;
+               goto append_string;
+
+       case LJJHI_P2S: /* Additional arg for JWE PBES2 */
+               if (!args->is_jwe)
+                       return -1;
+               goto append_string;
+       case LJJHI_P2C: /* Additional arg for JWE PBES2 */
+               if (!args->is_jwe)
+                       return -1;
+               goto append_string;
+
+       /* ignore what we don't understand */
+
+       default:
+               return 0;
+       }
+
+       return 0;
+
+append_string:
+
+       if (*args->temp_len < ctx->npos) {
+               lwsl_err("%s: out of parsing space\n", __func__);
+               return -1;
+       }
+
+       if (!args->jose->e[ctx->path_match - 1].buf) {
+               args->jose->e[ctx->path_match - 1].buf = (uint8_t *)args->temp;
+               args->jose->e[ctx->path_match - 1].len = 0;
+       }
+
+       memcpy(args->temp, ctx->buf, ctx->npos);
+       args->temp += ctx->npos;
+       *args->temp_len -= ctx->npos;
+       args->jose->e[ctx->path_match - 1].len += ctx->npos;
+
+       if (reason == LEJPCB_VAL_STR_END) {
+               n = lws_b64_decode_string_len(
+                       (const char *)args->jose->e[ctx->path_match - 1].buf,
+                       args->jose->e[ctx->path_match - 1].len,
+                       (char *)args->jose->e[ctx->path_match - 1].buf,
+                       args->jose->e[ctx->path_match - 1].len + 1);
+               if (n < 0) {
+                       lwsl_err("%s: b64 decode failed\n", __func__);
+                       return -1;
+               }
+
+               args->temp -= args->jose->e[ctx->path_match - 1].len - n - 1;
+               *args->temp_len +=
+                       args->jose->e[ctx->path_match - 1].len - n - 1;
+
+               args->jose->e[ctx->path_match - 1].len = n;
+       }
+
+       return 0;
+}
+
+void
+lws_jose_init(struct lws_jose *jose)
+{
+       memset(jose, 0, sizeof(*jose));
+}
+
+static void
+lws_jose_recip_destroy(struct lws_jws_recpient *r)
+{
+       lws_jwk_destroy(&r->jwk_ephemeral);
+       lws_jwk_destroy(&r->jwk);
+}
+
+void
+lws_jose_destroy(struct lws_jose *jose)
+{
+       int n;
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(jose->recipient); n++)
+               lws_jose_recip_destroy(&jose->recipient[n]);
+}
+
+
+static int
+lws_jose_parse(struct lws_jose *jose, const uint8_t *buf, int n,
+              char *temp, int *temp_len, int is_jwe)
+{
+       struct lejp_ctx jctx;
+       struct jose_cb_args args;
+       int m;
+
+       if (is_jwe)
+               /* prepare a context for JOSE epk ephemeral jwk parsing */
+               lws_jwk_init_jps(&args.jwk_jctx, &args.jps,
+                                &jose->recipient[jose->recipients].jwk_ephemeral,
+                                NULL, NULL);
+
+       args.is_jwe = is_jwe;
+       args.temp = temp;
+       args.temp_len = temp_len;
+       args.jose = jose;
+       args.recip = 0;
+       args.recipients_array = 0;
+       jose->recipients = 0;
+
+       lejp_construct(&jctx, lws_jws_jose_cb, &args, jws_jose,
+                      LWS_ARRAY_SIZE(jws_jose));
+
+       m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, n);
+       lejp_destruct(&jctx);
+       if (m < 0) {
+               lwsl_notice("%s: parse %.*s returned %d\n", __func__, n, buf, m);
+               return -1;
+       }
+
+       if (!args.recipients_array && jose->recipient[0].unprot[LJJHI_ALG].buf)
+               /* if no explicit recipients[], we got one */
+               jose->recipients++;
+
+       return 0;
+}
+
+int
+lws_jws_parse_jose(struct lws_jose *jose,
+                  const char *buf, int len, char *temp, int *temp_len)
+{
+       return lws_jose_parse(jose, (const uint8_t *)buf, len,
+                       temp, temp_len, 0);
+}
+
+int
+lws_jwe_parse_jose(struct lws_jose *jose,
+                  const char *buf, int len, char *temp, int *temp_len)
+{
+       return lws_jose_parse(jose,
+                             (const uint8_t *)buf, len, temp, temp_len, 1);
+}
+
+int
+lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk,
+               char *out, size_t out_len)
+{
+       struct lws_jwk *jwk;
+       char *end = out + out_len - 1;
+       int n, m, f, sub = 0, vl;
+
+       /* JOSE requires an alg */
+       if (!jose->alg || !jose->alg->alg)
+               goto bail;
+
+       *out++ = '{';
+
+       for (n = 0; n < LWS_COUNT_JOSE_HDR_ELEMENTS; n++) {
+               switch (n) {
+
+               /* strings */
+
+               case LJJHI_ALG: /* REQUIRED */
+               case LJJHI_JKU: /* Optional: string */
+               case LJJHI_KID: /* Optional: string */
+               case LJJHI_TYP: /* Optional: string: media type */
+               case LJJHI_CTY: /* Optional: string: content media type */
+               case LJJHI_X5U: /* Optional: string: pubkey cert / chain URL */
+               case LJJHI_ENC: /* JWE only: Optional: string */
+               case LJJHI_ZIP: /* JWE only: Optional: string ("DEF"=deflate) */
+                       if (jose->e[n].buf) {
+                               out += lws_snprintf(out, end - out,
+                                       "%s\"%s\":\"%s\"", sub ? ",\n" : "",
+                                       jws_jose[n], jose->e[n].buf);
+                               sub = 1;
+                       }
+                       break;
+
+               case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */
+               case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of cert */
+               case LJJHI_APU: /* Additional arg for JWE ECDH:  b64url */
+               case LJJHI_APV: /* Additional arg for JWE ECDH:  b64url */
+               case LJJHI_IV:  /* Additional arg for JWE AES:   b64url */
+               case LJJHI_TAG: /* Additional arg for JWE AES:   b64url */
+               case LJJHI_P2S: /* Additional arg for JWE PBES2: b64url: salt */
+                       if (jose->e[n].buf) {
+                               out += lws_snprintf(out, end - out,
+                                       "%s\"%s\":\"", sub ? ",\n" : "",
+                                               jws_jose[n]);
+                               sub = 1;
+                               m = lws_b64_encode_string_url((const char *)
+                                               jose->e[n].buf, jose->e[n].len,
+                                               out, end - out);
+                               if (m < 0)
+                                       return -1;
+                               out += m;
+                               out += lws_snprintf(out, end - out, "\"");
+                       }
+                       break;
+
+               case LJJHI_P2C: /* Additional arg for JWE PBES2: int: count */
+                       break; /* don't support atm */
+
+               case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */
+                       if (jose->e[n].buf) {
+                               out += lws_snprintf(out, end - out,
+                                       "%s\"%s\":\"", sub ? ",\n" : "",
+                                                       jws_jose[n]);
+                               sub = 1;
+                               m = lws_b64_encode_string((const char *)
+                                               jose->e[n].buf, jose->e[n].len,
+                                               out, end - out);
+                               if (m < 0)
+                                       return -1;
+                               out += m;
+                               out += lws_snprintf(out, end - out, "\"");
+                       }
+                       break;
+
+               case LJJHI_EPK: /* Additional arg for JWE ECDH:  eph pubkey */
+               case LJJHI_JWK: /* Optional: jwk JSON object: public key: */
+
+                       jwk = n == LJJHI_EPK ? &jose->recipient[0].jwk_ephemeral : aux_jwk;
+                       if (!jwk || !jwk->kty)
+                               break;
+
+                       out += lws_snprintf(out, end - out, "%s\"%s\":",
+                                           sub ? ",\n" : "", jws_jose[n]);
+                       sub = 1;
+                       vl = end - out;
+                       m = lws_jwk_export(jwk, 0, out, &vl);
+                       if (m < 0) {
+                               lwsl_notice("%s: failed to export key\n",
+                                               __func__);
+
+                               return -1;
+                       }
+                       out += m;
+                       break;
+
+               case LJJHI_CRIT:/* Optional for send, REQUIRED: array of strings:
+                                * mustn't contain standardized strings or null set */
+                       if (!jose->e[n].buf)
+                               break;
+
+                       out += lws_snprintf(out, end - out,
+                               "%s\"%s\":[", sub ? ",\n" : "", jws_jose[n]);
+                       sub = 1;
+
+                       m = 0;
+                       f = 1;
+                       while ((unsigned int)m < jose->e[n].len && (end - out) > 1) {
+                               if (jose->e[n].buf[m] == ' ') {
+                                       if (!f)
+                                               *out++ = '\"';
+
+                                       m++;
+                                       f = 1;
+                                       continue;
+                               }
+
+                               if (f) {
+                                       if (m)
+                                               *out++ = ',';
+                                       *out++ = '\"';
+                                       f = 0;
+                               }
+
+                               *out++ = jose->e[n].buf[m];
+                               m++;
+                       }
+
+                       break;
+               }
+       }
+
+       *out++ = '}';
+
+       if (out > end - 2)
+               return -1;
+
+       return out_len - (end - out) - 1;
+
+bail:
+       return -1;
+}
diff --git a/lib/jose/jws/jws.c b/lib/jose/jws/jws.c
new file mode 100644 (file)
index 0000000..2b37fd4
--- /dev/null
@@ -0,0 +1,929 @@
+/*
+ * libwebsockets - JSON Web Signature support
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "private.h"
+
+/*
+ * Currently only support flattened or compact (implicitly single signature)
+ */
+
+static const char * const jws_json[] = {
+       "protected", /* base64u */
+       "header", /* JSON */
+       "payload", /* base64u payload */
+       "signature", /* base64u signature */
+
+       //"signatures[].protected",
+       //"signatures[].header",
+       //"signatures[].signature"
+};
+
+enum lws_jws_json_tok {
+       LJWSJT_PROTECTED,
+       LJWSJT_HEADER,
+       LJWSJT_PAYLOAD,
+       LJWSJT_SIGNATURE,
+
+       // LJWSJT_SIGNATURES_PROTECTED,
+       // LJWSJT_SIGNATURES_HEADER,
+       // LJWSJT_SIGNATURES_SIGNATURE,
+};
+
+/* parse a JWS complete or flattened JSON object */
+
+struct jws_cb_args {
+       struct lws_jws *jws;
+
+       char *temp;
+       int *temp_len;
+};
+
+static signed char
+lws_jws_json_cb(struct lejp_ctx *ctx, char reason)
+{
+       struct jws_cb_args *args = (struct jws_cb_args *)ctx->user;
+       int n, m;
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       switch (ctx->path_match - 1) {
+
+       /* strings */
+
+       case LJWSJT_PROTECTED:  /* base64u: JOSE: must contain 'alg' */
+               m = LJWS_JOSE;
+               goto append_string;
+       case LJWSJT_PAYLOAD:    /* base64u */
+               m = LJWS_PYLD;
+               goto append_string;
+       case LJWSJT_SIGNATURE:  /* base64u */
+               m = LJWS_SIG;
+               goto append_string;
+
+       case LJWSJT_HEADER:     /* unprotected freeform JSON */
+               break;
+
+       default:
+               return -1;
+       }
+
+       return 0;
+
+append_string:
+
+       if (*args->temp_len < ctx->npos) {
+               lwsl_err("%s: out of parsing space\n", __func__);
+               return -1;
+       }
+
+       /*
+        * We keep both b64u and decoded in temp mapped using map / map_b64,
+        * the jws signature is actually over the b64 content not the plaintext,
+        * and we can't do it until we see the protected alg.
+        */
+
+       if (!args->jws->map_b64.buf[m]) {
+               args->jws->map_b64.buf[m] = args->temp;
+               args->jws->map_b64.len[m] = 0;
+       }
+
+       memcpy(args->temp, ctx->buf, ctx->npos);
+       args->temp += ctx->npos;
+       *args->temp_len -= ctx->npos;
+       args->jws->map_b64.len[m] += ctx->npos;
+
+       if (reason == LEJPCB_VAL_STR_END) {
+               args->jws->map.buf[m] = args->temp;
+
+               n = lws_b64_decode_string_len(
+                       (const char *)args->jws->map_b64.buf[m],
+                       args->jws->map_b64.len[m],
+                       (char *)args->temp, *args->temp_len);
+               if (n < 0) {
+                       lwsl_err("%s: b64 decode failed: in len %d, m %d\n", __func__, (int)args->jws->map_b64.len[m], m);
+                       return -1;
+               }
+
+               args->temp += n;
+               *args->temp_len -= n;
+               args->jws->map.len[m] = n;
+       }
+
+       return 0;
+}
+
+static int
+lws_jws_json_parse(struct lws_jws *jws, const uint8_t *buf, int len,
+                  char *temp, int *temp_len)
+{
+       struct jws_cb_args args;
+       struct lejp_ctx jctx;
+       int m = 0;
+
+       args.jws = jws;
+       args.temp = temp;
+       args.temp_len = temp_len;
+
+       lejp_construct(&jctx, lws_jws_json_cb, &args, jws_json,
+                      LWS_ARRAY_SIZE(jws_json));
+
+       m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, len);
+       lejp_destruct(&jctx);
+       if (m < 0) {
+               lwsl_notice("%s: parse returned %d\n", __func__, m);
+               return -1;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE void
+lws_jws_init(struct lws_jws *jws, struct lws_jwk *jwk,
+            struct lws_context *context)
+{
+       memset(jws, 0, sizeof(*jws));
+       jws->context = context;
+       jws->jwk = jwk;
+}
+
+static void
+lws_jws_map_bzero(struct lws_jws_map *map)
+{
+       int n;
+
+       /* no need to scrub first jose header element (it can be canned then) */
+
+       for (n = 1; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++)
+               if (map->buf[n])
+                       lws_explicit_bzero((void *)map->buf[n], map->len[n]);
+}
+
+LWS_VISIBLE void
+lws_jws_destroy(struct lws_jws *jws)
+{
+       lws_jws_map_bzero(&jws->map);
+       jws->jwk = NULL;
+}
+
+LWS_VISIBLE int
+lws_jws_dup_element(struct lws_jws_map *map, int idx, char *temp, int *temp_len,
+                   const void *in, size_t in_len, size_t actual_alloc)
+{
+       if (!actual_alloc)
+               actual_alloc = in_len;
+
+       if ((size_t)*temp_len < actual_alloc)
+               return -1;
+
+       memcpy(temp, in, in_len);
+
+       map->len[idx] = in_len;
+       map->buf[idx] = temp;
+
+       *temp_len -= actual_alloc;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jws_encode_b64_element(struct lws_jws_map *map, int idx,
+                          char *temp, int *temp_len, const void *in,
+                          size_t in_len)
+{
+       int n;
+
+       if (*temp_len < lws_base64_size((int)in_len))
+               return -1;
+
+       n = lws_jws_base64_enc(in, in_len, temp, *temp_len);
+       if (n < 0)
+               return -1;
+
+       map->len[idx] = n;
+       map->buf[idx] = temp;
+
+       *temp_len -= n;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jws_randomize_element(struct lws_context *context, struct lws_jws_map *map,
+                         int idx, char *temp, int *temp_len, size_t random_len,
+                         size_t actual_alloc)
+{
+       if (!actual_alloc)
+               actual_alloc = random_len;
+
+       if ((size_t)*temp_len < actual_alloc)
+               return -1;
+
+       map->len[idx] = random_len;
+       map->buf[idx] = temp;
+
+       if (lws_get_random(context, temp, random_len) != (int)random_len) {
+               lwsl_err("Problem getting random\n");
+               return -1;
+       }
+
+       *temp_len -= actual_alloc;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jws_alloc_element(struct lws_jws_map *map, int idx, char *temp,
+                     int *temp_len, size_t len, size_t actual_alloc)
+{
+       if (!actual_alloc)
+               actual_alloc = len;
+
+       if ((size_t)*temp_len < actual_alloc)
+               return -1;
+
+       map->len[idx] = len;
+       map->buf[idx] = temp;
+       *temp_len -= actual_alloc;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max)
+{
+       int n;
+
+       n = lws_b64_encode_string_url(in, in_len, out, out_max - 1);
+       if (n < 0) {
+               lwsl_notice("%s: in len %d too large for %d out buf\n",
+                               __func__, (int)in_len, (int)out_max);
+               return n; /* too large for output buffer */
+       }
+
+       /* trim the terminal = */
+       while (n && out[n - 1] == '=')
+               n--;
+
+       out[n] = '\0';
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_jws_b64_compact_map(const char *in, int len, struct lws_jws_map *map)
+{
+       int me = 0;
+
+       memset(map, 0, sizeof(*map));
+
+       map->buf[me] = (char *)in;
+       map->len[me] = 0;
+
+       while (len--) {
+               if (*in++ == '.') {
+                       if (++me == LWS_JWS_MAX_COMPACT_BLOCKS)
+                               return -1;
+                       map->buf[me] = (char *)in;
+                       map->len[me] = 0;
+                       continue;
+               }
+               map->len[me]++;
+       }
+
+       return me + 1;
+}
+
+/* b64 in, map contains decoded elements, if non-NULL,
+ * map_b64 set to b64 elements
+ */
+
+LWS_VISIBLE int
+lws_jws_compact_decode(const char *in, int len, struct lws_jws_map *map,
+                      struct lws_jws_map *map_b64, char *out,
+                      int *out_len)
+{
+       int blocks, n, m = 0;
+
+       if (!map_b64)
+               map_b64 = map;
+
+       memset(map_b64, 0, sizeof(*map_b64));
+       memset(map, 0, sizeof(*map));
+
+       blocks = lws_jws_b64_compact_map(in, len, map_b64);
+
+       if (blocks > LWS_JWS_MAX_COMPACT_BLOCKS)
+               return -1;
+
+       while (m < blocks) {
+               n = lws_b64_decode_string_len(map_b64->buf[m], map_b64->len[m],
+                                             out, *out_len);
+               if (n < 0) {
+                       lwsl_err("%s: b64 decode failed\n", __func__);
+                       return -1;
+               }
+               /* replace the map entry with the decoded content */
+               if (n)
+                       map->buf[m] = out;
+               else
+                       map->buf[m] = NULL;
+               map->len[m++] = n;
+               out += n;
+               *out_len -= n;
+
+               if (*out_len < 1)
+                       return -1;
+       }
+
+       return blocks;
+}
+
+static int
+lws_jws_compact_decode_map(struct lws_jws_map *map_b64, struct lws_jws_map *map,
+                          char *out, int *out_len)
+{
+       int n, m = 0;
+
+       for (n = 0; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++) {
+               n = lws_b64_decode_string_len(map_b64->buf[m], map_b64->len[m],
+                                             out, *out_len);
+               if (n < 0) {
+                       lwsl_err("%s: b64 decode failed\n", __func__);
+                       return -1;
+               }
+               /* replace the map entry with the decoded content */
+               map->buf[m] = out;
+               map->len[m++] = n;
+               out += n;
+               *out_len -= n;
+
+               if (*out_len < 1)
+                       return -1;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_jws_encode_section(const char *in, size_t in_len, int first, char **p,
+                      char *end)
+{
+       int n, len = (end - *p) - 1;
+       char *p_entry = *p;
+
+       if (len < 3)
+               return -1;
+
+       if (!first)
+               *(*p)++ = '.';
+
+       n = lws_jws_base64_enc(in, in_len, *p, len - 1);
+       if (n < 0)
+               return -1;
+
+       *p += n;
+
+       return (*p) - p_entry;
+}
+
+LWS_VISIBLE int
+lws_jws_compact_encode(struct lws_jws_map *map_b64, /* b64-encoded */
+                      const struct lws_jws_map *map,   /* non-b64 */
+                      char *buf, int *len)
+{
+       int n, m;
+
+       for (n = 0; n < LWS_JWS_MAX_COMPACT_BLOCKS; n++) {
+               if (!map->buf[n]) {
+                       map_b64->buf[n] = NULL;
+                       map_b64->len[n] = 0;
+                       continue;
+               }
+               m = lws_jws_base64_enc(map->buf[n], map->len[n], buf, *len);
+               if (m < 0)
+                       return -1;
+               buf += m;
+               *len -= m;
+               if (*len < 1)
+                       return -1;
+       }
+
+       return 0;
+}
+
+/*
+ * This takes both a base64 -encoded map and a plaintext map.
+ *
+ * JWS demands base-64 encoded elements for hash computation and at least for
+ * the JOSE header and signature, decoded versions too.
+ */
+
+LWS_VISIBLE int
+lws_jws_sig_confirm(struct lws_jws_map *map_b64, struct lws_jws_map *map,
+                   struct lws_jwk *jwk, struct lws_context *context)
+{
+       enum enum_genrsa_mode padding = LGRSAM_PKCS1_1_5;
+       char temp[256];
+       int n, h_len, b = 3, temp_len = sizeof(temp);
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_genhash_ctx hash_ctx;
+       struct lws_genec_ctx ecdsactx;
+       struct lws_genrsa_ctx rsactx;
+       struct lws_genhmac_ctx ctx;
+       struct lws_jose jose;
+
+       lws_jose_init(&jose);
+
+       /* only valid if no signature or key */
+       if (!map_b64->buf[LJWS_SIG] && !map->buf[LJWS_UHDR])
+               b = 2;
+
+       if (lws_jws_parse_jose(&jose, map->buf[LJWS_JOSE], map->len[LJWS_JOSE],
+                              temp, &temp_len) < 0) {
+               lwsl_notice("%s: parse failed\n", __func__);
+               return -1;
+       }
+
+       if (!strcmp(jose.alg->alg, "none")) {
+               /* "none" compact serialization has 2 blocks: jose.payload */
+               if (b != 2 || jwk)
+                       return -1;
+
+               /* the lack of a key matches the lack of a signature */
+               return 0;
+       }
+
+       /* all other have 3 blocks: jose.payload.sig */
+       if (b != 3 || !jwk) {
+               lwsl_notice("%s: %d blocks\n", __func__, b);
+               return -1;
+       }
+
+       switch (jose.alg->algtype_signing) {
+       case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS:
+       case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP:
+               padding = LGRSAM_PKCS1_OAEP_PSS;
+               /* fallthru */
+       case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5:
+
+               /* RSASSA-PKCS1-v1_5 or OAEP using SHA-256/384/512 */
+
+               if (jwk->kty != LWS_GENCRYPTO_KTY_RSA)
+                       return -1;
+
+               /* 6(RSA): compute the hash of the payload into "digest" */
+
+               if (lws_genhash_init(&hash_ctx, jose.alg->hash_type))
+                       return -1;
+
+               /*
+                * JWS Signing Input value:
+                *
+                * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
+                *      BASE64URL(JWS Payload)
+                */
+
+               if (lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_JOSE],
+                                                 map_b64->len[LJWS_JOSE]) ||
+                   lws_genhash_update(&hash_ctx, ".", 1) ||
+                   lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_PYLD],
+                                                 map_b64->len[LJWS_PYLD]) ||
+                   lws_genhash_destroy(&hash_ctx, digest)) {
+                       lws_genhash_destroy(&hash_ctx, NULL);
+
+                       return -1;
+               }
+               h_len = lws_genhash_size(jose.alg->hash_type);
+
+               if (lws_genrsa_create(&rsactx, jwk->e, context, padding,
+                               LWS_GENHASH_TYPE_UNKNOWN)) {
+                       lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                                   __func__);
+                       return -1;
+               }
+
+               n = lws_genrsa_hash_sig_verify(&rsactx, digest,
+                                              jose.alg->hash_type,
+                                              (uint8_t *)map->buf[LJWS_SIG],
+                                              map->len[LJWS_SIG]);
+
+               lws_genrsa_destroy(&rsactx);
+               if (n < 0) {
+                       lwsl_notice("%s: decrypt fail\n", __func__);
+                       return -1;
+               }
+
+               break;
+
+       case LWS_JOSE_ENCTYPE_NONE: /* HSxxx */
+
+               /* SHA256/384/512 HMAC */
+
+               h_len = lws_genhmac_size(jose.alg->hmac_type);
+
+               /* 6) compute HMAC over payload */
+
+               if (lws_genhmac_init(&ctx, jose.alg->hmac_type,
+                                    jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf,
+                                    jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len))
+                       return -1;
+
+               /*
+                * JWS Signing Input value:
+                *
+                * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
+                *   BASE64URL(JWS Payload)
+                */
+
+               if (lws_genhmac_update(&ctx, map_b64->buf[LJWS_JOSE],
+                                            map_b64->len[LJWS_JOSE]) ||
+                   lws_genhmac_update(&ctx, ".", 1) ||
+                   lws_genhmac_update(&ctx, map_b64->buf[LJWS_PYLD],
+                                            map_b64->len[LJWS_PYLD]) ||
+                   lws_genhmac_destroy(&ctx, digest)) {
+                       lws_genhmac_destroy(&ctx, NULL);
+
+                       return -1;
+               }
+
+               /* 7) Compare the computed and decoded hashes */
+
+               if (lws_timingsafe_bcmp(digest, map->buf[2], h_len)) {
+                       lwsl_notice("digest mismatch\n");
+
+                       return -1;
+               }
+
+               break;
+
+       case LWS_JOSE_ENCTYPE_ECDSA:
+
+               /* ECDSA using SHA-256/384/512 */
+
+               /* Confirm the key coming in with this makes sense */
+
+               /* has to be an EC key :-) */
+               if (jwk->kty != LWS_GENCRYPTO_KTY_EC)
+                       return -1;
+
+               /* key must state its curve */
+               if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
+                       return -1;
+
+               /* key must match the selected alg curve */
+               if (strcmp((const char *)jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
+                               jose.alg->curve_name))
+                       return -1;
+
+               /*
+                * JWS Signing Input value:
+                *
+                * BASE64URL(UTF8(JWS Protected Header)) || '.' ||
+                *      BASE64URL(JWS Payload)
+                *
+                * Validating the JWS Signature is a bit different from the
+                * previous examples.  We need to split the 64 member octet
+                * sequence of the JWS Signature (which is base64url decoded
+                * from the value encoded in the JWS representation) into two
+                * 32 octet sequences, the first representing R and the second
+                * S.  We then pass the public key (x, y), the signature (R, S),
+                * and the JWS Signing Input (which is the initial substring of
+                * the JWS Compact Serialization representation up until but not
+                * including the second period character) to an ECDSA signature
+                * verifier that has been configured to use the P-256 curve with
+                * the SHA-256 hash function.
+                */
+
+               if (lws_genhash_init(&hash_ctx, jose.alg->hash_type) ||
+                   lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_JOSE],
+                                                 map_b64->len[LJWS_JOSE]) ||
+                   lws_genhash_update(&hash_ctx, ".", 1) ||
+                   lws_genhash_update(&hash_ctx, map_b64->buf[LJWS_PYLD],
+                                                 map_b64->len[LJWS_PYLD]) ||
+                   lws_genhash_destroy(&hash_ctx, digest)) {
+                       lws_genhash_destroy(&hash_ctx, NULL);
+
+                       return -1;
+               }
+
+               h_len = lws_genhash_size(jose.alg->hash_type);
+
+               if (lws_genecdsa_create(&ecdsactx, context, NULL)) {
+                       lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                                   __func__);
+                       return -1;
+               }
+
+               if (lws_genecdsa_set_key(&ecdsactx, jwk->e)) {
+                       lws_genec_destroy(&ecdsactx);
+                       lwsl_notice("%s: ec key import fail\n", __func__);
+                       return -1;
+               }
+
+               n = lws_genecdsa_hash_sig_verify_jws(&ecdsactx, digest,
+                                                    jose.alg->hash_type,
+                                                    jose.alg->keybits_fixed,
+                                                 (uint8_t *)map->buf[LJWS_SIG],
+                                                    map->len[LJWS_SIG]);
+               lws_genec_destroy(&ecdsactx);
+               if (n < 0) {
+                       lwsl_notice("%s: verify fail\n", __func__);
+                       return -1;
+               }
+
+               break;
+
+       default:
+               lwsl_err("%s: unknown alg from jose\n", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+/* it's already a b64 map, we will make a temp plain version */
+
+LWS_VISIBLE int
+lws_jws_sig_confirm_compact_b64_map(struct lws_jws_map *map_b64,
+                                   struct lws_jwk *jwk,
+                                   struct lws_context *context,
+                                   char *temp, int *temp_len)
+{
+       struct lws_jws_map map;
+       int n;
+
+       n = lws_jws_compact_decode_map(map_b64, &map, temp, temp_len);
+       if (n > 3 || n < 0)
+               return -1;
+
+       return lws_jws_sig_confirm(map_b64, &map, jwk, context);
+}
+
+/*
+ * it's already a compact / concatenated b64 string, we will make a temp
+ * plain version
+ */
+
+LWS_VISIBLE int
+lws_jws_sig_confirm_compact_b64(const char *in, size_t len,
+                               struct lws_jws_map *map, struct lws_jwk *jwk,
+                               struct lws_context *context,
+                               char *temp, int *temp_len)
+{
+       struct lws_jws_map map_b64;
+       int n;
+
+       if (lws_jws_b64_compact_map(in, len, &map_b64) < 0)
+               return -1;
+
+       n = lws_jws_compact_decode(in, len, map, &map_b64, temp, temp_len);
+       if (n > 3 || n < 0)
+               return -1;
+
+       return lws_jws_sig_confirm(&map_b64, map, jwk, context);
+}
+
+/* it's already plain, we will make a temp b64 version */
+
+LWS_VISIBLE int
+lws_jws_sig_confirm_compact(struct lws_jws_map *map, struct lws_jwk *jwk,
+                           struct lws_context *context, char *temp,
+                           int *temp_len)
+{
+       struct lws_jws_map map_b64;
+
+       if (lws_jws_compact_encode(&map_b64, map, temp, temp_len) < 0)
+               return -1;
+
+       return lws_jws_sig_confirm(&map_b64, map, jwk, context);
+}
+
+int
+lws_jws_sig_confirm_json(const char *in, size_t len,
+                        struct lws_jws *jws, struct lws_jwk *jwk,
+                        struct lws_context *context,
+                        char *temp, int *temp_len)
+{
+       if (lws_jws_json_parse(jws, (const uint8_t *)in, len, temp, temp_len)) {
+               lwsl_err("%s: lws_jws_json_parse failed\n", __func__);
+
+               return -1;
+       }
+       return lws_jws_sig_confirm(&jws->map_b64, &jws->map, jwk, context);
+}
+
+
+int
+lws_jws_sign_from_b64(struct lws_jose *jose, struct lws_jws *jws,
+                     char *b64_sig, size_t sig_len)
+{
+       enum enum_genrsa_mode pad = LGRSAM_PKCS1_1_5;
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_genhash_ctx hash_ctx;
+       struct lws_genec_ctx ecdsactx;
+       struct lws_genrsa_ctx rsactx;
+       uint8_t *buf;
+       int n, m;
+
+       if (jose->alg->hash_type == LWS_GENHASH_TYPE_UNKNOWN &&
+           jose->alg->hmac_type == LWS_GENHMAC_TYPE_UNKNOWN &&
+           !strcmp(jose->alg->alg, "none"))
+               return 0;
+
+       if (lws_genhash_init(&hash_ctx, jose->alg->hash_type) ||
+           lws_genhash_update(&hash_ctx, jws->map_b64.buf[LJWS_JOSE],
+                                         jws->map_b64.len[LJWS_JOSE]) ||
+           lws_genhash_update(&hash_ctx, ".", 1) ||
+           lws_genhash_update(&hash_ctx, jws->map_b64.buf[LJWS_PYLD],
+                                         jws->map_b64.len[LJWS_PYLD]) ||
+           lws_genhash_destroy(&hash_ctx, digest)) {
+               lws_genhash_destroy(&hash_ctx, NULL);
+
+               return -1;
+       }
+
+       switch (jose->alg->algtype_signing) {
+       case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS:
+       case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP:
+               pad = LGRSAM_PKCS1_OAEP_PSS;
+               /* fallthru */
+       case LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5:
+
+               if (jws->jwk->kty != LWS_GENCRYPTO_KTY_RSA)
+                       return -1;
+
+               if (lws_genrsa_create(&rsactx, jws->jwk->e, jws->context,
+                                     pad, LWS_GENHASH_TYPE_UNKNOWN)) {
+                       lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                                   __func__);
+                       return -1;
+               }
+
+               n = jws->jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len;
+               buf = lws_malloc(lws_base64_size(n), "jws sign");
+               if (!buf)
+                       return -1;
+
+               n = lws_genrsa_hash_sign(&rsactx, digest, jose->alg->hash_type,
+                                        buf, n);
+               lws_genrsa_destroy(&rsactx);
+               if (n < 0) {
+                       lwsl_err("%s: lws_genrsa_hash_sign failed\n", __func__);
+                       lws_free(buf);
+
+                       return -1;
+               }
+
+               n = lws_jws_base64_enc((char *)buf, n, b64_sig, sig_len);
+               lws_free(buf);
+               if (n < 0) {
+                       lwsl_err("%s: lws_jws_base64_enc failed\n", __func__);
+               }
+
+               return n;
+
+       case LWS_JOSE_ENCTYPE_NONE:
+               return lws_jws_base64_enc((char *)digest,
+                                        lws_genhash_size(jose->alg->hash_type),
+                                         b64_sig, sig_len);
+       case LWS_JOSE_ENCTYPE_ECDSA:
+               /* ECDSA using SHA-256/384/512 */
+
+               /* the key coming in with this makes sense, right? */
+
+               /* has to be an EC key :-) */
+               if (jws->jwk->kty != LWS_GENCRYPTO_KTY_EC)
+                       return -1;
+
+               /* key must state its curve */
+               if (!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
+                       return -1;
+
+               /* must have all his pieces for a private key */
+               if (!jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_X].buf ||
+                   !jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].buf ||
+                   !jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf)
+                       return -1;
+
+               /* key must match the selected alg curve */
+               if (strcmp((const char *)
+                               jws->jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
+                           jose->alg->curve_name))
+                       return -1;
+
+               if (lws_genecdsa_create(&ecdsactx, jws->context, NULL)) {
+                       lwsl_notice("%s: lws_genrsa_public_decrypt_create\n",
+                                   __func__);
+                       return -1;
+               }
+
+               if (lws_genecdsa_set_key(&ecdsactx, jws->jwk->e)) {
+                       lws_genec_destroy(&ecdsactx);
+                       lwsl_notice("%s: ec key import fail\n", __func__);
+                       return -1;
+               }
+               m = lws_gencrypto_bits_to_bytes(jose->alg->keybits_fixed) * 2;
+               buf = lws_malloc(m, "jws sign");
+               if (!buf)
+                       return -1;
+
+               n = lws_genecdsa_hash_sign_jws(&ecdsactx, digest,
+                                              jose->alg->hash_type,
+                                              jose->alg->keybits_fixed,
+                                              (uint8_t *)buf, m);
+               lws_genec_destroy(&ecdsactx);
+               if (n < 0) {
+                       lws_free(buf);
+                       lwsl_notice("%s: lws_genecdsa_hash_sign_jws fail\n",
+                                       __func__);
+                       return -1;
+               }
+
+               n = lws_jws_base64_enc((char *)buf, m, b64_sig, sig_len);
+               lws_free(buf);
+
+               return n;
+
+       default:
+               break;
+       }
+
+       /* unknown key type */
+
+       return -1;
+}
+
+/*
+ * Flattened JWS JSON:
+ *
+ *  {
+ *    "payload":   "<payload contents>",
+ *    "protected": "<integrity-protected header contents>",
+ *    "header":    <non-integrity-protected header contents>,
+ *    "signature": "<signature contents>"
+ *   }
+ */
+
+LWS_VISIBLE int
+lws_jws_write_flattened_json(struct lws_jws *jws, char *flattened, size_t len)
+{
+       size_t n = 0;
+
+       if (len < 1)
+               return 1;
+
+       n += lws_snprintf(flattened + n, len - n , "{\"payload\": \"%.*s\",\n",
+                         jws->map_b64.len[LJWS_PYLD],
+                         jws->map_b64.buf[LJWS_PYLD]);
+
+       n += lws_snprintf(flattened + n, len - n , " \"protected\": \"%.*s\",\n",
+                         jws->map_b64.len[LJWS_JOSE],
+                         jws->map_b64.buf[LJWS_JOSE]);
+
+       if (jws->map_b64.buf[LJWS_UHDR])
+               n += lws_snprintf(flattened + n, len - n , " \"header\": %.*s,\n",
+                         jws->map_b64.len[LJWS_UHDR], jws->map_b64.buf[LJWS_UHDR]);
+
+       n += lws_snprintf(flattened + n, len - n , " \"signature\": \"%.*s\"}\n",
+                       jws->map_b64.len[LJWS_SIG], jws->map_b64.buf[LJWS_SIG]);
+
+       return (n >= len - 1);
+}
+
+LWS_VISIBLE int
+lws_jws_write_compact(struct lws_jws *jws, char *compact, size_t len)
+{
+       size_t n = 0;
+
+       if (len < 1)
+               return 1;
+
+       n += lws_snprintf(compact + n, len - n , "%.*s",
+                         jws->map_b64.len[LJWS_JOSE], jws->map_b64.buf[LJWS_JOSE]);
+       n += lws_snprintf(compact + n, len - n , ".%.*s",
+                         jws->map_b64.len[LJWS_PYLD], jws->map_b64.buf[LJWS_PYLD]);
+       n += lws_snprintf(compact + n, len - n , ".%.*s",
+                         jws->map_b64.len[LJWS_SIG], jws->map_b64.buf[LJWS_SIG]);
+
+       return n >= len - 1;
+}
diff --git a/lib/jose/jws/private.h b/lib/jose/jws/private.h
new file mode 100644 (file)
index 0000000..e7113d3
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * libwebsockets - JSON Web Signature support
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * JOSE is actually specified as part of JWS RFC7515.  JWE references RFC7515
+ * to specify its JOSE JSON object.  So it lives in ./lib/jose/jws/jose.c.
+ */
+
diff --git a/lib/jose/private.h b/lib/jose/private.h
new file mode 100644 (file)
index 0000000..5e755f5
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * libwebsockets - jose private header
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+void
+lws_jwk_destroy_elements(struct lws_gencrypto_keyelem *el, int m);
+
+void
+lws_jwk_init_jps(struct lejp_ctx *jctx, struct lws_jwk_parse_state *jps,
+                struct lws_jwk *jwk, lws_jwk_key_import_callback cb,
+                void *user);
+
+int
+lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk,
+               char *out, size_t out_len);
diff --git a/lib/lextable.h b/lib/lextable.h
deleted file mode 100644 (file)
index 2f4f079..0000000
+++ /dev/null
@@ -1,796 +0,0 @@
-/* pos 0000:   0 */    0x67 /* 'g' */, 0x40, 0x00  /* (to 0x0040 state   1) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       0x08, /* fail */
-/* pos 005e:  11 */    0xF4 /* 't' -> */,
-/* pos 005f:  12 */    0xE9 /* 'i' -> */,
-/* pos 0060:  13 */    0xEF /* 'o' -> */,
-/* pos 0061:  14 */    0xEE /* 'n' -> */,
-/* 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) */,
-                       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) */,
-                       0x08, /* fail */
-/* pos 0079:  24 */    0x6E /* 'n' */, 0x07, 0x00  /* (to 0x0080 state  25) */,
-                       0x6F /* 'o' */, 0x87, 0x01  /* (to 0x0203 state 243) */,
-                       0x08, /* fail */
-/* pos 0080:  25 */    0x6E /* 'n' */, 0x07, 0x00  /* (to 0x0087 state  26) */,
-                       0x74 /* 't' */, 0x86, 0x01  /* (to 0x0209 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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       0x08, /* fail */
-/* pos 015c: 133 */    0xF4 /* 't' -> */,
-/* pos 015d: 134 */    0x3A /* ':' */, 0x07, 0x00  /* (to 0x0164 state 135) */,
-                       0x2D /* '-' */, 0x59, 0x00  /* (to 0x01B9 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) */,
-                       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) */,
-                       0x08, /* fail */
-/* pos 0197: 166 */    0x6F /* 'o' */, 0x07, 0x00  /* (to 0x019E state 167) */,
-                       0x61 /* 'a' */, 0x8E, 0x01  /* (to 0x0328 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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       0x08, /* fail */
-/* pos 0254: 277 */    0x65 /* 'e' */, 0x07, 0x00  /* (to 0x025B state 278) */,
-                       0x72 /* 'r' */, 0x4D, 0x01  /* (to 0x03A4 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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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) */,
-                       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 */
diff --git a/lib/libev.c b/lib/libev.c
deleted file mode 100644 (file)
index e422d7e..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-void lws_feature_status_libev(struct lws_context_creation_info *info)
-{
-       if (lws_check_opt(info->options, LWS_SERVER_OPTION_LIBEV))
-               lwsl_notice("libev support compiled in and enabled\n");
-       else
-               lwsl_notice("libev support compiled in but disabled\n");
-}
-
-static void
-lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
-{
-       struct lws_io_watcher *lws_io = lws_container_of(watcher,
-                                       struct lws_io_watcher, ev_watcher);
-       struct lws_context *context = lws_io->context;
-       struct lws_pollfd eventfd;
-
-       if (revents & EV_ERROR)
-               return;
-
-       eventfd.fd = watcher->fd;
-       eventfd.events = 0;
-       eventfd.revents = EV_NONE;
-       if (revents & EV_READ) {
-               eventfd.events |= LWS_POLLIN;
-               eventfd.revents |= LWS_POLLIN;
-       }
-       if (revents & EV_WRITE) {
-               eventfd.events |= LWS_POLLOUT;
-               eventfd.revents |= LWS_POLLOUT;
-       }
-       lws_service_fd(context, &eventfd);
-}
-
-LWS_VISIBLE void
-lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents)
-{
-       ev_break(loop, EVBREAK_ALL);
-}
-
-LWS_VISIBLE int
-lws_ev_sigint_cfg(struct lws_context *context, int use_ev_sigint,
-                 lws_ev_signal_cb_t *cb)
-{
-       context->use_ev_sigint = use_ev_sigint;
-       if (cb)
-               context->lws_ev_sigint_cb = cb;
-       else
-               context->lws_ev_sigint_cb = &lws_ev_sigint_cb;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_ev_initloop(struct lws_context *context, struct ev_loop *loop, int tsi)
-{
-       struct ev_signal *w_sigint = &context->pt[tsi].w_sigint.ev_watcher;
-       struct ev_io *w_accept = &context->pt[tsi].w_accept.ev_watcher;
-       struct lws_vhost *vh = context->vhost_list;
-       const char * backend_name;
-       int status = 0;
-       int backend;
-
-       if (!loop)
-               loop = ev_loop_new(0);
-       else
-               context->pt[tsi].ev_loop_foreign = 1;
-
-       context->pt[tsi].io_loop_ev = loop;
-
-       /*
-        * Initialize the accept w_accept with all the listening sockets
-        * and register a callback for read operations
-        */
-       while (vh) {
-               if (vh->lserv_wsi) {
-                       vh->lserv_wsi->w_read.context = context;
-                       ev_io_init(w_accept, lws_accept_cb, vh->lserv_wsi->desc.sockfd,
-                                 EV_READ);
-               }
-               vh = vh->vhost_next;
-       }
-       ev_io_start(context->pt[tsi].io_loop_ev, w_accept);
-
-       /* Register the signal watcher unless the user says not to */
-       if (context->use_ev_sigint) {
-               ev_signal_init(w_sigint, context->lws_ev_sigint_cb, SIGINT);
-               ev_signal_start(context->pt[tsi].io_loop_ev, w_sigint);
-       }
-       backend = ev_backend(loop);
-
-       switch (backend) {
-       case EVBACKEND_SELECT:
-               backend_name = "select";
-               break;
-       case EVBACKEND_POLL:
-               backend_name = "poll";
-               break;
-       case EVBACKEND_EPOLL:
-               backend_name = "epoll";
-               break;
-       case EVBACKEND_KQUEUE:
-               backend_name = "kqueue";
-               break;
-       case EVBACKEND_DEVPOLL:
-               backend_name = "/dev/poll";
-               break;
-       case EVBACKEND_PORT:
-               backend_name = "Solaris 10 \"port\"";
-               break;
-       default:
-               backend_name = "Unknown libev backend";
-               break;
-       }
-
-       lwsl_notice(" libev backend: %s\n", backend_name);
-
-       return status;
-}
-
-void
-lws_libev_destroyloop(struct lws_context *context, int tsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[tsi];
-
-       if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV))
-               return;
-
-       if (!pt->io_loop_ev)
-               return;
-
-       ev_io_stop(pt->io_loop_ev, &pt->w_accept.ev_watcher);
-       if (context->use_ev_sigint)
-               ev_signal_stop(pt->io_loop_ev,
-                      &pt->w_sigint.ev_watcher);
-       if (!pt->ev_loop_foreign)
-               ev_loop_destroy(pt->io_loop_ev);
-}
-
-LWS_VISIBLE void
-lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc)
-{
-       struct lws_context *context = lws_get_context(new_wsi);
-       struct ev_io *r = &new_wsi->w_read.ev_watcher;
-       struct ev_io *w = &new_wsi->w_write.ev_watcher;
-       int fd;
-
-       if (!LWS_LIBEV_ENABLED(context))
-               return;
-
-       if (new_wsi->mode == LWSCM_RAW_FILEDESC)
-               fd = desc.filefd;
-       else
-               fd = desc.sockfd;
-
-       new_wsi->w_read.context = context;
-       new_wsi->w_write.context = context;
-       ev_io_init(r, lws_accept_cb, fd, EV_READ);
-       ev_io_init(w, lws_accept_cb, fd, EV_WRITE);
-}
-
-LWS_VISIBLE void
-lws_libev_io(struct lws *wsi, int flags)
-{
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-
-       if (!LWS_LIBEV_ENABLED(context))
-               return;
-
-       if (!pt->io_loop_ev)
-               return;
-
-       assert((flags & (LWS_EV_START | LWS_EV_STOP)) &&
-              (flags & (LWS_EV_READ | LWS_EV_WRITE)));
-
-       if (flags & LWS_EV_START) {
-               if (flags & LWS_EV_WRITE)
-                       ev_io_start(pt->io_loop_ev, &wsi->w_write.ev_watcher);
-               if (flags & LWS_EV_READ)
-                       ev_io_start(pt->io_loop_ev, &wsi->w_read.ev_watcher);
-       } else {
-               if (flags & LWS_EV_WRITE)
-                       ev_io_stop(pt->io_loop_ev, &wsi->w_write.ev_watcher);
-               if (flags & LWS_EV_READ)
-                       ev_io_stop(pt->io_loop_ev, &wsi->w_read.ev_watcher);
-       }
-}
-
-LWS_VISIBLE int
-lws_libev_init_fd_table(struct lws_context *context)
-{
-       int n;
-
-       if (!LWS_LIBEV_ENABLED(context))
-               return 0;
-
-       for (n = 0; n < context->count_threads; n++) {
-               context->pt[n].w_accept.context = context;
-               context->pt[n].w_sigint.context = context;
-       }
-
-       return 1;
-}
-
-LWS_VISIBLE void
-lws_libev_run(const struct lws_context *context, int tsi)
-{
-       if (context->pt[tsi].io_loop_ev && LWS_LIBEV_ENABLED(context))
-               ev_run(context->pt[tsi].io_loop_ev, 0);
-}
diff --git a/lib/libevent.c b/lib/libevent.c
deleted file mode 100644 (file)
index bae04b4..0000000
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-void lws_feature_status_libevent(struct lws_context_creation_info *info)
-{
-  if (lws_check_opt(info->options, LWS_SERVER_OPTION_LIBEVENT))
-    lwsl_notice("libevent support compiled in and enabled\n");
-  else
-    lwsl_notice("libevent support compiled in but disabled\n");
-}
-
-static void
-lws_event_cb(evutil_socket_t sock_fd, short revents, void *ctx)
-{
-  struct lws_io_watcher *lws_io = (struct lws_io_watcher *)ctx;
-  struct lws_context *context = lws_io->context;
-  struct lws_pollfd eventfd;
-
-  if (revents & EV_TIMEOUT)
-    return;
-
-  /* !!! EV_CLOSED doesn't exist in libevent2 */
-#if LIBEVENT_VERSION_NUMBER < 0x02000000
-  if (revents & EV_CLOSED)
-  {
-    event_del(lws_io->event_watcher);
-    event_free(lws_io->event_watcher);
-    return;
-  }
-#endif
-
-  eventfd.fd = sock_fd;
-  eventfd.events = 0;
-  eventfd.revents = 0;
-  if (revents & EV_READ)
-  {
-    eventfd.events |= LWS_POLLIN;
-    eventfd.revents |= LWS_POLLIN;
-  }
-  if (revents & EV_WRITE)
-  {
-    eventfd.events |= LWS_POLLOUT;
-    eventfd.revents |= LWS_POLLOUT;
-  }
-  lws_service_fd(context, &eventfd);
-}
-
-LWS_VISIBLE void
-lws_event_sigint_cb(evutil_socket_t sock_fd, short revents, void *ctx)
-{
-  struct lws_context_per_thread *pt = ctx;
-  if (!pt->ev_loop_foreign)
-    event_base_loopbreak(pt->io_loop_event_base);
-}
-
-LWS_VISIBLE int
-lws_event_sigint_cfg(struct lws_context *context, int use_event_sigint,
-      lws_event_signal_cb_t *cb)
-{
-  context->use_ev_sigint = use_event_sigint;
-  if (cb)
-    context->lws_event_sigint_cb = cb;
-  else
-    context->lws_event_sigint_cb = &lws_event_sigint_cb;
-
-  return 0;
-}
-
-LWS_VISIBLE int
-lws_event_initloop(struct lws_context *context, struct event_base *loop,
-    int tsi)
-{
-  if (!loop)
-  {
-    context->pt[tsi].io_loop_event_base = event_base_new();
-  }
-  else
-  {
-    context->pt[tsi].ev_loop_foreign = 1;
-    context->pt[tsi].io_loop_event_base = loop;
-  }
-
-  /*
-   * Initialize all events with the listening sockets
-   * and register a callback for read operations
-   */
-  struct lws_vhost *vh = context->vhost_list;
-  while (vh)
-  {
-    if (vh->lserv_wsi)
-    {
-      vh->lserv_wsi->w_read.context = context;
-      vh->lserv_wsi->w_read.event_watcher = event_new(
-          loop,
-          vh->lserv_wsi->desc.sockfd,
-          (EV_READ | EV_PERSIST),
-          lws_event_cb,
-          &vh->lserv_wsi->w_read);
-      event_add(vh->lserv_wsi->w_read.event_watcher, NULL);
-    }
-    vh = vh->vhost_next;
-  }
-
-  /* Register the signal watcher unless the user says not to */
-  if (context->use_ev_sigint)
-  {
-    struct event *w_sigint = evsignal_new(loop, SIGINT,
-        context->lws_event_sigint_cb, &context->pt[tsi]);
-    context->pt[tsi].w_sigint.event_watcher = w_sigint;
-    event_add(w_sigint, NULL);
-  }
-
-  return 0;
-}
-
-void
-lws_libevent_destroyloop(struct lws_context *context, int tsi)
-{
-  if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT))
-    return;
-
-  struct lws_context_per_thread *pt = &context->pt[tsi];
-  if (!pt->io_loop_event_base)
-    return;
-
-  /*
-   * Free all events with the listening sockets
-   */
-  struct lws_vhost *vh = context->vhost_list;
-  while (vh)
-  {
-    if (vh->lserv_wsi)
-    {
-      event_free(vh->lserv_wsi->w_read.event_watcher);
-      vh->lserv_wsi->w_read.event_watcher = NULL;
-    }
-    vh = vh->vhost_next;
-  }
-
-  if (context->use_ev_sigint)
-    event_free(pt->w_sigint.event_watcher);
-  if (!pt->ev_loop_foreign)
-    event_base_free(pt->io_loop_event_base);
-}
-
-LWS_VISIBLE void
-lws_libevent_accept(struct lws *new_wsi, lws_sock_file_fd_type desc)
-{
-  struct lws_context *context = lws_get_context(new_wsi);
-  if (!LWS_LIBEVENT_ENABLED(context))
-    return;
-
-  new_wsi->w_read.context = context;
-  new_wsi->w_write.context = context;
-
-  // Initialize the event
-  struct lws_context_per_thread *pt = &context->pt[(int)new_wsi->tsi];
-  int fd;
-  if (new_wsi->mode == LWSCM_RAW_FILEDESC)
-    fd = desc.filefd;
-  else
-    fd = desc.sockfd;
-  new_wsi->w_read.event_watcher = event_new(pt->io_loop_event_base, fd,
-      (EV_READ | EV_PERSIST), lws_event_cb, &new_wsi->w_read);
-  new_wsi->w_write.event_watcher = event_new(pt->io_loop_event_base, fd,
-      (EV_WRITE | EV_PERSIST), lws_event_cb, &new_wsi->w_write);
-}
-
-LWS_VISIBLE void
-lws_libevent_io(struct lws *wsi, int flags)
-{
-  struct lws_context *context = lws_get_context(wsi);
-
-  if (!LWS_LIBEVENT_ENABLED(context))
-    return;
-
-  struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-  if (!pt->io_loop_event_base || context->being_destroyed)
-    return;
-
-  assert((flags & (LWS_EV_START | LWS_EV_STOP)) &&
-         (flags & (LWS_EV_READ | LWS_EV_WRITE)));
-
-  if (flags & LWS_EV_START)
-  {
-    if (flags & LWS_EV_WRITE)
-    {
-      event_add(wsi->w_write.event_watcher, NULL);
-    }
-    if (flags & LWS_EV_READ)
-    {
-      event_add(wsi->w_read.event_watcher, NULL);
-    }
-  }
-  else
-  {
-    if (flags & LWS_EV_WRITE)
-    {
-      event_del(wsi->w_write.event_watcher);
-    }
-    if (flags & LWS_EV_READ)
-    {
-      event_del(wsi->w_read.event_watcher);
-    }
-  }
-}
-
-LWS_VISIBLE int
-lws_libevent_init_fd_table(struct lws_context *context)
-{
-  if (!LWS_LIBEVENT_ENABLED(context))
-    return 0;
-
-  int n;
-  for (n = 0; n < context->count_threads; n++)
-  {
-    context->pt[n].w_sigint.context = context;
-  }
-
-  return 1;
-}
-
-LWS_VISIBLE void
-lws_libevent_run(const struct lws_context *context, int tsi)
-{
-  // Run/Dispatch the event_base loop
-  if (context->pt[tsi].io_loop_event_base && LWS_LIBEVENT_ENABLED(context))
-    event_base_dispatch(context->pt[tsi].io_loop_event_base);
-}
diff --git a/lib/libuv.c b/lib/libuv.c
deleted file mode 100644 (file)
index 7d305d7..0000000
+++ /dev/null
@@ -1,723 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-void
-lws_feature_status_libuv(struct lws_context_creation_info *info)
-{
-       if (lws_check_opt(info->options, LWS_SERVER_OPTION_LIBUV))
-               lwsl_notice("libuv support compiled in and enabled\n");
-       else
-               lwsl_notice("libuv support compiled in but disabled\n");
-}
-
-static void
-lws_uv_idle(uv_idle_t *handle
-#if UV_VERSION_MAJOR == 0
-               , int status
-#endif
-)
-{
-       struct lws_context_per_thread *pt = lws_container_of(handle,
-                                       struct lws_context_per_thread, uv_idle);
-
-//     lwsl_debug("%s\n", __func__);
-
-       /*
-        * is there anybody with pending stuff that needs service forcing?
-        */
-       if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) {
-               /* -1 timeout means just do forced service */
-               _lws_plat_service_tsi(pt->context, -1, pt->tid);
-               /* still somebody left who wants forced service? */
-               if (!lws_service_adjust_timeout(pt->context, 1, pt->tid))
-                       /* yes... come back again later */
-//                     lwsl_debug("%s: done again\n", __func__);
-               return;
-       }
-
-       /* there is nobody who needs service forcing, shut down idle */
-       uv_idle_stop(handle);
-
-       //lwsl_debug("%s: done stop\n", __func__);
-}
-
-static void
-lws_io_cb(uv_poll_t *watcher, int status, int revents)
-{
-       struct lws_io_watcher *lws_io = lws_container_of(watcher,
-                                       struct lws_io_watcher, uv_watcher);
-       struct lws *wsi = lws_container_of(lws_io, struct lws, w_read);
-       struct lws_context *context = wsi->context;
-       struct lws_pollfd eventfd;
-
-#if defined(WIN32) || defined(_WIN32)
-       eventfd.fd = watcher->socket;
-#else
-       eventfd.fd = watcher->io_watcher.fd;
-#endif
-       eventfd.events = 0;
-       eventfd.revents = 0;
-
-       if (status < 0) {
-               /* at this point status will be an UV error, like UV_EBADF,
-               we treat all errors as LWS_POLLHUP */
-
-               /* you might want to return; instead of servicing the fd in some cases */
-               if (status == UV_EAGAIN)
-                       return;
-
-               eventfd.events |= LWS_POLLHUP;
-               eventfd.revents |= LWS_POLLHUP;
-       } else {
-               if (revents & UV_READABLE) {
-                       eventfd.events |= LWS_POLLIN;
-                       eventfd.revents |= LWS_POLLIN;
-               }
-               if (revents & UV_WRITABLE) {
-                       eventfd.events |= LWS_POLLOUT;
-                       eventfd.revents |= LWS_POLLOUT;
-               }
-       }
-       lws_service_fd(context, &eventfd);
-
-       uv_idle_start(&context->pt[(int)wsi->tsi].uv_idle, lws_uv_idle);
-}
-
-LWS_VISIBLE void
-lws_uv_sigint_cb(uv_signal_t *watcher, int signum)
-{
-       lwsl_err("internal signal handler caught signal %d\n", signum);
-       lws_libuv_stop(watcher->data);
-}
-
-LWS_VISIBLE int
-lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint,
-                 uv_signal_cb cb)
-{
-       context->use_ev_sigint = use_uv_sigint;
-       if (cb)
-               context->lws_uv_sigint_cb = cb;
-       else
-               context->lws_uv_sigint_cb = &lws_uv_sigint_cb;
-
-       return 0;
-}
-
-static void
-lws_uv_timeout_cb(uv_timer_t *timer
-#if UV_VERSION_MAJOR == 0
-               , int status
-#endif
-)
-{
-       struct lws_context_per_thread *pt = lws_container_of(timer,
-                       struct lws_context_per_thread, uv_timeout_watcher);
-
-       if (pt->context->requested_kill)
-               return;
-
-       lwsl_debug("%s\n", __func__);
-
-       lws_service_fd_tsi(pt->context, NULL, pt->tid);
-}
-
-static const int sigs[] = { SIGINT, SIGTERM, SIGSEGV, SIGFPE, SIGHUP };
-
-int
-lws_uv_initvhost(struct lws_vhost* vh, struct lws* wsi)
-{
-       struct lws_context_per_thread *pt;
-       int n;
-
-       if (!LWS_LIBUV_ENABLED(vh->context))
-               return 0;
-       if (!wsi)
-               wsi = vh->lserv_wsi;
-       if (!wsi)
-               return 0;
-       if (wsi->w_read.context)
-               return 0;
-
-       pt = &vh->context->pt[(int)wsi->tsi];
-       if (!pt->io_loop_uv)
-               return 0;
-
-       wsi->w_read.context = vh->context;
-       n = uv_poll_init_socket(pt->io_loop_uv,
-                               &wsi->w_read.uv_watcher, wsi->desc.sockfd);
-       if (n) {
-               lwsl_err("uv_poll_init failed %d, sockfd=%p\n",
-                                n, (void *)(lws_intptr_t)wsi->desc.sockfd);
-
-               return -1;
-       }
-       lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ);
-
-       return 0;
-}
-
-/*
- * This needs to be called after vhosts have been defined.
- *
- * If later, after server start, another vhost is added, this must be
- * called again to bind the vhost
- */
-
-LWS_VISIBLE int
-lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[tsi];
-       struct lws_vhost *vh = context->vhost_list;
-       int status = 0, n, ns, first = 1;
-
-       if (!pt->io_loop_uv) {
-               if (!loop) {
-                       loop = lws_malloc(sizeof(*loop));
-                       if (!loop) {
-                               lwsl_err("OOM\n");
-                               return -1;
-                       }
-       #if UV_VERSION_MAJOR > 0
-                       uv_loop_init(loop);
-       #else
-                       lwsl_err("This libuv is too old to work...\n");
-                       return 1;
-       #endif
-                       pt->ev_loop_foreign = 0;
-               } else {
-                       lwsl_notice(" Using foreign event loop...\n");
-                       pt->ev_loop_foreign = 1;
-               }
-
-               pt->io_loop_uv = loop;
-               uv_idle_init(loop, &pt->uv_idle);
-
-               ns = ARRAY_SIZE(sigs);
-               if (lws_check_opt(context->options,
-                                 LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN))
-                       ns = 2;
-
-               if (pt->context->use_ev_sigint) {
-                       assert(ns <= ARRAY_SIZE(pt->signals));
-                       for (n = 0; n < ns; n++) {
-                               uv_signal_init(loop, &pt->signals[n]);
-                               pt->signals[n].data = pt->context;
-                               uv_signal_start(&pt->signals[n],
-                                               context->lws_uv_sigint_cb, sigs[n]);
-                       }
-               }
-       } else
-               first = 0;
-
-       /*
-        * Initialize the accept wsi read watcher with all the listening sockets
-        * and register a callback for read operations
-        *
-        * We have to do it here because the uv loop(s) are not
-        * initialized until after context creation.
-        */
-       while (vh) {
-               if (lws_uv_initvhost(vh, vh->lserv_wsi) == -1)
-                       return -1;
-               vh = vh->vhost_next;
-       }
-
-       if (first) {
-               uv_timer_init(pt->io_loop_uv, &pt->uv_timeout_watcher);
-               uv_timer_start(&pt->uv_timeout_watcher, lws_uv_timeout_cb,
-                              10, 1000);
-       }
-
-       return status;
-}
-
-static void lws_uv_close_cb(uv_handle_t *handle)
-{
-       //lwsl_err("%s: handle %p\n", __func__, handle);
-}
-
-static void lws_uv_walk_cb(uv_handle_t *handle, void *arg)
-{
-       if (!uv_is_closing(handle))
-               uv_close(handle, lws_uv_close_cb);
-}
-
-LWS_VISIBLE void
-lws_close_all_handles_in_loop(uv_loop_t *loop)
-{
-       uv_walk(loop, lws_uv_walk_cb, NULL);
-}
-
-void
-lws_libuv_destroyloop(struct lws_context *context, int tsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[tsi];
-//     struct lws_context *ctx;
-       int m, budget = 100, ns;
-
-       if (!lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
-               return;
-
-       if (!pt->io_loop_uv)
-               return;
-
-       lwsl_notice("%s: closing signals + timers context %p\n", __func__, context);
-
-       if (context->use_ev_sigint) {
-               uv_signal_stop(&pt->w_sigint.uv_watcher);
-
-               ns = ARRAY_SIZE(sigs);
-               if (lws_check_opt(context->options, LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN))
-                       ns = 2;
-
-               for (m = 0; m < ns; m++) {
-                       uv_signal_stop(&pt->signals[m]);
-                       uv_close((uv_handle_t *)&pt->signals[m], lws_uv_close_cb);
-               }
-       }
-
-       uv_timer_stop(&pt->uv_timeout_watcher);
-       uv_close((uv_handle_t *)&pt->uv_timeout_watcher, lws_uv_close_cb);
-
-       uv_idle_stop(&pt->uv_idle);
-       uv_close((uv_handle_t *)&pt->uv_idle, lws_uv_close_cb);
-
-       if (pt->ev_loop_foreign)
-               return;
-
-       while (budget-- && uv_run(pt->io_loop_uv, UV_RUN_NOWAIT))
-               ;
-
-       lwsl_notice("%s: closing all loop handles context %p\n", __func__, context);
-
-       uv_stop(pt->io_loop_uv);
-
-       uv_walk(pt->io_loop_uv, lws_uv_walk_cb, NULL);
-
-       while (uv_run(pt->io_loop_uv, UV_RUN_NOWAIT))
-               ;
-#if UV_VERSION_MAJOR > 0
-       m = uv_loop_close(pt->io_loop_uv);
-       if (m == UV_EBUSY)
-               lwsl_err("%s: uv_loop_close: UV_EBUSY\n", __func__);
-#endif
-       lws_free(pt->io_loop_uv);
-}
-
-void
-lws_libuv_accept(struct lws *wsi, lws_sock_file_fd_type desc)
-{
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       if (!LWS_LIBUV_ENABLED(context))
-               return;
-
-       lwsl_debug("%s: new wsi %p\n", __func__, wsi);
-
-       wsi->w_read.context = context;
-       if (wsi->mode == LWSCM_RAW_FILEDESC)
-               uv_poll_init(pt->io_loop_uv, &wsi->w_read.uv_watcher,
-                            (int)desc.filefd);
-       else
-               uv_poll_init_socket(pt->io_loop_uv, &wsi->w_read.uv_watcher,
-                                   desc.sockfd);
-}
-
-void
-lws_libuv_io(struct lws *wsi, int flags)
-{
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-#if defined(WIN32) || defined(_WIN32)
-       int current_events = wsi->w_read.uv_watcher.events &
-                            (UV_READABLE | UV_WRITABLE);
-#else
-       int current_events = wsi->w_read.uv_watcher.io_watcher.pevents &
-                            (UV_READABLE | UV_WRITABLE);
-#endif
-       struct lws_io_watcher *w = &wsi->w_read;
-
-       if (!LWS_LIBUV_ENABLED(context))
-               return;
-
-       // lwsl_notice("%s: wsi: %p, flags:0x%x\n", __func__, wsi, flags);
-
-       // w->context is set after the loop is initialized
-
-       if (!pt->io_loop_uv || !w->context) {
-               lwsl_info("%s: no io loop yet\n", __func__);
-               return;
-       }
-
-       if (!((flags & (LWS_EV_START | LWS_EV_STOP)) &&
-             (flags & (LWS_EV_READ | LWS_EV_WRITE)))) {
-               lwsl_err("%s: assert: flags %d", __func__, flags);
-               assert(0);
-       }
-
-       if (flags & LWS_EV_START) {
-               if (flags & LWS_EV_WRITE)
-                       current_events |= UV_WRITABLE;
-
-               if (flags & LWS_EV_READ)
-                       current_events |= UV_READABLE;
-
-               uv_poll_start(&w->uv_watcher, current_events, lws_io_cb);
-       } else {
-               if (flags & LWS_EV_WRITE)
-                       current_events &= ~UV_WRITABLE;
-
-               if (flags & LWS_EV_READ)
-                       current_events &= ~UV_READABLE;
-
-               if (!(current_events & (UV_READABLE | UV_WRITABLE)))
-                       uv_poll_stop(&w->uv_watcher);
-               else
-                       uv_poll_start(&w->uv_watcher, current_events,
-                                     lws_io_cb);
-       }
-}
-
-int
-lws_libuv_init_fd_table(struct lws_context *context)
-{
-       int n;
-
-       if (!LWS_LIBUV_ENABLED(context))
-               return 0;
-
-       for (n = 0; n < context->count_threads; n++)
-               context->pt[n].w_sigint.context = context;
-
-       return 1;
-}
-
-LWS_VISIBLE void
-lws_libuv_run(const struct lws_context *context, int tsi)
-{
-       if (context->pt[tsi].io_loop_uv && LWS_LIBUV_ENABLED(context))
-               uv_run(context->pt[tsi].io_loop_uv, 0);
-}
-
-LWS_VISIBLE void
-lws_libuv_stop_without_kill(const struct lws_context *context, int tsi)
-{
-       if (context->pt[tsi].io_loop_uv && LWS_LIBUV_ENABLED(context))
-               uv_stop(context->pt[tsi].io_loop_uv);
-}
-
-static void
-lws_libuv_kill(const struct lws_context *context)
-{
-       int n;
-
-       lwsl_notice("%s\n", __func__);
-
-       for (n = 0; n < context->count_threads; n++)
-               if (context->pt[n].io_loop_uv &&
-                   LWS_LIBUV_ENABLED(context) )//&&
-                   //!context->pt[n].ev_loop_foreign)
-                       uv_stop(context->pt[n].io_loop_uv);
-}
-
-/*
- * This does not actually stop the event loop.  The reason is we have to pass
- * libuv handle closures through its event loop.  So this tries to close all
- * wsi, and set a flag; when all the wsi closures are finalized then we
- * actually stop the libuv event loops.
- */
-
-LWS_VISIBLE void
-lws_libuv_stop(struct lws_context *context)
-{
-       struct lws_context_per_thread *pt;
-       int n, m;
-
-       if (context->requested_kill)
-               return;
-
-       context->requested_kill = 1;
-
-       m = context->count_threads;
-       context->being_destroyed = 1;
-
-       while (m--) {
-               pt = &context->pt[m];
-
-               for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
-                       struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
-
-                       if (!wsi)
-                               continue;
-                       lws_close_free_wsi(wsi,
-                               LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
-                               /* no protocol close */);
-                       n--;
-               }
-       }
-
-       lwsl_info("%s: feels everything closed\n", __func__);
-       if (context->count_wsi_allocated == 0)
-               lws_libuv_kill(context);
-}
-
-LWS_VISIBLE uv_loop_t *
-lws_uv_getloop(struct lws_context *context, int tsi)
-{
-       if (context->pt[tsi].io_loop_uv && LWS_LIBUV_ENABLED(context))
-               return context->pt[tsi].io_loop_uv;
-
-       return NULL;
-}
-
-static void
-lws_libuv_closewsi(uv_handle_t* handle)
-{
-       struct lws *n = NULL, *wsi = (struct lws *)(((char *)handle) -
-                         (char *)(&n->w_read.uv_watcher));
-       struct lws_context *context = lws_get_context(wsi);
-       int lspd = 0;
-
-       if (wsi->mode == LWSCM_SERVER_LISTENER &&
-           wsi->context->deprecated) {
-               lspd = 1;
-               context->deprecation_pending_listen_close_count--;
-               if (!context->deprecation_pending_listen_close_count)
-                       lspd = 2;
-       }
-
-       lws_close_free_wsi_final(wsi);
-
-       if (lspd == 2 && context->deprecation_cb) {
-               lwsl_notice("calling deprecation callback\n");
-               context->deprecation_cb();
-       }
-
-       //lwsl_notice("%s: ctx %p: wsi left %d\n", __func__, context, context->count_wsi_allocated);
-
-       if (context->requested_kill && context->count_wsi_allocated == 0)
-               lws_libuv_kill(context);
-}
-
-void
-lws_libuv_closehandle(struct lws *wsi)
-{
-       struct lws_context *context = lws_get_context(wsi);
-
-       /* required to defer actual deletion until libuv has processed it */
-       uv_close((uv_handle_t*)&wsi->w_read.uv_watcher, lws_libuv_closewsi);
-
-       if (context->requested_kill && context->count_wsi_allocated == 0)
-               lws_libuv_kill(context);
-}
-
-static void
-lws_libuv_closewsi_m(uv_handle_t* handle)
-{
-       lws_sockfd_type sockfd = (lws_sockfd_type)(lws_intptr_t)handle->data;
-
-       compatible_close(sockfd);
-}
-
-void
-lws_libuv_closehandle_manually(struct lws *wsi)
-{
-       uv_handle_t *h = (void *)&wsi->w_read.uv_watcher;
-
-       h->data = (void *)(lws_intptr_t)wsi->desc.sockfd;
-       /* required to defer actual deletion until libuv has processed it */
-       uv_close((uv_handle_t*)&wsi->w_read.uv_watcher, lws_libuv_closewsi_m);
-}
-
-int
-lws_libuv_check_watcher_active(struct lws *wsi)
-{
-       uv_handle_t *h = (void *)&wsi->w_read.uv_watcher;
-
-       return uv_is_active(h);
-}
-
-
-#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
-
-LWS_VISIBLE int
-lws_plat_plugins_init(struct lws_context *context, const char * const *d)
-{
-       struct lws_plugin_capability lcaps;
-       struct lws_plugin *plugin;
-       lws_plugin_init_func initfunc;
-       int m, ret = 0;
-       void *v;
-       uv_dirent_t dent;
-       uv_fs_t req;
-       char path[256];
-       uv_lib_t lib;
-       int pofs = 0;
-
-#if  defined(__MINGW32__) || !defined(WIN32)
-       pofs = 3;
-#endif
-
-       lib.errmsg = NULL;
-       lib.handle = NULL;
-
-       uv_loop_init(&context->pu_loop);
-
-       lwsl_notice("  Plugins:\n");
-
-       while (d && *d) {
-
-               lwsl_notice("  Scanning %s\n", *d);
-               m =uv_fs_scandir(&context->pu_loop, &req, *d, 0, NULL);
-               if (m < 1) {
-                       lwsl_err("Scandir on %s failed\n", *d);
-                       return 1;
-               }
-
-               while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
-                       if (strlen(dent.name) < 7)
-                               continue;
-
-                       lwsl_notice("   %s\n", dent.name);
-
-                       lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d, dent.name);
-                       if (uv_dlopen(path, &lib)) {
-                               uv_dlerror(&lib);
-                               lwsl_err("Error loading DSO: %s\n", lib.errmsg);
-                               uv_dlclose(&lib);
-                               goto bail;
-                       }
-
-                       /* we could open it, can we get his init function? */
-
-#if !defined(WIN32) && !defined(__MINGW32__)
-                       m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
-                                    dent.name + pofs /* snip lib... */);
-                       path[m - 3] = '\0'; /* snip the .so */
-#else
-                       m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
-                                    dent.name + pofs);
-                       path[m - 4] = '\0'; /* snip the .dll */
-#endif
-                       if (uv_dlsym(&lib, path, &v)) {
-                               uv_dlerror(&lib);
-                               lwsl_err("Failed to get %s on %s: %s", path,
-                                               dent.name, lib.errmsg);
-                               uv_dlclose(&lib);
-                               goto bail;
-                       }
-                       initfunc = (lws_plugin_init_func)v;
-                       lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
-                       m = initfunc(context, &lcaps);
-                       if (m) {
-                               lwsl_err("Initializing %s failed %d\n", dent.name, m);
-                               goto skip;
-                       }
-
-                       plugin = lws_malloc(sizeof(*plugin));
-                       if (!plugin) {
-                               uv_dlclose(&lib);
-                               lwsl_err("OOM\n");
-                               goto bail;
-                       }
-                       plugin->list = context->plugin_list;
-                       context->plugin_list = plugin;
-                       strncpy(plugin->name, dent.name, sizeof(plugin->name) - 1);
-                       plugin->name[sizeof(plugin->name) - 1] = '\0';
-                       plugin->lib = lib;
-                       plugin->caps = lcaps;
-                       context->plugin_protocol_count += lcaps.count_protocols;
-                       context->plugin_extension_count += lcaps.count_extensions;
-
-                       continue;
-
-skip:
-                       uv_dlclose(&lib);
-               }
-bail:
-               uv_fs_req_cleanup(&req);
-               d++;
-       }
-
-       return ret;
-}
-
-LWS_VISIBLE int
-lws_plat_plugins_destroy(struct lws_context *context)
-{
-       struct lws_plugin *plugin = context->plugin_list, *p;
-       lws_plugin_destroy_func func;
-       char path[256];
-       void *v;
-       int m;
-       int pofs = 0;
-
-#if  defined(__MINGW32__) || !defined(WIN32)
-       pofs = 3;
-#endif
-
-       if (!plugin)
-               return 0;
-
-       // lwsl_notice("%s\n", __func__);
-
-       while (plugin) {
-               p = plugin;
-
-#if !defined(WIN32) && !defined(__MINGW32__)
-               m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + pofs);
-               path[m - 3] = '\0';
-#else
-               m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + pofs);
-               path[m - 4] = '\0';
-#endif
-
-               if (uv_dlsym(&plugin->lib, path, &v)) {
-                       uv_dlerror(&plugin->lib);
-                       lwsl_err("Failed to get %s on %s: %s", path,
-                                       plugin->name, plugin->lib.errmsg);
-               } else {
-                       func = (lws_plugin_destroy_func)v;
-                       m = func(context);
-                       if (m)
-                               lwsl_err("Destroying %s failed %d\n",
-                                               plugin->name, m);
-               }
-
-               uv_dlclose(&p->lib);
-               plugin = p->list;
-               p->list = NULL;
-               free(p);
-       }
-
-       context->plugin_list = NULL;
-
-       while (uv_loop_close(&context->pu_loop))
-               ;
-
-       return 0;
-}
-
-#endif
-
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
deleted file mode 100755 (executable)
index e774b93..0000000
+++ /dev/null
@@ -1,3634 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-#ifdef LWS_HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#if defined(WIN32) || defined(_WIN32)
-#else
-#include <sys/wait.h>
-#endif
-
-#ifdef LWS_USE_IPV6
-#if defined(WIN32) || defined(_WIN32)
-#include <Iphlpapi.h>
-#else
-#include <net/if.h>
-#endif
-#endif
-
-int log_level = LLL_ERR | LLL_WARN | LLL_NOTICE;
-static void (*lwsl_emit)(int level, const char *line)
-#ifndef LWS_PLAT_OPTEE
-       = lwsl_emit_stderr
-#endif
-       ;
-#ifndef LWS_PLAT_OPTEE
-static const char * const log_level_names[] = {
-       "ERR",
-       "WARN",
-       "NOTICE",
-       "INFO",
-       "DEBUG",
-       "PARSER",
-       "HEADER",
-       "EXTENSION",
-       "CLIENT",
-       "LATENCY",
-       "USER",
-       "?",
-       "?"
-};
-#endif
-
-void
-lws_free_wsi(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt;
-       int n;
-
-       if (!wsi)
-               return;
-       
-       pt = &wsi->context->pt[(int)wsi->tsi];
-
-       /* Protocol user data may be allocated either internally by lws
-        * or by specified the user.
-        * We should only free what we allocated. */
-       if (wsi->protocol && wsi->protocol->per_session_data_size &&
-           wsi->user_space && !wsi->user_space_externally_allocated)
-               lws_free(wsi->user_space);
-
-       lws_free_set_NULL(wsi->rxflow_buffer);
-       lws_free_set_NULL(wsi->trunc_alloc);
-
-       /* we may not have an ah, but may be on the waiting list... */
-       lwsl_info("ah det due to close\n");
-       /* we're closing, losing some rx is OK */
-       lws_header_table_force_to_detachable_state(wsi);
-       lws_header_table_detach(wsi, 0);
-
-       lws_pt_lock(pt);
-       for (n = 0; n < wsi->context->max_http_header_pool; n++) {
-               if (pt->ah_pool[n].in_use &&
-                   pt->ah_pool[n].wsi == wsi) {
-                       lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi);
-                       pt->ah_pool[n].in_use = 0;
-                       pt->ah_pool[n].wsi = NULL;
-                       pt->ah_count_in_use--;
-               }
-       }
-       lws_pt_unlock(pt);
-
-       /* since we will destroy the wsi, make absolutely sure now */
-
-       lws_ssl_remove_wsi_from_buffered_list(wsi);
-       lws_remove_from_timeout_list(wsi);
-
-       wsi->context->count_wsi_allocated--;
-       lwsl_debug("%s: %p, remaining wsi %d\n", __func__, wsi,
-                       wsi->context->count_wsi_allocated);
-
-       lws_free(wsi);
-}
-
-void
-lws_remove_from_timeout_list(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-
-       if (!wsi->timeout_list_prev) /* ie, not part of the list */
-               return;
-
-       lws_pt_lock(pt);
-       /* if we have a next guy, set his prev to our prev */
-       if (wsi->timeout_list)
-               wsi->timeout_list->timeout_list_prev = wsi->timeout_list_prev;
-       /* set our prev guy to our next guy instead of us */
-       *wsi->timeout_list_prev = wsi->timeout_list;
-
-       /* we're out of the list, we should not point anywhere any more */
-       wsi->timeout_list_prev = NULL;
-       wsi->timeout_list = NULL;
-       lws_pt_unlock(pt);
-}
-
-LWS_VISIBLE void
-lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       time_t now;
-
-       if (secs == LWS_TO_KILL_SYNC) {
-               lws_remove_from_timeout_list(wsi);
-               lwsl_debug("synchronously killing %p\n", wsi);
-               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-               return;
-       }
-
-       lws_pt_lock(pt);
-
-       time(&now);
-
-       if (reason && !wsi->timeout_list_prev) {
-               /* our next guy is current first guy */
-               wsi->timeout_list = pt->timeout_list;
-               /* if there is a next guy, set his prev ptr to our next ptr */
-               if (wsi->timeout_list)
-                       wsi->timeout_list->timeout_list_prev = &wsi->timeout_list;
-               /* our prev ptr is first ptr */
-               wsi->timeout_list_prev = &pt->timeout_list;
-               /* set the first guy to be us */
-               *wsi->timeout_list_prev = wsi;
-       }
-
-       lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs);
-       wsi->pending_timeout_limit = now + secs;
-       wsi->pending_timeout = reason;
-
-       lws_pt_unlock(pt);
-
-       if (!reason)
-               lws_remove_from_timeout_list(wsi);
-}
-
-static void
-lws_remove_child_from_any_parent(struct lws *wsi)
-{
-       struct lws **pwsi;
-       int seen = 0;
-
-       if (!wsi->parent)
-               return;
-
-       /* detach ourselves from parent's child list */
-       pwsi = &wsi->parent->child_list;
-       while (*pwsi) {
-               if (*pwsi == wsi) {
-                       lwsl_info("%s: detach %p from parent %p\n",
-                                       __func__, wsi, wsi->parent);
-
-                       if (wsi->parent->protocol)
-                               wsi->parent->protocol->callback(wsi,
-                                               LWS_CALLBACK_CHILD_CLOSING,
-                                              wsi->parent->user_space, wsi, 0);
-
-                       *pwsi = wsi->sibling_list;
-                       seen = 1;
-                       break;
-               }
-               pwsi = &(*pwsi)->sibling_list;
-       }
-       if (!seen)
-               lwsl_err("%s: failed to detach from parent\n", __func__);
-
-       wsi->parent = NULL;
-}
-
-int
-lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p)
-{
-//     if (wsi->protocol == p)
-//             return 0;
-       const struct lws_protocols *vp = wsi->vhost->protocols, *vpo;
-
-       if (wsi->protocol)
-               wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL,
-                                       wsi->user_space, NULL, 0);
-       if (!wsi->user_space_externally_allocated)
-               lws_free_set_NULL(wsi->user_space);
-
-       lws_same_vh_protocol_remove(wsi);
-
-       wsi->protocol = p;
-       if (!p)
-               return 0;
-
-       if (lws_ensure_user_space(wsi))
-               return 1;
-
-       if (p > vp && p < &vp[wsi->vhost->count_protocols])
-               lws_same_vh_protocol_insert(wsi, p - vp);
-       else {
-               int n = wsi->vhost->count_protocols;
-               int hit = 0;
-
-               vpo = vp;
-
-               while (n--) {
-                       if (p->name && vp->name && !strcmp(p->name, vp->name)) {
-                               hit = 1;
-                               lws_same_vh_protocol_insert(wsi, vp - vpo);
-                               break;
-                       }
-                       vp++;
-               }
-               if (!hit)
-                       lwsl_err("%s: protocol %p is not in vhost %s protocols list\n",
-                        __func__, p, wsi->vhost->name);
-       }
-
-       if (wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BIND_PROTOCOL,
-                                   wsi->user_space, NULL, 0))
-               return 1;
-
-       return 0;
-}
-
-void
-lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
-{
-       struct lws_context_per_thread *pt;
-       struct lws *wsi1, *wsi2;
-       struct lws_context *context;
-       struct lws_tokens eff_buf;
-       int n, m, ret;
-
-       lwsl_debug("%s: %p\n", __func__, wsi);
-
-       if (!wsi)
-               return;
-
-       lws_access_log(wsi);
-#if defined(LWS_WITH_ESP8266)
-       if (wsi->premature_rx)
-               lws_free(wsi->premature_rx);
-
-       if (wsi->pending_send_completion && !wsi->close_is_pending_send_completion) {
-               lwsl_notice("delaying close\n");
-               wsi->close_is_pending_send_completion = 1;
-               return;
-       }
-#endif
-
-       /* we're closing, losing some rx is OK */
-       lws_header_table_force_to_detachable_state(wsi);
-
-       context = wsi->context;
-       pt = &context->pt[(int)wsi->tsi];
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_CLOSE, 1);
-
-       /* if we have children, close them first */
-       if (wsi->child_list) {
-               wsi2 = wsi->child_list;
-               while (wsi2) {
-                       //lwsl_notice("%s: closing %p: close child %p\n",
-                       //              __func__, wsi, wsi2);
-                       wsi1 = wsi2->sibling_list;
-                       //lwsl_notice("%s: closing %p: next sibling %p\n",
-                       //              __func__, wsi2, wsi1);
-                       wsi2->parent = NULL;
-                       /* stop it doing shutdown processing */
-                       wsi2->socket_is_permanently_unusable = 1;
-                       lws_close_free_wsi(wsi2, reason);
-                       wsi2 = wsi1;
-               }
-               wsi->child_list = NULL;
-       }
-
-       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;
-       }
-
-#ifdef LWS_WITH_CGI
-       if (wsi->mode == LWSCM_CGI) {
-               /* we are not a network connection, but a handler for CGI io */
-               if (wsi->parent && wsi->parent->cgi)
-                       /* end the binding between us and master */
-                       wsi->parent->cgi->stdwsi[(int)wsi->cgi_channel] = NULL;
-               wsi->socket_is_permanently_unusable = 1;
-
-               lwsl_debug("------ %s: detected cgi fdhandler wsi %p\n", __func__, wsi);
-               goto just_kill_connection;
-       }
-
-       if (wsi->cgi) {
-               struct lws_cgi **pcgi = &pt->cgi_list;
-               /* remove us from the cgi list */
-               lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->cgi);
-               while (*pcgi) {
-                       if (*pcgi == wsi->cgi) {
-                               /* drop us from the pt cgi list */
-                               *pcgi = (*pcgi)->cgi_list;
-                               break;
-                       }
-                       pcgi = &(*pcgi)->cgi_list;
-               }
-               if (wsi->cgi->headers_buf) {
-                       lwsl_debug("close: freed cgi headers\n");
-                       lws_free_set_NULL(wsi->cgi->headers_buf);
-               }
-               /* we have a cgi going, we must kill it */
-               wsi->cgi->being_closed = 1;
-               lws_cgi_kill(wsi);
-       }
-#endif
-
-#if !defined(LWS_NO_CLIENT)
-       if (wsi->mode == LWSCM_HTTP_CLIENT ||
-           wsi->mode == LWSCM_WSCL_WAITING_CONNECT ||
-           wsi->mode == LWSCM_WSCL_WAITING_PROXY_REPLY ||
-           wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE ||
-           wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE2 ||
-           wsi->mode == LWSCM_WSCL_WAITING_SSL ||
-           wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY ||
-           wsi->mode == LWSCM_WSCL_WAITING_EXTENSION_CONNECT ||
-           wsi->mode == LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY ||
-           wsi->mode == LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY ||
-           wsi->mode == LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY)
-               if (wsi->u.hdr.stash)
-                       lws_free_set_NULL(wsi->u.hdr.stash);
-#endif
-
-       if (wsi->mode == LWSCM_RAW) {
-               wsi->protocol->callback(wsi,
-                       LWS_CALLBACK_RAW_CLOSE, wsi->user_space, NULL, 0);
-               wsi->socket_is_permanently_unusable = 1;
-               goto just_kill_connection;
-       }
-
-       if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED &&
-           wsi->u.http.fop_fd != NULL) {
-               lws_vfs_file_close(&wsi->u.http.fop_fd);
-               wsi->vhost->protocols->callback(wsi,
-                       LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0);
-               wsi->told_user_closed = 1;
-       }
-       if (wsi->socket_is_permanently_unusable ||
-           reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY ||
-           wsi->state == LWSS_SHUTDOWN)
-               goto just_kill_connection;
-
-       wsi->state_pre_close = wsi->state;
-
-       switch (wsi->state_pre_close) {
-       case LWSS_DEAD_SOCKET:
-               return;
-
-       /* we tried the polite way... */
-       case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION:
-       case LWSS_AWAITING_CLOSE_ACK:
-               goto just_kill_connection;
-
-       case LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE:
-               if (wsi->trunc_len) {
-                       lws_callback_on_writable(wsi);
-                       return;
-               }
-               lwsl_info("wsi %p completed LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi);
-               goto just_kill_connection;
-       default:
-               if (wsi->trunc_len) {
-                       lwsl_info("wsi %p entering LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi);
-                       wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE;
-                       lws_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5);
-                       return;
-               }
-               break;
-       }
-
-       if (wsi->mode == LWSCM_WSCL_WAITING_CONNECT ||
-           wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE)
-               goto just_kill_connection;
-
-       if (wsi->mode == LWSCM_HTTP_SERVING) {
-               if (wsi->user_space)
-                       wsi->vhost->protocols->callback(wsi,
-                                               LWS_CALLBACK_HTTP_DROP_PROTOCOL,
-                                              wsi->user_space, NULL, 0);
-               wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
-                                              wsi->user_space, NULL, 0);
-               wsi->told_user_closed = 1;
-       }
-       if (wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) {
-               wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_CLIENT_HTTP,
-                                              wsi->user_space, NULL, 0);
-               wsi->told_user_closed = 1;
-       }
-
-       /*
-        * are his extensions okay with him closing?  Eg he might be a mux
-        * parent and just his ch1 aspect is closing?
-        */
-
-       if (lws_ext_cb_active(wsi,
-                     LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE, NULL, 0) > 0) {
-               lwsl_ext("extension vetoed close\n");
-               return;
-       }
-
-       /*
-        * flush any tx pending from extensions, since we may send close packet
-        * if there are problems with send, just nuke the connection
-        */
-
-       do {
-               ret = 0;
-               eff_buf.token = NULL;
-               eff_buf.token_len = 0;
-
-               /* show every extension the new incoming data */
-
-               m = lws_ext_cb_active(wsi,
-                         LWS_EXT_CB_FLUSH_PENDING_TX, &eff_buf, 0);
-               if (m < 0) {
-                       lwsl_ext("Extension reports fatal error\n");
-                       goto just_kill_connection;
-               }
-               if (m)
-                       /*
-                        * at least one extension told us he has more
-                        * to spill, so we will go around again after
-                        */
-                       ret = 1;
-
-               /* assuming they left us something to send, send it */
-
-               if (eff_buf.token_len)
-                       if (lws_issue_raw(wsi, (unsigned char *)eff_buf.token,
-                                         eff_buf.token_len) !=
-                           eff_buf.token_len) {
-                               lwsl_debug("close: ext spill failed\n");
-                               goto just_kill_connection;
-                       }
-       } while (ret);
-
-       /*
-        * signal we are closing, lws_write will
-        * add any necessary version-specific stuff.  If the write fails,
-        * no worries we are closing anyway.  If we didn't initiate this
-        * close, then our state has been changed to
-        * LWSS_RETURNED_CLOSE_ALREADY and we will skip this.
-        *
-        * Likewise if it's a second call to close this connection after we
-        * sent the close indication to the peer already, we are in state
-        * LWSS_AWAITING_CLOSE_ACK and will skip doing this a second time.
-        */
-
-       if (wsi->state_pre_close == LWSS_ESTABLISHED &&
-           (wsi->u.ws.close_in_ping_buffer_len || /* already a reason */
-            (reason != LWS_CLOSE_STATUS_NOSTATUS &&
-            (reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)))) {
-               lwsl_debug("sending close indication...\n");
-
-               /* if no prepared close reason, use 1000 and no aux data */
-               if (!wsi->u.ws.close_in_ping_buffer_len) {
-                       wsi->u.ws.close_in_ping_buffer_len = 2;
-                       wsi->u.ws.ping_payload_buf[LWS_PRE] =
-                               (reason >> 8) & 0xff;
-                       wsi->u.ws.ping_payload_buf[LWS_PRE + 1] =
-                               reason & 0xff;
-               }
-
-#if defined (LWS_WITH_ESP8266)
-               wsi->close_is_pending_send_completion = 1;
-#endif
-
-               lwsl_debug("waiting for chance to send close\n");
-               wsi->waiting_to_send_close_frame = 1;
-               wsi->state = LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION;
-               lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 2);
-               lws_callback_on_writable(wsi);
-
-               return;
-       }
-
-just_kill_connection:
-
-       lws_remove_child_from_any_parent(wsi);
-
-#if 0
-       /* manage the vhost same protocol list entry */
-
-       if (wsi->same_vh_protocol_prev) { // we are on the vh list
-
-               // make guy who pointed to us, point to what our next was pointing to
-               *wsi->same_vh_protocol_prev = wsi->same_vh_protocol_next;
-
-               // if we had a next guy...
-               if (wsi->same_vh_protocol_next)
-                       // have him point back to our prev
-                       wsi->same_vh_protocol_next->same_vh_protocol_prev =
-                                       wsi->same_vh_protocol_prev;
-       }
-#endif
-
-#if LWS_POSIX
-       /*
-        * Testing with ab shows that we have to stage the socket close when
-        * the system is under stress... shutdown any further TX, change the
-        * state to one that won't emit anything more, and wait with a timeout
-        * for the POLLIN to show a zero-size rx before coming back and doing
-        * the actual close.
-        */
-       if (wsi->mode != LWSCM_RAW &&
-           !(wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) &&
-           wsi->state != LWSS_SHUTDOWN &&
-           wsi->state != LWSS_CLIENT_UNCONNECTED &&
-           reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY &&
-           !wsi->socket_is_permanently_unusable) {
-#ifdef LWS_OPENSSL_SUPPORT
-               if (lws_is_ssl(wsi) && wsi->ssl)
-               {
-                       lwsl_info("%s: shutting down SSL connection: %p (ssl %p, sock %d, state %d)\n", __func__, wsi, wsi->ssl, (int)(long)wsi->desc.sockfd, wsi->state);
-                       n = SSL_shutdown(wsi->ssl);
-                       if (n == 1) /* If finished the SSL shutdown, then do socket shutdown, else need to retry SSL shutdown */
-                               n = shutdown(wsi->desc.sockfd, SHUT_WR);
-                       else if (n == 0)
-                               lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN);
-                       else /* n < 0 */
-                       {
-                               int shutdown_error = SSL_get_error(wsi->ssl, n);
-                               lwsl_debug("SSL_shutdown returned %d, SSL_get_error: %d\n", n, shutdown_error);
-                               if (shutdown_error == SSL_ERROR_WANT_READ) {
-                                       lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN);
-                                       n = 0;
-                               } else if (shutdown_error == SSL_ERROR_WANT_WRITE) {
-                                       lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLOUT);
-                                       n = 0;
-                               } else { // actual error occurred, just close the connection
-                                       n = shutdown(wsi->desc.sockfd, SHUT_WR);
-                               }
-                       }
-               }
-               else
-#endif
-               {
-                       lwsl_info("%s: shutting down connection: %p (sock %d, state %d)\n", __func__, wsi, (int)(long)wsi->desc.sockfd, wsi->state);
-                       n = shutdown(wsi->desc.sockfd, SHUT_WR);
-               }
-               if (n)
-                       lwsl_debug("closing: shutdown (state %d) ret %d\n", wsi->state, LWS_ERRNO);
-
-// This causes problems with disconnection when the events are half closing connection
-// FD_READ | FD_CLOSE (33)
-#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP32)
-               /* libuv: no event available to guarantee completion */
-               if (!LWS_LIBUV_ENABLED(context)) {
-
-                       lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN);
-                       wsi->state = LWSS_SHUTDOWN;
-                       lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH,
-                                       context->timeout_secs);
-
-                       return;
-               }
-#endif
-       }
-#endif
-
-       lwsl_info("%s: real just_kill_connection: %p (sockfd %d)\n", __func__,
-                 wsi, wsi->desc.sockfd);
-       
-#ifdef LWS_WITH_HTTP_PROXY
-       if (wsi->rw) {
-               lws_rewrite_destroy(wsi->rw);
-               wsi->rw = NULL;
-       }
-#endif
-       /*
-        * we won't be servicing or receiving anything further from this guy
-        * delete socket from the internal poll list if still present
-        */
-       lws_ssl_remove_wsi_from_buffered_list(wsi);
-
-       lws_remove_from_timeout_list(wsi);
-
-       /* checking return redundant since we anyway close */
-       if (wsi->desc.sockfd != LWS_SOCK_INVALID)
-               remove_wsi_socket_from_fds(wsi);
-       else
-               lws_same_vh_protocol_remove(wsi);
-
-#if defined(LWS_WITH_ESP8266)
-       espconn_disconnect(wsi->desc.sockfd);
-#endif
-
-       wsi->state = LWSS_DEAD_SOCKET;
-
-       lws_free_set_NULL(wsi->rxflow_buffer);
-       if (wsi->state_pre_close == LWSS_ESTABLISHED ||
-           wsi->mode == LWSCM_WS_SERVING ||
-           wsi->mode == LWSCM_WS_CLIENT) {
-
-               if (wsi->u.ws.rx_draining_ext) {
-                       struct lws **w = &pt->rx_draining_ext_list;
-
-                       wsi->u.ws.rx_draining_ext = 0;
-                       /* remove us from context draining ext list */
-                       while (*w) {
-                               if (*w == wsi) {
-                                       *w = wsi->u.ws.rx_draining_ext_list;
-                                       break;
-                               }
-                               w = &((*w)->u.ws.rx_draining_ext_list);
-                       }
-                       wsi->u.ws.rx_draining_ext_list = NULL;
-               }
-
-               if (wsi->u.ws.tx_draining_ext) {
-                       struct lws **w = &pt->tx_draining_ext_list;
-
-                       wsi->u.ws.tx_draining_ext = 0;
-                       /* remove us from context draining ext list */
-                       while (*w) {
-                               if (*w == wsi) {
-                                       *w = wsi->u.ws.tx_draining_ext_list;
-                                       break;
-                               }
-                               w = &((*w)->u.ws.tx_draining_ext_list);
-                       }
-                       wsi->u.ws.tx_draining_ext_list = NULL;
-               }
-               lws_free_set_NULL(wsi->u.ws.rx_ubuf);
-
-               if (wsi->trunc_alloc)
-                       /* not going to be completed... nuke it */
-                       lws_free_set_NULL(wsi->trunc_alloc);
-
-               wsi->u.ws.ping_payload_len = 0;
-               wsi->u.ws.ping_pending_flag = 0;
-       }
-
-       /* tell the user it's all over for this guy */
-
-       if (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->mode == LWSCM_WS_CLIENT && wsi->state_pre_close == LWSS_HTTP) ||
-           (wsi->mode == LWSCM_WS_SERVING && wsi->state_pre_close == LWSS_HTTP))) {
-
-               if (wsi->user_space) {
-                       lwsl_debug("%s: doing LWS_CALLBACK_HTTP_DROP_PROTOCOL for %p prot %s\n", __func__, wsi, wsi->protocol->name);
-                       wsi->protocol->callback(wsi,
-                                       LWS_CALLBACK_HTTP_DROP_PROTOCOL,
-                                              wsi->user_space, NULL, 0);
-               }
-               lwsl_debug("calling back CLOSED\n");
-               wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED,
-                                       wsi->user_space, NULL, 0);
-       } else if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED) {
-               lwsl_debug("calling back CLOSED_HTTP\n");
-               wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
-                                              wsi->user_space, NULL, 0 );
-       } else if ((wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY ||
-                  wsi->mode == LWSCM_WSCL_WAITING_CONNECT) &&
-                  !wsi->already_did_cce) {
-                       wsi->vhost->protocols[0].callback(wsi,
-                                       LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
-                                       wsi->user_space, NULL, 0);
-       } else
-               lwsl_debug("not calling back closed mode=%d state=%d\n",
-                          wsi->mode, wsi->state_pre_close);
-
-       /* deallocate any active extension contexts */
-
-       if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
-               lwsl_warn("extension destruction failed\n");
-       /*
-        * inform all extensions in case they tracked this guy out of band
-        * even though not active on him specifically
-        */
-       if (lws_ext_cb_all_exts(context, wsi,
-                      LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING, NULL, 0) < 0)
-               lwsl_warn("ext destroy wsi failed\n");
-
-async_close:
-
-       wsi->socket_is_permanently_unusable = 1;
-
-#ifdef LWS_USE_LIBUV
-       if (!wsi->parent_carries_io)
-               if (LWS_LIBUV_ENABLED(context)) {
-                       if (wsi->listener) {
-                               lwsl_debug("%s: stopping listner libuv poll\n", __func__);
-                               uv_poll_stop(&wsi->w_read.uv_watcher);
-                       }
-                       lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi);
-                       /* libuv has to do his own close handle processing asynchronously */
-                       lws_libuv_closehandle(wsi);
-
-                       return;
-               }
-#endif
-
-       lws_close_free_wsi_final(wsi);
-}
-
-void
-lws_close_free_wsi_final(struct lws *wsi)
-{
-       int n;
-
-       if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->desc.sockfd)) {
-#if LWS_POSIX
-               //lwsl_err("*** closing sockfd %d\n", wsi->desc.sockfd);
-               n = compatible_close(wsi->desc.sockfd);
-               if (n)
-                       lwsl_debug("closing: close ret %d\n", LWS_ERRNO);
-
-#else
-               compatible_close(wsi->desc.sockfd);
-               (void)n;
-#endif
-               wsi->desc.sockfd = LWS_SOCK_INVALID;
-       }
-
-       /* outermost destroy notification for wsi (user_space still intact) */
-       wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY,
-                                      wsi->user_space, NULL, 0);
-
-#ifdef LWS_WITH_CGI
-       if (wsi->cgi) {
-               for (n = 0; n < 6; n++) {
-                       if (wsi->cgi->pipe_fds[n / 2][n & 1] == 0)
-                               lwsl_err("ZERO FD IN CGI CLOSE");
-
-                       if (wsi->cgi->pipe_fds[n / 2][n & 1] >= 0)
-                               close(wsi->cgi->pipe_fds[n / 2][n & 1]);
-               }
-
-               lws_free(wsi->cgi);
-       }
-#endif
-
-       lws_free_wsi(wsi);
-}
-
-LWS_VISIBLE LWS_EXTERN const char *
-lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len)
-{
-       int n = 0, sl = strlen(name);
-
-       while (lws_hdr_copy_fragment(wsi, buf, len,
-                         WSI_TOKEN_HTTP_URI_ARGS, n) >= 0) {
-
-               if (!strncmp(buf, name, sl))
-                       return buf + sl;
-
-               n++;
-       }
-
-       return NULL;
-}
-
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-LWS_VISIBLE int
-interface_to_sa(struct lws_vhost *vh, const char *ifname, struct sockaddr_in *addr, size_t addrlen)
-{
-       int ipv6 = 0;
-#ifdef LWS_USE_IPV6
-       ipv6 = LWS_IPV6_ENABLED(vh);
-#endif
-       (void)vh;
-
-       return lws_interface_to_sa(ipv6, ifname, addr, addrlen);
-}
-#endif
-
-#ifndef LWS_PLAT_OPTEE
-#if LWS_POSIX
-static int
-lws_get_addresses(struct lws_vhost *vh, void *ads, char *name,
-                 int name_len, char *rip, int rip_len)
-{
-#if LWS_POSIX
-       struct addrinfo ai, *res;
-       struct sockaddr_in addr4;
-
-       rip[0] = '\0';
-       name[0] = '\0';
-       addr4.sin_family = AF_UNSPEC;
-
-#ifdef LWS_USE_IPV6
-       if (LWS_IPV6_ENABLED(vh)) {
-               if (!lws_plat_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ads)->sin6_addr, rip, rip_len)) {
-                       lwsl_err("inet_ntop: %s", strerror(LWS_ERRNO));
-                       return -1;
-               }
-
-               // Strip off the IPv4 to IPv6 header if one exists
-               if (strncmp(rip, "::ffff:", 7) == 0)
-                       memmove(rip, rip + 7, strlen(rip) - 6);
-
-               getnameinfo((struct sockaddr *)ads,
-                               sizeof(struct sockaddr_in6), name,
-                                                       name_len, NULL, 0, 0);
-
-               return 0;
-       } else
-#endif
-       {
-               struct addrinfo *result;
-
-               memset(&ai, 0, sizeof ai);
-               ai.ai_family = PF_UNSPEC;
-               ai.ai_socktype = SOCK_STREAM;
-               ai.ai_flags = AI_CANONNAME;
-#if !defined(LWS_WITH_ESP32)
-               if (getnameinfo((struct sockaddr *)ads,
-                               sizeof(struct sockaddr_in),
-                               name, name_len, NULL, 0, 0))
-                       return -1;
-#endif
-
-               if (getaddrinfo(name, NULL, &ai, &result))
-                       return -1;
-
-               res = result;
-               while (addr4.sin_family == AF_UNSPEC && res) {
-                       switch (res->ai_family) {
-                       case AF_INET:
-                               addr4.sin_addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
-                               addr4.sin_family = AF_INET;
-                               break;
-                       }
-
-                       res = res->ai_next;
-               }
-               freeaddrinfo(result);
-       }
-
-       if (addr4.sin_family == AF_UNSPEC)
-               return -1;
-
-       if (lws_plat_inet_ntop(AF_INET, &addr4.sin_addr, rip, rip_len) == NULL)
-               return -1;
-
-       return 0;
-#else
-       (void)vh;
-       (void)ads;
-       (void)name;
-       (void)name_len;
-       (void)rip;
-       (void)rip_len;
-
-       return -1;
-#endif
-}
-#endif
-
-
-LWS_VISIBLE const char *
-lws_get_peer_simple(struct lws *wsi, char *name, int namelen)
-{
-#if LWS_POSIX
-       socklen_t len, olen;
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 sin6;
-#endif
-       struct sockaddr_in sin4;
-       int af = AF_INET;
-       void *p, *q;
-
-       if (wsi->parent_carries_io)
-               wsi = wsi->parent;
-
-#ifdef LWS_USE_IPV6
-       if (LWS_IPV6_ENABLED(wsi->vhost)) {
-               len = sizeof(sin6);
-               p = &sin6;
-               af = AF_INET6;
-               q = &sin6.sin6_addr;
-       } else
-#endif
-       {
-               len = sizeof(sin4);
-               p = &sin4;
-               q = &sin4.sin_addr;
-       }
-
-       olen = len;
-       if (getpeername(wsi->desc.sockfd, p, &len) < 0 || len > olen) {
-               lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO));
-               return NULL;
-       }
-
-       return lws_plat_inet_ntop(af, q, name, namelen);
-#else
-#if defined(LWS_WITH_ESP8266)
-       return lws_plat_get_peer_simple(wsi, name, namelen);
-#else
-       return NULL;
-#endif
-#endif
-}
-#endif
-
-LWS_VISIBLE void
-lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name,
-                      int name_len, char *rip, int rip_len)
-{
-#ifndef LWS_PLAT_OPTEE
-#if LWS_POSIX
-       socklen_t len;
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 sin6;
-#endif
-       struct sockaddr_in sin4;
-       struct lws_context *context = wsi->context;
-       int ret = -1;
-       void *p;
-
-       rip[0] = '\0';
-       name[0] = '\0';
-
-       lws_latency_pre(context, wsi);
-
-#ifdef LWS_USE_IPV6
-       if (LWS_IPV6_ENABLED(wsi->vhost)) {
-               len = sizeof(sin6);
-               p = &sin6;
-       } else
-#endif
-       {
-               len = sizeof(sin4);
-               p = &sin4;
-       }
-
-       if (getpeername(fd, p, &len) < 0) {
-               lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO));
-               goto bail;
-       }
-
-       ret = lws_get_addresses(wsi->vhost, p, name, name_len, rip, rip_len);
-
-bail:
-       lws_latency(context, wsi, "lws_get_peer_addresses", ret, 1);
-#endif
-#endif
-       (void)wsi;
-       (void)fd;
-       (void)name;
-       (void)name_len;
-       (void)rip;
-       (void)rip_len;
-
-}
-
-LWS_EXTERN void *
-lws_context_user(struct lws_context *context)
-{
-       return context->user_space;
-}
-
-LWS_VISIBLE struct lws_vhost *
-lws_vhost_get(struct lws *wsi)
-{
-       return wsi->vhost;
-}
-
-LWS_VISIBLE struct lws_vhost *
-lws_get_vhost(struct lws *wsi)
-{
-       return wsi->vhost;
-}
-
-LWS_VISIBLE const struct lws_protocols *
-lws_protocol_get(struct lws *wsi)
-{
-       return wsi->protocol;
-}
-
-LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
-lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name)
-{
-       int n;
-
-       for (n = 0; n < vh->count_protocols; n++)
-               if (!strcmp(name, vh->protocols[n].name))
-                       return &vh->protocols[n];
-
-       return NULL;
-}
-
-LWS_VISIBLE int
-lws_callback_all_protocol(struct lws_context *context,
-                         const struct lws_protocols *protocol, int reason)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       unsigned int n, m = context->count_threads;
-       struct lws *wsi;
-
-       while (m--) {
-               for (n = 0; n < pt->fds_count; n++) {
-                       wsi = wsi_from_fd(context, pt->fds[n].fd);
-                       if (!wsi)
-                               continue;
-                       if (wsi->protocol == protocol)
-                               protocol->callback(wsi, reason, wsi->user_space,
-                                                  NULL, 0);
-               }
-               pt++;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_callback_all_protocol_vhost(struct lws_vhost *vh,
-                         const struct lws_protocols *protocol, int reason)
-{
-       struct lws_context *context = vh->context;
-       struct lws_context_per_thread *pt = &context->pt[0];
-       unsigned int n, m = context->count_threads;
-       struct lws *wsi;
-
-       while (m--) {
-               for (n = 0; n < pt->fds_count; n++) {
-                       wsi = wsi_from_fd(context, pt->fds[n].fd);
-                       if (!wsi)
-                               continue;
-                       if (wsi->vhost == vh && wsi->protocol == protocol)
-                               protocol->callback(wsi, reason, wsi->user_space,
-                                                  NULL, 0);
-               }
-               pt++;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len)
-{
-       int n;
-
-       for (n = 0; n < wsi->vhost->count_protocols; n++)
-               if (wsi->vhost->protocols[n].callback(wsi, reason, NULL, in, len))
-                       return 1;
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops)
-{
-       context->fops = fops;
-}
-
-LWS_VISIBLE LWS_EXTERN lws_filepos_t
-lws_vfs_tell(lws_fop_fd_t fop_fd)
-{
-       return fop_fd->pos;
-}
-
-LWS_VISIBLE LWS_EXTERN lws_filepos_t
-lws_vfs_get_length(lws_fop_fd_t fop_fd)
-{
-       return fop_fd->len;
-}
-
-LWS_VISIBLE LWS_EXTERN uint32_t
-lws_vfs_get_mod_time(lws_fop_fd_t fop_fd)
-{
-       return fop_fd->mod_time;
-}
-
-LWS_VISIBLE lws_fileofs_t
-lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
-{
-       lws_fileofs_t ofs;
-       lwsl_debug("%s: seeking to %ld, len %ld\n", __func__, (long)offset, (long)fop_fd->len);
-       ofs = fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset - fop_fd->pos);
-       lwsl_debug("%s: result %ld, fop_fd pos %ld\n", __func__, (long)ofs, (long)fop_fd->pos);
-       return ofs;
-}
-
-
-LWS_VISIBLE lws_fileofs_t
-lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
-{
-       return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, fop_fd->len + fop_fd->pos + offset);
-}
-
-
-const struct lws_plat_file_ops *
-lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path,
-                   const char **vpath)
-{
-       const struct lws_plat_file_ops *pf;
-       const char *p = vfs_path;
-       int n;
-
-       *vpath = NULL;
-
-       /* no non-platform fops, just use that */
-
-       if (!fops->next)
-               return fops;
-
-       /*
-        *  scan the vfs path looking for indications we are to be
-        * handled by a specific fops
-        */
-
-       while (p && *p) {
-               if (*p != '/') {
-                       p++;
-                       continue;
-               }
-               /* the first one is always platform fops, so skip */
-               pf = fops->next;
-               while (pf) {
-                       n = 0;
-                       while (n < ARRAY_SIZE(pf->fi) && pf->fi[n].sig) {
-                               if (p >= vfs_path + pf->fi[n].len)
-                                       if (!strncmp(p - (pf->fi[n].len - 1),
-                                                   pf->fi[n].sig,
-                                                   pf->fi[n].len - 1)) {
-                                               *vpath = p + 1;
-                                               return pf;
-                                       }
-
-                               n++;
-                       }
-                       pf = pf->next;
-               }
-               p++;
-       }
-
-       return fops;
-}
-
-LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT
-lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
-                 lws_fop_flags_t *flags)
-{
-       const char *vpath = "";
-       const struct lws_plat_file_ops *selected = lws_vfs_select_fops(
-                       fops, vfs_path, &vpath);
-
-       return selected->LWS_FOP_OPEN(fops, vfs_path, vpath, flags);
-}
-
-
-/**
- * lws_now_secs() - seconds since 1970-1-1
- *
- */
-LWS_VISIBLE LWS_EXTERN unsigned long
-lws_now_secs(void)
-{
-       struct timeval tv;
-
-       gettimeofday(&tv, NULL);
-
-       return tv.tv_sec;
-}
-
-
-#if LWS_POSIX
-
-LWS_VISIBLE int
-lws_get_socket_fd(struct lws *wsi)
-{
-       return wsi->desc.sockfd;
-}
-
-#endif
-
-#ifdef LWS_LATENCY
-void
-lws_latency(struct lws_context *context, struct lws *wsi, const char *action,
-           int ret, int completed)
-{
-       unsigned long long u;
-       char buf[256];
-
-       u = time_in_microseconds();
-
-       if (!action) {
-               wsi->latency_start = u;
-               if (!wsi->action_start)
-                       wsi->action_start = u;
-               return;
-       }
-       if (completed) {
-               if (wsi->action_start == wsi->latency_start)
-                       sprintf(buf,
-                         "Completion first try lat %lluus: %p: ret %d: %s\n",
-                                       u - wsi->latency_start,
-                                                     (void *)wsi, ret, action);
-               else
-                       sprintf(buf,
-                         "Completion %lluus: lat %lluus: %p: ret %d: %s\n",
-                               u - wsi->action_start,
-                                       u - wsi->latency_start,
-                                                     (void *)wsi, ret, action);
-               wsi->action_start = 0;
-       } else
-               sprintf(buf, "lat %lluus: %p: ret %d: %s\n",
-                             u - wsi->latency_start, (void *)wsi, ret, action);
-
-       if (u - wsi->latency_start > context->worst_latency) {
-               context->worst_latency = u - wsi->latency_start;
-               strcpy(context->worst_latency_info, buf);
-       }
-       lwsl_latency("%s", buf);
-}
-#endif
-
-LWS_VISIBLE int
-lws_rx_flow_control(struct lws *wsi, int enable)
-{
-       if (enable == (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW))
-               return 0;
-
-       lwsl_info("%s: (0x%p, %d)\n", __func__, wsi, enable);
-       wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !!enable;
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_rx_flow_allow_all_protocol(const struct lws_context *context,
-                              const struct lws_protocols *protocol)
-{
-       const struct lws_context_per_thread *pt = &context->pt[0];
-       struct lws *wsi;
-       unsigned int n, m = context->count_threads;
-
-       while (m--) {
-               for (n = 0; n < pt->fds_count; n++) {
-                       wsi = wsi_from_fd(context, pt->fds[n].fd);
-                       if (!wsi)
-                               continue;
-                       if (wsi->protocol == protocol)
-                               lws_rx_flow_control(wsi, LWS_RXFLOW_ALLOW);
-               }
-               pt++;
-       }
-}
-
-LWS_VISIBLE extern const char *
-lws_canonical_hostname(struct lws_context *context)
-{
-       return (const char *)context->canonical_hostname;
-}
-
-int user_callback_handle_rxflow(lws_callback_function callback_function,
-                               struct lws *wsi,
-                               enum lws_callback_reasons reason, void *user,
-                               void *in, size_t len)
-{
-       int n;
-
-       n = callback_function(wsi, reason, user, in, len);
-       if (!n)
-               n = _lws_rx_flow_control(wsi);
-
-       return n;
-}
-
-#if defined(LWS_WITH_ESP8266)
-#undef strchr
-#define strchr ets_strchr
-#endif
-
-LWS_VISIBLE int
-lws_set_proxy(struct lws_vhost *vhost, const char *proxy)
-{
-#if !defined(LWS_WITH_ESP8266)
-       char *p;
-       char authstring[96];
-
-       if (!proxy)
-               return -1;
-
-       /* we have to deal with a possible redundant leading http:// */
-       if (!strncmp(proxy, "http://", 7))
-               proxy += 7;
-
-       p = strchr(proxy, '@');
-       if (p) { /* auth is around */
-
-               if ((unsigned int)(p - proxy) > sizeof(authstring) - 1)
-                       goto auth_too_long;
-
-               strncpy(authstring, proxy, p - proxy);
-               // null termination not needed on input
-               if (lws_b64_encode_string(authstring, (p - proxy),
-                               vhost->proxy_basic_auth_token,
-                   sizeof vhost->proxy_basic_auth_token) < 0)
-                       goto auth_too_long;
-
-               lwsl_info(" Proxy auth in use\n");
-
-               proxy = p + 1;
-       } else
-               vhost->proxy_basic_auth_token[0] = '\0';
-
-       strncpy(vhost->http_proxy_address, proxy,
-                               sizeof(vhost->http_proxy_address) - 1);
-       vhost->http_proxy_address[
-                               sizeof(vhost->http_proxy_address) - 1] = '\0';
-
-       p = strchr(vhost->http_proxy_address, ':');
-       if (!p && !vhost->http_proxy_port) {
-               lwsl_err("http_proxy needs to be ads:port\n");
-
-               return -1;
-       } else {
-               if (p) {
-                       *p = '\0';
-                       vhost->http_proxy_port = atoi(p + 1);
-               }
-       }
-
-       lwsl_info(" Proxy %s:%u\n", vhost->http_proxy_address,
-                       vhost->http_proxy_port);
-
-       return 0;
-
-auth_too_long:
-       lwsl_err("proxy auth too long\n");
-#endif
-       return -1;
-}
-
-#if defined(LWS_WITH_SOCKS5)
-LWS_VISIBLE int
-lws_set_socks(struct lws_vhost *vhost, const char *socks)
-{
-#if !defined(LWS_WITH_ESP8266)
-       char *p_at, *p_colon;
-       char user[96];
-       char password[96];
-
-       if (!socks)
-               return -1;
-
-       vhost->socks_user[0] = '\0';
-       vhost->socks_password[0] = '\0';
-
-       p_at = strchr(socks, '@');
-       if (p_at) { /* auth is around */
-               if ((unsigned int)(p_at - socks) > (sizeof(user)
-                       + sizeof(password) - 2)) {
-                       lwsl_err("Socks auth too long\n");
-                       goto bail;
-               }
-
-               p_colon = strchr(socks, ':');
-               if (p_colon) {
-                       if ((unsigned int)(p_colon - socks) > (sizeof(user)
-                               - 1) ) {
-                               lwsl_err("Socks user too long\n");
-                               goto bail;
-                       }
-                       if ((unsigned int)(p_at - p_colon) > (sizeof(password)
-                               - 1) ) {
-                               lwsl_err("Socks password too long\n");
-                               goto bail;
-                       }
-               }
-               strncpy(vhost->socks_user, socks, p_colon - socks);
-               strncpy(vhost->socks_password, p_colon + 1,
-                       p_at - (p_colon + 1));
-
-               lwsl_info(" Socks auth, user: %s, password: %s\n",
-                       vhost->socks_user, vhost->socks_password );
-
-               socks = p_at + 1;
-       }
-
-       strncpy(vhost->socks_proxy_address, socks,
-                               sizeof(vhost->socks_proxy_address) - 1);
-       vhost->socks_proxy_address[sizeof(vhost->socks_proxy_address) - 1]
-               = '\0';
-
-       p_colon = strchr(vhost->socks_proxy_address, ':');
-       if (!p_colon && !vhost->socks_proxy_port) {
-               lwsl_err("socks_proxy needs to be address:port\n");
-               return -1;
-       } else {
-               if (p_colon) {
-                       *p_colon = '\0';
-                       vhost->socks_proxy_port = atoi(p_colon + 1);
-               }
-       }
-
-       lwsl_info(" Socks %s:%u\n", vhost->socks_proxy_address,
-                       vhost->socks_proxy_port);
-
-       return 0;
-
-bail:
-#endif
-       return -1;
-}
-#endif
-
-LWS_VISIBLE const struct lws_protocols *
-lws_get_protocol(struct lws *wsi)
-{
-       return wsi->protocol;
-}
-
-LWS_VISIBLE int
-lws_is_final_fragment(struct lws *wsi)
-{
-       lwsl_info("%s: final %d, rx pk length %ld, draining %ld\n", __func__,
-                       wsi->u.ws.final, (long)wsi->u.ws.rx_packet_length,
-                       (long)wsi->u.ws.rx_draining_ext);
-       return wsi->u.ws.final && !wsi->u.ws.rx_packet_length && !wsi->u.ws.rx_draining_ext;
-}
-
-LWS_VISIBLE int
-lws_is_first_fragment(struct lws *wsi)
-{
-       return wsi->u.ws.first_fragment;
-}
-
-LWS_VISIBLE unsigned char
-lws_get_reserved_bits(struct lws *wsi)
-{
-       return wsi->u.ws.rsv;
-}
-
-int
-lws_ensure_user_space(struct lws *wsi)
-{
-       lwsl_info("%s: %p protocol %p\n", __func__, wsi, wsi->protocol);
-       if (!wsi->protocol)
-               return 1;
-
-       /* 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);
-               if (wsi->user_space  == NULL) {
-                       lwsl_err("Out of memory for conn user space\n");
-                       return 1;
-               }
-       } else
-               lwsl_info("%s: %p protocol pss %lu, user_space=%p\n",
-                         __func__, wsi, (long)wsi->protocol->per_session_data_size,
-                         wsi->user_space);
-       return 0;
-}
-
-LWS_VISIBLE int
-lwsl_timestamp(int level, char *p, int len)
-{
-#ifndef LWS_PLAT_OPTEE
-       time_t o_now = time(NULL);
-       unsigned long long now;
-       struct tm *ptm = NULL;
-#ifndef WIN32
-       struct tm tm;
-#endif
-       int n;
-
-#ifndef _WIN32_WCE
-#ifdef WIN32
-       ptm = localtime(&o_now);
-#else
-       if (localtime_r(&o_now, &tm))
-               ptm = &tm;
-#endif
-#endif
-       p[0] = '\0';
-       for (n = 0; n < LLL_COUNT; n++) {
-               if (level != (1 << n))
-                       continue;
-               now = time_in_microseconds() / 100;
-               if (ptm)
-                       n = lws_snprintf(p, len,
-                               "[%04d/%02d/%02d %02d:%02d:%02d:%04d] %s: ",
-                               ptm->tm_year + 1900,
-                               ptm->tm_mon + 1,
-                               ptm->tm_mday,
-                               ptm->tm_hour,
-                               ptm->tm_min,
-                               ptm->tm_sec,
-                               (int)(now % 10000), log_level_names[n]);
-               else
-                       n = lws_snprintf(p, len, "[%llu:%04d] %s: ",
-                                       (unsigned long long) now / 10000,
-                                       (int)(now % 10000), log_level_names[n]);
-               return n;
-       }
-#endif
-       return 0;
-}
-
-#ifndef LWS_PLAT_OPTEE
-LWS_VISIBLE void lwsl_emit_stderr(int level, const char *line)
-{
-#if !defined(LWS_WITH_ESP8266)
-       char buf[50];
-
-       lwsl_timestamp(level, buf, sizeof(buf));
-       fprintf(stderr, "%s%s", buf, line);
-#endif
-}
-#endif
-
-LWS_VISIBLE void _lws_logv(int filter, const char *format, va_list vl)
-{
-#if defined(LWS_WITH_ESP8266)
-       char buf[128];
-#else
-       char buf[256];
-#endif
-       int n;
-
-       if (!(log_level & filter))
-               return;
-
-       n = vsnprintf(buf, sizeof(buf) - 1, format, vl);
-       (void)n;
-#if defined(LWS_WITH_ESP8266)
-       buf[sizeof(buf) - 1] = '\0';
-#else
-       /* vnsprintf returns what it would have written, even if truncated */
-       if (n > sizeof(buf) - 1)
-               n = sizeof(buf) - 1;
-       if (n > 0)
-               buf[n] = '\0';
-#endif
-
-       lwsl_emit(filter, buf);
-}
-
-LWS_VISIBLE void _lws_log(int filter, const char *format, ...)
-{
-       va_list ap;
-
-       va_start(ap, format);
-       _lws_logv(filter, format, ap);
-       va_end(ap);
-}
-
-LWS_VISIBLE void lws_set_log_level(int level,
-                                  void (*func)(int level, const char *line))
-{
-       log_level = level;
-       if (func)
-               lwsl_emit = func;
-}
-
-LWS_VISIBLE int lwsl_visible(int level)
-{
-       return log_level & level;
-}
-
-LWS_VISIBLE int
-lws_is_ssl(struct lws *wsi)
-{
-#ifdef LWS_OPENSSL_SUPPORT
-       return wsi->use_ssl;
-#else
-       (void)wsi;
-       return 0;
-#endif
-}
-
-#ifdef LWS_OPENSSL_SUPPORT
-LWS_VISIBLE SSL*
-lws_get_ssl(struct lws *wsi)
-{
-       return wsi->ssl;
-}
-#endif
-
-LWS_VISIBLE int
-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)
-{
-#ifdef LWS_USE_HTTP2
-       /* 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;
-#else
-       (void)wsi;
-       return -1;
-#endif
-}
-
-LWS_VISIBLE void
-lws_union_transition(struct lws *wsi, enum connection_mode mode)
-{
-       lwsl_debug("%s: %p: mode %d\n", __func__, wsi, mode);
-       memset(&wsi->u, 0, sizeof(wsi->u));
-       wsi->mode = mode;
-}
-
-LWS_VISIBLE struct lws_plat_file_ops *
-lws_get_fops(struct lws_context *context)
-{
-       return (struct lws_plat_file_ops *)context->fops;
-}
-
-LWS_VISIBLE LWS_EXTERN struct lws_context *
-lws_get_context(const struct lws *wsi)
-{
-       return wsi->context;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_get_count_threads(struct lws_context *context)
-{
-       return context->count_threads;
-}
-
-LWS_VISIBLE LWS_EXTERN void *
-lws_wsi_user(struct lws *wsi)
-{
-       return wsi->user_space;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_set_wsi_user(struct lws *wsi, void *data)
-{
-       if (wsi->user_space_externally_allocated)
-               wsi->user_space = data;
-       else
-               lwsl_err("%s: Cannot set internally-allocated user_space\n",
-                        __func__);
-}
-
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_get_parent(const struct lws *wsi)
-{
-       return wsi->parent;
-}
-
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_get_child(const struct lws *wsi)
-{
-       return wsi->child_list;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_set_parent_carries_io(struct lws *wsi)
-{
-       wsi->parent_carries_io = 1;
-}
-
-LWS_VISIBLE LWS_EXTERN void *
-lws_get_opaque_parent_data(const struct lws *wsi)
-{
-       return wsi->opaque_parent_data;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_set_opaque_parent_data(struct lws *wsi, void *data)
-{
-       wsi->opaque_parent_data = data;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_get_child_pending_on_writable(const struct lws *wsi)
-{
-       return wsi->parent_pending_cb_on_writable;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_clear_child_pending_on_writable(struct lws *wsi)
-{
-       wsi->parent_pending_cb_on_writable = 0;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_get_close_length(struct lws *wsi)
-{
-       return wsi->u.ws.close_in_ping_buffer_len;
-}
-
-LWS_VISIBLE LWS_EXTERN unsigned char *
-lws_get_close_payload(struct lws *wsi)
-{
-       return &wsi->u.ws.ping_payload_buf[LWS_PRE];
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_close_reason(struct lws *wsi, enum lws_close_status status,
-                unsigned char *buf, size_t len)
-{
-       unsigned char *p, *start;
-       int budget = sizeof(wsi->u.ws.ping_payload_buf) - LWS_PRE;
-
-       assert(wsi->mode == LWSCM_WS_SERVING || wsi->mode == LWSCM_WS_CLIENT);
-
-       start = p = &wsi->u.ws.ping_payload_buf[LWS_PRE];
-
-       *p++ = (((int)status) >> 8) & 0xff;
-       *p++ = ((int)status) & 0xff;
-
-       if (buf)
-               while (len-- && p < start + budget)
-                       *p++ = *buf++;
-
-       wsi->u.ws.close_in_ping_buffer_len = p - start;
-}
-
-LWS_EXTERN int
-_lws_rx_flow_control(struct lws *wsi)
-{
-       struct lws *wsic = wsi->child_list;
-
-       /* if he has children, do those if they were changed */
-       while (wsic) {
-               if (wsic->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)
-                       _lws_rx_flow_control(wsic);
-
-               wsic = wsic->sibling_list;
-       }
-
-       /* there is no pending change */
-       if (!(wsi->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)) {
-//             lwsl_debug("%s: no pending change\n", __func__);
-               return 0;
-       }
-
-       /* stuff is still buffered, not ready to really accept new input */
-       if (wsi->rxflow_buffer) {
-               /* get ourselves called back to deal with stashed buffer */
-               lws_callback_on_writable(wsi);
-               return 0;
-       }
-
-       /* pending is cleared, we can change rxflow state */
-
-       wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE;
-
-       lwsl_info("rxflow: wsi %p change_to %d\n", wsi,
-                             wsi->rxflow_change_to & LWS_RXFLOW_ALLOW);
-
-       /* adjust the pollfd for this wsi */
-
-       if (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW) {
-               if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
-                       lwsl_info("%s: fail\n", __func__);
-                       return -1;
-               }
-       } else
-               if (lws_change_pollfd(wsi, LWS_POLLIN, 0))
-                       return -1;
-
-       return 0;
-}
-
-LWS_EXTERN int
-lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len)
-{
-       static const unsigned char e0f4[] = {
-               0xa0 | ((2 - 1) << 2) | 1, /* e0 */
-               0x80 | ((4 - 1) << 2) | 1, /* e1 */
-               0x80 | ((4 - 1) << 2) | 1, /* e2 */
-               0x80 | ((4 - 1) << 2) | 1, /* e3 */
-               0x80 | ((4 - 1) << 2) | 1, /* e4 */
-               0x80 | ((4 - 1) << 2) | 1, /* e5 */
-               0x80 | ((4 - 1) << 2) | 1, /* e6 */
-               0x80 | ((4 - 1) << 2) | 1, /* e7 */
-               0x80 | ((4 - 1) << 2) | 1, /* e8 */
-               0x80 | ((4 - 1) << 2) | 1, /* e9 */
-               0x80 | ((4 - 1) << 2) | 1, /* ea */
-               0x80 | ((4 - 1) << 2) | 1, /* eb */
-               0x80 | ((4 - 1) << 2) | 1, /* ec */
-               0x80 | ((2 - 1) << 2) | 1, /* ed */
-               0x80 | ((4 - 1) << 2) | 1, /* ee */
-               0x80 | ((4 - 1) << 2) | 1, /* ef */
-               0x90 | ((3 - 1) << 2) | 2, /* f0 */
-               0x80 | ((4 - 1) << 2) | 2, /* f1 */
-               0x80 | ((4 - 1) << 2) | 2, /* f2 */
-               0x80 | ((4 - 1) << 2) | 2, /* f3 */
-               0x80 | ((1 - 1) << 2) | 2, /* f4 */
-
-               0,                         /* s0 */
-               0x80 | ((4 - 1) << 2) | 0, /* s2 */
-               0x80 | ((4 - 1) << 2) | 1, /* s3 */
-       };
-       unsigned char s = *state;
-
-       while (len--) {
-               unsigned char c = *buf++;
-
-               if (!s) {
-                       if (c >= 0x80) {
-                               if (c < 0xc2 || c > 0xf4)
-                                       return 1;
-                               if (c < 0xe0)
-                                       s = 0x80 | ((4 - 1) << 2);
-                               else
-                                       s = e0f4[c - 0xe0];
-                       }
-               } else {
-                       if (c < (s & 0xf0) ||
-                           c >= (s & 0xf0) + 0x10 + ((s << 2) & 0x30))
-                               return 1;
-                       s = e0f4[21 + (s & 3)];
-               }
-       }
-
-       *state = s;
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_parse_uri(char *p, const char **prot, const char **ads, int *port,
-             const char **path)
-{
-       const char *end;
-       static const char *slash = "/";
-
-       /* cut up the location into address, port and path */
-       *prot = p;
-       while (*p && (*p != ':' || p[1] != '/' || p[2] != '/'))
-               p++;
-       if (!*p) {
-               end = p;
-               p = (char *)*prot;
-               *prot = end;
-       } else {
-               *p = '\0';
-               p += 3;
-       }
-       *ads = p;
-       if (!strcmp(*prot, "http") || !strcmp(*prot, "ws"))
-               *port = 80;
-       else if (!strcmp(*prot, "https") || !strcmp(*prot, "wss"))
-               *port = 443;
-
-       if (*p == '[')
-       {
-               ++(*ads);
-               while (*p && *p != ']')
-                       p++;
-               if (*p)
-                       *p++ = '\0';
-       }
-       else
-       {
-               while (*p && *p != ':' && *p != '/')
-                       p++;
-       }
-       if (*p == ':') {
-               *p++ = '\0';
-               *port = atoi(p);
-               while (*p && *p != '/')
-                       p++;
-       }
-       *path = slash;
-       if (*p) {
-               *p++ = '\0';
-               if (*p)
-                       *path = p;
-       }
-
-       return 0;
-}
-
-#ifdef LWS_NO_EXTENSIONS
-
-/* we need to provide dummy callbacks for internal exts
- * so user code runs when faced with a lib compiled with
- * extensions disabled.
- */
-
-int
-lws_extension_callback_pm_deflate(struct lws_context *context,
-                                  const struct lws_extension *ext,
-                                  struct lws *wsi,
-                                  enum lws_extension_callback_reasons reason,
-                                  void *user, void *in, size_t len)
-{
-       (void)context;
-       (void)ext;
-       (void)wsi;
-       (void)reason;
-       (void)user;
-       (void)in;
-       (void)len;
-
-       return 0;
-}
-#endif
-
-LWS_EXTERN int
-lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
-               const char *iface)
-{
-#if LWS_POSIX
-#ifdef LWS_USE_UNIX_SOCK
-       struct sockaddr_un serv_unix;
-#endif
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 serv_addr6;
-#endif
-       struct sockaddr_in serv_addr4;
-#ifndef LWS_PLAT_OPTEE
-       socklen_t len = sizeof(struct sockaddr_storage);
-#endif
-       int n;
-       struct sockaddr_storage sin;
-       struct sockaddr *v;
-
-#ifdef LWS_USE_UNIX_SOCK
-       if (LWS_UNIX_SOCK_ENABLED(vhost)) {
-               v = (struct sockaddr *)&serv_unix;
-               n = sizeof(struct sockaddr_un);
-               bzero((char *) &serv_unix, sizeof(serv_unix));
-               serv_unix.sun_family = AF_UNIX;
-               if (sizeof(serv_unix.sun_path) <= strlen(iface)) {
-                       lwsl_err("\"%s\" too long for UNIX domain socket\n",
-                                iface);
-                       return -1;
-               }
-               strcpy(serv_unix.sun_path, iface);
-               if (serv_unix.sun_path[0] == '@')
-                       serv_unix.sun_path[0] = '\0';
-
-       } else
-#endif
-#if defined(LWS_USE_IPV6) && !defined(LWS_WITH_ESP32)
-       if (LWS_IPV6_ENABLED(vhost)) {
-               v = (struct sockaddr *)&serv_addr6;
-               n = sizeof(struct sockaddr_in6);
-               bzero((char *) &serv_addr6, sizeof(serv_addr6));
-               if (iface) {
-                       if (interface_to_sa(vhost, iface,
-                                   (struct sockaddr_in *)v, n) < 0) {
-                               lwsl_err("Unable to find interface %s\n", iface);
-                               return -1;
-                       }
-                       serv_addr6.sin6_scope_id = lws_get_addr_scope(iface);
-               }
-
-               serv_addr6.sin6_family = AF_INET6;
-               serv_addr6.sin6_port = htons(port);
-       } else
-#endif
-       {
-               v = (struct sockaddr *)&serv_addr4;
-               n = sizeof(serv_addr4);
-               bzero((char *) &serv_addr4, sizeof(serv_addr4));
-               serv_addr4.sin_addr.s_addr = INADDR_ANY;
-               serv_addr4.sin_family = AF_INET;
-#if !defined(LWS_WITH_ESP32)
-
-               if (iface &&
-                   interface_to_sa(vhost, iface,
-                                   (struct sockaddr_in *)v, n) < 0) {
-                       lwsl_err("Unable to find interface %s\n", iface);
-                       return -1;
-               }
-#endif
-               serv_addr4.sin_port = htons(port);
-       } /* ipv4 */
-
-       n = bind(sockfd, v, n);
-#ifdef LWS_USE_UNIX_SOCK
-       if (n < 0 && LWS_UNIX_SOCK_ENABLED(vhost)) {
-               lwsl_err("ERROR on binding fd %d to \"%s\" (%d %d)\n",
-                               sockfd, iface, n, LWS_ERRNO);
-               return -1;
-       } else
-#endif
-       if (n < 0) {
-               lwsl_err("ERROR on binding fd %d to port %d (%d %d)\n",
-                               sockfd, port, n, LWS_ERRNO);
-               return -1;
-       }
-
-#ifndef LWS_PLAT_OPTEE
-       if (getsockname(sockfd, (struct sockaddr *)&sin, &len) == -1)
-               lwsl_warn("getsockname: %s\n", strerror(LWS_ERRNO));
-       else
-#endif
-#if defined(LWS_USE_IPV6)
-               port = (sin.ss_family == AF_INET6) ?
-                                 ntohs(((struct sockaddr_in6 *) &sin)->sin6_port) :
-                                 ntohs(((struct sockaddr_in *) &sin)->sin_port);
-#else
-               port = ntohs(((struct sockaddr_in *) &sin)->sin_port);
-#endif
-#endif
-
-       return port;
-}
-
-#if defined(LWS_USE_IPV6)
-LWS_EXTERN unsigned long
-lws_get_addr_scope(const char *ipaddr)
-{
-       unsigned long scope = 0;
-
-#ifndef WIN32
-       struct ifaddrs *addrs, *addr;
-       char ip[NI_MAXHOST];
-       unsigned int i;
-
-       getifaddrs(&addrs);
-       for (addr = addrs; addr; addr = addr->ifa_next) {
-               if (!addr->ifa_addr ||
-                       addr->ifa_addr->sa_family != AF_INET6)
-                       continue;
-
-               getnameinfo(addr->ifa_addr,
-                               sizeof(struct sockaddr_in6),
-                               ip, sizeof(ip),
-                               NULL, 0, NI_NUMERICHOST);
-
-               i = 0;
-               while (ip[i])
-                       if (ip[i++] == '%') {
-                               ip[i - 1] = '\0';
-                               break;
-                       }
-
-               if (!strcmp(ip, ipaddr)) {
-                       scope = if_nametoindex(addr->ifa_name);
-                       break;
-               }
-       }
-       freeifaddrs(addrs);
-#else
-       PIP_ADAPTER_ADDRESSES adapter, addrs = NULL;
-       PIP_ADAPTER_UNICAST_ADDRESS addr;
-       ULONG size = 0;
-       DWORD ret;
-       struct sockaddr_in6 *sockaddr;
-       char ip[NI_MAXHOST];
-       unsigned int i;
-       int found = 0;
-
-       for (i = 0; i < 5; i++)
-       {
-               ret = GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX,
-                               NULL, addrs, &size);
-               if ((ret == NO_ERROR) || (ret == ERROR_NO_DATA)) {
-                       break;
-               } else if (ret == ERROR_BUFFER_OVERFLOW)
-               {
-                       if (addrs)
-                               free(addrs);
-                       addrs = (IP_ADAPTER_ADDRESSES *) malloc(size);
-               } else
-               {
-                       if (addrs)
-                       {
-                               free(addrs);
-                               addrs = NULL;
-                       }
-                       lwsl_err("Failed to get IPv6 address table (%d)", ret);
-                       break;
-               }
-       }
-
-       if ((ret == NO_ERROR) && (addrs))
-       {
-               adapter = addrs;
-               while ((adapter) && (!found))
-               {
-                       addr = adapter->FirstUnicastAddress;
-                       while ((addr) && (!found))
-                       {
-                               if (addr->Address.lpSockaddr->sa_family == AF_INET6)
-                               {
-                                       sockaddr = (struct sockaddr_in6 *) (addr->Address.lpSockaddr);
-
-                                       lws_plat_inet_ntop(sockaddr->sin6_family, &sockaddr->sin6_addr,
-                                                       ip, sizeof(ip));
-
-                                       if (!strcmp(ip, ipaddr)) {
-                                               scope = sockaddr->sin6_scope_id;
-                                               found = 1;
-                                               break;
-                                       }
-                               }
-                               addr = addr->Next;
-                       }
-                       adapter = adapter->Next;
-               }
-       }
-       if (addrs)
-               free(addrs);
-#endif
-
-       return scope;
-}
-#endif
-
-LWS_EXTERN void
-lws_restart_ws_ping_pong_timer(struct lws *wsi)
-{
-       if (!wsi->context->ws_ping_pong_interval)
-               return;
-       if (wsi->state != LWSS_ESTABLISHED)
-               return;
-
-       wsi->u.ws.time_next_ping_check = (time_t)lws_now_secs() +
-                                   wsi->context->ws_ping_pong_interval;
-}
-
-static const char *hex = "0123456789ABCDEF";
-
-LWS_VISIBLE LWS_EXTERN const char *
-lws_sql_purify(char *escaped, const char *string, int len)
-{
-       const char *p = string;
-       char *q = escaped;
-
-       while (*p && len-- > 2) {
-               if (*p == '\'') {
-                       *q++ = '\'';
-                       *q++ = '\'';
-                       len --;
-                       p++;
-               } else
-                       *q++ = *p++;
-       }
-       *q = '\0';
-
-       return escaped;
-}
-
-LWS_VISIBLE LWS_EXTERN const char *
-lws_json_purify(char *escaped, const char *string, int len)
-{
-       const char *p = string;
-       char *q = escaped;
-
-       if (!p) {
-               escaped[0] = '\0';
-               return escaped;
-       }
-
-       while (*p && len-- > 6) {
-               if (*p == '\"' || *p == '\\' || *p < 0x20) {
-                       *q++ = '\\';
-                       *q++ = 'u';
-                       *q++ = '0';
-                       *q++ = '0';
-                       *q++ = hex[((*p) >> 4) & 15];
-                       *q++ = hex[(*p) & 15];
-                       len -= 5;
-                       p++;
-               } else
-                       *q++ = *p++;
-       }
-       *q = '\0';
-
-       return escaped;
-}
-
-LWS_VISIBLE LWS_EXTERN const char *
-lws_urlencode(char *escaped, const char *string, int len)
-{
-       const char *p = string;
-       char *q = escaped;
-
-       while (*p && len-- > 3) {
-               if (*p == ' ') {
-                       *q++ = '+';
-                       p++;
-                       continue;
-               }
-               if ((*p >= '0' && *p <= '9') ||
-                   (*p >= 'A' && *p <= 'Z') ||
-                   (*p >= 'a' && *p <= 'z')) {
-                       *q++ = *p++;
-                       continue;
-               }
-               *q++ = '%';
-               *q++ = hex[(*p >> 4) & 0xf];
-               *q++ = hex[*p & 0xf];
-
-               len -= 2;
-               p++;
-       }
-       *q = '\0';
-
-       return escaped;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_urldecode(char *string, const char *escaped, int len)
-{
-       int state = 0, n;
-       char sum = 0;
-
-       while (*escaped && len) {
-               switch (state) {
-               case 0:
-                       if (*escaped == '%') {
-                               state++;
-                               escaped++;
-                               continue;
-                       }
-                       if (*escaped == '+') {
-                               escaped++;
-                               *string++ = ' ';
-                               len--;
-                               continue;
-                       }
-                       *string++ = *escaped++;
-                       len--;
-                       break;
-               case 1:
-                       n = char_to_hex(*escaped);
-                       if (n < 0)
-                               return -1;
-                       escaped++;
-                       sum = n << 4;
-                       state++;
-                       break;
-
-               case 2:
-                       n = char_to_hex(*escaped);
-                       if (n < 0)
-                               return -1;
-                       escaped++;
-                       *string++ = sum | n;
-                       len--;
-                       state = 0;
-                       break;
-               }
-
-       }
-       *string = '\0';
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_finalize_startup(struct lws_context *context)
-{
-       struct lws_context_creation_info info;
-
-       info.uid = context->uid;
-       info.gid = context->gid;
-
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-       memcpy(info.caps, context->caps, sizeof(info.caps));
-       info.count_caps = context->count_caps;
-#endif
-
-       if (lws_check_opt(context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))
-               lws_plat_drop_app_privileges(&info);
-
-       return 0;
-}
-
-int
-lws_snprintf(char *str, size_t size, const char *format, ...)
-{
-       va_list ap;
-       int n;
-
-       if (!size)
-               return 0;
-
-       va_start(ap, format);
-       n = vsnprintf(str, size, format, ap);
-       va_end(ap);
-
-       if (n >= (int)size)
-               return size;
-
-       return n;
-}
-
-
-LWS_VISIBLE LWS_EXTERN int
-lws_is_cgi(struct lws *wsi) {
-#ifdef LWS_WITH_CGI
-       return !!wsi->cgi;
-#else
-       return 0;
-#endif
-}
-
-#ifdef LWS_WITH_CGI
-
-static int
-urlencode(const char *in, int inlen, char *out, int outlen)
-{
-       char *start = out, *end = out + outlen;
-
-       while (inlen-- && out < end - 4) {
-               if ((*in >= 'A' && *in <= 'Z') ||
-                   (*in >= 'a' && *in <= 'z') ||
-                   (*in >= '0' && *in <= '9') ||
-                   *in == '-' ||
-                   *in == '_' ||
-                   *in == '.' ||
-                   *in == '~') {
-                       *out++ = *in++;
-                       continue;
-               }
-               if (*in == ' ') {
-                       *out++ = '+';
-                       in++;
-                       continue;
-               }
-               *out++ = '%';
-               *out++ = hex[(*in) >> 4];
-               *out++ = hex[(*in++) & 15];
-       }
-       *out = '\0';
-
-       if (out >= end - 4)
-               return -1;
-
-       return out - start;
-}
-
-static struct lws *
-lws_create_basic_wsi(struct lws_context *context, int tsi)
-{
-       struct lws *new_wsi;
-
-       if ((unsigned int)context->pt[tsi].fds_count ==
-           context->fd_limit_per_thread - 1) {
-               lwsl_err("no space for new conn\n");
-               return NULL;
-       }
-
-       new_wsi = lws_zalloc(sizeof(struct lws));
-       if (new_wsi == NULL) {
-               lwsl_err("Out of memory for new connection\n");
-               return NULL;
-       }
-
-       new_wsi->tsi = tsi;
-       new_wsi->context = context;
-       new_wsi->pending_timeout = NO_PENDING_TIMEOUT;
-       new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
-
-       /* initialize the instance struct */
-
-       new_wsi->state = LWSS_CGI;
-       new_wsi->mode = LWSCM_CGI;
-       new_wsi->hdr_parsing_completed = 0;
-       new_wsi->position_in_fds_table = -1;
-
-       /*
-        * these can only be set once the protocol is known
-        * we set an unestablished connection's protocol pointer
-        * to the start of the defauly vhost supported list, so it can look
-        * for matching ones during the handshake
-        */
-       new_wsi->protocol = context->vhost_list->protocols;
-       new_wsi->user_space = NULL;
-       new_wsi->ietf_spec_revision = 0;
-       new_wsi->desc.sockfd = LWS_SOCK_INVALID;
-       context->count_wsi_allocated++;
-
-       return new_wsi;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len,
-       int timeout_secs, const struct lws_protocol_vhost_options *mp_cgienv)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       char *env_array[30], cgi_path[400], e[1024], *p = e,
-            *end = p + sizeof(e) - 1, tok[256], *t;
-       struct lws_cgi *cgi;
-       int n, m, i, uritok = -1;
-
-       /*
-        * give the master wsi a cgi struct
-        */
-
-       wsi->cgi = lws_zalloc(sizeof(*wsi->cgi));
-       if (!wsi->cgi) {
-               lwsl_err("%s: OOM\n", __func__);
-               return -1;
-       }
-
-       wsi->cgi->response_code = HTTP_STATUS_OK;
-
-       cgi = wsi->cgi;
-       cgi->wsi = wsi; /* set cgi's owning wsi */
-
-       /* create pipes for [stdin|stdout] and [stderr] */
-
-       for (n = 0; n < 3; n++)
-               if (pipe(cgi->pipe_fds[n]) == -1)
-                       goto bail1;
-
-       /* create cgi wsis for each stdin/out/err fd */
-
-       for (n = 0; n < 3; n++) {
-               cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi);
-               if (!cgi->stdwsi[n])
-                       goto bail2;
-               cgi->stdwsi[n]->cgi_channel = n;
-               cgi->stdwsi[n]->vhost = wsi->vhost;
-
-//             lwsl_err("%s: cgi %p: pipe fd %d -> fd %d / %d\n", __func__, wsi, n,
-//                      cgi->pipe_fds[n][!!(n == 0)], cgi->pipe_fds[n][!(n == 0)]);
-
-               /* read side is 0, stdin we want the write side, others read */
-               cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)];
-               if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) {
-                       lwsl_err("%s: setting NONBLOCK failed\n", __func__);
-                       goto bail2;
-               }
-       }
-
-       for (n = 0; n < 3; n++) {
-               lws_libuv_accept(cgi->stdwsi[n], cgi->stdwsi[n]->desc);
-               if (insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n]))
-                       goto bail3;
-               cgi->stdwsi[n]->parent = wsi;
-               cgi->stdwsi[n]->sibling_list = wsi->child_list;
-               wsi->child_list = cgi->stdwsi[n];
-       }
-
-       lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT);
-       lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN);
-       lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN);
-
-       lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__,
-                  cgi->stdwsi[LWS_STDIN]->desc.sockfd,
-                  cgi->stdwsi[LWS_STDOUT]->desc.sockfd,
-                  cgi->stdwsi[LWS_STDERR]->desc.sockfd);
-
-       lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs);
-
-       /* the cgi stdout is always sending us http1.x header data first */
-       wsi->hdr_state = LCHS_HEADER;
-
-       /* add us to the pt list of active cgis */
-       lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->cgi);
-       cgi->cgi_list = pt->cgi_list;
-       pt->cgi_list = cgi;
-
-       /* prepare his CGI env */
-
-       n = 0;
-
-       if (lws_is_ssl(wsi))
-               env_array[n++] = "HTTPS=ON";
-       if (wsi->u.hdr.ah) {
-               static const unsigned char meths[] = {
-                       WSI_TOKEN_GET_URI,
-                       WSI_TOKEN_POST_URI,
-                       WSI_TOKEN_OPTIONS_URI,
-                       WSI_TOKEN_PUT_URI,
-                       WSI_TOKEN_PATCH_URI,
-                       WSI_TOKEN_DELETE_URI,
-               };
-               static const char * const meth_names[] = {
-                       "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE",
-               };
-
-               for (m = 0; m < ARRAY_SIZE(meths); m++)
-                       if (lws_hdr_total_length(wsi, meths[m]) >=
-                                       script_uri_path_len) {
-                               uritok = meths[m];
-                               break;
-                       }
-
-               if (uritok < 0)
-                       goto bail3;
-
-               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++;
-
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "QUERY_STRING=");
-               /* dump the individual URI Arg parameters */
-               m = 0;
-               while (1) {
-                       i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok),
-                                            WSI_TOKEN_HTTP_URI_ARGS, m);
-                       if (i < 0)
-                               break;
-                       t = tok;
-                       while (*t && *t != '=' && p < end - 4)
-                               *p++ = *t++;
-                       if (*t == '=')
-                               *p++ = *t++;
-                       i = urlencode(t, i- (t - tok), p, end - p);
-                       if (i > 0) {
-                               p += i;
-                               *p++ = '&';
-                       }
-                       m++;
-               }
-               if (m)
-                       p--;
-               *p++ = '\0';
-
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "PATH_INFO=%s",
-                             lws_hdr_simple_ptr(wsi, uritok) +
-                             script_uri_path_len);
-               p++;
-       }
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) {
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "HTTP_REFERER=%s",
-                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER));
-               p++;
-       }
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "HTTP_HOST=%s",
-                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
-               p++;
-       }
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "HTTP_COOKIE=%s",
-                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE));
-               p++;
-       }
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) {
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "USER_AGENT=%s",
-                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT));
-               p++;
-       }
-       if (uritok == WSI_TOKEN_POST_URI) {
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
-                       env_array[n++] = p;
-                       p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s",
-                                     lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE));
-                       p++;
-               }
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
-                       env_array[n++] = p;
-                       p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s",
-                                     lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH));
-                       p++;
-               }
-       }
-       env_array[n++] = p;
-       p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1;
-
-       while (mp_cgienv) {
-               env_array[n++] = p;
-               p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name,
-                             mp_cgienv->value);
-               lwsl_debug("   Applying mount-specific cgi env '%s'\n",
-                          env_array[n - 1]);
-               p++;
-               mp_cgienv = mp_cgienv->next;
-       }
-
-       env_array[n++] = "SERVER_SOFTWARE=libwebsockets";
-       env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin";
-       env_array[n] = NULL;
-
-#if 0
-       for (m = 0; m < n; m++)
-               lwsl_err("    %s\n", env_array[m]);
-#endif
-
-       /*
-        * Actually having made the env, as a cgi we don't need the ah
-        * any more
-        */
-       if (lws_header_table_is_in_detachable_state(wsi))
-               lws_header_table_detach(wsi, 0);
-
-       /* we are ready with the redirection pipes... run the thing */
-#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
-       cgi->pid = fork();
-#else
-       cgi->pid = vfork();
-#endif
-       if (cgi->pid < 0) {
-               lwsl_err("fork failed, errno %d", errno);
-               goto bail3;
-       }
-
-#if defined(__linux__)
-       prctl(PR_SET_PDEATHSIG, SIGTERM);
-#endif
-       setpgrp(); /* stops on-daemonized main processess getting SIGINT from TTY */
-
-       if (cgi->pid) {
-               /* we are the parent process */
-               wsi->context->count_cgi_spawned++;
-               lwsl_debug("%s: cgi %p spawned PID %d\n", __func__, cgi, cgi->pid);
-               return 0;
-       }
-
-       /* somewhere we can at least read things and enter it */
-       if (chdir("/tmp"))
-               lwsl_notice("%s: Failed to chdir\n", __func__);
-
-       /* We are the forked process, redirect and kill inherited things.
-        *
-        * Because of vfork(), we cannot do anything that changes pages in
-        * the parent environment.  Stuff that changes kernel state for the
-        * process is OK.  Stuff that happens after the execvpe() is OK.
-        */
-
-       for (n = 0; n < 3; n++) {
-               if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) {
-                       lwsl_err("%s: stdin dup2 failed\n", __func__);
-                       goto bail3;
-               }
-               close(cgi->pipe_fds[n][!(n == 0)]);
-       }
-
-#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
-       for (m = 0; m < n; m++) {
-               p = strchr(env_array[m], '=');
-               *p++ = '\0';
-               setenv(env_array[m], p, 1);
-       }
-       execvp(exec_array[0], (char * const *)&exec_array[0]);
-#else
-       execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]);
-#endif
-
-       exit(1);
-
-bail3:
-       /* drop us from the pt cgi list */
-       pt->cgi_list = cgi->cgi_list;
-
-       while (--n >= 0)
-               remove_wsi_socket_from_fds(wsi->cgi->stdwsi[n]);
-bail2:
-       for (n = 0; n < 3; n++)
-               if (wsi->cgi->stdwsi[n])
-                       lws_free_wsi(cgi->stdwsi[n]);
-
-bail1:
-       for (n = 0; n < 3; n++) {
-               if (cgi->pipe_fds[n][0])
-                       close(cgi->pipe_fds[n][0]);
-               if (cgi->pipe_fds[n][1])
-                       close(cgi->pipe_fds[n][1]);
-       }
-
-       lws_free_set_NULL(wsi->cgi);
-
-       lwsl_err("%s: failed\n", __func__);
-
-       return -1;
-}
-
-/* we have to parse out these headers in the CGI output */
-
-static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = {
-       "content-length: ",
-       "location: ",
-       "status: ",
-       "transfer-encoding: chunked",
-};
-
-LWS_VISIBLE LWS_EXTERN int
-lws_cgi_write_split_stdout_headers(struct lws *wsi)
-{
-       int n, m;
-       unsigned char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start,
-            *end = &buf[sizeof(buf) - 1 - LWS_PRE];
-       char c;
-
-       if (!wsi->cgi)
-               return -1;
-
-       while (wsi->hdr_state != LHCS_PAYLOAD) {
-               /* we have to separate header / finalize and
-                * payload chunks, 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);
-                       if (lws_add_http_header_status(wsi, wsi->cgi->response_code, &p, end))
-                               return 1;
-                       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);
-
-                       /* finalize cached headers before dumping them */
-                       if (lws_finalize_http_header(wsi,
-                                       (unsigned char **)&wsi->cgi->headers_pos,
-                                       (unsigned char *)wsi->cgi->headers_end)) {
-
-                               lwsl_notice("finalize failed\n");
-                               return -1;
-                       }
-
-                       wsi->hdr_state = LHCS_DUMP_HEADERS;
-                       wsi->reason_bf |= 8;
-                       lws_callback_on_writable(wsi);
-                       /* back to the loop for writeability again */
-                       return 0;
-
-               case LHCS_DUMP_HEADERS:
-
-                       n = wsi->cgi->headers_pos - wsi->cgi->headers_dumped;
-                       if (n > 512)
-                               n = 512;
-
-                       lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n);
-
-                       m = lws_write(wsi, (unsigned char *)wsi->cgi->headers_dumped,
-                                     n, LWS_WRITE_HTTP_HEADERS);
-                       if (m < 0) {
-                               lwsl_debug("%s: write says %d\n", __func__, m);
-                               return -1;
-                       }
-                       wsi->cgi->headers_dumped += n;
-                       if (wsi->cgi->headers_dumped == wsi->cgi->headers_pos) {
-                               wsi->hdr_state = LHCS_PAYLOAD;
-                               lws_free_set_NULL(wsi->cgi->headers_buf);
-                               lwsl_debug("freed cgi headers\n");
-                       } else {
-                               wsi->reason_bf |= 8;
-                               lws_callback_on_writable(wsi);
-                       }
-
-                       /* writeability becomes uncertain now we wrote
-                        * something, we must return to the event loop
-                        */
-                       return 0;
-               }
-
-               if (!wsi->cgi->headers_buf) {
-                       /* if we don't already have a headers buf, cook one up */
-                       n = 2048;
-                       wsi->cgi->headers_buf = malloc(n);
-                       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_dumped = wsi->cgi->headers_pos;
-                       wsi->cgi->headers_end = wsi->cgi->headers_buf + n - 1;
-
-                       for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
-                               wsi->cgi->match[n] = 0;
-                               wsi->cgi->lp = 0;
-                       }
-               }
-
-               n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]), &c, 1);
-               if (n < 0) {
-                       if (errno != EAGAIN) {
-                               lwsl_debug("%s: read says %d\n", __func__, n);
-                               return -1;
-                       }
-                       else
-                               n = 0;
-
-                       if (wsi->cgi->headers_pos >= wsi->cgi->headers_end - 4) {
-                               lwsl_notice("CGI headers larger than buffer size\n");
-
-                               return -1;
-                       }
-               }
-               if (n) {
-                       lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, wsi->cgi->match[1], wsi->hdr_state);
-                       if (!c)
-                               return -1;
-                       switch (wsi->hdr_state) {
-                       case LCHS_HEADER:
-                               hdr:
-                               for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
-                                       /* significant headers with numeric decimal payloads */
-                                       if (!significant_hdr[n][wsi->cgi->match[n]] &&
-                                           (c >= '0' && c <= '9') &&
-                                           wsi->cgi->lp < sizeof(wsi->cgi->l) - 1) {
-                                               wsi->cgi->l[wsi->cgi->lp++] = c;
-                                               wsi->cgi->l[wsi->cgi->lp] = '\0';
-                                               switch (n) {
-                                               case SIGNIFICANT_HDR_CONTENT_LENGTH:
-                                                       wsi->cgi->content_length = atoll(wsi->cgi->l);
-                                                       break;
-                                               case SIGNIFICANT_HDR_STATUS:
-                                                       wsi->cgi->response_code = atol(wsi->cgi->l);
-                                                       lwsl_debug("Status set to %d\n", wsi->cgi->response_code);
-                                                       break;
-                                               default:
-                                                       break;
-                                               }
-                                       }
-                                       /* hits up to the NUL are sticky until next hdr */
-                                       if (significant_hdr[n][wsi->cgi->match[n]]) {
-                                               if (tolower(c) == significant_hdr[n][wsi->cgi->match[n]])
-                                                       wsi->cgi->match[n]++;
-                                               else
-                                                       wsi->cgi->match[n] = 0;
-                                       }
-                               }
-
-                               /* some cgi only send us \x0a for EOL */
-                               if (c == '\x0a') {
-                                       wsi->hdr_state = LCHS_SINGLE_0A;
-                                       *wsi->cgi->headers_pos++ = '\x0d';
-                               }
-                               *wsi->cgi->headers_pos++ = c;
-                               if (c == '\x0d')
-                                       wsi->hdr_state = LCHS_LF1;
-
-                               if (wsi->hdr_state != LCHS_HEADER &&
-                                   !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING][wsi->cgi->match[SIGNIFICANT_HDR_TRANSFER_ENCODING]]) {
-                                       lwsl_debug("cgi produced chunked\n");
-                                       wsi->cgi->explicitly_chunked = 1;
-                               }
-
-                               /* presence of Location: mandates 302 retcode */
-                               if (wsi->hdr_state != LCHS_HEADER &&
-                                   !significant_hdr[SIGNIFICANT_HDR_LOCATION][wsi->cgi->match[SIGNIFICANT_HDR_LOCATION]]) {
-                                       lwsl_debug("CGI: Location hdr seen\n");
-                                       wsi->cgi->response_code = 302;
-                               }
-
-                               break;
-                       case LCHS_LF1:
-                               *wsi->cgi->headers_pos++ = c;
-                               if (c == '\x0a') {
-                                       wsi->hdr_state = LCHS_CR2;
-                                       break;
-                               }
-                               /* we got \r[^\n]... it's unreasonable */
-                               lwsl_debug("%s: funny CRLF 0x%02X\n", __func__, (unsigned char)c);
-                               return -1;
-
-                       case LCHS_CR2:
-                               if (c == '\x0d') {
-                                       /* drop the \x0d */
-                                       wsi->hdr_state = LCHS_LF2;
-                                       break;
-                               }
-                               wsi->hdr_state = LCHS_HEADER;
-                               for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
-                                       wsi->cgi->match[n] = 0;
-                               wsi->cgi->lp = 0;
-                               goto hdr;
-
-                       case LCHS_LF2:
-                       case LCHS_SINGLE_0A:
-                               m = wsi->hdr_state;
-                               if (c == '\x0a') {
-                                       lwsl_debug("Content-Length: %lld\n", (unsigned long long)wsi->cgi->content_length);
-                                       wsi->hdr_state = LHCS_RESPONSE;
-                                       /* drop the \0xa ... finalize will add it if needed */
-                                       break;
-                               }
-                               if (m == LCHS_LF2)
-                                       /* we got \r\n\r[^\n]... it's unreasonable */
-                                       return -1;
-                               /* we got \x0anext header, it's reasonable */
-                               *wsi->cgi->headers_pos++ = c;
-                               wsi->hdr_state = LCHS_HEADER;
-                               for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
-                                       wsi->cgi->match[n] = 0;
-                               wsi->cgi->lp = 0;
-                               break;
-                       case LHCS_PAYLOAD:
-                               break;
-                       }
-               }
-
-               /* ran out of input, ended the headers, or filled up the headers buf */
-               if (!n || wsi->hdr_state == LHCS_PAYLOAD)
-                       return 0;
-       }
-
-       /* payload processing */
-
-       n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]),
-                start, sizeof(buf) - LWS_PRE);
-
-       if (n < 0 && errno != EAGAIN) {
-               lwsl_debug("%s: stdout read says %d\n", __func__, n);
-               return -1;
-       }
-       if (n > 0) {
-               m = lws_write(wsi, (unsigned char *)start, n, LWS_WRITE_HTTP);
-               //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;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_cgi_kill(struct lws *wsi)
-{
-       struct lws_cgi_args args;
-       int status, n;
-
-       lwsl_debug("%s: %p\n", __func__, wsi);
-
-       if (!wsi->cgi)
-               return 0;
-
-       if (wsi->cgi->pid > 0) {
-               n = waitpid(wsi->cgi->pid, &status, WNOHANG);
-               if (n > 0) {
-                       lwsl_debug("%s: PID %d reaped\n", __func__,
-                                   wsi->cgi->pid);
-                       goto handled;
-               }
-               /* kill the process group */
-               n = kill(-wsi->cgi->pid, SIGTERM);
-               lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", __func__,
-                               wsi->cgi->pid, n, errno);
-               if (n < 0) {
-                       /*
-                        * hum seen errno=3 when process is listed in ps,
-                        * it seems we don't always retain process grouping
-                        *
-                        * Direct these fallback attempt to the exact child
-                        */
-                       n = kill(wsi->cgi->pid, SIGTERM);
-                       if (n < 0) {
-                               n = kill(wsi->cgi->pid, SIGPIPE);
-                               if (n < 0) {
-                                       n = kill(wsi->cgi->pid, SIGKILL);
-                                       if (n < 0)
-                                               lwsl_err("%s: SIGKILL PID %d failed errno %d (maybe zombie)\n",
-                                                               __func__, wsi->cgi->pid, errno);
-                               }
-                       }
-               }
-               /* He could be unkillable because he's a zombie */
-               n = 1;
-               while (n > 0) {
-                       n = waitpid(-wsi->cgi->pid, &status, WNOHANG);
-                       if (n > 0)
-                               lwsl_debug("%s: reaped PID %d\n", __func__, n);
-                       if (n <= 0) {
-                               n = waitpid(wsi->cgi->pid, &status, WNOHANG);
-                               if (n > 0)
-                                       lwsl_debug("%s: reaped PID %d\n", __func__, n);
-                       }
-               }
-       }
-
-handled:
-       args.stdwsi = &wsi->cgi->stdwsi[0];
-
-       if (wsi->cgi->pid != -1 && user_callback_handle_rxflow(
-                       wsi->protocol->callback,
-                       wsi, LWS_CALLBACK_CGI_TERMINATED,
-                       wsi->user_space,
-                       (void *)&args, 0)) {
-               wsi->cgi->pid = -1;
-               if (!wsi->cgi->being_closed)
-                       lws_close_free_wsi(wsi, 0);
-       }
-
-       return 0;
-}
-
-LWS_EXTERN int
-lws_cgi_kill_terminated(struct lws_context_per_thread *pt)
-{
-       struct lws_cgi **pcgi, *cgi = NULL;
-       int status, n = 1;
-
-       while (n > 0) {
-               /* find finished guys but don't reap yet */
-               n = waitpid(-1, &status, WNOHANG);
-               if (n <= 0)
-                       continue;
-               lwsl_debug("%s: observed PID %d terminated\n", __func__, n);
-
-               pcgi = &pt->cgi_list;
-
-               /* check all the subprocesses on the cgi list */
-               while (*pcgi) {
-                       /* get the next one first as list may change */
-                       cgi = *pcgi;
-                       pcgi = &(*pcgi)->cgi_list;
-
-                       if (cgi->pid <= 0)
-                               continue;
-
-                       /* finish sending cached headers */
-                       if (cgi->headers_buf)
-                               continue;
-
-                       /* wait for stdout to be drained */
-                       if (cgi->content_length > cgi->content_length_seen)
-                               continue;
-
-                       if (cgi->content_length) {
-                               lwsl_debug("%s: wsi %p: expected content length seen: %lld\n",
-                                       __func__, cgi->wsi, (unsigned long long)cgi->content_length_seen);
-                       }
-
-                       /* reap it */
-                       waitpid(n, &status, WNOHANG);
-                       /*
-                        * he's already terminated so no need for kill()
-                        * but we should do the terminated cgi callback
-                        * and close him if he's not already closing
-                        */
-                       if (n == cgi->pid) {
-                               lwsl_debug("%s: found PID %d on cgi list\n",
-                                           __func__, n);
-
-                               if (!cgi->content_length && cgi->explicitly_chunked) {
-                                       /*
-                                        * well, if he sends chunked... give him 5s after the
-                                        * cgi terminated to send buffered
-                                        */
-                                       cgi->chunked_grace++;
-                                       continue;
-                               }
-
-                               /* defeat kill() */
-                               cgi->pid = 0;
-                               lws_cgi_kill(cgi->wsi);
-
-                               break;
-                       }
-                       cgi = NULL;
-               }
-               /* if not found on the cgi list, as he's one of ours, reap */
-               if (!cgi) {
-                       lwsl_debug("%s: reading PID %d although no cgi match\n",
-                                       __func__, n);
-                       waitpid(n, &status, WNOHANG);
-               }
-       }
-
-/* disable this to confirm timeout cgi cleanup flow */
-#if 1
-       pcgi = &pt->cgi_list;
-
-       /* check all the subprocesses on the cgi list */
-       while (*pcgi) {
-               /* get the next one first as list may change */
-               cgi = *pcgi;
-               pcgi = &(*pcgi)->cgi_list;
-
-               if (cgi->pid <= 0)
-                       continue;
-
-               /* we deferred killing him after reaping his PID */
-               if (cgi->chunked_grace) {
-                       cgi->chunked_grace++;
-                       if (cgi->chunked_grace < 5)
-                               continue;
-                       goto finish_him;
-               }
-
-               /* finish sending cached headers */
-               if (cgi->headers_buf)
-                       continue;
-
-               /* wait for stdout to be drained */
-               if (cgi->content_length > cgi->content_length_seen)
-                       continue;
-
-               if (cgi->content_length)
-                       lwsl_debug("%s: wsi %p: expected content length seen: %lld\n",
-                               __func__, cgi->wsi, (unsigned long long)cgi->content_length_seen);
-
-               /* reap it */
-               if (waitpid(cgi->pid, &status, WNOHANG) > 0) {
-
-                       if (!cgi->content_length) {
-                               /*
-                                * well, if he sends chunked... give him 5s after the
-                                * cgi terminated to send buffered
-                                */
-                               cgi->chunked_grace++;
-                               continue;
-                       }
-finish_him:
-                       lwsl_debug("%s: found PID %d on cgi list\n",
-                                   __func__, cgi->pid);
-                       /* defeat kill() */
-                       cgi->pid = 0;
-                       lws_cgi_kill(cgi->wsi);
-
-                       break;
-               }
-       }
-#endif
-
-       /* general anti zombie defence */
-//     n = waitpid(-1, &status, WNOHANG);
-       //if (n > 0)
-       //      lwsl_notice("%s: anti-zombie wait says %d\n", __func__, n);
-
-       return 0;
-}
-#endif
-
-#ifdef LWS_NO_EXTENSIONS
-LWS_EXTERN int
-lws_set_extension_option(struct lws *wsi, const char *ext_name,
-                        const char *opt_name, const char *opt_val)
-{
-       return -1;
-}
-#endif
-
-#ifdef LWS_WITH_ACCESS_LOG
-int
-lws_access_log(struct lws *wsi)
-{
-       char *p = wsi->access_log.user_agent, ass[512];
-       int l;
-
-       if (!wsi->access_log_pending)
-               return 0;
-
-       if (!wsi->access_log.header_log)
-               return 0;
-
-       if (!p)
-               p = "";
-
-       l = lws_snprintf(ass, sizeof(ass) - 1, "%s %d %lu %s\n",
-                    wsi->access_log.header_log,
-                    wsi->access_log.response, wsi->access_log.sent, p);
-
-       if (wsi->vhost->log_fd != (int)LWS_INVALID_FILE) {
-               if (write(wsi->vhost->log_fd, ass, l) != l)
-                       lwsl_err("Failed to write log\n");
-       } else
-               lwsl_err("%s", ass);
-
-       if (wsi->access_log.header_log) {
-               lws_free(wsi->access_log.header_log);
-               wsi->access_log.header_log = NULL;
-       }
-       if (wsi->access_log.user_agent) {
-               lws_free(wsi->access_log.user_agent);
-               wsi->access_log.user_agent = NULL;
-       }
-       wsi->access_log_pending = 0;
-
-       return 0;
-}
-#endif
-
-void
-lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs)
-{
-       const struct lws_vhost *vh = ctx->vhost_list;
-
-       while (vh) {
-
-               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->ws_upg += vh->conn_stats.ws_upg;
-               cs->http2_upg += vh->conn_stats.http2_upg;
-               cs->rejected += vh->conn_stats.rejected;
-
-               vh = vh->vhost_next;
-       }
-}
-
-#ifdef LWS_WITH_SERVER_STATUS
-
-LWS_EXTERN int
-lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len)
-{
-       static const char * const prots[] = {
-               "http://",
-               "https://",
-               "file://",
-               "cgi://",
-               ">http://",
-               ">https://",
-               "callback://"
-       };
-       char *orig = buf, *end = buf + len - 1, first = 1;
-       int n = 0;
-
-       if (len < 100)
-               return 0;
-
-       buf += lws_snprintf(buf, end - buf,
-                       "{\n \"name\":\"%s\",\n"
-                       " \"port\":\"%d\",\n"
-                       " \"use_ssl\":\"%d\",\n"
-                       " \"sts\":\"%d\",\n"
-                       " \"rx\":\"%llu\",\n"
-                       " \"tx\":\"%llu\",\n"
-                       " \"conn\":\"%lu\",\n"
-                       " \"trans\":\"%lu\",\n"
-                       " \"ws_upg\":\"%lu\",\n"
-                       " \"rejected\":\"%lu\",\n"
-                       " \"http2_upg\":\"%lu\""
-                       ,
-                       vh->name, vh->listen_port,
-#ifdef LWS_OPENSSL_SUPPORT
-                       vh->use_ssl,
-#else
-                       0,
-#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.ws_upg,
-                       vh->conn_stats.rejected,
-                       vh->conn_stats.http2_upg
-       );
-
-       if (vh->mount_list) {
-               const struct lws_http_mount *m = vh->mount_list;
-
-               buf += lws_snprintf(buf, end - buf, ",\n \"mounts\":[");
-               while (m) {
-                       if (!first)
-                               buf += lws_snprintf(buf, end - buf, ",");
-                       buf += lws_snprintf(buf, end - buf,
-                                       "\n  {\n   \"mountpoint\":\"%s\",\n"
-                                       "  \"origin\":\"%s%s\",\n"
-                                       "  \"cache_max_age\":\"%d\",\n"
-                                       "  \"cache_reuse\":\"%d\",\n"
-                                       "  \"cache_revalidate\":\"%d\",\n"
-                                       "  \"cache_intermediaries\":\"%d\"\n"
-                                       ,
-                                       m->mountpoint,
-                                       prots[m->origin_protocol],
-                                       m->origin,
-                                       m->cache_max_age,
-                                       m->cache_reusable,
-                                       m->cache_revalidate,
-                                       m->cache_intermediaries);
-                       if (m->def)
-                               buf += lws_snprintf(buf, end - buf,
-                                               ",\n  \"default\":\"%s\"",
-                                               m->def);
-                       buf += lws_snprintf(buf, end - buf, "\n  }");
-                       first = 0;
-                       m = m->mount_next;
-               }
-               buf += lws_snprintf(buf, end - buf, "\n ]");
-       }
-
-       if (vh->protocols) {
-               n = 0;
-               first = 1;
-
-               buf += lws_snprintf(buf, end - buf, ",\n \"ws-protocols\":[");
-               while (n < vh->count_protocols) {
-                       if (!first)
-                               buf += lws_snprintf(buf, end - buf, ",");
-                       buf += lws_snprintf(buf, end - buf,
-                                       "\n  {\n   \"%s\":{\n"
-                                       "    \"status\":\"ok\"\n   }\n  }"
-                                       ,
-                                       vh->protocols[n].name);
-                       first = 0;
-                       n++;
-               }
-               buf += lws_snprintf(buf, end - buf, "\n ]");
-       }
-
-       buf += lws_snprintf(buf, end - buf, "\n}");
-
-       return buf - orig;
-}
-
-
-LWS_EXTERN LWS_VISIBLE int
-lws_json_dump_context(const struct lws_context *context, char *buf, int len,
-               int hide_vhosts)
-{
-       char *orig = buf, *end = buf + len - 1, first = 1;
-       const struct lws_vhost *vh = context->vhost_list;
-       const struct lws_context_per_thread *pt;
-       time_t t = time(NULL);
-       int n, listening = 0, cgi_count = 0;
-       struct lws_conn_stats cs;
-       double d = 0;
-#ifdef LWS_WITH_CGI
-       struct lws_cgi * const *pcgi;
-#endif
-
-#ifdef LWS_USE_LIBUV
-       uv_uptime(&d);
-#endif
-
-       buf += lws_snprintf(buf, end - buf, "{ "
-                           "\"version\":\"%s\",\n"
-                           "\"uptime\":\"%ld\",\n",
-                           lws_get_library_version(),
-                           (long)d);
-
-#ifdef LWS_HAVE_GETLOADAVG
-       {
-               double d[3];
-               int m;
-
-               m = getloadavg(d, 3);
-               for (n = 0; n < m; n++) {
-                       buf += lws_snprintf(buf, end - buf,
-                               "\"l%d\":\"%.2f\",\n",
-                               n + 1, d[n]);
-               }
-       }
-#endif
-
-       buf += lws_snprintf(buf, end - buf, "\"contexts\":[\n");
-
-       buf += lws_snprintf(buf, end - buf, "{ "
-                               "\"context_uptime\":\"%ld\",\n"
-                               "\"cgi_spawned\":\"%d\",\n"
-                               "\"pt_fd_max\":\"%d\",\n"
-                               "\"ah_pool_max\":\"%d\",\n"
-                               "\"deprecated\":\"%d\",\n"
-                               "\"wsi_alive\":\"%d\",\n",
-                               (unsigned long)(t - context->time_up),
-                               context->count_cgi_spawned,
-                               context->fd_limit_per_thread,
-                               context->max_http_header_pool,
-                               context->deprecated,
-                               context->count_wsi_allocated);
-
-       buf += lws_snprintf(buf, end - buf, "\"pt\":[\n ");
-       for (n = 0; n < context->count_threads; n++) {
-               pt = &context->pt[n];
-               if (n)
-                       buf += lws_snprintf(buf, end - buf, ",");
-               buf += lws_snprintf(buf, end - buf,
-                               "\n  {\n"
-                               "    \"fds_count\":\"%d\",\n"
-                               "    \"ah_pool_inuse\":\"%d\",\n"
-                               "    \"ah_wait_list\":\"%d\"\n"
-                               "    }",
-                               pt->fds_count,
-                               pt->ah_count_in_use,
-                               pt->ah_wait_list_length);
-       }
-
-       buf += lws_snprintf(buf, end - buf, "]");
-
-       buf += lws_snprintf(buf, end - buf, ", \"vhosts\":[\n ");
-
-       first = 1;
-       vh = context->vhost_list;
-       listening = 0;
-       cs = context->conn_stats;
-       lws_sum_stats(context, &cs);
-       while (vh) {
-
-               if (!hide_vhosts) {
-                       if (!first)
-                               if(buf != end)
-                                       *buf++ = ',';
-                       buf += lws_json_dump_vhost(vh, buf, end - buf);
-                       first = 0;
-               }
-               if (vh->lserv_wsi)
-                       listening++;
-               vh = vh->vhost_next;
-       }
-
-       buf += lws_snprintf(buf, end - buf,
-                       "],\n\"listen_wsi\":\"%d\",\n"
-                       " \"rx\":\"%llu\",\n"
-                       " \"tx\":\"%llu\",\n"
-                       " \"conn\":\"%lu\",\n"
-                       " \"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);
-
-#ifdef LWS_WITH_CGI
-       for (n = 0; n < context->count_threads; n++) {
-               pt = &context->pt[n];
-               pcgi = &pt->cgi_list;
-
-               while (*pcgi) {
-                       pcgi = &(*pcgi)->cgi_list;
-
-                       cgi_count++;
-               }
-       }
-#endif
-       buf += lws_snprintf(buf, end - buf, ",\n \"cgi_alive\":\"%d\"\n ",
-                       cgi_count);
-
-       buf += lws_snprintf(buf, end - buf, "}");
-
-
-       buf += lws_snprintf(buf, end - buf, "]}\n ");
-
-       return buf - orig;
-}
-
-#endif
-
-#if defined(LWS_WITH_STATS)
-
-LWS_VISIBLE LWS_EXTERN uint64_t
-lws_stats_get(struct lws_context *context, int index)
-{
-       if (index >= LWSSTATS_SIZE)
-               return 0;
-
-       return context->lws_stats[index];
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_stats_log_dump(struct lws_context *context)
-{
-       struct lws_vhost *v = context->vhost_list;
-       int n;
-
-       if (!context->updated)
-               return;
-
-       context->updated = 0;
-
-       lwsl_notice("\n");
-       lwsl_notice("LWS internal statistics dump ----->\n");
-       lwsl_notice("LWSSTATS_C_CONNECTIONS:                     %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_CONNECTIONS));
-       lwsl_notice("LWSSTATS_C_API_CLOSE:                       %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_CLOSE));
-       lwsl_notice("LWSSTATS_C_API_READ:                        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_READ));
-       lwsl_notice("LWSSTATS_C_API_LWS_WRITE:                   %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_LWS_WRITE));
-       lwsl_notice("LWSSTATS_C_API_WRITE:                       %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_WRITE));
-       lwsl_notice("LWSSTATS_C_WRITE_PARTIALS:                  %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITE_PARTIALS));
-       lwsl_notice("LWSSTATS_C_WRITEABLE_CB_REQ:                %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_REQ));
-       lwsl_notice("LWSSTATS_C_WRITEABLE_CB_EFF_REQ:            %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_EFF_REQ));
-       lwsl_notice("LWSSTATS_C_WRITEABLE_CB:                    %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB));
-       lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_FAILED:          %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_FAILED));
-       lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED:        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED));
-       lwsl_notice("LWSSTATS_C_SSL_CONNS_HAD_RX:                %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX));
-
-       lwsl_notice("LWSSTATS_C_TIMEOUTS:                        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_TIMEOUTS));
-       lwsl_notice("LWSSTATS_C_SERVICE_ENTRY:                   %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SERVICE_ENTRY));
-       lwsl_notice("LWSSTATS_B_READ:                            %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_READ));
-       lwsl_notice("LWSSTATS_B_WRITE:                           %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_WRITE));
-       lwsl_notice("LWSSTATS_B_PARTIALS_ACCEPTED_PARTS:         %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS));
-       lwsl_notice("LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY: %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / 1000);
-       if (lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED))
-               lwsl_notice("  Avg accept delay:                         %8llums\n",
-                       (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) /
-                       lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) / 1000);
-       lwsl_notice("LWSSTATS_MS_SSL_RX_DELAY:                   %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) / 1000);
-       if (lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX))
-               lwsl_notice("  Avg accept-rx delay:                      %8llums\n",
-                       (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) /
-                       lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)) / 1000);
-
-       lwsl_notice("LWSSTATS_MS_WRITABLE_DELAY:                 %8lluus\n",
-                       (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY));
-       lwsl_notice("LWSSTATS_MS_WORST_WRITABLE_DELAY:           %8lluus\n",
-                               (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WORST_WRITABLE_DELAY));
-       if (lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB))
-               lwsl_notice("  Avg writable delay:                       %8lluus\n",
-                       (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY) /
-                       lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB)));
-       lwsl_notice("Simultaneous SSL restriction:               %8d/%d/%d\n", context->simultaneous_ssl,
-               context->simultaneous_ssl_restriction, context->ssl_gate_accepts);
-
-       lwsl_notice("Live wsi:                                   %8d\n", context->count_wsi_allocated);
-
-#if defined(LWS_WITH_STATS)
-       context->updated = 1;
-#endif
-
-       while (v) {
-               if (v->lserv_wsi) {
-
-                       struct lws_context_per_thread *pt = &context->pt[(int)v->lserv_wsi->tsi];
-                       struct lws_pollfd *pfd;
-
-                       pfd = &pt->fds[v->lserv_wsi->position_in_fds_table];
-
-                       lwsl_notice("  Listen port %d actual POLLIN:   %d\n",
-                                       v->listen_port, (int)pfd->events & LWS_POLLIN);
-               }
-
-               v = v->vhost_next;
-       }
-
-       for (n = 0; n < context->count_threads; n++) {
-               struct lws_context_per_thread *pt = &context->pt[n];
-               struct lws *wl;
-               int m = 0;
-
-               lwsl_notice("PT %d\n", n + 1);
-
-               lws_pt_lock(pt);
-
-               lwsl_notice("  AH in use / max:                  %d / %d\n",
-                               pt->ah_count_in_use,
-                               context->max_http_header_pool);
-
-               wl = pt->ah_wait_list;
-               while (wl) {
-                       m++;
-                       wl = wl->u.hdr.ah_wait_list;
-               }
-
-               lwsl_notice("  AH wait list count / actual:      %d / %d\n",
-                               pt->ah_wait_list_length, m);
-
-               lws_pt_unlock(pt);
-       }
-
-       lwsl_notice("\n");
-}
-
-void
-lws_stats_atomic_bump(struct lws_context * context,
-               struct lws_context_per_thread *pt, int index, uint64_t bump)
-{
-       lws_pt_lock(pt);
-       context->lws_stats[index] += bump;
-       if (index != LWSSTATS_C_SERVICE_ENTRY)
-               context->updated = 1;
-       lws_pt_unlock(pt);
-}
-
-void
-lws_stats_atomic_max(struct lws_context * context,
-               struct lws_context_per_thread *pt, int index, uint64_t val)
-{
-       lws_pt_lock(pt);
-       if (val > context->lws_stats[index]) {
-               context->lws_stats[index] = val;
-               context->updated = 1;
-       }
-       lws_pt_unlock(pt);
-}
-
-#endif
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
deleted file mode 100644 (file)
index 66ca787..0000000
+++ /dev/null
@@ -1,5184 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-/** @file */
-
-#ifndef LIBWEBSOCKET_H_3060898B846849FF9F88F5DB59B5950C
-#define LIBWEBSOCKET_H_3060898B846849FF9F88F5DB59B5950C
-
-#ifdef __cplusplus
-#include <cstddef>
-#include <cstdarg>
-#
-extern "C" {
-#else
-#include <stdarg.h>
-#endif
-
-#include "lws_config.h"
-
-/*
- * CARE: everything using cmake defines needs to be below here
- */
-
-#if defined(LWS_WITH_ESP8266)
-struct sockaddr_in;
-#define LWS_POSIX 0
-#else
-#define LWS_POSIX 1
-#endif
-
-#if defined(LWS_HAS_INTPTR_T)
-#include <stdint.h>
-#define lws_intptr_t intptr_t
-#else
-typedef unsigned long long lws_intptr_t;
-#endif
-
-#if defined(WIN32) || defined(_WIN32)
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#include <stddef.h>
-#include <basetsd.h>
-#ifndef _WIN32_WCE
-#include <fcntl.h>
-#else
-#define _O_RDONLY      0x0000
-#define O_RDONLY       _O_RDONLY
-#endif
-
-// Visual studio older than 2015 and WIN_CE has only _stricmp
-#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_WIN32_WCE)
-#define strcasecmp _stricmp
-#elif !defined(__MINGW32__)
-#define strcasecmp stricmp
-#endif
-#define getdtablesize() 30000
-
-#define LWS_INLINE __inline
-#define LWS_VISIBLE
-#define LWS_WARN_UNUSED_RESULT
-#define LWS_WARN_DEPRECATED
-#define LWS_FORMAT(string_index)
-
-#ifdef LWS_DLL
-#ifdef LWS_INTERNAL
-#define LWS_EXTERN extern __declspec(dllexport)
-#else
-#define LWS_EXTERN extern __declspec(dllimport)
-#endif
-#else
-#define LWS_EXTERN
-#endif
-
-#define LWS_INVALID_FILE INVALID_HANDLE_VALUE
-#define LWS_O_RDONLY _O_RDONLY
-#define LWS_O_WRONLY _O_WRONLY
-#define LWS_O_CREAT _O_CREAT
-#define LWS_O_TRUNC _O_TRUNC
-
-#if !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER < 1900) /* Visual Studio 2015 already defines this in <stdio.h> */
-#define lws_snprintf _snprintf
-#endif
-
-#ifndef __func__
-#define __func__ __FUNCTION__
-#endif
-
-#if !defined(__MINGW32__) &&(!defined(_MSC_VER) || _MSC_VER < 1900) && !defined(snprintf)
-#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
-#endif
-
-#else /* NOT WIN32 */
-#include <unistd.h>
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-#include <sys/capability.h>
-#endif
-
-#if defined(__NetBSD__) || defined(__FreeBSD__)
-#include <netinet/in.h>
-#endif
-
-#define LWS_INLINE inline
-#define LWS_O_RDONLY O_RDONLY
-#define LWS_O_WRONLY O_WRONLY
-#define LWS_O_CREAT O_CREAT
-#define LWS_O_TRUNC O_TRUNC
-
-#if !defined(LWS_WITH_ESP8266) && !defined(OPTEE_TA) && !defined(LWS_WITH_ESP32)
-#include <poll.h>
-#include <netdb.h>
-#define LWS_INVALID_FILE -1
-#else
-#define getdtablesize() (30)
-#if defined(LWS_WITH_ESP32)
-#define LWS_INVALID_FILE NULL
-#else
-#define LWS_INVALID_FILE NULL
-#endif
-#endif
-
-#if defined(__GNUC__)
-
-/* warn_unused_result attribute only supported by GCC 3.4 or later */
-#if __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
-#define LWS_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
-#else
-#define LWS_WARN_UNUSED_RESULT
-#endif
-
-#define LWS_VISIBLE __attribute__((visibility("default")))
-#define LWS_WARN_DEPRECATED __attribute__ ((deprecated))
-#define LWS_FORMAT(string_index) __attribute__ ((format(printf, string_index, string_index+1)))
-#else
-#define LWS_VISIBLE
-#define LWS_WARN_UNUSED_RESULT
-#define LWS_WARN_DEPRECATED
-#define LWS_FORMAT(string_index)
-#endif
-
-#if defined(__ANDROID__)
-#include <unistd.h>
-#define getdtablesize() sysconf(_SC_OPEN_MAX)
-#endif
-
-#endif
-
-#ifdef LWS_USE_LIBEV
-#include <ev.h>
-#endif /* LWS_USE_LIBEV */
-#ifdef LWS_USE_LIBUV
-#include <uv.h>
-#ifdef LWS_HAVE_UV_VERSION_H
-#include <uv-version.h>
-#endif
-#endif /* LWS_USE_LIBUV */
-#ifdef LWS_USE_LIBEVENT
-#include <event2/event.h>
-#endif /* LWS_USE_LIBEVENT */
-
-#ifndef LWS_EXTERN
-#define LWS_EXTERN extern
-#endif
-
-#ifdef _WIN32
-#define random rand
-#else
-#if !defined(OPTEE_TA)
-#include <sys/time.h>
-#include <unistd.h>
-#endif
-#endif
-
-#ifdef LWS_OPENSSL_SUPPORT
-
-#ifdef USE_WOLFSSL
-#ifdef USE_OLD_CYASSL
-#include <cyassl/openssl/ssl.h>
-#include <cyassl/error-ssl.h>
-#else
-#include <wolfssl/openssl/ssl.h>
-#include <wolfssl/error-ssl.h>
-#endif /* not USE_OLD_CYASSL */
-#else
-#include <openssl/ssl.h>
-#if !defined(LWS_WITH_ESP32)
-#include <openssl/err.h>
-#endif
-#endif /* not USE_WOLFSSL */
-#endif
-
-
-#define CONTEXT_PORT_NO_LISTEN -1
-#define CONTEXT_PORT_NO_LISTEN_SERVER -2
-
-/** \defgroup log Logging
- *
- * ##Logging
- *
- * Lws provides flexible and filterable logging facilities, which can be
- * used inside lws and in user code.
- *
- * Log categories may be individually filtered bitwise, and directed to built-in
- * sinks for syslog-compatible logging, or a user-defined function.
- */
-///@{
-
-enum lws_log_levels {
-       LLL_ERR = 1 << 0,
-       LLL_WARN = 1 << 1,
-       LLL_NOTICE = 1 << 2,
-       LLL_INFO = 1 << 3,
-       LLL_DEBUG = 1 << 4,
-       LLL_PARSER = 1 << 5,
-       LLL_HEADER = 1 << 6,
-       LLL_EXT = 1 << 7,
-       LLL_CLIENT = 1 << 8,
-       LLL_LATENCY = 1 << 9,
-       LLL_USER = 1 << 10,
-
-       LLL_COUNT = 11 /* set to count of valid flags */
-};
-
-LWS_VISIBLE LWS_EXTERN void _lws_log(int filter, const char *format, ...) LWS_FORMAT(2);
-LWS_VISIBLE LWS_EXTERN void _lws_logv(int filter, const char *format, va_list vl);
-/**
- * lwsl_timestamp: generate logging timestamp string
- *
- * \param level:       logging level
- * \param p:           char * buffer to take timestamp
- * \param len: length of p
- *
- * returns length written in p
- */
-LWS_VISIBLE LWS_EXTERN int
-lwsl_timestamp(int level, char *p, int len);
-
-/* these guys are unconditionally included */
-
-#define lwsl_err(...) _lws_log(LLL_ERR, __VA_ARGS__)
-#define lwsl_user(...) _lws_log(LLL_USER, __VA_ARGS__)
-
-#if !defined(LWS_WITH_NO_LOGS)
-/* notice and warn are usually included by being compiled in */
-#define lwsl_warn(...) _lws_log(LLL_WARN, __VA_ARGS__)
-#define lwsl_notice(...) _lws_log(LLL_NOTICE, __VA_ARGS__)
-#endif
-/*
- *  weaker logging can be deselected by telling CMake to build in RELEASE mode
- *  that gets rid of the overhead of checking while keeping _warn and _err
- *  active
- */
-
-#if defined(LWS_WITH_ESP8266)
-#undef _DEBUG
-#endif
-
-#ifdef _DEBUG
-#if defined(LWS_WITH_NO_LOGS)
-/* notice, warn and log are always compiled in */
-#define lwsl_warn(...) _lws_log(LLL_WARN, __VA_ARGS__)
-#define lwsl_notice(...) _lws_log(LLL_NOTICE, __VA_ARGS__)
-#endif
-#define lwsl_info(...) _lws_log(LLL_INFO, __VA_ARGS__)
-#define lwsl_debug(...) _lws_log(LLL_DEBUG, __VA_ARGS__)
-#define lwsl_parser(...) _lws_log(LLL_PARSER, __VA_ARGS__)
-#define lwsl_header(...)  _lws_log(LLL_HEADER, __VA_ARGS__)
-#define lwsl_ext(...)  _lws_log(LLL_EXT, __VA_ARGS__)
-#define lwsl_client(...) _lws_log(LLL_CLIENT, __VA_ARGS__)
-#define lwsl_latency(...) _lws_log(LLL_LATENCY, __VA_ARGS__)
-/**
- * lwsl_hexdump() - helper to hexdump a buffer (DEBUG builds only)
- *
- * \param buf: buffer start to dump
- * \param len: length of buffer to dump
- */
-LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len);
-
-#else /* no debug */
-#if defined(LWS_WITH_NO_LOGS)
-#define lwsl_warn(...) do {} while(0)
-#define lwsl_notice(...) do {} while(0)
-#endif
-#define lwsl_info(...) do {} while(0)
-#define lwsl_debug(...) do {} while(0)
-#define lwsl_parser(...) do {} while(0)
-#define lwsl_header(...) do {} while(0)
-#define lwsl_ext(...) do {} while(0)
-#define lwsl_client(...) do {} while(0)
-#define lwsl_latency(...) do {} while(0)
-#define lwsl_hexdump(a, b)
-
-#endif
-
-static LWS_INLINE int lws_is_be(void) {
-       const int probe = ~0xff;
-
-       return *(const char *)&probe;
-}
-
-/**
- * lws_set_log_level() - Set the logging bitfield
- * \param level:       OR together the LLL_ debug contexts you want output from
- * \param log_emit_function:   NULL to leave it as it is, or a user-supplied
- *                     function to perform log string emission instead of
- *                     the default stderr one.
- *
- *     log level defaults to "err", "warn" and "notice" contexts enabled and
- *     emission on stderr.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_set_log_level(int level,
-                 void (*log_emit_function)(int level, const char *line));
-
-/**
- * lwsl_emit_syslog() - helper log emit function writes to system log
- *
- * \param level: one of LLL_ log level indexes
- * \param line: log string
- *
- * You use this by passing the function pointer to lws_set_log_level(), to set
- * it as the log emit function, it is not called directly.
- */
-LWS_VISIBLE LWS_EXTERN void
-lwsl_emit_syslog(int level, const char *line);
-
-/**
- * lwsl_visible() - returns true if the log level should be printed
- *
- * \param level: one of LLL_ log level indexes
- *
- * This is useful if you have to do work to generate the log content, you
- * can skip the work if the log level used to print it is not actually
- * enabled at runtime.
- */
-LWS_VISIBLE LWS_EXTERN int
-lwsl_visible(int level);
-
-///@}
-
-
-#include <stddef.h>
-
-#ifndef lws_container_of
-#define lws_container_of(P,T,M)        ((T *)((char *)(P) - offsetof(T, M)))
-#endif
-
-
-struct lws;
-#ifndef ARRAY_SIZE
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
-#endif
-
-/* api change list for user code to test against */
-
-#define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_ARG
-
-/* the struct lws_protocols has the id field present */
-#define LWS_FEATURE_PROTOCOLS_HAS_ID_FIELD
-
-/* you can call lws_get_peer_write_allowance */
-#define LWS_FEATURE_PROTOCOLS_HAS_PEER_WRITE_ALLOWANCE
-
-/* extra parameter introduced in 917f43ab821 */
-#define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_LEN
-
-/* File operations stuff exists */
-#define LWS_FEATURE_FOPS
-
-
-#if defined(_WIN32)
-typedef SOCKET lws_sockfd_type;
-typedef HANDLE lws_filefd_type;
-#define lws_sockfd_valid(sfd) (!!sfd)
-struct lws_pollfd {
-       lws_sockfd_type fd; /**< file descriptor */
-       SHORT events; /**< which events to respond to */
-       SHORT revents; /**< which events happened */
-};
-#define LWS_POLLHUP (FD_CLOSE)
-#define LWS_POLLIN (FD_READ | FD_ACCEPT)
-#define LWS_POLLOUT (FD_WRITE)
-#else
-
-
-#if defined(LWS_WITH_ESP8266)
-
-#include <user_interface.h>
-#include <espconn.h>
-
-typedef struct espconn * lws_sockfd_type;
-typedef void * lws_filefd_type;
-#define lws_sockfd_valid(sfd) (!!sfd)
-struct pollfd {
-       lws_sockfd_type fd; /**< fd related to */
-       short events; /**< which POLL... events to respond to */
-       short revents; /**< which POLL... events occurred */
-};
-#define POLLIN         0x0001
-#define POLLPRI                0x0002
-#define POLLOUT                0x0004
-#define POLLERR                0x0008
-#define POLLHUP                0x0010
-#define POLLNVAL       0x0020
-
-struct lws_vhost;
-
-lws_sockfd_type esp8266_create_tcp_listen_socket(struct lws_vhost *vh);
-void esp8266_tcp_stream_accept(lws_sockfd_type fd, struct lws *wsi);
-
-#include <os_type.h>
-#include <osapi.h>
-#include "ets_sys.h"
-
-int ets_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3);
-#define snprintf  ets_snprintf
-
-typedef os_timer_t uv_timer_t;
-typedef void uv_cb_t(uv_timer_t *);
-
-void os_timer_disarm(void *);
-void os_timer_setfn(os_timer_t *, os_timer_func_t *, void *);
-
-void ets_timer_arm_new(os_timer_t *, int, int, int);
-
-//void os_timer_arm(os_timer_t *, int, int);
-
-#define UV_VERSION_MAJOR 1
-
-#define lws_uv_getloop(a, b) (NULL)
-
-static inline void uv_timer_init(void *l, uv_timer_t *t)
-{
-       (void)l;
-       memset(t, 0, sizeof(*t));
-       os_timer_disarm(t);
-}
-
-static inline void uv_timer_start(uv_timer_t *t, uv_cb_t *cb, int first, int rep)
-{
-       os_timer_setfn(t, (os_timer_func_t *)cb, t);
-       /* ms, repeat */
-       os_timer_arm(t, first, !!rep);
-}
-
-static inline void uv_timer_stop(uv_timer_t *t)
-{
-       os_timer_disarm(t);
-}
-
-#else
-#if defined(LWS_WITH_ESP32)
-
-typedef int lws_sockfd_type;
-typedef int lws_filefd_type;
-#define lws_sockfd_valid(sfd) (sfd >= 0)
-struct pollfd {
-       lws_sockfd_type fd; /**< fd related to */
-       short events; /**< which POLL... events to respond to */
-       short revents; /**< which POLL... events occurred */
-};
-#define POLLIN         0x0001
-#define POLLPRI                0x0002
-#define POLLOUT                0x0004
-#define POLLERR                0x0008
-#define POLLHUP                0x0010
-#define POLLNVAL       0x0020
-
-#include <freertos/FreeRTOS.h>
-#include <freertos/event_groups.h>
-#include <string.h>
-#include "esp_wifi.h"
-#include "esp_system.h"
-#include "esp_event.h"
-#include "esp_event_loop.h"
-#include "nvs.h"
-#include "driver/gpio.h"
-#include "esp_spi_flash.h"
-#include "freertos/timers.h"
-
-#if !defined(CONFIG_FREERTOS_HZ)
-#define CONFIG_FREERTOS_HZ 100
-#endif
-
-typedef TimerHandle_t uv_timer_t;
-typedef void uv_cb_t(uv_timer_t *);
-typedef void * uv_handle_t;
-
-struct timer_mapping {
-       uv_cb_t *cb;
-       uv_timer_t *t;
-};
-
-#define UV_VERSION_MAJOR 1
-
-#define lws_uv_getloop(a, b) (NULL)
-
-static inline void uv_timer_init(void *l, uv_timer_t *t)
-{
-       (void)l;
-       *t = NULL;
-}
-
-extern void esp32_uvtimer_cb(TimerHandle_t t);
-
-static inline void uv_timer_start(uv_timer_t *t, uv_cb_t *cb, int first, int rep)
-{
-       struct timer_mapping *tm = (struct timer_mapping *)malloc(sizeof(*tm));
-
-       if (!tm)
-               return;
-
-       tm->t = t;
-       tm->cb = cb;
-
-       *t = xTimerCreate("x", pdMS_TO_TICKS(first), !!rep, tm,
-                         (TimerCallbackFunction_t)esp32_uvtimer_cb);
-       xTimerStart(*t, 0);
-}
-
-static inline void uv_timer_stop(uv_timer_t *t)
-{
-       xTimerStop(*t, 0);
-}
-
-static inline void uv_close(uv_handle_t *h, void *v)
-{
-       free(pvTimerGetTimerID((uv_timer_t)h));
-       xTimerDelete(*(uv_timer_t *)h, 0);
-}
-
-/* ESP32 helper declarations */
-
-#include <mdns.h>
-#include <esp_partition.h>
-
-#define LWS_PLUGIN_STATIC
-#define LWS_MAGIC_REBOOT_TYPE_ADS 0x50001ffc
-#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY 0xb00bcafe
-#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY 0xfaceb00b
-#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON 0xf0cedfac
-
-
-/* user code provides these */
-
-extern void
-lws_esp32_identify_physical_device(void);
-
-/* lws-plat-esp32 provides these */
-
-typedef void (*lws_cb_scan_done)(uint16_t count, wifi_ap_record_t *recs, void *arg);
-
-enum genled_state {
-       LWSESP32_GENLED__INIT,
-       LWSESP32_GENLED__LOST_NETWORK,
-       LWSESP32_GENLED__NO_NETWORK,
-       LWSESP32_GENLED__CONN_AP,
-       LWSESP32_GENLED__GOT_IP,
-       LWSESP32_GENLED__OK,
-};
-
-struct lws_group_member {
-       struct lws_group_member *next;
-       uint64_t last_seen;
-       char model[16];
-       char role[16];
-       char host[32];
-       char mac[20];
-       int width, height;
-       struct ip4_addr addr;
-       struct ip6_addr addrv6;
-       uint8_t flags;
-};
-
-#define LWS_SYSTEM_GROUP_MEMBER_ADD            1
-#define LWS_SYSTEM_GROUP_MEMBER_CHANGE         2
-#define LWS_SYSTEM_GROUP_MEMBER_REMOVE         3
-
-#define LWS_GROUP_FLAG_SELF 1
-
-struct lws_esp32 {
-       char sta_ip[16];
-       char sta_mask[16];
-       char sta_gw[16];
-       char serial[16];
-       char opts[16];
-       char model[16];
-       char group[16];
-       char role[16];
-       char ssid[4][16];
-       char password[4][32];
-       char active_ssid[32];
-       char access_pw[16];
-       char hostname[32];
-       char mac[20];
-       mdns_server_t *mdns;
-               char region;
-               char inet;
-       char conn_ap;
-
-       enum genled_state genled;
-       uint64_t genled_t;
-
-       lws_cb_scan_done scan_consumer;
-       void *scan_consumer_arg;
-       struct lws_group_member *first;
-       int extant_group_members;
-};
-
-struct lws_esp32_image {
-       uint32_t romfs;
-       uint32_t romfs_len;
-       uint32_t json;
-       uint32_t json_len;
-};
-
-extern struct lws_esp32 lws_esp32;
-
-extern esp_err_t
-lws_esp32_event_passthru(void *ctx, system_event_t *event);
-extern void
-lws_esp32_wlan_config(void);
-extern void
-lws_esp32_wlan_start_ap(void);
-extern void
-lws_esp32_wlan_start_station(void);
-struct lws_context_creation_info;
-extern void
-lws_esp32_set_creation_defaults(struct lws_context_creation_info *info);
-extern struct lws_context *
-lws_esp32_init(struct lws_context_creation_info *);
-extern int
-lws_esp32_wlan_nvs_get(int retry);
-extern esp_err_t
-lws_nvs_set_str(nvs_handle handle, const char* key, const char* value);
-extern void
-lws_esp32_restart_guided(uint32_t type);
-extern const esp_partition_t *
-lws_esp_ota_get_boot_partition(void);
-extern int
-lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i, char *json, int json_len);
-extern int
-lws_esp32_leds_network_indication(void);
-
-extern uint32_t lws_esp32_get_reboot_type(void);
-extern uint16_t lws_esp32_sine_interp(int n);
-
-/* required in external code by esp32 plat (may just return if no leds) */
-extern void lws_esp32_leds_timer_cb(TimerHandle_t th);
-#else
-typedef int lws_sockfd_type;
-typedef int lws_filefd_type;
-#define lws_sockfd_valid(sfd) (sfd >= 0)
-#endif
-#endif
-
-#define lws_pollfd pollfd
-#define LWS_POLLHUP (POLLHUP|POLLERR)
-#define LWS_POLLIN (POLLIN)
-#define LWS_POLLOUT (POLLOUT)
-#endif
-
-
-#if (defined(WIN32) || defined(_WIN32)) && !defined(__MINGW32__)
-/* ... */
-#define ssize_t SSIZE_T
-#endif
-
-#if defined(WIN32) && defined(LWS_HAVE__STAT32I64)
-#include <sys/types.h>
-#include <sys/stat.h>
-#endif
-
-#if defined(LWS_HAVE_STDINT_H)
-#include <stdint.h>
-#else
-#if defined(WIN32) || defined(_WIN32)
-/* !!! >:-[  */
-typedef unsigned __int32 uint32_t;
-typedef unsigned __int16 uint16_t;
-typedef unsigned __int8 uint8_t;
-#else
-typedef unsigned int uint32_t;
-typedef unsigned short uint16_t;
-typedef unsigned char uint8_t;
-#endif
-#endif
-
-typedef unsigned long long lws_filepos_t;
-typedef long long lws_fileofs_t;
-typedef uint32_t lws_fop_flags_t;
-
-/** struct lws_pollargs - argument structure for all external poll related calls
- * passed in via 'in' */
-struct lws_pollargs {
-       lws_sockfd_type fd;     /**< applicable socket descriptor */
-       int events;             /**< the new event mask */
-       int prev_events;        /**< the previous event mask */
-};
-
-struct lws_tokens;
-struct lws_token_limits;
-
-/*! \defgroup wsclose Websocket Close
- *
- * ##Websocket close frame control
- *
- * When we close a ws connection, we can send a reason code and a short
- * UTF-8 description back with the close packet.
- */
-///@{
-
-/*
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-/** enum lws_close_status - RFC6455 close status codes */
-enum lws_close_status {
-       LWS_CLOSE_STATUS_NOSTATUS                               =    0,
-       LWS_CLOSE_STATUS_NORMAL                                 = 1000,
-       /**< 1000 indicates a normal closure, meaning that the purpose for
-      which the connection was established has been fulfilled. */
-       LWS_CLOSE_STATUS_GOINGAWAY                              = 1001,
-       /**< 1001 indicates that an endpoint is "going away", such as a server
-      going down or a browser having navigated away from a page. */
-       LWS_CLOSE_STATUS_PROTOCOL_ERR                           = 1002,
-       /**< 1002 indicates that an endpoint is terminating the connection due
-      to a protocol error. */
-       LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE                    = 1003,
-       /**< 1003 indicates that an endpoint is terminating the connection
-      because it has received a type of data it cannot accept (e.g., an
-      endpoint that understands only text data MAY send this if it
-      receives a binary message). */
-       LWS_CLOSE_STATUS_RESERVED                               = 1004,
-       /**< Reserved.  The specific meaning might be defined in the future. */
-       LWS_CLOSE_STATUS_NO_STATUS                              = 1005,
-       /**< 1005 is a reserved value and MUST NOT be set as a status code in a
-      Close control frame by an endpoint.  It is designated for use in
-      applications expecting a status code to indicate that no status
-      code was actually present. */
-       LWS_CLOSE_STATUS_ABNORMAL_CLOSE                         = 1006,
-       /**< 1006 is a reserved value and MUST NOT be set as a status code in a
-      Close control frame by an endpoint.  It is designated for use in
-      applications expecting a status code to indicate that the
-      connection was closed abnormally, e.g., without sending or
-      receiving a Close control frame. */
-       LWS_CLOSE_STATUS_INVALID_PAYLOAD                        = 1007,
-       /**< 1007 indicates that an endpoint is terminating the connection
-      because it has received data within a message that was not
-      consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
-      data within a text message). */
-       LWS_CLOSE_STATUS_POLICY_VIOLATION                       = 1008,
-       /**< 1008 indicates that an endpoint is terminating the connection
-      because it has received a message that violates its policy.  This
-      is a generic status code that can be returned when there is no
-      other more suitable status code (e.g., 1003 or 1009) or if there
-      is a need to hide specific details about the policy. */
-       LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE                      = 1009,
-       /**< 1009 indicates that an endpoint is terminating the connection
-      because it has received a message that is too big for it to
-      process. */
-       LWS_CLOSE_STATUS_EXTENSION_REQUIRED                     = 1010,
-       /**< 1010 indicates that an endpoint (client) is terminating the
-      connection because it has expected the server to negotiate one or
-      more extension, but the server didn't return them in the response
-      message of the WebSocket handshake.  The list of extensions that
-      are needed SHOULD appear in the /reason/ part of the Close frame.
-      Note that this status code is not used by the server, because it
-      can fail the WebSocket handshake instead */
-       LWS_CLOSE_STATUS_UNEXPECTED_CONDITION                   = 1011,
-       /**< 1011 indicates that a server is terminating the connection because
-      it encountered an unexpected condition that prevented it from
-      fulfilling the request. */
-       LWS_CLOSE_STATUS_TLS_FAILURE                            = 1015,
-       /**< 1015 is a reserved value and MUST NOT be set as a status code in a
-      Close control frame by an endpoint.  It is designated for use in
-      applications expecting a status code to indicate that the
-      connection was closed due to a failure to perform a TLS handshake
-      (e.g., the server certificate can't be verified). */
-
-       /****** add new things just above ---^ ******/
-
-       LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY               = 9999,
-};
-
-/**
- * lws_close_reason - Set reason and aux data to send with Close packet
- *             If you are going to return nonzero from the callback
- *             requesting the connection to close, you can optionally
- *             call this to set the reason the peer will be told if
- *             possible.
- *
- * \param wsi: The websocket connection to set the close reason on
- * \param status:      A valid close status from websocket standard
- * \param buf: NULL or buffer containing up to 124 bytes of auxiliary data
- * \param len: Length of data in \param buf to send
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_close_reason(struct lws *wsi, enum lws_close_status status,
-                unsigned char *buf, size_t len);
-
-///@}
-
-struct lws;
-struct lws_context;
-/* needed even with extensions disabled for create context */
-struct lws_extension;
-
-/*! \defgroup lwsmeta lws-meta
- *
- * ##lws-meta protocol
- *
- * The protocol wraps other muxed connections inside one tcp connection.
- *
- * Commands are assigned from 0x41 up (so they are valid unicode)
- */
-///@{
-
-enum lws_meta_commands {
-       LWS_META_CMD_OPEN_SUBCHANNEL = 'A',
-       /**< Client requests to open new subchannel
-        */
-       LWS_META_CMD_OPEN_RESULT,
-       /**< Result of client request to open new subchannel */
-       LWS_META_CMD_CLOSE_NOTIFY,
-       /**< Notification of subchannel closure */
-       LWS_META_CMD_CLOSE_RQ,
-       /**< client requests to close a subchannel */
-       LWS_META_CMD_WRITE,
-       /**< connection writes something to specific channel index */
-
-       /****** add new things just above ---^ ******/
-};
-
-/* channel numbers are transported offset by 0x20 so they are valid unicode */
-
-#define LWS_META_TRANSPORT_OFFSET 0x20
-
-///@}
-
-/*! \defgroup usercb User Callback
- *
- * ##User protocol callback
- *
- * The protocol callback is the primary way lws interacts with
- * user code.  For one of a list of a few dozen reasons the callback gets
- * called at some event to be handled.
- *
- * All of the events can be ignored, returning 0 is taken as "OK" and returning
- * nonzero in most cases indicates that the connection should be closed.
- */
-///@{
-
-struct lws_ssl_info {
-       int where;
-       int ret;
-};
-
-/*
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-/** enum lws_callback_reasons - reason you're getting a protocol callback */
-enum lws_callback_reasons {
-       LWS_CALLBACK_ESTABLISHED                                =  0,
-       /**< (VH) after the server completes a handshake with an incoming
-        * client.  If you built the library with ssl support, in is a
-        * pointer to the ssl struct associated with the connection or NULL.*/
-       LWS_CALLBACK_CLIENT_CONNECTION_ERROR                    =  1,
-       /**< the request client connection has been unable to complete a
-        * handshake with the remote server.  If in is non-NULL, you can
-        * find an error string of length len where it points to
-        *
-        * Diagnostic strings that may be returned include
-        *
-        *      "getaddrinfo (ipv6) failed"
-        *      "unknown address family"
-        *      "getaddrinfo (ipv4) failed"
-        *      "set socket opts failed"
-        *      "insert wsi failed"
-        *      "lws_ssl_client_connect1 failed"
-        *      "lws_ssl_client_connect2 failed"
-        *      "Peer hung up"
-        *      "read failed"
-        *      "HS: URI missing"
-        *      "HS: Redirect code but no Location"
-        *      "HS: URI did not parse"
-        *      "HS: Redirect failed"
-        *      "HS: Server did not return 200"
-        *      "HS: OOM"
-        *      "HS: disallowed by client filter"
-        *      "HS: disallowed at ESTABLISHED"
-        *      "HS: ACCEPT missing"
-        *      "HS: ws upgrade response not 101"
-        *      "HS: UPGRADE missing"
-        *      "HS: Upgrade to something other than websocket"
-        *      "HS: CONNECTION missing"
-        *      "HS: UPGRADE malformed"
-        *      "HS: PROTOCOL malformed"
-        *      "HS: Cannot match protocol"
-        *      "HS: EXT: list too big"
-        *      "HS: EXT: failed setting defaults"
-        *      "HS: EXT: failed parsing defaults"
-        *      "HS: EXT: failed parsing options"
-        *      "HS: EXT: Rejects server options"
-        *      "HS: EXT: unknown ext"
-        *      "HS: Accept hash wrong"
-        *      "HS: Rejected by filter cb"
-        *      "HS: OOM"
-        *      "HS: SO_SNDBUF failed"
-        *      "HS: Rejected at CLIENT_ESTABLISHED"
-        */
-       LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH                =  2,
-       /**< this is the last chance for the client user code to examine the
-        * http headers and decide to reject the connection.  If the
-        * content in the headers is interesting to the
-        * client (url, etc) it needs to copy it out at
-        * this point since it will be destroyed before
-        * the CLIENT_ESTABLISHED call */
-       LWS_CALLBACK_CLIENT_ESTABLISHED                         =  3,
-       /**< after your client connection completed
-        * a handshake with the remote server */
-       LWS_CALLBACK_CLOSED                                     =  4,
-       /**< when the websocket session ends */
-       LWS_CALLBACK_CLOSED_HTTP                                =  5,
-       /**< when a HTTP (non-websocket) session ends */
-       LWS_CALLBACK_RECEIVE                                    =  6,
-       /**< data has appeared for this server endpoint from a
-        * remote client, it can be found at *in and is
-        * len bytes long */
-       LWS_CALLBACK_RECEIVE_PONG                               =  7,
-       /**< servers receive PONG packets with this callback reason */
-       LWS_CALLBACK_CLIENT_RECEIVE                             =  8,
-       /**< data has appeared from the server for the client connection, it
-        * can be found at *in and is len bytes long */
-       LWS_CALLBACK_CLIENT_RECEIVE_PONG                        =  9,
-       /**< clients receive PONG packets with this callback reason */
-       LWS_CALLBACK_CLIENT_WRITEABLE                           = 10,
-       /**<  If you call lws_callback_on_writable() on a connection, you will
-        * get one of these callbacks coming when the connection socket
-        * is able to accept another write packet without blocking.
-        * If it already was able to take another packet without blocking,
-        * you'll get this callback at the next call to the service loop
-        * function.  Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE
-        * and servers get LWS_CALLBACK_SERVER_WRITEABLE. */
-       LWS_CALLBACK_SERVER_WRITEABLE                           = 11,
-       /**< See LWS_CALLBACK_CLIENT_WRITEABLE */
-       LWS_CALLBACK_HTTP                                       = 12,
-       /**< an http request has come from a client that is not
-        * asking to upgrade the connection to a websocket
-        * one.  This is a chance to serve http content,
-        * for example, to send a script to the client
-        * which will then open the websockets connection.
-        * in points to the URI path requested and
-        * lws_serve_http_file() makes it very
-        * simple to send back a file to the client.
-        * Normally after sending the file you are done
-        * with the http connection, since the rest of the
-        * activity will come by websockets from the script
-        * that was delivered by http, so you will want to
-        * return 1; to close and free up the connection. */
-       LWS_CALLBACK_HTTP_BODY                                  = 13,
-       /**< the next len bytes data from the http
-        * request body HTTP connection is now available in in. */
-       LWS_CALLBACK_HTTP_BODY_COMPLETION                       = 14,
-       /**< the expected amount of http request body has been delivered */
-       LWS_CALLBACK_HTTP_FILE_COMPLETION                       = 15,
-       /**< a file requested to be sent down http link has completed. */
-       LWS_CALLBACK_HTTP_WRITEABLE                             = 16,
-       /**< you can write more down the http protocol link now. */
-       LWS_CALLBACK_FILTER_NETWORK_CONNECTION                  = 17,
-       /**< called when a client connects to
-        * the server at network level; the connection is accepted but then
-        * passed to this callback to decide whether to hang up immediately
-        * or not, based on the client IP.  in contains the connection
-        * socket's descriptor. Since the client connection information is
-        * not available yet, wsi still pointing to the main server socket.
-        * Return non-zero to terminate the connection before sending or
-        * receiving anything. Because this happens immediately after the
-        * network connection from the client, there's no websocket protocol
-        * selected yet so this callback is issued only to protocol 0. */
-       LWS_CALLBACK_FILTER_HTTP_CONNECTION                     = 18,
-       /**< called when the request has
-        * been received and parsed from the client, but the response is
-        * not sent yet.  Return non-zero to disallow the connection.
-        * user is a pointer to the connection user space allocation,
-        * in is the URI, eg, "/"
-        * In your handler you can use the public APIs
-        * lws_hdr_total_length() / lws_hdr_copy() to access all of the
-        * headers using the header enums lws_token_indexes from
-        * libwebsockets.h to check for and read the supported header
-        * presence and content before deciding to allow the http
-        * connection to proceed or to kill the connection. */
-       LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED             = 19,
-       /**< A new client just had
-        * been connected, accepted, and instantiated into the pool. This
-        * callback allows setting any relevant property to it. Because this
-        * happens immediately after the instantiation of a new client,
-        * there's no websocket protocol selected yet so this callback is
-        * issued only to protocol 0. Only wsi is defined, pointing to the
-        * new client, and the return value is ignored. */
-       LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION                 = 20,
-       /**< called when the handshake has
-        * been received and parsed from the client, but the response is
-        * not sent yet.  Return non-zero to disallow the connection.
-        * user is a pointer to the connection user space allocation,
-        * in is the requested protocol name
-        * In your handler you can use the public APIs
-        * lws_hdr_total_length() / lws_hdr_copy() to access all of the
-        * headers using the header enums lws_token_indexes from
-        * libwebsockets.h to check for and read the supported header
-        * presence and content before deciding to allow the handshake
-        * to proceed or to kill the connection. */
-       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS     = 21,
-       /**< if configured for
-        * including OpenSSL support, this callback allows your user code
-        * to perform extra SSL_CTX_load_verify_locations() or similar
-        * calls to direct OpenSSL where to find certificates the client
-        * can use to confirm the remote server identity.  user is the
-        * OpenSSL SSL_CTX* */
-       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS     = 22,
-       /**< if configured for
-        * including OpenSSL support, this callback allows your user code
-        * to load extra certifcates into the server which allow it to
-        * verify the validity of certificates returned by clients.  user
-        * is the server's OpenSSL SSL_CTX* */
-       LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION   = 23,
-       /**< if the libwebsockets vhost was created with the option
-        * LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT, then this
-        * callback is generated during OpenSSL verification of the cert
-        * sent from the client.  It is sent to protocol[0] callback as
-        * no protocol has been negotiated on the connection yet.
-        * Notice that the libwebsockets context and wsi are both NULL
-        * during this callback.  See
-        *  http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html
-        * to understand more detail about the OpenSSL callback that
-        * generates this libwebsockets callback and the meanings of the
-        * arguments passed.  In this callback, user is the x509_ctx,
-        * in is the ssl pointer and len is preverify_ok
-        * Notice that this callback maintains libwebsocket return
-        * conventions, return 0 to mean the cert is OK or 1 to fail it.
-        * This also means that if you don't handle this callback then
-        * the default callback action of returning 0 allows the client
-        * certificates. */
-       LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER             = 24,
-       /**< this callback happens
-        * when a client handshake is being compiled.  user is NULL,
-        * in is a char **, it's pointing to a char * which holds the
-        * next location in the header buffer where you can add
-        * headers, and len is the remaining space in the header buffer,
-        * which is typically some hundreds of bytes.  So, to add a canned
-        * cookie, your handler code might look similar to:
-        *
-        *      char **p = (char **)in;
-        *
-        *      if (len < 100)
-        *              return 1;
-        *
-        *      *p += sprintf(*p, "Cookie: a=b\x0d\x0a");
-        *
-        *      return 0;
-        *
-        * Notice if you add anything, you just have to take care about
-        * the CRLF on the line you added.  Obviously this callback is
-        * optional, if you don't handle it everything is fine.
-        *
-        * Notice the callback is coming to protocols[0] all the time,
-        * because there is no specific protocol negotiated yet. */
-       LWS_CALLBACK_CONFIRM_EXTENSION_OKAY                     = 25,
-       /**< When the server handshake code
-        * sees that it does support a requested extension, before
-        * accepting the extension by additing to the list sent back to
-        * the client it gives this callback just to check that it's okay
-        * to use that extension.  It calls back to the requested protocol
-        * and with in being the extension name, len is 0 and user is
-        * valid.  Note though at this time the ESTABLISHED callback hasn't
-        * happened yet so if you initialize user content there, user
-        * content during this callback might not be useful for anything. */
-       LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED         = 26,
-       /**< When a client
-        * connection is being prepared to start a handshake to a server,
-        * each supported extension is checked with protocols[0] callback
-        * with this reason, giving the user code a chance to suppress the
-        * claim to support that extension by returning non-zero.  If
-        * unhandled, by default 0 will be returned and the extension
-        * support included in the header to the server.  Notice this
-        * callback comes to protocols[0]. */
-       LWS_CALLBACK_PROTOCOL_INIT                              = 27,
-       /**< One-time call per protocol, per-vhost using it, so it can
-        * do initial setup / allocations etc */
-       LWS_CALLBACK_PROTOCOL_DESTROY                           = 28,
-       /**< One-time call per protocol, per-vhost using it, indicating
-        * this protocol won't get used at all after this callback, the
-        * vhost is getting destroyed.  Take the opportunity to
-        * deallocate everything that was allocated by the protocol. */
-       LWS_CALLBACK_WSI_CREATE                                 = 29,
-       /**< outermost (earliest) wsi create notification to protocols[0] */
-       LWS_CALLBACK_WSI_DESTROY                                = 30,
-       /**< outermost (latest) wsi destroy notification to protocols[0] */
-       LWS_CALLBACK_GET_THREAD_ID                              = 31,
-       /**< lws can accept callback when writable requests from other
-        * threads, if you implement this callback and return an opaque
-        * current thread ID integer. */
-
-       /* external poll() management support */
-       LWS_CALLBACK_ADD_POLL_FD                                = 32,
-       /**< lws normally deals with its poll() or other event loop
-        * internally, but in the case you are integrating with another
-        * server you will need to have lws sockets share a
-        * polling array with the other server.  This and the other
-        * POLL_FD related callbacks let you put your specialized
-        * poll array interface code in the callback for protocol 0, the
-        * first protocol you support, usually the HTTP protocol in the
-        * serving case.
-        * This callback happens when a socket needs to be
-        * added to the polling loop: in points to a struct
-        * lws_pollargs; the fd member of the struct is the file
-        * descriptor, and events contains the active events
-        *
-        * If you are using the internal lws polling / event loop
-        * you can just ignore these callbacks. */
-       LWS_CALLBACK_DEL_POLL_FD                                = 33,
-       /**< This callback happens when a socket descriptor
-        * needs to be removed from an external polling array.  in is
-        * again the struct lws_pollargs containing the fd member
-        * to be removed.  If you are using the internal polling
-        * loop, you can just ignore it. */
-       LWS_CALLBACK_CHANGE_MODE_POLL_FD                        = 34,
-       /**< This callback happens when lws wants to modify the events for
-        * a connection.
-        * in is the struct lws_pollargs with the fd to change.
-        * The new event mask is in events member and the old mask is in
-        * the prev_events member.
-        * If you are using the internal polling loop, you can just ignore
-        * it. */
-       LWS_CALLBACK_LOCK_POLL                                  = 35,
-       /**< These allow the external poll changes driven
-        * by lws to participate in an external thread locking
-        * scheme around the changes, so the whole thing is threadsafe.
-        * These are called around three activities in the library,
-        *      - inserting a new wsi in the wsi / fd table (len=1)
-        *      - deleting a wsi from the wsi / fd table (len=1)
-        *      - changing a wsi's POLLIN/OUT state (len=0)
-        * Locking and unlocking external synchronization objects when
-        * len == 1 allows external threads to be synchronized against
-        * wsi lifecycle changes if it acquires the same lock for the
-        * duration of wsi dereference from the other thread context. */
-       LWS_CALLBACK_UNLOCK_POLL                                = 36,
-       /**< See LWS_CALLBACK_LOCK_POLL, ignore if using lws internal poll */
-
-       LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY       = 37,
-       /**< if configured for including OpenSSL support but no private key
-        * file has been specified (ssl_private_key_filepath is NULL), this is
-        * called to allow the user to set the private key directly via
-        * libopenssl and perform further operations if required; this might be
-        * useful in situations where the private key is not directly accessible
-        * by the OS, for example if it is stored on a smartcard.
-        * user is the server's OpenSSL SSL_CTX* */
-       LWS_CALLBACK_WS_PEER_INITIATED_CLOSE                    = 38,
-       /**< The peer has sent an unsolicited Close WS packet.  in and
-        * len are the optional close code (first 2 bytes, network
-        * order) and the optional additional information which is not
-        * defined in the standard, and may be a string or non-human- readable data.
-        * If you return 0 lws will echo the close and then close the
-        * connection.  If you return nonzero lws will just close the
-        * connection. */
-
-       LWS_CALLBACK_WS_EXT_DEFAULTS                            = 39,
-       /**<  */
-
-       LWS_CALLBACK_CGI                                        = 40,
-       /**<  */
-       LWS_CALLBACK_CGI_TERMINATED                             = 41,
-       /**<  */
-       LWS_CALLBACK_CGI_STDIN_DATA                             = 42,
-       /**<  */
-       LWS_CALLBACK_CGI_STDIN_COMPLETED                        = 43,
-       /**<  */
-       LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP                    = 44,
-       /**<  */
-       LWS_CALLBACK_CLOSED_CLIENT_HTTP                         = 45,
-       /**<  */
-       LWS_CALLBACK_RECEIVE_CLIENT_HTTP                        = 46,
-       /**<  */
-       LWS_CALLBACK_COMPLETED_CLIENT_HTTP                      = 47,
-       /**<  */
-       LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ                   = 48,
-       /**<  */
-       LWS_CALLBACK_HTTP_BIND_PROTOCOL                         = 49,
-       /**<  */
-       LWS_CALLBACK_HTTP_DROP_PROTOCOL                         = 50,
-       /**<  */
-       LWS_CALLBACK_CHECK_ACCESS_RIGHTS                        = 51,
-       /**<  */
-       LWS_CALLBACK_PROCESS_HTML                               = 52,
-       /**<  */
-       LWS_CALLBACK_ADD_HEADERS                                = 53,
-       /**<  */
-       LWS_CALLBACK_SESSION_INFO                               = 54,
-       /**<  */
-
-       LWS_CALLBACK_GS_EVENT                                   = 55,
-       /**<  */
-       LWS_CALLBACK_HTTP_PMO                                   = 56,
-       /**< per-mount options for this connection, called before
-        * the normal LWS_CALLBACK_HTTP when the mount has per-mount
-        * options
-        */
-       LWS_CALLBACK_CLIENT_HTTP_WRITEABLE                      = 57,
-       /**< when doing an HTTP type client connection, you can call
-        * lws_client_http_body_pending(wsi, 1) from
-        * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to get these callbacks
-        * sending the HTTP headers.
-        *
-        * From this callback, when you have sent everything, you should let
-        * lws know by calling lws_client_http_body_pending(wsi, 0)
-        */
-       LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION = 58,
-       /**< Similar to LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION
-        * this callback is called during OpenSSL verification of the cert
-        * sent from the server to the client. It is sent to protocol[0]
-        * callback as no protocol has been negotiated on the connection yet.
-        * Notice that the wsi is set because lws_client_connect_via_info was
-        * successful.
-        *
-        * See http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html
-        * to understand more detail about the OpenSSL callback that
-        * generates this libwebsockets callback and the meanings of the
-        * arguments passed. In this callback, user is the x509_ctx,
-        * in is the ssl pointer and len is preverify_ok.
-        *
-        * THIS IS NOT RECOMMENDED BUT if a cert validation error shall be
-        * overruled and cert shall be accepted as ok,
-        * X509_STORE_CTX_set_error((X509_STORE_CTX*)user, X509_V_OK); must be
-        * called and return value must be 0 to mean the cert is OK;
-        * returning 1 will fail the cert in any case.
-        *
-        * This also means that if you don't handle this callback then
-        * the default callback action of returning 0 will not accept the
-        * certificate in case of a validation error decided by the SSL lib.
-        *
-        * This is expected and secure behaviour when validating certificates.
-        *
-        * Note: LCCSCF_ALLOW_SELFSIGNED and
-        * LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK still work without this
-        * callback being implemented.
-        */
-       LWS_CALLBACK_RAW_RX                                     = 59,
-       /**< RAW mode connection RX */
-       LWS_CALLBACK_RAW_CLOSE                                  = 60,
-       /**< RAW mode connection is closing */
-       LWS_CALLBACK_RAW_WRITEABLE                              = 61,
-       /**< RAW mode connection may be written */
-       LWS_CALLBACK_RAW_ADOPT                                  = 62,
-       /**< RAW mode connection was adopted (equivalent to 'wsi created') */
-       LWS_CALLBACK_RAW_ADOPT_FILE                             = 63,
-       /**< RAW mode file was adopted (equivalent to 'wsi created') */
-       LWS_CALLBACK_RAW_RX_FILE                                = 64,
-       /**< RAW mode file has something to read */
-       LWS_CALLBACK_RAW_WRITEABLE_FILE                         = 65,
-       /**< RAW mode file is writeable */
-       LWS_CALLBACK_RAW_CLOSE_FILE                             = 66,
-       /**< RAW mode wsi that adopted a file is closing */
-       LWS_CALLBACK_SSL_INFO                                   = 67,
-       /**< SSL connections only.  An event you registered an
-        * interest in at the vhost has occurred on a connection
-        * using the vhost.  @in is a pointer to a
-        * struct lws_ssl_info containing information about the
-        * event*/
-       LWS_CALLBACK_CHILD_WRITE_VIA_PARENT                     = 68,
-       /**< Child has been marked with parent_carries_io attribute, so
-        * lws_write directs the to this callback at the parent,
-        * @in is a struct lws_write_passthru containing the args
-        * the lws_write() was called with.
-        */
-       LWS_CALLBACK_CHILD_CLOSING                              = 69,
-       /**< Sent to parent to notify them a child is closing / being
-        * destroyed.  @in is the child wsi.
-        */
-
-       /****** add new things just above ---^ ******/
-
-       LWS_CALLBACK_USER = 1000,
-       /**<  user code can use any including above without fear of clashes */
-};
-
-
-
-/**
- * typedef lws_callback_function() - User server actions
- * \param wsi: Opaque websocket instance pointer
- * \param reason:      The reason for the call
- * \param user:        Pointer to per-session user data allocated by library
- * \param in:          Pointer used for some callback reasons
- * \param len: Length set for some callback reasons
- *
- *     This callback is the way the user controls what is served.  All the
- *     protocol detail is hidden and handled by the library.
- *
- *     For each connection / session there is user data allocated that is
- *     pointed to by "user".  You set the size of this user data area when
- *     the library is initialized with lws_create_server.
- */
-typedef int
-lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason,
-                   void *user, void *in, size_t len);
-///@}
-
-/*! \defgroup extensions
- *
- * ##Extension releated functions
- *
- *  Ws defines optional extensions, lws provides the ability to implement these
- *  in user code if so desired.
- *
- *  We provide one extensions permessage-deflate.
- */
-///@{
-
-/*
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-enum lws_extension_callback_reasons {
-       LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT             =  0,
-       LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT             =  1,
-       LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT              =  2,
-       LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT              =  3,
-       LWS_EXT_CB_CONSTRUCT                            =  4,
-       LWS_EXT_CB_CLIENT_CONSTRUCT                     =  5,
-       LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE             =  6,
-       LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION        =  7,
-       LWS_EXT_CB_DESTROY                              =  8,
-       LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING              =  9,
-       LWS_EXT_CB_ANY_WSI_ESTABLISHED                  = 10,
-       LWS_EXT_CB_PACKET_RX_PREPARSE                   = 11,
-       LWS_EXT_CB_PACKET_TX_PRESEND                    = 12,
-       LWS_EXT_CB_PACKET_TX_DO_SEND                    = 13,
-       LWS_EXT_CB_HANDSHAKE_REPLY_TX                   = 14,
-       LWS_EXT_CB_FLUSH_PENDING_TX                     = 15,
-       LWS_EXT_CB_EXTENDED_PAYLOAD_RX                  = 16,
-       LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION          = 17,
-       LWS_EXT_CB_1HZ                                  = 18,
-       LWS_EXT_CB_REQUEST_ON_WRITEABLE                 = 19,
-       LWS_EXT_CB_IS_WRITEABLE                         = 20,
-       LWS_EXT_CB_PAYLOAD_TX                           = 21,
-       LWS_EXT_CB_PAYLOAD_RX                           = 22,
-       LWS_EXT_CB_OPTION_DEFAULT                       = 23,
-       LWS_EXT_CB_OPTION_SET                           = 24,
-       LWS_EXT_CB_OPTION_CONFIRM                       = 25,
-       LWS_EXT_CB_NAMED_OPTION_SET                     = 26,
-
-       /****** add new things just above ---^ ******/
-};
-
-/** enum lws_ext_options_types */
-enum lws_ext_options_types {
-       EXTARG_NONE, /**< does not take an argument */
-       EXTARG_DEC,  /**< requires a decimal argument */
-       EXTARG_OPT_DEC /**< may have an optional decimal argument */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility */
-};
-
-/** struct lws_ext_options -   Option arguments to the extension.  These are
- *                             used in the negotiation at ws upgrade time.
- *                             The helper function lws_ext_parse_options()
- *                             uses these to generate callbacks */
-struct lws_ext_options {
-       const char *name; /**< Option name, eg, "server_no_context_takeover" */
-       enum lws_ext_options_types type; /**< What kind of args the option can take */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility */
-};
-
-/** struct lws_ext_option_arg */
-struct lws_ext_option_arg {
-       const char *option_name; /**< may be NULL, option_index used then */
-       int option_index; /**< argument ordinal to use if option_name missing */
-       const char *start; /**< value */
-       int len; /**< length of value */
-};
-
-/**
- * typedef lws_extension_callback_function() - Hooks to allow extensions to operate
- * \param context:     Websockets context
- * \param ext: This extension
- * \param wsi: Opaque websocket instance pointer
- * \param reason:      The reason for the call
- * \param user:        Pointer to ptr to per-session user data allocated by library
- * \param in:          Pointer used for some callback reasons
- * \param len: Length set for some callback reasons
- *
- *     Each extension that is active on a particular connection receives
- *     callbacks during the connection lifetime to allow the extension to
- *     operate on websocket data and manage itself.
- *
- *     Libwebsockets takes care of allocating and freeing "user" memory for
- *     each active extension on each connection.  That is what is pointed to
- *     by the user parameter.
- *
- *     LWS_EXT_CB_CONSTRUCT:  called when the server has decided to
- *             select this extension from the list provided by the client,
- *             just before the server will send back the handshake accepting
- *             the connection with this extension active.  This gives the
- *             extension a chance to initialize its connection context found
- *             in user.
- *
- *     LWS_EXT_CB_CLIENT_CONSTRUCT: same as LWS_EXT_CB_CONSTRUCT
- *             but called when client is instantiating this extension.  Some
- *             extensions will work the same on client and server side and then
- *             you can just merge handlers for both CONSTRUCTS.
- *
- *     LWS_EXT_CB_DESTROY:  called when the connection the extension was
- *             being used on is about to be closed and deallocated.  It's the
- *             last chance for the extension to deallocate anything it has
- *             allocated in the user data (pointed to by user) before the
- *             user data is deleted.  This same callback is used whether you
- *             are in client or server instantiation context.
- *
- *     LWS_EXT_CB_PACKET_RX_PREPARSE: when this extension was active on
- *             a connection, and a packet of data arrived at the connection,
- *             it is passed to this callback to give the extension a chance to
- *             change the data, eg, decompress it.  user is pointing to the
- *             extension's private connection context data, in is pointing
- *             to an lws_tokens struct, it consists of a char * pointer called
- *             token, and an int called token_len.  At entry, these are
- *             set to point to the received buffer and set to the content
- *             length.  If the extension will grow the content, it should use
- *             a new buffer allocated in its private user context data and
- *             set the pointed-to lws_tokens members to point to its buffer.
- *
- *     LWS_EXT_CB_PACKET_TX_PRESEND: this works the same way as
- *             LWS_EXT_CB_PACKET_RX_PREPARSE above, except it gives the
- *             extension a chance to change websocket data just before it will
- *             be sent out.  Using the same lws_token pointer scheme in in,
- *             the extension can change the buffer and the length to be
- *             transmitted how it likes.  Again if it wants to grow the
- *             buffer safely, it should copy the data into its own buffer and
- *             set the lws_tokens token pointer to it.
- *
- *     LWS_EXT_CB_ARGS_VALIDATE:
- */
-typedef int
-lws_extension_callback_function(struct lws_context *context,
-                             const struct lws_extension *ext, struct lws *wsi,
-                             enum lws_extension_callback_reasons reason,
-                             void *user, void *in, size_t len);
-
-/** struct lws_extension -     An extension we support */
-struct lws_extension {
-       const char *name; /**< Formal extension name, eg, "permessage-deflate" */
-       lws_extension_callback_function *callback; /**< Service callback */
-       const char *client_offer; /**< String containing exts and options client offers */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility */
-};
-
-/**
- * lws_set_extension_option(): set extension option if possible
- *
- * \param wsi: websocket connection
- * \param ext_name:    name of ext, like "permessage-deflate"
- * \param opt_name:    name of option, like "rx_buf_size"
- * \param opt_val:     value to set option to
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_set_extension_option(struct lws *wsi, const char *ext_name,
-                        const char *opt_name, const char *opt_val);
-
-#ifndef LWS_NO_EXTENSIONS
-/* lws_get_internal_extensions() - DEPRECATED
- *
- * \Deprecated There is no longer a set internal extensions table.  The table is provided
- * by user code along with application-specific settings.  See the test
- * client and server for how to do.
- */
-static LWS_INLINE LWS_WARN_DEPRECATED const struct lws_extension *
-lws_get_internal_extensions(void) { return NULL; }
-
-/**
- * lws_ext_parse_options() - deal with parsing negotiated extension options
- *
- * \param ext: related extension struct
- * \param wsi: websocket connection
- * \param ext_user: per-connection extension private data
- * \param opts: list of supported options
- * \param o: option string to parse
- * \param len: length
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi,
-                      void *ext_user, const struct lws_ext_options *opts,
-                      const char *o, int len);
-#endif
-
-/** lws_extension_callback_pm_deflate() - extension for RFC7692
- *
- * \param context:     lws context
- * \param ext: related lws_extension struct
- * \param wsi: websocket connection
- * \param reason:      incoming callback reason
- * \param user:        per-connection extension private data
- * \param in:  pointer parameter
- * \param len: length parameter
- *
- * Built-in callback implementing RFC7692 permessage-deflate
- */
-LWS_EXTERN
-int lws_extension_callback_pm_deflate(
-       struct lws_context *context, const struct lws_extension *ext,
-       struct lws *wsi, enum lws_extension_callback_reasons reason,
-       void *user, void *in, size_t len);
-
-/*
- * The internal exts are part of the public abi
- * If we add more extensions, publish the callback here  ------v
- */
-///@}
-
-/*! \defgroup Protocols-and-Plugins Protocols and Plugins
- * \ingroup lwsapi
- *
- * ##Protocol and protocol plugin -related apis
- *
- * Protocols bind ws protocol names to a custom callback specific to that
- * protocol implementaion.
- *
- * A list of protocols can be passed in at context creation time, but it is
- * also legal to leave that NULL and add the protocols and their callback code
- * using plugins.
- *
- * Plugins are much preferable compared to cut and pasting code into an
- * application each time, since they can be used standalone.
- */
-///@{
-/** struct lws_protocols -     List of protocols and handlers client or server
- *                                     supports. */
-
-struct lws_protocols {
-       const char *name;
-       /**< Protocol name that must match the one given in the client
-        * Javascript new WebSocket(url, 'protocol') name. */
-       lws_callback_function *callback;
-       /**< The service callback used for this protocol.  It allows the
-        * service action for an entire protocol to be encapsulated in
-        * the protocol-specific callback */
-       size_t per_session_data_size;
-       /**< Each new connection using this protocol gets
-        * this much memory allocated on connection establishment and
-        * freed on connection takedown.  A pointer to this per-connection
-        * allocation is passed into the callback in the 'user' parameter */
-       size_t rx_buffer_size;
-       /**< lws allocates this much space for rx data and informs callback
-        * when something came.  Due to rx flow control, the callback may not
-        * be able to consume it all without having to return to the event
-        * loop.  That is supported in lws.
-        *
-        * If .tx_packet_size is 0, this also controls how much may be sent at once
-        * for backwards compatibility.
-        */
-       unsigned int id;
-       /**< ignored by lws, but useful to contain user information bound
-        * to the selected protocol.  For example if this protocol was
-        * called "myprotocol-v2", you might set id to 2, and the user
-        * code that acts differently according to the version can do so by
-        * switch (wsi->protocol->id), user code might use some bits as
-        * capability flags based on selected protocol version, etc. */
-       void *user; /**< ignored by lws, but user code can pass a pointer
-                       here it can later access from the protocol callback */
-       size_t tx_packet_size;
-       /**< 0 indicates restrict send() size to .rx_buffer_size for backwards-
-        * compatibility.
-        * If greater than zero, a single send() is restricted to this amount
-        * and any remainder is buffered by lws and sent afterwards also in
-        * these size chunks.  Since that is expensive, it's preferable
-        * to restrict one fragment you are trying to send to match this
-        * size.
-        */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility */
-};
-
-struct lws_vhost;
-
-/**
- * lws_vhost_name_to_protocol() - get vhost's protocol object from its name
- *
- * \param vh: vhost to search
- * \param name: protocol name
- *
- * Returns NULL or a pointer to the vhost's protocol of the requested name
- */
-LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
-lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name);
-
-/**
- * lws_get_protocol() - Returns a protocol pointer from a websocket
- *                               connection.
- * \param wsi: pointer to struct websocket you want to know the protocol of
- *
- *
- *     Some apis can act on all live connections of a given protocol,
- *     this is how you can get a pointer to the active protocol if needed.
- */
-LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
-lws_get_protocol(struct lws *wsi);
-
-/** lws_protocol_get() -  deprecated: use lws_get_protocol */
-LWS_VISIBLE LWS_EXTERN const struct lws_protocols *
-lws_protocol_get(struct lws *wsi) LWS_WARN_DEPRECATED;
-
-/**
- * lws_protocol_vh_priv_zalloc() - Allocate and zero down a protocol's per-vhost
- *                                storage
- * \param vhost:       vhost the instance is related to
- * \param prot:                protocol the instance is related to
- * \param size:                bytes to allocate
- *
- * Protocols often find it useful to allocate a per-vhost struct, this is a
- * helper to be called in the per-vhost init LWS_CALLBACK_PROTOCOL_INIT
- */
-LWS_VISIBLE LWS_EXTERN void *
-lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot,
-                           int size);
-
-/**
- * lws_protocol_vh_priv_get() - retreive a protocol's per-vhost storage
- *
- * \param vhost:       vhost the instance is related to
- * \param prot:                protocol the instance is related to
- *
- * Recover a pointer to the allocated per-vhost storage for the protocol created
- * by lws_protocol_vh_priv_zalloc() earlier
- */
-LWS_VISIBLE LWS_EXTERN void *
-lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot);
-
-/**
- * lws_finalize_startup() - drop initial process privileges
- *
- * \param context:     lws context
- *
- * This is called after the end of the vhost protocol initializations, but
- * you may choose to call it earlier
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_finalize_startup(struct lws_context *context);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_protocol_init(struct lws_context *context);
-
-#ifdef LWS_WITH_PLUGINS
-
-/* PLUGINS implies LIBUV */
-
-#define LWS_PLUGIN_API_MAGIC 180
-
-/** struct lws_plugin_capability - how a plugin introduces itself to lws */
-struct lws_plugin_capability {
-       unsigned int api_magic; /**< caller fills this in, plugin fills rest */
-       const struct lws_protocols *protocols; /**< array of supported protocols provided by plugin */
-       int count_protocols; /**< how many protocols */
-       const struct lws_extension *extensions; /**< array of extensions provided by plugin */
-       int count_extensions; /**< how many extensions */
-};
-
-typedef int (*lws_plugin_init_func)(struct lws_context *,
-                                   struct lws_plugin_capability *);
-typedef int (*lws_plugin_destroy_func)(struct lws_context *);
-
-/** struct lws_plugin */
-struct lws_plugin {
-       struct lws_plugin *list; /**< linked list */
-#if (UV_VERSION_MAJOR > 0)
-       uv_lib_t lib; /**< shared library pointer */
-#else
-       void *l; /**< so we can compile on ancient libuv */
-#endif
-       char name[64]; /**< name of the plugin */
-       struct lws_plugin_capability caps; /**< plugin capabilities */
-};
-
-#endif
-
-///@}
-
-
-/*! \defgroup generic-sessions plugin: generic-sessions
- * \ingroup Protocols-and-Plugins
- *
- * ##Plugin Generic-sessions related
- *
- * generic-sessions plugin provides a reusable, generic session and login /
- * register / forgot password framework including email verification.
- */
-///@{
-
-#define LWSGS_EMAIL_CONTENT_SIZE 16384
-/**< Maximum size of email we might send */
-
-/* SHA-1 binary and hexified versions */
-/** typedef struct lwsgw_hash_bin */
-typedef struct { unsigned char bin[20]; /**< binary representation of hash */} lwsgw_hash_bin;
-/** typedef struct lwsgw_hash */
-typedef struct { char id[41]; /**< ascii hex representation of hash */ } lwsgw_hash;
-
-/** enum lwsgs_auth_bits */
-enum lwsgs_auth_bits {
-       LWSGS_AUTH_LOGGED_IN = 1, /**< user is logged in as somebody */
-       LWSGS_AUTH_ADMIN = 2,   /**< logged in as the admin user */
-       LWSGS_AUTH_VERIFIED = 4,  /**< user has verified his email */
-       LWSGS_AUTH_FORGOT_FLOW = 8,     /**< he just completed "forgot password" flow */
-};
-
-/** struct lws_session_info - information about user session status */
-struct lws_session_info {
-       char username[32]; /**< username logged in as, or empty string */
-       char email[100]; /**< email address associated with login, or empty string */
-       char ip[72]; /**< ip address session was started from */
-       unsigned int mask; /**< access rights mask associated with session
-                           * see enum lwsgs_auth_bits */
-       char session[42]; /**< session id string, usable as opaque uid when not logged in */
-};
-
-/** enum lws_gs_event */
-enum lws_gs_event {
-       LWSGSE_CREATED, /**< a new user was created */
-       LWSGSE_DELETED  /**< an existing user was deleted */
-};
-
-/** struct lws_gs_event_args */
-struct lws_gs_event_args {
-       enum lws_gs_event event; /**< which event happened */
-       const char *username; /**< which username the event happened to */
-       const char *email; /**< the email address of that user */
-};
-
-///@}
-
-
-/*! \defgroup context-and-vhost
- * \ingroup lwsapi
- *
- * ##Context and Vhost releated functions
- *
- *  LWS requires that there is one context, in which you may define multiple
- *  vhosts.  Each vhost is a virtual host, with either its own listen port
- *  or sharing an existing one.  Each vhost has its own SSL context that can
- *  be set up individually or left disabled.
- *
- *  If you don't care about multiple "site" support, you can ignore it and
- *  lws will create a single default vhost at context creation time.
- */
-///@{
-
-/*
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-
-/** enum lws_context_options - context and vhost options */
-enum lws_context_options {
-       LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT     = (1 << 1) |
-                                                                 (1 << 12),
-       /**< (VH) Don't allow the connection unless the client has a
-        * client cert that we recognize; provides
-        * LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT */
-       LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME            = (1 << 2),
-       /**< (CTX) Don't try to get the server's hostname */
-       LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT             = (1 << 3) |
-                                                                 (1 << 12),
-       /**< (VH) Allow non-SSL (plaintext) connections on the same
-        * port as SSL is listening... undermines the security of SSL;
-        * provides  LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT */
-       LWS_SERVER_OPTION_LIBEV                                 = (1 << 4),
-       /**< (CTX) Use libev event loop */
-       LWS_SERVER_OPTION_DISABLE_IPV6                          = (1 << 5),
-       /**< (VH) Disable IPV6 support */
-       LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS                   = (1 << 6),
-       /**< (VH) Don't load OS CA certs, you will need to load your
-        * own CA cert(s) */
-       LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED                = (1 << 7),
-       /**< (VH) Accept connections with no valid Cert (eg, selfsigned) */
-       LWS_SERVER_OPTION_VALIDATE_UTF8                         = (1 << 8),
-       /**< (VH) Check UT-8 correctness */
-       LWS_SERVER_OPTION_SSL_ECDH                              = (1 << 9) |
-                                                                 (1 << 12),
-       /**< (VH)  initialize ECDH ciphers */
-       LWS_SERVER_OPTION_LIBUV                                 = (1 << 10),
-       /**< (CTX)  Use libuv event loop */
-       LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS                = (1 << 11) |
-                                                                 (1 << 12),
-       /**< (VH) Use http redirect to force http to https
-        * (deprecated: use mount redirection) */
-       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT                    = (1 << 12),
-       /**< (CTX) Initialize the SSL library at all */
-       LWS_SERVER_OPTION_EXPLICIT_VHOSTS                       = (1 << 13),
-       /**< (CTX) Only create the context when calling context
-        * create api, implies user code will create its own vhosts */
-       LWS_SERVER_OPTION_UNIX_SOCK                             = (1 << 14),
-       /**< (VH) Use Unix socket */
-       LWS_SERVER_OPTION_STS                                   = (1 << 15),
-       /**< (VH) Send Strict Transport Security header, making
-        * clients subsequently go to https even if user asked for http */
-       LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY                    = (1 << 16),
-       /**< (VH) Enable LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE to take effect */
-       LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE                     = (1 << 17),
-       /**< (VH) if set, only ipv6 allowed on the vhost */
-       LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN             = (1 << 18),
-       /**< (CTX) Libuv only: Do not spin on SIGSEGV / SIGFPE.  A segfault
-        * normally makes the lib spin so you can attach a debugger to it
-        * even if it happened without a debugger in place.  You can disable
-        * that by giving this option.
-        */
-       LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN                   = (1 << 19),
-       /**< For backwards-compatibility reasons, by default
-        * lws prepends "http://" to the origin you give in the client
-        * connection info struct.  If you give this flag when you create
-        * the context, only the string you give in the client connect
-        * info for .origin (if any) will be used directly.
-        */
-       LWS_SERVER_OPTION_FALLBACK_TO_RAW                       = (1 << 20),
-       /**< (VH) if invalid http is coming in the first line,  */
-       LWS_SERVER_OPTION_LIBEVENT                              = (1 << 21),
-       /**< (CTX) Use libevent event loop */
-       LWS_SERVER_OPTION_ONLY_RAW                              = (1 << 22),
-       /**< (VH) All connections to this vhost / port are RAW as soon as
-        * the connection is accepted, no HTTP is going to be coming.
-        */
-       LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE                    = (1 << 23),
-       /**< (VH) Set to allow multiple listen sockets on one interface +
-        * address + port.  The default is to strictly allow only one
-        * listen socket at a time.  This is automatically selected if you
-        * have multiple service threads.
-        */
-
-       /****** add new things just above ---^ ******/
-};
-
-#define lws_check_opt(c, f) (((c) & (f)) == (f))
-
-struct lws_plat_file_ops;
-
-/** struct lws_context_creation_info - parameters to create context and /or vhost with
- *
- * This is also used to create vhosts.... if LWS_SERVER_OPTION_EXPLICIT_VHOSTS
- * is not given, then for backwards compatibility one vhost is created at
- * context-creation time using the info from this struct.
- *
- * If LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, then no vhosts are created
- * at the same time as the context, they are expected to be created afterwards.
- */
-struct lws_context_creation_info {
-       int port;
-       /**< VHOST: Port to listen on. Use CONTEXT_PORT_NO_LISTEN to suppress
-        * listening for a client. Use CONTEXT_PORT_NO_LISTEN_SERVER if you are
-        * writing a server but you are using \ref sock-adopt instead of the
-        * built-in listener */
-       const char *iface;
-       /**< VHOST: NULL to bind the listen socket to all interfaces, or the
-        * interface name, eg, "eth2"
-        * If options specifies LWS_SERVER_OPTION_UNIX_SOCK, this member is
-        * the pathname of a UNIX domain socket. you can use the UNIX domain
-        * sockets in abstract namespace, by prepending an at symbol to the
-        * socket name. */
-       const struct lws_protocols *protocols;
-       /**< VHOST: Array of structures listing supported protocols and a protocol-
-        * specific callback for each one.  The list is ended with an
-        * entry that has a NULL callback pointer. */
-       const struct lws_extension *extensions;
-       /**< VHOST: NULL or array of lws_extension structs listing the
-        * extensions this context supports. */
-       const struct lws_token_limits *token_limits;
-       /**< CONTEXT: NULL or struct lws_token_limits pointer which is initialized
-        * with a token length limit for each possible WSI_TOKEN_ */
-       const char *ssl_private_key_password;
-       /**< VHOST: NULL or the passphrase needed for the private key */
-       const char *ssl_cert_filepath;
-       /**< VHOST: If libwebsockets was compiled to use ssl, and you want
-        * to listen using SSL, set to the filepath to fetch the
-        * server cert from, otherwise NULL for unencrypted */
-       const char *ssl_private_key_filepath;
-       /**<  VHOST: filepath to private key if wanting SSL mode;
-        * if this is set to NULL but sll_cert_filepath is set, the
-        * OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY callback is called
-        * to allow setting of the private key directly via openSSL
-        * library calls */
-       const char *ssl_ca_filepath;
-       /**< VHOST: CA certificate filepath or NULL */
-       const char *ssl_cipher_list;
-       /**< VHOST: List of valid ciphers to use (eg,
-        * "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"
-        * or you can leave it as NULL to get "DEFAULT" */
-       const char *http_proxy_address;
-       /**< VHOST: If non-NULL, attempts to proxy via the given address.
-        * If proxy auth is required, use format "username:password\@server:port" */
-       unsigned int http_proxy_port;
-       /**< VHOST: If http_proxy_address was non-NULL, uses this port */
-       int gid;
-       /**< CONTEXT: group id to change to after setting listen socket, or -1. */
-       int uid;
-       /**< CONTEXT: user id to change to after setting listen socket, or -1. */
-       unsigned int options;
-       /**< VHOST + CONTEXT: 0, or LWS_SERVER_OPTION_... bitfields */
-       void *user;
-       /**< CONTEXT: optional user pointer that can be recovered via the context
-        *              pointer using lws_context_user */
-       int ka_time;
-       /**< CONTEXT: 0 for no TCP keepalive, otherwise apply this keepalive
-        * timeout to all libwebsocket sockets, client or server */
-       int ka_probes;
-       /**< CONTEXT: if ka_time was nonzero, after the timeout expires how many
-        * times to try to get a response from the peer before giving up
-        * and killing the connection */
-       int ka_interval;
-       /**< CONTEXT: if ka_time was nonzero, how long to wait before each ka_probes
-        * attempt */
-#ifdef LWS_OPENSSL_SUPPORT
-       SSL_CTX *provided_client_ssl_ctx;
-       /**< CONTEXT: If non-null, swap out libwebsockets ssl
- *             implementation for the one provided by provided_ssl_ctx.
- *             Libwebsockets no longer is responsible for freeing the context
- *             if this option is selected. */
-#else /* maintain structure layout either way */
-       void *provided_client_ssl_ctx; /**< dummy if ssl disabled */
-#endif
-
-       short max_http_header_data;
-       /**< CONTEXT: The max amount of header payload that can be handled
-        * in an http request (unrecognized header payload is dropped) */
-       short max_http_header_pool;
-       /**< CONTEXT: The max number of connections with http headers that
-        * can be processed simultaneously (the corresponding memory is
-        * allocated for the lifetime of the context).  If the pool is
-        * busy new incoming connections must wait for accept until one
-        * becomes free. */
-
-       unsigned int count_threads;
-       /**< CONTEXT: how many contexts to create in an array, 0 = 1 */
-       unsigned int fd_limit_per_thread;
-       /**< CONTEXT: nonzero means restrict each service thread to this
-        * many fds, 0 means the default which is divide the process fd
-        * limit by the number of threads. */
-       unsigned int timeout_secs;
-       /**< VHOST: various processes involving network roundtrips in the
-        * library are protected from hanging forever by timeouts.  If
-        * nonzero, this member lets you set the timeout used in seconds.
-        * Otherwise a default timeout is used. */
-       const char *ecdh_curve;
-       /**< VHOST: if NULL, defaults to initializing server with "prime256v1" */
-       const char *vhost_name;
-       /**< VHOST: name of vhost, must match external DNS name used to
-        * access the site, like "warmcat.com" as it's used to match
-        * Host: header and / or SNI name for SSL. */
-       const char * const *plugin_dirs;
-       /**< CONTEXT: NULL, or NULL-terminated array of directories to
-        * scan for lws protocol plugins at context creation time */
-       const struct lws_protocol_vhost_options *pvo;
-       /**< VHOST: pointer to optional linked list of per-vhost
-        * options made accessible to protocols */
-       int keepalive_timeout;
-       /**< VHOST: (default = 0 = 60s) seconds to allow remote
-        * client to hold on to an idle HTTP/1.1 connection */
-       const char *log_filepath;
-       /**< VHOST: filepath to append logs to... this is opened before
-        *              any dropping of initial privileges */
-       const struct lws_http_mount *mounts;
-       /**< VHOST: optional linked list of mounts for this vhost */
-       const char *server_string;
-       /**< CONTEXT: string used in HTTP headers to identify server
- *             software, if NULL, "libwebsockets". */
-       unsigned int pt_serv_buf_size;
-       /**< CONTEXT: 0 = default of 4096.  This buffer is used by
-        * various service related features including file serving, it
-        * defines the max chunk of file that can be sent at once.
-        * At the risk of lws having to buffer failed large sends, it
-        * can be increased to, eg, 128KiB to improve throughput. */
-       unsigned int max_http_header_data2;
-       /**< CONTEXT: if max_http_header_data is 0 and this
-        * is nonzero, this will be used in place of the default.  It's
-        * like this for compatibility with the original short version,
-        * this is unsigned int length. */
-       long ssl_options_set;
-       /**< VHOST: Any bits set here will be set as SSL options */
-       long ssl_options_clear;
-       /**< VHOST: Any bits set here will be cleared as SSL options */
-       unsigned short ws_ping_pong_interval;
-       /**< CONTEXT: 0 for none, else interval in seconds between sending
-        * PINGs on idle websocket connections.  When the PING is sent,
-        * the PONG must come within the normal timeout_secs timeout period
-        * or the connection will be dropped.
-        * Any RX or TX traffic on the connection restarts the interval timer,
-        * so a connection which always sends or receives something at intervals
-        * less than the interval given here will never send PINGs / expect
-        * PONGs.  Conversely as soon as the ws connection is established, an
-        * idle connection will do the PING / PONG roundtrip as soon as
-        * ws_ping_pong_interval seconds has passed without traffic
-        */
-       const struct lws_protocol_vhost_options *headers;
-               /**< VHOST: pointer to optional linked list of per-vhost
-                * canned headers that are added to server responses */
-
-       const struct lws_protocol_vhost_options *reject_service_keywords;
-       /**< CONTEXT: Optional list of keywords and rejection codes + text.
-        *
-        * The keywords are checked for existing in the user agent string.
-        *
-        * Eg, "badrobot" "404 Not Found"
-        */
-       void *external_baggage_free_on_destroy;
-       /**< CONTEXT: NULL, or pointer to something externally malloc'd, that
-        * should be freed when the context is destroyed.  This allows you to
-        * automatically sync the freeing action to the context destruction
-        * action, so there is no need for an external free() if the context
-        * succeeded to create.
-        */
-
-#ifdef LWS_OPENSSL_SUPPORT
-        /**< CONTEXT: NULL or struct lws_token_limits pointer which is initialized
-        * with a token length limit for each possible WSI_TOKEN_ */
-       const char *client_ssl_private_key_password;
-       /**< VHOST: NULL or the passphrase needed for the private key */
-       const char *client_ssl_cert_filepath;
-       /**< VHOST: If libwebsockets was compiled to use ssl, and you want
-       * to listen using SSL, set to the filepath to fetch the
-       * server cert from, otherwise NULL for unencrypted */
-       const char *client_ssl_private_key_filepath;
-       /**<  VHOST: filepath to private key if wanting SSL mode;
-       * if this is set to NULL but sll_cert_filepath is set, the
-       * OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY callback is called
-       * to allow setting of the private key directly via openSSL
-       * library calls */
-       const char *client_ssl_ca_filepath;
-       /**< VHOST: CA certificate filepath or NULL */
-       const char *client_ssl_cipher_list;
-       /**< VHOST: List of valid ciphers to use (eg,
-       * "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"
-       * or you can leave it as NULL to get "DEFAULT" */
-#endif
-
-       const struct lws_plat_file_ops *fops;
-       /**< CONTEXT: NULL, or pointer to an array of fops structs, terminated
-        * by a sentinel with NULL .open.
-        *
-        * If NULL, lws provides just the platform file operations struct for
-        * backwards compatibility.
-        */
-       int simultaneous_ssl_restriction;
-       /**< CONTEXT: 0 (no limit) or limit of simultaneous SSL sessions possible.*/
-       const char *socks_proxy_address;
-       /**< VHOST: If non-NULL, attempts to proxy via the given address.
-        * If proxy auth is required, use format "username:password\@server:port" */
-       unsigned int socks_proxy_port;
-       /**< VHOST: If socks_proxy_address was non-NULL, uses this port */
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-       cap_value_t caps[4];
-       /**< CONTEXT: array holding Linux capabilities you want to
-        * continue to be available to the server after it transitions
-        * to a noprivileged user.  Usually none are needed but for, eg,
-        * .bind_iface, CAP_NET_RAW is required.  This gives you a way
-        * to still have the capability but drop root.
-        */
-       char count_caps;
-       /**< CONTEXT: count of Linux capabilities in .caps[].  0 means
-        * no capabilities will be inherited from root (the default) */
-#endif
-       int bind_iface;
-       /**< VHOST: nonzero to strictly bind sockets to the interface name in
-        * .iface (eg, "eth2"), using SO_BIND_TO_DEVICE.
-        *
-        * Requires SO_BINDTODEVICE support from your OS and CAP_NET_RAW
-        * capability.
-        *
-        * Notice that common things like access network interface IP from
-        * your local machine use your lo / loopback interface and will be
-        * disallowed by this.
-        */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility
-        *
-        * The below is to ensure later library versions with new
-        * members added above will see 0 (default) even if the app
-        * was not built against the newer headers.
-        */
-       int ssl_info_event_mask;
-       /**< VHOST: mask of ssl events to be reported on LWS_CALLBACK_SSL_INFO
-        * callback for connections on this vhost.  The mask values are of
-        * the form SSL_CB_ALERT, defined in openssl/ssl.h.  The default of
-        * 0 means no info events will be reported.
-        */
-       unsigned int timeout_secs_ah_idle;
-       /**< VHOST: seconds to allow a client to hold an ah without using it.
-        * 0 defaults to 10s. */
-
-       void *_unused[8]; /**< dummy */
-};
-
-/**
- * lws_create_context() - Create the websocket handler
- * \param info:        pointer to struct with parameters
- *
- *     This function creates the listening socket (if serving) and takes care
- *     of all initialization in one step.
- *
- *     If option LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, no vhost is
- *     created; you're expected to create your own vhosts afterwards using
- *     lws_create_vhost().  Otherwise a vhost named "default" is also created
- *     using the information in the vhost-related members, for compatibility.
- *
- *     After initialization, it returns a struct lws_context * that
- *     represents this server.  After calling, user code needs to take care
- *     of calling lws_service() with the context pointer to get the
- *     server's sockets serviced.  This must be done in the same process
- *     context as the initialization call.
- *
- *     The protocol callback functions are called for a handful of events
- *     including http requests coming in, websocket connections becoming
- *     established, and data arriving; it's also called periodically to allow
- *     async transmission.
- *
- *     HTTP requests are sent always to the FIRST protocol in protocol, since
- *     at that time websocket protocol has not been negotiated.  Other
- *     protocols after the first one never see any HTTP callback activity.
- *
- *     The server created is a simple http server by default; part of the
- *     websocket standard is upgrading this http connection to a websocket one.
- *
- *     This allows the same server to provide files like scripts and favicon /
- *     images or whatever over http and dynamic data over websockets all in
- *     one place; they're all handled in the user callback.
- */
-LWS_VISIBLE LWS_EXTERN struct lws_context *
-lws_create_context(struct lws_context_creation_info *info);
-
-/**
- * lws_context_destroy() - Destroy the websocket context
- * \param context:     Websocket context
- *
- *     This function closes any active connections and then frees the
- *     context.  After calling this, any further use of the context is
- *     undefined.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_context_destroy(struct lws_context *context);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_context_destroy2(struct lws_context *context);
-
-typedef int (*lws_reload_func)(void);
-
-/**
- * lws_context_deprecate() - Deprecate the websocket context
- * \param context:     Websocket context
- *
- *     This function is used on an existing context before superceding it
- *     with a new context.
- *
- *     It closes any listen sockets in the context, so new connections are
- *     not possible.
- *
- *     And it marks the context to be deleted when the number of active
- *     connections into it falls to zero.
- *
- *     Otherwise if you attach the deprecated context to the replacement
- *     context when it has been created using lws_context_attach_deprecated()
- *     both any deprecated and the new context will service their connections.
- *
- *     This is aimed at allowing seamless configuration reloads.
- *
- *     The callback cb will be called after the listen sockets are actually
- *     closed and may be reopened.  In the callback the new context should be
- *     configured and created.  (With libuv, socket close happens async after
- *     more loop events).
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_context_deprecate(struct lws_context *context, lws_reload_func cb);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_context_is_deprecated(struct lws_context *context);
-
-/**
- * lws_set_proxy() - Setups proxy to lws_context.
- * \param vhost:       pointer to struct lws_vhost you want set proxy for
- * \param proxy: pointer to c string containing proxy in format address:port
- *
- * Returns 0 if proxy string was parsed and proxy was setup.
- * Returns -1 if proxy is NULL or has incorrect format.
- *
- * This is only required if your OS does not provide the http_proxy
- * environment variable (eg, OSX)
- *
- *   IMPORTANT! You should call this function right after creation of the
- *   lws_context and before call to connect. If you call this
- *   function after connect behavior is undefined.
- *   This function will override proxy settings made on lws_context
- *   creation with genenv() call.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_set_proxy(struct lws_vhost *vhost, const char *proxy);
-
-/**
- * lws_set_socks() - Setup socks to lws_context.
- * \param vhost:       pointer to struct lws_vhost you want set socks for
- * \param socks: pointer to c string containing socks in format address:port
- *
- * Returns 0 if socks string was parsed and socks was setup.
- * Returns -1 if socks is NULL or has incorrect format.
- *
- * This is only required if your OS does not provide the socks_proxy
- * environment variable (eg, OSX)
- *
- *   IMPORTANT! You should call this function right after creation of the
- *   lws_context and before call to connect. If you call this
- *   function after connect behavior is undefined.
- *   This function will override proxy settings made on lws_context
- *   creation with genenv() call.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_set_socks(struct lws_vhost *vhost, const char *socks);
-
-struct lws_vhost;
-
-/**
- * lws_create_vhost() - Create a vhost (virtual server context)
- * \param context:     pointer to result of lws_create_context()
- * \param info:                pointer to struct with parameters
- *
- * This function creates a virtual server (vhost) using the vhost-related
- * members of the info struct.  You can create many vhosts inside one context
- * if you created the context with the option LWS_SERVER_OPTION_EXPLICIT_VHOSTS
- */
-LWS_VISIBLE LWS_EXTERN struct lws_vhost *
-lws_create_vhost(struct lws_context *context,
-                struct lws_context_creation_info *info);
-
-/**
- * lws_destroy_vhost() - Destroy a vhost (virtual server context)
- * \param vhost:       pointer to result of lws_create_vhost()
- *
- * This function destroys a vhost.  Normally, if you just want to exit,
- * then lws_destroy_context() will take care of everything.  If you want
- * to destroy an individual vhost and all connections and allocations, you
- * can do it with this.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_vhost_destroy(struct lws_vhost *vh);
-
-/**
- * lwsws_get_config_globals() - Parse a JSON server config file
- * \param info:                pointer to struct with parameters
- * \param d:           filepath of the config file
- * \param config_strings: storage for the config strings extracted from JSON,
- *                       the pointer is incremented as strings are stored
- * \param len:         pointer to the remaining length left in config_strings
- *                       the value is decremented as strings are stored
- *
- * This function prepares a n lws_context_creation_info struct with global
- * settings from a file d.
- *
- * Requires CMake option LWS_WITH_LEJP_CONF to have been enabled
- */
-LWS_VISIBLE LWS_EXTERN int
-lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
-                        char **config_strings, int *len);
-
-/**
- * lwsws_get_config_vhosts() - Create vhosts from a JSON server config file
- * \param context:     pointer to result of lws_create_context()
- * \param info:                pointer to struct with parameters
- * \param d:           filepath of the config file
- * \param config_strings: storage for the config strings extracted from JSON,
- *                       the pointer is incremented as strings are stored
- * \param len:         pointer to the remaining length left in config_strings
- *                       the value is decremented as strings are stored
- *
- * This function creates vhosts into a context according to the settings in
- *JSON files found in directory d.
- *
- * Requires CMake option LWS_WITH_LEJP_CONF to have been enabled
- */
-LWS_VISIBLE LWS_EXTERN int
-lwsws_get_config_vhosts(struct lws_context *context,
-                       struct lws_context_creation_info *info, const char *d,
-                       char **config_strings, int *len);
-
-/** lws_vhost_get() - \deprecated deprecated: use lws_get_vhost() */
-LWS_VISIBLE LWS_EXTERN struct lws_vhost *
-lws_vhost_get(struct lws *wsi) LWS_WARN_DEPRECATED;
-
-/**
- * lws_get_vhost() - return the vhost a wsi belongs to
- *
- * \param wsi: which connection
- */
-LWS_VISIBLE LWS_EXTERN struct lws_vhost *
-lws_get_vhost(struct lws *wsi);
-
-/**
- * lws_json_dump_vhost() - describe vhost state and stats in JSON
- *
- * \param vh: the vhost
- * \param buf: buffer to fill with JSON
- * \param len: max length of buf
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len);
-
-/**
- * lws_json_dump_context() - describe context state and stats in JSON
- *
- * \param context: the context
- * \param buf: buffer to fill with JSON
- * \param len: max length of buf
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_json_dump_context(const struct lws_context *context, char *buf, int len,
-                     int hide_vhosts);
-
-/**
- * lws_context_user() - get the user data associated with the context
- * \param context: Websocket context
- *
- * This returns the optional user allocation that can be attached to
- * the context the sockets live in at context_create time.  It's a way
- * to let all sockets serviced in the same context share data without
- * using globals statics in the user code.
- */
-LWS_VISIBLE LWS_EXTERN void *
-lws_context_user(struct lws_context *context);
-
-/*! \defgroup vhost-mounts Vhost mounts and options
- * \ingroup context-and-vhost-creation
- *
- * ##Vhost mounts and options
- */
-///@{
-/** struct lws_protocol_vhost_options - linked list of per-vhost protocol
- *                                     name=value options
- *
- * This provides a general way to attach a linked-list of name=value pairs,
- * which can also have an optional child link-list using the options member.
- */
-struct lws_protocol_vhost_options {
-       const struct lws_protocol_vhost_options *next; /**< linked list */
-       const struct lws_protocol_vhost_options *options; /**< child linked-list of more options for this node */
-       const char *name; /**< name of name=value pair */
-       const char *value; /**< value of name=value pair */
-};
-
-/** enum lws_mount_protocols
- * This specifies the mount protocol for a mountpoint, whether it is to be
- * served from a filesystem, or it is a cgi etc.
- */
-enum lws_mount_protocols {
-       LWSMPRO_HTTP            = 0, /**< http reverse proxy */
-       LWSMPRO_HTTPS           = 1, /**< https reverse proxy */
-       LWSMPRO_FILE            = 2, /**< serve from filesystem directory */
-       LWSMPRO_CGI             = 3, /**< pass to CGI to handle */
-       LWSMPRO_REDIR_HTTP      = 4, /**< redirect to http:// url */
-       LWSMPRO_REDIR_HTTPS     = 5, /**< redirect to https:// url */
-       LWSMPRO_CALLBACK        = 6, /**< hand by named protocol's callback */
-};
-
-/** struct lws_http_mount
- *
- * arguments for mounting something in a vhost's url namespace
- */
-struct lws_http_mount {
-       const struct lws_http_mount *mount_next;
-       /**< pointer to next struct lws_http_mount */
-       const char *mountpoint;
-       /**< mountpoint in http pathspace, eg, "/" */
-       const char *origin;
-       /**< path to be mounted, eg, "/var/www/warmcat.com" */
-       const char *def;
-       /**< default target, eg, "index.html" */
-       const char *protocol;
-       /**<"protocol-name" to handle mount */
-
-       const struct lws_protocol_vhost_options *cgienv;
-       /**< optional linked-list of cgi options.  These are created
-        * as environment variables for the cgi process
-        */
-       const struct lws_protocol_vhost_options *extra_mimetypes;
-       /**< optional linked-list of mimetype mappings */
-       const struct lws_protocol_vhost_options *interpret;
-       /**< optional linked-list of files to be interpreted */
-
-       int cgi_timeout;
-       /**< seconds cgi is allowed to live, if cgi://mount type */
-       int cache_max_age;
-       /**< max-age for reuse of client cache of files, seconds */
-       unsigned int auth_mask;
-       /**< bits set here must be set for authorized client session */
-
-       unsigned int cache_reusable:1; /**< set if client cache may reuse this */
-       unsigned int cache_revalidate:1; /**< set if client cache should revalidate on use */
-       unsigned int cache_intermediaries:1; /**< set if intermediaries are allowed to cache */
-
-       unsigned char origin_protocol; /**< one of enum lws_mount_protocols */
-       unsigned char mountpoint_len; /**< length of mountpoint string */
-
-       const char *basic_auth_login_file;
-       /**<NULL, or filepath to use to check basic auth logins against */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility
-        *
-        * The below is to ensure later library versions with new
-        * members added above will see 0 (default) even if the app
-        * was not built against the newer headers.
-        */
-
-       void *_unused[2]; /**< dummy */
-};
-///@}
-///@}
-
-/*! \defgroup client
- * \ingroup lwsapi
- *
- * ##Client releated functions
- * */
-///@{
-
-/** enum lws_client_connect_ssl_connection_flags - flags that may be used
- * with struct lws_client_connect_info ssl_connection member to control if
- * and how SSL checks apply to the client connection being created
- */
-
-enum lws_client_connect_ssl_connection_flags {
-       LCCSCF_USE_SSL                          = (1 << 0),
-       LCCSCF_ALLOW_SELFSIGNED                 = (1 << 1),
-       LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK  = (1 << 2),
-       LCCSCF_ALLOW_EXPIRED                    = (1 << 3)
-};
-
-/** struct lws_client_connect_info - parameters to connect with when using
- *                                 lws_client_connect_via_info() */
-
-struct lws_client_connect_info {
-       struct lws_context *context;
-       /**< lws context to create connection in */
-       const char *address;
-       /**< remote address to connect to */
-       int port;
-       /**< remote port to connect to */
-       int ssl_connection;
-       /**< nonzero for ssl */
-       const char *path;
-       /**< uri path */
-       const char *host;
-       /**< content of host header */
-       const char *origin;
-       /**< content of origin header */
-       const char *protocol;
-       /**< list of ws protocols we could accept */
-       int ietf_version_or_minus_one;
-       /**< deprecated: currently leave at 0 or -1 */
-       void *userdata;
-       /**< if non-NULL, use this as wsi user_data instead of malloc it */
-       const void *client_exts;
-       /**< UNUSED... provide in info.extensions at context creation time */
-       const char *method;
-       /**< if non-NULL, do this http method instead of ws[s] upgrade.
-        * use "GET" to be a simple http client connection */
-       struct lws *parent_wsi;
-       /**< if another wsi is responsible for this connection, give it here.
-        * this is used to make sure if the parent closes so do any
-        * child connections first. */
-       const char *uri_replace_from;
-       /**< if non-NULL, when this string is found in URIs in
-        * text/html content-encoding, it's replaced with uri_replace_to */
-       const char *uri_replace_to;
-       /**< see uri_replace_from */
-       struct lws_vhost *vhost;
-       /**< vhost to bind to (used to determine related SSL_CTX) */
-       struct lws **pwsi;
-       /**< if not NULL, store the new wsi here early in the connection
-        * process.  Although we return the new wsi, the call to create the
-        * client connection does progress the connection somewhat and may
-        * meet an error that will result in the connection being scrubbed and
-        * NULL returned.  While the wsi exists though, he may process a
-        * callback like CLIENT_CONNECTION_ERROR with his wsi: this gives the
-        * user callback a way to identify which wsi it is that faced the error
-        * even before the new wsi is returned and even if ultimately no wsi
-        * is returned.
-        */
-       const char *iface;
-       /**< NULL to allow routing on any interface, or interface name or IP
-        * to bind the socket to */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility
-        *
-        * The below is to ensure later library versions with new
-        * members added above will see 0 (default) even if the app
-        * was not built against the newer headers.
-        */
-
-       void *_unused[4]; /**< dummy */
-};
-
-/**
- * lws_client_connect_via_info() - Connect to another websocket server
- * \param ccinfo: pointer to lws_client_connect_info struct
- *
- *     This function creates a connection to a remote server using the
- *     information provided in ccinfo.
- */
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_client_connect_via_info(struct lws_client_connect_info * ccinfo);
-
-/**
- * lws_client_connect() - Connect to another websocket server
- *             \deprecated DEPRECATED use lws_client_connect_via_info
- * \param clients:     Websocket context
- * \param address:     Remote server address, eg, "myserver.com"
- * \param port:        Port to connect to on the remote server, eg, 80
- * \param ssl_connection:      0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self
- *                     signed certs
- * \param path:        Websocket path on server
- * \param host:        Hostname on server
- * \param origin:      Socket origin name
- * \param protocol:    Comma-separated list of protocols being asked for from
- *             the server, or just one.  The server will pick the one it
- *             likes best.  If you don't want to specify a protocol, which is
- *             legal, use NULL here.
- * \param ietf_version_or_minus_one: -1 to ask to connect using the default, latest
- *             protocol supported, or the specific protocol ordinal
- *
- *     This function creates a connection to a remote server
- */
-/* deprecated, use lws_client_connect_via_info() */
-LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
-lws_client_connect(struct lws_context *clients, const char *address,
-                  int port, int ssl_connection, const char *path,
-                  const char *host, const char *origin, const char *protocol,
-                  int ietf_version_or_minus_one) LWS_WARN_DEPRECATED;
-/* deprecated, use lws_client_connect_via_info() */
-/**
- * lws_client_connect_extended() - Connect to another websocket server
- *                     \deprecated DEPRECATED use lws_client_connect_via_info
- * \param clients:     Websocket context
- * \param address:     Remote server address, eg, "myserver.com"
- * \param port:        Port to connect to on the remote server, eg, 80
- * \param ssl_connection:      0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self
- *                     signed certs
- * \param path:        Websocket path on server
- * \param host:        Hostname on server
- * \param origin:      Socket origin name
- * \param protocol:    Comma-separated list of protocols being asked for from
- *             the server, or just one.  The server will pick the one it
- *             likes best.
- * \param ietf_version_or_minus_one: -1 to ask to connect using the default, latest
- *             protocol supported, or the specific protocol ordinal
- * \param userdata: Pre-allocated user data
- *
- *     This function creates a connection to a remote server
- */
-LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
-lws_client_connect_extended(struct lws_context *clients, const char *address,
-                           int port, int ssl_connection, const char *path,
-                           const char *host, const char *origin,
-                           const char *protocol, int ietf_version_or_minus_one,
-                           void *userdata) LWS_WARN_DEPRECATED;
-
-/**
- * lws_init_vhost_client_ssl() - also enable client SSL on an existing vhost
- *
- * \param info: client ssl related info
- * \param vhost: which vhost to initialize client ssl operations on
- *
- * You only need to call this if you plan on using SSL client connections on
- * the vhost.  For non-SSL client connections, it's not necessary to call this.
- *
- * The following members of info are used during the call
- *
- *      - options must have LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT set,
- *          otherwise the call does nothing
- *      - provided_client_ssl_ctx must be NULL to get a generated client
- *          ssl context, otherwise you can pass a prepared one in by setting it
- *      - ssl_cipher_list may be NULL or set to the client valid cipher list
- *      - ssl_ca_filepath may be NULL or client cert filepath
- *      - ssl_cert_filepath may be NULL or client cert filepath
- *      - ssl_private_key_filepath may be NULL or client cert private key
- *
- * You must create your vhost explicitly if you want to use this, so you have
- * a pointer to the vhost.  Create the context first with the option flag
- * LWS_SERVER_OPTION_EXPLICIT_VHOSTS and then call lws_create_vhost() with
- * the same info struct.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_init_vhost_client_ssl(const struct lws_context_creation_info *info,
-                         struct lws_vhost *vhost);
-/**
- * lws_http_client_read() - consume waiting received http client data
- *
- * \param wsi: client connection
- * \param buf: pointer to buffer pointer - fill with pointer to your buffer
- * \param len: pointer to chunk length - fill with max length of buffer
- *
- * This is called when the user code is notified client http data has arrived.
- * The user code may choose to delay calling it to consume the data, for example
- * waiting until an onward connection is writeable.
- *
- * For non-chunked connections, up to len bytes of buf are filled with the
- * received content.  len is set to the actual amount filled before return.
- *
- * For chunked connections, the linear buffer content contains the chunking
- * headers and it cannot be passed in one lump.  Instead, this function will
- * call back LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ with in pointing to the
- * chunk start and len set to the chunk length.  There will be as many calls
- * as there are chunks or partial chunks in the buffer.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_http_client_read(struct lws *wsi, char **buf, int *len);
-
-/**
- * lws_http_client_http_response() - get last HTTP response code
- *
- * \param wsi: client connection
- *
- * Returns the last server response code, eg, 200 for client http connections.
- */
-LWS_VISIBLE LWS_EXTERN unsigned int
-lws_http_client_http_response(struct lws *wsi);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_client_http_body_pending(struct lws *wsi, int something_left_to_send);
-
-/**
- * lws_client_http_body_pending() - control if client connection neeeds to send body
- *
- * \param wsi: client connection
- * \param something_left_to_send: nonzero if need to send more body, 0 (default)
- *                             if nothing more to send
- *
- * If you will send payload data with your HTTP client connection, eg, for POST,
- * when you set the related http headers in
- * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER callback you should also call
- * this API with something_left_to_send nonzero, and call
- * lws_callback_on_writable(wsi);
- *
- * After sending the headers, lws will call your callback with
- * LWS_CALLBACK_CLIENT_HTTP_WRITEABLE reason when writable.  You can send the
- * next part of the http body payload, calling lws_callback_on_writable(wsi);
- * if there is more to come, or lws_client_http_body_pending(wsi, 0); to
- * let lws know the last part is sent and the connection can move on.
- */
-
-///@}
-
-/** \defgroup service Built-in service loop entry
- *
- * ##Built-in service loop entry
- *
- * If you're not using libev / libuv, these apis are needed to enter the poll()
- * wait in lws and service any connections with pending events.
- */
-///@{
-
-/**
- * lws_service() - Service any pending websocket activity
- * \param context:     Websocket context
- * \param timeout_ms:  Timeout for poll; 0 means return immediately if nothing needed
- *             service otherwise block and service immediately, returning
- *             after the timeout if nothing needed service.
- *
- *     This function deals with any pending websocket traffic, for three
- *     kinds of event.  It handles these events on both server and client
- *     types of connection the same.
- *
- *     1) Accept new connections to our context's server
- *
- *     2) Call the receive callback for incoming frame data received by
- *         server or client connections.
- *
- *     You need to call this service function periodically to all the above
- *     functions to happen; if your application is single-threaded you can
- *     just call it in your main event loop.
- *
- *     Alternatively you can fork a new process that asynchronously handles
- *     calling this service in a loop.  In that case you are happy if this
- *     call blocks your thread until it needs to take care of something and
- *     would call it with a large nonzero timeout.  Your loop then takes no
- *     CPU while there is nothing happening.
- *
- *     If you are calling it in a single-threaded app, you don't want it to
- *     wait around blocking other things in your loop from happening, so you
- *     would call it with a timeout_ms of 0, so it returns immediately if
- *     nothing is pending, or as soon as it services whatever was pending.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_service(struct lws_context *context, int timeout_ms);
-
-/**
- * lws_service() - Service any pending websocket activity
- *
- * \param context:     Websocket context
- * \param timeout_ms:  Timeout for poll; 0 means return immediately if nothing needed
- *             service otherwise block and service immediately, returning
- *             after the timeout if nothing needed service.
- *
- * Same as lws_service(), but for a specific thread service index.  Only needed
- * if you are spawning multiple service threads.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi);
-
-/**
- * lws_cancel_service_pt() - Cancel servicing of pending socket activity
- *                             on one thread
- * \param wsi: Cancel service on the thread this wsi is serviced by
- *
- *     This function lets a call to lws_service() waiting for a timeout
- *     immediately return.
- *
- *     It works by creating a phony event and then swallowing it silently.
- *
- *     The reason it may be needed is when waiting in poll(), changes to
- *     the event masks are ignored by the OS until poll() is reentered.  This
- *     lets you halt the poll() wait and make the reentry happen immediately
- *     instead of having the wait out the rest of the poll timeout.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_cancel_service_pt(struct lws *wsi);
-
-/**
- * lws_cancel_service() - Cancel wait for new pending socket activity
- * \param context:     Websocket context
- *
- *     This function let a call to lws_service() waiting for a timeout
- *     immediately return.
- *
- *     What it basically does is provide a fake event that will be swallowed,
- *     so the wait in poll() is ended.  That's useful because poll() doesn't
- *     attend to changes in POLLIN/OUT/ERR until it re-enters the wait.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_cancel_service(struct lws_context *context);
-
-/**
- * lws_service_fd() - Service polled socket with something waiting
- * \param context:     Websocket context
- * \param pollfd:      The pollfd entry describing the socket fd and which events
- *             happened, or NULL to tell lws to do only timeout servicing.
- *
- * This function takes a pollfd that has POLLIN or POLLOUT activity and
- * services it according to the state of the associated
- * struct lws.
- *
- * The one call deals with all "service" that might happen on a socket
- * including listen accepts, http files as well as websocket protocol.
- *
- * If a pollfd says it has something, you can just pass it to
- * lws_service_fd() whether it is a socket handled by lws or not.
- * If it sees it is a lws socket, the traffic will be handled and
- * pollfd->revents will be zeroed now.
- *
- * If the socket is foreign to lws, it leaves revents alone.  So you can
- * see if you should service yourself by checking the pollfd revents
- * after letting lws try to service it.
- *
- * You should also call this with pollfd = NULL to just allow the
- * once-per-second global timeout checks; if less than a second since the last
- * check it returns immediately then.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd);
-
-/**
- * lws_service_fd_tsi() - Service polled socket in specific service thread
- * \param context:     Websocket context
- * \param pollfd:      The pollfd entry describing the socket fd and which events
- *             happened.
- * \param tsi: thread service index
- *
- * Same as lws_service_fd() but used with multiple service threads
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd,
-                  int tsi);
-
-/**
- * lws_service_adjust_timeout() - Check for any connection needing forced service
- * \param context:     Websocket context
- * \param timeout_ms:  The original poll timeout value.  You can just set this
- *                     to 1 if you don't really have a poll timeout.
- * \param tsi: thread service index
- *
- * Under some conditions connections may need service even though there is no
- * pending network action on them, this is "forced service".  For default
- * poll() and libuv / libev, the library takes care of calling this and
- * dealing with it for you.  But for external poll() integration, you need
- * access to the apis.
- *
- * If anybody needs "forced service", returned timeout is zero.  In that case,
- * you can call lws_service_tsi() with a timeout of -1 to only service
- * guys who need forced service.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi);
-
-/* Backwards compatibility */
-#define lws_plat_service_tsi lws_service_tsi
-
-LWS_VISIBLE LWS_EXTERN int
-lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd);
-
-///@}
-
-/*! \defgroup http HTTP
-
-    Modules related to handling HTTP
-*/
-//@{
-
-/*! \defgroup httpft HTTP File transfer
- * \ingroup http
-
-    APIs for sending local files in response to HTTP requests
-*/
-//@{
-
-/**
- * lws_get_mimetype() - Determine mimetype to use from filename
- *
- * \param file:                filename
- * \param m:           NULL, or mount context
- *
- * This uses a canned list of known filetypes first, if no match and m is
- * non-NULL, then tries a list of per-mount file suffix to mimtype mappings.
- *
- * Returns either NULL or a pointer to the mimetype matching the file.
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_get_mimetype(const char *file, const struct lws_http_mount *m);
-
-/**
- * lws_serve_http_file() - Send a file back to the client using http
- * \param wsi:         Websocket instance (available from user callback)
- * \param file:                The file to issue over http
- * \param content_type:        The http content type, eg, text/html
- * \param other_headers:       NULL or pointer to header string
- * \param other_headers_len:   length of the other headers if non-NULL
- *
- *     This function is intended to be called from the callback in response
- *     to http requests from the client.  It allows the callback to issue
- *     local files down the http link in a single step.
- *
- *     Returning <0 indicates error and the wsi should be closed.  Returning
- *     >0 indicates the file was completely sent and
- *     lws_http_transaction_completed() called on the wsi (and close if != 0)
- *     ==0 indicates the file transfer is started and needs more service later,
- *     the wsi should be left alone.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
-                   const char *other_headers, int other_headers_len);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_serve_http_file_fragment(struct lws *wsi);
-//@}
-
-/*! \defgroup html-chunked-substitution HTML Chunked Substitution
- * \ingroup http
- *
- * ##HTML chunked Substitution
- *
- * APIs for receiving chunks of text, replacing a set of variable names via
- * a callback, and then prepending and appending HTML chunked encoding
- * headers.
- */
-//@{
-
-enum http_status {
-       HTTP_STATUS_OK                                          = 200,
-       HTTP_STATUS_NO_CONTENT                                  = 204,
-       HTTP_STATUS_PARTIAL_CONTENT                             = 206,
-
-       HTTP_STATUS_MOVED_PERMANENTLY                           = 301,
-       HTTP_STATUS_FOUND                                       = 302,
-       HTTP_STATUS_SEE_OTHER                                   = 303,
-       HTTP_STATUS_NOT_MODIFIED                                = 304,
-
-       HTTP_STATUS_BAD_REQUEST                                 = 400,
-       HTTP_STATUS_UNAUTHORIZED,
-       HTTP_STATUS_PAYMENT_REQUIRED,
-       HTTP_STATUS_FORBIDDEN,
-       HTTP_STATUS_NOT_FOUND,
-       HTTP_STATUS_METHOD_NOT_ALLOWED,
-       HTTP_STATUS_NOT_ACCEPTABLE,
-       HTTP_STATUS_PROXY_AUTH_REQUIRED,
-       HTTP_STATUS_REQUEST_TIMEOUT,
-       HTTP_STATUS_CONFLICT,
-       HTTP_STATUS_GONE,
-       HTTP_STATUS_LENGTH_REQUIRED,
-       HTTP_STATUS_PRECONDITION_FAILED,
-       HTTP_STATUS_REQ_ENTITY_TOO_LARGE,
-       HTTP_STATUS_REQ_URI_TOO_LONG,
-       HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
-       HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE,
-       HTTP_STATUS_EXPECTATION_FAILED,
-
-       HTTP_STATUS_INTERNAL_SERVER_ERROR                       = 500,
-       HTTP_STATUS_NOT_IMPLEMENTED,
-       HTTP_STATUS_BAD_GATEWAY,
-       HTTP_STATUS_SERVICE_UNAVAILABLE,
-       HTTP_STATUS_GATEWAY_TIMEOUT,
-       HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
-};
-
-struct lws_process_html_args {
-       char *p; /**< pointer to the buffer containing the data */
-       int len; /**< length of the original data at p */
-       int max_len; /**< maximum length we can grow the data to */
-       int final; /**< set if this is the last chunk of the file */
-};
-
-typedef const char *(*lws_process_html_state_cb)(void *data, int index);
-
-struct lws_process_html_state {
-       char *start; /**< pointer to start of match */
-       char swallow[16]; /**< matched character buffer */
-       int pos; /**< position in match */
-       void *data; /**< opaque pointer */
-       const char * const *vars; /**< list of variable names */
-       int count_vars; /**< count of variable names */
-
-       lws_process_html_state_cb replace; /**< called on match to perform substitution */
-};
-
-/*! lws_chunked_html_process() - generic chunked substitution
- * \param args: buffer to process using chunked encoding
- * \param s: current processing state
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_chunked_html_process(struct lws_process_html_args *args,
-                        struct lws_process_html_state *s);
-//@}
-
-/** \defgroup HTTP-headers-read HTTP headers: read
- * \ingroup http
- *
- * ##HTTP header releated functions
- *
- *  In lws the client http headers are temporarily stored in a pool, only for the
- *  duration of the http part of the handshake.  It's because in most cases,
- *  the header content is ignored for the whole rest of the connection lifetime
- *  and would then just be taking up space needlessly.
- *
- *  During LWS_CALLBACK_HTTP when the URI path is delivered is the last time
- *  the http headers are still allocated, you can use these apis then to
- *  look at and copy out interesting header content (cookies, etc)
- *
- *  Notice that the header total length reported does not include a terminating
- *  '\0', however you must allocate for it when using the _copy apis.  So the
- *  length reported for a header containing "123" is 3, but you must provide
- *  a buffer of length 4 so that "123\0" may be copied into it, or the copy
- *  will fail with a nonzero return code.
- *
- *  In the special case of URL arguments, like ?x=1&y=2, the arguments are
- *  stored in a token named for the method, eg,  WSI_TOKEN_GET_URI if it
- *  was a GET or WSI_TOKEN_POST_URI if POST.  You can check the total
- *  length to confirm the method.
- *
- *  For URL arguments, each argument is stored urldecoded in a "fragment", so
- *  you can use the fragment-aware api lws_hdr_copy_fragment() to access each
- *  argument in turn: the fragments contain urldecoded strings like x=1 or y=2.
- *
- *  As a convenience, lws has an api that will find the fragment with a
- *  given name= part, lws_get_urlarg_by_name().
- */
-///@{
-
-/** struct lws_tokens
- * you need these to look at headers that have been parsed if using the
- * LWS_CALLBACK_FILTER_CONNECTION callback.  If a header from the enum
- * list below is absent, .token = NULL and token_len = 0.  Otherwise .token
- * points to .token_len chars containing that header content.
- */
-struct lws_tokens {
-       char *token; /**< pointer to start of the token */
-       int token_len; /**< length of the token's value */
-};
-
-/* enum lws_token_indexes
- * these have to be kept in sync with lextable.h / minilex.c
- *
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-enum lws_token_indexes {
-       WSI_TOKEN_GET_URI                                       =  0,
-       WSI_TOKEN_POST_URI                                      =  1,
-       WSI_TOKEN_OPTIONS_URI                                   =  2,
-       WSI_TOKEN_HOST                                          =  3,
-       WSI_TOKEN_CONNECTION                                    =  4,
-       WSI_TOKEN_UPGRADE                                       =  5,
-       WSI_TOKEN_ORIGIN                                        =  6,
-       WSI_TOKEN_DRAFT                                         =  7,
-       WSI_TOKEN_CHALLENGE                                     =  8,
-       WSI_TOKEN_EXTENSIONS                                    =  9,
-       WSI_TOKEN_KEY1                                          = 10,
-       WSI_TOKEN_KEY2                                          = 11,
-       WSI_TOKEN_PROTOCOL                                      = 12,
-       WSI_TOKEN_ACCEPT                                        = 13,
-       WSI_TOKEN_NONCE                                         = 14,
-       WSI_TOKEN_HTTP                                          = 15,
-       WSI_TOKEN_HTTP2_SETTINGS                                = 16,
-       WSI_TOKEN_HTTP_ACCEPT                                   = 17,
-       WSI_TOKEN_HTTP_AC_REQUEST_HEADERS                       = 18,
-       WSI_TOKEN_HTTP_IF_MODIFIED_SINCE                        = 19,
-       WSI_TOKEN_HTTP_IF_NONE_MATCH                            = 20,
-       WSI_TOKEN_HTTP_ACCEPT_ENCODING                          = 21,
-       WSI_TOKEN_HTTP_ACCEPT_LANGUAGE                          = 22,
-       WSI_TOKEN_HTTP_PRAGMA                                   = 23,
-       WSI_TOKEN_HTTP_CACHE_CONTROL                            = 24,
-       WSI_TOKEN_HTTP_AUTHORIZATION                            = 25,
-       WSI_TOKEN_HTTP_COOKIE                                   = 26,
-       WSI_TOKEN_HTTP_CONTENT_LENGTH                           = 27,
-       WSI_TOKEN_HTTP_CONTENT_TYPE                             = 28,
-       WSI_TOKEN_HTTP_DATE                                     = 29,
-       WSI_TOKEN_HTTP_RANGE                                    = 30,
-       WSI_TOKEN_HTTP_REFERER                                  = 31,
-       WSI_TOKEN_KEY                                           = 32,
-       WSI_TOKEN_VERSION                                       = 33,
-       WSI_TOKEN_SWORIGIN                                      = 34,
-
-       WSI_TOKEN_HTTP_COLON_AUTHORITY                          = 35,
-       WSI_TOKEN_HTTP_COLON_METHOD                             = 36,
-       WSI_TOKEN_HTTP_COLON_PATH                               = 37,
-       WSI_TOKEN_HTTP_COLON_SCHEME                             = 38,
-       WSI_TOKEN_HTTP_COLON_STATUS                             = 39,
-
-       WSI_TOKEN_HTTP_ACCEPT_CHARSET                           = 40,
-       WSI_TOKEN_HTTP_ACCEPT_RANGES                            = 41,
-       WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN              = 42,
-       WSI_TOKEN_HTTP_AGE                                      = 43,
-       WSI_TOKEN_HTTP_ALLOW                                    = 44,
-       WSI_TOKEN_HTTP_CONTENT_DISPOSITION                      = 45,
-       WSI_TOKEN_HTTP_CONTENT_ENCODING                         = 46,
-       WSI_TOKEN_HTTP_CONTENT_LANGUAGE                         = 47,
-       WSI_TOKEN_HTTP_CONTENT_LOCATION                         = 48,
-       WSI_TOKEN_HTTP_CONTENT_RANGE                            = 49,
-       WSI_TOKEN_HTTP_ETAG                                     = 50,
-       WSI_TOKEN_HTTP_EXPECT                                   = 51,
-       WSI_TOKEN_HTTP_EXPIRES                                  = 52,
-       WSI_TOKEN_HTTP_FROM                                     = 53,
-       WSI_TOKEN_HTTP_IF_MATCH                                 = 54,
-       WSI_TOKEN_HTTP_IF_RANGE                                 = 55,
-       WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE                      = 56,
-       WSI_TOKEN_HTTP_LAST_MODIFIED                            = 57,
-       WSI_TOKEN_HTTP_LINK                                     = 58,
-       WSI_TOKEN_HTTP_LOCATION                                 = 59,
-       WSI_TOKEN_HTTP_MAX_FORWARDS                             = 60,
-       WSI_TOKEN_HTTP_PROXY_AUTHENTICATE                       = 61,
-       WSI_TOKEN_HTTP_PROXY_AUTHORIZATION                      = 62,
-       WSI_TOKEN_HTTP_REFRESH                                  = 63,
-       WSI_TOKEN_HTTP_RETRY_AFTER                              = 64,
-       WSI_TOKEN_HTTP_SERVER                                   = 65,
-       WSI_TOKEN_HTTP_SET_COOKIE                               = 66,
-       WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY                = 67,
-       WSI_TOKEN_HTTP_TRANSFER_ENCODING                        = 68,
-       WSI_TOKEN_HTTP_USER_AGENT                               = 69,
-       WSI_TOKEN_HTTP_VARY                                     = 70,
-       WSI_TOKEN_HTTP_VIA                                      = 71,
-       WSI_TOKEN_HTTP_WWW_AUTHENTICATE                         = 72,
-
-       WSI_TOKEN_PATCH_URI                                     = 73,
-       WSI_TOKEN_PUT_URI                                       = 74,
-       WSI_TOKEN_DELETE_URI                                    = 75,
-
-       WSI_TOKEN_HTTP_URI_ARGS                                 = 76,
-       WSI_TOKEN_PROXY                                         = 77,
-       WSI_TOKEN_HTTP_X_REAL_IP                                = 78,
-       WSI_TOKEN_HTTP1_0                                       = 79,
-       WSI_TOKEN_X_FORWARDED_FOR                               = 80,
-       WSI_TOKEN_CONNECT                                       = 81,
-       /****** add new things just above ---^ ******/
-
-       /* use token storage to stash these internally, not for
-        * user use */
-
-       _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
-       _WSI_TOKEN_CLIENT_PEER_ADDRESS,
-       _WSI_TOKEN_CLIENT_URI,
-       _WSI_TOKEN_CLIENT_HOST,
-       _WSI_TOKEN_CLIENT_ORIGIN,
-       _WSI_TOKEN_CLIENT_METHOD,
-       _WSI_TOKEN_CLIENT_IFACE,
-
-       /* always last real token index*/
-       WSI_TOKEN_COUNT,
-
-       /* parser state additions, no storage associated */
-       WSI_TOKEN_NAME_PART,
-       WSI_TOKEN_SKIPPING,
-       WSI_TOKEN_SKIPPING_SAW_CR,
-       WSI_PARSING_COMPLETE,
-       WSI_INIT_TOKEN_MUXURL,
-};
-
-struct lws_token_limits {
-       unsigned short token_limit[WSI_TOKEN_COUNT]; /**< max chars for this token */
-};
-
-/**
- * lws_token_to_string() - returns a textual representation of a hdr token index
- *
- * \param: token index
- */
-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
- *             terminating '\0'
- *
- * \param wsi: websocket connection
- * \param h: which header index we are interested in
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h);
-
-/**
- * lws_hdr_fragment_length: report length of a single fragment of a header
- *             The returned length does not include the space for a
- *             terminating '\0'
- *
- * \param wsi: websocket connection
- * \param h: which header index we are interested in
- * \param frag_idx: which fragment of h we want to get the length of
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx);
-
-/**
- * lws_hdr_copy() - copy a single fragment of the given header to a buffer
- *             The buffer length len must include space for an additional
- *             terminating '\0', or it will fail returning -1.
- *
- * \param wsi: websocket connection
- * \param dest: destination buffer
- * \param len: length of destination buffer
- * \param h: which header index we are interested in
- *
- * copies the whole, aggregated header, even if it was delivered in
- * several actual headers piece by piece
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_hdr_copy(struct lws *wsi, char *dest, int len, enum lws_token_indexes h);
-
-/**
- * lws_hdr_copy_fragment() - copy a single fragment of the given header to a buffer
- *             The buffer length len must include space for an additional
- *             terminating '\0', or it will fail returning -1.
- *             If the requested fragment index is not present, it fails
- *             returning -1.
- *
- * \param wsi: websocket connection
- * \param dest: destination buffer
- * \param len: length of destination buffer
- * \param h: which header index we are interested in
- * \param frag_idx: which fragment of h we want to copy
- *
- * Normally this is only useful
- * to parse URI arguments like ?x=1&y=2, token index WSI_TOKEN_HTTP_URI_ARGS
- * fragment 0 will contain "x=1" and fragment 1 "y=2"
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_hdr_copy_fragment(struct lws *wsi, char *dest, int len,
-                     enum lws_token_indexes h, int frag_idx);
-
-/**
- * lws_get_urlarg_by_name() - return pointer to arg value if present
- * \param wsi: the connection to check
- * \param name: the arg name, like "token="
- * \param buf: the buffer to receive the urlarg (including the name= part)
- * \param len: the length of the buffer to receive the urlarg
- *
- *     Returns NULL if not found or a pointer inside buf to just after the
- *     name= part.
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len);
-///@}
-
-/*! \defgroup HTTP-headers-create HTTP headers: create
- *
- * ## HTTP headers: Create
- *
- * These apis allow you to create HTTP response headers in a way compatible with
- * both HTTP/1.x and HTTP/2.
- *
- * They each append to a buffer taking care about the buffer end, which is
- * passed in as a pointer.  When data is written to the buffer, the current
- * position p is updated accordingly.
- *
- * All of these apis are LWS_WARN_UNUSED_RESULT as they can run out of space
- * and fail with nonzero return.
- */
-///@{
-
-#define LWSAHH_CODE_MASK                       ((1 << 16) - 1)
-#define LWSAHH_FLAG_NO_SERVER_NAME             (1 << 30)
-
-/**
- * lws_add_http_header_status() - add the HTTP response status code
- *
- * \param wsi: the connection to check
- * \param code: an HTTP code like 200, 404 etc (see enum http_status)
- * \param p: pointer to current position in buffer pointer
- * \param end: pointer to end of buffer
- *
- * Adds the initial response code, so should be called first.
- *
- * Code may additionally take OR'd flags:
- *
- *    LWSAHH_FLAG_NO_SERVER_NAME:  don't apply server name header this time
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_add_http_header_status(struct lws *wsi,
-                          unsigned int code, unsigned char **p,
-                          unsigned char *end);
-/**
- * lws_add_http_header_by_name() - append named header and value
- *
- * \param wsi: the connection to check
- * \param name: the hdr name, like "my-header"
- * \param value: the value after the = for this header
- * \param length: the length of the value
- * \param p: pointer to current position in buffer pointer
- * \param end: pointer to end of buffer
- *
- * Appends name: value to the headers
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
-                           const unsigned char *value, int length,
-                           unsigned char **p, unsigned char *end);
-/**
- * lws_add_http_header_by_token() - append given header and value
- *
- * \param wsi: the connection to check
- * \param token: the token index for the hdr
- * \param value: the value after the = for this header
- * \param length: the length of the value
- * \param p: pointer to current position in buffer pointer
- * \param end: pointer to end of buffer
- *
- * Appends name=value to the headers, but is able to take advantage of better
- * HTTP/2 coding mechanisms where possible.
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
-                            const unsigned char *value, int length,
-                            unsigned char **p, unsigned char *end);
-/**
- * lws_add_http_header_content_length() - append content-length helper
- *
- * \param wsi: the connection to check
- * \param content_length: the content length to use
- * \param p: pointer to current position in buffer pointer
- * \param end: pointer to end of buffer
- *
- * Appends content-length: content_length to the headers
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_add_http_header_content_length(struct lws *wsi,
-                                  lws_filepos_t content_length,
-                                  unsigned char **p, unsigned char *end);
-/**
- * lws_finalize_http_header() - terminate header block
- *
- * \param wsi: the connection to check
- * \param p: pointer to current position in buffer pointer
- * \param end: pointer to end of buffer
- *
- * Indicates no more headers will be added
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_finalize_http_header(struct lws *wsi, unsigned char **p,
-                        unsigned char *end);
-///@}
-
-/** \defgroup form-parsing  Form Parsing
- * \ingroup http
- * ##POSTed form parsing functions
- *
- * These lws_spa (stateful post arguments) apis let you parse and urldecode
- * POSTed form arguments, both using simple urlencoded and multipart transfer
- * encoding.
- *
- * It's capable of handling file uploads as well a named input parsing,
- * and the apis are the same for both form upload styles.
- *
- * You feed it a list of parameter names and it creates pointers to the
- * urldecoded arguments: file upload parameters pass the file data in chunks to
- * a user-supplied callback as they come.
- *
- * Since it's stateful, it handles the incoming data needing more than one
- * POST_BODY callback and has no limit on uploaded file size.
- */
-///@{
-
-/** enum lws_spa_fileupload_states */
-enum lws_spa_fileupload_states {
-       LWS_UFS_CONTENT,
-       /**< a chunk of file content has arrived */
-       LWS_UFS_FINAL_CONTENT,
-       /**< the last chunk (possibly zero length) of file content has arrived */
-       LWS_UFS_OPEN
-       /**< a new file is starting to arrive */
-};
-
-/**
- * lws_spa_fileupload_cb() - callback to receive file upload data
- *
- * \param data: opt_data pointer set in lws_spa_create
- * \param name: name of the form field being uploaded
- * \param filename: original filename from client
- * \param buf: start of data to receive
- * \param len: length of data to receive
- * \param state: information about how this call relates to file
- *
- * Notice name and filename shouldn't be trusted, as they are passed from
- * HTTP provided by the client.
- */
-typedef int (*lws_spa_fileupload_cb)(void *data, const char *name,
-                       const char *filename, char *buf, int len,
-                       enum lws_spa_fileupload_states state);
-
-/** struct lws_spa - opaque urldecode parser capable of handling multipart
- *                     and file uploads */
-struct lws_spa;
-
-/**
- * lws_spa_create() - create urldecode parser
- *
- * \param wsi: lws connection (used to find Content Type)
- * \param param_names: array of form parameter names, like "username"
- * \param count_params: count of param_names
- * \param max_storage: total amount of form parameter values we can store
- * \param opt_cb: NULL, or callback to receive file upload data.
- * \param opt_data: NULL, or user pointer provided to opt_cb.
- *
- * Creates a urldecode parser and initializes it.
- *
- * opt_cb can be NULL if you just want normal name=value parsing, however
- * if one or more entries in your form are bulk data (file transfer), you
- * can provide this callback and filter on the name callback parameter to
- * treat that urldecoded data separately.  The callback should return -1
- * in case of fatal error, and 0 if OK.
- */
-LWS_VISIBLE LWS_EXTERN struct lws_spa *
-lws_spa_create(struct lws *wsi, const char * const *param_names,
-              int count_params, int max_storage, lws_spa_fileupload_cb opt_cb,
-              void *opt_data);
-
-/**
- * lws_spa_process() - parses a chunk of input data
- *
- * \param spa: the parser object previously created
- * \param in: incoming, urlencoded data
- * \param len: count of bytes valid at \param in
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_process(struct lws_spa *spa, const char *in, int len);
-
-/**
- * lws_spa_finalize() - indicate incoming data completed
- *
- * \param spa: the parser object previously created
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_finalize(struct lws_spa *spa);
-
-/**
- * lws_spa_get_length() - return length of parameter value
- *
- * \param spa: the parser object previously created
- * \param n: parameter ordinal to return length of value for
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_get_length(struct lws_spa *spa, int n);
-
-/**
- * lws_spa_get_string() - return pointer to parameter value
- * \param spa: the parser object previously created
- * \param n: parameter ordinal to return pointer to value for
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_spa_get_string(struct lws_spa *spa, int n);
-
-/**
- * lws_spa_destroy() - destroy parser object
- *
- * \param spa: the parser object previously created
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_destroy(struct lws_spa *spa);
-///@}
-
-/*! \defgroup urlendec Urlencode and Urldecode
- * \ingroup http
- *
- * ##HTML chunked Substitution
- *
- * APIs for receiving chunks of text, replacing a set of variable names via
- * a callback, and then prepending and appending HTML chunked encoding
- * headers.
- */
-//@{
-
-/**
- * lws_urlencode() - like strncpy but with urlencoding
- *
- * \param escaped: output buffer
- * \param string: input buffer ('/0' terminated)
- * \param len: output buffer max length
- *
- * Because urlencoding expands the output string, it's not
- * possible to do it in-place, ie, with escaped == string
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_urlencode(char *escaped, const char *string, int len);
-
-/*
- * URLDECODE 1 / 2
- *
- * This simple urldecode only operates until the first '\0' and requires the
- * data to exist all at once
- */
-/**
- * lws_urldecode() - like strncpy but with urldecoding
- *
- * \param string: output buffer
- * \param escaped: input buffer ('\0' terminated)
- * \param len: output buffer max length
- *
- * This is only useful for '\0' terminated strings
- *
- * Since urldecoding only shrinks the output string, it is possible to
- * do it in-place, ie, string == escaped
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_urldecode(char *string, const char *escaped, int len);
-///@}
-/**
- * lws_return_http_status() - Return simple http status
- * \param wsi:         Websocket instance (available from user callback)
- * \param code:                Status index, eg, 404
- * \param html_body:           User-readable HTML description < 1KB, or NULL
- *
- *     Helper to report HTTP errors back to the client cleanly and
- *     consistently
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_return_http_status(struct lws *wsi, unsigned int code,
-                      const char *html_body);
-
-/**
- * lws_http_redirect() - write http redirect into buffer
- *
- * \param wsi: websocket connection
- * \param code:        HTTP response code (eg, 301)
- * \param loc: where to redirect to
- * \param len: length of loc
- * \param p:   pointer current position in buffer (updated as we write)
- * \param end: pointer to end of buffer
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len,
-                 unsigned char **p, unsigned char *end);
-
-/**
- * lws_http_transaction_completed() - wait for new http transaction or close
- * \param wsi: websocket connection
- *
- *     Returns 1 if the HTTP connection must close now
- *     Returns 0 and resets connection to wait for new HTTP header /
- *       transaction if possible
- */
-LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_http_transaction_completed(struct lws *wsi);
-///@}
-
-/*! \defgroup pur Sanitize / purify SQL and JSON helpers
- *
- * ##Sanitize / purify SQL and JSON helpers
- *
- * APIs for escaping untrusted JSON and SQL safely before use
- */
-//@{
-
-/**
- * lws_sql_purify() - like strncpy but with escaping for sql quotes
- *
- * \param escaped: output buffer
- * \param string: input buffer ('/0' terminated)
- * \param len: output buffer max length
- *
- * Because escaping expands the output string, it's not
- * possible to do it in-place, ie, with escaped == string
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_sql_purify(char *escaped, const char *string, int len);
-
-/**
- * lws_json_purify() - like strncpy but with escaping for json chars
- *
- * \param escaped: output buffer
- * \param string: input buffer ('/0' terminated)
- * \param len: output buffer max length
- *
- * Because escaping expands the output string, it's not
- * possible to do it in-place, ie, with escaped == string
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_json_purify(char *escaped, const char *string, int len);
-///@}
-
-/*! \defgroup ev libev helpers
- *
- * ##libev helpers
- *
- * APIs specific to libev event loop itegration
- */
-///@{
-
-#ifdef LWS_USE_LIBEV
-typedef void (lws_ev_signal_cb_t)(EV_P_ struct ev_signal *w, int revents);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_ev_sigint_cfg(struct lws_context *context, int use_ev_sigint,
-                 lws_ev_signal_cb_t *cb);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_ev_initloop(struct lws_context *context, struct ev_loop *loop, int tsi);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents);
-#endif /* LWS_USE_LIBEV */
-
-///@}
-
-/*! \defgroup uv libuv helpers
- *
- * ##libuv helpers
- *
- * APIs specific to libuv event loop itegration
- */
-///@{
-#ifdef LWS_USE_LIBUV
-LWS_VISIBLE LWS_EXTERN int
-lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint,
-                 uv_signal_cb cb);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_libuv_run(const struct lws_context *context, int tsi);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_libuv_stop(struct lws_context *context);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_libuv_stop_without_kill(const struct lws_context *context, int tsi);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi);
-
-LWS_VISIBLE LWS_EXTERN uv_loop_t *
-lws_uv_getloop(struct lws_context *context, int tsi);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_uv_sigint_cb(uv_signal_t *watcher, int signum);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_close_all_handles_in_loop(uv_loop_t *loop);
-#endif /* LWS_USE_LIBUV */
-///@}
-
-/*! \defgroup event libevent helpers
- *
- * ##libevent helpers
- *
- * APIs specific to libevent event loop itegration
- */
-///@{
-
-#ifdef LWS_USE_LIBEVENT
-typedef void (lws_event_signal_cb_t) (evutil_socket_t sock_fd, short revents,
-                 void *ctx);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_event_sigint_cfg(struct lws_context *context, int use_event_sigint,
-                 lws_event_signal_cb_t cb);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_event_initloop(struct lws_context *context, struct event_base *loop,
-                 int tsi);
-
-LWS_VISIBLE LWS_EXTERN void
-lws_event_sigint_cb(evutil_socket_t sock_fd, short revents,
-                 void *ctx);
-#endif /* LWS_USE_LIBEVENT */
-
-///@}
-
-/*! \defgroup timeout Connection timeouts
-
-    APIs related to setting connection timeouts
-*/
-//@{
-
-/*
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-enum pending_timeout {
-       NO_PENDING_TIMEOUT                                      =  0,
-       PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE                 =  1,
-       PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE               =  2,
-       PENDING_TIMEOUT_ESTABLISH_WITH_SERVER                   =  3,
-       PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE                =  4,
-       PENDING_TIMEOUT_AWAITING_PING                           =  5,
-       PENDING_TIMEOUT_CLOSE_ACK                               =  6,
-       PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE     =  7,
-       PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE                   =  8,
-       PENDING_TIMEOUT_SSL_ACCEPT                              =  9,
-       PENDING_TIMEOUT_HTTP_CONTENT                            = 10,
-       PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND                 = 11,
-       PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE                  = 12,
-       PENDING_TIMEOUT_SHUTDOWN_FLUSH                          = 13,
-       PENDING_TIMEOUT_CGI                                     = 14,
-       PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE                     = 15,
-       PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING                 = 16,
-       PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG                  = 17,
-       PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD                    = 18,
-       PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY           = 19,
-       PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY            = 20,
-       PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY               = 21,
-       PENDING_TIMEOUT_KILLED_BY_SSL_INFO                      = 22,
-       PENDING_TIMEOUT_KILLED_BY_PARENT                        = 23,
-       PENDING_TIMEOUT_CLOSE_SEND                              = 24,
-       PENDING_TIMEOUT_HOLDING_AH                              = 25,
-
-       /****** add new things just above ---^ ******/
-};
-
-#define LWS_TO_KILL_ASYNC -1
-/**< If LWS_TO_KILL_ASYNC is given as the timeout sec in a lws_set_timeout()
- * call, then the connection is marked to be killed at the next timeout
- * check.  This is how you should force-close the wsi being serviced if
- * you are doing it outside the callback (where you should close by nonzero
- * return).
- */
-#define LWS_TO_KILL_SYNC -2
-/**< If LWS_TO_KILL_SYNC is given as the timeout sec in a lws_set_timeout()
- * call, then the connection is closed before returning (which may delete
- * the wsi).  This should only be used where the wsi being closed is not the
- * wsi currently being serviced.
- */
-/**
- * lws_set_timeout() - marks the wsi as subject to a timeout
- *
- * You will not need this unless you are doing something special
- *
- * \param wsi: Websocket connection instance
- * \param reason:      timeout reason
- * \param secs:        how many seconds.  You may set to LWS_TO_KILL_ASYNC to
- *             force the connection to timeout at the next opportunity, or
- *             LWS_TO_KILL_SYNC to close it synchronously if you know the
- *             wsi is not the one currently being serviced.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs);
-///@}
-
-/*! \defgroup sending-data Sending data
-
-    APIs related to writing data on a connection
-*/
-//@{
-#if !defined(LWS_SIZEOFPTR)
-#define LWS_SIZEOFPTR (sizeof (void *))
-#endif
-#if !defined(u_int64_t)
-#define u_int64_t unsigned long long
-#endif
-
-#if defined(__x86_64__)
-#define _LWS_PAD_SIZE 16       /* Intel recommended for best performance */
-#else
-#define _LWS_PAD_SIZE LWS_SIZEOFPTR   /* Size of a pointer on the target arch */
-#endif
-#define _LWS_PAD(n) (((n) % _LWS_PAD_SIZE) ? \
-               ((n) + (_LWS_PAD_SIZE - ((n) % _LWS_PAD_SIZE))) : (n))
-/* last 2 is for lws-meta */
-#define LWS_PRE _LWS_PAD(4 + 10 + 2)
-/* used prior to 1.7 and retained for backward compatibility */
-#define LWS_SEND_BUFFER_PRE_PADDING LWS_PRE
-#define LWS_SEND_BUFFER_POST_PADDING 0
-
-/*
- * NOTE: These public enums are part of the abi.  If you want to add one,
- * add it at where specified so existing users are unaffected.
- */
-enum lws_write_protocol {
-       LWS_WRITE_TEXT                                          = 0,
-       /**< Send a ws TEXT message,the pointer must have LWS_PRE valid
-        * memory behind it.  The receiver expects only valid utf-8 in the
-        * payload */
-       LWS_WRITE_BINARY                                        = 1,
-       /**< Send a ws BINARY message, the pointer must have LWS_PRE valid
-        * memory behind it.  Any sequence of bytes is valid */
-       LWS_WRITE_CONTINUATION                                  = 2,
-       /**< Continue a previous ws message, the pointer must have LWS_PRE valid
-        * memory behind it */
-       LWS_WRITE_HTTP                                          = 3,
-       /**< Send HTTP content */
-
-       /* LWS_WRITE_CLOSE is handled by lws_close_reason() */
-       LWS_WRITE_PING                                          = 5,
-       LWS_WRITE_PONG                                          = 6,
-
-       /* Same as write_http but we know this write ends the transaction */
-       LWS_WRITE_HTTP_FINAL                                    = 7,
-
-       /* HTTP2 */
-
-       LWS_WRITE_HTTP_HEADERS                                  = 8,
-       /**< Send http headers (http2 encodes this payload and LWS_WRITE_HTTP
-        * payload differently, http 1.x links also handle this correctly. so
-        * to be compatible with both in the future,header response part should
-        * be sent using this regardless of http version expected)
-        */
-
-       /****** add new things just above ---^ ******/
-
-       /* flags */
-
-       LWS_WRITE_NO_FIN = 0x40,
-       /**< This part of the message is not the end of the message */
-
-       LWS_WRITE_CLIENT_IGNORE_XOR_MASK = 0x80
-       /**< client packet payload goes out on wire unmunged
-        * only useful for security tests since normal servers cannot
-        * decode the content if used */
-};
-
-/* used with LWS_CALLBACK_CHILD_WRITE_VIA_PARENT */
-
-struct lws_write_passthru {
-       struct lws *wsi;
-       unsigned char *buf;
-       size_t len;
-       enum lws_write_protocol wp;
-};
-
-
-/**
- * lws_write() - Apply protocol then write data to client
- * \param wsi: Websocket instance (available from user callback)
- * \param buf: The data to send.  For data being sent on a websocket
- *             connection (ie, not default http), this buffer MUST have
- *             LWS_PRE bytes valid BEFORE the pointer.
- *             This is so the protocol header data can be added in-situ.
- * \param len: Count of the data bytes in the payload starting from buf
- * \param protocol:    Use LWS_WRITE_HTTP to reply to an http connection, and one
- *             of LWS_WRITE_BINARY or LWS_WRITE_TEXT to send appropriate
- *             data on a websockets connection.  Remember to allow the extra
- *             bytes before and after buf if LWS_WRITE_BINARY or LWS_WRITE_TEXT
- *             are used.
- *
- *     This function provides the way to issue data back to the client
- *     for both http and websocket protocols.
- *
- * IMPORTANT NOTICE!
- *
- * When sending with websocket protocol
- *
- * LWS_WRITE_TEXT,
- * LWS_WRITE_BINARY,
- * LWS_WRITE_CONTINUATION,
- * LWS_WRITE_PING,
- * LWS_WRITE_PONG
- *
- * the send buffer has to have LWS_PRE bytes valid BEFORE
- * the buffer pointer you pass to lws_write().
- *
- * This allows us to add protocol info before and after the data, and send as
- * one packet on the network without payload copying, for maximum efficiency.
- *
- * So for example you need this kind of code to use lws_write with a
- * 128-byte payload
- *
- *   char buf[LWS_PRE + 128];
- *
- *   // fill your part of the buffer... for example here it's all zeros
- *   memset(&buf[LWS_PRE], 0, 128);
- *
- *   lws_write(wsi, &buf[LWS_PRE], 128, LWS_WRITE_TEXT);
- *
- * When sending HTTP, with
- *
- * LWS_WRITE_HTTP,
- * LWS_WRITE_HTTP_HEADERS
- * LWS_WRITE_HTTP_FINAL
- *
- * there is no protocol data prepended, and don't need to take care about the
- * LWS_PRE bytes valid before the buffer pointer.
- *
- * LWS_PRE is at least the frame nonce + 2 header + 8 length
- * LWS_SEND_BUFFER_POST_PADDING is deprecated, it's now 0 and can be left off.
- * The example apps no longer use it.
- *
- * Pad LWS_PRE to the CPU word size, so that word references
- * to the address immediately after the padding won't cause an unaligned access
- * error. Sometimes for performance reasons the recommended padding is even
- * larger than sizeof(void *).
- *
- *     In the case of sending using websocket protocol, be sure to allocate
- *     valid storage before and after buf as explained above.  This scheme
- *     allows maximum efficiency of sending data and protocol in a single
- *     packet while not burdening the user code with any protocol knowledge.
- *
- *     Return may be -1 for a fatal error needing connection close, or the
- *     number of bytes sent.
- *
- * Truncated Writes
- * ================
- *
- * The OS may not accept everything you asked to write on the connection.
- *
- * Posix defines POLLOUT indication from poll() to show that the connection
- * will accept more write data, but it doesn't specifiy how much.  It may just
- * accept one byte of whatever you wanted to send.
- *
- * LWS will buffer the remainder automatically, and send it out autonomously.
- *
- * During that time, WRITABLE callbacks will be suppressed.
- *
- * This is to handle corner cases where unexpectedly the OS refuses what we
- * usually expect it to accept.  You should try to send in chunks that are
- * almost always accepted in order to avoid the inefficiency of the buffering.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_write(struct lws *wsi, unsigned char *buf, size_t len,
-         enum lws_write_protocol protocol);
-
-/* helper for case where buffer may be const */
-#define lws_write_http(wsi, buf, len) \
-       lws_write(wsi, (unsigned char *)(buf), len, LWS_WRITE_HTTP)
-///@}
-
-/** \defgroup callback-when-writeable Callback when writeable
- *
- * ##Callback When Writeable
- *
- * lws can only write data on a connection when it is able to accept more
- * data without blocking.
- *
- * So a basic requirement is we should only use the lws_write() apis when the
- * connection we want to write on says that he can accept more data.
- *
- * When lws cannot complete your send at the time, it will buffer the data
- * and send it in the background, suppressing any further WRITEABLE callbacks
- * on that connection until it completes.  So it is important to write new
- * things in a new writeable callback.
- *
- * These apis reflect the various ways we can indicate we would like to be
- * called back when one or more connections is writeable.
- */
-///@{
-
-/**
- * lws_callback_on_writable() - Request a callback when this socket
- *                                      becomes able to be written to without
- *                                      blocking
- *
- * \param wsi: Websocket connection instance to get callback for
- *
- * - Which:  only this wsi
- * - When:   when the individual connection becomes writeable
- * - What: LWS_CALLBACK_*_WRITEABLE
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_on_writable(struct lws *wsi);
-
-/**
- * lws_callback_on_writable_all_protocol() - Request a callback for all
- *                     connections using the given protocol when it
- *                     becomes possible to write to each socket without
- *                     blocking in turn.
- *
- * \param context:     lws_context
- * \param protocol:    Protocol whose connections will get callbacks
- *
- * - Which:  connections using this protocol on ANY VHOST
- * - When:   when the individual connection becomes writeable
- * - What: LWS_CALLBACK_*_WRITEABLE
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_on_writable_all_protocol(const struct lws_context *context,
-                                     const struct lws_protocols *protocol);
-
-/**
- * lws_callback_on_writable_all_protocol_vhost() - Request a callback for
- *                     all connections on same vhost using the given protocol
- *                     when it becomes possible to write to each socket without
- *                     blocking in turn.
- *
- * \param vhost:       Only consider connections on this lws_vhost
- * \param protocol:    Protocol whose connections will get callbacks
- *
- * - Which:  connections using this protocol on GIVEN VHOST ONLY
- * - When:   when the individual connection becomes writeable
- * - What: LWS_CALLBACK_*_WRITEABLE
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
-                                     const struct lws_protocols *protocol);
-
-/**
- * lws_callback_all_protocol() - Callback all connections using
- *                             the given protocol with the given reason
- *
- * \param context:     lws_context
- * \param protocol:    Protocol whose connections will get callbacks
- * \param reason:      Callback reason index
- *
- * - Which:  connections using this protocol on ALL VHOSTS
- * - When:   before returning
- * - What:   reason
- *
- * This isn't normally what you want... normally any update of connection-
- * specific information can wait until a network-related callback like rx,
- * writable, or close.
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_all_protocol(struct lws_context *context,
-                         const struct lws_protocols *protocol, int reason);
-
-/**
- * lws_callback_all_protocol_vhost() - Callback all connections using
- *                             the given protocol with the given reason
- *
- * \param vh:          Vhost whose connections will get callbacks
- * \param protocol:    Which protocol to match
- * \param reason:      Callback reason index
- *
- * - Which:  connections using this protocol on GIVEN VHOST ONLY
- * - When:   now
- * - What:   reason
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_all_protocol_vhost(struct lws_vhost *vh,
-                         const struct lws_protocols *protocol, int reason);
-
-/**
- * lws_callback_vhost_protocols() - Callback all protocols enabled on a vhost
- *                                     with the given reason
- *
- * \param wsi: wsi whose vhost will get callbacks
- * \param reason:      Callback reason index
- * \param in:          in argument to callback
- * \param len: len argument to callback
- *
- * - Which:  connections using this protocol on same VHOST as wsi ONLY
- * - When:   now
- * - What:   reason
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
-                   void *user, void *in, size_t len);
-
-/**
- * lws_get_socket_fd() - returns the socket file descriptor
- *
- * You will not need this unless you are doing something special
- *
- * \param wsi: Websocket connection instance
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_get_socket_fd(struct lws *wsi);
-
-/**
- * lws_get_peer_write_allowance() - get the amount of data writeable to peer
- *                                     if known
- *
- * \param wsi: Websocket connection instance
- *
- * if the protocol does not have any guidance, returns -1.  Currently only
- * http2 connections get send window information from this API.  But your code
- * should use it so it can work properly with any protocol.
- *
- * If nonzero return is the amount of payload data the peer or intermediary has
- * reported it has buffer space for.  That has NO relationship with the amount
- * of buffer space your OS can accept on this connection for a write action.
- *
- * This number represents the maximum you could send to the peer or intermediary
- * on this connection right now without the protocol complaining.
- *
- * lws manages accounting for send window updates and payload writes
- * automatically, so this number reflects the situation at the peer or
- * intermediary dynamically.
- */
-LWS_VISIBLE LWS_EXTERN size_t
-lws_get_peer_write_allowance(struct lws *wsi);
-///@}
-
-/**
- * lws_rx_flow_control() - Enable and disable socket servicing for
- *                             received packets.
- *
- * If the output side of a server process becomes choked, this allows flow
- * control for the input side.
- *
- * \param wsi: Websocket connection instance to get callback for
- * \param enable:      0 = disable read servicing for this connection, 1 = enable
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_rx_flow_control(struct lws *wsi, int enable);
-
-/**
- * lws_rx_flow_allow_all_protocol() - Allow all connections with this protocol to receive
- *
- * When the user server code realizes it can accept more input, it can
- * call this to have the RX flow restriction removed from all connections using
- * the given protocol.
- * \param context:     lws_context
- * \param protocol:    all connections using this protocol will be allowed to receive
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_rx_flow_allow_all_protocol(const struct lws_context *context,
-                              const struct lws_protocols *protocol);
-
-/**
- * lws_remaining_packet_payload() - Bytes to come before "overall"
- *                                           rx packet is complete
- * \param wsi:         Websocket instance (available from user callback)
- *
- *     This function is intended to be called from the callback if the
- *  user code is interested in "complete packets" from the client.
- *  libwebsockets just passes through payload as it comes and issues a buffer
- *  additionally when it hits a built-in limit.  The LWS_CALLBACK_RECEIVE
- *  callback handler can use this API to find out if the buffer it has just
- *  been given is the last piece of a "complete packet" from the client --
- *  when that is the case lws_remaining_packet_payload() will return
- *  0.
- *
- *  Many protocols won't care becuse their packets are always small.
- */
-LWS_VISIBLE LWS_EXTERN size_t
-lws_remaining_packet_payload(struct lws *wsi);
-
-
-/** \defgroup sock-adopt Socket adoption helpers
- * ##Socket adoption helpers
- *
- * When integrating with an external app with its own event loop, these can
- * be used to accept connections from someone else's listening socket.
- *
- * When using lws own event loop, these are not needed.
- */
-///@{
-
-/**
- * lws_adopt_socket() - adopt foreign socket as if listen socket accepted it
- * for the default vhost of context.
- * \param context: lws context
- * \param accept_fd: fd of already-accepted socket to adopt
- *
- * Either returns new wsi bound to accept_fd, or closes accept_fd and
- * returns NULL, having cleaned up any new wsi pieces.
- *
- * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
- * to ws or just serve http.
- */
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd);
-/**
- * lws_adopt_socket_vhost() - adopt foreign socket as if listen socket accepted it
- * for vhost
- * \param vhost: lws vhost
- * \param accept_fd: fd of already-accepted socket to adopt
- *
- * Either returns new wsi bound to accept_fd, or closes accept_fd and
- * returns NULL, having cleaned up any new wsi pieces.
- *
- * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
- * to ws or just serve http.
- */
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd);
-
-typedef enum {
-       LWS_ADOPT_RAW_FILE_DESC = 0,    /* convenience constant */
-       LWS_ADOPT_HTTP = 1,             /* flag: absent implies RAW */
-       LWS_ADOPT_SOCKET = 2,           /* flag: absent implies file descr */
-       LWS_ADOPT_ALLOW_SSL = 4,        /* flag: if set requires LWS_ADOPT_SOCKET */
-       LWS_ADOPT_WS_PARENTIO = 8,      /* flag: ws mode parent handles IO
-                                        *   if given must be only flag
-                                        *   wsi put directly into ws mode
-                                        */
-} lws_adoption_type;
-
-typedef union {
-       lws_sockfd_type sockfd;
-       lws_filefd_type filefd;
-} lws_sock_file_fd_type;
-
-/*
-* lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor
-* if socket descriptor, should already have been accepted from listen socket
-*
-* \param vhost: lws vhost
-* \param type: OR-ed combinations of lws_adoption_type flags
-* \param fd: union with either .sockfd or .filefd set
-* \param vh_prot_name: NULL or vh protocol name to bind raw connection to
-* \param parent: NULL or struct lws to attach new_wsi to as a child
-*
-* Either returns new wsi bound to accept_fd, or closes accept_fd and
-* returns NULL, having cleaned up any new wsi pieces.
-*
-* If LWS_ADOPT_SOCKET is set, LWS adopts the socket in http serving mode, it's
-* ready to accept an upgrade to ws or just serve http.
-*
-* parent may be NULL, if given it should be an existing wsi that will become the
-* parent of the new wsi created by this call.
-*/
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
-                          lws_sock_file_fd_type fd, const char *vh_prot_name,
-                          struct lws *parent);
-
-/**
- * lws_adopt_socket_readbuf() - adopt foreign socket and first rx as if listen socket accepted it
- * for the default vhost of context.
- * \param context:     lws context
- * \param accept_fd:   fd of already-accepted socket to adopt
- * \param readbuf:     NULL or pointer to data that must be drained before reading from
- *             accept_fd
- * \param len: The length of the data held at \param readbuf
- *
- * Either returns new wsi bound to accept_fd, or closes accept_fd and
- * returns NULL, having cleaned up any new wsi pieces.
- *
- * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
- * to ws or just serve http.
- *
- * If your external code did not already read from the socket, you can use
- * lws_adopt_socket() instead.
- *
- * This api is guaranteed to use the data at \param readbuf first, before reading from
- * the socket.
- *
- * readbuf is limited to the size of the ah rx buf, currently 2048 bytes.
- */
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd,
-                         const char *readbuf, size_t len);
-/**
- * lws_adopt_socket_vhost_readbuf() - adopt foreign socket and first rx as if listen socket
- * accepted it for vhost.
- * \param vhost:       lws vhost
- * \param accept_fd:   fd of already-accepted socket to adopt
- * \param readbuf:     NULL or pointer to data that must be drained before reading from
- *                     accept_fd
- * \param len:         The length of the data held at \param readbuf
- *
- * Either returns new wsi bound to accept_fd, or closes accept_fd and
- * returns NULL, having cleaned up any new wsi pieces.
- *
- * LWS adopts the socket in http serving mode, it's ready to accept an upgrade
- * to ws or just serve http.
- *
- * If your external code did not already read from the socket, you can use
- * lws_adopt_socket() instead.
- *
- * This api is guaranteed to use the data at \param readbuf first, before reading from
- * the socket.
- *
- * readbuf is limited to the size of the ah rx buf, currently 2048 bytes.
- */
-LWS_VISIBLE LWS_EXTERN struct lws *
-lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, lws_sockfd_type accept_fd,
-                               const char *readbuf, size_t len);
-///@}
-
-/** \defgroup net Network related helper APIs
- * ##Network related helper APIs
- *
- * These wrap miscellaneous useful network-related functions
- */
-///@{
-
-/**
- * lws_canonical_hostname() - returns this host's hostname
- *
- * This is typically used by client code to fill in the host parameter
- * when making a client connection.  You can only call it after the context
- * has been created.
- *
- * \param context:     Websocket context
- */
-LWS_VISIBLE LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT
-lws_canonical_hostname(struct lws_context *context);
-
-/**
- * lws_get_peer_addresses() - Get client address information
- * \param wsi: Local struct lws associated with
- * \param fd:          Connection socket descriptor
- * \param name:        Buffer to take client address name
- * \param name_len:    Length of client address name buffer
- * \param rip: Buffer to take client address IP dotted quad
- * \param rip_len:     Length of client address IP buffer
- *
- *     This function fills in name and rip with the name and IP of
- *     the client connected with socket descriptor fd.  Names may be
- *     truncated if there is not enough room.  If either cannot be
- *     determined, they will be returned as valid zero-length strings.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name,
-                      int name_len, char *rip, int rip_len);
-
-/**
- * lws_get_peer_simple() - Get client address information without RDNS
- *
- * \param wsi: Local struct lws associated with
- * \param name:        Buffer to take client address name
- * \param namelen:     Length of client address name buffer
- *
- * This provides a 123.123.123.123 type IP address in name from the
- * peer that has connected to wsi
- */
-LWS_VISIBLE LWS_EXTERN const char *
-lws_get_peer_simple(struct lws *wsi, char *name, int namelen);
-#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32)
-/**
- * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct
- *
- * \param ipv6:        Allow IPV6 addresses
- * \param ifname:      Interface name or IP
- * \param addr:        struct sockaddr_in * to be written
- * \param addrlen:     Length of addr
- *
- * This converts a textual network interface name to a sockaddr usable by
- * other network functions
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
-                   size_t addrlen);
-///@}
-#endif
-
-/** \defgroup misc Miscellaneous APIs
-* ##Miscellaneous APIs
-*
-* Various APIs outside of other categories
-*/
-///@{
-
-/**
- * lws_start_foreach_ll(): linkedlist iterator helper start
- *
- * \param type: type of iteration, eg, struct xyz *
- * \param it: iterator var name to create
- * \param start: start of list
- *
- * This helper creates an iterator and starts a while (it) {
- * loop.  The iterator runs through the linked list starting at start and
- * ends when it gets a NULL.
- * The while loop should be terminated using lws_start_foreach_ll().
- */
-#define lws_start_foreach_ll(type, it, start)\
-{ \
-       type it = start; \
-       while (it) {
-
-/**
- * lws_end_foreach_ll(): linkedlist iterator helper end
- *
- * \param it: same iterator var name given when starting
- * \param nxt: member name in the iterator pointing to next list element
- *
- * This helper is the partner for lws_start_foreach_ll() that ends the
- * while loop.
- */
-
-#define lws_end_foreach_ll(it, nxt) \
-               it = it->nxt; \
-       } \
-}
-
-/**
- * lws_start_foreach_llp(): linkedlist pointer iterator helper start
- *
- * \param type: type of iteration, eg, struct xyz **
- * \param it: iterator var name to create
- * \param start: start of list
- *
- * This helper creates an iterator and starts a while (it) {
- * loop.  The iterator runs through the linked list starting at the
- * address of start and ends when it gets a NULL.
- * The while loop should be terminated using lws_start_foreach_llp().
- *
- * This helper variant iterates using a pointer to the previous linked-list
- * element.  That allows you to easily delete list members by rewriting the
- * previous pointer to the element's next pointer.
- */
-#define lws_start_foreach_llp(type, it, start)\
-{ \
-       type it = &(start); \
-       while (*(it)) {
-
-/**
- * lws_end_foreach_llp(): linkedlist pointer iterator helper end
- *
- * \param it: same iterator var name given when starting
- * \param nxt: member name in the iterator pointing to next list element
- *
- * This helper is the partner for lws_start_foreach_llp() that ends the
- * while loop.
- */
-
-#define lws_end_foreach_llp(it, nxt) \
-               it = &(*(it))->nxt; \
-       } \
-}
-
-/**
- * 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));
-///@}
-
-/** \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
- *
- * These provide SHA-1 and B64 helper apis
- */
-///@{
-#ifdef LWS_SHA1_USE_OPENSSL_NAME
-#define lws_SHA1 SHA1
-#else
-/**
- * lws_SHA1(): make a SHA-1 digest of a buffer
- *
- * \param d: incoming buffer
- * \param n: length of incoming buffer
- * \param md: buffer for message digest (must be >= 20 bytes)
- *
- * Reduces any size buffer into a 20-byte SHA-1 hash.
- */
-LWS_VISIBLE LWS_EXTERN unsigned char *
-lws_SHA1(const unsigned char *d, size_t n, unsigned char *md);
-#endif
-/**
- * lws_b64_encode_string(): encode a string into base 64
- *
- * \param in: incoming buffer
- * \param in_len: length of incoming buffer
- * \param out: result buffer
- * \param out_size: length of result buffer
- *
- * Encodes a string using b64
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_b64_encode_string(const char *in, int in_len, char *out, int out_size);
-/**
- * lws_b64_decode_string(): decode a string from base 64
- *
- * \param in: incoming buffer
- * \param out: result buffer
- * \param out_size: length of result buffer
- *
- * Decodes a string using b64
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_b64_decode_string(const char *in, char *out, int out_size);
-///@}
-
-
-/*! \defgroup cgi cgi handling
- *
- * ##CGI handling
- *
- * These functions allow low-level control over stdin/out/err of the cgi.
- *
- * However for most cases, binding the cgi to http in and out, the default
- * lws implementation already does the right thing.
- */
-#ifdef LWS_WITH_CGI
-enum lws_enum_stdinouterr {
-       LWS_STDIN = 0,
-       LWS_STDOUT = 1,
-       LWS_STDERR = 2,
-};
-
-enum lws_cgi_hdr_state {
-       LCHS_HEADER,
-       LCHS_CR1,
-       LCHS_LF1,
-       LCHS_CR2,
-       LCHS_LF2,
-       LHCS_RESPONSE,
-       LHCS_DUMP_HEADERS,
-       LHCS_PAYLOAD,
-       LCHS_SINGLE_0A,
-};
-
-struct lws_cgi_args {
-       struct lws **stdwsi; /**< get fd with lws_get_socket_fd() */
-       enum lws_enum_stdinouterr ch; /**< channel index */
-       unsigned char *data; /**< for messages with payload */
-       enum lws_cgi_hdr_state hdr_state; /**< track where we are in cgi headers */
-       int len; /**< length */
-};
-
-
-/**
- * lws_cgi: spawn network-connected cgi process
- *
- * \param wsi: connection to own the process
- * \param exec_array: array of "exec-name" "arg1" ... "argn" NULL
- * \param script_uri_path_len: how many chars on the left of the uri are the path to the cgi
- * \param timeout_secs: seconds script should be allowed to run
- * \param mp_cgienv: pvo list with per-vhost cgi options to put in env
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_cgi(struct lws *wsi, const char * const *exec_array,
-       int script_uri_path_len, int timeout_secs,
-       const struct lws_protocol_vhost_options *mp_cgienv);
-
-/**
- * lws_cgi_write_split_stdout_headers: write cgi output accounting for header part
- *
- * \param wsi: connection to own the process
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_cgi_write_split_stdout_headers(struct lws *wsi);
-
-/**
- * lws_cgi_kill: terminate cgi process associated with wsi
- *
- * \param wsi: connection to own the process
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_cgi_kill(struct lws *wsi);
-#endif
-///@}
-
-
-/*! \defgroup fops file operation wrapping
- *
- * ##File operation wrapping
- *
- * Use these helper functions if you want to access a file from the perspective
- * of a specific wsi, which is usually the case.  If you just want contextless
- * file access, use the fops callbacks directly with NULL wsi instead of these
- * helpers.
- *
- * If so, then it calls the platform handler or user overrides where present
- * (as defined in info->fops)
- *
- * The advantage from all this is user code can be portable for file operations
- * without having to deal with differences between platforms.
- */
-//@{
-
-/** struct lws_plat_file_ops - Platform-specific file operations
- *
- * These provide platform-agnostic ways to deal with filesystem access in the
- * library and in the user code.
- */
-
-#if defined(LWS_WITH_ESP32)
-/* sdk preprocessor defs? compiler issue? gets confused with member names */
-#define LWS_FOP_OPEN           _open
-#define LWS_FOP_CLOSE          _close
-#define LWS_FOP_SEEK_CUR       _seek_cur
-#define LWS_FOP_READ           _read
-#define LWS_FOP_WRITE          _write
-#else
-#define LWS_FOP_OPEN           open
-#define LWS_FOP_CLOSE          close
-#define LWS_FOP_SEEK_CUR       seek_cur
-#define LWS_FOP_READ           read
-#define LWS_FOP_WRITE          write
-#endif
-
-#define LWS_FOP_FLAGS_MASK                ((1 << 23) - 1)
-#define LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP (1 << 24)
-#define LWS_FOP_FLAG_COMPR_IS_GZIP        (1 << 25)
-#define LWS_FOP_FLAG_MOD_TIME_VALID       (1 << 26)
-#define LWS_FOP_FLAG_VIRTUAL              (1 << 27)
-
-struct lws_plat_file_ops;
-
-struct lws_fop_fd {
-       lws_filefd_type                 fd;
-       /**< real file descriptor related to the file... */
-       const struct lws_plat_file_ops  *fops;
-       /**< fops that apply to this fop_fd */
-       void                            *filesystem_priv;
-       /**< ignored by lws; owned by the fops handlers */
-       lws_filepos_t                   pos;
-       /**< generic "position in file" */
-       lws_filepos_t                   len;
-       /**< generic "length of file" */
-       lws_fop_flags_t                 flags;
-       /**< copy of the returned flags */
-       uint32_t                        mod_time;
-       /**< optional "modification time of file", only valid if .open()
-        * set the LWS_FOP_FLAG_MOD_TIME_VALID flag */
-};
-typedef struct lws_fop_fd *lws_fop_fd_t;
-
-struct lws_fops_index {
-       const char *sig;        /* NULL or vfs signature, eg, ".zip/" */
-       uint8_t len;            /* length of above string */
-};
-
-struct lws_plat_file_ops {
-       lws_fop_fd_t (*LWS_FOP_OPEN)(const struct lws_plat_file_ops *fops,
-                                    const char *filename, const char *vpath,
-                                    lws_fop_flags_t *flags);
-       /**< Open file (always binary access if plat supports it)
-        * vpath may be NULL, or if the fops understands it, the point at which
-        * the filename's virtual part starts.
-        * *flags & LWS_FOP_FLAGS_MASK should be set to O_RDONLY or O_RDWR.
-        * If the file may be gzip-compressed,
-        * LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP is set.  If it actually is
-        * gzip-compressed, then the open handler should OR
-        * LWS_FOP_FLAG_COMPR_IS_GZIP on to *flags before returning.
-        */
-       int (*LWS_FOP_CLOSE)(lws_fop_fd_t *fop_fd);
-       /**< close file AND set the pointer to NULL */
-       lws_fileofs_t (*LWS_FOP_SEEK_CUR)(lws_fop_fd_t fop_fd,
-                                         lws_fileofs_t offset_from_cur_pos);
-       /**< seek from current position */
-       int (*LWS_FOP_READ)(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                           uint8_t *buf, lws_filepos_t len);
-       /**< Read from file, on exit *amount is set to amount actually read */
-       int (*LWS_FOP_WRITE)(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                            uint8_t *buf, lws_filepos_t len);
-       /**< Write to file, on exit *amount is set to amount actually written */
-
-       struct lws_fops_index fi[3];
-       /**< vfs path signatures implying use of this fops */
-
-       const struct lws_plat_file_ops *next;
-       /**< NULL or next fops in list */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility */
-};
-
-/**
- * lws_get_fops() - get current file ops
- *
- * \param context: context
- */
-LWS_VISIBLE LWS_EXTERN struct lws_plat_file_ops * LWS_WARN_UNUSED_RESULT
-lws_get_fops(struct lws_context *context);
-LWS_VISIBLE LWS_EXTERN void
-lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops);
-/**
- * lws_vfs_tell() - get current file position
- *
- * \param fop_fd: fop_fd we are asking about
- */
-LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT
-lws_vfs_tell(lws_fop_fd_t fop_fd);
-/**
- * lws_vfs_get_length() - get current file total length in bytes
- *
- * \param fop_fd: fop_fd we are asking about
- */
-LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT
-lws_vfs_get_length(lws_fop_fd_t fop_fd);
-/**
- * lws_vfs_get_mod_time() - get time file last modified
- *
- * \param fop_fd: fop_fd we are asking about
- */
-LWS_VISIBLE LWS_EXTERN uint32_t LWS_WARN_UNUSED_RESULT
-lws_vfs_get_mod_time(lws_fop_fd_t fop_fd);
-/**
- * lws_vfs_file_seek_set() - seek relative to start of file
- *
- * \param fop_fd: fop_fd we are seeking in
- * \param offset: offset from start of file
- */
-LWS_VISIBLE LWS_EXTERN lws_fileofs_t
-lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
-/**
- * lws_vfs_file_seek_end() - seek relative to end of file
- *
- * \param fop_fd: fop_fd we are seeking in
- * \param offset: offset from start of file
- */
-LWS_VISIBLE LWS_EXTERN lws_fileofs_t
-lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
-
-extern struct lws_plat_file_ops fops_zip;
-
-/**
- * lws_plat_file_open() - open vfs filepath
- *
- * \param fops: file ops struct that applies to this descriptor
- * \param vfs_path: filename to open
- * \param flags: pointer to open flags
- *
- * The vfs_path is scanned for known fops signatures, and the open directed
- * to any matching fops open.
- *
- * User code should use this api to perform vfs opens.
- *
- * returns semi-opaque handle
- */
-LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT
-lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
-                 lws_fop_flags_t *flags);
-
-/**
- * lws_plat_file_close() - close file
- *
- * \param fop_fd: file handle to close
- */
-static LWS_INLINE int
-lws_vfs_file_close(lws_fop_fd_t *fop_fd)
-{
-       return (*fop_fd)->fops->LWS_FOP_CLOSE(fop_fd);
-}
-
-/**
- * lws_plat_file_seek_cur() - close file
- *
- *
- * \param fop_fd: file handle
- * \param offset: position to seek to
- */
-static LWS_INLINE lws_fileofs_t
-lws_vfs_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
-{
-       return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset);
-}
-/**
- * lws_plat_file_read() - read from file
- *
- * \param fop_fd: file handle
- * \param amount: how much to read (rewritten by call)
- * \param buf: buffer to write to
- * \param len: max length
- */
-static LWS_INLINE int LWS_WARN_UNUSED_RESULT
-lws_vfs_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                  uint8_t *buf, lws_filepos_t len)
-{
-       return fop_fd->fops->LWS_FOP_READ(fop_fd, amount, buf, len);
-}
-/**
- * lws_plat_file_write() - write from file
- *
- * \param fop_fd: file handle
- * \param amount: how much to write (rewritten by call)
- * \param buf: buffer to read from
- * \param len: max length
- */
-static LWS_INLINE int LWS_WARN_UNUSED_RESULT
-lws_vfs_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                   uint8_t *buf, lws_filepos_t len)
-{
-       return fop_fd->fops->LWS_FOP_WRITE(fop_fd, amount, buf, len);
-}
-
-/* these are the platform file operations implementations... they can
- * be called directly and used in fops arrays
- */
-
-LWS_VISIBLE LWS_EXTERN lws_fop_fd_t
-_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-                   const char *vpath, lws_fop_flags_t *flags);
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_file_close(lws_fop_fd_t *fop_fd);
-LWS_VISIBLE LWS_EXTERN lws_fileofs_t
-_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset);
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                   uint8_t *buf, lws_filepos_t len);
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                    uint8_t *buf, lws_filepos_t len);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf,
-                lws_filepos_t *amount);
-//@}
-
-/** \defgroup smtp
- * \ingroup lwsapi
- * ##SMTP related functions
- *
- * These apis let you communicate with a local SMTP server to send email from
- * lws.  It handles all the SMTP sequencing and protocol actions.
- *
- * Your system should have postfix, sendmail or another MTA listening on port
- * 25 and able to send email using the "mail" commandline app.  Usually distro
- * MTAs are configured for this by default.
- *
- * It runs via its own libuv events if initialized (which requires giving it
- * a libuv loop to attach to).
- *
- * It operates using three callbacks, on_next() queries if there is a new email
- * to send, on_get_body() asks for the body of the email, and on_sent() is
- * called after the email is successfully sent.
- *
- * To use it
- *
- *  - create an lws_email struct
- *
- *  - initialize data, loop, the email_* strings, max_content_size and
- *    the callbacks
- *
- *  - call lws_email_init()
- *
- *  When you have at least one email to send, call lws_email_check() to
- *  schedule starting to send it.
- */
-//@{
-#ifdef LWS_WITH_SMTP
-
-/** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */
-enum lwsgs_smtp_states {
-       LGSSMTP_IDLE, /**< awaiting new email */
-       LGSSMTP_CONNECTING, /**< opening tcp connection to MTA */
-       LGSSMTP_CONNECTED, /**< tcp connection to MTA is connected */
-       LGSSMTP_SENT_HELO, /**< sent the HELO */
-       LGSSMTP_SENT_FROM, /**< sent FROM */
-       LGSSMTP_SENT_TO, /**< sent TO */
-       LGSSMTP_SENT_DATA, /**< sent DATA request */
-       LGSSMTP_SENT_BODY, /**< sent the email body */
-       LGSSMTP_SENT_QUIT, /**< sent the session quit */
-};
-
-/** struct lws_email - abstract context for performing SMTP operations */
-struct lws_email {
-       void *data;
-       /**< opaque pointer set by user code and available to the callbacks */
-       uv_loop_t *loop;
-       /**< the libuv loop we will work on */
-
-       char email_smtp_ip[32]; /**< Fill before init, eg, "127.0.0.1" */
-       char email_helo[32];    /**< Fill before init, eg, "myserver.com" */
-       char email_from[100];   /**< Fill before init or on_next */
-       char email_to[100];     /**< Fill before init or on_next */
-
-       unsigned int max_content_size;
-       /**< largest possible email body size */
-
-       /* Fill all the callbacks before init */
-
-       int (*on_next)(struct lws_email *email);
-       /**< (Fill in before calling lws_email_init)
-        * called when idle, 0 = another email to send, nonzero is idle.
-        * If you return 0, all of the email_* char arrays must be set
-        * to something useful. */
-       int (*on_sent)(struct lws_email *email);
-       /**< (Fill in before calling lws_email_init)
-        * called when transfer of the email to the SMTP server was
-        * successful, your callback would remove the current email
-        * from its queue */
-       int (*on_get_body)(struct lws_email *email, char *buf, int len);
-       /**< (Fill in before calling lws_email_init)
-        * called when the body part of the queued email is about to be
-        * sent to the SMTP server. */
-
-
-       /* private things */
-       uv_timer_t timeout_email; /**< private */
-       enum lwsgs_smtp_states estate; /**< private */
-       uv_connect_t email_connect_req; /**< private */
-       uv_tcp_t email_client; /**< private */
-       time_t email_connect_started; /**< private */
-       char email_buf[256]; /**< private */
-       char *content; /**< private */
-};
-
-/**
- * lws_email_init() - Initialize a struct lws_email
- *
- * \param email: struct lws_email to init
- * \param loop: libuv loop to use
- * \param max_content: max email content size
- *
- * Prepares a struct lws_email for use ending SMTP
- */
-LWS_VISIBLE LWS_EXTERN int
-lws_email_init(struct lws_email *email, uv_loop_t *loop, int max_content);
-
-/**
- * lws_email_check() - Request check for new email
- *
- * \param email: struct lws_email context to check
- *
- * Schedules a check for new emails in 1s... call this when you have queued an
- * email for send.
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_email_check(struct lws_email *email);
-/**
- * lws_email_destroy() - stop using the struct lws_email
- *
- * \param email: the struct lws_email context
- *
- * Stop sending email using email and free allocations
- */
-LWS_VISIBLE LWS_EXTERN void
-lws_email_destroy(struct lws_email *email);
-
-#endif
-//@}
-
-/*
- * Stats are all uint64_t numbers that start at 0.
- * Index names here have the convention
- *
- *  _C_ counter
- *  _B_ byte count
- *  _MS_ millisecond count
- */
-
-enum {
-       LWSSTATS_C_CONNECTIONS, /**< count incoming connections */
-       LWSSTATS_C_API_CLOSE, /**< count calls to close api */
-       LWSSTATS_C_API_READ, /**< count calls to read from socket api */
-       LWSSTATS_C_API_LWS_WRITE, /**< count calls to lws_write API */
-       LWSSTATS_C_API_WRITE, /**< count calls to write API */
-       LWSSTATS_C_WRITE_PARTIALS, /**< count of partial writes */
-       LWSSTATS_C_WRITEABLE_CB_REQ, /**< count of writable callback requests */
-       LWSSTATS_C_WRITEABLE_CB_EFF_REQ, /**< count of effective writable callback requests */
-       LWSSTATS_C_WRITEABLE_CB, /**< count of writable callbacks */
-       LWSSTATS_C_SSL_CONNECTIONS_FAILED, /**< count of failed SSL connections */
-       LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, /**< count of accepted SSL connections */
-       LWSSTATS_C_SSL_CONNS_HAD_RX, /**< count of accepted SSL conns that have had some RX */
-       LWSSTATS_C_TIMEOUTS, /**< count of timed-out connections */
-       LWSSTATS_C_SERVICE_ENTRY, /**< count of entries to lws service loop */
-       LWSSTATS_B_READ, /**< aggregate bytes read */
-       LWSSTATS_B_WRITE, /**< aggregate bytes written */
-       LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, /**< aggreate of size of accepted write data from new partials */
-       LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, /**< aggregate delay in accepting connection */
-       LWSSTATS_MS_WRITABLE_DELAY, /**< aggregate delay between asking for writable and getting cb */
-       LWSSTATS_MS_WORST_WRITABLE_DELAY, /**< single worst delay between asking for writable and getting cb */
-       LWSSTATS_MS_SSL_RX_DELAY, /**< aggregate delay between ssl accept complete and first RX */
-
-       /* Add new things just above here ---^
-        * This is part of the ABI, don't needlessly break compatibility */
-       LWSSTATS_SIZE
-};
-
-#if defined(LWS_WITH_STATS)
-
-LWS_VISIBLE LWS_EXTERN uint64_t
-lws_stats_get(struct lws_context *context, int index);
-LWS_VISIBLE LWS_EXTERN void
-lws_stats_log_dump(struct lws_context *context);
-#else
-static LWS_INLINE uint64_t
-lws_stats_get(struct lws_context *context, int index) { return 0; }
-static LWS_INLINE void
-lws_stats_log_dump(struct lws_context *context) { }
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/lib/lws-plat-esp8266.c b/lib/lws-plat-esp8266.c
deleted file mode 100644 (file)
index 39f6930..0000000
+++ /dev/null
@@ -1,700 +0,0 @@
-#include "private-libwebsockets.h"
-
-#include "ip_addr.h"
-
-/* forced into this because new espconn accepted callbacks carry no context ptr */
-static struct lws_context *hacky_context;
-static unsigned int time_high, ot;
-
-/*
- * included from libwebsockets.c for esp8266 builds
- */
-
-unsigned long long time_in_microseconds(void)
-{
-       unsigned int t = system_get_time();
-       
-       if (ot > t)
-               time_high++;
-       ot = t;
-       
-       return (((long long)time_high) << 32) | t;
-}
-
-int gettimeofday(struct timeval *tv, void *tz)
-{
-       unsigned long long t = time_in_microseconds();
-       
-       tv->tv_sec = t / 1000000;
-       tv->tv_usec = t % 1000000;
-
-       return 0;
-}
-
-time_t time(time_t *tloc)
-{
-       unsigned long long t = time_in_microseconds();
-
-       if (tloc)
-               *tloc = t / 1000000;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_get_random(struct lws_context *context, void *buf, int len)
-{
-//     return read(context->fd_random, (char *)buf, len);
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_send_pipe_choked(struct lws *wsi)
-{
-       return wsi->pending_send_completion;
-}
-
-LWS_VISIBLE struct lws *
-wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
-{
-       int n;
-
-       for (n = 0; n < context->max_fds; n++)
-               if (context->connpool[n] == fd)
-                       return (struct lws *)context->connpool[n + context->max_fds];
-
-       return NULL;
-}
-
-LWS_VISIBLE int
-lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
-{
-       //lwsl_notice("%s: wsi %p: len %d\n", __func__, wsi, len);      
-       
-       wsi->pending_send_completion++;
-       espconn_send(wsi->desc.sockfd, buf, len);
-       
-       return len;
-}
-
-void abort(void)
-{
-       while(1) ;
-}
-
-void exit(int n)
-{
-       abort();
-}
-
-void _sint(void *s)
-{
-}
-
-LWS_VISIBLE int
-insert_wsi(struct lws_context *context, struct lws *wsi)
-{
-       (void)context;
-       (void)wsi;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-delete_from_fd(struct lws_context *context, lws_sockfd_type fd)
-{
-       (void)context;
-       (void)fd;
-
-       return 1;
-}
-
-struct tm *localtime(const time_t *timep)
-{
-       return NULL;
-}
-struct tm *localtime_r(const time_t *timep, struct tm *t)
-{
-       return NULL;
-}
-
-int atoi(const char *s)
-{
-       int n = 0;
-
-       while (*s && (*s >= '0' && *s <= '9'))
-               n = (n * 10) + ((*s++) - '0');
-
-       return n;
-}
-
-#undef isxdigit
-int isxdigit(int c)
-{
-       if (c >= 'A' && c <= 'F')
-               return 1;
-
-       if (c >= 'a' && c <= 'f')
-               return 1;
-
-       if (c >= '0' && c <= '9')
-               return 1;
-
-       return 0;
-}
-
-int strcasecmp(const char *s1, const char *s2)
-{
-       char a, b;
-       while (*s1 && *s2) {
-               a = *s1++;
-               b = *s2++;
-
-               if (a == b)
-                       continue;
-
-               if (a >= 'a' && a <= 'z')
-                       a -= 'a' - 'A';
-               if (b >= 'a' && b <= 'z')
-                       b -= 'a' - 'A';
-
-               if (a != b)
-                       return 1;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_poll_listen_fd(struct lws_pollfd *fd)
-{
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_cancel_service_pt(struct lws *wsi)
-{
-}
-
-LWS_VISIBLE void
-lws_cancel_service(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
-{
-       extern void output_redirect(const char *str);
-       output_redirect(line);
-}
-
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
-{
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_check_connection_error(struct lws *wsi)
-{
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_service(struct lws_context *context, int timeout_ms)
-{
-//     return _lws_plat_service_tsi(context, timeout_ms, 0);
-       return 0;
-}
-
-static int
-esp8266_find_free_conn(struct lws_context *context)
-{
-       int n;
-
-       for (n = 0; n < context->max_fds; n++)
-               if (!context->connpool[n]) {
-                       lwsl_info(" using connpool %d\n", n);
-                       return n;
-               }
-       
-       lwsl_err("%s: no free conns\n", __func__);
-       
-       return -1;
-}
-
-lws_sockfd_type
-esp8266_create_tcp_listen_socket(struct lws_vhost *vh)
-{
-       int n = esp8266_find_free_conn(vh->context);
-       struct espconn *conn;
-       
-       if (n < 0)
-               return NULL;
-       
-       conn = lws_zalloc(sizeof *conn);
-       if (!conn)
-               return NULL;
-       
-       vh->context->connpool[n] = conn;
-       
-       conn->type = ESPCONN_TCP;
-       conn->state = ESPCONN_NONE;
-       conn->proto.tcp = &vh->tcp;
-       
-       return conn;
-}
-
-const char *
-lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen)
-{
-       unsigned char *p = wsi->desc.sockfd->proto.tcp->remote_ip;
-
-       lws_snprintf(name, namelen, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
-
-       return name;
-}
-
-LWS_VISIBLE int
-lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len)
-{
-       //lwsl_notice("%s\n", __func__);
-       
-       if (!wsi->context->rxd)
-               return 0;
-
-       if (len < wsi->context->rxd_len)
-               lwsl_err("trunc read (%d vs %d)\n", len, wsi->context->rxd_len);
-       else
-               len = wsi->context->rxd_len;
-       
-       ets_memcpy(buf, wsi->context->rxd, len);
-       
-       wsi->context->rxd = NULL;
-       
-       return len;
-}
-
-static void
-cb_1Hz(void *arg)
-{
-       struct lws_context *context = arg;
-       struct lws_context_per_thread *pt = &context->pt[0];
-       struct lws *wsi;
-       struct lws_pollfd *pollfd;
-       int n;
-
-       /* Service any ah that has pending rx */
-       for (n = 0; n < context->max_http_header_pool; n++)
-               if (pt->ah_pool[n].rxpos != pt->ah_pool[n].rxlen) {
-                       wsi = pt->ah_pool[n].wsi;
-                       pollfd = &pt->fds[wsi->position_in_fds_table];
-                       if (pollfd->events & LWS_POLLIN) {
-                               pollfd->revents |= LWS_POLLIN;
-                               lws_service_fd(context, pollfd);
-                       }
-               }
-
-       /* handle timeouts */
-
-       lws_service_fd(context, NULL);
-}
-
-static void
-esp8266_cb_rx(void *arg, char *data, unsigned short len)
-{
-       struct espconn *conn = arg;
-       struct lws *wsi = conn->reverse;
-       struct lws_context_per_thread *pt = &wsi->context->pt[0];
-       struct lws_pollfd pollfd;
-       int n = 0;
-
-       /*
-        * if we're doing HTTP headers, and we have no ah, check if there is
-        * a free ah, if not, have to buffer it
-        */
-       if (!wsi->hdr_parsing_completed && !wsi->u.hdr.ah) {
-               for (n = 0; n < wsi->context->max_http_header_pool; n++)
-                       if (!pt->ah_pool[n].in_use)
-                               break;
-
-               n = n == wsi->context->max_http_header_pool;
-       }
-
-       if (!(pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN) || n) {
-               wsi->premature_rx = realloc(wsi->premature_rx,
-                                           wsi->prem_rx_size + len);
-               if (!wsi->premature_rx)
-                       return;
-               os_memcpy((char *)wsi->premature_rx + wsi->prem_rx_size, data, len);
-               wsi->prem_rx_size += len;
-       //      lwsl_notice("%s: wsi %p: len %d BUFFERING\n", __func__, wsi, len);
-
-               if (n) /* we know it will fail, but we will get on the wait list */
-                       n = lws_header_table_attach(wsi, 0);
-
-               (void)n;
-               return;
-       }
-
-       //lwsl_err("%s: wsi %p. len %d\n", __func__, wsi, len);
-
-        pollfd.fd = arg;
-        pollfd.events = LWS_POLLIN;
-        pollfd.revents = LWS_POLLIN;
-        
-        wsi->context->rxd = data;
-        wsi->context->rxd_len = len;
-
-        lws_service_fd(lws_get_context(wsi), &pollfd);
-
-}
-
-static void
-esp8266_cb_sent(void *arg)
-{
-       struct espconn *conn = arg;
-       struct lws *wsi = conn->reverse;
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       
-//     lwsl_err("%s: wsi %p (psc %d) wsi->position_in_fds_table=%d\n", __func__, wsi, wsi->pending_send_completion, wsi->position_in_fds_table);
-       
-       wsi->pending_send_completion--;
-       if (wsi->close_is_pending_send_completion &&
-           !wsi->pending_send_completion &&
-           !lws_partial_buffered(wsi)) {
-               lwsl_notice("doing delayed close\n");
-               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-       }
-       
-       if (pt->fds[wsi->position_in_fds_table].events & LWS_POLLOUT) {
-               struct lws_pollfd pollfd;
-
-               pollfd.fd = arg;
-               pollfd.events = LWS_POLLOUT;
-               pollfd.revents = LWS_POLLOUT;
-
-//             lwsl_notice("informing POLLOUT\n");
-               
-               lws_service_fd(lws_get_context(wsi), &pollfd);
-       }
-}
-
-static void
-esp8266_cb_disconnected(void *arg)
-{
-       struct espconn *conn = arg;
-       struct lws *wsi = conn->reverse;
-       int n;
-
-       lwsl_notice("%s: %p\n", __func__, wsi);
-       
-       for (n = 0; n < hacky_context->max_fds; n++)
-               if (hacky_context->connpool[n] == arg) {
-                       hacky_context->connpool[n] = NULL;
-                       lwsl_info(" freed connpool %d\n", n);
-               }
-       
-       if (wsi) {
-               conn->reverse = NULL;
-               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-               lwsl_notice("closed ok\n");
-       }
-}
-
-static void
-esp8266_cb_recon(void *arg, signed char err)
-{
-       struct espconn *conn = arg;
-
-       lwsl_err("%s: wsi %p. err %d\n", __func__, conn->reverse, err);
-
-       conn->state = ESPCONN_CLOSE;            
-
-       esp8266_cb_disconnected(arg);   
-}
-
-/*
- * there is no reliable indication of which listen socket we were accepted on.
- */
-
-static void
-esp8266_cb_connect(void *arg)
-{
-       struct espconn *cs = arg;
-//     struct ip_addr *ipa = (struct ip_addr *)cs->proto.tcp->remote_ip;
-       struct lws_vhost *vh = hacky_context->vhost_list;
-//     struct ip_info info;
-       struct lws *wsi;
-       int n;
-
-       lwsl_notice("%s: (wsi coming): %p\n", __func__, cs->reverse);
-#if 0
-       wifi_get_ip_info(0, &info);
-       if (ip_addr_netcmp(ipa, &info.ip, &info.netmask)) {
-               /* we are on the same subnet as the AP, ie, connected to AP */
-               while (vh && strcmp(vh->name, "ap"))
-                       vh = vh->vhost_next;
-       } else
-               while (vh && !strcmp(vh->name, "ap"))
-                       vh = vh->vhost_next;
-
-       if (!vh)
-               goto bail;
-#endif
-       n = esp8266_find_free_conn(hacky_context);
-       if (n < 0)
-               goto bail;
-       
-       hacky_context->connpool[n] = cs;
-       
-       espconn_recv_hold(cs);
-
-       wsi = lws_adopt_socket_vhost(vh, cs);
-       if (!wsi)
-               goto bail;
-       
-       lwsl_err("%s: wsi %p (using free_conn %d): vh %s\n", __func__, wsi, n, vh->name);
-
-       espconn_regist_recvcb(cs, esp8266_cb_rx);
-       espconn_regist_reconcb(cs, esp8266_cb_recon);
-       espconn_regist_disconcb(cs, esp8266_cb_disconnected);
-       espconn_regist_sentcb(cs, esp8266_cb_sent);
-       
-       espconn_set_opt(cs, ESPCONN_NODELAY | ESPCONN_REUSEADDR);
-       espconn_regist_time(cs, 7200, 1);
-
-       return;
-
-bail:
-       lwsl_err("%s: bailed]n", __func__);
-       espconn_disconnect(cs);
-}
-
-void
-esp8266_tcp_stream_bind(lws_sockfd_type fd, int port, struct lws *wsi)
-{
-       fd->proto.tcp->local_port = port;
-       fd->reverse = wsi;
-       
-       hacky_context = wsi->context;
-       
-       espconn_regist_connectcb(fd, esp8266_cb_connect);
-       /* hmmm it means, listen() + accept() */
-       espconn_accept(fd);
-
-       espconn_tcp_set_max_con_allow(fd, 10);
-}
-
-void
-esp8266_tcp_stream_accept(lws_sockfd_type fd, struct lws *wsi)
-{
-       int n;
-
-       fd->reverse = wsi;
-
-       for (n = 0; n < wsi->context->max_fds ; n++)
-               if (wsi->context->connpool[n] == wsi->desc.sockfd)
-                       wsi->position_in_fds_table = n;
-}
-
-LWS_VISIBLE int
-lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd)
-{
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_context_early_init(void)
-{
-       espconn_tcp_set_max_con(12);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_context_early_destroy(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE void
-lws_plat_context_late_destroy(struct lws_context *context)
-{
-#if 0
-       struct lws_context_per_thread *pt = &context->pt[0];
-       int m = context->count_threads;
-
-       if (context->lws_lookup)
-               lws_free(context->lws_lookup);
-
-       while (m--) {
-               close(pt->dummy_pipe_fds[0]);
-               close(pt->dummy_pipe_fds[1]);
-               pt++;
-       }
-#endif
-//     close(context->fd_random);
-}
-
-/* cast a struct sockaddr_in6 * into addr for ipv6 */
-
-LWS_VISIBLE int
-lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
-                   size_t addrlen)
-{
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-
-       context->connpool[wsi->position_in_fds_table + context->max_fds] = (lws_sockfd_type)wsi;
-       wsi->desc.sockfd->reverse = wsi;
-       pt->fds_count++;
-}
-
-LWS_VISIBLE void
-lws_plat_delete_socket_from_fds(struct lws_context *context,
-                                               struct lws *wsi, int m)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];   
-       int n;
-       
-       for (n = 0; n < wsi->context->max_fds; n++)
-               if (wsi->context->connpool[n] == wsi->desc.sockfd) {
-                       wsi->context->connpool[n] = NULL;
-                       wsi->context->connpool[n + wsi->context->max_fds] = NULL;
-                       lwsl_notice(" freed connpool %d\n", n);
-               }
-       
-       wsi->desc.sockfd->reverse = NULL;
-       pt->fds_count--;
-}
-
-LWS_VISIBLE void
-lws_plat_service_periodic(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_change_pollfd(struct lws_context *context,
-                      struct lws *wsi, struct lws_pollfd *pfd)
-{
-       void *p;
-
-       //lwsl_notice("%s: %p: wsi->pift=%d, events %d\n",
-       //              __func__, wsi, wsi->position_in_fds_table, pfd->events);
-       
-       if (pfd->events & LWS_POLLIN) {
-               if (wsi->premature_rx) {
-                       lwsl_notice("replaying buffered rx: wsi %p\n", wsi);
-                       p = wsi->premature_rx;
-                       wsi->premature_rx = NULL;
-                       esp8266_cb_rx(wsi->desc.sockfd,
-                                     (char *)p + wsi->prem_rx_pos,
-                                     wsi->prem_rx_size - wsi->prem_rx_pos);
-                       wsi->prem_rx_size = 0;
-                       wsi->prem_rx_pos = 0;
-                       lws_free(p);
-               }
-               if (espconn_recv_unhold(wsi->desc.sockfd) < 0)
-                       return -1;
-       } else
-               if (espconn_recv_hold(wsi->desc.sockfd) < 0)
-                       return -1;
-       
-       if (!(pfd->events & LWS_POLLOUT))
-               return 0;
-       
-       if (!wsi->pending_send_completion) {
-               pfd->revents |= LWS_POLLOUT;
-
-//             lwsl_notice("doing POLLOUT\n");
-               lws_service_fd(lws_get_context(wsi), pfd);
-       } //else
-               //lwsl_notice("pending sc\n");
-
-       return 0;
-}
-
-LWS_VISIBLE const char *
-lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
-{
-//     return inet_ntop(af, src, dst, cnt);
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_inet_pton(int af, const char *src, void *dst)
-{
-       //return inet_pton(af, src, dst);
-       return 1;
-}
-
-LWS_VISIBLE int
-lws_plat_init(struct lws_context *context,
-             struct lws_context_creation_info *info)
-{
-//     struct lws_context_per_thread *pt = &context->pt[0];
-//     int n = context->count_threads, fd;
-
-       /* master context has the global fd lookup array */
-       context->connpool = lws_zalloc(sizeof(struct espconn *) *
-                                        context->max_fds * 2);
-       if (context->connpool == NULL) {
-               lwsl_err("OOM on lws_lookup array for %d connections\n",
-                        context->max_fds);
-               return 1;
-       }
-
-       lwsl_notice(" mem: platform fd map: %5u bytes\n",
-                   sizeof(struct espconn *) * context->max_fds);
-//     fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
-
-//     context->fd_random = fd;
-//     if (context->fd_random < 0) {
-//             lwsl_err("Unable to open random device %s %d\n",
-//                      SYSTEM_RANDOM_FILEPATH, context->fd_random);
-//             return 1;
-//     }
-
-        os_memset(&context->to_timer, 0, sizeof(os_timer_t));
-        os_timer_disarm(&context->to_timer);
-        os_timer_setfn(&context->to_timer, (os_timer_func_t *)cb_1Hz, context);
-        os_timer_arm(&context->to_timer, 1000, 1);
-
-       if (!lws_libev_init_fd_table(context) &&
-           !lws_libuv_init_fd_table(context) &&
-           !lws_libevent_init_fd_table(context)) {
-               /* otherwise libev handled it instead */
-#if 0
-               while (n--) {
-                       if (pipe(pt->dummy_pipe_fds)) {
-                               lwsl_err("Unable to create pipe\n");
-                               return 1;
-                       }
-
-                       /* use the read end of pipe as first item */
-                       pt->fds[0].fd = pt->dummy_pipe_fds[0];
-                       pt->fds[0].events = LWS_POLLIN;
-                       pt->fds[0].revents = 0;
-                       pt->fds_count = 1;
-                       pt++;
-               }
-#endif
-       }
-
-
-#ifdef LWS_WITH_PLUGINS
-       if (info->plugin_dirs)
-               lws_plat_plugins_init(context, info->plugin_dirs);
-#endif
-
-       return 0;
-}
diff --git a/lib/lws-plat-optee.c b/lib/lws-plat-optee.c
deleted file mode 100644 (file)
index 3006a6d..0000000
+++ /dev/null
@@ -1,329 +0,0 @@
-#include "private-libwebsockets.h"
-
-/*
- * included from libwebsockets.c for OPTEE builds
- */
-
-void TEE_GenerateRandom(void *randomBuffer, uint32_t randomBufferLen);
-
-unsigned long long time_in_microseconds(void)
-{
-       return ((unsigned long long)time(NULL)) * 1000000;
-}
-#if 0
-LWS_VISIBLE int
-lws_get_random(struct lws_context *context, void *buf, int len)
-{
-       TEE_GenerateRandom(buf, len);
-
-       return len;
-}
-#endif
-LWS_VISIBLE int
-lws_send_pipe_choked(struct lws *wsi)
-{
-#if 0
-       struct lws_pollfd fds;
-
-       /* treat the fact we got a truncated send pending as if we're choked */
-       if (wsi->trunc_len)
-               return 1;
-
-       fds.fd = wsi->desc.sockfd;
-       fds.events = POLLOUT;
-       fds.revents = 0;
-
-       if (poll(&fds, 1, 0) != 1)
-               return 1;
-
-       if ((fds.revents & POLLOUT) == 0)
-               return 1;
-#endif
-       /* okay to send another packet without blocking */
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_poll_listen_fd(struct lws_pollfd *fd)
-{
-//     return poll(fd, 1, 0);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_cancel_service_pt(struct lws *wsi)
-{
-#if 0
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       char buf = 0;
-
-       if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
-               lwsl_err("Cannot write to dummy pipe");
-#endif
-}
-
-LWS_VISIBLE void
-lws_cancel_service(struct lws_context *context)
-{
-#if 0
-       struct lws_context_per_thread *pt = &context->pt[0];
-       char buf = 0, m = context->count_threads;
-
-       while (m--) {
-               if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
-                       lwsl_err("Cannot write to dummy pipe");
-               pt++;
-       }
-#endif
-}
-#if 0
-LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
-{
-       IMSG("%d: %s\n", level, line);
-}
-#endif
-
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
-{
-       struct lws_context_per_thread *pt;
-       int n = -1, m, c;
-       //char buf;
-
-       /* stay dead once we are dead */
-
-       if (!context || !context->vhost_list)
-               return 1;
-
-       pt = &context->pt[tsi];
-
-       if (timeout_ms < 0)
-               goto faked_service;
-
-       if (!context->service_tid_detected) {
-               struct lws _lws;
-
-               memset(&_lws, 0, sizeof(_lws));
-               _lws.context = context;
-
-               context->service_tid_detected =
-                       context->vhost_list->protocols[0].callback(
-                       &_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
-       }
-       context->service_tid = context->service_tid_detected;
-
-       /*
-        * is there anybody with pending stuff that needs service forcing?
-        */
-       if (!lws_service_adjust_timeout(context, 1, tsi)) {
-               lwsl_notice("%s: doing forced service\n", __func__);
-               /* -1 timeout means just do forced service */
-               _lws_plat_service_tsi(context, -1, pt->tid);
-               /* still somebody left who wants forced service? */
-               if (!lws_service_adjust_timeout(context, 1, pt->tid))
-                       /* yes... come back again quickly */
-                       timeout_ms = 0;
-       }
-#if 1
-       n = poll(pt->fds, pt->fds_count, timeout_ms);
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if (!pt->rx_draining_ext_list &&
-           !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
-#else
-       if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
-#endif
-               lws_service_fd_tsi(context, NULL, tsi);
-               return 0;
-       }
-#endif
-faked_service:
-       m = lws_service_flag_pending(context, tsi);
-       if (m)
-               c = -1; /* unknown limit */
-       else
-               if (n < 0) {
-                       if (LWS_ERRNO != LWS_EINTR)
-                               return -1;
-                       return 0;
-               } else
-                       c = n;
-
-       /* any socket with events to service? */
-       for (n = 0; n < pt->fds_count && c; n++) {
-               if (!pt->fds[n].revents)
-                       continue;
-
-               c--;
-#if 0
-               if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) {
-                       if (read(pt->fds[n].fd, &buf, 1) != 1)
-                               lwsl_err("Cannot read from dummy pipe.");
-                       continue;
-               }
-#endif
-               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
-               if (m < 0)
-                       return -1;
-               /* if something closed, retry this slot */
-               if (m)
-                       n--;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_check_connection_error(struct lws *wsi)
-{
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_service(struct lws_context *context, int timeout_ms)
-{
-       return _lws_plat_service_tsi(context, timeout_ms, 0);
-}
-
-LWS_VISIBLE int
-lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
-{
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_context_early_init(void)
-{
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_context_early_destroy(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE void
-lws_plat_context_late_destroy(struct lws_context *context)
-{
-       if (context->lws_lookup)
-               lws_free(context->lws_lookup);
-}
-
-/* cast a struct sockaddr_in6 * into addr for ipv6 */
-
-LWS_VISIBLE int
-lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
-                   size_t addrlen)
-{
-       return -1;
-}
-
-LWS_VISIBLE void
-lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       pt->fds[pt->fds_count++].revents = 0;
-}
-
-LWS_VISIBLE void
-lws_plat_delete_socket_from_fds(struct lws_context *context,
-                                               struct lws *wsi, int m)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       pt->fds_count--;
-}
-
-LWS_VISIBLE void
-lws_plat_service_periodic(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_change_pollfd(struct lws_context *context,
-                     struct lws *wsi, struct lws_pollfd *pfd)
-{
-       return 0;
-}
-
-LWS_VISIBLE const char *
-lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
-{
-       //return inet_ntop(af, src, dst, cnt);
-       return "lws_plat_inet_ntop";
-}
-
-LWS_VISIBLE int
-lws_plat_inet_pton(int af, const char *src, void *dst)
-{
-       //return inet_pton(af, src, dst);
-       return 1;
-}
-
-LWS_VISIBLE lws_fop_fd_t
-_lws_plat_file_open(lws_plat_file_open(struct lws_plat_file_ops *fops,
-                   const char *filename, lws_fop_flags_t *flags)
-{
-       return NULL;
-}
-
-LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t *fop_fd)
-{
-       return 0;
-}
-
-LWS_VISIBLE lws_fileofs_t
-_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
-{
-       return 0;
-}
-
-LWS_VISIBLE  int
-_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                   uint8_t *buf, lws_filepos_t len)
-{
-
-       return 0;
-}
-
-LWS_VISIBLE  int
-_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                    uint8_t *buf, lws_filepos_t len)
-{
-
-       return 0;
-}
-
-
-LWS_VISIBLE int
-lws_plat_init(struct lws_context *context,
-             struct lws_context_creation_info *info)
-{
-       /* master context has the global fd lookup array */
-       context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
-                                        context->max_fds);
-       if (context->lws_lookup == NULL) {
-               lwsl_err("OOM on lws_lookup array for %d connections\n",
-                        context->max_fds);
-               return 1;
-       }
-
-       lwsl_notice(" mem: platform fd map: %5lu bytes\n",
-                   (long)sizeof(struct lws *) * context->max_fds);
-
-#ifdef LWS_WITH_PLUGINS
-       if (info->plugin_dirs)
-               lws_plat_plugins_init(context, info->plugin_dirs);
-#endif
-
-       return 0;
-}
diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c
deleted file mode 100644 (file)
index 28e5bbe..0000000
+++ /dev/null
@@ -1,828 +0,0 @@
-#include "private-libwebsockets.h"
-
-#include <pwd.h>
-#include <grp.h>
-
-#ifdef LWS_WITH_PLUGINS
-#include <dlfcn.h>
-#endif
-#include <dirent.h>
-
-
-/*
- * included from libwebsockets.c for unix builds
- */
-
-unsigned long long time_in_microseconds(void)
-{
-       struct timeval tv;
-       gettimeofday(&tv, NULL);
-       return ((unsigned long long)tv.tv_sec * 1000000LL) + tv.tv_usec;
-}
-
-LWS_VISIBLE int
-lws_get_random(struct lws_context *context, void *buf, int len)
-{
-       return read(context->fd_random, (char *)buf, len);
-}
-
-LWS_VISIBLE int
-lws_send_pipe_choked(struct lws *wsi)
-{
-       struct lws_pollfd fds;
-
-       /* treat the fact we got a truncated send pending as if we're choked */
-       if (wsi->trunc_len)
-               return 1;
-
-       fds.fd = wsi->desc.sockfd;
-       fds.events = POLLOUT;
-       fds.revents = 0;
-
-       if (poll(&fds, 1, 0) != 1)
-               return 1;
-
-       if ((fds.revents & POLLOUT) == 0)
-               return 1;
-
-       /* okay to send another packet without blocking */
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_poll_listen_fd(struct lws_pollfd *fd)
-{
-       return poll(fd, 1, 0);
-}
-
-LWS_VISIBLE void
-lws_cancel_service_pt(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       char buf = 0;
-
-       if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
-               lwsl_err("Cannot write to dummy pipe");
-}
-
-LWS_VISIBLE void
-lws_cancel_service(struct lws_context *context)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       char buf = 0, m = context->count_threads;
-
-       while (m--) {
-               if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1)
-                       lwsl_err("Cannot write to dummy pipe");
-               pt++;
-       }
-}
-
-LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
-{
-       int syslog_level = LOG_DEBUG;
-
-       switch (level) {
-       case LLL_ERR:
-               syslog_level = LOG_ERR;
-               break;
-       case LLL_WARN:
-               syslog_level = LOG_WARNING;
-               break;
-       case LLL_NOTICE:
-               syslog_level = LOG_NOTICE;
-               break;
-       case LLL_INFO:
-               syslog_level = LOG_INFO;
-               break;
-       }
-       syslog(syslog_level, "%s", line);
-}
-
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
-{
-       struct lws_context_per_thread *pt;
-       int n = -1, m, c;
-       char buf;
-
-       /* stay dead once we are dead */
-
-       if (!context || !context->vhost_list)
-               return 1;
-
-       pt = &context->pt[tsi];
-
-       lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1);
-
-       if (timeout_ms < 0)
-               goto faked_service;
-
-       lws_libev_run(context, tsi);
-       lws_libuv_run(context, tsi);
-       lws_libevent_run(context, tsi);
-
-       if (!context->service_tid_detected) {
-               struct lws _lws;
-
-               memset(&_lws, 0, sizeof(_lws));
-               _lws.context = context;
-
-               context->service_tid_detected =
-                       context->vhost_list->protocols[0].callback(
-                       &_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
-       }
-       context->service_tid = context->service_tid_detected;
-
-       /*
-        * is there anybody with pending stuff that needs service forcing?
-        */
-       if (!lws_service_adjust_timeout(context, 1, tsi)) {
-               /* -1 timeout means just do forced service */
-               _lws_plat_service_tsi(context, -1, pt->tid);
-               /* still somebody left who wants forced service? */
-               if (!lws_service_adjust_timeout(context, 1, pt->tid))
-                       /* yes... come back again quickly */
-                       timeout_ms = 0;
-       }
-
-       n = poll(pt->fds, pt->fds_count, timeout_ms);
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if (!pt->rx_draining_ext_list &&
-           !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
-#else
-       if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
-#endif
-               lws_service_fd_tsi(context, NULL, tsi);
-               return 0;
-       }
-
-faked_service:
-       m = lws_service_flag_pending(context, tsi);
-       if (m)
-               c = -1; /* unknown limit */
-       else
-               if (n < 0) {
-                       if (LWS_ERRNO != LWS_EINTR)
-                               return -1;
-                       return 0;
-               } else
-                       c = n;
-
-       /* any socket with events to service? */
-       for (n = 0; n < pt->fds_count && c; n++) {
-               if (!pt->fds[n].revents)
-                       continue;
-
-               c--;
-
-               if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) {
-                       if (read(pt->fds[n].fd, &buf, 1) != 1)
-                               lwsl_err("Cannot read from dummy pipe.");
-                       continue;
-               }
-
-               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
-               if (m < 0)
-                       return -1;
-               /* if something closed, retry this slot */
-               if (m)
-                       n--;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_check_connection_error(struct lws *wsi)
-{
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_service(struct lws_context *context, int timeout_ms)
-{
-       return _lws_plat_service_tsi(context, timeout_ms, 0);
-}
-
-LWS_VISIBLE int
-lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
-{
-       int optval = 1;
-       socklen_t optlen = sizeof(optval);
-
-#if defined(__APPLE__) || \
-    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
-    defined(__NetBSD__) || \
-    defined(__OpenBSD__)
-       struct protoent *tcp_proto;
-#endif
-
-       if (vhost->ka_time) {
-               /* enable keepalive on this socket */
-               optval = 1;
-               if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-
-#if defined(__APPLE__) || \
-    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
-    defined(__NetBSD__) || \
-        defined(__CYGWIN__) || defined(__OpenBSD__) || defined (__sun)
-
-               /*
-                * didn't find a way to set these per-socket, need to
-                * tune kernel systemwide values
-                */
-#else
-               /* set the keepalive conditions we want on it too */
-               optval = vhost->ka_time;
-               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-
-               optval = vhost->ka_interval;
-               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-
-               optval = vhost->ka_probes;
-               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-#endif
-       }
-
-#if defined(SO_BINDTODEVICE)
-       if (vhost->bind_iface && vhost->iface) {
-               lwsl_info("binding listen skt to %s using SO_BINDTODEVICE\n", vhost->iface);
-               if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, vhost->iface,
-                               strlen(vhost->iface)) < 0) {
-                       lwsl_warn("Failed to bind to device %s\n", vhost->iface);
-                       return 1;
-               }
-       }
-#endif
-
-       /* Disable Nagle */
-       optval = 1;
-#if defined (__sun)
-       if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
-               return 1;
-#elif !defined(__APPLE__) && \
-      !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) &&        \
-      !defined(__NetBSD__) && \
-      !defined(__OpenBSD__)
-       if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
-               return 1;
-#else
-       tcp_proto = getprotobyname("TCP");
-       if (setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, &optval, optlen) < 0)
-               return 1;
-#endif
-
-       /* We are nonblocking... */
-       if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
-               return 1;
-
-       return 0;
-}
-
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-static void
-_lws_plat_apply_caps(int mode, cap_value_t *cv, int count)
-{
-       cap_t caps;
-
-       if (!count)
-               return;
-
-       caps = cap_get_proc();
-
-       cap_set_flag(caps, mode, count, cv, CAP_SET);
-       cap_set_proc(caps);
-       prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
-       cap_free(caps);
-}
-#endif
-
-LWS_VISIBLE void
-lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
-{
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-       int n;
-#endif
-
-       if (info->gid != -1)
-               if (setgid(info->gid))
-                       lwsl_warn("setgid: %s\n", strerror(LWS_ERRNO));
-
-       if (info->uid != -1) {
-               struct passwd *p = getpwuid(info->uid);
-
-               if (p) {
-
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-                       _lws_plat_apply_caps(CAP_PERMITTED, info->caps, info->count_caps);
-#endif
-
-                       initgroups(p->pw_name, info->gid);
-                       if (setuid(info->uid))
-                               lwsl_warn("setuid: %s\n", strerror(LWS_ERRNO));
-                       else
-                               lwsl_notice("Set privs to user '%s'\n", p->pw_name);
-
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-                       _lws_plat_apply_caps(CAP_EFFECTIVE, info->caps, info->count_caps);
-
-                       if (info->count_caps)
-                               for (n = 0; n < info->count_caps; n++)
-                                       lwsl_notice("   RETAINING CAPABILITY %d\n", (int)info->caps[n]);
-#endif
-
-               } else
-                       lwsl_warn("getpwuid: unable to find uid %d", info->uid);
-       }
-}
-
-#ifdef LWS_WITH_PLUGINS
-
-#if defined(LWS_USE_LIBUV) && UV_VERSION_MAJOR > 0
-
-/* libuv.c implements these in a cross-platform way */
-
-#else
-
-static int filter(const struct dirent *ent)
-{
-       if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
-               return 0;
-
-       return 1;
-}
-
-LWS_VISIBLE int
-lws_plat_plugins_init(struct lws_context * context, const char * const *d)
-{
-       struct lws_plugin_capability lcaps;
-       struct lws_plugin *plugin;
-       lws_plugin_init_func initfunc;
-       struct dirent **namelist;
-       int n, i, m, ret = 0;
-       char path[256];
-       void *l;
-
-       lwsl_notice("  Plugins:\n");
-
-       while (d && *d) {
-               n = scandir(*d, &namelist, filter, alphasort);
-               if (n < 0) {
-                       lwsl_err("Scandir on %s failed\n", *d);
-                       return 1;
-               }
-
-               for (i = 0; i < n; i++) {
-                       if (strlen(namelist[i]->d_name) < 7)
-                               goto inval;
-
-                       lwsl_notice("   %s\n", namelist[i]->d_name);
-
-                       lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d,
-                                namelist[i]->d_name);
-                       l = dlopen(path, RTLD_NOW);
-                       if (!l) {
-                               lwsl_err("Error loading DSO: %s\n", dlerror());
-                               while (i++ < n)
-                                       free(namelist[i]);
-                               goto bail;
-                       }
-                       /* we could open it, can we get his init function? */
-                       m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
-                                    namelist[i]->d_name + 3 /* snip lib... */);
-                       path[m - 3] = '\0'; /* snip the .so */
-                       initfunc = dlsym(l, path);
-                       if (!initfunc) {
-                               lwsl_err("Failed to get init on %s: %s",
-                                               namelist[i]->d_name, dlerror());
-                               dlclose(l);
-                       }
-                       lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
-                       m = initfunc(context, &lcaps);
-                       if (m) {
-                               lwsl_err("Initializing %s failed %d\n",
-                                       namelist[i]->d_name, m);
-                               dlclose(l);
-                               goto skip;
-                       }
-
-                       plugin = lws_malloc(sizeof(*plugin));
-                       if (!plugin) {
-                               lwsl_err("OOM\n");
-                               goto bail;
-                       }
-                       plugin->list = context->plugin_list;
-                       context->plugin_list = plugin;
-                       strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1);
-                       plugin->name[sizeof(plugin->name) - 1] = '\0';
-                       plugin->l = l;
-                       plugin->caps = lcaps;
-                       context->plugin_protocol_count += lcaps.count_protocols;
-                       context->plugin_extension_count += lcaps.count_extensions;
-
-                       free(namelist[i]);
-                       continue;
-
-       skip:
-                       dlclose(l);
-       inval:
-                       free(namelist[i]);
-               }
-               free(namelist);
-               d++;
-       }
-
-bail:
-       free(namelist);
-
-       return ret;
-}
-
-LWS_VISIBLE int
-lws_plat_plugins_destroy(struct lws_context * context)
-{
-       struct lws_plugin *plugin = context->plugin_list, *p;
-       lws_plugin_destroy_func func;
-       char path[256];
-       int m;
-
-       if (!plugin)
-               return 0;
-
-       lwsl_notice("%s\n", __func__);
-
-       while (plugin) {
-               p = plugin;
-               m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + 3);
-               path[m - 3] = '\0';
-               func = dlsym(plugin->l, path);
-               if (!func) {
-                       lwsl_err("Failed to get destroy on %s: %s",
-                                       plugin->name, dlerror());
-                       goto next;
-               }
-               m = func(context);
-               if (m)
-                       lwsl_err("Initializing %s failed %d\n",
-                               plugin->name, m);
-next:
-               dlclose(p->l);
-               plugin = p->list;
-               p->list = NULL;
-               free(p);
-       }
-
-       context->plugin_list = NULL;
-
-       return 0;
-}
-
-#endif
-#endif
-
-
-#if 0
-static void
-sigabrt_handler(int x)
-{
-       printf("%s\n", __func__);
-       //*(char *)0 = 0;
-}
-#endif
-
-LWS_VISIBLE int
-lws_plat_context_early_init(void)
-{
-#if !defined(LWS_AVOID_SIGPIPE_IGN)
-       signal(SIGPIPE, SIG_IGN);
-#endif
-
-//     signal(SIGABRT, sigabrt_handler);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_context_early_destroy(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE void
-lws_plat_context_late_destroy(struct lws_context *context)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       int m = context->count_threads;
-
-#ifdef LWS_WITH_PLUGINS
-       if (context->plugin_list)
-               lws_plat_plugins_destroy(context);
-#endif
-
-       if (context->lws_lookup)
-               lws_free(context->lws_lookup);
-
-       while (m--) {
-               if (pt->dummy_pipe_fds[0])
-                       close(pt->dummy_pipe_fds[0]);
-               if (pt->dummy_pipe_fds[1])
-                       close(pt->dummy_pipe_fds[1]);
-               pt++;
-       }
-       if (!context->fd_random)
-               lwsl_err("ZERO RANDOM FD\n");
-       if (context->fd_random != LWS_INVALID_FILE)
-               close(context->fd_random);
-}
-
-/* cast a struct sockaddr_in6 * into addr for ipv6 */
-
-LWS_VISIBLE int
-lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
-                   size_t addrlen)
-{
-       int rc = -1;
-
-       struct ifaddrs *ifr;
-       struct ifaddrs *ifc;
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
-#endif
-
-       getifaddrs(&ifr);
-       for (ifc = ifr; ifc != NULL && rc; ifc = ifc->ifa_next) {
-               if (!ifc->ifa_addr)
-                       continue;
-
-               lwsl_info(" interface %s vs %s\n", ifc->ifa_name, ifname);
-
-               if (strcmp(ifc->ifa_name, ifname))
-                       continue;
-
-               switch (ifc->ifa_addr->sa_family) {
-               case AF_INET:
-#ifdef LWS_USE_IPV6
-                       if (ipv6) {
-                               /* map IPv4 to IPv6 */
-                               bzero((char *)&addr6->sin6_addr,
-                                               sizeof(struct in6_addr));
-                               addr6->sin6_addr.s6_addr[10] = 0xff;
-                               addr6->sin6_addr.s6_addr[11] = 0xff;
-                               memcpy(&addr6->sin6_addr.s6_addr[12],
-                                       &((struct sockaddr_in *)ifc->ifa_addr)->sin_addr,
-                                                       sizeof(struct in_addr));
-                       } else
-#endif
-                               memcpy(addr,
-                                       (struct sockaddr_in *)ifc->ifa_addr,
-                                                   sizeof(struct sockaddr_in));
-                       break;
-#ifdef LWS_USE_IPV6
-               case AF_INET6:
-                       memcpy(&addr6->sin6_addr,
-                         &((struct sockaddr_in6 *)ifc->ifa_addr)->sin6_addr,
-                                                      sizeof(struct in6_addr));
-                       break;
-#endif
-               default:
-                       continue;
-               }
-               rc = 0;
-       }
-
-       freeifaddrs(ifr);
-
-       if (rc == -1) {
-               /* check if bind to IP address */
-#ifdef LWS_USE_IPV6
-               if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1)
-                       rc = 0;
-               else
-#endif
-               if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1)
-                       rc = 0;
-       }
-
-       return rc;
-}
-
-LWS_VISIBLE void
-lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ);
-       lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ);
-       lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ);
-
-       pt->fds[pt->fds_count++].revents = 0;
-}
-
-LWS_VISIBLE void
-lws_plat_delete_socket_from_fds(struct lws_context *context,
-                                               struct lws *wsi, int m)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
-       lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
-       lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
-
-       pt->fds_count--;
-}
-
-LWS_VISIBLE void
-lws_plat_service_periodic(struct lws_context *context)
-{
-       /* if our parent went down, don't linger around */
-       if (context->started_with_parent &&
-           kill(context->started_with_parent, 0) < 0)
-               kill(getpid(), SIGTERM);
-}
-
-LWS_VISIBLE int
-lws_plat_change_pollfd(struct lws_context *context,
-                     struct lws *wsi, struct lws_pollfd *pfd)
-{
-       return 0;
-}
-
-LWS_VISIBLE const char *
-lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
-{
-       return inet_ntop(af, src, dst, cnt);
-}
-
-LWS_VISIBLE int
-lws_plat_inet_pton(int af, const char *src, void *dst)
-{
-       return inet_pton(af, src, dst);
-}
-
-LWS_VISIBLE lws_fop_fd_t
-_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-                   const char *vpath, lws_fop_flags_t *flags)
-{
-       struct stat stat_buf;
-       int ret = open(filename, (*flags) & LWS_FOP_FLAGS_MASK, 0664);
-       lws_fop_fd_t fop_fd;
-
-       if (ret < 0)
-               return NULL;
-
-       if (fstat(ret, &stat_buf) < 0)
-               goto bail;
-
-       fop_fd = malloc(sizeof(*fop_fd));
-       if (!fop_fd)
-               goto bail;
-
-       fop_fd->fops = fops;
-       fop_fd->flags = *flags;
-       fop_fd->fd = ret;
-       fop_fd->filesystem_priv = NULL; /* we don't use it */
-       fop_fd->len = stat_buf.st_size;
-       fop_fd->pos = 0;
-
-       return fop_fd;
-
-bail:
-       close(ret);
-       return NULL;
-}
-
-LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t *fop_fd)
-{
-       int fd = (*fop_fd)->fd;
-
-       free(*fop_fd);
-       *fop_fd = NULL;
-
-       return close(fd);
-}
-
-LWS_VISIBLE lws_fileofs_t
-_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
-{
-       lws_fileofs_t r;
-
-       if (offset > 0 && offset > fop_fd->len - fop_fd->pos)
-               offset = fop_fd->len - fop_fd->pos;
-
-       if ((lws_fileofs_t)fop_fd->pos + offset < 0)
-               offset = -fop_fd->pos;
-
-       r = lseek(fop_fd->fd, offset, SEEK_CUR);
-
-       if (r >= 0)
-               fop_fd->pos = r;
-       else
-               lwsl_err("error seeking from cur %ld, offset %ld\n",
-                        (long)fop_fd->pos, (long)offset);
-
-       return r;
-}
-
-LWS_VISIBLE int
-_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                   uint8_t *buf, lws_filepos_t len)
-{
-       long n;
-
-       n = read((int)fop_fd->fd, buf, len);
-       if (n == -1) {
-               *amount = 0;
-               return -1;
-       }
-       fop_fd->pos += n;
-       lwsl_debug("%s: read %ld of req %ld, pos %ld, len %ld\n", __func__, n,
-                  (long)len, (long)fop_fd->pos, (long)fop_fd->len);
-       *amount = n;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                    uint8_t *buf, lws_filepos_t len)
-{
-       long n;
-
-       n = write((int)fop_fd->fd, buf, len);
-       if (n == -1) {
-               *amount = 0;
-               return -1;
-       }
-
-       fop_fd->pos += n;
-       *amount = n;
-
-       return 0;
-}
-
-
-LWS_VISIBLE int
-lws_plat_init(struct lws_context *context,
-             struct lws_context_creation_info *info)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       int n = context->count_threads, fd;
-
-       /* master context has the global fd lookup array */
-       context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
-                                        context->max_fds);
-       if (context->lws_lookup == NULL) {
-               lwsl_err("OOM on lws_lookup array for %d connections\n",
-                        context->max_fds);
-               return 1;
-       }
-
-       lwsl_notice(" mem: platform fd map: %5lu bytes\n",
-                   (unsigned long)(sizeof(struct lws *) * context->max_fds));
-       fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
-
-       context->fd_random = fd;
-       if (context->fd_random < 0) {
-               lwsl_err("Unable to open random device %s %d\n",
-                        SYSTEM_RANDOM_FILEPATH, context->fd_random);
-               return 1;
-       }
-
-       if (!lws_libev_init_fd_table(context) &&
-           !lws_libuv_init_fd_table(context) &&
-           !lws_libevent_init_fd_table(context)) {
-               /* otherwise libev handled it instead */
-
-               while (n--) {
-                       if (pipe(pt->dummy_pipe_fds)) {
-                               lwsl_err("Unable to create pipe\n");
-                               return 1;
-                       }
-
-                       /* use the read end of pipe as first item */
-                       pt->fds[0].fd = pt->dummy_pipe_fds[0];
-                       pt->fds[0].events = LWS_POLLIN;
-                       pt->fds[0].revents = 0;
-                       pt->fds_count = 1;
-                       pt++;
-               }
-       }
-
-#ifdef LWS_WITH_PLUGINS
-       if (info->plugin_dirs)
-               lws_plat_plugins_init(context, info->plugin_dirs);
-#endif
-
-       return 0;
-}
diff --git a/lib/lws-plat-win.c b/lib/lws-plat-win.c
deleted file mode 100644 (file)
index fc4f1fd..0000000
+++ /dev/null
@@ -1,742 +0,0 @@
-#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
-#define _WINSOCK_DEPRECATED_NO_WARNINGS
-#endif
-#include "private-libwebsockets.h"
-
-unsigned long long
-time_in_microseconds()
-{
-#ifndef DELTA_EPOCH_IN_MICROSECS
-#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
-#endif
-       FILETIME filetime;
-       ULARGE_INTEGER datetime;
-
-#ifdef _WIN32_WCE
-       GetCurrentFT(&filetime);
-#else
-       GetSystemTimeAsFileTime(&filetime);
-#endif
-
-       /*
-        * As per Windows documentation for FILETIME, copy the resulting FILETIME structure to a
-        * ULARGE_INTEGER structure using memcpy (using memcpy instead of direct assignment can
-        * prevent alignment faults on 64-bit Windows).
-        */
-       memcpy(&datetime, &filetime, sizeof(datetime));
-
-       /* Windows file times are in 100s of nanoseconds. */
-       return (datetime.QuadPart - DELTA_EPOCH_IN_MICROSECS) / 10;
-}
-
-#ifdef _WIN32_WCE
-time_t time(time_t *t)
-{
-       time_t ret = time_in_microseconds() / 1000000;
-
-       if(t != NULL)
-               *t = ret;
-
-       return ret;
-}
-#endif
-
-/* file descriptor hash management */
-
-struct lws *
-wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
-{
-       int h = LWS_FD_HASH(fd);
-       int n = 0;
-
-       for (n = 0; n < context->fd_hashtable[h].length; n++)
-               if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd)
-                       return context->fd_hashtable[h].wsi[n];
-
-       return NULL;
-}
-
-int
-insert_wsi(struct lws_context *context, struct lws *wsi)
-{
-       int h = LWS_FD_HASH(wsi->desc.sockfd);
-
-       if (context->fd_hashtable[h].length == (getdtablesize() - 1)) {
-               lwsl_err("hash table overflow\n");
-               return 1;
-       }
-
-       context->fd_hashtable[h].wsi[context->fd_hashtable[h].length++] = wsi;
-
-       return 0;
-}
-
-int
-delete_from_fd(struct lws_context *context, lws_sockfd_type fd)
-{
-       int h = LWS_FD_HASH(fd);
-       int n = 0;
-
-       for (n = 0; n < context->fd_hashtable[h].length; n++)
-               if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) {
-                       while (n < context->fd_hashtable[h].length) {
-                               context->fd_hashtable[h].wsi[n] =
-                                               context->fd_hashtable[h].wsi[n + 1];
-                               n++;
-                       }
-                       context->fd_hashtable[h].length--;
-
-                       return 0;
-               }
-
-       lwsl_err("Failed to find fd %d requested for "
-                "delete in hashtable\n", fd);
-       return 1;
-}
-
-LWS_VISIBLE int lws_get_random(struct lws_context *context,
-                                                                void *buf, int len)
-{
-       int n;
-       char *p = (char *)buf;
-
-       for (n = 0; n < len; n++)
-               p[n] = (unsigned char)rand();
-
-       return n;
-}
-
-LWS_VISIBLE int lws_send_pipe_choked(struct lws *wsi)
-{
-       /* treat the fact we got a truncated send pending as if we're choked */
-       if (wsi->trunc_len)
-               return 1;
-
-       return (int)wsi->sock_send_blocking;
-}
-
-LWS_VISIBLE int lws_poll_listen_fd(struct lws_pollfd *fd)
-{
-       fd_set readfds;
-       struct timeval tv = { 0, 0 };
-
-       assert((fd->events & LWS_POLLIN) == LWS_POLLIN);
-
-       FD_ZERO(&readfds);
-       FD_SET(fd->fd, &readfds);
-
-       return select(fd->fd + 1, &readfds, NULL, NULL, &tv);
-}
-
-LWS_VISIBLE void
-lws_cancel_service(struct lws_context *context)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       int n = context->count_threads;
-
-       while (n--) {
-               WSASetEvent(pt->events[0]);
-               pt++;
-       }
-}
-
-LWS_VISIBLE void
-lws_cancel_service_pt(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       WSASetEvent(pt->events[0]);
-}
-
-LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
-{
-       lwsl_emit_stderr(level, line);
-}
-
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
-{
-       struct lws_context_per_thread *pt;
-       WSANETWORKEVENTS networkevents;
-       struct lws_pollfd *pfd;
-       struct lws *wsi;
-       unsigned int i;
-       DWORD ev;
-       int n, m;
-
-       /* stay dead once we are dead */
-       if (context == NULL)
-               return 1;
-
-       pt = &context->pt[tsi];
-
-       if (!context->service_tid_detected) {
-               struct lws _lws;
-
-               memset(&_lws, 0, sizeof(_lws));
-               _lws.context = context;
-
-               context->service_tid_detected = context->vhost_list->
-                       protocols[0].callback(&_lws, LWS_CALLBACK_GET_THREAD_ID,
-                                                 NULL, NULL, 0);
-       }
-       context->service_tid = context->service_tid_detected;
-
-       if (timeout_ms < 0)
-       {
-                       if (lws_service_flag_pending(context, tsi)) {
-                       /* any socket with events to service? */
-                       for (n = 0; n < (int)pt->fds_count; n++) {
-                               if (!pt->fds[n].revents)
-                                       continue;
-
-                               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
-                               if (m < 0)
-                                       return -1;
-                               /* if something closed, retry this slot */
-                               if (m)
-                                       n--;
-                       }
-               }
-               return 0;
-       }
-
-       for (i = 0; i < pt->fds_count; ++i) {
-               pfd = &pt->fds[i];
-
-               if (!(pfd->events & LWS_POLLOUT))
-                       continue;
-
-               wsi = wsi_from_fd(context, pfd->fd);
-               if (wsi->listener)
-                       continue;
-               if (!wsi || wsi->sock_send_blocking)
-                       continue;
-               pfd->revents = LWS_POLLOUT;
-               n = lws_service_fd(context, pfd);
-               if (n < 0)
-                       return -1;
-               /* if something closed, retry this slot */
-               if (n)
-                       i--;
-       }
-
-       /*
-        * is there anybody with pending stuff that needs service forcing?
-        */
-       if (!lws_service_adjust_timeout(context, 1, tsi)) {
-               /* -1 timeout means just do forced service */
-               _lws_plat_service_tsi(context, -1, pt->tid);
-               /* still somebody left who wants forced service? */
-               if (!lws_service_adjust_timeout(context, 1, pt->tid))
-                       /* yes... come back again quickly */
-                       timeout_ms = 0;
-       }
-
-       ev = WSAWaitForMultipleEvents( 1,  pt->events , FALSE, timeout_ms, FALSE);
-       if (ev == WSA_WAIT_EVENT_0) {
-               unsigned int eIdx;
-
-               WSAResetEvent(pt->events[0]);
-
-               for (eIdx = 0; eIdx < pt->fds_count; ++eIdx) {
-                       if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, 0, &networkevents) == SOCKET_ERROR) {
-                               lwsl_err("WSAEnumNetworkEvents() failed with error %d\n", LWS_ERRNO);
-                               return -1;
-                       }
-
-                       pfd = &pt->fds[eIdx];
-                       pfd->revents = (short)networkevents.lNetworkEvents;
-
-                       if ((networkevents.lNetworkEvents & FD_CONNECT) &&
-                                networkevents.iErrorCode[FD_CONNECT_BIT] &&
-                                networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EALREADY &&
-                                networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EINPROGRESS &&
-                                networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EWOULDBLOCK &&
-                                networkevents.iErrorCode[FD_CONNECT_BIT] != WSAEINVAL) {
-                               lwsl_debug("Unable to connect errno=%d\n",
-                                          networkevents.iErrorCode[FD_CONNECT_BIT]);
-                               pfd->revents = LWS_POLLHUP;
-                       } else
-                               pfd->revents = (short)networkevents.lNetworkEvents;
-
-                       if (pfd->revents & LWS_POLLOUT) {
-                               wsi = wsi_from_fd(context, pfd->fd);
-                               if (wsi)
-                                       wsi->sock_send_blocking = 0;
-                       }
-                        /* if something closed, retry this slot */
-                       if (pfd->revents & LWS_POLLHUP)
-                                       --eIdx;
-
-                       if( pfd->revents != 0 ) {
-                               lws_service_fd_tsi(context, pfd, tsi);
-
-                       }
-               }
-       }
-
-       context->service_tid = 0;
-
-       if (ev == WSA_WAIT_TIMEOUT) {
-               lws_service_fd(context, NULL);
-       }
-       return 0;;
-}
-
-LWS_VISIBLE int
-lws_plat_service(struct lws_context *context, int timeout_ms)
-{
-       return _lws_plat_service_tsi(context, timeout_ms, 0);
-}
-
-LWS_VISIBLE int
-lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd)
-{
-       int optval = 1;
-       int optlen = sizeof(optval);
-       u_long optl = 1;
-       DWORD dwBytesRet;
-       struct tcp_keepalive alive;
-       int protonbr;
-#ifndef _WIN32_WCE
-       struct protoent *tcp_proto;
-#endif
-
-       if (vhost->ka_time) {
-               /* enable keepalive on this socket */
-               optval = 1;
-               if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-                                                (const char *)&optval, optlen) < 0)
-                       return 1;
-
-               alive.onoff = TRUE;
-               alive.keepalivetime = vhost->ka_time;
-               alive.keepaliveinterval = vhost->ka_interval;
-
-               if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),
-                                                 NULL, 0, &dwBytesRet, NULL, NULL))
-                       return 1;
-       }
-
-       /* Disable Nagle */
-       optval = 1;
-#ifndef _WIN32_WCE
-       tcp_proto = getprotobyname("TCP");
-       if (!tcp_proto) {
-               lwsl_err("getprotobyname() failed with error %d\n", LWS_ERRNO);
-               return 1;
-       }
-       protonbr = tcp_proto->p_proto;
-#else
-       protonbr = 6;
-#endif
-
-       setsockopt(fd, protonbr, TCP_NODELAY, (const char *)&optval, optlen);
-
-       /* We are nonblocking... */
-       ioctlsocket(fd, FIONBIO, &optl);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_context_early_init(void)
-{
-       WORD wVersionRequested;
-       WSADATA wsaData;
-       int err;
-
-       /* Use the MAKEWORD(lowbyte, highbyte) macro from Windef.h */
-       wVersionRequested = MAKEWORD(2, 2);
-
-       err = WSAStartup(wVersionRequested, &wsaData);
-       if (!err)
-               return 0;
-       /*
-        * Tell the user that we could not find a usable
-        * Winsock DLL
-        */
-       lwsl_err("WSAStartup failed with error: %d\n", err);
-
-       return 1;
-}
-
-LWS_VISIBLE void
-lws_plat_context_early_destroy(struct lws_context *context)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       int n = context->count_threads;
-
-       while (n--) {
-               if (pt->events) {
-                       WSACloseEvent(pt->events[0]);
-                       lws_free(pt->events);
-               }
-               pt++;
-       }
-}
-
-LWS_VISIBLE void
-lws_plat_context_late_destroy(struct lws_context *context)
-{
-       int n;
-
-       for (n = 0; n < FD_HASHTABLE_MODULUS; n++) {
-               if (context->fd_hashtable[n].wsi)
-                       lws_free(context->fd_hashtable[n].wsi);
-       }
-
-       WSACleanup();
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_interface_to_sa(int ipv6,
-               const char *ifname, struct sockaddr_in *addr, size_t addrlen)
-{
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
-
-       if (ipv6) {
-               if (lws_plat_inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) {
-                       return 0;
-               }
-       }
-#endif
-
-       long long address = inet_addr(ifname);
-
-       if (address == INADDR_NONE) {
-               struct hostent *entry = gethostbyname(ifname);
-               if (entry)
-                       address = ((struct in_addr *)entry->h_addr_list[0])->s_addr;
-       }
-
-       if (address == INADDR_NONE)
-               return -1;
-
-       addr->sin_addr.s_addr = (lws_intptr_t)address;
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       pt->fds[pt->fds_count++].revents = 0;
-       pt->events[pt->fds_count] = pt->events[0];
-       WSAEventSelect(wsi->desc.sockfd, pt->events[0],
-                          LWS_POLLIN | LWS_POLLHUP | FD_CONNECT);
-}
-
-LWS_VISIBLE void
-lws_plat_delete_socket_from_fds(struct lws_context *context,
-                                               struct lws *wsi, int m)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       pt->events[m + 1] = pt->events[pt->fds_count--];
-}
-
-LWS_VISIBLE void
-lws_plat_service_periodic(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_check_connection_error(struct lws *wsi)
-{
-       int optVal;
-       int optLen = sizeof(int);
-
-       if (getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR,
-                          (char*)&optVal, &optLen) != SOCKET_ERROR && optVal &&
-               optVal != LWS_EALREADY && optVal != LWS_EINPROGRESS &&
-               optVal != LWS_EWOULDBLOCK && optVal != WSAEINVAL) {
-                  lwsl_debug("Connect failed SO_ERROR=%d\n", optVal);
-                  return 1;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_change_pollfd(struct lws_context *context,
-                         struct lws *wsi, struct lws_pollfd *pfd)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       long networkevents = LWS_POLLHUP | FD_CONNECT;
-
-       if ((pfd->events & LWS_POLLIN))
-               networkevents |= LWS_POLLIN;
-
-       if ((pfd->events & LWS_POLLOUT))
-               networkevents |= LWS_POLLOUT;
-
-       if (WSAEventSelect(wsi->desc.sockfd,
-                       pt->events[0],
-                                                  networkevents) != SOCKET_ERROR)
-               return 0;
-
-       lwsl_err("WSAEventSelect() failed with error %d\n", LWS_ERRNO);
-
-       return 1;
-}
-
-LWS_VISIBLE const char *
-lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
-{
-       WCHAR *buffer;
-       DWORD bufferlen = cnt;
-       BOOL ok = FALSE;
-
-       buffer = lws_malloc(bufferlen * 2);
-       if (!buffer) {
-               lwsl_err("Out of memory\n");
-               return NULL;
-       }
-
-       if (af == AF_INET) {
-               struct sockaddr_in srcaddr;
-               bzero(&srcaddr, sizeof(srcaddr));
-               srcaddr.sin_family = AF_INET;
-               memcpy(&(srcaddr.sin_addr), src, sizeof(srcaddr.sin_addr));
-
-               if (!WSAAddressToStringW((struct sockaddr*)&srcaddr, sizeof(srcaddr), 0, buffer, &bufferlen))
-                       ok = TRUE;
-#ifdef LWS_USE_IPV6
-       } else if (af == AF_INET6) {
-               struct sockaddr_in6 srcaddr;
-               bzero(&srcaddr, sizeof(srcaddr));
-               srcaddr.sin6_family = AF_INET6;
-               memcpy(&(srcaddr.sin6_addr), src, sizeof(srcaddr.sin6_addr));
-
-               if (!WSAAddressToStringW((struct sockaddr*)&srcaddr, sizeof(srcaddr), 0, buffer, &bufferlen))
-                       ok = TRUE;
-#endif
-       } else
-               lwsl_err("Unsupported type\n");
-
-       if (!ok) {
-               int rv = WSAGetLastError();
-               lwsl_err("WSAAddressToString() : %d\n", rv);
-       } else {
-               if (WideCharToMultiByte(CP_ACP, 0, buffer, bufferlen, dst, cnt, 0, NULL) <= 0)
-                       ok = FALSE;
-       }
-
-       lws_free(buffer);
-       return ok ? dst : NULL;
-}
-
-LWS_VISIBLE int
-lws_plat_inet_pton(int af, const char *src, void *dst)
-{
-       WCHAR *buffer;
-       DWORD bufferlen = strlen(src) + 1;
-       BOOL ok = FALSE;
-
-       buffer = lws_malloc(bufferlen * 2);
-       if (!buffer) {
-               lwsl_err("Out of memory\n");
-               return -1;
-       }
-
-       if (MultiByteToWideChar(CP_ACP, 0, src, bufferlen, buffer, bufferlen) <= 0) {
-               lwsl_err("Failed to convert multi byte to wide char\n");
-               lws_free(buffer);
-               return -1;
-       }
-
-       if (af == AF_INET) {
-               struct sockaddr_in dstaddr;
-               int dstaddrlen = sizeof(dstaddr);
-               bzero(&dstaddr, sizeof(dstaddr));
-               dstaddr.sin_family = AF_INET;
-
-               if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) {
-                       ok = TRUE;
-                       memcpy(dst, &dstaddr.sin_addr, sizeof(dstaddr.sin_addr));
-               }
-#ifdef LWS_USE_IPV6
-       } else if (af == AF_INET6) {
-               struct sockaddr_in6 dstaddr;
-               int dstaddrlen = sizeof(dstaddr);
-               bzero(&dstaddr, sizeof(dstaddr));
-               dstaddr.sin6_family = AF_INET6;
-
-               if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) {
-                       ok = TRUE;
-                       memcpy(dst, &dstaddr.sin6_addr, sizeof(dstaddr.sin6_addr));
-               }
-#endif
-       } else
-               lwsl_err("Unsupported type\n");
-
-       if (!ok) {
-               int rv = WSAGetLastError();
-               lwsl_err("WSAAddressToString() : %d\n", rv);
-       }
-
-       lws_free(buffer);
-       return ok ? 1 : -1;
-}
-
-LWS_VISIBLE lws_fop_fd_t
-_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-                   const char *vpath, lws_fop_flags_t *flags)
-{
-       HANDLE ret;
-       WCHAR buf[MAX_PATH];
-       lws_fop_fd_t fop_fd;
-       LARGE_INTEGER llFileSize = {0};
-
-       MultiByteToWideChar(CP_UTF8, 0, filename, -1, buf, ARRAY_SIZE(buf));
-       if (((*flags) & 7) == _O_RDONLY) {
-               ret = CreateFileW(buf, GENERIC_READ, FILE_SHARE_READ,
-                         NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
-       } else {
-               ret = CreateFileW(buf, GENERIC_WRITE, 0, NULL,
-                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-       }
-
-       if (ret == LWS_INVALID_FILE)
-               goto bail;
-
-       fop_fd = malloc(sizeof(*fop_fd));
-       if (!fop_fd)
-               goto bail;
-
-       fop_fd->fops = fops;
-       fop_fd->fd = ret;
-       fop_fd->filesystem_priv = NULL; /* we don't use it */
-       fop_fd->flags = *flags;
-       fop_fd->len = GetFileSize(ret, NULL);
-       if(GetFileSizeEx(ret, &llFileSize))
-               fop_fd->len = llFileSize.QuadPart;
-
-       fop_fd->pos = 0;
-
-       return fop_fd;
-
-bail:
-       return NULL;
-}
-
-LWS_VISIBLE int
-_lws_plat_file_close(lws_fop_fd_t *fop_fd)
-{
-       HANDLE fd = (*fop_fd)->fd;
-
-       free(*fop_fd);
-       *fop_fd = NULL;
-
-       CloseHandle((HANDLE)fd);
-
-       return 0;
-}
-
-LWS_VISIBLE lws_fileofs_t
-_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
-{
-       LARGE_INTEGER l;
-
-       l.QuadPart = offset;
-       return SetFilePointerEx((HANDLE)fop_fd->fd, l, NULL, FILE_CURRENT);
-}
-
-LWS_VISIBLE int
-_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                   uint8_t *buf, lws_filepos_t len)
-{
-       DWORD _amount;
-
-       if (!ReadFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) {
-               *amount = 0;
-
-               return 1;
-       }
-
-       fop_fd->pos += _amount;
-       *amount = (unsigned long)_amount;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
-                        uint8_t* buf, lws_filepos_t len)
-{
-       DWORD _amount;
-
-       if (!WriteFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) {
-               *amount = 0;
-
-               return 1;
-       }
-
-       fop_fd->pos += _amount;
-       *amount = (unsigned long)_amount;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_init(struct lws_context *context,
-                 struct lws_context_creation_info *info)
-{
-       struct lws_context_per_thread *pt = &context->pt[0];
-       int i, n = context->count_threads;
-
-       for (i = 0; i < FD_HASHTABLE_MODULUS; i++) {
-               context->fd_hashtable[i].wsi =
-                       lws_zalloc(sizeof(struct lws*) * context->max_fds);
-
-               if (!context->fd_hashtable[i].wsi)
-                       return -1;
-       }
-
-       while (n--) {
-               pt->events = lws_malloc(sizeof(WSAEVENT) *
-                                       (context->fd_limit_per_thread + 1));
-               if (pt->events == NULL) {
-                       lwsl_err("Unable to allocate events array for %d connections\n",
-                                       context->fd_limit_per_thread + 1);
-                       return 1;
-               }
-
-               pt->fds_count = 0;
-               pt->events[0] = WSACreateEvent();
-
-               pt++;
-       }
-
-       context->fd_random = 0;
-
-#ifdef LWS_WITH_PLUGINS
-       if (info->plugin_dirs)
-               lws_plat_plugins_init(context, info->plugin_dirs);
-#endif
-
-       return 0;
-}
-
-
-int kill(int pid, int sig)
-{
-       lwsl_err("Sorry Windows doesn't support kill().");
-       exit(0);
-}
-
-int fork(void)
-{
-       lwsl_err("Sorry Windows doesn't support fork().");
-       exit(0);
-}
-
similarity index 80%
rename from lib/base64-decode.c
rename to lib/misc/base64-decode.c
index c8f11d2..b46a942 100644 (file)
 
 #include <stdio.h>
 #include <string.h>
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
-static const char encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+static const char encode_orig[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                             "abcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char encode_url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                            "abcdefghijklmnopqrstuvwxyz0123456789-_";
 static const char decode[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW"
                             "$$$$$$XYZ[\\]^_`abcdefghijklmnopq";
 
-LWS_VISIBLE int
-lws_b64_encode_string(const char *in, int in_len, char *out, int out_size)
+static int
+_lws_b64_encode_string(const char *encode, const char *in, int in_len,
+                      char *out, int out_size)
 {
        unsigned char triple[3];
        int i;
-       int len;
        int line = 0;
        int done = 0;
 
        while (in_len) {
-               len = 0;
+               int len = 0;
                for (i = 0; i < 3; i++) {
                        if (in_len) {
                                triple[i] = *in++;
@@ -89,26 +91,47 @@ lws_b64_encode_string(const char *in, int in_len, char *out, int out_size)
        return done;
 }
 
+LWS_VISIBLE int
+lws_b64_encode_string(const char *in, int in_len, char *out, int out_size)
+{
+       return _lws_b64_encode_string(encode_orig, in, in_len, out, out_size);
+}
+
+LWS_VISIBLE int
+lws_b64_encode_string_url(const char *in, int in_len, char *out, int out_size)
+{
+       return _lws_b64_encode_string(encode_url, in, in_len, out, out_size);
+}
+
 /*
  * returns length of decoded string in out, or -1 if out was too small
  * according to out_size
+ *
+ * Only reads up to in_len chars, otherwise if in_len is -1 on entry reads until
+ * the first NUL in the input.
  */
 
-LWS_VISIBLE int
-lws_b64_decode_string(const char *in, char *out, int out_size)
+static int
+_lws_b64_decode_string(const char *in, int in_len, char *out, int out_size)
 {
        int len, i, c = 0, done = 0;
        unsigned char v, quad[4];
 
-       while (*in) {
+       while (in_len && *in) {
 
                len = 0;
-               for (i = 0; i < 4 && *in; i++) {
+               for (i = 0; i < 4 && in_len && *in; i++) {
 
                        v = 0;
                        c = 0;
-                       while (*in && !v) {
+                       while (in_len && *in  && !v) {
                                c = v = *in++;
+                               in_len--;
+                               /* support the url base64 variant too */
+                               if (v == '-')
+                                       c = v = '+';
+                               if (v == '_')
+                                       c = v = '/';
                                v = (v < 43 || v > 122) ? 0 : decode[v - 43];
                                if (v)
                                        v = (v == '$') ? 0 : v - 61;
@@ -121,7 +144,7 @@ lws_b64_decode_string(const char *in, char *out, int out_size)
                                quad[i] = 0;
                }
 
-               if (out_size < (done + len - 1))
+               if (out_size < (done + len + 1))
                        /* out buffer is too small */
                        return -1;
 
@@ -131,7 +154,7 @@ lws_b64_decode_string(const char *in, char *out, int out_size)
                 * bytes." (wikipedia)
                 */
 
-               if (!*in && c == '=')
+               if ((!in_len || !*in) && c == '=')
                        len--;
 
                if (len >= 2)
@@ -152,6 +175,18 @@ lws_b64_decode_string(const char *in, char *out, int out_size)
        return done;
 }
 
+LWS_VISIBLE int
+lws_b64_decode_string(const char *in, char *out, int out_size)
+{
+       return _lws_b64_decode_string(in, -1, out, out_size);
+}
+
+LWS_VISIBLE int
+lws_b64_decode_string_len(const char *in, int in_len, char *out, int out_size)
+{
+       return _lws_b64_decode_string(in, in_len, out, out_size);
+}
+
 #if 0
 int
 lws_b64_selftest(void)
similarity index 81%
rename from lib/daemonize.c
rename to lib/misc/daemonize.c
index 8ec58a3..52fd6ea 100644 (file)
@@ -9,6 +9,10 @@
  *
  * This version is LGPL2.1+SLE like the rest of libwebsockets and is
  * Copyright (c)2006 - 2013 Andy Green <andy@warmcat.com>
+ *
+ *
+ * You're much better advised to use systemd to daemonize stuff without needing
+ * this kind of support in the app itself.
  */
 
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
-int pid_daemon;
+pid_t pid_daemon;
 static char *lock_path;
 
-int get_daemonize_pid()
+pid_t get_daemonize_pid()
 {
        return pid_daemon;
 }
@@ -35,7 +39,7 @@ int get_daemonize_pid()
 static void
 child_handler(int signum)
 {
-       int fd, len, sent;
+       int len, sent, fd;
        char sz[20];
 
        switch (signum) {
@@ -46,25 +50,27 @@ child_handler(int signum)
 
        case SIGUSR1: /* positive confirmation we daemonized well */
 
-               if (lock_path) {
-                       /* Create the lock file as the current user */
+               if (!lock_path)
+                       exit(0);
 
-                       fd = open(lock_path, O_TRUNC | O_RDWR | O_CREAT, 0640);
-                       if (fd < 0) {
-                               fprintf(stderr,
-                                  "unable to create lock file %s, code=%d (%s)\n",
-                                       lock_path, errno, strerror(errno));
-                               exit(0);
-                       }
-                       len = sprintf(sz, "%u", pid_daemon);
-                       sent = write(fd, sz, len);
-                       if (sent != len)
-                               fprintf(stderr,
-                                 "unable to write pid to lock file %s, code=%d (%s)\n",
-                                                    lock_path, errno, strerror(errno));
+               /* Create the lock file as the current user */
 
-                       close(fd);
+               fd = lws_open(lock_path, O_TRUNC | O_RDWR | O_CREAT, 0640);
+               if (fd < 0) {
+                       fprintf(stderr,
+                          "unable to create lock file %s, code=%d (%s)\n",
+                               lock_path, errno, strerror(errno));
+                       exit(0);
                }
+               len = sprintf(sz, "%u", pid_daemon);
+               sent = write(fd, sz, len);
+               if (sent != len)
+                       fprintf(stderr,
+                         "unable to write pid to lock file %s, code=%d (%s)\n",
+                                            lock_path, errno, strerror(errno));
+
+               close(fd);
+
                exit(0);
                //!!(sent == len));
 
@@ -98,35 +104,39 @@ lws_daemonize(const char *_lock_path)
 {
        struct sigaction act;
        pid_t sid, parent;
-       int n, fd, ret;
-       char buf[10];
 
        /* already a daemon */
 //     if (getppid() == 1)
 //             return 1;
 
        if (_lock_path) {
-               fd = open(_lock_path, O_RDONLY);
+               int n;
+
+               int fd = lws_open(_lock_path, O_RDONLY);
                if (fd >= 0) {
+                       char buf[10];
+
                        n = read(fd, buf, sizeof(buf));
                        close(fd);
                        if (n) {
+                               int ret;
                                n = atoi(buf);
                                ret = kill(n, 0);
                                if (ret >= 0) {
                                        fprintf(stderr,
-                                            "Daemon already running from pid %d\n", n);
+                                            "Daemon already running pid %d\n",
+                                            n);
                                        exit(1);
                                }
                                fprintf(stderr,
-                                   "Removing stale lock file %s from dead pid %d\n",
-                                                                        _lock_path, n);
+                                   "Removing stale lock %s from dead pid %d\n",
+                                                       _lock_path, n);
                                unlink(lock_path);
                        }
                }
 
                n = strlen(_lock_path) + 1;
-               lock_path = lws_malloc(n);
+               lock_path = lws_malloc(n, "daemonize lock");
                if (!lock_path) {
                        fprintf(stderr, "Out of mem in lws_daemonize\n");
                        return 1;
@@ -141,7 +151,7 @@ lws_daemonize(const char *_lock_path)
 
        /* Fork off the parent process */
        pid_daemon = fork();
-       if (pid_daemon < 0) {
+       if ((int)pid_daemon < 0) {
                fprintf(stderr, "unable to fork daemon, code=%d (%s)",
                    errno, strerror(errno));
                exit(9);
diff --git a/lib/misc/dir.c b/lib/misc/dir.c
new file mode 100644 (file)
index 0000000..f3d8781
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Lws directory scan wrapper
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define NO_GNU_SOURCE_THIS_TIME
+#define _DARWIN_C_SOURCE
+
+#include <libwebsockets.h>
+#include "core/private.h"
+#include <string.h>
+#include <stdio.h>
+
+#if defined(LWS_WITH_LIBUV) && UV_VERSION_MAJOR > 0
+
+int
+lws_dir(const char *dirpath, void *user, lws_dir_callback_function cb)
+{
+       struct lws_dir_entry lde;
+       uv_dirent_t dent;
+       uv_fs_t req;
+       int ret = 1, ir;
+       uv_loop_t loop;
+
+       ir = uv_loop_init(&loop);
+       if (ir) {
+               lwsl_err("%s: loop init failed %d\n", __func__, ir);
+       }
+
+       ir = uv_fs_scandir(&loop, &req, dirpath, 0, NULL);
+       if (ir < 0) {
+               lwsl_err("Scandir on %s failed, errno %d\n", dirpath, LWS_ERRNO);
+               return 2;
+       }
+
+       while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
+               lde.name = dent.name;
+               lde.type = (int)dent.type;
+               if (cb(dirpath, user, &lde))
+                       goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       uv_fs_req_cleanup(&req);
+       while (uv_loop_close(&loop))
+               ;
+
+       return ret;
+}
+
+#else
+
+#if !defined(_WIN32) && !defined(LWS_WITH_ESP32)
+
+#include <dirent.h>
+
+static int filter(const struct dirent *ent)
+{
+       if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+               return 0;
+
+       return 1;
+}
+
+int
+lws_dir(const char *dirpath, void *user, lws_dir_callback_function cb)
+{
+       struct lws_dir_entry lde;
+       struct dirent **namelist;
+       int n, i, ret = 1;
+
+       n = scandir((char *)dirpath, &namelist, filter, alphasort);
+       if (n < 0) {
+               lwsl_err("Scandir on '%s' failed, errno %d\n", dirpath, LWS_ERRNO);
+               return 1;
+       }
+
+       for (i = 0; i < n; i++) {
+               if (strchr(namelist[i]->d_name, '~'))
+                       goto skip;
+               lde.name = namelist[i]->d_name;
+
+               /*
+                * some filesystems don't report this (ZFS) and tell that
+                * files are LDOT_UNKNOWN
+                */
+
+#if defined(__smartos__)
+        struct stat s;
+        stat(namelist[i]->d_name, &s);
+               switch (s.st_mode) {
+               case S_IFBLK:
+                       lde.type = LDOT_BLOCK;
+                       break;
+               case S_IFCHR:
+                       lde.type = LDOT_CHAR;
+                       break;
+               case S_IFDIR:
+                       lde.type = LDOT_DIR;
+                       break;
+               case S_IFIFO:
+                       lde.type = LDOT_FIFO;
+                       break;
+               case S_IFLNK:
+                       lde.type = LDOT_LINK;
+                       break;
+               case S_IFREG:
+                       lde.type = LDOT_FILE;
+                       break;
+               default:
+                       lde.type = LDOT_UNKNOWN;
+                       break;
+               }
+#else
+               switch (namelist[i]->d_type) {
+               case DT_BLK:
+                       lde.type = LDOT_BLOCK;
+                       break;
+               case DT_CHR:
+                       lde.type = LDOT_CHAR;
+                       break;
+               case DT_DIR:
+                       lde.type = LDOT_DIR;
+                       break;
+               case DT_FIFO:
+                       lde.type = LDOT_FIFO;
+                       break;
+               case DT_LNK:
+                       lde.type = LDOT_LINK;
+                       break;
+               case DT_REG:
+                       lde.type = LDOT_FILE;
+                       break;
+               case DT_SOCK:
+                       lde.type = LDOTT_SOCKET;
+                       break;
+               default:
+                       lde.type = LDOT_UNKNOWN;
+                       break;
+               }
+#endif
+               if (cb(dirpath, user, &lde)) {
+                       while (i++ < n)
+                               free(namelist[i]);
+                       goto bail;
+               }
+skip:
+               free(namelist[i]);
+       }
+
+       ret = 0;
+
+bail:
+       free(namelist);
+
+       return ret;
+}
+
+#else
+#error "If you want lws_dir onw windows, you need libuv"
+#endif
+#endif
diff --git a/lib/misc/diskcache.c b/lib/misc/diskcache.c
new file mode 100644 (file)
index 0000000..0a31810
--- /dev/null
@@ -0,0 +1,476 @@
+/*
+ * libwebsockets - disk cache helpers
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include <pthread.h>
+
+#include "core/private.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <time.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+struct file_entry {
+       lws_list_ptr sorted;
+       lws_list_ptr prev;
+       char name[64];
+       time_t modified;
+       size_t size;
+};
+
+struct lws_diskcache_scan {
+       struct file_entry *batch;
+       const char *cache_dir_base;
+       lws_list_ptr head;
+       time_t last_scan_completed;
+       uint64_t agg_size;
+       uint64_t cache_size_limit;
+       uint64_t avg_size;
+       uint64_t cache_tries;
+       uint64_t cache_hits;
+       int cache_subdir;
+       int batch_in_use;
+       int agg_file_count;
+       int secs_waiting;
+};
+
+#define KIB (1024)
+#define MIB (KIB * KIB)
+
+#define lp_to_fe(p, _n) lws_list_ptr_container(p, struct file_entry, _n)
+
+static const char *hex = "0123456789abcdef";
+
+#define BATCH_COUNT 128
+
+static int
+fe_modified_sort(lws_list_ptr a, lws_list_ptr b)
+{
+       struct file_entry *p1 = lp_to_fe(a, sorted), *p2 = lp_to_fe(b, sorted);
+
+       return p2->modified - p1->modified;
+}
+
+struct lws_diskcache_scan *
+lws_diskcache_create(const char *cache_dir_base, uint64_t cache_size_limit)
+{
+       struct lws_diskcache_scan *lds = lws_malloc(sizeof(*lds), "cachescan");
+
+       if (!lds)
+               return NULL;
+
+       memset(lds, 0, sizeof(*lds));
+
+       lds->cache_dir_base = cache_dir_base;
+       lds->cache_size_limit = cache_size_limit;
+
+       return lds;
+}
+
+void
+lws_diskcache_destroy(struct lws_diskcache_scan **lds)
+{
+       if ((*lds)->batch)
+               lws_free((*lds)->batch);
+       lws_free(*lds);
+       *lds = NULL;
+}
+
+int
+lws_diskcache_prepare(const char *cache_base_dir, int mode, int uid)
+{
+       char dir[256];
+       int n, m;
+
+       (void)mkdir(cache_base_dir, mode);
+       if (chown(cache_base_dir, uid, -1))
+               lwsl_err("%s: %s: unable to chown %d\n", __func__,
+                        cache_base_dir, uid);
+
+       for (n = 0; n < 16; n++) {
+               lws_snprintf(dir, sizeof(dir), "%s/%c", cache_base_dir, hex[n]);
+               (void)mkdir(dir, mode);
+               if (chown(dir, uid, -1))
+                       lwsl_err("%s: %s: unable to chown %d\n", __func__,
+                                                dir, uid);
+               for (m = 0; m < 16; m++) {
+                       lws_snprintf(dir, sizeof(dir), "%s/%c/%c",
+                                    cache_base_dir, hex[n], hex[m]);
+                       (void)mkdir(dir, mode);
+                       if (chown(dir, uid, -1))
+                               lwsl_err("%s: %s: unable to chown %d\n",
+                                        __func__, dir, uid);
+               }
+       }
+
+       return 0;
+}
+
+/* copies and then truncates the incoming name, and renames the file at the
+ * untruncated path to have the new truncated name */
+
+int
+lws_diskcache_finalize_name(char *cache)
+{
+       char ren[256], *p;
+
+       strncpy(ren, cache, sizeof(ren) - 1);
+       ren[sizeof(ren) - 1] = '\0';
+       p = strchr(cache, '~');
+       if (p) {
+               *p = '\0';
+               if (rename(ren, cache)) {
+                       lwsl_err("%s: problem renaming %s to %s\n", __func__,
+                                ren, cache);
+                       return 1;
+               }
+
+               return 0;
+       }
+
+       return 1;
+}
+
+int
+lws_diskcache_query(struct lws_diskcache_scan *lds, int is_bot,
+                   const char *hash_hex, int *_fd, char *cache, int cache_len,
+                   size_t *extant_cache_len)
+{
+       struct stat s;
+       int n;
+
+       /* caching is disabled? */
+       if (!lds->cache_dir_base)
+               return LWS_DISKCACHE_QUERY_NO_CACHE;
+
+       if (!is_bot)
+               lds->cache_tries++;
+
+       n = lws_snprintf(cache, cache_len, "%s/%c/%c/%s", lds->cache_dir_base,
+                        hash_hex[0], hash_hex[1], hash_hex);
+
+       lwsl_info("%s: job cache %s\n", __func__, cache);
+
+       *_fd = open(cache, O_RDONLY);
+       if (*_fd >= 0) {
+               int fd;
+
+               if (!is_bot)
+                       lds->cache_hits++;
+
+               if (fstat(*_fd, &s)) {
+                       close(*_fd);
+
+                       return LWS_DISKCACHE_QUERY_NO_CACHE;
+               }
+
+               *extant_cache_len = (size_t)s.st_size;
+
+               /* "touch" the hit cache file so it's last for LRU now */
+               fd = open(cache, O_RDWR);
+               if (fd >= 0)
+                       close(fd);
+
+               return LWS_DISKCACHE_QUERY_EXISTS;
+       }
+
+       /* bots are too random to pollute the cache with their antics */
+       if (is_bot)
+               return LWS_DISKCACHE_QUERY_NO_CACHE;
+
+       /* let's create it first with a unique temp name */
+
+       lws_snprintf(cache + n, cache_len - n, "~%d-%p", (int)getpid(),
+                    extant_cache_len);
+
+       *_fd = open(cache, O_RDWR | O_CREAT | O_TRUNC, 0600);
+       if (*_fd < 0) {
+               /* well... ok... we will proceed without cache then... */
+               lwsl_notice("%s: Problem creating cache %s: errno %d\n",
+                           __func__, cache, errno);
+               return LWS_DISKCACHE_QUERY_NO_CACHE;
+       }
+
+       return LWS_DISKCACHE_QUERY_CREATING;
+}
+
+int
+lws_diskcache_secs_to_idle(struct lws_diskcache_scan *lds)
+{
+       return lds->secs_waiting;
+}
+
+/*
+ * The goal is to collect the oldest BATCH_COUNT filepaths and filesizes from
+ * the dirs under the cache dir.  Since we don't need or want a full list of
+ * files in there in memory at once, we restrict the linked-list size to
+ * BATCH_COUNT entries, and once it is full, simply ignore any further files
+ * that are newer than the newest one on that list.  Files older than the
+ * newest guy already on the list evict the newest guy already on the list
+ * and are sorted into the correct order.  In this way no matter the number
+ * of files to be processed the memory requirement is fixed at BATCH_COUNT
+ * struct file_entry-s.
+ *
+ * The oldest subset of BATCH_COUNT files are sorted into the cd->batch
+ * allocation in more recent -> least recent order.
+ *
+ * We want to track the total size of all files we saw as well, so we know if
+ * we need to actually do anything yet to restrict how much space it's taking
+ * up.
+ *
+ * And we want to do those things statefully and incrementally instead of one
+ * big atomic operation, since the user may want a huge cache, so we look in
+ * one cache dir at a time and track state in the repodir struct.
+ *
+ * When we have seen everything, we add the doubly-linked prev pointers and then
+ * if we are over the limit, start deleting up to BATCH_COUNT files working back
+ * from the end.
+ */
+
+int
+lws_diskcache_trim(struct lws_diskcache_scan *lds)
+{
+       size_t cache_size_limit = lds->cache_size_limit;
+       char dirpath[132], filepath[132 + 32];
+       lws_list_ptr lp, op = NULL;
+       int files_trimmed = 0;
+       struct file_entry *p;
+       int fd, n, ret = -1;
+       size_t trimmed = 0;
+       struct dirent *de;
+       struct stat s;
+       DIR *dir;
+
+       if (!lds->cache_subdir) {
+
+               if (lds->last_scan_completed + lds->secs_waiting > time(NULL))
+                       return 0;
+
+               lds->batch = lws_malloc(sizeof(struct file_entry) *
+                               BATCH_COUNT, "cache_trim");
+               if (!lds->batch) {
+                       lwsl_err("%s: OOM\n", __func__);
+
+                       return 1;
+               }
+               lds->agg_size = 0;
+               lds->head = NULL;
+               lds->batch_in_use = 0;
+               lds->agg_file_count = 0;
+       }
+
+       lws_snprintf(dirpath, sizeof(dirpath), "%s/%c/%c",
+                    lds->cache_dir_base, hex[(lds->cache_subdir >> 4) & 15],
+                    hex[lds->cache_subdir & 15]);
+
+       dir = opendir(dirpath);
+       if (!dir) {
+               lwsl_err("Unable to walk repo dir '%s'\n",
+                        lds->cache_dir_base);
+               return -1;
+       }
+
+       do {
+               de = readdir(dir);
+               if (!de)
+                       break;
+
+               if (de->d_type != DT_REG)
+                       continue;
+
+               lds->agg_file_count++;
+
+               lws_snprintf(filepath, sizeof(filepath), "%s/%s", dirpath,
+                            de->d_name);
+
+               fd = open(filepath, O_RDONLY);
+               if (fd < 0) {
+                       lwsl_err("%s: cannot open %s\n", __func__, filepath);
+
+                       continue;
+               }
+
+               n = fstat(fd, &s);
+               close(fd);
+               if (n) {
+                       lwsl_notice("%s: cannot stat %s\n", __func__, filepath);
+                       continue;
+               }
+
+               lds->agg_size += s.st_size;
+
+               if (lds->batch_in_use == BATCH_COUNT) {
+                       /*
+                        * once we filled up the batch with candidates, we don't
+                        * need to consider any files newer than the newest guy
+                        * on the list...
+                        */
+                       if (lp_to_fe(lds->head, sorted)->modified < s.st_mtime)
+                               continue;
+
+                       /*
+                        * ... and if we find an older file later, we know it
+                        * will be replacing the newest guy on the list, so use
+                        * that directly...
+                        */
+                       p = lds->head;
+                       lds->head = p->sorted;
+               } else
+                       /* we are still accepting anything to fill the batch */
+
+                       p = &lds->batch[lds->batch_in_use++];
+
+               p->sorted = NULL;
+               strncpy(p->name, de->d_name, sizeof(p->name) - 1);
+               p->name[sizeof(p->name) - 1] = '\0';
+               p->modified = s.st_mtime;
+               p->size = s.st_size;
+
+               lws_list_ptr_insert(&lds->head, &p->sorted, fe_modified_sort);
+       } while (de);
+
+       ret = 0;
+
+       lds->cache_subdir++;
+       if (lds->cache_subdir != 0x100)
+               goto done;
+
+       /* we completed the whole scan... */
+
+       /* if really no guidence, then 256MiB */
+       if (!cache_size_limit)
+               cache_size_limit = 256 * 1024 * 1024;
+
+       if (lds->agg_size > cache_size_limit) {
+
+               /* apply prev pointers to make the list doubly-linked */
+
+               lp = lds->head;
+               while (lp) {
+                       p = lp_to_fe(lp, sorted);
+
+                       p->prev = op;
+                       op = &p->prev;
+                       lp = p->sorted;
+               }
+
+               /*
+                * reverse the list (start from tail, now traverse using
+                * .prev)... it's oldest-first now...
+                */
+
+               lp = op;
+
+               while (lp && lds->agg_size > cache_size_limit) {
+                       p = lp_to_fe(lp, prev);
+
+                       lws_snprintf(filepath, sizeof(filepath), "%s/%c/%c/%s",
+                                    lds->cache_dir_base, p->name[0],
+                                    p->name[1], p->name);
+
+                       if (!unlink(filepath)) {
+                               lds->agg_size -= p->size;
+                               trimmed += p->size;
+                               files_trimmed++;
+                       } else
+                               lwsl_notice("%s: Failed to unlink %s\n",
+                                           __func__, filepath);
+
+                       lp = p->prev;
+               }
+
+               if (files_trimmed)
+                       lwsl_notice("%s: %s: trimmed %d files totalling "
+                                   "%lldKib, leaving %lldMiB\n", __func__,
+                                   lds->cache_dir_base, files_trimmed,
+                                   ((unsigned long long)trimmed) / KIB,
+                                   ((unsigned long long)lds->agg_size) / MIB);
+       }
+
+       if (lds->agg_size && lds->agg_file_count)
+               lds->avg_size = lds->agg_size / lds->agg_file_count;
+
+       /*
+        * estimate how long we can go before scanning again... default we need
+        * to start again immediately
+        */
+
+       lds->last_scan_completed = time(NULL);
+       lds->secs_waiting = 1;
+
+       if (lds->agg_size < cache_size_limit) {
+               uint64_t avg = 4096, capacity, projected;
+
+               /* let's use 80% of the real average for margin */
+               if (lds->agg_size && lds->agg_file_count)
+                       avg = ((lds->agg_size * 8) / lds->agg_file_count) / 10;
+
+               /*
+                * if we collected BATCH_COUNT files of the average size,
+                * how much can we clean up in 256s?
+                */
+
+               capacity = avg * BATCH_COUNT;
+
+               /*
+                * if the cache grew by 10%, would we hit the limit even then?
+                */
+               projected = (lds->agg_size * 11) / 10;
+               if (projected < cache_size_limit)
+                       /* no... */
+                       lds->secs_waiting  = (256 / 2) * ((cache_size_limit -
+                                                   projected) / capacity);
+
+               /*
+                * large waits imply we may not have enough info yet, so
+                * check once an hour at least.
+                */
+
+               if (lds->secs_waiting > 3600)
+                       lds->secs_waiting = 3600;
+       } else
+               lds->secs_waiting = 0;
+
+       lwsl_info("%s: cache %s: %lldKiB / %lldKiB, next scan %ds\n", __func__,
+                 lds->cache_dir_base,
+                 (unsigned long long)lds->agg_size / KIB,
+                 (unsigned long long)cache_size_limit / KIB,
+                 lds->secs_waiting);
+
+       lws_free(lds->batch);
+       lds->batch = NULL;
+
+       lds->cache_subdir = 0;
+
+done:
+       closedir(dir);
+
+       return ret;
+}
diff --git a/lib/misc/fts/README.md b/lib/misc/fts/README.md
new file mode 100644 (file)
index 0000000..fcb225c
--- /dev/null
@@ -0,0 +1,315 @@
+# LWS Full Text Search
+
+## Introduction
+
+![lwsac flow](/doc-assets/lws-fts.svg)
+
+The general approach is to scan one or more UTF-8 input text "files" (they may
+only exist in memory) and create an in-memory optimized trie for every token in
+the file.
+
+This can then be serialized out to disk in the form of a single index file (no
+matter how many input files were involved or how large they were).
+
+The implementation is designed to be modest on memory and cpu for both index
+creation and querying, and suitable for weak machines with some kind of random
+access storage.  For searching only memory to hold results is required, the
+actual searches and autocomplete suggestions are done very rapidly by seeking
+around structures in the on-disk index file.
+
+Function|Related Link
+---|---
+Public API|[include/libwebsockets/lws-fts.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-fts.h)
+CI test app|[minimal-examples/api-tests/api-test-fts](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-fts)
+Demo minimal example|[minimal-examples/http-server/minimal-http-server-fulltext-search](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/http-server/minimal-http-server-fulltext-search)
+Live Demo|[https://libwebsockets.org/ftsdemo/](https://libwebsockets.org/ftsdemo/)
+
+## Query API overview
+
+Searching returns a potentially very large lwsac allocated object, with contents
+and max size controlled by the members of a struct lws_fts_search_params passed
+to the search function.  Three kinds of result are possible:
+
+### Autocomplete suggestions
+
+These are useful to provide lists of extant results in
+realtime as the user types characters that constrain the search.  So if the
+user has typed 'len', any hits for 'len' itself are reported along with
+'length', and whatever else is in the index beginning 'len'..  The results are
+selected using and are accompanied by an aggregated count of results down that
+path, and the results so the "most likely" results already measured by potential
+hits appear first.
+These results are in a linked-list headed by `result.autocomplete_head` and
+each is in a `struct lws_fts_result_autocomplete`.
+They're enabled in the search results by giving the flag
+ `LWSFTS_F_QUERY_AUTOCOMPLETE` in the search parameter flags.
+### Filepath results 
+
+Simply a list of input files containing the search term with some statistics,
+one file is mentioned in a `struct lws_fts_result_filepath` result struct.
+
+This would be useful for creating a selection UI to "drill down" to individual
+files when there are many with matches.
+
+This is enabled by the `LWSFTS_F_QUERY_FILES` search flag.
+
+### Filepath and line results
+Same as the file path list, but for each filepath, information on the line
+numbers and input file offset where the line starts are provided.
+
+This is enabled by `LWSFTS_F_QUERY_FILE_LINES`... if you additionally give
+`LWSFTS_F_QUERY_QUOTE_LINE` flag then the contents of each hit line from the
+input file are also provided.
+## Result format inside the lwsac
+
+A `struct lws_fts_result` at the start of the lwsac contains heads for linked-
+lists of autocomplete and filepath results inside the lwsac.
+
+For autocomplete suggestions, the string itself is immediately after the
+`struct lws_fts_result_autocomplete` in memory.  For filepath results, after
+each `struct lws_fts_result_filepath` is
+
+ - match information depending on the flags given to the search
+ - the filepath string
+You can always skip the line number table to get the filepath string by adding
+.matches_length to the address of the byte after the struct.
+
+The matches information is either
+
+ - 0 bytes per match
+ - 2x int32_t per match (8 bytes) if `LWSFTS_F_QUERY_FILE_LINES` given... the
+   first is the native-endian line number of the match, the second is the
+   byte offset in the original file where that line starts
+
+ - 2 x int32_t as above plus a const char * if `LWSFTS_F_QUERY_QUOTE_LINE` is
+   also given... this points to a NUL terminated string also stored in the
+   results lwsac that contains up to 255 chars of the line from the original
+   file.  In some cases, the original file was either virtual (you are indexing
+   a git revision) or is not stored with the index, in that case you can't
+   usefully use `LWSFTS_F_QUERY_QUOTE_LINE`.
+
+To facilitate interpreting what is stored per match, the original search flags
+that created the result are stored in the `struct lws_fts_result`.
+
+## Indexing In-memory and serialized to file
+
+When creating the trie, in-memory structs are used with various optimization
+schemes trading off memory usage for speed.  While in-memory, it's possible to
+add more indexed filepaths to the single index.  Once the trie is complete in
+terms of having indexed everything, it is serialized to disk.
+
+These contain many additional housekeeping pointers and trie entries which can
+be optimized out.  Most in-memory values must be held literally in large types,
+whereas most of the values in the serialized file use smaller VLI which use
+more or less bytes according to the value.  So the peak memory requirements for
+large tries are much bigger than the size of the serialized trie file that is
+output.
+
+For the linux kernel at 4.14 and default indexing whitelist on a 2.8GHz AMD
+threadripper (using one thread), the stats are:
+
+Name|Value
+---|---
+Files indexed|52932
+Input corpus size|694MiB
+Indexing cpu time|50.1s (>1000 files / sec; 13.8MBytes/sec)
+Peak alloc|78MiB
+Serialization time|202ms
+Trie File size|347MiB
+
+To index libwebsockets master under the same conditions:
+
+Name|Value
+---|---
+Files indexed|489
+Input corpus size|3MiB
+Indexing time|123ms
+Peak alloc|3MiB
+Serialization time|1ms
+Trie File size|1.4MiB
+
+
+Once it's generated, querying the trie file is very inexpensive, even when there
+are lots of results.
+
+ - trie entry child lists are kept sorted by the character they map to.  This
+   allows discovering there is no match as soon as a character later in the
+   order than the one being matched is seen
+   
+ - for the root trie, in addition to the linked-list child + sibling entries,
+   a 256-entry pointer table is associated with the root trie, allowing one-
+   step lookup.  But as the table is 2KiB, it's too expensive to use on all
+   trie entries
+
+## Structure on disk
+
+All explicit multibyte numbers are stored in Network (MSB-first) byte order.
+
+ - file header
+ - filepath line number tables
+ - filepath information
+ - filepath map table
+ - tries, trie instances (hits), trie child tables
+
+### VLI coding
+
+VLI (Variable Length Integer) coding works like this
+
+[b7 EON] [b6 .. b0  DATA]
+
+If EON = 0, then DATA represents the Least-significant 7 bits of the number.
+if EON = 1, DATA represents More-significant 7-bits that should be shifted
+left until the byte with EON = 0 is found to terminate the number.
+
+The VLI used is predicated around 32-bit unsigned integers
+
+Examples:
+
+ - 0x30            =    48
+ - 0x81 30         =   176
+ - 0x81 0x80 0x00  = 16384
+
+Bytes | Range
+---|---
+1|<= 127
+2|<= 16K - 1
+3|<= 2M -1
+4|<= 256M - 1
+5|<= 4G - 1
+
+The coding is very efficient if there's a high probabilty the number being
+stored is not large.  So it's great for line numbers for example, where most
+files have less that 16K lines and the VLI for the line number fits in 2 bytes,
+but if you meet a huge file, the VLI coding can also handle it.
+
+All numbers except a few in the headers that are actually written after the
+following data are stored using VLI for space- efficiency without limiting
+capability.  The numbers that are fixed up after the fact have to have a fixed
+size and can't use VLI.
+
+### File header
+
+The first byte of the file header where the magic is, is "fileoffset" 0.  All
+the stored "fileoffset"s are relative to that.
+
+The header has a fixed size of 16 bytes.
+
+size|function
+---|---
+32-bits|Magic 0xCA7A5F75
+32-bits|Fileoffset to root trie entry
+32-bits|Size of the trie file when it was created (to detect truncation)
+32-bits|Fileoffset to the filepath map
+32-bits|Number of filepaths
+
+### Filepath line tables
+
+Immediately after the file header are the line length tables.
+
+As the input files are parsed, line length tables are written for each file...
+at that time the rest of the parser data is held in memory so nothing else is
+in the file yet.  These allow you to map logical line numbers in the file to
+file offsets space- and time- efficiently without having to walk through the
+file contents.
+
+The line information is cut into blocks, allowing quick skipping over the VLI
+data that doesn't contain the line you want just by following the 8-byte header
+part.
+
+Once you find the block with your line, you have to iteratively add the VLIs
+until you hit the one you want.
+
+For normal text files with average line length below 128, the VLIs will
+typically be a single byte.  So a block of 200 line lengths is typically
+208 bytes long.
+
+There is a final linetable chunk consisting of all zeros to indicate the end
+of the filepath line chunk series for a filepath.
+
+size|function
+---|---
+16-bit|length of this chunk itself in bytes
+16-bit|count of lines covered in this chunk
+32-bit|count of bytes in the input file this chunk covers 
+VLI...|for each line in the chunk, the number of bytes in the line
+
+
+### Filepaths
+
+The single trie in the file may contain information from multiple files, for
+example one trie may cover all files in a directory.  The "Filepaths" are
+listed after the line tables, and referred to by index thereafter.
+
+For each filepath, one after the other:
+
+size|function
+---|---
+VLI|fileoffset of the start of this filepath's line table
+VLI|count of lines in the file
+VLI|length of filepath in bytes
+...|the filepath (with no NUL)
+
+### Filepath map
+
+To facilitate rapid filepath lookup, there's a filepath map table with a 32-bit
+fileoffset per filepath.  This is the way to convert filepath indexes to
+information on the filepath like its name, etc
+
+size|function
+---|---
+32-bit...|fileoffset to filepath table for each filepath
+
+### Trie entries
+
+Immediately after that, the trie entries are dumped, for each one a header:
+
+#### Trie entry header
+
+size|function
+---|---
+VLI|Fileoffset of first file table in this trie entry instance list
+VLI|number of child trie entries this trie entry has
+VLI|number of instances this trie entry has
+
+The child list follows immediately after this header
+
+#### Trie entry instance file
+
+For each file that has instances of this symbol:
+
+size|function
+---|---
+VLI|Fileoffset of next file table in this trie entry instance list
+VLI|filepath index
+VLI|count of line number instances following
+
+#### Trie entry file line number table
+
+Then for the file mentioned above, a list of all line numbers in the file with
+the symbol in them, in ascending order.  As a VLI, the median size per entry
+will typically be ~15.9 bits due to the probability of line numbers below 16K.
+
+size|function
+---|---
+VLI|line number
+...
+
+#### Trie entry child table
+
+For each child node
+
+size|function
+---|---
+VLI|file offset of child
+VLI|instance count belonging directly to this child
+VLI|aggregated number of instances down all descendent paths of child
+VLI|aggregated number of children down all descendent paths of child
+VLI|match string length
+...|the match string
diff --git a/lib/misc/fts/private.h b/lib/misc/fts/private.h
new file mode 100644 (file)
index 0000000..066c76f
--- /dev/null
@@ -0,0 +1,23 @@
+#include <libwebsockets.h>
+
+/* if you need > 2GB trie files */
+//typedef off_t jg2_file_offset;
+typedef uint32_t jg2_file_offset;
+
+struct lws_fts_file {
+       int fd;
+       jg2_file_offset root, flen, filepath_table;
+       int max_direct_hits;
+       int max_completion_hits;
+       int filepaths;
+};
+
+
+
+#define TRIE_FILE_HDR_SIZE 20
+#define MAX_VLI 5
+
+#define LWS_FTS_LINES_PER_CHUNK 200
+
+int
+rq32(unsigned char *b, uint32_t *d);
diff --git a/lib/misc/fts/trie-fd.c b/lib/misc/fts/trie-fd.c
new file mode 100644 (file)
index 0000000..cdb2e42
--- /dev/null
@@ -0,0 +1,1001 @@
+/*
+ * libjsongit2 - trie file functions
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "misc/fts/private.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define AC_COUNT_STASHED_CHILDREN 8
+
+struct ch {
+       jg2_file_offset ofs;
+       char name[64];
+       int inst;
+       int child_agg;
+       int name_length;
+       int effpos;
+       int descendents;
+};
+
+struct wac {
+       struct ch ch[AC_COUNT_STASHED_CHILDREN];
+
+       jg2_file_offset self;
+       jg2_file_offset tifs;
+       int child_count;
+       int child;
+
+       int agg;
+       int desc;
+       char done_children;
+       char once;
+};
+
+struct linetable {
+       struct linetable *next;
+
+       int chunk_line_number_start;
+       int chunk_line_number_count;
+
+       off_t chunk_filepos_start;
+
+       off_t vli_ofs_in_index;
+};
+
+static uint32_t
+b32(unsigned char *b)
+{
+       return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+}
+
+static uint16_t
+b16(unsigned char *b)
+{
+       return (b[0] << 8) | b[1];
+}
+
+static int
+lws_fts_filepath(struct lws_fts_file *jtf, int filepath_index, char *result,
+                size_t len, uint32_t *ofs_linetable, uint32_t *lines)
+{
+       unsigned char buf[256 + 15];
+       uint32_t flen;
+       int ra, bp = 0;
+       size_t m;
+       off_t o;
+
+       if (filepath_index > jtf->filepaths)
+               return 1;
+
+       if (lseek(jtf->fd, jtf->filepath_table + (4 * filepath_index),
+                       SEEK_SET) < 0) {
+               lwsl_err("%s: unable to seek\n", __func__);
+
+               return 1;
+       }
+
+       ra = read(jtf->fd, buf, 4);
+       if (ra < 0)
+               return 1;
+
+       o = (unsigned int)b32(buf);
+       if (lseek(jtf->fd, o, SEEK_SET) < 0) {
+               lwsl_err("%s: unable to seek\n", __func__);
+
+               return 1;
+       }
+
+       ra = read(jtf->fd, buf, sizeof(buf));
+       if (ra < 0)
+               return 1;
+
+       if (ofs_linetable)
+               bp += rq32(&buf[bp], ofs_linetable);
+       else
+               bp += rq32(&buf[bp], &flen);
+       if (lines)
+               bp += rq32(&buf[bp], lines);
+       else
+               bp += rq32(&buf[bp], &flen);
+       bp += rq32(&buf[bp], &flen);
+
+       m = flen;
+       if (len - 1 < m)
+               m = flen - 1;
+
+       strncpy(result, (char *)&buf[bp], m);
+       result[m] = '\0';
+       result[len - 1] = '\0';
+
+       return 0;
+}
+
+/*
+ * returns -1 for fail or fd open on the trie file.
+ *
+ * *root is set to the position of the root trie entry.
+ * *flen is set to the length of the whole file
+ */
+
+int
+lws_fts_adopt(struct lws_fts_file *jtf)
+{
+       unsigned char buf[256];
+       off_t ot;
+
+       if (read(jtf->fd, buf, TRIE_FILE_HDR_SIZE) != TRIE_FILE_HDR_SIZE) {
+               lwsl_err("%s: unable to read file header\n", __func__);
+               goto bail;
+       }
+
+       if (buf[0] != 0xca || buf[1] != 0x7a ||
+           buf[2] != 0x5f || buf[3] != 0x75) {
+               lwsl_err("%s: bad magic %02X %02X %02X %02X\n", __func__,
+                        buf[0], buf[1], buf[2], buf[3]);
+               goto bail;
+       }
+
+       jtf->root = b32(&buf[4]);
+
+       ot = lseek(jtf->fd, 0, SEEK_END);
+       if (ot < 0) {
+               lwsl_err("%s: unable to seek\n", __func__);
+
+               goto bail;
+       }
+       jtf->flen = ot;
+
+       if (jtf->flen != b32(&buf[8])) {
+               lwsl_err("%s: file size doesn't match expected\n", __func__);
+
+               goto bail;
+       }
+
+       jtf->filepath_table = b32(&buf[12]);
+       jtf->filepaths = b32(&buf[16]);
+
+       return jtf->fd;
+
+bail:
+       return -1;
+}
+
+struct lws_fts_file *
+lws_fts_open(const char *filepath)
+{
+       struct lws_fts_file *jtf;
+
+       jtf = lws_malloc(sizeof(*jtf), "fts open");
+       if (!jtf)
+               goto bail1;
+
+       jtf->fd = open(filepath, O_RDONLY);
+       if (jtf->fd < 0) {
+               lwsl_err("%s: unable to open %s\n", __func__, filepath);
+               goto bail2;
+       }
+
+       if (lws_fts_adopt(jtf) < 0)
+               goto bail3;
+
+       return jtf;
+
+bail3:
+       close(jtf->fd);
+bail2:
+       lws_free(jtf);
+bail1:
+       return NULL;
+}
+
+void
+lws_fts_close(struct lws_fts_file *jtf)
+{
+       close(jtf->fd);
+       lws_free(jtf);
+}
+
+#define grab(_pos, _size) { \
+               bp = 0; \
+               if (lseek(jtf->fd, _pos, SEEK_SET) < 0) { \
+                       lwsl_err("%s: unable to seek\n", __func__); \
+\
+                       goto bail; \
+               } \
+\
+               ra = read(jtf->fd, buf, _size); \
+               if (ra < 0) \
+                       goto bail; \
+}
+
+static struct linetable *
+lws_fts_cache_chunktable(struct lws_fts_file *jtf, uint32_t ofs_linetable,
+                        struct lwsac **linetable_head)
+{
+       struct linetable *lt, *first = NULL, **prev = NULL;
+       unsigned char buf[8];
+       int line = 1, bp, ra;
+       off_t cfs = 0;
+
+       *linetable_head = NULL;
+
+       do {
+               grab(ofs_linetable, sizeof(buf));
+
+               lt = lwsac_use(linetable_head, sizeof(*lt), 0);
+               if (!lt)
+                       goto bail;
+               if (!first)
+                       first = lt;
+
+               lt->next = NULL;
+               if (prev)
+                       *prev = lt;
+               prev = &lt->next;
+
+               lt->chunk_line_number_start = line;
+               lt->chunk_line_number_count = b16(&buf[bp + 2]);
+               lt->vli_ofs_in_index = ofs_linetable + 8;
+               lt->chunk_filepos_start = cfs;
+
+               line += lt->chunk_line_number_count;
+
+               cfs += b32(&buf[bp + 4]);
+               ofs_linetable += b16(&buf[bp]);
+
+       } while (b16(&buf[bp]));
+
+       return first;
+
+bail:
+       lwsac_free(linetable_head);
+
+       return NULL;
+}
+
+static int
+lws_fts_getfileoffset(struct lws_fts_file *jtf, struct linetable *ltstart,
+                     int line, off_t *_ofs)
+{
+       struct linetable *lt = ltstart;
+       unsigned char buf[LWS_FTS_LINES_PER_CHUNK * 5];
+       uint32_t ll;
+       off_t ofs;
+       int bp, ra;
+
+       /* first figure out which chunk */
+
+       do {
+               if (line >= lt->chunk_line_number_start &&
+                   line < lt->chunk_line_number_start +
+                           lt->chunk_line_number_count)
+                       break;
+
+               lt = lt->next;
+       } while (lt);
+
+       if (!lt)
+               goto bail;
+
+       /* we know it's in this chunk */
+
+       ofs = lt->chunk_filepos_start;
+       line -= lt->chunk_line_number_start;
+
+       grab(lt->vli_ofs_in_index, sizeof(buf));
+
+       bp = 0;
+       while (line) {
+               bp += rq32(&buf[bp], &ll);
+               ofs += ll;
+               line--;
+       }
+
+       /* we know the offset it is at in the original file */
+
+       *_ofs = ofs;
+
+       return 0;
+
+bail:
+       lwsl_info("%s: bail %d\n", __func__, line);
+
+       return 1;
+}
+
+static int
+ac_record(struct lws_fts_file *jtf, struct lwsac **results_head,
+         const char *needle, int pos, struct wac *s, int sp,
+         uint32_t instances, uint32_t agg_instances, uint32_t children,
+         struct lws_fts_result_autocomplete ***ppac)
+{
+       struct lws_fts_result_autocomplete *ac;
+       int n, m;
+       char *p;
+
+       if (!instances && !agg_instances)
+               return 1;
+
+       m = pos;
+       for (n = 1; n <= sp; n++)
+               m += s[n].ch[s[n].child - 1].name_length;
+
+       ac = lwsac_use(results_head, sizeof(*ac) + m + 1, 0);
+       if (!ac)
+               return -1;
+
+       p = (char *)(ac + 1);
+
+       **ppac = ac;
+       ac->next = NULL;
+       *ppac = &ac->next;
+       ac->instances = instances;
+       ac->agg_instances = agg_instances;
+       ac->ac_length = m;
+       ac->has_children = !!children;
+       ac->elided = 0;
+
+       memcpy(p, needle, pos);
+       p += pos;
+
+       for (n = 1; n <= sp; n++) {
+               int w = s[n].child - 1;
+
+               memcpy(p, s[n].ch[w].name, s[n].ch[w].name_length);
+               p += s[n].ch[w].name_length;
+       }
+       p = (char *)(ac + 1);
+       p[m] = '\0';
+
+       /*
+        * deduct this child's instance weight from his antecdents to track
+        * relative path attractiveness dynamically, after we already used its
+        * best results (children are sorted best-first)
+        */
+       for (n = sp; n >= 0; n--) {
+               s[n].ch[s[n].child - 1].child_agg -= instances;
+               s[n].agg -= instances;
+       }
+
+       return 0;
+}
+
+struct lws_fts_result *
+lws_fts_search(struct lws_fts_file *jtf, struct lws_fts_search_params *ftsp)
+{
+       uint32_t children, instances, co, sl, agg, slt, chunk,
+                fileofs_tif_start, desc, agg_instances;
+       int pos = 0, n, m, nl, bp, base = 0, ra, palm, budget, sp, ofd = -1;
+       unsigned long long tf = lws_now_usecs();
+       struct lws_fts_result_autocomplete **pac = NULL;
+       char stasis, nac = 0, credible, needle[32];
+       struct lws_fts_result_filepath *fp;
+       struct lws_fts_result *result;
+       unsigned char buf[4096];
+       off_t o, child_ofs;
+       struct wac s[128];
+
+       ftsp->results_head = NULL;
+
+       if (!ftsp->needle)
+               return NULL;
+
+       nl = (int)strlen(ftsp->needle);
+       if ((size_t)nl > sizeof(needle) - 2)
+               return NULL;
+
+       result = lwsac_use(&ftsp->results_head, sizeof(*result), 0);
+       if (!result)
+               return NULL;
+
+       /* start with no results... */
+
+       result->autocomplete_head = NULL;
+       pac = &result->autocomplete_head;
+       result->filepath_head = NULL;
+       result->duration_ms = 0;
+       result->effective_flags = ftsp->flags;
+
+       palm = 0;
+
+       for (n = 0; n < nl; n++)
+               needle[n] = tolower(ftsp->needle[n]);
+       needle[nl] = '\0';
+
+       o = jtf->root;
+       do {
+               bp = 0;
+               base = 0;
+
+               grab(o, sizeof(buf));
+
+               child_ofs = o + bp;
+               bp += rq32(&buf[bp], &fileofs_tif_start);
+               bp += rq32(&buf[bp], &children);
+               bp += rq32(&buf[bp], &instances);
+               bp += rq32(&buf[bp], &agg_instances);
+               palm = pos;
+
+               /* the children follow here */
+
+               if (pos == nl) {
+
+                       nac = 0;
+                       if (!fileofs_tif_start)
+                               /*
+                                * we matched, but there are no instances of
+                                * this, it's actually an intermediate
+                                */
+
+                               goto autocomp;
+
+                       /* we leave with bp positioned at the instance list */
+
+                       o = fileofs_tif_start;
+                       grab(o, sizeof(buf));
+                       break;
+               }
+
+               if (ra - bp < 1024) {
+
+                       /*
+                        * We don't have enough.  So reload the buffer starting
+                        * at where we got to.
+                        */
+
+                       base += bp;
+                       grab(o + base, sizeof(buf));
+               }
+
+               /* gets set if any child COULD match needle if it went on */
+
+               credible = 0;
+               for (n = 0; (uint32_t)n < children; n++) {
+                       uint32_t inst;
+
+                       bp += rq32(&buf[bp], &co);
+                       bp += rq32(&buf[bp], &inst);
+                       bp += rq32(&buf[bp], &agg);
+                       bp += rq32(&buf[bp], &desc);
+                       bp += rq32(&buf[bp], &sl);
+
+                       if (sl > (uint32_t)(nl - pos)) {
+
+                               /*
+                                * it can't be a match because it's longer than
+                                * our needle string (but that leaves it as a
+                                * perfectly fine autocomplete candidate)
+                                */
+                               size_t g = nl - pos;
+
+                               /*
+                                * "credible" means at least one child matches
+                                * all the chars in needle up to as many as it
+                                * has.  If not "credible" this path cannot
+                                * match.
+                                */
+                               if (!strncmp((char *)&buf[bp], &needle[pos], g))
+                                       credible = 1;
+                               else
+                                       /*
+                                        * deflate the parent agg using the
+                                        * knowledge this child is not on the
+                                        * path shown by the remainder of needle
+                                        */
+                                       agg_instances -= agg;
+
+                               nac = 0;
+                               bp += sl;
+                               slt = 0;
+                               pos = palm;
+                               goto ensure;
+                       }
+
+                       /* the comparison string potentially has huge length */
+
+                       slt = sl;
+                       while (slt) {
+
+                               /*
+                                * the strategy is to compare whatever we have
+                                * lying around, then bring in more if it didn't
+                                * fail to match yet.  That way we don't bring
+                                * in anything we could already have known was
+                                * not needed due to a match fail.
+                                */
+
+                               chunk = ra - bp;
+                               if (chunk > slt)
+                                       chunk = slt;
+
+                               if ((chunk == 1 && needle[pos] != buf[bp]) ||
+                                   (chunk != 1 &&
+                                    memcmp(&needle[pos], &buf[bp], chunk))) {
+
+                                       /*
+                                        * it doesn't match... so nothing can
+                                        * autocomplete this...
+                                        */
+                                       bp += slt;
+                                       slt = 0;
+                                       nac = 1;
+                                       goto ensure;
+                               }
+
+                               slt -= chunk;
+                               pos += chunk;
+                               bp += chunk;
+
+                               /* so far, it matches */
+
+                               if (!slt) {
+                                       /* we matched the whole thing */
+                                       o = co;
+                                       if (!co)
+                                               goto bail;
+                                       n = (int)children;
+                                       credible = 1;
+                               }
+
+ensure:
+                               /*
+                                * do we have at least buf more to match, or the
+                                * remainder of the string, whichever is less?
+                                *
+                                * bp may exceed sizeof(buf) on no match path
+                                */
+                               chunk = sizeof(buf);
+                               if (slt < chunk)
+                                       chunk = slt;
+
+                               if (ra - bp >= (int)chunk)
+                                       continue;
+
+                               /*
+                                * We don't have enough.  So reload buf starting
+                                * at where we got to.
+                                */
+                               base += bp;
+                               grab(o + base, sizeof(buf));
+
+                       } /* while we are still comparing */
+
+               } /* for each child */
+
+               if ((uint32_t)n == children) {
+                       if (!credible)
+                               goto bail;
+
+                       nac = 0;
+                       goto autocomp;
+               }
+       } while(1);
+
+       result->duration_ms = (int)((lws_now_usecs() - tf) / 1000);
+
+       if (!instances && !children)
+               return result;
+
+       /* the match list may easily exceed one read buffer load ... */
+
+       o += bp;
+
+       /*
+        * Only do the file match list if it was requested in the search flags
+        */
+
+       if (!(ftsp->flags & LWSFTS_F_QUERY_FILES))
+               goto autocomp;
+
+       do {
+               uint32_t fi, tot, line, ro, ofs_linetable, lines, fplen,
+                       *u, _o;
+               struct lwsac *lt_head = NULL;
+               struct linetable *ltst;
+               char path[256], *pp;
+               int footprint;
+               off_t fo;
+
+               ofd = -1;
+               grab(o, sizeof(buf));
+
+               ro = o;
+               bp += rq32(&buf[bp], &_o);
+               o = _o;
+
+               assert(!o || o > TRIE_FILE_HDR_SIZE);
+
+               bp += rq32(&buf[bp], &fi);
+               bp += rq32(&buf[bp], &tot);
+
+               if (lws_fts_filepath(jtf, fi, path, sizeof(path) - 1,
+                                    &ofs_linetable, &lines)) {
+                       lwsl_err("can't get filepath index %d\n", fi);
+                       goto bail;
+               }
+
+               if (ftsp->only_filepath && strcmp(path, ftsp->only_filepath))
+                       continue;
+
+               ltst = lws_fts_cache_chunktable(jtf, ofs_linetable, &lt_head);
+               if (!ltst)
+                       goto bail;
+
+               if (ftsp->flags & LWSFTS_F_QUERY_QUOTE_LINE) {
+                       ofd = open(path, O_RDONLY);
+                       if (ofd < 0) {
+                               lwsac_free(&lt_head);
+                               goto bail;
+                       }
+               }
+
+               fplen = (int)strlen(path);
+               footprint = sizeof(*fp) + fplen + 1;
+               if (ftsp->flags & LWSFTS_F_QUERY_FILE_LINES) {
+                       /* line number and offset in file */
+                       footprint += 2 * sizeof(uint32_t) * tot;
+
+                       if (ftsp->flags & LWSFTS_F_QUERY_QUOTE_LINE)
+                               /* pointer to quote string */
+                               footprint += sizeof(void *) * tot;
+               }
+
+               fp = lwsac_use(&ftsp->results_head, footprint, 0);
+               if (!fp) {
+                       lwsac_free(&lt_head);
+                       goto bail;
+               }
+
+               fp->filepath_length = fplen;
+               fp->lines_in_file = lines;
+               fp->matches = tot;
+               fp->matches_length = footprint - sizeof(*fp) - (fplen + 1);
+               fp->next = result->filepath_head;
+               result->filepath_head = fp;
+
+               /* line table first so it can be aligned */
+
+               u = (uint32_t*)(fp + 1);
+
+               if (ftsp->flags & LWSFTS_F_QUERY_FILE_LINES) {
+
+                       /* for each line number */
+
+                       for (n = 0; (uint32_t)n < tot; n++) {
+
+                               unsigned char lbuf[256], *p;
+                               char ebuf[384];
+                               const char **v;
+                               int m;
+
+                               if ((ra - bp) < 8) {
+                                       base += bp;
+                                       grab(ro + base, sizeof(buf));
+                               }
+
+                               bp += rq32(&buf[bp], &line);
+                               *u++ = line;
+
+                               if (lws_fts_getfileoffset(jtf, ltst, line, &fo))
+                                       continue;
+
+                               *u++ = (uint32_t)fo;
+
+                               if (!(ftsp->flags & LWSFTS_F_QUERY_QUOTE_LINE))
+                                       continue;
+
+                               if (lseek(ofd, fo, SEEK_SET) < 0)
+                                       continue;
+
+                               m = read(ofd, lbuf, sizeof(lbuf) - 1);
+                               if (m < 0)
+                                       continue;
+                               lbuf[sizeof(lbuf) - 1] = '\0';
+
+                               p = (unsigned char *)strchr((char *)lbuf, '\n');
+                               if (p)
+                                       m = lws_ptr_diff(p, lbuf);
+                               lbuf[m] = '\0';
+                               p = (unsigned char *)strchr((char *)lbuf, '\r');
+                               if (p)
+                                       m = lws_ptr_diff(p, lbuf);
+                               lbuf[m] = '\0';
+
+                               lws_json_purify(ebuf, (const char *)lbuf,
+                                               sizeof(ebuf) - 1);
+                               m = (int)strlen(ebuf);
+
+                               p = lwsac_use(&ftsp->results_head, m + 1, 0);
+                               if (!p) {
+                                       lwsac_free(&lt_head);
+                                       goto bail;
+                               }
+
+                               memcpy(p, ebuf, m);
+                               p[m] = '\0';
+                               v = (const char **)u;
+                               *v = (const char *)p;
+                               u += sizeof(const char *) / sizeof(uint32_t);
+                       }
+               }
+
+               pp = ((char *)&fp[1]) + fp->matches_length;
+               memcpy(pp, path, fplen);
+               pp[fplen] = '\0';
+
+               if (ofd >= 0) {
+                       close(ofd);
+                       ofd = -1;
+               }
+
+               lwsac_free(&lt_head);
+
+               if (ftsp->only_filepath)
+                       break;
+
+       } while (o);
+
+       /* sort the instance file list by results density */
+
+       do {
+               struct lws_fts_result_filepath **prf, *rf1, *rf2;
+
+               stasis = 1;
+
+               /* bubble sort keeps going until nothing changed */
+
+               prf = &result->filepath_head;
+               while (*prf) {
+
+                       rf1 = *prf;
+                       rf2 = rf1->next;
+
+                       if (rf2 && rf1->lines_in_file && rf2->lines_in_file &&
+                           ((rf1->matches * 1000) / rf1->lines_in_file) <
+                           ((rf2->matches * 1000) / rf2->lines_in_file)) {
+                               stasis = 0;
+
+                               *prf = rf2;
+                               rf1->next = rf2->next;
+                               rf2->next = rf1;
+                       }
+
+                       prf = &(*prf)->next;
+               }
+
+       } while (!stasis);
+
+autocomp:
+
+       if (!(ftsp->flags & LWSFTS_F_QUERY_AUTOCOMPLETE) || nac)
+               return result;
+
+       /*
+        * autocomplete (ie, the descendent paths that yield the most hits)
+        *
+        * We actually need to spider the earliest terminal descendents from
+        * the child we definitely got past, and present the first n terminal
+        * strings.  The descendents are already sorted in order of highest
+        * aggregated hits in their descendents first, so simply collecting n
+        * earliest leaf children is enough.
+        *
+        * The leaf children may be quite deep down in a stack however.  So we
+        * have to go through all the walking motions collecting and retaining
+        * child into for when we come back up the walk.
+        *
+        * We can completely ignore file instances for this, we just need the
+        * earliest children.  And we can restrict how many children we stash
+        * in each stack level to eg, 5.
+        *
+        * child_ofs comes in pointing at the start of the trie entry that is
+        * to be the starting point for making suggestions.
+        */
+
+       budget = ftsp->max_autocomplete;
+       base = 0;
+       bp = 0;
+       pac = &result->autocomplete_head;
+       sp = 0;
+       if (pos > (int)sizeof(s[sp].ch[0].name) - 1)
+               pos = (int)sizeof(s[sp].ch[0].name) - 1;
+
+       memset(&s[sp], 0, sizeof(s[sp]));
+
+       s[sp].child = 1;
+       s[sp].tifs = fileofs_tif_start;
+       s[sp].self = child_ofs;
+       s[sp].ch[0].effpos = pos;
+
+       if (pos == nl)
+               n = ac_record(jtf, &ftsp->results_head, needle, pos, s, 0,
+                             instances, agg_instances, children, &pac);
+
+       while (sp >= 0 && budget) {
+               int nobump = 0;
+               struct ch *tch = &s[sp].ch[s[sp].child - 1];
+
+               grab(child_ofs, sizeof(buf));
+
+               bp += rq32(&buf[bp], &fileofs_tif_start);
+               bp += rq32(&buf[bp], &children);
+               bp += rq32(&buf[bp], &instances);
+               bp += rq32(&buf[bp], &agg_instances);
+
+               if (sp > 0 && s[sp - 1].done_children &&
+                   tch->effpos + tch->name_length >= nl &&
+                   tch->inst && fileofs_tif_start) {
+                       n = ac_record(jtf, &ftsp->results_head, needle, pos, s,
+                                     sp, tch->inst, tch->child_agg,
+                                     tch->descendents, &pac);
+                       if (n < 0)
+                               goto bail;
+                       if (!n)
+                               if (--budget == 0)
+                                       break;
+               }
+
+               if (!s[sp].done_children && children) {
+                       s[sp].done_children = 1;
+                       sp++;
+                       memset(&s[sp], 0, sizeof(s[sp]));
+                       s[sp].tifs = fileofs_tif_start;
+                       s[sp].self = child_ofs;
+
+                       for (n = 0; n < (int)children && s[sp].child_count <
+                                           (int)LWS_ARRAY_SIZE(s[0].ch); n++) {
+                               uint32_t slen, cho, agg, inst;
+                               int i = s[sp].child_count;
+                               struct ch *ch = &s[sp].ch[i];
+                               size_t max;
+
+                               bp += rq32(&buf[bp], &cho);
+                               bp += rq32(&buf[bp], &inst);
+                               bp += rq32(&buf[bp], &agg);
+                               bp += rq32(&buf[bp], &desc);
+                               bp += rq32(&buf[bp], &slen);
+
+                               max = slen;
+                               if (max > sizeof(ch->name) - 1)
+                                       max = sizeof(ch->name) - 1;
+
+                               strncpy(ch->name, (char *)&buf[bp], max);
+                               bp += slen;
+
+                               ch->name_length = (int)max;
+                               ch->name[sizeof(ch->name) - 1] = '\0';
+                               ch->inst = inst;
+                               ch->effpos =
+                                      s[sp - 1].ch[s[sp - 1].child - 1].effpos;
+
+                               ch->child_agg = agg;
+                               ch->descendents = desc;
+
+                               /*
+                                * if we have more needle chars than we matched
+                                * to get this far, we can only allow potential
+                                * matches that are consistent with the
+                                * additional unmatched character(s)...
+                                */
+
+                               m = nl - ch->effpos;
+                               if (m > ch->name_length)
+                                       m = ch->name_length;
+
+                               if (m > 0 &&
+                                   strncmp(&needle[ch->effpos], ch->name, m))
+                                       continue;
+
+                               ch->effpos += m;
+                               s[sp].ch[s[sp].child_count++].ofs = cho;
+                       }
+
+               }
+
+               while (sp >= 0 && s[sp].child >= s[sp].child_count) {
+                       s[sp].done_children = 0;
+                       sp--;
+               }
+
+               /*
+                * Compare parent remaining agg vs parent's next siblings' still
+                * intact original agg... if the next sibling has more, abandon
+                * the parent path and go with the sibling... this keeps the
+                * autocomplete results related to popularity.
+                */
+
+               nobump = 0;
+               n = sp - 1;
+               while (n >= 0) {
+                       struct lws_fts_result_autocomplete *ac =
+                               (struct lws_fts_result_autocomplete *)pac;
+
+                       if (s[n].child < s[n].child_count &&
+                           s[n].ch[s[n].child - 1].child_agg <
+                                   s[n].ch[s[n].child].child_agg) {
+
+                               if (pac)
+                                       /*
+                                        * mark the autocomplete result that
+                                        * there were more children down his
+                                        * path that we skipped in these results
+                                        */
+                                       ac->elided = 1;
+
+                               for (m = n; m < sp + 1; m++)
+                                       s[m].done_children = 0;
+                               sp = n;
+                               child_ofs = s[sp].ch[s[sp].child++].ofs;
+                               nobump = 1;
+                       }
+
+                       n--;
+               }
+
+               if (nobump || sp < 0)
+                       continue;
+
+               child_ofs = s[sp].ch[s[sp].child++].ofs;
+       }
+
+       /* let's do a final sort into agg order */
+
+       do {
+               struct lws_fts_result_autocomplete *ac1, *ac2;
+
+               stasis = 1;
+
+               /* bubble sort keeps going until nothing changed */
+
+               pac = &result->autocomplete_head;
+               while (*pac) {
+
+                       ac1 = *pac;
+                       ac2 = ac1->next;
+
+                       if (ac2 && ac1->instances < ac2->instances) {
+                               stasis = 0;
+
+                               *pac = ac2;
+                               ac1->next = ac2->next;
+                               ac2->next = ac1;
+                       }
+
+                       pac = &(*pac)->next;
+               }
+
+       } while (!stasis);
+
+       return result;
+
+bail:
+       if (ofd >= 0)
+               close(ofd);
+
+       lwsl_info("%s: search ended up at bail\n", __func__);
+
+       return result;
+}
diff --git a/lib/misc/fts/trie.c b/lib/misc/fts/trie.c
new file mode 100644 (file)
index 0000000..d188165
--- /dev/null
@@ -0,0 +1,1369 @@
+/*
+ * libwebsockets - trie
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ * The functions allow
+ *
+ *  - collecting a concordance of strings from one or more files (eg, a
+ *    directory of files) into a single in-memory, lac-backed trie;
+ *
+ *  - to optimize and serialize the in-memory trie to an fd;
+ *
+ *  - to very quickly report any instances of a string in any of the files
+ *    indexed by the trie, by a seeking around a serialized trie fd, without
+ *    having to load it all in memory
+ */
+
+#include "core/private.h"
+#include "misc/fts/private.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+
+struct lws_fts_entry;
+
+/* notice these are stored in t->lwsac_input_head which has input file scope */
+
+struct lws_fts_filepath {
+       struct lws_fts_filepath *next;
+       struct lws_fts_filepath *prev;
+       char filepath[256];
+       jg2_file_offset ofs;
+       jg2_file_offset line_table_ofs;
+       int filepath_len;
+       int file_index;
+       int total_lines;
+       int priority;
+};
+
+/* notice these are stored in t->lwsac_input_head which has input file scope */
+
+struct lws_fts_lines {
+       struct lws_fts_lines *lines_next;
+       /*
+        * amount of line numbers needs to meet average count for best
+        * efficiency.
+        *
+        * Line numbers are stored in VLI format since if we don't, around half
+        * the total lac allocation consists of struct lws_fts_lines...
+        * size chosen to maintain 8-byte struct alignment
+        */
+       uint8_t vli[119];
+       char count;
+};
+
+/* this represents the instances of a symbol inside a given filepath */
+
+struct lws_fts_instance_file {
+       /* linked-list of tifs generated for current file */
+       struct lws_fts_instance_file *inst_file_next;
+       struct lws_fts_entry *owner;
+       struct lws_fts_lines *lines_list, *lines_tail;
+       uint32_t file_index;
+       uint32_t total;
+
+       /*
+        * optimization for the common case there's only 1 - ~3 matches, so we
+        * don't have to allocate any lws_fts_lines struct
+        *
+        * Using 8 bytes total for this maintains 8-byte struct alignment...
+        */
+
+       uint8_t vli[7];
+       char count;
+};
+
+/*
+ * this is the main trie in-memory allocation object
+ */
+
+struct lws_fts_entry {
+       struct lws_fts_entry *parent;
+
+       struct lws_fts_entry *child_list;
+       struct lws_fts_entry *sibling;
+
+       /*
+        * care... this points to content in t->lwsac_input_head, it goes
+        * out of scope when the input file being indexed completes
+        */
+       struct lws_fts_instance_file *inst_file_list;
+
+       jg2_file_offset ofs_last_inst_file;
+
+       char *suffix; /* suffix string or NULL if one char (in .c) */
+       jg2_file_offset ofs;
+       uint32_t child_count;
+       uint32_t instance_count;
+       uint32_t agg_inst_count;
+       uint32_t agg_child_count;
+       uint32_t suffix_len;
+       unsigned char c;
+};
+
+/* there's only one of these per trie file */
+
+struct lws_fts {
+       struct lwsac *lwsac_head;
+       struct lwsac *lwsac_input_head;
+       struct lws_fts_entry *root;
+       struct lws_fts_filepath *filepath_list;
+       struct lws_fts_filepath *fp;
+
+       struct lws_fts_entry *parser;
+       struct lws_fts_entry *root_lookup[256];
+
+       /*
+        * head of linked-list of tifs generated for current file
+        * care... this points to content in t->lwsac_input_head
+        */
+       struct lws_fts_instance_file *tif_list;
+
+       jg2_file_offset c; /* length of output file so far */
+
+       uint64_t agg_trie_creation_us;
+       uint64_t agg_raw_input;
+       uint64_t worst_lwsac_input_size;
+       int last_file_index;
+       int chars_in_line;
+       jg2_file_offset last_block_len_ofs;
+       int line_number;
+       int lines_in_unsealed_linetable;
+       int next_file_index;
+       int count_entries;
+
+       int fd;
+       unsigned int agg_pos;
+       unsigned int str_match_pos;
+
+       unsigned char aggregate;
+       unsigned char agg[128];
+};
+
+/* since the kernel case allocates >300MB, no point keeping this too low */
+
+#define TRIE_LWSAC_BLOCK_SIZE (1024 * 1024)
+
+#define spill(margin, force) \
+       if (bp && ((uint32_t)bp >= (sizeof(buf) - (margin)) || (force))) { \
+               if (write(t->fd, buf, bp) != bp) { \
+                       lwsl_err("%s: write %d failed (%d)\n", __func__, \
+                                bp, errno); \
+                       return 1; \
+               } \
+               t->c += bp; \
+               bp = 0; \
+       }
+
+static int
+g32(unsigned char *b, uint32_t d)
+{
+       *b++ = (d >> 24) & 0xff;
+       *b++ = (d >> 16) & 0xff;
+       *b++ = (d >> 8) & 0xff;
+       *b = d & 0xff;
+
+       return 4;
+}
+
+static int
+g16(unsigned char *b, int d)
+{
+       *b++ = (d >> 8) & 0xff;
+       *b = d & 0xff;
+
+       return 2;
+}
+
+static int
+wq32(unsigned char *b, uint32_t d)
+{
+       unsigned char *ob = b;
+
+       if (d > (1 << 28) - 1)
+               *b++ = ((d >> 28) | 0x80) & 0xff;
+
+       if (d > (1 << 21) - 1)
+               *b++ = ((d >> 21) | 0x80) & 0xff;
+
+       if (d > (1 << 14) - 1)
+               *b++ = ((d >> 14) | 0x80) & 0xff;
+
+       if (d > (1 << 7) - 1)
+               *b++ = ((d >> 7) | 0x80) & 0xff;
+
+       *b++ = d & 0x7f;
+
+       return (int)(b - ob);
+}
+
+
+/* read a VLI, return the number of bytes used */
+
+int
+rq32(unsigned char *b, uint32_t *d)
+{
+       unsigned char *ob = b;
+       uint32_t t = 0;
+
+       t = *b & 0x7f;
+       if (*(b++) & 0x80) {
+               t = (t << 7) | (*b & 0x7f);
+               if (*(b++) & 0x80) {
+                       t = (t << 7) | (*b & 0x7f);
+                       if (*(b++) & 0x80) {
+                               t = (t << 7) | (*b & 0x7f);
+                               if (*(b++) & 0x80) {
+                                       t = (t << 7) | (*b & 0x7f);
+                                       b++;
+                               }
+                       }
+               }
+       }
+
+       *d = t;
+
+       return (int)(b - ob);
+}
+
+struct lws_fts *
+lws_fts_create(int fd)
+{
+       struct lws_fts *t;
+       struct lwsac *lwsac_head = NULL;
+       unsigned char buf[TRIE_FILE_HDR_SIZE];
+
+       t = lwsac_use(&lwsac_head, sizeof(*t), TRIE_LWSAC_BLOCK_SIZE);
+       if (!t)
+               return NULL;
+
+       memset(t, 0, sizeof(*t));
+
+       t->fd = fd;
+       t->lwsac_head = lwsac_head;
+       t->root = lwsac_use(&lwsac_head, sizeof(*t->root),
+                           TRIE_LWSAC_BLOCK_SIZE);
+       if (!t->root)
+               goto unwind;
+
+       memset(t->root, 0, sizeof(*t->root));
+       t->parser = t->root;
+       t->last_file_index = -1;
+       t->line_number = 1;
+       t->filepath_list = NULL;
+
+       memset(t->root_lookup, 0, sizeof(*t->root_lookup));
+
+       /* write the header */
+
+       buf[0] = 0xca;
+       buf[1] = 0x7a;
+       buf[2] = 0x5f;
+       buf[3] = 0x75;
+
+       /* (these are filled in with correct data at the end) */
+
+       /* file offset to root trie entry */
+       g32(&buf[4], 0);
+       /* file length when it was created */
+       g32(&buf[8], 0);
+       /* fileoffset to the filepath table */
+       g32(&buf[0xc], 0);
+       /* count of filepaths */
+       g32(&buf[0x10], 0);
+
+       if (write(t->fd, buf, TRIE_FILE_HDR_SIZE) != TRIE_FILE_HDR_SIZE) {
+               lwsl_err("%s: trie header write failed\n", __func__);
+               goto unwind;
+       }
+
+       t->c = TRIE_FILE_HDR_SIZE;
+
+       return t;
+
+unwind:
+       lwsac_free(&lwsac_head);
+
+       return NULL;
+}
+
+void
+lws_fts_destroy(struct lws_fts **trie)
+{
+       struct lwsac *lwsac_head = (*trie)->lwsac_head;
+       lwsac_free(&(*trie)->lwsac_input_head);
+       lwsac_free(&lwsac_head);
+       *trie = NULL;
+}
+
+int
+lws_fts_file_index(struct lws_fts *t, const char *filepath, int filepath_len,
+                   int priority)
+{
+       struct lws_fts_filepath *fp = t->filepath_list;
+#if 0
+       while (fp) {
+               if (fp->filepath_len == filepath_len &&
+                   !strcmp(fp->filepath, filepath))
+                       return fp->file_index;
+
+               fp = fp->next;
+       }
+#endif
+       fp = lwsac_use(&t->lwsac_head, sizeof(*fp), TRIE_LWSAC_BLOCK_SIZE);
+       if (!fp)
+               return -1;
+
+       fp->next = t->filepath_list;
+       t->filepath_list = fp;
+       strncpy(fp->filepath, filepath, sizeof(fp->filepath) - 1);
+       fp->filepath[sizeof(fp->filepath) - 1] = '\0';
+       fp->filepath_len = filepath_len;
+       fp->file_index = t->next_file_index++;
+       fp->line_table_ofs = t->c;
+       fp->priority = priority;
+       fp->total_lines = 0;
+       t->fp = fp;
+
+       return fp->file_index;
+}
+
+static struct lws_fts_entry *
+lws_fts_entry_child_add(struct lws_fts *t, unsigned char c,
+                       struct lws_fts_entry *parent)
+{
+       struct lws_fts_entry *e, **pe;
+
+       e = lwsac_use(&t->lwsac_head, sizeof(*e), TRIE_LWSAC_BLOCK_SIZE);
+       if (!e)
+               return NULL;
+
+       memset(e, 0, sizeof(*e));
+
+       e->c = c;
+       parent->child_count++;
+       e->parent = parent;
+       t->count_entries++;
+
+       /* keep the parent child list in ascending sort order for c */
+
+       pe = &parent->child_list;
+       while (*pe) {
+               assert((*pe)->parent == parent);
+               if ((*pe)->c > c) {
+                       /* add it before */
+                       e->sibling = *pe;
+                       *pe = e;
+                       break;
+               }
+               pe = &(*pe)->sibling;
+       }
+
+       if (!*pe) {
+               /* add it at the end */
+               e->sibling = NULL;
+               *pe = e;
+       }
+
+       return e;
+}
+
+static int
+finalize_per_input(struct lws_fts *t)
+{
+       struct lws_fts_instance_file *tif;
+       unsigned char buf[8192];
+       uint64_t lwsac_input_size;
+       jg2_file_offset temp;
+       int bp = 0;
+
+       bp += g16(&buf[bp], 0);
+       bp += g16(&buf[bp], 0);
+       bp += g32(&buf[bp], 0);
+       if (write(t->fd, buf, bp) != bp)
+               return 1;
+       t->c += bp;
+       bp = 0;
+
+       /*
+        * Write the generated file index + instances (if any)
+        *
+        * Notice the next same-parent file instance fileoffset list is
+        * backwards, so it does not require seeks to fill in.  The first
+        * entry has 0 but the second entry points to the first entry (whose
+        * fileoffset is known).
+        *
+        * After all the file instance structs are finalized,
+        * .ofs_last_inst_file contains the fileoffset of that child's tif
+        * list head in the file.
+        *
+        * The file instances are written to disk in the order that the files
+        * were indexed, along with their prev pointers inline.
+        */
+
+       tif = t->tif_list;
+       while (tif) {
+               struct lws_fts_lines *i;
+
+               spill((3 * MAX_VLI) + tif->count, 0);
+
+               temp = tif->owner->ofs_last_inst_file;
+               if (tif->total)
+                       tif->owner->ofs_last_inst_file = t->c + bp;
+
+               assert(!temp || (temp > TRIE_FILE_HDR_SIZE && temp < t->c));
+
+               /* fileoffset of prev instance file for this entry, or 0 */
+               bp += wq32(&buf[bp], temp);
+               bp += wq32(&buf[bp], tif->file_index);
+               bp += wq32(&buf[bp], tif->total);
+
+               /* remove any pointers into this disposable lac footprint */
+               tif->owner->inst_file_list = NULL;
+
+               memcpy(&buf[bp], &tif->vli, tif->count);
+               bp += tif->count;
+
+               i = tif->lines_list;
+               while (i) {
+                       spill(i->count, 0);
+                       memcpy(&buf[bp], &i->vli, i->count);
+                       bp += i->count;
+
+                       i = i->lines_next;
+               }
+
+               tif = tif->inst_file_next;
+       }
+
+       spill(0, 1);
+
+       assert(lseek(t->fd, 0, SEEK_END) == (off_t)t->c);
+
+       if (t->lwsac_input_head) {
+               lwsac_input_size = lwsac_total_alloc(t->lwsac_input_head);
+               if (lwsac_input_size > t->worst_lwsac_input_size)
+                       t->worst_lwsac_input_size = lwsac_input_size;
+       }
+
+       /*
+        * those per-file allocations are all on a separate lac so we can
+        * free it cleanly afterwards
+        */
+       lwsac_free(&t->lwsac_input_head);
+
+       /* and lose the pointer into the deallocated lac */
+       t->tif_list = NULL;
+
+       return 0;
+}
+
+/*
+ * 0 = punctuation, whitespace, brackets etc
+ * 1 = character inside symbol set
+ * 2 = upper-case character inside symbol set
+ */
+
+static char classify[] = {
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+       0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, //1,
+       0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+#if 0
+static const char *
+name_entry(struct lws_fts_entry *e1, char *s, int len)
+{
+       struct lws_fts_entry *e2;
+       int n = len;
+
+       s[--n] = '\0';
+
+       e2 = e1;
+       while (e2) {
+               if (e2->suffix) {
+                       if ((int)e2->suffix_len < n) {
+                               n -= e2->suffix_len;
+                               memcpy(&s[n], e2->suffix, e2->suffix_len);
+                       }
+               } else {
+                       n--;
+                       s[n] = e2->c;
+               }
+
+               e2 = e2->parent;
+       }
+
+       return &s[n + 1];
+}
+#endif
+
+/*
+ * as we parse the input, we create a line length table for the file index.
+ * Only the file header has been written before we start doing this.
+ */
+
+int
+lws_fts_fill(struct lws_fts *t, uint32_t file_index, const char *buf,
+            size_t len)
+{
+       unsigned long long tf = lws_now_usecs();
+       unsigned char c, linetable[256], vlibuf[8];
+       struct lws_fts_entry *e, *e1, *dcl;
+       struct lws_fts_instance_file *tif;
+       int bp = 0, sline, chars, m;
+       char *osuff, skipline = 0;
+       struct lws_fts_lines *tl;
+       unsigned int olen, n;
+       off_t lbh;
+
+       if ((int)file_index != t->last_file_index) {
+               if (t->last_file_index >= 0)
+                       finalize_per_input(t);
+               t->last_file_index = file_index;
+               t->line_number = 1;
+               t->chars_in_line = 0;
+               t->lines_in_unsealed_linetable = 0;
+       }
+
+       t->agg_raw_input += len;
+
+resume:
+
+       chars = 0;
+       lbh = t->c;
+       sline = t->line_number;
+       bp += g16(&linetable[bp], 0);
+       bp += g16(&linetable[bp], 0);
+       bp += g32(&linetable[bp], 0);
+
+       while (len) {
+               char go_around = 0;
+
+               if (t->lines_in_unsealed_linetable >= LWS_FTS_LINES_PER_CHUNK)
+                       break;
+
+               len--;
+
+               c = (unsigned char)*buf++;
+               t->chars_in_line++;
+               if (c == '\n') {
+                       skipline = 0;
+                       t->filepath_list->total_lines++;
+                       t->lines_in_unsealed_linetable++;
+                       t->line_number++;
+
+                       bp += wq32(&linetable[bp], t->chars_in_line);
+                       if ((unsigned int)bp > sizeof(linetable) - 6) {
+                               if (write(t->fd, linetable, bp) != bp) {
+                                       lwsl_err("%s: linetable write failed\n",
+                                                       __func__);
+                                       return 1;
+                               }
+                               t->c += bp;
+                               bp = 0;
+                               // assert(lseek(t->fd, 0, SEEK_END) == t->c);
+                       }
+
+                       chars += t->chars_in_line;
+                       t->chars_in_line = 0;
+
+                       /*
+                        * Detect overlength lines and skip them (eg, BASE64
+                        * in css etc)
+                        */
+
+                       if (len > 200) {
+                               n = 0;
+                               m = 0;
+                               while (n < 200 && m < 80 && buf[n] != '\n') {
+                                      if (buf[n] == ' ' || buf[n] == '\t')
+                                              m = 0;
+                                       n++;
+                                       m++;
+                               }
+
+                               /* 80 lines no whitespace, or >=200-char line */
+
+                               if (m == 80 || n == 200)
+                                       skipline = 1;
+                       }
+
+                       goto seal;
+               }
+               if (skipline)
+                       continue;
+
+               m = classify[(int)c];
+               if (!m)
+                       goto seal;
+               if (m == 2)
+                       c += 'a' - 'A';
+
+               if (t->aggregate) {
+
+                       /*
+                        * We created a trie entry for an earlier char in this
+                        * symbol already.  So we know at the moment, any
+                        * further chars in the symbol are the only children.
+                        *
+                        * Aggregate them and add them as a string suffix to
+                        * the trie symbol at the end (when we know how much to
+                        * allocate).
+                        */
+
+                       if (t->agg_pos < sizeof(t->agg) - 1)
+                               /* symbol is not too long to stash */
+                               t->agg[t->agg_pos++] = c;
+
+                       continue;
+               }
+
+               if (t->str_match_pos) {
+                       go_around = 1;
+                       goto seal;
+               }
+
+               /* zeroth-iteration child matching */
+
+               if (t->parser == t->root) {
+                       e = t->root_lookup[(int)c];
+                       if (e) {
+                               t->parser = e;
+                               continue;
+                       }
+               } else {
+
+                       /* look for the char amongst the children */
+
+                       e = t->parser->child_list;
+                       while (e) {
+
+                               /* since they're alpha ordered... */
+                               if (e->c > c) {
+                                       e = NULL;
+                                       break;
+                               }
+                               if (e->c == c) {
+                                       t->parser = e;
+
+                                       if (e->suffix)
+                                               t->str_match_pos = 1;
+
+                                       break;
+                               }
+
+                               e = e->sibling;
+                       }
+
+                       if (e)
+                               continue;
+               }
+
+               /*
+                * we are blazing a new trail, add a new child representing
+                * the whole suffix that couldn't be matched until now.
+                */
+
+               e = lws_fts_entry_child_add(t, c, t->parser);
+               if (!e) {
+                       lwsl_err("%s: lws_fts_entry_child_add failed\n",
+                                       __func__);
+                       return 1;
+               }
+
+               /* if it's the root node, keep the root_lookup table in sync */
+
+               if (t->parser == t->root)
+                       t->root_lookup[(int)c] = e;
+
+               /* follow the new path */
+               t->parser = e;
+
+               {
+                       struct lws_fts_entry **pe = &e->child_list;
+                       while (*pe) {
+                               assert((*pe)->parent == e);
+
+                               pe = &(*pe)->sibling;
+                       }
+               }
+
+               /*
+                * If there are any more symbol characters coming, just
+                * create a suffix string on t->parser instead of what must
+                * currently be single-child nodes, since we just created e
+                * as a child with a single character due to no existing match
+                * on that single character... so if no match on 'h' with this
+                * guy's parent, we created e that matches on the single char
+                * 'h'.  If the symbol continues ... 'a' 'p' 'p' 'y', then
+                * instead of creating singleton child nodes under e,
+                * modify e to match on the whole string suffix "happy".
+                *
+                * If later "hoppy" appears, we will remove the suffix on e,
+                * so it reverts to a char match for 'h', add singleton children
+                * for 'a' and 'o', and attach a "ppy" suffix child to each of
+                * those.
+                *
+                * We want to do this so we don't have to allocate trie entries
+                * for every char in the string to save memory and consequently
+                * time.
+                *
+                * Don't try this optimization if the parent is the root node...
+                * it's not compatible with it's root_lookup table and it's
+                * highly likely children off the root entry are going to have
+                * to be fragmented.
+                */
+
+               if (e->parent != t->root) {
+                       t->aggregate = 1;
+                       t->agg_pos = 0;
+               }
+
+               continue;
+
+seal:
+               if (t->str_match_pos) {
+
+                       /*
+                        * We're partway through matching an elaborated string
+                        * on a child, not just a character.  String matches
+                        * only exist when we met a child entry that only had
+                        * one path until now... so we had an 'h', and the
+                        * only child had a string "hello".
+                        *
+                        * We are following the right path and will not need
+                        * to back up, but we may find as we go we have the
+                        * first instance of a second child path, eg, "help".
+                        *
+                        * When we get to the 'p', we have to split what was
+                        * the only string option "hello" into "hel" and then
+                        * two child entries, for "lo" and 'p'.
+                        */
+
+                       if (c == t->parser->suffix[t->str_match_pos++]) {
+                               if (t->str_match_pos < t->parser->suffix_len)
+                                       continue;
+
+                               /*
+                                * We simply matched everything, continue
+                                * parsing normally from this trie entry.
+                                */
+
+                               t->str_match_pos = 0;
+                               continue;
+                       }
+
+                       /*
+                        * So... we hit a mismatch somewhere... it means we
+                        * have to split this string entry.
+                        *
+                        * We know the first char actually matched in order to
+                        * start down this road.  So for the current trie entry,
+                        * we need to truncate his suffix at the char before
+                        * this mismatched one, where we diverged (if the
+                        * second char, simply remove the suffix string from the
+                        * current trie entry to turn it back to a 1-char match)
+                        *
+                        * The original entry, which becomes the lhs post-split,
+                        * is t->parser.
+                        */
+
+                       olen = t->parser->suffix_len;
+                       osuff = t->parser->suffix;
+
+                       if (t->str_match_pos == 2)
+                               t->parser->suffix = NULL;
+                       else
+                               t->parser->suffix_len = t->str_match_pos - 1;
+
+                       /*
+                        * Then we need to create a new child trie entry that
+                        * represents the remainder of the original string
+                        * path that we didn't match.  For the "hello" /
+                        * "help" case, this guy will have "lo".
+                        *
+                        * Any instances or children (not siblings...) that were
+                        * attached to the original trie entry must be detached
+                        * first and then migrate to this new guy that completes
+                        * the original string.
+                        */
+
+                       dcl = t->parser->child_list;
+                       m = t->parser->child_count;
+
+                       t->parser->child_list = NULL;
+                       t->parser->child_count = 0;
+
+                       e = lws_fts_entry_child_add(t,
+                                       osuff[t->str_match_pos - 1], t->parser);
+                       if (!e) {
+                               lwsl_err("%s: lws_fts_entry_child_add fail1\n",
+                                               __func__);
+                               return 1;
+                       }
+
+                       e->child_list = dcl;
+                       e->child_count = m;
+                       /*
+                        * any children we took over must point to us as the
+                        * parent now they appear on our child list
+                        */
+                       e1 = e->child_list;
+                       while (e1) {
+                               e1->parent = e;
+                               e1 = e1->sibling;
+                       }
+
+                       /*
+                        * We detached any children, gave them to the new guy
+                        * and replaced them with just our new guy
+                        */
+                       t->parser->child_count = 1;
+                       t->parser->child_list = e;
+
+                       /*
+                        * any instances that belonged to the original entry we
+                        * are splitting now must be reassigned to the end
+                        * part
+                        */
+
+                       e->inst_file_list = t->parser->inst_file_list;
+                       if (e->inst_file_list)
+                               e->inst_file_list->owner = e;
+                       t->parser->inst_file_list = NULL;
+                       e->instance_count = t->parser->instance_count;
+                       t->parser->instance_count = 0;
+
+                       e->ofs_last_inst_file = t->parser->ofs_last_inst_file;
+                       t->parser->ofs_last_inst_file = 0;
+
+                       if (t->str_match_pos != olen) {
+                               /* we diverged partway */
+                               e->suffix = &osuff[t->str_match_pos - 1];
+                               e->suffix_len = olen - (t->str_match_pos - 1);
+                       }
+
+                       /*
+                        * if the current char is a terminal, skip creating a
+                        * new way forward.
+                        */
+
+                       if (classify[(int)c]) {
+
+                               /*
+                                * Lastly we need to create a new child trie
+                                * entry that represents the new way forward
+                                * from the point that we diverged.  For the
+                                * "hello" / "help" case, this guy will start
+                                * as a child of "hel" with the single
+                                * character match 'p'.
+                                *
+                                * Since he becomes the current parser context,
+                                * more symbol characters may be coming to make
+                                * him into, eg, "helping", in which case he
+                                * will acquire a suffix eventually of "ping"
+                                * via the aggregation stuff
+                                */
+
+                               e = lws_fts_entry_child_add(t, c, t->parser);
+                               if (!e) {
+                                       lwsl_err("%s: child_add fail2\n",
+                                                __func__);
+                                       return 1;
+                               }
+                       }
+
+                       /* go on following this path */
+                       t->parser = e;
+
+                       t->aggregate = 1;
+                       t->agg_pos = 0;
+
+                       t->str_match_pos = 0;
+
+                       if (go_around)
+                               continue;
+
+                       /* this is intended to be a seal */
+               }
+
+
+               /* end of token */
+
+               if (t->aggregate && t->agg_pos) {
+
+                       /* if nothing in agg[]: leave as single char match */
+
+                       /* otherwise copy out the symbol aggregation */
+                       t->parser->suffix = lwsac_use(&t->lwsac_head,
+                                                   t->agg_pos + 1,
+                                                   TRIE_LWSAC_BLOCK_SIZE);
+                       if (!t->parser->suffix) {
+                               lwsl_err("%s: lac for suffix failed\n",
+                                               __func__);
+                               return 1;
+                       }
+
+                       /* add the first char at the beginning */
+                       *t->parser->suffix = t->parser->c;
+                       /* and then add the agg buffer stuff */
+                       memcpy(t->parser->suffix + 1, t->agg, t->agg_pos);
+                       t->parser->suffix_len = t->agg_pos + 1;
+               }
+               t->aggregate = 0;
+
+               if (t->parser == t->root) /* multiple terminal chars */
+                       continue;
+
+               if (!t->parser->inst_file_list ||
+                   t->parser->inst_file_list->file_index != file_index) {
+                       tif = lwsac_use(&t->lwsac_input_head, sizeof(*tif),
+                                     TRIE_LWSAC_BLOCK_SIZE);
+                       if (!tif) {
+                               lwsl_err("%s: lac for tif failed\n",
+                                               __func__);
+                               return 1;
+                       }
+
+                       tif->file_index = file_index;
+                       tif->owner = t->parser;
+                       tif->lines_list = NULL;
+                       tif->lines_tail = NULL;
+                       tif->total = 0;
+                       tif->count = 0;
+                       tif->inst_file_next = t->tif_list;
+                       t->tif_list = tif;
+
+                       t->parser->inst_file_list = tif;
+               }
+
+               /*
+                * A naive allocation strategy for this leads to 50% of the
+                * total inmem lac allocation being for line numbers...
+                *
+                * It's mainly solved by only holding the instance and line
+                * number tables for the duration of a file being input, as soon
+                * as one input file is finished it is written to disk.
+                *
+                * For the common case of 1 - ~3 matches the line number are
+                * stored in a small VLI array inside the filepath inst.  If the
+                * next one won't fit, it allocates a line number struct with
+                * more vli space and continues chaining those if needed.
+                */
+
+               n = wq32(vlibuf, t->line_number);
+               tif = t->parser->inst_file_list;
+
+               if (!tif->lines_list) {
+                       /* we are still trying to use the file inst vli */
+                       if (LWS_ARRAY_SIZE(tif->vli) - tif->count >= n) {
+                               tif->count += wq32(tif->vli + tif->count,
+                                                  t->line_number);
+                               goto after;
+                       }
+                       /* we are going to have to allocate */
+               }
+
+               /* can we add to an existing line numbers struct? */
+               if (tif->lines_tail &&
+                   LWS_ARRAY_SIZE(tif->lines_tail->vli) -
+                           tif->lines_tail->count >= n) {
+                       tif->lines_tail->count += wq32(tif->lines_tail->vli +
+                                                      tif->lines_tail->count,
+                                                      t->line_number);
+                       goto after;
+               }
+
+               /* either no existing line numbers struct at tail, or full */
+
+               /* have to create a(nother) line numbers struct */
+               tl = lwsac_use(&t->lwsac_input_head, sizeof(*tl),
+                            TRIE_LWSAC_BLOCK_SIZE);
+               if (!tl) {
+                       lwsl_err("%s: lac for tl failed\n", __func__);
+                       return 1;
+               }
+               tl->lines_next = NULL;
+               if (tif->lines_tail)
+                       tif->lines_tail->lines_next = tl;
+
+               tif->lines_tail = tl;
+               if (!tif->lines_list)
+                       tif->lines_list = tl;
+
+               tl->count = wq32(tl->vli, t->line_number);
+after:
+               tif->total++;
+#if 0
+               {
+                       char s[128];
+                       const char *ne = name_entry(t->parser, s, sizeof(s));
+
+                       if (!strcmp(ne, "describ")) {
+                               lwsl_err("     %s %d\n", ne, t->str_match_pos);
+                               write(1, buf - 10, 20);
+                       }
+               }
+#endif
+               t->parser->instance_count++;
+               t->parser = t->root;
+               t->str_match_pos = 0;
+       }
+
+       /* seal off the line length table block */
+
+       if (bp) {
+               if (write(t->fd, linetable, bp) != bp)
+                       return 1;
+               t->c += bp;
+               bp = 0;
+       }
+
+       if (lseek(t->fd, lbh, SEEK_SET) < 0) {
+               lwsl_err("%s: seek to 0x%llx failed\n", __func__,
+                               (unsigned long long)lbh);
+               return 1;
+       }
+
+       g16(linetable, t->c - lbh);
+       g16(linetable + 2, t->line_number - sline);
+       g32(linetable + 4, chars);
+       if (write(t->fd, linetable, 8) != 8) {
+               lwsl_err("%s: write linetable header failed\n", __func__);
+               return 1;
+       }
+
+       assert(lseek(t->fd, 0, SEEK_END) == (off_t)t->c);
+
+       if (lseek(t->fd, t->c, SEEK_SET) < 0) {
+               lwsl_err("%s: end seek failed\n", __func__);
+               return 1;
+       }
+
+       bp = 0;
+
+       if (len) {
+               t->lines_in_unsealed_linetable = 0;
+               goto resume;
+       }
+
+       /* dump the collected per-input instance and line data, and free it */
+
+       t->agg_trie_creation_us += lws_now_usecs() - tf;
+
+       return 0;
+}
+
+/* refer to ./README.md */
+
+int
+lws_fts_serialize(struct lws_fts *t)
+{
+       struct lws_fts_filepath *fp = t->filepath_list, *ofp;
+       unsigned long long tf = lws_now_usecs();
+       struct lws_fts_entry *e, *e1, *s[256];
+       unsigned char buf[8192], stasis;
+       int n, bp, sp = 0, do_parent;
+
+       (void)tf;
+       finalize_per_input(t);
+
+       /*
+        * Compute aggregated instance counts (parents should know the total
+        * number of instances below each child path)
+        *
+        *
+        * If we have
+        *
+        * (root) -> (c1) -> (c2)
+        *        -> (c3)
+        *
+        * we need to visit the nodes in the order
+        *
+        * c2, c1, c3, root
+        */
+
+       sp = 0;
+       s[0] = t->root;
+       do_parent = 0;
+       while (sp >= 0) {
+               int n;
+
+               /* aggregate in every antecedent */
+
+               for (n = 0; n <= sp; n++) {
+                       s[n]->agg_inst_count += s[sp]->instance_count;
+                       s[n]->agg_child_count += s[sp]->child_count;
+               }
+
+               /* handle any children before the parent */
+
+               if (s[sp]->child_list) {
+                       if (sp + 1 == LWS_ARRAY_SIZE(s)) {
+                               lwsl_err("Stack too deep\n");
+
+                               goto bail;
+                       }
+
+                       s[sp + 1] = s[sp]->child_list;
+                       sp++;
+                       continue;
+               }
+
+               do {
+                       if (s[sp]->sibling) {
+                               s[sp] = s[sp]->sibling;
+                               break;
+                       } else
+                               sp--;
+               } while (sp >= 0);
+       }
+
+       /* dump the filepaths and set prev */
+
+       fp = t->filepath_list;
+       ofp = NULL;
+       bp = 0;
+       while (fp) {
+
+               fp->ofs = t->c + bp;
+               n = (int)strlen(fp->filepath);
+               spill(15 + n, 0);
+
+               bp += wq32(&buf[bp], fp->line_table_ofs);
+               bp += wq32(&buf[bp], fp->total_lines);
+               bp += wq32(&buf[bp], n);
+               memcpy(&buf[bp], fp->filepath, n);
+               bp += n;
+
+               fp->prev = ofp;
+               ofp = fp;
+               fp = fp->next;
+       }
+
+       spill(0, 1);
+
+       /* record the fileoffset of the filepath map and filepath count */
+
+       if (lseek(t->fd, 0xc, SEEK_SET) < 0)
+               goto bail_seek;
+
+       g32(buf, t->c + bp);
+       g32(buf + 4, t->next_file_index);
+       if (write(t->fd, buf, 8) != 8)
+               goto bail;
+
+       if (lseek(t->fd, t->c + bp, SEEK_SET) < 0)
+               goto bail_seek;
+
+       /* dump the filepath map, starting from index 0, which is at the tail */
+
+       fp = ofp;
+       bp = 0;
+       while (fp) {
+               spill(5, 0);
+               g32(buf + bp, fp->ofs);
+               bp += 4;
+               fp = fp->prev;
+       }
+       spill(0, 1);
+
+       /*
+        * The trie entries in reverse order... because of the reversal, we have
+        * always written children first, and marked them with their file offset
+        * before we come to refer to them.
+        */
+
+       bp = 0;
+       sp = 0;
+       s[0] = t->root;
+       do_parent = 0;
+       while (s[sp]) {
+
+               /* handle any children before the parent */
+
+               if (!do_parent && s[sp]->child_list) {
+
+                       if (sp + 1 == LWS_ARRAY_SIZE(s)) {
+                               lwsl_err("Stack too deep\n");
+
+                               goto bail;
+                       }
+
+                       s[sp + 1] = s[sp]->child_list;
+                       sp++;
+                       continue;
+               }
+
+               /* leaf nodes with no children */
+
+               e = s[sp];
+               e->ofs = t->c + bp;
+
+               /* write the trie entry header */
+
+               spill((3 * MAX_VLI), 0);
+
+               bp += wq32(&buf[bp], e->ofs_last_inst_file);
+               bp += wq32(&buf[bp], e->child_count);
+               bp += wq32(&buf[bp], e->instance_count);
+               bp += wq32(&buf[bp], e->agg_inst_count);
+
+               /* sort the children in order of highest aggregate hits first */
+
+               do {
+                       struct lws_fts_entry **pe, *te1, *te2;
+
+                       stasis = 1;
+
+                       /* bubble sort keeps going until nothing changed */
+
+                       pe = &e->child_list;
+                       while (*pe) {
+
+                               te1 = *pe;
+                               te2 = te1->sibling;
+
+                               if (te2 && te1->agg_inst_count <
+                                          te2->agg_inst_count) {
+                                       stasis = 0;
+
+                                       *pe = te2;
+                                       te1->sibling = te2->sibling;
+                                       te2->sibling = te1;
+                               }
+
+                               pe = &(*pe)->sibling;
+                       }
+
+               } while (!stasis);
+
+               /* write the children */
+
+               e1 = e->child_list;
+               while (e1) {
+                       spill((5 * MAX_VLI) + e1->suffix_len + 1, 0);
+
+                       bp += wq32(&buf[bp], e1->ofs);
+                       bp += wq32(&buf[bp], e1->instance_count);
+                       bp += wq32(&buf[bp], e1->agg_inst_count);
+                       bp += wq32(&buf[bp], e1->agg_child_count);
+
+                       if (e1->suffix) { /* string  */
+                               bp += wq32(&buf[bp], e1->suffix_len);
+                               memmove(&buf[bp], e1->suffix, e1->suffix_len);
+                               bp += e1->suffix_len;
+                       } else { /* char */
+                               bp += wq32(&buf[bp], 1);
+                               buf[bp++] = e1->c;
+                       }
+#if 0
+                       if (e1->suffix && e1->suffix_len == 3 &&
+                           !memcmp(e1->suffix, "cri", 3)) {
+                               struct lws_fts_entry *e2;
+
+                               e2 = e1;
+                               while (e2){
+                                       if (e2->suffix)
+                                               lwsl_notice("%s\n", e2->suffix);
+                                       else
+                                               lwsl_notice("%c\n", e2->c);
+
+                                       e2 = e2->parent;
+                               }
+
+                               lwsl_err("*** %c CRI inst %d ch %d\n", e1->parent->c,
+                                               e1->instance_count, e1->child_count);
+                       }
+#endif
+                       e1 = e1->sibling;
+               }
+
+               /* if there are siblings, do those next */
+
+               if (do_parent) {
+                       do_parent = 0;
+                       sp--;
+               }
+
+               if (s[sp]->sibling)
+                       s[sp] = s[sp]->sibling;
+               else {
+                       /* if there are no siblings, do the parent */
+                       do_parent = 1;
+                       s[sp] = s[sp]->parent;
+               }
+       }
+
+       spill(0, 1);
+
+       assert(lseek(t->fd, 0, SEEK_END) == (off_t)t->c);
+
+       /* drop the correct root trie offset + file length into the header */
+
+       if (lseek(t->fd, 4, SEEK_SET) < 0) {
+               lwsl_err("%s: unable to seek\n", __func__);
+
+               goto bail;
+       }
+
+       g32(buf, t->root->ofs);
+       g32(buf + 4, t->c);
+       if (write(t->fd, buf, 0x8) != 0x8)
+               goto bail;
+
+       lwsl_notice("%s: index %d files (%uMiB) cpu time %dms, "
+                   "alloc: %dKiB + %dKiB, "
+                   "serialize: %dms, file: %dKiB\n", __func__,
+                   t->next_file_index,
+                   (int)(t->agg_raw_input / (1024 * 1024)),
+                   (int)(t->agg_trie_creation_us / 1000),
+                   (int)(lwsac_total_alloc(t->lwsac_head) / 1024),
+                   (int)(t->worst_lwsac_input_size / 1024),
+                   (int)((lws_now_usecs() - tf) / 1000),
+                   (int)(t->c / 1024));
+
+       return 0;
+
+bail_seek:
+       lwsl_err("%s: problem seekings\n", __func__);
+
+bail:
+       return 1;
+}
+
+
similarity index 95%
rename from lib/getifaddrs.c
rename to lib/misc/getifaddrs.c
index 0783019..735b899 100644 (file)
@@ -1,9 +1,4 @@
 /*
- * downloaded from
- * http://ftp.uninett.no/pub/OpenBSD/src/kerberosV/src/lib/roken/getifaddrs.c
- */
-#if !LWS_HAVE_GETIFADDRS
-/*
  * Copyright (c) 2000 - 2001 Kungliga Tekniska H�gskolan
  * (Royal Institute of Technology, Stockholm, Sweden).
  * All rights reserved.
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
+ *
+ * originally downloaded from
+ *
+ * http://ftp.uninett.no/pub/OpenBSD/src/kerberosV/src/lib/roken/getifaddrs.c
  */
 
 #include <errno.h>
@@ -44,7 +43,7 @@
 #include <string.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
 #ifdef LWS_HAVE_SYS_SOCKIO_H
 #include <sys/sockio.h>
@@ -84,7 +83,7 @@ getifaddrs2(struct ifaddrs **ifap, int af, int siocgifconf, int siocgifflags,
 
        buf_size = 8192;
        for (;;) {
-               buf = lws_zalloc(buf_size);
+               buf = lws_zalloc(buf_size, "getifaddrs2");
                if (buf == NULL) {
                        ret = ENOMEM;
                        goto error_out;
@@ -136,12 +135,12 @@ getifaddrs2(struct ifaddrs **ifap, int af, int siocgifconf, int siocgifflags,
                        goto error_out;
                }
 
-               *end = lws_malloc(sizeof(**end));
+               *end = lws_malloc(sizeof(**end), "getifaddrs");
 
                (*end)->ifa_next = NULL;
                (*end)->ifa_name = strdup(ifr->ifr_name);
                (*end)->ifa_flags = ifreq.ifr_flags;
-               (*end)->ifa_addr = lws_malloc(salen);
+               (*end)->ifa_addr = lws_malloc(salen, "getifaddrs");
                memcpy((*end)->ifa_addr, sa, salen);
                (*end)->ifa_netmask = NULL;
 
@@ -149,12 +148,12 @@ getifaddrs2(struct ifaddrs **ifap, int af, int siocgifconf, int siocgifflags,
                /* fix these when we actually need them */
                if (ifreq.ifr_flags & IFF_BROADCAST) {
                        (*end)->ifa_broadaddr =
-                               lws_malloc(sizeof(ifr->ifr_broadaddr));
+                               lws_malloc(sizeof(ifr->ifr_broadaddr), "getifaddrs");
                        memcpy((*end)->ifa_broadaddr, &ifr->ifr_broadaddr,
                                                    sizeof(ifr->ifr_broadaddr));
                } else if (ifreq.ifr_flags & IFF_POINTOPOINT) {
                        (*end)->ifa_dstaddr =
-                               lws_malloc(sizeof(ifr->ifr_dstaddr));
+                               lws_malloc(sizeof(ifr->ifr_dstaddr), "getifaddrs");
                        memcpy((*end)->ifa_dstaddr, &ifr->ifr_dstaddr,
                                                      sizeof(ifr->ifr_dstaddr));
                } else
@@ -269,4 +268,3 @@ main()
        return 0;
 }
 #endif
-#endif
similarity index 100%
rename from lib/getifaddrs.h
rename to lib/misc/getifaddrs.h
similarity index 66%
rename from lib/lejp.c
rename to lib/misc/lejp.c
index 50ff4b5..2190413 100644 (file)
@@ -1,16 +1,56 @@
 /*
  * Lightweight Embedded JSON Parser
  *
- * Copyright (C) 2013 Andy Green <andy@warmcat.com>
- * This code is licensed under LGPL 2.1
- * http://www.gnu.org/licenses/lgpl-2.1.html
+ * Copyright (C) 2013-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
  */
 
+#include <libwebsockets.h>
+#include "core/private.h"
 #include <string.h>
-#include "lejp.h"
-
 #include <stdio.h>
 
+static const char * const parser_errs[] = {
+       "",
+       "",
+       "No opening '{'",
+       "Expected closing '}'",
+       "Expected '\"'",
+       "String underrun",
+       "Illegal unescaped control char",
+       "Illegal escape format",
+       "Illegal hex number",
+       "Expected ':'",
+       "Illegal value start",
+       "Digit required after decimal point",
+       "Bad number format",
+       "Bad exponent format",
+       "Unknown token",
+       "Too many ']'",
+       "Mismatched ']'",
+       "Expected ']'",
+       "JSON nesting limit exceeded",
+       "Nesting tracking used up",
+       "Number too long",
+       "Comma or block end expected",
+       "Unknown",
+       "Parser callback errored (see earlier error)",
+};
+
 /**
  * lejp_construct - prepare a struct lejp_ctx for use
  *
  * \param callback:    your user callback which will received parsed tokens
  * \param user:        optional user data pointer untouched by lejp
  * \param paths:       your array of name elements you are interested in
- * \param count_paths: ARRAY_SIZE() of @paths
+ * \param count_paths: LWS_ARRAY_SIZE() of @paths
  *
  * Prepares your context struct for use with lejp
  */
 
 void
 lejp_construct(struct lejp_ctx *ctx,
-       char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
+       signed char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
                        const char * const *paths, unsigned char count_paths)
 {
        ctx->st[0].s = 0;
@@ -34,15 +74,20 @@ lejp_construct(struct lejp_ctx *ctx,
        ctx->st[0].b = 0;
        ctx->sp = 0;
        ctx->ipos = 0;
-       ctx->ppos = 0;
        ctx->path_match = 0;
+       ctx->path_stride = 0;
        ctx->path[0] = '\0';
-       ctx->callback = callback;
        ctx->user = user;
-       ctx->paths = paths;
-       ctx->count_paths = count_paths;
        ctx->line = 1;
-       ctx->callback(ctx, LEJPCB_CONSTRUCTED);
+
+       ctx->pst_sp = 0;
+       ctx->pst[0].callback = callback;
+       ctx->pst[0].paths = paths;
+       ctx->pst[0].count_paths = count_paths;
+       ctx->pst[0].user = NULL;
+       ctx->pst[0].ppos = 0;
+
+       ctx->pst[0].callback(ctx, LEJPCB_CONSTRUCTED);
 }
 
 /**
@@ -59,7 +104,7 @@ void
 lejp_destruct(struct lejp_ctx *ctx)
 {
        /* no allocations... just let callback know what it happening */
-       ctx->callback(ctx, LEJPCB_DESTRUCTED);
+       ctx->pst[0].callback(ctx, LEJPCB_DESTRUCTED);
 }
 
 /**
@@ -86,25 +131,31 @@ lejp_destruct(struct lejp_ctx *ctx)
 
 void
 lejp_change_callback(struct lejp_ctx *ctx,
-                      char (*callback)(struct lejp_ctx *ctx, char reason))
+                    signed char (*callback)(struct lejp_ctx *ctx, char reason))
 {
-       ctx->callback(ctx, LEJPCB_DESTRUCTED);
-       ctx->callback = callback;
-       ctx->callback(ctx, LEJPCB_CONSTRUCTED);
-       ctx->callback(ctx, LEJPCB_START);
+       ctx->pst[0].callback(ctx, LEJPCB_DESTRUCTED);
+       ctx->pst[0].callback = callback;
+       ctx->pst[0].callback(ctx, LEJPCB_CONSTRUCTED);
+       ctx->pst[0].callback(ctx, LEJPCB_START);
 }
 
-static void
+void
 lejp_check_path_match(struct lejp_ctx *ctx)
 {
        const char *p, *q;
-       int n;
+       int n, s = sizeof(char *);
+
+       if (ctx->path_stride)
+               s = ctx->path_stride;
 
        /* we only need to check if a match is not active */
-       for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) {
+       for (n = 0; !ctx->path_match &&
+            n < ctx->pst[ctx->pst_sp].count_paths; n++) {
                ctx->wildcount = 0;
                p = ctx->path;
-               q = ctx->paths[n];
+
+               q = *((char **)(((char *)ctx->pst[ctx->pst_sp].paths) + (n * s)));
+
                while (*p && *q) {
                        if (*q != '*') {
                                if (*p != *q)
@@ -113,7 +164,7 @@ lejp_check_path_match(struct lejp_ctx *ctx)
                                q++;
                                continue;
                        }
-                       ctx->wild[ctx->wildcount++] = p - ctx->path;
+                       ctx->wild[ctx->wildcount++] = lws_ptr_diff(p, ctx->path);
                        q++;
                        /*
                         * if * has something after it, match to .
@@ -130,7 +181,7 @@ lejp_check_path_match(struct lejp_ctx *ctx)
                        continue;
 
                ctx->path_match = n + 1;
-               ctx->path_match_len = ctx->ppos;
+               ctx->path_match_len = ctx->pst[ctx->pst_sp].ppos;
                return;
        }
 
@@ -148,7 +199,8 @@ lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len)
 
        n = ctx->wild[wildcard];
 
-       while (--len && n < ctx->ppos && (n == ctx->wild[wildcard] || ctx->path[n] != '.'))
+       while (--len && n < ctx->pst[ctx->pst_sp].ppos &&
+              (n == ctx->wild[wildcard] || ctx->path[n] != '.'))
                *dest++ = ctx->path[n++];
 
        *dest = '\0';
@@ -181,12 +233,11 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
        static const char esc_tran[] = "\"\\/\b\f\n\r\t";
        static const char tokens[] = "rue alse ull ";
 
-       if (!ctx->sp && !ctx->ppos)
-               ctx->callback(ctx, LEJPCB_START);
+       if (!ctx->sp && !ctx->pst[ctx->pst_sp].ppos)
+               ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_START);
 
        while (len--) {
                c = *json++;
-
                s = ctx->st[ctx->sp].s;
 
                /* skip whitespace unless we should care */
@@ -212,7 +263,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                ret = LEJP_REJECT_IDLE_NO_BRACE;
                                goto reject;
                        }
-                       if (ctx->callback(ctx, LEJPCB_OBJECT_START)) {
+                       if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_START)) {
                                ret = LEJP_REJECT_CALLBACK;
                                goto reject;
                        }
@@ -238,13 +289,13 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
 
                case LEJP_MP_STRING:
                        if (c == '\"') {
-                               if (!ctx->sp) {
+                               if (!ctx->sp) { /* JSON can't end on quote */
                                        ret = LEJP_REJECT_MP_STRING_UNDERRUN;
                                        goto reject;
                                }
                                if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
                                        ctx->buf[ctx->npos] = '\0';
-                                       if (ctx->callback(ctx,
+                                       if (ctx->pst[ctx->pst_sp].callback(ctx,
                                                      LEJPCB_VAL_STR_END) < 0) {
                                                ret = LEJP_REJECT_CALLBACK;
                                                goto reject;
@@ -351,10 +402,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                goto reject;
                        }
                        ctx->st[ctx->sp].s = LEJP_MP_VALUE;
-                       ctx->path[ctx->ppos] = '\0';
+                       ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
 
                        lejp_check_path_match(ctx);
-                       if (ctx->callback(ctx, LEJPCB_PAIR_NAME)) {
+                       if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_PAIR_NAME)) {
                                ret = LEJP_REJECT_CALLBACK;
                                goto reject;
                        }
@@ -375,7 +426,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                c = LEJP_MP_STRING;
                                ctx->npos = 0;
                                ctx->buf[0] = '\0';
-                               if (ctx->callback(ctx, LEJPCB_VAL_STR_START)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_STR_START)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -386,7 +437,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
                                c = LEJP_MEMBERS;
                                lejp_check_path_match(ctx);
-                               if (ctx->callback(ctx, LEJPCB_OBJECT_START)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_START)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -397,20 +448,46 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                /* push */
                                ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END;
                                c = LEJP_MP_VALUE;
-                               ctx->path[ctx->ppos++] = '[';
-                               ctx->path[ctx->ppos++] = ']';
-                               ctx->path[ctx->ppos] = '\0';
-                               if (ctx->callback(ctx, LEJPCB_ARRAY_START)) {
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '[';
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos++] = ']';
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_START)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
                                ctx->i[ctx->ipos++] = 0;
-                               if (ctx->ipos > ARRAY_SIZE(ctx->i)) {
+                               if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) {
                                        ret = LEJP_REJECT_MP_DELIM_ISTACK;
                                        goto reject;
                                }
                                goto add_stack_level;
 
+                       case ']':
+                               /* pop */
+                               if (!ctx->sp) { /* JSON can't end on ] */
+                                       ret = LEJP_REJECT_MP_C_OR_E_UNDERF;
+                                       goto reject;
+                               }
+                               ctx->sp--;
+                               if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) {
+                                       ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY;
+                                       goto reject;
+                               }
+                               /* drop the path [n] bit */
+                               if (ctx->sp) {
+                                       ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p;
+                                       ctx->ipos = ctx->st[ctx->sp - 1].i;
+                               }
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
+                               if (ctx->path_match &&
+                                   ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
+                                       /*
+                                        * we shrank the path to be
+                                        * smaller than the matching point
+                                        */
+                                       ctx->path_match = 0;
+                               goto array_end;
+
                        case 't': /* true */
                                ctx->uni = 0;
                                ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
@@ -437,14 +514,14 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                goto append_npos;
                        }
 
-                       if (ctx->dcount < 10 && c >= '0' && c <= '9') {
+                       if (ctx->dcount < 20 && c >= '0' && c <= '9') {
                                if (ctx->f & LEJP_SEEN_POINT)
                                        ctx->f |= LEJP_SEEN_POST_POINT;
                                ctx->dcount++;
                                goto append_npos;
                        }
                        if (c == '.') {
-                               if (ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) {
+                               if (!ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) {
                                        ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
                                        goto reject;
                                }
@@ -478,12 +555,12 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
 
                        ctx->buf[ctx->npos] = '\0';
                        if (ctx->f & LEJP_SEEN_POINT) {
-                               if (ctx->callback(ctx, LEJPCB_VAL_NUM_FLOAT)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_NUM_FLOAT)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
                        } else {
-                               if (ctx->callback(ctx, LEJPCB_VAL_NUM_INT)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_NUM_INT)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -514,7 +591,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                        case 3:
                                ctx->buf[0] = '1';
                                ctx->buf[1] = '\0';
-                               if (ctx->callback(ctx, LEJPCB_VAL_TRUE)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_TRUE)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -522,14 +599,14 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                        case 8:
                                ctx->buf[0] = '0';
                                ctx->buf[1] = '\0';
-                               if (ctx->callback(ctx, LEJPCB_VAL_FALSE)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_FALSE)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
                                break;
                        case 12:
                                ctx->buf[0] = '\0';
-                               if (ctx->callback(ctx, LEJPCB_VAL_NULL)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_NULL)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -539,12 +616,12 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                        break;
 
                case LEJP_MP_COMMA_OR_END:
-                       ctx->path[ctx->ppos] = '\0';
+                       ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                        if (c == ',') {
                                /* increment this stack level's index */
                                ctx->st[ctx->sp].s = LEJP_M_P;
                                if (!ctx->sp) {
-                                       ctx->ppos = 0;
+                                       ctx->pst[ctx->pst_sp].ppos = 0;
                                        /*
                                         * since we came back to root level,
                                         * no path can still match
@@ -552,10 +629,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                        ctx->path_match = 0;
                                        break;
                                }
-                               ctx->ppos = ctx->st[ctx->sp - 1].p;
-                               ctx->path[ctx->ppos] = '\0';
+                               ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p;
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                                if (ctx->path_match &&
-                                              ctx->ppos <= ctx->path_match_len)
+                                               ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
                                        /*
                                         * we shrank the path to be
                                         * smaller than the matching point
@@ -571,7 +648,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                break;
                        }
                        if (c == ']') {
-                               if (!ctx->sp) {
+                               if (!ctx->sp) {  /* JSON can't end on ] */
                                        ret = LEJP_REJECT_MP_C_OR_E_UNDERF;
                                        goto reject;
                                }
@@ -582,11 +659,13 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                        goto reject;
                                }
                                /* drop the path [n] bit */
-                               ctx->ppos = ctx->st[ctx->sp - 1].p;
-                               ctx->ipos = ctx->st[ctx->sp - 1].i;
-                               ctx->path[ctx->ppos] = '\0';
+                               if (ctx->sp) {
+                                       ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p;
+                                       ctx->ipos = ctx->st[ctx->sp - 1].i;
+                               }
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                                if (ctx->path_match &&
-                                              ctx->ppos <= ctx->path_match_len)
+                                               ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len)
                                        /*
                                         * we shrank the path to be
                                         * smaller than the matching point
@@ -597,30 +676,40 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                                goto redo_character;
                        }
                        if (c == '}') {
-                               if (ctx->sp == 0) {
+                               if (!ctx->sp) {
                                        lejp_check_path_match(ctx);
-                                       if (ctx->callback(ctx, LEJPCB_OBJECT_END)) {
+                                       if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_END)) {
                                                ret = LEJP_REJECT_CALLBACK;
                                                goto reject;
                                        }
-                                       ctx->callback(ctx, LEJPCB_COMPLETE);
-                                       /* done, return unused amount */
-                                       return len;
+                                       if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_COMPLETE))
+                                               goto reject;
+                                       else
+                                               /* done, return unused amount */
+                                               return len;
                                }
+
                                /* pop */
+
                                ctx->sp--;
-                               ctx->ppos = ctx->st[ctx->sp - 1].p;
-                               ctx->ipos = ctx->st[ctx->sp - 1].i;
-                               ctx->path[ctx->ppos] = '\0';
+                               if (ctx->sp) {
+                                       ctx->pst[ctx->pst_sp].ppos =
+                                                       ctx->st[ctx->sp].p;
+                                       ctx->ipos = ctx->st[ctx->sp].i;
+                               }
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                                if (ctx->path_match &&
-                                              ctx->ppos <= ctx->path_match_len)
+                                   ctx->pst[ctx->pst_sp].ppos <=
+                                                   ctx->path_match_len)
                                        /*
                                         * we shrank the path to be
                                         * smaller than the matching point
                                         */
                                        ctx->path_match = 0;
+
                                lejp_check_path_match(ctx);
-                               if (ctx->callback(ctx, LEJPCB_OBJECT_END)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx,
+                                                       LEJPCB_OBJECT_END)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -631,15 +720,16 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                        goto reject;
 
                case LEJP_MP_ARRAY_END:
-                       ctx->path[ctx->ppos] = '\0';
+array_end:
+                       ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                        if (c == ',') {
                                /* increment this stack level's index */
                                if (ctx->ipos)
                                        ctx->i[ctx->ipos - 1]++;
                                ctx->st[ctx->sp].s = LEJP_MP_VALUE;
                                if (ctx->sp)
-                                       ctx->ppos = ctx->st[ctx->sp - 1].p;
-                               ctx->path[ctx->ppos] = '\0';
+                                       ctx->pst[ctx->pst_sp].ppos = ctx->st[ctx->sp - 1].p;
+                               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                                break;
                        }
                        if (c != ']') {
@@ -648,7 +738,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
                        }
 
                        ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
-                       ctx->callback(ctx, LEJPCB_ARRAY_END);
+                       ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_END);
                        break;
                }
 
@@ -659,7 +749,7 @@ emit_string_char:
                        /* assemble the string value into chunks */
                        ctx->buf[ctx->npos++] = c;
                        if (ctx->npos == sizeof(ctx->buf) - 1) {
-                               if (ctx->callback(ctx, LEJPCB_VAL_STR_CHUNK)) {
+                               if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_VAL_STR_CHUNK)) {
                                        ret = LEJP_REJECT_CALLBACK;
                                        goto reject;
                                }
@@ -668,22 +758,23 @@ emit_string_char:
                        continue;
                }
                /* name part of name:value pair */
-               ctx->path[ctx->ppos++] = c;
+               ctx->path[ctx->pst[ctx->pst_sp].ppos++] = c;
                continue;
 
 add_stack_level:
                /* push on to the object stack */
-               if (ctx->ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END &&
-                               ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END)
-                       ctx->path[ctx->ppos++] = '.';
+               if (ctx->pst[ctx->pst_sp].ppos &&
+                   ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END &&
+                   ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END)
+                       ctx->path[ctx->pst[ctx->pst_sp].ppos++] = '.';
 
-               ctx->st[ctx->sp].p = ctx->ppos;
+               ctx->st[ctx->sp].p = ctx->pst[ctx->pst_sp].ppos;
                ctx->st[ctx->sp].i = ctx->ipos;
-               if (++ctx->sp == ARRAY_SIZE(ctx->st)) {
+               if (++ctx->sp == LWS_ARRAY_SIZE(ctx->st)) {
                        ret = LEJP_REJECT_STACK_OVERFLOW;
                        goto reject;
                }
-               ctx->path[ctx->ppos] = '\0';
+               ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0';
                ctx->st[ctx->sp].s = c;
                ctx->st[ctx->sp].b = 0;
                continue;
@@ -704,6 +795,65 @@ redo_character:
        return LEJP_CONTINUE;
 
 reject:
-       ctx->callback(ctx, LEJPCB_FAILED);
+       ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_FAILED);
        return ret;
 }
+
+int
+lejp_parser_push(struct lejp_ctx *ctx, void *user, const char * const *paths,
+                unsigned char paths_count, lejp_callback lejp_cb)
+{
+       struct _lejp_parsing_stack *p;
+
+       if (ctx->pst_sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH)
+               return -1;
+
+       lejp_check_path_match(ctx);
+
+       ctx->pst[ctx->pst_sp].path_match = ctx->path_match;
+       ctx->pst_sp++;
+
+       p = &ctx->pst[ctx->pst_sp];
+       p->user = user;
+       p->callback = lejp_cb;
+       p->paths = paths;
+       p->count_paths = paths_count;
+       p->ppos = 0;
+
+       ctx->path_match = 0;
+       lejp_check_path_match(ctx);
+
+       lwsl_debug("%s: pushed parser stack to %d (path %s)\n", __func__,
+                  ctx->pst_sp, ctx->path);
+
+       return 0;
+}
+
+int
+lejp_parser_pop(struct lejp_ctx *ctx)
+{
+       if (!ctx->pst_sp)
+               return -1;
+
+       ctx->pst_sp--;
+       lwsl_debug("%s: popped parser stack to %d\n", __func__, ctx->pst_sp);
+
+       ctx->path_match = 0; /* force it to check */
+       lejp_check_path_match(ctx);
+
+       return 0;
+}
+
+const char *
+lejp_error_to_string(int e)
+{
+       if (e > 0)
+               e = 0;
+       else
+               e = -e;
+
+       if (e >= (int)LWS_ARRAY_SIZE(parser_errs))
+               return "Unknown error";
+
+       return parser_errs[e];
+}
diff --git a/lib/misc/lws-ring.c b/lib/misc/lws-ring.c
new file mode 100644 (file)
index 0000000..bbd4df9
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * libwebsockets - lws-ring multi-tail abstract ringbuffer api
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+LWS_VISIBLE LWS_EXTERN struct lws_ring *
+lws_ring_create(size_t element_len, size_t count,
+               void (*destroy_element)(void *))
+{
+       struct lws_ring *ring = lws_malloc(sizeof(*ring), "ring create");
+
+       if (!ring)
+               return NULL;
+
+       ring->buflen = (uint32_t)(count * element_len);
+       ring->element_len = (uint32_t)element_len;
+       ring->head = 0;
+       ring->oldest_tail = 0;
+       ring->destroy_element = destroy_element;
+
+       ring->buf = lws_malloc(ring->buflen, "ring buf");
+       if (!ring->buf) {
+               lws_free(ring);
+
+               return NULL;
+       }
+
+       return ring;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_destroy(struct lws_ring *ring)
+{
+       if (ring->destroy_element)
+               while (ring->oldest_tail != ring->head) {
+                       ring->destroy_element((uint8_t *)ring->buf +
+                                             ring->oldest_tail);
+                       ring->oldest_tail =
+                               (ring->oldest_tail + ring->element_len) %
+                               ring->buflen;
+               }
+       if (ring->buf)
+               lws_free_set_NULL(ring->buf);
+
+       lws_free(ring);
+}
+
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_get_count_free_elements(struct lws_ring *ring)
+{
+       int f;
+
+       /*
+        * possible ringbuf patterns
+        *
+        * h == t
+        * |--------t***h---|
+        * |**h-----------t*|
+        * |t**************h|
+        * |*****ht*********|
+        */
+       if (ring->head == ring->oldest_tail)
+               f = ring->buflen - ring->element_len;
+       else
+               if (ring->head < ring->oldest_tail)
+                       f = (ring->oldest_tail - ring->head) -
+                           ring->element_len;
+               else
+                       f = (ring->buflen - ring->head) + ring->oldest_tail -
+                           ring->element_len;
+
+       if (f < 2)
+               return 0;
+
+       return f / ring->element_len;
+}
+
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_get_count_waiting_elements(struct lws_ring *ring, uint32_t *tail)
+{      int f;
+
+       if (!tail)
+               tail = &ring->oldest_tail;
+       /*
+        * possible ringbuf patterns
+        *
+        * h == t
+        * |--------t***h---|
+        * |**h-----------t*|
+        * |t**************h|
+        * |*****ht*********|
+        */
+       if (ring->head == *tail)
+               f = 0;
+       else
+               if (ring->head > *tail)
+                       f = (ring->head - *tail);
+               else
+                       f = (ring->buflen - *tail) + ring->head;
+
+       return f / ring->element_len;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start,
+                                 size_t *bytes)
+{
+       int n;
+
+       /* n is how many bytes the whole fifo can take */
+       n = (int)(lws_ring_get_count_free_elements(ring) * ring->element_len);
+
+       if (!n)
+               return 1;
+
+       if (ring->head + n > ring->buflen) {
+               *start = (void *)(((uint8_t *)ring->buf) + ring->head);
+               *bytes = ring->buflen - ring->head;
+
+               return 0;
+       }
+
+       *start = (void *)(((uint8_t *)ring->buf) + ring->head);
+       *bytes = n;
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_bump_head(struct lws_ring *ring, size_t bytes)
+{
+       ring->head = (ring->head + (uint32_t)bytes) % ring->buflen;
+}
+
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_insert(struct lws_ring *ring, const void *src, size_t max_count)
+{
+       const uint8_t *osrc = src;
+       int m, n;
+
+       /* n is how many bytes the whole fifo can take */
+       n = (int)(lws_ring_get_count_free_elements(ring) * ring->element_len);
+
+       /* restrict n to how much we want to insert */
+       if ((uint32_t)n > max_count * ring->element_len)
+               n = (int)(max_count * ring->element_len);
+
+       /*
+        * n is legal to insert, but as an optimization we can cut the
+        * insert into one or two memcpys, depending on if it wraps
+        */
+       if (ring->head + n > ring->buflen) {
+
+               /*
+                * He does wrap.  The first memcpy should take us up to
+                * the end of the buffer
+                */
+
+               m = ring->buflen - ring->head;
+               memcpy(((uint8_t *)ring->buf) + ring->head, src, m);
+               /* we know it will wrap exactly back to zero */
+               ring->head = 0;
+
+               /* adapt the second memcpy for what we already did */
+
+               src = ((uint8_t *)src) + m;
+               n -= m;
+       }
+
+       memcpy(((uint8_t *)ring->buf) + ring->head, src, n);
+       ring->head = (ring->head + n) % ring->buflen;
+
+       return (((uint8_t *)src + n) - osrc) / ring->element_len;
+}
+
+LWS_VISIBLE LWS_EXTERN size_t
+lws_ring_consume(struct lws_ring *ring, uint32_t *tail, void *dest,
+                size_t max_count)
+{
+       uint8_t *odest = dest;
+       void *orig_tail = tail;
+       uint32_t fake_tail;
+       int m, n;
+
+       if (!tail) {
+               fake_tail = ring->oldest_tail;
+               tail = &fake_tail;
+       }
+
+       /* n is how many bytes the whole fifo has for us */
+       n = (int)(lws_ring_get_count_waiting_elements(ring, tail) *
+                                                       ring->element_len);
+
+       /* restrict n to how much we want to insert */
+       if ((size_t)n > max_count * ring->element_len)
+               n = (int)(max_count * ring->element_len);
+
+       if (!dest) {
+               *tail = ((*tail) + n) % ring->buflen;
+               if (!orig_tail) /* single tail */
+                       lws_ring_update_oldest_tail(ring, *tail);
+
+               return n / ring->element_len;
+       }
+       if (*tail + n > ring->buflen) {
+
+               /*
+                * He does wrap.  The first memcpy should take us up to
+                * the end of the buffer
+                */
+
+               m = ring->buflen - *tail;
+               memcpy(dest, ((uint8_t *)ring->buf) + *tail, m);
+               /* we know it will wrap exactly back to zero */
+               *tail = 0;
+
+               /* adapt the second memcpy for what we already did */
+
+               dest = ((uint8_t *)dest) + m;
+               n -= m;
+       }
+
+       memcpy(dest, ((uint8_t *)ring->buf) + *tail, n);
+
+       *tail = ((*tail) + n) % ring->buflen;
+       if (!orig_tail) /* single tail */
+               lws_ring_update_oldest_tail(ring, *tail);
+
+       return (((uint8_t *)dest + n) - odest) / ring->element_len;
+}
+
+LWS_VISIBLE LWS_EXTERN const void *
+lws_ring_get_element(struct lws_ring *ring, uint32_t *tail)
+{
+       if (!tail)
+               tail = &ring->oldest_tail;
+
+       if (*tail == ring->head)
+               return NULL;
+
+       return ((uint8_t *)ring->buf) + *tail;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_update_oldest_tail(struct lws_ring *ring, uint32_t tail)
+{
+       if (!ring->destroy_element) {
+               ring->oldest_tail = tail;
+               return;
+       }
+
+       while (ring->oldest_tail != tail) {
+               ring->destroy_element((uint8_t *)ring->buf + ring->oldest_tail);
+               ring->oldest_tail = (ring->oldest_tail + ring->element_len) %
+                                   ring->buflen;
+       }
+}
+
+LWS_VISIBLE LWS_EXTERN uint32_t
+lws_ring_get_oldest_tail(struct lws_ring *ring)
+{
+       return ring->oldest_tail;
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_ring_dump(struct lws_ring *ring, uint32_t *tail)
+{
+       if (tail == NULL)
+               tail = &ring->oldest_tail;
+       lwsl_notice("ring %p: buflen %u, elem_len %u, head %u, oldest_tail %u\n"
+                   "     free_elems: %u; for tail %u, waiting elements: %u\n",
+                   ring, ring->buflen, ring->element_len, ring->head,
+                   ring->oldest_tail,
+                   (int)lws_ring_get_count_free_elements(ring), *tail,
+                   (int)lws_ring_get_count_waiting_elements(ring, tail));
+}
diff --git a/lib/misc/lws-struct-lejp.c b/lib/misc/lws-struct-lejp.c
new file mode 100644 (file)
index 0000000..af637cc
--- /dev/null
@@ -0,0 +1,762 @@
+/*
+ * libwebsockets - lws_struct JSON serialization helpers
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <libwebsockets.h>
+#include <core/private.h>
+
+#include <assert.h>
+
+signed char
+lws_struct_schema_only_lejp_cb(struct lejp_ctx *ctx, char reason)
+{
+       lws_struct_args_t *a = (lws_struct_args_t *)ctx->user;
+       const lws_struct_map_t *map = a->map_st[ctx->pst_sp];
+       int n = a->map_entries_st[ctx->pst_sp];
+       lejp_callback cb = map->lejp_cb;
+
+       if (reason != LEJPCB_VAL_STR_END || ctx->path_match != 1)
+               return 0;
+
+       while (n--) {
+               if (strcmp(ctx->buf, map->colname)) {
+                       map++;
+                       continue;
+               }
+
+               a->dest = lwsac_use_zero(&a->ac, map->aux, a->ac_block_size);
+               if (!a->dest) {
+                       lwsl_err("%s: OOT\n", __func__);
+
+                       return 1;
+               }
+               a->dest_len = map->aux;
+
+               if (!cb)
+                       cb = lws_struct_default_lejp_cb;
+
+               lejp_parser_push(ctx, a->dest, &map->child_map[0].colname,
+                                (uint8_t)map->child_map_size, cb);
+               a->map_st[ctx->pst_sp] = map->child_map;
+               a->map_entries_st[ctx->pst_sp] = map->child_map_size;
+
+               return 0;
+       }
+
+       lwsl_notice("%s: unknown schema %s\n", __func__, ctx->buf);
+
+       return 1;
+}
+
+static int
+lws_struct_lejp_push(struct lejp_ctx *ctx, lws_struct_args_t *args,
+                    const lws_struct_map_t *map, uint8_t *ch)
+{
+       lejp_callback cb = map->lejp_cb;
+
+       if (!cb)
+               cb = lws_struct_default_lejp_cb;
+
+       lejp_parser_push(ctx, ch, (const char * const*)map->child_map,
+                        (uint8_t)map->child_map_size, cb);
+
+       args->map_st[ctx->pst_sp] = map->child_map;
+       args->map_entries_st[ctx->pst_sp] = map->child_map_size;
+
+       return 0;
+}
+
+signed char
+lws_struct_default_lejp_cb(struct lejp_ctx *ctx, char reason)
+{
+       lws_struct_args_t *args = (lws_struct_args_t *)ctx->user;
+       const lws_struct_map_t *map, *pmap = NULL;
+       uint8_t *ch;
+       char *u;
+       int n;
+
+       if (reason == LEJPCB_ARRAY_END) {
+               lejp_parser_pop(ctx);
+
+               return 0;
+       }
+
+       if (reason == LEJPCB_ARRAY_START) {
+               map = &args->map_st[ctx->pst_sp][ctx->path_match - 1];
+               n = args->map_entries_st[ctx->pst_sp];
+
+               if (map->type == LSMT_LIST)
+                       lws_struct_lejp_push(ctx, args, map, NULL);
+
+               return 0;
+       }
+
+       if (ctx->pst_sp)
+               pmap = &args->map_st[ctx->pst_sp - 1]
+                        [ctx->pst[ctx->pst_sp - 1].path_match - 1];
+       map = &args->map_st[ctx->pst_sp][ctx->path_match - 1];
+       n = args->map_entries_st[ctx->pst_sp];
+
+       if (reason == LEJPCB_OBJECT_START) {
+
+               if (map->type != LSMT_CHILD_PTR) {
+                       ctx->pst[ctx->pst_sp].user = NULL;
+
+                       return 0;
+               }
+               pmap = map;
+
+               lws_struct_lejp_push(ctx, args, map, NULL);
+               map = &args->map_st[ctx->pst_sp][ctx->path_match - 1];
+               n = args->map_entries_st[ctx->pst_sp];
+       }
+
+       if (reason == LEJPCB_OBJECT_END && pmap && pmap->type == LSMT_CHILD_PTR)
+               lejp_parser_pop(ctx);
+
+       if (map->type == LSMT_SCHEMA) {
+
+               while (n--) {
+                       if (strcmp(map->colname, ctx->buf)) {
+                               map++;
+                               continue;
+                       }
+
+                       /* instantiate the correct toplevel object */
+
+                       ch = lwsac_use_zero(&args->ac, map->aux,
+                                           args->ac_block_size);
+                       if (!ch) {
+                               lwsl_err("OOM\n");
+
+                               return 1;
+                       }
+
+                       lws_struct_lejp_push(ctx, args, map, ch);
+
+                       return 0;
+               }
+               lwsl_notice("%s: unknown schema\n", __func__);
+
+               goto cleanup;
+       }
+
+       if (!ctx->pst[ctx->pst_sp].user) {
+               struct lws_dll2_owner *owner;
+               struct lws_dll2 *list;
+
+               /* create list item object if none already */
+
+               if (!ctx->path_match || !pmap)
+                       return 0;
+
+               map = &args->map_st[ctx->pst_sp - 1][ctx->path_match - 1];
+               n = args->map_entries_st[ctx->pst_sp - 1];
+
+               if (pmap->type != LSMT_LIST && pmap->type != LSMT_CHILD_PTR)
+                       return 1;
+
+               /* we need to create a child or array item object */
+
+               owner = (struct lws_dll2_owner *)
+                       (((char *)ctx->pst[ctx->pst_sp - 1].user) + pmap->ofs);
+
+               assert(pmap->aux);
+
+               /* instantiate one of the child objects */
+
+               ctx->pst[ctx->pst_sp].user = lwsac_use_zero(&args->ac,
+                                               pmap->aux, args->ac_block_size);
+               if (!ctx->pst[ctx->pst_sp].user) {
+                       lwsl_err("OOM\n");
+
+                       return 1;
+               }
+               lwsl_notice("%s: created child object size %d\n", __func__,
+                               (int)pmap->aux);
+
+               if (pmap->type == LSMT_LIST) {
+                       list = (struct lws_dll2 *)((char *)ctx->pst[ctx->pst_sp].user +
+                               map->ofs_clist);
+
+                       lws_dll2_add_tail(list, owner);
+               }
+       }
+
+       if (!ctx->path_match)
+               return 0;
+
+       if (reason == LEJPCB_VAL_STR_CHUNK) {
+               lejp_collation_t *coll;
+
+               /* don't cache stuff we are going to ignore */
+
+               if (map->type == LSMT_STRING_CHAR_ARRAY &&
+                   args->chunks_length >= map->aux)
+                       return 0;
+
+               coll = lwsac_use_zero(&args->ac_chunks, sizeof(*coll),
+                                     sizeof(*coll));
+               if (!coll) {
+                       lwsl_err("%s: OOT\n", __func__);
+
+                       return 1;
+               }
+               coll->chunks.prev = NULL;
+               coll->chunks.next = NULL;
+               coll->chunks.owner = NULL;
+
+               coll->len = ctx->npos;
+               lws_dll2_add_tail(&coll->chunks, &args->chunks_owner);
+
+               memcpy(coll->buf, ctx->buf, ctx->npos);
+
+               args->chunks_length += ctx->npos;
+
+               return 0;
+       }
+
+       if (reason != LEJPCB_VAL_STR_END && reason != LEJPCB_VAL_NUM_INT &&
+           reason != LEJPCB_VAL_TRUE && reason != LEJPCB_VAL_FALSE)
+               return 0;
+
+       /* this is the end of the string */
+
+       if (ctx->pst[ctx->pst_sp].user && pmap && pmap->type == LSMT_CHILD_PTR) {
+               void **pp = (void **)
+                       (((char *)ctx->pst[ctx->pst_sp - 1].user) + pmap->ofs);
+
+               *pp = ctx->pst[ctx->pst_sp].user;
+       }
+
+       u = (char *)ctx->pst[ctx->pst_sp].user;
+       if (!u)
+               u = (char *)ctx->pst[ctx->pst_sp - 1].user;
+
+       {
+               char **pp, *s;
+               size_t lim, b;
+               long long li;
+
+               switch (map->type) {
+               case LSMT_SIGNED:
+                       if (map->aux == sizeof(signed char)) {
+                               signed char *pc;
+                               pc = (signed char *)(u + map->ofs);
+                               *pc = atoi(ctx->buf);
+                               break;
+                       }
+                       if (map->aux == sizeof(int)) {
+                               int *pi;
+                               pi = (int *)(u + map->ofs);
+                               *pi = atoi(ctx->buf);
+                               break;
+                       }
+                       if (map->aux == sizeof(long)) {
+                               long *pl;
+                               pl = (long *)(u + map->ofs);
+                               *pl = atol(ctx->buf);
+                       } else {
+                               long long *pll;
+                               pll = (long long *)(u + map->ofs);
+                               *pll = atoll(ctx->buf);
+                       }
+                       break;
+
+               case LSMT_UNSIGNED:
+                       if (map->aux == sizeof(unsigned char)) {
+                               unsigned char *pc;
+                               pc = (unsigned char *)(u + map->ofs);
+                               *pc = atoi(ctx->buf);
+                               break;
+                       }
+                       if (map->aux == sizeof(unsigned int)) {
+                               unsigned int *pi;
+                               pi = (unsigned int *)(u + map->ofs);
+                               *pi = atoi(ctx->buf);
+                               break;
+                       }
+                       if (map->aux == sizeof(unsigned long)) {
+                               unsigned long *pl;
+                               pl = (unsigned long *)(u + map->ofs);
+                               *pl = atol(ctx->buf);
+                       } else {
+                               unsigned long long *pll;
+                               pll = (unsigned long long *)(u + map->ofs);
+                               *pll = atoll(ctx->buf);
+                       }
+                       break;
+
+               case LSMT_BOOLEAN:
+                       li = reason == LEJPCB_VAL_TRUE;
+                       if (map->aux == sizeof(char)) {
+                               char *pc;
+                               pc = (char *)(u + map->ofs);
+                               *pc = (char)li;
+                               break;
+                       }
+                       if (map->aux == sizeof(int)) {
+                               int *pi;
+                               pi = (int *)(u + map->ofs);
+                               *pi = (int)li;
+                       } else {
+                               uint64_t *p64;
+                               p64 = (uint64_t *)(u + map->ofs);
+                               *p64 = li;
+                       }
+                       break;
+
+               case LSMT_STRING_CHAR_ARRAY:
+                       s = (char *)(u + map->ofs);
+                       lim = map->aux - 1;
+                       goto chunk_copy;
+
+               case LSMT_STRING_PTR:
+                       pp = (char **)(u + map->ofs);
+                       lim = args->chunks_length + ctx->npos;
+                       s = lwsac_use(&args->ac, lim + 1, args->ac_block_size);
+                       if (!s)
+                               goto cleanup;
+                       *pp = s;
+
+chunk_copy:
+                       s[lim] = '\0';
+                       /* copy up to lim from the string chunk ac first */
+                       lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
+                                               args->chunks_owner.head) {
+                               lejp_collation_t *coll = (lejp_collation_t *)p;
+
+                               if (lim) {
+                                       b = coll->len;
+                                       if (b > lim)
+                                               b = lim;
+                                       memcpy(s, coll->buf, b);
+                                       s += b;
+                                       lim -= b;
+                               }
+                       } lws_end_foreach_dll_safe(p, p1);
+
+                       lwsac_free(&args->ac_chunks);
+                       args->chunks_owner.count = 0;
+                       args->chunks_owner.head = NULL;
+                       args->chunks_owner.tail = NULL;
+
+                       if (lim) {
+                               b = ctx->npos;
+                               if (b > lim)
+                                       b = lim;
+                               memcpy(s, ctx->buf, b);
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (args->cb)
+               args->cb(args->dest, args->cb_arg);
+
+       return 0;
+
+cleanup:
+       lwsl_notice("%s: cleanup\n", __func__);
+       lwsac_free(&args->ac_chunks);
+       args->chunks_owner.count = 0;
+       args->chunks_owner.head = NULL;
+       args->chunks_owner.tail = NULL;
+
+       return 1;
+}
+
+static const char * schema[] = { "schema" };
+
+int
+lws_struct_json_init_parse(struct lejp_ctx *ctx, lejp_callback cb, void *user)
+{
+       if (!cb)
+               cb = lws_struct_schema_only_lejp_cb;
+       lejp_construct(ctx, cb, user, schema, 1);
+
+       ctx->path_stride = sizeof(lws_struct_map_t);
+
+       return 0;
+}
+
+lws_struct_serialize_t *
+lws_struct_json_serialize_create(const lws_struct_map_t *map,
+                                size_t map_entries, int flags,
+                                void *ptoplevel)
+{
+       lws_struct_serialize_t *js = lws_zalloc(sizeof(*js), __func__);
+       lws_struct_serialize_st_t *j;
+
+       if (!js)
+               return NULL;
+
+       js->flags = flags;
+
+       j = &js->st[0];
+       j->map = map;
+       j->map_entries = map_entries;
+       j->obj = ptoplevel;
+       j->idt = 0;
+
+       return js;
+}
+
+void
+lws_struct_json_serialize_destroy(lws_struct_serialize_t **pjs)
+{
+       if (!*pjs)
+               return;
+
+       lws_free(*pjs);
+
+       *pjs = NULL;
+}
+
+static void
+lws_struct_pretty(lws_struct_serialize_t *js, uint8_t **pbuf, size_t *plen)
+{
+       if (js->flags & LSSERJ_FLAG_PRETTY) {
+               int n;
+
+               *(*pbuf)++ = '\n';
+               (*plen)--;
+               for (n = 0; n < js->st[js->sp].idt; n++) {
+                       *(*pbuf)++ = ' ';
+                       (*plen)--;
+               }
+       }
+}
+
+lws_struct_json_serialize_result_t
+lws_struct_json_serialize(lws_struct_serialize_t *js, uint8_t *buf,
+                         size_t len, size_t *written)
+{
+       lws_struct_serialize_st_t *j;
+       const lws_struct_map_t *map;
+       size_t budget = 0, olen = len;
+       struct lws_dll2_owner *o;
+       unsigned long long uli;
+       const char *q;
+       const void *p;
+       char dbuf[72];
+       long long li;
+       int n;
+
+       *written = 0;
+       *buf = '\0';
+
+       while (len > sizeof(dbuf) + 20) {
+               j = &js->st[js->sp];
+               map = &j->map[j->map_entry];
+               q = j->obj + map->ofs;
+
+               /* early check if the entry should be elided */
+
+               switch (map->type) {
+               case LSMT_STRING_PTR:
+               case LSMT_CHILD_PTR:
+                       q = (char *)*(char **)q;
+                       if (!q)
+                               goto up;
+                       break;
+
+               case LSMT_LIST:
+                       o = (struct lws_dll2_owner *)q;
+                       p = j->dllpos = lws_dll2_get_head(o);
+                       if (!p)
+                               goto up;
+                       break;
+
+               default:
+                       break;
+               }
+
+               if (j->subsequent) {
+                       *buf++ = ',';
+                       len--;
+                       lws_struct_pretty(js, &buf, &len);
+               }
+               j->subsequent = 1;
+
+               if (map->type != LSMT_SCHEMA && !js->offset) {
+                       n = lws_snprintf((char *)buf, len, "\"%s\":",
+                                           map->colname);
+                       buf += n;
+                       len -= n;
+                       if (js->flags & LSSERJ_FLAG_PRETTY) {
+                               *buf++ = ' ';
+                               len--;
+                       }
+               }
+
+               switch (map->type) {
+               case LSMT_BOOLEAN:
+               case LSMT_UNSIGNED:
+                       if (map->aux == sizeof(char)) {
+                               uli = *(unsigned char *)q;
+                       } else {
+                               if (map->aux == sizeof(int)) {
+                                       uli = *(unsigned int *)q;
+                               } else {
+                                       if (map->aux == sizeof(long))
+                                               uli = *(unsigned long *)q;
+                                       else
+                                               uli = *(unsigned long long *)q;
+                               }
+                       }
+                       q = dbuf;
+
+                       if (map->type == LSMT_BOOLEAN) {
+                               budget = lws_snprintf(dbuf, sizeof(dbuf),
+                                               "%s", uli ? "true" : "false");
+                       } else
+                               budget = lws_snprintf(dbuf, sizeof(dbuf),
+                                                     "%llu", uli);
+                       break;
+
+               case LSMT_SIGNED:
+                       if (map->aux == sizeof(signed char)) {
+                               li = (long long)*(signed char *)q;
+                       } else {
+                               if (map->aux == sizeof(int)) {
+                                       li = (long long)*(int *)q;
+                               } else {
+                                       if (map->aux == sizeof(long))
+                                               li = (long long)*(long *)q;
+                                       else
+                                               li = *(long long *)q;
+                               }
+                       }
+                       q = dbuf;
+                       budget = lws_snprintf(dbuf, sizeof(dbuf), "%lld", li);
+                       break;
+
+               case LSMT_STRING_CHAR_ARRAY:
+                       budget = strlen(q);
+                       if (!js->offset) {
+                               *buf++ = '\"';
+                               len--;
+                       }
+                       break;
+
+               case LSMT_STRING_PTR:
+                       budget = strlen(q);
+                       if (!js->offset) {
+                               *buf++ = '\"';
+                               len--;
+                       }
+                       break;
+               case LSMT_LIST:
+                       *buf++ = '[';
+                       len--;
+                       if (js->sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH)
+                               return LSJS_RESULT_ERROR;
+
+                       /* add a stack level to handle parsing array members */
+
+                       o = (struct lws_dll2_owner *)q;
+                       p = j->dllpos = lws_dll2_get_head(o);
+
+                       if (!j->dllpos) {
+                               *buf++ = ']';
+                               len--;
+                               goto up;
+                       }
+
+                       n = j->idt;
+                       j = &js->st[++js->sp];
+                       j->idt = n + 2;
+                       j->map = map->child_map;
+                       j->map_entries = map->child_map_size;
+                       j->size = map->aux;
+                       j->subsequent = 0;
+                       j->map_entry = 0;
+                       lws_struct_pretty(js, &buf, &len);
+                       *buf++ = '{';
+                       len--;
+                       lws_struct_pretty(js, &buf, &len);
+                       if (p)
+                               j->obj = ((char *)p) - j->map->ofs_clist;
+                       else
+                               j->obj = NULL;
+                       continue;
+
+               case LSMT_CHILD_PTR:
+
+                       if (js->sp + 1 == LEJP_MAX_PARSING_STACK_DEPTH)
+                               return LSJS_RESULT_ERROR;
+
+                       /* add a stack level tto handle parsing child members */
+
+                       n = j->idt;
+                       j = &js->st[++js->sp];
+                       j->idt = n + 2;
+                       j->map = map->child_map;
+                       j->map_entries = map->child_map_size;
+                       j->size = map->aux;
+                       j->subsequent = 0;
+                       j->map_entry = 0;
+                       *buf++ = '{';
+                       len--;
+                       lws_struct_pretty(js, &buf, &len);
+                       j->obj = q;
+                       continue;
+
+               case LSMT_SCHEMA:
+                       q = dbuf;
+                       *buf++ = '{';
+                       len--;
+                       j = &js->st[++js->sp];
+                       lws_struct_pretty(js, &buf, &len);
+                       budget = lws_snprintf(dbuf, 15, "\"schema\":");
+                       if (js->flags & LSSERJ_FLAG_PRETTY)
+                               dbuf[budget++] = ' ';
+
+                       budget += lws_snprintf(dbuf + budget,
+                                              sizeof(dbuf) - budget,
+                                             "\"%s\"", map->colname);
+
+
+                       if (js->sp != 1)
+                               return LSJS_RESULT_ERROR;
+                       j->map = map->child_map;
+                       j->map_entries = map->child_map_size;
+                       j->size = map->aux;
+                       j->subsequent = 0;
+                       j->map_entry = 0;
+                       j->obj = js->st[js->sp - 1].obj;
+                       j->dllpos = NULL;
+                       /* we're actually at the same level */
+                       j->subsequent = 1;
+                       j->idt = 1;
+                       break;
+               }
+
+               q += js->offset;
+               budget -= js->remaining;
+
+               if (budget > len) {
+                       js->remaining = budget - len;
+                       js->offset = len;
+                       budget = len;
+               } else {
+                       js->remaining = 0;
+                       js->offset = 0;
+               }
+
+               memcpy(buf, q, budget);
+               buf += budget;
+               *buf = '\0';
+               len -= budget;
+
+               switch (map->type) {
+               case LSMT_STRING_CHAR_ARRAY:
+               case LSMT_STRING_PTR:
+                       *buf++ = '\"';
+                       len--;
+                       break;
+               case LSMT_SCHEMA:
+                       continue;
+               default:
+                       break;
+               }
+
+               if (js->remaining)
+                       continue;
+up:
+               if (++j->map_entry < j->map_entries)
+                       continue;
+
+               if (!js->sp)
+                       continue;
+               js->sp--;
+               if (!js->sp) {
+                       lws_struct_pretty(js, &buf, &len);
+                       *buf++ = '}';
+                       len--;
+                       lws_struct_pretty(js, &buf, &len);
+                       break;
+               }
+               js->offset = 0;
+               j = &js->st[js->sp];
+               map = &j->map[j->map_entry];
+
+               if (map->type == LSMT_CHILD_PTR) {
+                       lws_struct_pretty(js, &buf, &len);
+                       *buf++ = '}';
+                       len--;
+
+                       /* we have done the singular child pointer */
+
+                       js->offset = 0;
+                       goto up;
+               }
+
+               if (map->type != LSMT_LIST)
+                       continue;
+               /*
+                * we are coming back up to an array map, it means we should
+                * advance to the next array member if there is one
+                */
+
+               lws_struct_pretty(js, &buf, &len);
+               *buf++ = '}';
+               len--;
+
+               p = j->dllpos = j->dllpos->next;
+               if (j->dllpos) {
+                       /*
+                        * there was another item in the array to do... let's
+                        * move on to that nd do it
+                        */
+                       *buf++ = ',';
+                       len--;
+                       lws_struct_pretty(js, &buf, &len);
+                       js->offset = 0;
+                       j = &js->st[++js->sp];
+                       j->map_entry = 0;
+                       map = &j->map[j->map_entry];
+
+                       *buf++ = '{';
+                       len--;
+                       lws_struct_pretty(js, &buf, &len);
+
+                       j->subsequent = 0;
+                       j->obj = ((char *)p) - j->map->ofs_clist;
+                       continue;
+               }
+
+               /* there are no further items in the array */
+
+               js->offset = 0;
+               lws_struct_pretty(js, &buf, &len);
+               *buf++ = ']';
+               len--;
+               goto up;
+       }
+
+       *written = olen - len;
+       *buf = '\0'; /* convenience, a NUL after the official end */
+
+       return LSJS_RESULT_FINISH;
+}
diff --git a/lib/misc/lws-struct-sqlite.c b/lib/misc/lws-struct-sqlite.c
new file mode 100644 (file)
index 0000000..e89343e
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * libwebsockets - lws_struct JSON serialization helpers
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <libwebsockets.h>
+#include <core/private.h>
+
+#include <sqlite3.h>
+
+/*
+ * we get one of these per matching result from the query
+ */
+
+static int
+lws_struct_sq3_deser_cb(void *priv, int cols, char **cv, char **cn)
+{
+       lws_struct_args_t *a = (lws_struct_args_t *)priv;
+       const lws_struct_map_t *map = a->map_st[0];
+       int n, mems = a->map_entries_st[0];
+       lws_dll2_owner_t *o = (lws_dll2_owner_t *)a->cb_arg;
+       char *u = lwsac_use_zero(&a->ac, a->dest_len, a->ac_block_size);
+       long long li;
+       size_t lim;
+       char **pp;
+       char *s;
+
+       if (!u) {
+               lwsl_err("OOM\n");
+
+               return 1;
+       }
+
+       lws_dll2_add_tail((lws_dll2_t *)((char *)u + a->toplevel_dll2_ofs), o);
+
+       while (mems--) {
+               for (n = 0; n < cols; n++) {
+                       if (!cv[n] || strcmp(cn[n], map->colname))
+                               continue;
+
+                       switch (map->type) {
+                       case LSMT_SIGNED:
+                               if (map->aux == sizeof(signed char)) {
+                                       signed char *pc;
+                                       pc = (signed char *)(u + map->ofs);
+                                       *pc = atoi(cv[n]);
+                                       break;
+                               }
+                               if (map->aux == sizeof(int)) {
+                                       int *pi;
+                                       pi = (int *)(u + map->ofs);
+                                       *pi = atoi(cv[n]);
+                                       break;
+                               }
+                               if (map->aux == sizeof(long)) {
+                                       long *pl;
+                                       pl = (long *)(u + map->ofs);
+                                       *pl = atol(cv[n]);
+                                       break;
+                               }
+                               {
+                                       long long *pll;
+                                       pll = (long long *)(u + map->ofs);
+                                       *pll = atoll(cv[n]);
+                               }
+                               break;
+
+                       case LSMT_UNSIGNED:
+                               if (map->aux == sizeof(unsigned char)) {
+                                       unsigned char *pc;
+                                       pc = (unsigned char *)(u + map->ofs);
+                                       *pc = atoi(cv[n]);
+                                       break;
+                               }
+                               if (map->aux == sizeof(unsigned int)) {
+                                       unsigned int *pi;
+                                       pi = (unsigned int *)(u + map->ofs);
+                                       *pi = atoi(cv[n]);
+                                       break;
+                               }
+                               if (map->aux == sizeof(unsigned long)) {
+                                       unsigned long *pl;
+                                       pl = (unsigned long *)(u + map->ofs);
+                                       *pl = atol(cv[n]);
+                                       break;
+                               }
+                               {
+                                       unsigned long long *pll;
+                                       pll = (unsigned long long *)(u + map->ofs);
+                                       *pll = atoll(cv[n]);
+                               }
+                               break;
+
+                       case LSMT_BOOLEAN:
+                               li = 0;
+                               if (!strcmp(cv[n], "true") ||
+                                   !strcmp(cv[n], "TRUE") || cv[n][0] == '1')
+                                       li = 1;
+                               if (map->aux == sizeof(char)) {
+                                       char *pc;
+                                       pc = (char *)(u + map->ofs);
+                                       *pc = (char)li;
+                                       break;
+                               }
+                               if (map->aux == sizeof(int)) {
+                                       int *pi;
+                                       pi = (int *)(u + map->ofs);
+                                       *pi = (int)li;
+                               } else {
+                                       uint64_t *p64;
+                                       p64 = (uint64_t *)(u + map->ofs);
+                                       *p64 = li;
+                               }
+                               break;
+
+                       case LSMT_STRING_CHAR_ARRAY:
+                               s = (char *)(u + map->ofs);
+                               lim = map->aux - 1;
+                               lws_strncpy(s, cv[n], lim);
+                               break;
+
+                       case LSMT_STRING_PTR:
+                               pp = (char **)(u + map->ofs);
+                               lim = strlen(cv[n]);
+                               s = lwsac_use(&a->ac, lim + 1, a->ac_block_size);
+                               if (!s)
+                                       return 1;
+                               *pp = s;
+                               memcpy(s, cv[n], lim);
+                               s[lim] = '\0';
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               map++;
+       }
+
+       return 0;
+}
+
+/*
+ * Call this with an LSM_SCHEMA map, its colname is the table name and its
+ * type information describes the toplevel type.  Schema is dereferenced and
+ * put in args before the actual sq3 query, which is given the child map.
+ */
+
+int
+lws_struct_sq3_deserialize(sqlite3 *pdb, const lws_struct_map_t *schema,
+                          lws_dll2_owner_t *o, struct lwsac **ac,
+                          uint64_t start, int limit)
+{
+       char s[150], where[32];
+       lws_struct_args_t a;
+
+       memset(&a, 0, sizeof(a));
+       a.cb_arg = o; /* lws_dll2_owner tracking query result objects */
+       a.map_st[0]  = schema->child_map;
+       a.map_entries_st[0] = schema->child_map_size;
+       a.dest_len = schema->aux; /* size of toplevel object to allocate */
+       a.toplevel_dll2_ofs = schema->ofs;
+
+       lws_dll2_owner_clear(o);
+
+       where[0] = '\0';
+       if (start)
+               lws_snprintf(where, sizeof(where), " where when < %llu ",
+                               (unsigned long long)start);
+
+       lws_snprintf(s, sizeof(s) - 1, "select * "
+                    "from %s %s order by created desc limit %d;",
+                    schema->colname, where, limit);
+
+       if (sqlite3_exec(pdb, s, lws_struct_sq3_deser_cb, &a, NULL) != SQLITE_OK) {
+               lwsl_err("%s: fail\n", sqlite3_errmsg(pdb));
+               lwsac_free(&a.ac);
+               return -1;
+       }
+
+       *ac = a.ac;
+
+       return 0;
+}
+
+int
+lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema)
+{
+       const lws_struct_map_t *map = schema->child_map;
+       int map_size = schema->child_map_size, subsequent = 0;
+       char s[2048], *p = s, *end = &s[sizeof(s) - 1], *pri = "primary key";
+
+       p += lws_snprintf(p, end - p, "create table if not exists %s (",
+                         schema->colname);
+
+       while (map_size--) {
+               if (map->type > LSMT_STRING_PTR) {
+                       map++;
+                       continue;
+               }
+               if (subsequent && (end - p) > 3)
+                       *p++ = ',';
+               subsequent = 1;
+               if (map->type < LSMT_STRING_CHAR_ARRAY)
+                       p += lws_snprintf(p, end - p, "%s integer %s",
+                                         map->colname, pri);
+               else
+                       p += lws_snprintf(p, end - p, "%s varchar %s",
+                                         map->colname, pri);
+               pri = "";
+               map++;
+       }
+
+       p += lws_snprintf(p, end - p, ");");
+
+       if (sqlite3_exec(pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
+               lwsl_err("%s: %s: fail\n", __func__, sqlite3_errmsg(pdb));
+
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+lws_struct_sq3_open(struct lws_context *context, const char *sqlite3_path,
+                   sqlite3 **pdb)
+{
+       int uid = 0, gid = 0;
+
+       if (sqlite3_open_v2(sqlite3_path, pdb,
+                           SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+                           NULL) != SQLITE_OK) {
+               lwsl_err("%s: Unable to open db %s: %s\n",
+                        __func__, sqlite3_path, sqlite3_errmsg(*pdb));
+
+               return 1;
+       }
+
+       lws_get_effective_uid_gid(context, &uid, &gid);
+       if (uid)
+               chown(sqlite3_path, uid, gid);
+       chmod(sqlite3_path, 0600);
+
+       lwsl_notice("%s: created %s owned by %u:%u mode 0600\n", __func__,
+                       sqlite3_path, (unsigned int)uid, (unsigned int)gid);
+
+       sqlite3_extended_result_codes(*pdb, 1);
+
+       return 0;
+}
+
+int
+lws_struct_sq3_close(sqlite3 **pdb)
+{
+       if (!*pdb)
+               return 0;
+
+       sqlite3_close(*pdb);
+       *pdb = NULL;
+
+       return 0;
+}
diff --git a/lib/misc/lwsac/README.md b/lib/misc/lwsac/README.md
new file mode 100644 (file)
index 0000000..e33bc8e
--- /dev/null
@@ -0,0 +1,106 @@
+## LWS Allocated Chunks
+
+![lwsac flow](/doc-assets/lwsac.svg)
+
+These apis provide a way to manage a linked-list of allocated chunks...
+
+[ HEAD alloc ] -> [ next alloc ] -> [ next alloc ] -> [ curr alloc ]
+
+... and sub-allocate trivially inside the chunks.  These sub-allocations are
+not tracked by lwsac at all, there is a "used" high-water mark for each chunk
+that's simply advanced by the amount sub-allocated.  If the allocation size
+matches the platform pointer alignment, there is zero overhead to sub-allocate
+(otherwise the allocation is padded to the next platform pointer alignment
+automatically).
+
+If you have an unknown amount of relatively little things to allocate, including
+strings or other unstructured data, lwsac is significantly more efficient than
+individual allocations using malloc or so.
+
+[lwsac full public api](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-lwsac.h)
+
+## lwsac_use() api
+
+```
+/**
+ * lwsac_use - allocate / use some memory from a lwsac
+ *
+ * \param head: pointer to the lwsac list object
+ * \param ensure: the number of bytes we want to use
+ * \param chunk_size: 0, or the size of the chunk to (over)allocate if
+ *                     what we want won't fit in the current tail chunk.  If
+ *                     0, the default value of 4000 is used. If ensure is
+ *                     larger, it is used instead.
+ *
+ * This also serves to init the lwsac if *head is NULL.  Basically it does
+ * whatever is necessary to return you a pointer to ensure bytes of memory
+ * reserved for the caller.
+ *
+ * Returns NULL if OOM.
+ */
+LWS_VISIBLE LWS_EXTERN void *
+lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size);
+```
+
+When you make an sub-allocation using `lwsac_use()`, you can either
+set the `chunk_size` arg to zero, defaulting to 4000, or a specific chunk size.
+In the event the requested sub-allocation exceeds the chunk size, the chunk
+size is increated to match it automatically for this allocation only.
+
+Subsequent `lwsac_use()` calls will advance internal pointers to use up the
+remaining space inside the current chunk if possible; if not enough remaining
+space it is skipped, a new allocation is chained on and the request pointed to
+there.
+
+Lwsac does not store information about sub-allocations.  There is really zero
+overhead for individual sub-allocations (unless their size is not
+pointer-aligned, in which case the actual amount sub-allocated is rounded up to
+the next pointer alignment automatically).  For structs, which are pointer-
+aligned naturally, and a chunk size relatively large for the sub-allocation
+size, lwsac is extremely efficient even for huge numbers of small allocations.
+
+This makes lwsac very effective when the total amount of allocation needed is
+not known at the start and may be large... it will simply add on chunks to cope
+with whatever happens.
+
+## lwsac_free() api
+
+```
+/**
+ * lwsac_free - deallocate all chunks in the lwsac and set head NULL
+ *
+ * \param head: pointer to the lwsac list object
+ *
+ * This deallocates all chunks in the lwsac, then sets *head to NULL.  All
+ * lwsac_use() pointers are invalidated in one hit without individual frees.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lwsac_free(struct lwsac **head);
+```
+
+When you are finished with the lwsac, you simply free the chain of allocated
+chunks using lwsac_free() on the lwsac head.  There's no tracking or individual
+destruction of suballocations - the whole chain of chunks the suballocations
+live in are freed and invalidated all together.
+
+If the structs stored in the lwsac allocated things **outside** the lwsac, then the
+user must unwind through them and perform the frees.  But the idea of lwsac is
+things stored in the lwsac also suballocate into the lwsac, and point into the
+lwsac if they need to, avoiding any need to visit them during destroy.  It's
+like clearing up after a kids' party by gathering up a disposable tablecloth:
+no matter what was left on the table, it's all gone in one step.
+
+## lws_list_ptr helpers
+
+```
+/* sort may be NULL if you don't care about order */
+LWS_VISIBLE LWS_EXTERN void
+lws_list_ptr_insert(lws_list_ptr *phead, lws_list_ptr *add,
+                   lws_list_ptr_sort_func_t sort);
+```
+
+A common pattern needed with sub-allocated structs is they are on one or more
+linked-list.  To make that simple to do cleanly, lws_list... apis are provided
+along with a generic insertion function that can take a sort callback.  These
+allow a struct to participate on multiple linked-lists simultaneously.
+
diff --git a/lib/misc/lwsac/cached-file.c b/lib/misc/lwsac/cached-file.c
new file mode 100644 (file)
index 0000000..6598201
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * libwebsockets - lws alloc chunk live file caching
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
+
+#include "core/private.h"
+#include "misc/lwsac/private.h"
+
+/*
+ * Helper for caching a file in memory in a lac, but also to check at intervals
+ * no less than 5s if the file is still fresh.
+ *
+ * Set *cache to NULL the first time before calling.
+ *
+ * You should call this each time before using the cache... if it's
+ *
+ *  - less than 5s since the last freshness check, and
+ *  - the file is already in memory
+ *
+ * it just returns with *cache left alone; this costs very little.  You should
+ * call `lwsac_use_cached_file_start()` and `lwsac_use_cached_file_end()`
+ * to lock the cache against deletion while you are using it.
+ *
+ * If it's
+ *
+ *  - at least 5s since the last freshness check, and
+ *  - the file timestamp has changed
+ *
+ * then
+ *
+ *  - the file is reloaded into a new lac and *cache set to that
+ *
+ *  - the old cache lac, if any, is detached (so it will be freed when its
+ *    reference count reaches zero, or immediately if nobody has it)
+ *
+ * Note the call can fail due to OOM or filesystem issue at any time.
+ *
+ *
+ * After the LAC header there is stored a `struct cached_file_info` and then
+ * the raw file contents.  *
+ *
+ *  [LAC header]
+ *  [struct cached_file_info]
+ *  [file contents]  <--- *cache is set to here
+ *
+ * The api returns a lwsac_cached_file_t type offset to point to the file
+ * contents.  Helpers for reference counting and freeing are also provided
+ * that take that type and know how to correct it back to operate on the LAC.
+ */
+
+#define cache_file_to_lac(c) ((struct lwsac *)((char *)c - \
+                             sizeof(struct cached_file_info) - \
+                             sizeof(struct lwsac)))
+
+void
+lwsac_use_cached_file_start(lwsac_cached_file_t cache)
+{
+       struct lwsac *lac = cache_file_to_lac(cache);
+
+       lac->refcount++;
+       // lwsl_debug("%s: html refcount: %d\n", __func__, lac->refcount);
+}
+
+void
+lwsac_use_cached_file_end(lwsac_cached_file_t *cache)
+{
+       struct lwsac *lac;
+
+       if (!cache || !*cache)
+               return;
+
+       lac = cache_file_to_lac(*cache);
+
+       if (!lac->refcount)
+               lwsl_err("%s: html refcount zero on entry\n", __func__);
+
+       if (lac->refcount && !--lac->refcount && lac->detached) {
+               *cache = NULL; /* not usable any more */
+               lwsac_free(&lac);
+       }
+}
+
+void
+lwsac_use_cached_file_detach(lwsac_cached_file_t *cache)
+{
+       struct lwsac *lac = cache_file_to_lac(*cache);
+
+       lac->detached = 1;
+       if (lac->refcount)
+               return;
+
+       *cache = NULL;
+       lwsac_free(&lac);
+}
+
+int
+lwsac_cached_file(const char *filepath, lwsac_cached_file_t *cache, size_t *len)
+{
+       struct cached_file_info *info = NULL;
+       lwsac_cached_file_t old = *cache;
+       struct lwsac *lac = NULL;
+       time_t t = time(NULL);
+       unsigned char *a;
+       struct stat s;
+       size_t all;
+       ssize_t rd;
+       int fd;
+
+       if (old) { /* we already have a cached copy of it */
+
+               info = (struct cached_file_info *)((*cache) - sizeof(*info));
+
+               if (t - info->last_confirm < 5)
+                       /* we checked it as fresh less than 5s ago, use old */
+                       return 0;
+       }
+
+       /*
+        * ...it's been 5s, we should check again on the filesystem
+        * that the file hasn't changed
+        */
+
+       fd = open(filepath, O_RDONLY);
+       if (fd < 0) {
+               lwsl_err("%s: cannot open %s\n", __func__, filepath);
+
+               return 1;
+       }
+
+       if (fstat(fd, &s)) {
+               lwsl_err("%s: cannot stat %s\n", __func__, filepath);
+
+               goto bail;
+       }
+
+       if (old && s.st_mtime == info->s.st_mtime) {
+               /* it still seems to be the same as our cached one */
+               info->last_confirm = t;
+
+               close(fd);
+
+               return 0;
+       }
+
+       /*
+        * we either didn't cache it yet, or it has changed since we cached
+        * it... reload in a new lac and then detach the old lac.
+        */
+
+       all = sizeof(*info) + s.st_size + 1;
+
+       info = lwsac_use(&lac, all, all);
+       if (!info)
+               goto bail;
+
+       info->s = s;
+       info->last_confirm = t;
+
+       a = (unsigned char *)(info + 1);
+
+       *len = s.st_size;
+       a[s.st_size] = '\0';
+
+       rd = read(fd, a, s.st_size);
+       if (rd != s.st_size) {
+               lwsl_err("%s: cannot read %s (%d)\n", __func__, filepath,
+                        (int)rd);
+               goto bail1;
+       }
+
+       close(fd);
+
+       *cache = (lwsac_cached_file_t)a;
+       if (old)
+               lwsac_use_cached_file_detach(&old);
+
+       return 0;
+
+bail1:
+       lwsac_free(&lac);
+
+bail:
+       close(fd);
+
+       return 1;
+}
+
+#endif
diff --git a/lib/misc/lwsac/lwsac.c b/lib/misc/lwsac/lwsac.c
new file mode 100644 (file)
index 0000000..6471c16
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * libwebsockets - lws alloc chunk
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "misc/lwsac/private.h"
+
+void
+lws_list_ptr_insert(lws_list_ptr *head, lws_list_ptr *add,
+                   lws_list_ptr_sort_func_t sort_func)
+{
+       while (sort_func && *head) {
+               if (sort_func(add, *head) <= 0)
+                       break;
+
+               head = *head;
+       }
+
+       *add = *head;
+       *head = add;
+}
+
+size_t
+lwsac_align(size_t length)
+{
+       size_t align = sizeof(int *);
+
+       if (length & (align - 1))
+               length += align - (length & (align - 1));
+
+       return length;
+}
+
+size_t
+lwsac_sizeof(void)
+{
+       return sizeof(struct lwsac);
+}
+
+size_t
+lwsac_get_tail_pos(struct lwsac *lac)
+{
+       return lac->ofs;
+}
+
+struct lwsac *
+lwsac_get_next(struct lwsac *lac)
+{
+       return lac->next;
+}
+
+void *
+lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size)
+{
+       struct lwsac *chunk;
+       size_t ofs, alloc;
+
+       /* ensure there's a chunk and enough space in it for this name */
+
+       if (!*head || (*head)->curr->alloc_size - (*head)->curr->ofs < ensure) {
+
+               if (!chunk_size)
+                       alloc = LWSAC_CHUNK_SIZE + sizeof(*chunk);
+               else
+                       alloc = chunk_size + sizeof(*chunk);
+
+               /*
+                * If we get asked for something outside our expectation,
+                * allocate to meet it
+                */
+
+               if (ensure >= alloc - sizeof(*chunk))
+                       alloc = ensure + sizeof(*chunk);
+
+               chunk = malloc(alloc);
+               if (!chunk) {
+                       lwsl_err("%s: OOM trying to alloc %llud\n", __func__,
+                                       (unsigned long long)alloc);
+                       return NULL;
+               }
+
+               if (!*head) {
+                       *head = chunk;
+                       chunk->total_alloc_size = 0;
+                       chunk->total_blocks = 0;
+               }
+               else
+                       (*head)->curr->next = chunk;
+
+               (*head)->curr = chunk;
+               (*head)->curr->head = *head;
+
+               chunk->next = NULL;
+               chunk->alloc_size = alloc;
+               chunk->detached = 0;
+               chunk->refcount = 0;
+
+               (*head)->total_alloc_size += alloc;
+               (*head)->total_blocks++;
+
+               /*
+                * belabouring the point... ofs is aligned to the platform's
+                * generic struct alignment at the start then
+                */
+               (*head)->curr->ofs = sizeof(*chunk);
+       }
+
+       ofs = (*head)->curr->ofs;
+
+       (*head)->curr->ofs += lwsac_align(ensure);
+       if ((*head)->curr->ofs >= (*head)->curr->alloc_size)
+               (*head)->curr->ofs = (*head)->curr->alloc_size;
+
+       return (char *)(*head)->curr + ofs;
+}
+
+void *
+lwsac_use_zero(struct lwsac **head, size_t ensure, size_t chunk_size)
+{
+       void *p = lwsac_use(head, ensure, chunk_size);
+
+       if (p)
+               memset(p, 0, ensure);
+
+       return p;
+}
+
+void *
+lwsac_use_zeroed(struct lwsac **head, size_t ensure, size_t chunk_size)
+{
+       void *r = lwsac_use(head, ensure, chunk_size);
+
+       if (r)
+               memset(r, 0, ensure);
+
+       return r;
+}
+
+void
+lwsac_free(struct lwsac **head)
+{
+       struct lwsac *it = *head;
+
+       *head = NULL;
+       lwsl_debug("%s: head %p\n", __func__, *head);
+
+       while (it) {
+               struct lwsac *tmp = it->next;
+
+               free(it);
+               it = tmp;
+       }
+}
+
+void
+lwsac_info(struct lwsac *head)
+{
+       if (!head)
+               lwsl_debug("%s: empty\n", __func__);
+       else
+               lwsl_debug("%s: lac %p: %dKiB in %d blocks\n", __func__, head,
+                  (int)(head->total_alloc_size >> 10), head->total_blocks);
+}
+
+uint64_t
+lwsac_total_alloc(struct lwsac *head)
+{
+       return head->total_alloc_size;
+}
+
+void
+lwsac_reference(struct lwsac *head)
+{
+       head->refcount++;
+       lwsl_debug("%s: head %p: (det %d) refcount -> %d\n",
+                   __func__, head, head->detached, head->refcount);
+}
+
+void
+lwsac_unreference(struct lwsac **head)
+{
+       if (!(*head))
+               return;
+
+       if (!(*head)->refcount)
+               lwsl_warn("%s: refcount going below zero\n", __func__);
+
+       (*head)->refcount--;
+
+       lwsl_debug("%s: head %p: (det %d) refcount -> %d\n",
+                   __func__, *head, (*head)->detached, (*head)->refcount);
+
+       if ((*head)->detached && !(*head)->refcount) {
+               lwsl_debug("%s: head %p: FREED\n", __func__, *head);
+               lwsac_free(head);
+       }
+}
+
+void
+lwsac_detach(struct lwsac **head)
+{
+       (*head)->detached = 1;
+       if (!(*head)->refcount) {
+               lwsl_debug("%s: head %p: FREED\n", __func__, *head);
+               lwsac_free(head);
+       } else
+               lwsl_debug("%s: head %p: refcount %d: Marked as detached\n",
+                           __func__, *head, (*head)->refcount);
+}
diff --git a/lib/misc/lwsac/private.h b/lib/misc/lwsac/private.h
new file mode 100644 (file)
index 0000000..827906e
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * libwebsockets - lws alloc chunk
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined(LWS_PLAT_OPTEE)
+#include <sys/stat.h>
+#endif
+
+/* under page size of 4096 to allow overhead */
+#define LWSAC_CHUNK_SIZE 4000
+
+/*
+ * the chunk list members all point back to the head themselves so the list
+ * can be detached from the formal head and free itself when its reference
+ * count reaches zero.
+ */
+
+struct lwsac {
+       struct lwsac *next;
+       struct lwsac *head; /* pointer back to the first chunk */
+       struct lwsac *curr; /* applies to head chunk only */
+       size_t total_alloc_size; /* applies to head chunk only */
+       size_t alloc_size;
+       size_t ofs; /* next writeable position inside chunk */
+       int refcount; /* applies to head chunk only */
+       int total_blocks; /* applies to head chunk only */
+       char detached; /* if our refcount gets to zero, free the chunk list */
+};
+
+#if !defined(LWS_PLAT_OPTEE)
+struct cached_file_info {
+       struct stat s;
+       time_t last_confirm;
+};
+#endif
diff --git a/lib/misc/peer-limits.c b/lib/misc/peer-limits.c
new file mode 100644 (file)
index 0000000..53d9421
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * libwebsockets - peer limits tracking
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/* requires context->lock */
+static void
+__lws_peer_remove_from_peer_wait_list(struct lws_context *context,
+                                     struct lws_peer *peer)
+{
+       struct lws_peer *df;
+
+       lws_start_foreach_llp(struct lws_peer **, p, context->peer_wait_list) {
+               if (*p == peer) {
+                       df = *p;
+
+                       *p = df->peer_wait_list;
+                       df->peer_wait_list = NULL;
+
+                       return;
+               }
+       } lws_end_foreach_llp(p, peer_wait_list);
+}
+
+/* requires context->lock */
+static void
+__lws_peer_add_to_peer_wait_list(struct lws_context *context,
+                                struct lws_peer *peer)
+{
+       __lws_peer_remove_from_peer_wait_list(context, peer);
+
+       peer->peer_wait_list = context->peer_wait_list;
+       context->peer_wait_list = peer;
+}
+
+
+struct lws_peer *
+lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd)
+{
+       struct lws_context *context = vhost->context;
+       socklen_t rlen = 0;
+       void *q;
+       uint8_t *q8;
+       struct lws_peer *peer;
+       uint32_t hash = 0;
+       int n, af = AF_INET;
+       struct sockaddr_storage addr;
+
+       if (vhost->options & LWS_SERVER_OPTION_UNIX_SOCK)
+               return NULL;
+
+#ifdef LWS_WITH_IPV6
+       if (LWS_IPV6_ENABLED(vhost)) {
+               af = AF_INET6;
+       }
+#endif
+       rlen = sizeof(addr);
+       if (getpeername(sockfd, (struct sockaddr*)&addr, &rlen))
+               /* eg, udp doesn't have to have a peer */
+               return NULL;
+
+#ifdef LWS_WITH_IPV6
+       if (af == AF_INET)
+#endif
+       {
+               struct sockaddr_in *s = (struct sockaddr_in *)&addr;
+               q = &s->sin_addr;
+               rlen = sizeof(s->sin_addr);
+       }
+#ifdef LWS_WITH_IPV6
+       else {
+               struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
+               q = &s->sin6_addr;
+               rlen = sizeof(s->sin6_addr);
+       }
+#endif
+
+       q8 = q;
+       for (n = 0; n < (int)rlen; n++)
+               hash = (((hash << 4) | (hash >> 28)) * n) ^ q8[n];
+
+       hash = hash % context->pl_hash_elements;
+
+       lws_context_lock(context, "peer search"); /* <======================= */
+
+       lws_start_foreach_ll(struct lws_peer *, peerx,
+                            context->pl_hash_table[hash]) {
+               if (peerx->af == af && !memcmp(q, peerx->addr, rlen)) {
+                       lws_context_unlock(context); /* === */
+                       return peerx;
+               }
+       } lws_end_foreach_ll(peerx, next);
+
+       lwsl_info("%s: creating new peer\n", __func__);
+
+       peer = lws_zalloc(sizeof(*peer), "peer");
+       if (!peer) {
+               lws_context_unlock(context); /* === */
+               lwsl_err("%s: OOM for new peer\n", __func__);
+               return NULL;
+       }
+
+       context->count_peers++;
+       peer->next = context->pl_hash_table[hash];
+       peer->hash = hash;
+       peer->af = af;
+       context->pl_hash_table[hash] = peer;
+       memcpy(peer->addr, q, rlen);
+       time(&peer->time_created);
+       /*
+        * On creation, the peer has no wsi attached, so is created on the
+        * wait list.  When a wsi is added it is removed from the wait list.
+        */
+       time(&peer->time_closed_all);
+       __lws_peer_add_to_peer_wait_list(context, peer);
+
+       lws_context_unlock(context); /* ====================================> */
+
+       return peer;
+}
+
+/* requires context->lock */
+static int
+__lws_peer_destroy(struct lws_context *context, struct lws_peer *peer)
+{
+       lws_start_foreach_llp(struct lws_peer **, p,
+                             context->pl_hash_table[peer->hash]) {
+               if (*p == peer) {
+                       struct lws_peer *df = *p;
+                       *p = df->next;
+                       lws_free(df);
+                       context->count_peers--;
+
+                       return 0;
+               }
+       } lws_end_foreach_llp(p, next);
+
+       return 1;
+}
+
+void
+lws_peer_cull_peer_wait_list(struct lws_context *context)
+{
+       struct lws_peer *df;
+       time_t t;
+
+       time(&t);
+
+       if (context->next_cull && t < context->next_cull)
+               return;
+
+       lws_context_lock(context, "peer cull"); /* <========================= */
+
+       context->next_cull = t + 5;
+
+       lws_start_foreach_llp(struct lws_peer **, p, context->peer_wait_list) {
+               if (t - (*p)->time_closed_all > 10) {
+                       df = *p;
+
+                       /* remove us from the peer wait list */
+                       *p = df->peer_wait_list;
+                       df->peer_wait_list = NULL;
+
+                       __lws_peer_destroy(context, df);
+                       continue; /* we already point to next, if any */
+               }
+       } lws_end_foreach_llp(p, peer_wait_list);
+
+       lws_context_unlock(context); /* ====================================> */
+}
+
+void
+lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer,
+                struct lws *wsi)
+{
+       if (!peer)
+               return;
+
+       lws_context_lock(context, "peer add"); /* <========================== */
+
+       peer->count_wsi++;
+       wsi->peer = peer;
+       __lws_peer_remove_from_peer_wait_list(context, peer);
+
+       lws_context_unlock(context); /* ====================================> */
+}
+
+void
+lws_peer_dump_from_wsi(struct lws *wsi)
+{
+       struct lws_peer *peer;
+
+       if (!wsi || !wsi->peer)
+               return;
+
+       peer = wsi->peer;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       lwsl_notice("%s: wsi %p: created %llu: wsi: %d/%d, ah %d/%d\n",
+                       __func__,
+                       wsi, (unsigned long long)peer->time_created,
+                       peer->count_wsi, peer->total_wsi,
+                       peer->http.count_ah, peer->http.total_ah);
+#else
+       lwsl_notice("%s: wsi %p: created %llu: wsi: %d/%d\n", __func__,
+                       wsi, (unsigned long long)peer->time_created,
+                       peer->count_wsi, peer->total_wsi);
+#endif
+}
+
+void
+lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer)
+{
+       if (!peer)
+               return;
+
+       lws_context_lock(context, "peer wsi close"); /* <==================== */
+
+       assert(peer->count_wsi);
+       peer->count_wsi--;
+
+       if (!peer->count_wsi
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+                       && !peer->http.count_ah
+#endif
+       ) {
+               /*
+                * in order that we can accumulate peer activity correctly
+                * allowing for periods when the peer has no connections,
+                * we don't synchronously destroy the peer when his last
+                * wsi closes.  Instead we mark the time his last wsi
+                * closed and add him to a peer_wait_list to be reaped
+                * later if no further activity is coming.
+                */
+               time(&peer->time_closed_all);
+               __lws_peer_add_to_peer_wait_list(context, peer);
+       }
+
+       lws_context_unlock(context); /* ====================================> */
+}
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+int
+lws_peer_confirm_ah_attach_ok(struct lws_context *context,
+                             struct lws_peer *peer)
+{
+       if (!peer)
+               return 0;
+
+       if (context->ip_limit_ah &&
+           peer->http.count_ah >= context->ip_limit_ah) {
+               lwsl_info("peer reached ah limit %d, deferring\n",
+                               context->ip_limit_ah);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+void
+lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer)
+{
+       if (!peer)
+               return;
+
+       lws_context_lock(context, "peer ah detach"); /* <==================== */
+       assert(peer->http.count_ah);
+       peer->http.count_ah--;
+       lws_context_unlock(context); /* ====================================> */
+}
+#endif
similarity index 95%
rename from lib/romfs.c
rename to lib/misc/romfs.c
index 540382e..814999a 100644 (file)
@@ -119,15 +119,16 @@ static romfs_inode_t
 romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
 {
        romfs_inode_t level, i = start, i_in;
-       const char *p, *n, *cp;
+       const char *p, *cp;
        uint32_t next_be;
 
        if (start == (romfs_inode_t)romfs)
                i = skip_and_pad((romfs_inode_t)romfs);
        level = i;
        while (i != (romfs_inode_t)romfs) {
+               const char *n = ((const char *)i) + sizeof(*i);
+
                p = path;
-               n = ((const char *)i) + sizeof(*i);
                i_in = i;
 
                set_cache(i, sizeof(*i));
@@ -136,12 +137,16 @@ romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
                cp = (const char *)cache;
                set_cache((romfs_inode_t)n, RFS_STRING_MAX);
 
-               while (*p && *p != '/' && *cp && *p == *cp && (p - path) < RFS_STRING_MAX) {
+               while (*p && *p != '/' && *cp && *p == *cp &&
+                      (p - path) < RFS_STRING_MAX) {
                        p++;
                        n++;
                        cp++;
                }
 
+               while (*p == '/' && p[1] == '/')
+                       p++;
+
                if (!*cp && (!*p || *p == '/') &&
                    (ntohl(next_be) & 7) == RFST_HARDLINK) {
                        set_cache(i, sizeof(*i));
@@ -162,6 +167,9 @@ romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path)
                if (!*p && *cp == '/')
                        return NULL;
 
+               while (*p == '/' && p[1] == '/')
+                       p++;
+
                if (*p == '/' && !*cp) {
                        set_cache(i, sizeof(*i));
                        switch (ntohl(ci->next) & 7) {
similarity index 100%
rename from lib/romfs.h
rename to lib/misc/romfs.h
similarity index 96%
rename from lib/sha-1.c
rename to lib/misc/sha-1.c
index 9353fbe..c17a437 100644 (file)
@@ -32,7 +32,7 @@
  * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
  */
 
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
 #ifdef LWS_HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -45,7 +45,7 @@ struct sha1_ctxt {
        } h;
        union {
                unsigned char           b8[8];
-               u_int64_t               b64[1];
+               uint64_t                b64[1];
        } c;
        union {
                unsigned char           b8[64];
@@ -183,7 +183,7 @@ sha1_step(struct sha1_ctxt *ctxt)
        H(3) = H(3) + d;
        H(4) = H(4) + e;
 
-       bzero(&ctxt->m.b8[0], 64);
+       memset(&ctxt->m.b8[0], 0, 64);
 }
 
 /*------------------------------------------------------------*/
@@ -191,7 +191,7 @@ sha1_step(struct sha1_ctxt *ctxt)
 static void
 _sha1_init(struct sha1_ctxt *ctxt)
 {
-       bzero(ctxt, sizeof(struct sha1_ctxt));
+       memset(ctxt, 0, sizeof(struct sha1_ctxt));
        H(0) = 0x67452301;
        H(1) = 0xefcdab89;
        H(2) = 0x98badcfe;
@@ -210,14 +210,14 @@ sha1_pad(struct sha1_ctxt *ctxt)
        padstart = COUNT % 64;
        padlen = 64 - padstart;
        if (padlen < 8) {
-               bzero(&ctxt->m.b8[padstart], padlen);
+               memset(&ctxt->m.b8[padstart], 0, padlen);
                COUNT += (unsigned char)padlen;
                COUNT %= 64;
                sha1_step(ctxt);
                padstart = COUNT % 64;  /* should be 0 */
                padlen = 64 - padstart; /* should be 64 */
        }
-       bzero(&ctxt->m.b8[padstart], padlen - 8);
+       memset(&ctxt->m.b8[padstart], 0, padlen - 8);
        COUNT += ((unsigned char)padlen - 8);
        COUNT %= 64;
 #if BYTE_ORDER == BIG_ENDIAN
@@ -236,18 +236,14 @@ sha1_pad(struct sha1_ctxt *ctxt)
 void
 sha1_loop(struct sha1_ctxt *ctxt, const unsigned char *input, size_t len)
 {
-       size_t gaplen;
-       size_t gapstart;
        size_t off;
-       size_t copysiz;
 
        off = 0;
 
        while (off < len) {
-               gapstart = COUNT % 64;
-               gaplen = 64 - gapstart;
+               size_t gapstart = COUNT % 64, gaplen = 64 - gapstart,
+                      copysiz = (gaplen < len - off) ? gaplen : len - off;
 
-               copysiz = (gaplen < len - off) ? gaplen : len - off;
                memcpy(&ctxt->m.b8[gapstart], &input[off], copysiz);
                COUNT += (unsigned char)copysiz;
                COUNT %= 64;
diff --git a/lib/misc/threadpool/README.md b/lib/misc/threadpool/README.md
new file mode 100644 (file)
index 0000000..7b5dece
--- /dev/null
@@ -0,0 +1,182 @@
+## Threadpool
+
+### Overview
+
+![overview](/doc-assets/threadpool.svg)
+
+An api that lets you create a pool of worker threads, and a queue of tasks that
+are bound to a wsi.  Tasks in their own thread  synchronize communication to the
+lws service thread of the wsi via `LWS_CALLBACK_SERVER_WRITEABLE` and friends.
+
+Tasks can produce some output, then return that they want to "sync" with the
+service thread.  That causes a `LWS_CALLBACK_SERVER_WRITEABLE` in the service
+thread context, where the output can be consumed, and the task told to continue,
+or completed tasks be reaped.
+
+ALL of the details related to thread synchronization and an associated wsi in
+the lws service thread context are handled by the threadpool api, without needing
+any pthreads in user code.
+
+### Example
+
+https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/ws-server/minimal-ws-server-threadpool
+
+### Lifecycle considerations
+
+#### Tasks vs wsi
+
+Although all tasks start out as being associated to a wsi, in fact the lifetime
+of a task and that of the wsi are not necessarily linked.
+
+You may start a long task, eg, that runs atomically in its thread for 30s, and
+at any time the client may close the connection, eg, close a browser window.
+
+There are arrangements that a task can "check in" periodically with lws to see
+if it has been asked to stop, allowing the task lifetime to be related to the
+wsi lifetime somewhat, but some tasks are going to be atomic and longlived.
+
+For that reason, at wsi close an ongoing task can detach from the wsi and
+continue until it ends or understands it has been asked to stop.  To make
+that work, the task is created with a `cleanup` callback that performs any
+freeing independent of still having a wsi around to do it... the task takes over
+responsibility to free the user pointer on destruction when the task is created.
+
+![Threadpool States](/doc-assets/threadpool-states.svg)
+
+#### Reaping completed tasks
+
+Once created, although tasks may run asynchronously, the task itself does not
+get destroyed on completion but added to a "done queue".  Only when the lws
+service thread context queries the task state with `lws_threadpool_task_status()`
+may the task be reaped and memory freed.
+
+This is analogous to unix processes and `wait()`.
+
+If a task became detached from its wsi, then joining the done queue is enough
+to get the task reaped, since there's nobody left any more to synchronize the
+reaping with.
+
+### User interface
+
+The api is declared at https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-threadpool.h
+
+#### Threadpool creation / destruction
+
+The threadpool should be created at program or vhost init using
+`lws_threadpool_create()` and destroyed on exit or vhost destruction using
+first `lws_threadpool_finish()` and then `lws_threadpool_destroy()`.
+
+Threadpools should be named, varargs are provided on the create function
+to facilite eg, naming the threadpool by the vhost it's associated with.
+
+Threadpool creation takes an args struct with the following members:
+
+Member|function
+---|---
+threads|The maxiumum number of independent threads in the pool
+max_queue_depth|The maximum number of tasks allowed to wait for a place in the pool
+
+#### Task creation / destruction
+
+Tasks are created and queued using `lws_threadpool_enqueue()`, this takes an
+args struct with the following members
+
+Member|function
+---|---
+wsi|The wsi the task is initially associated with
+user|An opaque user-private pointer used for communication with the lws service thread and private state / data
+task|A pointer to the function that will run in the pool thread
+cleanup|A pointer to a function that will clean up finished or stopped tasks (perhaps freeing user)
+
+Tasks also should have a name, the creation function again provides varargs
+to simplify naming the task with string elements related to who started it
+and why.
+
+#### The task function itself
+
+The task function receives the task user pointer and the task state.  The
+possible task states are
+
+State|Meaning
+---|---
+LWS_TP_STATUS_QUEUED|Task is still waiting for a pool thread
+LWS_TP_STATUS_RUNNING|Task is supposed to do its work
+LWS_TP_STATUS_SYNCING|Task is blocked waiting for sync from lws service thread
+LWS_TP_STATUS_STOPPING|Task has been asked to stop but didn't stop yet
+LWS_TP_STATUS_FINISHED|Task has reported it has completed
+LWS_TP_STATUS_STOPPED|Task has aborted
+
+The task function will only be told `LWS_TP_STATUS_RUNNING` or
+`LWS_TP_STATUS_STOPPING` in its status argument... RUNNING means continue with the
+user task and STOPPING means clean up and return `LWS_TP_RETURN_STOPPED`.
+
+If possible every 100ms or so the task should return `LWS_TP_RETURN_CHECKING_IN`
+to allow lws to inform it reasonably quickly that it has been asked to stop
+(eg, because the related wsi has closed), or if it can continue.  If not
+possible, it's okay but eg exiting the application may experience delays
+until the running task finishes, and since the wsi may have gone, the work
+is wasted.
+
+The task function may return one of
+
+Return|Meaning
+---|---
+LWS_TP_RETURN_CHECKING_IN|Still wants to run, but confirming nobody asked him to stop.  Will be called again immediately with `LWS_TP_STATUS_RUNNING` or `LWS_TP_STATUS_STOPPING`
+LWS_TP_RETURN_SYNC|Task wants to trigger a WRITABLE callback and block until lws service thread restarts it with `lws_threadpool_task_sync()`
+LWS_TP_RETURN_FINISHED|Task has finished, successfully as far as it goes
+LWS_TP_RETURN_STOPPED|Task has finished, aborting in response to a request to stop
+
+The SYNC or CHECKING_IN return may also have a flag `LWS_TP_RETURN_FLAG_OUTLIVE`
+applied to it, which indicates to threadpool that this task wishes to remain
+unstopped after the wsi closes.  This is useful in the case where the task
+understands it will take a long time to complete, and wants to return a
+complete status and maybe close the connection, perhaps with a token identifying
+the task.  The task can then be monitored separately by using the token.
+
+#### Synchronizing
+
+The task can choose to "SYNC" with the lws service thread, in other words
+cause a WRITABLE callback on the associated wsi in the lws service thread
+context and block itself until it hears back from there via
+`lws_threadpool_task_sync()` to resume the task.
+
+This is typically used when, eg, the task has filled its buffer, or ringbuffer,
+and needs to pause operations until what's done has been sent and some buffer
+space is open again.
+
+In the WRITABLE callback, in lws service thread context, the buffer can be
+sent with `lws_write()` and then `lws_threadpool_task_sync()` to allow the task
+to fill another buffer and continue that way.
+
+If the WRITABLE callback determines that the task should stop, it can just call
+`lws_threadpool_task_sync()` with the second argument as 1, to force the task
+to stop immediately after it resumes.
+
+#### The cleanup function
+
+When a finished task is reaped, or a task that become detached from its initial
+wsi completes or is stopped, it calls the `.cleanup` function defined in the
+task creation args struct to free anything related to the user pointer.
+
+With threadpool, responsibility for freeing allocations used by the task belongs
+strictly with the task, via the `.cleanup` function, once the task has been
+enqueued.  That's different from a typical non-threadpool protocol where the
+wsi lifecycle controls deallocation.  This reflects the fact that the task
+may outlive the wsi.
+
+#### Protecting against WRITABLE and / or SYNC duplication
+
+Care should be taken than data prepared by the task thread in the user priv
+memory should only be sent once.  For example, after sending data from a user
+priv buffer of a given length stored in the priv, zero down the length.
+
+Task execution and the SYNC writable callbacks are mutually exclusive, so there
+is no danger of collision between the task thread and the lws service thread if
+the reason for the callback is a SYNC operation from the task thread.
+
+### Thread overcommit
+
+If the tasks running on the threads are ultimately network-bound for all or some
+of their processing (via the SYNC with the WRITEABLE callback), it's possible
+to overcommit the number of threads in the pool compared to the number of
+threads the processor has in hardware to get better occupancy in the CPU.
diff --git a/lib/misc/threadpool/threadpool.c b/lib/misc/threadpool/threadpool.c
new file mode 100644 (file)
index 0000000..de9bab7
--- /dev/null
@@ -0,0 +1,1013 @@
+/*
+ * libwebsockets - threadpool api
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include <pthread.h>
+
+#include "core/private.h"
+
+#include <string.h>
+#include <stdio.h>
+
+struct lws_threadpool;
+
+struct lws_threadpool_task {
+       struct lws_threadpool_task *task_queue_next;
+
+       struct lws_threadpool *tp;
+       char name[32];
+       struct lws_threadpool_task_args args;
+
+       lws_usec_t created;
+       lws_usec_t acquired;
+       lws_usec_t done;
+       lws_usec_t entered_state;
+
+       lws_usec_t acc_running;
+       lws_usec_t acc_syncing;
+
+       pthread_cond_t wake_idle;
+
+       enum lws_threadpool_task_status status;
+
+       int late_sync_retries;
+
+       char wanted_writeable_cb;
+       char outlive;
+};
+
+struct lws_pool {
+       struct lws_threadpool *tp;
+       pthread_t thread;
+       pthread_mutex_t lock; /* part of task wake_idle */
+       struct lws_threadpool_task *task;
+       lws_usec_t acquired;
+       int worker_index;
+};
+
+struct lws_threadpool {
+       pthread_mutex_t lock; /* protects all pool lists */
+       pthread_cond_t wake_idle;
+       struct lws_pool *pool_list;
+
+       struct lws_context *context;
+       struct lws_threadpool *tp_list; /* context list of threadpools */
+
+       struct lws_threadpool_task *task_queue_head;
+       struct lws_threadpool_task *task_done_head;
+
+       char name[32];
+
+       int threads_in_pool;
+       int queue_depth;
+       int done_queue_depth;
+       int max_queue_depth;
+       int running_tasks;
+
+       unsigned int destroying:1;
+};
+
+static int
+ms_delta(lws_usec_t now, lws_usec_t then)
+{
+       return (int)((now - then) / 1000);
+}
+
+static void
+us_accrue(lws_usec_t *acc, lws_usec_t then)
+{
+       lws_usec_t now = lws_now_usecs();
+
+       *acc += now - then;
+}
+
+static int
+pc_delta(lws_usec_t now, lws_usec_t then, lws_usec_t us)
+{
+       lws_usec_t delta = (now - then) + 1;
+
+       return (int)((us * 100) / delta);
+}
+
+static void
+__lws_threadpool_task_dump(struct lws_threadpool_task *task, char *buf, int len)
+{
+       lws_usec_t now = lws_now_usecs();
+       char *end = buf + len - 1;
+       int syncms = 0, runms = 0;
+
+       if (!task->acquired) {
+               buf += lws_snprintf(buf, end - buf,
+                                   "task: %s, QUEUED queued: %dms",
+                                   task->name, ms_delta(now, task->created));
+
+               return;
+       }
+
+       if (task->acc_running)
+               runms = task->acc_running;
+
+       if (task->acc_syncing)
+               syncms = task->acc_syncing;
+
+       if (!task->done) {
+               buf += lws_snprintf(buf, end - buf,
+                       "task: %s, ONGOING state %d (%dms) alive: %dms "
+                       "(queued %dms, acquired: %dms, "
+                       "run: %d%%, sync: %d%%)", task->name, task->status,
+                       ms_delta(now, task->entered_state),
+                       ms_delta(now, task->created),
+                       ms_delta(task->acquired, task->created),
+                       ms_delta(now, task->acquired),
+                       pc_delta(now, task->acquired, runms),
+                       pc_delta(now, task->acquired, syncms));
+
+               return;
+       }
+
+       buf += lws_snprintf(buf, end - buf,
+               "task: %s, DONE state %d lived: %dms "
+               "(queued %dms, on thread: %dms, "
+               "ran: %d%%, synced: %d%%)", task->name, task->status,
+               ms_delta(task->done, task->created),
+               ms_delta(task->acquired, task->created),
+               ms_delta(task->done, task->acquired),
+               pc_delta(task->done, task->acquired, runms),
+               pc_delta(task->done, task->acquired, syncms));
+}
+
+void
+lws_threadpool_dump(struct lws_threadpool *tp)
+{
+#if defined(_DEBUG)
+       struct lws_threadpool_task **c;
+       char buf[160];
+       int n, count;
+
+       pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */
+
+       lwsl_thread("%s: tp: %s, Queued: %d, Run: %d, Done: %d\n", __func__,
+                   tp->name, tp->queue_depth, tp->running_tasks,
+                   tp->done_queue_depth);
+
+       count = 0;
+       c = &tp->task_queue_head;
+       while (*c) {
+               struct lws_threadpool_task *task = *c;
+               __lws_threadpool_task_dump(task, buf, sizeof(buf));
+               lwsl_thread("  - %s\n", buf);
+               count++;
+
+               c = &(*c)->task_queue_next;
+       }
+
+       if (count != tp->queue_depth)
+               lwsl_err("%s: tp says queue depth %d, but actually %d\n",
+                        __func__, tp->queue_depth, count);
+
+       count = 0;
+       for (n = 0; n < tp->threads_in_pool; n++) {
+               struct lws_pool *pool = &tp->pool_list[n];
+               struct lws_threadpool_task *task = pool->task;
+
+               if (task) {
+                       __lws_threadpool_task_dump(task, buf, sizeof(buf));
+                       lwsl_thread("  - worker %d: %s\n", n, buf);
+                       count++;
+               }
+       }
+
+       if (count != tp->running_tasks)
+               lwsl_err("%s: tp says %d running_tasks, but actually %d\n",
+                        __func__, tp->running_tasks, count);
+
+       count = 0;
+       c = &tp->task_done_head;
+       while (*c) {
+               struct lws_threadpool_task *task = *c;
+               __lws_threadpool_task_dump(task, buf, sizeof(buf));
+               lwsl_thread("  - %s\n", buf);
+               count++;
+
+               c = &(*c)->task_queue_next;
+       }
+
+       if (count != tp->done_queue_depth)
+               lwsl_err("%s: tp says done_queue_depth %d, but actually %d\n",
+                        __func__, tp->done_queue_depth, count);
+
+       pthread_mutex_unlock(&tp->lock); /* --------------- tp unlock */
+#endif
+}
+
+static void
+state_transition(struct lws_threadpool_task *task,
+                enum lws_threadpool_task_status status)
+{
+       task->entered_state = lws_now_usecs();
+       task->status = status;
+}
+
+static void
+lws_threadpool_task_cleanup_destroy(struct lws_threadpool_task *task)
+{
+       if (task->args.cleanup)
+               task->args.cleanup(task->args.wsi, task->args.user);
+
+       if (task->args.wsi)
+               task->args.wsi->tp_task = NULL;
+
+       lwsl_thread("%s: tp %p: cleaned finished task for wsi %p\n",
+                   __func__, task->tp, task->args.wsi);
+
+       lws_free(task);
+}
+
+static void
+__lws_threadpool_reap(struct lws_threadpool_task *task)
+{
+       struct lws_threadpool_task **c, *t = NULL;
+       struct lws_threadpool *tp = task->tp;
+
+       /* remove the task from the done queue */
+
+       c = &tp->task_done_head;
+
+       while (*c) {
+               if ((*c) == task) {
+                       t = *c;
+                       *c = t->task_queue_next;
+                       t->task_queue_next = NULL;
+                       tp->done_queue_depth--;
+
+                       lwsl_thread("%s: tp %s: reaped task wsi %p\n", __func__,
+                                  tp->name, task->args.wsi);
+
+                       break;
+               }
+               c = &(*c)->task_queue_next;
+       }
+
+       if (!t)
+               lwsl_err("%s: task %p not in done queue\n", __func__, task);
+
+       /* call the task's cleanup and delete the task itself */
+
+       lws_threadpool_task_cleanup_destroy(task);
+}
+
+/*
+ * this gets called from each tsi service context after the service was
+ * cancelled... we need to ask for the writable callback from the matching
+ * tsi context for any wsis bound to a worked thread that need it
+ */
+
+int
+lws_threadpool_tsi_context(struct lws_context *context, int tsi)
+{
+       struct lws_threadpool_task **c, *task = NULL;
+       struct lws_threadpool *tp;
+       struct lws *wsi;
+
+       lws_context_lock(context, __func__);
+
+       tp = context->tp_list_head;
+       while (tp) {
+               int n;
+
+               /* for the running (syncing...) tasks... */
+
+               for (n = 0; n < tp->threads_in_pool; n++) {
+                       struct lws_pool *pool = &tp->pool_list[n];
+
+                       task = pool->task;
+                       if (!task)
+                               continue;
+
+                       wsi = task->args.wsi;
+                       if (!wsi || wsi->tsi != tsi ||
+                           !task->wanted_writeable_cb)
+                               continue;
+
+                       task->wanted_writeable_cb = 0;
+                       lws_memory_barrier();
+
+                       /*
+                        * finally... we can ask for the callback on
+                        * writable from the correct service thread
+                        * context
+                        */
+
+                       lws_callback_on_writable(wsi);
+               }
+
+               /* for the done tasks... */
+
+               c = &tp->task_done_head;
+
+               while (*c) {
+                       task = *c;
+                       wsi = task->args.wsi;
+
+                       if (wsi && wsi->tsi == tsi &&
+                           task->wanted_writeable_cb) {
+
+                               task->wanted_writeable_cb = 0;
+                               lws_memory_barrier();
+
+                               /*
+                                * finally... we can ask for the callback on
+                                * writable from the correct service thread
+                                * context
+                                */
+
+                               lws_callback_on_writable(wsi);
+                       }
+
+                       c = &task->task_queue_next;
+               }
+
+               tp = tp->tp_list;
+       }
+
+       lws_context_unlock(context);
+
+       return 0;
+}
+
+static int
+lws_threadpool_worker_sync(struct lws_pool *pool,
+                          struct lws_threadpool_task *task)
+{
+       enum lws_threadpool_task_status temp;
+       struct timespec abstime;
+       struct lws *wsi;
+       int tries = 15;
+
+       /* block until writable acknowledges */
+       lwsl_debug("%s: %p: LWS_TP_RETURN_SYNC in\n", __func__, task);
+       pthread_mutex_lock(&pool->lock); /* ======================= pool lock */
+
+       lwsl_info("%s: %s: task %p (%s): syncing with wsi %p\n", __func__,
+                   pool->tp->name, task, task->name, task->args.wsi);
+
+       temp = task->status;
+       state_transition(task, LWS_TP_STATUS_SYNCING);
+       while (tries--) {
+               wsi = task->args.wsi;
+
+               /*
+                * if the wsi is no longer attached to this task, there is
+                * nothing we can sync to usefully.  Since the work wants to
+                * sync, it means we should react to the situation by telling
+                * the task it can't continue usefully by stopping it.
+                */
+
+               if (!wsi) {
+                       lwsl_thread("%s: %s: task %p (%s): No longer bound to any "
+                                "wsi to sync to\n", __func__, pool->tp->name,
+                                task, task->name);
+
+                       state_transition(task, LWS_TP_STATUS_STOPPING);
+                       goto done;
+               }
+
+               /*
+                * So tries times this is the maximum time between SYNC asking
+                * for a callback on writable and actually getting it we are
+                * willing to sit still for.
+                *
+                * If it is exceeded, we will stop the task.
+                */
+               abstime.tv_sec = time(NULL) + 2;
+               abstime.tv_nsec = 0;
+
+               task->wanted_writeable_cb = 1;
+               lws_memory_barrier();
+
+               /*
+                * This will cause lws_threadpool_tsi_context() to get called
+                * from each tsi service context, where we can safely ask for
+                * a callback on writeable on the wsi we are associated with.
+                */
+               lws_cancel_service(lws_get_context(wsi));
+
+               /*
+                * so the danger here is that we asked for a writable callback
+                * on the wsi, but for whatever reason, we are never going to
+                * get one.  To avoid deadlocking forever, we allow a set time
+                * for the sync to happen naturally, otherwise the cond wait
+                * times out and we stop the task.
+                */
+
+               if (pthread_cond_timedwait(&task->wake_idle, &pool->lock,
+                                          &abstime) == ETIMEDOUT) {
+                       task->late_sync_retries++;
+                       if (!tries) {
+                               lwsl_err("%s: %s: task %p (%s): SYNC timed out "
+                                        "(associated wsi %p)\n",
+                                        __func__, pool->tp->name, task,
+                                        task->name, task->args.wsi);
+
+                               state_transition(task, LWS_TP_STATUS_STOPPING);
+                               goto done;
+                       }
+
+                       continue;
+               } else
+                       break;
+       }
+
+       if (task->status == LWS_TP_STATUS_SYNCING)
+               state_transition(task, temp);
+
+       lwsl_debug("%s: %p: LWS_TP_RETURN_SYNC out\n", __func__, task);
+
+done:
+       pthread_mutex_unlock(&pool->lock); /* ----------------- - pool unlock */
+
+       return 0;
+}
+
+static void *
+lws_threadpool_worker(void *d)
+{
+       struct lws_threadpool_task **c, **c2, *task;
+       struct lws_pool *pool = d;
+       struct lws_threadpool *tp = pool->tp;
+       char buf[160];
+
+       while (!tp->destroying) {
+
+               /* we have no running task... wait and get one from the queue */
+
+               pthread_mutex_lock(&tp->lock); /* =================== tp lock */
+
+               /*
+                * if there's no task already waiting in the queue, wait for
+                * the wake_idle condition to signal us that might have changed
+                */
+               while (!tp->task_queue_head && !tp->destroying)
+                       pthread_cond_wait(&tp->wake_idle, &tp->lock);
+
+               if (tp->destroying) {
+                       pthread_mutex_unlock(&tp->lock);  /* ------ tp unlock */
+                       continue;
+               }
+
+               c = &tp->task_queue_head;
+               c2 = NULL;
+               task = NULL;
+               pool->task = NULL;
+
+               /* look at the queue tail */
+               while (*c) {
+                       c2 = c;
+                       c = &(*c)->task_queue_next;
+               }
+
+               /* is there a task at the queue tail? */
+               if (c2 && *c2) {
+                       pool->task = task = *c2;
+                       task->acquired = pool->acquired = lws_now_usecs();
+                       /* remove it from the queue */
+                       *c2 = task->task_queue_next;
+                       task->task_queue_next = NULL;
+                       tp->queue_depth--;
+                       /* mark it as running */
+                       state_transition(task, LWS_TP_STATUS_RUNNING);
+               }
+
+               /* someone else got it first... wait and try again */
+               if (!task) {
+                       pthread_mutex_unlock(&tp->lock);  /* ------ tp unlock */
+                       continue;
+               }
+
+               task->wanted_writeable_cb = 0;
+
+               /* we have acquired a new task */
+
+               __lws_threadpool_task_dump(task, buf, sizeof(buf));
+
+               lwsl_thread("%s: %s: worker %d ACQUIRING: %s\n",
+                           __func__, tp->name, pool->worker_index, buf);
+               tp->running_tasks++;
+
+               pthread_mutex_unlock(&tp->lock); /* --------------- tp unlock */
+
+               /*
+                * 1) The task can return with LWS_TP_RETURN_CHECKING_IN to
+                * "resurface" periodically, and get called again with
+                * cont = 1 immediately to indicate it is picking up where it
+                * left off if the task is not being "stopped".
+                *
+                * This allows long tasks to respond to requests to stop in
+                * a clean and opaque way.
+                *
+                * 2) The task can return with LWS_TP_RETURN_SYNC to register
+                * a "callback on writable" request on the service thread and
+                * block until it hears back from the WRITABLE handler.
+                *
+                * This allows the work on the thread to be synchronized to the
+                * previous work being dispatched cleanly.
+                *
+                * 3) The task can return with LWS_TP_RETURN_FINISHED to
+                * indicate its work is completed nicely.
+                *
+                * 4) The task can return with LWS_TP_RETURN_STOPPED to indicate
+                * it stopped and cleaned up after incomplete work.
+                */
+
+               do {
+                       lws_usec_t then;
+                       int n;
+
+                       if (tp->destroying || !task->args.wsi) {
+                               lwsl_info("%s: stopping on wsi gone\n", __func__);
+                               state_transition(task, LWS_TP_STATUS_STOPPING);
+                       }
+
+                       then = lws_now_usecs();
+                       n = task->args.task(task->args.user, task->status);
+                       lwsl_debug("   %d, status %d\n", n, task->status);
+                       us_accrue(&task->acc_running, then);
+                       if (n & LWS_TP_RETURN_FLAG_OUTLIVE)
+                               task->outlive = 1;
+                       switch (n & 7) {
+                       case LWS_TP_RETURN_CHECKING_IN:
+                               /* if not destroying the tp, continue */
+                               break;
+                       case LWS_TP_RETURN_SYNC:
+                               if (!task->args.wsi) {
+                                       lwsl_debug("%s: task that wants to "
+                                                   "outlive lost wsi asked "
+                                                   "to sync: bypassed\n",
+                                                   __func__);
+                                       break;
+                               }
+                               /* block until writable acknowledges */
+                               then = lws_now_usecs();
+                               lws_threadpool_worker_sync(pool, task);
+                               us_accrue(&task->acc_syncing, then);
+                               break;
+                       case LWS_TP_RETURN_FINISHED:
+                               state_transition(task, LWS_TP_STATUS_FINISHED);
+                               break;
+                       case LWS_TP_RETURN_STOPPED:
+                               state_transition(task, LWS_TP_STATUS_STOPPED);
+                               break;
+                       }
+               } while (task->status == LWS_TP_STATUS_RUNNING);
+
+               pthread_mutex_lock(&tp->lock); /* =================== tp lock */
+
+               tp->running_tasks--;
+
+               if (pool->task->status == LWS_TP_STATUS_STOPPING)
+                       state_transition(task, LWS_TP_STATUS_STOPPED);
+
+               /* move the task to the done queue */
+
+               pool->task->task_queue_next = tp->task_done_head;
+               tp->task_done_head = task;
+               tp->done_queue_depth++;
+               pool->task->done = lws_now_usecs();
+
+               if (!pool->task->args.wsi &&
+                   (pool->task->status == LWS_TP_STATUS_STOPPED ||
+                    pool->task->status == LWS_TP_STATUS_FINISHED)) {
+
+                       __lws_threadpool_task_dump(pool->task, buf, sizeof(buf));
+                       lwsl_thread("%s: %s: worker %d REAPING: %s\n",
+                                   __func__, tp->name, pool->worker_index,
+                                   buf);
+
+                       /*
+                        * there is no longer any wsi attached, so nothing is
+                        * going to take care of reaping us.  So we must take
+                        * care of it ourselves.
+                        */
+                       __lws_threadpool_reap(pool->task);
+               } else {
+
+                       __lws_threadpool_task_dump(pool->task, buf, sizeof(buf));
+                       lwsl_thread("%s: %s: worker %d DONE: %s\n",
+                                   __func__, tp->name, pool->worker_index,
+                                   buf);
+
+                       /* signal the associated wsi to take a fresh look at
+                        * task status */
+
+                       if (pool->task->args.wsi) {
+                               task->wanted_writeable_cb = 1;
+
+                               lws_cancel_service(
+                                       lws_get_context(pool->task->args.wsi));
+                       }
+               }
+
+               pool->task = NULL;
+               pthread_mutex_unlock(&tp->lock); /* --------------- tp unlock */
+       }
+
+       /* threadpool is being destroyed */
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+struct lws_threadpool *
+lws_threadpool_create(struct lws_context *context,
+                     const struct lws_threadpool_create_args *args,
+                     const char *format, ...)
+{
+       struct lws_threadpool *tp;
+       va_list ap;
+       int n;
+
+       tp = lws_malloc(sizeof(*tp) + (sizeof(struct lws_pool) * args->threads),
+                       "threadpool alloc");
+       if (!tp)
+               return NULL;
+
+       memset(tp, 0, sizeof(*tp) + (sizeof(struct lws_pool) * args->threads));
+       tp->pool_list = (struct lws_pool *)(tp + 1);
+       tp->max_queue_depth = args->max_queue_depth;
+
+       va_start(ap, format);
+       n = vsnprintf(tp->name, sizeof(tp->name) - 1, format, ap);
+       va_end(ap);
+
+       lws_context_lock(context, __func__);
+
+       tp->context = context;
+       tp->tp_list = context->tp_list_head;
+       context->tp_list_head = tp;
+
+       lws_context_unlock(context);
+
+       pthread_mutex_init(&tp->lock, NULL);
+       pthread_cond_init(&tp->wake_idle, NULL);
+
+       for (n = 0; n < args->threads; n++) {
+#if defined(LWS_HAS_PTHREAD_SETNAME_NP)
+               char name[16];
+#endif
+               tp->pool_list[n].tp = tp;
+               tp->pool_list[n].worker_index = n;
+               pthread_mutex_init(&tp->pool_list[n].lock, NULL);
+               if (pthread_create(&tp->pool_list[n].thread, NULL,
+                                  lws_threadpool_worker, &tp->pool_list[n])) {
+                       lwsl_err("thread creation failed\n");
+               } else {
+#if defined(LWS_HAS_PTHREAD_SETNAME_NP)
+                       lws_snprintf(name, sizeof(name), "%s-%d", tp->name, n);
+                       pthread_setname_np(tp->pool_list[n].thread, name);
+#endif
+                       tp->threads_in_pool++;
+               }
+       }
+
+       return tp;
+}
+
+void
+lws_threadpool_finish(struct lws_threadpool *tp)
+{
+       struct lws_threadpool_task **c, *task;
+
+       pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */
+
+       /* nothing new can start, running jobs will abort as STOPPED and the
+        * pool threads will exit ASAP (they are joined in destroy) */
+       tp->destroying = 1;
+
+       /* stop everyone in the pending queue and move to the done queue */
+
+       c = &tp->task_queue_head;
+       while (*c) {
+               task = *c;
+               *c = task->task_queue_next;
+               task->task_queue_next = tp->task_done_head;
+               tp->task_done_head = task;
+               state_transition(task, LWS_TP_STATUS_STOPPED);
+               tp->queue_depth--;
+               tp->done_queue_depth++;
+               task->done = lws_now_usecs();
+
+               c = &task->task_queue_next;
+       }
+
+       pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */
+
+       pthread_cond_broadcast(&tp->wake_idle);
+}
+
+void
+lws_threadpool_destroy(struct lws_threadpool *tp)
+{
+       struct lws_threadpool_task *task, *next;
+       struct lws_threadpool **ptp;
+       void *retval;
+       int n;
+
+       /* remove us from the context list of threadpools */
+
+       lws_context_lock(tp->context, __func__);
+
+       ptp = &tp->context->tp_list_head;
+       while (*ptp) {
+               if (*ptp == tp) {
+                       *ptp = tp->tp_list;
+                       break;
+               }
+               ptp = &(*ptp)->tp_list;
+       }
+
+       lws_context_unlock(tp->context);
+
+
+       pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */
+
+       tp->destroying = 1;
+       pthread_cond_broadcast(&tp->wake_idle);
+       pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */
+
+       lws_threadpool_dump(tp);
+
+       for (n = 0; n < tp->threads_in_pool; n++) {
+               task = tp->pool_list[n].task;
+
+               /* he could be sitting waiting for SYNC */
+
+               if (task != NULL)
+                       pthread_cond_broadcast(&task->wake_idle);
+
+               pthread_join(tp->pool_list[n].thread, &retval);
+               pthread_mutex_destroy(&tp->pool_list[n].lock);
+       }
+       lwsl_info("%s: all threadpools exited\n", __func__);
+
+       task = tp->task_done_head;
+       while (task) {
+               next = task->task_queue_next;
+               lws_threadpool_task_cleanup_destroy(task);
+               tp->done_queue_depth--;
+               task = next;
+       }
+
+       pthread_mutex_destroy(&tp->lock);
+
+       lws_free(tp);
+}
+
+/*
+ * we want to stop and destroy the task and related priv.  The wsi may no
+ * longer exist.
+ */
+
+int
+lws_threadpool_dequeue(struct lws *wsi)
+{
+       struct lws_threadpool *tp;
+       struct lws_threadpool_task **c, *task;
+       int n;
+
+       task = wsi->tp_task;
+       if (!task)
+               return 0;
+
+       tp = task->tp;
+       pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */
+
+       if (task->outlive && !tp->destroying) {
+
+               /* disconnect from wsi, and wsi from task */
+
+               wsi->tp_task = NULL;
+               task->args.wsi = NULL;
+
+               goto bail;
+       }
+
+
+       c = &tp->task_queue_head;
+
+       /* is he queued waiting for a chance to run?  Mark him as stopped and
+        * move him on to the done queue */
+
+       while (*c) {
+               if ((*c) == task) {
+                       *c = task->task_queue_next;
+                       task->task_queue_next = tp->task_done_head;
+                       tp->task_done_head = task;
+                       state_transition(task, LWS_TP_STATUS_STOPPED);
+                       tp->queue_depth--;
+                       tp->done_queue_depth++;
+                       task->done = lws_now_usecs();
+
+                       lwsl_debug("%s: tp %p: removed queued task wsi %p\n",
+                                   __func__, tp, task->args.wsi);
+
+                       break;
+               }
+               c = &(*c)->task_queue_next;
+       }
+
+       /* is he on the done queue? */
+
+       c = &tp->task_done_head;
+       while (*c) {
+               if ((*c) == task) {
+                       *c = task->task_queue_next;
+                       task->task_queue_next = NULL;
+                       lws_threadpool_task_cleanup_destroy(task);
+                       tp->done_queue_depth--;
+                       goto bail;
+               }
+               c = &(*c)->task_queue_next;
+       }
+
+       /* he's not in the queue... is he already running on a thread? */
+
+       for (n = 0; n < tp->threads_in_pool; n++) {
+               if (!tp->pool_list[n].task || tp->pool_list[n].task != task)
+                       continue;
+
+               /*
+                * ensure we don't collide with tests or changes in the
+                * worker thread
+                */
+               pthread_mutex_lock(&tp->pool_list[n].lock);
+
+               /*
+                * mark him as having been requested to stop...
+                * the caller will hear about it in his service thread
+                * context as a request to close
+                */
+               state_transition(task, LWS_TP_STATUS_STOPPING);
+
+               /* disconnect from wsi, and wsi from task */
+
+               task->args.wsi->tp_task = NULL;
+               task->args.wsi = NULL;
+
+               pthread_mutex_unlock(&tp->pool_list[n].lock);
+
+               lwsl_debug("%s: tp %p: request stop running task "
+                           "for wsi %p\n", __func__, tp, task->args.wsi);
+
+               break;
+       }
+
+       if (n == tp->threads_in_pool) {
+               /* can't find it */
+               lwsl_notice("%s: tp %p: no task for wsi %p, decoupling\n",
+                           __func__, tp, task->args.wsi);
+               task->args.wsi->tp_task = NULL;
+               task->args.wsi = NULL;
+       }
+
+bail:
+       pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */
+
+       return 0;
+}
+
+struct lws_threadpool_task *
+lws_threadpool_enqueue(struct lws_threadpool *tp,
+                      const struct lws_threadpool_task_args *args,
+                      const char *format, ...)
+{
+       struct lws_threadpool_task *task = NULL;
+       va_list ap;
+
+       if (tp->destroying)
+               return NULL;
+
+       pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */
+
+       /*
+        * if there's room on the queue, the job always goes on the queue
+        * first, then any free thread may pick it up after the wake_idle
+        */
+
+       if (tp->queue_depth == tp->max_queue_depth) {
+               lwsl_notice("%s: queue reached limit %d\n", __func__,
+                           tp->max_queue_depth);
+
+               goto bail;
+       }
+
+       /*
+        * create the task object
+        */
+
+       task = lws_malloc(sizeof(*task), __func__);
+       if (!task)
+               goto bail;
+
+       memset(task, 0, sizeof(*task));
+       pthread_cond_init(&task->wake_idle, NULL);
+       task->args = *args;
+       task->tp = tp;
+       task->created = lws_now_usecs();
+
+       va_start(ap, format);
+       vsnprintf(task->name, sizeof(task->name) - 1, format, ap);
+       va_end(ap);
+
+       /*
+        * add him on the tp task queue
+        */
+
+       task->task_queue_next = tp->task_queue_head;
+       state_transition(task, LWS_TP_STATUS_QUEUED);
+       tp->task_queue_head = task;
+       tp->queue_depth++;
+
+       /*
+        * mark the wsi itself as depending on this tp (so wsi close for
+        * whatever reason can clean up)
+        */
+
+       args->wsi->tp_task = task;
+
+       lwsl_thread("%s: tp %s: enqueued task %p (%s) for wsi %p, depth %d\n",
+                   __func__, tp->name, task, task->name, args->wsi,
+                   tp->queue_depth);
+
+       /* alert any idle thread there's something new on the task list */
+
+       lws_memory_barrier();
+       pthread_cond_signal(&tp->wake_idle);
+
+bail:
+       pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */
+
+       return task;
+}
+
+/* this should be called from the service thread */
+
+enum lws_threadpool_task_status
+lws_threadpool_task_status_wsi(struct lws *wsi,
+                              struct lws_threadpool_task **task, void **user)
+{
+       enum lws_threadpool_task_status status;
+       struct lws_threadpool *tp;
+
+       *task = wsi->tp_task;
+       if (!*task)
+               return -1;
+
+       tp = (*task)->tp;
+       *user = (*task)->args.user;
+       status = (*task)->status;
+
+       if (status == LWS_TP_STATUS_FINISHED ||
+           status == LWS_TP_STATUS_STOPPED) {
+               char buf[160];
+
+               pthread_mutex_lock(&tp->lock); /* ================ tpool lock */
+               __lws_threadpool_task_dump(*task, buf, sizeof(buf));
+               lwsl_thread("%s: %s: service thread REAPING: %s\n",
+                           __func__, tp->name, buf);
+               __lws_threadpool_reap(*task);
+               lws_memory_barrier();
+               pthread_mutex_unlock(&tp->lock); /* ------------ tpool unlock */
+       }
+
+       return status;
+}
+
+void
+lws_threadpool_task_sync(struct lws_threadpool_task *task, int stop)
+{
+       lwsl_debug("%s\n", __func__);
+
+       if (stop)
+               state_transition(task, LWS_TP_STATUS_STOPPING);
+
+       pthread_cond_signal(&task->wake_idle);
+}
diff --git a/lib/output.c b/lib/output.c
deleted file mode 100644 (file)
index 465cb5f..0000000
+++ /dev/null
@@ -1,871 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-static int
-lws_0405_frame_mask_generate(struct lws *wsi)
-{
-#if 0
-       wsi->u.ws.mask[0] = 0;
-       wsi->u.ws.mask[1] = 0;
-       wsi->u.ws.mask[2] = 0;
-       wsi->u.ws.mask[3] = 0;
-#else
-       int n;
-       /* fetch the per-frame nonce */
-
-       n = lws_get_random(lws_get_context(wsi), wsi->u.ws.mask, 4);
-       if (n != 4) {
-               lwsl_parser("Unable to read from random device %s %d\n",
-                           SYSTEM_RANDOM_FILEPATH, n);
-               return 1;
-       }
-#endif
-       /* start masking from first byte of masking key buffer */
-       wsi->u.ws.mask_idx = 0;
-
-       return 0;
-}
-
-#ifdef _DEBUG
-
-LWS_VISIBLE void lwsl_hexdump(void *vbuf, size_t len)
-{
-       unsigned char *buf = (unsigned char *)vbuf;
-       unsigned int n, m, start;
-       char line[80];
-       char *p;
-
-       lwsl_parser("\n");
-
-       for (n = 0; n < len;) {
-               start = n;
-               p = line;
-
-               p += sprintf(p, "%04X: ", start);
-
-               for (m = 0; m < 16 && n < len; m++)
-                       p += sprintf(p, "%02X ", buf[n++]);
-               while (m++ < 16)
-                       p += sprintf(p, "   ");
-
-               p += sprintf(p, "   ");
-
-               for (m = 0; m < 16 && (start + m) < len; m++) {
-                       if (buf[start + m] >= ' ' && buf[start + m] < 127)
-                               *p++ = buf[start + m];
-                       else
-                               *p++ = '.';
-               }
-               while (m++ < 16)
-                       *p++ = ' ';
-
-               *p++ = '\n';
-               *p = '\0';
-               lwsl_debug("%s", line);
-       }
-       lwsl_debug("\n");
-}
-
-#endif
-
-/*
- * notice this returns number of bytes consumed, or -1
- */
-
-int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
-{
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       size_t real_len = len;
-       unsigned int n;
-       int m;
-
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_WRITE, 1);
-
-       if (!len)
-               return 0;
-       /* just ignore sends after we cleared the truncation buffer */
-       if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE &&
-           !wsi->trunc_len)
-               return len;
-
-       if (wsi->trunc_len && (buf < wsi->trunc_alloc ||
-           buf > (wsi->trunc_alloc + wsi->trunc_len + wsi->trunc_offset))) {
-               char dump[20];
-               strncpy(dump, (char *)buf, sizeof(dump) - 1);
-               dump[sizeof(dump) - 1] = '\0';
-#if defined(LWS_WITH_ESP8266)
-               lwsl_err("****** %p: Sending new %lu (%s), pending truncated ...\n",
-                        wsi, (unsigned long)len, dump);
-#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",
-                        wsi, (unsigned long)len, dump);
-#endif
-               assert(0);
-
-               return -1;
-       }
-
-       m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_DO_SEND, &buf, len);
-       if (m < 0)
-               return -1;
-       if (m) /* handled */ {
-               n = m;
-               goto handle_truncated_send;
-       }
-
-       if (!lws_socket_is_valid(wsi->desc.sockfd))
-               lwsl_warn("** error invalid sock but expected to send\n");
-
-       /* limit sending */
-       if (wsi->protocol->tx_packet_size)
-               n = wsi->protocol->tx_packet_size;
-       else {
-               n = wsi->protocol->rx_buffer_size;
-               if (!n)
-                       n = context->pt_serv_buf_size;
-       }
-       n += LWS_PRE + 4;
-       if (n > len)
-               n = len;
-#if defined(LWS_WITH_ESP8266)  
-       if (wsi->pending_send_completion) {
-               n = 0;
-               goto handle_truncated_send;
-       }
-#endif
-
-       /* nope, send it on the socket directly */
-       lws_latency_pre(context, wsi);
-       n = lws_ssl_capable_write(wsi, buf, n);
-       lws_latency(context, wsi, "send lws_issue_raw", n, n == len);
-
-       //lwsl_notice("lws_ssl_capable_write: %d\n", n);
-
-       switch (n) {
-       case LWS_SSL_CAPABLE_ERROR:
-               /* we're going to close, let close know sends aren't possible */
-               wsi->socket_is_permanently_unusable = 1;
-               return -1;
-       case LWS_SSL_CAPABLE_MORE_SERVICE:
-               /* nothing got sent, not fatal, retry the whole thing later */
-               n = 0;
-               break;
-       }
-
-handle_truncated_send:
-       /*
-        * we were already handling a truncated send?
-        */
-       if (wsi->trunc_len) {
-               lwsl_info("%p partial adv %d (vs %ld)\n", wsi, n, (long)real_len);
-               wsi->trunc_offset += n;
-               wsi->trunc_len -= n;
-
-               if (!wsi->trunc_len) {
-                       lwsl_info("***** %p partial send completed\n", wsi);
-                       /* done with it, but don't free it */
-                       n = real_len;
-                       if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) {
-                               lwsl_info("***** %p signalling to close now\n", wsi);
-                               return -1; /* retry closing now */
-                       }
-               }
-               /* always callback on writeable */
-               lws_callback_on_writable(wsi);
-
-               return n;
-       }
-
-       if ((unsigned int)n == real_len)
-               /* what we just sent went out cleanly */
-               return n;
-
-       /*
-        * Newly truncated send.  Buffer the remainder (it will get
-        * first priority next time the socket is writable)
-        */
-       lwsl_debug("%p new partial sent %d from %lu total\n", wsi, n,
-                   (unsigned long)real_len);
-
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITE_PARTIALS, 1);
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, n);
-
-       /*
-        *  - if we still have a suitable malloc lying around, use it
-        *  - or, if too small, reallocate it
-        *  - or, if no buffer, create it
-        */
-       if (!wsi->trunc_alloc || real_len - n > wsi->trunc_alloc_len) {
-               lws_free(wsi->trunc_alloc);
-
-               wsi->trunc_alloc_len = real_len - n;
-               wsi->trunc_alloc = lws_malloc(real_len - n);
-               if (!wsi->trunc_alloc) {
-                       lwsl_err("truncated send: unable to malloc %lu\n",
-                                (unsigned long)(real_len - n));
-                       return -1;
-               }
-       }
-       wsi->trunc_offset = 0;
-       wsi->trunc_len = real_len - n;
-       memcpy(wsi->trunc_alloc, buf + n, real_len - n);
-
-       /* since something buffered, force it to get another chance to send */
-       lws_callback_on_writable(wsi);
-
-       return real_len;
-}
-
-LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len,
-                         enum lws_write_protocol wp)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       int masked7 = (wsi->mode == LWSCM_WS_CLIENT);
-       unsigned char is_masked_bit = 0;
-       unsigned char *dropmask = NULL;
-       struct lws_tokens eff_buf;
-       int pre = 0, n;
-       size_t orig_len = len;
-
-       if (wsi->parent_carries_io) {
-               struct lws_write_passthru pas;
-
-               pas.buf = buf;
-               pas.len = len;
-               pas.wp = wp;
-               pas.wsi = wsi;
-
-               if (wsi->parent->protocol->callback(wsi->parent,
-                               LWS_CALLBACK_CHILD_WRITE_VIA_PARENT,
-                               wsi->parent->user_space,
-                               (void *)&pas, 0))
-                       return 1;
-
-               return len;
-       }
-
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_LWS_WRITE, 1);
-
-       if ((int)len < 0) {
-               lwsl_err("%s: suspicious len int %d, ulong %lu\n", __func__,
-                               (int)len, (unsigned long)len);
-               return -1;
-       }
-
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_WRITE, len);
-
-#ifdef LWS_WITH_ACCESS_LOG
-       wsi->access_log.sent += len;
-#endif
-       if (wsi->vhost)
-               wsi->vhost->conn_stats.tx += len;
-
-       if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) {
-               /* remove us from the list */
-               struct lws **w = &pt->tx_draining_ext_list;
-
-       //      lwsl_notice("%s: TX EXT DRAINING: Remove from list\n", __func__);
-               wsi->u.ws.tx_draining_ext = 0;
-               /* remove us from context draining ext list */
-               while (*w) {
-                       if (*w == wsi) {
-                               *w = wsi->u.ws.tx_draining_ext_list;
-                               break;
-                       }
-                       w = &((*w)->u.ws.tx_draining_ext_list);
-               }
-               wsi->u.ws.tx_draining_ext_list = NULL;
-               wp = (wsi->u.ws.tx_draining_stashed_wp & 0xc0) |
-                               LWS_WRITE_CONTINUATION;
-
-               lwsl_ext("FORCED draining wp to 0x%02X\n", wp);
-       }
-
-       lws_restart_ws_ping_pong_timer(wsi);
-
-       if (wp == LWS_WRITE_HTTP ||
-           wp == LWS_WRITE_HTTP_FINAL ||
-           wp == LWS_WRITE_HTTP_HEADERS)
-               goto send_raw;
-
-       /* if not in a state to send stuff, then just send nothing */
-
-       if (wsi->state != LWSS_ESTABLISHED &&
-           ((wsi->state != LWSS_RETURNED_CLOSE_ALREADY &&
-             wsi->state != LWSS_AWAITING_CLOSE_ACK) ||
-                           wp != LWS_WRITE_CLOSE))
-               return 0;
-
-       /* if we are continuing a frame that already had its header done */
-
-       if (wsi->u.ws.inside_frame) {
-               lwsl_debug("INSIDE FRAME\n");
-               goto do_more_inside_frame;
-       }
-
-       wsi->u.ws.clean_buffer = 1;
-
-       /*
-        * give a chance to the extensions to modify payload
-        * the extension may decide to produce unlimited payload erratically
-        * (eg, compression extension), so we require only that if he produces
-        * something, it will be a complete fragment of the length known at
-        * the time (just the fragment length known), and if he has
-        * more we will come back next time he is writeable and allow him to
-        * produce more fragments until he's drained.
-        *
-        * This allows what is sent each time it is writeable to be limited to
-        * a size that can be sent without partial sends or blocking, allows
-        * interleaving of control frames and other connection service.
-        */
-       eff_buf.token = (char *)buf;
-       eff_buf.token_len = len;
-
-       switch ((int)wp) {
-       case LWS_WRITE_PING:
-       case LWS_WRITE_PONG:
-       case LWS_WRITE_CLOSE:
-               break;
-       default:
-               lwsl_debug("LWS_EXT_CB_PAYLOAD_TX\n");
-               n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &eff_buf, wp);
-               if (n < 0)
-                       return -1;
-
-               if (n && eff_buf.token_len) {
-                       lwsl_debug("drain len %d\n", (int)eff_buf.token_len);
-                       /* extension requires further draining */
-                       wsi->u.ws.tx_draining_ext = 1;
-                       wsi->u.ws.tx_draining_ext_list = pt->tx_draining_ext_list;
-                       pt->tx_draining_ext_list = wsi;
-                       /* we must come back to do more */
-                       lws_callback_on_writable(wsi);
-                       /*
-                        * keep a copy of the write type for the overall
-                        * action that has provoked generation of these
-                        * fragments, so the last guy can use its FIN state.
-                        */
-                       wsi->u.ws.tx_draining_stashed_wp = wp;
-                       /* this is definitely not actually the last fragment
-                        * because the extension asserted he has more coming
-                        * So make sure this intermediate one doesn't go out
-                        * with a FIN.
-                        */
-                       wp |= LWS_WRITE_NO_FIN;
-               }
-
-               if (eff_buf.token_len && wsi->u.ws.stashed_write_pending) {
-                       wsi->u.ws.stashed_write_pending = 0;
-                       wp = (wp &0xc0) | (int)wsi->u.ws.stashed_write_type;
-               }
-       }
-
-       /*
-        * an extension did something we need to keep... for example, if
-        * compression extension, it has already updated its state according
-        * to this being issued
-        */
-       if ((char *)buf != eff_buf.token) {
-               /*
-                * ext might eat it, but not have anything to issue yet.
-                * In that case we have to follow his lead, but stash and
-                * replace the write type that was lost here the first time.
-                */
-               if (len && !eff_buf.token_len) {
-                       if (!wsi->u.ws.stashed_write_pending)
-                               wsi->u.ws.stashed_write_type = (char)wp & 0x3f;
-                       wsi->u.ws.stashed_write_pending = 1;
-                       return len;
-               }
-               /*
-                * extension recreated it:
-                * need to buffer this if not all sent
-                */
-               wsi->u.ws.clean_buffer = 0;
-       }
-
-       buf = (unsigned char *)eff_buf.token;
-       len = eff_buf.token_len;
-
-       lwsl_debug("%p / %d\n", buf, (int)len);
-
-       if (!buf) {
-               lwsl_err("null buf (%d)\n", (int)len);
-               return -1;
-       }
-
-       switch (wsi->ietf_spec_revision) {
-       case 13:
-               if (masked7) {
-                       pre += 4;
-                       dropmask = &buf[0 - pre];
-                       is_masked_bit = 0x80;
-               }
-
-               switch (wp & 0xf) {
-               case LWS_WRITE_TEXT:
-                       n = LWSWSOPC_TEXT_FRAME;
-                       break;
-               case LWS_WRITE_BINARY:
-                       n = LWSWSOPC_BINARY_FRAME;
-                       break;
-               case LWS_WRITE_CONTINUATION:
-                       n = LWSWSOPC_CONTINUATION;
-                       break;
-
-               case LWS_WRITE_CLOSE:
-                       n = LWSWSOPC_CLOSE;
-                       break;
-               case LWS_WRITE_PING:
-                       n = LWSWSOPC_PING;
-                       break;
-               case LWS_WRITE_PONG:
-                       n = LWSWSOPC_PONG;
-                       break;
-               default:
-                       lwsl_warn("lws_write: unknown write opc / wp\n");
-                       return -1;
-               }
-
-               if (!(wp & LWS_WRITE_NO_FIN))
-                       n |= 1 << 7;
-
-               if (len < 126) {
-                       pre += 2;
-                       buf[-pre] = n;
-                       buf[-pre + 1] = (unsigned char)(len | is_masked_bit);
-               } else {
-                       if (len < 65536) {
-                               pre += 4;
-                               buf[-pre] = n;
-                               buf[-pre + 1] = 126 | is_masked_bit;
-                               buf[-pre + 2] = (unsigned char)(len >> 8);
-                               buf[-pre + 3] = (unsigned char)len;
-                       } else {
-                               pre += 10;
-                               buf[-pre] = n;
-                               buf[-pre + 1] = 127 | is_masked_bit;
-#if defined __LP64__
-                                       buf[-pre + 2] = (len >> 56) & 0x7f;
-                                       buf[-pre + 3] = len >> 48;
-                                       buf[-pre + 4] = len >> 40;
-                                       buf[-pre + 5] = len >> 32;
-#else
-                                       buf[-pre + 2] = 0;
-                                       buf[-pre + 3] = 0;
-                                       buf[-pre + 4] = 0;
-                                       buf[-pre + 5] = 0;
-#endif
-                               buf[-pre + 6] = (unsigned char)(len >> 24);
-                               buf[-pre + 7] = (unsigned char)(len >> 16);
-                               buf[-pre + 8] = (unsigned char)(len >> 8);
-                               buf[-pre + 9] = (unsigned char)len;
-                       }
-               }
-               break;
-       }
-
-do_more_inside_frame:
-
-       /*
-        * Deal with masking if we are in client -> server direction and
-        * the wp demands it
-        */
-
-       if (masked7) {
-               if (!wsi->u.ws.inside_frame)
-                       if (lws_0405_frame_mask_generate(wsi)) {
-                               lwsl_err("frame mask generation failed\n");
-                               return -1;
-                       }
-
-               /*
-                * in v7, just mask the payload
-                */
-               if (dropmask) { /* never set if already inside frame */
-                       for (n = 4; n < (int)len + 4; n++)
-                               dropmask[n] = dropmask[n] ^ wsi->u.ws.mask[
-                                       (wsi->u.ws.mask_idx++) & 3];
-
-                       /* copy the frame nonce into place */
-                       memcpy(dropmask, wsi->u.ws.mask, 4);
-               }
-       }
-
-send_raw:
-       switch ((int)wp) {
-       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_PONG:
-       case LWS_WRITE_PING:
-#ifdef LWS_USE_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;
-                       }
-
-                       if ((wp == LWS_WRITE_HTTP ||
-                            wp == LWS_WRITE_HTTP_FINAL) &&
-                           wsi->u.http.content_length) {
-                               wsi->u.http.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) {
-                                       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) {
-                               lwsl_info("%s: setting END_STREAM\n", __func__);
-                               flags |= LWS_HTTP2_FLAG_END_STREAM;
-                       }
-
-                       return lws_http2_frame_write(wsi, n, flags,
-                                       wsi->u.http2.my_stream_id, len, buf);
-               }
-#endif
-               return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre);
-       default:
-               break;
-       }
-
-       /*
-        * give any active extensions a chance to munge the buffer
-        * before send.  We pass in a pointer to an lws_tokens struct
-        * prepared with the default buffer and content length that's in
-        * there.  Rather than rewrite the default buffer, extensions
-        * that expect to grow the buffer can adapt .token to
-        * point to their own per-connection buffer in the extension
-        * user allocation.  By default with no extensions or no
-        * extension callback handling, just the normal input buffer is
-        * used then so it is efficient.
-        *
-        * callback returns 1 in case it wants to spill more buffers
-        *
-        * This takes care of holding the buffer if send is incomplete, ie,
-        * if wsi->u.ws.clean_buffer is 0 (meaning an extension meddled with
-        * the buffer).  If wsi->u.ws.clean_buffer is 1, it will instead
-        * return to the user code how much OF THE USER BUFFER was consumed.
-        */
-
-       n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre);
-       wsi->u.ws.inside_frame = 1;
-       if (n <= 0)
-               return n;
-
-       if (n == (int)len + pre) {
-               /* everything in the buffer was handled (or rebuffered...) */
-               wsi->u.ws.inside_frame = 0;
-               return orig_len;
-       }
-
-       /*
-        * it is how many bytes of user buffer got sent... may be < orig_len
-        * in which case callback when writable has already been arranged
-        * and user code can call lws_write() again with the rest
-        * later.
-        */
-
-       return n - pre;
-}
-
-LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       struct lws_process_html_args args;
-       lws_filepos_t amount, poss;
-       unsigned char *p;
-#if defined(LWS_WITH_RANGES)
-       unsigned char finished = 0;
-#endif
-       int n, m;
-
-       // lwsl_notice("%s (trunc len %d)\n", __func__, wsi->trunc_len);
-
-       while (wsi->http2_substream || !lws_send_pipe_choked(wsi)) {
-
-               if (wsi->trunc_len) {
-                       if (lws_issue_raw(wsi, wsi->trunc_alloc +
-                                         wsi->trunc_offset,
-                                         wsi->trunc_len) < 0) {
-                               lwsl_info("%s: closing\n", __func__);
-                               goto file_had_it;
-                       }
-                       continue;
-               }
-
-               if (wsi->u.http.filepos == wsi->u.http.filelen)
-                       goto all_sent;
-
-               n = 0;
-
-               p = pt->serv_buf;
-
-#if defined(LWS_WITH_RANGES)
-               if (wsi->u.http.range.count_ranges && !wsi->u.http.range.inside) {
-
-                       lwsl_notice("%s: doing range start %llu\n", __func__, wsi->u.http.range.start);
-
-                       if ((long long)lws_vfs_file_seek_cur(wsi->u.http.fop_fd,
-                                                  wsi->u.http.range.start -
-                                                  wsi->u.http.filepos) < 0)
-                               goto file_had_it;
-
-                       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,
-                                       "_lws\x0d\x0a"
-                                       "Content-Type: %s\x0d\x0a"
-                                       "Content-Range: bytes %llu-%llu/%llu\x0d\x0a"
-                                       "\x0d\x0a",
-                                       wsi->u.http.multipart_content_type,
-                                       wsi->u.http.range.start,
-                                       wsi->u.http.range.end,
-                                       wsi->u.http.range.extent);
-                               p += n;
-                       }
-
-                       wsi->u.http.range.budget = wsi->u.http.range.end -
-                                                  wsi->u.http.range.start + 1;
-                       wsi->u.http.range.inside = 1;
-               }
-#endif
-
-               poss = context->pt_serv_buf_size - n;
-
-               /*
-                * if there is a hint about how much we will do well to send at one time,
-                * restrict ourselves to only trying to send that.
-                */
-               if (wsi->protocol->tx_packet_size && poss > wsi->protocol->tx_packet_size)
-                       poss = wsi->protocol->tx_packet_size;
-
-#if defined(LWS_WITH_RANGES)
-               if (wsi->u.http.range.count_ranges) {
-                       if (wsi->u.http.range.count_ranges > 1)
-                               poss -= 7; /* allow for final boundary */
-                       if (poss > wsi->u.http.range.budget)
-                               poss = wsi->u.http.range.budget;
-               }
-#endif
-               if (wsi->sending_chunked) {
-                       /* we need to drop the chunk size in here */
-                       p += 10;
-                       /* allow for the chunk to grow by 128 in translation */
-                       poss -= 10 + 128;
-               }
-
-               if (lws_vfs_file_read(wsi->u.http.fop_fd, &amount, p, poss) < 0)
-                       goto file_had_it; /* caller will close */
-               
-               //lwsl_notice("amount %ld\n", amount);
-
-               if (wsi->sending_chunked)
-                       n = (int)amount;
-               else
-                       n = (p - pt->serv_buf) + (int)amount;
-               if (n) {
-                       lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
-                                       context->timeout_secs);
-
-                       if (wsi->sending_chunked) {
-                               args.p = (char *)p;
-                               args.len = n;
-                               args.max_len = (unsigned int)poss + 128;
-                               args.final = wsi->u.http.filepos + n ==
-                                            wsi->u.http.filelen;
-                               if (user_callback_handle_rxflow(
-                                    wsi->vhost->protocols[(int)wsi->protocol_interpret_idx].callback, wsi,
-                                    LWS_CALLBACK_PROCESS_HTML,
-                                    wsi->user_space, &args, 0) < 0)
-                                       goto file_had_it;
-                               n = args.len;
-                               p = (unsigned char *)args.p;
-                       } else
-                               p = pt->serv_buf;
-
-#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,
-                                       "_lws\x0d\x0a"); // append trailing boundary
-                               lwsl_debug("added trailing boundary\n");
-                       }
-#endif
-                       m = lws_write(wsi, p, n,
-                                     wsi->u.http.filepos == wsi->u.http.filelen ?
-                                       LWS_WRITE_HTTP_FINAL :
-                                       LWS_WRITE_HTTP
-                               );
-                       if (m < 0)
-                               goto file_had_it;
-
-                       wsi->u.http.filepos += amount;
-
-#if defined(LWS_WITH_RANGES)
-                       if (wsi->u.http.range.count_ranges >= 1) {
-                               wsi->u.http.range.budget -= amount;
-                               if (wsi->u.http.range.budget == 0) {
-                                       lwsl_notice("range budget exhausted\n");
-                                       wsi->u.http.range.inside = 0;
-                                       wsi->u.http.range.send_ctr++;
-
-                                       if (lws_ranges_next(&wsi->u.http.range) < 1) {
-                                               finished = 1;
-                                               goto all_sent;
-                                       }
-                               }
-                       }
-#endif
-
-                       if (m != n) {
-                               /* adjust for what was not sent */
-                               if (lws_vfs_file_seek_cur(wsi->u.http.fop_fd,
-                                                          m - n) ==
-                                                            (unsigned long)-1)
-                                       goto file_had_it;
-                       }
-               }
-all_sent:
-               if ((!wsi->trunc_len && wsi->u.http.filepos == wsi->u.http.filelen)
-#if defined(LWS_WITH_RANGES)
-                   || finished)
-#else
-               )
-#endif
-                    {
-                       wsi->state = LWSS_HTTP;
-                       /* we might be in keepalive, so close it off here */
-                       lws_vfs_file_close(&wsi->u.http.fop_fd);
-                       
-                       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;
-
-                       return 1;  /* >0 indicates completed */
-               }
-       }
-
-       lws_callback_on_writable(wsi);
-
-       return 0; /* indicates further processing must be done */
-
-file_had_it:
-       lws_vfs_file_close(&wsi->u.http.fop_fd);
-
-       return -1;
-}
-
-#if LWS_POSIX
-LWS_VISIBLE int
-lws_ssl_capable_read_no_ssl(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;
-
-       lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1);
-
-       n = recv(wsi->desc.sockfd, (char *)buf, len, 0);
-       if (n >= 0) {
-               if (wsi->vhost)
-                       wsi->vhost->conn_stats.rx += n;
-               lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n);
-               lws_restart_ws_ping_pong_timer(wsi);
-               return n;
-       }
-#if LWS_POSIX
-       if (LWS_ERRNO == LWS_EAGAIN ||
-           LWS_ERRNO == LWS_EWOULDBLOCK ||
-           LWS_ERRNO == LWS_EINTR)
-               return LWS_SSL_CAPABLE_MORE_SERVICE;
-#endif
-       lwsl_notice("error on reading from skt : %d\n", LWS_ERRNO);
-       return LWS_SSL_CAPABLE_ERROR;
-}
-
-LWS_VISIBLE int
-lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
-{
-       int n = 0;
-
-#if LWS_POSIX
-       n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL);
-//     lwsl_info("%s: sent len %d result %d", __func__, len, n);
-       if (n >= 0)
-               return n;
-
-       if (LWS_ERRNO == LWS_EAGAIN ||
-           LWS_ERRNO == LWS_EWOULDBLOCK ||
-           LWS_ERRNO == LWS_EINTR) {
-               if (LWS_ERRNO == LWS_EWOULDBLOCK) {
-                       lws_set_blocking_send(wsi);
-               }
-
-               return LWS_SSL_CAPABLE_MORE_SERVICE;
-       }
-#else
-       (void)n;
-       (void)wsi;
-       (void)buf;
-       (void)len;
-       // !!!
-#endif
-
-       lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", len, wsi->desc.sockfd, n, LWS_ERRNO);
-       return LWS_SSL_CAPABLE_ERROR;
-}
-#endif
-LWS_VISIBLE int
-lws_ssl_pending_no_ssl(struct lws *wsi)
-{
-       (void)wsi;
-#if defined(LWS_WITH_ESP32)
-       return 100;
-#else
-       return 0;
-#endif
-}
diff --git a/lib/parsers.c b/lib/parsers.c
deleted file mode 100644 (file)
index 2f4799e..0000000
+++ /dev/null
@@ -1,1634 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-const unsigned char lextable[] = {
-       #include "lextable.h"
-};
-
-#define FAIL_CHAR 0x08
-
-int LWS_WARN_UNUSED_RESULT
-lextable_decode(int pos, char c)
-{
-       if (c >= 'A' && c <= 'Z')
-               c += 'a' - 'A';
-
-       while (1) {
-               if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */
-                       if ((lextable[pos] & 0x7f) != c)
-                               return -1;
-                       /* fall thru */
-                       pos++;
-                       if (lextable[pos] == FAIL_CHAR)
-                               return -1;
-                       return pos;
-               }
-
-               if (lextable[pos] == FAIL_CHAR)
-                       return -1;
-
-               /* b7 = 0, end or 3-byte */
-               if (lextable[pos] < FAIL_CHAR) /* terminal marker */
-                       return pos;
-
-               if (lextable[pos] == c) /* goto */
-                       return pos + (lextable[pos + 1]) +
-                                               (lextable[pos + 2] << 8);
-               /* fall thru goto */
-               pos += 3;
-               /* continue */
-       }
-}
-
-void
-_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));
-       ah->nfrag = 0;
-       ah->pos = 0;
-       ah->http_response = 0;
-}
-
-// doesn't scrub the ah rxbuffer by default, parent must do if needed
-
-void
-lws_header_table_reset(struct lws *wsi, int autoservice)
-{
-       struct allocated_headers *ah = wsi->u.hdr.ah;
-       struct lws_context_per_thread *pt;
-       struct lws_pollfd *pfd;
-
-       /* if we have the idea we're resetting 'our' ah, must be bound to one */
-       assert(ah);
-       /* ah also concurs with ownership */
-       assert(ah->wsi == wsi);
-
-       _lws_header_table_reset(ah);
-
-        wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
-        wsi->u.hdr.lextable_pos = 0;
-
-       /* since we will restart the ah, our new headers are not completed */
-       wsi->hdr_parsing_completed = 0;
-
-       /* while we hold the ah, keep a timeout on the wsi */
-       lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH,
-                       wsi->vhost->timeout_secs_ah_idle);
-
-       /*
-        * if we inherited pending rx (from socket adoption deferred
-        * processing), apply and free it.
-        */
-       if (wsi->u.hdr.preamble_rx) {
-               memcpy(ah->rx, wsi->u.hdr.preamble_rx,
-                      wsi->u.hdr.preamble_rx_len);
-               ah->rxlen = wsi->u.hdr.preamble_rx_len;
-               lws_free_set_NULL(wsi->u.hdr.preamble_rx);
-
-               if (autoservice) {
-                       lwsl_notice("%s: calling service on readbuf ah\n", __func__);
-
-                       pt = &wsi->context->pt[(int)wsi->tsi];
-
-                       /* unlike a normal connect, we have the headers already
-                        * (or the first part of them anyway)
-                        */
-                       pfd = &pt->fds[wsi->position_in_fds_table];
-                       pfd->revents |= LWS_POLLIN;
-                       lwsl_err("%s: calling service\n", __func__);
-                       lws_service_fd_tsi(wsi->context, pfd, wsi->tsi);
-               }
-       }
-}
-
-int LWS_WARN_UNUSED_RESULT
-lws_header_table_attach(struct lws *wsi, int autoservice)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       struct lws_pollargs pa;
-       struct lws **pwsi;
-       int n;
-
-       lwsl_info("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__, (void *)wsi,
-                (void *)wsi->u.hdr.ah, wsi->tsi, pt->ah_count_in_use);
-
-       /* if we are already bound to one, just clear it down */
-       if (wsi->u.hdr.ah) {
-               lwsl_info("cleardown\n");
-               goto reset;
-       }
-
-       lws_pt_lock(pt);
-       pwsi = &pt->ah_wait_list;
-       while (*pwsi) {
-               if (*pwsi == wsi) {
-                       /* if already waiting on list, if no new ah just ret */
-                       if (pt->ah_count_in_use ==
-                           context->max_http_header_pool) {
-                               lwsl_notice("%s: no free ah to attach\n", __func__);
-                               goto bail;
-                       }
-                       /* new ah.... remove ourselves from waiting list */
-                       *pwsi = wsi->u.hdr.ah_wait_list; /* set our prev to our next */
-                       wsi->u.hdr.ah_wait_list = NULL; /* no next any more */
-                       pt->ah_wait_list_length--;
-                       break;
-               }
-               pwsi = &(*pwsi)->u.hdr.ah_wait_list;
-       }
-       /*
-        * pool is all busy... add us to waiting list and return that we
-        * weren't able to deliver it right now
-        */
-       if (pt->ah_count_in_use == context->max_http_header_pool) {
-               lwsl_info("%s: adding %p to ah waiting list\n", __func__, wsi);
-               wsi->u.hdr.ah_wait_list = pt->ah_wait_list;
-               pt->ah_wait_list = wsi;
-               pt->ah_wait_list_length++;
-
-               /* we cannot accept input then */
-
-               _lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa);
-               goto bail;
-       }
-
-       for (n = 0; n < context->max_http_header_pool; n++)
-               if (!pt->ah_pool[n].in_use)
-                       break;
-
-       /* if the count of in use said something free... */
-       assert(n != context->max_http_header_pool);
-
-       wsi->u.hdr.ah = &pt->ah_pool[n];
-       wsi->u.hdr.ah->in_use = 1;
-       pt->ah_pool[n].wsi = wsi; /* mark our owner */
-       pt->ah_count_in_use++;
-
-       _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa);
-
-       lwsl_info("%s: did attach wsi %p: ah %p: count %d (on exit)\n", __func__,
-                 (void *)wsi, (void *)wsi->u.hdr.ah, pt->ah_count_in_use);
-
-       lws_pt_unlock(pt);
-
-reset:
-
-       /* and reset the rx state */
-       wsi->u.hdr.ah->rxpos = 0;
-       wsi->u.hdr.ah->rxlen = 0;
-
-       lws_header_table_reset(wsi, autoservice);
-       time(&wsi->u.hdr.ah->assigned);
-
-#ifndef LWS_NO_CLIENT
-       if (wsi->state == LWSS_CLIENT_UNCONNECTED)
-               if (!lws_client_connect_via_info2(wsi))
-                       /* our client connect has failed, the wsi
-                        * has been closed
-                        */
-                       return -1;
-#endif
-
-       return 0;
-
-bail:
-       lws_pt_unlock(pt);
-
-       return 1;
-}
-
-void
-lws_header_table_force_to_detachable_state(struct lws *wsi)
-{
-       if (wsi->u.hdr.ah) {
-               wsi->u.hdr.ah->rxpos = -1;
-               wsi->u.hdr.ah->rxlen = -1;
-               wsi->hdr_parsing_completed = 1;
-       }
-}
-
-int
-lws_header_table_is_in_detachable_state(struct lws *wsi)
-{
-       struct allocated_headers *ah = wsi->u.hdr.ah;
-
-       return ah && ah->rxpos == ah->rxlen && wsi->hdr_parsing_completed;
-}
-
-void
-__lws_remove_from_ah_waiting_list(struct lws *wsi)
-{
-        struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       struct lws **pwsi =&pt->ah_wait_list;
-
-       if (wsi->u.hdr.ah)
-               return;
-
-       while (*pwsi) {
-               if (*pwsi == wsi) {
-                       lwsl_info("%s: wsi %p, remv wait\n",
-                                 __func__, wsi);
-                       *pwsi = wsi->u.hdr.ah_wait_list;
-                       wsi->u.hdr.ah_wait_list = NULL;
-                       pt->ah_wait_list_length--;
-                       return;
-               }
-               pwsi = &(*pwsi)->u.hdr.ah_wait_list;
-       }
-}
-
-
-int lws_header_table_detach(struct lws *wsi, int autoservice)
-{
-       struct lws_context *context = wsi->context;
-       struct allocated_headers *ah = wsi->u.hdr.ah;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       struct lws_pollargs pa;
-       struct lws **pwsi;
-       time_t now;
-
-       lws_pt_lock(pt);
-       __lws_remove_from_ah_waiting_list(wsi);
-       lws_pt_unlock(pt);
-
-       if (!ah)
-               return 0;
-
-       lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__,
-                 (void *)wsi, (void *)ah, wsi->tsi,
-                 pt->ah_count_in_use);
-
-       if (wsi->u.hdr.preamble_rx)
-               lws_free_set_NULL(wsi->u.hdr.preamble_rx);
-
-       /* may not be detached while he still has unprocessed rx */
-       if (!lws_header_table_is_in_detachable_state(wsi)) {
-               lwsl_err("%s: %p: CANNOT DETACH rxpos:%d, rxlen:%d, wsi->hdr_parsing_completed = %d\n", __func__, wsi,
-                               ah->rxpos, ah->rxlen, wsi->hdr_parsing_completed);
-               return 0;
-       }
-
-       lws_pt_lock(pt);
-
-       /* we did have an ah attached */
-       time(&now);
-       if (ah->assigned && now - ah->assigned > 3) {
-               /*
-                * we're detaching the ah, but it was held an
-                * unreasonably long time
-                */
-               lwsl_notice("%s: wsi %p: ah held %ds, "
-                           "ah.rxpos %d, ah.rxlen %d, mode/state %d %d,"
-                           "wsi->more_rx_waiting %d\n", __func__, wsi,
-                           (int)(now - ah->assigned),
-                           ah->rxpos, ah->rxlen, wsi->mode, wsi->state,
-                           wsi->more_rx_waiting);
-       }
-
-       ah->assigned = 0;
-
-       /* if we think we're detaching one, there should be one in use */
-       assert(pt->ah_count_in_use > 0);
-       /* and this specific one should have been in use */
-       assert(ah->in_use);
-       wsi->u.hdr.ah = NULL;
-       ah->wsi = NULL; /* no owner */
-
-       pwsi = &pt->ah_wait_list;
-
-       /* oh there is nobody on the waiting list... leave it at that then */
-       if (!*pwsi) {
-               ah->in_use = 0;
-               pt->ah_count_in_use--;
-
-               goto bail;
-       }
-
-       /* somebody else on same tsi is waiting, give it to oldest guy */
-
-       lwsl_info("pt wait list %p\n", *pwsi);
-       while ((*pwsi)->u.hdr.ah_wait_list)
-               pwsi = &(*pwsi)->u.hdr.ah_wait_list;
-
-       wsi = *pwsi;
-       lwsl_info("last wsi in wait list %p\n", wsi);
-
-       wsi->u.hdr.ah = ah;
-       ah->wsi = wsi; /* new owner */
-       /* and reset the rx state */
-       ah->rxpos = 0;
-       ah->rxlen = 0;
-       lws_header_table_reset(wsi, autoservice);
-       time(&wsi->u.hdr.ah->assigned);
-
-       /* clients acquire the ah and then insert themselves in fds table... */
-       if (wsi->position_in_fds_table != -1) {
-               lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi);
-
-               /* 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);
-       }
-
-       /* point prev guy to next guy in list instead */
-       *pwsi = wsi->u.hdr.ah_wait_list;
-       /* the guy who got one is out of the list */
-       wsi->u.hdr.ah_wait_list = NULL;
-       pt->ah_wait_list_length--;
-
-#ifndef LWS_NO_CLIENT
-       if (wsi->state == LWSS_CLIENT_UNCONNECTED) {
-               lws_pt_unlock(pt);
-
-               if (!lws_client_connect_via_info2(wsi)) {
-                       /* our client connect has failed, the wsi
-                        * has been closed
-                        */
-
-                       return -1;
-               }
-               return 0;
-       }
-#endif
-
-       assert(!!pt->ah_wait_list_length == !!(lws_intptr_t)pt->ah_wait_list);
-bail:
-       lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__,
-         (void *)wsi, (void *)ah, wsi->tsi,
-         pt->ah_count_in_use);
-       lws_pt_unlock(pt);
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx)
-{
-       int n;
-
-       if (!wsi->u.hdr.ah)
-               return 0;
-
-       n = wsi->u.hdr.ah->frag_index[h];
-       if (!n)
-               return 0;
-       do {
-               if (!frag_idx)
-                       return wsi->u.hdr.ah->frags[n].len;
-               n = wsi->u.hdr.ah->frags[n].nfrag;
-       } while (frag_idx-- && n);
-
-       return 0;
-}
-
-LWS_VISIBLE int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h)
-{
-       int n;
-       int len = 0;
-
-       if (!wsi->u.hdr.ah)
-               return 0;
-
-       n = wsi->u.hdr.ah->frag_index[h];
-       if (!n)
-               return 0;
-       do {
-               len += wsi->u.hdr.ah->frags[n].len;
-               n = wsi->u.hdr.ah->frags[n].nfrag;
-       } while (n);
-
-       return len;
-}
-
-LWS_VISIBLE int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len,
-                                     enum lws_token_indexes h, int frag_idx)
-{
-       int n = 0;
-       int f;
-
-       if (!wsi->u.hdr.ah)
-               return -1;
-
-       f = wsi->u.hdr.ah->frag_index[h];
-
-       if (!f)
-               return -1;
-
-       while (n < frag_idx) {
-               f = wsi->u.hdr.ah->frags[f].nfrag;
-               if (!f)
-                       return -1;
-               n++;
-       }
-
-       if (wsi->u.hdr.ah->frags[f].len >= len)
-               return -1;
-
-       memcpy(dst, wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[f].offset,
-              wsi->u.hdr.ah->frags[f].len);
-       dst[wsi->u.hdr.ah->frags[f].len] = '\0';
-
-       return wsi->u.hdr.ah->frags[f].len;
-}
-
-LWS_VISIBLE int lws_hdr_copy(struct lws *wsi, char *dst, int len,
-                            enum lws_token_indexes h)
-{
-       int toklen = lws_hdr_total_length(wsi, h);
-       int n;
-
-       if (toklen >= len)
-               return -1;
-
-       if (!wsi->u.hdr.ah)
-               return -1;
-
-       n = wsi->u.hdr.ah->frag_index[h];
-       if (!n)
-               return 0;
-
-       do {
-               strcpy(dst, &wsi->u.hdr.ah->data[wsi->u.hdr.ah->frags[n].offset]);
-               dst += wsi->u.hdr.ah->frags[n].len;
-               n = wsi->u.hdr.ah->frags[n].nfrag;
-       } while (n);
-
-       return toklen;
-}
-
-char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h)
-{
-       int n;
-
-       n = wsi->u.hdr.ah->frag_index[h];
-       if (!n)
-               return NULL;
-
-       return wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[n].offset;
-}
-
-int LWS_WARN_UNUSED_RESULT
-lws_pos_in_bounds(struct lws *wsi)
-{
-       if (wsi->u.hdr.ah->pos < (unsigned int)wsi->context->max_http_header_data)
-               return 0;
-
-       if (wsi->u.hdr.ah->pos == wsi->context->max_http_header_data) {
-               lwsl_err("Ran out of header data space\n");
-               return 1;
-       }
-
-       /*
-        * with these tests everywhere, it should never be able to exceed
-        * the limit, only meet the limit
-        */
-
-       lwsl_err("%s: pos %d, limit %d\n", __func__, wsi->u.hdr.ah->pos,
-                wsi->context->max_http_header_data);
-       assert(0);
-
-       return 1;
-}
-
-int LWS_WARN_UNUSED_RESULT
-lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s)
-{
-       wsi->u.hdr.ah->nfrag++;
-       if (wsi->u.hdr.ah->nfrag == ARRAY_SIZE(wsi->u.hdr.ah->frags)) {
-               lwsl_warn("More hdr frags than we can deal with, dropping\n");
-               return -1;
-       }
-
-       wsi->u.hdr.ah->frag_index[h] = wsi->u.hdr.ah->nfrag;
-
-       wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].offset = wsi->u.hdr.ah->pos;
-       wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len = 0;
-       wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].nfrag = 0;
-
-       do {
-               if (lws_pos_in_bounds(wsi))
-                       return -1;
-
-               wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = *s;
-               if (*s)
-                       wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len++;
-       } while (*s++);
-
-       return 0;
-}
-
-signed char char_to_hex(const char c)
-{
-       if (c >= '0' && c <= '9')
-               return c - '0';
-
-       if (c >= 'a' && c <= 'f')
-               return c - 'a' + 10;
-
-       if (c >= 'A' && c <= 'F')
-               return c - 'A' + 10;
-
-       return -1;
-}
-
-static int LWS_WARN_UNUSED_RESULT
-issue_char(struct lws *wsi, unsigned char c)
-{
-       unsigned short frag_len;
-
-       if (lws_pos_in_bounds(wsi))
-               return -1;
-
-       frag_len = wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len;
-       /*
-        * If we haven't hit the token limit, just copy the character into
-        * the header
-        */
-       if (frag_len < wsi->u.hdr.current_token_limit) {
-               wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c;
-               if (c)
-                       wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len++;
-               return 0;
-       }
-
-       /* Insert a null character when we *hit* the limit: */
-       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,
-                         wsi->u.hdr.current_token_limit);
-       }
-
-       return 1;
-}
-
-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;
-
-       assert(wsi->u.hdr.ah);
-
-       switch (wsi->u.hdr.parser_state) {
-       default:
-
-               lwsl_parser("WSI_TOK_(%d) '%c'\n", wsi->u.hdr.parser_state, c);
-
-               /* collect into malloc'd buffers */
-               /* optional initial space swallow */
-               if (!ah->frags[ah->frag_index[wsi->u.hdr.parser_state]].len &&
-                   c == ' ')
-                       break;
-
-               for (m = 0; m < ARRAY_SIZE(methods); m++)
-                       if (wsi->u.hdr.parser_state == methods[m])
-                               break;
-               if (m == ARRAY_SIZE(methods))
-                       /* it was not any of the methods */
-                       goto check_eol;
-
-               /* special URI processing... end at space */
-
-               if (c == ' ') {
-                       /* enforce starting with / */
-                       if (!ah->frags[ah->nfrag].len)
-                               if (issue_char(wsi, '/') < 0)
-                                       return -1;
-
-                       if (wsi->u.hdr.ups == URIPS_SEEN_SLASH_DOT_DOT) {
-                               /*
-                                * 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] != '/');
-                               }
-                       }
-
-                       /* begin parsing HTTP version: */
-                       if (issue_char(wsi, '\0') < 0)
-                               return -1;
-                       wsi->u.hdr.parser_state = WSI_TOKEN_HTTP;
-                       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;
-                       }
-                       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);
-                       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 = '_';
-
-                       /* 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 &&
-                   c == '\x0d') {
-                       if (wsi->u.hdr.ues != URIES_IDLE)
-                               goto forbid;
-
-                       c = '\0';
-                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
-                       lwsl_parser("*\n");
-               }
-
-               n = issue_char(wsi, c);
-               if ((int)n < 0)
-                       return -1;
-               if (n > 0)
-                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING;
-
-swallow:
-               /* per-protocol end of headers management */
-
-               if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE)
-                       goto set_parsing_complete;
-               break;
-
-               /* collecting and checking a name part */
-       case WSI_TOKEN_NAME_PART:
-               lwsl_parser("WSI_TOKEN_NAME_PART '%c' (mode=%d)\n", c, wsi->mode);
-
-               wsi->u.hdr.lextable_pos =
-                               lextable_decode(wsi->u.hdr.lextable_pos, c);
-               /*
-                * Server needs to look out for unknown methods...
-                */
-               if (wsi->u.hdr.lextable_pos < 0 &&
-                   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]]) {
-                                       /*
-                                        * already had the method, no idea what
-                                        * this crap from the client is, ignore
-                                        */
-                                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING;
-                                       break;
-                               }
-                       /*
-                        * hm it's an unknown http method from a client in fact,
-                        * it cannot be valid http
-                        */
-                       if (m == ARRAY_SIZE(methods)) {
-                               /*
-                                * are we set up to accept raw in these cases?
-                                */
-                               if (lws_check_opt(wsi->vhost->options,
-                                          LWS_SERVER_OPTION_FALLBACK_TO_RAW))
-                                       return 2; /* transition to raw */
-
-                               lwsl_info("Unknown method - dropping\n");
-                               goto forbid;
-                       }
-                       break;
-               }
-               /*
-                * ...otherwise for a client, let him ignore unknown headers
-                * coming from the server
-                */
-               if (wsi->u.hdr.lextable_pos < 0) {
-                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING;
-                       break;
-               }
-
-               if (lextable[wsi->u.hdr.lextable_pos] < FAIL_CHAR) {
-                       /* terminal state */
-
-                       n = ((unsigned int)lextable[wsi->u.hdr.lextable_pos] << 8) |
-                                       lextable[wsi->u.hdr.lextable_pos + 1];
-
-                       lwsl_parser("known hdr %d\n", n);
-                       for (m = 0; m < ARRAY_SIZE(methods); m++)
-                               if (n == methods[m] &&
-                                   ah->frag_index[methods[m]]) {
-                                       lwsl_warn("Duplicated method\n");
-                                       return -1;
-                               }
-
-                       /*
-                        * WSORIGIN is protocol equiv to ORIGIN,
-                        * JWebSocket likes to send it, map to ORIGIN
-                        */
-                       if (n == WSI_TOKEN_SWORIGIN)
-                               n = WSI_TOKEN_ORIGIN;
-
-                       wsi->u.hdr.parser_state = (enum lws_token_indexes)
-                                                       (WSI_TOKEN_GET_URI + n);
-
-                       if (context->token_limits)
-                               wsi->u.hdr.current_token_limit =
-                                       context->token_limits->token_limit[
-                                                      wsi->u.hdr.parser_state];
-                       else
-                               wsi->u.hdr.current_token_limit =
-                                       wsi->context->max_http_header_data;
-
-                       if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE)
-                               goto set_parsing_complete;
-
-                       goto start_fragment;
-               }
-               break;
-
-start_fragment:
-               ah->nfrag++;
-excessive:
-               if (ah->nfrag == ARRAY_SIZE(ah->frags)) {
-                       lwsl_warn("More hdr frags than we can deal with\n");
-                       return -1;
-               }
-
-               ah->frags[ah->nfrag].offset = ah->pos;
-               ah->frags[ah->nfrag].len = 0;
-               ah->frags[ah->nfrag].nfrag = 0;
-
-               n = ah->frag_index[wsi->u.hdr.parser_state];
-               if (!n) { /* first fragment */
-                       ah->frag_index[wsi->u.hdr.parser_state] = ah->nfrag;
-                       break;
-               }
-               /* continuation */
-               while (ah->frags[n].nfrag)
-                       n = ah->frags[n].nfrag;
-               ah->frags[n].nfrag = ah->nfrag;
-
-               if (issue_char(wsi, ' ') < 0)
-                       return -1;
-               break;
-
-               /* skipping arg part of a name we didn't recognize */
-       case WSI_TOKEN_SKIPPING:
-               lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c);
-
-               if (c == '\x0d')
-                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
-               break;
-
-       case WSI_TOKEN_SKIPPING_SAW_CR:
-               lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c);
-               if (wsi->u.hdr.ues != URIES_IDLE)
-                       goto forbid;
-               if (c == '\x0a') {
-                       wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
-                       wsi->u.hdr.lextable_pos = 0;
-               } else
-                       wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING;
-               break;
-               /* we're done, ignore anything else */
-
-       case WSI_PARSING_COMPLETE:
-               lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c);
-               break;
-       }
-
-       return 0;
-
-set_parsing_complete:
-       if (wsi->u.hdr.ues != URIES_IDLE)
-               goto forbid;
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) {
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION))
-                       wsi->ietf_spec_revision =
-                              atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION));
-
-               lwsl_parser("v%02d hdrs completed\n", wsi->ietf_spec_revision);
-       }
-       wsi->u.hdr.parser_state = WSI_PARSING_COMPLETE;
-       wsi->hdr_parsing_completed = 1;
-
-       return 0;
-
-forbid:
-       lwsl_notice(" forbidding on uri sanitation\n");
-       lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
-       return -1;
-}
-
-LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi)
-{
-       return wsi->u.ws.frame_is_binary;
-}
-
-void
-lws_add_wsi_to_draining_ext_list(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-
-       if (wsi->u.ws.rx_draining_ext)
-               return;
-
-       lwsl_ext("%s: RX EXT DRAINING: Adding to list\n", __func__);
-
-       wsi->u.ws.rx_draining_ext = 1;
-       wsi->u.ws.rx_draining_ext_list = pt->rx_draining_ext_list;
-       pt->rx_draining_ext_list = wsi;
-}
-
-void
-lws_remove_wsi_from_draining_ext_list(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       struct lws **w = &pt->rx_draining_ext_list;
-
-       if (!wsi->u.ws.rx_draining_ext)
-               return;
-
-       lwsl_ext("%s: RX EXT DRAINING: Removing from list\n", __func__);
-
-       wsi->u.ws.rx_draining_ext = 0;
-
-       /* remove us from context draining ext list */
-       while (*w) {
-               if (*w == wsi) {
-                       /* if us, point it instead to who we were pointing to */
-                       *w = wsi->u.ws.rx_draining_ext_list;
-                       break;
-               }
-               w = &((*w)->u.ws.rx_draining_ext_list);
-       }
-       wsi->u.ws.rx_draining_ext_list = NULL;
-}
-
-/*
- * client-parser.c: lws_client_rx_sm() needs to be roughly kept in
- *   sync with changes here, esp related to ext draining
- */
-
-int
-lws_rx_sm(struct lws *wsi, unsigned char c)
-{
-       int callback_action = LWS_CALLBACK_RECEIVE;
-       int ret = 0, n, rx_draining_ext = 0;
-       struct lws_tokens eff_buf;
-
-       eff_buf.token = NULL;
-       eff_buf.token_len = 0;
-       if (wsi->socket_is_permanently_unusable)
-               return -1;
-
-       switch (wsi->lws_rx_parse_state) {
-       case LWS_RXPS_NEW:
-               if (wsi->u.ws.rx_draining_ext) {
-                       eff_buf.token = NULL;
-                       eff_buf.token_len = 0;
-                       lws_remove_wsi_from_draining_ext_list(wsi);
-                       rx_draining_ext = 1;
-                       lwsl_debug("%s: doing draining flow\n", __func__);
-
-                       goto drain_extension;
-               }
-               switch (wsi->ietf_spec_revision) {
-               case 13:
-                       /*
-                        * no prepended frame key any more
-                        */
-                       wsi->u.ws.all_zero_nonce = 1;
-                       goto handle_first;
-
-               default:
-                       lwsl_warn("lws_rx_sm: unknown spec version %d\n",
-                                                      wsi->ietf_spec_revision);
-                       break;
-               }
-               break;
-       case LWS_RXPS_04_mask_1:
-               wsi->u.ws.mask[1] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_mask_2;
-               break;
-       case LWS_RXPS_04_mask_2:
-               wsi->u.ws.mask[2] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_mask_3;
-               break;
-       case LWS_RXPS_04_mask_3:
-               wsi->u.ws.mask[3] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-
-               /*
-                * start from the zero'th byte in the XOR key buffer since
-                * this is the start of a frame with a new key
-                */
-
-               wsi->u.ws.mask_idx = 0;
-
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1;
-               break;
-
-       /*
-        *  04 logical framing from the spec (all this is masked when incoming
-        *  and has to be unmasked)
-        *
-        * We ignore the possibility of extension data because we don't
-        * negotiate any extensions at the moment.
-        *
-        *    0                   1                   2                   3
-        *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-        *   +-+-+-+-+-------+-+-------------+-------------------------------+
-        *   |F|R|R|R| opcode|R| Payload len |    Extended payload length    |
-        *   |I|S|S|S|  (4)  |S|     (7)     |             (16/63)           |
-        *   |N|V|V|V|       |V|             |   (if payload len==126/127)   |
-        *   | |1|2|3|       |4|             |                               |
-        *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
-        *   |     Extended payload length continued, if payload len == 127  |
-        *   + - - - - - - - - - - - - - - - +-------------------------------+
-        *   |                               |         Extension data        |
-        *   +-------------------------------+ - - - - - - - - - - - - - - - +
-        *   :                                                               :
-        *   +---------------------------------------------------------------+
-        *   :                       Application data                        :
-        *   +---------------------------------------------------------------+
-        *
-        *  We pass payload through to userland as soon as we get it, ignoring
-        *  FIN.  It's up to userland to buffer it up if it wants to see a
-        *  whole unfragmented block of the original size (which may be up to
-        *  2^63 long!)
-        */
-
-       case LWS_RXPS_04_FRAME_HDR_1:
-handle_first:
-
-               wsi->u.ws.opcode = c & 0xf;
-               wsi->u.ws.rsv = c & 0x70;
-               wsi->u.ws.final = !!((c >> 7) & 1);
-
-               switch (wsi->u.ws.opcode) {
-               case LWSWSOPC_TEXT_FRAME:
-               case LWSWSOPC_BINARY_FRAME:
-                       wsi->u.ws.rsv_first_msg = (c & 0x70);
-                       wsi->u.ws.frame_is_binary =
-                            wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME;
-                       wsi->u.ws.first_fragment = 1;
-                       break;
-               case 3:
-               case 4:
-               case 5:
-               case 6:
-               case 7:
-               case 0xb:
-               case 0xc:
-               case 0xd:
-               case 0xe:
-               case 0xf:
-                       lwsl_info("illegal opcode\n");
-                       return -1;
-               }
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN:
-
-               wsi->u.ws.this_frame_masked = !!(c & 0x80);
-
-               switch (c & 0x7f) {
-               case 126:
-                       /* control frames are not allowed to have big lengths */
-                       if (wsi->u.ws.opcode & 8)
-                               goto illegal_ctl_length;
-
-                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
-                       break;
-               case 127:
-                       /* control frames are not allowed to have big lengths */
-                       if (wsi->u.ws.opcode & 8)
-                               goto illegal_ctl_length;
-
-                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
-                       break;
-               default:
-                       wsi->u.ws.rx_packet_length = c & 0x7f;
-                       if (wsi->u.ws.this_frame_masked)
-                               wsi->lws_rx_parse_state =
-                                               LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-                       else
-                               if (wsi->u.ws.rx_packet_length)
-                                       wsi->lws_rx_parse_state =
-                                       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-                               else {
-                                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                                       goto spill;
-                               }
-                       break;
-               }
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN16_2:
-               wsi->u.ws.rx_packet_length = c << 8;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN16_1:
-               wsi->u.ws.rx_packet_length |= c;
-               if (wsi->u.ws.this_frame_masked)
-                       wsi->lws_rx_parse_state =
-                                       LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-               else
-                       wsi->lws_rx_parse_state =
-                               LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_8:
-               if (c & 0x80) {
-                       lwsl_warn("b63 of length must be zero\n");
-                       /* kill the connection */
-                       return -1;
-               }
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length = ((size_t)c) << 56;
-#else
-               wsi->u.ws.rx_packet_length = 0;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_7:
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 48;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_6:
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 40;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_5:
-#if defined __LP64__
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 32;
-#endif
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_4:
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 24;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_3:
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 16;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_2:
-               wsi->u.ws.rx_packet_length |= ((size_t)c) << 8;
-               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
-               break;
-
-       case LWS_RXPS_04_FRAME_HDR_LEN64_1:
-               wsi->u.ws.rx_packet_length |= ((size_t)c);
-               if (wsi->u.ws.this_frame_masked)
-                       wsi->lws_rx_parse_state =
-                                       LWS_RXPS_07_COLLECT_FRAME_KEY_1;
-               else
-                       wsi->lws_rx_parse_state =
-                               LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
-               wsi->u.ws.mask[0] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
-               wsi->u.ws.mask[1] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
-               wsi->u.ws.mask[2] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
-               break;
-
-       case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
-               wsi->u.ws.mask[3] = c;
-               if (c)
-                       wsi->u.ws.all_zero_nonce = 0;
-               wsi->lws_rx_parse_state =
-                                       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
-               wsi->u.ws.mask_idx = 0;
-               if (wsi->u.ws.rx_packet_length == 0) {
-                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                       goto spill;
-               }
-               break;
-
-
-       case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
-               assert(wsi->u.ws.rx_ubuf);
-
-               if (wsi->u.ws.rx_draining_ext)
-                       goto drain_extension;
-
-               if (wsi->u.ws.rx_ubuf_head + LWS_PRE >=
-                   wsi->u.ws.rx_ubuf_alloc) {
-                       lwsl_err("Attempted overflow \n");
-                       return -1;
-               }
-               if (wsi->u.ws.all_zero_nonce)
-                       wsi->u.ws.rx_ubuf[LWS_PRE +
-                                        (wsi->u.ws.rx_ubuf_head++)] = c;
-               else
-                       wsi->u.ws.rx_ubuf[LWS_PRE +
-                              (wsi->u.ws.rx_ubuf_head++)] =
-                                  c ^ wsi->u.ws.mask[
-                                           (wsi->u.ws.mask_idx++) & 3];
-
-               if (--wsi->u.ws.rx_packet_length == 0) {
-                       /* spill because we have the whole frame */
-                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-                       goto spill;
-               }
-
-               /*
-                * if there's no protocol max frame size given, we are
-                * supposed to default to context->pt_serv_buf_size
-                */
-
-               if (!wsi->protocol->rx_buffer_size &&
-                   wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size)
-                       break;
-               else
-                       if (wsi->protocol->rx_buffer_size &&
-                                       wsi->u.ws.rx_ubuf_head !=
-                                                 wsi->protocol->rx_buffer_size)
-                       break;
-
-               /* spill because we filled our rx buffer */
-spill:
-               /*
-                * is this frame a control packet we should take care of at this
-                * layer?  If so service it and hide it from the user callback
-                */
-
-               lwsl_parser("spill on %s\n", wsi->protocol->name);
-
-               switch (wsi->u.ws.opcode) {
-               case LWSWSOPC_CLOSE:
-
-                       /* is this an acknowledgement of our close? */
-                       if (wsi->state == LWSS_AWAITING_CLOSE_ACK) {
-                               /*
-                                * fine he has told us he is closing too, let's
-                                * finish our close
-                                */
-                               lwsl_parser("seen client close ack\n");
-                               return -1;
-                       }
-                       if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY)
-                               /* if he sends us 2 CLOSE, kill him */
-                               return -1;
-
-                       if (lws_partial_buffered(wsi)) {
-                               /*
-                                * if we're in the middle of something,
-                                * we can't do a normal close response and
-                                * have to just close our end.
-                                */
-                               wsi->socket_is_permanently_unusable = 1;
-                               lwsl_parser("Closing on peer close due to Pending tx\n");
-                               return -1;
-                       }
-
-                       if (user_callback_handle_rxflow(
-                                       wsi->protocol->callback, wsi,
-                                       LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
-                                       wsi->user_space,
-                                       &wsi->u.ws.rx_ubuf[LWS_PRE],
-                                       wsi->u.ws.rx_ubuf_head))
-                               return -1;
-
-                       lwsl_parser("server sees client close packet\n");
-                       wsi->state = LWSS_RETURNED_CLOSE_ALREADY;
-                       /* deal with the close packet contents as a PONG */
-                       wsi->u.ws.payload_is_close = 1;
-                       goto process_as_ping;
-
-               case LWSWSOPC_PING:
-                       lwsl_info("received %d byte ping, sending pong\n",
-                                                wsi->u.ws.rx_ubuf_head);
-
-                       if (wsi->u.ws.ping_pending_flag) {
-                               /*
-                                * there is already a pending ping payload
-                                * we should just log and drop
-                                */
-                               lwsl_parser("DROP PING since one pending\n");
-                               goto ping_drop;
-                       }
-process_as_ping:
-                       /* control packets can only be < 128 bytes long */
-                       if (wsi->u.ws.rx_ubuf_head > 128 - 3) {
-                               lwsl_parser("DROP PING payload too large\n");
-                               goto ping_drop;
-                       }
-
-                       /* stash the pong payload */
-                       memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE,
-                              &wsi->u.ws.rx_ubuf[LWS_PRE],
-                               wsi->u.ws.rx_ubuf_head);
-
-                       wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head;
-                       wsi->u.ws.ping_pending_flag = 1;
-
-                       /* get it sent as soon as possible */
-                       lws_callback_on_writable(wsi);
-ping_drop:
-                       wsi->u.ws.rx_ubuf_head = 0;
-                       return 0;
-
-               case LWSWSOPC_PONG:
-                       lwsl_info("received pong\n");
-                       lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE],
-                                    wsi->u.ws.rx_ubuf_head);
-
-                       if (wsi->pending_timeout == PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) {
-                               lwsl_info("received expected PONG on wsi %p\n", wsi);
-                               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-                       }
-
-                       /* issue it */
-                       callback_action = LWS_CALLBACK_RECEIVE_PONG;
-                       break;
-
-               case LWSWSOPC_TEXT_FRAME:
-               case LWSWSOPC_BINARY_FRAME:
-               case LWSWSOPC_CONTINUATION:
-                       break;
-
-               default:
-                       lwsl_parser("passing opc %x up to exts\n",
-                                   wsi->u.ws.opcode);
-                       /*
-                        * It's something special we can't understand here.
-                        * Pass the payload up to the extension's parsing
-                        * state machine.
-                        */
-
-                       eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE];
-                       eff_buf.token_len = wsi->u.ws.rx_ubuf_head;
-
-                       if (lws_ext_cb_active(wsi, LWS_EXT_CB_EXTENDED_PAYLOAD_RX,
-                                             &eff_buf, 0) <= 0)
-                               /* not handle or fail */
-                               lwsl_ext("ext opc opcode 0x%x unknown\n",
-                                        wsi->u.ws.opcode);
-
-                       wsi->u.ws.rx_ubuf_head = 0;
-                       return 0;
-               }
-
-               /*
-                * No it's real payload, pass it up to the user callback.
-                * It's nicely buffered with the pre-padding taken care of
-                * so it can be sent straight out again using lws_write
-                */
-
-               eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE];
-               eff_buf.token_len = wsi->u.ws.rx_ubuf_head;
-
-drain_extension:
-               lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len);
-
-               if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
-                   wsi->state == LWSS_AWAITING_CLOSE_ACK)
-                       goto already_done;
-
-               n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0);
-               /* eff_buf may be pointing somewhere completely different now,
-                * it's the output
-                */
-               wsi->u.ws.first_fragment = 0;
-               if (n < 0) {
-                       /*
-                        * we may rely on this to get RX, just drop connection
-                        */
-                       wsi->socket_is_permanently_unusable = 1;
-                       return -1;
-               }
-
-               if (rx_draining_ext && eff_buf.token_len == 0)
-                       goto already_done;
-
-               if (n && eff_buf.token_len) {
-                       /* extension had more... main loop will come back */
-                       lws_add_wsi_to_draining_ext_list(wsi);
-               } else
-                       lws_remove_wsi_from_draining_ext_list(wsi);
-
-               if (eff_buf.token_len > 0 ||
-                   callback_action == LWS_CALLBACK_RECEIVE_PONG) {
-                       eff_buf.token[eff_buf.token_len] = '\0';
-
-                       if (wsi->protocol->callback) {
-
-                               if (callback_action == LWS_CALLBACK_RECEIVE_PONG)
-                                       lwsl_info("Doing pong callback\n");
-
-                               ret = user_callback_handle_rxflow(
-                                               wsi->protocol->callback,
-                                               wsi,
-                                               (enum lws_callback_reasons)callback_action,
-                                               wsi->user_space,
-                                               eff_buf.token,
-                                               eff_buf.token_len);
-                       }
-                       else
-                               lwsl_err("No callback on payload spill!\n");
-               }
-
-already_done:
-               wsi->u.ws.rx_ubuf_head = 0;
-               break;
-       }
-
-       return ret;
-
-illegal_ctl_length:
-
-       lwsl_warn("Control frame with xtended length is illegal\n");
-       /* kill the connection */
-       return -1;
-}
-
-LWS_VISIBLE size_t
-lws_remaining_packet_payload(struct lws *wsi)
-{
-       return wsi->u.ws.rx_packet_length;
-}
-
-/* Once we reach LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED, we know how much
- * to expect in that state and can deal with it in bulk more efficiently.
- */
-
-int
-lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf,
-                                  size_t *len)
-{
-       unsigned char *buffer = *buf, mask[4];
-       int buffer_size, n;
-       unsigned int avail;
-       char *rx_ubuf;
-
-       if (wsi->protocol->rx_buffer_size)
-               buffer_size = wsi->protocol->rx_buffer_size;
-       else
-               buffer_size = wsi->context->pt_serv_buf_size;
-       avail = buffer_size - wsi->u.ws.rx_ubuf_head;
-
-       /* do not consume more than we should */
-       if (avail > wsi->u.ws.rx_packet_length)
-               avail = wsi->u.ws.rx_packet_length;
-
-       /* do not consume more than what is in the buffer */
-       if (avail > *len)
-               avail = *len;
-
-       /* we want to leave 1 byte for the parser to handle properly */
-       if (avail <= 1)
-               return 0;
-
-       avail--;
-       rx_ubuf = wsi->u.ws.rx_ubuf + LWS_PRE + wsi->u.ws.rx_ubuf_head;
-       if (wsi->u.ws.all_zero_nonce)
-               memcpy(rx_ubuf, buffer, avail);
-       else {
-
-               for (n = 0; n < 4; n++)
-                       mask[n] = wsi->u.ws.mask[(wsi->u.ws.mask_idx + n) & 3];
-
-               /* deal with 4-byte chunks using unwrapped loop */
-               n = avail >> 2;
-               while (n--) {
-                       *(rx_ubuf++) = *(buffer++) ^ mask[0];
-                       *(rx_ubuf++) = *(buffer++) ^ mask[1];
-                       *(rx_ubuf++) = *(buffer++) ^ mask[2];
-                       *(rx_ubuf++) = *(buffer++) ^ mask[3];
-               }
-               /* and the remaining bytes bytewise */
-               for (n = 0; n < (int)(avail & 3); n++)
-                       *(rx_ubuf++) = *(buffer++) ^ mask[n];
-
-               wsi->u.ws.mask_idx = (wsi->u.ws.mask_idx + avail) & 3;
-       }
-
-       (*buf) += avail;
-       wsi->u.ws.rx_ubuf_head += avail;
-       wsi->u.ws.rx_packet_length -= avail;
-       *len -= avail;
-
-       return avail;
-}
diff --git a/lib/plat/esp32/esp32-fds.c b/lib/plat/esp32/esp32-fds.c
new file mode 100644 (file)
index 0000000..a6fc86c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+void
+lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       pt->fds[pt->fds_count++].revents = 0;
+}
+
+void
+lws_plat_delete_socket_from_fds(struct lws_context *context,
+                                               struct lws *wsi, int m)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       pt->fds_count--;
+}
+
+int
+lws_plat_change_pollfd(struct lws_context *context,
+                     struct lws *wsi, struct lws_pollfd *pfd)
+{
+       return 0;
+}
+
+int
+insert_wsi(const struct lws_context *context, struct lws *wsi)
+{
+    assert(context->lws_lookup[wsi->desc.sockfd -
+                               lws_plat_socket_offset()] == 0);
+
+    context->lws_lookup[wsi->desc.sockfd - \
+                      lws_plat_socket_offset()] = wsi;
+
+    return 0;
+}
\ No newline at end of file
diff --git a/lib/plat/esp32/esp32-file.c b/lib/plat/esp32/esp32-file.c
new file mode 100644 (file)
index 0000000..f2909bb
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int lws_plat_apply_FD_CLOEXEC(int n)
+{
+       return 0;
+}
+
+
+LWS_VISIBLE lws_fop_fd_t IRAM_ATTR
+_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
+                   const char *vpath, lws_fop_flags_t *flags)
+{
+       struct stat stat_buf;
+       lws_fop_fd_t fop_fd;
+       int ret = open(filename, *flags, 0664);
+
+       if (ret < 0)
+               return NULL;
+
+       if (fstat(ret, &stat_buf) < 0)
+               goto bail;
+
+       fop_fd = lws_malloc(sizeof(*fop_fd), "fops open");
+       if (!fop_fd)
+               goto bail;
+
+       fop_fd->fops = fops;
+       fop_fd->fd = ret;
+       fop_fd->flags = *flags;
+       fop_fd->filesystem_priv = NULL; /* we don't use it */
+       fop_fd->pos = 0;
+       fop_fd->len = stat_buf.st_size;
+
+       return fop_fd;
+
+bail:
+       close(ret);
+
+       return NULL;
+}
+
+LWS_VISIBLE int IRAM_ATTR
+_lws_plat_file_close(lws_fop_fd_t *fops_fd)
+{
+       int fd = (*fops_fd)->fd;
+
+       lws_free(*fops_fd);
+       *fops_fd = NULL;
+
+       return close(fd);
+}
+
+LWS_VISIBLE lws_fileofs_t IRAM_ATTR
+_lws_plat_file_seek_cur(lws_fop_fd_t fops_fd, lws_fileofs_t offset)
+{
+       return lseek(fops_fd->fd, offset, SEEK_CUR);
+}
+
+LWS_VISIBLE int IRAM_ATTR
+_lws_plat_file_read(lws_fop_fd_t fops_fd, lws_filepos_t *amount,
+                   uint8_t *buf, lws_filepos_t len)
+{
+       long n;
+
+       n = read(fops_fd->fd, buf, len);
+       if (n == -1) {
+               *amount = 0;
+               return -1;
+       }
+       fops_fd->pos += n;
+       *amount = n;
+
+       return 0;
+}
+
+LWS_VISIBLE int IRAM_ATTR
+_lws_plat_file_write(lws_fop_fd_t fops_fd, lws_filepos_t *amount,
+                    uint8_t *buf, lws_filepos_t len)
+{
+       long n;
+
+       n = write(fops_fd->fd, buf, len);
+       if (n == -1) {
+               *amount = 0;
+               return -1;
+       }
+       fops_fd->pos += n;
+       *amount = n;
+
+       return 0;
+}
+
+#if defined(LWS_AMAZON_RTOS)
+int
+lws_find_string_in_file(const char *filename, const char *string, int stringlen)
+{
+    return 0;
+}
+#else
+int
+lws_find_string_in_file(const char *filename, const char *string, int stringlen)
+{
+       nvs_handle nvh;
+       size_t s;
+       int n;
+       char buf[64], result[64];
+       const char *p = strchr(string, ':'), *q;
+
+       if (!p)
+               return 0;
+
+       q = string;
+       n = 0;
+       while (n < sizeof(buf) - 1 && q != p)
+               buf[n++] = *q++;
+       buf[n] = '\0';
+
+       ESP_ERROR_CHECK(nvs_open(filename, NVS_READWRITE, &nvh));
+
+       s = sizeof(result) - 1;
+       n = nvs_get_str(nvh, buf, result, &s);
+       nvs_close(nvh);
+
+       if (n != ESP_OK)
+               return 0;
+
+       return !strcmp(p + 1, result);
+}
+#endif
+
+#if !defined(LWS_AMAZON_RTOS)
+LWS_VISIBLE int
+lws_plat_write_file(const char *filename, void *buf, int len)
+{
+       nvs_handle nvh;
+       int n;
+
+       if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+               lwsl_notice("%s: failed to open nvs\n", __func__);
+               return -1;
+       }
+
+       n = nvs_set_blob(nvh, filename, buf, len);
+       if (n >= 0)
+               nvs_commit(nvh);
+
+       nvs_close(nvh);
+
+       lwsl_notice("%s: wrote %s (%d)\n", __func__, filename, n);
+
+       return n;
+}
+
+/* we write vhostname.cert.pem and vhostname.key.pem, 0 return means OK */
+
+LWS_VISIBLE int
+lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
+                       int len)
+{
+       const char *name = vhost->tls.alloc_cert_path;
+
+       if (is_key)
+               name = vhost->tls.key_path;
+
+       return lws_plat_write_file(name, buf, len) < 0;
+}
+
+LWS_VISIBLE int
+lws_plat_read_file(const char *filename, void *buf, int len)
+{
+       nvs_handle nvh;
+       size_t s = 0;
+       int n = 0;
+
+       if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+               lwsl_notice("%s: failed to open nvs\n", __func__);
+               return 1;
+       }
+
+       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+       if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK)
+               goto bail;
+       if (s > (size_t)len)
+               goto bail;
+
+       n = nvs_get_blob(nvh, filename, buf, &s);
+
+       nvs_close(nvh);
+
+       lwsl_notice("%s: read %s (%d)\n", __func__, filename, (int)s);
+
+       if (n)
+               return -1;
+
+       return (int)s;
+
+bail:
+       nvs_close(nvh);
+
+       return -1;
+}
+#endif /* LWS_AMAZON_RTOS */
similarity index 53%
rename from lib/lws-plat-esp32.c
rename to lib/plat/esp32/esp32-helpers.c
index 2765416..7ecaf6f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * libwebsockets - small server side websockets and web server implementation
+ * libwebsockets - lib/plat/lws-plat-esp32.c
  *
  * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
  *
  *  MA  02110-1301  USA
  */
 
-#include "private-libwebsockets.h"
-#include "freertos/timers.h"
-#include <esp_attr.h>
-#include <esp_system.h>
+#include "core/private.h"
 
-/*
- * included from libwebsockets.c for unix builds
- */
-
-unsigned long long time_in_microseconds(void)
-{
-       struct timeval tv;
-       gettimeofday(&tv, NULL);
-       return ((unsigned long long)tv.tv_sec * 1000000LL) + tv.tv_usec;
-}
-
-LWS_VISIBLE int
-lws_get_random(struct lws_context *context, void *buf, int len)
-{
-       uint8_t *pb = buf;
-
-       while (len) {
-               uint32_t r = esp_random();
-               uint8_t *p = (uint8_t *)&r;
-               int b = 4;
-
-               if (len < b)
-                       b = len;
-
-               len -= b;
-
-               while (b--)
-                       *pb++ = p[b];
-       }
-
-       return pb - (uint8_t *)buf;
-}
-
-LWS_VISIBLE int
-lws_send_pipe_choked(struct lws *wsi)
-{
-       fd_set writefds;
-       struct timeval tv = { 0, 0 };
-
-       /* treat the fact we got a truncated send pending as if we're choked */
-       if (wsi->trunc_len)
-               return 1;
-
-       FD_ZERO(&writefds);
-       FD_SET(wsi->desc.sockfd, &writefds);
-
-       if (select(wsi->desc.sockfd + 1, NULL, &writefds, NULL, &tv) < 1)
-               return 1;
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_poll_listen_fd(struct lws_pollfd *fd)
-{
-       fd_set readfds;
-       struct timeval tv = { 0, 0 };
-
-       FD_ZERO(&readfds);
-       FD_SET(fd->fd, &readfds);
-
-       return select(fd->fd + 1, &readfds, NULL, NULL, &tv);
-}
-
-LWS_VISIBLE void
-lws_cancel_service_pt(struct lws *wsi)
-{
-}
-
-LWS_VISIBLE void
-lws_cancel_service(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
-{
-       printf("%d: %s", level, line);
-}
-
-LWS_VISIBLE LWS_EXTERN int
-_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
-{
-       struct lws_context_per_thread *pt;
-       int n = -1, m, c;
-
-       /* stay dead once we are dead */
-
-       if (!context || !context->vhost_list)
-               return 1;
-
-       pt = &context->pt[tsi];
-       lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1);
-
-       if (timeout_ms < 0)
-               goto faked_service;
-
-       if (!context->service_tid_detected) {
-               struct lws _lws;
-
-               memset(&_lws, 0, sizeof(_lws));
-               _lws.context = context;
-
-               context->service_tid_detected =
-                       context->vhost_list->protocols[0].callback(
-                       &_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
-       }
-       context->service_tid = context->service_tid_detected;
-
-       /*
-        * is there anybody with pending stuff that needs service forcing?
-        */
-       if (!lws_service_adjust_timeout(context, 1, tsi)) {
-               /* -1 timeout means just do forced service */
-               _lws_plat_service_tsi(context, -1, pt->tid);
-               /* still somebody left who wants forced service? */
-               if (!lws_service_adjust_timeout(context, 1, pt->tid))
-                       /* yes... come back again quickly */
-                       timeout_ms = 0;
-       }
-
-//     n = poll(pt->fds, pt->fds_count, timeout_ms);
-       {
-               fd_set readfds, writefds, errfds;
-               struct timeval tv = { timeout_ms / 1000,
-                                     (timeout_ms % 1000) * 1000 }, *ptv = &tv;
-               int max_fd = 0;
-               FD_ZERO(&readfds);
-               FD_ZERO(&writefds);
-               FD_ZERO(&errfds);
-
-               for (n = 0; n < pt->fds_count; n++) {
-                       pt->fds[n].revents = 0;
-                       if (pt->fds[n].fd >= max_fd)
-                               max_fd = pt->fds[n].fd;
-                       if (pt->fds[n].events & LWS_POLLIN)
-                               FD_SET(pt->fds[n].fd, &readfds);
-                       if (pt->fds[n].events & LWS_POLLOUT)
-                               FD_SET(pt->fds[n].fd, &writefds);
-                       FD_SET(pt->fds[n].fd, &errfds);
-               }
-
-               n = select(max_fd + 1, &readfds, &writefds, &errfds, ptv);
-               for (n = 0; n < pt->fds_count; n++) {
-                       if (FD_ISSET(pt->fds[n].fd, &readfds))
-                               pt->fds[n].revents |= LWS_POLLIN;
-                       if (FD_ISSET(pt->fds[n].fd, &writefds))
-                               pt->fds[n].revents |= LWS_POLLOUT;
-                       if (FD_ISSET(pt->fds[n].fd, &errfds))
-                               pt->fds[n].revents |= LWS_POLLHUP;
-               }
-       }
-
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if (!pt->rx_draining_ext_list &&
-           !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
-#else
-       if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
-#endif
-               lws_service_fd_tsi(context, NULL, tsi);
-               return 0;
-       }
-
-faked_service:
-       m = lws_service_flag_pending(context, tsi);
-       if (m)
-               c = -1; /* unknown limit */
-       else
-               if (n < 0) {
-                       if (LWS_ERRNO != LWS_EINTR)
-                               return -1;
-                       return 0;
-               } else
-                       c = n;
-
-       /* any socket with events to service? */
-       for (n = 0; n < pt->fds_count && c; n++) {
-               if (!pt->fds[n].revents)
-                       continue;
-
-               c--;
-
-               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
-               if (m < 0)
-                       return -1;
-               /* if something closed, retry this slot */
-               if (m)
-                       n--;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_check_connection_error(struct lws *wsi)
-{
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_plat_service(struct lws_context *context, int timeout_ms)
-{
-       return _lws_plat_service_tsi(context, timeout_ms, 0);
-}
-
-LWS_VISIBLE int
-lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
-{
-       int optval = 1;
-       socklen_t optlen = sizeof(optval);
-
-#if defined(__APPLE__) || \
-    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
-    defined(__NetBSD__) || \
-    defined(__OpenBSD__)
-       struct protoent *tcp_proto;
-#endif
-
-       if (vhost->ka_time) {
-               /* enable keepalive on this socket */
-               optval = 1;
-               if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-
-#if defined(__APPLE__) || \
-    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
-    defined(__NetBSD__) || \
-        defined(__CYGWIN__) || defined(__OpenBSD__) || defined (__sun)
-
-               /*
-                * didn't find a way to set these per-socket, need to
-                * tune kernel systemwide values
-                */
-#else
-               /* set the keepalive conditions we want on it too */
-               optval = vhost->ka_time;
-               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-
-               optval = vhost->ka_interval;
-               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-
-               optval = vhost->ka_probes;
-               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT,
-                              (const void *)&optval, optlen) < 0)
-                       return 1;
-#endif
-       }
-
-       /* Disable Nagle */
-       optval = 1;
-//     if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
-//             return 1;
-       if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &optval, optlen) < 0)
-               return 1;
-
-       /* We are nonblocking... */
-       if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
-               return 1;
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_context_early_init(void)
-{
-       //signal(SIGPIPE, SIG_IGN);
-
-//     signal(SIGABRT, sigabrt_handler);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_plat_context_early_destroy(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE void
-lws_plat_context_late_destroy(struct lws_context *context)
-{
-#ifdef LWS_WITH_PLUGINS
-       if (context->plugin_list)
-               lws_plat_plugins_destroy(context);
-#endif
-
-       if (context->lws_lookup)
-               lws_free(context->lws_lookup);
-}
-
-/* cast a struct sockaddr_in6 * into addr for ipv6 */
-
-LWS_VISIBLE int
-lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
-                   size_t addrlen)
-{
-#if 0
-       int rc = -1;
-
-       struct ifaddrs *ifr;
-       struct ifaddrs *ifc;
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
-#endif
-
-       getifaddrs(&ifr);
-       for (ifc = ifr; ifc != NULL && rc; ifc = ifc->ifa_next) {
-               if (!ifc->ifa_addr)
-                       continue;
-
-               lwsl_info(" interface %s vs %s\n", ifc->ifa_name, ifname);
-
-               if (strcmp(ifc->ifa_name, ifname))
-                       continue;
-
-               switch (ifc->ifa_addr->sa_family) {
-               case AF_INET:
-#ifdef LWS_USE_IPV6
-                       if (ipv6) {
-                               /* map IPv4 to IPv6 */
-                               bzero((char *)&addr6->sin6_addr,
-                                               sizeof(struct in6_addr));
-                               addr6->sin6_addr.s6_addr[10] = 0xff;
-                               addr6->sin6_addr.s6_addr[11] = 0xff;
-                               memcpy(&addr6->sin6_addr.s6_addr[12],
-                                       &((struct sockaddr_in *)ifc->ifa_addr)->sin_addr,
-                                                       sizeof(struct in_addr));
-                       } else
-#endif
-                               memcpy(addr,
-                                       (struct sockaddr_in *)ifc->ifa_addr,
-                                                   sizeof(struct sockaddr_in));
-                       break;
-#ifdef LWS_USE_IPV6
-               case AF_INET6:
-                       memcpy(&addr6->sin6_addr,
-                         &((struct sockaddr_in6 *)ifc->ifa_addr)->sin6_addr,
-                                                      sizeof(struct in6_addr));
-                       break;
-#endif
-               default:
-                       continue;
-               }
-               rc = 0;
-       }
-
-       freeifaddrs(ifr);
-
-       if (rc == -1) {
-               /* check if bind to IP address */
-#ifdef LWS_USE_IPV6
-               if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1)
-                       rc = 0;
-               else
-#endif
-               if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1)
-                       rc = 0;
-       }
-
-       return rc;
-#endif
-
-       return -1;
-}
-
-LWS_VISIBLE void
-lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       pt->fds[pt->fds_count++].revents = 0;
-}
-
-LWS_VISIBLE void
-lws_plat_delete_socket_from_fds(struct lws_context *context,
-                                               struct lws *wsi, int m)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       pt->fds_count--;
-}
-
-LWS_VISIBLE void
-lws_plat_service_periodic(struct lws_context *context)
-{
-}
-
-LWS_VISIBLE int
-lws_plat_change_pollfd(struct lws_context *context,
-                     struct lws *wsi, struct lws_pollfd *pfd)
-{
-       return 0;
-}
-
-LWS_VISIBLE const char *
-lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
-{
-       return inet_ntop(af, src, dst, cnt);
-}
-
-LWS_VISIBLE int
-lws_plat_inet_pton(int af, const char *src, void *dst)
-{
-       return 1; //  inet_pton(af, src, dst);
-}
-
-LWS_VISIBLE lws_fop_fd_t IRAM_ATTR
-_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
-                   const char *vpath, lws_fop_flags_t *flags)
-{
-       struct stat stat_buf;
-       lws_fop_fd_t fop_fd;
-       int ret = open(filename, *flags, 0664);
-
-       if (ret < 0)
-               return NULL;
-
-       if (fstat(ret, &stat_buf) < 0)
-               goto bail;
-
-       fop_fd = malloc(sizeof(*fop_fd));
-       if (!fop_fd)
-               goto bail;
-
-       fop_fd->fops = fops;
-       fop_fd->fd = ret;
-       fop_fd->flags = *flags;
-       fop_fd->filesystem_priv = NULL; /* we don't use it */
-       fop_fd->pos = 0;
-       fop_fd->len = stat_buf.st_size;
-
-       return fop_fd;
-
-bail:
-       close(ret);
-
-       return NULL;
-}
-
-LWS_VISIBLE int IRAM_ATTR
-_lws_plat_file_close(lws_fop_fd_t *fops_fd)
-{
-       int fd = (*fops_fd)->fd;
-
-       free(*fops_fd);
-       *fops_fd = NULL;
-
-       return close(fd);
-}
-
-LWS_VISIBLE lws_fileofs_t IRAM_ATTR
-_lws_plat_file_seek_cur(lws_fop_fd_t fops_fd, lws_fileofs_t offset)
-{
-       return lseek(fops_fd->fd, offset, SEEK_CUR);
-}
-
-LWS_VISIBLE int IRAM_ATTR
-_lws_plat_file_read(lws_fop_fd_t fops_fd, lws_filepos_t *amount,
-                   uint8_t *buf, lws_filepos_t len)
-{
-       long n;
-
-       n = read(fops_fd->fd, buf, len);
-       if (n == -1) {
-               *amount = 0;
-               return -1;
-       }
-       fops_fd->pos += n;
-       *amount = n;
-
-       return 0;
-}
-
-LWS_VISIBLE int IRAM_ATTR
-_lws_plat_file_write(lws_fop_fd_t fops_fd, lws_filepos_t *amount,
-                    uint8_t *buf, lws_filepos_t len)
-{
-       long n;
-
-       n = write(fops_fd->fd, buf, len);
-       if (n == -1) {
-               *amount = 0;
-               return -1;
-       }
-       fops_fd->pos += n;
-       *amount = n;
-
-       return 0;
-}
-
-
-LWS_VISIBLE int
-lws_plat_init(struct lws_context *context,
-             struct lws_context_creation_info *info)
-{
-       /* master context has the global fd lookup array */
-       context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
-                                        context->max_fds);
-       if (context->lws_lookup == NULL) {
-               lwsl_err("OOM on lws_lookup array for %d connections\n",
-                        context->max_fds);
-               return 1;
-       }
-
-       lwsl_notice(" mem: platform fd map: %5lu bytes\n",
-                   (unsigned long)(sizeof(struct lws *) * context->max_fds));
-
-#ifdef LWS_WITH_PLUGINS
-       if (info->plugin_dirs)
-               lws_plat_plugins_init(context, info->plugin_dirs);
-#endif
-
-       return 0;
-}
-
-
-LWS_VISIBLE void esp32_uvtimer_cb(TimerHandle_t t)
-{
-       struct timer_mapping *p = pvTimerGetTimerID(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"
+#include "misc/romfs.h"
 #include <esp_ota_ops.h>
 #include <tcpip_adapter.h>
 #include <esp_image_format.h>
@@ -587,7 +32,6 @@ char *ERR_error_string(unsigned long e, char *buf)
 struct lws_esp32 lws_esp32 = {
        .model = CONFIG_LWS_MODEL_NAME,
        .serial = "unknown",
-       .region = WIFI_COUNTRY_US, // default to safest option
 };
 
 /*
@@ -595,41 +39,46 @@ struct lws_esp32 lws_esp32 = {
  */
 
 enum lws_gapss {
-       LWS_GAPSS_INITIAL,      /* just started up, init and move to LWS_GAPSS_SCAN */
+       LWS_GAPSS_INITIAL,      /* just started up, init and move to
+                                * LWS_GAPSS_SCAN */
        LWS_GAPSS_SCAN,         /*
-                                * Unconnected, scanning: AP known in one of the config
-                                * slots -> configure it, start timeout + LWS_GAPSS_STAT,
-                                * if no AP already up in same group with lower MAC,
-                                * after a random period start up our AP (LWS_GAPSS_AP)
+                                * Unconnected, scanning: AP known in one of the
+                                * config slots -> configure it, start timeout +
+                                * LWS_GAPSS_STAT, if no AP already up in same
+                                * group with lower MAC, after a random period
+                                * start up our AP (LWS_GAPSS_AP)
                                 */
        LWS_GAPSS_AP,           /*
-                                * Trying to be the group AP... periodically do a scan
-                                * LWS_GAPSS_AP_SCAN, faster and then slower
+                                * Trying to be the group AP... periodically do
+                                * a scan LWS_GAPSS_AP_SCAN, faster and then
+                                * slower
                                         */
        LWS_GAPSS_AP_SCAN,      /*
-                                * doing a scan while trying to be the group AP... if
-                                * we see a lower MAC being the AP for the same group
-                                * AP, abandon being an AP and join that AP as a
-                                * station
+                                * doing a scan while trying to be the group
+                                * AP... if we see a lower MAC being the AP for
+                                * the same group AP, abandon being an AP and
+                                * join that AP as a station
                                 */
        LWS_GAPSS_STAT_GRP_AP,  /*
-                                * We have decided to join another group member who is
-                                * being the AP, as its MAC is lower than ours.  This
-                                * is a stable state, but we still do periodic scans
-                                * (LWS_GAPSS_STAT_GRP_AP_SCAN) and will always prefer
-                                * an AP configured in a slot.
+                                * We have decided to join another group member
+                                * who is being the AP, as its MAC is lower than
+                                * ours.  This is a stable state, but we still
+                                * do periodic scans LWS_GAPSS_STAT_GRP_AP_SCAN
+                                * and will always prefer an AP configured in a
+                                * slot.
                                 */
        LWS_GAPSS_STAT_GRP_AP_SCAN,
                                /*
-                                * We have joined a group member who is doing the AP
-                                * job... we want to check every now and then if a
-                                * configured AP has appeared that we should better
-                                * use instead.  Otherwise stay in LWS_GAPSS_STAT_GRP_AP
+                                * We have joined a group member who is doing
+                                * the AP job... we want to check every now and
+                                * then if a configured AP has appeared that we
+                                * should better use instead.  Otherwise stay in
+                                * LWS_GAPSS_STAT_GRP_AP
                                 */
        LWS_GAPSS_STAT,         /*
-                                * trying to connect to another non-group AP.  If we
-                                * don't get an IP within a timeout and retries,
-                                * blacklist it and go back 
+                                * trying to connect to another non-group AP.
+                                * If we don't get an IP within a timeout and
+                                * retries, blacklist it and go back
                                 */
        LWS_GAPSS_STAT_HAPPY,
 };
@@ -646,13 +95,15 @@ static const char *gapss_str[] = {
 };
 
 static romfs_t lws_esp32_romfs;
-static TimerHandle_t leds_timer, scan_timer, debounce_timer
+static TimerHandle_t leds_timer, scan_timer, debounce_timer, association_timer
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
 , mdns_timer
 #endif
 ;
 static enum lws_gapss gapss = LWS_GAPSS_INITIAL;
-static char bdown;
+#if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
+static mdns_result_t *mdns_results_head;
+#endif
 
 #define GPIO_SW 14
 
@@ -749,6 +200,28 @@ static void lws_esp32_scan_timer_cb(TimerHandle_t th)
                lwsl_err("scan start failed %d\n", n);
 }
 
+static void lws_esp32_assoc_timer_cb(TimerHandle_t th)
+{
+       int n;
+
+       xTimerStop(association_timer, 0);
+
+       if (gapss == LWS_GAPSS_STAT_HAPPY) {
+               lwsl_debug("%s: saw we were happy\n", __func__);
+
+               return;
+       }
+
+       lwsl_notice("%s: forcing rescan\n", __func__);
+
+       lws_gapss_to(LWS_GAPSS_SCAN);
+       scan_ongoing = 0;
+       n = esp_wifi_scan_start(&scan_config, false);
+       if (n != ESP_OK)
+               lwsl_err("scan start failed %d\n", n);
+}
+
+
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
 
 void __attribute__(( weak ))
@@ -769,57 +242,42 @@ void lws_group_member_event_call(int e, void *p)
 }
 
 static int
-get_txt_param(const char *txt, const char *param, char *result, int len)
+get_txt_param(const mdns_result_t *mr, const char *param, char *result, int len)
 {
        const char *p;
 
-again:
-       p = strstr(txt, param);
+       *result = '\0';
+
+       p = strstr(mr->txt->key, param);
        if (!p) {
                *result = '\0';
                return 1;
        }
 
-       p += strlen(param);
-       if (*p != '=') {
-               txt = p;
-               goto again;
-       }
-       p++;
-       while (*p && *p != '&' && --len)
-               *result++ = *p++;
-
-       *result = '\0';
+       lws_strncpy(result, mr->txt->value, len);
 
        return 0;
 }
 
 static void lws_esp32_mdns_timer_cb(TimerHandle_t th)
 {
-       uint64_t now = time_in_microseconds(); 
+       uint64_t now = lws_now_usecs();
        struct lws_group_member *p, **p1;
-       const mdns_result_t *r;
-       int n, m;
-
-       if (!lws_esp32.mdns)
-               return;
-       n = mdns_query_end(lws_esp32.mdns);
+       const mdns_result_t *r = mdns_results_head;
 
-       for (m = 0; m < n; m++) {
+       while (r) {
                char ch = 0, group[16];
 
-               r = mdns_result_get(lws_esp32.mdns, m);
-
-               get_txt_param(r->txt, "group", group, sizeof(group));
+               get_txt_param(r, "group", group, sizeof(group));
                if (strcmp(group, lws_esp32.group)) /* not our group */ {
                        lwsl_notice("group %s vs %s  %s\n",
-                                       group, lws_esp32.group, r->txt);
+                                       group, lws_esp32.group, r->txt->value);
                        continue;
                }
 
                p = lws_esp32.first;
                while (p) {
-                       if (strcmp(r->host, p->host))
+                       if (strcmp(r->hostname, p->host))
                                goto next;
                        if (memcmp(&r->addr, &p->addr, sizeof(r->addr)))
                                goto next;
@@ -831,22 +289,22 @@ next:
                }
                if (!p) { /* did not find */
                        char temp[8];
-                       p = malloc(sizeof(*p));
+
+                       p = lws_malloc(sizeof(*p), "group");
                        if (!p)
                                continue;
-                       strncpy(p->host, r->host, sizeof(p->host) - 1);
-                       p->host[sizeof(p->host) - 1] = '\0';
+                       lws_strncpy(p->host, r->hostname, sizeof(p->host));
 
-                       get_txt_param(r->txt, "model", p->model, sizeof(p->model));
-                       get_txt_param(r->txt, "role", p->role, sizeof(p->role));
-                       get_txt_param(r->txt, "mac", p->mac, sizeof(p->mac));
-                       get_txt_param(r->txt, "width", temp, sizeof(temp));
+                       get_txt_param(r, "model", p->model, sizeof(p->model));
+                       get_txt_param(r, "role", p->role, sizeof(p->role));
+                       get_txt_param(r, "mac", p->mac, sizeof(p->mac));
+                       get_txt_param(r, "width", temp, sizeof(temp));
                        p->width = atoi(temp);
-                       get_txt_param(r->txt, "height", temp, sizeof(temp));
+                       get_txt_param(r, "height", temp, sizeof(temp));
                        p->height = atoi(temp);
 
                        memcpy(&p->addr, &r->addr, sizeof(p->addr));
-                       memcpy(&p->addrv6, &r->addrv6, sizeof(p->addrv6));
+//                     memcpy(&p->addrv6, &r->addrv6, sizeof(p->addrv6));
                        p->last_seen = now;
                        p->flags = 0;
                        p->next = lws_esp32.first;
@@ -859,16 +317,16 @@ next:
                                memcpy(&p->addr, &r->addr, sizeof(p->addr));
                                ch = 1;
                        }
-                       if (memcmp(&p->addrv6, &r->addrv6, sizeof(p->addrv6))) {
+/*                     if (memcmp(&p->addrv6, &r->addrv6, sizeof(p->addrv6))) {
                                memcpy(&p->addrv6, &r->addrv6, sizeof(p->addrv6));
                                ch = 1;
-                       }
+                       } */
                        if (ch)
                                lws_group_member_event_call(LWS_SYSTEM_GROUP_MEMBER_CHANGE, p);
                }
        }
 
-       mdns_result_free(lws_esp32.mdns);
+       mdns_query_results_free(mdns_results_head);
 
        /* garbage-collect group members not seen for too long */
        p1 = &lws_esp32.first;
@@ -880,13 +338,14 @@ next:
                        *p1 = p->next;
 
                        lws_group_member_event_call(LWS_SYSTEM_GROUP_MEMBER_REMOVE, p);
-                       free(p);
+                       lws_free(p);
                        continue;
                }
                p1 = &(*p1)->next;
        }
 
-       mdns_query(lws_esp32.mdns, "_lwsgrmem", "_tcp", 0);
+       mdns_query_txt(lws_esp32.group, "_lwsgrmem", "_tcp", 0,
+                              &mdns_results_head);
        xTimerStart(mdns_timer, 0);
 }
 #endif
@@ -899,19 +358,20 @@ lws_esp32_button(int down)
 void IRAM_ATTR
 gpio_irq(void *arg)
 {
-       bdown ^= 1;
        gpio_set_intr_type(GPIO_SW, GPIO_INTR_DISABLE);
        xTimerStart(debounce_timer, 0);
-
-       lws_esp32_button(bdown);
 }
 
 static void lws_esp32_debounce_timer_cb(TimerHandle_t th)
 {
-       if (bdown)
+       if (lws_esp32.button_is_down)
                gpio_set_intr_type(GPIO_SW, GPIO_INTR_POSEDGE);
        else
                gpio_set_intr_type(GPIO_SW, GPIO_INTR_NEGEDGE);
+
+       lws_esp32.button_is_down = gpio_get_level(GPIO_SW);
+
+       lws_esp32_button(lws_esp32.button_is_down);
 }
 
 
@@ -944,8 +404,8 @@ end_scan()
        uint16_t count_ap_records;
        int n, m;
 
-       count_ap_records = ARRAY_SIZE(ap_records);
-       if (esp_wifi_scan_get_ap_records(&count_ap_records, ap_records) != ESP_OK) {
+       count_ap_records = LWS_ARRAY_SIZE(ap_records);
+       if (esp_wifi_scan_get_ap_records(&count_ap_records, ap_records)) {
                lwsl_err("%s: failed\n", __func__);
                return;
        }
@@ -954,7 +414,7 @@ end_scan()
                goto passthru;
 
        if (gapss != LWS_GAPSS_SCAN) {
-               lwsl_notice("ignoring scan as gapss %s\n", gapss_str[gapss]);
+               lwsl_info("ignoring scan as gapss %s\n", gapss_str[gapss]);
                goto passthru;
        }
 
@@ -965,20 +425,22 @@ end_scan()
            !lws_esp32.ssid[3][0])
                goto passthru;
 
-       lwsl_notice("checking %d scan records\n", count_ap_records);
+       lwsl_info("checking %d scan records\n", count_ap_records);
 
        for (n = 0; n < 4; n++) {
 
                if (!lws_esp32.ssid[(n + try_slot + 1) & 3][0])
                        continue;
 
-               lwsl_notice("looking for %s\n", lws_esp32.ssid[(n + try_slot + 1) & 3]);
+               lwsl_debug("looking for %s\n",
+                           lws_esp32.ssid[(n + try_slot + 1) & 3]);
 
                /* this ssid appears in scan results? */
 
                for (m = 0; m < count_ap_records; m++) {
                        // lwsl_notice("  %s\n", ap_records[m].ssid);
-                       if (strcmp((char *)ap_records[m].ssid, lws_esp32.ssid[(n + try_slot + 1) & 3]) == 0)
+                       if (!strcmp((char *)ap_records[m].ssid,
+                                   lws_esp32.ssid[(n + try_slot + 1) & 3]))
                                goto hit;
                }
 
@@ -987,18 +449,22 @@ end_scan()
 hit:
                m = (n + try_slot + 1) & 3;
                try_slot = m;
-               lwsl_notice("Attempting connection with slot %d: %s:\n", m,
+               lwsl_info("Attempting connection with slot %d: %s:\n", m,
                                lws_esp32.ssid[m]);
                /* set the ssid we last tried to connect to */
-               strncpy(lws_esp32.active_ssid, lws_esp32.ssid[m],
-                               sizeof(lws_esp32.active_ssid) - 1);
-               lws_esp32.active_ssid[sizeof(lws_esp32.active_ssid) - 1] = '\0';
+               lws_strncpy(lws_esp32.active_ssid, lws_esp32.ssid[m],
+                               sizeof(lws_esp32.active_ssid));
 
-               strncpy((char *)sta_config.sta.ssid, lws_esp32.ssid[m], sizeof(sta_config.sta.ssid) - 1);
-               strncpy((char *)sta_config.sta.password, lws_esp32.password[m], sizeof(sta_config.sta.password) - 1);
+               lws_strncpy((char *)sta_config.sta.ssid, lws_esp32.ssid[m],
+                       sizeof(sta_config.sta.ssid));
+               lws_strncpy((char *)sta_config.sta.password, lws_esp32.password[m],
+                       sizeof(sta_config.sta.password));
 
-               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
+               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA,
+                                          (const char *)&config.ap.ssid[7]);
                lws_gapss_to(LWS_GAPSS_STAT);
+               xTimerStop(association_timer, 0);
+               xTimerStart(association_timer, 0);
 
                esp_wifi_set_config(WIFI_IF_STA, &sta_config);
                esp_wifi_connect();
@@ -1010,14 +476,15 @@ hit:
 
 passthru:
        if (lws_esp32.scan_consumer)
-               lws_esp32.scan_consumer(count_ap_records, ap_records, lws_esp32.scan_consumer_arg);
+               lws_esp32.scan_consumer(count_ap_records, ap_records,
+                                       lws_esp32.scan_consumer_arg);
 
 }
 
 static void
 lws_set_genled(int n)
 {
-       lws_esp32.genled_t = time_in_microseconds();
+       lws_esp32.genled_t = lws_now_usecs();
        lws_esp32.genled = n;
 }
 
@@ -1027,7 +494,7 @@ lws_esp32_leds_network_indication(void)
        uint64_t us, r;
        int n, fadein = 100, speed = 1199, div = 1, base = 0;
 
-       r = time_in_microseconds();
+       r = lws_now_usecs();
        us = r - lws_esp32.genled_t;
 
        switch (lws_esp32.genled) {
@@ -1076,7 +543,6 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
        struct lws_group_member *mem;
        int n;
 #endif
-       char slot[8];
        nvs_handle nvh;
        uint32_t use;
 
@@ -1087,16 +553,15 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                /* fallthru */
        case SYSTEM_EVENT_STA_DISCONNECTED:
                lwsl_notice("SYSTEM_EVENT_STA_DISCONNECTED\n");
+               if (sntp_enabled())
+                       sntp_stop();
                lws_esp32.conn_ap = 0;
                lws_esp32.inet = 0;
                lws_esp32.sta_ip[0] = '\0';
                lws_esp32.sta_mask[0] = '\0';
                lws_esp32.sta_gw[0] = '\0';
                lws_gapss_to(LWS_GAPSS_SCAN);
-               if (lws_esp32.mdns)
-                       mdns_service_remove_all(lws_esp32.mdns);
-               mdns_free(lws_esp32.mdns);
-               lws_esp32.mdns = NULL;
+               mdns_free();
                lws_set_genled(LWSESP32_GENLED__LOST_NETWORK);
                start_scan();
                esp_wifi_connect();
@@ -1121,6 +586,8 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                                (uint8_t *)&event->event_info.got_ip.ip_info.gw);
 
                if (!nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+                       char slot[8];
+
                        lws_snprintf(slot, sizeof(slot) - 1, "%duse", try_slot);
                        use = 0;
                        nvs_get_u32(nvh, slot, &use);
@@ -1132,35 +599,37 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                lws_gapss_to(LWS_GAPSS_STAT_HAPPY);
 
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
-               n = mdns_init(TCPIP_ADAPTER_IF_STA, &lws_esp32.mdns);
+               n = mdns_init();
                if (!n) {
-                       static char *txta[6];
+                       static mdns_txt_item_t txta[6];
+                       static char wh[2][6];
                        int w, h;
 
-                       mdns_set_hostname(lws_esp32.mdns, lws_esp32.hostname);
-                       mdns_set_instance(lws_esp32.mdns, lws_esp32.group);
-                       mdns_service_add(lws_esp32.mdns, "_lwsgrmem", "_tcp", 443);
-                       if (txta[0])
-                               free(txta[0]);
-                       txta[0] = malloc(32 * ARRAY_SIZE(txta));
-                       if (!txta[0]) {
-                               lwsl_notice("mdns OOM\n");
-                               break;
-                       }
-                       txta[1] = &txta[0][32];
-                       txta[2] = &txta[1][32];
-                       txta[3] = &txta[2][32];
-                       txta[4] = &txta[3][32];
-                       txta[5] = &txta[4][32];
+                       mdns_hostname_set(lws_esp32.hostname);
+                       mdns_instance_name_set(lws_esp32.group);
 
                        lws_get_iframe_size(&w, &h);
 
-                       lws_snprintf(txta[0], 31, "model=%s", lws_esp32.model);
-                       lws_snprintf(txta[1], 31, "group=%s", lws_esp32.group);
-                       lws_snprintf(txta[2], 31, "role=%s", lws_esp32.role);
-                       lws_snprintf(txta[3], 31, "mac=%s", lws_esp32.mac);
-                       lws_snprintf(txta[4], 31, "width=%d", w);
-                       lws_snprintf(txta[5], 31, "height=%d", h);
+                       txta[0].key = "model";
+                       txta[1].key = "group";
+                       txta[2].key = "role";
+                       txta[3].key = "mac";
+                       txta[4].key = "width";
+                       txta[5].key = "height";
+
+                       txta[0].value = lws_esp32.model;
+                       txta[1].value = lws_esp32.group;
+                       txta[2].value = lws_esp32.role;
+                       txta[3].value = lws_esp32.mac;
+                       txta[4].value = wh[0];
+                       txta[5].value = wh[1];
+
+                       lws_snprintf(wh[0], 6, "%d", w);
+                       lws_snprintf(wh[1], 6, "%d", h);
+
+                       mdns_service_add(lws_esp32.group,
+                                        "_lwsgrmem", "_tcp", 443, txta,
+                                        LWS_ARRAY_SIZE(txta));
 
                        mem = lws_esp32.first;
                        while (mem) {
@@ -1170,7 +639,8 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                        }
 
                        if (!mem) {
-                               struct lws_group_member *mem = malloc(sizeof(*mem));
+                               struct lws_group_member *mem =
+                                             lws_malloc(sizeof(*mem), "group");
                                if (mem) {
                                        mem->last_seen = ~(uint64_t)0;
                                        strcpy(mem->model, lws_esp32.model);
@@ -1178,37 +648,46 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                                        strcpy(mem->host, lws_esp32.hostname);
                                        strcpy(mem->mac, lws_esp32.mac);
                                        mem->flags = LWS_GROUP_FLAG_SELF;
-                                       lws_get_iframe_size(&mem->width, &mem->height);
-                                       memcpy(&mem->addr, &event->event_info.got_ip.ip_info.ip,
-                                                       sizeof(mem->addr));
-                                       memcpy(&mem->addrv6, &event->event_info.got_ip6.ip6_info.ip,
-                                                       sizeof(mem->addrv6));
+                                       lws_get_iframe_size(&mem->width,
+                                                           &mem->height);
+                                       memcpy(&mem->addr,
+                                              &event->event_info.got_ip.ip_info.ip,
+                                              sizeof(mem->addr));
+                                       memcpy(&mem->addrv6,
+                                              &event->event_info.got_ip6.ip6_info.ip,
+                                              sizeof(mem->addrv6));
                                        mem->next = lws_esp32.first;
                                        lws_esp32.first = mem;
                                        lws_esp32.extant_group_members++;
 
-                                       lws_group_member_event_call(LWS_SYSTEM_GROUP_MEMBER_ADD, mem);
+                                       lws_group_member_event_call(
+                                             LWS_SYSTEM_GROUP_MEMBER_ADD, mem);
                                }
                        } else { /* update our IP */
-                                       memcpy(&mem->addr, &event->event_info.got_ip.ip_info.ip,
-                                                       sizeof(mem->addr));
-                                       memcpy(&mem->addrv6, &event->event_info.got_ip6.ip6_info.ip,
-                                                       sizeof(mem->addrv6));
-                                       lws_group_member_event_call(LWS_SYSTEM_GROUP_MEMBER_CHANGE, mem);
+                               memcpy(&mem->addr,
+                                      &event->event_info.got_ip.ip_info.ip,
+                                      sizeof(mem->addr));
+                               memcpy(&mem->addrv6,
+                                      &event->event_info.got_ip6.ip6_info.ip,
+                                      sizeof(mem->addrv6));
+                               lws_group_member_event_call(
+                                          LWS_SYSTEM_GROUP_MEMBER_CHANGE, mem);
                        }
 
-
-                       if (mdns_service_txt_set(lws_esp32.mdns, "_lwsgrmem", "_tcp", ARRAY_SIZE(txta),
-                                                (const char **)txta))
-                               lwsl_notice("txt set failed\n");
                } else
                        lwsl_err("unable to init mdns on STA: %d\n", n);
 
-               mdns_query(lws_esp32.mdns, "_lwsgrmem", "_tcp", 0);
+               mdns_query_txt(lws_esp32.group, "_lwsgrmem", "_tcp", 0,
+                              &mdns_results_head);
                xTimerStart(mdns_timer, 0);
 #endif
 
                lwsl_notice(" --- Got IP %s\n", lws_esp32.sta_ip);
+               if (!sntp_enabled()) {
+                       sntp_setoperatingmode(SNTP_OPMODE_POLL);
+                       sntp_setservername(0, "pool.ntp.org");
+                       sntp_init();
+               }
                break;
 
        case SYSTEM_EVENT_SCAN_DONE:
@@ -1225,7 +704,7 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
 
 static lws_fop_fd_t IRAM_ATTR
 esp32_lws_fops_open(const struct lws_plat_file_ops *fops, const char *filename,
-                const char *vfs_path, lws_fop_flags_t *flags)
+                    const char *vfs_path, lws_fop_flags_t *flags)
 {
        struct esp32_file *f = malloc(sizeof(*f));
        lws_fop_fd_t fop_fd;
@@ -1248,7 +727,7 @@ esp32_lws_fops_open(const struct lws_plat_file_ops *fops, const char *filename,
        fop_fd->mod_time = csum;
        *flags |= LWS_FOP_FLAG_MOD_TIME_VALID;
        fop_fd->flags = *flags;
-       
+
        fop_fd->len = len;
        fop_fd->pos = 0;
 
@@ -1274,7 +753,7 @@ static lws_fileofs_t IRAM_ATTR
 esp32_lws_fops_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset_from_cur_pos)
 {
        fop_fd->pos += offset_from_cur_pos;
-       
+
        if (fop_fd->pos > fop_fd->len)
                fop_fd->pos = fop_fd->len;
 
@@ -1319,16 +798,18 @@ int
 lws_esp32_wlan_nvs_get(int retry)
 {
        nvs_handle nvh;
-       char r[2], lws_esp32_force_ap = 0, slot[12];
+       char lws_esp32_force_ap = 0, slot[12];
        size_t s;
        uint8_t mac[6];
        int n;
 
        esp_efuse_mac_get_default(mac);
        mac[5] |= 1; /* match the AP MAC */
-       snprintf(lws_esp32.serial, sizeof(lws_esp32.serial) - 1, "%02X%02X%02X", mac[3], mac[4], mac[5]);
-       snprintf(lws_esp32.mac, sizeof(lws_esp32.mac) - 1, "%02X%02X%02X%02X%02X%02X", mac[0],
-                       mac[1], mac[2], mac[3], mac[4], mac[5]);
+       snprintf(lws_esp32.serial, sizeof(lws_esp32.serial) - 1,
+                "%02X%02X%02X", mac[3], mac[4], mac[5]);
+       snprintf(lws_esp32.mac, sizeof(lws_esp32.mac) - 1,
+                "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3],
+                mac[4], mac[5]);
 
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
 
@@ -1357,11 +838,6 @@ lws_esp32_wlan_nvs_get(int retry)
        if (nvs_get_str(nvh, "opts", lws_esp32.opts, &s) != ESP_OK)
                lws_esp32_force_ap = 1;
 
-       s = sizeof(r);
-       if (nvs_get_str(nvh, "region", r, &s) != ESP_OK)
-               lws_esp32_force_ap = 1;
-       else
-               lws_esp32.region = atoi(r);
        lws_esp32.access_pw[0] = '\0';
        nvs_get_str(nvh, "access_pw", lws_esp32.access_pw, &s);
 
@@ -1401,6 +877,8 @@ lws_esp32_wlan_config(void)
        };
        int n;
 
+       lwsl_debug("%s\n", __func__);
+
        ledc_timer_config(&ledc_timer);
 
        lws_set_genled(LWSESP32_GENLED__INIT);
@@ -1413,6 +891,8 @@ lws_esp32_wlan_config(void)
                           (TimerCallbackFunction_t)lws_esp32_scan_timer_cb);
         debounce_timer = xTimerCreate("lws_db", pdMS_TO_TICKS(100), 0, NULL,
                           (TimerCallbackFunction_t)lws_esp32_debounce_timer_cb);
+        association_timer = xTimerCreate("lws_assoc", pdMS_TO_TICKS(10000), 0, NULL,
+                          (TimerCallbackFunction_t)lws_esp32_assoc_timer_cb);
 
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
         mdns_timer = xTimerCreate("lws_mdns", pdMS_TO_TICKS(5000), 0, NULL,
@@ -1452,7 +932,6 @@ lws_esp32_wlan_start_ap(void)
 
        ESP_ERROR_CHECK( esp_wifi_init(&cfg));
        ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM));
-       esp_wifi_set_country(lws_esp32.region);
 
        ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_APSTA) );
        ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &config) );
@@ -1462,8 +941,9 @@ lws_esp32_wlan_start_ap(void)
        esp_wifi_scan_start(&scan_config, false);
 
        if (sta_config.sta.ssid[0]) {
-               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
-               esp_wifi_set_auto_connect(1);
+               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_set_config(WIFI_IF_STA, &sta_config));
                ESP_ERROR_CHECK( esp_wifi_connect());
@@ -1477,16 +957,16 @@ lws_esp32_wlan_start_station(void)
 
        ESP_ERROR_CHECK( esp_wifi_init(&cfg));
        ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM));
-       esp_wifi_set_country(lws_esp32.region);
 
        ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA));
        ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
 
        ESP_ERROR_CHECK( esp_wifi_start());
 
-       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());
+       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());
 
        lws_esp32_scan_timer_cb(NULL);
 }
@@ -1494,7 +974,8 @@ lws_esp32_wlan_start_station(void)
 const esp_partition_t *
 lws_esp_ota_get_boot_partition(void)
 {
-       const esp_partition_t *part = esp_ota_get_boot_partition(), *factory_part, *ota;
+       const esp_partition_t *part = esp_ota_get_boot_partition(),
+                             *factory_part, *ota;
        esp_image_header_t eih, ota_eih;
        uint32_t *p_force_factory_magic = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS;
 
@@ -1509,7 +990,7 @@ lws_esp_ota_get_boot_partition(void)
        if (eih.spi_mode == 0xff ||
            *p_force_factory_magic == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY ||
            *p_force_factory_magic == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON
-          ) {
+       ) {
                /*
                 * we believed we were going to boot OTA, but we fell
                 * back to FACTORY in the bootloader when we saw it
@@ -1518,8 +999,8 @@ lws_esp_ota_get_boot_partition(void)
                 * factory partition right now.
                 */
                part = factory_part;
-       } 
-       
+       }
+
 #ifdef CONFIG_LWS_IS_FACTORY_APPLICATION
        else
                if (ota_eih.spi_mode != 0xff &&
@@ -1531,31 +1012,33 @@ lws_esp_ota_get_boot_partition(void)
                         * it means we were just written and need to copy
                         * ourselves into the FACTORY slot.
                         */
-                       lwsl_notice("Copying FACTORY update into place 0x%x len 0x%x\n",
-                                   factory_part->address, factory_part->size);
-                       esp_task_wdt_feed();
-                       if (spi_flash_erase_range(factory_part->address, factory_part->size) != ESP_OK) {
+                       lwsl_notice("Copying FACTORY update into place "
+                                   "0x%x len 0x%x\n", factory_part->address,
+                                   factory_part->size);
+                       esp_task_wdt_reset();
+                       if (spi_flash_erase_range(factory_part->address,
+                                                 factory_part->size)) {
                                lwsl_err("spi: Failed to erase\n");
                                goto retry;
                        }
 
                        for (n = 0; n < factory_part->size; n += sizeof(buf)) {
-                               esp_task_wdt_feed();
-                               spi_flash_read(part->address + n , buf, sizeof(buf));
-                               if (spi_flash_write(factory_part->address + n, buf, sizeof(buf)) != ESP_OK) {
+                               esp_task_wdt_reset();
+                               spi_flash_read(part->address + n , buf,
+                                              sizeof(buf));
+                               if (spi_flash_write(factory_part->address + n,
+                                                   buf, sizeof(buf))) {
                                        lwsl_err("spi: Failed to write\n");
                                        goto retry;
                                }
                        }
 
-                       /* destroy our OTA image header */
-                       spi_flash_erase_range(ota->address, 4096);
-
                        /*
-                        * with no viable OTA image, we will come back up in factory
-                        * where the user can reload the OTA image
+                        * We send a message to the bootloader to erase the OTA header, we will come back up in
+                        * factory where the user can reload the OTA image
                         */
                        lwsl_notice("  FACTORY copy successful, rebooting\n");
+                       lws_esp32_restart_guided(LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY_ERASE_OTA);
 retry:
                        esp_restart();
                }
@@ -1577,19 +1060,17 @@ lws_esp32_set_creation_defaults(struct lws_context_creation_info *info)
        part = lws_esp_ota_get_boot_partition();
        (void)part;
 
+       info->vhost_name = "default";
        info->port = 443;
-       info->fd_limit_per_thread = 30;
-       info->max_http_header_pool = 3;
+       info->fd_limit_per_thread = 16;
+       info->max_http_header_pool = 5;
        info->max_http_header_data = 1024;
        info->pt_serv_buf_size = 4096;
        info->keepalive_timeout = 30;
        info->timeout_secs = 30;
-       info->simultaneous_ssl_restriction = 3;
+       info->simultaneous_ssl_restriction = 2;
        info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
-                      LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-
-       info->ssl_cert_filepath = "ssl-pub.pem";
-       info->ssl_private_key_filepath = "ssl-pri.pem";
+                       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
 }
 
 int
@@ -1603,8 +1084,10 @@ lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i,
        spi_flash_read(part->address , &eih, sizeof(eih));
        hdr = part->address + sizeof(eih);
 
-       if (eih.magic != ESP_IMAGE_HEADER_MAGIC)
+       if (eih.magic != ESP_IMAGE_HEADER_MAGIC) {
+               lwsl_notice("%s: bad image header magic\n", __func__);
                return 1;
+       }
 
        eis.data_len = 0;
        while (eih.segment_count-- && eis.data_len != 0xffffffff) {
@@ -1613,7 +1096,12 @@ lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i,
        }
        hdr += (~hdr & 15) + 1;
 
-       i->romfs = hdr + 4;
+       if (eih.hash_appended)
+               hdr += 0x20;
+
+//     lwsl_notice("romfs estimated at 0x%x\n", hdr);
+
+       i->romfs = hdr + 0x4;
        spi_flash_read(hdr, &i->romfs_len, sizeof(i->romfs_len));
        i->json = i->romfs + i->romfs_len + 4;
        spi_flash_read(i->json - 4, &i->json_len, sizeof(i->json_len));
@@ -1626,35 +1114,165 @@ lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i,
        return 0;
 }
 
-struct lws_context *
-lws_esp32_init(struct lws_context_creation_info *info)
+static int
+_rngf(void *context, unsigned char *buf, size_t len)
 {
-       const esp_partition_t *part = lws_esp_ota_get_boot_partition();
-       struct lws_context *context;
-       struct lws_esp32_image i;
-       struct lws_vhost *vhost;
+       if ((size_t)lws_get_random(context, buf, len) == len)
+               return 0;
+
+       return -1;
+}
+
+int
+lws_esp32_selfsigned(struct lws_vhost *vhost)
+{
+       mbedtls_x509write_cert crt;
+       char subject[200];
+       mbedtls_pk_context mpk;
+       int buf_size = 4096, n;
+       uint8_t *buf = malloc(buf_size); /* malloc because given to user code */
+       mbedtls_mpi mpi;
        nvs_handle nvh;
-       char buf[512];
        size_t s;
-       int n;
 
-       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+       lwsl_notice("%s: %s\n", __func__, vhost->name);
+
+       if (!buf)
+               return -1;
+
+       if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+               lwsl_notice("%s: can't open nvs\n", __func__);
+               free(buf);
+               return 1;
+       }
+
        n = 0;
-       s = 1;
-       if (nvs_get_blob(nvh, "ssl-pub.pem", NULL, &s) == ESP_OK)
-               n = 1;
-       s = 1;
-       if (nvs_get_blob(nvh, "ssl-pri.pem", NULL, &s) == ESP_OK)
+       if (!nvs_get_blob(nvh, vhost->tls.alloc_cert_path, NULL, &s))
+               n |= 1;
+       if (!nvs_get_blob(nvh, vhost->tls.key_path, NULL, &s))
                n |= 2;
+
        nvs_close(nvh);
+       if (n == 3) {
+               lwsl_notice("%s: certs exist\n", __func__);
+               free(buf);
+               return 0; /* certs already exist */
+       }
+
+       lwsl_notice("%s: creating selfsigned initial certs\n", __func__);
+
+       mbedtls_x509write_crt_init(&crt);
+
+       mbedtls_pk_init(&mpk);
+       if (mbedtls_pk_setup(&mpk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) {
+               lwsl_notice("%s: pk_setup failed\n", __func__);
+               goto fail;
+       }
+       lwsl_notice("%s: generating 2048-bit RSA keypair... "
+                   "this may take a minute or so...\n", __func__);
+       n = mbedtls_rsa_gen_key(mbedtls_pk_rsa(mpk), _rngf, vhost->context,
+                               2048, 65537);
+       if (n) {
+               lwsl_notice("%s: failed to generate keys\n", __func__);
+               goto fail1;
+       }
+       lwsl_notice("%s: keys done\n", __func__);
+
+       /* subject must be formatted like "C=TW,O=warmcat,CN=myserver" */
+
+       lws_snprintf(subject, sizeof(subject) - 1,
+                    "C=TW,ST=New Taipei City,L=Taipei,O=warmcat,CN=%s",
+                    lws_esp32.hostname);
+
+       if (mbedtls_x509write_crt_set_subject_name(&crt, subject)) {
+               lwsl_notice("set SN failed\n");
+               goto fail1;
+       }
+       mbedtls_x509write_crt_set_subject_key(&crt, &mpk);
+       if (mbedtls_x509write_crt_set_issuer_name(&crt, subject)) {
+               lwsl_notice("set IN failed\n");
+               goto fail1;
+       }
+       mbedtls_x509write_crt_set_issuer_key(&crt, &mpk);
+
+       lws_get_random(vhost->context, &n, sizeof(n));
+       lws_snprintf(subject, sizeof(subject), "%d", n);
+
+       mbedtls_mpi_init(&mpi);
+       mbedtls_mpi_read_string(&mpi, 10, subject);
+       mbedtls_x509write_crt_set_serial(&crt, &mpi);
+       mbedtls_mpi_free(&mpi);
+
+       mbedtls_x509write_crt_set_validity(&crt, "20171105235959",
+                                          "20491231235959");
+
+       mbedtls_x509write_crt_set_key_usage(&crt,
+                                           MBEDTLS_X509_KU_DIGITAL_SIGNATURE |
+                                           MBEDTLS_X509_KU_KEY_ENCIPHERMENT);
+
 
-       if (n != 3) {
-               /* we are not configured for SSL yet... fall back to port 80 / http */
-               info->port = 80;
-               info->options &= ~LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-               lwsl_notice("No SSL certs... using port 80\n");
+       mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256);
+
+       n = mbedtls_x509write_crt_pem(&crt, buf, buf_size, _rngf,
+                                     vhost->context);
+       if (n < 0) {
+               lwsl_notice("%s: write crt der failed\n", __func__);
+               goto fail1;
+       }
+
+       lws_plat_write_cert(vhost, 0, 0, buf, strlen((const char *)buf));
+
+       if (mbedtls_pk_write_key_pem(&mpk, buf, buf_size)) {
+               lwsl_notice("write key pem failed\n");
+               goto fail1;
        }
 
+       lws_plat_write_cert(vhost, 1, 0, buf, strlen((const char *)buf));
+
+       mbedtls_pk_free(&mpk);
+       mbedtls_x509write_crt_free(&crt);
+
+       lwsl_notice("%s: cert creation complete\n", __func__);
+
+       return n;
+
+fail1:
+       mbedtls_pk_free(&mpk);
+fail:
+       mbedtls_x509write_crt_free(&crt);
+       free(buf);
+
+       nvs_close(nvh);
+
+       return -1;
+}
+
+void
+lws_esp32_update_acme_info(void)
+{
+        int n;
+
+       n = lws_plat_read_file("acme-email", lws_esp32.le_email,
+                              sizeof(lws_esp32.le_email) - 1);
+       if (n >= 0)
+               lws_esp32.le_email[n] = '\0';
+
+       n = lws_plat_read_file("acme-cn", lws_esp32.le_dns,
+                              sizeof(lws_esp32.le_dns) - 1);
+       if (n >= 0)
+               lws_esp32.le_dns[n] = '\0';
+}
+
+struct lws_context *
+lws_esp32_init(struct lws_context_creation_info *info, struct lws_vhost **pvh)
+{
+       const esp_partition_t *part = lws_esp_ota_get_boot_partition();
+       struct lws_context *context;
+       struct lws_esp32_image i;
+       struct lws_vhost *vhost;
+       struct lws wsi;
+       char buf[512];
+
        context = lws_create_context(info);
        if (context == NULL) {
                lwsl_err("Failed to create context\n");
@@ -1662,10 +1280,11 @@ lws_esp32_init(struct lws_context_creation_info *info)
        }
 
        lws_esp32_get_image_info(part, &i, buf, sizeof(buf) - 1);
-       
+
        lws_esp32_romfs = (romfs_t)i.romfs;
        if (!romfs_mount_check(lws_esp32_romfs)) {
-               lwsl_err("Failed to mount ROMFS at %p 0x%x\n", lws_esp32_romfs, i.romfs);
+               lwsl_err("mount error on ROMFS at %p 0x%x\n", lws_esp32_romfs,
+                        i.romfs);
                return NULL;
        }
 
@@ -1677,13 +1296,31 @@ lws_esp32_init(struct lws_context_creation_info *info)
 
        lws_set_fops(context, &fops);
 
+       info->options |= LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX |
+                        LWS_SERVER_OPTION_IGNORE_MISSING_CERT;
+
        vhost = lws_create_vhost(context, info);
-       if (!vhost)
+       if (!vhost) {
                lwsl_err("Failed to create vhost\n");
-       else
-               lws_init_vhost_client_ssl(info, vhost); 
+               return NULL;
+       }
+
+       lws_esp32_update_acme_info();
+
+       lws_esp32_selfsigned(vhost);
+       wsi.context = vhost->context;
+       wsi.vhost = vhost;
 
-       lws_protocol_init(context);
+       lws_tls_server_certs_load(vhost, &wsi, info->ssl_cert_filepath,
+                       info->ssl_private_key_filepath, NULL, 0, NULL, 0);
+
+       lws_init_vhost_client_ssl(info, vhost);
+
+       if (pvh)
+               *pvh = vhost;
+
+       if (lws_protocol_init(context))
+               return NULL;
 
        return context;
 }
@@ -1728,4 +1365,3 @@ uint16_t lws_esp32_sine_interp(int n)
         return (sine_lu(n >> 4) * (15 - (n & 15)) +
                 sine_lu((n >> 4) + 1) * (n & 15)) / 15;
 }
-
diff --git a/lib/plat/esp32/esp32-init.c b/lib/plat/esp32/esp32-init.c
new file mode 100644 (file)
index 0000000..2f537d0
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+lws_plat_context_early_init(void)
+{
+       return 0;
+}
+
+void
+lws_plat_context_early_destroy(struct lws_context *context)
+{
+#if defined(LWS_AMAZON_RTOS)
+       mbedtls_ctr_drbg_free(&context->mcdc);
+       mbedtls_entropy_free(&context->mec);
+#endif
+}
+
+void
+lws_plat_context_late_destroy(struct lws_context *context)
+{
+#ifdef LWS_WITH_PLUGINS
+       if (context->plugin_list)
+               lws_plat_plugins_destroy(context);
+#endif
+
+       if (context->lws_lookup)
+               lws_free(context->lws_lookup);
+}
+
+#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 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,
+       /* H2SET_RESERVED7 */                              0,
+       /* H2SET_ENABLE_CONNECT_PROTOCOL */                1,
+}};
+#endif
+
+int
+lws_plat_init(struct lws_context *context,
+             const struct lws_context_creation_info *info)
+{
+#if defined(LWS_AMAZON_RTOS)
+       int n;
+
+       /* initialize platform random through mbedtls */
+       mbedtls_entropy_init(&context->mec);
+       mbedtls_ctr_drbg_init(&context->mcdc);
+
+       n = mbedtls_ctr_drbg_seed(&context->mcdc, mbedtls_entropy_func,
+                                 &context->mec, NULL, 0);
+       if (n) {
+               lwsl_err("%s: mbedtls_ctr_drbg_seed() returned 0x%x\n",
+                        __func__, n);
+
+               return 1;
+       }
+#endif
+
+       /* master context has the global fd lookup array */
+       context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
+                                        context->max_fds, "esp32 lws_lookup");
+       if (context->lws_lookup == NULL) {
+               lwsl_err("OOM on lws_lookup array for %d connections\n",
+                        context->max_fds);
+               return 1;
+       }
+
+       lwsl_notice(" mem: platform fd map: %5lu bytes\n",
+                   (unsigned long)(sizeof(struct lws *) * context->max_fds));
+
+#ifdef LWS_WITH_PLUGINS
+       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;
+}
diff --git a/lib/plat/esp32/esp32-misc.c b/lib/plat/esp32/esp32-misc.c
new file mode 100644 (file)
index 0000000..65de064
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+lws_usec_t
+lws_now_usecs(void)
+{
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       return ((unsigned long long)tv.tv_sec * 1000000LL) + tv.tv_usec;
+}
+
+LWS_VISIBLE int
+lws_get_random(struct lws_context *context, void *buf, int len)
+{
+#if defined(LWS_AMAZON_RTOS)
+       int n;
+
+       n = mbedtls_ctr_drbg_random(&context->mcdc, buf, len);
+       if (!n)
+               return len;
+
+       /* failed */
+
+       lwsl_err("%s: mbedtls_ctr_drbg_random returned 0x%x\n", __func__, n);
+
+       return 0;
+#else
+       uint8_t *pb = buf;
+
+       while (len) {
+               uint32_t r = esp_random();
+               uint8_t *p = (uint8_t *)&r;
+               int b = 4;
+
+               if (len < b)
+                       b = len;
+
+               len -= b;
+
+               while (b--)
+                       *pb++ = p[b];
+       }
+
+       return pb - (uint8_t *)buf;
+#endif
+}
+
+
+LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
+{
+       lwsl_emit_stderr(level, line);
+}
+
+int
+lws_plat_drop_app_privileges(struct lws_context *context, int actually_init)
+{
+       return 0;
+}
+
+int
+lws_plat_recommended_rsa_bits(void)
+{
+       /*
+        * 2048-bit key generation takes up to a minute on ESP32, 4096
+        * is like 15 minutes +
+        */
+       return 2048;
+}
+
+void esp32_uvtimer_cb(TimerHandle_t t)
+{
+       struct timer_mapping *p = pvTimerGetTimerID(t);
+
+       p->cb(p->t);
+}
+
diff --git a/lib/plat/esp32/esp32-pipe.c b/lib/plat/esp32/esp32-pipe.c
new file mode 100644 (file)
index 0000000..c8e11c8
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+lws_plat_pipe_create(struct lws *wsi)
+{
+       return 1;
+}
+
+int
+lws_plat_pipe_signal(struct lws *wsi)
+{
+       return 1;
+}
+
+void
+lws_plat_pipe_close(struct lws *wsi)
+{
+}
diff --git a/lib/plat/esp32/esp32-service.c b/lib/plat/esp32/esp32-service.c
new file mode 100644 (file)
index 0000000..36c7c69
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+lws_plat_service(struct lws_context *context, int timeout_ms)
+{
+       int n = _lws_plat_service_tsi(context, timeout_ms, 0);
+
+       lws_service_fd_tsi(context, NULL, 0);
+#if !defined(LWS_AMAZON_RTOS)
+       esp_task_wdt_reset();
+#endif
+
+       return n;
+}
+
+
+LWS_EXTERN int
+_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
+{
+       struct lws_context_per_thread *pt;
+       lws_usec_t timeout_us;
+       int n = -1, m, c;
+
+       /* stay dead once we are dead */
+
+       if (!context || !context->vhost_list)
+               return 1;
+
+       pt = &context->pt[tsi];
+       lws_stats_bump(pt, LWSSTATS_C_SERVICE_ENTRY, 1);
+
+       {
+               unsigned long m = lws_now_secs();
+
+               if (m > context->time_last_state_dump) {
+                       context->time_last_state_dump = m;
+#if defined(LWS_AMAZON_RTOS)
+                       n = xPortGetFreeHeapSize();
+#else
+                       n = esp_get_free_heap_size();
+#endif
+                       if ((unsigned int)n != context->last_free_heap) {
+                               if ((unsigned int)n > context->last_free_heap)
+                                       lwsl_notice(" heap :%ld (+%ld)\n",
+                                                   (unsigned long)n,
+                                                   (unsigned long)(n -
+                                                     context->last_free_heap));
+                               else
+                                       lwsl_notice(" heap :%ld (-%ld)\n",
+                                                   (unsigned long)n,
+                                                   (unsigned long)(
+                                                     context->last_free_heap -
+                                                     n));
+                               context->last_free_heap = n;
+                       }
+               }
+       }
+
+       if (timeout_ms < 0)
+               goto faked_service;
+
+       /* force a default timeout of 23 days */
+       timeout_ms = 2000000000;
+       timeout_us = ((lws_usec_t)timeout_ms) * LWS_US_PER_MS;
+
+       if (!pt->service_tid_detected) {
+               struct lws *_lws = lws_zalloc(sizeof(*_lws), "tid probe");
+
+               if (!_lws)
+                       return 1;
+               _lws->context = context;
+
+               pt->service_tid = context->vhost_list->protocols[0].callback(
+                       _lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
+               pt->service_tid_detected = 1;
+               lws_free(_lws);
+       }
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(context, 1, tsi)) {
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(context, 1, pt->tid))
+                       /* yes... come back again quickly */
+                       timeout_us = 0;
+       }
+
+       if (timeout_us) {
+               lws_usec_t us;
+
+               lws_pt_lock(pt, __func__);
+               /* don't stay in poll wait longer than next hr timeout */
+               us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+               if (us && us < timeout_us)
+                       timeout_us = us;
+
+               lws_pt_unlock(pt);
+       }
+
+//     n = poll(pt->fds, pt->fds_count, timeout_ms);
+       {
+               fd_set readfds, writefds, errfds;
+               struct timeval tv = { timeout_us / LWS_US_PER_SEC,
+                                     timeout_us % LWS_US_PER_SEC }, *ptv = &tv;
+               int max_fd = 0;
+               FD_ZERO(&readfds);
+               FD_ZERO(&writefds);
+               FD_ZERO(&errfds);
+
+               for (n = 0; n < (int)pt->fds_count; n++) {
+                       pt->fds[n].revents = 0;
+                       if (pt->fds[n].fd >= max_fd)
+                               max_fd = pt->fds[n].fd;
+                       if (pt->fds[n].events & LWS_POLLIN)
+                               FD_SET(pt->fds[n].fd, &readfds);
+                       if (pt->fds[n].events & LWS_POLLOUT)
+                               FD_SET(pt->fds[n].fd, &writefds);
+                       FD_SET(pt->fds[n].fd, &errfds);
+               }
+
+               n = select(max_fd + 1, &readfds, &writefds, &errfds, ptv);
+               n = 0;
+               for (m = 0; m < (int)pt->fds_count; m++) {
+                       c = 0;
+                       if (FD_ISSET(pt->fds[m].fd, &readfds)) {
+                               pt->fds[m].revents |= LWS_POLLIN;
+                               c = 1;
+                       }
+                       if (FD_ISSET(pt->fds[m].fd, &writefds)) {
+                               pt->fds[m].revents |= LWS_POLLOUT;
+                               c = 1;
+                       }
+                       if (FD_ISSET(pt->fds[m].fd, &errfds)) {
+                               // lwsl_notice("errfds %d\n", pt->fds[m].fd);
+                               pt->fds[m].revents |= LWS_POLLHUP;
+                               c = 1;
+                       }
+
+                       if (c)
+                               n++;
+               }
+       }
+
+       m = 0;
+
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+       m |= !!pt->ws.rx_draining_ext_list;
+#endif
+
+       if (pt->context->tls_ops &&
+           pt->context->tls_ops->fake_POLLIN_for_buffered)
+               m |= pt->context->tls_ops->fake_POLLIN_for_buffered(pt);
+
+       if (!m && !n) {
+               lws_service_fd_tsi(context, NULL, tsi);
+               return 0;
+       }
+
+
+faked_service:
+       m = lws_service_flag_pending(context, tsi);
+       if (m)
+               c = -1; /* unknown limit */
+       else
+               if (n < 0) {
+                       if (LWS_ERRNO != LWS_EINTR)
+                               return -1;
+                       return 0;
+               } else
+                       c = n;
+
+       /* any socket with events to service? */
+       for (n = 0; n < (int)pt->fds_count && c; n++) {
+               if (!pt->fds[n].revents)
+                       continue;
+
+               c--;
+
+               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
+               if (m < 0)
+                       return -1;
+               /* if something closed, retry this slot */
+               if (m)
+                       n--;
+       }
+
+       return 0;
+}
diff --git a/lib/plat/esp32/esp32-sockets.c b/lib/plat/esp32/esp32-sockets.c
new file mode 100644 (file)
index 0000000..80b50c8
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * libwebsockets - lib/plat/lws-plat-esp32.c
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+lws_send_pipe_choked(struct lws *wsi)
+{
+       struct lws *wsi_eff = wsi;
+       fd_set writefds;
+       struct timeval tv = { 0, 0 };
+       int n;
+#if defined(LWS_WITH_HTTP2)
+       wsi_eff = lws_get_network_wsi(wsi);
+#endif
+
+       /* the fact we checked implies we avoided back-to-back writes */
+       wsi_eff->could_have_pending = 0;
+
+       /* treat the fact we got a truncated send pending as if we're choked */
+       if (lws_has_buffered_out(wsi)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+           || wsi->http.comp_ctx.buflist_comp ||
+              wsi->http.comp_ctx.may_have_more
+#endif
+       )
+               return 1;
+
+       FD_ZERO(&writefds);
+       FD_SET(wsi_eff->desc.sockfd, &writefds);
+
+       n = select(wsi_eff->desc.sockfd + 1, NULL, &writefds, NULL, &tv);
+       if (n < 0)
+               return 1; /* choked */
+
+       return !n; /* n = 0 = not writable = choked */
+}
+
+int
+lws_poll_listen_fd(struct lws_pollfd *fd)
+{
+       fd_set readfds;
+       struct timeval tv = { 0, 0 };
+
+       FD_ZERO(&readfds);
+       FD_SET(fd->fd, &readfds);
+
+       return select(fd->fd + 1, &readfds, NULL, NULL, &tv);
+}
+
+int
+lws_plat_check_connection_error(struct lws *wsi)
+{
+       return 0;
+}
+
+int
+lws_plat_set_nonblocking(int fd)
+{
+       return fcntl(fd, F_SETFL, O_NONBLOCK) < 0;
+}
+
+int
+lws_plat_set_socket_options(struct lws_vhost *vhost, int fd, int unix_skt)
+{
+       int optval = 1;
+       socklen_t optlen = sizeof(optval);
+
+#if defined(__APPLE__) || \
+    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
+    defined(__NetBSD__) || \
+    defined(__OpenBSD__)
+       struct protoent *tcp_proto;
+#endif
+
+       if (vhost->ka_time) {
+               /* enable keepalive on this socket */
+               optval = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+
+#if defined(__APPLE__) || \
+    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
+    defined(__NetBSD__) || \
+        defined(__CYGWIN__) || defined(__OpenBSD__) || defined (__sun)
+
+               /*
+                * didn't find a way to set these per-socket, need to
+                * tune kernel systemwide values
+                */
+#else
+               /* set the keepalive conditions we want on it too */
+               optval = vhost->ka_time;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+
+               optval = vhost->ka_interval;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+
+               optval = vhost->ka_probes;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+#endif
+       }
+
+       /* Disable Nagle */
+       optval = 1;
+       if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &optval, optlen) < 0)
+               return 1;
+
+       return lws_plat_set_nonblocking(fd);
+}
+
+/* cast a struct sockaddr_in6 * into addr for ipv6 */
+
+int
+lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
+                   size_t addrlen)
+{
+#if 0
+       int rc = LWS_ITOSA_NOT_EXIST;
+
+       struct ifaddrs *ifr;
+       struct ifaddrs *ifc;
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
+#endif
+
+       getifaddrs(&ifr);
+       for (ifc = ifr; ifc != NULL && rc; ifc = ifc->ifa_next) {
+               if (!ifc->ifa_addr)
+                       continue;
+
+               lwsl_info(" interface %s vs %s\n", ifc->ifa_name, ifname);
+
+               if (strcmp(ifc->ifa_name, ifname))
+                       continue;
+
+               switch (ifc->ifa_addr->sa_family) {
+               case AF_INET:
+#ifdef LWS_WITH_IPV6
+                       if (ipv6) {
+                               /* map IPv4 to IPv6 */
+                               memset((char *)&addr6->sin6_addr, 0,
+                                               sizeof(struct in6_addr));
+                               addr6->sin6_addr.s6_addr[10] = 0xff;
+                               addr6->sin6_addr.s6_addr[11] = 0xff;
+                               memcpy(&addr6->sin6_addr.s6_addr[12],
+                                       &((struct sockaddr_in *)ifc->ifa_addr)->sin_addr,
+                                                       sizeof(struct in_addr));
+                       } else
+#endif
+                               memcpy(addr,
+                                       (struct sockaddr_in *)ifc->ifa_addr,
+                                                   sizeof(struct sockaddr_in));
+                       break;
+#ifdef LWS_WITH_IPV6
+               case AF_INET6:
+                       memcpy(&addr6->sin6_addr,
+                         &((struct sockaddr_in6 *)ifc->ifa_addr)->sin6_addr,
+                                                      sizeof(struct in6_addr));
+                       break;
+#endif
+               default:
+                       continue;
+               }
+               rc = LWS_ITOSA_USABLE;
+       }
+
+       freeifaddrs(ifr);
+
+       if (rc == LWS_ITOSA_NOT_EXIST) {
+               /* check if bind to IP address */
+#ifdef LWS_WITH_IPV6
+               if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1)
+                       rc = LWS_ITOSA_USABLE;
+               else
+#endif
+               if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1)
+                       rc = LWS_ITOSA_USABLE;
+       }
+
+       return rc;
+#endif
+
+       return LWS_ITOSA_NOT_EXIST;
+}
+
+const char *
+lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
+{
+       return inet_ntop(af, src, dst, cnt);
+}
+
+int
+lws_plat_inet_pton(int af, const char *src, void *dst)
+{
+       return 1; //  inet_pton(af, src, dst);
+}
+
+
+
+
diff --git a/lib/plat/esp32/esp_attr.h b/lib/plat/esp32/esp_attr.h
new file mode 100644 (file)
index 0000000..5bf9a22
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef __ESP_ATTR_H__
+#define __ESP_ATTR_H__
+
+#define ROMFN_ATTR
+
+//Normally, the linker script will put all code and rodata in flash,
+//and all variables in shared RAM. These macros can be used to redirect
+//particular functions/variables to other memory regions.
+
+// Forces code into IRAM instead of flash.
+#define IRAM_ATTR __attribute__((section(".iram1")))
+
+// Forces data into DRAM instead of flash
+#define DRAM_ATTR __attribute__((section(".dram1")))
+
+// Forces data to be 4 bytes aligned
+#define WORD_ALIGNED_ATTR __attribute__((aligned(4)))
+
+// Forces data to be placed to DMA-capable places
+#define DMA_ATTR WORD_ALIGNED_ATTR DRAM_ATTR
+
+// Forces a string into DRAM instead of flash
+// Use as ets_printf(DRAM_STR("Hello world!\n"));
+#define DRAM_STR(str) (__extension__({static const DRAM_ATTR char __c[] = (str); (const char *)&__c;}))
+
+// Forces code into RTC fast memory. See "docs/deep-sleep-stub.rst"
+#define RTC_IRAM_ATTR __attribute__((section(".rtc.text")))
+
+// Forces data into RTC slow memory. See "docs/deep-sleep-stub.rst"
+// Any variable marked with this attribute will keep its value
+// during a deep sleep / wake cycle.
+#define RTC_DATA_ATTR __attribute__((section(".rtc.data")))
+
+// Forces read-only data into RTC slow memory. See "docs/deep-sleep-stub.rst"
+#define RTC_RODATA_ATTR __attribute__((section(".rtc.rodata")))
+
+// Forces data into noinit section to avoid initialization after restart.
+#define __NOINIT_ATTR __attribute__((section(".noinit")))
+
+// Forces data into RTC slow memory of .noinit section.
+// Any variable marked with this attribute will keep its value
+// after restart or during a deep sleep / wake cycle.
+#define RTC_NOINIT_ATTR  __attribute__((section(".rtc_noinit")))
+
+#endif /* __ESP_ATTR_H__ */
diff --git a/lib/plat/esp32/private.h b/lib/plat/esp32/private.h
new file mode 100644 (file)
index 0000000..fa48c51
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  Included from lib/core/private.h if LWS_WITH_ESP32
+ */
+
+#define MSG_NOSIGNAL 0
+#define SOMAXCONN 3
+
+#if defined(LWS_AMAZON_RTOS)
+ int
+ open(const char *path, int oflag, ...);
+#else
+ #include <fcntl.h>
+#endif
+
+ #include <strings.h>
+ #include <unistd.h>
+ #include <sys/stat.h>
+ #include <sys/types.h>
+ #include <sys/time.h>
+ #include <netdb.h>
+
+ #ifndef __cplusplus
+  #include <errno.h>
+ #endif
+ #include <netdb.h>
+ #include <signal.h>
+#if defined(LWS_AMAZON_RTOS)
+const char *
+gai_strerror(int);
+#else
+ #include <sys/socket.h>
+#endif
+
+#if defined(LWS_AMAZON_RTOS)
+ #include "FreeRTOS.h"
+ #include "timers.h"
+ #include <esp_attr.h>
+#else
+ #include "freertos/timers.h"
+ #include <esp_attr.h>
+ #include <esp_system.h>
+ #include <esp_task_wdt.h>
+#endif
+
+#include "lwip/apps/sntp.h"
+
+#include <lwip/sockets.h>
+
+ #if defined(LWS_BUILTIN_GETIFADDRS)
+  #include "./misc/getifaddrs.h"
+ #endif
+
+ #define LWS_ERRNO errno
+ #define LWS_EAGAIN EAGAIN
+ #define LWS_EALREADY EALREADY
+ #define LWS_EINPROGRESS EINPROGRESS
+ #define LWS_EINTR EINTR
+ #define LWS_EISCONN EISCONN
+ #define LWS_ENOTCONN ENOTCONN
+ #define LWS_EWOULDBLOCK EWOULDBLOCK
+ #define LWS_EADDRINUSE EADDRINUSE
+
+ #define lws_set_blocking_send(wsi)
+
+ #ifndef LWS_NO_FORK
+  #ifdef LWS_HAVE_SYS_PRCTL_H
+   #include <sys/prctl.h>
+  #endif
+ #endif
+
+#define compatible_close(x) close(x)
+#define lws_plat_socket_offset() LWIP_SOCKET_OFFSET
+#define wsi_from_fd(A,B)  A->lws_lookup[B - lws_plat_socket_offset()]
+
+struct lws_context;
+struct lws;
+
+int
+insert_wsi(const struct lws_context *context, struct lws *wsi);
+
+#define delete_from_fd(A,B) A->lws_lookup[B - lws_plat_socket_offset()] = 0
+
diff --git a/lib/plat/optee/lws-plat-optee.c b/lib/plat/optee/lws-plat-optee.c
new file mode 100644 (file)
index 0000000..21307fd
--- /dev/null
@@ -0,0 +1,213 @@
+#include "core/private.h"
+
+#if !defined(LWS_WITH_NETWORK)
+#include <crypto/crypto.h>
+#endif
+
+int errno;
+
+#if !defined(LWS_WITH_NETWORK)
+char *
+strcpy(char *dest, const char *src)
+{
+       char *desto = dest;
+
+       while (*src)
+               *(dest++) = *(src++);
+
+       *(dest++) = '\0';
+
+       return desto;
+}
+
+char *strncpy(char *dest, const char *src, size_t limit)
+{
+       char *desto = dest;
+
+       while (*src && limit--)
+               *(dest++) = *(src++);
+
+       if (limit)
+               *(dest++) = '\0';
+
+       return desto;
+}
+
+#endif
+
+int lws_plat_apply_FD_CLOEXEC(int n)
+{
+       return 0;
+}
+
+void TEE_GenerateRandom(void *randomBuffer, uint32_t randomBufferLen);
+#if defined(LWS_WITH_NETWORK)
+uint64_t
+lws_now_usecs(void)
+{
+       return ((unsigned long long)time(NULL)) * 1000000;
+}
+#endif
+
+int
+lws_get_random(struct lws_context *context, void *buf, int len)
+{
+#if defined(LWS_WITH_NETWORK)
+       TEE_GenerateRandom(buf, len);
+#else
+       crypto_rng_read(buf, len);
+#endif
+
+       return len;
+}
+
+
+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 */
+};
+
+void lwsl_emit_optee(int level, const char *line)
+{
+        char buf[50], linecp[512];
+        int n, m = LWS_ARRAY_SIZE(colours) - 1;
+
+        lwsl_timestamp(level, buf, sizeof(buf));
+
+        n = 1 << (LWS_ARRAY_SIZE(colours) - 1);
+        while (n) {
+                if (level & n)
+                        break;
+                m--;
+                n >>= 1;
+        }
+        n = strlen(line);
+        if ((unsigned int)n > sizeof(linecp) - 1)
+                n = sizeof(linecp) - 1;
+        if (n) {
+                memcpy(linecp, line, n - 1);
+               linecp[n - 1] = '\0';
+       } else
+               linecp[0] = '\0';
+        EMSG("%c%s%s%s%c[0m", 27, colours[m], buf, linecp, 27);
+}
+
+int
+lws_plat_set_nonblocking(int fd)
+{
+       return 0;
+}
+
+int
+lws_plat_drop_app_privileges(struct lws_context *context, int actually_init)
+{
+       return 0;
+}
+
+int
+lws_plat_context_early_init(void)
+{
+       return 0;
+}
+
+void
+lws_plat_context_early_destroy(struct lws_context *context)
+{
+}
+
+void
+lws_plat_context_late_destroy(struct lws_context *context)
+{
+#if defined(LWS_WITH_NETWORK)
+       if (context->lws_lookup)
+               lws_free(context->lws_lookup);
+#endif
+}
+
+lws_fop_fd_t
+_lws_plat_file_open(const struct lws_plat_file_ops *fops,
+                   const char *filename, const char *vpath, lws_fop_flags_t *flags)
+{
+       return NULL;
+}
+
+int
+_lws_plat_file_close(lws_fop_fd_t *fop_fd)
+{
+       return 0;
+}
+
+lws_fileofs_t
+_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+       return 0;
+}
+
+ int
+_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                   uint8_t *buf, lws_filepos_t len)
+{
+
+       return 0;
+}
+
+ int
+_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                    uint8_t *buf, lws_filepos_t len)
+{
+
+       return 0;
+}
+
+
+int
+lws_plat_init(struct lws_context *context,
+             const struct lws_context_creation_info *info)
+{
+#if defined(LWS_WITH_NETWORK)
+       /* master context has the global fd lookup array */
+       context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
+                                        context->max_fds, "lws_lookup");
+       if (context->lws_lookup == NULL) {
+               lwsl_err("OOM on lws_lookup array for %d connections\n",
+                        context->max_fds);
+               return 1;
+       }
+
+       lwsl_notice(" mem: platform fd map: %5lu bytes\n",
+                   (long)sizeof(struct lws *) * context->max_fds);
+#endif
+#ifdef LWS_WITH_PLUGINS
+       if (info->plugin_dirs)
+               lws_plat_plugins_init(context, info->plugin_dirs);
+#endif
+
+       return 0;
+}
+
+int
+lws_plat_write_file(const char *filename, void *buf, int len)
+{
+       return 1;
+}
+
+int
+lws_plat_read_file(const char *filename, void *buf, int len)
+{
+       return -1;
+}
+
+int
+lws_plat_recommended_rsa_bits(void)
+{
+       return 4096;
+}
diff --git a/lib/plat/optee/network.c b/lib/plat/optee/network.c
new file mode 100644 (file)
index 0000000..2677de8
--- /dev/null
@@ -0,0 +1,235 @@
+#include "core/private.h"
+
+
+int
+lws_plat_pipe_create(struct lws *wsi)
+{
+       return 1;
+}
+
+int
+lws_plat_pipe_signal(struct lws *wsi)
+{
+       return 1;
+}
+
+void
+lws_plat_pipe_close(struct lws *wsi)
+{
+}
+
+LWS_VISIBLE int
+lws_send_pipe_choked(struct lws *wsi)
+{
+       struct lws *wsi_eff;
+
+#if defined(LWS_WITH_HTTP2)
+       wsi_eff = lws_get_network_wsi(wsi);
+#else
+       wsi_eff = wsi;
+#endif
+
+       /* the fact we checked implies we avoided back-to-back writes */
+       wsi_eff->could_have_pending = 0;
+
+       /* treat the fact we got a truncated send pending as if we're choked */
+       if (lws_has_buffered_out(wsi_eff)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+           || wsi->http.comp_ctx.buflist_comp ||
+              wsi->http.comp_ctx.may_have_more
+#endif
+       )
+               return 1;
+
+       /* okay to send another packet without blocking */
+
+       return 0;
+}
+
+int
+lws_poll_listen_fd(struct lws_pollfd *fd)
+{
+//     return poll(fd, 1, 0);
+
+       return 0;
+}
+
+
+LWS_EXTERN int
+_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
+{
+       lws_usec_t timeout_us = timeout_ms * LWS_US_PER_MS;
+       struct lws_context_per_thread *pt;
+       int n = -1, m, c;
+       //char buf;
+
+       /* stay dead once we are dead */
+
+       if (!context || !context->vhost_list)
+               return 1;
+
+       pt = &context->pt[tsi];
+
+       if (timeout_ms < 0)
+               goto faked_service;
+
+       if (!pt->service_tid_detected) {
+               struct lws _lws;
+
+               memset(&_lws, 0, sizeof(_lws));
+               _lws.context = context;
+
+               pt->service_tid = context->vhost_list->protocols[0].callback(
+                       &_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
+               pt->service_tid_detected = 1;
+       }
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(context, 1, tsi)) {
+               lwsl_notice("%s: doing forced service\n", __func__);
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(context, 1, pt->tid))
+                       /* yes... come back again quickly */
+                       timeout_us = 0;
+       }
+
+       if (timeout_us) {
+               lws_usec_t us;
+
+               lws_pt_lock(pt, __func__);
+               /* don't stay in poll wait longer than next hr timeout */
+               us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+               if (us && us < timeout_us)
+                       timeout_us = us;
+
+               lws_pt_unlock(pt);
+       }
+
+       n = poll(pt->fds, pt->fds_count, timeout_us / LWS_US_PER_MS);
+
+       m = 0;
+
+       if (pt->context->tls_ops &&
+           pt->context->tls_ops->fake_POLLIN_for_buffered)
+               m = pt->context->tls_ops->fake_POLLIN_for_buffered(pt);
+
+       if (/*!pt->ws.rx_draining_ext_list && */!m && !n) { /* nothing to do */
+               lws_service_fd_tsi(context, NULL, tsi);
+               return 0;
+       }
+
+faked_service:
+       m = lws_service_flag_pending(context, tsi);
+       if (m)
+               c = -1; /* unknown limit */
+       else
+               if (n < 0) {
+                       if (LWS_ERRNO != LWS_EINTR)
+                               return -1;
+                       return 0;
+               } else
+                       c = n;
+
+       /* any socket with events to service? */
+       for (n = 0; n < (int)pt->fds_count && c; n++) {
+               if (!pt->fds[n].revents)
+                       continue;
+
+               c--;
+#if 0
+               if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) {
+                       if (read(pt->fds[n].fd, &buf, 1) != 1)
+                               lwsl_err("Cannot read from dummy pipe.");
+                       continue;
+               }
+#endif
+               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
+               if (m < 0)
+                       return -1;
+               /* if something closed, retry this slot */
+               if (m)
+                       n--;
+       }
+
+       return 0;
+}
+
+int
+lws_plat_check_connection_error(struct lws *wsi)
+{
+       return 0;
+}
+
+int
+lws_plat_service(struct lws_context *context, int timeout_ms)
+{
+       return _lws_plat_service_tsi(context, timeout_ms, 0);
+}
+
+int
+lws_plat_set_socket_options(struct lws_vhost *vhost, int fd, int unix_skt)
+{
+       return 0;
+}
+
+
+int
+lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
+                       int len)
+{
+       return 1;
+}
+
+
+/* cast a struct sockaddr_in6 * into addr for ipv6 */
+
+int
+lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
+                   size_t addrlen)
+{
+       return -1;
+}
+
+void
+lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       pt->fds[pt->fds_count++].revents = 0;
+}
+
+void
+lws_plat_delete_socket_from_fds(struct lws_context *context,
+                                               struct lws *wsi, int m)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       pt->fds_count--;
+}
+
+int
+lws_plat_change_pollfd(struct lws_context *context,
+                     struct lws *wsi, struct lws_pollfd *pfd)
+{
+       return 0;
+}
+
+const char *
+lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
+{
+       //return inet_ntop(af, src, dst, cnt);
+       return "lws_plat_inet_ntop";
+}
+
+int
+lws_plat_inet_pton(int af, const char *src, void *dst)
+{
+       //return inet_pton(af, src, dst);
+       return 1;
+}
+
+
diff --git a/lib/plat/optee/private.h b/lib/plat/optee/private.h
new file mode 100644 (file)
index 0000000..256289f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  Included from lib/core/private.h if LWS_WITH_OPTEE
+ */
+
+ #include <unistd.h>
+ #include <sys/types.h>
+
+ #define LWS_ERRNO errno
+ #define LWS_EAGAIN EAGAIN
+ #define LWS_EALREADY EALREADY
+ #define LWS_EINPROGRESS EINPROGRESS
+ #define LWS_EINTR EINTR
+ #define LWS_EISCONN EISCONN
+ #define LWS_ENOTCONN ENOTCONN
+ #define LWS_EWOULDBLOCK EWOULDBLOCK
+ #define LWS_EADDRINUSE EADDRINUSE
+
+ #define lws_set_blocking_send(wsi)
+
+#define compatible_close(x) close(x)
+#define lws_plat_socket_offset() (0)
+#define wsi_from_fd(A,B)  A->lws_lookup[B - lws_plat_socket_offset()]
+#define insert_wsi(A,B)   assert(A->lws_lookup[B->desc.sockfd - \
+                                 lws_plat_socket_offset()] == 0); \
+                                A->lws_lookup[B->desc.sockfd - \
+                                 lws_plat_socket_offset()] = B
+#define delete_from_fd(A,B) A->lws_lookup[B - lws_plat_socket_offset()] = 0
+
diff --git a/lib/plat/unix/private.h b/lib/plat/unix/private.h
new file mode 100644 (file)
index 0000000..1702d4b
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  Included from lib/core/private.h if no explicit platform
+ */
+
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <poll.h>
+#include <netdb.h>
+
+#ifndef __cplusplus
+#include <errno.h>
+#endif
+#include <netdb.h>
+#include <signal.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/un.h>
+
+#if defined(__APPLE__)
+#include <machine/endian.h>
+#endif
+#if defined(__FreeBSD__)
+#include <sys/endian.h>
+#endif
+#if defined(__linux__)
+#include <endian.h>
+#endif
+#if defined(__QNX__)
+       #include <gulliver.h>
+       #if defined(__LITTLEENDIAN__)
+               #define BYTE_ORDER __LITTLEENDIAN__
+               #define LITTLE_ENDIAN __LITTLEENDIAN__
+               #define BIG_ENDIAN 4321  /* to show byte order (taken from gcc); for suppres warning that BIG_ENDIAN is not defined. */
+       #endif
+       #if defined(__BIGENDIAN__)
+               #define BYTE_ORDER __BIGENDIAN__
+               #define LITTLE_ENDIAN 1234  /* to show byte order (taken from gcc); for suppres warning that LITTLE_ENDIAN is not defined. */
+               #define BIG_ENDIAN __BIGENDIAN__
+       #endif
+#endif
+
+#if defined(__sun) && defined(__GNUC__)
+
+#include <arpa/nameser_compat.h>
+
+#if !defined (BYTE_ORDER)
+#define BYTE_ORDER __BYTE_ORDER__
+#endif
+
+#if !defined(LITTLE_ENDIAN)
+#define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__
+#endif
+
+#if !defined(BIG_ENDIAN)
+#define BIG_ENDIAN __ORDER_BIG_ENDIAN__
+#endif
+
+#endif /* sun + GNUC */
+
+#if !defined(BYTE_ORDER)
+#define BYTE_ORDER __BYTE_ORDER
+#endif
+#if !defined(LITTLE_ENDIAN)
+#define LITTLE_ENDIAN __LITTLE_ENDIAN
+#endif
+#if !defined(BIG_ENDIAN)
+#define BIG_ENDIAN __BIG_ENDIAN
+#endif
+
+#if defined(LWS_BUILTIN_GETIFADDRS)
+#include "./misc/getifaddrs.h"
+#else
+
+#if defined(__HAIKU__)
+#define _BSD_SOURCE
+#endif
+#include <ifaddrs.h>
+
+#endif
+
+#if defined (__sun) || defined(__HAIKU__) || defined(__QNX__) || defined(__ANDROID__)
+#include <syslog.h>
+
+#if defined(__ANDROID__)
+#include <sys/resource.h>
+#endif
+
+#else
+#include <sys/syslog.h>
+#endif
+
+#ifdef __QNX__
+# include "netinet/tcp_var.h"
+# define TCP_KEEPINTVL TCPCTL_KEEPINTVL
+# define TCP_KEEPIDLE  TCPCTL_KEEPIDLE
+# define TCP_KEEPCNT   TCPCTL_KEEPCNT
+#endif
+
+#define LWS_ERRNO errno
+#define LWS_EAGAIN EAGAIN
+#define LWS_EALREADY EALREADY
+#define LWS_EINPROGRESS EINPROGRESS
+#define LWS_EINTR EINTR
+#define LWS_EISCONN EISCONN
+#define LWS_ENOTCONN ENOTCONN
+#define LWS_EWOULDBLOCK EWOULDBLOCK
+#define LWS_EADDRINUSE EADDRINUSE
+#define lws_set_blocking_send(wsi)
+#define LWS_SOCK_INVALID (-1)
+
+struct lws_context;
+
+struct lws *
+wsi_from_fd(const struct lws_context *context, int fd);
+
+int
+insert_wsi(const struct lws_context *context, struct lws *wsi);
+
+void
+delete_from_fd(const struct lws_context *context, int fd);
+
+#ifndef LWS_NO_FORK
+#ifdef LWS_HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+#endif
+
+#define compatible_close(x) close(x)
+#define lws_plat_socket_offset() (0)
+
+/*
+ * Mac OSX as well as iOS do not define the MSG_NOSIGNAL flag,
+ * but happily have something equivalent in the SO_NOSIGPIPE flag.
+ */
+#ifdef __APPLE__
+#define MSG_NOSIGNAL SO_NOSIGPIPE
+#endif
+
+/*
+ * Solaris 11.X only supports POSIX 2001, MSG_NOSIGNAL appears in
+ * POSIX 2008.
+ */
+#if defined(__sun) && !defined(__smartos__)
+ #define MSG_NOSIGNAL 0
+#endif
diff --git a/lib/plat/unix/unix-caps.c b/lib/plat/unix/unix-caps.c
new file mode 100644 (file)
index 0000000..3414e30
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+#include <pwd.h>
+#include <grp.h>
+
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+static void
+_lws_plat_apply_caps(int mode, const cap_value_t *cv, int count)
+{
+       cap_t caps;
+
+       if (!count)
+               return;
+
+       caps = cap_get_proc();
+
+       cap_set_flag(caps, mode, count, cv, CAP_SET);
+       cap_set_proc(caps);
+       prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
+       cap_free(caps);
+}
+#endif
+
+int
+lws_plat_user_colon_group_to_ids(const char *u_colon_g, uid_t *puid, gid_t *pgid)
+{
+       char *colon = strchr(u_colon_g, ':'), u[33];
+       struct passwd *p;
+       struct group *g;
+       int ulen;
+
+       if (!colon)
+               return 1;
+
+       ulen = lws_ptr_diff(colon, u_colon_g);
+       if (ulen < 2 || ulen > (int)sizeof(u) - 1)
+               return 1;
+
+       memcpy(u, u_colon_g, ulen);
+       u[ulen] = '\0';
+
+       colon++;
+
+       g = getgrnam(colon);
+       if (!g) {
+               lwsl_err("%s: unknown group '%s'\n", __func__, colon);
+
+               return 1;
+       }
+       *pgid = g->gr_gid;
+
+       p = getpwnam(u);
+       if (!p) {
+               lwsl_err("%s: unknown group '%s'\n", __func__, u);
+
+               return 1;
+       }
+       *puid = p->pw_uid;
+
+       return 0;
+}
+
+int
+lws_plat_drop_app_privileges(struct lws_context *context, int actually_drop)
+{
+       struct passwd *p;
+       struct group *g;
+
+       /* if he gave us the groupname, align gid to match it */
+
+       if (context->groupname) {
+               g = getgrnam(context->groupname);
+
+               if (g) {
+                       lwsl_info("%s: group %s -> gid %u\n", __func__,
+                                 context->groupname, g->gr_gid);
+                       context->gid = g->gr_gid;
+               } else {
+                       lwsl_err("%s: unknown groupname '%s'\n", __func__,
+                                context->groupname);
+
+                       return 1;
+               }
+       }
+
+       /* if he gave us the username, align uid to match it */
+
+       if (context->username) {
+               p = getpwnam(context->username);
+
+               if (p) {
+                       context->uid = p->pw_uid;
+
+                       lwsl_info("%s: username %s -> uid %u\n", __func__,
+                                 context->username, (unsigned int)p->pw_uid);
+               } else {
+                       lwsl_err("%s: unknown username %s\n", __func__,
+                                context->username);
+
+                       return 1;
+               }
+       }
+
+       if (!actually_drop)
+               return 0;
+
+       /* if he gave us the gid or we have it from the groupname, set it */
+
+       if (context->gid && context->gid != -1) {
+               g = getgrgid(context->gid);
+
+               if (!g) {
+                       lwsl_err("%s: cannot find name for gid %d\n",
+                                 __func__, context->gid);
+
+                       return 1;
+               }
+
+               if (setgid(context->gid)) {
+                       lwsl_err("%s: setgid: %s failed\n", __func__,
+                                strerror(LWS_ERRNO));
+
+                       return 1;
+               }
+
+               lwsl_notice("%s: effective group '%s'\n", __func__,
+                           g->gr_name);
+       } else
+               lwsl_info("%s: not changing group\n", __func__);
+
+
+       /* if he gave us the uid or we have it from the username, set it */
+
+       if (context->uid && context->uid != -1) {
+               p = getpwuid(context->uid);
+
+               if (!p) {
+                       lwsl_err("%s: getpwuid: unable to find uid %d\n",
+                                __func__, context->uid);
+                       return 1;
+               }
+
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+               _lws_plat_apply_caps(CAP_PERMITTED, context->caps,
+                                    context->count_caps);
+#endif
+
+               initgroups(p->pw_name, context->gid);
+               if (setuid(context->uid)) {
+                       lwsl_err("%s: setuid: %s failed\n", __func__,
+                                 strerror(LWS_ERRNO));
+
+                       return 1;
+               } else
+                       lwsl_notice("%s: effective user '%s'\n",
+                                   __func__, p->pw_name);
+
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+               _lws_plat_apply_caps(CAP_EFFECTIVE, context->caps,
+                                    context->count_caps);
+
+               if (context->count_caps) {
+                       int n;
+                       for (n = 0; n < context->count_caps; n++)
+                               lwsl_notice("   RETAINING CAP %d\n",
+                                           (int)context->caps[n]);
+               }
+#endif
+       } else
+               lwsl_info("%s: not changing user\n", __func__);
+
+       return 0;
+}
diff --git a/lib/plat/unix/unix-fds.c b/lib/plat/unix/unix-fds.c
new file mode 100644 (file)
index 0000000..221d967
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+struct lws *
+wsi_from_fd(const struct lws_context *context, int fd)
+{
+       struct lws **p, **done;
+
+       if (!context->max_fds_unrelated_to_ulimit)
+               return context->lws_lookup[fd - lws_plat_socket_offset()];
+
+       /* slow fds handling */
+
+       p = context->lws_lookup;
+       done = &p[context->max_fds];
+
+       while (p != done) {
+               if (*p && (*p)->desc.sockfd == fd)
+                       return *p;
+               p++;
+       }
+
+       return NULL;
+}
+
+int
+insert_wsi(const struct lws_context *context, struct lws *wsi)
+{
+       struct lws **p, **done;
+
+       if (!context->max_fds_unrelated_to_ulimit) {
+               assert(context->lws_lookup[wsi->desc.sockfd -
+                                          lws_plat_socket_offset()] == 0);
+
+               context->lws_lookup[wsi->desc.sockfd - \
+                                 lws_plat_socket_offset()] = wsi;
+
+               return 0;
+       }
+
+       /* slow fds handling */
+
+       p = context->lws_lookup;
+       done = &p[context->max_fds];
+
+#if defined(_DEBUG)
+
+       /* confirm it doesn't already exist */
+
+       while (p != done && *p != wsi)
+               p++;
+
+       assert(p == done);
+       p = context->lws_lookup;
+
+       /* confirm fd doesn't already exist */
+
+       while (p != done && (!*p || (*p && (*p)->desc.sockfd != wsi->desc.sockfd)))
+               p++;
+
+       if (p != done) {
+               lwsl_err("%s: wsi %p already says it has fd %d\n",
+                               __func__, *p, wsi->desc.sockfd);
+               assert(0);
+       }
+       p = context->lws_lookup;
+#endif
+
+       /* find an empty slot */
+
+       while (p != done && *p)
+               p++;
+
+       if (p == done) {
+               lwsl_err("%s: reached max fds\n", __func__);
+               return 1;
+       }
+
+       *p = wsi;
+
+       return 0;
+}
+
+void
+delete_from_fd(const struct lws_context *context, int fd)
+{
+
+       struct lws **p, **done;
+
+       if (!context->max_fds_unrelated_to_ulimit) {
+               context->lws_lookup[fd - lws_plat_socket_offset()] = NULL;
+
+               return;
+       }
+
+       /* slow fds handling */
+
+       p = context->lws_lookup;
+       done = &p[context->max_fds];
+
+       /* find the match */
+
+       while (p != done && (!*p || (*p && (*p)->desc.sockfd != fd)))
+               p++;
+
+       if (p == done)
+               lwsl_err("%s: fd %d not found\n", __func__, fd);
+       else
+               *p = NULL;
+
+#if defined(_DEBUG)
+       p = context->lws_lookup;
+       while (p != done && (!*p || (*p && (*p)->desc.sockfd != fd)))
+               p++;
+
+       if (p != done) {
+               lwsl_err("%s: fd %d in lws_lookup again at %d\n", __func__,
+                               fd, (int)(p - context->lws_lookup));
+               assert(0);
+       }
+#endif
+}
+
+void
+lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       if (context->event_loop_ops->io)
+               context->event_loop_ops->io(wsi, LWS_EV_START | LWS_EV_READ);
+
+       pt->fds[pt->fds_count++].revents = 0;
+}
+
+void
+lws_plat_delete_socket_from_fds(struct lws_context *context,
+                                               struct lws *wsi, int m)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       if (context->event_loop_ops->io)
+               context->event_loop_ops->io(wsi,
+                               LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE);
+
+       pt->fds_count--;
+}
+
+int
+lws_plat_change_pollfd(struct lws_context *context,
+                     struct lws *wsi, struct lws_pollfd *pfd)
+{
+       return 0;
+}
diff --git a/lib/plat/unix/unix-file.c b/lib/plat/unix/unix-file.c
new file mode 100644 (file)
index 0000000..bcdc213
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+#include <pwd.h>
+#include <grp.h>
+
+#ifdef LWS_WITH_PLUGINS
+#include <dlfcn.h>
+#endif
+#include <dirent.h>
+
+int lws_plat_apply_FD_CLOEXEC(int n)
+{
+       if (n == -1)
+               return 0;
+
+       return fcntl(n, F_SETFD, FD_CLOEXEC);
+}
+
+int
+lws_plat_write_file(const char *filename, void *buf, int len)
+{
+       int m, fd;
+
+       fd = lws_open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+
+       if (fd == -1)
+               return 1;
+
+       m = write(fd, buf, len);
+       close(fd);
+
+       return m != len;
+}
+
+int
+lws_plat_read_file(const char *filename, void *buf, int len)
+{
+       int n, fd = lws_open(filename, O_RDONLY);
+       if (fd == -1)
+               return -1;
+
+       n = read(fd, buf, len);
+       close(fd);
+
+       return n;
+}
+
+lws_fop_fd_t
+_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
+                   const char *vpath, lws_fop_flags_t *flags)
+{
+       struct stat stat_buf;
+       int ret = lws_open(filename, (*flags) & LWS_FOP_FLAGS_MASK, 0664);
+       lws_fop_fd_t fop_fd;
+
+       if (ret < 0)
+               return NULL;
+
+       if (fstat(ret, &stat_buf) < 0)
+               goto bail;
+
+       fop_fd = malloc(sizeof(*fop_fd));
+       if (!fop_fd)
+               goto bail;
+
+       fop_fd->fops = fops;
+       fop_fd->flags = *flags;
+       fop_fd->fd = ret;
+       fop_fd->filesystem_priv = NULL; /* we don't use it */
+       fop_fd->len = stat_buf.st_size;
+       fop_fd->pos = 0;
+
+       return fop_fd;
+
+bail:
+       close(ret);
+       return NULL;
+}
+
+int
+_lws_plat_file_close(lws_fop_fd_t *fop_fd)
+{
+       int fd = (*fop_fd)->fd;
+
+       free(*fop_fd);
+       *fop_fd = NULL;
+
+       return close(fd);
+}
+
+lws_fileofs_t
+_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+       lws_fileofs_t r;
+
+       if (offset > 0 &&
+           offset > (lws_fileofs_t)fop_fd->len - (lws_fileofs_t)fop_fd->pos)
+               offset = fop_fd->len - fop_fd->pos;
+
+       if ((lws_fileofs_t)fop_fd->pos + offset < 0)
+               offset = -fop_fd->pos;
+
+       r = lseek(fop_fd->fd, offset, SEEK_CUR);
+
+       if (r >= 0)
+               fop_fd->pos = r;
+       else
+               lwsl_err("error seeking from cur %ld, offset %ld\n",
+                        (long)fop_fd->pos, (long)offset);
+
+       return r;
+}
+
+int
+_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                   uint8_t *buf, lws_filepos_t len)
+{
+       long n;
+
+       n = read((int)fop_fd->fd, buf, len);
+       if (n == -1) {
+               *amount = 0;
+               return -1;
+       }
+       fop_fd->pos += n;
+       lwsl_debug("%s: read %ld of req %ld, pos %ld, len %ld\n", __func__, n,
+                  (long)len, (long)fop_fd->pos, (long)fop_fd->len);
+       *amount = n;
+
+       return 0;
+}
+
+int
+_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                    uint8_t *buf, lws_filepos_t len)
+{
+       long n;
+
+       n = write((int)fop_fd->fd, buf, len);
+       if (n == -1) {
+               *amount = 0;
+               return -1;
+       }
+
+       fop_fd->pos += n;
+       *amount = n;
+
+       return 0;
+}
+
diff --git a/lib/plat/unix/unix-init.c b/lib/plat/unix/unix-init.c
new file mode 100644 (file)
index 0000000..6204e52
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+#include <pwd.h>
+#include <grp.h>
+
+#ifdef LWS_WITH_PLUGINS
+#include <dlfcn.h>
+#endif
+#include <dirent.h>
+
+#if defined(LWS_HAVE_MALLOC_TRIM)
+#include <malloc.h>
+#endif
+
+#if defined(LWS_WITH_NETWORK)
+static void
+lws_sul_plat_unix(lws_sorted_usec_list_t *sul)
+{
+       struct lws_context_per_thread *pt =
+               lws_container_of(sul, struct lws_context_per_thread, sul_plat);
+       struct lws_context *context = pt->context;
+#if defined(LWS_ROLE_CGI) || defined(LWS_ROLE_DBUS)
+       time_t now = time(NULL);
+#endif
+
+#if !defined(LWS_NO_DAEMONIZE)
+       /* if our parent went down, don't linger around */
+       if (pt->context->started_with_parent &&
+           kill(pt->context->started_with_parent, 0) < 0)
+               kill(getpid(), SIGTERM);
+#endif
+#if defined(LWS_HAVE_MALLOC_TRIM)
+       malloc_trim(4 * 1024);
+#endif
+
+       if (pt->context->deprecated && !pt->context->count_wsi_allocated) {
+               lwsl_notice("%s: ending deprecated context\n", __func__);
+               kill(getpid(), SIGINT);
+               return;
+       }
+
+       lws_check_deferred_free(context, 0, 0);
+
+       lws_context_lock(context, "periodic checks");
+       lws_start_foreach_llp(struct lws_vhost **, pv,
+                             context->no_listener_vhost_list) {
+               struct lws_vhost *v = *pv;
+               lwsl_debug("deferred iface: checking if on vh %s\n", (*pv)->name);
+               if (_lws_vhost_init_server(NULL, *pv) == 0) {
+                       /* became happy */
+                       lwsl_notice("vh %s: became connected\n", v->name);
+                       *pv = v->no_listener_vhost_list;
+                       v->no_listener_vhost_list = NULL;
+                       break;
+               }
+       } lws_end_foreach_llp(pv, no_listener_vhost_list);
+       lws_context_unlock(context);
+
+#if defined(LWS_ROLE_CGI)
+       role_ops_cgi.periodic_checks(context, 0, now);
+#endif
+#if defined(LWS_ROLE_DBUS)
+       role_ops_dbus.periodic_checks(context, 0, now);
+#endif
+
+       __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_plat, 30 * LWS_US_PER_SEC);
+}
+#endif
+
+int
+lws_plat_init(struct lws_context *context,
+             const struct lws_context_creation_info *info)
+{
+       int fd;
+#if defined(LWS_WITH_NETWORK)
+       /*
+        * master context has the process-global fd lookup array.  This can be
+        * done two different ways now; one or the other is done depending on if
+        * info->fd_limit_per_thread was snonzero
+        *
+        *  - default: allocate a worst-case lookup array sized for ulimit -n
+        *             and use the fd directly as an index into it
+        *
+        *  - slow:    allocate context->max_fds entries only (which can be
+        *             forced at context creation time to be
+        *             info->fd_limit_per_thread * the number of threads)
+        *             and search the array to lookup fds
+        *
+        * the default way is optimized for server, if you only use one or two
+        * client wsi the slow way may save a lot of memory.
+        *
+        * Both ways allocate an array of struct lws *... one allocates it for
+        * all possible fd indexes the process could produce and uses it as a
+        * map, the other allocates for an amount of wsi the lws context is
+        * expected to use and searches through it to manipulate it.
+        */
+
+       context->lws_lookup = lws_zalloc(sizeof(struct lws *) *
+                                        context->max_fds, "lws_lookup");
+
+       if (!context->lws_lookup) {
+               lwsl_err("%s: OOM on alloc lws_lookup array for %d conn\n",
+                        __func__, context->max_fds);
+               return 1;
+       }
+
+       lwsl_info(" mem: platform fd map: %5lu B\n",
+                   (unsigned long)(sizeof(struct lws *) * context->max_fds));
+#endif
+       fd = lws_open(SYSTEM_RANDOM_FILEPATH, O_RDONLY);
+
+       context->fd_random = fd;
+       if (context->fd_random < 0) {
+               lwsl_err("Unable to open random device %s %d\n",
+                        SYSTEM_RANDOM_FILEPATH, context->fd_random);
+               return 1;
+       }
+
+#if defined(LWS_WITH_PLUGINS)
+       if (info->plugin_dirs)
+               lws_plat_plugins_init(context, info->plugin_dirs);
+#endif
+
+
+#if defined(LWS_WITH_NETWORK)
+       /* we only need to do this on pt[0] */
+
+       context->pt[0].sul_plat.cb = lws_sul_plat_unix;
+       __lws_sul_insert(&context->pt[0].pt_sul_owner, &context->pt[0].sul_plat,
+                        30 * LWS_US_PER_SEC);
+#endif
+
+       return 0;
+}
+
+int
+lws_plat_context_early_init(void)
+{
+#if !defined(LWS_AVOID_SIGPIPE_IGN)
+       signal(SIGPIPE, SIG_IGN);
+#endif
+
+       return 0;
+}
+
+void
+lws_plat_context_early_destroy(struct lws_context *context)
+{
+}
+
+void
+lws_plat_context_late_destroy(struct lws_context *context)
+{
+#ifdef LWS_WITH_PLUGINS
+       if (context->plugin_list)
+               lws_plat_plugins_destroy(context);
+#endif
+#if defined(LWS_WITH_NETWORK)
+       if (context->lws_lookup)
+               lws_free_set_NULL(context->lws_lookup);
+#endif
+       if (!context->fd_random)
+               lwsl_err("ZERO RANDOM FD\n");
+       if (context->fd_random != LWS_INVALID_FILE)
+               close(context->fd_random);
+}
diff --git a/lib/plat/unix/unix-misc.c b/lib/plat/unix/unix-misc.c
new file mode 100644 (file)
index 0000000..f5e7a94
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+lws_usec_t
+lws_now_usecs(void)
+{
+#if defined(LWS_HAVE_CLOCK_GETTIME)
+       struct timespec ts;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts))
+               return 0;
+
+       return (ts.tv_sec * LWS_US_PER_SEC) + (ts.tv_nsec / LWS_NS_PER_US);
+#else
+       struct timeval now;
+
+       gettimeofday(&now, NULL);
+       return (now.tv_sec * 1000000ll) + now.tv_usec;
+#endif
+}
+
+LWS_VISIBLE int
+lws_get_random(struct lws_context *context, void *buf, int len)
+{
+       return read(context->fd_random, (char *)buf, len);
+}
+
+LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
+{
+       int syslog_level = LOG_DEBUG;
+
+       switch (level) {
+       case LLL_ERR:
+               syslog_level = LOG_ERR;
+               break;
+       case LLL_WARN:
+               syslog_level = LOG_WARNING;
+               break;
+       case LLL_NOTICE:
+               syslog_level = LOG_NOTICE;
+               break;
+       case LLL_INFO:
+               syslog_level = LOG_INFO;
+               break;
+       }
+       syslog(syslog_level, "%s", line);
+}
+
+
+int
+lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
+                       int len)
+{
+       int n;
+
+       n = write(fd, buf, len);
+
+       fsync(fd);
+       if (lseek(fd, 0, SEEK_SET) < 0)
+               return 1;
+
+       return n != len;
+}
+
+
+int
+lws_plat_recommended_rsa_bits(void)
+{
+       return 4096;
+}
diff --git a/lib/plat/unix/unix-pipe.c b/lib/plat/unix/unix-pipe.c
new file mode 100644 (file)
index 0000000..64ce253
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+
+int
+lws_plat_pipe_create(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+#if defined(LWS_HAVE_PIPE2)
+       return pipe2(pt->dummy_pipe_fds, O_NONBLOCK);
+#else
+       return pipe(pt->dummy_pipe_fds);
+#endif
+}
+
+int
+lws_plat_pipe_signal(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       char buf = 0;
+       int n;
+
+       n = write(pt->dummy_pipe_fds[1], &buf, 1);
+
+       return n != 1;
+}
+
+void
+lws_plat_pipe_close(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       if (pt->dummy_pipe_fds[0] && pt->dummy_pipe_fds[0] != -1)
+               close(pt->dummy_pipe_fds[0]);
+       if (pt->dummy_pipe_fds[1] && pt->dummy_pipe_fds[1] != -1)
+               close(pt->dummy_pipe_fds[1]);
+
+       pt->dummy_pipe_fds[0] = pt->dummy_pipe_fds[1] = -1;
+}
+
diff --git a/lib/plat/unix/unix-plugins.c b/lib/plat/unix/unix-plugins.c
new file mode 100644 (file)
index 0000000..a56046a
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+#include <pwd.h>
+#include <grp.h>
+
+#ifdef LWS_WITH_PLUGINS
+#include <dlfcn.h>
+#endif
+#include <dirent.h>
+
+static int filter(const struct dirent *ent)
+{
+       if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+               return 0;
+
+       return 1;
+}
+
+int
+lws_plat_plugins_init(struct lws_context * context, const char * const *d)
+{
+       struct lws_plugin_capability lcaps;
+       struct lws_plugin *plugin;
+       lws_plugin_init_func initfunc;
+       struct dirent **namelist;
+       int n, i, m, ret = 0;
+       char path[256];
+       void *l;
+
+#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
+               return lws_uv_plugins_init(context, d);
+#endif
+
+       lwsl_notice("  Plugins:\n");
+
+       while (d && *d) {
+               n = scandir(*d, &namelist, filter, alphasort);
+               if (n < 0) {
+                       lwsl_err("Scandir on %s failed\n", *d);
+                       return 1;
+               }
+
+               for (i = 0; i < n; i++) {
+                       if (strlen(namelist[i]->d_name) < 7)
+                               goto inval;
+
+                       lwsl_notice("   %s\n", namelist[i]->d_name);
+
+                       lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d,
+                                namelist[i]->d_name);
+                       l = dlopen(path, RTLD_NOW);
+                       if (!l) {
+                               lwsl_err("Error loading DSO: %s\n", dlerror());
+                               while (i++ < n)
+                                       free(namelist[i]);
+                               goto bail;
+                       }
+                       /* we could open it, can we get his init function? */
+                       m = lws_snprintf(path, sizeof(path) - 1, "init_%s",
+                                    namelist[i]->d_name + 3 /* snip lib... */);
+                       path[m - 3] = '\0'; /* snip the .so */
+                       initfunc = dlsym(l, path);
+                       if (!initfunc) {
+                               lwsl_err("Failed to get init on %s: %s",
+                                               namelist[i]->d_name, dlerror());
+                               dlclose(l);
+                       }
+                       lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
+                       m = initfunc(context, &lcaps);
+                       if (m) {
+                               lwsl_err("Initializing %s failed %d\n",
+                                       namelist[i]->d_name, m);
+                               dlclose(l);
+                               goto skip;
+                       }
+
+                       plugin = lws_malloc(sizeof(*plugin), "plugin");
+                       if (!plugin) {
+                               lwsl_err("OOM\n");
+                               goto bail;
+                       }
+                       plugin->list = context->plugin_list;
+                       context->plugin_list = plugin;
+                       lws_strncpy(plugin->name, namelist[i]->d_name,
+                                   sizeof(plugin->name));
+                       plugin->l = l;
+                       plugin->caps = lcaps;
+                       context->plugin_protocol_count += lcaps.count_protocols;
+                       context->plugin_extension_count += lcaps.count_extensions;
+
+                       free(namelist[i]);
+                       continue;
+
+       skip:
+                       dlclose(l);
+       inval:
+                       free(namelist[i]);
+               }
+               free(namelist);
+               d++;
+       }
+
+       return 0;
+
+bail:
+       free(namelist);
+
+       return ret;
+}
+
+int
+lws_plat_plugins_destroy(struct lws_context * context)
+{
+       struct lws_plugin *plugin = context->plugin_list, *p;
+       lws_plugin_destroy_func func;
+       char path[256];
+       int m;
+
+#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
+               return lws_uv_plugins_destroy(context);
+#endif
+
+       if (!plugin)
+               return 0;
+
+       lwsl_notice("%s\n", __func__);
+
+       while (plugin) {
+               p = plugin;
+               m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s",
+                                plugin->name + 3);
+               path[m - 3] = '\0';
+               func = dlsym(plugin->l, path);
+               if (!func) {
+                       lwsl_err("Failed to get destroy on %s: %s",
+                                       plugin->name, dlerror());
+                       goto next;
+               }
+               m = func(context);
+               if (m)
+                       lwsl_err("Initializing %s failed %d\n",
+                               plugin->name, m);
+next:
+               dlclose(p->l);
+               plugin = p->list;
+               p->list = NULL;
+               free(p);
+       }
+
+       context->plugin_list = NULL;
+
+       return 0;
+}
diff --git a/lib/plat/unix/unix-service.c b/lib/plat/unix/unix-service.c
new file mode 100644 (file)
index 0000000..198da6d
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+int
+lws_poll_listen_fd(struct lws_pollfd *fd)
+{
+       return poll(fd, 1, 0);
+}
+
+LWS_EXTERN int
+_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
+{
+       volatile struct lws_foreign_thread_pollfd *ftp, *next;
+       volatile struct lws_context_per_thread *vpt;
+       struct lws_context_per_thread *pt;
+       lws_usec_t timeout_us;
+       int n = -1, m, c;
+
+       /* stay dead once we are dead */
+
+       if (!context || !context->vhost_list)
+               return 1;
+
+       pt = &context->pt[tsi];
+       vpt = (volatile struct lws_context_per_thread *)pt;
+
+       lws_stats_bump(pt, LWSSTATS_C_SERVICE_ENTRY, 1);
+
+       if (timeout_ms < 0)
+               goto faked_service;
+
+       /* force a default timeout of 23 days */
+       timeout_ms = 2000000000;
+       timeout_us = ((lws_usec_t)timeout_ms) * LWS_US_PER_MS;
+
+       if (context->event_loop_ops->run_pt)
+               context->event_loop_ops->run_pt(context, tsi);
+
+       if (!pt->service_tid_detected) {
+               struct lws _lws;
+
+               memset(&_lws, 0, sizeof(_lws));
+               _lws.context = context;
+
+               pt->service_tid = context->vhost_list->protocols[0].callback(
+                                       &_lws, LWS_CALLBACK_GET_THREAD_ID,
+                                       NULL, NULL, 0);
+               pt->service_tid_detected = 1;
+       }
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(context, 1, tsi)) {
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(context, 1, pt->tid))
+                       /* yes... come back again quickly */
+                       timeout_us = 0;
+       }
+
+       if (timeout_us) {
+               lws_usec_t us;
+
+               lws_pt_lock(pt, __func__);
+               /* don't stay in poll wait longer than next hr timeout */
+               us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+               if (us && us < timeout_us)
+                       timeout_us = us;
+
+               lws_pt_unlock(pt);
+       }
+
+       vpt->inside_poll = 1;
+       lws_memory_barrier();
+       n = poll(pt->fds, pt->fds_count, timeout_us / LWS_US_PER_MS);
+       vpt->inside_poll = 0;
+       lws_memory_barrier();
+
+       /* Collision will be rare and brief.  Just spin until it completes */
+       while (vpt->foreign_spinlock)
+               ;
+
+       /*
+        * At this point we are not inside a foreign thread pollfd change,
+        * and we have marked ourselves as outside the poll() wait.  So we
+        * are the only guys that can modify the lws_foreign_thread_pollfd
+        * list on the pt.  Drain the list and apply the changes to the
+        * affected pollfds in the correct order.
+        */
+
+       lws_pt_lock(pt, __func__);
+
+       ftp = vpt->foreign_pfd_list;
+       //lwsl_notice("cleared list %p\n", ftp);
+       while (ftp) {
+               struct lws *wsi;
+               struct lws_pollfd *pfd;
+
+               next = ftp->next;
+               pfd = &vpt->fds[ftp->fd_index];
+               if (lws_socket_is_valid(pfd->fd)) {
+                       wsi = wsi_from_fd(context, pfd->fd);
+                       if (wsi)
+                               __lws_change_pollfd(wsi, ftp->_and, ftp->_or);
+               }
+               lws_free((void *)ftp);
+               ftp = next;
+       }
+       vpt->foreign_pfd_list = NULL;
+       lws_memory_barrier();
+
+       lws_pt_unlock(pt);
+
+       m = 0;
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+       m |= !!pt->ws.rx_draining_ext_list;
+#endif
+
+#if defined(LWS_WITH_TLS)
+       if (pt->context->tls_ops &&
+           pt->context->tls_ops->fake_POLLIN_for_buffered)
+               m |= pt->context->tls_ops->fake_POLLIN_for_buffered(pt);
+#endif
+
+       if (
+#if (defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)) || defined(LWS_WITH_TLS)
+               !m &&
+#endif
+               !n) { /* nothing to do */
+               lws_service_do_ripe_rxflow(pt);
+
+               return 0;
+       }
+
+faked_service:
+       m = lws_service_flag_pending(context, tsi);
+       if (m)
+               c = -1; /* unknown limit */
+       else
+               if (n < 0) {
+                       if (LWS_ERRNO != LWS_EINTR)
+                               return -1;
+                       return 0;
+               } else
+                       c = n;
+
+       /* any socket with events to service? */
+       for (n = 0; n < (int)pt->fds_count && c; n++) {
+               if (!pt->fds[n].revents)
+                       continue;
+
+               c--;
+
+               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
+               if (m < 0) {
+                       lwsl_err("%s: lws_service_fd_tsi returned %d\n",
+                                __func__, m);
+                       return -1;
+               }
+               /* if something closed, retry this slot */
+               if (m)
+                       n--;
+       }
+
+       lws_service_do_ripe_rxflow(pt);
+
+       return 0;
+}
+
+int
+lws_plat_check_connection_error(struct lws *wsi)
+{
+       return 0;
+}
+
+int
+lws_plat_service(struct lws_context *context, int timeout_ms)
+{
+       return _lws_plat_service_tsi(context, timeout_ms, 0);
+}
diff --git a/lib/plat/unix/unix-sockets.c b/lib/plat/unix/unix-sockets.c
new file mode 100644 (file)
index 0000000..e356f91
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define _GNU_SOURCE
+#include "core/private.h"
+
+#include <pwd.h>
+#include <grp.h>
+
+
+
+int
+lws_send_pipe_choked(struct lws *wsi)
+{
+       struct lws_pollfd fds;
+       struct lws *wsi_eff;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       if (wsi->ws && wsi->ws->tx_draining_ext)
+               return 1;
+#endif
+
+#if defined(LWS_WITH_HTTP2)
+       wsi_eff = lws_get_network_wsi(wsi);
+#else
+       wsi_eff = wsi;
+#endif
+
+       /* the fact we checked implies we avoided back-to-back writes */
+       wsi_eff->could_have_pending = 0;
+
+       /* treat the fact we got a truncated send pending as if we're choked */
+       if (lws_has_buffered_out(wsi_eff)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+           ||wsi->http.comp_ctx.buflist_comp ||
+           wsi->http.comp_ctx.may_have_more
+#endif
+           )
+               return 1;
+
+       fds.fd = wsi_eff->desc.sockfd;
+       fds.events = POLLOUT;
+       fds.revents = 0;
+
+       if (poll(&fds, 1, 0) != 1)
+               return 1;
+
+       if ((fds.revents & POLLOUT) == 0)
+               return 1;
+
+       /* okay to send another packet without blocking */
+
+       return 0;
+}
+
+int
+lws_plat_set_nonblocking(int fd)
+{
+       return fcntl(fd, F_SETFL, O_NONBLOCK) < 0;
+}
+
+int
+lws_plat_set_socket_options(struct lws_vhost *vhost, int fd, int unix_skt)
+{
+       int optval = 1;
+       socklen_t optlen = sizeof(optval);
+
+#if defined(__APPLE__) || \
+    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
+    defined(__NetBSD__) || \
+    defined(__OpenBSD__) || \
+    defined(__HAIKU__)
+       struct protoent *tcp_proto;
+#endif
+
+       (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+       if (!unix_skt && vhost->ka_time) {
+               /* enable keepalive on this socket */
+               optval = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+
+#if defined(__APPLE__) || \
+    defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
+    defined(__NetBSD__) || \
+    defined(__CYGWIN__) || defined(__OpenBSD__) || defined (__sun) || \
+    defined(__HAIKU__)
+
+               /*
+                * didn't find a way to set these per-socket, need to
+                * tune kernel systemwide values
+                */
+#else
+               /* set the keepalive conditions we want on it too */
+
+#if defined(LWS_HAVE_TCP_USER_TIMEOUT)
+               optval = 1000 * (vhost->ka_time +
+                                (vhost->ka_interval * vhost->ka_probes));
+               if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+#endif
+               optval = vhost->ka_time;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+
+               optval = vhost->ka_interval;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+
+               optval = vhost->ka_probes;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT,
+                              (const void *)&optval, optlen) < 0)
+                       return 1;
+#endif
+       }
+
+#if defined(SO_BINDTODEVICE)
+       if (!unix_skt && vhost->bind_iface && vhost->iface) {
+               lwsl_info("binding listen skt to %s using SO_BINDTODEVICE\n", vhost->iface);
+               if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, vhost->iface,
+                               strlen(vhost->iface)) < 0) {
+                       lwsl_warn("Failed to bind to device %s\n", vhost->iface);
+                       return 1;
+               }
+       }
+#endif
+
+       /* Disable Nagle */
+       optval = 1;
+#if defined (__sun) || defined(__QNX__)
+       if (!unix_skt && setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
+               return 1;
+#elif !defined(__APPLE__) && \
+      !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) &&        \
+      !defined(__NetBSD__) && \
+      !defined(__OpenBSD__) && \
+      !defined(__HAIKU__)
+       if (!unix_skt && setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0)
+               return 1;
+#else
+       tcp_proto = getprotobyname("TCP");
+       if (!unix_skt && setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, &optval, optlen) < 0)
+               return 1;
+#endif
+
+       return lws_plat_set_nonblocking(fd);
+}
+
+
+/* cast a struct sockaddr_in6 * into addr for ipv6 */
+
+int
+lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr,
+                   size_t addrlen)
+{
+       int rc = LWS_ITOSA_NOT_EXIST;
+
+       struct ifaddrs *ifr;
+       struct ifaddrs *ifc;
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
+#endif
+
+       getifaddrs(&ifr);
+       for (ifc = ifr; ifc != NULL && rc; ifc = ifc->ifa_next) {
+               if (!ifc->ifa_addr)
+                       continue;
+
+               lwsl_debug(" interface %s vs %s (fam %d) ipv6 %d\n",
+                          ifc->ifa_name, ifname,
+                          ifc->ifa_addr->sa_family, ipv6);
+
+               if (strcmp(ifc->ifa_name, ifname))
+                       continue;
+
+               switch (ifc->ifa_addr->sa_family) {
+#if defined(AF_PACKET)
+               case AF_PACKET:
+                       /* interface exists but is not usable */
+                       rc = LWS_ITOSA_NOT_USABLE;
+                       continue;
+#endif
+
+               case AF_INET:
+#ifdef LWS_WITH_IPV6
+                       if (ipv6) {
+                               /* map IPv4 to IPv6 */
+                               memset((char *)&addr6->sin6_addr, 0,
+                                               sizeof(struct in6_addr));
+                               addr6->sin6_addr.s6_addr[10] = 0xff;
+                               addr6->sin6_addr.s6_addr[11] = 0xff;
+                               memcpy(&addr6->sin6_addr.s6_addr[12],
+                                      &((struct sockaddr_in *)ifc->ifa_addr)->sin_addr,
+                                                       sizeof(struct in_addr));
+                               lwsl_debug("%s: uplevelling ipv4 bind to ipv6\n", __func__);
+                       } else
+#endif
+                               memcpy(addr,
+                                       (struct sockaddr_in *)ifc->ifa_addr,
+                                                   sizeof(struct sockaddr_in));
+                       break;
+#ifdef LWS_WITH_IPV6
+               case AF_INET6:
+                       memcpy(&addr6->sin6_addr,
+                         &((struct sockaddr_in6 *)ifc->ifa_addr)->sin6_addr,
+                                                      sizeof(struct in6_addr));
+                       break;
+#endif
+               default:
+                       continue;
+               }
+               rc = LWS_ITOSA_USABLE;
+       }
+
+       freeifaddrs(ifr);
+
+       if (rc) {
+               /* check if bind to IP address */
+#ifdef LWS_WITH_IPV6
+               if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1)
+                       rc = LWS_ITOSA_USABLE;
+               else
+#endif
+               if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1)
+                       rc = LWS_ITOSA_USABLE;
+       }
+
+       return rc;
+}
+
+
+const char *
+lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
+{
+       return inet_ntop(af, src, dst, cnt);
+}
+
+int
+lws_plat_inet_pton(int af, const char *src, void *dst)
+{
+       return inet_pton(af, src, dst);
+}
diff --git a/lib/plat/windows/private.h b/lib/plat/windows/private.h
new file mode 100644 (file)
index 0000000..a7756d8
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  Included from lib/core/private.h if defined(WIN32) || defined(_WIN32)
+ */
+
+ #ifndef WIN32_LEAN_AND_MEAN
+  #define WIN32_LEAN_AND_MEAN
+ #endif
+
+ #if defined(WINVER) && (WINVER < 0x0501)
+  #undef WINVER
+  #undef _WIN32_WINNT
+  #define WINVER 0x0501
+  #define _WIN32_WINNT WINVER
+ #endif
+
+ #define LWS_NO_DAEMONIZE
+ #define LWS_ERRNO WSAGetLastError()
+ #define LWS_EAGAIN WSAEWOULDBLOCK
+ #define LWS_EALREADY WSAEALREADY
+ #define LWS_EINPROGRESS WSAEINPROGRESS
+ #define LWS_EINTR WSAEINTR
+ #define LWS_EISCONN WSAEISCONN
+ #define LWS_ENOTCONN WSAENOTCONN
+ #define LWS_EWOULDBLOCK WSAEWOULDBLOCK
+ #define LWS_EADDRINUSE WSAEADDRINUSE
+ #define MSG_NOSIGNAL 0
+ #define SHUT_RDWR SD_BOTH
+ #define SOL_TCP IPPROTO_TCP
+ #define SHUT_WR SD_SEND
+
+ #define compatible_close(fd) closesocket(fd)
+ #define lws_set_blocking_send(wsi) wsi->sock_send_blocking = 1
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+ #include <windows.h>
+ #include <tchar.h>
+ #ifdef LWS_HAVE_IN6ADDR_H
+  #include <in6addr.h>
+ #endif
+ #include <mstcpip.h>
+ #include <io.h>
+
+ #if !defined(LWS_HAVE_ATOLL)
+  #if defined(LWS_HAVE__ATOI64)
+   #define atoll _atoi64
+  #else
+   #warning No atoll or _atoi64 available, using atoi
+   #define atoll atoi
+  #endif
+ #endif
+
+ #ifndef __func__
+  #define __func__ __FUNCTION__
+ #endif
+
+ #ifdef LWS_HAVE__VSNPRINTF
+  #define vsnprintf _vsnprintf
+ #endif
+
+/* we don't have an implementation for this on windows... */
+int kill(int pid, int sig);
+int fork(void);
+#ifndef SIGINT
+#define SIGINT 2
+#endif
+
+#include <gettimeofday.h>
+
+#ifndef BIG_ENDIAN
+ #define BIG_ENDIAN    4321  /* to show byte order (taken from gcc) */
+#endif
+#ifndef LITTLE_ENDIAN
+ #define LITTLE_ENDIAN 1234
+#endif
+#ifndef BYTE_ORDER
+ #define BYTE_ORDER LITTLE_ENDIAN
+#endif
+
+#undef __P
+#ifndef __P
+ #if __STDC__
+  #define __P(protos) protos
+ #else
+  #define __P(protos) ()
+ #endif
+#endif
+
+#ifdef _WIN32
+ #ifndef FD_HASHTABLE_MODULUS
+  #define FD_HASHTABLE_MODULUS 32
+ #endif
+#endif
+
+#define lws_plat_socket_offset() (0)
+
+struct lws;
+struct lws_context;
+
+#define LWS_FD_HASH(fd) ((fd ^ (fd >> 8) ^ (fd >> 16)) % FD_HASHTABLE_MODULUS)
+struct lws_fd_hashtable {
+       struct lws **wsi;
+       int length;
+};
+
+
+#ifdef LWS_DLL
+#ifdef LWS_INTERNAL
+#define LWS_EXTERN extern __declspec(dllexport)
+#else
+#define LWS_EXTERN extern __declspec(dllimport)
+#endif
+#else
+#define LWS_EXTERN
+#endif
+
+typedef SOCKET lws_sockfd_type;
+typedef HANDLE lws_filefd_type;
+#define LWS_WIN32_HANDLE_TYPES
+
+LWS_EXTERN struct lws *
+wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd);
+
+LWS_EXTERN int
+insert_wsi(struct lws_context *context, struct lws *wsi);
+
+LWS_EXTERN int
+delete_from_fd(struct lws_context *context, lws_sockfd_type fd);
diff --git a/lib/plat/windows/windows-fds.c b/lib/plat/windows/windows-fds.c
new file mode 100644 (file)
index 0000000..0d324e8
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+struct lws *
+wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
+{
+       int h = LWS_FD_HASH(fd);
+       int n = 0;
+
+       for (n = 0; n < context->fd_hashtable[h].length; n++)
+               if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd)
+                       return context->fd_hashtable[h].wsi[n];
+
+       return NULL;
+}
+
+int
+insert_wsi(struct lws_context *context, struct lws *wsi)
+{
+       int h = LWS_FD_HASH(wsi->desc.sockfd);
+
+       if (context->fd_hashtable[h].length == (getdtablesize() - 1)) {
+               lwsl_err("hash table overflow\n");
+               return 1;
+       }
+
+       context->fd_hashtable[h].wsi[context->fd_hashtable[h].length++] = wsi;
+
+       return 0;
+}
+
+int
+delete_from_fd(struct lws_context *context, lws_sockfd_type fd)
+{
+       int h = LWS_FD_HASH(fd);
+       int n = 0;
+
+       for (n = 0; n < context->fd_hashtable[h].length; n++)
+               if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) {
+                       while (n < context->fd_hashtable[h].length) {
+                               context->fd_hashtable[h].wsi[n] =
+                                       context->fd_hashtable[h].wsi[n + 1];
+                               n++;
+                       }
+                       context->fd_hashtable[h].length--;
+
+                       return 0;
+               }
+
+       lwsl_err("Failed to find fd %d requested for "
+                "delete in hashtable\n", fd);
+       return 1;
+}
diff --git a/lib/plat/windows/windows-file.c b/lib/plat/windows/windows-file.c
new file mode 100644 (file)
index 0000000..6516b70
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+int lws_plat_apply_FD_CLOEXEC(int n)
+{
+       return 0;
+}
+
+lws_fop_fd_t
+_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename,
+                   const char *vpath, lws_fop_flags_t *flags)
+{
+       HANDLE ret;
+       WCHAR buf[MAX_PATH];
+       lws_fop_fd_t fop_fd;
+       LARGE_INTEGER llFileSize = {0};
+
+       MultiByteToWideChar(CP_UTF8, 0, filename, -1, buf, LWS_ARRAY_SIZE(buf));
+       if (((*flags) & 7) == _O_RDONLY) {
+               ret = CreateFileW(buf, GENERIC_READ, FILE_SHARE_READ,
+                         NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+       } else {
+               ret = CreateFileW(buf, GENERIC_WRITE, 0, NULL,
+                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+       }
+
+       if (ret == LWS_INVALID_FILE)
+               goto bail;
+
+       fop_fd = malloc(sizeof(*fop_fd));
+       if (!fop_fd)
+               goto bail;
+
+       fop_fd->fops = fops;
+       fop_fd->fd = ret;
+       fop_fd->filesystem_priv = NULL; /* we don't use it */
+       fop_fd->flags = *flags;
+       fop_fd->len = GetFileSize(ret, NULL);
+       if(GetFileSizeEx(ret, &llFileSize))
+               fop_fd->len = llFileSize.QuadPart;
+
+       fop_fd->pos = 0;
+
+       return fop_fd;
+
+bail:
+       return NULL;
+}
+
+int
+_lws_plat_file_close(lws_fop_fd_t *fop_fd)
+{
+       HANDLE fd = (*fop_fd)->fd;
+
+       free(*fop_fd);
+       *fop_fd = NULL;
+
+       CloseHandle((HANDLE)fd);
+
+       return 0;
+}
+
+lws_fileofs_t
+_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset)
+{
+       LARGE_INTEGER l;
+
+       l.QuadPart = offset;
+       return SetFilePointerEx((HANDLE)fop_fd->fd, l, NULL, FILE_CURRENT);
+}
+
+int
+_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                   uint8_t *buf, lws_filepos_t len)
+{
+       DWORD _amount;
+
+       if (!ReadFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) {
+               *amount = 0;
+
+               return 1;
+       }
+
+       fop_fd->pos += _amount;
+       *amount = (unsigned long)_amount;
+
+       return 0;
+}
+
+int
+_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount,
+                        uint8_t* buf, lws_filepos_t len)
+{
+       DWORD _amount;
+
+       if (!WriteFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) {
+               *amount = 0;
+
+               return 1;
+       }
+
+       fop_fd->pos += _amount;
+       *amount = (unsigned long)_amount;
+
+       return 0;
+}
+
+
+int
+lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
+                       int len)
+{
+       int n;
+
+       n = write(fd, buf, len);
+
+       lseek(fd, 0, SEEK_SET);
+
+       return n != len;
+}
+
+int
+lws_plat_write_file(const char *filename, void *buf, int len)
+{
+       int m, fd;
+
+       fd = lws_open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+
+       if (fd == -1)
+               return -1;
+
+       m = write(fd, buf, len);
+       close(fd);
+
+       return m != len;
+}
+
+int
+lws_plat_read_file(const char *filename, void *buf, int len)
+{
+       int n, fd = lws_open(filename, O_RDONLY);
+       if (fd == -1)
+               return -1;
+
+       n = read(fd, buf, len);
+       close(fd);
+
+       return n;
+}
+
diff --git a/lib/plat/windows/windows-init.c b/lib/plat/windows/windows-init.c
new file mode 100644 (file)
index 0000000..5a2df0d
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+int
+lws_plat_drop_app_privileges(struct lws_context *context, int actually_set)
+{
+       return 0;
+}
+
+int
+lws_plat_context_early_init(void)
+{
+       WORD wVersionRequested;
+       WSADATA wsaData;
+       int err;
+
+       /* Use the MAKEWORD(lowbyte, highbyte) macro from Windef.h */
+       wVersionRequested = MAKEWORD(2, 2);
+
+       err = WSAStartup(wVersionRequested, &wsaData);
+       if (!err)
+               return 0;
+       /*
+        * Tell the user that we could not find a usable
+        * Winsock DLL
+        */
+       lwsl_err("WSAStartup failed with error: %d\n", err);
+
+       return 1;
+}
+
+int
+lws_plat_init(struct lws_context *context,
+             const struct lws_context_creation_info *info)
+{
+       struct lws_context_per_thread *pt = &context->pt[0];
+       int i, n = context->count_threads;
+
+       for (i = 0; i < FD_HASHTABLE_MODULUS; i++) {
+               context->fd_hashtable[i].wsi =
+                       lws_zalloc(sizeof(struct lws*) * context->max_fds,
+                                  "win hashtable");
+
+               if (!context->fd_hashtable[i].wsi)
+                       return -1;
+       }
+
+       while (n--) {
+               pt->fds_count = 0;
+               pt->events = WSACreateEvent(); /* the cancel event */
+               InitializeCriticalSection(&pt->interrupt_lock);
+
+               pt++;
+       }
+
+       context->fd_random = 0;
+
+#ifdef LWS_WITH_PLUGINS
+       if (info->plugin_dirs)
+               lws_plat_plugins_init(context, info->plugin_dirs);
+#endif
+
+       return 0;
+}
+
+void
+lws_plat_context_early_destroy(struct lws_context *context)
+{
+       struct lws_context_per_thread *pt = &context->pt[0];
+       int n = context->count_threads;
+
+       while (n--) {
+               WSACloseEvent(pt->events);
+               DeleteCriticalSection(&pt->interrupt_lock);
+               pt++;
+       }
+}
+
+void
+lws_plat_context_late_destroy(struct lws_context *context)
+{
+       int n;
+
+       for (n = 0; n < FD_HASHTABLE_MODULUS; n++) {
+               if (context->fd_hashtable[n].wsi)
+                       lws_free(context->fd_hashtable[n].wsi);
+       }
+
+       WSACleanup();
+}
diff --git a/lib/plat/windows/windows-misc.c b/lib/plat/windows/windows-misc.c
new file mode 100644 (file)
index 0000000..9059a73
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+
+lws_usec_t
+lws_now_usecs(void)
+{
+#ifndef DELTA_EPOCH_IN_MICROSECS
+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
+#endif
+       FILETIME filetime;
+       ULARGE_INTEGER datetime;
+
+#ifdef _WIN32_WCE
+       GetCurrentFT(&filetime);
+#else
+       GetSystemTimeAsFileTime(&filetime);
+#endif
+
+       /*
+        * As per Windows documentation for FILETIME, copy the resulting
+        * FILETIME structure to a ULARGE_INTEGER structure using memcpy
+        * (using memcpy instead of direct assignment can prevent alignment
+        * faults on 64-bit Windows).
+        */
+       memcpy(&datetime, &filetime, sizeof(datetime));
+
+       /* Windows file times are in 100s of nanoseconds. */
+       return (datetime.QuadPart / 10) - DELTA_EPOCH_IN_MICROSECS;
+}
+
+
+#ifdef _WIN32_WCE
+time_t time(time_t *t)
+{
+       time_t ret = lws_now_usecs() / 1000000;
+
+       if(t != NULL)
+               *t = ret;
+
+       return ret;
+}
+#endif
+
+LWS_VISIBLE int
+lws_get_random(struct lws_context *context, void *buf, int len)
+{
+       int n;
+       char *p = (char *)buf;
+
+       for (n = 0; n < len; n++)
+               p[n] = (unsigned char)rand();
+
+       return n;
+}
+
+
+LWS_VISIBLE void
+lwsl_emit_syslog(int level, const char *line)
+{
+       lwsl_emit_stderr(level, line);
+}
+
+
+int kill(int pid, int sig)
+{
+       lwsl_err("Sorry Windows doesn't support kill().");
+       exit(0);
+}
+
+int fork(void)
+{
+       lwsl_err("Sorry Windows doesn't support fork().");
+       exit(0);
+}
+
+
+int
+lws_plat_recommended_rsa_bits(void)
+{
+       return 4096;
+}
+
+
+
diff --git a/lib/plat/windows/windows-pipe.c b/lib/plat/windows/windows-pipe.c
new file mode 100644 (file)
index 0000000..0953871
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+int
+lws_plat_pipe_create(struct lws *wsi)
+{
+       return 1;
+}
+
+int
+lws_plat_pipe_signal(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       EnterCriticalSection(&pt->interrupt_lock);
+       pt->interrupt_requested = 1;
+       LeaveCriticalSection(&pt->interrupt_lock);
+       WSASetEvent(pt->events); /* trigger the cancel event */
+
+       return 0;
+}
+
+void
+lws_plat_pipe_close(struct lws *wsi)
+{
+}
diff --git a/lib/plat/windows/windows-plugins.c b/lib/plat/windows/windows-plugins.c
new file mode 100644 (file)
index 0000000..7f83524
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+int
+lws_plat_plugins_init(struct lws_context * context, const char * const *d)
+{
+#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
+               return lws_uv_plugins_init(context, d);
+#endif
+
+       return 0;
+}
+
+int
+lws_plat_plugins_destroy(struct lws_context * context)
+{
+#if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
+       if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV))
+               return lws_uv_plugins_destroy(context);
+#endif
+
+       return 0;
+}
diff --git a/lib/plat/windows/windows-service.c b/lib/plat/windows/windows-service.c
new file mode 100644 (file)
index 0000000..a0f0046
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+
+LWS_EXTERN int
+_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
+{
+       struct lws_context_per_thread *pt;
+       WSANETWORKEVENTS networkevents;
+       struct lws_pollfd *pfd;
+       lws_usec_t timeout_us;
+       struct lws *wsi;
+       unsigned int i;
+       DWORD ev;
+       int n;
+       unsigned int eIdx;
+       int interrupt_requested;
+
+       /* stay dead once we are dead */
+       if (context == NULL || !context->vhost_list)
+               return 1;
+
+       pt = &context->pt[tsi];
+
+       if (!pt->service_tid_detected) {
+               struct lws _lws;
+
+               memset(&_lws, 0, sizeof(_lws));
+               _lws.context = context;
+
+               pt->service_tid = context->vhost_list->
+                       protocols[0].callback(&_lws, LWS_CALLBACK_GET_THREAD_ID,
+                                                 NULL, NULL, 0);
+               pt->service_tid_detected = 1;
+       }
+
+       if (timeout_ms < 0) {
+               if (lws_service_flag_pending(context, tsi)) {
+                       /* any socket with events to service? */
+                       for (n = 0; n < (int)pt->fds_count; n++) {
+                               int m;
+                               if (!pt->fds[n].revents)
+                                       continue;
+
+                               m = lws_service_fd_tsi(context, &pt->fds[n], tsi);
+                               if (m < 0)
+                                       return -1;
+                               /* if something closed, retry this slot */
+                               if (m)
+                                       n--;
+                       }
+               }
+               return 0;
+       }
+
+       /* force a default timeout of 23 days */
+       timeout_ms = 2000000000;
+       timeout_us = ((lws_usec_t)timeout_ms) * LWS_US_PER_MS;
+
+       if (context->event_loop_ops->run_pt)
+               context->event_loop_ops->run_pt(context, tsi);
+
+       for (i = 0; i < pt->fds_count; ++i) {
+               pfd = &pt->fds[i];
+
+               if (!(pfd->events & LWS_POLLOUT))
+                       continue;
+
+               wsi = wsi_from_fd(context, pfd->fd);
+               if (!wsi || wsi->listener)
+                       continue;
+               if (wsi->sock_send_blocking)
+                       continue;
+               pfd->revents = LWS_POLLOUT;
+               n = lws_service_fd(context, pfd);
+               if (n < 0)
+                       return -1;
+
+               /*
+                * Force WSAWaitForMultipleEvents() to check events
+                * and then return immediately.
+                */
+               timeout_us = 0;
+
+               /* if something closed, retry this slot */
+               if (n)
+                       i--;
+       }
+
+       /*
+        * is there anybody with pending stuff that needs service forcing?
+        */
+       if (!lws_service_adjust_timeout(context, 1, tsi)) {
+               /* -1 timeout means just do forced service */
+               _lws_plat_service_tsi(context, -1, pt->tid);
+               /* still somebody left who wants forced service? */
+               if (!lws_service_adjust_timeout(context, 1, pt->tid))
+                       /* yes... come back again quickly */
+                       timeout_us = 0;
+       }
+
+       if (timeout_us) {
+               lws_usec_t us;
+
+               lws_pt_lock(pt, __func__);
+               /* don't stay in poll wait longer than next hr timeout */
+               us = __lws_sul_check(&pt->pt_sul_owner, lws_now_usecs());
+               if (us && us < timeout_us)
+                       timeout_us = us;
+
+               lws_pt_unlock(pt);
+       }
+
+       for (n = 0; n < (int)pt->fds_count; n++)
+               WSAEventSelect(pt->fds[n].fd, pt->events,
+                      FD_READ | (!!(pt->fds[n].events & LWS_POLLOUT) * FD_WRITE) |
+                      FD_OOB | FD_ACCEPT |
+                      FD_CONNECT | FD_CLOSE | FD_QOS |
+                      FD_ROUTING_INTERFACE_CHANGE |
+                      FD_ADDRESS_LIST_CHANGE);
+
+       ev = WSAWaitForMultipleEvents(1, &pt->events, FALSE,
+                                     (DWORD)(timeout_us / LWS_US_PER_MS), FALSE);
+       if (ev == WSA_WAIT_EVENT_0) {
+               EnterCriticalSection(&pt->interrupt_lock);
+               interrupt_requested = pt->interrupt_requested;
+               pt->interrupt_requested = 0;
+               LeaveCriticalSection(&pt->interrupt_lock);
+               if (interrupt_requested) {
+                       lws_broadcast(pt, LWS_CALLBACK_EVENT_WAIT_CANCELLED,
+                                     NULL, 0);
+                       return 0;
+               }
+
+#if defined(LWS_WITH_TLS)
+               if (pt->context->tls_ops &&
+                   pt->context->tls_ops->fake_POLLIN_for_buffered)
+                       pt->context->tls_ops->fake_POLLIN_for_buffered(pt);
+#endif
+
+               for (eIdx = 0; eIdx < pt->fds_count; ++eIdx) {
+                       unsigned int err;
+
+                       if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, pt->events,
+                                       &networkevents) == SOCKET_ERROR) {
+                               lwsl_err("WSAEnumNetworkEvents() failed "
+                                        "with error %d\n", LWS_ERRNO);
+                               return -1;
+                       }
+
+                       if (!networkevents.lNetworkEvents)
+                               networkevents.lNetworkEvents = LWS_POLLOUT;
+
+                       pfd = &pt->fds[eIdx];
+                       pfd->revents = (short)networkevents.lNetworkEvents;
+
+                       err = networkevents.iErrorCode[FD_CONNECT_BIT];
+
+                       if ((networkevents.lNetworkEvents & FD_CONNECT) &&
+                            err && err != LWS_EALREADY &&
+                            err != LWS_EINPROGRESS && err != LWS_EWOULDBLOCK &&
+                            err != WSAEINVAL) {
+                               lwsl_debug("Unable to connect errno=%d\n", err);
+                               pfd->revents |= LWS_POLLHUP;
+                       }
+
+                       if (pfd->revents & LWS_POLLOUT) {
+                               wsi = wsi_from_fd(context, pfd->fd);
+                               if (wsi)
+                                       wsi->sock_send_blocking = 0;
+                       }
+                        /* if something closed, retry this slot */
+                       if (pfd->revents & LWS_POLLHUP)
+                               --eIdx;
+
+                       if (pfd->revents) {
+                               recv(pfd->fd, NULL, 0, 0);
+                               lws_service_fd_tsi(context, pfd, tsi);
+                       }
+               }
+       } else if (ev == WSA_WAIT_TIMEOUT) {
+               lws_service_fd(context, NULL);
+       } else if (ev == WSA_WAIT_FAILED)
+               return 0;
+
+       return 0;
+}
+
+int
+lws_plat_service(struct lws_context *context, int timeout_ms)
+{
+       return _lws_plat_service_tsi(context, timeout_ms, 0);
+}
diff --git a/lib/plat/windows/windows-sockets.c b/lib/plat/windows/windows-sockets.c
new file mode 100644 (file)
index 0000000..6ebff31
--- /dev/null
@@ -0,0 +1,293 @@
+#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
+#define _WINSOCK_DEPRECATED_NO_WARNINGS
+#endif
+#include "core/private.h"
+
+
+LWS_VISIBLE int
+lws_send_pipe_choked(struct lws *wsi)
+{      struct lws *wsi_eff;
+
+#if defined(LWS_WITH_HTTP2)
+       wsi_eff = lws_get_network_wsi(wsi);
+#else
+       wsi_eff = wsi;
+#endif
+       /* the fact we checked implies we avoided back-to-back writes */
+       wsi_eff->could_have_pending = 0;
+
+       /* treat the fact we got a truncated send pending as if we're choked */
+       if (lws_has_buffered_out(wsi_eff)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+           ||wsi->http.comp_ctx.buflist_comp ||
+             wsi->http.comp_ctx.may_have_more
+#endif
+       )
+               return 1;
+
+       return (int)wsi_eff->sock_send_blocking;
+}
+
+int
+lws_poll_listen_fd(struct lws_pollfd *fd)
+{
+       fd_set readfds;
+       struct timeval tv = { 0, 0 };
+
+       assert((fd->events & LWS_POLLIN) == LWS_POLLIN);
+
+       FD_ZERO(&readfds);
+       FD_SET(fd->fd, &readfds);
+
+       return select(((int)fd->fd) + 1, &readfds, NULL, NULL, &tv);
+}
+
+int
+lws_plat_set_nonblocking(int fd)
+{
+       u_long optl = 1;
+
+       return !!ioctlsocket(fd, FIONBIO, &optl);
+}
+
+int
+lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd,
+                           int unix_skt)
+{
+       int optval = 1;
+       int optlen = sizeof(optval);
+       DWORD dwBytesRet;
+       struct tcp_keepalive alive;
+       int protonbr;
+#ifndef _WIN32_WCE
+       struct protoent *tcp_proto;
+#endif
+
+       if (vhost->ka_time) {
+               /* enable keepalive on this socket */
+               optval = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+                              (const char *)&optval, optlen) < 0)
+                       return 1;
+
+               alive.onoff = TRUE;
+               alive.keepalivetime = vhost->ka_time * 1000;
+               alive.keepaliveinterval = vhost->ka_interval * 1000;
+
+               if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),
+                            NULL, 0, &dwBytesRet, NULL, NULL))
+                       return 1;
+       }
+
+       /* Disable Nagle */
+       optval = 1;
+#ifndef _WIN32_WCE
+       tcp_proto = getprotobyname("TCP");
+       if (!tcp_proto) {
+               lwsl_err("getprotobyname() failed with error %d\n", LWS_ERRNO);
+               return 1;
+       }
+       protonbr = tcp_proto->p_proto;
+#else
+       protonbr = 6;
+#endif
+
+       setsockopt(fd, protonbr, TCP_NODELAY, (const char *)&optval, optlen);
+
+       return lws_plat_set_nonblocking(fd);
+}
+
+
+LWS_EXTERN int
+lws_interface_to_sa(int ipv6,
+               const char *ifname, struct sockaddr_in *addr, size_t addrlen)
+{
+#ifdef LWS_WITH_IPV6
+       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
+
+       if (ipv6) {
+               if (lws_plat_inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) {
+                       return LWS_ITOSA_USABLE;
+               }
+       }
+#endif
+
+       long long address = inet_addr(ifname);
+
+       if (address == INADDR_NONE) {
+               struct hostent *entry = gethostbyname(ifname);
+               if (entry)
+                       address = ((struct in_addr *)entry->h_addr_list[0])->s_addr;
+       }
+
+       if (address == INADDR_NONE)
+               return LWS_ITOSA_NOT_EXIST;
+
+       addr->sin_addr.s_addr = (unsigned long)(lws_intptr_t)address;
+
+       return LWS_ITOSA_USABLE;
+}
+
+void
+lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       int n = LWS_POLLIN | LWS_POLLHUP | FD_CONNECT;
+
+       if (wsi->udp) {
+               lwsl_info("%s: UDP\n", __func__);
+               n = LWS_POLLIN;
+       }
+
+       pt->fds[pt->fds_count++].revents = 0;
+       WSAEventSelect(wsi->desc.sockfd, pt->events, n);
+}
+
+void
+lws_plat_delete_socket_from_fds(struct lws_context *context,
+                                               struct lws *wsi, int m)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+
+       pt->fds_count--;
+}
+
+
+int
+lws_plat_check_connection_error(struct lws *wsi)
+{
+       int optVal;
+       int optLen = sizeof(int);
+
+       if (getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR,
+                          (char*)&optVal, &optLen) != SOCKET_ERROR && optVal &&
+               optVal != LWS_EALREADY && optVal != LWS_EINPROGRESS &&
+               optVal != LWS_EWOULDBLOCK && optVal != WSAEINVAL) {
+                  lwsl_debug("Connect failed SO_ERROR=%d\n", optVal);
+                  return 1;
+       }
+
+       return 0;
+}
+
+int
+lws_plat_change_pollfd(struct lws_context *context,
+                         struct lws *wsi, struct lws_pollfd *pfd)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       long e = LWS_POLLHUP | FD_CONNECT;
+
+       if ((pfd->events & LWS_POLLIN))
+               e |= LWS_POLLIN;
+
+       if ((pfd->events & LWS_POLLOUT))
+               e |= LWS_POLLOUT;
+
+       if (WSAEventSelect(wsi->desc.sockfd, pt->events, e) != SOCKET_ERROR)
+               return 0;
+
+       lwsl_err("WSAEventSelect() failed with error %d\n", LWS_ERRNO);
+
+       return 1;
+}
+
+const char *
+lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt)
+{
+       WCHAR *buffer;
+       DWORD bufferlen = cnt;
+       BOOL ok = FALSE;
+
+       buffer = lws_malloc(bufferlen * 2, "inet_ntop");
+       if (!buffer) {
+               lwsl_err("Out of memory\n");
+               return NULL;
+       }
+
+       if (af == AF_INET) {
+               struct sockaddr_in srcaddr;
+               memset(&srcaddr, 0, sizeof(srcaddr));
+               srcaddr.sin_family = AF_INET;
+               memcpy(&(srcaddr.sin_addr), src, sizeof(srcaddr.sin_addr));
+
+               if (!WSAAddressToStringW((struct sockaddr*)&srcaddr, sizeof(srcaddr), 0, buffer, &bufferlen))
+                       ok = TRUE;
+#ifdef LWS_WITH_IPV6
+       } else if (af == AF_INET6) {
+               struct sockaddr_in6 srcaddr;
+               memset(&srcaddr, 0, sizeof(srcaddr));
+               srcaddr.sin6_family = AF_INET6;
+               memcpy(&(srcaddr.sin6_addr), src, sizeof(srcaddr.sin6_addr));
+
+               if (!WSAAddressToStringW((struct sockaddr*)&srcaddr, sizeof(srcaddr), 0, buffer, &bufferlen))
+                       ok = TRUE;
+#endif
+       } else
+               lwsl_err("Unsupported type\n");
+
+       if (!ok) {
+               int rv = WSAGetLastError();
+               lwsl_err("WSAAddressToString() : %d\n", rv);
+       } else {
+               if (WideCharToMultiByte(CP_ACP, 0, buffer, bufferlen, dst, cnt, 0, NULL) <= 0)
+                       ok = FALSE;
+       }
+
+       lws_free(buffer);
+       return ok ? dst : NULL;
+}
+
+int
+lws_plat_inet_pton(int af, const char *src, void *dst)
+{
+       WCHAR *buffer;
+       DWORD bufferlen = (int)strlen(src) + 1;
+       BOOL ok = FALSE;
+
+       buffer = lws_malloc(bufferlen * 2, "inet_pton");
+       if (!buffer) {
+               lwsl_err("Out of memory\n");
+               return -1;
+       }
+
+       if (MultiByteToWideChar(CP_ACP, 0, src, bufferlen, buffer, bufferlen) <= 0) {
+               lwsl_err("Failed to convert multi byte to wide char\n");
+               lws_free(buffer);
+               return -1;
+       }
+
+       if (af == AF_INET) {
+               struct sockaddr_in dstaddr;
+               int dstaddrlen = sizeof(dstaddr);
+
+               memset(&dstaddr, 0, sizeof(dstaddr));
+               dstaddr.sin_family = AF_INET;
+
+               if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) {
+                       ok = TRUE;
+                       memcpy(dst, &dstaddr.sin_addr, sizeof(dstaddr.sin_addr));
+               }
+#ifdef LWS_WITH_IPV6
+       } else if (af == AF_INET6) {
+               struct sockaddr_in6 dstaddr;
+               int dstaddrlen = sizeof(dstaddr);
+
+               memset(&dstaddr, 0, sizeof(dstaddr));
+               dstaddr.sin6_family = AF_INET6;
+
+               if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) {
+                       ok = TRUE;
+                       memcpy(dst, &dstaddr.sin6_addr, sizeof(dstaddr.sin6_addr));
+               }
+#endif
+       } else
+               lwsl_err("Unsupported type\n");
+
+       if (!ok) {
+               int rv = WSAGetLastError();
+               lwsl_err("WSAAddressToString() : %d\n", rv);
+       }
+
+       lws_free(buffer);
+       return ok ? 1 : -1;
+}
diff --git a/lib/pollfd.c b/lib/pollfd.c
deleted file mode 100644 (file)
index 25167dd..0000000
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-int
-_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa)
-{
-       struct lws_context_per_thread *pt;
-       struct lws_context *context;
-       int ret = 0, pa_events = 1;
-       struct lws_pollfd *pfd;
-       int sampled_tid, tid;
-
-       if (!wsi || wsi->position_in_fds_table < 0)
-               return 0;
-
-       if (wsi->handling_pollout && !_and && _or == LWS_POLLOUT) {
-               /*
-                * Happening alongside service thread handling POLLOUT.
-                * The danger is when he is finished, he will disable POLLOUT,
-                * countermanding what we changed here.
-                *
-                * Instead of changing the fds, inform the service thread
-                * what happened, and ask it to leave POLLOUT active on exit
-                */
-               wsi->leave_pollout_active = 1;
-               /*
-                * by definition service thread is not in poll wait, so no need
-                * to cancel service
-                */
-
-               lwsl_debug("%s: using leave_pollout_active\n", __func__);
-
-               return 0;
-       }
-
-       context = wsi->context;
-       pt = &context->pt[(int)wsi->tsi];
-       assert(wsi->position_in_fds_table >= 0 &&
-              wsi->position_in_fds_table < pt->fds_count);
-
-       pfd = &pt->fds[wsi->position_in_fds_table];
-       pa->fd = wsi->desc.sockfd;
-       pa->prev_events = pfd->events;
-       pa->events = pfd->events = (pfd->events & ~_and) | _or;
-
-       //lwsl_notice("%s: wsi %p, posin %d. from %d -> %d\n", __func__, wsi, wsi->position_in_fds_table, pa->prev_events, pa->events);
-
-
-       if (wsi->http2_substream)
-               return 0;
-
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CHANGE_MODE_POLL_FD,
-                                          wsi->user_space, (void *)pa, 0)) {
-               ret = -1;
-               goto bail;
-       }
-
-       if (_and & LWS_POLLIN) {
-               lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ);
-               lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ);
-               lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ);
-       }
-       if (_or & LWS_POLLIN) {
-               lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ);
-               lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ);
-               lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ);
-       }
-       if (_and & LWS_POLLOUT) {
-               lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE);
-               lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_WRITE);
-               lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_WRITE);
-       }
-       if (_or & LWS_POLLOUT) {
-               lws_libev_io(wsi, LWS_EV_START | LWS_EV_WRITE);
-               lws_libuv_io(wsi, LWS_EV_START | LWS_EV_WRITE);
-               lws_libevent_io(wsi, LWS_EV_START | LWS_EV_WRITE);
-       }
-
-       /*
-        * if we changed something in this pollfd...
-        *   ... and we're running in a different thread context
-        *     than the service thread...
-        *       ... and the service thread is waiting ...
-        *         then cancel it to force a restart with our changed events
-        */
-#if LWS_POSIX
-       pa_events = pa->prev_events != pa->events;
-#endif
-
-       if (pa_events) {
-
-               if (lws_plat_change_pollfd(context, wsi, pfd)) {
-                       lwsl_info("%s failed\n", __func__);
-                       ret = -1;
-                       goto bail;
-               }
-
-               sampled_tid = context->service_tid;
-               if (sampled_tid) {
-                       tid = wsi->vhost->protocols[0].callback(wsi,
-                                    LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0);
-                       if (tid == -1) {
-                               ret = -1;
-                               goto bail;
-                       }
-                       if (tid != sampled_tid)
-                               lws_cancel_service_pt(wsi);
-               }
-       }
-bail:
-       return ret;
-}
-
-#ifndef LWS_NO_SERVER
-static void
-lws_accept_modulation(struct lws_context_per_thread *pt, int allow)
-{
-// multithread listen seems broken
-#if 0
-       struct lws_vhost *vh = context->vhost_list;
-       struct lws_pollargs pa1;
-
-       while (vh) {
-               if (allow)
-                       _lws_change_pollfd(pt->wsi_listening,
-                                          0, LWS_POLLIN, &pa1);
-               else
-                       _lws_change_pollfd(pt->wsi_listening,
-                                          LWS_POLLIN, 0, &pa1);
-               vh = vh->vhost_next;
-       }
-#endif
-}
-#endif
-
-int
-insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_pollargs pa = { wsi->desc.sockfd, LWS_POLLIN, 0 };
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       int ret = 0;
-
-
-       lwsl_debug("%s: %p: tsi=%d, sock=%d, pos-in-fds=%d\n",
-                 __func__, wsi, wsi->tsi, wsi->desc.sockfd, pt->fds_count);
-
-       if ((unsigned int)pt->fds_count >= context->fd_limit_per_thread) {
-               lwsl_err("Too many fds (%d vs %d)\n", context->max_fds,
-                               context->fd_limit_per_thread    );
-               return 1;
-       }
-
-#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266)
-       if (wsi->desc.sockfd >= context->max_fds) {
-               lwsl_err("Socket fd %d is too high (%d)\n",
-                        wsi->desc.sockfd, context->max_fds);
-               return 1;
-       }
-#endif
-
-       assert(wsi);
-       assert(wsi->vhost);
-       assert(lws_socket_is_valid(wsi->desc.sockfd));
-
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
-                                          wsi->user_space, (void *) &pa, 1))
-               return -1;
-
-       lws_pt_lock(pt);
-       pt->count_conns++;
-       insert_wsi(context, wsi);
-#if defined(LWS_WITH_ESP8266)
-       if (wsi->position_in_fds_table == -1)
-#endif
-               wsi->position_in_fds_table = pt->fds_count;
-
-       // lwsl_notice("%s: %p: setting posinfds %d\n", __func__, wsi, wsi->position_in_fds_table);
-
-       pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd;
-#if LWS_POSIX
-       pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN;
-#else
-       pt->fds[wsi->position_in_fds_table].events = 0; // LWS_POLLIN;
-#endif
-       pa.events = pt->fds[pt->fds_count].events;
-
-       lws_plat_insert_socket_into_fds(context, wsi);
-
-       /* external POLL support via protocol 0 */
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD,
-                                          wsi->user_space, (void *) &pa, 0))
-               ret =  -1;
-#ifndef LWS_NO_SERVER
-       /* if no more room, defeat accepts on this thread */
-       if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1)
-               lws_accept_modulation(pt, 0);
-#endif
-       lws_pt_unlock(pt);
-
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
-                                          wsi->user_space, (void *)&pa, 1))
-               ret = -1;
-
-       return ret;
-}
-
-int
-remove_wsi_socket_from_fds(struct lws *wsi)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_pollargs pa = { wsi->desc.sockfd, 0, 0 };
-#if !defined(LWS_WITH_ESP8266)
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       struct lws *end_wsi;
-       int v;
-#endif
-       int m, ret = 0;
-
-       if (wsi->parent_carries_io) {
-               lws_same_vh_protocol_remove(wsi);
-               return 0;
-       }
-
-#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266)
-       if (wsi->desc.sockfd > context->max_fds) {
-               lwsl_err("fd %d too high (%d)\n", wsi->desc.sockfd, context->max_fds);
-               return 1;
-       }
-#endif
-
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
-                                          wsi->user_space, (void *)&pa, 1))
-               return -1;
-
-       lws_same_vh_protocol_remove(wsi);
-
-       /* the guy who is to be deleted's slot index in pt->fds */
-       m = wsi->position_in_fds_table;
-       
-#if !defined(LWS_WITH_ESP8266)
-       lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | LWS_EV_PREPARE_DELETION);
-       lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | LWS_EV_PREPARE_DELETION);
-
-       lws_pt_lock(pt);
-
-       lwsl_debug("%s: wsi=%p, sock=%d, fds pos=%d, end guy pos=%d, endfd=%d\n",
-                 __func__, wsi, wsi->desc.sockfd, wsi->position_in_fds_table,
-                 pt->fds_count, pt->fds[pt->fds_count].fd);
-
-       /* have the last guy take up the now vacant slot */
-       pt->fds[m] = pt->fds[pt->fds_count - 1];
-#endif
-       /* this decrements pt->fds_count */
-       lws_plat_delete_socket_from_fds(context, wsi, m);
-#if !defined(LWS_WITH_ESP8266)
-       v = (int) pt->fds[m].fd;
-       /* end guy's "position in fds table" is now the deletion guy's old one */
-       end_wsi = wsi_from_fd(context, v);
-       if (!end_wsi) {
-               lwsl_err("no wsi found for sock fd %d at pos %d, pt->fds_count=%d\n", (int)pt->fds[m].fd, m, pt->fds_count);
-               assert(0);
-       } else
-               end_wsi->position_in_fds_table = m;
-
-       /* deletion guy's lws_lookup entry needs nuking */
-       delete_from_fd(context, wsi->desc.sockfd);
-       /* removed wsi has no position any more */
-       wsi->position_in_fds_table = -1;
-
-       /* remove also from external POLL support via protocol 0 */
-       if (lws_socket_is_valid(wsi->desc.sockfd))
-               if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD,
-                                                  wsi->user_space, (void *) &pa, 0))
-                       ret = -1;
-#ifndef LWS_NO_SERVER
-       if (!context->being_destroyed)
-               /* if this made some room, accept connects on this thread */
-               if ((unsigned int)pt->fds_count < context->fd_limit_per_thread - 1)
-                       lws_accept_modulation(pt, 1);
-#endif
-       lws_pt_unlock(pt);
-
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
-                                          wsi->user_space, (void *) &pa, 1))
-               ret = -1;
-#endif
-       return ret;
-}
-
-int
-lws_change_pollfd(struct lws *wsi, int _and, int _or)
-{
-       struct lws_context_per_thread *pt;
-       struct lws_context *context;
-       struct lws_pollargs pa;
-       int ret = 0;
-
-       if (!wsi || !wsi->protocol || wsi->position_in_fds_table < 0)
-               return 1;
-
-       context = lws_get_context(wsi);
-       if (!context)
-               return 1;
-
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
-                                          wsi->user_space,  (void *) &pa, 0))
-               return -1;
-
-       pt = &context->pt[(int)wsi->tsi];
-
-       lws_pt_lock(pt);
-       ret = _lws_change_pollfd(wsi, _and, _or, &pa);
-       lws_pt_unlock(pt);
-       if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL,
-                                          wsi->user_space, (void *) &pa, 0))
-               ret = -1;
-
-       return ret;
-}
-
-LWS_VISIBLE int
-lws_callback_on_writable(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt;
-#ifdef LWS_USE_HTTP2
-       struct lws *network_wsi, *wsi2;
-       int already;
-#endif
-
-       if (wsi->state == LWSS_SHUTDOWN)
-               return 0;
-
-       if (wsi->socket_is_permanently_unusable)
-               return 0;
-
-       if (wsi->parent_carries_io) {
-               int n = lws_callback_on_writable(wsi->parent);
-
-               if (n < 0)
-                       return n;
-
-               wsi->parent_pending_cb_on_writable = 1;
-               return 1;
-       }
-
-       pt = &wsi->context->pt[(int)wsi->tsi];
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_REQ, 1);
-#if defined(LWS_WITH_STATS)
-       if (!wsi->active_writable_req_us) {
-               wsi->active_writable_req_us = time_in_microseconds();
-               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1);
-       }
-#endif
-
-#ifdef LWS_USE_HTTP2
-       lwsl_info("%s: %p\n", __func__, wsi);
-
-       if (wsi->mode != LWSCM_HTTP2_SERVING)
-               goto network_sock;
-
-       if (wsi->u.http2.requested_POLLOUT) {
-               lwsl_info("already pending writable\n");
-               return 1;
-       }
-
-       if (wsi->u.http2.tx_credit <= 0) {
-               /*
-                * other side is not able to cope with us sending
-                * anything so no matter if we have POLLOUT on our side.
-                *
-                * 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;
-               return 0;
-       }
-
-       network_wsi = lws_http2_get_network_wsi(wsi);
-       already = network_wsi->u.http2.requested_POLLOUT;
-
-       /* mark everybody above him as requesting pollout */
-
-       wsi2 = wsi;
-       while (wsi2) {
-               wsi2->u.http2.requested_POLLOUT = 1;
-               lwsl_info("mark %p pending writable\n", wsi2);
-               wsi2 = wsi2->u.http2.parent_wsi;
-       }
-
-       /* for network action, act only on the network wsi */
-
-       wsi = network_wsi;
-       if (already)
-               return 1;
-network_sock:
-#endif
-
-       if (lws_ext_cb_active(wsi, LWS_EXT_CB_REQUEST_ON_WRITEABLE, NULL, 0))
-               return 1;
-
-       if (wsi->position_in_fds_table < 0) {
-               lwsl_err("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd);
-               return -1;
-       }
-
-       if (lws_change_pollfd(wsi, 0, LWS_POLLOUT))
-               return -1;
-
-       return 1;
-}
-
-/*
- * stitch protocol choice into the vh protocol linked list
- * We always insert ourselves at the start of the list
- *
- * X <-> B
- * X <-> pAn <-> pB
- *
- * Illegal to attach more than once without detach inbetween
- */
-void
-lws_same_vh_protocol_insert(struct lws *wsi, int n)
-{
-       //lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n",
-       //              __func__,
-       //              wsi->vhost->same_vh_protocol_list[n],
-       //              wsi->same_vh_protocol_prev);
-
-       if (wsi->same_vh_protocol_prev || wsi->same_vh_protocol_next) {
-               lws_same_vh_protocol_remove(wsi);
-               lwsl_notice("Attempted to attach wsi twice to same vh prot\n");
-       }
-
-       wsi->same_vh_protocol_prev = /* guy who points to us */
-               &wsi->vhost->same_vh_protocol_list[n];
-       wsi->same_vh_protocol_next = /* old first guy is our next */
-                       wsi->vhost->same_vh_protocol_list[n];
-       /* we become the new first guy */
-       wsi->vhost->same_vh_protocol_list[n] = wsi;
-
-       if (wsi->same_vh_protocol_next)
-               /* old first guy points back to us now */
-               wsi->same_vh_protocol_next->same_vh_protocol_prev =
-                               &wsi->same_vh_protocol_next;
-}
-
-void
-lws_same_vh_protocol_remove(struct lws *wsi)
-{
-       /*
-        * detach ourselves from vh protocol list if we're on one
-        * A -> B -> C
-        * A -> C , or, B -> C, or A -> B
-        *
-        * OK to call on already-detached wsi
-        */
-       lwsl_info("%s: removing same prot wsi %p\n", __func__, wsi);
-
-       if (wsi->same_vh_protocol_prev) {
-               assert (*(wsi->same_vh_protocol_prev) == wsi);
-               lwsl_info("have prev %p, setting him to our next %p\n",
-                        wsi->same_vh_protocol_prev,
-                        wsi->same_vh_protocol_next);
-
-               /* guy who pointed to us should point to our next */
-               *(wsi->same_vh_protocol_prev) = wsi->same_vh_protocol_next;
-       }
-
-       /* our next should point back to our prev */
-       if (wsi->same_vh_protocol_next) {
-               wsi->same_vh_protocol_next->same_vh_protocol_prev =
-                               wsi->same_vh_protocol_prev;
-       }
-
-       wsi->same_vh_protocol_prev = NULL;
-       wsi->same_vh_protocol_next = NULL;
-}
-
-
-LWS_VISIBLE int
-lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
-                                     const struct lws_protocols *protocol)
-{
-       struct lws *wsi;
-
-       if (protocol < vhost->protocols ||
-           protocol >= (vhost->protocols + vhost->count_protocols)) {
-
-               lwsl_err("%s: protocol %p is not from vhost %p (%p - %p)\n",
-                       __func__, protocol, vhost->protocols, vhost,
-                       (vhost->protocols + vhost->count_protocols));
-
-               return -1;
-       }
-
-       wsi = vhost->same_vh_protocol_list[protocol - vhost->protocols];
-       //lwsl_notice("%s: protocol %p, start wsi %p\n", __func__, protocol, wsi);
-       while (wsi) {
-               //lwsl_notice("%s: protocol %p, this wsi %p (wsi->protocol=%p)\n",
-               //              __func__, protocol, wsi, wsi->protocol);
-               assert(wsi->protocol == protocol);
-               assert(*wsi->same_vh_protocol_prev == wsi);
-               if (wsi->same_vh_protocol_next) {
-               //      lwsl_err("my next says %p\n", wsi->same_vh_protocol_next);
-               //      lwsl_err("my next's prev says %p\n",
-               //              wsi->same_vh_protocol_next->same_vh_protocol_prev);
-                       assert(wsi->same_vh_protocol_next->same_vh_protocol_prev == &wsi->same_vh_protocol_next);
-               }
-               //lwsl_notice("  apv: %p\n", wsi);
-               lws_callback_on_writable(wsi);
-               wsi = wsi->same_vh_protocol_next;
-       }
-
-       return 0;
-}
-
-LWS_VISIBLE int
-lws_callback_on_writable_all_protocol(const struct lws_context *context,
-                                     const struct lws_protocols *protocol)
-{
-       struct lws_vhost *vhost = context->vhost_list;
-       int n;
-
-       while (vhost) {
-               for (n = 0; n < vhost->count_protocols; n++)
-                       if (protocol->callback ==
-                           vhost->protocols[n].callback &&
-                           !strcmp(protocol->name, vhost->protocols[n].name))
-                               break;
-               if (n != vhost->count_protocols)
-                       lws_callback_on_writable_all_protocol_vhost(
-                               vhost, &vhost->protocols[n]);
-
-               vhost = vhost->vhost_next;
-       }
-
-       return 0;
-}
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
deleted file mode 100644 (file)
index 1fd06b1..0000000
+++ /dev/null
@@ -1,2270 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010 - 2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "lws_config.h"
-#include "lws_config_private.h"
-
-
-#if defined(LWS_WITH_CGI) && defined(LWS_HAVE_VFORK)
-#define  _GNU_SOURCE
-#endif
-
-#ifdef LWS_HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <ctype.h>
-#include <limits.h>
-#include <stdarg.h>
-
-#if defined(LWS_WITH_ESP32)
-#define MSG_NOSIGNAL 0
-#define SOMAXCONN 3
-#endif
-
-#if defined(LWS_WITH_ESP8266)
-#include <user_interface.h>
-#define assert(n)
-
-/* rom-provided stdc functions for free, ensure use these instead of libc ones */
-
-int ets_vsprintf(char *str, const char *format, va_list argptr);
-int ets_vsnprintf(char *buffer, size_t sizeOfBuffer,  const char *format, va_list argptr);
-int ets_snprintf(char *str, size_t size, const char *format, ...);
-int ets_sprintf(char *str, const char *format, ...);
-int os_printf_plus(const char *format, ...);
-#undef malloc
-#undef realloc
-#undef free
-void *pvPortMalloc(size_t s, const char *f, int line);
-#define malloc(s) pvPortMalloc(s, "", 0)
-void *pvPortRealloc(void *p, size_t s, const char *f, int line);
-#define realloc(p, s) pvPortRealloc(p, s, "", 0)
-void vPortFree(void *p, const char *f, int line);
-#define free(p) vPortFree(p, "", 0)
-#undef memcpy
-void *ets_memcpy(void *dest, const void *src, size_t n);
-#define memcpy ets_memcpy
-void *ets_memset(void *dest, int v, size_t n);
-#define memset ets_memset
-char *ets_strcpy(char *dest, const char *src);
-#define strcpy ets_strcpy
-char *ets_strncpy(char *dest, const char *src, size_t n);
-#define strncpy ets_strncpy
-char *ets_strstr(const char *haystack, const char *needle);
-#define strstr ets_strstr
-int ets_strcmp(const char *s1, const char *s2);
-int ets_strncmp(const char *s1, const char *s2, size_t n);
-#define strcmp ets_strcmp
-#define strncmp ets_strncmp
-size_t ets_strlen(const char *s);
-#define strlen ets_strlen
-void *ets_memmove(void *dest, const void *src, size_t n);
-#define memmove ets_memmove
-char *ets_strchr(const char *s, int c);
-#define strchr_ets_strchr
-#undef _DEBUG
-#include <osapi.h>
-
-#else
-#define STORE_IN_ROM
-#include <assert.h>
-#endif
-#if LWS_MAX_SMP > 1
-#include <pthread.h>
-#endif
-
-#ifdef LWS_HAVE_SYS_STAT_H
-#include <sys/stat.h>
-#endif
-
-#if defined(WIN32) || defined(_WIN32)
-#if (WINVER < 0x0501)
-#undef WINVER
-#undef _WIN32_WINNT
-#define WINVER 0x0501
-#define _WIN32_WINNT WINVER
-#endif
-#define LWS_NO_DAEMONIZE
-#define LWS_ERRNO WSAGetLastError()
-#define LWS_EAGAIN WSAEWOULDBLOCK
-#define LWS_EALREADY WSAEALREADY
-#define LWS_EINPROGRESS WSAEINPROGRESS
-#define LWS_EINTR WSAEINTR
-#define LWS_EISCONN WSAEISCONN
-#define LWS_EWOULDBLOCK WSAEWOULDBLOCK
-#define MSG_NOSIGNAL 0
-#define SHUT_RDWR SD_BOTH
-#define SOL_TCP IPPROTO_TCP
-#define SHUT_WR SD_SEND
-
-#define compatible_close(fd) closesocket(fd)
-#define lws_set_blocking_send(wsi) wsi->sock_send_blocking = 1
-#define lws_socket_is_valid(x) (!!x)
-#define LWS_SOCK_INVALID 0
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#include <windows.h>
-#include <tchar.h>
-#ifdef LWS_HAVE_IN6ADDR_H
-#include <in6addr.h>
-#endif
-#include <mstcpip.h>
-#include <io.h>
-
-#if !defined(LWS_HAVE_ATOLL)
-#if defined(LWS_HAVE__ATOI64)
-#define atoll _atoi64
-#else
-#warning No atoll or _atoi64 available, using atoi
-#define atoll atoi
-#endif
-#endif
-
-#ifndef __func__
-#define __func__ __FUNCTION__
-#endif
-
-#ifdef LWS_HAVE__VSNPRINTF
-#define vsnprintf _vsnprintf
-#endif
-
-/* we don't have an implementation for this on windows... */
-int kill(int pid, int sig);
-int fork(void);
-#ifndef SIGINT
-#define SIGINT 2
-#endif
-
-#else /* not windows --> */
-
-#include <fcntl.h>
-#include <strings.h>
-#include <unistd.h>
-#include <sys/types.h>
-
-#ifndef __cplusplus
-#include <errno.h>
-#endif
-#include <netdb.h>
-#include <signal.h>
-#ifdef LWS_WITH_ESP8266
-#include <sockets.h>
-#define vsnprintf ets_vsnprintf
-#define snprintf ets_snprintf
-#define sprintf ets_sprintf
-
-int kill(int pid, int sig);
-
-#else
-#include <sys/socket.h>
-#endif
-#ifdef LWS_WITH_HTTP_PROXY
-#include <hubbub/hubbub.h>
-#include <hubbub/parser.h>
-#endif
-#if defined(LWS_BUILTIN_GETIFADDRS)
- #include <getifaddrs.h>
-#else
- #if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32)
- #include <ifaddrs.h>
- #endif
-#endif
-#if defined (__ANDROID__)
-#include <syslog.h>
-#include <sys/resource.h>
-#elif defined (__sun)
-#include <syslog.h>
-#else
-#if !defined(LWS_WITH_ESP8266)  && !defined(LWS_WITH_ESP32)
-#include <sys/syslog.h>
-#endif
-#endif
-#include <netdb.h>
-#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32)
-#include <sys/mman.h>
-#include <sys/un.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <poll.h>
-#endif
-#ifdef LWS_USE_LIBEV
-#include <ev.h>
-#endif
-#ifdef LWS_USE_LIBUV
-#include <uv.h>
-#endif
-#ifdef LWS_USE_LIBEVENT
-#include <event2/event.h>
-#endif
-
-#ifndef LWS_NO_FORK
-#ifdef LWS_HAVE_SYS_PRCTL_H
-#include <sys/prctl.h>
-#endif
-#endif
-
-#include <sys/time.h>
-
-#define LWS_ERRNO errno
-#define LWS_EAGAIN EAGAIN
-#define LWS_EALREADY EALREADY
-#define LWS_EINPROGRESS EINPROGRESS
-#define LWS_EINTR EINTR
-#define LWS_EISCONN EISCONN
-#define LWS_EWOULDBLOCK EWOULDBLOCK
-
-#define lws_set_blocking_send(wsi)
-
-#if defined(LWS_WITH_ESP8266)
-#define lws_socket_is_valid(x) ((x) != NULL)
-#define LWS_SOCK_INVALID (NULL)
-struct lws;
-const char *
-lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen);
-#else
-#define lws_socket_is_valid(x) (x >= 0)
-#define LWS_SOCK_INVALID (-1)
-#endif
-#endif
-
-#ifndef LWS_HAVE_BZERO
-#ifndef bzero
-#define bzero(b, len) (memset((b), '\0', (len)), (void) 0)
-#endif
-#endif
-
-#ifndef LWS_HAVE_STRERROR
-#define strerror(x) ""
-#endif
-
-#ifdef LWS_OPENSSL_SUPPORT
-
-#ifdef USE_WOLFSSL
-#ifdef USE_OLD_CYASSL
-#include <cyassl/openssl/ssl.h>
-#include <cyassl/error-ssl.h>
-#else
-#include <wolfssl/openssl/ssl.h>
-#include <wolfssl/error-ssl.h>
-#define OPENSSL_NO_TLSEXT
-#endif /* not USE_OLD_CYASSL */
-#else
-#include <openssl/ssl.h>
-#if !defined(LWS_WITH_ESP32)
-#include <openssl/evp.h>
-#include <openssl/err.h>
-#include <openssl/md5.h>
-#include <openssl/sha.h>
-#ifdef LWS_HAVE_OPENSSL_ECDH_H
-#include <openssl/ecdh.h>
-#endif
-#include <openssl/x509v3.h>
-#endif
-#if (OPENSSL_VERSION_NUMBER < 0x0009080afL)
-/* later openssl defines this to negate the presence of tlsext... but it was only
- * introduced at 0.9.8j.  Earlier versions don't know it exists so don't
- * define it... making it look like the feature exists...
- */
-#define OPENSSL_NO_TLSEXT
-#endif
-#endif /* not USE_WOLFSSL */
-#endif
-
-#include "libwebsockets.h"
-#if defined(WIN32) || defined(_WIN32)
-#else
-static inline int compatible_close(int fd) { return close(fd); }
-#endif
-
-#if defined(WIN32) || defined(_WIN32)
-#include <gettimeofday.h>
-#endif
-
-#if defined(LWS_WITH_ESP8266)
-#undef compatible_close
-#define compatible_close(fd) { fd->state=ESPCONN_CLOSE; espconn_delete(fd); }
-lws_sockfd_type
-esp8266_create_tcp_stream_socket(void);
-void
-esp8266_tcp_stream_bind(lws_sockfd_type fd, int port, struct lws *wsi);
-#ifndef BIG_ENDIAN
-#define BIG_ENDIAN    4321  /* to show byte order (taken from gcc) */
-#endif
-#ifndef LITTLE_ENDIAN
-#define LITTLE_ENDIAN 1234
-#endif
-#ifndef BYTE_ORDER
-#define BYTE_ORDER LITTLE_ENDIAN
-#endif
-#endif
-
-
-#if defined(WIN32) || defined(_WIN32)
-
-#ifndef BIG_ENDIAN
-#define BIG_ENDIAN    4321  /* to show byte order (taken from gcc) */
-#endif
-#ifndef LITTLE_ENDIAN
-#define LITTLE_ENDIAN 1234
-#endif
-#ifndef BYTE_ORDER
-#define BYTE_ORDER LITTLE_ENDIAN
-#endif
-#ifndef u_int64_t
-typedef unsigned __int64 u_int64_t;
-#endif
-
-#undef __P
-#ifndef __P
-#if __STDC__
-#define __P(protos) protos
-#else
-#define __P(protos) ()
-#endif
-#endif
-
-#else
-
-#include <sys/stat.h>
-#include <sys/time.h>
-
-#if defined(__APPLE__)
-#include <machine/endian.h>
-#elif defined(__FreeBSD__)
-#include <sys/endian.h>
-#elif defined(__linux__)
-#include <endian.h>
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#if defined(__QNX__)
-       #include <gulliver.h>
-       #if defined(__LITTLEENDIAN__)
-               #define BYTE_ORDER __LITTLEENDIAN__
-               #define LITTLE_ENDIAN __LITTLEENDIAN__
-               #define BIG_ENDIAN 4321  /* to show byte order (taken from gcc); for suppres warning that BIG_ENDIAN is not defined. */
-       #endif
-       #if defined(__BIGENDIAN__)
-               #define BYTE_ORDER __BIGENDIAN__
-               #define LITTLE_ENDIAN 1234  /* to show byte order (taken from gcc); for suppres warning that LITTLE_ENDIAN is not defined. */
-               #define BIG_ENDIAN __BIGENDIAN__
-       #endif
-#endif
-
-#if defined(__sun) && defined(__GNUC__)
-
-#include <arpa/nameser_compat.h>
-
-#if !defined (BYTE_ORDER)
-# define BYTE_ORDER __BYTE_ORDER__
-#endif
-
-#if !defined(LITTLE_ENDIAN)
-# define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__
-#endif
-
-#if !defined(BIG_ENDIAN)
-# define BIG_ENDIAN __ORDER_BIG_ENDIAN__
-#endif
-
-#endif /* sun + GNUC */
-
-#if !defined(BYTE_ORDER)
-# define BYTE_ORDER __BYTE_ORDER
-#endif
-#if !defined(LITTLE_ENDIAN)
-# define LITTLE_ENDIAN __LITTLE_ENDIAN
-#endif
-#if !defined(BIG_ENDIAN)
-# define BIG_ENDIAN __BIG_ENDIAN
-#endif
-
-#endif
-
-/*
- * Mac OSX as well as iOS do not define the MSG_NOSIGNAL flag,
- * but happily have something equivalent in the SO_NOSIGPIPE flag.
- */
-#ifdef __APPLE__
-#define MSG_NOSIGNAL SO_NOSIGPIPE
-#endif
-
-/*
- * Solaris 11.X only supports POSIX 2001, MSG_NOSIGNAL appears in
- * POSIX 2008.
- */
-#ifdef __sun
-#define MSG_NOSIGNAL 0
-#endif
-
-#ifdef _WIN32
-#ifndef FD_HASHTABLE_MODULUS
-#define FD_HASHTABLE_MODULUS 32
-#endif
-#endif
-
-#ifndef LWS_DEF_HEADER_LEN
-#define LWS_DEF_HEADER_LEN 4096
-#endif
-#ifndef LWS_DEF_HEADER_POOL
-#define LWS_DEF_HEADER_POOL 4
-#endif
-#ifndef LWS_MAX_PROTOCOLS
-#define LWS_MAX_PROTOCOLS 5
-#endif
-#ifndef LWS_MAX_EXTENSIONS_ACTIVE
-#define LWS_MAX_EXTENSIONS_ACTIVE 2
-#endif
-#ifndef LWS_MAX_EXT_OFFERS
-#define LWS_MAX_EXT_OFFERS 8
-#endif
-#ifndef SPEC_LATEST_SUPPORTED
-#define SPEC_LATEST_SUPPORTED 13
-#endif
-#ifndef AWAITING_TIMEOUT
-#define AWAITING_TIMEOUT 20
-#endif
-#ifndef CIPHERS_LIST_STRING
-#define CIPHERS_LIST_STRING "DEFAULT"
-#endif
-#ifndef LWS_SOMAXCONN
-#define LWS_SOMAXCONN SOMAXCONN
-#endif
-
-#define MAX_WEBSOCKET_04_KEY_LEN 128
-
-#ifndef SYSTEM_RANDOM_FILEPATH
-#define SYSTEM_RANDOM_FILEPATH "/dev/urandom"
-#endif
-
-enum lws_websocket_opcodes_07 {
-       LWSWSOPC_CONTINUATION = 0,
-       LWSWSOPC_TEXT_FRAME = 1,
-       LWSWSOPC_BINARY_FRAME = 2,
-
-       LWSWSOPC_NOSPEC__MUX = 7,
-
-       /* control extensions 8+ */
-
-       LWSWSOPC_CLOSE = 8,
-       LWSWSOPC_PING = 9,
-       LWSWSOPC_PONG = 0xa,
-};
-
-
-enum lws_connection_states {
-       LWSS_HTTP,
-       LWSS_HTTP_ISSUING_FILE,
-       LWSS_HTTP_HEADERS,
-       LWSS_HTTP_BODY,
-       LWSS_DEAD_SOCKET,
-       LWSS_ESTABLISHED,
-       LWSS_CLIENT_HTTP_ESTABLISHED,
-       LWSS_CLIENT_UNCONNECTED,
-       LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION,
-       LWSS_RETURNED_CLOSE_ALREADY,
-       LWSS_AWAITING_CLOSE_ACK,
-       LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE,
-       LWSS_SHUTDOWN,
-
-       LWSS_HTTP2_AWAIT_CLIENT_PREFACE,
-       LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS,
-       LWSS_HTTP2_ESTABLISHED,
-
-       LWSS_CGI,
-};
-
-enum http_version {
-       HTTP_VERSION_1_0,
-       HTTP_VERSION_1_1,
-       HTTP_VERSION_2
-};
-
-enum http_connection_type {
-       HTTP_CONNECTION_CLOSE,
-       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,
-
-       LWS_RXPS_04_mask_1,
-       LWS_RXPS_04_mask_2,
-       LWS_RXPS_04_mask_3,
-
-       LWS_RXPS_04_FRAME_HDR_1,
-       LWS_RXPS_04_FRAME_HDR_LEN,
-       LWS_RXPS_04_FRAME_HDR_LEN16_2,
-       LWS_RXPS_04_FRAME_HDR_LEN16_1,
-       LWS_RXPS_04_FRAME_HDR_LEN64_8,
-       LWS_RXPS_04_FRAME_HDR_LEN64_7,
-       LWS_RXPS_04_FRAME_HDR_LEN64_6,
-       LWS_RXPS_04_FRAME_HDR_LEN64_5,
-       LWS_RXPS_04_FRAME_HDR_LEN64_4,
-       LWS_RXPS_04_FRAME_HDR_LEN64_3,
-       LWS_RXPS_04_FRAME_HDR_LEN64_2,
-       LWS_RXPS_04_FRAME_HDR_LEN64_1,
-
-       LWS_RXPS_07_COLLECT_FRAME_KEY_1,
-       LWS_RXPS_07_COLLECT_FRAME_KEY_2,
-       LWS_RXPS_07_COLLECT_FRAME_KEY_3,
-       LWS_RXPS_07_COLLECT_FRAME_KEY_4,
-
-       LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED
-};
-
-#define LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP 32
-
-enum connection_mode {
-       LWSCM_HTTP_SERVING,
-       LWSCM_HTTP_SERVING_ACCEPTED, /* actual HTTP service going on */
-       LWSCM_PRE_WS_SERVING_ACCEPT,
-
-       LWSCM_WS_SERVING,
-       LWSCM_WS_CLIENT,
-
-       LWSCM_HTTP2_SERVING,
-
-       /* transient, ssl delay hiding */
-       LWSCM_SSL_ACK_PENDING,
-       LWSCM_SSL_INIT,
-       /* as above, but complete into LWSCM_RAW */
-       LWSCM_SSL_ACK_PENDING_RAW,
-       LWSCM_SSL_INIT_RAW,
-
-       /* special internal types */
-       LWSCM_SERVER_LISTENER,
-       LWSCM_CGI, /* stdin, stdout, stderr for another cgi master wsi */
-       LWSCM_RAW, /* raw with bulk handling */
-       LWSCM_RAW_FILEDESC, /* raw without bulk handling */
-
-       /* HTTP Client related */
-       LWSCM_HTTP_CLIENT = LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP,
-       LWSCM_HTTP_CLIENT_ACCEPTED, /* actual HTTP service going on */
-       LWSCM_WSCL_WAITING_CONNECT,
-       LWSCM_WSCL_WAITING_PROXY_REPLY,
-       LWSCM_WSCL_ISSUE_HANDSHAKE,
-       LWSCM_WSCL_ISSUE_HANDSHAKE2,
-       LWSCM_WSCL_ISSUE_HTTP_BODY,
-       LWSCM_WSCL_WAITING_SSL,
-       LWSCM_WSCL_WAITING_SERVER_REPLY,
-       LWSCM_WSCL_WAITING_EXTENSION_CONNECT,
-       LWSCM_WSCL_PENDING_CANDIDATE_CHILD,
-       LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY,
-       LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY,
-       LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY,
-
-       /****** add new things just above ---^ ******/
-
-
-};
-
-/* enums of socks version */
-enum socks_version {
-       SOCKS_VERSION_4 = 4,
-       SOCKS_VERSION_5 = 5
-};
-
-/* enums of subnegotiation version */
-enum socks_subnegotiation_version {
-       SOCKS_SUBNEGOTIATION_VERSION_1 = 1,
-};
-
-/* enums of socks commands */
-enum socks_command {
-       SOCKS_COMMAND_CONNECT = 1,
-       SOCKS_COMMAND_BIND = 2,
-       SOCKS_COMMAND_UDP_ASSOCIATE = 3
-};
-
-/* enums of socks address type */
-enum socks_atyp {
-       SOCKS_ATYP_IPV4 = 1,
-       SOCKS_ATYP_DOMAINNAME = 3,
-       SOCKS_ATYP_IPV6 = 4
-};
-
-/* enums of socks authentication methods */
-enum socks_auth_method {
-       SOCKS_AUTH_NO_AUTH = 0,
-       SOCKS_AUTH_GSSAPI = 1,
-       SOCKS_AUTH_USERNAME_PASSWORD = 2
-};
-
-/* enums of subnegotiation status */
-enum socks_subnegotiation_status {
-       SOCKS_SUBNEGOTIATION_STATUS_SUCCESS = 0,
-};
-
-/* enums of socks request reply */
-enum socks_request_reply {
-       SOCKS_REQUEST_REPLY_SUCCESS = 0,
-       SOCKS_REQUEST_REPLY_FAILURE_GENERAL = 1,
-       SOCKS_REQUEST_REPLY_CONNECTION_NOT_ALLOWED = 2,
-       SOCKS_REQUEST_REPLY_NETWORK_UNREACHABLE = 3,
-       SOCKS_REQUEST_REPLY_HOST_UNREACHABLE = 4,
-       SOCKS_REQUEST_REPLY_CONNECTION_REFUSED = 5,
-       SOCKS_REQUEST_REPLY_TTL_EXPIRED = 6,
-       SOCKS_REQUEST_REPLY_COMMAND_NOT_SUPPORTED = 7,
-       SOCKS_REQUEST_REPLY_ATYP_NOT_SUPPORTED = 8
-};
-
-/* enums used to generate socks messages */
-enum socks_msg_type {
-       /* greeting */
-       SOCKS_MSG_GREETING,
-       /* credential, user name and password */
-       SOCKS_MSG_USERNAME_PASSWORD,
-       /* connect command */
-       SOCKS_MSG_CONNECT
-};
-
-enum {
-       LWS_RXFLOW_ALLOW = (1 << 0),
-       LWS_RXFLOW_PENDING_CHANGE = (1 << 1),
-};
-
-/* this is not usable directly by user code any more, lws_close_reason() */
-#define LWS_WRITE_CLOSE 4
-
-struct lws_protocols;
-struct lws;
-
-#if defined(LWS_USE_LIBEV) || defined(LWS_USE_LIBUV) || defined(LWS_USE_LIBEVENT)
-
-struct lws_io_watcher {
-#ifdef LWS_USE_LIBEV
-       ev_io ev_watcher;
-#endif
-#ifdef LWS_USE_LIBUV
-       uv_poll_t uv_watcher;
-#endif
-#ifdef LWS_USE_LIBEVENT
-       struct event *event_watcher;
-#endif
-       struct lws_context *context;
-};
-
-struct lws_signal_watcher {
-#ifdef LWS_USE_LIBEV
-       ev_signal ev_watcher;
-#endif
-#ifdef LWS_USE_LIBUV
-       uv_signal_t uv_watcher;
-#endif
-#ifdef LWS_USE_LIBEVENT
-       struct event *event_watcher;
-#endif
-       struct lws_context *context;
-};
-#endif
-
-#ifdef _WIN32
-#define LWS_FD_HASH(fd) ((fd ^ (fd >> 8) ^ (fd >> 16)) % FD_HASHTABLE_MODULUS)
-struct lws_fd_hashtable {
-       struct lws **wsi;
-       int length;
-};
-#endif
-
-/*
- * This is totally opaque to code using the library.  It's exported as a
- * forward-reference pointer-only declaration; the user can use the pointer with
- * other APIs to get information out of it.
- */
-
-struct lws_fragments {
-       unsigned int offset;
-       unsigned short len;
-       unsigned char nfrag; /* which ah->frag[] continues this content, or 0 */
-};
-
-/*
- * these are assigned from a pool held in the context.
- * Both client and server mode uses them for http header analysis
- */
-
-struct allocated_headers {
-       struct lws *wsi; /* owner */
-       char *data; /* prepared by context init to point to dedicated storage */
-       /*
-        * the randomly ordered fragments, indexed by frag_index and
-        * lws_fragments->nfrag for continuation.
-        */
-       struct lws_fragments frags[WSI_TOKEN_COUNT * 2];
-       time_t assigned;
-       /*
-        * for each recognized token, frag_index says which frag[] his data
-        * starts in (0 means the token did not appear)
-        * the actual header data gets dumped as it comes in, into data[]
-        */
-       unsigned char frag_index[WSI_TOKEN_COUNT];
-       unsigned char rx[2048];
-
-       unsigned int rxpos;
-       unsigned int rxlen;
-       unsigned int pos;
-
-       unsigned int http_response;
-
-#ifndef LWS_NO_CLIENT
-       char initial_handshake_hash_base64[30];
-#endif
-
-       unsigned char in_use;
-       unsigned char nfrag;
-};
-
-/*
- * so we can have n connections being serviced simultaneously,
- * these things need to be isolated per-thread.
- */
-
-struct lws_context_per_thread {
-#if LWS_MAX_SMP > 1
-       pthread_mutex_t lock;
-#endif
-       struct lws_pollfd *fds;
-#if defined(LWS_WITH_ESP8266)
-       struct lws **lws_vs_fds_index;
-#endif
-       struct lws *rx_draining_ext_list;
-       struct lws *tx_draining_ext_list;
-       struct lws *timeout_list;
-#if defined(LWS_USE_LIBUV) || defined(LWS_USE_LIBEVENT)
-       struct lws_context *context;
-#endif
-#ifdef LWS_WITH_CGI
-       struct lws_cgi *cgi_list;
-#endif
-       void *http_header_data;
-       struct allocated_headers *ah_pool;
-       struct lws *ah_wait_list;
-       int ah_wait_list_length;
-#ifdef LWS_OPENSSL_SUPPORT
-       struct lws *pending_read_list; /* linked list */
-#endif
-#if defined(LWS_USE_LIBEV)
-       struct ev_loop *io_loop_ev;
-#endif
-#if defined(LWS_USE_LIBUV)
-       uv_loop_t *io_loop_uv;
-       uv_signal_t signals[8];
-       uv_timer_t uv_timeout_watcher;
-       uv_idle_t uv_idle;
-#endif
-#if defined(LWS_USE_LIBEVENT)
-       struct event_base *io_loop_event_base;
-#endif
-#if defined(LWS_USE_LIBEV)
-       struct lws_io_watcher w_accept;
-#endif
-#if defined(LWS_USE_LIBEV) || defined(LWS_USE_LIBUV) || defined(LWS_USE_LIBEVENT)
-       struct lws_signal_watcher w_sigint;
-       unsigned char ev_loop_foreign:1;
-#endif
-
-       unsigned long count_conns;
-       /*
-        * usable by anything in the service code, but only if the scope
-        * does not last longer than the service action (since next service
-        * of any socket can likewise use it and overwrite)
-        */
-       unsigned char *serv_buf;
-#ifdef _WIN32
-       WSAEVENT *events;
-#else
-       lws_sockfd_type dummy_pipe_fds[2];
-#endif
-       unsigned int fds_count;
-
-       short ah_count_in_use;
-       unsigned char tid;
-       unsigned char lock_depth;
-};
-
-struct lws_conn_stats {
-       unsigned long long rx, tx;
-       unsigned long conn, trans, ws_upg, http2_upg, rejected;
-};
-
-void
-lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs);
-
-/*
- * virtual host -related context information
- *   vhostwide SSL context
- *   vhostwide proxy
- *
- * hierarchy:
- *
- * context -> vhost -> wsi
- *
- * incoming connection non-SSL vhost binding:
- *
- *    listen socket -> wsi -> select vhost after first headers
- *
- * incoming connection SSL vhost binding:
- *
- *    SSL SNI -> wsi -> bind after SSL negotiation
- */
-
-struct lws_vhost {
-#if !defined(LWS_WITH_ESP8266)
-       char http_proxy_address[128];
-       char proxy_basic_auth_token[128];
-#if defined(LWS_WITH_SOCKS5)
-       char socks_proxy_address[128];
-       char socks_user[96];
-       char socks_password[96];
-#endif
-#endif
-#if defined(LWS_WITH_ESP8266)
-       /* listen sockets need a place to hang their hat */
-       esp_tcp tcp;
-#endif
-       struct lws_conn_stats conn_stats;
-       struct lws_context *context;
-       struct lws_vhost *vhost_next;
-       const struct lws_http_mount *mount_list;
-       struct lws *lserv_wsi;
-       const char *name;
-       const char *iface;
-#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
-       int bind_iface;
-#endif
-       const struct lws_protocols *protocols;
-       void **protocol_vh_privs;
-       const struct lws_protocol_vhost_options *pvo;
-       const struct lws_protocol_vhost_options *headers;
-       struct lws **same_vh_protocol_list;
-#ifdef LWS_OPENSSL_SUPPORT
-       SSL_CTX *ssl_ctx;
-       SSL_CTX *ssl_client_ctx;
-#endif
-#ifndef LWS_NO_EXTENSIONS
-       const struct lws_extension *extensions;
-#endif
-
-       int listen_port;
-       unsigned int http_proxy_port;
-#if defined(LWS_WITH_SOCKS5)
-       unsigned int socks_proxy_port;
-#endif
-       unsigned int options;
-       int count_protocols;
-       int ka_time;
-       int ka_probes;
-       int ka_interval;
-       int keepalive_timeout;
-       int timeout_secs_ah_idle;
-       int ssl_info_event_mask;
-#ifdef LWS_WITH_ACCESS_LOG
-       int log_fd;
-#endif
-
-#ifdef LWS_OPENSSL_SUPPORT
-       int use_ssl;
-       int allow_non_ssl_on_ssl_port;
-       unsigned int user_supplied_ssl_ctx:1;
-#endif
-
-       unsigned int created_vhost_protocols:1;
-       unsigned int being_destroyed:1;
-
-       unsigned char default_protocol_index;
-       unsigned char raw_protocol_index;
-};
-
-struct lws_deferred_free
-{
-       struct lws_deferred_free *next;
-       time_t deadline;
-       void *payload;
-};
-
-/*
- * the rest is managed per-context, that includes
- *
- *  - processwide single fd -> wsi lookup
- *  - contextwide headers pool
- */
-
-struct lws_context {
-       time_t last_timeout_check_s;
-       time_t last_ws_ping_pong_check_s;
-       time_t time_up;
-       const struct lws_plat_file_ops *fops;
-       struct lws_plat_file_ops fops_platform;
-#if defined(LWS_WITH_ZIP_FOPS)
-       struct lws_plat_file_ops fops_zip;
-#endif
-       struct lws_context_per_thread pt[LWS_MAX_SMP];
-       struct lws_conn_stats conn_stats;
-#ifdef _WIN32
-/* different implementation between unix and windows */
-       struct lws_fd_hashtable fd_hashtable[FD_HASHTABLE_MODULUS];
-#else
-#if defined(LWS_WITH_ESP8266)
-       struct espconn **connpool; /* .reverse points to the wsi */
-       void *rxd;
-       int rxd_len;
-       os_timer_t to_timer;
-#else
-       struct lws **lws_lookup;  /* fd to wsi */
-#endif
-#endif
-       struct lws_vhost *vhost_list;
-       struct lws_vhost *vhost_pending_destruction_list;
-       struct lws_plugin *plugin_list;
-       struct lws_deferred_free *deferred_free_list;
-
-       void *external_baggage_free_on_destroy;
-       const struct lws_token_limits *token_limits;
-       void *user_space;
-       const char *server_string;
-       const struct lws_protocol_vhost_options *reject_service_keywords;
-       lws_reload_func deprecation_cb;
-
-#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
-       cap_value_t caps[4];
-       char count_caps;
-#endif
-
-#if defined(LWS_USE_LIBEV)
-       lws_ev_signal_cb_t * lws_ev_sigint_cb;
-#endif
-#if defined(LWS_USE_LIBUV)
-       uv_signal_cb lws_uv_sigint_cb;
-       uv_loop_t pu_loop;
-#endif
-#if defined(LWS_USE_LIBEVENT)
-       lws_event_signal_cb_t * lws_event_sigint_cb;
-#endif
-       char canonical_hostname[128];
-#ifdef LWS_LATENCY
-       unsigned long worst_latency;
-       char worst_latency_info[256];
-#endif
-
-#if defined(LWS_WITH_STATS)
-       uint64_t lws_stats[LWSSTATS_SIZE];
-       uint64_t last_dump;
-       int updated;
-#endif
-
-       int max_fds;
-#if defined(LWS_USE_LIBEV) || defined(LWS_USE_LIBUV) || defined(LWS_USE_LIBEVENT)
-       int use_ev_sigint;
-#endif
-       int started_with_parent;
-       int uid, gid;
-
-       int fd_random;
-#ifdef LWS_OPENSSL_SUPPORT
-#define lws_ssl_anybody_has_buffered_read(w) \
-               (w->vhost->use_ssl && \
-                w->context->pt[(int)w->tsi].pending_read_list)
-#define lws_ssl_anybody_has_buffered_read_tsi(c, t) \
-               (/*c->use_ssl && */ \
-                c->pt[(int)t].pending_read_list)
-#else
-#define lws_ssl_anybody_has_buffered_read(ctx) (0)
-#define lws_ssl_anybody_has_buffered_read_tsi(ctx, t) (0)
-#endif
-       int count_wsi_allocated;
-       int count_cgi_spawned;
-       unsigned int options;
-       unsigned int fd_limit_per_thread;
-       unsigned int timeout_secs;
-       unsigned int pt_serv_buf_size;
-       int max_http_header_data;
-       int simultaneous_ssl_restriction;
-       int simultaneous_ssl;
-
-       unsigned int deprecated:1;
-       unsigned int being_destroyed:1;
-       unsigned int being_destroyed1:1;
-       unsigned int requested_kill:1;
-       unsigned int protocol_init_done:1;
-       unsigned int ssl_gate_accepts:1;
-       /*
-        * set to the Thread ID that's doing the service loop just before entry
-        * to poll indicates service thread likely idling in poll()
-        * volatile because other threads may check it as part of processing
-        * for pollfd event change.
-        */
-       volatile int service_tid;
-       int service_tid_detected;
-
-       short max_http_header_pool;
-       short count_threads;
-       short plugin_protocol_count;
-       short plugin_extension_count;
-       short server_string_len;
-       unsigned short ws_ping_pong_interval;
-       unsigned short deprecation_pending_listen_close_count;
-       uint8_t max_fi;
-};
-
-int
-lws_check_deferred_free(struct lws_context *context, int force);
-
-#define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x]
-#define lws_get_vh_protocol(vh, x) vh->protocols[x]
-
-LWS_EXTERN void
-lws_close_free_wsi_final(struct lws *wsi);
-LWS_EXTERN void
-lws_libuv_closehandle(struct lws *wsi);
-LWS_EXTERN void
-lws_libuv_closehandle_manually(struct lws *wsi);
-LWS_EXTERN int
-lws_libuv_check_watcher_active(struct lws *wsi);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_plat_plugins_init(struct lws_context * context, const char * const *d);
-
-LWS_VISIBLE LWS_EXTERN int
-lws_plat_plugins_destroy(struct lws_context * context);
-
-LWS_EXTERN void
-lws_restart_ws_ping_pong_timer(struct lws *wsi);
-
-struct lws *
-lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd);
-
-
-enum {
-       LWS_EV_READ = (1 << 0),
-       LWS_EV_WRITE = (1 << 1),
-       LWS_EV_START = (1 << 2),
-       LWS_EV_STOP = (1 << 3),
-
-       LWS_EV_PREPARE_DELETION = (1 << 31),
-};
-
-#if defined(LWS_USE_LIBEV)
-LWS_EXTERN void
-lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc);
-LWS_EXTERN void
-lws_libev_io(struct lws *wsi, int flags);
-LWS_EXTERN int
-lws_libev_init_fd_table(struct lws_context *context);
-LWS_EXTERN void
-lws_libev_destroyloop(struct lws_context *context, int tsi);
-LWS_EXTERN void
-lws_libev_run(const struct lws_context *context, int tsi);
-#define LWS_LIBEV_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV)
-LWS_EXTERN void lws_feature_status_libev(struct lws_context_creation_info *info);
-#else
-#define lws_libev_accept(_a, _b) ((void) 0)
-#define lws_libev_io(_a, _b) ((void) 0)
-#define lws_libev_init_fd_table(_a) (0)
-#define lws_libev_run(_a, _b) ((void) 0)
-#define lws_libev_destroyloop(_a, _b) ((void) 0)
-#define LWS_LIBEV_ENABLED(context) (0)
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-#define lws_feature_status_libev(_a) \
-                       lwsl_notice("libev support not compiled in\n")
-#else
-#define lws_feature_status_libev(_a)
-#endif
-#endif
-
-#if defined(LWS_USE_LIBUV)
-LWS_EXTERN void
-lws_libuv_accept(struct lws *new_wsi, lws_sock_file_fd_type desc);
-LWS_EXTERN void
-lws_libuv_io(struct lws *wsi, int flags);
-LWS_EXTERN int
-lws_libuv_init_fd_table(struct lws_context *context);
-LWS_EXTERN void
-lws_libuv_run(const struct lws_context *context, int tsi);
-LWS_EXTERN void
-lws_libuv_destroyloop(struct lws_context *context, int tsi);
-LWS_EXTERN int
-lws_uv_initvhost(struct lws_vhost* vh, struct lws*);
-#define LWS_LIBUV_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV)
-LWS_EXTERN void lws_feature_status_libuv(struct lws_context_creation_info *info);
-#else
-#define lws_libuv_accept(_a, _b) ((void) 0)
-#define lws_libuv_io(_a, _b) ((void) 0)
-#define lws_libuv_init_fd_table(_a) (0)
-#define lws_libuv_run(_a, _b) ((void) 0)
-#define lws_libuv_destroyloop(_a, _b) ((void) 0)
-#define LWS_LIBUV_ENABLED(context) (0)
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-#define lws_feature_status_libuv(_a) \
-                       lwsl_notice("libuv support not compiled in\n")
-#else
-#define lws_feature_status_libuv(_a)
-#endif
-#endif
-
-#if defined(LWS_USE_LIBEVENT)
-LWS_EXTERN void
-lws_libevent_accept(struct lws *new_wsi, lws_sock_file_fd_type desc);
-LWS_EXTERN void
-lws_libevent_io(struct lws *wsi, int flags);
-LWS_EXTERN int
-lws_libevent_init_fd_table(struct lws_context *context);
-LWS_EXTERN void
-lws_libevent_destroyloop(struct lws_context *context, int tsi);
-LWS_EXTERN void
-lws_libevent_run(const struct lws_context *context, int tsi);
-#define LWS_LIBEVENT_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT)
-LWS_EXTERN void lws_feature_status_libevent(struct lws_context_creation_info *info);
-#else
-#define lws_libevent_accept(_a, _b) ((void) 0)
-#define lws_libevent_io(_a, _b) ((void) 0)
-#define lws_libevent_init_fd_table(_a) (0)
-#define lws_libevent_run(_a, _b) ((void) 0)
-#define lws_libevent_destroyloop(_a, _b) ((void) 0)
-#define LWS_LIBEVENT_ENABLED(context) (0)
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-#define lws_feature_status_libevent(_a) \
-                       lwsl_notice("libevent support not compiled in\n")
-#else
-#define lws_feature_status_libevent(_a)
-#endif
-#endif
-
-
-#ifdef LWS_USE_IPV6
-#define LWS_IPV6_ENABLED(vh) \
-       (!lws_check_opt(vh->context->options, LWS_SERVER_OPTION_DISABLE_IPV6) && \
-        !lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_IPV6))
-#else
-#define LWS_IPV6_ENABLED(context) (0)
-#endif
-
-#ifdef LWS_USE_UNIX_SOCK
-#define LWS_UNIX_SOCK_ENABLED(vhost) \
-       (vhost->options & LWS_SERVER_OPTION_UNIX_SOCK)
-#else
-#define LWS_UNIX_SOCK_ENABLED(vhost) (0)
-#endif
-
-typedef union {
-#ifdef LWS_USE_IPV6
-       struct sockaddr_in6 sa6;
-#endif
-       struct sockaddr_in sa4;
-} sockaddr46;
-
-enum uri_path_states {
-       URIPS_IDLE,
-       URIPS_SEEN_SLASH,
-       URIPS_SEEN_SLASH_DOT,
-       URIPS_SEEN_SLASH_DOT_DOT,
-};
-
-enum uri_esc_states {
-       URIES_IDLE,
-       URIES_SEEN_PERCENT,
-       URIES_SEEN_PERCENT_H1,
-};
-
-/* notice that these union members:
- *
- *  hdr
- *  http
- *  http2
- *
- * all have a pointer to allocated_headers struct as their first member.
- *
- * It means for allocated_headers access, the three union paths can all be
- * used interchangeably to access the same data
- */
-
-
-#ifndef LWS_NO_CLIENT
-struct client_info_stash {
-       char address[256];
-       char path[4096];
-       char host[256];
-       char origin[256];
-       char protocol[256];
-       char method[16];
-       char iface[16];
-};
-#endif
-
-struct _lws_header_related {
-       /* MUST be first in struct */
-       struct allocated_headers *ah;
-       struct lws *ah_wait_list;
-       unsigned char *preamble_rx;
-#ifndef LWS_NO_CLIENT
-       struct client_info_stash *stash;
-#endif
-       unsigned int preamble_rx_len;
-       enum uri_path_states ups;
-       enum uri_esc_states ues;
-       short lextable_pos;
-       unsigned int current_token_limit;
-
-       char esc_stash;
-       char post_literal_equal;
-       unsigned char parser_state; /* enum lws_token_indexes */
-};
-
-#if defined(LWS_WITH_RANGES)
-enum range_states {
-       LWSRS_NO_ACTIVE_RANGE,
-       LWSRS_BYTES_EQ,
-       LWSRS_FIRST,
-       LWSRS_STARTING,
-       LWSRS_ENDING,
-       LWSRS_COMPLETED,
-       LWSRS_SYNTAX,
-};
-
-struct lws_range_parsing {
-       unsigned long long start, end, extent, agg, budget;
-       const char buf[128];
-       int pos;
-       enum range_states state;
-       char start_valid, end_valid, ctr, count_ranges, did_try, inside, send_ctr;
-};
-
-int
-lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp, unsigned long long extent);
-int
-lws_ranges_next(struct lws_range_parsing *rp);
-void
-lws_ranges_reset(struct lws_range_parsing *rp);
-#endif
-
-struct _lws_http_mode_related {
-       /* MUST be first in struct */
-       struct allocated_headers *ah; /* mirroring  _lws_header_related */
-       struct lws *ah_wait_list;
-       unsigned char *preamble_rx;
-#ifndef LWS_NO_CLIENT
-       struct client_info_stash *stash;
-#endif
-       unsigned int preamble_rx_len;
-       struct lws *new_wsi_list;
-       lws_filepos_t filepos;
-       lws_filepos_t filelen;
-       lws_fop_fd_t fop_fd;
-
-#if defined(LWS_WITH_RANGES)
-       struct lws_range_parsing range;
-       char multipart_content_type[64];
-#endif
-
-       enum http_version request_version;
-       enum http_connection_type connection_type;
-       lws_filepos_t content_length;
-       lws_filepos_t content_remain;
-};
-
-#ifdef LWS_USE_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,
-
-       LWS_HTTP2_SETTINGS__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,
-
-       LWS_HTTP2_FRAME_TYPE_COUNT /* always last */
-};
-
-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,
-};
-
-#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 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,
-
-       HPKS_HLEN,
-       HPKS_HLEN_EXT,
-
-       HPKS_DATA,
-
-       /* optional after last header block */
-       HKPS_OPT_DISCARD_PADDING,
-};
-
-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_SIZE_5
-};
-
-struct hpack_dt_entry {
-       int token; /* additions that don't map to a token are ignored */
-       int arg_offset;
-       int arg_len;
-};
-
-struct hpack_dynamic_table {
-       struct hpack_dt_entry *entries;
-       char *args;
-       int pos;
-       int next;
-       int num_entries;
-       int args_length;
-};
-
-struct _lws_http2_related {
-       /*
-        * having this first lets us also re-use all HTTP union code
-        * and in turn, http_mode_related has allocated headers in right
-        * place so we can use the header apis on the wsi directly still
-        */
-       struct _lws_http_mode_related http; /* MUST BE FIRST IN STRUCT */
-
-       struct http2_settings my_settings;
-       struct http2_settings peer_settings;
-
-       struct lws *parent_wsi;
-       struct lws *next_child_wsi;
-
-       struct hpack_dynamic_table *hpack_dyn_table;
-       struct lws *stream_wsi;
-       unsigned char ping_payload[8];
-       unsigned char one_setting[LWS_HTTP2_SETTINGS_LENGTH];
-
-       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;
-       unsigned int child_count;
-       int my_priority;
-
-       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 short round_robin_POLLOUT;
-       unsigned short count_POLLOUT_children;
-       unsigned short hpack_pos;
-
-       unsigned char type;
-       unsigned char flags;
-       unsigned char frame_state;
-       unsigned char padding;
-       unsigned char hpack_m;
-       unsigned char initialized;
-};
-
-#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->u.http2.parent_wsi)
-
-#endif
-
-struct _lws_websocket_related {
-       /* cheapest way to deal with ah overlap with ws union transition */
-       struct _lws_header_related hdr;
-       char *rx_ubuf;
-       unsigned int rx_ubuf_alloc;
-       struct lws *rx_draining_ext_list;
-       struct lws *tx_draining_ext_list;
-       time_t time_next_ping_check;
-       size_t rx_packet_length;
-       unsigned int rx_ubuf_head;
-       unsigned char mask[4];
-       /* Also used for close content... control opcode == < 128 */
-       unsigned char ping_payload_buf[128 - 3 + LWS_PRE];
-
-       unsigned char ping_payload_len;
-       unsigned char mask_idx;
-       unsigned char opcode;
-       unsigned char rsv;
-       unsigned char rsv_first_msg;
-       /* zero if no info, or length including 2-byte close code */
-       unsigned char close_in_ping_buffer_len;
-       unsigned char utf8;
-       unsigned char stashed_write_type;
-       unsigned char tx_draining_stashed_wp;
-
-       unsigned int final:1;
-       unsigned int frame_is_binary:1;
-       unsigned int all_zero_nonce:1;
-       unsigned int this_frame_masked:1;
-       unsigned int inside_frame:1; /* next write will be more of frame */
-       unsigned int clean_buffer:1; /* buffer not rewritten by extension */
-       unsigned int payload_is_close:1; /* process as PONG, but it is close */
-       unsigned int ping_pending_flag:1;
-       unsigned int continuation_possible:1;
-       unsigned int owed_a_fin:1;
-       unsigned int check_utf8:1;
-       unsigned int defeat_check_utf8:1;
-       unsigned int pmce_compressed_message:1;
-       unsigned int stashed_write_pending:1;
-       unsigned int rx_draining_ext:1;
-       unsigned int tx_draining_ext:1;
-       unsigned int send_check_ping:1;
-       unsigned int first_fragment:1;
-};
-
-#ifdef LWS_WITH_CGI
-
-enum {
-       SIGNIFICANT_HDR_CONTENT_LENGTH,
-       SIGNIFICANT_HDR_LOCATION,
-       SIGNIFICANT_HDR_STATUS,
-       SIGNIFICANT_HDR_TRANSFER_ENCODING,
-
-       SIGNIFICANT_HDR_COUNT
-};
-
-/* wsi who is master of the cgi points to an lws_cgi */
-
-struct lws_cgi {
-       struct lws_cgi *cgi_list;
-       struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */
-       struct lws *wsi; /* owner */
-       unsigned char *headers_buf;
-       unsigned char *headers_pos;
-       unsigned char *headers_dumped;
-       unsigned char *headers_end;
-       lws_filepos_t content_length;
-       lws_filepos_t content_length_seen;
-       int pipe_fds[3][2];
-       int match[SIGNIFICANT_HDR_COUNT];
-       int pid;
-       int response_code;
-       int lp;
-       char l[12];
-
-       unsigned int being_closed:1;
-       unsigned int explicitly_chunked:1;
-
-       unsigned char chunked_grace;
-};
-#endif
-
-signed char char_to_hex(const char c);
-
-#ifndef LWS_NO_CLIENT
-enum lws_chunk_parser {
-       ELCP_HEX,
-       ELCP_CR,
-       ELCP_CONTENT,
-       ELCP_POST_CR,
-       ELCP_POST_LF,
-};
-#endif
-
-struct lws_rewrite;
-
-#ifdef LWS_WITH_ACCESS_LOG
-struct lws_access_log {
-       char *header_log;
-       char *user_agent;
-       unsigned long sent;
-       int response;
-};
-#endif
-
-struct lws {
-
-       /* structs */
-       /* members with mutually exclusive lifetimes are unionized */
-
-       union u {
-               struct _lws_http_mode_related http;
-#ifdef LWS_USE_HTTP2
-               struct _lws_http2_related http2;
-#endif
-               struct _lws_header_related hdr;
-               struct _lws_websocket_related ws;
-       } u;
-
-       /* lifetime members */
-
-#if defined(LWS_USE_LIBEV) || defined(LWS_USE_LIBUV) || defined(LWS_USE_LIBEVENT)
-       struct lws_io_watcher w_read;
-#endif
-#if defined(LWS_USE_LIBEV) || defined(LWS_USE_LIBEVENT)
-       struct lws_io_watcher w_write;
-#endif
-       time_t pending_timeout_limit;
-
-       /* pointers */
-
-       struct lws_context *context;
-       struct lws_vhost *vhost;
-       struct lws *parent; /* points to parent, if any */
-       struct lws *child_list; /* points to first child */
-       struct lws *sibling_list; /* subsequent children at same level */
-#ifdef LWS_WITH_CGI
-       struct lws_cgi *cgi; /* wsi being cgi master have one of these */
-#endif
-       const struct lws_protocols *protocol;
-       struct lws **same_vh_protocol_prev, *same_vh_protocol_next;
-       struct lws *timeout_list;
-       struct lws **timeout_list_prev;
-#ifdef LWS_WITH_ACCESS_LOG
-       struct lws_access_log access_log;
-#endif
-       void *user_space;
-       void *opaque_parent_data;
-       /* rxflow handling */
-       unsigned char *rxflow_buffer;
-       /* truncated send handling */
-       unsigned char *trunc_alloc; /* non-NULL means buffering in progress */
-
-#if defined (LWS_WITH_ESP8266)
-       void *premature_rx;
-       unsigned short prem_rx_size, prem_rx_pos;
-#endif
-
-#ifndef LWS_NO_EXTENSIONS
-       const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE];
-       void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE];
-#endif
-#ifdef LWS_OPENSSL_SUPPORT
-       SSL *ssl;
-       BIO *client_bio;
-       struct lws *pending_read_list_prev, *pending_read_list_next;
-#if defined(LWS_WITH_STATS)
-       uint64_t accept_start_us;
-       char seen_rx;
-#endif
-#endif
-#ifdef LWS_WITH_HTTP_PROXY
-       struct lws_rewrite *rw;
-#endif
-#ifdef LWS_LATENCY
-       unsigned long action_start;
-       unsigned long latency_start;
-#endif
-       lws_sock_file_fd_type desc; /* .filefd / .sockfd */
-#if defined(LWS_WITH_STATS)
-       uint64_t active_writable_req_us;
-#endif
-       /* ints */
-       int position_in_fds_table;
-       int rxflow_len;
-       int 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 */
-#ifndef LWS_NO_CLIENT
-       int chunk_remaining;
-#endif
-       unsigned int cache_secs;
-
-       unsigned int hdr_parsing_completed:1;
-       unsigned int http2_substream:1;
-       unsigned int listener:1;
-       unsigned int user_space_externally_allocated:1;
-       unsigned int socket_is_permanently_unusable:1;
-       unsigned int rxflow_change_to:2;
-       unsigned int more_rx_waiting:1; /* has to live here since ah may stick to end */
-       unsigned int conn_stat_done:1;
-       unsigned int cache_reuse:1;
-       unsigned int cache_revalidate:1;
-       unsigned int cache_intermediaries:1;
-       unsigned int favoured_pollin:1;
-       unsigned int sending_chunked:1;
-       unsigned int already_did_cce:1;
-       unsigned int told_user_closed:1;
-       unsigned int waiting_to_send_close_frame:1;
-       unsigned int ipv6:1;
-       unsigned int parent_carries_io:1;
-       unsigned int parent_pending_cb_on_writable:1;
-
-#if defined(LWS_WITH_ESP8266)
-       unsigned int pending_send_completion:3;
-       unsigned int close_is_pending_send_completion:1;
-#endif
-#ifdef LWS_WITH_ACCESS_LOG
-       unsigned int access_log_pending:1;
-#endif
-#ifndef LWS_NO_CLIENT
-       unsigned int do_ws:1; /* whether we are doing http or ws flow */
-       unsigned int chunked:1; /* if the clientside connection is chunked */
-       unsigned int client_rx_avail:1;
-       unsigned int client_http_body_pending:1;
-#endif
-#ifdef LWS_WITH_HTTP_PROXY
-       unsigned int perform_rewrite:1;
-#endif
-#ifndef LWS_NO_EXTENSIONS
-       unsigned int extension_data_pending:1;
-#endif
-#ifdef LWS_OPENSSL_SUPPORT
-       unsigned int use_ssl:4;
-#endif
-#ifdef _WIN32
-       unsigned int sock_send_blocking:1;
-#endif
-#ifdef LWS_OPENSSL_SUPPORT
-       unsigned int redirect_to_https:1;
-#endif
-
-       /* volatile to make sure code is aware other thread can change */
-       volatile unsigned int handling_pollout:1;
-       volatile unsigned int leave_pollout_active:1;
-
-#ifndef LWS_NO_CLIENT
-       unsigned short c_port;
-#endif
-
-       /* chars */
-#ifndef LWS_NO_EXTENSIONS
-       unsigned char count_act_ext;
-#endif
-       unsigned char 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;
-#ifdef LWS_WITH_CGI
-       char cgi_channel; /* which of stdin/out/err */
-       char hdr_state;
-#endif
-#ifndef LWS_NO_CLIENT
-       char chunk_parser; /* enum lws_chunk_parser */
-#endif
-#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT)
-       char reason_bf; /* internal writeable callback reason bitfield */
-#endif
-};
-
-LWS_EXTERN int log_level;
-
-LWS_EXTERN int
-lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
-               const char *iface);
-
-#if defined(LWS_USE_IPV6)
-LWS_EXTERN unsigned long
-lws_get_addr_scope(const char *ipaddr);
-#endif
-
-LWS_EXTERN void
-lws_close_free_wsi(struct lws *wsi, enum lws_close_status);
-
-LWS_EXTERN int
-remove_wsi_socket_from_fds(struct lws *wsi);
-LWS_EXTERN int
-lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len);
-
-#ifndef LWS_LATENCY
-static inline void
-lws_latency(struct lws_context *context, struct lws *wsi, const char *action,
-           int ret, int completion) {
-       do {
-               (void)context; (void)wsi; (void)action; (void)ret;
-               (void)completion;
-       } while (0);
-}
-static inline void
-lws_latency_pre(struct lws_context *context, struct lws *wsi) {
-       do { (void)context; (void)wsi; } while (0);
-}
-#else
-#define lws_latency_pre(_context, _wsi) lws_latency(_context, _wsi, NULL, 0, 0)
-extern void
-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_http_action(struct lws *wsi);
-
-LWS_EXTERN int
-lws_b64_selftest(void);
-
-LWS_EXTERN int
-lws_service_flag_pending(struct lws_context *context, int tsi);
-
-#if defined(_WIN32) || defined(LWS_WITH_ESP8266)
-LWS_EXTERN struct lws *
-wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd);
-
-LWS_EXTERN int
-insert_wsi(struct lws_context *context, struct lws *wsi);
-
-LWS_EXTERN int
-delete_from_fd(struct lws_context *context, lws_sockfd_type fd);
-#else
-#define wsi_from_fd(A,B)  A->lws_lookup[B]
-#define insert_wsi(A,B)   assert(A->lws_lookup[B->desc.sockfd] == 0); A->lws_lookup[B->desc.sockfd]=B
-#define delete_from_fd(A,B) A->lws_lookup[B]=0
-#endif
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len);
-
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_service_timeout_check(struct lws *wsi, unsigned int sec);
-
-LWS_EXTERN void
-lws_remove_from_timeout_list(struct lws *wsi);
-
-LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
-lws_client_connect_2(struct lws *wsi);
-
-LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT
-lws_client_reset(struct lws **wsi, int ssl, const char *address, int port,
-                const char *path, const char *host);
-
-LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
-lws_create_new_server_wsi(struct lws_vhost *vhost);
-
-LWS_EXTERN char * LWS_WARN_UNUSED_RESULT
-lws_generate_client_handshake(struct lws *wsi, char *pkt);
-
-LWS_EXTERN int
-lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd);
-
-LWS_EXTERN struct lws *
-lws_client_connect_via_info2(struct lws *wsi);
-
-/*
- * EXTENSIONS
- */
-
-#ifndef LWS_NO_EXTENSIONS
-LWS_VISIBLE void
-lws_context_init_extensions(struct lws_context_creation_info *info,
-                           struct lws_context *context);
-LWS_EXTERN int
-lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r,
-                         void *v, size_t len);
-
-LWS_EXTERN int
-lws_ext_cb_active(struct lws *wsi, int reason, void *buf, int len);
-LWS_EXTERN int
-lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, int reason,
-                   void *arg, int len);
-
-#else
-#define lws_any_extension_handled(_a, _b, _c, _d) (0)
-#define lws_ext_cb_active(_a, _b, _c, _d) (0)
-#define lws_ext_cb_all_exts(_a, _b, _c, _d, _e) (0)
-#define lws_issue_raw_ext_access lws_issue_raw
-#define lws_context_init_extensions(_a, _b)
-#endif
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_client_interpret_server_handshake(struct lws *wsi);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_rx_sm(struct lws *wsi, unsigned char c);
-
-LWS_EXTERN int
-lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, size_t *len);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len);
-
-LWS_EXTERN void
-lws_union_transition(struct lws *wsi, enum connection_mode mode);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-user_callback_handle_rxflow(lws_callback_function, struct lws *wsi,
-                           enum lws_callback_reasons reason, void *user,
-                           void *in, size_t len);
-#ifdef LWS_USE_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);
-LWS_EXTERN int
-lws_http2_interpret_settings_payload(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,
-                                    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_EXTERN int lws_hpack_interpret(struct lws *wsi,
-                                  unsigned char c);
-LWS_EXTERN 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);
-LWS_EXTERN int
-lws_add_http2_header_by_token(struct lws *wsi,
-                           enum lws_token_indexes token,
-                           const unsigned char *value, int length,
-                           unsigned char **p, unsigned char *end);
-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);
-#else
-#define lws_http2_configure_if_upgraded(x)
-#endif
-
-LWS_EXTERN int
-lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd);
-
-LWS_EXTERN int
-lws_plat_check_connection_error(struct lws *wsi);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_header_table_attach(struct lws *wsi, int autoservice);
-
-LWS_EXTERN int
-lws_header_table_detach(struct lws *wsi, int autoservice);
-
-LWS_EXTERN void
-lws_header_table_reset(struct lws *wsi, int autoservice);
-void
-_lws_header_table_reset(struct allocated_headers *ah);
-
-void
-lws_header_table_force_to_detachable_state(struct lws *wsi);
-int
-lws_header_table_is_in_detachable_state(struct lws *wsi);
-
-LWS_EXTERN char * LWS_WARN_UNUSED_RESULT
-lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ensure_user_space(struct lws *wsi);
-
-LWS_EXTERN int
-lws_change_pollfd(struct lws *wsi, int _and, int _or);
-
-#ifndef LWS_NO_SERVER
-int lws_context_init_server(struct lws_context_creation_info *info,
-                           struct lws_vhost *vhost);
-LWS_EXTERN struct lws_vhost *
-lws_select_vhost(struct lws_context *context, int port, const char *servername);
-LWS_EXTERN int
-handshake_0405(struct lws_context *context, struct lws *wsi);
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len);
-LWS_EXTERN void
-lws_server_get_canonical_hostname(struct lws_context *context,
-                                 struct lws_context_creation_info *info);
-#else
-#define lws_context_init_server(_a, _b) (0)
-#define lws_interpret_incoming_packet(_a, _b, _c) (0)
-#define lws_server_get_canonical_hostname(_a, _b)
-#endif
-
-#ifndef LWS_NO_DAEMONIZE
-LWS_EXTERN int get_daemonize_pid();
-#else
-#define get_daemonize_pid() (0)
-#endif
-
-#if !defined(LWS_WITH_ESP8266)
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-interface_to_sa(struct lws_vhost *vh, const char *ifname,
-               struct sockaddr_in *addr, size_t addrlen);
-#endif
-LWS_EXTERN void lwsl_emit_stderr(int level, const char *line);
-
-enum lws_ssl_capable_status {
-       LWS_SSL_CAPABLE_ERROR = -1,
-       LWS_SSL_CAPABLE_MORE_SERVICE = -2,
-};
-
-#ifndef LWS_OPENSSL_SUPPORT
-#define LWS_SSL_ENABLED(context) (0)
-#define lws_context_init_server_ssl(_a, _b) (0)
-#define lws_ssl_destroy(_a)
-#define lws_context_init_http2_ssl(_a)
-#define lws_ssl_capable_read lws_ssl_capable_read_no_ssl
-#define lws_ssl_capable_write lws_ssl_capable_write_no_ssl
-#define lws_ssl_pending lws_ssl_pending_no_ssl
-#define lws_server_socket_service_ssl(_b, _c) (0)
-#define lws_ssl_close(_a) (0)
-#define lws_ssl_context_destroy(_a)
-#define lws_ssl_SSL_CTX_destroy(_a)
-#define lws_ssl_remove_wsi_from_buffered_list(_a)
-#define lws_context_init_ssl_library(_a)
-#else
-#define LWS_SSL_ENABLED(context) (context->use_ssl)
-LWS_EXTERN int openssl_websocket_private_data_index;
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len);
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len);
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ssl_pending(struct lws *wsi);
-LWS_EXTERN int
-lws_context_init_ssl_library(struct lws_context_creation_info *info);
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_server_socket_service_ssl(struct lws *new_wsi, lws_sockfd_type accept_fd);
-LWS_EXTERN int
-lws_ssl_close(struct lws *wsi);
-LWS_EXTERN void
-lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost);
-LWS_EXTERN void
-lws_ssl_context_destroy(struct lws_context *context);
-LWS_VISIBLE void
-lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi);
-LWS_EXTERN int
-lws_ssl_client_bio_create(struct lws *wsi);
-LWS_EXTERN int
-lws_ssl_client_connect1(struct lws *wsi);
-LWS_EXTERN int
-lws_ssl_client_connect2(struct lws *wsi);
-LWS_EXTERN void
-lws_ssl_elaborate_error(void);
-#ifndef LWS_NO_SERVER
-LWS_EXTERN int
-lws_context_init_server_ssl(struct lws_context_creation_info *info,
-                           struct lws_vhost *vhost);
-#else
-#define lws_context_init_server_ssl(_a, _b) (0)
-#endif
-LWS_EXTERN void
-lws_ssl_destroy(struct lws_vhost *vhost);
-/* HTTP2-related */
-
-#ifdef LWS_USE_HTTP2
-LWS_EXTERN void
-lws_context_init_http2_ssl(struct lws_vhost *vhost);
-#else
-#define lws_context_init_http2_ssl(_a)
-#endif
-#endif
-
-#if LWS_MAX_SMP > 1
-static LWS_INLINE void
-lws_pt_mutex_init(struct lws_context_per_thread *pt)
-{
-       pthread_mutex_init(&pt->lock, NULL);
-}
-
-static LWS_INLINE void
-lws_pt_mutex_destroy(struct lws_context_per_thread *pt)
-{
-       pthread_mutex_destroy(&pt->lock);
-}
-
-static LWS_INLINE void
-lws_pt_lock(struct lws_context_per_thread *pt)
-{
-       if (!pt->lock_depth++)
-               pthread_mutex_lock(&pt->lock);
-}
-
-static LWS_INLINE void
-lws_pt_unlock(struct lws_context_per_thread *pt)
-{
-       if (!(--pt->lock_depth))
-               pthread_mutex_unlock(&pt->lock);
-}
-#else
-#define lws_pt_mutex_init(_a) (void)(_a)
-#define lws_pt_mutex_destroy(_a) (void)(_a)
-#define lws_pt_lock(_a) (void)(_a)
-#define lws_pt_unlock(_a) (void)(_a)
-#endif
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_ssl_pending_no_ssl(struct lws *wsi);
-
-#ifdef LWS_WITH_HTTP_PROXY
-struct lws_rewrite {
-       hubbub_parser *parser;
-       hubbub_parser_optparams params;
-       const char *from, *to;
-       int from_len, to_len;
-       unsigned char *p, *end;
-       struct lws *wsi;
-};
-static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len)
-{
-       if (s->len != len)
-               return 1;
-
-       return strncmp((const char *)s->ptr, p, len);
-}
-typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw);
-LWS_EXTERN struct lws_rewrite *
-lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to);
-LWS_EXTERN void
-lws_rewrite_destroy(struct lws_rewrite *r);
-LWS_EXTERN int
-lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len);
-#endif
-
-#ifndef LWS_NO_CLIENT
-LWS_EXTERN int lws_client_socket_service(struct lws_context *context,
-                                        struct lws *wsi,
-                                        struct lws_pollfd *pollfd);
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_http_transaction_completed_client(struct lws *wsi);
-#ifdef LWS_OPENSSL_SUPPORT
-LWS_EXTERN int
-lws_context_init_client_ssl(struct lws_context_creation_info *info,
-                           struct lws_vhost *vhost);
-
-LWS_EXTERN void
-lws_ssl_info_callback(const SSL *ssl, int where, int ret);
-
-#else
-       #define lws_context_init_client_ssl(_a, _b) (0)
-#endif
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len);
-LWS_EXTERN void
-lws_decode_ssl_error(void);
-#else
-#define lws_context_init_client_ssl(_a, _b) (0)
-#define lws_handshake_client(_a, _b, _c) (0)
-#endif
-
-LWS_EXTERN int
-_lws_rx_flow_control(struct lws *wsi);
-
-LWS_EXTERN int
-_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa);
-
-#ifndef LWS_NO_SERVER
-LWS_EXTERN int
-lws_server_socket_service(struct lws_context *context, struct lws *wsi,
-                         struct lws_pollfd *pollfd);
-LWS_EXTERN int
-lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len);
-#else
-#define lws_server_socket_service(_a, _b, _c) (0)
-#define lws_handshake_server(_a, _b, _c) (0)
-#endif
-
-#ifdef LWS_WITH_ACCESS_LOG
-LWS_EXTERN int
-lws_access_log(struct lws *wsi);
-#else
-#define lws_access_log(_a)
-#endif
-
-LWS_EXTERN int
-lws_cgi_kill_terminated(struct lws_context_per_thread *pt);
-
-int
-lws_protocol_init(struct lws_context *context);
-
-int
-lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p);
-
-const struct lws_http_mount *
-lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len);
-
-/*
- * custom allocator
- */
-LWS_EXTERN void *
-lws_realloc(void *ptr, size_t size);
-
-LWS_EXTERN void * LWS_WARN_UNUSED_RESULT
-lws_zalloc(size_t size);
-
-#ifdef LWS_PLAT_OPTEE
-void *lws_malloc(size_t size);
-void lws_free(void *p);
-#define lws_free_set_NULL(P)    do { lws_free(P); (P) = NULL; } while(0)
-#else
-#define lws_malloc(S)  lws_realloc(NULL, S)
-#define lws_free(P)    lws_realloc(P, 0)
-#define lws_free_set_NULL(P)   do { lws_realloc(P, 0); (P) = NULL; } while(0)
-#endif
-
-const struct lws_plat_file_ops *
-lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path,
-                   const char **vpath);
-
-/* lws_plat_ */
-LWS_EXTERN void
-lws_plat_delete_socket_from_fds(struct lws_context *context,
-                               struct lws *wsi, int m);
-LWS_EXTERN void
-lws_plat_insert_socket_into_fds(struct lws_context *context,
-                               struct lws *wsi);
-LWS_EXTERN void
-lws_plat_service_periodic(struct lws_context *context);
-
-LWS_EXTERN int
-lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi,
-                      struct lws_pollfd *pfd);
-LWS_EXTERN void
-lws_add_wsi_to_draining_ext_list(struct lws *wsi);
-LWS_EXTERN void
-lws_remove_wsi_from_draining_ext_list(struct lws *wsi);
-LWS_EXTERN int
-lws_plat_context_early_init(void);
-LWS_EXTERN void
-lws_plat_context_early_destroy(struct lws_context *context);
-LWS_EXTERN void
-lws_plat_context_late_destroy(struct lws_context *context);
-LWS_EXTERN int
-lws_poll_listen_fd(struct lws_pollfd *fd);
-LWS_EXTERN int
-lws_plat_service(struct lws_context *context, int timeout_ms);
-LWS_EXTERN LWS_VISIBLE int
-_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi);
-LWS_EXTERN int
-lws_plat_init(struct lws_context *context,
-             struct lws_context_creation_info *info);
-LWS_EXTERN void
-lws_plat_drop_app_privileges(struct lws_context_creation_info *info);
-LWS_EXTERN unsigned long long
-time_in_microseconds(void);
-LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT
-lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt);
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_plat_inet_pton(int af, const char *src, void *dst);
-
-LWS_EXTERN int LWS_WARN_UNUSED_RESULT
-lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len);
-LWS_EXTERN int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
-                               lws_filepos_t *amount);
-LWS_EXTERN int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf,
-              lws_filepos_t *amount);
-
-LWS_EXTERN void
-lws_same_vh_protocol_remove(struct lws *wsi);
-LWS_EXTERN void
-lws_same_vh_protocol_insert(struct lws *wsi, int n);
-
-#if defined(LWS_WITH_STATS)
-void
-lws_stats_atomic_bump(struct lws_context * context,
-               struct lws_context_per_thread *pt, int index, uint64_t bump);
-void
-lws_stats_atomic_max(struct lws_context * context,
-               struct lws_context_per_thread *pt, int index, uint64_t val);
-#else
-static inline uint64_t lws_stats_atomic_bump(struct lws_context * context,
-               struct lws_context_per_thread *pt, int index, uint64_t bump) {
-       (void)context; (void)pt; (void)index; (void)bump; return 0; }
-static inline uint64_t lws_stats_atomic_max(struct lws_context * context,
-               struct lws_context_per_thread *pt, int index, uint64_t val) {
-       (void)context; (void)pt; (void)index; (void)val; return 0; }
-#endif
-
-/* socks */
-void socks_generate_msg(struct lws *wsi, enum socks_msg_type type,
-                       size_t *msg_len);
-
-#ifdef __cplusplus
-};
-#endif
diff --git a/lib/roles/README.md b/lib/roles/README.md
new file mode 100644 (file)
index 0000000..7905cfa
--- /dev/null
@@ -0,0 +1,161 @@
+## Information for new role implementers
+
+### Introduction
+
+In lws the "role" is the job the wsi is doing in the system, eg,
+being an http1 or h2, or ws connection, or being a listen socket, etc.
+
+This is different than, eg, a new ws protocol or a different callback
+for an existing role.  A new role is needed when you want to add support for
+something completely new, like a completely new wire protocol that
+doesn't use http or ws.
+
+So... what's the point of implementing the protocol inside the lws role framework?
+
+You inherit all the well-maintained lws core functionality around:
+
+ - connection lifecycle sequencing in a valgrind-clean way
+
+ - proxy support, HTTP and Socks5
+
+ - tls support working equally on mbedTLS and OpenSSL and derivatives without any code in the role
+
+ - apis for cert lifecycle management and parsing
+
+ - event loop support working on all the lws event loops (poll, libuv , ev, and event)
+
+ - clean connection tracking and closing even on advanced event loops
+
+ - user code follows the same simple callbacks on wsi
+
+ - multi-vhost support
+
+ - core multithreaded service support with usually no locking requirement on the role code
+
+ - direct compatibility with all other lws roles + protocols in the same event loop
+
+ - compatibility with higher-level stuff like lwsws as the server application
+
+### Code placement
+
+The code specific to that role should live in `./lib/roles/**role name**`
+
+If a role is asymmetic between a client and server side, like http is, it
+should generally be implemented as a single role.
+
+### Allowing control over enabling roles
+
+All roles should add a cmake define `LWS_ROLE_**role name**` and make its build
+dependent on it in CMakeLists.txt.  Export the cmakedefine in `./cmake/lws_config.h.in`
+as well so user builds can understand if the role is available in the lws build it is
+trying to bind to.
+
+If the role is disabled in cmake, nothing in its directory is built.
+
+### Role ops struct
+
+The role is defined by `struct lws_role_ops` in `lib/roles/private.h`,
+each role instantiates one of these and fills in the appropriate ops
+callbacks to perform its job.  By convention that lives in
+`./lib/roles/**role name**/ops-**role_name**.c`.
+
+### Private role declarations
+
+Truly private declarations for the role can go in the role directory as you like.
+However when the declarations must be accessible to other things in lws build, eg,
+the role adds members to `struct lws` when enabled, they should be in the role
+directory in a file `private.h`.
+
+Search for "bring in role private declarations" in `./lib/roles/private.h
+and add your private role file there following the style used for the other roles,
+eg,
+
+```
+#if defined(LWS_ROLE_WS)
+ #include "roles/ws/private.h"
+#else
+ #define lwsi_role_ws(wsi) (0)
+#endif
+```
+
+If the role is disabled at cmake, nothing from its private.h should be used anywhere.
+
+### Integrating role assets to lws
+
+If your role needs special storage in lws objects, that's no problem.  But to keep
+things sane, there are some rules.
+
+ - declare a "container struct" in your private.h for everything, eg, the ws role wants
+   to add storage in lws_vhost for enabled extensions, it declares in its private.h
+
+```
+struct lws_vhost_role_ws {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       const struct lws_extension *extensions;
+#endif
+};
+```
+
+ - add your role content in one place in the lws struct, protected by `#if defined(LWS_ROLE_**role name**)`,
+   eg, again for LWS_ROLE_WS
+
+```
+       struct lws_vhost {
+
+...
+
+#if defined(LWS_ROLE_WS)
+       struct lws_vhost_role_ws ws;
+#endif
+
+...
+```
+
+### Adding to lws available roles list
+
+Edit the NULL-terminated array `available_roles` at the top of `./lib/core/context.c` to include
+a pointer to your new role's ops struct, following the style already there.
+
+```
+const struct lws_role_ops * available_roles[] = {
+#if defined(LWS_ROLE_H2)
+       &role_ops_h2,
+#endif
+...
+```
+
+This makes lws aware that your role exists, and it can auto-generate some things like
+ALPN lists, and call your role ops callbacks for things like hooking vhost creation.
+
+### Enabling role adoption
+
+The primary way wsi get bound to a specific role is via the lws adoption api
+`lws_adopt_descriptor_vhost()`.  Add flags as necessary in `./include/libwebsockets/lws-adopt.h`
+`enum lws_adoption_type` and follow the existing code in `lws_adopt_descriptor_vhost()`
+to bind a wsi with suitable flags to your role ops.
+
+### Implementation of the role
+
+After that plumbing-in is completed, the role ops you declare are "live" on a wsi
+bound to them via the adoption api.
+
+The core support for wsis in lws has some generic concepts
+
+ - the wsi holds a pointer member `role_ops` that indicates which role ops the
+   wsi is bound to
+
+ - the wsi holds a generic uint32 `wsistate` that contains role flags and wsi state
+
+ - role flags are provided (LWSIFR_CLIENT, LWSIFR_SERVER) to differentiate between
+   client and server connections inside a wsi, along with helpers `lwsi_role_client(wsi)`
+   and `lwsi_role_server(wsi)`.
+
+ - lws provides around 30 generic states for the wsi starting from 'unconnected' through
+   various proxy or tunnel states, to 'established', and then various states shutting
+   down until 'dead socket'.  The states have testable flags and helpers to discover if
+   the wsi state is before establishment `lwsi_state_est(wsi)` and if in the state it is
+   in, it can handle pollout `lwsi_state_can_handle_POLLOUT(wsi)`.
+
+ - You set the initial binding, role flags and state using `lws_role_transition()`.  Afterwards
+   you can adjust the state using `lwsi_set_state()`.
+
diff --git a/lib/roles/cgi/cgi-server.c b/lib/roles/cgi/cgi-server.c
new file mode 100644 (file)
index 0000000..ea93815
--- /dev/null
@@ -0,0 +1,1232 @@
+/*
+ * libwebsockets - CGI management
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#define  _GNU_SOURCE
+
+#include "core/private.h"
+
+#if defined(WIN32) || defined(_WIN32)
+#else
+#include <sys/wait.h>
+#endif
+
+static const char *hex = "0123456789ABCDEF";
+
+static int
+urlencode(const char *in, int inlen, char *out, int outlen)
+{
+       char *start = out, *end = out + outlen;
+
+       while (inlen-- && out < end - 4) {
+               if ((*in >= 'A' && *in <= 'Z') ||
+                   (*in >= 'a' && *in <= 'z') ||
+                   (*in >= '0' && *in <= '9') ||
+                   *in == '-' ||
+                   *in == '_' ||
+                   *in == '.' ||
+                   *in == '~') {
+                       *out++ = *in++;
+                       continue;
+               }
+               if (*in == ' ') {
+                       *out++ = '+';
+                       in++;
+                       continue;
+               }
+               *out++ = '%';
+               *out++ = hex[(*in) >> 4];
+               *out++ = hex[(*in++) & 15];
+       }
+       *out = '\0';
+
+       if (out >= end - 4)
+               return -1;
+
+       return out - start;
+}
+
+static struct lws *
+lws_create_basic_wsi(struct lws_context *context, int tsi)
+{
+       struct lws *new_wsi;
+
+       if (!context->vhost_list)
+               return NULL;
+
+       if ((unsigned int)context->pt[tsi].fds_count ==
+           context->fd_limit_per_thread - 1) {
+               lwsl_err("no space for new conn\n");
+               return NULL;
+       }
+
+       new_wsi = lws_zalloc(sizeof(struct lws), "new wsi");
+       if (new_wsi == NULL) {
+               lwsl_err("Out of memory for new connection\n");
+               return NULL;
+       }
+
+       new_wsi->tsi = tsi;
+       new_wsi->context = context;
+       new_wsi->pending_timeout = NO_PENDING_TIMEOUT;
+       new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
+
+       /* initialize the instance struct */
+
+       lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, &role_ops_cgi);
+
+       new_wsi->hdr_parsing_completed = 0;
+       new_wsi->position_in_fds_table = LWS_NO_FDS_POS;
+
+       /*
+        * these can only be set once the protocol is known
+        * we set an unestablished connection's protocol pointer
+        * to the start of the defauly vhost supported list, so it can look
+        * for matching ones during the handshake
+        */
+       new_wsi->protocol = context->vhost_list->protocols;
+       new_wsi->user_space = NULL;
+       new_wsi->desc.sockfd = LWS_SOCK_INVALID;
+       context->count_wsi_allocated++;
+
+       return new_wsi;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_cgi(struct lws *wsi, const char * const *exec_array,
+       int script_uri_path_len, int timeout_secs,
+       const struct lws_protocol_vhost_options *mp_cgienv)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       char *env_array[30], cgi_path[500], e[1024], *p = e,
+            *end = p + sizeof(e) - 1, tok[256], *t, *sum, *sumend;
+       struct lws_cgi *cgi;
+       int n, m = 0, i, uritok = -1, c;
+
+       /*
+        * give the master wsi a cgi struct
+        */
+
+       wsi->http.cgi = lws_zalloc(sizeof(*wsi->http.cgi), "new cgi");
+       if (!wsi->http.cgi) {
+               lwsl_err("%s: OOM\n", __func__);
+               return -1;
+       }
+
+       wsi->http.cgi->response_code = HTTP_STATUS_OK;
+
+       cgi = wsi->http.cgi;
+       cgi->wsi = wsi; /* set cgi's owning wsi */
+       sum = cgi->summary;
+       sumend = sum + strlen(cgi->summary) - 1;
+
+       for (n = 0; n < 3; n++) {
+               cgi->pipe_fds[n][0] = -1;
+               cgi->pipe_fds[n][1] = -1;
+       }
+
+       /* create pipes for [stdin|stdout] and [stderr] */
+
+       for (n = 0; n < 3; n++)
+               if (pipe(cgi->pipe_fds[n]) == -1)
+                       goto bail1;
+
+       /* create cgi wsis for each stdin/out/err fd */
+
+       for (n = 0; n < 3; n++) {
+               cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi);
+               if (!cgi->stdwsi[n]) {
+                       lwsl_err("%s: unable to create cgi stdwsi\n", __func__);
+                       goto bail2;
+               }
+               cgi->stdwsi[n]->cgi_channel = n;
+               lws_vhost_bind_wsi(wsi->vhost, cgi->stdwsi[n]);
+
+               lwsl_debug("%s: cgi stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__,
+                          cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)],
+                          cgi->pipe_fds[n][!(n == 0)]);
+
+               /* read side is 0, stdin we want the write side, others read */
+               cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)];
+               if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL,
+                   O_NONBLOCK) < 0) {
+                       lwsl_err("%s: setting NONBLOCK failed\n", __func__);
+                       goto bail2;
+               }
+       }
+
+       for (n = 0; n < 3; n++) {
+               if (wsi->context->event_loop_ops->accept)
+                       if (wsi->context->event_loop_ops->accept(cgi->stdwsi[n]))
+                               goto bail3;
+
+               if (__insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n]))
+                       goto bail3;
+               cgi->stdwsi[n]->parent = wsi;
+               cgi->stdwsi[n]->sibling_list = wsi->child_list;
+               wsi->child_list = cgi->stdwsi[n];
+       }
+
+       if (lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT))
+               goto bail3;
+       if (lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN))
+               goto bail3;
+       if (lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN))
+               goto bail3;
+
+       lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__,
+                  cgi->stdwsi[LWS_STDIN]->desc.sockfd,
+                  cgi->stdwsi[LWS_STDOUT]->desc.sockfd,
+                  cgi->stdwsi[LWS_STDERR]->desc.sockfd);
+
+       if (timeout_secs)
+               lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs);
+
+       /* the cgi stdout is always sending us http1.x header data first */
+       wsi->hdr_state = LCHS_HEADER;
+
+       /* add us to the pt list of active cgis */
+       lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->http.cgi);
+       cgi->cgi_list = pt->http.cgi_list;
+       pt->http.cgi_list = cgi;
+
+       sum += lws_snprintf(sum, sumend - sum, "%s ", exec_array[0]);
+
+       if (0) {
+               char *pct = lws_hdr_simple_ptr(wsi,
+                               WSI_TOKEN_HTTP_CONTENT_ENCODING);
+
+               if (pct && !strcmp(pct, "gzip"))
+                       wsi->http.cgi->gzip_inflate = 1;
+       }
+
+       /* prepare his CGI env */
+
+       n = 0;
+
+       if (lws_is_ssl(wsi))
+               env_array[n++] = "HTTPS=ON";
+       if (wsi->http.ah) {
+               static const unsigned char meths[] = {
+                       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,
+               #ifdef LWS_WITH_HTTP2
+                       WSI_TOKEN_HTTP_COLON_PATH,
+               #endif
+               };
+               static const char * const meth_names[] = {
+                       "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE",
+                       "CONNECT", "HEAD", ":path"
+               };
+
+               if (script_uri_path_len >= 0)
+                       for (m = 0; m < (int)LWS_ARRAY_SIZE(meths); m++)
+                               if (lws_hdr_total_length(wsi, meths[m]) >=
+                                               script_uri_path_len) {
+                                       uritok = meths[m];
+                                       break;
+                               }
+
+               if (script_uri_path_len < 0 && uritok < 0)
+                       goto bail3;
+//             if (script_uri_path_len < 0)
+//                     uritok = 0;
+
+               if (m >= 0) {
+                       env_array[n++] = p;
+                       if (m < 8) {
+                               p += lws_snprintf(p, end - p,
+                                                 "REQUEST_METHOD=%s",
+                                                 meth_names[m]);
+                               sum += lws_snprintf(sum, sumend - sum, "%s ",
+                                                   meth_names[m]);
+                       } else {
+                               p += lws_snprintf(p, end - p,
+                                                 "REQUEST_METHOD=%s",
+                         lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD));
+                               sum += lws_snprintf(sum, sumend - sum, "%s ",
+                                       lws_hdr_simple_ptr(wsi,
+                                                 WSI_TOKEN_HTTP_COLON_METHOD));
+                       }
+                       p++;
+               }
+
+               if (uritok >= 0)
+                       sum += lws_snprintf(sum, sumend - sum, "%s ",
+                                           lws_hdr_simple_ptr(wsi, uritok));
+
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "QUERY_STRING=");
+               /* dump the individual URI Arg parameters */
+               m = 0;
+               while (script_uri_path_len >= 0) {
+                       i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok),
+                                            WSI_TOKEN_HTTP_URI_ARGS, m);
+                       if (i < 0)
+                               break;
+                       t = tok;
+                       while (*t && *t != '=' && p < end - 4)
+                               *p++ = *t++;
+                       if (*t == '=')
+                               *p++ = *t++;
+                       i = urlencode(t, i- (t - tok), p, end - p);
+                       if (i > 0) {
+                               p += i;
+                               *p++ = '&';
+                       }
+                       m++;
+               }
+               if (m)
+                       p--;
+               *p++ = '\0';
+
+               if (uritok >= 0) {
+                       strcpy(cgi_path, "REQUEST_URI=");
+                       c = lws_hdr_copy(wsi, cgi_path + 12,
+                                        sizeof(cgi_path) - 12, uritok);
+                       if (c < 0)
+                               goto bail3;
+
+                       cgi_path[sizeof(cgi_path) - 1] = '\0';
+                       env_array[n++] = cgi_path;
+               }
+
+               sum += lws_snprintf(sum, sumend - sum, "%s", env_array[n - 1]);
+
+               if (script_uri_path_len >= 0) {
+                       env_array[n++] = p;
+                       p += lws_snprintf(p, end - p, "PATH_INFO=%s",
+                                     cgi_path + 12 + script_uri_path_len);
+                       p++;
+               }
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_REFERER=%s",
+                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER));
+               p++;
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_HOST=%s",
+                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
+               p++;
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_COOKIE=");
+               m = lws_hdr_copy(wsi, p, end - p, WSI_TOKEN_HTTP_COOKIE);
+               if (m > 0)
+                       p += lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE);
+               *p++ = '\0';
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_USER_AGENT=%s",
+                           lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT));
+               p++;
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_CONTENT_ENCODING=%s",
+                     lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING));
+               p++;
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_ACCEPT=%s",
+                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT));
+               p++;
+       }
+       if (script_uri_path_len >= 0 &&
+           lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "HTTP_ACCEPT_ENCODING=%s",
+                     lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING));
+               p++;
+       }
+       if (script_uri_path_len >= 0 &&
+           uritok == WSI_TOKEN_POST_URI) {
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
+                       env_array[n++] = p;
+                       p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s",
+                         lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE));
+                       p++;
+               }
+               if (!wsi->http.cgi->gzip_inflate &&
+                   lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
+                       env_array[n++] = p;
+                       p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s",
+                                         lws_hdr_simple_ptr(wsi,
+                                         WSI_TOKEN_HTTP_CONTENT_LENGTH));
+                       p++;
+               }
+
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
+                       wsi->http.cgi->post_in_expected =
+                               atoll(lws_hdr_simple_ptr(wsi,
+                                               WSI_TOKEN_HTTP_CONTENT_LENGTH));
+       }
+
+
+       env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin";
+
+       env_array[n++] = p;
+       p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1;
+
+       while (mp_cgienv) {
+               env_array[n++] = p;
+               p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name,
+                             mp_cgienv->value);
+               if (!strcmp(mp_cgienv->name, "GIT_PROJECT_ROOT")) {
+                       wsi->http.cgi->implied_chunked = 1;
+                       wsi->http.cgi->explicitly_chunked = 1;
+               }
+               lwsl_info("   Applying mount-specific cgi env '%s'\n",
+                          env_array[n - 1]);
+               p++;
+               mp_cgienv = mp_cgienv->next;
+       }
+
+       env_array[n++] = "SERVER_SOFTWARE=libwebsockets";
+       env_array[n] = NULL;
+
+#if 0
+       for (m = 0; m < n; m++)
+               lwsl_notice("    %s\n", env_array[m]);
+#endif
+
+       /*
+        * Actually having made the env, as a cgi we don't need the ah
+        * any more
+        */
+       if (script_uri_path_len >= 0)
+               lws_header_table_detach(wsi, 0);
+
+       /* we are ready with the redirection pipes... run the thing */
+#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
+       cgi->pid = fork();
+#else
+       cgi->pid = vfork();
+#endif
+       if (cgi->pid < 0) {
+               lwsl_err("fork failed, errno %d", errno);
+               goto bail3;
+       }
+
+#if defined(__linux__)
+       prctl(PR_SET_PDEATHSIG, SIGTERM);
+#endif
+       if (script_uri_path_len >= 0)
+               /* stops non-daemonized main processess getting SIGINT
+                * from TTY */
+               setpgrp();
+
+       if (cgi->pid) {
+               /* we are the parent process */
+               wsi->context->count_cgi_spawned++;
+               lwsl_info("%s: cgi %p spawned PID %d\n", __func__,
+                          cgi, cgi->pid);
+
+               /*
+                *  close:                stdin:r, stdout:w, stderr:w
+                * hide from other forks: stdin:w, stdout:r, stderr:r
+                */
+               for (n = 0; n < 3; n++) {
+                       lws_plat_apply_FD_CLOEXEC(cgi->pipe_fds[n][!!(n == 0)]);
+                       close(cgi->pipe_fds[n][!(n == 0)]);
+               }
+
+               /* inform cgi owner of the child PID */
+               n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                           LWS_CALLBACK_CGI_PROCESS_ATTACH,
+                                           wsi->user_space, NULL, cgi->pid);
+               (void)n;
+
+               return 0;
+       }
+
+       /* somewhere we can at least read things and enter it */
+       if (chdir("/tmp"))
+               lwsl_notice("%s: Failed to chdir\n", __func__);
+
+       /* We are the forked process, redirect and kill inherited things.
+        *
+        * Because of vfork(), we cannot do anything that changes pages in
+        * the parent environment.  Stuff that changes kernel state for the
+        * process is OK.  Stuff that happens after the execvpe() is OK.
+        */
+
+       for (n = 0; n < 3; n++) {
+               if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) {
+                       lwsl_err("%s: stdin dup2 failed\n", __func__);
+                       goto bail3;
+               }
+               close(cgi->pipe_fds[n][0]);
+               close(cgi->pipe_fds[n][1]);
+       }
+
+#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
+       for (m = 0; m < n; m++) {
+               p = strchr(env_array[m], '=');
+               *p++ = '\0';
+               setenv(env_array[m], p, 1);
+       }
+       execvp(exec_array[0], (char * const *)&exec_array[0]);
+#else
+       execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]);
+#endif
+
+       exit(1);
+
+bail3:
+       /* drop us from the pt cgi list */
+       pt->http.cgi_list = cgi->cgi_list;
+
+       while (--n >= 0)
+               __remove_wsi_socket_from_fds(wsi->http.cgi->stdwsi[n]);
+bail2:
+       for (n = 0; n < 3; n++)
+               if (wsi->http.cgi->stdwsi[n])
+                       __lws_free_wsi(cgi->stdwsi[n]);
+
+bail1:
+       for (n = 0; n < 3; n++) {
+               if (cgi->pipe_fds[n][0] >= 0)
+                       close(cgi->pipe_fds[n][0]);
+               if (cgi->pipe_fds[n][1] >= 0)
+                       close(cgi->pipe_fds[n][1]);
+       }
+
+       lws_free_set_NULL(wsi->http.cgi);
+
+       lwsl_err("%s: failed\n", __func__);
+
+       return -1;
+}
+
+/* we have to parse out these headers in the CGI output */
+
+static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = {
+       "content-length: ",
+       "location: ",
+       "status: ",
+       "transfer-encoding: chunked",
+       "content-encoding: gzip",
+};
+
+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, cmd;
+       unsigned char buf[LWS_PRE + 4096], *start = &buf[LWS_PRE], *p = start,
+                       *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name,
+                       *value = NULL;
+       char c, hrs;
+
+       if (!wsi->http.cgi)
+               return -1;
+
+       while (wsi->hdr_state != LHCS_PAYLOAD) {
+               /*
+                * We have to separate header / finalize and payload chunks,
+                * since they need to be handled separately
+                */
+               switch (wsi->hdr_state) {
+               case LHCS_RESPONSE:
+                       lwsl_debug("LHCS_RESPONSE: issuing response %d\n",
+                                  wsi->http.cgi->response_code);
+                       if (lws_add_http_header_status(wsi,
+                                                  wsi->http.cgi->response_code,
+                                                      &p, end))
+                               return 1;
+                       if (!wsi->http.cgi->explicitly_chunked &&
+                           !wsi->http.cgi->content_length &&
+                               lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+                                       (unsigned char *)"chunked", 7, &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_NO_FIN);
+
+                       /*
+                        * so we have a bunch of http/1 style ascii headers
+                        * starting from wsi->http.cgi->headers_buf through
+                        * wsi->http.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->http.cgi->headers_start;
+                       wsi->http.cgi->headers_start =
+                                       wsi->http.cgi->headers_pos;
+                       wsi->http.cgi->headers_dumped =
+                                       wsi->http.cgi->headers_start;
+                       hrs = HR_NAME;
+                       name = buf;
+
+                       while (p < wsi->http.cgi->headers_start) {
+                               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->http.cgi->headers_start) {
+                                               *name = '\0';
+                                               if ((strcmp((const char *)buf,
+                                                           "transfer-encoding")
+                                               )) {
+                                                       lwsl_debug("+ %s: %s\n",
+                                                                  buf, value);
+                                                       if (
+                                       lws_add_http_header_by_name(wsi, buf,
+                                       (unsigned char *)value, name - value,
+                                       (unsigned char **)&wsi->http.cgi->headers_pos,
+                                       (unsigned char *)wsi->http.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->http.cgi->headers_pos,
+                             (unsigned char *)wsi->http.cgi->headers_end)) {
+
+                               lwsl_notice("finalize failed\n");
+                               return -1;
+                       }
+
+                       wsi->hdr_state = LHCS_DUMP_HEADERS;
+                       wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS;
+                       lws_callback_on_writable(wsi);
+                       /* back to the loop for writeability again */
+                       return 0;
+
+               case LHCS_DUMP_HEADERS:
+
+                       n = wsi->http.cgi->headers_pos -
+                           wsi->http.cgi->headers_dumped;
+                       if (n > 512)
+                               n = 512;
+
+                       lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n);
+
+                       cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION;
+                       if (wsi->http.cgi->headers_dumped + n !=
+                           wsi->http.cgi->headers_pos) {
+                               lwsl_notice("adding no fin flag\n");
+                               cmd |= LWS_WRITE_NO_FIN;
+                       }
+
+                       m = lws_write(wsi,
+                                (unsigned char *)wsi->http.cgi->headers_dumped,
+                                     n, cmd);
+                       if (m < 0) {
+                               lwsl_debug("%s: write says %d\n", __func__, m);
+                               return -1;
+                       }
+                       wsi->http.cgi->headers_dumped += n;
+                       if (wsi->http.cgi->headers_dumped ==
+                           wsi->http.cgi->headers_pos) {
+                               wsi->hdr_state = LHCS_PAYLOAD;
+                               lws_free_set_NULL(wsi->http.cgi->headers_buf);
+                               lwsl_debug("freed cgi headers\n");
+                       } else {
+                               wsi->reason_bf |=
+                                       LWS_CB_REASON_AUX_BF__CGI_HEADERS;
+                               lws_callback_on_writable(wsi);
+                       }
+
+                       /* writeability becomes uncertain now we wrote
+                        * something, we must return to the event loop
+                        */
+                       return 0;
+               }
+
+               if (!wsi->http.cgi->headers_buf) {
+                       /* if we don't already have a headers buf, cook one */
+                       n = 2048;
+                       if (wsi->http2_substream)
+                               n = 4096;
+                       wsi->http.cgi->headers_buf = lws_malloc(n + LWS_PRE,
+                                                          "cgi hdr buf");
+                       if (!wsi->http.cgi->headers_buf) {
+                               lwsl_err("OOM\n");
+                               return -1;
+                       }
+
+                       lwsl_debug("allocated cgi hdrs\n");
+                       wsi->http.cgi->headers_start =
+                                       wsi->http.cgi->headers_buf + LWS_PRE;
+                       wsi->http.cgi->headers_pos = wsi->http.cgi->headers_start;
+                       wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_pos;
+                       wsi->http.cgi->headers_end =
+                                       wsi->http.cgi->headers_buf + n - 1;
+
+                       for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
+                               wsi->http.cgi->match[n] = 0;
+                               wsi->http.cgi->lp = 0;
+                       }
+               }
+
+               n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]);
+               if (n < 0)
+                       return -1;
+               n = read(n, &c, 1);
+               if (n < 0) {
+                       if (errno != EAGAIN) {
+                               lwsl_debug("%s: read says %d\n", __func__, n);
+                               return -1;
+                       }
+                       else
+                               n = 0;
+
+                       if (wsi->http.cgi->headers_pos >=
+                                       wsi->http.cgi->headers_end - 4) {
+                               lwsl_notice("CGI hdrs > buf size\n");
+
+                               return -1;
+                       }
+               }
+               if (!n)
+                       goto agin;
+
+               lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c,
+                          wsi->http.cgi->match[1], wsi->hdr_state);
+               if (!c)
+                       return -1;
+               switch (wsi->hdr_state) {
+               case LCHS_HEADER:
+                       hdr:
+                       for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
+                               /*
+                                * significant headers with
+                                * numeric decimal payloads
+                                */
+                               if (!significant_hdr[n][wsi->http.cgi->match[n]] &&
+                                   (c >= '0' && c <= '9') &&
+                                   wsi->http.cgi->lp < (int)sizeof(wsi->http.cgi->l) - 1) {
+                                       wsi->http.cgi->l[wsi->http.cgi->lp++] = c;
+                                       wsi->http.cgi->l[wsi->http.cgi->lp] = '\0';
+                                       switch (n) {
+                                       case SIGNIFICANT_HDR_CONTENT_LENGTH:
+                                               wsi->http.cgi->content_length =
+                                                       atoll(wsi->http.cgi->l);
+                                               break;
+                                       case SIGNIFICANT_HDR_STATUS:
+                                               wsi->http.cgi->response_code =
+                                                       atol(wsi->http.cgi->l);
+                                               lwsl_debug("Status set to %d\n",
+                                                  wsi->http.cgi->response_code);
+                                               break;
+                                       default:
+                                               break;
+                                       }
+                               }
+                               /* hits up to the NUL are sticky until next hdr */
+                               if (significant_hdr[n][wsi->http.cgi->match[n]]) {
+                                       if (tolower(c) ==
+                                           significant_hdr[n][wsi->http.cgi->match[n]])
+                                               wsi->http.cgi->match[n]++;
+                                       else
+                                               wsi->http.cgi->match[n] = 0;
+                               }
+                       }
+
+                       /* some cgi only send us \x0a for EOL */
+                       if (c == '\x0a') {
+                               wsi->hdr_state = LCHS_SINGLE_0A;
+                               *wsi->http.cgi->headers_pos++ = '\x0d';
+                       }
+                       *wsi->http.cgi->headers_pos++ = c;
+                       if (c == '\x0d')
+                               wsi->hdr_state = LCHS_LF1;
+
+                       if (wsi->hdr_state != LCHS_HEADER &&
+                           !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING]
+                                   [wsi->http.cgi->match[
+                                        SIGNIFICANT_HDR_TRANSFER_ENCODING]]) {
+                               lwsl_info("cgi produced chunked\n");
+                               wsi->http.cgi->explicitly_chunked = 1;
+                       }
+
+                       /* presence of Location: mandates 302 retcode */
+                       if (wsi->hdr_state != LCHS_HEADER &&
+                           !significant_hdr[SIGNIFICANT_HDR_LOCATION][
+                             wsi->http.cgi->match[SIGNIFICANT_HDR_LOCATION]]) {
+                               lwsl_debug("CGI: Location hdr seen\n");
+                               wsi->http.cgi->response_code = 302;
+                       }
+                       break;
+               case LCHS_LF1:
+                       *wsi->http.cgi->headers_pos++ = c;
+                       if (c == '\x0a') {
+                               wsi->hdr_state = LCHS_CR2;
+                               break;
+                       }
+                       /* we got \r[^\n]... it's unreasonable */
+                       lwsl_debug("%s: funny CRLF 0x%02X\n", __func__,
+                                  (unsigned char)c);
+                       return -1;
+
+               case LCHS_CR2:
+                       if (c == '\x0d') {
+                               /* drop the \x0d */
+                               wsi->hdr_state = LCHS_LF2;
+                               break;
+                       }
+                       wsi->hdr_state = LCHS_HEADER;
+                       for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
+                               wsi->http.cgi->match[n] = 0;
+                       wsi->http.cgi->lp = 0;
+                       goto hdr;
+
+               case LCHS_LF2:
+               case LCHS_SINGLE_0A:
+                       m = wsi->hdr_state;
+                       if (c == '\x0a') {
+                               lwsl_debug("Content-Length: %lld\n",
+                                       (unsigned long long)
+                                       wsi->http.cgi->content_length);
+                               wsi->hdr_state = LHCS_RESPONSE;
+                               /*
+                                * drop the \0xa ... finalize
+                                * will add it if needed (HTTP/1)
+                                */
+                               break;
+                       }
+                       if (m == LCHS_LF2)
+                               /* we got \r\n\r[^\n]... unreasonable */
+                               return -1;
+                       /* we got \x0anext header, it's reasonable */
+                       *wsi->http.cgi->headers_pos++ = c;
+                       wsi->hdr_state = LCHS_HEADER;
+                       for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
+                               wsi->http.cgi->match[n] = 0;
+                       wsi->http.cgi->lp = 0;
+                       break;
+               case LHCS_PAYLOAD:
+                       break;
+               }
+
+agin:
+               /* ran out of input, ended the hdrs, or filled up the hdrs buf */
+               if (!n || wsi->hdr_state == LHCS_PAYLOAD)
+                       return 0;
+       }
+
+       /* payload processing */
+
+       m = !wsi->http.cgi->implied_chunked && !wsi->http2_substream &&
+           !wsi->http.cgi->explicitly_chunked &&
+           !wsi->http.cgi->content_length;
+       n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]);
+       if (n < 0)
+               return -1;
+       if (m) {
+               uint8_t term[LWS_PRE + 6];
+
+               lwsl_info("%s: zero chunk\n", __func__);
+
+               memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5);
+
+               if (lws_write(wsi, term + LWS_PRE, 5,
+                             LWS_WRITE_HTTP_FINAL) != 5)
+                       return -1;
+
+               wsi->http.cgi->cgi_transaction_over = 1;
+
+               return 0;
+       }
+
+       n = read(n, start, sizeof(buf) - LWS_PRE);
+
+       if (n < 0 && errno != EAGAIN) {
+               lwsl_debug("%s: stdout read says %d\n", __func__, n);
+               return -1;
+       }
+       if (n > 0) {
+/*
+               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);
+                       memmove(start + m, start, n);
+                       memcpy(start, chdr, m);
+                       memcpy(start + m + n, "\x0d\x0a", 2);
+                       n += m + 2;
+               }
+               */
+
+#if defined(LWS_WITH_HTTP2)
+               if (wsi->http2_substream) {
+                       struct lws *nwsi = lws_get_network_wsi(wsi);
+
+                       __lws_set_timeout(wsi,
+                               PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
+
+                       if (!nwsi->immortal_substream_count)
+                               __lws_set_timeout(nwsi,
+                                       PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
+               }
+#endif
+
+               cmd = LWS_WRITE_HTTP;
+               if (wsi->http.cgi->content_length_seen + n ==
+                                               wsi->http.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->http.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);
+                       else
+                               return -1;
+                       return 1;
+               }
+               wsi->cgi_stdout_zero_length = 1;
+       }
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_cgi_kill(struct lws *wsi)
+{
+       struct lws_cgi_args args;
+       int status, n;
+
+       lwsl_debug("%s: %p\n", __func__, wsi);
+
+       if (!wsi->http.cgi)
+               return 0;
+
+       if (wsi->http.cgi->pid > 0) {
+               n = waitpid(wsi->http.cgi->pid, &status, WNOHANG);
+               if (n > 0) {
+                       lwsl_debug("%s: PID %d reaped\n", __func__,
+                                   wsi->http.cgi->pid);
+                       goto handled;
+               }
+               /* kill the process group */
+               n = kill(-wsi->http.cgi->pid, SIGTERM);
+               lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n",
+                          __func__, wsi->http.cgi->pid, n, errno);
+               if (n < 0) {
+                       /*
+                        * hum seen errno=3 when process is listed in ps,
+                        * it seems we don't always retain process grouping
+                        *
+                        * Direct these fallback attempt to the exact child
+                        */
+                       n = kill(wsi->http.cgi->pid, SIGTERM);
+                       if (n < 0) {
+                               n = kill(wsi->http.cgi->pid, SIGPIPE);
+                               if (n < 0) {
+                                       n = kill(wsi->http.cgi->pid, SIGKILL);
+                                       if (n < 0)
+                                               lwsl_info("%s: SIGKILL PID %d "
+                                                        "failed errno %d "
+                                                        "(maybe zombie)\n",
+                                                        __func__,
+                                                wsi->http.cgi->pid, errno);
+                               }
+                       }
+               }
+               /* He could be unkillable because he's a zombie */
+               n = 1;
+               while (n > 0) {
+                       n = waitpid(-wsi->http.cgi->pid, &status, WNOHANG);
+                       if (n > 0)
+                               lwsl_debug("%s: reaped PID %d\n", __func__, n);
+                       if (n <= 0) {
+                               n = waitpid(wsi->http.cgi->pid, &status, WNOHANG);
+                               if (n > 0)
+                                       lwsl_debug("%s: reaped PID %d\n",
+                                                  __func__, n);
+                       }
+               }
+       }
+
+handled:
+       args.stdwsi = &wsi->http.cgi->stdwsi[0];
+
+       if (wsi->http.cgi->pid != -1) {
+               n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                               LWS_CALLBACK_CGI_TERMINATED,
+                                               wsi->user_space, (void *)&args,
+                                               wsi->http.cgi->pid);
+               wsi->http.cgi->pid = -1;
+               if (n && !wsi->http.cgi->being_closed)
+                       lws_close_free_wsi(wsi, 0, "lws_cgi_kill");
+       }
+
+       return 0;
+}
+
+LWS_EXTERN int
+lws_cgi_kill_terminated(struct lws_context_per_thread *pt)
+{
+       struct lws_cgi **pcgi, *cgi = NULL;
+       int status, n = 1;
+
+       while (n > 0) {
+               /* find finished guys but don't reap yet */
+               n = waitpid(-1, &status, WNOHANG);
+               if (n <= 0)
+                       continue;
+               lwsl_debug("%s: observed PID %d terminated\n", __func__, n);
+
+               pcgi = &pt->http.cgi_list;
+
+               /* check all the subprocesses on the cgi list */
+               while (*pcgi) {
+                       /* get the next one first as list may change */
+                       cgi = *pcgi;
+                       pcgi = &(*pcgi)->cgi_list;
+
+                       if (cgi->pid <= 0)
+                               continue;
+
+                       /* finish sending cached headers */
+                       if (cgi->headers_buf)
+                               continue;
+
+                       /* wait for stdout to be drained */
+                       if (cgi->content_length > cgi->content_length_seen)
+                               continue;
+
+                       if (cgi->content_length) {
+                               lwsl_debug("%s: wsi %p: expected content "
+                                          "length seen: %lld\n", __func__,
+                                          cgi->wsi,
+                               (unsigned long long)cgi->content_length_seen);
+                       }
+
+                       /* reap it */
+                       waitpid(n, &status, WNOHANG);
+                       /*
+                        * he's already terminated so no need for kill()
+                        * but we should do the terminated cgi callback
+                        * and close him if he's not already closing
+                        */
+                       if (n == cgi->pid) {
+                               lwsl_debug("%s: found PID %d on cgi list\n",
+                                           __func__, n);
+
+                               if (!cgi->content_length) {
+                                       /*
+                                        * well, if he sends chunked...
+                                        * give him 2s after the
+                                        * cgi terminated to send buffered
+                                        */
+                                       cgi->chunked_grace++;
+                                       continue;
+                               }
+
+                               /* defeat kill() */
+                               cgi->pid = 0;
+                               lws_cgi_kill(cgi->wsi);
+
+                               break;
+                       }
+                       cgi = NULL;
+               }
+               /* if not found on the cgi list, as he's one of ours, reap */
+               if (!cgi) {
+                       lwsl_debug("%s: reading PID %d although no cgi match\n",
+                                       __func__, n);
+                       waitpid(n, &status, WNOHANG);
+               }
+       }
+
+       pcgi = &pt->http.cgi_list;
+
+       /* check all the subprocesses on the cgi list */
+       while (*pcgi) {
+               /* get the next one first as list may change */
+               cgi = *pcgi;
+               pcgi = &(*pcgi)->cgi_list;
+
+               if (cgi->pid <= 0)
+                       continue;
+
+               /* we deferred killing him after reaping his PID */
+               if (cgi->chunked_grace) {
+                       cgi->chunked_grace++;
+                       if (cgi->chunked_grace < 2)
+                               continue;
+                       goto finish_him;
+               }
+
+               /* finish sending cached headers */
+               if (cgi->headers_buf)
+                       continue;
+
+               /* wait for stdout to be drained */
+               if (cgi->content_length > cgi->content_length_seen)
+                       continue;
+
+               if (cgi->content_length)
+                       lwsl_debug("%s: wsi %p: expected "
+                                  "content len seen: %lld\n", __func__,
+                                  cgi->wsi,
+                               (unsigned long long)cgi->content_length_seen);
+
+               /* reap it */
+               if (waitpid(cgi->pid, &status, WNOHANG) > 0) {
+
+                       if (!cgi->content_length) {
+                               /*
+                                * well, if he sends chunked...
+                                * give him 2s after the
+                                * cgi terminated to send buffered
+                                */
+                               cgi->chunked_grace++;
+                               continue;
+                       }
+finish_him:
+                       lwsl_debug("%s: found PID %d on cgi list\n",
+                                   __func__, cgi->pid);
+
+                       /* defeat kill() */
+                       cgi->pid = 0;
+                       lws_cgi_kill(cgi->wsi);
+
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN struct lws *
+lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch)
+{
+       if (!wsi->http.cgi)
+               return NULL;
+
+       return wsi->http.cgi->stdwsi[ch];
+}
+
+void
+lws_cgi_remove_and_kill(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws_cgi **pcgi = &pt->http.cgi_list;
+
+       /* remove us from the cgi list */
+       lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->http.cgi);
+       while (*pcgi) {
+               if (*pcgi == wsi->http.cgi) {
+                       /* drop us from the pt cgi list */
+                       *pcgi = (*pcgi)->cgi_list;
+                       break;
+               }
+               pcgi = &(*pcgi)->cgi_list;
+       }
+       if (wsi->http.cgi->headers_buf) {
+               lwsl_debug("close: freed cgi headers\n");
+               lws_free_set_NULL(wsi->http.cgi->headers_buf);
+       }
+       /* we have a cgi going, we must kill it */
+       wsi->http.cgi->being_closed = 1;
+       lws_cgi_kill(wsi);
+}
diff --git a/lib/roles/cgi/ops-cgi.c b/lib/roles/cgi/ops-cgi.c
new file mode 100644 (file)
index 0000000..4880e21
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+static int
+rops_handle_POLLIN_cgi(struct lws_context_per_thread *pt, struct lws *wsi,
+                      struct lws_pollfd *pollfd)
+{
+       struct lws_cgi_args args;
+
+       assert(wsi->role_ops == &role_ops_cgi);
+
+       if (wsi->cgi_channel >= LWS_STDOUT &&
+           !(pollfd->revents & pollfd->events & LWS_POLLIN))
+               return LWS_HPI_RET_HANDLED;
+
+       if (wsi->cgi_channel == LWS_STDIN &&
+           !(pollfd->revents & pollfd->events & LWS_POLLOUT))
+               return LWS_HPI_RET_HANDLED;
+
+       if (wsi->cgi_channel == LWS_STDIN &&
+           lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+               lwsl_info("failed at set pollfd\n");
+               return LWS_HPI_RET_WSI_ALREADY_DIED;
+       }
+
+       args.ch = wsi->cgi_channel;
+       args.stdwsi = &wsi->parent->http.cgi->stdwsi[0];
+       args.hdr_state = wsi->hdr_state;
+
+       lwsl_debug("CGI LWS_STDOUT %p wsistate 0x%x\n",
+                  wsi->parent, wsi->wsistate);
+
+       if (user_callback_handle_rxflow(wsi->parent->protocol->callback,
+                                       wsi->parent, LWS_CALLBACK_CGI,
+                                       wsi->parent->user_space,
+                                       (void *)&args, 0))
+               return 1;
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+static int
+rops_handle_POLLOUT_cgi(struct lws *wsi)
+{
+       return LWS_HP_RET_USER_SERVICE;
+}
+
+static int
+rops_periodic_checks_cgi(struct lws_context *context, int tsi, time_t now)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+
+       lws_cgi_kill_terminated(pt);
+
+       return 0;
+}
+
+static int
+rops_destroy_role_cgi(struct lws *wsi)
+{
+#if defined(LWS_WITH_ZLIB)
+       if (!wsi->http.cgi)
+               return 0;
+       if (!wsi->http.cgi->gzip_init)
+               return 0;
+
+       inflateEnd(&wsi->http.cgi->inflate);
+       wsi->http.cgi->gzip_init = 0;
+#endif
+
+       return 0;
+}
+
+struct lws_role_ops role_ops_cgi = {
+       /* role name */                 "cgi",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           rops_periodic_checks_cgi,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_cgi,
+       /* handle_POLLOUT */            rops_handle_POLLOUT_cgi,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              rops_destroy_role_cgi,
+       /* adoption_bind */             NULL,
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { 0, 0 },
+       /* rx_cb clnt, srv */           { 0, 0 },
+       /* writeable cb clnt, srv */    { 0, 0 },
+       /* close cb clnt, srv */        { 0, 0 },
+       /* protocol_bind_cb c,s */      { 0, 0 },
+       /* protocol_unbind_cb c,s */    { 0, 0 },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/cgi/private.h b/lib/roles/cgi/private.h
new file mode 100644 (file)
index 0000000..53e5f72
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_WS
+ */
+
+#if defined(LWS_WITH_ZLIB)
+#if defined(LWS_WITH_MINIZ)
+#include <miniz.h>
+#else
+#include <zlib.h>
+#endif
+#endif
+
+extern struct lws_role_ops role_ops_cgi;
+
+#define lwsi_role_cgi(wsi) (wsi->role_ops == &role_ops_cgi)
+
+#define LWS_HTTP_CHUNK_HDR_SIZE 16
+
+enum {
+       SIGNIFICANT_HDR_CONTENT_LENGTH,         /* numeric */
+       SIGNIFICANT_HDR_LOCATION,
+       SIGNIFICANT_HDR_STATUS,                 /* numeric */
+       SIGNIFICANT_HDR_TRANSFER_ENCODING,
+       SIGNIFICANT_HDR_CONTENT_ENCODING_GZIP,
+
+       SIGNIFICANT_HDR_COUNT
+};
+
+struct lws;
+
+/* wsi who is master of the cgi points to an lws_cgi */
+
+struct lws_cgi {
+       struct lws_cgi *cgi_list;
+       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;
+
+       char summary[128];
+#if defined(LWS_WITH_ZLIB)
+       z_stream inflate;
+       uint8_t inflate_buf[1024];
+#endif
+
+       lws_filepos_t post_in_expected;
+       lws_filepos_t content_length;
+       lws_filepos_t content_length_seen;
+
+       int pipe_fds[3][2];
+       int match[SIGNIFICANT_HDR_COUNT];
+       char l[12];
+       int pid;
+       int response_code;
+       int lp;
+
+       unsigned char being_closed:1;
+       unsigned char explicitly_chunked:1;
+       unsigned char cgi_transaction_over:1;
+       unsigned char implied_chunked:1;
+       unsigned char gzip_inflate:1;
+       unsigned char gzip_init:1;
+
+       unsigned char chunked_grace;
+};
diff --git a/lib/roles/dbus/README.md b/lib/roles/dbus/README.md
new file mode 100644 (file)
index 0000000..7d479da
--- /dev/null
@@ -0,0 +1,83 @@
+# DBUS Role Support
+
+## DBUS-related distro packages
+
+Fedora: dbus-devel
+Debian / Ubuntu: libdbus-1-dev
+
+## Enabling for build at cmake
+
+Fedora example:
+```
+$ cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
+```
+
+Ubuntu example:
+```
+$ cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
+```
+
+Dbus requires two include paths, which you can force by setting `LWS_DBUS_INCLUDE1`
+and `LWS_DBUS_INCLUDE2`.  Although INCLUDE1 is usually guessable, both can be
+forced to allow cross-build.
+
+If these are not forced, then lws cmake will try to check some popular places,
+for `LWS_DBUS_INCLUDE1`, on both Fedora and Debian / Ubuntu, this is
+`/usr/include/dbus-1.0`... if the directory exists, it is used.
+
+For `LWS_DBUS_INCLUDE2`, it is the arch-specific dbus header which may be
+packaged separately than the main dbus headers.  On Fedora, this is in
+`/usr/lib[64]/dbus-1.0/include`... if not given externally, lws cmake will
+try `/usr/lib64/dbus-1.0/include`.  On Debian / Ubuntu, the package installs
+it in an arch-specific dir like `/usr/lib/x86_64-linux-gnu/dbus-1.0/include`,
+you should force the path.
+
+The library path is usually \[lib\] "dbus-1", but this can also be forced if
+you want to build cross or use a special build, via `LWS_DBUS_LIB`.
+
+## Building against local dbus build
+
+If you built your own local dbus and installed it in /usr/local, then
+this is the incantation to direct lws to use the local version of dbus:
+
+```
+cmake .. -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE1="/usr/local/include/dbus-1.0" -DLWS_DBUS_INCLUDE2="/usr/local/lib/dbus-1.0/include" -DLWS_DBUS_LIB="/usr/local/lib/libdbus-1.so"
+```
+
+You'll also need to give the loader a helping hand to do what you want if
+there's a perfectly good dbus lib already in `/usr/lib[64]` using `LD_PRELOAD`
+like this
+
+```
+LD_PRELOAD=/usr/local/lib/libdbus-1.so.3.24.0 myapp
+```
+
+## Lws dbus api exports
+
+Because of the irregular situation with libdbus includes, if lws exports the
+dbus helpers, which use dbus types, as usual from `#include <libwebsockets.h>`
+then if lws was compiled with dbus role support it forces all users to take
+care about the dbus include path mess whether they use dbus themselves or not.
+
+For that reason, if you need access to the lws dbus apis, you must explicitly
+include them by
+
+```
+#include <libwebsockets/lws-dbus.h>
+```
+
+This includes `<dbus/dbus.h>` and so requires the include paths set up.  But
+otherwise non-dbus users that don't include `libwebsockets/lws-dbus.h` don't
+have to care about it.
+
+## DBUS and valgrind
+
+https://cgit.freedesktop.org/dbus/dbus/tree/README.valgrind
+
+1) One-time 6KiB "Still reachable" caused by abstract unix domain socket + libc
+`getgrouplist()` via nss... bug since 2004(!)
+
+https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=273051
+
+
+
diff --git a/lib/roles/dbus/dbus.c b/lib/roles/dbus/dbus.c
new file mode 100644 (file)
index 0000000..0b82125
--- /dev/null
@@ -0,0 +1,530 @@
+/*
+ * libwebsockets - dbus role
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ * This role for wrapping dbus fds in a wsi + role is unusual in that the
+ * wsi it creates and binds to the role do not have control over the related fd
+ * lifecycle.  In fact dbus doesn't inform us directly about the lifecycle of
+ * the fds it wants to be managed by the lws event loop.
+ *
+ * What it does tell us is when it wants to wait on POLLOUT and / or POLLIN,
+ * and since it should stop any watchers before close, we take the approach to
+ * create a lightweight "shadow" wsi for any fd from dbus that has a POLLIN or
+ * POLLOUT wait active.  When the dbus fd asks to have no wait active, we
+ * destroy the wsi, since this is indistinguishable from dbus close path
+ * behaviour.  If it actually stays alive and later asks to wait again, well no
+ * worries we create a new shadow wsi until it looks like it is closing again.
+ */
+
+#include <core/private.h>
+
+#include <libwebsockets/lws-dbus.h>
+
+/*
+ * retreives existing or creates new shadow wsi for fd owned by dbus stuff.
+ *
+ * Requires vhost lock
+ */
+
+static struct lws *
+__lws_shadow_wsi(struct lws_dbus_ctx *ctx, DBusWatch *w, int fd, int create_ok)
+{
+       struct lws *wsi;
+
+       if (fd < 0 || fd >= (int)ctx->vh->context->fd_limit_per_thread) {
+               lwsl_err("%s: fd %d vs fds_count %d\n", __func__, fd,
+                               (int)ctx->vh->context->fd_limit_per_thread);
+               assert(0);
+
+               return NULL;
+       }
+
+       wsi = wsi_from_fd(ctx->vh->context, fd);
+       if (wsi) {
+               assert(wsi->opaque_parent_data == ctx);
+
+               return wsi;
+       }
+
+       if (!create_ok)
+               return NULL;
+
+       wsi = lws_zalloc(sizeof(*wsi), "shadow wsi");
+       if (wsi == NULL) {
+               lwsl_err("Out of mem\n");
+               return NULL;
+       }
+
+       lwsl_info("%s: creating shadow wsi\n", __func__);
+
+       wsi->context = ctx->vh->context;
+       wsi->desc.sockfd = fd;
+       lws_role_transition(wsi, 0, LRS_ESTABLISHED, &role_ops_dbus);
+       wsi->protocol = ctx->vh->protocols;
+       wsi->tsi = ctx->tsi;
+       wsi->shadow = 1;
+       wsi->opaque_parent_data = ctx;
+       ctx->w[0] = w;
+
+       lws_vhost_bind_wsi(ctx->vh, wsi);
+       if (__insert_wsi_socket_into_fds(ctx->vh->context, wsi)) {
+               lwsl_err("inserting wsi socket into fds failed\n");
+               lws_vhost_unbind_wsi(wsi);
+               lws_free(wsi);
+               return NULL;
+       }
+
+       ctx->vh->context->count_wsi_allocated++;
+
+       return wsi;
+}
+
+/*
+ * Requires vhost lock
+ */
+
+static int
+__lws_shadow_wsi_destroy(struct lws_dbus_ctx *ctx, struct lws *wsi)
+{
+       lwsl_info("%s: destroying shadow wsi\n", __func__);
+
+       if (__remove_wsi_socket_from_fds(wsi)) {
+               lwsl_err("%s: unable to remove %d from fds\n", __func__,
+                               wsi->desc.sockfd);
+
+               return 1;
+       }
+
+       ctx->vh->context->count_wsi_allocated--;
+       lws_vhost_unbind_wsi(wsi);
+
+       lws_free(wsi);
+
+       return 0;
+}
+
+
+static void
+handle_dispatch_status(DBusConnection *c, DBusDispatchStatus s, void *data)
+{
+       lwsl_info("%s: new dbus dispatch status: %d\n", __func__, s);
+}
+
+/*
+ * These are complicated by the fact libdbus can have two separate DBusWatch
+ * objects for the same fd, to control watching POLLIN and POLLOUT individually.
+ *
+ * However we will actually watch using poll(), where the unit is the fd, and
+ * it has a unified events field with just POLLIN / POLLOUT flags.
+ *
+ * So we have to be prepared for one or two watchers coming in any order.
+ */
+
+static dbus_bool_t
+lws_dbus_add_watch(DBusWatch *w, void *data)
+{
+       struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data;
+       struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi];
+       unsigned int flags = 0, lws_flags = 0;
+       struct lws *wsi;
+       int n;
+
+       lws_pt_lock(pt, __func__);
+
+       wsi = __lws_shadow_wsi(ctx, w, dbus_watch_get_unix_fd(w), 1);
+       if (!wsi) {
+               lws_pt_unlock(pt);
+               lwsl_err("%s: unable to get wsi\n", __func__);
+
+               return FALSE;
+       }
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               if (w == ctx->w[n])
+                       break;
+
+       if (n == (int)LWS_ARRAY_SIZE(ctx->w))
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+                       if (!ctx->w[n]) {
+                               ctx->w[n] = w;
+                               break;
+                       }
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               if (ctx->w[n])
+                       flags |= dbus_watch_get_flags(ctx->w[n]);
+
+       if (flags & DBUS_WATCH_READABLE)
+               lws_flags |= LWS_POLLIN;
+       if (flags & DBUS_WATCH_WRITABLE)
+               lws_flags |= LWS_POLLOUT;
+
+       lwsl_info("%s: w %p, fd %d, data %p, flags %d\n", __func__, w,
+                 dbus_watch_get_unix_fd(w), data, lws_flags);
+
+       __lws_change_pollfd(wsi, 0, lws_flags);
+
+       lws_pt_unlock(pt);
+
+       return TRUE;
+}
+
+static int
+check_destroy_shadow_wsi(struct lws_dbus_ctx *ctx, struct lws *wsi)
+{
+       int n;
+
+       if (!wsi)
+               return 0;
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               if (ctx->w[n])
+                       return 0;
+
+       __lws_shadow_wsi_destroy(ctx, wsi);
+
+       if (!ctx->conn || !ctx->hup || ctx->timeouts)
+               return 0;
+
+       if (dbus_connection_get_dispatch_status(ctx->conn) ==
+                                                    DBUS_DISPATCH_DATA_REMAINS)
+               return 0;
+
+       if (ctx->cb_closing)
+               ctx->cb_closing(ctx);
+
+       return 1;
+}
+
+static void
+lws_dbus_remove_watch(DBusWatch *w, void *data)
+{
+       struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data;
+       struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi];
+       unsigned int flags = 0, lws_flags = 0;
+       struct lws *wsi;
+       int n;
+
+       lws_pt_lock(pt, __func__);
+
+       wsi = __lws_shadow_wsi(ctx, w, dbus_watch_get_unix_fd(w), 0);
+       if (!wsi)
+               goto bail;
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               if (w == ctx->w[n]) {
+                       ctx->w[n] = NULL;
+                       break;
+               }
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               if (ctx->w[n])
+                       flags |= dbus_watch_get_flags(ctx->w[n]);
+
+       if ((~flags) & DBUS_WATCH_READABLE)
+               lws_flags |= LWS_POLLIN;
+       if ((~flags) & DBUS_WATCH_WRITABLE)
+               lws_flags |= LWS_POLLOUT;
+
+       lwsl_info("%s: w %p, fd %d, data %p, clearing lws flags %d\n",
+                 __func__, w, dbus_watch_get_unix_fd(w), data, lws_flags);
+
+       __lws_change_pollfd(wsi, lws_flags, 0);
+
+bail:
+       lws_pt_unlock(pt);
+}
+
+static void
+lws_dbus_toggle_watch(DBusWatch *w, void *data)
+{
+       if (dbus_watch_get_enabled(w))
+               lws_dbus_add_watch(w, data);
+       else
+               lws_dbus_remove_watch(w, data);
+}
+
+
+static dbus_bool_t
+lws_dbus_add_timeout(DBusTimeout *t, void *data)
+{
+       struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data;
+       struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi];
+       int ms = dbus_timeout_get_interval(t);
+       struct lws_role_dbus_timer *dbt;
+       time_t ti = time(NULL);
+
+       if (!dbus_timeout_get_enabled(t))
+               return TRUE;
+
+       if (ms < 1000)
+               ms = 1000;
+
+       dbt = lws_malloc(sizeof(*dbt), "dbus timer");
+       if (!dbt)
+               return FALSE;
+
+       lwsl_info("%s: adding timeout %dms\n", __func__,
+                       dbus_timeout_get_interval(t));
+
+       dbt->data = t;
+       dbt->fire = ti + (ms < 1000);
+       dbt->timer_list.prev = NULL;
+       dbt->timer_list.next = NULL;
+       dbt->timer_list.owner = NULL;
+       lws_dll2_add_head(&dbt->timer_list, &pt->dbus.timer_list_owner);
+
+       ctx->timeouts++;
+
+       return TRUE;
+}
+
+static void
+lws_dbus_remove_timeout(DBusTimeout *t, void *data)
+{
+       struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data;
+       struct lws_context_per_thread *pt = &ctx->vh->context->pt[ctx->tsi];
+
+       lwsl_info("%s: t %p, data %p\n", __func__, t, data);
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
+                               lws_dll2_get_head(&pt->dbus.timer_list_owner)) {
+               struct lws_role_dbus_timer *r = lws_container_of(rdt,
+                                       struct lws_role_dbus_timer, timer_list);
+               if (t == r->data) {
+                       lws_dll2_remove(rdt);
+                       lws_free(rdt);
+                       ctx->timeouts--;
+                       break;
+               }
+       } lws_end_foreach_dll_safe(rdt, nx);
+}
+
+static void
+lws_dbus_toggle_timeout(DBusTimeout *t, void *data)
+{
+       if (dbus_timeout_get_enabled(t))
+               lws_dbus_add_timeout(t, data);
+       else
+               lws_dbus_remove_timeout(t, data);
+}
+
+/*
+ * This sets up a connection along the same lines as
+ * dbus_connection_setup_with_g_main(), but for using the lws event loop.
+ */
+
+int
+lws_dbus_connection_setup(struct lws_dbus_ctx *ctx, DBusConnection *conn,
+                         lws_dbus_closing_t cb_closing)
+{
+       int n;
+
+       ctx->conn = conn;
+       ctx->cb_closing = cb_closing;
+       ctx->hup = 0;
+       ctx->timeouts = 0;
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               ctx->w[n] = NULL;
+
+       if (!dbus_connection_set_watch_functions(conn, lws_dbus_add_watch,
+                                                lws_dbus_remove_watch,
+                                                lws_dbus_toggle_watch,
+                                                ctx, NULL)) {
+               lwsl_err("%s: dbus_connection_set_watch_functions fail\n",
+                        __func__);
+               return 1;
+       }
+
+       if (!dbus_connection_set_timeout_functions(conn,
+                                                  lws_dbus_add_timeout,
+                                                  lws_dbus_remove_timeout,
+                                                  lws_dbus_toggle_timeout,
+                                                  ctx, NULL)) {
+               lwsl_err("%s: dbus_connection_set_timeout_functions fail\n",
+                        __func__);
+               return 1;
+       }
+
+       dbus_connection_set_dispatch_status_function(conn,
+                                                    handle_dispatch_status,
+                                                    ctx, NULL);
+
+       return 0;
+}
+
+/*
+ * This wraps dbus_server_listen(), additionally taking care of the event loop
+ * -related setups.
+ */
+
+DBusServer *
+lws_dbus_server_listen(struct lws_dbus_ctx *ctx, const char *ads, DBusError *e,
+                      DBusNewConnectionFunction new_conn)
+{
+       ctx->cb_closing = NULL;
+       ctx->hup = 0;
+       ctx->timeouts = 0;
+
+       ctx->dbs = dbus_server_listen(ads, e);
+       if (!ctx->dbs)
+               return NULL;
+
+       dbus_server_set_new_connection_function(ctx->dbs, new_conn, ctx, NULL);
+
+       if (!dbus_server_set_watch_functions(ctx->dbs, lws_dbus_add_watch,
+                                            lws_dbus_remove_watch,
+                                            lws_dbus_toggle_watch,
+                                            ctx, NULL)) {
+               lwsl_err("%s: dbus_connection_set_watch_functions fail\n",
+                        __func__);
+               goto bail;
+       }
+
+       if (!dbus_server_set_timeout_functions(ctx->dbs, lws_dbus_add_timeout,
+                                              lws_dbus_remove_timeout,
+                                              lws_dbus_toggle_timeout,
+                                              ctx, NULL)) {
+               lwsl_err("%s: dbus_connection_set_timeout_functions fail\n",
+                        __func__);
+               goto bail;
+       }
+
+       return ctx->dbs;
+
+bail:
+       dbus_server_disconnect(ctx->dbs);
+       dbus_server_unref(ctx->dbs);
+
+       return NULL;
+}
+
+
+/*
+ * There shouldn't be a race here with watcher removal and poll wait, because
+ * everything including the dbus activity is serialized in one event loop.
+ *
+ * If it removes the watcher and we remove the wsi and fd entry before this,
+ * actually we can no longer map the fd to this invalidated wsi pointer to call
+ * this.
+ */
+
+static int
+rops_handle_POLLIN_dbus(struct lws_context_per_thread *pt, struct lws *wsi,
+                       struct lws_pollfd *pollfd)
+{
+       struct lws_dbus_ctx *ctx =
+                       (struct lws_dbus_ctx *)wsi->opaque_parent_data;
+       unsigned int flags = 0;
+       int n;
+
+       if (pollfd->revents & LWS_POLLIN)
+               flags |= DBUS_WATCH_READABLE;
+       if (pollfd->revents & LWS_POLLOUT)
+               flags |= DBUS_WATCH_WRITABLE;
+
+       if (pollfd->revents & (LWS_POLLHUP))
+               ctx->hup = 1;
+
+       /*
+        * POLLIN + POLLOUT gets us called here on the corresponding shadow
+        * wsi.  wsi->opaque_parent_data is the watcher handle bound to the wsi
+        */
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(ctx->w); n++)
+               if (ctx->w[n] && !dbus_watch_handle(ctx->w[n], flags))
+                       lwsl_err("%s: dbus_watch_handle failed\n", __func__);
+
+       if (ctx->conn) {
+               lwsl_info("%s: conn: flags %d\n", __func__, flags);
+
+               while (dbus_connection_get_dispatch_status(ctx->conn) ==
+                                               DBUS_DISPATCH_DATA_REMAINS)
+                       dbus_connection_dispatch(ctx->conn);
+
+               handle_dispatch_status(NULL, DBUS_DISPATCH_DATA_REMAINS, NULL);
+
+               check_destroy_shadow_wsi(ctx, wsi);
+       } else
+               if (ctx->dbs)
+                       /* ??? */
+                       lwsl_debug("%s: dbs: %d\n", __func__, flags);
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+static int
+rops_periodic_checks_dbus(struct lws_context *context, int tsi, time_t now)
+{
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+
+       /*
+        * locking shouldn't be needed here, because periodic_checks is called
+        * from the tsi-specific service thread context, and only the same
+        * service thread can modify stuff on the same pt.
+        */
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
+                        lws_dll2_get_head(&pt->dbus.timer_list_owner)) {
+               struct lws_role_dbus_timer *r = lws_container_of(rdt,
+                                       struct lws_role_dbus_timer, timer_list);
+
+               if (now > r->fire) {
+                       lwsl_notice("%s: firing timer\n", __func__);
+                       dbus_timeout_handle(r->data);
+                       lws_dll2_remove(rdt);
+                       lws_free(rdt);
+               }
+       } lws_end_foreach_dll_safe(rdt, nx);
+
+       return 0;
+}
+
+struct lws_role_ops role_ops_dbus = {
+       /* role name */                 "dbus",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           rops_periodic_checks_dbus,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_dbus,
+       /* handle_POLLOUT */            NULL,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              NULL,
+       /* adoption_bind */             NULL,
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { 0, 0 },
+       /* rx_cb clnt, srv */           { 0, 0 },
+       /* writeable cb clnt, srv */    { 0, 0 },
+       /* close cb clnt, srv */        { 0, 0 },
+       /* protocol_bind_cb c,s */      { 0, 0 },
+       /* protocol_unbind_cb c,s */    { 0, 0 },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/dbus/private.h b/lib/roles/dbus/private.h
new file mode 100644 (file)
index 0000000..5d7a9ac
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_DBUS
+ */
+
+#include <dbus/dbus.h>
+
+extern struct lws_role_ops role_ops_dbus;
+
+#define lwsi_role_dbus(wsi) (wsi->role_ops == &role_ops_dbus)
+
+struct lws_role_dbus_timer {
+       struct lws_dll2 timer_list;
+       void *data;
+       time_t fire;
+};
+
+struct lws_pt_role_dbus {
+       struct lws_dll2_owner timer_list_owner;
+};
+
+struct _lws_dbus_mode_related {
+       DBusConnection *conn;
+};
diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c
new file mode 100644 (file)
index 0000000..87228e8
--- /dev/null
@@ -0,0 +1,1170 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+#ifndef min
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+
+/*
+ * We have to take care about parsing because the headers may be split
+ * into multiple fragments.  They may contain unknown headers with arbitrary
+ * argument lengths.  So, we parse using a single-character at a time state
+ * machine that is completely independent of packet size.
+ *
+ * Returns <0 for error or length of chars consumed from buf (up to len)
+ */
+
+int
+lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
+{
+       unsigned char *last_char, *oldbuf = buf;
+       lws_filepos_t body_chunk_len;
+       size_t n;
+
+       lwsl_debug("%s: h1 path: wsi state 0x%x\n", __func__, lwsi_state(wsi));
+
+       switch (lwsi_state(wsi)) {
+
+       case LRS_ISSUING_FILE:
+               return 0;
+
+       case LRS_ESTABLISHED:
+
+               if (lwsi_role_ws(wsi))
+                       goto ws_mode;
+
+               if (lwsi_role_client(wsi))
+                       break;
+
+               wsi->hdr_parsing_completed = 0;
+
+               /* fallthru */
+
+       case LRS_HEADERS:
+               if (!wsi->http.ah) {
+                       lwsl_err("%s: LRS_HEADERS: NULL ah\n", __func__);
+                       assert(0);
+               }
+               lwsl_parser("issuing %d bytes to parser\n", (int)len);
+#if defined(LWS_ROLE_WS) && !defined(LWS_NO_CLIENT)
+               if (lws_ws_handshake_client(wsi, &buf, (size_t)len))
+                       goto bail;
+#endif
+               last_char = buf;
+               if (lws_handshake_server(wsi, &buf, (size_t)len))
+                       /* Handshake indicates this session is done. */
+                       goto bail;
+
+               /* we might have transitioned to RAW */
+               if (wsi->role_ops == &role_ops_raw_skt ||
+                   wsi->role_ops == &role_ops_raw_file)
+                        /* we gave the read buffer to RAW handler already */
+                       goto read_ok;
+
+               /*
+                * It's possible that we've exhausted our data already, or
+                * rx flow control has stopped us dealing with this early,
+                * but lws_handshake_server doesn't update len for us.
+                * Figure out how much was read, so that we can proceed
+                * appropriately:
+                */
+               len -= (buf - last_char);
+
+               if (!wsi->hdr_parsing_completed)
+                       /* More header content on the way */
+                       goto read_ok;
+
+               switch (lwsi_state(wsi)) {
+                       case LRS_ESTABLISHED:
+                       case LRS_HEADERS:
+                               goto read_ok;
+                       case LRS_ISSUING_FILE:
+                               goto read_ok;
+                       case LRS_DISCARD_BODY:
+                       case LRS_BODY:
+                               wsi->http.rx_content_remain =
+                                               wsi->http.rx_content_length;
+                               if (wsi->http.rx_content_remain)
+                                       goto http_postbody;
+
+                               /* there is no POST content */
+                               goto postbody_completion;
+                       default:
+                               break;
+               }
+               break;
+
+       case LRS_DISCARD_BODY:
+       case LRS_BODY:
+http_postbody:
+               lwsl_debug("%s: http post body: remain %d\n", __func__,
+                           (int)wsi->http.rx_content_remain);
+
+               if (!wsi->http.rx_content_remain)
+                       goto postbody_completion;
+
+               while (len && wsi->http.rx_content_remain) {
+                       /* Copy as much as possible, up to the limit of:
+                        * what we have in the read buffer (len)
+                        * remaining portion of the POST body (content_remain)
+                        */
+                       body_chunk_len = min(wsi->http.rx_content_remain, len);
+                       wsi->http.rx_content_remain -= body_chunk_len;
+                       // len -= body_chunk_len;
+#ifdef LWS_WITH_CGI
+                       if (wsi->http.cgi) {
+                               struct lws_cgi_args args;
+
+                               args.ch = LWS_STDIN;
+                               args.stdwsi = &wsi->http.cgi->stdwsi[0];
+                               args.data = buf;
+                               args.len = body_chunk_len;
+
+                               /* returns how much used */
+                               n = user_callback_handle_rxflow(
+                                       wsi->protocol->callback,
+                                       wsi, LWS_CALLBACK_CGI_STDIN_DATA,
+                                       wsi->user_space,
+                                       (void *)&args, 0);
+                               if ((int)n < 0)
+                                       goto bail;
+                       } else {
+#endif
+                               if (lwsi_state(wsi) != LRS_DISCARD_BODY) {
+                               n = wsi->protocol->callback(wsi,
+                                       LWS_CALLBACK_HTTP_BODY, wsi->user_space,
+                                       buf, (size_t)body_chunk_len);
+                               if (n)
+                                       goto bail;
+                               }
+                               n = (size_t)body_chunk_len;
+#ifdef LWS_WITH_CGI
+                       }
+#endif
+                       buf += n;
+
+                       if (wsi->http.rx_content_remain)  {
+                               lws_set_timeout(wsi,
+                                               PENDING_TIMEOUT_HTTP_CONTENT,
+                                               wsi->context->timeout_secs);
+                               break;
+                       }
+                       /* he sent all the content in time */
+postbody_completion:
+#ifdef LWS_WITH_CGI
+                       /*
+                        * If we're running a cgi, we can't let him off the
+                        * hook just because he sent his POST data
+                        */
+                       if (wsi->http.cgi)
+                               lws_set_timeout(wsi, PENDING_TIMEOUT_CGI,
+                                               wsi->context->timeout_secs);
+                       else
+#endif
+                       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+#ifdef LWS_WITH_CGI
+                       if (!wsi->http.cgi)
+#endif
+                       {
+#if !defined(LWS_NO_SERVER)
+                               if (lwsi_state(wsi) == LRS_DISCARD_BODY) {
+                                       /*
+                                        * repeat the transaction completed
+                                        * that got us into this state, having
+                                        * consumed the pending body now
+                                        */
+
+                                       if (lws_http_transaction_completed(wsi))
+                                               return -1;
+                                       break;
+                               }
+#endif
+                               lwsl_info("HTTP_BODY_COMPLETION: %p (%s)\n",
+                                         wsi, wsi->protocol->name);
+
+                               n = wsi->protocol->callback(wsi,
+                                       LWS_CALLBACK_HTTP_BODY_COMPLETION,
+                                       wsi->user_space, NULL, 0);
+                               if (n)
+                                       goto bail;
+
+                               if (wsi->http2_substream)
+                                       lwsi_set_state(wsi, LRS_ESTABLISHED);
+                       }
+
+                       break;
+               }
+               break;
+
+       case LRS_RETURNED_CLOSE:
+       case LRS_AWAITING_CLOSE_ACK:
+       case LRS_WAITING_TO_SEND_CLOSE:
+       case LRS_SHUTDOWN:
+
+ws_mode:
+#if !defined(LWS_NO_CLIENT) && defined(LWS_ROLE_WS)
+               // lwsl_notice("%s: ws_mode\n", __func__);
+               if (lws_ws_handshake_client(wsi, &buf, (size_t)len))
+                       goto bail;
+#endif
+#if defined(LWS_ROLE_WS)
+               if (lwsi_role_ws(wsi) && lwsi_role_server(wsi) &&
+                       /*
+                        * for h2 we are on the swsi
+                        */
+                   lws_parse_ws(wsi, &buf, (size_t)len) < 0) {
+                       lwsl_info("%s: lws_parse_ws bailed\n", __func__);
+                       goto bail;
+               }
+#endif
+               // lwsl_notice("%s: ws_mode: buf moved on by %d\n", __func__,
+               //             lws_ptr_diff(buf, oldbuf));
+               break;
+
+       case LRS_DEFERRING_ACTION:
+               lwsl_notice("%s: LRS_DEFERRING_ACTION\n", __func__);
+               break;
+
+       case LRS_SSL_ACK_PENDING:
+               break;
+
+       case LRS_DEAD_SOCKET:
+               lwsl_err("%s: Unhandled state LRS_DEAD_SOCKET\n", __func__);
+               goto bail;
+               // assert(0);
+               /* fallthru */
+
+       default:
+               lwsl_err("%s: Unhandled state %d\n", __func__, lwsi_state(wsi));
+               assert(0);
+               goto bail;
+       }
+
+read_ok:
+       /* Nothing more to do for now */
+//     lwsl_info("%s: %p: read_ok, used %ld (len %d, state %d)\n", __func__,
+//               wsi, (long)(buf - oldbuf), (int)len, wsi->state);
+
+       return lws_ptr_diff(buf, oldbuf);
+
+bail:
+       /*
+        * h2 / h2-ws calls us recursively in
+        *
+        * lws_read_h1()->
+        *   lws_h2_parser()->
+        *     lws_read_h1()
+        *
+        * pattern, having stripped the h2 framing in the middle.
+        *
+        * When taking down the whole connection, make sure that only the
+        * outer lws_read() does the wsi close.
+        */
+       if (!wsi->outer_will_close)
+               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                  "lws_read_h1 bail");
+
+       return -1;
+}
+#if !defined(LWS_NO_SERVER)
+static int
+lws_h1_server_socket_service(struct lws *wsi, struct lws_pollfd *pollfd)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws_tokens ebuf;
+       int n, buffered;
+
+       if (lwsi_state(wsi) == LRS_DEFERRING_ACTION)
+               goto try_pollout;
+
+       /* any incoming data ready? */
+
+       if (!(pollfd->revents & pollfd->events & LWS_POLLIN))
+               goto try_pollout;
+
+       /*
+        * If we previously just did POLLIN when IN and OUT were signaled
+        * (because POLLIN processing may have used up the POLLOUT), don't let
+        * that happen twice in a row... next time we see the situation favour
+        * POLLOUT
+        */
+
+       if (wsi->favoured_pollin &&
+           (pollfd->revents & pollfd->events & LWS_POLLOUT)) {
+               // lwsl_notice("favouring pollout\n");
+               wsi->favoured_pollin = 0;
+               goto try_pollout;
+       }
+
+       /*
+        * We haven't processed that the tunnel is set up yet, so
+        * defer reading
+        */
+
+       if (lwsi_state(wsi) == LRS_SSL_ACK_PENDING)
+               return LWS_HPI_RET_HANDLED;
+
+       /* these states imply we MUST have an ah attached */
+
+       if ((lwsi_state(wsi) == LRS_ESTABLISHED ||
+            lwsi_state(wsi) == LRS_ISSUING_FILE ||
+            lwsi_state(wsi) == LRS_HEADERS ||
+            lwsi_state(wsi) == LRS_DISCARD_BODY ||
+            lwsi_state(wsi) == LRS_BODY)) {
+
+               if (!wsi->http.ah && lws_header_table_attach(wsi, 0)) {
+                       lwsl_info("%s: wsi %p: ah not available\n", __func__,
+                                 wsi);
+                       goto try_pollout;
+               }
+
+               /*
+                * We got here because there was specifically POLLIN...
+                * regardless of our buflist state, we need to get it,
+                * and either use it, or append to the buflist and use
+                * buflist head material.
+                *
+                * We will not notice a connection close until the buflist is
+                * exhausted and we tried to do a read of some kind.
+                */
+
+               buffered = lws_buflist_aware_read(pt, wsi, &ebuf);
+               switch (ebuf.len) {
+               case 0:
+                       lwsl_info("%s: read 0 len a\n", __func__);
+                       wsi->seen_zero_length_recv = 1;
+                       if (lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                               goto fail;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       /*
+                        * autobahn requires us to win the race between close
+                        * and draining the extensions
+                        */
+                       if (wsi->ws &&
+                           (wsi->ws->rx_draining_ext ||
+                            wsi->ws->tx_draining_ext))
+                               goto try_pollout;
+#endif
+                       /*
+                        * normally, we respond to close with logically closing
+                        * our side immediately
+                        */
+                       goto fail;
+
+               case LWS_SSL_CAPABLE_ERROR:
+                       goto fail;
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       goto try_pollout;
+               }
+
+               /* just ignore incoming if waiting for close */
+               if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) {
+                       lwsl_notice("%s: just ignoring\n", __func__);
+                       goto try_pollout;
+               }
+
+               if (lwsi_state(wsi) == LRS_ISSUING_FILE) {
+                       // lwsl_notice("stashing: wsi %p: bd %d\n", wsi, buffered);
+                       if (lws_buflist_aware_consume(wsi, &ebuf, 0, buffered))
+                               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+                       goto try_pollout;
+               }
+
+               /*
+                * Otherwise give it to whoever wants it according to the
+                * connection state
+                */
+#if defined(LWS_ROLE_H2)
+               if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY)
+                       n = lws_read_h2(wsi, ebuf.token, ebuf.len);
+               else
+#endif
+                       n = lws_read_h1(wsi, ebuf.token, ebuf.len);
+               if (n < 0) /* we closed wsi */
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+
+               lwsl_debug("%s: consumed %d\n", __func__, n);
+
+               if (lws_buflist_aware_consume(wsi, &ebuf, n, buffered))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               /*
+                * during the parsing our role changed to something non-http,
+                * so the ah has no further meaning
+                */
+
+               if (wsi->http.ah &&
+                   !lwsi_role_h1(wsi) &&
+                   !lwsi_role_h2(wsi) &&
+                   !lwsi_role_cgi(wsi))
+                       lws_header_table_detach(wsi, 0);
+
+               /*
+                * He may have used up the writability above, if we will defer
+                * POLLOUT processing in favour of POLLIN, note it
+                */
+
+               if (pollfd->revents & LWS_POLLOUT)
+                       wsi->favoured_pollin = 1;
+
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       /*
+        * He may have used up the writability above, if we will defer POLLOUT
+        * processing in favour of POLLIN, note it
+        */
+
+       if (pollfd->revents & LWS_POLLOUT)
+               wsi->favoured_pollin = 1;
+
+try_pollout:
+
+       /* this handles POLLOUT for http serving fragments */
+
+       if (!(pollfd->revents & LWS_POLLOUT))
+               return LWS_HPI_RET_HANDLED;
+
+       /* one shot */
+       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+               lwsl_notice("%s a\n", __func__);
+               goto fail;
+       }
+
+       /* clear back-to-back write detection */
+       wsi->could_have_pending = 0;
+
+       if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) {
+               lwsl_debug("%s: LRS_DEFERRING_ACTION now writable\n", __func__);
+
+               lwsi_set_state(wsi, LRS_ESTABLISHED);
+               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+                       lwsl_info("failed at set pollfd\n");
+                       goto fail;
+               }
+       }
+
+       if (!wsi->hdr_parsing_completed)
+               return LWS_HPI_RET_HANDLED;
+
+       if (lwsi_state(wsi) != LRS_ISSUING_FILE) {
+
+               if (lws_has_buffered_out(wsi)) {
+                       //lwsl_notice("%s: completing partial\n", __func__);
+                       if (lws_issue_raw(wsi, NULL, 0) < 0) {
+                               lwsl_info("%s signalling to close\n", __func__);
+                               goto fail;
+                       }
+                       return LWS_HPI_RET_HANDLED;
+               }
+
+               lws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB, 1);
+#if defined(LWS_WITH_STATS)
+               if (wsi->active_writable_req_us) {
+                       uint64_t ul = lws_now_usecs() -
+                                       wsi->active_writable_req_us;
+
+                       lws_stats_bump(pt, LWSSTATS_US_WRITABLE_DELAY_AVG, ul);
+                       lws_stats_max(pt,
+                                 LWSSTATS_US_WORST_WRITABLE_DELAY, ul);
+                       wsi->active_writable_req_us = 0;
+               }
+#endif
+
+               n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                               LWS_CALLBACK_HTTP_WRITEABLE,
+                                               wsi->user_space, NULL, 0);
+               if (n < 0) {
+                       lwsl_info("writeable_fail\n");
+                       goto fail;
+               }
+
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       /* >0 == completion, <0 == error
+        *
+        * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
+        * it's done.  That's the case even if we just completed the
+        * send, so wait for that.
+        */
+       n = lws_serve_http_file_fragment(wsi);
+       if (n < 0)
+               goto fail;
+
+       return LWS_HPI_RET_HANDLED;
+
+
+fail:
+       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                          "server socket svc fail");
+
+       return LWS_HPI_RET_WSI_ALREADY_DIED;
+}
+#endif
+
+static int
+rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi,
+                      struct lws_pollfd *pollfd)
+{
+       if (lwsi_state(wsi) == LRS_IDLING) {
+               uint8_t buf[1];
+               int rlen;
+
+               /*
+                * h1 staggered spins here in IDLING if we don't close it.
+                * It shows POLLIN but the tls connection returns ERROR if
+                * you try to read it.
+                */
+
+               // lwsl_notice("%s: %p: wsistate 0x%x %s, revents 0x%x\n",
+               //          __func__, wsi, wsi->wsistate, wsi->role_ops->name,
+               //          pollfd->revents);
+
+               rlen = lws_ssl_capable_read(wsi, buf, sizeof(buf));
+               if (rlen == LWS_SSL_CAPABLE_ERROR)
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+#ifdef LWS_WITH_CGI
+       if (wsi->http.cgi && (pollfd->revents & LWS_POLLOUT)) {
+               if (lws_handle_POLLOUT_event(wsi, pollfd))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+#if 0
+
+       /*
+        * !!! lws_serve_http_file_fragment() seems to duplicate most of
+        * lws_handle_POLLOUT_event() in its own loop...
+        */
+       lwsl_debug("%s: %d %d\n", __func__, (pollfd->revents & LWS_POLLOUT),
+                       lwsi_state_can_handle_POLLOUT(wsi));
+
+       if ((pollfd->revents & LWS_POLLOUT) &&
+           lwsi_state_can_handle_POLLOUT(wsi) &&
+           lws_handle_POLLOUT_event(wsi, pollfd)) {
+               if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+                       lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE);
+               /* the write failed... it's had it */
+               wsi->socket_is_permanently_unusable = 1;
+
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+#endif
+
+
+       /* Priority 2: pre- compression transform */
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.comp_ctx.buflist_comp ||
+           wsi->http.comp_ctx.may_have_more) {
+               enum lws_write_protocol wp = LWS_WRITE_HTTP;
+
+               lwsl_info("%s: completing comp partial (buflist_comp %p, may %d)\n",
+                               __func__, wsi->http.comp_ctx.buflist_comp,
+                               wsi->http.comp_ctx.may_have_more
+                               );
+
+               if (wsi->role_ops->write_role_protocol(wsi, NULL, 0, &wp) < 0) {
+                       lwsl_info("%s signalling to close\n", __func__);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+               lws_callback_on_writable(wsi);
+
+               if (!wsi->http.comp_ctx.buflist_comp &&
+                   !wsi->http.comp_ctx.may_have_more &&
+                   wsi->http.deferred_transaction_completed) {
+                       wsi->http.deferred_transaction_completed = 0;
+                       if (lws_http_transaction_completed(wsi))
+                               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+        if (lws_is_flowcontrolled(wsi))
+                /* We cannot deal with any kind of new RX because we are
+                 * RX-flowcontrolled.
+                 */
+               return LWS_HPI_RET_HANDLED;
+
+#if !defined(LWS_NO_SERVER)
+       if (!lwsi_role_client(wsi)) {
+               int n;
+
+               lwsl_debug("%s: %p: wsistate 0x%x\n", __func__, wsi,
+                          wsi->wsistate);
+               n = lws_h1_server_socket_service(wsi, pollfd);
+               if (n != LWS_HPI_RET_HANDLED)
+                       return n;
+               if (lwsi_state(wsi) != LRS_SSL_INIT)
+                       if (lws_server_socket_service_ssl(wsi,
+                                                         LWS_SOCK_INVALID))
+                               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+#ifndef LWS_NO_CLIENT
+       if ((pollfd->revents & LWS_POLLIN) &&
+            wsi->hdr_parsing_completed && !wsi->told_user_closed) {
+
+               /*
+                * In SSL mode we get POLLIN notification about
+                * encrypted data in.
+                *
+                * But that is not necessarily related to decrypted
+                * data out becoming available; in may need to perform
+                * other in or out before that happens.
+                *
+                * simply mark ourselves as having readable data
+                * and turn off our POLLIN
+                */
+               wsi->client_rx_avail = 1;
+               if (lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               //lwsl_notice("calling back %s\n", wsi->protocol->name);
+
+               /* let user code know, he'll usually ask for writeable
+                * callback and drain / re-enable it there
+                */
+               if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                              LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
+                                               wsi->user_space, NULL, 0)) {
+                       lwsl_info("RECEIVE_CLIENT_HTTP closed it\n");
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+//     if (lwsi_state(wsi) == LRS_ESTABLISHED)
+//             return LWS_HPI_RET_HANDLED;
+
+#if !defined(LWS_NO_CLIENT)
+       if ((pollfd->revents & LWS_POLLOUT) &&
+           lws_handle_POLLOUT_event(wsi, pollfd)) {
+               lwsl_debug("POLLOUT event closed it\n");
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       if (lws_client_socket_service(wsi, pollfd, NULL))
+               return LWS_HPI_RET_WSI_ALREADY_DIED;
+#endif
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+static int
+rops_handle_POLLOUT_h1(struct lws *wsi)
+{
+
+       if (lwsi_state(wsi) == LRS_ISSUE_HTTP_BODY) {
+#if defined(LWS_WITH_HTTP_PROXY)
+               if (wsi->http.proxy_clientside) {
+                       unsigned char *buf, prebuf[LWS_PRE + 1024];
+                       size_t len = lws_buflist_next_segment_len(
+                                       &wsi->parent->http.buflist_post_body, &buf);
+                       int n;
+
+                       if (len > sizeof(prebuf) - LWS_PRE)
+                               len = sizeof(prebuf) - LWS_PRE;
+
+                       if (len) {
+
+                               memcpy(prebuf + LWS_PRE, buf, len);
+
+                               lwsl_debug("%s: %p: proxying body %d %d %d %d %d\n",
+                                               __func__, wsi, (int)len,
+                                               (int)wsi->http.tx_content_length,
+                                               (int)wsi->http.tx_content_remain,
+                                               (int)wsi->http.rx_content_length,
+                                               (int)wsi->http.rx_content_remain
+                                               );
+
+                               n = lws_write(wsi, prebuf + LWS_PRE, len, LWS_WRITE_HTTP);
+                               if (n < 0) {
+                                       lwsl_err("%s: PROXY_BODY: write %d failed\n",
+                                                __func__, (int)len);
+                                       return LWS_HP_RET_BAIL_DIE;
+                               }
+
+                               lws_buflist_use_segment(&wsi->parent->http.buflist_post_body, len);
+                       }
+
+                       if (wsi->parent->http.buflist_post_body)
+                               lws_callback_on_writable(wsi);
+                       else {
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+                               /* prepare ourselves to do the parsing */
+                               wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART;
+                               wsi->http.ah->lextable_pos = 0;
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                               wsi->http.ah->unk_pos = 0;
+#endif
+#endif
+                               lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
+                               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
+                                               wsi->context->timeout_secs);
+                       }
+               }
+#endif
+               return LWS_HP_RET_USER_SERVICE;
+       }
+
+       if (lwsi_role_client(wsi))
+               return LWS_HP_RET_USER_SERVICE;
+
+       return LWS_HP_RET_BAIL_OK;
+}
+
+static int
+rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len,
+                           enum lws_write_protocol *wp)
+{
+       size_t olen = len;
+       int n;
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.lcs && (((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL ||
+                             ((*wp) & 0x1f) == LWS_WRITE_HTTP)) {
+               unsigned char mtubuf[1400 + LWS_PRE +
+                                    LWS_HTTP_CHUNK_HDR_MAX_SIZE +
+                                    LWS_HTTP_CHUNK_TRL_MAX_SIZE],
+                             *out = mtubuf + LWS_PRE +
+                                    LWS_HTTP_CHUNK_HDR_MAX_SIZE;
+               size_t o = sizeof(mtubuf) - LWS_PRE -
+                          LWS_HTTP_CHUNK_HDR_MAX_SIZE -
+                          LWS_HTTP_CHUNK_TRL_MAX_SIZE;
+
+               n = lws_http_compression_transform(wsi, buf, len, wp, &out, &o);
+               if (n)
+                       return n;
+
+               lwsl_info("%s: %p: transformed %d bytes to %d "
+                          "(wp 0x%x, more %d)\n", __func__, wsi, (int)len,
+                          (int)o, (int)*wp, wsi->http.comp_ctx.may_have_more);
+
+               if (!o)
+                       return olen;
+
+               if (wsi->http.comp_ctx.chunking) {
+                       char c[LWS_HTTP_CHUNK_HDR_MAX_SIZE + 2];
+                       /*
+                        * this only needs dealing with on http/1.1 to allow
+                        * pipelining
+                        */
+                       n = lws_snprintf(c, sizeof(c), "%X\x0d\x0a", (int)o);
+                       lwsl_info("%s: chunk (%d) %s", __func__, (int)o, c);
+                       out -= n;
+                       o += n;
+                       memcpy(out, c, n);
+                       out[o++] = '\x0d';
+                       out[o++] = '\x0a';
+
+                       if (((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL) {
+                               lwsl_info("%s: final chunk\n", __func__);
+                               out[o++] = '0';
+                               out[o++] = '\x0d';
+                               out[o++] = '\x0a';
+                               out[o++] = '\x0d';
+                               out[o++] = '\x0a';
+                       }
+               }
+
+               buf = out;
+               len = o;
+       }
+#endif
+
+       n = lws_issue_raw(wsi, (unsigned char *)buf, len);
+       if (n < 0)
+               return n;
+
+       /* hide there may have been compression */
+
+       return (int)olen;
+}
+
+static int
+rops_alpn_negotiated_h1(struct lws *wsi, const char *alpn)
+{
+       lwsl_debug("%s: client %d\n", __func__, lwsi_role_client(wsi));
+#if !defined(LWS_NO_CLIENT)
+       if (lwsi_role_client(wsi)) {
+               /*
+                * If alpn asserts it is http/1.1, server support for KA is
+                * mandatory.
+                *
+                * Knowing this lets us proceed with sending pipelined headers
+                * before we received the first response headers.
+                */
+               wsi->keepalive_active = 1;
+       }
+#endif
+
+       return 0;
+}
+
+static int
+rops_destroy_role_h1(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct allocated_headers *ah;
+
+       /* we may not have an ah, but may be on the waiting list... */
+       lwsl_info("%s: ah det due to close\n", __func__);
+       __lws_header_table_detach(wsi, 0);
+
+        ah = pt->http.ah_list;
+
+       while (ah) {
+               if (ah->in_use && ah->wsi == wsi) {
+                       lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi);
+                       ah->in_use = 0;
+                       ah->wsi = NULL;
+                       pt->http.ah_count_in_use--;
+                       break;
+               }
+               ah = ah->next;
+       }
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       lws_http_compression_destroy(wsi);
+#endif
+
+#ifdef LWS_ROLE_WS
+       lws_free_set_NULL(wsi->ws);
+#endif
+       return 0;
+}
+
+#if !defined(LWS_NO_SERVER)
+
+static int
+rops_adoption_bind_h1(struct lws *wsi, int type, const char *vh_prot_name)
+{
+       if (!(type & LWS_ADOPT_HTTP))
+               return 0; /* no match */
+
+       if (type & _LWS_ADOPT_FINISH && !lwsi_role_http(wsi))
+               return 0;
+
+       if (type & _LWS_ADOPT_FINISH) {
+               if (!lws_header_table_attach(wsi, 0))
+                       lwsl_debug("Attached ah immediately\n");
+               else
+                       lwsl_info("%s: waiting for ah\n", __func__);
+
+               return 1;
+       }
+
+       lws_role_transition(wsi, LWSIFR_SERVER, (type & LWS_ADOPT_ALLOW_SSL) ?
+                           LRS_SSL_INIT : LRS_HEADERS, &role_ops_h1);
+
+       /*
+        * We have to bind to h1 as a default even when we're actually going to
+        * replace it as an h2 bind later.  So don't take this seriously if the
+        * default is disabled (ws upgrade caees properly about it)
+        */
+
+       if (!vh_prot_name && wsi->vhost->default_protocol_index <
+                            wsi->vhost->count_protocols)
+               wsi->protocol = &wsi->vhost->protocols[
+                               wsi->vhost->default_protocol_index];
+       else
+               wsi->protocol = &wsi->vhost->protocols[0];
+
+       /* the transport is accepted... give him time to negotiate */
+       lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
+                       wsi->context->timeout_secs);
+
+       return 1; /* bound */
+}
+
+#endif
+
+#if !defined(LWS_NO_CLIENT)
+
+static const char * const http_methods[] = {
+       "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT"
+};
+
+static int
+rops_client_bind_h1(struct lws *wsi, const struct lws_client_connect_info *i)
+{
+       int n;
+
+       if (!i) {
+               /* we are finalizing an already-selected role */
+
+               /*
+                * If we stay in http, assuming there wasn't already-set
+                * external user_space, since we know our initial protocol
+                * we can assign the user space now, otherwise do it after the
+                * ws subprotocol negotiated
+                */
+               if (!wsi->user_space && wsi->stash->method)
+                       if (lws_ensure_user_space(wsi))
+                               return 1;
+
+                /*
+                 * For ws, default to http/1.1 only.  If i->alpn had been set
+                 * though, defer to whatever he has set in there (eg, "h2").
+                 *
+                 * The problem is he has to commit to h2 before he can find
+                 * out if the server has the SETTINGS for ws-over-h2 enabled;
+                 * if not then ws is not possible on that connection.  So we
+                 * only try h2 if he assertively said to use h2 alpn, otherwise
+                 * ws implies alpn restriction to h1.
+                 */
+               if (!wsi->stash->method && !wsi->stash->alpn) {
+                       wsi->stash->alpn = lws_strdup("http/1.1");
+                       if (!wsi->stash->alpn)
+                               return 1;
+               }
+
+               /* if we went on the ah waiting list, it's ok, we can wait.
+                *
+                * When we do get the ah, now or later, he will end up at
+                * lws_http_client_connect_via_info2().
+                */
+               if (lws_header_table_attach(wsi, 0)
+#ifndef LWS_NO_CLIENT
+                               < 0)
+                       /*
+                        * if we failed here, the connection is already closed
+                        * and freed.
+                        */
+                       return -1;
+#else
+                       )
+                               return 0;
+#endif
+
+               return 0;
+       }
+
+       /*
+        * Clients that want to be h1, h2, or ws all start out as h1
+        * (we don't yet know if the server supports h2 or ws)
+        */
+
+       if (!i->method) { /* websockets */
+#if defined(LWS_ROLE_WS)
+               if (lws_create_client_ws_object(i, wsi))
+                       goto fail_wsi;
+#else
+               lwsl_err("%s: ws role not configured\n", __func__);
+
+               goto fail_wsi;
+#endif
+               goto bind_h1;
+       }
+
+       /* if a recognized http method, bind to it */
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(http_methods); n++)
+               if (!strcmp(i->method, http_methods[n]))
+                       goto bind_h1;
+
+       /* other roles may bind to it */
+
+       return 0; /* no match */
+
+bind_h1:
+       /* assert the mode and union status (hdr) clearly */
+       lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_h1);
+
+       return 1; /* matched */
+
+fail_wsi:
+       return -1;
+}
+#endif
+
+#if 0
+static int
+rops_perform_user_POLLOUT_h1(struct lws *wsi)
+{
+       volatile struct lws *vwsi = (volatile struct lws *)wsi;
+       int n;
+
+       /* priority 1: post compression-transform buffered output */
+
+       if (lws_has_buffered_out(wsi)) {
+               lwsl_debug("%s: completing partial\n", __func__);
+               if (lws_issue_raw(wsi, NULL, 0) < 0) {
+                       lwsl_info("%s signalling to close\n", __func__);
+                       return -1;
+               }
+               n = 0;
+               vwsi->leave_pollout_active = 1;
+               goto cleanup;
+       }
+
+       /* priority 2: pre compression-transform buffered output */
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.comp_ctx.buflist_comp ||
+           wsi->http.comp_ctx.may_have_more) {
+               enum lws_write_protocol wp = LWS_WRITE_HTTP;
+
+               lwsl_info("%s: completing comp partial"
+                          "(buflist_comp %p, may %d)\n",
+                          __func__, wsi->http.comp_ctx.buflist_comp,
+                           wsi->http.comp_ctx.may_have_more);
+
+               if (rops_write_role_protocol_h1(wsi, NULL, 0, &wp) < 0) {
+                       lwsl_info("%s signalling to close\n", __func__);
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "comp write fail");
+               }
+               n = 0;
+               vwsi->leave_pollout_active = 1;
+               goto cleanup;
+       }
+#endif
+
+       /* priority 3: if no buffered out and waiting for that... */
+
+       if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) {
+               wsi->socket_is_permanently_unusable = 1;
+               return -1;
+       }
+
+       /* priority 4: user writeable callback */
+
+       vwsi = (volatile struct lws *)wsi;
+       vwsi->leave_pollout_active = 0;
+
+       n = lws_callback_as_writeable(wsi);
+
+cleanup:
+       vwsi->handling_pollout = 0;
+
+       if (vwsi->leave_pollout_active)
+               lws_change_pollfd(wsi, 0, LWS_POLLOUT);
+
+       return n;
+}
+#endif
+
+static int
+rops_close_kill_connection_h1(struct lws *wsi, enum lws_close_status reason)
+{
+#if defined(LWS_WITH_HTTP_PROXY)
+       struct lws *wsi_eff = lws_client_wsi_effective(wsi);
+
+       if (!wsi_eff->http.proxy_clientside)
+               return 0;
+
+       wsi_eff->http.proxy_clientside = 0;
+
+       if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff,
+                                       LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
+                                       wsi_eff->user_space, NULL, 0))
+               return 0;
+#endif
+       return 0;
+}
+
+int
+rops_init_context_h1(struct lws_context *context,
+                    const struct lws_context_creation_info *info)
+{
+       /*
+        * We only want to do this once... we will do it if no h2 support
+        * otherwise let h2 ops do it.
+        */
+#if !defined(LWS_ROLE_H2)
+       int n;
+
+       for (n = 0; n < context->count_threads; n++) {
+               struct lws_context_per_thread *pt = &context->pt[n];
+
+               pt->sul_ah_lifecheck.cb = lws_sul_http_ah_lifecheck;
+
+               __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_ah_lifecheck,
+                                30 * LWS_US_PER_SEC);
+       }
+#endif
+
+       return 0;
+}
+
+struct lws_role_ops role_ops_h1 = {
+       /* role name */                 "h1",
+       /* alpn id */                   "http/1.1",
+       /* check_upgrades */            NULL,
+       /* init_context */              rops_init_context_h1,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_h1,
+       /* handle_POLLOUT */            rops_handle_POLLOUT_h1,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       rops_write_role_protocol_h1,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           rops_alpn_negotiated_h1,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     rops_close_kill_connection_h1,
+       /* destroy_role */              rops_destroy_role_h1,
+#if !defined(LWS_NO_SERVER)
+       /* adoption_bind */             rops_adoption_bind_h1,
+#else
+                                       NULL,
+#endif
+#if !defined(LWS_NO_CLIENT)
+       /* client_bind */               rops_client_bind_h1,
+#else
+                                       NULL,
+#endif
+       /* adoption_cb clnt, srv */     { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED,
+                                         LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED },
+       /* rx_cb clnt, srv */           { LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
+                                         0 /* may be POST, etc */ },
+       /* writeable cb clnt, srv */    { LWS_CALLBACK_CLIENT_HTTP_WRITEABLE,
+                                         LWS_CALLBACK_HTTP_WRITEABLE },
+       /* close cb clnt, srv */        { LWS_CALLBACK_CLOSED_CLIENT_HTTP,
+                                         LWS_CALLBACK_CLOSED_HTTP },
+       /* protocol_bind cb c, srv */   { LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL,
+                                         LWS_CALLBACK_HTTP_BIND_PROTOCOL },
+       /* protocol_unbind cb c, srv */ { LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL,
+                                         LWS_CALLBACK_HTTP_DROP_PROTOCOL },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/h1/private.h b/lib/roles/h1/private.h
new file mode 100644 (file)
index 0000000..3f53954
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_H1
+ *
+ *  Most of the h1 business is defined in the h1 / h2 common roles/http dir
+ */
+
+extern struct lws_role_ops role_ops_h1;
+#define lwsi_role_h1(wsi) (wsi->role_ops == &role_ops_h1)
diff --git a/lib/roles/h2/hpack.c b/lib/roles/h2/hpack.c
new file mode 100644 (file)
index 0000000..f8b89c3
--- /dev/null
@@ -0,0 +1,1416 @@
+/*
+ * lib/hpack.c
+ *
+ * Copyright (C) 2014-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * Official static header table for HPACK
+ *        +-------+-----------------------------+---------------+
+          | 1     | :authority                  |               |
+          | 2     | :method                     | GET           |
+          | 3     | :method                     | POST          |
+          | 4     | :path                       | /             |
+          | 5     | :path                       | /index.html   |
+          | 6     | :scheme                     | http          |
+          | 7     | :scheme                     | https         |
+          | 8     | :status                     | 200           |
+          | 9     | :status                     | 204           |
+          | 10    | :status                     | 206           |
+          | 11    | :status                     | 304           |
+          | 12    | :status                     | 400           |
+          | 13    | :status                     | 404           |
+          | 14    | :status                     | 500           |
+          | 15    | accept-charset              |               |
+          | 16    | accept-encoding             | gzip, deflate |
+          | 17    | accept-language             |               |
+          | 18    | accept-ranges               |               |
+          | 19    | accept                      |               |
+          | 20    | access-control-allow-origin |               |
+          | 21    | age                         |               |
+          | 22    | allow                       |               |
+          | 23    | authorization               |               |
+          | 24    | cache-control               |               |
+          | 25    | content-disposition         |               |
+          | 26    | content-encoding            |               |
+          | 27    | content-language            |               |
+          | 28    | content-length              |               |
+          | 29    | content-location            |               |
+          | 30    | content-range               |               |
+          | 31    | content-type                |               |
+          | 32    | cookie                      |               |
+          | 33    | date                        |               |
+          | 34    | etag                        |               |
+          | 35    | expect                      |               |
+          | 36    | expires                     |               |
+          | 37    | from                        |               |
+          | 38    | host                        |               |
+          | 39    | if-match                    |               |
+          | 40    | if-modified-since           |               |
+          | 41    | if-none-match               |               |
+          | 42    | if-range                    |               |
+          | 43    | if-unmodified-since         |               |
+          | 44    | last-modified               |               |
+          | 45    | link                        |               |
+          | 46    | location                    |               |
+          | 47    | max-forwards                |               |
+          | 48    | proxy-authenticate          |               |
+          | 49    | proxy-authorization         |               |
+          | 50    | range                       |               |
+          | 51    | referer                     |               |
+          | 52    | refresh                     |               |
+          | 53    | retry-after                 |               |
+          | 54    | server                      |               |
+          | 55    | set-cookie                  |               |
+          | 56    | strict-transport-security   |               |
+          | 57    | transfer-encoding           |               |
+          | 58    | user-agent                  |               |
+          | 59    | vary                        |               |
+          | 60    | via                         |               |
+          | 61    | www-authenticate            |               |
+          +-------+-----------------------------+---------------+
+*/
+
+static const uint8_t static_hdr_len[62] = {
+               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,
+       WSI_TOKEN_HTTP_COLON_METHOD,
+       WSI_TOKEN_HTTP_COLON_METHOD,
+       WSI_TOKEN_HTTP_COLON_PATH,
+       WSI_TOKEN_HTTP_COLON_PATH,
+       WSI_TOKEN_HTTP_COLON_SCHEME,
+       WSI_TOKEN_HTTP_COLON_SCHEME,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_COLON_STATUS,
+       WSI_TOKEN_HTTP_ACCEPT_CHARSET,
+       WSI_TOKEN_HTTP_ACCEPT_ENCODING,
+       WSI_TOKEN_HTTP_ACCEPT_LANGUAGE,
+       WSI_TOKEN_HTTP_ACCEPT_RANGES,
+       WSI_TOKEN_HTTP_ACCEPT,
+       WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN,
+       WSI_TOKEN_HTTP_AGE,
+       WSI_TOKEN_HTTP_ALLOW,
+       WSI_TOKEN_HTTP_AUTHORIZATION,
+       WSI_TOKEN_HTTP_CACHE_CONTROL,
+       WSI_TOKEN_HTTP_CONTENT_DISPOSITION,
+       WSI_TOKEN_HTTP_CONTENT_ENCODING,
+       WSI_TOKEN_HTTP_CONTENT_LANGUAGE,
+       WSI_TOKEN_HTTP_CONTENT_LENGTH,
+       WSI_TOKEN_HTTP_CONTENT_LOCATION,
+       WSI_TOKEN_HTTP_CONTENT_RANGE,
+       WSI_TOKEN_HTTP_CONTENT_TYPE,
+       WSI_TOKEN_HTTP_COOKIE,
+       WSI_TOKEN_HTTP_DATE,
+       WSI_TOKEN_HTTP_ETAG,
+       WSI_TOKEN_HTTP_EXPECT,
+       WSI_TOKEN_HTTP_EXPIRES,
+       WSI_TOKEN_HTTP_FROM,
+       WSI_TOKEN_HOST,
+       WSI_TOKEN_HTTP_IF_MATCH,
+       WSI_TOKEN_HTTP_IF_MODIFIED_SINCE,
+       WSI_TOKEN_HTTP_IF_NONE_MATCH,
+       WSI_TOKEN_HTTP_IF_RANGE,
+       WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE,
+       WSI_TOKEN_HTTP_LAST_MODIFIED,
+       WSI_TOKEN_HTTP_LINK,
+       WSI_TOKEN_HTTP_LOCATION,
+       WSI_TOKEN_HTTP_MAX_FORWARDS,
+       WSI_TOKEN_HTTP_PROXY_AUTHENTICATE,
+       WSI_TOKEN_HTTP_PROXY_AUTHORIZATION,
+       WSI_TOKEN_HTTP_RANGE,
+       WSI_TOKEN_HTTP_REFERER,
+       WSI_TOKEN_HTTP_REFRESH,
+       WSI_TOKEN_HTTP_RETRY_AFTER,
+       WSI_TOKEN_HTTP_SERVER,
+       WSI_TOKEN_HTTP_SET_COOKIE,
+       WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY,
+       WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+       WSI_TOKEN_HTTP_USER_AGENT,
+       WSI_TOKEN_HTTP_VARY,
+       WSI_TOKEN_HTTP_VIA,
+       WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
+};
+
+/* some of the entries imply values as well as header names */
+
+static const char * const http2_canned[] = {
+       "",
+       "",
+       "GET",
+       "POST",
+       "/",
+       "/index.html",
+       "http",
+       "https",
+       "200",
+       "204",
+       "206",
+       "304",
+       "400",
+       "404",
+       "500",
+       "",
+       "gzip, deflate"
+};
+
+/* see minihuf.c */
+
+#include "huftable.h"
+
+static int huftable_decode(int pos, char c)
+{
+       int q = pos + !!c;
+
+       if (lextable_terms[q >> 3] & (1 << (q & 7))) /* terminal */
+               return lextable[q] | 0x8000;
+
+       return pos + (lextable[q] << 1);
+}
+
+static int lws_frag_start(struct lws *wsi, int hdr_token_idx)
+{
+       struct allocated_headers *ah = wsi->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__);
+               return 1;
+       }
+
+       if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frag_index)) {
+               lwsl_err("%s: frag index %d too big\n", __func__, ah->nfrag);
+               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_COLON_PROTOCOL ||
+            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;
+
+       /*
+        * Okay, but we could be, eg, the second or subsequent cookie: header
+        */
+
+       if (ah->frag_index[hdr_token_idx]) {
+               int n;
+
+               /* find the last fragment for this header... */
+               n = ah->frag_index[hdr_token_idx];
+               while (ah->frags[n].nfrag)
+                       n = ah->frags[n].nfrag;
+               /* and point it to continue in our continuation fragment */
+               ah->frags[n].nfrag = ah->nfrag;
+
+               /* cookie continuations need a separator token of ';' */
+               if (hdr_token_idx == WSI_TOKEN_HTTP_COOKIE) {
+                       ah->data[ah->pos++] = ';';
+                       ah->frags[ah->nfrag].len++;
+               }
+       } else
+               ah->frag_index[hdr_token_idx] = ah->nfrag;
+
+       return 0;
+}
+
+static int lws_frag_append(struct lws *wsi, unsigned char c)
+{
+       struct allocated_headers *ah = wsi->http.ah;
+
+       ah->data[ah->pos++] = c;
+       ah->frags[ah->nfrag].len++;
+
+       return (int)ah->pos >= wsi->context->max_http_header_data;
+}
+
+static int lws_frag_end(struct lws *wsi)
+{
+       lwsl_header("%s\n", __func__);
+       if (lws_frag_append(wsi, 0))
+               return 1;
+
+       /* don't account for the terminating NUL in the logical length */
+       wsi->http.ah->frags[wsi->http.ah->nfrag].len--;
+
+       wsi->http.ah->nfrag++;
+       return 0;
+}
+
+int
+lws_hdr_extant(struct lws *wsi, enum lws_token_indexes h)
+{
+       struct allocated_headers *ah = wsi->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];
+       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, 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->h2.h2n)
+               return -1;
+
+       dyn = &wsi->h2.h2n->hpack_dyn_table;
+
+       if (index < 0)
+               return -1;
+
+       if (index < (int)LWS_ARRAY_SIZE(static_token)) {
+               if (arg && index < (int)LWS_ARRAY_SIZE(http2_canned)) {
+                       *arg = http2_canned[index];
+                       *len = (int)strlen(http2_canned[index]);
+               }
+               if (hdr_len)
+                       *hdr_len = static_hdr_len[index];
+
+               return static_token[index];
+       }
+
+       if (!dyn) {
+               lwsl_notice("no dynamic table\n");
+               return -1;
+       }
+
+       if (index < (int)LWS_ARRAY_SIZE(static_token) ||
+           index >= (int)LWS_ARRAY_SIZE(static_token) + dyn->used_entries) {
+               lwsl_info("  %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 -= (int)LWS_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_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->h2.h2n)
+               return 1;
+       dyn = &nwsi->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)LWS_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 + LWS_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 new_index;
+
+       /* dynamic table only belongs to network wsi */
+       wsi = lws_get_network_wsi(wsi);
+       if (!wsi->h2.h2n)
+               return 1;
+       dyn = &wsi->h2.h2n->hpack_dyn_table;
+
+       if (!dyn->entries) {
+               lwsl_err("%s: unsized dyn table\n", __func__);
+
+               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);
+       }
+
+       /*
+        * evict guys to make room, allowing for some overage.  We have to
+        * take care about getting a single huge header, and evicting
+        * everything
+        */
+
+       while (dyn->virtual_payload_usage &&
+              dyn->used_entries &&
+              dyn->virtual_payload_usage + hdr_len + len >
+                               dyn->virtual_payload_max + 1024) {
+               int n = (dyn->pos - dyn->used_entries) % dyn->num_entries;
+               if (n < 0)
+                       n += dyn->num_entries;
+               lws_dynamic_free(dyn, n);
+       }
+
+       if (dyn->used_entries < dyn->num_entries)
+               dyn->used_entries++;
+
+       dyn->entries[new_index].value_len = 0;
+
+       if (lws_hdr_index != LWS_HPACK_IGNORE_ENTRY) {
+               if (dyn->entries[new_index].value)
+                       lws_free_set_NULL(dyn->entries[new_index].value);
+               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)LWS_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->h2.h2n)
+               goto bail;
+
+       dyn = &nwsi->h2.h2n->hpack_dyn_table;
+       lwsl_info("%s: from %d to %d, lim %d\n", __func__,
+                 (int)dyn->num_entries, size,
+                 nwsi->vhost->h2.set.s[H2SET_HEADER_TABLE_SIZE]);
+
+       if (!size) {
+               size = dyn->num_entries * 8;
+               lws_hpack_destroy_dynamic_header(wsi);
+       }
+
+       if (size > (int)nwsi->vhost->h2.set.s[H2SET_HEADER_TABLE_SIZE]) {
+               lwsl_info("rejecting hpack dyn size %u vs %u\n", size,
+                               nwsi->vhost->h2.set.s[H2SET_HEADER_TABLE_SIZE]);
+
+               // this seems necessary to work with some browsers
+
+               if (nwsi->vhost->h2.set.s[H2SET_HEADER_TABLE_SIZE] == 65536 &&
+                               size == 65537) { /* h2spec */
+                       lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR,
+                                 "Asked for header table bigger than we told");
+                       goto bail;
+               }
+
+               size = nwsi->vhost->h2.set.s[H2SET_HEADER_TABLE_SIZE];
+       }
+
+       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;
+
+       // lwsl_notice("dte requested size %d\n", size);
+
+       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;
+       if (size)
+               dyn->pos = min % size;
+       else
+               dyn->pos = 0;
+
+       lws_h2_dynamic_table_dump(wsi);
+
+       return 0;
+
+bail:
+       lwsl_info("%s: failed to resize to %d\n", __func__, size);
+
+       return 1;
+}
+
+void
+lws_hpack_destroy_dynamic_header(struct lws *wsi)
+{
+       struct hpack_dynamic_table *dyn;
+       int n;
+
+       if (!wsi->h2.h2n)
+               return;
+
+       dyn = &wsi->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 < (int)LWS_ARRAY_SIZE(http2_canned))
+               p = http2_canned[idx];
+
+       if (lws_frag_start(wsi, tok))
+               return 1;
+
+       if (p)
+               while (*p && len--)
+                       if (lws_frag_append(wsi, *p++))
+                               return 1;
+
+       if (lws_frag_end(wsi))
+               return 1;
+
+       lws_dump_header(wsi, tok);
+
+       return 0;
+}
+
+static uint8_t lws_header_implies_psuedoheader_map[] = {
+       0x07, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00 /* <-64 */,
+       0x0e /* <- 72 */, 0x04 /* <- 80 */, 0, 0, 0, 0
+};
+
+static int
+lws_hpack_handle_pseudo_rules(struct lws *nwsi, struct lws *wsi, int m)
+{
+       if (m == LWS_HPACK_IGNORE_ENTRY || m == -1)
+               return 0;
+
+       if (wsi->seen_nonpseudoheader &&
+           (lws_header_implies_psuedoheader_map[m >> 3] & (1 << (m & 7)))) {
+
+               lwsl_info("lws tok %d seems to be a pseudoheader\n", m);
+
+               /*
+                * 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 (!(lws_header_implies_psuedoheader_map[m >> 3] & (1 << (m & 7))))
+               wsi->seen_nonpseudoheader = 1;
+
+       return 0;
+}
+
+int lws_hpack_interpret(struct lws *wsi, unsigned char c)
+{
+       struct lws *nwsi = lws_get_network_wsi(wsi);
+       struct lws_h2_netconn *h2n = nwsi->h2.h2n;
+       struct allocated_headers *ah = wsi->http.ah;
+       unsigned int prev;
+       unsigned char c1;
+       int n, m, plen;
+
+       if (!h2n)
+               return -1;
+
+       /*
+        * 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;
+               ah->parser_state = 255;
+
+               if (c & 0x80) { /* 1....  indexed header field only */
+                       /* just a possibly-extended integer */
+                       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) {
+                               h2n->hpack_len = 0;
+                               h2n->hpack_m = 0x7f;
+                               h2n->hpack = HPKS_IDX_EXT;
+                               break;
+                       }
+                       if (!h2n->hdr_idx) {
+                               lws_h2_goaway(nwsi, H2_ERR_COMPRESSION_ERROR,
+                                             "hdr index 0 seen");
+                                       return 1;
+                       }
+
+                       m = lws_token_from_index(wsi, h2n->hdr_idx,
+                                                NULL, NULL, NULL);
+                       if (lws_hpack_handle_pseudo_rules(nwsi, wsi, m))
+                               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) { /* 01.... indexed or literal header incr idx */
+                       /*
+                        * [possibly-ext hdr idx (6) | new literal hdr name]
+                        * H + possibly-ext value length
+                        * literal value
+                        */
+                       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 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) {
+                               h2n->hpack_m = 0x3f;
+                               h2n->hpack_len = 0;
+                               h2n->hpack = HPKS_IDX_EXT;
+                               break;
+                       }
+
+                       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 */
+                       /*
+                        * follows 0x40 except 4-bit hdr idx
+                        * and don't add to index
+                        */
+                       if (c == 0) { /* literal name */
+                               h2n->hpack_type = HPKT_LITERAL_HDR_VALUE;
+                               lwsl_header("   HPKT_LITERAL_HDR_VALUE\n");
+                               h2n->hpack = HPKS_HLEN;
+                               h2n->value = 0;
+                               break;
+                       }
+                       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 */
+                       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) {
+                               h2n->hpack_len = c & 0xf;
+                               h2n->hpack_m = 0xf;
+                               h2n->hpack_len = 0;
+                               h2n->hpack = HPKS_IDX_EXT;
+                               break;
+                       }
+                       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) */
+                       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;
+                       }
+                       h2n->last_action_dyntable_resize = 1;
+                       if (lws_hpack_dynamic_size(wsi, h2n->hpack_len))
+                               return 1;
+                       break;
+               }
+               break;
+
+       case HPKS_IDX_EXT:
+               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+ ] */
+               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) {
+                       ah->parser_state = WSI_TOKEN_NAME_PART;
+                       ah->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 = ah->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;
+               }
+               break;
+
+       case HPKS_HLEN_EXT:
+               h2n->hpack_len = h2n->hpack_len |
+                                ((c & 0x7f) << h2n->ext_count);
+               h2n->ext_count += 7;
+               if (c & 0x80) /* extended integer not complete yet */
+                       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 (h2n->huff) {
+                               char b = (c >> 7) & 1;
+                               prev = h2n->hpack_pos;
+                               h2n->hpack_pos = huftable_decode(
+                                               h2n->hpack_pos, b);
+                               c <<= 1;
+                               if (h2n->hpack_pos == 0xffff) {
+                                       lwsl_notice("Huffman err\n");
+                                       return 1;
+                               }
+                               if (!(h2n->hpack_pos & 0x8000)) {
+                                       if (!b)
+                                               h2n->zero_huff_padding = 1;
+                                       h2n->huff_pad++;
+                                       continue;
+                               }
+                               c1 = h2n->hpack_pos & 0x7fff;
+                               h2n->hpack_pos = 0;
+                               h2n->huff_pad = 0;
+                               h2n->zero_huff_padding = 0;
+
+                               /* 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;
+
+                       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
+                                       //lwsl_header("ignoring %c\n", c1);
+                       } else {
+                               /*
+                                * Convert name using existing parser,
+                                * If h2n->unknown_header == 0, result is
+                                * in wsi->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;
+                               }
+                               plen = 1;
+                               if (!h2n->unknown_header &&
+                                   lws_parse(wsi, &c1, &plen))
+                                       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_info("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->parser_state: %d\n",
+                                       ah->parser_state);
+
+                       if (ah->parser_state == WSI_TOKEN_NAME_PART) {
+                               /* h2 headers come without the colon */
+                               c1 = ':';
+                               plen = 1;
+                               n = lws_parse(wsi, &c1, &plen);
+                               (void)n;
+                       }
+
+                       if (ah->parser_state == WSI_TOKEN_NAME_PART ||
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                           ah->parser_state == WSI_TOKEN_UNKNOWN_VALUE_PART ||
+#endif
+                           ah->parser_state == WSI_TOKEN_SKIPPING) {
+                               h2n->unknown_header = 1;
+                               ah->parser_state = -1;
+                               wsi->seen_nonpseudoheader = 1;
+                       }
+               }
+
+               n = 8;
+
+               /* 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;
+               }
+
+               /*
+                * we have got both the header and value
+                */
+
+               m = -1;
+               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 = ah->parser_state;
+                       if (h2n->unknown_header ||
+                           ah->parser_state == WSI_TOKEN_NAME_PART ||
+                           ah->parser_state == WSI_TOKEN_SKIPPING) {
+                               if (h2n->first_hdr_char == ':') {
+                                       lwsl_info("HPKT_LITERAL_HDR_VALUE_INCR:"
+                                                 " end state %d unk hdr %d\n",
+                                                 ah->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_INDEXED_HDR_6_VALUE_INCR) {
+
+                       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 = ah->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 (lws_hpack_handle_pseudo_rules(nwsi, wsi, m))
+                       return 1;
+
+               h2n->is_first_header_char = 1;
+               h2n->hpack = HPKS_TYPE;
+               break;
+       }
+
+       return 0;
+}
+
+
+
+static int
+lws_h2_num_start(int starting_bits, unsigned long num)
+{
+       unsigned 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)
+{
+       unsigned int mask = (1 << starting_bits) - 1;
+
+       if (num < mask)
+               return 0;
+
+       num -= mask;
+       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,
+                                const unsigned char *value, int length,
+                                unsigned char **p, unsigned char *end)
+{
+       int len;
+
+       lwsl_header("%s: %p  %s:%s\n", __func__, *p, name, value);
+
+       len = (int)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; /* literal hdr, literal name,  */
+
+       *((*p)++) = 0 | lws_h2_num_start(7, len); /* non-HUF */
+       if (lws_h2_num(7, len, p, end))
+               return 1;
+
+       /* upper-case header names are verboten in h2, but OK on h1, so
+        * they're not illegal per se.  Silently convert them for h2... */
+
+       while(len--)
+               *((*p)++) = tolower((int)*name++);
+
+       *((*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;
+
+       return 0;
+}
+
+int lws_add_http2_header_by_token(struct lws *wsi, enum lws_token_indexes token,
+                                 const unsigned char *value, int length,
+                                 unsigned char **p, unsigned char *end)
+{
+       const unsigned char *name;
+
+       name = lws_token_to_string(token);
+       if (!name)
+               return 1;
+
+       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)
+{
+       unsigned char status[10];
+       int n;
+
+       wsi->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,
+                                         status, n, p, end))
+
+               return 1;
+
+       return 0;
+}
diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c
new file mode 100644 (file)
index 0000000..76449b2
--- /dev/null
@@ -0,0 +1,2397 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+
+#include "core/private.h"
+
+/*
+ * 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),
+};
+
+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",
+       "reserved",
+       "H2SET_ENABLE_CONNECT_PROTOCOL"
+};
+
+void
+lws_h2_dump_settings(struct http2_settings *set)
+{
+       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
+
+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;
+}
+
+void lws_h2_init(struct lws *wsi)
+{
+       wsi->h2.h2n->set = wsi->vhost->h2.set;
+}
+
+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->h2.h2_state],
+                       h2_state_names[s]);
+               
+       (void)h2_state_names;
+       wsi->h2.h2_state = (uint8_t)s;
+}
+
+struct lws *
+lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
+                           unsigned int sid)
+{
+       struct lws *wsi;
+       struct lws *nwsi = lws_get_network_wsi(parent_wsi);
+       struct lws_h2_netconn *h2n = nwsi->h2.h2n;
+
+       /*
+        * 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) {
+               lwsl_info("%s: tried to open lower sid %d\n", __func__, sid);
+               lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, "Bad sid");
+               return NULL;
+       }
+
+       /* no more children allowed by parent */
+       if (parent_wsi->h2.child_count + 1 >
+           parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
+               lwsl_notice("reached concurrent stream limit\n");
+               return NULL;
+       }
+       wsi = lws_create_new_server_wsi(vh, parent_wsi->tsi);
+       if (!wsi) {
+               lwsl_notice("new server wsi failed (vh %p)\n", vh);
+               return NULL;
+       }
+
+       h2n->highest_sid_opened = sid;
+       wsi->h2.my_sid = sid;
+       wsi->http2_substream = 1;
+       wsi->seen_nonpseudoheader = 0;
+
+       wsi->h2.parent_wsi = parent_wsi;
+       wsi->role_ops = parent_wsi->role_ops;
+       /* new guy's sibling is whoever was the first child before */
+       wsi->h2.sibling_list = parent_wsi->h2.child_list;
+       /* first child is now the new guy */
+       parent_wsi->h2.child_list = wsi;
+       parent_wsi->h2.child_count++;
+
+       wsi->h2.my_priority = 16;
+       wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
+       wsi->h2.peer_tx_cr_est =
+                       nwsi->vhost->h2.set.s[H2SET_INITIAL_WINDOW_SIZE];
+
+       lwsi_set_state(wsi, LRS_ESTABLISHED);
+       lwsi_set_role(wsi, lwsi_role(parent_wsi));
+
+       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->h2.tx_cr, wsi->h2.peer_tx_cr_est, nwsi->h2.tx_cr);
+
+       return wsi;
+
+bail1:
+       /* undo the insert */
+       parent_wsi->h2.child_list = wsi->h2.sibling_list;
+       parent_wsi->h2.child_count--;
+
+       vh->context->count_wsi_allocated--;
+
+       if (wsi->user_space)
+               lws_free_set_NULL(wsi->user_space);
+       vh->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
+       lws_vhost_unbind_wsi(wsi);
+       lws_free(wsi);
+
+       return NULL;
+}
+
+struct lws *
+lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)
+{
+       struct lws *nwsi = lws_get_network_wsi(parent_wsi);
+
+       /* no more children allowed by parent */
+       if (parent_wsi->h2.child_count + 1 >
+           parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
+               lwsl_notice("reached concurrent stream limit\n");
+               return NULL;
+       }
+
+       /* sid is set just before issuing the headers, ensuring monoticity */
+
+       wsi->seen_nonpseudoheader = 0;
+#if !defined(LWS_NO_CLIENT)
+       wsi->client_h2_substream = 1;
+#endif
+       wsi->h2.initialized = 1;
+
+       wsi->h2.parent_wsi = parent_wsi;
+       /* new guy's sibling is whoever was the first child before */
+       wsi->h2.sibling_list = parent_wsi->h2.child_list;
+       /* first child is now the new guy */
+       parent_wsi->h2.child_list = wsi;
+       parent_wsi->h2.child_count++;
+
+       wsi->h2.my_priority = 16;
+       wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
+       wsi->h2.peer_tx_cr_est =
+                       nwsi->vhost->h2.set.s[H2SET_INITIAL_WINDOW_SIZE];
+
+       if (lws_ensure_user_space(wsi))
+               goto bail1;
+
+       lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS,
+                           &role_ops_h2);
+
+       lws_callback_on_writable(wsi);
+
+       wsi->vhost->conn_stats.h2_subs++;
+
+       return wsi;
+
+bail1:
+       /* undo the insert */
+       parent_wsi->h2.child_list = wsi->h2.sibling_list;
+       parent_wsi->h2.child_count--;
+
+       if (wsi->user_space)
+               lws_free_set_NULL(wsi->user_space);
+       wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
+       lws_free(wsi);
+
+       return NULL;
+}
+
+
+int lws_h2_issue_preface(struct lws *wsi)
+{
+       struct lws_h2_netconn *h2n = wsi->h2.h2n;
+       struct lws_h2_protocol_send *pps;
+
+       if (lws_issue_raw(wsi, (uint8_t *)preface, strlen(preface)) !=
+               (int)strlen(preface))
+               return 1;
+
+       lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS,
+                           &role_ops_h2);
+
+       h2n->count = 0;
+       wsi->h2.tx_cr = 65535;
+
+       /*
+        * we must send a settings frame
+        */
+       pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS);
+       if (!pps)
+               return 1;
+       lws_pps_schedule(wsi, pps);
+       lwsl_info("%s: h2 client sending settings\n", __func__);
+
+       return 0;
+}
+
+struct lws *
+lws_h2_wsi_from_id(struct lws *parent_wsi, unsigned int sid)
+{
+       lws_start_foreach_ll(struct lws *, wsi, parent_wsi->h2.child_list) {
+               if (wsi->h2.my_sid == sid)
+                       return wsi;
+       } lws_end_foreach_ll(wsi, h2.sibling_list);
+
+       return NULL;
+}
+
+int lws_remove_server_child_wsi(struct lws_context *context, struct lws *wsi)
+{
+       lws_start_foreach_llp(struct lws **, w, wsi->h2.child_list) {
+               if (*w == wsi) {
+                       *w = wsi->h2.sibling_list;
+                       (wsi->h2.parent_wsi)->h2.child_count--;
+                       return 0;
+               }
+       } lws_end_foreach_llp(w, h2.sibling_list);
+
+       lwsl_err("%s: can't find %p\n", __func__, wsi);
+
+       return 1;
+}
+
+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->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);
+}
+
+int
+lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason)
+{
+       struct lws_h2_netconn *h2n = wsi->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;
+       pps->u.ga.highest_sid = h2n->highest_sid;
+       lws_strncpy(pps->u.ga.str, reason, sizeof(pps->u.ga.str));
+       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->h2.h2n;
+       struct lws_h2_protocol_send *pps;
+
+       if (!h2n)
+               return 0;
+
+       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_H2_SETTINGS_LEN)
+               return 1;
+
+       while (len >= LWS_H2_SETTINGS_LEN) {
+               a = (buf[0] << 8) | buf[1];
+               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;
+                       }
+
+#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
+                       //FIXME: Workaround for FIRMWARE-4632 until cloud-side issue is fixed.
+                       if (b == 0x7fffffff) {
+                               b = 65535;
+                               lwsl_info("init window size 0x7fffffff\n");
+                               break;
+                       }
+                       //FIXME: end of FIRMWARE-4632 workaround
+#endif
+
+                       /*
+                        * 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->h2.child_list) {
+                               lwsl_info("%s: adi child tc cr %d +%d -> %d",
+                                           __func__,
+                                           w->h2.tx_cr, b - settings->s[a],
+                                           w->h2.tx_cr + b - settings->s[a]);
+                               w->h2.tx_cr += b - settings->s[a];
+                               if (w->h2.tx_cr > 0 &&
+                                   w->h2.tx_cr <=
+                                                 (int32_t)(b - settings->s[a]))
+                                       lws_callback_on_writable(w);
+                       } lws_end_foreach_ll(w, h2.sibling_list);
+
+                       break;
+               case H2SET_MAX_FRAME_SIZE:
+                       if (b < wsi->vhost->h2.set.s[H2SET_MAX_FRAME_SIZE]) {
+                               lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
+                                             "Frame size < initial");
+                               return 1;
+                       }
+                       if (b > 0x00ffffff) {
+                               lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
+                                             "Settings Frame size above max");
+                               return 1;
+                       }
+                       break;
+               case H2SET_MAX_HEADER_LIST_SIZE:
+                       break;
+               }
+               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;
+}
+
+/* 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.
+ */
+
+int
+lws_h2_tx_cr_get(struct lws *wsi)
+{
+       int c = wsi->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->h2.tx_cr);
+
+       if (nwsi->h2.tx_cr < c)
+               c = nwsi->h2.tx_cr;
+
+       if (c < 0)
+               return 0;
+
+       return c;
+}
+
+void
+lws_h2_tx_cr_consume(struct lws *wsi, int consumed)
+{
+       struct lws *nwsi = lws_get_network_wsi(wsi);
+
+       wsi->h2.tx_cr -= consumed;
+
+       if (nwsi != wsi)
+               nwsi->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;
+
+       //if (wsi->h2_stream_carries_ws)
+       // lwsl_hexdump_level(LLL_NOTICE, buf, len);
+
+       *p++ = len >> 16;
+       *p++ = len >> 8;
+       *p++ = len;
+       *p++ = type;
+       *p++ = flags;
+       *p++ = sid >> 24;
+       *p++ = sid >> 16;
+       *p++ = sid >> 8;
+       *p++ = sid;
+
+       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->h2.tx_cr, nwsi->h2.tx_cr);
+
+       if (type == LWS_H2_FRAME_TYPE_DATA) {
+               if (wsi->h2.tx_cr < (int)len)
+                       lwsl_err("%s: %p: sending payload len %d"
+                                " but tx_cr only %d!\n", __func__, wsi,
+                                len, wsi->h2.tx_cr);
+               lws_h2_tx_cr_consume(wsi, len);
+       }
+
+       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_h2_set_bin(struct lws *wsi, int n, unsigned char *buf)
+{
+       *buf++ = n >> 8;
+       *buf++ = n;
+       *buf++ = wsi->h2.h2n->set.s[n] >> 24;
+       *buf++ = wsi->h2.h2n->set.s[n] >> 16;
+       *buf++ = wsi->h2.h2n->set.s[n] >> 8;
+       *buf = wsi->h2.h2n->set.s[n];
+}
+
+/* we get called on the network connection */
+
+int lws_h2_do_pps_send(struct lws *wsi)
+{
+       struct lws_h2_netconn *h2n = wsi->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, flags = 0;
+
+       if (!h2n)
+               return 1;
+
+       /* 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 peer
+                */
+               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->h2.h2n->set.s[n]);
+                               lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]);
+                               m += sizeof(h2n->one_setting);
+                       }
+               n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
+                                      flags, LWS_H2_STREAM_ID_MASTER, m,
+                                      &set[LWS_PRE]);
+               if (n != m) {
+                       lwsl_info("send %d %d\n", n, m);
+                       goto bail;
+               }
+               break;
+
+       case LWS_H2_PPS_ACK_SETTINGS:
+               /* send ack ... always empty */
+               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);
+                       goto bail;
+               }
+               /* this is the end of the preface dance then? */
+               if (lwsi_state(wsi) == LRS_H2_AWAIT_SETTINGS) {
+                       lwsi_set_state(wsi, LRS_ESTABLISHED);
+                       wsi->http.fop_fd = NULL;
+                       if (lws_is_ssl(lws_get_network_wsi(wsi)))
+                               break;
+                       /*
+                        * we need to treat the headers from the upgrade as the
+                        * first job.  So these need to get shifted to sid 1.
+                        */
+                       h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, 1);
+                       if (!h2n->swsi)
+                               goto bail;
+
+                       /* pass on the initial headers to SID 1 */
+                       h2n->swsi->http.ah = wsi->http.ah;
+                       wsi->http.ah = NULL;
+
+                       lwsl_info("%s: inherited headers %p\n", __func__,
+                                 h2n->swsi->http.ah);
+                       h2n->swsi->h2.tx_cr =
+                               h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
+                       lwsl_info("initial tx credit on conn %p: %d\n",
+                                 h2n->swsi, h2n->swsi->h2.tx_cr);
+                       h2n->swsi->h2.initialized = 1;
+                       /* demanded by HTTP2 */
+                       h2n->swsi->h2.END_STREAM = 1;
+                       lwsl_info("servicing initial http request\n");
+
+                       wsi->vhost->conn_stats.h2_trans++;
+
+                       if (lws_http_action(h2n->swsi))
+                               goto bail;
+
+                       break;
+               }
+               break;
+       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);
+                       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 *)pps->u.ga.str;
+               n = 0;
+               while (*q && n++ < (int)sizeof(pps->u.ga.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,
+                                      lws_ptr_diff(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) {
+                       lwsl_debug("%s: closing cwsi %p %s %s (wsi %p)\n",
+                                  __func__, cwsi, cwsi->role_ops->name,
+                                  cwsi->protocol->name, wsi);
+                       lws_close_free_wsi(cwsi, 0, "reset stream");
+               }
+               break;
+
+       case LWS_H2_PPS_UPDATE_WINDOW:
+               lwsl_debug("Issuing 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 header completion.
+ */
+static int
+lws_h2_parse_frame_header(struct lws *wsi)
+{
+       struct lws_h2_netconn *h2n = wsi->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 */
+
+       if (!wsi->immortal_substream_count)
+#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
+               lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, wsi->vhost->keepalive_timeout);
+#else
+               lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
+#endif
+
+       if (h2n->sid)
+               h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid);
+
+       lwsl_debug("%p (%p): fr hdr: typ 0x%x, fla 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
+                */
+               lwsl_info("received oversize frame %d\n", h2n->length);
+               lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
+                             "Peer ignored our frame size setting");
+               return 1;
+       }
+
+       if (h2n->swsi)
+               lwsl_info("%s: wsi %p, State: %s, received cmd %d\n",
+                 __func__, h2n->swsi,
+                 h2_state_names[h2n->swsi->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) {
+                       if (h2n->sid <= h2n->highest_sid_opened
+#if !defined(LWS_NO_CLIENT)
+                                       && wsi->client_h2_alpn
+#endif
+                       ) {
+                               lwsl_notice("ignoring straggling data\n");
+                               /* ie, IGNORE */
+                               h2n->type = LWS_H2_FRAME_TYPE_COUNT;
+                       } else {
+                               lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
+                                     "Data for nonexistent 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, rxcmd %d\n",
+                         __func__, h2n->swsi, h2n->sid, h2n->type);
+                       lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
+                                    "Data for nonexistent sid");
+                       return 0;
+               }
+       }
+
+       if (h2n->swsi && h2n->sid &&
+           !(http2_rx_validity[h2n->swsi->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->h2.h2_state], h2n->type,
+                         http2_rx_validity[h2n->swsi->h2.h2_state]);
+
+               if (h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED ||
+                   h2n->swsi->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) {
+                       lwsl_info("DATA: 0 sid\n");
+                       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) {
+                       lwsl_notice("DATA: NULL swsi\n");
+                       break;
+               }
+
+               lwsl_info("DATA rx on state %d\n", h2n->swsi->h2.h2_state);
+
+               if (
+                   h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE ||
+                   h2n->swsi->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:
+               lwsl_debug("LWS_H2_FRAME_TYPE_GOAWAY received\n");
+               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;
+                       }
+
+                       if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
+                               return 0;
+
+                       if (wsi->upgraded_to_http2) {
+                               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->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 && !h2n->swsi->h2.END_STREAM &&
+                   h2n->swsi->h2.END_HEADERS &&
+                   !(h2n->flags & LWS_H2_FLAG_END_STREAM)) {
+                       lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
+                                     "extra HEADERS together");
+                       return 1;
+               }
+
+#if !defined(LWS_NO_CLIENT)
+               if (wsi->client_h2_alpn) {
+                       if (h2n->sid) {
+                               h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid);
+                               lwsl_info("HEADERS: nwsi %p: sid %d mapped "
+                                         "to wsi %p\n", wsi, h2n->sid,
+                                         h2n->swsi);
+                               if (!h2n->swsi)
+                                       break;
+                       }
+                       goto update_end_headers;
+               }
+#endif
+
+               if (!h2n->swsi) {
+                       /* no more children allowed by parent */
+                       if (wsi->h2.child_count + 1 >
+                           wsi->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;
+                       }
+
+                       pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
+                       if (!pps)
+                               goto cleanup_wsi;
+                       pps->u.update_window.sid = h2n->sid;
+                       pps->u.update_window.credit = 4 * 65536;
+                       h2n->swsi->h2.peer_tx_cr_est +=
+                                               pps->u.update_window.credit;
+                       lws_pps_schedule(wsi, pps);
+
+                       pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
+                       if (!pps)
+                               goto cleanup_wsi;
+                       pps->u.update_window.sid = 0;
+                       pps->u.update_window.credit = 4 * 65536;
+                       wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
+                       lws_pps_schedule(wsi, pps);
+               }
+
+               /*
+                * ah needs attaching to child wsi, even though
+                * we only fill it from network wsi
+                */
+               if (!h2n->swsi->http.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->h2.child_list) {
+                       if (w->h2.my_sid < h2n->sid &&
+                           w->h2.h2_state == LWS_H2_STATE_IDLE)
+                               lws_close_free_wsi(w, 0, "h2 sid close");
+                       assert(w->h2.sibling_list != w);
+               } lws_end_foreach_ll(w, h2.sibling_list);
+
+
+               /* END_STREAM means after servicing this, close the stream */
+               h2n->swsi->h2.END_STREAM =
+                                       !!(h2n->flags & LWS_H2_FLAG_END_STREAM);
+               lwsl_info("%s: hdr END_STREAM = %d\n",__func__,
+                         h2n->swsi->h2.END_STREAM);
+
+               h2n->cont_exp = !(h2n->flags & LWS_H2_FLAG_END_HEADERS);
+               h2n->cont_exp_sid = h2n->sid;
+               h2n->cont_exp_headers = 1;
+       //      lws_header_table_reset(h2n->swsi, 0);
+
+update_end_headers:
+               /* no END_HEADERS means CONTINUATION must come */
+               h2n->swsi->h2.END_HEADERS =
+                               !!(h2n->flags & LWS_H2_FLAG_END_HEADERS);
+               lwsl_info("%p: END_HEADERS %d\n", h2n->swsi,
+                         h2n->swsi->h2.END_HEADERS);
+               if (h2n->swsi->h2.END_HEADERS)
+                       h2n->cont_exp = 0;
+               lwsl_debug("END_HEADERS %d\n", h2n->swsi->h2.END_HEADERS);
+               break;
+
+cleanup_wsi:
+
+               return 1;
+
+       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;
+       case LWS_H2_FRAME_TYPE_COUNT:
+               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;
+}
+
+static const char * const method_names[] = {
+       "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD"
+};
+static unsigned char method_index[] = {
+       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,
+};
+
+/*
+ * The last byte of the whole frame has been handled.
+ * Perform actions for frame completion.
+ *
+ * This is the crunch time for parsing that may have occured on a network
+ * wsi with a pending partial send... we may call lws_http_action() to send
+ * a response, conflicting with the partial.
+ *
+ * So in that case we change the wsi state and do the lws_http_action() in the
+ * WRITABLE handler as a priority.
+ */
+static int
+lws_h2_parse_end_of_frame(struct lws *wsi)
+{
+       struct lws_h2_netconn *h2n = wsi->h2.h2n;
+       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->h2.initialized) {
+               wsi->h2.tx_cr = h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
+               lwsl_info("initial tx credit on master %p: %d\n", wsi,
+                         wsi->h2.tx_cr);
+               wsi->h2.initialized = 1;
+       }
+
+       if (h2n->collected_priority && (h2n->dep & ~(1u << 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_SETTINGS:
+
+#if !defined(LWS_NO_CLIENT)
+               if (wsi->client_h2_alpn &&
+                   !(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) {
+                       struct lws_h2_protocol_send *pps;
+
+                       /* migrate original client ask on to substream 1 */
+
+                       wsi->http.fop_fd = NULL;
+
+                       /*
+                        * we need to treat the headers from the upgrade as the
+                        * first job.  So these need to get shifted to sid 1.
+                        */
+                       h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, 1);
+                       if (!h2n->swsi)
+                               return 1;
+                       h2n->sid = 1;
+
+                       assert(lws_h2_wsi_from_id(wsi, 1) == h2n->swsi);
+
+                       lws_role_transition(wsi, LWSIFR_CLIENT,
+                                           LRS_H2_WAITING_TO_SEND_HEADERS,
+                                           &role_ops_h2);
+
+                       lws_role_transition(h2n->swsi, LWSIFR_CLIENT,
+                                           LRS_H2_WAITING_TO_SEND_HEADERS,
+                                           &role_ops_h2);
+
+                       /* pass on the initial headers to SID 1 */
+                       h2n->swsi->http.ah = wsi->http.ah;
+                       h2n->swsi->client_h2_substream = 1;
+
+                       h2n->swsi->protocol = wsi->protocol;
+                       if (h2n->swsi->user_space && !h2n->swsi->user_space_externally_allocated)
+                               lws_free(h2n->swsi->user_space);
+                       h2n->swsi->user_space = wsi->user_space;
+                       h2n->swsi->user_space_externally_allocated =
+                                       wsi->user_space_externally_allocated;
+                       h2n->swsi->opaque_user_data = wsi->opaque_user_data;
+                       wsi->opaque_user_data = NULL;
+
+                       wsi->user_space = NULL;
+
+                       if (h2n->swsi->http.ah)
+                               h2n->swsi->http.ah->wsi = h2n->swsi;
+                       wsi->http.ah = NULL;
+
+                       lwsl_info("%s: MIGRATING nwsi %p: swsi %p\n", __func__,
+                                 wsi, h2n->swsi);
+                       h2n->swsi->h2.tx_cr =
+                               h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
+                       lwsl_info("initial tx credit on conn %p: %d\n",
+                                 h2n->swsi, h2n->swsi->h2.tx_cr);
+                       h2n->swsi->h2.initialized = 1;
+
+                       lws_callback_on_writable(h2n->swsi);
+
+                       pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
+                       if (!pps)
+                               return 1;
+                       lws_pps_schedule(wsi, pps);
+                       lwsl_info("%s: scheduled settings ack PPS\n", __func__);
+
+                       /* also attach any queued guys */
+
+                       /* we have a transaction queue that wants to pipeline */
+                       lws_vhost_lock(wsi->vhost);
+                       lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
+                                 wsi->dll2_cli_txn_queue_owner.head) {
+                               struct lws *w = lws_container_of(d, struct lws,
+                                               dll2_cli_txn_queue);
+
+                               if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2) {
+                                       lwsl_info("%s: cli pipeq %p to be h2\n",
+                                                       __func__, w);
+                                       /* remove ourselves from client queue */
+                                       lws_dll2_remove(&w->dll2_cli_txn_queue);
+
+                                       /* attach ourselves as an h2 stream */
+                                       lws_wsi_h2_adopt(wsi, w);
+                               }
+                       } lws_end_foreach_dll_safe(d, d1);
+                       lws_vhost_unlock(wsi->vhost);
+               }
+#endif
+               break;
+
+       case LWS_H2_FRAME_TYPE_CONTINUATION:
+       case LWS_H2_FRAME_TYPE_HEADERS:
+
+               if (!h2n->swsi)
+                       break;
+
+               /* 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->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->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 !defined(LWS_NO_CLIENT)
+               if (h2n->swsi->client_h2_substream) {
+                       if (lws_client_interpret_server_handshake(h2n->swsi)) {
+                               lws_h2_rst_stream(h2n->swsi,
+                                                 H2_ERR_STREAM_CLOSED,
+                                                 "protocol CLI_EST closed it");
+                               break;
+                       }
+               }
+#endif
+
+               if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
+                       h2n->swsi->http.rx_content_length  = atoll(
+                               lws_hdr_simple_ptr(h2n->swsi,
+                                     WSI_TOKEN_HTTP_CONTENT_LENGTH));
+                       h2n->swsi->http.rx_content_remain =
+                                       h2n->swsi->http.rx_content_length;
+                       lwsl_info("setting rx_content_length %lld\n",
+                                 (long long)h2n->swsi->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 > (int)sizeof(buf) - 1) {
+                                       n++;
+                                       continue;
+                               }
+
+                               if (lws_hdr_copy(h2n->swsi, buf, sizeof buf,
+                                                n) < 0) {
+                                       lwsl_info("    %s !oversize!\n",
+                                                 (char *)c);
+                               } else {
+                                       buf[sizeof(buf) - 1] = '\0';
+
+                                       lwsl_info("    %s = %s\n",
+                                                 (char *)c, buf);
+                               }
+                               n++;
+                       } while (c);
+               }
+
+               if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE ||
+                   h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED) {
+                       lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
+                                     "Banning service on CLOSED_REMOTE");
+                       break;
+               }
+
+               switch (h2n->swsi->h2.h2_state) {
+               case LWS_H2_STATE_OPEN:
+                       if (h2n->swsi->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->h2.END_STREAM)
+                               lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
+                       break;
+               }
+
+#if !defined(LWS_NO_CLIENT)
+               if (h2n->swsi->client_h2_substream) {
+                       lwsl_info("%s: headers: client path\n", __func__);
+                       break;
+               }
+#endif
+
+               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;
+                       }
+               }
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+               lws_http_compression_validate(h2n->swsi);
+#endif
+
+               wsi->vhost->conn_stats.h2_trans++;
+               p = lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD);
+               /*
+                * duplicate :path into the individual method uri header
+                * index, so that it looks the same as h1 in the ah
+                */
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(method_names); n++)
+                       if (!strcasecmp(p, method_names[n])) {
+                               h2n->swsi->http.ah->frag_index[method_index[n]] =
+                                               h2n->swsi->http.ah->frag_index[
+                                                    WSI_TOKEN_HTTP_COLON_PATH];
+                               break;
+                       }
+
+               lwsl_debug("%s: setting DEF_ACT from 0x%x\n", __func__,
+                          h2n->swsi->wsistate);
+               lwsi_set_state(h2n->swsi, LRS_DEFERRING_ACTION);
+               lws_callback_on_writable(h2n->swsi);
+               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->h2.END_STREAM &&
+                   h2n->swsi->http.rx_content_length &&
+                   h2n->swsi->http.rx_content_remain) {
+                       lws_h2_rst_stream(h2n->swsi, H2_ERR_PROTOCOL_ERROR,
+                                         "Not enough rx content");
+                       break;
+               }
+
+               if (h2n->swsi->h2.END_STREAM &&
+                   h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN)
+                       lws_h2_state(h2n->swsi,
+                                    LWS_H2_STATE_HALF_CLOSED_REMOTE);
+
+               if (h2n->swsi->h2.END_STREAM &&
+                   h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
+                       lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
+
+#if !defined(LWS_NO_CLIENT)
+               /*
+                * client... remote END_STREAM implies we weren't going to
+                * send anything else anyway.
+                */
+
+               if (h2n->swsi->client_h2_substream &&
+                   h2n->flags & LWS_H2_FLAG_END_STREAM) {
+                       lwsl_info("%s: %p: DATA: end stream\n",
+                                 __func__, h2n->swsi);
+
+                       if (h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN) {
+                               lws_h2_state(h2n->swsi,
+                                            LWS_H2_STATE_HALF_CLOSED_REMOTE);
+               //              lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
+               //                                "client done");
+
+               //              if (lws_http_transaction_completed_client(h2n->swsi))
+               //                      lwsl_debug("tx completed returned close\n");
+                       }
+
+                       //if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
+                       {
+                               lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
+
+                               lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
+                                                 "client done");
+
+                               if (lws_http_transaction_completed_client(h2n->swsi))
+                                       lwsl_debug("tx completed returned close\n");
+                       }
+               }
+#endif
+               break;
+
+       case LWS_H2_FRAME_TYPE_PING:
+               if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack
+               } else {/* they're sending us a ping request */
+                       struct lws_h2_protocol_send *pps =
+                                       lws_h2_new_pps(LWS_H2_PPS_PONG);
+                       if (!pps)
+                               return 1;
+
+                       lwsl_info("rx ping, preparing pong\n");
+
+                       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 &= ~(1u << 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 (eff_wsi->vhost->options &
+                       LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW &&
+                   (uint64_t)eff_wsi->h2.tx_cr + (uint64_t)h2n->hpack_e_dep >
+                   (uint64_t)0x7fffffff)
+                       h2n->hpack_e_dep = 0x7fffffff - eff_wsi->h2.tx_cr;
+
+               if ((uint64_t)eff_wsi->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->h2.tx_cr;
+               eff_wsi->h2.tx_cr += h2n->hpack_e_dep;
+
+               if (n <= 0 && eff_wsi->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->h2.child_list) {
+                       lws_callback_on_writable(w);
+               } lws_end_foreach_ll(w, h2.sibling_list);
+
+               if (eff_wsi->h2.skint && lws_h2_tx_cr_get(eff_wsi)) {
+                       lwsl_info("%s: %p: skint\n", __func__, wsi);
+                       eff_wsi->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->h2.GOING_AWAY = 1;
+
+               return 1;
+
+       case LWS_H2_FRAME_TYPE_RST_STREAM:
+               lwsl_info("LWS_H2_FRAME_TYPE_RST_STREAM: sid %d: reason 0x%x\n",
+                         h2n->sid, h2n->hpack_e_dep);
+               break;
+
+       case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */
+               break;
+       }
+
+       return 0;
+}
+
+/*
+ * This may want to send something on the network wsi, which may be in the
+ * middle of a partial send.  PPS sends are OK because they are queued to
+ * go through the WRITABLE handler already.
+ *
+ * The read parser for the network wsi has no choice but to parse its stream
+ * anyway, because otherwise it will not be able to get tx credit window
+ * messages.
+ *
+ * Therefore if we will send non-PPS, ie, lws_http_action() for a stream
+ * wsi, we must change its state and handle it as a priority in the
+ * POLLOUT handler instead of writing it here.
+ *
+ * About closing... for the main network wsi, it should return nonzero to
+ * close it all.  If it needs to close an swsi, it can do it here.
+ */
+int
+lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
+             lws_filepos_t *inused)
+{
+       struct lws_h2_netconn *h2n = wsi->h2.h2n;
+       struct lws_h2_protocol_send *pps;
+       unsigned char c, *oldin = in;
+       int n, m;
+
+       if (!h2n)
+               goto fail;
+
+       while (inlen--) {
+
+               c = *in++;
+
+               // lwsl_notice("%s: 0x%x\n", __func__, c);
+
+               switch (lwsi_state(wsi)) {
+               case LRS_H2_AWAIT_PREFACE:
+                       if (preface[h2n->count++] != c)
+                               goto fail;
+
+                       if (preface[h2n->count])
+                               break;
+
+                       lwsl_info("http2: %p: established\n", wsi);
+                       lwsi_set_state(wsi, LRS_H2_AWAIT_SETTINGS);
+                       h2n->count = 0;
+                       wsi->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)
+                               goto fail;
+                       lws_pps_schedule(wsi, pps);
+                       break;
+
+               case LRS_H2_WAITING_TO_SEND_HEADERS:
+               case LRS_ESTABLISHED:
+               case LRS_H2_AWAIT_SETTINGS:
+                       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->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__);
+                                       goto fail;
+                               }
+                               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_info("%s: LWS_H2_FRAME_TYPE_DATA\n",
+                                         __func__);
+
+                               /*
+                                * let the network wsi live a bit longer if
+                                * subs are active... our frame may take a long
+                                * time to chew through
+                                */
+                               if (!wsi->immortal_substream_count)
+                                       lws_set_timeout(wsi,
+                                         PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
+#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
+                                         wsi->vhost->keepalive_timeout);
+#else
+                                         31);
+#endif
+
+                               if (!h2n->swsi)
+                                       break;
+
+                               if (lws_buflist_next_segment_len(
+                                               &h2n->swsi->buflist, NULL))
+                                       lwsl_info("%s: substream has pending\n",
+                                                 __func__);
+
+                               if (lwsi_role_http(h2n->swsi) &&
+                                   lwsi_state(h2n->swsi) == LRS_ESTABLISHED) {
+                                       lwsi_set_state(h2n->swsi, LRS_BODY);
+                                       lwsl_info("%s: swsi %p to LRS_BODY\n",
+                                                       __func__, h2n->swsi);
+                               }
+
+                               if (lws_hdr_total_length(h2n->swsi,
+                                            WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
+                                   h2n->swsi->http.rx_content_length &&
+                                   h2n->swsi->http.rx_content_remain <
+                                                   inlen + 1 && /* last */
+                                   h2n->inside < h2n->length) {
+                                       /* unread data in frame */
+                                       lws_h2_goaway(wsi,
+                                                     H2_ERR_PROTOCOL_ERROR,
+                                           "More rx than content_length told");
+                                       break;
+                               }
+
+                               /*
+                                * We operate on a frame.  The RX we have at
+                                * hand may exceed the current frame.
+                                */
+
+                               n = (int)inlen + 1;
+                               if (n > (int)(h2n->length - h2n->count + 1)) {
+                                       n = h2n->length - h2n->count + 1;
+                                       lwsl_debug("---- restricting len to %d vs %ld\n", n, (long)inlen + 1);
+                               }
+#if !defined(LWS_NO_CLIENT)
+                               if (h2n->swsi->client_h2_substream) {
+
+                                       m = user_callback_handle_rxflow(
+                                               h2n->swsi->protocol->callback,
+                                               h2n->swsi,
+                                         LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+                                               h2n->swsi->user_space,
+                                               in - 1, n);
+
+                                       in += n - 1;
+                                       h2n->inside += n;
+                                       h2n->count += n - 1;
+                                       inlen -= n - 1;
+
+                                       if (m) {
+                                               lwsl_info("RECEIVE_CLIENT_HTTP "
+                                                         "closed it\n");
+                                               goto close_swsi_and_return;
+                                       }
+
+                                       break;
+                               } else
+#endif
+                               {
+
+                                       if (lwsi_state(h2n->swsi) == LRS_DEFERRING_ACTION) {
+                                               // lwsl_notice("appending because we are in LRS_DEFERRING_ACTION\n");
+                                               m = lws_buflist_append_segment(
+                                                       &h2n->swsi->buflist,
+                                                               in - 1, n);
+                                               if (m < 0)
+                                                       return -1;
+                                               if (m) {
+                                                       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+                                                       lwsl_debug("%s: added %p to rxflow list\n", __func__, wsi);
+                                                       lws_dll2_add_head(&h2n->swsi->dll_buflist, &pt->dll_buflist_owner);
+                                               }
+                                               in += n - 1;
+                                               h2n->inside += n;
+                                               h2n->count += n - 1;
+                                               inlen -= n - 1;
+
+                                               lwsl_debug("%s: deferred %d\n", __func__, n);
+                                               goto do_windows;
+                                       }
+
+                                       h2n->swsi->outer_will_close = 1;
+                                       /*
+                                        * choose the length for this go so that we end at
+                                        * the frame boundary, in the case there is already
+                                        * more waiting leave it for next time around
+                                        */
+
+                                       n = lws_read_h1(h2n->swsi, in - 1, n);
+                                       // lwsl_notice("%s: lws_read_h1 %d\n", __func__, n);
+                                       h2n->swsi->outer_will_close = 0;
+                                       /*
+                                        * can return 0 in POST body with
+                                        * content len exhausted somehow.
+                                        */
+                                       if (n < 0 ||
+                                           (!n && !lws_buflist_next_segment_len(&wsi->buflist, NULL))) {
+                                               lwsl_info("%s: lws_read_h1 told %d %d / %d\n",
+                                                       __func__, n, h2n->count, h2n->length);
+                                               in += h2n->length - h2n->count;
+                                               h2n->inside = h2n->length;
+                                               h2n->count = h2n->length - 1;
+
+                                               //if (n < 0)
+                                               //      goto already_closed_swsi;
+                                               goto close_swsi_and_return;
+                                       }
+
+                                       inlen -= n - 1;
+                                       in += n - 1;
+                                       h2n->inside += n;
+                                       h2n->count += n - 1;
+                               }
+
+do_windows:
+                               /* account for both network and stream wsi windows */
+
+                               wsi->h2.peer_tx_cr_est -= n;
+                               h2n->swsi->h2.peer_tx_cr_est -= n;
+
+       //                      lwsl_notice("   peer_tx_cr_est %d, parent %d\n",
+       //                                 h2n->swsi->h2.peer_tx_cr_est, wsi->h2.peer_tx_cr_est);
+
+                               if (h2n->swsi->h2.peer_tx_cr_est < (int)(2 * h2n->length) + 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 = (2 * h2n->length + 65536);
+                                       h2n->swsi->h2.peer_tx_cr_est += pps->u.update_window.credit; 
+                                       lws_pps_schedule(wsi, pps);
+                               }
+                               if (wsi->h2.peer_tx_cr_est < (int)(2 * h2n->length) + 65536) {
+                                       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 = (2 * h2n->length + 65536);
+                                       wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
+                                       lws_pps_schedule(wsi, pps);
+                               }
+
+                               // lwsl_notice("%s: count %d len %d\n", __func__, (int)h2n->count, (int)h2n->length);
+
+                               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 & ~(1u << 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:
+                               h2n->hpack_e_dep <<= 8;
+                               h2n->hpack_e_dep |= c;
+                               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);
+
+                               goto fail;
+                       }
+
+frame_end:
+                       if (h2n->count > h2n->length) {
+                               lwsl_notice("%s: count > length %d %d\n",
+                                           __func__, h2n->count, h2n->length);
+                               goto fail;
+                       }
+                       if (h2n->count != h2n->length)
+                               break;
+
+                       /*
+                        * end of frame just happened
+                        */
+                       if (lws_h2_parse_end_of_frame(wsi))
+                               goto fail;
+
+                       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))
+                                       goto fail;
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       *inused = in - oldin;
+
+       return 0;
+
+close_swsi_and_return:
+
+       lws_close_free_wsi(h2n->swsi, 0, "close_swsi_and_return");
+       h2n->swsi = NULL;
+       h2n->frame_state = 0;
+       h2n->count = 0;
+
+// already_closed_swsi:
+       *inused = in - oldin;
+
+       return 2;
+
+fail:
+       *inused = in - oldin;
+
+       return 1;
+}
+
+int
+lws_h2_client_handshake(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       uint8_t *buf, *start, *p, *end;
+       char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD),
+            *uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI);
+       struct lws *nwsi = lws_get_network_wsi(wsi);
+       struct lws_h2_protocol_send *pps;
+       int n;
+       /*
+        * 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.
+        */
+       int sid = nwsi->h2.h2n->highest_sid_opened + 2;
+
+       nwsi->h2.h2n->highest_sid_opened = sid;
+       wsi->h2.my_sid = sid;
+
+       lwsl_info("%s: CLIENT_WAITING_TO_SEND_HEADERS: pollout (sid %d)\n",
+                       __func__, wsi->h2.my_sid);
+
+       pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
+       if (!pps)
+               return 1;
+       pps->u.update_window.sid = sid;
+       pps->u.update_window.credit = 4 * 65536;
+       wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
+       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 = 4 * 65536;
+       wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
+       lws_pps_schedule(wsi, pps);
+
+       p = start = buf = pt->serv_buf + LWS_PRE;
+       end = start + wsi->context->pt_serv_buf_size - LWS_PRE - 1;
+
+       /* it's time for us to send our client stream headers */
+
+       if (!meth)
+               meth = "GET";
+
+       if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_COLON_METHOD,
+                               (unsigned char *)meth,
+                               (int)strlen(meth), &p, end))
+               goto fail_length;
+
+       if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_COLON_SCHEME,
+                               (unsigned char *)"https", 4,
+                               &p, end))
+               goto fail_length;
+
+       if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_COLON_PATH,
+                               (unsigned char *)uri,
+                               lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI),
+                               &p, end))
+               goto fail_length;
+
+       if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_COLON_AUTHORITY,
+                               (unsigned char *)lws_hdr_simple_ptr(wsi,
+                                               _WSI_TOKEN_CLIENT_ORIGIN),
+                       lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_ORIGIN),
+                               &p, end))
+               goto fail_length;
+
+       /* give userland a chance to append, eg, cookies */
+
+       if (wsi->protocol->callback(wsi,
+                               LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
+                               wsi->user_space, &p, (end - p) - 12))
+               goto fail_length;
+
+       if (lws_finalize_http_header(wsi, &p, end))
+               goto fail_length;
+
+       n = lws_write(wsi, start, p - start,
+                     LWS_WRITE_HTTP_HEADERS);
+       if (n != (p - start)) {
+               lwsl_err("_write returned %d from %ld\n", n,
+                        (long)(p - start));
+               return -1;
+       }
+
+       lws_h2_state(wsi, LWS_H2_STATE_OPEN);
+       lwsi_set_state(wsi, LRS_ESTABLISHED);
+
+       return 0;
+
+fail_length:
+       lwsl_err("Client hdrs too long: incr context info.pt_serv_buf_size\n");
+
+       return -1;
+}
+
+int
+lws_h2_ws_handshake(struct lws *wsi)
+{
+       uint8_t buf[LWS_PRE + 2048], *p = buf + LWS_PRE, *start = p,
+               *end = &buf[sizeof(buf) - 1];
+       const struct lws_http_mount *hit;
+       const char * uri_ptr;
+       int n, m;
+
+       if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
+               return -1;
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) > 64)
+               return -1;
+
+       if (wsi->proxied_ws_parent && wsi->child_list) {
+               if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) {
+                       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
+                               (uint8_t *)lws_hdr_simple_ptr(wsi,
+                                                          WSI_TOKEN_PROTOCOL),
+                               strlen(lws_hdr_simple_ptr(wsi,
+                                                          WSI_TOKEN_PROTOCOL)),
+                                                &p, end))
+                       return -1;
+               }
+       } else {
+
+               /* we can only return the protocol header if:
+                *  - one came in, and ... */
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
+                   /*  - it is not an empty string */
+                   wsi->protocol->name && wsi->protocol->name[0]) {
+                       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
+                                                (unsigned char *)wsi->protocol->name,
+                                                (int)strlen(wsi->protocol->name),
+                                                &p, end))
+                       return -1;
+               }
+       }
+
+       if (lws_finalize_http_header(wsi, &p, end))
+               return -1;
+
+       m = lws_ptr_diff(p, start);
+       // lwsl_hexdump_notice(start, m);
+       n = lws_write(wsi, start, m, LWS_WRITE_HTTP_HEADERS);
+       if (n != m) {
+               lwsl_err("_write returned %d from %d\n", n, m);
+
+               return -1;
+       }
+
+       /*
+        * alright clean up, set our state to generic ws established, the
+        * mode / state of the nwsi will get the h2 processing done.
+        */
+
+       lwsi_set_state(wsi, LRS_ESTABLISHED);
+       wsi->lws_rx_parse_state = 0; // ==LWS_RXPS_NEW;
+
+       uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH);
+       n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
+       hit = lws_find_mount(wsi, uri_ptr, n);
+
+       if (hit && hit->cgienv &&
+           wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, wsi->user_space,
+                                   (void *)hit->cgienv, 0))
+               return 1;
+
+       return 0;
+}
+
+int
+lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
+{
+       unsigned char *oldbuf = buf;
+       lws_filepos_t body_chunk_len;
+
+       // lwsl_notice("%s: h2 path: wsistate 0x%x len %d\n", __func__,
+       //              wsi->wsistate, (int)len);
+
+       /*
+        * wsi here is always the network connection wsi, not a stream
+        * wsi.  Once we unpicked the framing we will find the right
+        * swsi and make it the target of the frame.
+        *
+        * If it's ws over h2, the nwsi will get us here to do the h2
+        * processing, and that will call us back with the swsi +
+        * ESTABLISHED state for the inner payload, handled in a later
+        * case.
+        */
+       while (len) {
+               int m;
+
+               /*
+                * we were accepting input but now we stopped doing so
+                */
+               if (lws_is_flowcontrolled(wsi)) {
+                       lws_rxflow_cache(wsi, buf, 0, (int)len);
+                       buf += len;
+                       len = 0;
+                       break;
+               }
+
+               /*
+                * lws_h2_parser() may send something; when it gets the
+                * whole frame, it will want to perform some action
+                * involving a reply.  But we may be in a partial send
+                * situation on the network wsi...
+                *
+                * Even though we may be in a partial send and unable to
+                * send anything new, we still have to parse the network
+                * wsi in order to gain tx credit to send, which is
+                * potentially necessary to clear the old partial send.
+                *
+                * ALL network wsi-specific frames are sent by PPS
+                * already, these are sent as a priority on the writable
+                * handler, and so respect partial sends.  The only
+                * problem is when a stream wsi wants to send an, eg,
+                * reply headers frame in response to the parsing
+                * we will do now... the *stream wsi* must stall in a
+                * different state until it is able to do so from a
+                * priority on the WRITABLE callback, same way that
+                * file transfers operate.
+                */
+
+               m = lws_h2_parser(wsi, buf, len, &body_chunk_len);
+               if (m && m != 2) {
+                       lwsl_debug("%s: http2_parser bail: %d\n", __func__, m);
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "lws_read_h2 bail");
+
+                       return -1;
+               }
+               if (m == 2) {
+                       /* swsi has been closed */
+                       buf += body_chunk_len;
+                       len -= body_chunk_len;
+                       break;
+               }
+
+               buf += body_chunk_len;
+               len -= body_chunk_len;
+       }
+
+       return lws_ptr_diff(buf, oldbuf);
+}
+
similarity index 100%
rename from lib/huftable.h
rename to lib/roles/h2/huftable.h
similarity index 98%
rename from lib/minihuf.c
rename to lib/roles/h2/minihuf.c
index eaf84e5..ee19e37 100644 (file)
@@ -16,7 +16,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-#define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0]))
+#define LWS_ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0]))
 
 struct huf {
        unsigned int code;
@@ -340,7 +340,7 @@ int main(void)
        int fails = 0;
 
        m = 0;
-       while (m < ARRAY_SIZE(state)) {
+       while (m < LWS_ARRAY_SIZE(state)) {
                for (j = 0; j < PARALLEL; j++) {
                        state[m].state[j] = 0xffff;
                        state[m].terminal = 0;
@@ -348,7 +348,7 @@ int main(void)
                m++;
        }
 
-       while (n < ARRAY_SIZE(huf_literal)) {
+       while (n < LWS_ARRAY_SIZE(huf_literal)) {
 
                m = 0;
                walk = 0;
@@ -474,7 +474,7 @@ again:
         * Try to parse every legal input string
         */
 
-       for (n = 0; n < ARRAY_SIZE(huf_literal); n++) {
+       for (n = 0; n < LWS_ARRAY_SIZE(huf_literal); n++) {
                walk = 0;
                m = 0;
                y = -1;
diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c
new file mode 100644 (file)
index 0000000..eb87fce
--- /dev/null
@@ -0,0 +1,1247 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+/*
+ * These are the standardized defaults.
+ * Override what actually goes in the vhost settings in platform or user code.
+ * Leave these alone because they are used to determine "what is different
+ * from the protocol defaults".
+ */
+const struct http2_settings lws_h2_defaults = { {
+       1,
+       /* H2SET_HEADER_TABLE_SIZE */                   4096,
+       /* *** This controls how many entries in the dynamic table ***
+        * Allows the sender to inform the remote endpoint of the maximum
+        * size of the header compression table used to decode header
+        * blocks, in octets.  The encoder can select any size equal to or
+        * less than this value by using signaling specific to the header
+        * compression format inside a header block (see [COMPRESSION]).
+        * The initial value is 4,096 octets.
+        */
+       /* H2SET_ENABLE_PUSH */                            1,
+       /* H2SET_MAX_CONCURRENT_STREAMS */        0x7fffffff,
+       /* H2SET_INITIAL_WINDOW_SIZE */                65535,
+       /* H2SET_MAX_FRAME_SIZE */                     16384,
+       /* H2SET_MAX_HEADER_LIST_SIZE */          0x7fffffff,
+       /*< This advisory setting informs a peer of the maximum size of
+        * header list that the sender is prepared to accept, in octets.
+        * The value is based on the uncompressed size of header fields,
+        * including the length of the name and value in octets plus an
+        * overhead of 32 octets for each header field.
+        */
+       /* H2SET_RESERVED7 */                              0,
+       /* H2SET_ENABLE_CONNECT_PROTOCOL */                0,
+}};
+
+/* these are the "lws defaults"... they can be overridden in plat */
+
+const struct http2_settings lws_h2_stock_settings = { {
+       1,
+       /* H2SET_HEADER_TABLE_SIZE */                   65536, /* ffox */
+       /* *** This controls how many entries in the dynamic table ***
+        * Allows the sender to inform the remote endpoint of the maximum
+        * size of the header compression table used to decode header
+        * blocks, in octets.  The encoder can select any size equal to or
+        * less than this value by using signaling specific to the header
+        * compression format inside a header block (see [COMPRESSION]).
+        * The initial value is 4,096 octets.
+        *
+        * Can't pass h2spec with less than 4096 here...
+        */
+       /* H2SET_ENABLE_PUSH */                            1,
+       /* H2SET_MAX_CONCURRENT_STREAMS */                24,
+       /* H2SET_INITIAL_WINDOW_SIZE */                65535,
+       /* H2SET_MAX_FRAME_SIZE */                     16384,
+       /* H2SET_MAX_HEADER_LIST_SIZE */                4096,
+       /*< This advisory setting informs a peer of the maximum size of
+        * header list that the sender is prepared to accept, in octets.
+        * The value is based on the uncompressed size of header fields,
+        * including the length of the name and value in octets plus an
+        * overhead of 32 octets for each header field.
+        */
+       /* H2SET_RESERVED7 */                              0,
+       /* H2SET_ENABLE_CONNECT_PROTOCOL */                1,
+}};
+
+/*
+ * The wsi at this level is the network wsi
+ */
+
+static int
+rops_handle_POLLIN_h2(struct lws_context_per_thread *pt, struct lws *wsi,
+                      struct lws_pollfd *pollfd)
+{
+       struct lws_tokens ebuf;
+       unsigned int pending = 0;
+       char buffered = 0;
+       struct lws *wsi1;
+       int n, m;
+
+#ifdef LWS_WITH_CGI
+       if (wsi->http.cgi && (pollfd->revents & LWS_POLLOUT)) {
+               if (lws_handle_POLLOUT_event(wsi, pollfd))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+        lwsl_info("%s: wsistate 0x%x, pollout %d\n", __func__,
+                  wsi->wsistate, pollfd->revents & LWS_POLLOUT);
+
+       /*
+        * something went wrong with parsing the handshake, and
+        * we ended up back in the event loop without completing it
+        */
+       if (lwsi_state(wsi) == LRS_PRE_WS_SERVING_ACCEPT) {
+               wsi->socket_is_permanently_unusable = 1;
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       if (lwsi_state(wsi) == LRS_WAITING_CONNECT) {
+#if !defined(LWS_NO_CLIENT)
+               if ((pollfd->revents & LWS_POLLOUT) &&
+                   lws_handle_POLLOUT_event(wsi, pollfd)) {
+                       lwsl_debug("POLLOUT event closed it\n");
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               n = lws_client_socket_service(wsi, pollfd, NULL);
+               if (n)
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+#endif
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       /* 1: something requested a callback when it was OK to write */
+
+       if ((pollfd->revents & LWS_POLLOUT) &&
+           lwsi_state_can_handle_POLLOUT(wsi) &&
+           lws_handle_POLLOUT_event(wsi, pollfd)) {
+               if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+                       lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE);
+               /* the write failed... it's had it */
+               wsi->socket_is_permanently_unusable = 1;
+
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+           lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE ||
+           lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
+               /*
+                * we stopped caring about anything except control
+                * packets.  Force flow control off, defeat tx
+                * draining.
+                */
+               lws_rx_flow_control(wsi, 1);
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws)
+                       wsi->ws->tx_draining_ext = 0;
+#endif
+       }
+
+       if (wsi->http2_substream || wsi->upgraded_to_http2) {
+               wsi1 = lws_get_network_wsi(wsi);
+               if (wsi1 && lws_has_buffered_out(wsi1))
+                       /*
+                        * We cannot deal with any kind of new RX
+                        * because we are dealing with a partial send
+                        * (new RX may trigger new http_action() that
+                        * expect to be able to send)
+                        */
+                       return LWS_HPI_RET_HANDLED;
+       }
+
+read:
+       /* 3: network wsi buflist needs to be drained */
+
+       // lws_buflist_describe(&wsi->buflist, wsi);
+
+       ebuf.len = (int)lws_buflist_next_segment_len(&wsi->buflist,
+                                               &ebuf.token);
+       if (ebuf.len) {
+               lwsl_info("draining buflist (len %d)\n", ebuf.len);
+               buffered = 1;
+               goto drain;
+       }
+
+       if (!lws_ssl_pending(wsi) &&
+           !(pollfd->revents & pollfd->events & LWS_POLLIN))
+               return LWS_HPI_RET_HANDLED;
+
+       if (!(lwsi_role_client(wsi) &&
+             (lwsi_state(wsi) != LRS_ESTABLISHED &&
+              lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) {
+
+               ebuf.token = pt->serv_buf;
+               ebuf.len = lws_ssl_capable_read(wsi,
+                                       ebuf.token,
+                                       wsi->context->pt_serv_buf_size);
+               switch (ebuf.len) {
+               case 0:
+                       lwsl_info("%s: zero length read\n", __func__);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       lwsl_info("SSL Capable more service\n");
+                       return LWS_HPI_RET_HANDLED;
+               case LWS_SSL_CAPABLE_ERROR:
+                       lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n", __func__);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               // lwsl_notice("%s: Actual RX %d\n", __func__, ebuf.len);
+               // if (ebuf.len > 0)
+               //      lwsl_hexdump_notice(ebuf.token, ebuf.len);
+       }
+
+       if (ebuf.len < 0)
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+drain:
+#ifndef LWS_NO_CLIENT
+       if (lwsi_role_http(wsi) && lwsi_role_client(wsi) &&
+           wsi->hdr_parsing_completed && !wsi->told_user_closed) {
+
+               /*
+                * In SSL mode we get POLLIN notification about
+                * encrypted data in.
+                *
+                * But that is not necessarily related to decrypted
+                * data out becoming available; in may need to perform
+                * other in or out before that happens.
+                *
+                * simply mark ourselves as having readable data
+                * and turn off our POLLIN
+                */
+               wsi->client_rx_avail = 1;
+               if (lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               /* let user code know, he'll usually ask for writeable
+                * callback and drain / re-enable it there
+                */
+               if (user_callback_handle_rxflow(
+                               wsi->protocol->callback,
+                               wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
+                               wsi->user_space, NULL, 0)) {
+                       lwsl_info("RECEIVE_CLIENT_HTTP closed it\n");
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+       /* service incoming data */
+
+       if (ebuf.len) {
+               n = 0;
+               if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY &&
+                   lwsi_state(wsi) != LRS_DISCARD_BODY)
+                       n = lws_read_h2(wsi, ebuf.token, ebuf.len);
+               else
+                       n = lws_read_h1(wsi, ebuf.token, ebuf.len);
+
+               if (n < 0) {
+                       /* we closed wsi */
+                       n = 0;
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+               }
+
+               if (n && buffered) {
+                       m = lws_buflist_use_segment(&wsi->buflist, n);
+                       lwsl_info("%s: draining rxflow: used %d, next %d\n",
+                                   __func__, n, m);
+                       if (!m) {
+                               lwsl_notice("%s: removed %p from dll_buflist\n",
+                                           __func__, wsi);
+                               lws_dll2_remove(&wsi->dll_buflist);
+                       }
+               } else
+                       if (n && n != ebuf.len) {
+                               m = lws_buflist_append_segment(&wsi->buflist,
+                                               ebuf.token + n,
+                                               ebuf.len - n);
+                               if (m < 0)
+                                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+                               if (m) {
+                                       lwsl_debug("%s: added %p to rxflow list\n",
+                                                       __func__, wsi);
+                                       lws_dll2_add_head(&wsi->dll_buflist,
+                                                        &pt->dll_buflist_owner);
+                               }
+                       }
+       }
+
+       // lws_buflist_describe(&wsi->buflist, wsi);
+
+#if 0
+
+       /*
+        * This seems to be too aggressive... we don't want the ah stuck
+        * there but eg, WINDOW_UPDATE may come and detach it if we leave
+        * it like that... it will get detached at stream close
+        */
+
+       if (wsi->http.ah
+#if !defined(LWS_NO_CLIENT)
+                       && !wsi->client_h2_alpn
+#endif
+                       ) {
+               lwsl_err("xxx\n");
+
+               lws_header_table_detach(wsi, 0);
+       }
+#endif
+
+       pending = lws_ssl_pending(wsi);
+       if (pending) {
+               // lwsl_info("going around\n");
+               goto read;
+       }
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+int rops_handle_POLLOUT_h2(struct lws *wsi)
+{
+       // lwsl_notice("%s\n", __func__);
+
+       if (lwsi_state(wsi) == LRS_ISSUE_HTTP_BODY)
+               return LWS_HP_RET_USER_SERVICE;
+
+       /*
+        * Priority 2: H2 protocol packets
+        */
+       if ((wsi->upgraded_to_http2
+#if !defined(LWS_NO_CLIENT)
+                       || wsi->client_h2_alpn
+#endif
+                       ) && wsi->h2.h2n->pps) {
+               lwsl_info("servicing pps\n");
+               /*
+                * this is called on the network connection, but may close
+                * substreams... that may affect callers
+                */
+               if (lws_h2_do_pps_send(wsi)) {
+                       wsi->socket_is_permanently_unusable = 1;
+                       return LWS_HP_RET_BAIL_DIE;
+               }
+               if (wsi->h2.h2n->pps)
+                       return LWS_HP_RET_BAIL_OK;
+
+               /* we can resume whatever we were doing */
+               lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_ENABLE |
+                                        LWS_RXFLOW_REASON_H2_PPS_PENDING);
+
+               return LWS_HP_RET_BAIL_OK; /* leave POLLOUT active */
+       }
+
+       /* Priority 4: if we are closing, not allowed to send more data frags
+        *             which means user callback or tx ext flush banned now
+        */
+       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+               return LWS_HP_RET_USER_SERVICE;
+
+       return LWS_HP_RET_USER_SERVICE;
+}
+
+static int
+rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len,
+                           enum lws_write_protocol *wp)
+{
+       unsigned char flags = 0, base = (*wp) & 0x1f;
+       size_t olen = len;
+       int n;
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       unsigned char mtubuf[4096 + LWS_PRE];
+#endif
+
+       /* if not in a state to send stuff, then just send nothing */
+
+       if (!lwsi_role_ws(wsi) &&
+           base != LWS_WRITE_HTTP &&
+           base != LWS_WRITE_HTTP_FINAL &&
+           base != LWS_WRITE_HTTP_HEADERS_CONTINUATION &&
+           base != LWS_WRITE_HTTP_HEADERS &&
+           ((lwsi_state(wsi) != LRS_RETURNED_CLOSE &&
+             lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE &&
+             lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK)
+#if defined(LWS_ROLE_WS)
+          || base != LWS_WRITE_CLOSE
+#endif
+       )) {
+               //assert(0);
+               lwsl_notice("binning wsistate 0x%x %d\n", wsi->wsistate, *wp);
+               return 0;
+       }
+
+       /* compression transform... */
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.lcs) {
+               unsigned char *out = mtubuf + LWS_PRE;
+               size_t o = sizeof(mtubuf) - LWS_PRE;
+
+               n = lws_http_compression_transform(wsi, buf, len, wp, &out, &o);
+               if (n)
+                       return n;
+
+               lwsl_info("%s: %p: transformed %d bytes to %d "
+                          "(wp 0x%x, more %d)\n", __func__,
+                          wsi, (int)len, (int)o, (int)*wp,
+                          wsi->http.comp_ctx.may_have_more);
+
+               buf = out;
+               len = o;
+               base = (*wp) & 0x1f;
+
+               if (!len)
+                       return olen;
+       }
+#endif
+
+       /*
+        * ws-over-h2 also ends up here after the ws framing applied
+        */
+
+       n = LWS_H2_FRAME_TYPE_DATA;
+       if (base == LWS_WRITE_HTTP_HEADERS) {
+               n = LWS_H2_FRAME_TYPE_HEADERS;
+               if (!((*wp) & LWS_WRITE_NO_FIN))
+                       flags = LWS_H2_FLAG_END_HEADERS;
+               if (wsi->h2.send_END_STREAM ||
+                   ((*wp) & LWS_WRITE_H2_STREAM_END)) {
+                       flags |= LWS_H2_FLAG_END_STREAM;
+                       wsi->h2.send_END_STREAM = 1;
+               }
+       }
+
+       if (base == LWS_WRITE_HTTP_HEADERS_CONTINUATION) {
+               n = LWS_H2_FRAME_TYPE_CONTINUATION;
+               if (!((*wp) & LWS_WRITE_NO_FIN))
+                       flags = LWS_H2_FLAG_END_HEADERS;
+               if (wsi->h2.send_END_STREAM ||
+                   ((*wp) & LWS_WRITE_H2_STREAM_END)) {
+                       flags |= LWS_H2_FLAG_END_STREAM;
+                       wsi->h2.send_END_STREAM = 1;
+               }
+       }
+
+       if ((base == LWS_WRITE_HTTP ||
+            base == LWS_WRITE_HTTP_FINAL) &&
+            wsi->http.tx_content_length) {
+               wsi->http.tx_content_remain -= len;
+               lwsl_info("%s: wsi %p: tx_content_rem = %llu\n", __func__, wsi,
+                         (unsigned long long)wsi->http.tx_content_remain);
+               if (!wsi->http.tx_content_remain) {
+                       lwsl_info("%s: selecting final write mode\n", __func__);
+                       base = *wp = LWS_WRITE_HTTP_FINAL;
+               }
+       }
+
+       if (base == LWS_WRITE_HTTP_FINAL || ((*wp) & LWS_WRITE_H2_STREAM_END)) {
+               lwsl_info("%s: %p: setting END_STREAM\n", __func__, wsi);
+               flags |= LWS_H2_FLAG_END_STREAM;
+               wsi->h2.send_END_STREAM = 1;
+       }
+
+       n = lws_h2_frame_write(wsi, n, flags, wsi->h2.my_sid, (int)len, buf);
+       if (n < 0)
+               return n;
+
+       /* hide it may have been compressed... */
+
+       return (int)olen;
+}
+
+static int
+rops_check_upgrades_h2(struct lws *wsi)
+{
+#if defined(LWS_ROLE_WS)
+       struct lws *nwsi;
+       char *p;
+
+       /*
+        * with H2 there's also a way to upgrade a stream to something
+        * else... :method is CONNECT and :protocol says the name of
+        * the new protocol we want to carry.  We have to have sent a
+        * SETTINGS saying that we support it though.
+        */
+       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD);
+       if (!wsi->vhost->h2.set.s[H2SET_ENABLE_CONNECT_PROTOCOL] ||
+           !wsi->http2_substream || !p || strcmp(p, "CONNECT"))
+               return LWS_UPG_RET_CONTINUE;
+
+       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_COLON_PROTOCOL);
+       if (!p || strcmp(p, "websocket"))
+               return LWS_UPG_RET_CONTINUE;
+
+       nwsi = lws_get_network_wsi(wsi);
+
+       wsi->vhost->conn_stats.ws_upg++;
+       lwsl_info("Upgrade h2 to ws\n");
+       wsi->h2_stream_carries_ws = 1;
+       nwsi->immortal_substream_count++;
+       if (lws_process_ws_upgrade(wsi))
+               return LWS_UPG_RET_BAIL;
+
+       if (nwsi->immortal_substream_count == 1)
+               lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0);
+
+       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+       lwsl_info("Upgraded h2 to ws OK\n");
+
+       return LWS_UPG_RET_DONE;
+#else
+       return LWS_UPG_RET_CONTINUE;
+#endif
+}
+
+static int
+rops_init_vhost_h2(struct lws_vhost *vh,
+                  const struct lws_context_creation_info *info)
+{
+       vh->h2.set = vh->context->set;
+       if (info->http2_settings[0]) {
+               int n;
+
+               for (n = 1; n < LWS_H2_SETTINGS_LEN; n++)
+                       vh->h2.set.s[n] = info->http2_settings[n];
+       }
+
+       return 0;
+}
+
+static int
+rops_init_context_h2(struct lws_context *context,
+                    const struct lws_context_creation_info *info)
+{
+       int n;
+
+       context->set = lws_h2_stock_settings;
+
+       /*
+        * We only want to do this once... we will do it if we are built
+        * otherwise h1 ops will do it (or nobody if no http at all)
+        */
+
+       for (n = 0; n < context->count_threads; n++) {
+               struct lws_context_per_thread *pt = &context->pt[n];
+
+               pt->sul_ah_lifecheck.cb = lws_sul_http_ah_lifecheck;
+
+               __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_ah_lifecheck,
+                                30 * LWS_US_PER_SEC);
+       }
+
+       return 0;
+}
+
+static lws_fileofs_t
+rops_tx_credit_h2(struct lws *wsi)
+{
+       return lws_h2_tx_cr_get(wsi);
+}
+
+static int
+rops_destroy_role_h2(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct allocated_headers *ah;
+
+       /* we may not have an ah, but may be on the waiting list... */
+       lwsl_info("%s: ah det due to close\n", __func__);
+       __lws_header_table_detach(wsi, 0);
+
+       ah = pt->http.ah_list;
+
+       while (ah) {
+               if (ah->in_use && ah->wsi == wsi) {
+                       lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi);
+                       ah->in_use = 0;
+                       ah->wsi = NULL;
+                       pt->http.ah_count_in_use--;
+                       break;
+               }
+               ah = ah->next;
+       }
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       lws_http_compression_destroy(wsi);
+#endif
+
+       if (wsi->upgraded_to_http2 || wsi->http2_substream) {
+               lws_hpack_destroy_dynamic_header(wsi);
+
+               if (wsi->h2.h2n)
+                       lws_free_set_NULL(wsi->h2.h2n);
+       }
+
+       return 0;
+}
+
+static int
+rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason)
+{
+       struct lws *wsi2;
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       if (wsi->http.proxy_clientside) {
+               struct lws *wsi_eff = lws_client_wsi_effective(wsi);
+
+               wsi->http.proxy_clientside = 0;
+
+               if (user_callback_handle_rxflow(wsi_eff->protocol->callback,
+                                               wsi_eff,
+                                           LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
+                                               wsi_eff->user_space, NULL, 0))
+                       wsi->http.proxy_clientside = 0;
+       }
+#endif
+
+       if (wsi->http2_substream && wsi->h2_stream_carries_ws)
+               lws_h2_rst_stream(wsi, 0, "none");
+
+       if (wsi->h2.parent_wsi && lwsl_visible(LLL_INFO)) {
+               lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi,
+                         wsi->h2.parent_wsi);
+               lws_start_foreach_llp(struct lws **, w,
+                                     wsi->h2.parent_wsi->h2.child_list) {
+                       lwsl_info("   \\---- child %s %p\n",
+                                 (*w)->role_ops ? (*w)->role_ops->name : "?", *w);
+               } lws_end_foreach_llp(w, h2.sibling_list);
+       }
+
+       if (wsi->upgraded_to_http2 || wsi->http2_substream
+#if !defined(LWS_NO_CLIENT)
+                       || wsi->client_h2_substream
+#endif
+       ) {
+               lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi);
+
+               if (wsi->h2.child_list && lwsl_visible(LLL_INFO)) {
+                       lwsl_info(" parent %p: closing children: list:\n", wsi);
+                       lws_start_foreach_llp(struct lws **, w,
+                                             wsi->h2.child_list) {
+                               lwsl_info("   \\---- child %s %p\n",
+                                         (*w)->role_ops ? (*w)->role_ops->name : "?",
+                                         *w);
+                       } lws_end_foreach_llp(w, h2.sibling_list);
+               }
+               if (wsi->h2.child_list) {
+                       /* trigger closing of all of our http2 children first */
+                       lws_start_foreach_llp(struct lws **, w,
+                                             wsi->h2.child_list) {
+                               lwsl_info("   closing child %p\n", *w);
+                               /* disconnect from siblings */
+                               wsi2 = (*w)->h2.sibling_list;
+                               (*w)->h2.sibling_list = NULL;
+                               (*w)->socket_is_permanently_unusable = 1;
+                               __lws_close_free_wsi(*w, reason, "h2 child recurse");
+                               *w = wsi2;
+                               continue;
+                       } lws_end_foreach_llp(w, h2.sibling_list);
+               }
+       }
+
+       if (wsi->upgraded_to_http2) {
+               /* remove pps */
+               struct lws_h2_protocol_send *w = wsi->h2.h2n->pps, *w1;
+
+               while (w) {
+                       w1 = w->next;
+                       free(w);
+                       w = w1;
+               }
+               wsi->h2.h2n->pps = NULL;
+       }
+
+       if ((
+#if !defined(LWS_NO_CLIENT)
+                       wsi->client_h2_substream ||
+#endif
+                       wsi->http2_substream) &&
+            wsi->h2.parent_wsi) {
+               lwsl_info("  %p: disentangling from siblings\n", wsi);
+               lws_start_foreach_llp(struct lws **, w,
+                               wsi->h2.parent_wsi->h2.child_list) {
+                       /* disconnect from siblings */
+                       if (*w == wsi) {
+                               wsi2 = (*w)->h2.sibling_list;
+                               (*w)->h2.sibling_list = NULL;
+                               *w = wsi2;
+                               lwsl_info("  %p disentangled from sibling %p\n",
+                                         wsi, wsi2);
+                               break;
+                       }
+               } lws_end_foreach_llp(w, h2.sibling_list);
+               wsi->h2.parent_wsi->h2.child_count--;
+               wsi->h2.parent_wsi = NULL;
+               if (wsi->h2.pending_status_body)
+                       lws_free_set_NULL(wsi->h2.pending_status_body);
+       }
+
+       if (wsi->h2_stream_carries_ws || wsi->h2_stream_carries_sse) {
+               struct lws *nwsi = lws_get_network_wsi(wsi);
+
+               nwsi->immortal_substream_count--;
+               /* if no ws, then put a timeout on the parent wsi */
+               if (!nwsi->immortal_substream_count)
+                       __lws_set_timeout(nwsi,
+                               PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
+       }
+
+       return 0;
+}
+
+static int
+rops_callback_on_writable_h2(struct lws *wsi)
+{
+       struct lws *network_wsi, *wsi2;
+       int already;
+
+       //lwsl_notice("%s: %p (wsistate 0x%x)\n", __func__, wsi, wsi->wsistate);
+
+//     if (!lwsi_role_h2(wsi) && !lwsi_role_h2_ENCAPSULATION(wsi))
+//             return 0;
+
+       if (wsi->h2.requested_POLLOUT
+#if !defined(LWS_NO_CLIENT)
+                       && !wsi->client_h2_alpn
+#endif
+       ) {
+               lwsl_debug("already pending writable\n");
+               return 1;
+       }
+
+       /* is this for DATA or for control messages? */
+       if (wsi->upgraded_to_http2 && !wsi->h2.h2n->pps &&
+           !lws_h2_tx_cr_get(wsi)) {
+               /*
+                * other side is not able to cope with us sending DATA
+                * anything so no matter if we have POLLOUT on our side if it's
+                * DATA we want to send.
+                *
+                * Delay waiting for our POLLOUT until peer indicates he has
+                * space for more using tx window command in http2 layer
+                */
+               lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi,
+                           wsi->h2.tx_cr);
+               wsi->h2.skint = 1;
+               return 0;
+       }
+
+       wsi->h2.skint = 0;
+       network_wsi = lws_get_network_wsi(wsi);
+       already = network_wsi->h2.requested_POLLOUT;
+
+       /* mark everybody above him as requesting pollout */
+
+       wsi2 = wsi;
+       while (wsi2) {
+               wsi2->h2.requested_POLLOUT = 1;
+               lwsl_info("mark %p pending writable\n", wsi2);
+               wsi2 = wsi2->h2.parent_wsi;
+       }
+
+       /* for network action, act only on the network wsi */
+
+       if (already
+#if !defined(LWS_NO_CLIENT)
+                       && !network_wsi->client_h2_alpn
+                       && !network_wsi->client_h2_substream
+#endif
+                       )
+               return 1;
+
+       return 0;
+}
+
+static void
+lws_h2_dump_waiting_children(struct lws *wsi)
+{
+#if defined(_DEBUG)
+       lwsl_info("%s: %p: children waiting for POLLOUT service:\n",
+                 __func__, wsi);
+
+       wsi = wsi->h2.child_list;
+       while (wsi) {
+               lwsl_info("  %c %p %s %s\n",
+                         wsi->h2.requested_POLLOUT ? '*' : ' ',
+                         wsi, wsi->role_ops->name, wsi->protocol->name);
+
+               wsi = wsi->h2.sibling_list;
+       }
+#endif
+}
+
+static int
+lws_h2_bind_for_post_before_action(struct lws *wsi)
+{
+       const char *p;
+
+       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD);
+       if (p && !strcmp(p, "POST")) {
+               const struct lws_http_mount *hit =
+                               lws_find_mount(wsi,
+                                       lws_hdr_simple_ptr(wsi,
+                                           WSI_TOKEN_HTTP_COLON_PATH),
+                                       lws_hdr_total_length(wsi,
+                                           WSI_TOKEN_HTTP_COLON_PATH));
+
+               lwsl_debug("%s: %s: hit %p: %s\n", __func__,
+                           lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH),
+                           hit, hit ? hit->origin : "null");
+               if (hit) {
+                       const struct lws_protocols *pp;
+                       const char *name = hit->origin;
+
+                       if (hit->protocol)
+                               name = hit->protocol;
+
+                       pp = lws_vhost_name_to_protocol(wsi->vhost, name);
+                       if (!pp) {
+                               lwsl_info("Unable to find protocol '%s'\n", name);
+                               return 1;
+                       }
+
+                       if (lws_bind_protocol(wsi, pp, __func__))
+                               return 1;
+               }
+
+               lwsl_info("%s: setting LRS_BODY from 0x%x (%s)\n", __func__,
+                           wsi->wsistate, wsi->protocol->name);
+               lwsi_set_state(wsi, LRS_BODY);
+       }
+
+       return 0;
+}
+
+/*
+ * we are the 'network wsi' for potentially many muxed child wsi with
+ * no network connection of their own, who have to use us for all their
+ * network actions.  So we use a round-robin scheme to share out the
+ * POLLOUT notifications to our children.
+ *
+ * But because any child could exhaust the socket's ability to take
+ * writes, we can only let one child get notified each time.
+ *
+ * In addition children may be closed / deleted / added between POLLOUT
+ * notifications, so we can't hold pointers
+ */
+
+static int
+rops_perform_user_POLLOUT_h2(struct lws *wsi)
+{
+       struct lws **wsi2, *wsi2a;
+#if defined(LWS_ROLE_WS)
+       int write_type = LWS_WRITE_PONG;
+#endif
+       int n;
+
+       wsi = lws_get_network_wsi(wsi);
+
+       wsi->h2.requested_POLLOUT = 0;
+       if (!wsi->h2.initialized) {
+               lwsl_info("pollout on uninitialized http2 conn\n");
+               return 0;
+       }
+
+       lws_h2_dump_waiting_children(wsi);
+
+       wsi2 = &wsi->h2.child_list;
+       if (!*wsi2)
+               return 0;
+
+       do {
+               struct lws *w, **wa;
+
+               wa = &(*wsi2)->h2.sibling_list;
+               if (!(*wsi2)->h2.requested_POLLOUT)
+                       goto next_child;
+
+               /*
+                * we're going to do writable callback for this child.
+                * move him to be the last child
+                */
+
+               lwsl_debug("servicing child %p\n", *wsi2);
+
+               w = *wsi2;
+               while (w) {
+                       if (!w->h2.sibling_list) { /* w is the current last */
+                               lwsl_debug("w=%p, *wsi2 = %p\n", w, *wsi2);
+                               if (w == *wsi2) /* we are already last */
+                                       break;
+                               /* last points to us as new last */
+                               w->h2.sibling_list = *wsi2;
+                               /* guy pointing to us until now points to
+                                * our old next */
+                               *wsi2 = (*wsi2)->h2.sibling_list;
+                               /* we point to nothing because we are last */
+                               w->h2.sibling_list->h2.sibling_list = NULL;
+                               /* w becomes us */
+                               w = w->h2.sibling_list;
+                               break;
+                       }
+                       w = w->h2.sibling_list;
+               }
+
+               if (!w) {
+                       wa = &wsi->h2.child_list;
+                       goto next_child;
+               }
+
+               w->h2.requested_POLLOUT = 0;
+               lwsl_info("%s: child %p (wsistate 0x%x)\n", __func__, w,
+                         w->wsistate);
+
+               /* priority 1: post compression-transform buffered output */
+
+               if (lws_has_buffered_out(w)) {
+                       lwsl_debug("%s: completing partial\n", __func__);
+                       if (lws_issue_raw(w, NULL, 0) < 0) {
+                               lwsl_info("%s signalling to close\n", __func__);
+                               lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                                  "h2 end stream 1");
+                               wa = &wsi->h2.child_list;
+                               goto next_child;
+                       }
+                       lws_callback_on_writable(w);
+                       wa = &wsi->h2.child_list;
+                       goto next_child;
+               }
+
+               /* priority 2: pre compression-transform buffered output */
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+               if (w->http.comp_ctx.buflist_comp ||
+                   w->http.comp_ctx.may_have_more) {
+                       enum lws_write_protocol wp = LWS_WRITE_HTTP;
+
+                       lwsl_info("%s: completing comp partial"
+                                  "(buflist_comp %p, may %d)\n",
+                                  __func__, w->http.comp_ctx.buflist_comp,
+                                   w->http.comp_ctx.may_have_more);
+
+                       if (rops_write_role_protocol_h2(w, NULL, 0, &wp) < 0) {
+                               lwsl_info("%s signalling to close\n", __func__);
+                               lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                                  "comp write fail");
+                       }
+                       lws_callback_on_writable(w);
+                       wa = &wsi->h2.child_list;
+                       goto next_child;
+               }
+#endif
+
+               /* priority 3: if no buffered out and waiting for that... */
+
+               if (lwsi_state(w) == LRS_FLUSHING_BEFORE_CLOSE) {
+                       w->socket_is_permanently_unusable = 1;
+                       lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "h2 end stream 1");
+                       wa = &wsi->h2.child_list;
+                       goto next_child;
+               }
+
+               /* if we arrived here, even by looping, we checked choked */
+               w->could_have_pending = 0;
+               wsi->could_have_pending = 0;
+
+               if (w->h2.pending_status_body) {
+                       w->h2.send_END_STREAM = 1;
+                       n = lws_write(w, (uint8_t *)w->h2.pending_status_body +
+                                        LWS_PRE,
+                                        strlen(w->h2.pending_status_body +
+                                               LWS_PRE), LWS_WRITE_HTTP_FINAL);
+                       lws_free_set_NULL(w->h2.pending_status_body);
+                       lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "h2 end stream 1");
+                       wa = &wsi->h2.child_list;
+                       goto next_child;
+               }
+
+               if (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS) {
+                       if (lws_h2_client_handshake(w))
+                               return -1;
+
+                       goto next_child;
+               }
+
+               if (lwsi_state(w) == LRS_DEFERRING_ACTION) {
+
+                       /*
+                        * we had to defer the http_action to the POLLOUT
+                        * handler, because we know it will send something and
+                        * only in the POLLOUT handler do we know for sure
+                        * that there is no partial pending on the network wsi.
+                        */
+
+                       lwsi_set_state(w, LRS_ESTABLISHED);
+
+                       lws_h2_bind_for_post_before_action(w);
+
+                       lwsl_info("  h2 action start...\n");
+                       n = lws_http_action(w);
+                       if (n < 0)
+                               lwsl_info ("   h2 action result %d\n", n);
+                       else
+                       lwsl_info("  h2 action result %d "
+                                 "(wsi->http.rx_content_remain %lld)\n",
+                                 n, w->http.rx_content_remain);
+
+                       /*
+                        * Commonly we only managed to start a larger transfer
+                        * that will complete asynchronously under its own wsi
+                        * states.  In those cases we will hear about
+                        * END_STREAM going out in the POLLOUT handler.
+                        */
+                       if (n >= 0 && !w->h2.pending_status_body &&
+                           (n || w->h2.send_END_STREAM)) {
+                               lwsl_info("closing stream after h2 action\n");
+                               lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                                  "h2 end stream");
+                               wa = &wsi->h2.child_list;
+                       }
+
+                       if (n < 0)
+                               wa = &wsi->h2.child_list;
+
+                       goto next_child;
+               }
+
+               if (lwsi_state(w) == LRS_ISSUING_FILE) {
+
+                       ((volatile struct lws *)w)->leave_pollout_active = 0;
+
+                       /* >0 == completion, <0 == error
+                        *
+                        * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION
+                        * callback when it's done.  That's the case even if we
+                        * just completed the send, so wait for that.
+                        */
+                       n = lws_serve_http_file_fragment(w);
+                       lwsl_debug("lws_serve_http_file_fragment says %d\n", n);
+
+                       /*
+                        * We will often hear about out having sent the final
+                        * DATA here... if so close the actual wsi
+                        */
+                       if (n < 0 || w->h2.send_END_STREAM) {
+                               lwsl_debug("Closing POLLOUT child %p\n", w);
+                               lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                                  "h2 end stream file");
+                               wa = &wsi->h2.child_list;
+                               goto next_child;
+                       }
+                       if (n > 0)
+                               if (lws_http_transaction_completed(w))
+                                       return -1;
+                       if (!n) {
+                               lws_callback_on_writable(w);
+                               (w)->h2.requested_POLLOUT = 1;
+                       }
+
+                       goto next_child;
+               }
+
+#if defined(LWS_ROLE_WS)
+
+               /* Notify peer that we decided to close */
+
+               if (lwsi_role_ws(w) &&
+                   lwsi_state(w) == LRS_WAITING_TO_SEND_CLOSE) {
+                       lwsl_debug("sending close packet\n");
+                       w->waiting_to_send_close_frame = 0;
+                       n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE],
+                                     w->ws->close_in_ping_buffer_len,
+                                     LWS_WRITE_CLOSE);
+                       if (n >= 0) {
+                               lwsi_set_state(w, LRS_AWAITING_CLOSE_ACK);
+                               lws_set_timeout(w, PENDING_TIMEOUT_CLOSE_ACK, 5);
+                               lwsl_debug("sent close frame, awaiting ack\n");
+                       }
+
+                       goto next_child;
+               }
+
+               /*
+                * Acknowledge receipt of peer's notification he closed,
+                * then logically close ourself
+                */
+
+               if ((lwsi_role_ws(w) && w->ws->ping_pending_flag) ||
+                   (lwsi_state(w) == LRS_RETURNED_CLOSE &&
+                    w->ws->payload_is_close)) {
+
+                       if (w->ws->payload_is_close)
+                               write_type = LWS_WRITE_CLOSE |
+                                            LWS_WRITE_H2_STREAM_END;
+
+                       n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE],
+                                     w->ws->ping_payload_len, write_type);
+                       if (n < 0)
+                               return -1;
+
+                       /* well he is sent, mark him done */
+                       w->ws->ping_pending_flag = 0;
+                       if (w->ws->payload_is_close) {
+                               /* oh... a close frame... then we are done */
+                               lwsl_debug("Ack'd peer's close packet\n");
+                               w->ws->payload_is_close = 0;
+                               lwsi_set_state(w, LRS_RETURNED_CLOSE);
+                               lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                                  "returned close packet");
+                               wa = &wsi->h2.child_list;
+                               goto next_child;
+                       }
+
+                       lws_callback_on_writable(w);
+                       (w)->h2.requested_POLLOUT = 1;
+
+                       /* otherwise for PING, leave POLLOUT active both ways */
+                       goto next_child;
+               }
+#endif
+               if (lws_callback_as_writeable(w)) {
+                       lwsl_info("Closing POLLOUT child (end stream %d)\n",
+                                 w->h2.send_END_STREAM);
+                       lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "h2 pollout handle");
+                       wa = &wsi->h2.child_list;
+               } else
+                        if (w->h2.send_END_STREAM)
+                               lws_h2_state(w, LWS_H2_STATE_HALF_CLOSED_LOCAL);
+
+next_child:
+               wsi2 = wa;
+       } while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi));
+
+       // lws_h2_dump_waiting_children(wsi);
+
+       wsi2a = wsi->h2.child_list;
+       while (wsi2a) {
+               if (wsi2a->h2.requested_POLLOUT) {
+                       if (lws_change_pollfd(wsi, 0, LWS_POLLOUT))
+                               return -1;
+                       break;
+               }
+               wsi2a = wsi2a->h2.sibling_list;
+       }
+
+       return 0;
+}
+
+static struct lws *
+rops_encapsulation_parent_h2(struct lws *wsi)
+{
+       if (wsi->h2.parent_wsi)
+               return wsi->h2.parent_wsi;
+
+       return NULL;
+}
+
+static int
+rops_alpn_negotiated_h2(struct lws *wsi, const char *alpn)
+{
+       struct allocated_headers *ah;
+
+       lwsl_debug("%s: client %d\n", __func__, lwsi_role_client(wsi));
+#if !defined(LWS_NO_CLIENT)
+       if (lwsi_role_client(wsi)) {
+               lwsl_info("%s: upgraded to H2\n", __func__);
+               wsi->client_h2_alpn = 1;
+       }
+#endif
+
+       wsi->upgraded_to_http2 = 1;
+       wsi->vhost->conn_stats.h2_alpn++;
+
+       /* adopt the header info */
+
+       ah = wsi->http.ah;
+
+       lws_role_transition(wsi, LWSIFR_SERVER, LRS_H2_AWAIT_PREFACE,
+                           &role_ops_h2);
+
+       /* http2 union member has http union struct at start */
+       wsi->http.ah = ah;
+
+       if (!wsi->h2.h2n)
+               wsi->h2.h2n = lws_zalloc(sizeof(*wsi->h2.h2n), "h2n");
+       if (!wsi->h2.h2n)
+               return 1;
+
+       lws_h2_init(wsi);
+
+       /* HTTP2 union */
+
+       lws_hpack_dynamic_size(wsi,
+                          wsi->h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]);
+       wsi->h2.tx_cr = 65535;
+
+       lwsl_info("%s: wsi %p: configured for h2\n", __func__, wsi);
+
+       return 0;
+}
+
+struct lws_role_ops role_ops_h2 = {
+       /* role name */                 "h2",
+       /* alpn id */                   "h2",
+       /* check_upgrades */            rops_check_upgrades_h2,
+       /* init_context */              rops_init_context_h2,
+       /* init_vhost */                rops_init_vhost_h2,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_h2,
+       /* handle_POLLOUT */            rops_handle_POLLOUT_h2,
+       /* perform_user_POLLOUT */      rops_perform_user_POLLOUT_h2,
+       /* callback_on_writable */      rops_callback_on_writable_h2,
+       /* tx_credit */                 rops_tx_credit_h2,
+       /* write_role_protocol */       rops_write_role_protocol_h2,
+       /* encapsulation_parent */      rops_encapsulation_parent_h2,
+       /* alpn_negotiated */           rops_alpn_negotiated_h2,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     rops_close_kill_connection_h2,
+       /* destroy_role */              rops_destroy_role_h2,
+       /* adoption_bind */             NULL,
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED,
+                                         LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED },
+       /* rx cb clnt, srv */           { LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
+                                         0 /* may be POST, etc */ },
+       /* writeable cb clnt, srv */    { LWS_CALLBACK_CLIENT_HTTP_WRITEABLE,
+                                         LWS_CALLBACK_HTTP_WRITEABLE },
+       /* close cb clnt, srv */        { LWS_CALLBACK_CLOSED_CLIENT_HTTP,
+                                         LWS_CALLBACK_CLOSED_HTTP },
+       /* protocol_bind cb c, srv */   { LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL,
+                                         LWS_CALLBACK_HTTP_BIND_PROTOCOL },
+       /* protocol_unbind cb c, srv */ { LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL,
+                                         LWS_CALLBACK_HTTP_DROP_PROTOCOL },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/h2/private.h b/lib/roles/h2/private.h
new file mode 100644 (file)
index 0000000..f649687
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_H2
+ */
+
+extern struct lws_role_ops role_ops_h2;
+#define lwsi_role_h2(wsi) (wsi->role_ops == &role_ops_h2)
+
+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_RESERVED7,
+       H2SET_ENABLE_CONNECT_PROTOCOL, /* defined in mcmanus-httpbis-h2-ws-02 */
+
+       H2SET_COUNT /* always last */
+};
+
+struct http2_settings {
+       uint32_t s[H2SET_COUNT];
+};
+
+struct lws_vhost_role_h2 {
+       struct http2_settings set;
+};
+
+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_H2_FRAME_TYPE_COUNT /* always last */
+};
+
+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_H2_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 */
+};
+
+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
+        */
+};
+
+void
+lws_h2_state(struct lws *wsi, enum lws_h2_states s);
+
+#define LWS_H2_STREAM_ID_MASTER 0
+#define LWS_H2_SETTINGS_LEN 6
+#define LWS_H2_FLAG_SETTINGS_ACK 1
+
+enum http2_hpack_state {
+       HPKS_TYPE,
+
+       HPKS_IDX_EXT,
+
+       HPKS_HLEN,
+       HPKS_HLEN_EXT,
+
+       HPKS_DATA,
+};
+
+/*
+ * 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,             /* 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 {
+       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; /* malloc'd */
+       uint32_t virtual_payload_usage;
+       uint32_t virtual_payload_max;
+       uint16_t pos;
+       uint16_t used_entries;
+       uint16_t num_entries;
+};
+
+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;
+};
+
+/*
+ * 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 */
+
+       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 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;
+
+       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 {
+
+       struct lws_h2_netconn *h2n; /* malloc'd for root net conn */
+       struct lws *parent_wsi;
+       struct lws *child_list;
+       struct lws *sibling_list;
+
+       char *pending_status_body;
+
+       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 skint:1;
+
+       uint16_t round_robin_POLLOUT;
+       uint16_t count_POLLOUT_children;
+
+       uint8_t h2_state; /* the RFC7540 state of the connection */
+       uint8_t weight;
+       uint8_t initialized;
+};
+
+#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->h2.parent_wsi)
+
+int
+lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason);
+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_h2_settings(struct lws *nwsi, struct http2_settings *settings,
+               unsigned char *buf, int len);
+LWS_EXTERN int
+lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
+             lws_filepos_t *inused);
+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_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
+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);
+LWS_EXTERN int
+lws_add_http2_header_by_token(struct lws *wsi,
+                             enum lws_token_indexes token,
+                             const unsigned char *value, int length,
+                             unsigned char **p, unsigned char *end);
+LWS_EXTERN int
+lws_add_http2_header_status(struct lws *wsi,
+                           unsigned int code, unsigned char **p,
+                           unsigned char *end);
+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;
+LWS_EXTERN int
+lws_h2_ws_handshake(struct lws *wsi);
+LWS_EXTERN int lws_h2_issue_preface(struct lws *wsi);
+LWS_EXTERN int
+lws_h2_client_handshake(struct lws *wsi);
+LWS_EXTERN struct lws *
+lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi);
+int
+lws_handle_POLLOUT_event_h2(struct lws *wsi);
+int
+lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len);
diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c
new file mode 100644 (file)
index 0000000..53498ec
--- /dev/null
@@ -0,0 +1,1192 @@
+#include "core/private.h"
+
+static int
+lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result)
+{
+       struct addrinfo hints;
+
+       memset(&hints, 0, sizeof(hints));
+       *result = NULL;
+
+#ifdef LWS_WITH_IPV6
+       if (wsi->ipv6) {
+
+#if !defined(__ANDROID__)
+               hints.ai_family = AF_INET6;
+               hints.ai_flags = AI_V4MAPPED;
+#endif
+       } else
+#endif
+       {
+               hints.ai_family = PF_UNSPEC;
+               hints.ai_socktype = SOCK_STREAM;
+       }
+
+       return getaddrinfo(ads, NULL, &hints, result);
+}
+
+
+struct lws *
+lws_client_connect_3(struct lws *wsi, struct lws *wsi_piggyback, ssize_t plen)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       const char *meth = NULL;
+       struct lws_pollfd pfd;
+       const char *cce = "";
+       int n, m, rawish = 0;
+
+       if (wsi->stash)
+               meth = wsi->stash->method;
+       else
+               meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
+
+       if (meth && !strcmp(meth, "RAW"))
+               rawish = 1;
+
+       if (wsi_piggyback)
+               goto send_hs;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       /* we are connected to server, or proxy */
+
+       /* http proxy */
+       if (wsi->vhost->http.http_proxy_port) {
+
+               /*
+                * OK from now on we talk via the proxy, so connect to that
+                *
+                * (will overwrite existing pointer,
+                * leaving old string/frag there but unreferenced)
+                */
+               if (wsi->stash) {
+                       lws_free(wsi->stash->address);
+                       wsi->stash->address =
+                               lws_strdup(wsi->vhost->http.http_proxy_address);
+                       if (!wsi->stash->address)
+                               goto failed;
+               } else
+                       if (lws_hdr_simple_create(wsi,
+                                       _WSI_TOKEN_CLIENT_PEER_ADDRESS,
+                                         wsi->vhost->http.http_proxy_address))
+                       goto failed;
+               wsi->c_port = wsi->vhost->http.http_proxy_port;
+
+               n = send(wsi->desc.sockfd, (char *)pt->serv_buf, (int)plen,
+                        MSG_NOSIGNAL);
+               if (n < 0) {
+                       lwsl_debug("ERROR writing to proxy socket\n");
+                       cce = "proxy write failed";
+                       goto failed;
+               }
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
+                               AWAITING_TIMEOUT);
+
+               lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY);
+
+               return wsi;
+       }
+#endif
+#if defined(LWS_WITH_SOCKS5)
+       /* socks proxy */
+       else if (wsi->vhost->socks_proxy_port) {
+               n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen,
+                        MSG_NOSIGNAL);
+               if (n < 0) {
+                       lwsl_debug("ERROR writing socks greeting\n");
+                       cce = "socks write failed";
+                       goto failed;
+               }
+
+               lws_set_timeout(wsi,
+                               PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY,
+                               AWAITING_TIMEOUT);
+
+               lwsi_set_state(wsi, LRS_WAITING_SOCKS_GREETING_REPLY);
+
+               return wsi;
+       }
+#endif
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+send_hs:
+
+       if (wsi_piggyback &&
+           !lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) {
+               /*
+                * We are pipelining on an already-established connection...
+                * we can skip tls establishment.
+                */
+
+               lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2);
+
+               /*
+                * we can't send our headers directly, because they have to
+                * be sent when the parent is writeable.  The parent will check
+                * for anybody on his client transaction queue that is in
+                * LRS_H1C_ISSUE_HANDSHAKE2, and let them write.
+                *
+                * If we are trying to do this too early, before the master
+                * connection has written his own headers, then it will just
+                * wait in the queue until it's possible to send them.
+                */
+               lws_callback_on_writable(wsi_piggyback);
+               lwsl_info("%s: wsi %p: waiting to send hdrs (par state 0x%x)\n",
+                           __func__, wsi, lwsi_state(wsi_piggyback));
+       } else {
+               lwsl_info("%s: wsi %p: %s %s client created own conn (raw %d)\n",
+                           __func__, wsi, wsi->role_ops->name,
+                           wsi->protocol->name, rawish);
+
+               /* we are making our own connection */
+               if (!rawish)
+                       lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE);
+               else {
+                       /* for a method = "RAW" connection, this makes us
+                        * established */
+
+                       /* clear his established timeout */
+                       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+                       m = wsi->role_ops->adoption_cb[0];
+                       if (m) {
+                               n = user_callback_handle_rxflow(
+                                               wsi->protocol->callback, wsi,
+                                               m, wsi->user_space, NULL, 0);
+                               if (n < 0) {
+                                       lwsl_info("LWS_CALLBACK_RAW_PROXY_CLI_ADOPT failed\n");
+                                       goto failed;
+                               }
+                       }
+
+                       /* service.c pollout processing wants this */
+                       wsi->hdr_parsing_completed = 1;
+                       lwsl_info("%s: setting ESTABLISHED\n", __func__);
+                       lwsi_set_state(wsi, LRS_ESTABLISHED);
+
+                       return wsi;
+               }
+
+               /*
+                * provoke service to issue the handshake directly.
+                *
+                * we need to do it this way because in the proxy case, this is
+                * the next state and executed only if and when we get a good
+                * proxy response inside the state machine... but notice in
+                * SSL case this may not have sent anything yet with 0 return,
+                * and won't until many retries from main loop.  To stop that
+                * becoming endless, cover with a timeout.
+                */
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
+                               AWAITING_TIMEOUT);
+
+               assert(lws_socket_is_valid(wsi->desc.sockfd));
+
+               pfd.fd = wsi->desc.sockfd;
+               pfd.events = LWS_POLLIN;
+               pfd.revents = LWS_POLLIN;
+
+               n = lws_service_fd(wsi->context, &pfd);
+               if (n < 0) {
+                       cce = "first service failed";
+                       goto failed;
+               }
+               if (n) /* returns 1 on failure after closing wsi */
+                       return NULL;
+       }
+#endif
+       return wsi;
+
+failed:
+       lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
+
+       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2");
+
+       return NULL;
+}
+
+struct lws *
+lws_client_connect_2(struct lws *wsi)
+{
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       const char *adsin;
+       ssize_t plen = 0;
+#endif
+#if defined(LWS_WITH_UNIX_SOCK)
+       struct sockaddr_un sau;
+       char unix_skt = 0;
+#endif
+       int n, port = 0;
+       const char *cce = "", *iface;
+       const struct sockaddr *psa;
+       const char *meth = NULL;
+       struct addrinfo *result;
+       const char *ads;
+       sockaddr46 sa46;
+
+#ifdef LWS_WITH_IPV6
+       char ipv6only = lws_check_opt(wsi->vhost->options,
+                       LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY |
+                       LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
+       struct sockaddr_in addr;
+#if defined(__ANDROID__)
+       ipv6only = 0;
+#endif
+#endif
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (!wsi->http.ah && !wsi->stash) {
+               cce = "ah was NULL at cc2";
+               lwsl_err("%s\n", cce);
+               goto oom4;
+       }
+
+       /* we can only piggyback GET or POST */
+
+       if (wsi->stash)
+               meth = wsi->stash->method;
+       else
+               meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
+
+       if (meth && strcmp(meth, "GET") && strcmp(meth, "POST"))
+               goto create_new_conn;
+
+       /* we only pipeline connections that said it was okay */
+
+       if (!wsi->client_pipeline)
+               goto create_new_conn;
+
+       /*
+        * let's take a look first and see if there are any already-active
+        * client connections we can piggy-back on.
+        */
+
+       adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
+
+       lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
+                       lws_dll2_get_head(&wsi->vhost->dll_cli_active_conns_owner)) {
+               struct lws *w = lws_container_of(d, struct lws,
+                                                dll_cli_active_conns);
+
+               lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin,
+                          w->cli_hostname_copy, wsi->c_port, w->c_port);
+
+               if (w != wsi && w->cli_hostname_copy &&
+                   !strcmp(adsin, w->cli_hostname_copy) &&
+#if defined(LWS_WITH_TLS)
+                   (wsi->tls.use_ssl & LCCSCF_USE_SSL) ==
+                    (w->tls.use_ssl & LCCSCF_USE_SSL) &&
+#endif
+                   wsi->c_port == w->c_port) {
+
+                       /* someone else is already connected to the right guy */
+
+                       /* do we know for a fact pipelining won't fly? */
+                       if (w->keepalive_rejected) {
+                               lwsl_info("defeating pipelining due to no "
+                                           "keepalive on server\n");
+                               lws_vhost_unlock(wsi->vhost); /* } ---------- */
+                               goto create_new_conn;
+                       }
+#if defined (LWS_WITH_HTTP2)
+                       /*
+                        * h2: in usable state already: just use it without
+                        *     going through the queue
+                        */
+                       if (w->client_h2_alpn &&
+                           (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS ||
+                            lwsi_state(w) == LRS_ESTABLISHED)) {
+
+                               lwsl_info("%s: just join h2 directly\n",
+                                               __func__);
+
+                               wsi->client_h2_alpn = 1;
+                               lws_wsi_h2_adopt(w, wsi);
+                               lws_vhost_unlock(wsi->vhost); /* } ---------- */
+
+                               return wsi;
+                       }
+#endif
+
+                       lwsl_info("apply %p to txn queue on %p state 0x%lx\n",
+                                 wsi, w, (unsigned long)w->wsistate);
+                       /*
+                        * ...let's add ourselves to his transaction queue...
+                        * we are adding ourselves at the HEAD
+                        */
+                       lws_dll2_add_head(&wsi->dll2_cli_txn_queue,
+                                         &w->dll2_cli_txn_queue_owner);
+
+                       /*
+                        * h1: pipeline our headers out on him,
+                        * and wait for our turn at client transaction_complete
+                        * to take over parsing the rx.
+                        */
+                       lws_vhost_unlock(wsi->vhost); /* } ---------- */
+                       return lws_client_connect_3(wsi, w, plen);
+               }
+
+       } lws_end_foreach_dll_safe(d, d1);
+
+       lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */
+
+create_new_conn:
+#endif
+
+       /*
+        * clients who will create their own fresh connection keep a copy of
+        * the hostname they originally connected to, in case other connections
+        * want to use it too
+        */
+
+       if (!wsi->cli_hostname_copy) {
+               if (wsi->stash)
+                       wsi->cli_hostname_copy = lws_strdup(wsi->stash->host);
+               else {
+                       char *pa = lws_hdr_simple_ptr(wsi,
+                                             _WSI_TOKEN_CLIENT_PEER_ADDRESS);
+                       if (pa)
+                               wsi->cli_hostname_copy = lws_strdup(pa);
+               }
+       }
+
+       /*
+        * If we made our own connection, and we're doing a method that can take
+        * a pipeline, we are an "active client connection".
+        *
+        * Add ourselves to the vhost list of those so that others can
+        * piggyback on our transaction queue
+        */
+
+       if (meth && (!strcmp(meth, "GET") || !strcmp(meth, "POST")) &&
+           lws_dll2_is_detached(&wsi->dll2_cli_txn_queue) &&
+           lws_dll2_is_detached(&wsi->dll_cli_active_conns)) {
+               lws_vhost_lock(wsi->vhost);
+               /* caution... we will have to unpick this on oom4 path */
+               lws_dll2_add_head(&wsi->dll_cli_active_conns,
+                                &wsi->vhost->dll_cli_active_conns_owner);
+               lws_vhost_unlock(wsi->vhost);
+       }
+
+       /*
+        * unix socket destination?
+        */
+
+       if (wsi->stash)
+               ads = wsi->stash->address;
+       else
+               ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
+#if defined(LWS_WITH_UNIX_SOCK)
+       if (*ads == '+') {
+               ads++;
+               memset(&sau, 0, sizeof(sau));
+               sau.sun_family = AF_UNIX;
+               strncpy(sau.sun_path, ads, sizeof(sau.sun_path));
+               sau.sun_path[sizeof(sau.sun_path) - 1] = '\0';
+
+               lwsl_info("%s: Unix skt: %s\n", __func__, ads);
+
+               if (sau.sun_path[0] == '@')
+                       sau.sun_path[0] = '\0';
+
+               unix_skt = 1;
+               goto ads_known;
+       }
+#endif
+
+       /*
+        * start off allowing ipv6 on connection if vhost allows it
+        */
+       wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost);
+#ifdef LWS_WITH_IPV6
+       if (wsi->stash)
+               iface = wsi->stash->iface;
+       else
+               iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
+
+       if (wsi->ipv6 && iface &&
+           inet_pton(AF_INET, iface, &addr.sin_addr) == 1) {
+               lwsl_notice("%s: client connection forced to IPv4\n", __func__);
+               wsi->ipv6 = 0;
+       }
+#endif
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+
+       /* Decide what it is we need to connect to:
+        *
+        * Priority 1: connect to http proxy */
+
+       if (wsi->vhost->http.http_proxy_port) {
+               plen = lws_snprintf((char *)pt->serv_buf, 256,
+                       "CONNECT %s:%u HTTP/1.0\x0d\x0a"
+                       "User-agent: libwebsockets\x0d\x0a",
+                       ads, wsi->c_port);
+
+               if (wsi->vhost->proxy_basic_auth_token[0])
+                       plen += lws_snprintf((char *)pt->serv_buf + plen, 256,
+                                       "Proxy-authorization: basic %s\x0d\x0a",
+                                       wsi->vhost->proxy_basic_auth_token);
+
+               plen += lws_snprintf((char *)pt->serv_buf + plen, 5, "\x0d\x0a");
+               ads = wsi->vhost->http.http_proxy_address;
+               port = wsi->vhost->http.http_proxy_port;
+#else
+               if (0) {
+#endif
+
+#if defined(LWS_WITH_SOCKS5)
+
+       /* Priority 2: Connect to SOCK5 Proxy */
+
+       } else if (wsi->vhost->socks_proxy_port) {
+               if (socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen)) {
+                       cce = "socks msg too large";
+                       goto oom4;
+               }
+
+               lwsl_client("Sending SOCKS Greeting\n");
+               ads = wsi->vhost->socks_proxy_address;
+               port = wsi->vhost->socks_proxy_port;
+#endif
+       } else {
+
+               /* Priority 3: Connect directly */
+
+               /* ads already set */
+               port = wsi->c_port;
+       }
+
+       /*
+        * prepare the actual connection
+        * to whatever we decided to connect to
+        */
+
+       lwsl_info("%s: %p: address %s:%u\n", __func__, wsi, ads, port);
+
+       n = lws_getaddrinfo46(wsi, ads, &result);
+       memset(&sa46, 0, sizeof(sa46));
+#ifdef LWS_WITH_IPV6
+       if (wsi->ipv6) {
+               struct sockaddr_in6 *sa6;
+
+               if (n || !result) {
+                       /* lws_getaddrinfo46 failed, there is no usable result */
+                       lwsl_notice("%s: lws_getaddrinfo46 failed %d\n",
+                                       __func__, n);
+                       cce = "ipv6 lws_getaddrinfo46 failed";
+                       goto oom4;
+               }
+
+               sa6 = ((struct sockaddr_in6 *)result->ai_addr);
+               sa46.sa6.sin6_family = AF_INET6;
+               switch (result->ai_family) {
+               case AF_INET:
+                       if (ipv6only)
+                               break;
+                       /* map IPv4 to IPv6 */
+                       memset((char *)&sa46.sa6.sin6_addr, 0,
+                                               sizeof(sa46.sa6.sin6_addr));
+                       sa46.sa6.sin6_addr.s6_addr[10] = 0xff;
+                       sa46.sa6.sin6_addr.s6_addr[11] = 0xff;
+                       memcpy(&sa46.sa6.sin6_addr.s6_addr[12],
+                               &((struct sockaddr_in *)result->ai_addr)->sin_addr,
+                                                       sizeof(struct in_addr));
+                       lwsl_notice("uplevelling AF_INET to AF_INET6\n");
+                       break;
+
+               case AF_INET6:
+                       memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr,
+                                               sizeof(struct in6_addr));
+                       sa46.sa6.sin6_scope_id = sa6->sin6_scope_id;
+                       sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo;
+                       break;
+               default:
+                       lwsl_err("Unknown address family\n");
+                       freeaddrinfo(result);
+                       cce = "unknown address family";
+                       goto oom4;
+               }
+       } else
+#endif /* use ipv6 */
+
+       /* use ipv4 */
+       {
+               void *p = NULL;
+
+               if (!n) {
+                       struct addrinfo *res = result;
+
+                       /* pick the first AF_INET (IPv4) result */
+
+                       while (!p && res) {
+                               switch (res->ai_family) {
+                               case AF_INET:
+                                       p = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
+                                       break;
+                               }
+
+                               res = res->ai_next;
+                       }
+#if defined(LWS_FALLBACK_GETHOSTBYNAME)
+               } else if (n == EAI_SYSTEM) {
+                       struct hostent *host;
+
+                       lwsl_info("ipv4 getaddrinfo err, try gethostbyname\n");
+                       host = gethostbyname(ads);
+                       if (host) {
+                               p = host->h_addr;
+                       } else {
+                               lwsl_err("gethostbyname failed\n");
+                               cce = "gethostbyname (ipv4) failed";
+                               goto oom4;
+                       }
+#endif
+               } else {
+                       lwsl_err("getaddrinfo failed: %d\n", n);
+                       cce = "getaddrinfo failed";
+                       goto oom4;
+               }
+
+               if (!p) {
+                       if (result)
+                               freeaddrinfo(result);
+                       lwsl_err("Couldn't identify address\n");
+                       cce = "unable to lookup address";
+                       goto oom4;
+               }
+
+               sa46.sa4.sin_family = AF_INET;
+               sa46.sa4.sin_addr = *((struct in_addr *)p);
+               memset(&sa46.sa4.sin_zero, 0, sizeof(sa46.sa4.sin_zero));
+       }
+
+       if (result)
+               freeaddrinfo(result);
+
+#if defined(LWS_WITH_UNIX_SOCK)
+ads_known:
+#endif
+
+       /* now we decided on ipv4 or ipv6, set the port */
+
+       if (!lws_socket_is_valid(wsi->desc.sockfd)) {
+
+               if (wsi->context->event_loop_ops->check_client_connect_ok &&
+                   wsi->context->event_loop_ops->check_client_connect_ok(wsi)) {
+                       cce = "waiting for event loop watcher to close";
+                       goto oom4;
+               }
+
+#if defined(LWS_WITH_UNIX_SOCK)
+               if (unix_skt) {
+                       wsi->unix_skt = 1;
+                       wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+               } else
+#endif
+               {
+
+#ifdef LWS_WITH_IPV6
+               if (wsi->ipv6)
+                       wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+               else
+#endif
+                       wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0);
+               }
+
+               if (!lws_socket_is_valid(wsi->desc.sockfd)) {
+                       lwsl_warn("Unable to open socket\n");
+                       cce = "unable to open socket";
+                       goto oom4;
+               }
+
+               if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd,
+#if defined(LWS_WITH_UNIX_SOCK)
+                                               unix_skt)) {
+#else
+                                               0)) {
+#endif
+                       lwsl_err("Failed to set wsi socket options\n");
+                       compatible_close(wsi->desc.sockfd);
+                       cce = "set socket opts failed";
+                       goto oom4;
+               }
+
+               lwsi_set_state(wsi, LRS_WAITING_CONNECT);
+
+#if !defined(LWS_AMAZON_RTOS)
+               if (wsi->context->event_loop_ops->accept)
+                       if (wsi->context->event_loop_ops->accept(wsi)) {
+                               compatible_close(wsi->desc.sockfd);
+                               cce = "event loop accept failed";
+                               goto oom4;
+                       }
+#endif
+
+               if (__insert_wsi_socket_into_fds(wsi->context, wsi)) {
+                       compatible_close(wsi->desc.sockfd);
+                       cce = "insert wsi failed";
+                       goto oom4;
+               }
+
+               if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
+                       compatible_close(wsi->desc.sockfd);
+                       cce = "change_pollfd failed";
+                       goto oom4;
+               }
+
+               /*
+                * past here, we can't simply free the structs as error
+                * handling as oom4 does.  We have to run the whole close flow.
+                */
+
+               if (!wsi->protocol)
+                       wsi->protocol = &wsi->vhost->protocols[0];
+
+               wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE,
+                                       wsi->user_space, NULL, 0);
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
+                               AWAITING_TIMEOUT);
+
+               if (wsi->stash)
+                       iface = wsi->stash->iface;
+               else
+                       iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
+
+               if (iface) {
+                       n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0,
+                                           iface, wsi->ipv6);
+                       if (n < 0) {
+                               cce = "unable to bind socket";
+                               goto failed;
+                       }
+               }
+       }
+
+#if defined(LWS_WITH_UNIX_SOCK)
+       if (unix_skt) {
+               psa = (const struct sockaddr *)&sau;
+               n = sizeof(sau);
+       } else
+#endif
+
+       {
+#ifdef LWS_WITH_IPV6
+               if (wsi->ipv6) {
+                       sa46.sa6.sin6_port = htons(port);
+                       n = sizeof(struct sockaddr_in6);
+                       psa = (const struct sockaddr *)&sa46;
+               } else
+#endif
+               {
+                       sa46.sa4.sin_port = htons(port);
+                       n = sizeof(struct sockaddr);
+                       psa = (const struct sockaddr *)&sa46;
+               }
+       }
+
+       if (connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n) == -1 ||
+           LWS_ERRNO == LWS_EISCONN) {
+               if (LWS_ERRNO == LWS_EALREADY ||
+                   LWS_ERRNO == LWS_EINPROGRESS ||
+                   LWS_ERRNO == LWS_EWOULDBLOCK
+#ifdef _WIN32
+                       || LWS_ERRNO == WSAEINVAL
+#endif
+               ) {
+                       lwsl_client("nonblocking connect retry (errno = %d)\n",
+                                   LWS_ERRNO);
+
+                       if (lws_plat_check_connection_error(wsi)) {
+                               cce = "socket connect failed";
+                               goto failed;
+                       }
+
+                       /*
+                        * must do specifically a POLLOUT poll to hear
+                        * about the connect completion
+                        */
+                       if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) {
+                               cce = "POLLOUT set failed";
+                               goto failed;
+                       }
+
+                       return wsi;
+               }
+
+               if (LWS_ERRNO != LWS_EISCONN) {
+                       lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO);
+                       cce = "connect failed";
+                       goto failed;
+               }
+       }
+
+
+
+       return lws_client_connect_3(wsi, NULL, plen);
+
+
+oom4:
+       if (lwsi_role_client(wsi) && wsi->protocol /* && lwsi_state_est(wsi) */)
+               lws_inform_client_conn_fail(wsi,(void *)cce, strlen(cce));
+
+       /* take care that we might be inserted in fds already */
+       if (wsi->position_in_fds_table != LWS_NO_FDS_POS)
+               goto failed1;
+
+       /*
+        * We can't be an active client connection any more, if we thought
+        * that was what we were going to be doing.  It should be if we are
+        * failing by oom4 path, we are still called by
+        * lws_client_connect_via_info() and will be returning NULL to that,
+        * so nobody else should have had a chance to queue on us.
+        */
+       {
+               struct lws_vhost *vhost = wsi->vhost;
+
+               lws_vhost_lock(vhost);
+               __lws_free_wsi(wsi);
+               lws_vhost_unlock(vhost);
+       }
+
+       return NULL;
+
+failed:
+       lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
+
+failed1:
+       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2");
+
+       return NULL;
+}
+
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+
+/**
+ * lws_client_reset() - retarget a connected wsi to start over with a new
+ *                     connection (ie, redirect)
+ *                     this only works if still in HTTP, ie, not upgraded yet
+ * wsi:                connection to reset
+ * address:    network address of the new server
+ * port:       port to connect to
+ * path:       uri path to connect to on the new server
+ * host:       host header to send to the new server
+ */
+LWS_VISIBLE struct lws *
+lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
+                const char *path, const char *host)
+{
+       char origin[300] = "", protocol[300] = "", method[32] = "",
+            iface[16] = "", alpn[32] = "", *p;
+       struct lws *wsi;
+
+       if (!pwsi)
+               return NULL;
+
+       wsi = *pwsi;
+
+       if (wsi->redirects == 3) {
+               lwsl_err("%s: Too many redirects\n", __func__);
+               return NULL;
+       }
+       wsi->redirects++;
+
+       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN);
+       if (p)
+               lws_strncpy(origin, p, sizeof(origin));
+
+       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
+       if (p)
+               lws_strncpy(protocol, p, sizeof(protocol));
+
+       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
+       if (p)
+               lws_strncpy(method, p, sizeof(method));
+
+       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
+       if (p)
+               lws_strncpy(iface, p, sizeof(iface));
+
+       p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ALPN);
+       if (p)
+               lws_strncpy(alpn, p, sizeof(alpn));
+
+       if (!port) {
+               port = 443;
+               ssl = 1;
+       }
+
+       lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n",
+                  address, port, path, ssl);
+
+       /* close the connection by hand */
+
+#if defined(LWS_WITH_TLS)
+       lws_ssl_close(wsi);
+#endif
+
+       __remove_wsi_socket_from_fds(wsi);
+
+       if (wsi->context->event_loop_ops->close_handle_manually)
+               wsi->context->event_loop_ops->close_handle_manually(wsi);
+       else
+               compatible_close(wsi->desc.sockfd);
+
+#if defined(LWS_WITH_TLS)
+       wsi->tls.use_ssl = ssl;
+#else
+       if (ssl) {
+               lwsl_err("%s: not configured for ssl\n", __func__);
+               return NULL;
+       }
+#endif
+
+       wsi->desc.sockfd = LWS_SOCK_INVALID;
+       lwsi_set_state(wsi, LRS_UNCONNECTED);
+       wsi->protocol = NULL;
+       wsi->pending_timeout = NO_PENDING_TIMEOUT;
+       wsi->c_port = port;
+       wsi->hdr_parsing_completed = 0;
+       _lws_header_table_reset(wsi->http.ah);
+
+       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address))
+               return NULL;
+
+       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host))
+               return NULL;
+
+       if (origin[0])
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN,
+                                         origin))
+                       return NULL;
+       if (protocol[0])
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
+                                         protocol))
+                       return NULL;
+       if (method[0])
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD,
+                                         method))
+                       return NULL;
+
+       if (iface[0])
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE,
+                                         iface))
+                       return NULL;
+       if (alpn[0])
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN,
+                                         alpn))
+                       return NULL;
+
+       origin[0] = '/';
+       strncpy(&origin[1], path, sizeof(origin) - 2);
+       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, origin))
+               return NULL;
+
+       *pwsi = lws_client_connect_2(wsi);
+
+       return *pwsi;
+}
+
+#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_WITH_HUBBUB)
+hubbub_error
+html_parser_cb(const hubbub_token *token, void *pw)
+{
+       struct lws_rewrite *r = (struct lws_rewrite *)pw;
+       char buf[1024], *start = buf + LWS_PRE, *p = start,
+            *end = &buf[sizeof(buf) - 1];
+       size_t i;
+
+       switch (token->type) {
+       case HUBBUB_TOKEN_DOCTYPE:
+
+               p += lws_snprintf(p, end - p, "<!DOCTYPE %.*s %s ",
+                               (int) token->data.doctype.name.len,
+                               token->data.doctype.name.ptr,
+                               token->data.doctype.force_quirks ?
+                                               "(force-quirks) " : "");
+
+               if (token->data.doctype.public_missing)
+                       lwsl_debug("\tpublic: missing\n");
+               else
+                       p += lws_snprintf(p, end - p, "PUBLIC \"%.*s\"\n",
+                               (int) token->data.doctype.public_id.len,
+                               token->data.doctype.public_id.ptr);
+
+               if (token->data.doctype.system_missing)
+                       lwsl_debug("\tsystem: missing\n");
+               else
+                       p += lws_snprintf(p, end - p, " \"%.*s\">\n",
+                               (int) token->data.doctype.system_id.len,
+                               token->data.doctype.system_id.ptr);
+
+               break;
+       case HUBBUB_TOKEN_START_TAG:
+               p += lws_snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len,
+                               token->data.tag.name.ptr);
+
+/*                             (token->data.tag.self_closing) ?
+                                               "(self-closing) " : "",
+                               (token->data.tag.n_attributes > 0) ?
+                                               "attributes:" : "");
+*/
+               for (i = 0; i < token->data.tag.n_attributes; i++) {
+                       if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) ||
+                           !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) ||
+                           !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) {
+                               const char *pp = (const char *)token->data.tag.attributes[i].value.ptr;
+                               int plen = (int) token->data.tag.attributes[i].value.len;
+
+                               if (strncmp(pp, "http:", 5) && strncmp(pp, "https:", 6)) {
+
+                                       if (!hstrcmp(&token->data.tag.attributes[i].value,
+                                                    r->from, r->from_len)) {
+                                               pp += r->from_len;
+                                               plen -= r->from_len;
+                                       }
+                                       p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"",
+                                              (int) token->data.tag.attributes[i].name.len,
+                                              token->data.tag.attributes[i].name.ptr,
+                                              r->to, plen, pp);
+                                       continue;
+                               }
+                       }
+
+                       p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"",
+                               (int) token->data.tag.attributes[i].name.len,
+                               token->data.tag.attributes[i].name.ptr,
+                               (int) token->data.tag.attributes[i].value.len,
+                               token->data.tag.attributes[i].value.ptr);
+               }
+               p += lws_snprintf(p, end - p, ">");
+               break;
+       case HUBBUB_TOKEN_END_TAG:
+               p += lws_snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len,
+                               token->data.tag.name.ptr);
+/*
+                               (token->data.tag.self_closing) ?
+                                               "(self-closing) " : "",
+                               (token->data.tag.n_attributes > 0) ?
+                                               "attributes:" : "");
+*/
+               for (i = 0; i < token->data.tag.n_attributes; i++) {
+                       p += lws_snprintf(p, end - p, " %.*s='%.*s'\n",
+                               (int) token->data.tag.attributes[i].name.len,
+                               token->data.tag.attributes[i].name.ptr,
+                               (int) token->data.tag.attributes[i].value.len,
+                               token->data.tag.attributes[i].value.ptr);
+               }
+               p += lws_snprintf(p, end - p, ">");
+               break;
+       case HUBBUB_TOKEN_COMMENT:
+               p += lws_snprintf(p, end - p, "<!-- %.*s -->\n",
+                               (int) token->data.comment.len,
+                               token->data.comment.ptr);
+               break;
+       case HUBBUB_TOKEN_CHARACTER:
+               if (token->data.character.len == 1) {
+                       if (*token->data.character.ptr == '<') {
+                               p += lws_snprintf(p, end - p, "&lt;");
+                               break;
+                       }
+                       if (*token->data.character.ptr == '>') {
+                               p += lws_snprintf(p, end - p, "&gt;");
+                               break;
+                       }
+                       if (*token->data.character.ptr == '&') {
+                               p += lws_snprintf(p, end - p, "&amp;");
+                               break;
+                       }
+               }
+
+               p += lws_snprintf(p, end - p, "%.*s", (int) token->data.character.len,
+                               token->data.character.ptr);
+               break;
+       case HUBBUB_TOKEN_EOF:
+               p += lws_snprintf(p, end - p, "\n");
+               break;
+       }
+
+       if (r->wsi->protocol_bind_balance &&
+           user_callback_handle_rxflow(r->wsi->protocol->callback,
+                       r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+                       r->wsi->user_space, start, p - start))
+               return -1;
+
+       return HUBBUB_OK;
+}
+#endif
+
+#endif
+
+struct lws *
+lws_http_client_connect_via_info2(struct lws *wsi)
+{
+       struct client_info_stash *stash = wsi->stash;
+
+       lwsl_debug("%s: %p (stash %p)\n", __func__, wsi, stash);
+
+       if (!stash)
+               return wsi;
+
+       wsi->opaque_user_data = wsi->stash->opaque_user_data;
+
+       if (stash->method && !strcmp(stash->method, "RAW"))
+               goto no_ah;
+
+       /*
+        * we're not necessarily in a position to action these right away,
+        * stash them... we only need during connect phase so into a temp
+        * allocated stash
+        */
+       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
+                                 stash->address))
+               goto bail1;
+
+       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path))
+               goto bail1;
+
+       if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host))
+               goto bail1;
+
+       if (stash->origin)
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN,
+                                         stash->origin))
+                       goto bail1;
+       /*
+        * this is a list of protocols we tell the server we're okay with
+        * stash it for later when we compare server response with it
+        */
+       if (stash->protocol)
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
+                                         stash->protocol))
+                       goto bail1;
+       if (stash->method)
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD,
+                                         stash->method))
+                       goto bail1;
+       if (stash->iface)
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE,
+                                         stash->iface))
+                       goto bail1;
+       if (stash->alpn)
+               if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN,
+                                         stash->alpn))
+                       goto bail1;
+
+#if defined(LWS_WITH_SOCKS5)
+       if (!wsi->vhost->socks_proxy_port)
+               lws_client_stash_destroy(wsi);
+#endif
+
+no_ah:
+       wsi->context->count_wsi_allocated++;
+
+       return lws_client_connect_2(wsi);
+
+bail1:
+#if defined(LWS_WITH_SOCKS5)
+       if (!wsi->vhost->socks_proxy_port)
+               lws_free_set_NULL(wsi->stash);
+#endif
+
+       return NULL;
+}
+
+#if defined(LWS_WITH_SOCKS5)
+int
+socks_generate_msg(struct lws *wsi, enum socks_msg_type type, ssize_t *msg_len)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       uint8_t *p = pt->serv_buf, *end = &p[context->pt_serv_buf_size];
+       ssize_t n, passwd_len;
+       short net_num;
+       char *cp;
+
+       switch (type) {
+       case SOCKS_MSG_GREETING:
+               if (lws_ptr_diff(end, p) < 4)
+                       return 1;
+               /* socks version, version 5 only */
+               *p++ = SOCKS_VERSION_5;
+               /* number of methods */
+               *p++ = 2;
+               /* username password method */
+               *p++ = SOCKS_AUTH_USERNAME_PASSWORD;
+               /* no authentication method */
+               *p++ = SOCKS_AUTH_NO_AUTH;
+               break;
+
+       case SOCKS_MSG_USERNAME_PASSWORD:
+               n = strlen(wsi->vhost->socks_user);
+               passwd_len = strlen(wsi->vhost->socks_password);
+
+               if (n > 254 || passwd_len > 254)
+                       return 1;
+
+               if (lws_ptr_diff(end, p) < 3 + n + passwd_len)
+                       return 1;
+
+               /* the subnegotiation version */
+               *p++ = SOCKS_SUBNEGOTIATION_VERSION_1;
+
+               /* length of the user name */
+               *p++ = n;
+               /* user name */
+               memcpy(p, wsi->vhost->socks_user, n);
+               p += n;
+
+               /* length of the password */
+               *p++ = passwd_len;
+
+               /* password */
+               memcpy(p, wsi->vhost->socks_password, passwd_len);
+               p += passwd_len;
+               break;
+
+       case SOCKS_MSG_CONNECT:
+               n = strlen(wsi->stash->address);
+
+               if (n > 254 || lws_ptr_diff(end, p) < 5 + n + 2)
+                       return 1;
+
+               cp = (char *)&net_num;
+
+               /* socks version */
+               *p++ = SOCKS_VERSION_5;
+               /* socks command */
+               *p++ = SOCKS_COMMAND_CONNECT;
+               /* reserved */
+               *p++ = 0;
+               /* address type */
+               *p++ = SOCKS_ATYP_DOMAINNAME;
+               /* length of ---> */
+               *p++ = n;
+
+               /* the address we tell SOCKS proxy to connect to */
+               memcpy(p, wsi->stash->address, n);
+               p += n;
+
+               net_num = htons(wsi->c_port);
+
+               /* the port we tell SOCKS proxy to connect to */
+               *p++ = cp[0];
+               *p++ = cp[1];
+
+               break;
+               
+       default:
+               return 1;
+       }
+
+       *msg_len = lws_ptr_diff(p, pt->serv_buf);
+
+       return 0;
+}
+#endif
diff --git a/lib/roles/http/client/client.c b/lib/roles/http/client/client.c
new file mode 100644 (file)
index 0000000..65b7c54
--- /dev/null
@@ -0,0 +1,1334 @@
+/*
+ * libwebsockets - lib/client/client.c
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+LWS_VISIBLE LWS_EXTERN void
+lws_client_http_body_pending(struct lws *wsi, int something_left_to_send)
+{
+       wsi->client_http_body_pending = !!something_left_to_send;
+}
+
+/*
+ * return self, or queued client wsi we are acting on behalf of
+ *
+ * That is the TAIL of the queue (new queue elements are added at the HEAD)
+ */
+
+struct lws *
+lws_client_wsi_effective(struct lws *wsi)
+{
+       struct lws_dll2 *tail = lws_dll2_get_tail(&wsi->dll2_cli_txn_queue_owner);
+
+       if (!wsi->transaction_from_pipeline_queue || !tail)
+               return wsi;
+
+       return lws_container_of(tail, struct lws, dll2_cli_txn_queue);
+}
+
+/*
+ * return self or the guy we are queued under
+ *
+ * REQUIRES VHOST LOCK HELD
+ */
+
+static struct lws *
+_lws_client_wsi_master(struct lws *wsi)
+{
+       struct lws_dll2_owner *o = wsi->dll2_cli_txn_queue.owner;
+
+       if (!o)
+               return wsi;
+
+       return lws_container_of(o, struct lws, dll2_cli_txn_queue_owner);
+}
+
+int
+lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd,
+                         struct lws *wsi_conn)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       char *p = (char *)&pt->serv_buf[0];
+       struct lws *w;
+#if defined(LWS_WITH_TLS)
+       char ebuf[128];
+#endif
+       const char *cce = NULL;
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       ssize_t len = 0;
+       unsigned char c;
+#endif
+       char *sb = p;
+       int n = 0;
+#if defined(LWS_WITH_SOCKS5)
+       int conn_mode = 0, pending_timeout = 0;
+#endif
+
+       if ((pollfd->revents & LWS_POLLOUT) &&
+            wsi->keepalive_active &&
+            wsi->dll2_cli_txn_queue_owner.head) {
+               struct lws *wfound = NULL;
+
+               lwsl_debug("%s: pollout HANDSHAKE2\n", __func__);
+
+               /*
+                * We have a transaction queued that wants to pipeline.
+                *
+                * We have to allow it to send headers strictly in the order
+                * that it was queued, ie, tail-first.
+                */
+               lws_vhost_lock(wsi->vhost);
+               lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
+                                 wsi->dll2_cli_txn_queue_owner.head) {
+                       struct lws *w = lws_container_of(d, struct lws,
+                                                 dll2_cli_txn_queue);
+
+                       lwsl_debug("%s: %p states 0x%lx\n", __func__, w,
+                                  (unsigned long)w->wsistate);
+                       if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2)
+                               wfound = w;
+               } lws_end_foreach_dll_safe(d, d1);
+
+               if (wfound) {
+                       /*
+                        * pollfd has the master sockfd in it... we
+                        * need to use that in HANDSHAKE2 to understand
+                        * which wsi to actually write on
+                        */
+                       if (lws_client_socket_service(wfound, pollfd, wsi) < 0) {
+                               /* closed */
+
+                               lws_vhost_unlock(wsi->vhost);
+
+                               return -1;
+                       }
+
+                       lws_callback_on_writable(wsi);
+               } else
+                       lwsl_debug("%s: didn't find anything in txn q in HS2\n",
+                                                          __func__);
+
+               lws_vhost_unlock(wsi->vhost);
+
+               return 0;
+       }
+
+       switch (lwsi_state(wsi)) {
+
+       case LRS_WAITING_CONNECT:
+
+               /*
+                * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
+                * timeout protection set in client-handshake.c
+                */
+
+               if (!lws_client_connect_2(wsi)) {
+                       /* closed */
+                       lwsl_client("closed\n");
+                       return -1;
+               }
+
+               /* either still pending connection, or changed mode */
+               return 0;
+
+#if defined(LWS_WITH_SOCKS5)
+       /* SOCKS Greeting Reply */
+       case LRS_WAITING_SOCKS_GREETING_REPLY:
+       case LRS_WAITING_SOCKS_AUTH_REPLY:
+       case LRS_WAITING_SOCKS_CONNECT_REPLY:
+
+               /* handle proxy hung up on us */
+
+               if (pollfd->revents & LWS_POLLHUP) {
+                       lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
+                                 (void *)wsi, pollfd->fd);
+                       cce = "socks conn dead";
+                       goto bail3;
+               }
+
+               n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
+               if (n < 0) {
+                       if (LWS_ERRNO == LWS_EAGAIN) {
+                               lwsl_debug("SOCKS read EAGAIN, retrying\n");
+                               return 0;
+                       }
+                       lwsl_err("ERROR reading from SOCKS socket\n");
+                       cce = "socks recv fail";
+                       goto bail3;
+               }
+
+               switch (lwsi_state(wsi)) {
+
+               case LRS_WAITING_SOCKS_GREETING_REPLY:
+                       if (pt->serv_buf[0] != SOCKS_VERSION_5)
+                               goto socks_reply_fail;
+
+                       if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) {
+                               lwsl_client("SOCKS GR: No Auth Method\n");
+                               if (socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len))
+                                       goto socks_send_msg_fail;
+                               conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY;
+                               pending_timeout =
+                                  PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
+                               goto socks_send;
+                       }
+
+                       if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) {
+                               lwsl_client("SOCKS GR: User/Pw Method\n");
+                               if (socks_generate_msg(wsi,
+                                                  SOCKS_MSG_USERNAME_PASSWORD,
+                                                  &len))
+                                       goto socks_send_msg_fail;
+                               conn_mode = LRS_WAITING_SOCKS_AUTH_REPLY;
+                               pending_timeout =
+                                     PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY;
+                               goto socks_send;
+                       }
+                       goto socks_reply_fail;
+
+               case LRS_WAITING_SOCKS_AUTH_REPLY:
+                       if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 ||
+                           pt->serv_buf[1] !=
+                                           SOCKS_SUBNEGOTIATION_STATUS_SUCCESS)
+                               goto socks_reply_fail;
+
+                       lwsl_client("SOCKS password OK, sending connect\n");
+                       if (socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len)) {
+socks_send_msg_fail:
+                               *cce = "socks gen msg fail";
+                               goto bail3;
+                       }
+                       conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY;
+                       pending_timeout =
+                                  PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
+socks_send:
+                       n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
+                                MSG_NOSIGNAL);
+                       if (n < 0) {
+                               lwsl_debug("ERROR writing to socks proxy\n");
+                               cce = "socks write fail";
+                               goto bail3;
+                       }
+
+                       lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
+                       lwsi_set_state(wsi, conn_mode);
+                       break;
+
+socks_reply_fail:
+                       lwsl_notice("socks reply: v%d, err %d\n",
+                                   pt->serv_buf[0], pt->serv_buf[1]);
+                       cce = "socks reply fail";
+                       goto bail3;
+
+               case LRS_WAITING_SOCKS_CONNECT_REPLY:
+                       if (pt->serv_buf[0] != SOCKS_VERSION_5 ||
+                           pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS)
+                               goto socks_reply_fail;
+
+                       lwsl_client("socks connect OK\n");
+
+                       /* free stash since we are done with it */
+                       lws_client_stash_destroy(wsi);
+                       if (lws_hdr_simple_create(wsi,
+                                                _WSI_TOKEN_CLIENT_PEER_ADDRESS,
+                                              wsi->vhost->socks_proxy_address)) {
+                               cce = "socks connect fail";
+                               goto bail3;
+                       }
+
+                       wsi->c_port = wsi->vhost->socks_proxy_port;
+
+                       /* clear his proxy connection timeout */
+                       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+                       goto start_ws_handshake;
+               default:
+                       break;
+               }
+               break;
+#endif
+
+       case LRS_WAITING_PROXY_REPLY:
+
+               /* handle proxy hung up on us */
+
+               if (pollfd->revents & LWS_POLLHUP) {
+
+                       lwsl_warn("Proxy connection %p (fd=%d) dead\n",
+                                 (void *)wsi, pollfd->fd);
+
+                       cce = "proxy conn dead";
+                       goto bail3;
+               }
+
+               n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
+               if (n < 0) {
+                       if (LWS_ERRNO == LWS_EAGAIN) {
+                               lwsl_debug("Proxy read EAGAIN... retrying\n");
+                               return 0;
+                       }
+                       lwsl_err("ERROR reading from proxy socket\n");
+                       cce = "proxy read err";
+                       goto bail3;
+               }
+
+               pt->serv_buf[13] = '\0';
+               if (strncmp(sb, "HTTP/1.0 200 ", 13) &&
+                   strncmp(sb, "HTTP/1.1 200 ", 13)) {
+                       lwsl_err("%s: ERROR proxy did not reply with h1\n",
+                                       __func__);
+                       cce = "proxy not h1";
+                       goto bail3;
+               }
+
+               /* clear his proxy connection timeout */
+
+               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+               /* fallthru */
+
+       case LRS_H1C_ISSUE_HANDSHAKE:
+
+               /*
+                * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
+                * timeout protection set in client-handshake.c
+                *
+                * take care of our lws_callback_on_writable
+                * happening at a time when there's no real connection yet
+                */
+#if defined(LWS_WITH_SOCKS5)
+start_ws_handshake:
+#endif
+               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0))
+                       return -1;
+
+#if defined(LWS_WITH_TLS)
+               /* we can retry this... just cook the SSL BIO the first time */
+
+               if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !wsi->tls.ssl &&
+                   lws_ssl_client_bio_create(wsi) < 0) {
+                       cce = "bio_create failed";
+                       goto bail3;
+               }
+
+               if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
+                       n = lws_ssl_client_connect1(wsi);
+                       if (!n)
+                               return 0;
+                       if (n < 0) {
+                               cce = "lws_ssl_client_connect1 failed";
+                               goto bail3;
+                       }
+               } else
+                       wsi->tls.ssl = NULL;
+
+               /* fallthru */
+
+       case LRS_WAITING_SSL:
+
+               if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
+                       n = lws_ssl_client_connect2(wsi, ebuf, sizeof(ebuf));
+                       if (!n)
+                               return 0;
+                       if (n < 0) {
+                               cce = ebuf;
+                               goto bail3;
+                       }
+               } else
+                       wsi->tls.ssl = NULL;
+#endif
+#if defined (LWS_WITH_HTTP2)
+               if (wsi->client_h2_alpn) {
+                       /*
+                        * We connected to the server and set up tls, and
+                        * negotiated "h2".
+                        *
+                        * So this is it, we are an h2 master client connection
+                        * now, not an h1 client connection.
+                        */
+#if defined (LWS_WITH_TLS)
+                       lws_tls_server_conn_alpn(wsi);
+#endif
+
+                       /* send the H2 preface to legitimize the connection */
+                       if (lws_h2_issue_preface(wsi)) {
+                               cce = "error sending h2 preface";
+                               goto bail3;
+                       }
+
+                       break;
+               }
+#endif
+               lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2);
+               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
+                               context->timeout_secs);
+
+               /* fallthru */
+
+       case LRS_H1C_ISSUE_HANDSHAKE2:
+               p = lws_generate_client_handshake(wsi, p);
+               if (p == NULL) {
+                       if (wsi->role_ops == &role_ops_raw_skt ||
+                           wsi->role_ops == &role_ops_raw_file)
+                               return 0;
+
+                       lwsl_err("Failed to generate handshake for client\n");
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "chs");
+                       return 0;
+               }
+
+               /* send our request to the server */
+               lws_latency_pre(context, wsi);
+
+               w = _lws_client_wsi_master(wsi);
+               lwsl_info("%s: HANDSHAKE2: %p: sending headers on %p "
+                         "(wsistate 0x%lx 0x%lx), w sock %d, wsi sock %d\n",
+                         __func__, wsi, w, (unsigned long)wsi->wsistate,
+                         (unsigned long)w->wsistate, w->desc.sockfd,
+                         wsi->desc.sockfd);
+
+               n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb));
+               lws_latency(context, wsi, "send lws_issue_raw", n,
+                           n == p - sb);
+               switch (n) {
+               case LWS_SSL_CAPABLE_ERROR:
+                       lwsl_debug("ERROR writing to client socket\n");
+                       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "cws");
+                       return 0;
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       lws_callback_on_writable(wsi);
+                       break;
+               }
+
+               if (wsi->client_http_body_pending) {
+                       lwsl_debug("body pending\n");
+                       lwsi_set_state(wsi, LRS_ISSUE_HTTP_BODY);
+                       lws_set_timeout(wsi,
+                                       PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
+                                       context->timeout_secs);
+#if defined(LWS_WITH_HTTP_PROXY)
+                       if (wsi->http.proxy_clientside)
+                               lws_callback_on_writable(wsi);
+#endif
+                       /* user code must ask for writable callback */
+                       break;
+               }
+
+               lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
+               wsi->hdr_parsing_completed = 0;
+
+               if (lwsi_state(w) == LRS_IDLING) {
+                       lwsi_set_state(w, LRS_WAITING_SERVER_REPLY);
+                       w->hdr_parsing_completed = 0;
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+                       w->http.ah->parser_state = WSI_TOKEN_NAME_PART;
+                       w->http.ah->lextable_pos = 0;
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                       w->http.ah->unk_pos = 0;
+#endif
+                       /* If we're (re)starting on hdr, need other implied init */
+                       wsi->http.ah->ues = URIES_IDLE;
+#endif
+               }
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
+                               wsi->context->timeout_secs);
+
+               lws_callback_on_writable(w);
+
+               goto client_http_body_sent;
+
+       case LRS_ISSUE_HTTP_BODY:
+#if defined(LWS_WITH_HTTP_PROXY)
+                       if (wsi->http.proxy_clientside) {
+                               lws_callback_on_writable(wsi);
+                               break;
+                       }
+#endif
+               if (wsi->client_http_body_pending) {
+                       //lws_set_timeout(wsi,
+                       //              PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
+                       //              context->timeout_secs);
+                       /* user code must ask for writable callback */
+                       break;
+               }
+client_http_body_sent:
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+               /* prepare ourselves to do the parsing */
+               wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART;
+               wsi->http.ah->lextable_pos = 0;
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+               wsi->http.ah->unk_pos = 0;
+#endif
+#endif
+               lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
+               lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
+                               context->timeout_secs);
+               break;
+
+       case LRS_WAITING_SERVER_REPLY:
+               /*
+                * 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);
+                       cce = "Peer hung up";
+                       goto bail3;
+               }
+
+               if (!(pollfd->revents & LWS_POLLIN))
+                       break;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+               /* 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)
+                * coalesce both handshake response and websocket traffic
+                * in one packet, since at that point the connection is
+                * definitively ready from browser pov.
+                */
+               len = 1;
+               while (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE &&
+                      len > 0) {
+                       int plen = 1;
+
+                       n = lws_ssl_capable_read(wsi, &c, 1);
+                       lws_latency(context, wsi, "send lws_issue_raw", n,
+                                   n == 1);
+                       switch (n) {
+                       case 0:
+                       case LWS_SSL_CAPABLE_ERROR:
+                               cce = "read failed";
+                               goto bail3;
+                       case LWS_SSL_CAPABLE_MORE_SERVICE:
+                               return 0;
+                       }
+
+                       if (lws_parse(wsi, &c, &plen)) {
+                               lwsl_warn("problems parsing header\n");
+                               cce = "problems parsing header";
+                               goto bail3;
+                       }
+               }
+
+               /*
+                * hs may also be coming in multiple packets, there is a 5-sec
+                * libwebsocket timeout still active here too, so if parsing did
+                * not complete just wait for next packet coming in this state
+                */
+               if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE)
+                       break;
+
+#endif
+
+               /*
+                * otherwise deal with the handshake.  If there's any
+                * 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:
+               lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n");
+               if (cce)
+                       lwsl_info("reason: %s\n", cce);
+               lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
+
+               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cbail3");
+               return -1;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+
+int LWS_WARN_UNUSED_RESULT
+lws_http_transaction_completed_client(struct lws *wsi)
+{
+       struct lws *wsi_eff = lws_client_wsi_effective(wsi);
+
+       lwsl_info("%s: wsi: %p, wsi_eff: %p (%s)\n", __func__, wsi, wsi_eff,
+                   wsi_eff->protocol->name);
+
+       if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff,
+                                       LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
+                                       wsi_eff->user_space, NULL, 0)) {
+               lwsl_debug("%s: Completed call returned nonzero (role 0x%lx)\n",
+                          __func__, (unsigned long)lwsi_role(wsi_eff));
+               return -1;
+       }
+
+       /*
+        * Are we constitutionally capable of having a queue, ie, we are on
+        * the "active client connections" list?
+        *
+        * If not, that's it for us.
+        */
+
+       if (lws_dll2_is_detached(&wsi->dll_cli_active_conns))
+               return -1;
+
+       /* if this was a queued guy, close him and remove from queue */
+
+       if (wsi->transaction_from_pipeline_queue) {
+               lwsl_debug("closing queued wsi %p\n", wsi_eff);
+               /* so the close doesn't trigger a CCE */
+               wsi_eff->already_did_cce = 1;
+               __lws_close_free_wsi(wsi_eff,
+                       LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE,
+                       "queued client done");
+       }
+
+       _lws_header_table_reset(wsi->http.ah);
+
+       /* after the first one, they can only be coming from the queue */
+       wsi->transaction_from_pipeline_queue = 1;
+
+       wsi->http.rx_content_length = 0;
+       wsi->hdr_parsing_completed = 0;
+
+       /* is there a new tail after removing that one? */
+       wsi_eff = lws_client_wsi_effective(wsi);
+
+       /*
+        * Do we have something pipelined waiting?
+        * it's OK if he hasn't managed to send his headers yet... he's next
+        * in line to do that...
+        */
+       if (wsi_eff == wsi) {
+               /*
+                * Nothing pipelined... we should hang around a bit
+                * in case something turns up...
+                */
+               lwsl_info("%s: nothing pipelined waiting\n", __func__);
+               lwsi_set_state(wsi, LRS_IDLING);
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5);
+
+               return 0;
+       }
+
+       /*
+        * H1: we can serialize the queued guys into the same ah
+        * H2: everybody needs their own ah until their own STREAM_END
+        */
+
+       /* otherwise set ourselves up ready to go again */
+       lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
+
+       wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART;
+       wsi->http.ah->lextable_pos = 0;
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+       wsi->http.ah->unk_pos = 0;
+#endif
+
+       lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
+                       wsi->context->timeout_secs);
+
+       /* If we're (re)starting on headers, need other implied init */
+       wsi->http.ah->ues = URIES_IDLE;
+
+       lwsl_info("%s: %p: new queued transaction as %p\n", __func__, wsi,
+                 wsi_eff);
+       lws_callback_on_writable(wsi);
+
+       return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN unsigned int
+lws_http_client_http_response(struct lws *_wsi)
+{
+       struct lws *wsi;
+       unsigned int resp;
+
+       if (_wsi->http.ah && _wsi->http.ah->http_response)
+               return _wsi->http.ah->http_response;
+
+       lws_vhost_lock(_wsi->vhost);
+       wsi = _lws_client_wsi_master(_wsi);
+       resp = wsi->http.ah->http_response;
+       lws_vhost_unlock(_wsi->vhost);
+
+       return resp;
+}
+#endif
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+int
+lws_client_interpret_server_handshake(struct lws *wsi)
+{
+       int n, port = 0, ssl = 0;
+       int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR;
+       const char *prot, *ads = NULL, *path, *cce = NULL;
+       struct allocated_headers *ah;
+       struct lws *w = lws_client_wsi_effective(wsi);
+       char *p, *q;
+       char new_path[300];
+
+       lws_client_stash_destroy(wsi);
+
+       ah = wsi->http.ah;
+       if (!wsi->do_ws) {
+               /* we are being an http client...
+                */
+#if defined(LWS_ROLE_H2)
+               if (wsi->client_h2_alpn || wsi->client_h2_substream) {
+                       lwsl_debug("%s: %p: transitioning to h2 client\n",
+                                  __func__, wsi);
+                       lws_role_transition(wsi, LWSIFR_CLIENT,
+                                           LRS_ESTABLISHED, &role_ops_h2);
+               } else
+#endif
+               {
+#if defined(LWS_ROLE_H1)
+                       {
+                       lwsl_debug("%s: %p: transitioning to h1 client\n",
+                                  __func__, wsi);
+                       lws_role_transition(wsi, LWSIFR_CLIENT,
+                                           LRS_ESTABLISHED, &role_ops_h1);
+                       }
+#else
+                       return -1;
+#endif
+               }
+
+               wsi->http.ah = ah;
+               ah->http_response = 0;
+       }
+
+       /*
+        * well, what the server sent looked reasonable for syntax.
+        * Now let's confirm it sent all the necessary headers
+        *
+        * http (non-ws) client will expect something like this
+        *
+        * HTTP/1.0.200
+        * server:.libwebsockets
+        * content-type:.text/html
+        * content-length:.17703
+        * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000
+        */
+
+       wsi->http.conn_type = HTTP_CONNECTION_KEEP_ALIVE;
+       if (!wsi->client_h2_substream) {
+               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
+               if (wsi->do_ws && !p) {
+                       lwsl_info("no URI\n");
+                       cce = "HS: URI missing";
+                       goto bail3;
+               }
+               if (!p) {
+                       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0);
+                       wsi->http.conn_type = HTTP_CONNECTION_CLOSE;
+               }
+               if (!p) {
+                       cce = "HS: URI missing";
+                       lwsl_info("no URI\n");
+                       goto bail3;
+               }
+       } else {
+               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_STATUS);
+               if (!p) {
+                       cce = "HS: :status missing";
+                       lwsl_info("no status\n");
+                       goto bail3;
+               }
+       }
+       n = atoi(p);
+       if (ah)
+               ah->http_response = n;
+
+       if (
+#if defined(LWS_WITH_HTTP_PROXY)
+           !wsi->http.proxy_clientside &&
+#endif
+           (n == 301 || n == 302 || n == 303 || n == 307 || n == 308)) {
+               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION);
+               if (!p) {
+                       cce = "HS: Redirect code but no Location";
+                       goto bail3;
+               }
+
+               /* Relative reference absolute path */
+               if (p[0] == '/') {
+#if defined(LWS_WITH_TLS)
+                       ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL;
+#endif
+                       ads = lws_hdr_simple_ptr(wsi,
+                                                _WSI_TOKEN_CLIENT_PEER_ADDRESS);
+                       port = wsi->c_port;
+                       /* +1 as lws_client_reset expects leading / omitted */
+                       path = p + 1;
+               }
+               /* Absolute (Full) URI */
+               else if (strchr(p, ':')) {
+                       if (lws_parse_uri(p, &prot, &ads, &port, &path)) {
+                               cce = "HS: URI did not parse";
+                               goto bail3;
+                       }
+
+                       if (!strcmp(prot, "wss") || !strcmp(prot, "https"))
+                               ssl = 1;
+               }
+               /* Relative reference relative path */
+               else {
+                       /* This doesn't try to calculate an absolute path,
+                        * that will be left to the server */
+#if defined(LWS_WITH_TLS)
+                       ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL;
+#endif
+                       ads = lws_hdr_simple_ptr(wsi,
+                                                _WSI_TOKEN_CLIENT_PEER_ADDRESS);
+                       port = wsi->c_port;
+                       /* +1 as lws_client_reset expects leading / omitted */
+                       path = new_path + 1;
+                       if (lws_hdr_simple_ptr(wsi,_WSI_TOKEN_CLIENT_URI))
+                               lws_strncpy(new_path, lws_hdr_simple_ptr(wsi,
+                                  _WSI_TOKEN_CLIENT_URI), sizeof(new_path));
+                       else {
+                               new_path[0] = '/';
+                               new_path[1] = '\0';
+                       }
+                       q = strrchr(new_path, '/');
+                       if (q)
+                               lws_strncpy(q + 1, p, sizeof(new_path) -
+                                                       (q - new_path) - 1);
+                       else
+                               path = p;
+               }
+
+#if defined(LWS_WITH_TLS)
+               if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !ssl) {
+                       cce = "HS: Redirect attempted SSL downgrade";
+                       goto bail3;
+               }
+#endif
+
+               if (!ads) /* make coverity happy */ {
+                       cce = "no ads";
+                       goto bail3;
+               }
+
+               if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) {
+                       /* there are two ways to fail out with NULL return...
+                        * simple, early problem where the wsi is intact, or
+                        * we went through with the reconnect attempt and the
+                        * wsi is already closed.  In the latter case, the wsi
+                        * has beet set to NULL additionally.
+                        */
+                       lwsl_err("Redirect failed\n");
+                       cce = "HS: Redirect failed";
+                       if (wsi)
+                               goto bail3;
+
+                       return 1;
+               }
+               return 0;
+       }
+
+       if (!wsi->do_ws) {
+
+               /* if h1 KA is allowed, enable the queued pipeline guys */
+
+               if (!wsi->client_h2_alpn && !wsi->client_h2_substream &&
+                   w == wsi) { /* ie, coming to this for the first time */
+                       if (wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE)
+                               wsi->keepalive_active = 1;
+                       else {
+                               /*
+                                * Ugh... now the main http connection has seen
+                                * both sides, we learn the server doesn't
+                                * support keepalive.
+                                *
+                                * That means any guys queued on us are going
+                                * to have to be restarted from connect2 with
+                                * their own connections.
+                                */
+
+                               /*
+                                * stick around telling any new guys they can't
+                                * pipeline to this server
+                                */
+                               wsi->keepalive_rejected = 1;
+
+                               lws_vhost_lock(wsi->vhost);
+                               lws_start_foreach_dll_safe(struct lws_dll2 *,
+                                                          d, d1,
+                                 wsi->dll2_cli_txn_queue_owner.head) {
+                                       struct lws *ww = lws_container_of(d,
+                                               struct lws,
+                                               dll2_cli_txn_queue);
+
+                                       /* remove him from our queue */
+                                       lws_dll2_remove(&ww->dll2_cli_txn_queue);
+                                       /* give up on pipelining */
+                                       ww->client_pipeline = 0;
+
+                                       /* go back to "trying to connect" state */
+                                       lws_role_transition(ww, LWSIFR_CLIENT,
+                                                           LRS_UNCONNECTED,
+#if defined(LWS_ROLE_H1)
+                                                           &role_ops_h1);
+#else
+#if defined (LWS_ROLE_H2)
+                                                           &role_ops_h2);
+#else
+                                                           &role_ops_raw);
+#endif
+#endif
+                                       ww->user_space = NULL;
+                               } lws_end_foreach_dll_safe(d, d1);
+                               lws_vhost_unlock(wsi->vhost);
+                       }
+               }
+
+#ifdef LWS_WITH_HTTP_PROXY
+               wsi->http.perform_rewrite = 0;
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
+                       if (!strncmp(lws_hdr_simple_ptr(wsi,
+                                               WSI_TOKEN_HTTP_CONTENT_TYPE),
+                                               "text/html", 9))
+                               wsi->http.perform_rewrite = 0;
+               }
+#endif
+
+               /* allocate the per-connection user memory (if any) */
+               if (lws_ensure_user_space(wsi)) {
+                       lwsl_err("Problem allocating wsi user mem\n");
+                       cce = "HS: OOM";
+                       goto bail2;
+               }
+
+               /* he may choose to send us stuff in chunked transfer-coding */
+               wsi->chunked = 0;
+               wsi->chunk_remaining = 0; /* ie, next thing is chunk size */
+               if (lws_hdr_total_length(wsi,
+                                       WSI_TOKEN_HTTP_TRANSFER_ENCODING)) {
+                       wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi,
+                                              WSI_TOKEN_HTTP_TRANSFER_ENCODING),
+                                               "chunked");
+                       /* first thing is hex, after payload there is crlf */
+                       wsi->chunk_parser = ELCP_HEX;
+               }
+
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
+                       wsi->http.rx_content_length =
+                                       atoll(lws_hdr_simple_ptr(wsi,
+                                               WSI_TOKEN_HTTP_CONTENT_LENGTH));
+                       lwsl_info("%s: incoming content length %llu\n",
+                                   __func__, (unsigned long long)
+                                           wsi->http.rx_content_length);
+                       wsi->http.rx_content_remain =
+                                       wsi->http.rx_content_length;
+               } else /* can't do 1.1 without a content length or chunked */
+                       if (!wsi->chunked)
+                               wsi->http.conn_type = HTTP_CONNECTION_CLOSE;
+
+               /*
+                * we seem to be good to go, give client last chance to check
+                * headers and OK it
+                */
+               if (w->protocol->callback(w,
+                               LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
+                                           w->user_space, NULL, 0)) {
+
+                       cce = "HS: disallowed by client filter";
+                       goto bail2;
+               }
+
+               /* clear his proxy connection timeout */
+               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+               wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
+
+               /* call him back to inform him he is up */
+               if (w->protocol->callback(w,
+                                           LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP,
+                                           w->user_space, NULL, 0)) {
+                       cce = "HS: disallowed at ESTABLISHED";
+                       goto bail3;
+               }
+
+               /*
+                * for pipelining, master needs to keep his ah... guys who
+                * queued on him can drop it now though.
+                */
+
+               if (w != wsi)
+                       /* free up parsing allocations for queued guy */
+                       lws_header_table_detach(w, 0);
+
+               lwsl_info("%s: client connection up\n", __func__);
+
+               /*
+                * Did we get a response from the server with an explicit
+                * content-length of zero?  If so, this transaction is already
+                * completed at the end of the header processing...
+                */
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
+                   !wsi->http.rx_content_length)
+                       return !!lws_http_transaction_completed_client(wsi);
+
+               return 0;
+       }
+
+#if defined(LWS_ROLE_WS)
+       switch (lws_client_ws_upgrade(wsi, &cce)) {
+       case 2:
+               goto bail2;
+       case 3:
+               goto bail3;
+       }
+
+       return 0;
+#endif
+
+bail3:
+       close_reason = LWS_CLOSE_STATUS_NOSTATUS;
+
+bail2:
+       if (wsi->protocol) {
+               n = 0;
+               if (cce)
+                       n = (int)strlen(cce);
+
+               lws_inform_client_conn_fail(wsi, (void *)cce, (unsigned int)n);
+       }
+
+       lwsl_info("closing connection (prot %s) "
+                 "due to bail2 connection error: %s\n", wsi->protocol ?
+                                 wsi->protocol->name : "unknown", cce);
+
+       /* closing will free up his parsing allocations */
+       lws_close_free_wsi(wsi, close_reason, "c hs interp");
+
+       return 1;
+}
+#endif
+
+char *
+lws_generate_client_handshake(struct lws *wsi, char *pkt)
+{
+       char *p = pkt;
+       const char *meth;
+       const char *pp = lws_hdr_simple_ptr(wsi,
+                               _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
+
+       meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
+       if (!meth) {
+               meth = "GET";
+               wsi->do_ws = 1;
+       } else {
+               wsi->do_ws = 0;
+       }
+
+       if (!strcmp(meth, "RAW")) {
+               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+               lwsl_notice("client transition to raw\n");
+
+               if (pp) {
+                       const struct lws_protocols *pr;
+
+                       pr = lws_vhost_name_to_protocol(wsi->vhost, pp);
+
+                       if (!pr) {
+                               lwsl_err("protocol %s not enabled on vhost\n",
+                                        pp);
+                               return NULL;
+                       }
+
+                       lws_bind_protocol(wsi, pr, __func__);
+               }
+
+               if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT,
+                                             wsi->user_space, NULL, 0))
+                       return NULL;
+
+               lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED,
+                                   &role_ops_raw_skt);
+               lws_header_table_detach(wsi, 1);
+
+               return NULL;
+       }
+
+       /*
+        * 04 example client handshake
+        *
+        * GET /chat HTTP/1.1
+        * Host: server.example.com
+        * Upgrade: websocket
+        * Connection: Upgrade
+        * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+        * Sec-WebSocket-Origin: http://example.com
+        * Sec-WebSocket-Protocol: chat, superchat
+        * Sec-WebSocket-Version: 4
+        */
+
+       p += lws_snprintf(p, 2048, "%s %s HTTP/1.1\x0d\x0a", meth,
+                    lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI));
+
+       p += lws_snprintf(p, 64, "Pragma: no-cache\x0d\x0a"
+                       "Cache-Control: no-cache\x0d\x0a");
+
+       p += lws_snprintf(p, 128, "Host: %s\x0d\x0a",
+                    lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST));
+
+       if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) {
+               if (lws_check_opt(wsi->context->options,
+                                 LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN))
+                       p += lws_snprintf(p, 128, "Origin: %s\x0d\x0a",
+                                    lws_hdr_simple_ptr(wsi,
+                                                    _WSI_TOKEN_CLIENT_ORIGIN));
+               else
+                       p += lws_snprintf(p, 128, "Origin: http://%s\x0d\x0a",
+                                    lws_hdr_simple_ptr(wsi,
+                                                    _WSI_TOKEN_CLIENT_ORIGIN));
+       }
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       if (wsi->parent &&
+           lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
+               p += lws_snprintf(p, 128, "Content-Length: %s\x0d\x0a",
+                       lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH));
+               if (atoi(lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)))
+                       wsi->client_http_body_pending = 1;
+       }
+       if (wsi->parent &&
+           lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_AUTHORIZATION)) {
+               p += lws_snprintf(p, 128, "Authorization: %s\x0d\x0a",
+                       lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_AUTHORIZATION));
+       }
+       if (wsi->parent &&
+           lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
+               p += lws_snprintf(p, 128, "Content-Type: %s\x0d\x0a",
+                       lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_TYPE));
+       }
+#endif
+
+#if defined(LWS_ROLE_WS)
+       if (wsi->do_ws) {
+               const char *conn1 = "";
+       //      if (!wsi->client_pipeline)
+       //              conn1 = "close, ";
+               p = lws_generate_client_ws_handshake(wsi, p, conn1);
+       } else
+#endif
+       {
+               if (!wsi->client_pipeline)
+                       p += lws_snprintf(p, 64, "connection: close\x0d\x0a");
+       }
+
+       /* give userland a chance to append, eg, cookies */
+
+       if (wsi->protocol->callback(wsi,
+                       LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
+                       wsi->user_space, &p,
+                       (pkt + wsi->context->pt_serv_buf_size) - p - 12))
+               return NULL;
+
+       p += lws_snprintf(p, 4, "\x0d\x0a");
+
+       // puts(pkt);
+
+       return p;
+}
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+
+LWS_VISIBLE int
+lws_http_client_read(struct lws *wsi, char **buf, int *len)
+{
+       int rlen, n;
+
+       rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len);
+       *len = 0;
+
+       // lwsl_notice("%s: rlen %d\n", __func__, rlen);
+
+       /* allow the source to signal he has data again next time */
+       if (lws_change_pollfd(wsi, 0, LWS_POLLIN))
+               return -1;
+
+       if (rlen == LWS_SSL_CAPABLE_ERROR) {
+               lwsl_debug("%s: SSL capable error\n", __func__);
+               return -1;
+       }
+
+       if (rlen <= 0)
+               return 0;
+
+       *len = rlen;
+       wsi->client_rx_avail = 0;
+
+       /*
+        * server may insist on transfer-encoding: chunked,
+        * so http client must deal with it
+        */
+spin_chunks:
+       while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) {
+               switch (wsi->chunk_parser) {
+               case ELCP_HEX:
+                       if ((*buf)[0] == '\x0d') {
+                               wsi->chunk_parser = ELCP_CR;
+                               break;
+                       }
+                       n = char_to_hex((*buf)[0]);
+                       if (n < 0) {
+                               lwsl_info("%s: chunking failure\n", __func__);
+                               return -1;
+                       }
+                       wsi->chunk_remaining <<= 4;
+                       wsi->chunk_remaining |= n;
+                       break;
+               case ELCP_CR:
+                       if ((*buf)[0] != '\x0a') {
+                               lwsl_info("%s: chunking failure\n", __func__);
+                               return -1;
+                       }
+                       wsi->chunk_parser = ELCP_CONTENT;
+                       lwsl_info("chunk %d\n", wsi->chunk_remaining);
+                       if (wsi->chunk_remaining)
+                               break;
+                       lwsl_info("final chunk\n");
+                       goto completed;
+
+               case ELCP_CONTENT:
+                       break;
+
+               case ELCP_POST_CR:
+                       if ((*buf)[0] != '\x0d') {
+                               lwsl_info("%s: chunking failure\n", __func__);
+
+                               return -1;
+                       }
+
+                       wsi->chunk_parser = ELCP_POST_LF;
+                       break;
+
+               case ELCP_POST_LF:
+                       if ((*buf)[0] != '\x0a') {
+                               lwsl_info("%s: chunking failure\n", __func__);
+
+                               return -1;
+                       }
+
+                       wsi->chunk_parser = ELCP_HEX;
+                       wsi->chunk_remaining = 0;
+                       break;
+               }
+               (*buf)++;
+               (*len)--;
+       }
+
+       if (wsi->chunked && !wsi->chunk_remaining)
+               return 0;
+
+       if (wsi->http.rx_content_remain &&
+           wsi->http.rx_content_remain < (unsigned int)*len)
+               n = (int)wsi->http.rx_content_remain;
+       else
+               n = *len;
+
+       if (wsi->chunked && wsi->chunk_remaining &&
+           wsi->chunk_remaining < n)
+               n = wsi->chunk_remaining;
+
+#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_WITH_HUBBUB)
+       /* hubbub */
+       if (wsi->http.perform_rewrite)
+               lws_rewrite_parse(wsi->http.rw, (unsigned char *)*buf, n);
+       else
+#endif
+       {
+               struct lws *wsi_eff = lws_client_wsi_effective(wsi);
+
+               if (
+#if defined(LWS_WITH_HTTP_PROXY)
+                   !wsi_eff->protocol_bind_balance ==
+                   !!wsi_eff->http.proxy_clientside &&
+#else
+                   !!wsi_eff->protocol_bind_balance &&
+#endif
+                   user_callback_handle_rxflow(wsi_eff->protocol->callback,
+                               wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+                               wsi_eff->user_space, *buf, n)) {
+                       lwsl_info("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n",
+                                  __func__);
+
+                       return -1;
+               }
+       }
+
+       if (wsi->chunked && wsi->chunk_remaining) {
+               (*buf) += n;
+               wsi->chunk_remaining -= n;
+               *len -= n;
+       }
+
+       if (wsi->chunked && !wsi->chunk_remaining)
+               wsi->chunk_parser = ELCP_POST_CR;
+
+       if (wsi->chunked && *len)
+               goto spin_chunks;
+
+       if (wsi->chunked)
+               return 0;
+
+       /* if we know the content length, decrement the content remaining */
+       if (wsi->http.rx_content_length > 0)
+               wsi->http.rx_content_remain -= n;
+
+       // lwsl_notice("rx_content_remain %lld, rx_content_length %lld\n",
+       //      wsi->http.rx_content_remain, wsi->http.rx_content_length);
+
+       if (wsi->http.rx_content_remain || !wsi->http.rx_content_length)
+               return 0;
+
+completed:
+
+       if (lws_http_transaction_completed_client(wsi)) {
+               lwsl_notice("%s: transaction completed says -1\n", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+#endif
diff --git a/lib/roles/http/compression/README.md b/lib/roles/http/compression/README.md
new file mode 100644 (file)
index 0000000..8d9d57f
--- /dev/null
@@ -0,0 +1,17 @@
+HTTP compression
+----------------
+
+This directory contains generic compression transforms that can be applied to
+specifically HTTP content streams, after the header, be it h1 or h2.
+
+The compression transforms expose an "ops" type struct and a compressor name
+as used by `content-encoding`... the ops struct definition can be found in
+./private.h.
+
+Because the compression transform depends on being able to send on its output
+before it can process new input, the transform adds a new kind of buflist
+`wsi->buflist_comp` that represents pre-compression transform data
+("input data" from the perspective of the compression transform) that was
+delivered to be processed but couldn't be accepted.
+
+Currently, zlib 'deflate' and brotli 'br' are supported on the server side.
diff --git a/lib/roles/http/compression/brotli/brotli.c b/lib/roles/http/compression/brotli/brotli.c
new file mode 100644 (file)
index 0000000..0c977de
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+
+static int
+lcs_init_compression_brotli(lws_comp_ctx_t *ctx, int decomp)
+{
+       ctx->is_decompression = decomp;
+
+       if (!decomp) {
+               ctx->u.br_en = BrotliEncoderCreateInstance(NULL, NULL, NULL);
+               if (ctx->u.br_en) {
+                       BrotliEncoderSetParameter(ctx->u.br_en,
+                                       BROTLI_PARAM_MODE, BROTLI_MODE_TEXT);
+                       BrotliEncoderSetParameter(ctx->u.br_en,
+                               BROTLI_PARAM_QUALITY, BROTLI_MIN_QUALITY);
+               }
+       }
+       else
+               ctx->u.br_de = BrotliDecoderCreateInstance(NULL, NULL, NULL);
+
+       return !ctx->u.br_de;
+}
+
+static int
+lcs_process_brotli(lws_comp_ctx_t *ctx, const void *in, size_t *ilen_iused,
+                  void *out, size_t *olen_oused)
+{
+       size_t a_in, a_out, t_out;
+       const uint8_t *n_in;
+       uint8_t *n_out;
+       int n;
+
+       n_in = (void *)in;
+       a_in = *ilen_iused;
+       a_out = *olen_oused;
+       n_out = out;
+       t_out = 0;
+
+       if (!ctx->is_decompression) {
+
+               if (!a_in && !BrotliEncoderHasMoreOutput(ctx->u.br_en)) {
+                       *olen_oused = 0;
+
+                       goto bail;
+               }
+
+               n = BROTLI_OPERATION_PROCESS;
+               if (!ctx->buflist_comp && ctx->final_on_input_side)
+                       n = BROTLI_OPERATION_FINISH;
+
+               if (BrotliEncoderCompressStream(ctx->u.br_en, n, &a_in, &n_in,
+                                               &a_out, &n_out, &t_out) ==
+                   BROTLI_FALSE) {
+                       lwsl_err("brotli encode failed\n");
+
+                       return -1;
+               }
+
+               ctx->may_have_more = !a_out;
+
+       } else {
+               n = BrotliDecoderDecompressStream(ctx->u.br_de, &a_in, &n_in,
+                                                 &a_out, &n_out, &t_out);
+
+               switch (n) {
+               case BROTLI_DECODER_RESULT_ERROR:
+                       lwsl_err("brotli decoder error\n");
+                       return -1;
+               }
+       }
+
+       *ilen_iused -= a_in;
+       *olen_oused -= a_out;
+
+bail:
+       if (!ctx->is_decompression)
+               return BrotliEncoderIsFinished(ctx->u.br_en);
+       else
+               return BrotliDecoderIsFinished(ctx->u.br_de);
+}
+
+static void
+lcs_destroy_brotli(lws_comp_ctx_t *ctx)
+{
+       if (!ctx)
+               return;
+
+       if (!(*ctx).is_decompression)
+               BrotliEncoderDestroyInstance((*ctx).u.br_en);
+       else
+               BrotliDecoderDestroyInstance((*ctx).u.br_de);
+
+       (*ctx).u.generic_ctx_ptr = NULL;
+}
+
+struct lws_compression_support lcs_brotli = {
+       /* .encoding_name */            "br",
+       /* .init_compression */         lcs_init_compression_brotli,
+       /* .process */                  lcs_process_brotli,
+       /* .destroy */                  lcs_destroy_brotli,
+};
diff --git a/lib/roles/http/compression/deflate/deflate.c b/lib/roles/http/compression/deflate/deflate.c
new file mode 100644 (file)
index 0000000..c092ec5
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static int
+lcs_init_compression_deflate(lws_comp_ctx_t *ctx, int decomp)
+{
+       int n;
+
+       ctx->is_decompression = decomp;
+       ctx->u.deflate = lws_malloc(sizeof(*ctx->u.deflate), __func__);
+
+       if (!ctx->u.deflate)
+               return 2;
+
+       memset(ctx->u.deflate, 0, sizeof(*ctx->u.deflate));
+
+       if (!decomp &&
+           (n = deflateInit2(ctx->u.deflate, 1, Z_DEFLATED, -15, 8,
+                        Z_DEFAULT_STRATEGY)) != Z_OK) {
+               lwsl_err("deflate init failed: %d\n", n);
+               lws_free_set_NULL(ctx->u.deflate);
+
+               return 1;
+       }
+
+       if (decomp &&
+           inflateInit2(ctx->u.deflate, 16 + 15) != Z_OK) {
+               lws_free_set_NULL(ctx->u.deflate);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int
+lcs_process_deflate(lws_comp_ctx_t *ctx, const void *in, size_t *ilen_iused,
+                   void *out, size_t *olen_oused)
+{
+       size_t olen_oused_in = *olen_oused;
+       int n;
+
+       ctx->u.deflate->next_in = (void *)in;
+       ctx->u.deflate->avail_in = *ilen_iused;
+
+       ctx->u.deflate->next_out = out;
+       ctx->u.deflate->avail_out = *olen_oused;
+
+       if (!ctx->is_decompression)
+               n = deflate(ctx->u.deflate, Z_SYNC_FLUSH);
+       else
+               n = inflate(ctx->u.deflate, Z_SYNC_FLUSH);
+
+       switch (n) {
+       case Z_NEED_DICT:
+       case Z_STREAM_ERROR:
+       case Z_DATA_ERROR:
+       case Z_MEM_ERROR:
+               lwsl_err("zlib error inflate %d\n", n);
+               return -1;
+       }
+
+       *ilen_iused -= ctx->u.deflate->avail_in;
+       *olen_oused -= ctx->u.deflate->avail_out;
+
+       /* it's ambiguous with zlib... */
+       ctx->may_have_more = (*olen_oused == olen_oused_in);
+
+       return n == Z_STREAM_END;
+}
+
+static void
+lcs_destroy_deflate(lws_comp_ctx_t *ctx)
+{
+       if (!ctx)
+               return;
+
+       if (!(*ctx).is_decompression)
+               deflateEnd((*ctx).u.deflate);
+       else
+               inflateEnd((*ctx).u.deflate);
+
+       lws_free_set_NULL(ctx->u.deflate);
+}
+
+struct lws_compression_support lcs_deflate = {
+       /* .encoding_name */            "deflate",
+       /* .init_compression */         lcs_init_compression_deflate,
+       /* .process */                  lcs_process_deflate,
+       /* .destroy */                  lcs_destroy_deflate,
+};
diff --git a/lib/roles/http/compression/private.h b/lib/roles/http/compression/private.h
new file mode 100644 (file)
index 0000000..97b5d7b
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_WITH_HTTP_STREAM_COMPRESSION
+ */
+
+#if defined(LWS_WITH_MINIZ)
+#include <miniz.h>
+#else
+#include <zlib.h>
+#endif
+#if defined(LWS_WITH_HTTP_BROTLI)
+#include <brotli/encode.h>
+#include <brotli/decode.h>
+#endif
+
+/*
+ * struct holding union of all the available compression methods' context data,
+ * and state if it's compressing or decompressing
+ */
+
+typedef struct lws_compression_ctx {
+       union {
+
+#if defined(LWS_WITH_HTTP_BROTLI)
+               BrotliEncoderState *br_en;
+               BrotliDecoderState *br_de;
+#endif
+               z_stream *deflate;
+               void *generic_ctx_ptr;
+       } u;
+
+       struct lws_buflist *buflist_comp;
+
+       unsigned int is_decompression:1;
+       unsigned int final_on_input_side:1;
+       unsigned int may_have_more:1;
+       unsigned int chunking:1;
+} lws_comp_ctx_t;
+
+/* generic structure defining the interface to a compression method */
+
+struct lws_compression_support {
+       /** compression name as used by, eg, content-ecoding */
+       const char *encoding_name;
+       /** create a compression context for the compression method, or NULL */
+       int (*init_compression)(lws_comp_ctx_t *ctx, int decomp);
+       /** pass data into the context to be processed */
+       int (*process)(lws_comp_ctx_t *ctx, const void *in, size_t *ilen_iused,
+                      void *out, size_t *olen_oused);
+       /** destroy the de/compression context */
+       void (*destroy)(lws_comp_ctx_t *ctx);
+};
+
+extern struct lws_compression_support lcs_deflate;
+extern struct lws_compression_support lcs_brotli;
+
+int
+lws_http_compression_validate(struct lws *wsi);
+
+int
+lws_http_compression_transform(struct lws *wsi, unsigned char *buf,
+                              size_t len, enum lws_write_protocol *wp,
+                              unsigned char **outbuf, size_t *olen_oused);
+
+void
+lws_http_compression_destroy(struct lws *wsi);
diff --git a/lib/roles/http/compression/stream.c b/lib/roles/http/compression/stream.c
new file mode 100644 (file)
index 0000000..7acb58e
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/* compression methods listed in order of preference */
+
+struct lws_compression_support *lcs_available[] = {
+#if defined(LWS_WITH_HTTP_BROTLI)
+       &lcs_brotli,
+#endif
+       &lcs_deflate,
+};
+
+/* compute acceptable compression encodings while we still have an ah */
+
+int
+lws_http_compression_validate(struct lws *wsi)
+{
+       const char *a;
+       size_t n;
+
+       wsi->http.comp_accept_mask = 0;
+
+       if (!wsi->http.ah || !lwsi_role_server(wsi))
+               return 0;
+
+       a = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING);
+       if (!a)
+               return 0;
+
+       for (n = 0; n < LWS_ARRAY_SIZE(lcs_available); n++)
+               if (strstr(a, lcs_available[n]->encoding_name))
+                       wsi->http.comp_accept_mask |= 1 << n;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_http_compression_apply(struct lws *wsi, const char *name,
+                          unsigned char **p, unsigned char *end, char decomp)
+{
+       size_t n;
+
+       for (n = 0; n < LWS_ARRAY_SIZE(lcs_available); n++) {
+               /* if name is non-NULL, choose only that compression method */
+               if (name && !strcmp(lcs_available[n]->encoding_name, name))
+                       continue;
+               /*
+                * If we're the server, confirm that the client told us he could
+                * handle this kind of compression transform...
+                */
+               if (!decomp && !(wsi->http.comp_accept_mask & (1 << n)))
+                       continue;
+
+               /* let's go with this one then... */
+               break;
+       }
+
+       if (n == LWS_ARRAY_SIZE(lcs_available))
+               return 1;
+
+       lcs_available[n]->init_compression(&wsi->http.comp_ctx, decomp);
+       if (!wsi->http.comp_ctx.u.generic_ctx_ptr) {
+               lwsl_err("%s: init_compression %d failed\n", __func__, (int)n);
+               return 1;
+       }
+
+       wsi->http.lcs = lcs_available[n];
+       wsi->http.comp_ctx.may_have_more = 0;
+       wsi->http.comp_ctx.final_on_input_side = 0;
+       wsi->http.comp_ctx.chunking = 0;
+       wsi->http.comp_ctx.is_decompression = decomp;
+
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
+                       (unsigned char *)lcs_available[n]->encoding_name,
+                       strlen(lcs_available[n]->encoding_name), p, end))
+               return -1;
+
+       lwsl_info("%s: wsi %p: applied %s content-encoding\n", __func__,
+                   wsi, lcs_available[n]->encoding_name);
+
+       return 0;
+}
+
+void
+lws_http_compression_destroy(struct lws *wsi)
+{
+       if (!wsi->http.lcs || !wsi->http.comp_ctx.u.generic_ctx_ptr)
+               return;
+
+       wsi->http.lcs->destroy(&wsi->http.comp_ctx);
+
+       wsi->http.lcs = NULL;
+}
+
+/*
+ * This manages the compression transform independent of h1 or h2.
+ *
+ * wsi->buflist_comp stashes pre-transform input that was not yet compressed
+ */
+
+int
+lws_http_compression_transform(struct lws *wsi, unsigned char *buf,
+                              size_t len, enum lws_write_protocol *wp,
+                              unsigned char **outbuf, size_t *olen_oused)
+{
+       size_t ilen_iused = len;
+       int n, use = 0, wp1f = (*wp) & 0x1f;
+       lws_comp_ctx_t *ctx = &wsi->http.comp_ctx;
+
+       ctx->may_have_more = 0;
+
+       if (!wsi->http.lcs ||
+           (wp1f != LWS_WRITE_HTTP && wp1f != LWS_WRITE_HTTP_FINAL)) {
+               *outbuf = buf;
+               *olen_oused = len;
+
+               return 0;
+       }
+
+       if (wp1f == LWS_WRITE_HTTP_FINAL) {
+               /*
+                * ...we may get a large buffer that represents the final input
+                * buffer, but it may form multiple frames after being
+                * tranformed by compression; only the last of those is actually
+                * the final frame on the output stream.
+                *
+                * Note that we have received the FINAL input, and downgrade it
+                * to a non-final for now.
+                */
+               ctx->final_on_input_side = 1;
+               *wp = LWS_WRITE_HTTP | ((*wp) & ~0x1f);
+       }
+
+       if (ctx->buflist_comp || ctx->may_have_more) {
+               /*
+                * we can't send this new stuff when we have old stuff
+                * buffered and not compressed yet.  Add it to the tail
+                * and switch to trying to process the head.
+                */
+               if (buf && len) {
+                       if (lws_buflist_append_segment(
+                                       &ctx->buflist_comp, buf, len) < 0)
+                               return -1;
+                       lwsl_debug("%s: %p: adding %d to comp buflist\n",
+                                  __func__,wsi, (int)len);
+               }
+
+               len = lws_buflist_next_segment_len(&ctx->buflist_comp, &buf);
+               ilen_iused = len;
+               use = 1;
+               lwsl_debug("%s: %p: trying comp buflist %d\n", __func__, wsi,
+                          (int)len);
+       }
+
+       if (!buf && ilen_iused)
+               return 0;
+
+       lwsl_debug("%s: %p: pre-process: ilen_iused %d, olen_oused %d\n",
+                  __func__, wsi, (int)ilen_iused, (int)*olen_oused);
+
+       n = wsi->http.lcs->process(ctx, buf, &ilen_iused, *outbuf, olen_oused);
+
+       if (n && n != 1) {
+               lwsl_err("%s: problem with compression\n", __func__);
+
+               return -1;
+       }
+
+       if (!ctx->may_have_more && ctx->final_on_input_side)
+               *wp = LWS_WRITE_HTTP_FINAL | ((*wp) & ~0x1f);
+
+       lwsl_debug("%s: %p: more %d, ilen_iused %d\n", __func__, wsi,
+                  ctx->may_have_more, (int)ilen_iused);
+
+       if (use && ilen_iused) {
+               /*
+                * we were flushing stuff from the buflist head... account for
+                * however much actually got processed by the compression
+                * transform
+                */
+               lws_buflist_use_segment(&ctx->buflist_comp, ilen_iused);
+               lwsl_debug("%s: %p: marking %d of comp buflist as used "
+                          "(ctx->buflist_comp %p)\n", __func__, wsi,
+                          (int)len, ctx->buflist_comp);
+       }
+
+       if (!use && ilen_iused != len) {
+                /*
+                 * ...we were sending stuff from the caller directly and not
+                 * all of it got processed... stash on the buflist tail
+                 */
+               if (lws_buflist_append_segment(&ctx->buflist_comp,
+                                          buf + ilen_iused, len - ilen_iused) < 0)
+                       return -1;
+
+               lwsl_debug("%s: buffering %d unused comp input\n", __func__,
+                          (int)(len - ilen_iused));
+       }
+       if (ctx->buflist_comp || ctx->may_have_more)
+               lws_callback_on_writable(wsi);
+
+       return 0;
+}
diff --git a/lib/roles/http/header.c b/lib/roles/http/header.c
new file mode 100644 (file)
index 0000000..d161d78
--- /dev/null
@@ -0,0 +1,616 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "lextable-strings.h"
+
+
+const unsigned char *
+lws_token_to_string(enum lws_token_indexes token)
+{
+       if ((unsigned int)token >= LWS_ARRAY_SIZE(set))
+               return NULL;
+
+       return (unsigned char *)set[token];
+}
+
+int
+lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
+                           const unsigned char *value, int length,
+                           unsigned char **p, unsigned char *end)
+{
+#ifdef LWS_WITH_HTTP2
+       if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi))
+               return lws_add_http2_header_by_name(wsi, name,
+                                                   value, length, p, end);
+#else
+       (void)wsi;
+#endif
+       if (name) {
+               while (*p < end && *name)
+                       *((*p)++) = *name++;
+               if (*p == end)
+                       return 1;
+               *((*p)++) = ' ';
+       }
+       if (*p + length + 3 >= end)
+               return 1;
+
+       memcpy(*p, value, length);
+       *p += length;
+       *((*p)++) = '\x0d';
+       *((*p)++) = '\x0a';
+
+       return 0;
+}
+
+int lws_finalize_http_header(struct lws *wsi, unsigned char **p,
+                            unsigned char *end)
+{
+#ifdef LWS_WITH_HTTP2
+       if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi))
+               return 0;
+#else
+       (void)wsi;
+#endif
+       if ((lws_intptr_t)(end - *p) < 3)
+               return 1;
+       *((*p)++) = '\x0d';
+       *((*p)++) = '\x0a';
+
+       return 0;
+}
+
+int
+lws_finalize_write_http_header(struct lws *wsi, unsigned char *start,
+                              unsigned char **pp, unsigned char *end)
+{
+       unsigned char *p;
+       int len;
+
+       if (lws_finalize_http_header(wsi, pp, end))
+               return 1;
+
+       p = *pp;
+       len = lws_ptr_diff(p, start);
+
+       if (lws_write(wsi, start, len, LWS_WRITE_HTTP_HEADERS) != len)
+               return 1;
+
+       return 0;
+}
+
+int
+lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
+                            const unsigned char *value, int length,
+                            unsigned char **p, unsigned char *end)
+{
+       const unsigned char *name;
+#ifdef LWS_WITH_HTTP2
+       if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi))
+               return lws_add_http2_header_by_token(wsi, token, value,
+                                                    length, p, end);
+#endif
+       name = lws_token_to_string(token);
+       if (!name)
+               return 1;
+
+       return lws_add_http_header_by_name(wsi, name, value, length, p, end);
+}
+
+int
+lws_add_http_header_content_length(struct lws *wsi,
+                                  lws_filepos_t content_length,
+                                  unsigned char **p, unsigned char *end)
+{
+       char b[24];
+       int n;
+
+       n = lws_snprintf(b, sizeof(b) - 1, "%llu", (unsigned long long)content_length);
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH,
+                                        (unsigned char *)b, n, p, end))
+               return 1;
+       wsi->http.tx_content_length = content_length;
+       wsi->http.tx_content_remain = content_length;
+
+       lwsl_info("%s: wsi %p: tx_content_length/remain %llu\n", __func__,
+                       wsi, (unsigned long long)content_length);
+
+       return 0;
+}
+
+int
+lws_add_http_common_headers(struct lws *wsi, unsigned int code,
+                           const char *content_type, lws_filepos_t content_len,
+                           unsigned char **p, unsigned char *end)
+{
+       const char *ka[] = { "close", "keep-alive" };
+       int types[] = { HTTP_CONNECTION_CLOSE, HTTP_CONNECTION_KEEP_ALIVE },
+                       t = 0;
+
+       if (lws_add_http_header_status(wsi, code, p, end))
+               return 1;
+
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                       (unsigned char *)content_type,
+                                       (int)strlen(content_type), p, end))
+               return 1;
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (!wsi->http.lcs &&
+           (!strncmp(content_type, "text/", 5) ||
+            !strcmp(content_type, "application/javascript") ||
+            !strcmp(content_type, "image/svg+xml")))
+               lws_http_compression_apply(wsi, NULL, p, end, 0);
+#endif
+
+       /*
+        * if we decided to compress it, we don't know the content length...
+        * the compressed data will go out chunked on h1
+        */
+       if (
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+           !wsi->http.lcs &&
+#endif
+            content_len != LWS_ILLEGAL_HTTP_CONTENT_LEN) {
+               if (lws_add_http_header_content_length(wsi, content_len,
+                                                      p, end))
+                       return 1;
+       } else {
+               /* there was no length... it normally means CONNECTION_CLOSE */
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+
+               if (!wsi->http2_substream && wsi->http.lcs) {
+                       /* so...
+                        *  - h1 connection
+                        *  - http compression transform active
+                        *  - did not send content length
+                        *
+                        * then mark as chunked...
+                        */
+                       wsi->http.comp_ctx.chunking = 1;
+                       if (lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+                                       (unsigned char *)"chunked", 7, p, end))
+                               return -1;
+
+                       /* ... but h1 compression is chunked, if active we can
+                        * still pipeline
+                        */
+                       if (wsi->http.lcs &&
+                           wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE)
+                               t = 1;
+               }
+#endif
+               if (!wsi->http2_substream) {
+                       if (lws_add_http_header_by_token(wsi,
+                                                WSI_TOKEN_CONNECTION,
+                                                (unsigned char *)ka[t],
+                                                (int)strlen(ka[t]), p, end))
+                               return 1;
+
+                       wsi->http.conn_type = types[t];
+               }
+       }
+
+       return 0;
+}
+
+static const char * const err400[] = {
+       "Bad Request",
+       "Unauthorized",
+       "Payment Required",
+       "Forbidden",
+       "Not Found",
+       "Method Not Allowed",
+       "Not Acceptable",
+       "Proxy Auth Required",
+       "Request Timeout",
+       "Conflict",
+       "Gone",
+       "Length Required",
+       "Precondition Failed",
+       "Request Entity Too Large",
+       "Request URI too Long",
+       "Unsupported Media Type",
+       "Requested Range Not Satisfiable",
+       "Expectation Failed"
+};
+
+static const char * const err500[] = {
+       "Internal Server Error",
+       "Not Implemented",
+       "Bad Gateway",
+       "Service Unavailable",
+       "Gateway Timeout",
+       "HTTP Version Not Supported"
+};
+
+/* security best practices from Mozilla Observatory */
+
+static const
+struct lws_protocol_vhost_options pvo_hsbph[] = {{
+       NULL, NULL, "referrer-policy:", "no-referrer"
+}, {
+       &pvo_hsbph[0], NULL, "x-frame-options:", "deny"
+}, {
+       &pvo_hsbph[1], NULL, "x-xss-protection:", "1; mode=block"
+}, {
+       &pvo_hsbph[2], NULL, "x-content-type-options:", "nosniff"
+}, {
+       &pvo_hsbph[3], NULL, "content-security-policy:",
+       "default-src 'none'; img-src 'self' data: ; "
+               "script-src 'self'; font-src 'self'; "
+               "style-src 'self'; connect-src 'self'; "
+               "frame-ancestors 'none'; base-uri 'none';"
+               "form-action 'self';"
+}};
+
+int
+lws_add_http_header_status(struct lws *wsi, unsigned int _code,
+                          unsigned char **p, unsigned char *end)
+{
+       static const char * const hver[] = {
+               "HTTP/1.0", "HTTP/1.1", "HTTP/2"
+       };
+       const struct lws_protocol_vhost_options *headers;
+       unsigned int code = _code & LWSAHH_CODE_MASK;
+       const char *description = "", *p1;
+       unsigned char code_and_desc[60];
+       int n;
+
+#ifdef LWS_WITH_ACCESS_LOG
+       wsi->http.access_log.response = code;
+#endif
+
+#ifdef LWS_WITH_HTTP2
+       if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) {
+               n = lws_add_http2_header_status(wsi, code, p, end);
+               if (n)
+                       return n;
+       } else
+#endif
+       {
+               if (code >= 400 && code < (400 + LWS_ARRAY_SIZE(err400)))
+                       description = err400[code - 400];
+               if (code >= 500 && code < (500 + LWS_ARRAY_SIZE(err500)))
+                       description = err500[code - 500];
+
+               if (code == 100)
+                       description = "Continue";
+               if (code == 200)
+                       description = "OK";
+               if (code == 304)
+                       description = "Not Modified";
+               else
+                       if (code >= 300 && code < 400)
+                               description = "Redirect";
+
+               if (wsi->http.request_version < LWS_ARRAY_SIZE(hver))
+                       p1 = hver[wsi->http.request_version];
+               else
+                       p1 = hver[0];
+
+               n = lws_snprintf((char *)code_and_desc, sizeof(code_and_desc) - 1, "%s %u %s", p1, code,
+                           description);
+
+               if (lws_add_http_header_by_name(wsi, NULL, code_and_desc, n, p,
+                                               end))
+                       return 1;
+       }
+
+       headers = wsi->vhost->headers;
+       while (headers) {
+               if (lws_add_http_header_by_name(wsi,
+                               (const unsigned char *)headers->name,
+                               (unsigned char *)headers->value,
+                               (int)strlen(headers->value), p, end))
+                       return 1;
+
+               headers = headers->next;
+       }
+
+       if (wsi->vhost->options &
+           LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE) {
+               headers = &pvo_hsbph[LWS_ARRAY_SIZE(pvo_hsbph) - 1];
+               while (headers) {
+                       if (lws_add_http_header_by_name(wsi,
+                                       (const unsigned char *)headers->name,
+                                       (unsigned char *)headers->value,
+                                       (int)strlen(headers->value), p, end))
+                               return 1;
+
+                       headers = headers->next;
+               }
+       }
+
+       if (wsi->context->server_string &&
+           !(_code & LWSAHH_FLAG_NO_SERVER_NAME))
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
+                               (unsigned char *)wsi->context->server_string,
+                               wsi->context->server_string_len, p, end))
+                       return 1;
+
+       if (wsi->vhost->options & LWS_SERVER_OPTION_STS)
+               if (lws_add_http_header_by_name(wsi, (unsigned char *)
+                               "Strict-Transport-Security:",
+                               (unsigned char *)"max-age=15768000 ; "
+                               "includeSubDomains", 36, p, end))
+                       return 1;
+
+       if (*p >= (end - 2)) {
+               lwsl_err("%s: reached end of buffer\n", __func__);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_return_http_status(struct lws *wsi, unsigned int code,
+                      const char *html_body)
+{
+       struct lws_context *context = lws_get_context(wsi);
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       unsigned char *p = pt->serv_buf + LWS_PRE;
+       unsigned char *start = p;
+       unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE;
+       char *body = (char *)start + context->pt_serv_buf_size - 512;
+       int n = 0, m = 0, len;
+       char slen[20];
+
+       if (!wsi->vhost) {
+               lwsl_err("%s: wsi not bound to vhost\n", __func__);
+
+               return 1;
+       }
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (!wsi->handling_404 &&
+           wsi->vhost->http.error_document_404 &&
+           code == HTTP_STATUS_NOT_FOUND)
+               /* we should do a redirect, and do the 404 there */
+               if (lws_http_redirect(wsi, HTTP_STATUS_FOUND,
+                              (uint8_t *)wsi->vhost->http.error_document_404,
+                              (int)strlen(wsi->vhost->http.error_document_404),
+                              &p, end) > 0)
+                       return 0;
+#endif
+
+       /* if the redirect failed, just do a simple status */
+       p = start;
+
+       if (!html_body)
+               html_body = "";
+
+       if (lws_add_http_header_status(wsi, code, &p, end))
+               return 1;
+
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                        (unsigned char *)"text/html", 9,
+                                        &p, end))
+               return 1;
+
+       len = lws_snprintf(body, 510, "<html><head>"
+               "<meta charset=utf-8 http-equiv=\"Content-Language\" "
+                       "content=\"en\"/>"
+               "<link rel=\"stylesheet\" type=\"text/css\" "
+                       "href=\"/error.css\"/>"
+               "</head><body><h1>%u</h1>%s</body></html>", code, html_body);
+
+
+       n = lws_snprintf(slen, 12, "%d", len);
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH,
+                                        (unsigned char *)slen, n, &p, end))
+               return 1;
+
+       if (lws_finalize_http_header(wsi, &p, end))
+               return 1;
+
+#if defined(LWS_WITH_HTTP2)
+       if (wsi->http2_substream) {
+
+               /*
+                * 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, lws_ptr_diff(p, start),
+                             LWS_WRITE_HTTP_HEADERS);
+               if (m != lws_ptr_diff(p, start))
+                       return 1;
+
+               /*
+                * ... but stash the body and send it as a priority next
+                * handle_POLLOUT
+                */
+               wsi->http.tx_content_length = len;
+               wsi->http.tx_content_remain = len;
+
+               wsi->h2.pending_status_body = lws_malloc(len + LWS_PRE + 1,
+                                                       "pending status body");
+               if (!wsi->h2.pending_status_body)
+                       return -1;
+
+               strcpy(wsi->h2.pending_status_body + LWS_PRE, 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.
+                */
+
+               n = lws_ptr_diff(p, start) + len;
+               memcpy(p, body, len);
+               m = lws_write(wsi, start, n, LWS_WRITE_HTTP);
+               if (m != n)
+                       return 1;
+       }
+
+       return m != n;
+}
+
+LWS_VISIBLE int
+lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len,
+                 unsigned char **p, unsigned char *end)
+{
+       unsigned char *start = *p;
+
+       if (lws_add_http_header_status(wsi, code, p, end))
+               return -1;
+
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, loc, len,
+                                        p, end))
+               return -1;
+       /*
+        * if we're going with http/1.1 and keepalive, we have to give fake
+        * content metadata so the client knows we completed the transaction and
+        * it can do the redirect...
+        */
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                        (unsigned char *)"text/html", 9, p,
+                                        end))
+               return -1;
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH,
+                                        (unsigned char *)"0", 1, p, end))
+               return -1;
+
+       if (lws_finalize_http_header(wsi, p, end))
+               return -1;
+
+       return lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS |
+                                                LWS_WRITE_H2_STREAM_END);
+}
+
+#if !defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+LWS_VISIBLE int
+lws_http_compression_apply(struct lws *wsi, const char *name,
+                          unsigned char **p, unsigned char *end, char decomp)
+{
+       (void)wsi;
+       (void)name;
+       (void)p;
+       (void)end;
+       (void)decomp;
+
+       return 0;
+}
+#endif
+
+int
+lws_http_headers_detach(struct lws *wsi)
+{
+       return lws_header_table_detach(wsi, 0);
+}
+
+void
+lws_sul_http_ah_lifecheck(lws_sorted_usec_list_t *sul)
+{
+       struct allocated_headers *ah;
+       struct lws_context_per_thread *pt = lws_container_of(sul,
+                       struct lws_context_per_thread, sul_ah_lifecheck);
+       struct lws *wsi;
+       time_t now;
+       int m;
+
+       now = time(NULL);
+
+       lws_pt_lock(pt, __func__);
+
+       ah = pt->http.ah_list;
+       while (ah) {
+               int len;
+               char buf[256];
+               const unsigned char *c;
+
+               if (!ah->in_use || !ah->wsi || !ah->assigned ||
+                   (ah->wsi->vhost &&
+                    (now - ah->assigned) <
+                    ah->wsi->vhost->timeout_secs_ah_idle + 360)) {
+                       ah = ah->next;
+                       continue;
+               }
+
+               /*
+                * a single ah session somehow got held for
+                * an unreasonable amount of time.
+                *
+                * Dump info on the connection...
+                */
+               wsi = ah->wsi;
+               buf[0] = '\0';
+#if !defined(LWS_PLAT_OPTEE)
+               lws_get_peer_simple(wsi, buf, sizeof(buf));
+#else
+               buf[0] = '\0';
+#endif
+               lwsl_notice("ah excessive hold: wsi %p\n"
+                           "  peer address: %s\n"
+                           "  ah pos %lu\n",
+                           wsi, buf, (unsigned long)ah->pos);
+               buf[0] = '\0';
+               m = 0;
+               do {
+                       c = lws_token_to_string(m);
+                       if (!c)
+                               break;
+                       if (!(*c))
+                               break;
+
+                       len = lws_hdr_total_length(wsi, m);
+                       if (!len || len > (int)sizeof(buf) - 1) {
+                               m++;
+                               continue;
+                       }
+
+                       if (lws_hdr_copy(wsi, buf, sizeof buf, m) > 0) {
+                               buf[sizeof(buf) - 1] = '\0';
+
+                               lwsl_notice("   %s = %s\n",
+                                           (const char *)c, buf);
+                       }
+                       m++;
+               } while (1);
+
+               /* explicitly detach the ah */
+               lws_header_table_detach(wsi, 0);
+
+               /* ... and then drop the connection */
+
+               __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                            "excessive ah");
+
+               ah = pt->http.ah_list;
+       }
+
+       lws_pt_unlock(pt);
+}
similarity index 88%
rename from lib/lextable-strings.h
rename to lib/roles/http/lextable-strings.h
index c9fe6ff..1e4fee8 100644 (file)
@@ -1,10 +1,6 @@
 /* set of parsable strings -- ALL LOWER CASE */
 
-#if !defined(STORE_IN_ROM)
-#define STORE_IN_ROM
-#endif
-
-STORE_IN_ROM static const char * const set[] = {
+static const char * const set[] = {
        "get ",
        "post ",
        "options ",
@@ -96,6 +92,12 @@ STORE_IN_ROM static const char * const set[] = {
 
        "x-forwarded-for",
        "connect ",
+       "head ",
+       "te:",          /* http/2 wants it to reject it */
+       "replay-nonce:", /* ACME */
+       ":protocol",            /* defined in mcmanus-httpbis-h2-ws-02 */
+
+       "x-auth-token:",
 
        "", /* not matchable */
 
diff --git a/lib/roles/http/lextable.h b/lib/roles/http/lextable.h
new file mode 100644 (file)
index 0000000..9a8063b
--- /dev/null
@@ -0,0 +1,838 @@
+/* pos 0000:   0 */    0x67 /* 'g' */, 0x40, 0x00  /* (to 0x0040 state   1) */,
+                       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' */, 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 /* ':' */, 0x56, 0x02  /* (to 0x027A state 299) */,
+                       0x65 /* 'e' */, 0xE8, 0x02  /* (to 0x030F state 409) */,
+                       0x66 /* 'f' */, 0x04, 0x03  /* (to 0x032E state 425) */,
+                       0x6C /* 'l' */, 0x26, 0x03  /* (to 0x0353 state 458) */,
+                       0x6D /* 'm' */, 0x49, 0x03  /* (to 0x0379 state 484) */,
+                       0x74 /* 't' */, 0xB8, 0x03  /* (to 0x03EB state 578) */,
+                       0x76 /* 'v' */, 0xD9, 0x03  /* (to 0x040F state 606) */,
+                       0x77 /* 'w' */, 0xE6, 0x03  /* (to 0x041F state 614) */,
+                       0x78 /* 'x' */, 0x0D, 0x04  /* (to 0x0449 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' */, 0x95, 0x01  /* (to 0x01DD state 211) */,
+                       0x61 /* 'a' */, 0xE6, 0x03  /* (to 0x0431 state 631) */,
+                       0x75 /* 'u' */, 0xE8, 0x03  /* (to 0x0436 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' */, 0x51, 0x00  /* (to 0x00AB state  42) */,
+                       0x08, /* fail */
+/* pos 005e:  11 */    0xF4 /* 't' -> */,
+/* pos 005f:  12 */    0xE9 /* 'i' -> */,
+/* pos 0060:  13 */    0xEF /* 'o' -> */,
+/* pos 0061:  14 */    0xEE /* 'n' -> */,
+/* pos 0062:  15 */    0xF3 /* 's' -> */,
+/* pos 0063:  16 */    0xA0 /* ' ' -> */,
+/* pos 0064:  17 */    0x00, 0x02                  /* - terminal marker  2 - */,
+/* pos 0066:  18 */    0x6F /* 'o' */, 0x0A, 0x00  /* (to 0x0070 state  19) */,
+                       0x74 /* 't' */, 0xBF, 0x00  /* (to 0x0128 state 110) */,
+                       0x65 /* 'e' */, 0x04, 0x04  /* (to 0x0470 state 676) */,
+                       0x08, /* fail */
+/* 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 007c:  24 */    0x6E /* 'n' */, 0x07, 0x00  /* (to 0x0083 state  25) */,
+                       0x6F /* 'o' */, 0x87, 0x01  /* (to 0x0206 state 243) */,
+                       0x08, /* fail */
+/* pos 0083:  25 */    0x6E /* 'n' */, 0x07, 0x00  /* (to 0x008A state  26) */,
+                       0x74 /* 't' */, 0x86, 0x01  /* (to 0x020C state 248) */,
+                       0x08, /* fail */
+/* 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 /* ' ' */, 0xDE, 0x03  /* (to 0x046E state 675) */,
+                       0x08, /* fail */
+/* 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' */, 0x68, 0x03  /* (to 0x0404 state 596) */,
+                       0x72 /* 'r' */, 0xA0, 0x03  /* (to 0x043F state 642) */,
+                       0x08, /* fail */
+/* 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' */, 0x1C, 0x03  /* (to 0x03D1 state 553) */,
+                       0x08, /* fail */
+/* pos 00b9:  49 */    0x63 /* 'c' */, 0x0A, 0x00  /* (to 0x00C3 state  50) */,
+                       0x72 /* 'r' */, 0x05, 0x03  /* (to 0x03C1 state 539) */,
+                       0x74 /* 't' */, 0x08, 0x03  /* (to 0x03C7 state 544) */,
+                       0x08, /* fail */
+/* 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' */, 0x89, 0x01  /* (to 0x0269 state 284) */,
+                       0x6F /* 'o' */, 0x8F, 0x01  /* (to 0x0272 state 292) */,
+                       0x08, /* fail */
+/* 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 /* ':' */, 0x62, 0x01  /* (to 0x0267 state 283) */,
+                       0x08, /* fail */
+/* 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 0131: 113 */    0xB1 /* '1' -> */,
+/* pos 0132: 114 */    0xAE /* '.' -> */,
+/* pos 0133: 115 */    0x31 /* '1' */, 0x07, 0x00  /* (to 0x013A state 116) */,
+                       0x30 /* '0' */, 0x27, 0x03  /* (to 0x045D state 660) */,
+                       0x08, /* fail */
+/* 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' */, 0x86, 0x01  /* (to 0x02D5 state 358) */,
+                       0x6C /* 'l' */, 0x87, 0x01  /* (to 0x02D9 state 361) */,
+                       0x08, /* fail */
+/* 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 015f: 133 */    0xF4 /* 't' -> */,
+/* pos 0160: 134 */    0x3A /* ':' */, 0x07, 0x00  /* (to 0x0167 state 135) */,
+                       0x2D /* '-' */, 0x59, 0x00  /* (to 0x01BC state 192) */,
+                       0x08, /* fail */
+/* 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' */, 0x51, 0x01  /* (to 0x02C7 state 345) */,
+                       0x08, /* fail */
+/* 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' */, 0xA7, 0x01  /* (to 0x033A state 435) */,
+                       0x75 /* 'u' */, 0xAB, 0x01  /* (to 0x0341 state 441) */,
+                       0x08, /* fail */
+/* pos 019a: 166 */    0x6F /* 'o' */, 0x07, 0x00  /* (to 0x01A1 state 167) */,
+                       0x61 /* 'a' */, 0x97, 0x01  /* (to 0x0334 state 430) */,
+                       0x08, /* fail */
+/* 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' */, 0xF4, 0x00  /* (to 0x02B6 state 330) */,
+                       0x72 /* 'r' */, 0xFA, 0x00  /* (to 0x02BF state 338) */,
+                       0x08, /* fail */
+/* 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' */, 0xA7, 0x01  /* (to 0x0387 state 497) */,
+                       0x08, /* fail */
+/* 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' */, 0xC9, 0x00  /* (to 0x02DF state 366) */,
+                       0x65 /* 'e' */, 0xD3, 0x00  /* (to 0x02EC state 378) */,
+                       0x72 /* 'r' */, 0xEC, 0x00  /* (to 0x0308 state 403) */,
+                       0x08, /* fail */
+/* pos 0220: 253 */    0x65 /* 'e' */, 0x0A, 0x00  /* (to 0x022A state 254) */,
+                       0x61 /* 'a' */, 0xD3, 0x00  /* (to 0x02F6 state 387) */,
+                       0x6F /* 'o' */, 0xD9, 0x00  /* (to 0x02FF state 395) */,
+                       0x08, /* fail */
+/* 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' */, 0xFF, 0x01  /* (to 0x0439 state 637) */,
+                       0x08, /* fail */
+/* 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 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' */, 0x0A, 0x00  /* (to 0x025A state 277) */,
+                       0x74 /* 't' */, 0x63, 0x01  /* (to 0x03B6 state 529) */,
+                       0x70 /* 'p' */, 0x22, 0x02  /* (to 0x0478 state 682) */,
+                       0x08, /* fail */
+/* pos 025a: 277 */    0x65 /* 'e' */, 0x07, 0x00  /* (to 0x0261 state 278) */,
+                       0x72 /* 'r' */, 0x53, 0x01  /* (to 0x03B0 state 524) */,
+                       0x08, /* fail */
+/* pos 0261: 278 */    0xF2 /* 'r' -> */,
+/* pos 0262: 279 */    0xE5 /* 'e' -> */,
+/* pos 0263: 280 */    0xF2 /* 'r' -> */,
+/* pos 0264: 281 */    0xBA /* ':' -> */,
+/* pos 0265: 282 */    0x00, 0x1F                  /* - terminal marker 31 - */,
+/* pos 0267: 283 */    0x00, 0x20                  /* - terminal marker 32 - */,
+/* pos 0269: 284 */    0xE5 /* 'e' -> */,
+/* pos 026a: 285 */    0xF2 /* 'r' -> */,
+/* pos 026b: 286 */    0xF3 /* 's' -> */,
+/* pos 026c: 287 */    0xE9 /* 'i' -> */,
+/* pos 026d: 288 */    0xEF /* 'o' -> */,
+/* pos 026e: 289 */    0xEE /* 'n' -> */,
+/* pos 026f: 290 */    0xBA /* ':' -> */,
+/* pos 0270: 291 */    0x00, 0x21                  /* - terminal marker 33 - */,
+/* pos 0272: 292 */    0xF2 /* 'r' -> */,
+/* pos 0273: 293 */    0xE9 /* 'i' -> */,
+/* pos 0274: 294 */    0xE7 /* 'g' -> */,
+/* pos 0275: 295 */    0xE9 /* 'i' -> */,
+/* pos 0276: 296 */    0xEE /* 'n' -> */,
+/* pos 0277: 297 */    0xBA /* ':' -> */,
+/* pos 0278: 298 */    0x00, 0x22                  /* - terminal marker 34 - */,
+/* pos 027a: 299 */    0x61 /* 'a' */, 0x0D, 0x00  /* (to 0x0287 state 300) */,
+                       0x6D /* 'm' */, 0x14, 0x00  /* (to 0x0291 state 309) */,
+                       0x70 /* 'p' */, 0x18, 0x00  /* (to 0x0298 state 315) */,
+                       0x73 /* 's' */, 0x20, 0x00  /* (to 0x02A3 state 319) */,
+                       0x08, /* fail */
+/* pos 0287: 300 */    0xF5 /* 'u' -> */,
+/* pos 0288: 301 */    0xF4 /* 't' -> */,
+/* pos 0289: 302 */    0xE8 /* 'h' -> */,
+/* pos 028a: 303 */    0xEF /* 'o' -> */,
+/* pos 028b: 304 */    0xF2 /* 'r' -> */,
+/* pos 028c: 305 */    0xE9 /* 'i' -> */,
+/* pos 028d: 306 */    0xF4 /* 't' -> */,
+/* pos 028e: 307 */    0xF9 /* 'y' -> */,
+/* pos 028f: 308 */    0x00, 0x23                  /* - terminal marker 35 - */,
+/* pos 0291: 309 */    0xE5 /* 'e' -> */,
+/* pos 0292: 310 */    0xF4 /* 't' -> */,
+/* pos 0293: 311 */    0xE8 /* 'h' -> */,
+/* pos 0294: 312 */    0xEF /* 'o' -> */,
+/* pos 0295: 313 */    0xE4 /* 'd' -> */,
+/* pos 0296: 314 */    0x00, 0x24                  /* - terminal marker 36 - */,
+/* pos 0298: 315 */    0x61 /* 'a' */, 0x07, 0x00  /* (to 0x029F state 316) */,
+                       0x72 /* 'r' */, 0xE9, 0x01  /* (to 0x0484 state 693) */,
+                       0x08, /* fail */
+/* pos 029f: 316 */    0xF4 /* 't' -> */,
+/* pos 02a0: 317 */    0xE8 /* 'h' -> */,
+/* pos 02a1: 318 */    0x00, 0x25                  /* - terminal marker 37 - */,
+/* pos 02a3: 319 */    0x63 /* 'c' */, 0x07, 0x00  /* (to 0x02AA state 320) */,
+                       0x74 /* 't' */, 0x0A, 0x00  /* (to 0x02B0 state 325) */,
+                       0x08, /* fail */
+/* pos 02aa: 320 */    0xE8 /* 'h' -> */,
+/* pos 02ab: 321 */    0xE5 /* 'e' -> */,
+/* pos 02ac: 322 */    0xED /* 'm' -> */,
+/* pos 02ad: 323 */    0xE5 /* 'e' -> */,
+/* pos 02ae: 324 */    0x00, 0x26                  /* - terminal marker 38 - */,
+/* pos 02b0: 325 */    0xE1 /* 'a' -> */,
+/* pos 02b1: 326 */    0xF4 /* 't' -> */,
+/* pos 02b2: 327 */    0xF5 /* 'u' -> */,
+/* pos 02b3: 328 */    0xF3 /* 's' -> */,
+/* pos 02b4: 329 */    0x00, 0x27                  /* - terminal marker 39 - */,
+/* pos 02b6: 330 */    0xE8 /* 'h' -> */,
+/* pos 02b7: 331 */    0xE1 /* 'a' -> */,
+/* pos 02b8: 332 */    0xF2 /* 'r' -> */,
+/* pos 02b9: 333 */    0xF3 /* 's' -> */,
+/* pos 02ba: 334 */    0xE5 /* 'e' -> */,
+/* pos 02bb: 335 */    0xF4 /* 't' -> */,
+/* pos 02bc: 336 */    0xBA /* ':' -> */,
+/* pos 02bd: 337 */    0x00, 0x28                  /* - terminal marker 40 - */,
+/* pos 02bf: 338 */    0xE1 /* 'a' -> */,
+/* pos 02c0: 339 */    0xEE /* 'n' -> */,
+/* pos 02c1: 340 */    0xE7 /* 'g' -> */,
+/* pos 02c2: 341 */    0xE5 /* 'e' -> */,
+/* pos 02c3: 342 */    0xF3 /* 's' -> */,
+/* pos 02c4: 343 */    0xBA /* ':' -> */,
+/* pos 02c5: 344 */    0x00, 0x29                  /* - terminal marker 41 - */,
+/* pos 02c7: 345 */    0xEC /* 'l' -> */,
+/* pos 02c8: 346 */    0xEC /* 'l' -> */,
+/* pos 02c9: 347 */    0xEF /* 'o' -> */,
+/* pos 02ca: 348 */    0xF7 /* 'w' -> */,
+/* pos 02cb: 349 */    0xAD /* '-' -> */,
+/* pos 02cc: 350 */    0xEF /* 'o' -> */,
+/* pos 02cd: 351 */    0xF2 /* 'r' -> */,
+/* pos 02ce: 352 */    0xE9 /* 'i' -> */,
+/* pos 02cf: 353 */    0xE7 /* 'g' -> */,
+/* pos 02d0: 354 */    0xE9 /* 'i' -> */,
+/* pos 02d1: 355 */    0xEE /* 'n' -> */,
+/* pos 02d2: 356 */    0xBA /* ':' -> */,
+/* pos 02d3: 357 */    0x00, 0x2A                  /* - terminal marker 42 - */,
+/* pos 02d5: 358 */    0xE5 /* 'e' -> */,
+/* pos 02d6: 359 */    0xBA /* ':' -> */,
+/* pos 02d7: 360 */    0x00, 0x2B                  /* - terminal marker 43 - */,
+/* pos 02d9: 361 */    0xEC /* 'l' -> */,
+/* pos 02da: 362 */    0xEF /* 'o' -> */,
+/* pos 02db: 363 */    0xF7 /* 'w' -> */,
+/* pos 02dc: 364 */    0xBA /* ':' -> */,
+/* pos 02dd: 365 */    0x00, 0x2C                  /* - terminal marker 44 - */,
+/* pos 02df: 366 */    0xE9 /* 'i' -> */,
+/* pos 02e0: 367 */    0xF3 /* 's' -> */,
+/* pos 02e1: 368 */    0xF0 /* 'p' -> */,
+/* pos 02e2: 369 */    0xEF /* 'o' -> */,
+/* pos 02e3: 370 */    0xF3 /* 's' -> */,
+/* pos 02e4: 371 */    0xE9 /* 'i' -> */,
+/* pos 02e5: 372 */    0xF4 /* 't' -> */,
+/* pos 02e6: 373 */    0xE9 /* 'i' -> */,
+/* pos 02e7: 374 */    0xEF /* 'o' -> */,
+/* pos 02e8: 375 */    0xEE /* 'n' -> */,
+/* pos 02e9: 376 */    0xBA /* ':' -> */,
+/* pos 02ea: 377 */    0x00, 0x2D                  /* - terminal marker 45 - */,
+/* pos 02ec: 378 */    0xEE /* 'n' -> */,
+/* pos 02ed: 379 */    0xE3 /* 'c' -> */,
+/* pos 02ee: 380 */    0xEF /* 'o' -> */,
+/* pos 02ef: 381 */    0xE4 /* 'd' -> */,
+/* pos 02f0: 382 */    0xE9 /* 'i' -> */,
+/* pos 02f1: 383 */    0xEE /* 'n' -> */,
+/* pos 02f2: 384 */    0xE7 /* 'g' -> */,
+/* pos 02f3: 385 */    0xBA /* ':' -> */,
+/* pos 02f4: 386 */    0x00, 0x2E                  /* - terminal marker 46 - */,
+/* pos 02f6: 387 */    0xEE /* 'n' -> */,
+/* pos 02f7: 388 */    0xE7 /* 'g' -> */,
+/* pos 02f8: 389 */    0xF5 /* 'u' -> */,
+/* pos 02f9: 390 */    0xE1 /* 'a' -> */,
+/* pos 02fa: 391 */    0xE7 /* 'g' -> */,
+/* pos 02fb: 392 */    0xE5 /* 'e' -> */,
+/* pos 02fc: 393 */    0xBA /* ':' -> */,
+/* pos 02fd: 394 */    0x00, 0x2F                  /* - terminal marker 47 - */,
+/* pos 02ff: 395 */    0xE3 /* 'c' -> */,
+/* pos 0300: 396 */    0xE1 /* 'a' -> */,
+/* pos 0301: 397 */    0xF4 /* 't' -> */,
+/* pos 0302: 398 */    0xE9 /* 'i' -> */,
+/* pos 0303: 399 */    0xEF /* 'o' -> */,
+/* pos 0304: 400 */    0xEE /* 'n' -> */,
+/* pos 0305: 401 */    0xBA /* ':' -> */,
+/* pos 0306: 402 */    0x00, 0x30                  /* - terminal marker 48 - */,
+/* pos 0308: 403 */    0xE1 /* 'a' -> */,
+/* pos 0309: 404 */    0xEE /* 'n' -> */,
+/* pos 030a: 405 */    0xE7 /* 'g' -> */,
+/* pos 030b: 406 */    0xE5 /* 'e' -> */,
+/* pos 030c: 407 */    0xBA /* ':' -> */,
+/* pos 030d: 408 */    0x00, 0x31                  /* - terminal marker 49 - */,
+/* pos 030f: 409 */    0x74 /* 't' */, 0x07, 0x00  /* (to 0x0316 state 410) */,
+                       0x78 /* 'x' */, 0x09, 0x00  /* (to 0x031B state 414) */,
+                       0x08, /* fail */
+/* pos 0316: 410 */    0xE1 /* 'a' -> */,
+/* pos 0317: 411 */    0xE7 /* 'g' -> */,
+/* pos 0318: 412 */    0xBA /* ':' -> */,
+/* pos 0319: 413 */    0x00, 0x32                  /* - terminal marker 50 - */,
+/* pos 031b: 414 */    0xF0 /* 'p' -> */,
+/* pos 031c: 415 */    0x65 /* 'e' */, 0x07, 0x00  /* (to 0x0323 state 416) */,
+                       0x69 /* 'i' */, 0x09, 0x00  /* (to 0x0328 state 420) */,
+                       0x08, /* fail */
+/* pos 0323: 416 */    0xE3 /* 'c' -> */,
+/* pos 0324: 417 */    0xF4 /* 't' -> */,
+/* pos 0325: 418 */    0xBA /* ':' -> */,
+/* pos 0326: 419 */    0x00, 0x33                  /* - terminal marker 51 - */,
+/* pos 0328: 420 */    0xF2 /* 'r' -> */,
+/* pos 0329: 421 */    0xE5 /* 'e' -> */,
+/* pos 032a: 422 */    0xF3 /* 's' -> */,
+/* pos 032b: 423 */    0xBA /* ':' -> */,
+/* pos 032c: 424 */    0x00, 0x34                  /* - terminal marker 52 - */,
+/* pos 032e: 425 */    0xF2 /* 'r' -> */,
+/* pos 032f: 426 */    0xEF /* 'o' -> */,
+/* pos 0330: 427 */    0xED /* 'm' -> */,
+/* pos 0331: 428 */    0xBA /* ':' -> */,
+/* pos 0332: 429 */    0x00, 0x35                  /* - terminal marker 53 - */,
+/* pos 0334: 430 */    0xF4 /* 't' -> */,
+/* pos 0335: 431 */    0xE3 /* 'c' -> */,
+/* pos 0336: 432 */    0xE8 /* 'h' -> */,
+/* pos 0337: 433 */    0xBA /* ':' -> */,
+/* pos 0338: 434 */    0x00, 0x36                  /* - terminal marker 54 - */,
+/* pos 033a: 435 */    0xE1 /* 'a' -> */,
+/* pos 033b: 436 */    0xEE /* 'n' -> */,
+/* pos 033c: 437 */    0xE7 /* 'g' -> */,
+/* pos 033d: 438 */    0xE5 /* 'e' -> */,
+/* pos 033e: 439 */    0xBA /* ':' -> */,
+/* pos 033f: 440 */    0x00, 0x37                  /* - terminal marker 55 - */,
+/* pos 0341: 441 */    0xEE /* 'n' -> */,
+/* pos 0342: 442 */    0xED /* 'm' -> */,
+/* pos 0343: 443 */    0xEF /* 'o' -> */,
+/* pos 0344: 444 */    0xE4 /* 'd' -> */,
+/* pos 0345: 445 */    0xE9 /* 'i' -> */,
+/* pos 0346: 446 */    0xE6 /* 'f' -> */,
+/* pos 0347: 447 */    0xE9 /* 'i' -> */,
+/* pos 0348: 448 */    0xE5 /* 'e' -> */,
+/* pos 0349: 449 */    0xE4 /* 'd' -> */,
+/* pos 034a: 450 */    0xAD /* '-' -> */,
+/* pos 034b: 451 */    0xF3 /* 's' -> */,
+/* pos 034c: 452 */    0xE9 /* 'i' -> */,
+/* pos 034d: 453 */    0xEE /* 'n' -> */,
+/* pos 034e: 454 */    0xE3 /* 'c' -> */,
+/* pos 034f: 455 */    0xE5 /* 'e' -> */,
+/* pos 0350: 456 */    0xBA /* ':' -> */,
+/* pos 0351: 457 */    0x00, 0x38                  /* - terminal marker 56 - */,
+/* pos 0353: 458 */    0x61 /* 'a' */, 0x0A, 0x00  /* (to 0x035D state 459) */,
+                       0x69 /* 'i' */, 0x15, 0x00  /* (to 0x036B state 472) */,
+                       0x6F /* 'o' */, 0x17, 0x00  /* (to 0x0370 state 476) */,
+                       0x08, /* fail */
+/* pos 035d: 459 */    0xF3 /* 's' -> */,
+/* pos 035e: 460 */    0xF4 /* 't' -> */,
+/* pos 035f: 461 */    0xAD /* '-' -> */,
+/* pos 0360: 462 */    0xED /* 'm' -> */,
+/* pos 0361: 463 */    0xEF /* 'o' -> */,
+/* pos 0362: 464 */    0xE4 /* 'd' -> */,
+/* pos 0363: 465 */    0xE9 /* 'i' -> */,
+/* pos 0364: 466 */    0xE6 /* 'f' -> */,
+/* pos 0365: 467 */    0xE9 /* 'i' -> */,
+/* pos 0366: 468 */    0xE5 /* 'e' -> */,
+/* pos 0367: 469 */    0xE4 /* 'd' -> */,
+/* pos 0368: 470 */    0xBA /* ':' -> */,
+/* pos 0369: 471 */    0x00, 0x39                  /* - terminal marker 57 - */,
+/* pos 036b: 472 */    0xEE /* 'n' -> */,
+/* pos 036c: 473 */    0xEB /* 'k' -> */,
+/* pos 036d: 474 */    0xBA /* ':' -> */,
+/* pos 036e: 475 */    0x00, 0x3A                  /* - terminal marker 58 - */,
+/* pos 0370: 476 */    0xE3 /* 'c' -> */,
+/* pos 0371: 477 */    0xE1 /* 'a' -> */,
+/* pos 0372: 478 */    0xF4 /* 't' -> */,
+/* pos 0373: 479 */    0xE9 /* 'i' -> */,
+/* pos 0374: 480 */    0xEF /* 'o' -> */,
+/* pos 0375: 481 */    0xEE /* 'n' -> */,
+/* pos 0376: 482 */    0xBA /* ':' -> */,
+/* pos 0377: 483 */    0x00, 0x3B                  /* - terminal marker 59 - */,
+/* pos 0379: 484 */    0xE1 /* 'a' -> */,
+/* pos 037a: 485 */    0xF8 /* 'x' -> */,
+/* pos 037b: 486 */    0xAD /* '-' -> */,
+/* pos 037c: 487 */    0xE6 /* 'f' -> */,
+/* pos 037d: 488 */    0xEF /* 'o' -> */,
+/* pos 037e: 489 */    0xF2 /* 'r' -> */,
+/* pos 037f: 490 */    0xF7 /* 'w' -> */,
+/* pos 0380: 491 */    0xE1 /* 'a' -> */,
+/* pos 0381: 492 */    0xF2 /* 'r' -> */,
+/* pos 0382: 493 */    0xE4 /* 'd' -> */,
+/* pos 0383: 494 */    0xF3 /* 's' -> */,
+/* pos 0384: 495 */    0xBA /* ':' -> */,
+/* pos 0385: 496 */    0x00, 0x3C                  /* - terminal marker 60 - */,
+/* pos 0387: 497 */    0xF8 /* 'x' -> */,
+/* pos 0388: 498 */    0xF9 /* 'y' -> */,
+/* pos 0389: 499 */    0x2D /* '-' */, 0x07, 0x00  /* (to 0x0390 state 500) */,
+                       0x20 /* ' ' */, 0xBB, 0x00  /* (to 0x0447 state 649) */,
+                       0x08, /* fail */
+/* pos 0390: 500 */    0xE1 /* 'a' -> */,
+/* pos 0391: 501 */    0xF5 /* 'u' -> */,
+/* pos 0392: 502 */    0xF4 /* 't' -> */,
+/* pos 0393: 503 */    0xE8 /* 'h' -> */,
+/* pos 0394: 504 */    0x65 /* 'e' */, 0x07, 0x00  /* (to 0x039B state 505) */,
+                       0x6F /* 'o' */, 0x0E, 0x00  /* (to 0x03A5 state 514) */,
+                       0x08, /* fail */
+/* pos 039b: 505 */    0xEE /* 'n' -> */,
+/* pos 039c: 506 */    0xF4 /* 't' -> */,
+/* pos 039d: 507 */    0xE9 /* 'i' -> */,
+/* pos 039e: 508 */    0xE3 /* 'c' -> */,
+/* pos 039f: 509 */    0xE1 /* 'a' -> */,
+/* pos 03a0: 510 */    0xF4 /* 't' -> */,
+/* pos 03a1: 511 */    0xE5 /* 'e' -> */,
+/* pos 03a2: 512 */    0xBA /* ':' -> */,
+/* pos 03a3: 513 */    0x00, 0x3D                  /* - terminal marker 61 - */,
+/* pos 03a5: 514 */    0xF2 /* 'r' -> */,
+/* pos 03a6: 515 */    0xE9 /* 'i' -> */,
+/* pos 03a7: 516 */    0xFA /* 'z' -> */,
+/* pos 03a8: 517 */    0xE1 /* 'a' -> */,
+/* pos 03a9: 518 */    0xF4 /* 't' -> */,
+/* pos 03aa: 519 */    0xE9 /* 'i' -> */,
+/* pos 03ab: 520 */    0xEF /* 'o' -> */,
+/* pos 03ac: 521 */    0xEE /* 'n' -> */,
+/* pos 03ad: 522 */    0xBA /* ':' -> */,
+/* pos 03ae: 523 */    0x00, 0x3E                  /* - terminal marker 62 - */,
+/* pos 03b0: 524 */    0xE5 /* 'e' -> */,
+/* pos 03b1: 525 */    0xF3 /* 's' -> */,
+/* pos 03b2: 526 */    0xE8 /* 'h' -> */,
+/* pos 03b3: 527 */    0xBA /* ':' -> */,
+/* pos 03b4: 528 */    0x00, 0x3F                  /* - terminal marker 63 - */,
+/* pos 03b6: 529 */    0xF2 /* 'r' -> */,
+/* pos 03b7: 530 */    0xF9 /* 'y' -> */,
+/* pos 03b8: 531 */    0xAD /* '-' -> */,
+/* pos 03b9: 532 */    0xE1 /* 'a' -> */,
+/* pos 03ba: 533 */    0xE6 /* 'f' -> */,
+/* pos 03bb: 534 */    0xF4 /* 't' -> */,
+/* pos 03bc: 535 */    0xE5 /* 'e' -> */,
+/* pos 03bd: 536 */    0xF2 /* 'r' -> */,
+/* pos 03be: 537 */    0xBA /* ':' -> */,
+/* pos 03bf: 538 */    0x00, 0x40                  /* - terminal marker 64 - */,
+/* pos 03c1: 539 */    0xF6 /* 'v' -> */,
+/* pos 03c2: 540 */    0xE5 /* 'e' -> */,
+/* pos 03c3: 541 */    0xF2 /* 'r' -> */,
+/* pos 03c4: 542 */    0xBA /* ':' -> */,
+/* pos 03c5: 543 */    0x00, 0x41                  /* - terminal marker 65 - */,
+/* pos 03c7: 544 */    0xAD /* '-' -> */,
+/* pos 03c8: 545 */    0xE3 /* 'c' -> */,
+/* pos 03c9: 546 */    0xEF /* 'o' -> */,
+/* pos 03ca: 547 */    0xEF /* 'o' -> */,
+/* pos 03cb: 548 */    0xEB /* 'k' -> */,
+/* pos 03cc: 549 */    0xE9 /* 'i' -> */,
+/* pos 03cd: 550 */    0xE5 /* 'e' -> */,
+/* pos 03ce: 551 */    0xBA /* ':' -> */,
+/* pos 03cf: 552 */    0x00, 0x42                  /* - terminal marker 66 - */,
+/* pos 03d1: 553 */    0xF2 /* 'r' -> */,
+/* pos 03d2: 554 */    0xE9 /* 'i' -> */,
+/* pos 03d3: 555 */    0xE3 /* 'c' -> */,
+/* pos 03d4: 556 */    0xF4 /* 't' -> */,
+/* pos 03d5: 557 */    0xAD /* '-' -> */,
+/* pos 03d6: 558 */    0xF4 /* 't' -> */,
+/* pos 03d7: 559 */    0xF2 /* 'r' -> */,
+/* pos 03d8: 560 */    0xE1 /* 'a' -> */,
+/* pos 03d9: 561 */    0xEE /* 'n' -> */,
+/* pos 03da: 562 */    0xF3 /* 's' -> */,
+/* pos 03db: 563 */    0xF0 /* 'p' -> */,
+/* pos 03dc: 564 */    0xEF /* 'o' -> */,
+/* pos 03dd: 565 */    0xF2 /* 'r' -> */,
+/* pos 03de: 566 */    0xF4 /* 't' -> */,
+/* pos 03df: 567 */    0xAD /* '-' -> */,
+/* pos 03e0: 568 */    0xF3 /* 's' -> */,
+/* pos 03e1: 569 */    0xE5 /* 'e' -> */,
+/* pos 03e2: 570 */    0xE3 /* 'c' -> */,
+/* pos 03e3: 571 */    0xF5 /* 'u' -> */,
+/* pos 03e4: 572 */    0xF2 /* 'r' -> */,
+/* pos 03e5: 573 */    0xE9 /* 'i' -> */,
+/* pos 03e6: 574 */    0xF4 /* 't' -> */,
+/* pos 03e7: 575 */    0xF9 /* 'y' -> */,
+/* pos 03e8: 576 */    0xBA /* ':' -> */,
+/* pos 03e9: 577 */    0x00, 0x43                  /* - terminal marker 67 - */,
+/* pos 03eb: 578 */    0x72 /* 'r' */, 0x07, 0x00  /* (to 0x03F2 state 579) */,
+                       0x65 /* 'e' */, 0x87, 0x00  /* (to 0x0475 state 680) */,
+                       0x08, /* fail */
+/* pos 03f2: 579 */    0xE1 /* 'a' -> */,
+/* pos 03f3: 580 */    0xEE /* 'n' -> */,
+/* pos 03f4: 581 */    0xF3 /* 's' -> */,
+/* pos 03f5: 582 */    0xE6 /* 'f' -> */,
+/* pos 03f6: 583 */    0xE5 /* 'e' -> */,
+/* pos 03f7: 584 */    0xF2 /* 'r' -> */,
+/* pos 03f8: 585 */    0xAD /* '-' -> */,
+/* pos 03f9: 586 */    0xE5 /* 'e' -> */,
+/* pos 03fa: 587 */    0xEE /* 'n' -> */,
+/* pos 03fb: 588 */    0xE3 /* 'c' -> */,
+/* pos 03fc: 589 */    0xEF /* 'o' -> */,
+/* pos 03fd: 590 */    0xE4 /* 'd' -> */,
+/* pos 03fe: 591 */    0xE9 /* 'i' -> */,
+/* pos 03ff: 592 */    0xEE /* 'n' -> */,
+/* pos 0400: 593 */    0xE7 /* 'g' -> */,
+/* pos 0401: 594 */    0xBA /* ':' -> */,
+/* pos 0402: 595 */    0x00, 0x44                  /* - terminal marker 68 - */,
+/* pos 0404: 596 */    0xE5 /* 'e' -> */,
+/* pos 0405: 597 */    0xF2 /* 'r' -> */,
+/* pos 0406: 598 */    0xAD /* '-' -> */,
+/* pos 0407: 599 */    0xE1 /* 'a' -> */,
+/* pos 0408: 600 */    0xE7 /* 'g' -> */,
+/* pos 0409: 601 */    0xE5 /* 'e' -> */,
+/* pos 040a: 602 */    0xEE /* 'n' -> */,
+/* pos 040b: 603 */    0xF4 /* 't' -> */,
+/* pos 040c: 604 */    0xBA /* ':' -> */,
+/* pos 040d: 605 */    0x00, 0x45                  /* - terminal marker 69 - */,
+/* pos 040f: 606 */    0x61 /* 'a' */, 0x07, 0x00  /* (to 0x0416 state 607) */,
+                       0x69 /* 'i' */, 0x09, 0x00  /* (to 0x041B state 611) */,
+                       0x08, /* fail */
+/* pos 0416: 607 */    0xF2 /* 'r' -> */,
+/* pos 0417: 608 */    0xF9 /* 'y' -> */,
+/* pos 0418: 609 */    0xBA /* ':' -> */,
+/* pos 0419: 610 */    0x00, 0x46                  /* - terminal marker 70 - */,
+/* pos 041b: 611 */    0xE1 /* 'a' -> */,
+/* pos 041c: 612 */    0xBA /* ':' -> */,
+/* pos 041d: 613 */    0x00, 0x47                  /* - terminal marker 71 - */,
+/* pos 041f: 614 */    0xF7 /* 'w' -> */,
+/* pos 0420: 615 */    0xF7 /* 'w' -> */,
+/* pos 0421: 616 */    0xAD /* '-' -> */,
+/* pos 0422: 617 */    0xE1 /* 'a' -> */,
+/* pos 0423: 618 */    0xF5 /* 'u' -> */,
+/* pos 0424: 619 */    0xF4 /* 't' -> */,
+/* pos 0425: 620 */    0xE8 /* 'h' -> */,
+/* pos 0426: 621 */    0xE5 /* 'e' -> */,
+/* pos 0427: 622 */    0xEE /* 'n' -> */,
+/* pos 0428: 623 */    0xF4 /* 't' -> */,
+/* pos 0429: 624 */    0xE9 /* 'i' -> */,
+/* pos 042a: 625 */    0xE3 /* 'c' -> */,
+/* pos 042b: 626 */    0xE1 /* 'a' -> */,
+/* pos 042c: 627 */    0xF4 /* 't' -> */,
+/* pos 042d: 628 */    0xE5 /* 'e' -> */,
+/* pos 042e: 629 */    0xBA /* ':' -> */,
+/* pos 042f: 630 */    0x00, 0x48                  /* - terminal marker 72 - */,
+/* pos 0431: 631 */    0xF4 /* 't' -> */,
+/* pos 0432: 632 */    0xE3 /* 'c' -> */,
+/* pos 0433: 633 */    0xE8 /* 'h' -> */,
+/* pos 0434: 634 */    0x00, 0x49                  /* - terminal marker 73 - */,
+/* pos 0436: 635 */    0xF4 /* 't' -> */,
+/* pos 0437: 636 */    0x00, 0x4A                  /* - terminal marker 74 - */,
+/* pos 0439: 637 */    0xEC /* 'l' -> */,
+/* pos 043a: 638 */    0xE5 /* 'e' -> */,
+/* pos 043b: 639 */    0xF4 /* 't' -> */,
+/* pos 043c: 640 */    0xE5 /* 'e' -> */,
+/* pos 043d: 641 */    0x00, 0x4B                  /* - terminal marker 75 - */,
+/* pos 043f: 642 */    0xE9 /* 'i' -> */,
+/* pos 0440: 643 */    0xAD /* '-' -> */,
+/* pos 0441: 644 */    0xE1 /* 'a' -> */,
+/* pos 0442: 645 */    0xF2 /* 'r' -> */,
+/* pos 0443: 646 */    0xE7 /* 'g' -> */,
+/* pos 0444: 647 */    0xF3 /* 's' -> */,
+/* pos 0445: 648 */    0x00, 0x4C                  /* - terminal marker 76 - */,
+/* pos 0447: 649 */    0x00, 0x4D                  /* - terminal marker 77 - */,
+/* pos 0449: 650 */    0xAD /* '-' -> */,
+/* pos 044a: 651 */    0x72 /* 'r' */, 0x0A, 0x00  /* (to 0x0454 state 652) */,
+                       0x66 /* 'f' */, 0x13, 0x00  /* (to 0x0460 state 662) */,
+                       0x61 /* 'a' */, 0x3C, 0x00  /* (to 0x048C state 700) */,
+                       0x08, /* fail */
+/* pos 0454: 652 */    0xE5 /* 'e' -> */,
+/* pos 0455: 653 */    0xE1 /* 'a' -> */,
+/* pos 0456: 654 */    0xEC /* 'l' -> */,
+/* pos 0457: 655 */    0xAD /* '-' -> */,
+/* pos 0458: 656 */    0xE9 /* 'i' -> */,
+/* pos 0459: 657 */    0xF0 /* 'p' -> */,
+/* pos 045a: 658 */    0xBA /* ':' -> */,
+/* pos 045b: 659 */    0x00, 0x4E                  /* - terminal marker 78 - */,
+/* pos 045d: 660 */    0xA0 /* ' ' -> */,
+/* pos 045e: 661 */    0x00, 0x4F                  /* - terminal marker 79 - */,
+/* pos 0460: 662 */    0xEF /* 'o' -> */,
+/* pos 0461: 663 */    0xF2 /* 'r' -> */,
+/* pos 0462: 664 */    0xF7 /* 'w' -> */,
+/* pos 0463: 665 */    0xE1 /* 'a' -> */,
+/* pos 0464: 666 */    0xF2 /* 'r' -> */,
+/* pos 0465: 667 */    0xE4 /* 'd' -> */,
+/* pos 0466: 668 */    0xE5 /* 'e' -> */,
+/* pos 0467: 669 */    0xE4 /* 'd' -> */,
+/* pos 0468: 670 */    0xAD /* '-' -> */,
+/* pos 0469: 671 */    0xE6 /* 'f' -> */,
+/* pos 046a: 672 */    0xEF /* 'o' -> */,
+/* pos 046b: 673 */    0xF2 /* 'r' -> */,
+/* pos 046c: 674 */    0x00, 0x50                  /* - terminal marker 80 - */,
+/* pos 046e: 675 */    0x00, 0x51                  /* - terminal marker 81 - */,
+/* pos 0470: 676 */    0xE1 /* 'a' -> */,
+/* pos 0471: 677 */    0xE4 /* 'd' -> */,
+/* pos 0472: 678 */    0xA0 /* ' ' -> */,
+/* pos 0473: 679 */    0x00, 0x52                  /* - terminal marker 82 - */,
+/* pos 0475: 680 */    0xBA /* ':' -> */,
+/* pos 0476: 681 */    0x00, 0x53                  /* - terminal marker 83 - */,
+/* pos 0478: 682 */    0xEC /* 'l' -> */,
+/* pos 0479: 683 */    0xE1 /* 'a' -> */,
+/* pos 047a: 684 */    0xF9 /* 'y' -> */,
+/* pos 047b: 685 */    0xAD /* '-' -> */,
+/* pos 047c: 686 */    0xEE /* 'n' -> */,
+/* pos 047d: 687 */    0xEF /* 'o' -> */,
+/* pos 047e: 688 */    0xEE /* 'n' -> */,
+/* pos 047f: 689 */    0xE3 /* 'c' -> */,
+/* pos 0480: 690 */    0xE5 /* 'e' -> */,
+/* pos 0481: 691 */    0xBA /* ':' -> */,
+/* pos 0482: 692 */    0x00, 0x54                  /* - terminal marker 84 - */,
+/* pos 0484: 693 */    0xEF /* 'o' -> */,
+/* pos 0485: 694 */    0xF4 /* 't' -> */,
+/* pos 0486: 695 */    0xEF /* 'o' -> */,
+/* pos 0487: 696 */    0xE3 /* 'c' -> */,
+/* pos 0488: 697 */    0xEF /* 'o' -> */,
+/* pos 0489: 698 */    0xEC /* 'l' -> */,
+/* pos 048a: 699 */    0x00, 0x55                  /* - terminal marker 85 - */,
+/* pos 048c: 700 */    0xF5 /* 'u' -> */,
+/* pos 048d: 701 */    0xF4 /* 't' -> */,
+/* pos 048e: 702 */    0xE8 /* 'h' -> */,
+/* pos 048f: 703 */    0xAD /* '-' -> */,
+/* pos 0490: 704 */    0xF4 /* 't' -> */,
+/* pos 0491: 705 */    0xEF /* 'o' -> */,
+/* pos 0492: 706 */    0xEB /* 'k' -> */,
+/* pos 0493: 707 */    0xE5 /* 'e' -> */,
+/* pos 0494: 708 */    0xEE /* 'n' -> */,
+/* pos 0495: 709 */    0xBA /* ':' -> */,
+/* pos 0496: 710 */    0x00, 0x56                  /* - terminal marker 86 - */,
+/* total size 1176 bytes */
similarity index 100%
rename from lib/minilex.c
rename to lib/roles/http/minilex.c
diff --git a/lib/roles/http/private.h b/lib/roles/http/private.h
new file mode 100644 (file)
index 0000000..5759c85
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if either H1 or H2 roles are
+ *  enabled
+ */
+
+#if defined(LWS_WITH_HUBBUB)
+  #include <hubbub/hubbub.h>
+  #include <hubbub/parser.h>
+ #endif
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+#include "roles/http/compression/private.h"
+#endif
+
+#define lwsi_role_http(wsi) (lwsi_role_h1(wsi) || lwsi_role_h2(wsi))
+
+enum http_version {
+       HTTP_VERSION_1_0,
+       HTTP_VERSION_1_1,
+       HTTP_VERSION_2
+};
+
+enum http_conn_type {
+       HTTP_CONNECTION_CLOSE,
+       HTTP_CONNECTION_KEEP_ALIVE
+};
+
+/*
+ * This is totally opaque to code using the library.  It's exported as a
+ * forward-reference pointer-only declaration; the user can use the pointer with
+ * other APIs to get information out of it.
+ */
+
+#if defined(LWS_WITH_ESP32)
+typedef uint16_t ah_data_idx_t;
+#else
+typedef uint32_t ah_data_idx_t;
+#endif
+
+struct lws_fragments {
+       ah_data_idx_t   offset;
+       uint16_t        len;
+       uint8_t         nfrag; /* which ah->frag[] continues this content, or 0 */
+       uint8_t         flags; /* only http2 cares */
+};
+
+#if defined(LWS_WITH_RANGES)
+enum range_states {
+       LWSRS_NO_ACTIVE_RANGE,
+       LWSRS_BYTES_EQ,
+       LWSRS_FIRST,
+       LWSRS_STARTING,
+       LWSRS_ENDING,
+       LWSRS_COMPLETED,
+       LWSRS_SYNTAX,
+};
+
+struct lws_range_parsing {
+       unsigned long long start, end, extent, agg, budget;
+       const char buf[128];
+       int pos;
+       enum range_states state;
+       char start_valid, end_valid, ctr, count_ranges, did_try, inside, send_ctr;
+};
+
+int
+lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp,
+               unsigned long long extent);
+int
+lws_ranges_next(struct lws_range_parsing *rp);
+void
+lws_ranges_reset(struct lws_range_parsing *rp);
+#endif
+
+/*
+ * these are assigned from a pool held in the context.
+ * Both client and server mode uses them for http header analysis
+ */
+
+struct allocated_headers {
+       struct allocated_headers *next; /* linked list */
+       struct lws *wsi; /* owner */
+       char *data; /* prepared by context init to point to dedicated storage */
+       ah_data_idx_t data_length;
+       /*
+        * the randomly ordered fragments, indexed by frag_index and
+        * lws_fragments->nfrag for continuation.
+        */
+       struct lws_fragments frags[WSI_TOKEN_COUNT];
+       time_t assigned;
+       /*
+        * for each recognized token, frag_index says which frag[] his data
+        * starts in (0 means the token did not appear)
+        * the actual header data gets dumped as it comes in, into data[]
+        */
+       uint8_t frag_index[WSI_TOKEN_COUNT];
+
+#ifndef LWS_NO_CLIENT
+       char initial_handshake_hash_base64[30];
+#endif
+       int hdr_token_idx;
+
+       ah_data_idx_t pos;
+       ah_data_idx_t http_response;
+       ah_data_idx_t current_token_limit;
+
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+       ah_data_idx_t unk_pos; /* to undo speculative unknown header */
+       ah_data_idx_t unk_value_pos;
+
+       ah_data_idx_t unk_ll_head;
+       ah_data_idx_t unk_ll_tail;
+#endif
+
+       int16_t lextable_pos;
+
+       uint8_t in_use;
+       uint8_t nfrag;
+       char /*enum uri_path_states */ ups;
+       char /*enum uri_esc_states */ ues;
+
+       char esc_stash;
+       char post_literal_equal;
+       uint8_t /* enum lws_token_indexes */ parser_state;
+};
+
+
+
+#if defined(LWS_WITH_HUBBUB)
+struct lws_rewrite {
+       hubbub_parser *parser;
+       hubbub_parser_optparams params;
+       const char *from, *to;
+       int from_len, to_len;
+       unsigned char *p, *end;
+       struct lws *wsi;
+};
+static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len)
+{
+       if ((int)s->len != len)
+               return 1;
+
+       return strncmp((const char *)s->ptr, p, len);
+}
+typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw);
+LWS_EXTERN struct lws_rewrite *
+lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to);
+LWS_EXTERN void
+lws_rewrite_destroy(struct lws_rewrite *r);
+LWS_EXTERN int
+lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len);
+#endif
+
+struct lws_pt_role_http {
+       struct allocated_headers *ah_list;
+       struct lws *ah_wait_list;
+#ifdef LWS_WITH_CGI
+       struct lws_cgi *cgi_list;
+#endif
+       int ah_wait_list_length;
+       uint32_t ah_pool_length;
+
+       int ah_count_in_use;
+};
+
+struct lws_peer_role_http {
+       uint32_t count_ah;
+       uint32_t total_ah;
+};
+
+struct lws_vhost_role_http {
+       char http_proxy_address[128];
+       const struct lws_http_mount *mount_list;
+       const char *error_document_404;
+       unsigned int http_proxy_port;
+};
+
+#ifdef LWS_WITH_ACCESS_LOG
+struct lws_access_log {
+       char *header_log;
+       char *user_agent;
+       char *referrer;
+       unsigned long sent;
+       int response;
+};
+#endif
+
+#define LWS_HTTP_CHUNK_HDR_MAX_SIZE (6 + 2) /* 6 hex digits and then CRLF */
+#define LWS_HTTP_CHUNK_TRL_MAX_SIZE (2 + 5) /* CRLF, then maybe 0 CRLF CRLF */
+
+struct _lws_http_mode_related {
+       struct lws *new_wsi_list;
+
+       unsigned char *pending_return_headers;
+       size_t pending_return_headers_len;
+       size_t prh_content_length;
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       struct lws_rewrite *rw;
+       struct lws_buflist *buflist_post_body;
+#endif
+       struct allocated_headers *ah;
+       struct lws *ah_wait_list;
+
+       lws_filepos_t filepos;
+       lws_filepos_t filelen;
+       lws_fop_fd_t fop_fd;
+
+#if defined(LWS_WITH_RANGES)
+       struct lws_range_parsing range;
+       char multipart_content_type[64];
+#endif
+
+#ifdef LWS_WITH_ACCESS_LOG
+       struct lws_access_log access_log;
+#endif
+#ifdef LWS_WITH_CGI
+       struct lws_cgi *cgi; /* wsi being cgi master have one of these */
+#endif
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       struct lws_compression_support *lcs;
+       lws_comp_ctx_t comp_ctx;
+       unsigned char comp_accept_mask;
+#endif
+
+       enum http_version request_version;
+       enum http_conn_type conn_type;
+       lws_filepos_t tx_content_length;
+       lws_filepos_t tx_content_remain;
+       lws_filepos_t rx_content_length;
+       lws_filepos_t rx_content_remain;
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       unsigned int perform_rewrite:1;
+       unsigned int proxy_clientside:1;
+       unsigned int proxy_parent_chunked:1;
+#endif
+       unsigned int deferred_transaction_completed:1;
+       unsigned int content_length_explicitly_zero:1;
+       unsigned int did_stream_close:1;
+};
+
+
+#ifndef LWS_NO_CLIENT
+enum lws_chunk_parser {
+       ELCP_HEX,
+       ELCP_CR,
+       ELCP_CONTENT,
+       ELCP_POST_CR,
+       ELCP_POST_LF,
+};
+#endif
+
+enum lws_parse_urldecode_results {
+       LPUR_CONTINUE,
+       LPUR_SWALLOW,
+       LPUR_FORBID,
+       LPUR_EXCESSIVE,
+};
+
+enum lws_check_basic_auth_results {
+       LCBA_CONTINUE,
+       LCBA_FAILED_AUTH,
+       LCBA_END_TRANSACTION,
+};
+
+enum lws_check_basic_auth_results
+lws_check_basic_auth(struct lws *wsi, const char *basic_auth_login_file);
+
+int
+lws_unauthorised_basic_auth(struct lws *wsi);
+
+int
+lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len);
+
+void
+_lws_header_table_reset(struct allocated_headers *ah);
+
+LWS_EXTERN int
+_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah);
+
+int
+lws_http_proxy_start(struct lws *wsi, const struct lws_http_mount *hit,
+                    char *uri_ptr, char ws);
+
+typedef struct lws_sorted_usec_list lws_sorted_usec_list_t;
+
+void
+lws_sul_http_ah_lifecheck(lws_sorted_usec_list_t *sul);
diff --git a/lib/roles/http/server/access-log.c b/lib/roles/http/server/access-log.c
new file mode 100644 (file)
index 0000000..74ae30e
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * libwebsockets - server access log handling
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * Produce Apache-compatible log string for wsi, like this:
+ *
+ * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800]
+ * "GET /aep-screen.png HTTP/1.1"
+ * 200 152987 "https://libwebsockets.org/index.html"
+ * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36"
+ *
+ */
+
+extern const char * const method_names[];
+
+static const char * const hver[] = {
+       "HTTP/1.0", "HTTP/1.1", "HTTP/2"
+};
+
+void
+lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int uri_len, int meth)
+{
+       char da[64], uri[256];
+       const char *pa, *me;
+       time_t t = time(NULL);
+       int l = 256, m;
+#ifdef LWS_WITH_IPV6
+       char ads[INET6_ADDRSTRLEN];
+#else
+       char ads[INET_ADDRSTRLEN];
+#endif
+       struct tm *tmp;
+
+       if (!wsi->vhost)
+               return;
+
+       /* only worry about preparing it if we store it */
+       if (wsi->vhost->log_fd == (int)LWS_INVALID_FILE)
+               return;
+
+       if (wsi->access_log_pending)
+               lws_access_log(wsi);
+
+       wsi->http.access_log.header_log = lws_malloc(l, "access log");
+       if (!wsi->http.access_log.header_log)
+               return;
+
+       tmp = localtime(&t);
+       if (tmp)
+               strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp);
+       else
+               strcpy(da, "01/Jan/1970:00:00:00 +0000");
+
+       pa = lws_get_peer_simple(wsi, ads, sizeof(ads));
+       if (!pa)
+               pa = "(unknown)";
+
+       if (wsi->http2_substream)
+               me = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD);
+       else
+               me = method_names[meth];
+       if (!me)
+               me = "(null)";
+
+       m = uri_len;
+       if (m > (int)sizeof(uri) - 1)
+               m = sizeof(uri) - 1;
+
+       strncpy(uri, uri_ptr, m);
+       uri[m] = '\0';
+
+       lws_snprintf(wsi->http.access_log.header_log, l,
+                    "%s - - [%s] \"%s %s %s\"",
+                    pa, da, me, uri, hver[wsi->http.request_version]);
+
+       //lwsl_notice("%s\n", wsi->http.access_log.header_log);
+
+       l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT);
+       if (l) {
+               wsi->http.access_log.user_agent =
+                               lws_malloc(l + 5, "access log");
+               if (!wsi->http.access_log.user_agent) {
+                       lwsl_err("OOM getting user agent\n");
+                       lws_free_set_NULL(wsi->http.access_log.header_log);
+                       return;
+               }
+               wsi->http.access_log.user_agent[0] = '\0';
+
+               if (lws_hdr_copy(wsi, wsi->http.access_log.user_agent, l + 4,
+                                WSI_TOKEN_HTTP_USER_AGENT) >= 0)
+                       for (m = 0; m < l; m++)
+                               if (wsi->http.access_log.user_agent[m] == '\"')
+                                       wsi->http.access_log.user_agent[m] = '\'';
+       }
+       l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER);
+       if (l) {
+               wsi->http.access_log.referrer = lws_malloc(l + 5, "referrer");
+               if (!wsi->http.access_log.referrer) {
+                       lwsl_err("OOM getting referrer\n");
+                       lws_free_set_NULL(wsi->http.access_log.user_agent);
+                       lws_free_set_NULL(wsi->http.access_log.header_log);
+                       return;
+               }
+               wsi->http.access_log.referrer[0] = '\0';
+               if (lws_hdr_copy(wsi, wsi->http.access_log.referrer,
+                               l + 4, WSI_TOKEN_HTTP_REFERER) >= 0)
+
+                       for (m = 0; m < l; m++)
+                               if (wsi->http.access_log.referrer[m] == '\"')
+                                       wsi->http.access_log.referrer[m] = '\'';
+       }
+       wsi->access_log_pending = 1;
+}
+
+
+int
+lws_access_log(struct lws *wsi)
+{
+       char *p = wsi->http.access_log.user_agent, ass[512],
+            *p1 = wsi->http.access_log.referrer;
+       int l;
+
+       if (!wsi->vhost)
+               return 0;
+
+       if (wsi->vhost->log_fd == (int)LWS_INVALID_FILE)
+               return 0;
+
+       if (!wsi->access_log_pending)
+               return 0;
+
+       if (!wsi->http.access_log.header_log)
+               return 0;
+
+       if (!p)
+               p = "";
+
+       if (!p1)
+               p1 = "";
+
+       /*
+        * We do this in two parts to restrict an oversize referrer such that
+        * we will always have space left to append an empty useragent, while
+        * maintaining the structure of the log text
+        */
+       l = lws_snprintf(ass, sizeof(ass) - 7, "%s %d %lu \"%s",
+                        wsi->http.access_log.header_log,
+                        wsi->http.access_log.response,
+                        wsi->http.access_log.sent, p1);
+       if (strlen(p) > sizeof(ass) - 6 - l) {
+               p[sizeof(ass) - 6 - l] = '\0';
+               l--;
+       }
+       l += lws_snprintf(ass + l, sizeof(ass) - 1 - l, "\" \"%s\"\n", p);
+
+       ass[sizeof(ass) - 1] = '\0';
+
+       if (write(wsi->vhost->log_fd, ass, l) != l)
+               lwsl_err("Failed to write log\n");
+
+       if (wsi->http.access_log.header_log) {
+               lws_free(wsi->http.access_log.header_log);
+               wsi->http.access_log.header_log = NULL;
+       }
+       if (wsi->http.access_log.user_agent) {
+               lws_free(wsi->http.access_log.user_agent);
+               wsi->http.access_log.user_agent = NULL;
+       }
+       if (wsi->http.access_log.referrer) {
+               lws_free(wsi->http.access_log.referrer);
+               wsi->http.access_log.referrer = NULL;
+       }
+       wsi->access_log_pending = 0;
+
+       return 0;
+}
+
similarity index 98%
rename from lib/fops-zip.c
rename to lib/roles/http/server/fops-zip.c
index 45818c7..5c1d482 100644 (file)
  *  MA  02110-1301  USA
  */
 
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
+#if defined(LWS_WITH_MINIZ)
+#include <miniz.h>
+#else
 #include <zlib.h>
+#endif
 
 /*
  * This code works with zip format containers which may have files compressed
@@ -241,13 +245,13 @@ lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len)
                if (priv->hdr.filename_len != len)
                        goto next;
 
-               if (len >= sizeof(buf) - 1)
+               if (len >= (int)sizeof(buf) - 1)
                        return LWS_FZ_ERR_NAME_TOO_LONG;
 
                if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd,
                                                        &amount, buf, len))
                        return LWS_FZ_ERR_NAME_READ;
-               if (amount != len)
+               if ((int)amount != len)
                        return LWS_FZ_ERR_NAME_READ;
 
                buf[len] = '\0';
@@ -340,7 +344,7 @@ lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
         * will come pointing at "/index.html"
         */
 
-       priv = lws_zalloc(sizeof(*priv));
+       priv = lws_zalloc(sizeof(*priv), "fops_zip priv");
        if (!priv)
                return NULL;
 
@@ -348,9 +352,8 @@ lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
 
        m = sizeof(rp) - 1;
        if ((vpath - vfs_path - 1) < m)
-               m = vpath - vfs_path - 1;
-       strncpy(rp, vfs_path, m);
-       rp[m] = '\0';
+               m = lws_ptr_diff(vpath, vfs_path) - 1;
+       lws_strncpy(rp, vfs_path, m + 1);
 
        /* open the zip file itself using the incoming fops, not fops_zip */
 
@@ -363,7 +366,7 @@ lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path,
        if (*vpath == '/')
                vpath++;
 
-       m = lws_fops_zip_scan(priv, vpath, strlen(vpath));
+       m = lws_fops_zip_scan(priv, vpath, (int)strlen(vpath));
        if (m) {
                lwsl_err("unable to find record matching '%s' %d\n", vpath, m);
                goto bail2;
@@ -565,7 +568,7 @@ spin:
                switch (ret) {
                case Z_NEED_DICT:
                        ret = Z_DATA_ERROR;
-                       /* and fall through */
+                       /* fallthru */
                case Z_DATA_ERROR:
                case Z_MEM_ERROR:
 
similarity index 66%
rename from lib/lejp-conf.c
rename to lib/roles/http/server/lejp-conf.c
index 3a1586d..e8dfffd 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets web server application
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
@@ -19,8 +19,7 @@
  *  MA  02110-1301  USA
  */
 
-#include "private-libwebsockets.h"
-#include "lejp.h"
+#include "core/private.h"
 
 #ifndef _WIN32
 /* this is needed for Travis CI */
@@ -32,6 +31,8 @@
 static const char * const paths_global[] = {
        "global.uid",
        "global.gid",
+       "global.username",
+       "global.groupname",
        "global.count-threads",
        "global.init-ssl",
        "global.server-string",
@@ -40,11 +41,14 @@ static const char * const paths_global[] = {
        "global.timeout-secs",
        "global.reject-service-keywords[].*",
        "global.reject-service-keywords[]",
+       "global.default-alpn",
 };
 
 enum lejp_global_paths {
        LEJPGP_UID,
        LEJPGP_GID,
+       LEJPGP_USERNAME,
+       LEJPGP_GROUPNAME,
        LEJPGP_COUNT_THREADS,
        LWJPGP_INIT_SSL,
        LEJPGP_SERVER_STRING,
@@ -52,7 +56,8 @@ enum lejp_global_paths {
        LWJPGP_PINGPONG_SECS,
        LWJPGP_TIMEOUT_SECS,
        LWJPGP_REJECT_SERVICE_KEYWORDS_NAME,
-       LWJPGP_REJECT_SERVICE_KEYWORDS
+       LWJPGP_REJECT_SERVICE_KEYWORDS,
+       LWJPGP_DEFAULT_ALPN,
 };
 
 static const char * const paths_vhosts[] = {
@@ -62,6 +67,7 @@ static const char * const paths_vhosts[] = {
        "vhosts[].port",
        "vhosts[].interface",
        "vhosts[].unix-socket",
+       "vhosts[].unix-socket-perms",
        "vhosts[].sts",
        "vhosts[].host-ssl-key",
        "vhosts[].host-ssl-cert",
@@ -99,6 +105,26 @@ static const char * const paths_vhosts[] = {
        "vhosts[].client-ssl-cert",
        "vhosts[].client-ssl-ca",
        "vhosts[].client-ssl-ciphers",
+       "vhosts[].onlyraw",
+       "vhosts[].client-cert-required",
+       "vhosts[].ignore-missing-cert",
+       "vhosts[].error-document-404",
+       "vhosts[].alpn",
+       "vhosts[].ssl-client-option-set",
+       "vhosts[].ssl-client-option-clear",
+       "vhosts[].tls13-ciphers",
+       "vhosts[].client-tls13-ciphers",
+       "vhosts[].strict-host-check",
+
+       "vhosts[].listen-accept-role",
+       "vhosts[].listen-accept-protocol",
+       "vhosts[].apply-listen-accept", /* deprecates "onlyraw" */
+       "vhosts[].fallback-listen-accept",
+       "vhosts[].allow-non-tls",
+       "vhosts[].redirect-http",
+       "vhosts[].allow-http-on-https",
+
+       "vhosts[].disable-no-protocol-ws-upgrades",
 };
 
 enum lejp_vhost_paths {
@@ -108,6 +134,7 @@ enum lejp_vhost_paths {
        LEJPVP_PORT,
        LEJPVP_INTERFACE,
        LEJPVP_UNIXSKT,
+       LEJPVP_UNIXSKT_PERMS,
        LEJPVP_STS,
        LEJPVP_HOST_SSL_KEY,
        LEJPVP_HOST_SSL_CERT,
@@ -145,33 +172,26 @@ enum lejp_vhost_paths {
        LEJPVP_CLIENT_SSL_CERT,
        LEJPVP_CLIENT_SSL_CA,
        LEJPVP_CLIENT_CIPHERS,
-};
-
-static const char * const parser_errs[] = {
-       "",
-       "",
-       "No opening '{'",
-       "Expected closing '}'",
-       "Expected '\"'",
-       "String underrun",
-       "Illegal unescaped control char",
-       "Illegal escape format",
-       "Illegal hex number",
-       "Expected ':'",
-       "Illegal value start",
-       "Digit required after decimal point",
-       "Bad number format",
-       "Bad exponent format",
-       "Unknown token",
-       "Too many ']'",
-       "Mismatched ']'",
-       "Expected ']'",
-       "JSON nesting limit exceeded",
-       "Nesting tracking used up",
-       "Number too long",
-       "Comma or block end expected",
-       "Unknown",
-       "Parser callback errored (see earlier error)",
+       LEJPVP_FLAG_ONLYRAW,
+       LEJPVP_FLAG_CLIENT_CERT_REQUIRED,
+       LEJPVP_IGNORE_MISSING_CERT,
+       LEJPVP_ERROR_DOCUMENT_404,
+       LEJPVP_ALPN,
+       LEJPVP_SSL_CLIENT_OPTION_SET,
+       LEJPVP_SSL_CLIENT_OPTION_CLEAR,
+       LEJPVP_TLS13_CIPHERS,
+       LEJPVP_CLIENT_TLS13_CIPHERS,
+       LEJPVP_FLAG_STRICT_HOST_CHECK,
+
+       LEJPVP_LISTEN_ACCEPT_ROLE,
+       LEJPVP_LISTEN_ACCEPT_PROTOCOL,
+       LEJPVP_FLAG_APPLY_LISTEN_ACCEPT,
+       LEJPVP_FLAG_FALLBACK_LISTEN_ACCEPT,
+       LEJPVP_FLAG_ALLOW_NON_TLS,
+       LEJPVP_FLAG_REDIRECT_HTTP,
+       LEJPVP_FLAG_ALLOW_HTTP_ON_HTTPS,
+
+       LEJPVP_FLAG_DISABLE_NO_PROTOCOL_WS_UPGRADES,
 };
 
 #define MAX_PLUGIN_DIRS 10
@@ -180,6 +200,7 @@ struct jpargs {
        struct lws_context_creation_info *info;
        struct lws_context *context;
        const struct lws_protocols *protocols;
+       const struct lws_protocols **pprotocols;
        const struct lws_extension *extensions;
        char *p, *end, valid;
        struct lws_http_mount *head, *last;
@@ -191,9 +212,11 @@ struct jpargs {
        const char **plugin_dirs;
        int count_plugin_dirs;
 
+       unsigned int reject_ws_with_no_protocol:1;
        unsigned int enable_client_ssl:1;
        unsigned int fresh_mount:1;
        unsigned int any_vhosts:1;
+       unsigned int chunk:1;
 };
 
 static void *
@@ -202,6 +225,8 @@ lwsws_align(struct jpargs *a)
        if ((lws_intptr_t)(a->p) & 15)
                a->p += 16 - ((lws_intptr_t)(a->p) & 15);
 
+       a->chunk = 0;
+
        return a->p;
 }
 
@@ -214,14 +239,23 @@ arg_to_bool(const char *s)
        if (n)
                return 1;
 
-       for (n = 0; n < ARRAY_SIZE(on); n++)
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(on); n++)
                if (!strcasecmp(s, on[n]))
                        return 1;
 
        return 0;
 }
 
-static char
+static void
+set_reset_flag(unsigned int *p, const char *state, unsigned int flag)
+{
+       if (arg_to_bool(state))
+               *p |= flag;
+       else
+               *p &= ~(flag);
+}
+
+static signed char
 lejp_globals_cb(struct lejp_ctx *ctx, char reason)
 {
        struct jpargs *a = (struct jpargs *)ctx->user;
@@ -238,7 +272,7 @@ lejp_globals_cb(struct lejp_ctx *ctx, char reason)
                rej = lwsws_align(a);
                a->p += sizeof(*rej);
 
-               n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
+               n = lejp_get_wildcard(ctx, 0, a->p, lws_ptr_diff(a->end, a->p));
                rej->next = a->info->reject_service_keywords;
                a->info->reject_service_keywords = rej;
                rej->name = a->p;
@@ -257,6 +291,12 @@ lejp_globals_cb(struct lejp_ctx *ctx, char reason)
        case LEJPGP_GID:
                a->info->gid = atoi(ctx->buf);
                return 0;
+       case LEJPGP_USERNAME:
+               a->info->username = a->p;
+               break;
+       case LEJPGP_GROUPNAME:
+               a->info->groupname = a->p;
+               break;
        case LEJPGP_COUNT_THREADS:
                a->info->count_threads = atoi(ctx->buf);
                return 0;
@@ -283,6 +323,10 @@ lejp_globals_cb(struct lejp_ctx *ctx, char reason)
                a->info->timeout_secs = atoi(ctx->buf);
                return 0;
 
+       case LWJPGP_DEFAULT_ALPN:
+               a->info->alpn = a->p;
+               break;
+
        default:
                return 0;
        }
@@ -294,7 +338,7 @@ dostring:
        return 0;
 }
 
-static char
+static signed char
 lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
 {
        struct jpargs *a = (struct jpargs *)ctx->user;
@@ -310,20 +354,41 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
 #endif
 
        if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) {
+               uint32_t i[4];
+               const char *ss;
+
                /* set the defaults for this vhost */
+               a->reject_ws_with_no_protocol = 0;
                a->valid = 1;
                a->head = NULL;
                a->last = NULL;
-               a->info->port = 0;
-               a->info->iface = NULL;
+
+               i[0] = a->info->count_threads;
+               i[1] = a->info->options & (
+                       LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME |
+                       LWS_SERVER_OPTION_LIBUV |
+                       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                       LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+                       LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN |
+                       LWS_SERVER_OPTION_LIBEVENT |
+                       LWS_SERVER_OPTION_LIBEV
+                               );
+               ss = a->info->server_string;
+               i[2] = a->info->ws_ping_pong_interval;
+               i[3] = a->info->timeout_secs;
+
+               memset(a->info, 0, sizeof(*a->info));
+
+               a->info->count_threads = i[0];
+               a->info->options = i[1];
+               a->info->server_string = ss;
+               a->info->ws_ping_pong_interval = i[2];
+               a->info->timeout_secs = i[3];
+
                a->info->protocols = a->protocols;
+               a->info->pprotocols = a->pprotocols;
                a->info->extensions = a->extensions;
-               a->info->ssl_cert_filepath = NULL;
-               a->info->ssl_private_key_filepath = NULL;
-               a->info->ssl_ca_filepath = NULL;
-               a->info->client_ssl_cert_filepath = NULL;
-               a->info->client_ssl_private_key_filepath = NULL;
-               a->info->client_ssl_ca_filepath = NULL;
+#if defined(LWS_WITH_TLS)
                a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
                        "ECDHE-RSA-AES256-GCM-SHA384:"
                        "DHE-RSA-AES256-GCM-SHA384:"
@@ -337,7 +402,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                        "!DHE-RSA-AES256-SHA256:"
                        "!AES256-GCM-SHA384:"
                        "!AES256-SHA256";
-               a->info->timeout_secs = 5;
+#endif
                a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
                                       "ECDHE-RSA-AES256-GCM-SHA384:"
                                       "DHE-RSA-AES256-GCM-SHA384:"
@@ -351,13 +416,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                                       "!DHE-RSA-AES256-SHA256:"
                                       "!AES256-GCM-SHA384:"
                                       "!AES256-SHA256";
-               a->info->pvo = NULL;
-               a->info->headers = NULL;
                a->info->keepalive_timeout = 5;
-               a->info->log_filepath = NULL;
-               a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK |
-                                     LWS_SERVER_OPTION_STS);
-               a->enable_client_ssl = 0;
        }
 
        if (reason == LEJPCB_OBJECT_START &&
@@ -372,12 +431,12 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                a->pvo = lwsws_align(a);
                a->p += sizeof(*a->pvo);
 
-               n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
+               n = lejp_get_wildcard(ctx, 0, a->p, lws_ptr_diff(a->end, a->p));
                /* ie, enable this protocol, no options yet */
                a->pvo->next = a->info->pvo;
                a->info->pvo = a->pvo;
                a->pvo->name = a->p;
-               lwsl_notice("  adding protocol %s\n", a->p);
+               lwsl_info("  adding protocol %s\n", a->p);
                a->p += n;
                a->pvo->value = a->p;
                a->pvo->options = NULL;
@@ -385,25 +444,31 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
        }
 
        /* this catches, eg, vhosts[].headers[].xxx */
-       if (reason == LEJPCB_VAL_STR_END &&
+       if ((reason == LEJPCB_VAL_STR_END || reason == LEJPCB_VAL_STR_CHUNK) &&
            ctx->path_match == LEJPVP_HEADERS_NAME + 1) {
-               headers = lwsws_align(a);
-               a->p += sizeof(*headers);
 
-               n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
-               /* ie, enable this protocol, no options yet */
-               headers->next = a->info->headers;
-               a->info->headers = headers;
-               headers->name = a->p;
-               // lwsl_notice("  adding header %s=%s\n", a->p, ctx->buf);
-               a->p += n - 1;
-               *(a->p++) = ':';
-               if (a->p < a->end)
-                       *(a->p++) = '\0';
-               else
-                       *(a->p - 1) = '\0';
-               headers->value = a->p;
-               headers->options = NULL;
+               if (!a->chunk) {
+                       headers = lwsws_align(a);
+                       a->p += sizeof(*headers);
+
+                       n = lejp_get_wildcard(ctx, 0, a->p,
+                                       lws_ptr_diff(a->end, a->p));
+                       /* ie, add this header */
+                       headers->next = a->info->headers;
+                       a->info->headers = headers;
+                       headers->name = a->p;
+
+                       lwsl_notice("  adding header %s=%s\n", a->p, ctx->buf);
+                       a->p += n - 1;
+                       *(a->p++) = ':';
+                       if (a->p < a->end)
+                               *(a->p++) = '\0';
+                       else
+                               *(a->p - 1) = '\0';
+                       headers->value = a->p;
+                       headers->options = NULL;
+               }
+               a->chunk = reason == LEJPCB_VAL_STR_CHUNK;
                goto dostring;
        }
 
@@ -414,8 +479,9 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                struct lws_vhost *vhost;
 
                //lwsl_notice("%s\n", ctx->path);
-               if (!a->info->port) {
-                       lwsl_err("Port required (eg, 443)");
+               if (!a->info->port &&
+                   !(a->info->options & LWS_SERVER_OPTION_UNIX_SOCK)) {
+                       lwsl_err("Port required (eg, 443)\n");
                        return 1;
                }
                a->valid = 0;
@@ -429,19 +495,33 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                }
                a->any_vhosts = 1;
 
+               if (a->reject_ws_with_no_protocol) {
+                       a->reject_ws_with_no_protocol = 0;
+
+                       vhost->default_protocol_index = 255;
+               }
+
+#if defined(LWS_WITH_TLS)
                if (a->enable_client_ssl) {
-                       const char *cert_filepath = a->info->client_ssl_cert_filepath;
-                       const char *private_key_filepath = a->info->client_ssl_private_key_filepath;
-                       const char *ca_filepath = a->info->client_ssl_ca_filepath;
-                       const char *cipher_list = a->info->client_ssl_cipher_list;
+                       const char *cert_filepath =
+                                       a->info->client_ssl_cert_filepath;
+                       const char *private_key_filepath =
+                                      a->info->client_ssl_private_key_filepath;
+                       const char *ca_filepath =
+                                       a->info->client_ssl_ca_filepath;
+                       const char *cipher_list =
+                                       a->info->client_ssl_cipher_list;
+
                        memset(a->info, 0, sizeof(*a->info));
                        a->info->client_ssl_cert_filepath = cert_filepath;
-                       a->info->client_ssl_private_key_filepath = private_key_filepath;
+                       a->info->client_ssl_private_key_filepath =
+                                                       private_key_filepath;
                        a->info->client_ssl_ca_filepath = ca_filepath;
                        a->info->client_ssl_cipher_list = cipher_list;
                        a->info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
                        lws_init_vhost_client_ssl(a->info, vhost);
                }
+#endif
 
                return 0;
        }
@@ -472,17 +552,17 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                if (a->last)
                        a->last->mount_next = m;
 
-               for (n = 0; n < ARRAY_SIZE(mount_protocols); n++)
+               for (n = 0; n < (int)LWS_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]);
                                break;
                        }
 
-               if (n == ARRAY_SIZE(mount_protocols)) {
+               if (n == (int)LWS_ARRAY_SIZE(mount_protocols)) {
                        lwsl_err("unsupported protocol:// %s\n", a->m.origin);
                        return 1;
                }
@@ -515,6 +595,9 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                else
                        a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK);
                return 0;
+       case LEJPVP_UNIXSKT_PERMS:
+               a->info->unix_socket_perms = a->p;
+               break;
        case LEJPVP_STS:
                if (arg_to_bool(ctx->buf))
                        a->info->options |= LWS_SERVER_OPTION_STS;
@@ -571,12 +654,21 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
        case LEJPVP_KEEPALIVE_TIMEOUT:
                a->info->keepalive_timeout = atoi(ctx->buf);
                return 0;
+#if defined(LWS_WITH_TLS)
        case LEJPVP_CLIENT_CIPHERS:
                a->info->client_ssl_cipher_list = a->p;
                break;
+#endif
        case LEJPVP_CIPHERS:
                a->info->ssl_cipher_list = a->p;
                break;
+       case LEJPVP_TLS13_CIPHERS:
+               a->info->tls1_3_plus_cipher_list = a->p;
+               break;
+       case LEJPVP_CLIENT_TLS13_CIPHERS:
+               a->info->client_tls_1_3_plus_cipher_list = a->p;
+               break;
+
        case LEJPVP_ECDH_CURVE:
                a->info->ecdh_curve = a->p;
                break;
@@ -588,13 +680,13 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                mp_cgienv->next = a->m.cgienv;
                a->m.cgienv = mp_cgienv;
 
-               n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
+               n = lejp_get_wildcard(ctx, 0, a->p, lws_ptr_diff(a->end, a->p));
                mp_cgienv->name = a->p;
                a->p += n;
                mp_cgienv->value = a->p;
                mp_cgienv->options = NULL;
-               //lwsl_notice("    adding pmo / cgi-env '%s' = '%s'\n", mp_cgienv->name,
-               //              mp_cgienv->value);
+               //lwsl_notice("    adding pmo / cgi-env '%s' = '%s'\n",
+               //              mp_cgienv->name, mp_cgienv->value);
                goto dostring;
 
        case LEJPVP_PROTOCOL_NAME_OPT:
@@ -605,7 +697,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                pvo = lwsws_align(a);
                a->p += sizeof(*a->pvo);
 
-               n = lejp_get_wildcard(ctx, 1, a->p, a->end - a->p);
+               n = lejp_get_wildcard(ctx, 1, a->p, lws_ptr_diff(a->end, a->p));
                /* ie, enable this protocol, no options yet */
                pvo->next = a->pvo->options;
                a->pvo->options = pvo;
@@ -619,12 +711,12 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                a->pvo_em = lwsws_align(a);
                a->p += sizeof(*a->pvo_em);
 
-               n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
+               n = lejp_get_wildcard(ctx, 0, a->p, lws_ptr_diff(a->end, a->p));
                /* ie, enable this protocol, no options yet */
                a->pvo_em->next = a->m.extra_mimetypes;
                a->m.extra_mimetypes = a->pvo_em;
                a->pvo_em->name = a->p;
-               lwsl_notice("  adding extra-mimetypes %s -> %s\n", a->p, ctx->buf);
+               lwsl_notice("  + extra-mimetypes %s -> %s\n", a->p, ctx->buf);
                a->p += n;
                a->pvo_em->value = a->p;
                a->pvo_em->options = NULL;
@@ -634,7 +726,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                a->pvo_int = lwsws_align(a);
                a->p += sizeof(*a->pvo_int);
 
-               n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
+               n = lejp_get_wildcard(ctx, 0, a->p, lws_ptr_diff(a->end, a->p));
                /* ie, enable this protocol, no options yet */
                a->pvo_int->next = a->m.interpret;
                a->m.interpret = a->pvo_int;
@@ -649,6 +741,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
        case LEJPVP_ENABLE_CLIENT_SSL:
                a->enable_client_ssl = arg_to_bool(ctx->buf);
                return 0;
+#if defined(LWS_WITH_TLS)
        case LEJPVP_CLIENT_SSL_KEY:
                a->info->client_ssl_private_key_filepath = a->p;
                break;
@@ -658,22 +751,44 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
        case LEJPVP_CLIENT_SSL_CA:
                a->info->client_ssl_ca_filepath = a->p;
                break;
+#endif
 
        case LEJPVP_NOIPV6:
-               if (arg_to_bool(ctx->buf))
-                       a->info->options |= LWS_SERVER_OPTION_DISABLE_IPV6;
-               else
-                       a->info->options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6);
+               set_reset_flag(&a->info->options, ctx->buf,
+                              LWS_SERVER_OPTION_DISABLE_IPV6);
+               return 0;
+
+       case LEJPVP_FLAG_ONLYRAW:
+               set_reset_flag(&a->info->options, ctx->buf,
+                           LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG);
                return 0;
 
        case LEJPVP_IPV6ONLY:
                a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY;
+               set_reset_flag(&a->info->options, ctx->buf,
+                              LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
+               return 0;
+
+       case LEJPVP_FLAG_CLIENT_CERT_REQUIRED:
                if (arg_to_bool(ctx->buf))
-                       a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE;
-               else
-                       a->info->options &= ~(LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
+                       a->info->options |=
+                           LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
+               return 0;
+
+       case LEJPVP_IGNORE_MISSING_CERT:
+               set_reset_flag(&a->info->options, ctx->buf,
+                               LWS_SERVER_OPTION_IGNORE_MISSING_CERT);
                return 0;
 
+       case LEJPVP_FLAG_STRICT_HOST_CHECK:
+               set_reset_flag(&a->info->options, ctx->buf,
+                       LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK);
+               return 0;
+
+       case LEJPVP_ERROR_DOCUMENT_404:
+               a->info->error_document_404 = a->p;
+               break;
+
        case LEJPVP_SSL_OPTION_SET:
                a->info->ssl_options_set |= atol(ctx->buf);
                return 0;
@@ -681,25 +796,73 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
                a->info->ssl_options_clear |= atol(ctx->buf);
                return 0;
 
+       case LEJPVP_SSL_CLIENT_OPTION_SET:
+               a->info->ssl_client_options_set |= atol(ctx->buf);
+               return 0;
+       case LEJPVP_SSL_CLIENT_OPTION_CLEAR:
+               a->info->ssl_client_options_clear |= atol(ctx->buf);
+               return 0;
+
+       case LEJPVP_ALPN:
+               a->info->alpn = a->p;
+               break;
+
+       case LEJPVP_LISTEN_ACCEPT_ROLE:
+               a->info->listen_accept_role = a->p;
+               break;
+       case LEJPVP_LISTEN_ACCEPT_PROTOCOL:
+               a->info->listen_accept_protocol = a->p;
+               break;
+
+       case LEJPVP_FLAG_APPLY_LISTEN_ACCEPT:
+               set_reset_flag(&a->info->options, ctx->buf,
+                       LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG);
+               return 0;
+       case LEJPVP_FLAG_FALLBACK_LISTEN_ACCEPT:
+               lwsl_notice("vh %s: LEJPVP_FLAG_FALLBACK_LISTEN_ACCEPT: %s\n",
+                           a->info->vhost_name, ctx->buf);
+               set_reset_flag(&a->info->options, ctx->buf,
+                     LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG);
+               return 0;
+       case LEJPVP_FLAG_ALLOW_NON_TLS:
+               set_reset_flag(&a->info->options, ctx->buf,
+                              LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT);
+               return 0;
+       case LEJPVP_FLAG_REDIRECT_HTTP:
+               set_reset_flag(&a->info->options, ctx->buf,
+                              LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS);
+               return 0;
+       case LEJPVP_FLAG_ALLOW_HTTP_ON_HTTPS:
+               set_reset_flag(&a->info->options, ctx->buf,
+                              LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER);
+               return 0;
+
+       case LEJPVP_FLAG_DISABLE_NO_PROTOCOL_WS_UPGRADES:
+               a->reject_ws_with_no_protocol = 1;
+               return 0;
+
        default:
                return 0;
        }
 
 dostring:
        p = ctx->buf;
+       p[LEJP_STRING_CHUNK] = '\0';
        p1 = strstr(p, ESC_INSTALL_DATADIR);
        if (p1) {
-               n = p1 - p;
+               n = lws_ptr_diff(p1, p);
                if (n > a->end - a->p)
-                       n = a->end - a->p;
-               strncpy(a->p, p, n);
+                       n = lws_ptr_diff(a->end, a->p);
+               lws_strncpy(a->p, p, n + 1);
                a->p += n;
-               a->p += lws_snprintf(a->p, a->end - a->p, "%s", LWS_INSTALL_DATADIR);
+               a->p += lws_snprintf(a->p, a->end - a->p, "%s",
+                                    LWS_INSTALL_DATADIR);
                p += n + strlen(ESC_INSTALL_DATADIR);
        }
 
        a->p += lws_snprintf(a->p, a->end - a->p, "%s", p);
-       *(a->p)++ = '\0';
+       if (reason == LEJPCB_VAL_STR_END)
+               *(a->p)++ = '\0';
 
        return 0;
 }
@@ -714,9 +877,9 @@ lwsws_get_config(void *user, const char *f, const char * const *paths,
 {
        unsigned char buf[128];
        struct lejp_ctx ctx;
-       int n, m, fd;
+       int n, m = 0, fd;
 
-       fd = open(f, O_RDONLY);
+       fd = lws_open(f, O_RDONLY);
        if (fd < 0) {
                lwsl_err("Cannot open %s\n", f);
                return 2;
@@ -738,103 +901,41 @@ lwsws_get_config(void *user, const char *f, const char * const *paths,
 
        if (m < 0) {
                lwsl_err("%s(%u): parsing error %d: %s\n", f, n, m,
-                        parser_errs[-m]);
+                        lejp_error_to_string(m));
                return 2;
        }
 
        return 0;
 }
 
-#if defined(LWS_USE_LIBUV) && UV_VERSION_MAJOR > 0
+struct lws_dir_args {
+       void *user;
+       const char * const *paths;
+       int count_paths;
+       lejp_callback cb;
+};
 
 static int
-lwsws_get_config_d(void *user, const char *d, const char * const *paths,
-                  int count_paths, lejp_callback cb)
+lwsws_get_config_d_cb(const char *dirpath, void *user,
+                     struct lws_dir_entry *lde)
 {
-       uv_dirent_t dent;
-       uv_fs_t req;
+       struct lws_dir_args *da = (struct lws_dir_args *)user;
        char path[256];
-       int ret = 0, ir;
-       uv_loop_t loop;
-
-       ir = uv_loop_init(&loop);
-       if (ir) {
-               lwsl_err("%s: loop init failed %d\n", __func__, ir);
-       }
-
-       if (!uv_fs_scandir(&loop, &req, d, 0, NULL)) {
-               lwsl_err("Scandir on %s failed\n", d);
-               return 2;
-       }
-
-       while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
-               lws_snprintf(path, sizeof(path) - 1, "%s/%s", d, dent.name);
-               ret = lwsws_get_config(user, path, paths, count_paths, cb);
-               if (ret)
-                       goto bail;
-       }
-
-bail:
-       uv_fs_req_cleanup(&req);
-       while (uv_loop_close(&loop))
-               ;
-
-       return ret;
-}
-
-#else
 
-#ifndef _WIN32
-static int filter(const struct dirent *ent)
-{
-       if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+       if (lde->type != LDOT_FILE && lde->type != LDOT_UNKNOWN /* ZFS */)
                return 0;
 
-       return 1;
-}
-#endif
-
-static int
-lwsws_get_config_d(void *user, const char *d, const char * const *paths,
-                  int count_paths, lejp_callback cb)
-{
-#ifndef _WIN32
-       struct dirent **namelist;
-       char path[256];
-       int n, i, ret = 0;
-
-       n = scandir(d, &namelist, filter, alphasort);
-       if (n < 0) {
-               lwsl_err("Scandir on %s failed\n", d);
-       }
+       lws_snprintf(path, sizeof(path) - 1, "%s/%s", dirpath, lde->name);
 
-       for (i = 0; i < n; i++) {
-               lws_snprintf(path, sizeof(path) - 1, "%s/%s", d,
-                        namelist[i]->d_name);
-               ret = lwsws_get_config(user, path, paths, count_paths, cb);
-               if (ret) {
-                       while (i++ < n)
-                               free(namelist[i]);
-                       goto bail;
-               }
-               free(namelist[i]);
-       }
-
-bail:
-       free(namelist);
-
-       return ret;
-#else
-       return 0;
-#endif
+       return lwsws_get_config(da->user, path, da->paths,
+                               da->count_paths, da->cb);
 }
 
-#endif
-
 int
 lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
                         char **cs, int *len)
 {
+       struct lws_dir_args da;
        struct jpargs a;
        const char * const *old = info->plugin_dirs;
        char dd[128];
@@ -860,17 +961,22 @@ lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
 
        lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d);
        if (lwsws_get_config(&a, dd, paths_global,
-                            ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
+                            LWS_ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
                return 1;
        lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d);
-       if (lwsws_get_config_d(&a, dd, paths_global,
-                              ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
+
+       da.user = &a;
+       da.paths = paths_global;
+       da.count_paths = LWS_ARRAY_SIZE(paths_global),
+       da.cb = lejp_globals_cb;
+
+       if (lws_dir(dd, &da, lwsws_get_config_d_cb) > 1)
                return 1;
 
        a.plugin_dirs[a.count_plugin_dirs] = NULL;
 
        *cs = a.p;
-       *len = a.end - a.p;
+       *len = lws_ptr_diff(a.end, a.p);
 
        return 0;
 }
@@ -880,6 +986,7 @@ lwsws_get_config_vhosts(struct lws_context *context,
                        struct lws_context_creation_info *info, const char *d,
                        char **cs, int *len)
 {
+       struct lws_dir_args da;
        struct jpargs a;
        char dd[128];
 
@@ -891,19 +998,25 @@ lwsws_get_config_vhosts(struct lws_context *context,
        a.valid = 0;
        a.context = context;
        a.protocols = info->protocols;
+       a.pprotocols = info->pprotocols;
        a.extensions = info->extensions;
 
        lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d);
        if (lwsws_get_config(&a, dd, paths_vhosts,
-                            ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
+                            LWS_ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
                return 1;
        lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d);
-       if (lwsws_get_config_d(&a, dd, paths_vhosts,
-                              ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
+
+       da.user = &a;
+       da.paths = paths_vhosts;
+       da.count_paths = LWS_ARRAY_SIZE(paths_vhosts),
+       da.cb = lejp_vhosts_cb;
+
+       if (lws_dir(dd, &da, lwsws_get_config_d_cb) > 1)
                return 1;
 
        *cs = a.p;
-       *len = a.end - a.p;
+       *len = lws_ptr_diff(a.end, a.p);
 
        if (!a.any_vhosts) {
                lwsl_err("Need at least one vhost\n");
diff --git a/lib/roles/http/server/lws-spa.c b/lib/roles/http/server/lws-spa.c
new file mode 100644 (file)
index 0000000..4e25695
--- /dev/null
@@ -0,0 +1,675 @@
+/*
+ * libwebsockets - Stateful urldecode for POST
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#define LWS_MAX_ELEM_NAME 32
+
+enum urldecode_stateful {
+       US_NAME,
+       US_IDLE,
+       US_PC1,
+       US_PC2,
+
+       MT_LOOK_BOUND_IN,
+       MT_HNAME,
+       MT_DISP,
+       MT_TYPE,
+       MT_IGNORE1,
+       MT_IGNORE2,
+       MT_IGNORE3,
+       MT_COMPLETED,
+};
+
+static const char * const mp_hdr[] = {
+       "content-disposition: ",
+       "content-type: ",
+       "\x0d\x0a"
+};
+
+struct lws_spa;
+
+typedef int (*lws_urldecode_stateful_cb)(struct lws_spa *spa,
+               const char *name, char **buf, int len, int final);
+
+struct lws_urldecode_stateful {
+       char *out;
+       struct lws_spa *data;
+       struct lws *wsi;
+       char name[LWS_MAX_ELEM_NAME];
+       char temp[LWS_MAX_ELEM_NAME];
+       char content_type[32];
+       char content_disp[32];
+       char content_disp_filename[256];
+       char mime_boundary[128];
+       int out_len;
+       int pos;
+       int hdr_idx;
+       int mp;
+       int sum;
+
+       unsigned int multipart_form_data:1;
+       unsigned int inside_quote:1;
+       unsigned int subname:1;
+       unsigned int boundary_real_crlf:1;
+
+       enum urldecode_stateful state;
+
+       lws_urldecode_stateful_cb output;
+};
+
+struct lws_spa {
+       struct lws_urldecode_stateful *s;
+       lws_spa_create_info_t i;
+       int *param_length;
+       char finalized;
+       char **params;
+       char *storage;
+       char *end;
+};
+
+static struct lws_urldecode_stateful *
+lws_urldecode_s_create(struct lws_spa *spa, struct lws *wsi, char *out,
+                      int out_len, lws_urldecode_stateful_cb output)
+{
+       struct lws_urldecode_stateful *s;
+       char buf[205], *p;
+       int m = 0;
+
+       if (spa->i.ac)
+               s = lwsac_use_zero(spa->i.ac, sizeof(*s), spa->i.ac_chunk_size);
+       else
+               s = lws_zalloc(sizeof(*s), "stateful urldecode");
+
+       if (!s)
+               return NULL;
+
+       s->out = out;
+       s->out_len  = out_len;
+       s->output = output;
+       s->pos = 0;
+       s->sum = 0;
+       s->mp = 0;
+       s->state = US_NAME;
+       s->name[0] = '\0';
+       s->data = spa;
+       s->wsi = wsi;
+
+       if (lws_hdr_copy(wsi, buf, sizeof(buf),
+                        WSI_TOKEN_HTTP_CONTENT_TYPE) > 0) {
+       /* multipart/form-data;
+        * boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */
+
+               if (!strncmp(buf, "multipart/form-data", 19)) {
+                       s->multipart_form_data = 1;
+                       s->state = MT_LOOK_BOUND_IN;
+                       s->mp = 2;
+                       p = strstr(buf, "boundary=");
+                       if (p) {
+                               p += 9;
+                               s->mime_boundary[m++] = '\x0d';
+                               s->mime_boundary[m++] = '\x0a';
+                               s->mime_boundary[m++] = '-';
+                               s->mime_boundary[m++] = '-';
+                               while (m < (int)sizeof(s->mime_boundary) - 1 &&
+                                      *p && *p != ' ')
+                                       s->mime_boundary[m++] = *p++;
+
+                               s->mime_boundary[m] = '\0';
+
+                               lwsl_info("boundary '%s'\n", s->mime_boundary);
+                       }
+               }
+       }
+
+       return s;
+}
+
+static int
+lws_urldecode_s_process(struct lws_urldecode_stateful *s, const char *in,
+                       int len)
+{
+       int n, m, hit = 0;
+       char c, was_end = 0;
+
+       while (len--) {
+               if (s->pos == s->out_len - s->mp - 1) {
+                       if (s->output(s->data, s->name, &s->out, s->pos,
+                                     LWS_UFS_CONTENT))
+                               return -1;
+
+                       was_end = s->pos;
+                       s->pos = 0;
+               }
+               switch (s->state) {
+
+               /* states for url arg style */
+
+               case US_NAME:
+                       s->inside_quote = 0;
+                       if (*in == '=') {
+                               s->name[s->pos] = '\0';
+                               s->pos = 0;
+                               s->state = US_IDLE;
+                               in++;
+                               continue;
+                       }
+                       if (*in == '&') {
+                               s->name[s->pos] = '\0';
+                               if (s->output(s->data, s->name, &s->out,
+                                             s->pos, LWS_UFS_FINAL_CONTENT))
+                                       return -1;
+                               s->pos = 0;
+                               s->state = US_IDLE;
+                               in++;
+                               continue;
+                       }
+                       if (s->pos >= (int)sizeof(s->name) - 1) {
+                               lwsl_notice("Name too long\n");
+                               return -1;
+                       }
+                       s->name[s->pos++] = *in++;
+                       break;
+               case US_IDLE:
+                       if (*in == '%') {
+                               s->state++;
+                               in++;
+                               continue;
+                       }
+                       if (*in == '&') {
+                               s->out[s->pos] = '\0';
+                               if (s->output(s->data, s->name, &s->out,
+                                             s->pos, LWS_UFS_FINAL_CONTENT))
+                                       return -1;
+                               s->pos = 0;
+                               s->state = US_NAME;
+                               in++;
+                               continue;
+                       }
+                       if (*in == '+') {
+                               in++;
+                               s->out[s->pos++] = ' ';
+                               continue;
+                       }
+                       s->out[s->pos++] = *in++;
+                       break;
+               case US_PC1:
+                       n = char_to_hex(*in);
+                       if (n < 0)
+                               return -1;
+
+                       in++;
+                       s->sum = n << 4;
+                       s->state++;
+                       break;
+
+               case US_PC2:
+                       n = char_to_hex(*in);
+                       if (n < 0)
+                               return -1;
+
+                       in++;
+                       s->out[s->pos++] = s->sum | n;
+                       s->state = US_IDLE;
+                       break;
+
+
+               /* states for multipart / mime style */
+
+               case MT_LOOK_BOUND_IN:
+retry_as_first:
+                       if (*in == s->mime_boundary[s->mp] &&
+                           s->mime_boundary[s->mp]) {
+                               in++;
+                               s->mp++;
+                               if (!s->mime_boundary[s->mp]) {
+                                       s->mp = 0;
+                                       s->state = MT_IGNORE1;
+
+                                       if (s->pos || was_end)
+                                               if (s->output(s->data, s->name,
+                                                     &s->out, s->pos,
+                                                     LWS_UFS_FINAL_CONTENT))
+                                                       return -1;
+
+                                       s->pos = 0;
+
+                                       s->content_disp[0] = '\0';
+                                       s->name[0] = '\0';
+                                       s->content_disp_filename[0] = '\0';
+                                       s->boundary_real_crlf = 1;
+                               }
+                               continue;
+                       }
+                       if (s->mp) {
+                               n = 0;
+                               if (!s->boundary_real_crlf)
+                                       n = 2;
+                               if (s->mp >= n) {
+                                       memcpy(s->out + s->pos,
+                                              s->mime_boundary + n, s->mp - n);
+                                       s->pos += s->mp;
+                                       s->mp = 0;
+                                       goto retry_as_first;
+                               }
+                       }
+
+                       s->out[s->pos++] = *in;
+                       in++;
+                       s->mp = 0;
+                       break;
+
+               case MT_HNAME:
+                       m = 0;
+                       c =*in;
+                       if (c >= 'A' && c <= 'Z')
+                               c += 'a' - 'A';
+                       for (n = 0; n < (int)LWS_ARRAY_SIZE(mp_hdr); n++)
+                               if (c == mp_hdr[n][s->mp]) {
+                                       m++;
+                                       hit = n;
+                               }
+                       in++;
+                       if (!m) {
+                               /* Unknown header - ignore it */
+                               s->state = MT_IGNORE1;
+                               s->mp = 0;
+                               continue;
+                       }
+
+                       s->mp++;
+                       if (m != 1)
+                               continue;
+
+                       if (mp_hdr[hit][s->mp])
+                               continue;
+
+                       s->mp = 0;
+                       s->temp[0] = '\0';
+                       s->subname = 0;
+
+                       if (hit == 2)
+                               s->state = MT_LOOK_BOUND_IN;
+                       else
+                               s->state += hit + 1;
+                       break;
+
+               case MT_DISP:
+                       /* form-data; name="file"; filename="t.txt" */
+
+                       if (*in == '\x0d') {
+                               if (s->content_disp_filename[0])
+                                       if (s->output(s->data, s->name,
+                                                     &s->out, s->pos,
+                                                     LWS_UFS_OPEN))
+                                               return -1;
+                               s->state = MT_IGNORE2;
+                               goto done;
+                       }
+                       if (*in == ';') {
+                               s->subname = 1;
+                               s->temp[0] = '\0';
+                               s->mp = 0;
+                               goto done;
+                       }
+
+                       if (*in == '\"') {
+                               s->inside_quote ^= 1;
+                               goto done;
+                       }
+
+                       if (s->subname) {
+                               if (*in == '=') {
+                                       s->temp[s->mp] = '\0';
+                                       s->subname = 0;
+                                       s->mp = 0;
+                                       goto done;
+                               }
+                               if (s->mp < (int)sizeof(s->temp) - 1 &&
+                                   (*in != ' ' || s->inside_quote))
+                                       s->temp[s->mp++] = *in;
+                               goto done;
+                       }
+
+                       if (!s->temp[0]) {
+                               if (s->mp < (int)sizeof(s->content_disp) - 1)
+                                       s->content_disp[s->mp++] = *in;
+                               if (s->mp < (int)sizeof(s->content_disp))
+                                       s->content_disp[s->mp] = '\0';
+                               goto done;
+                       }
+
+                       if (!strcmp(s->temp, "name")) {
+                               if (s->mp < (int)sizeof(s->name) - 1)
+                                       s->name[s->mp++] = *in;
+                               else
+                                       s->mp = (int)sizeof(s->name) - 1;
+                               s->name[s->mp] = '\0';
+                               goto done;
+                       }
+
+                       if (!strcmp(s->temp, "filename")) {
+                               if (s->mp < (int)sizeof(s->content_disp_filename) - 1)
+                                       s->content_disp_filename[s->mp++] = *in;
+                               s->content_disp_filename[s->mp] = '\0';
+                               goto done;
+                       }
+done:
+                       in++;
+                       break;
+
+               case MT_TYPE:
+                       if (*in == '\x0d')
+                               s->state = MT_IGNORE2;
+                       else {
+                               if (s->mp < (int)sizeof(s->content_type) - 1)
+                                       s->content_type[s->mp++] = *in;
+                               s->content_type[s->mp] = '\0';
+                       }
+                       in++;
+                       break;
+
+               case MT_IGNORE1:
+                       if (*in == '\x0d')
+                               s->state = MT_IGNORE2;
+                       if (*in == '-')
+                               s->state = MT_IGNORE3;
+                       in++;
+                       break;
+
+               case MT_IGNORE2:
+                       s->mp = 0;
+                       if (*in == '\x0a')
+                               s->state = MT_HNAME;
+                       in++;
+                       break;
+
+               case MT_IGNORE3:
+                       if (*in == '\x0d')
+                               s->state = MT_IGNORE1;
+                       if (*in == '-') {
+                               s->state = MT_COMPLETED;
+                               s->wsi->http.rx_content_remain = 0;
+                       }
+                       in++;
+                       break;
+               case MT_COMPLETED:
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int
+lws_urldecode_s_destroy(struct lws_spa *spa, struct lws_urldecode_stateful *s)
+{
+       int ret = 0;
+
+       if (s->state != US_IDLE)
+               ret = -1;
+
+       if (!ret)
+               if (s->output(s->data, s->name, &s->out, s->pos,
+                             LWS_UFS_FINAL_CONTENT))
+                       ret = -1;
+
+       if (s->output(s->data, s->name, NULL, 0, LWS_UFS_CLOSE))
+               return -1;
+
+       if (!spa->i.ac)
+               lws_free(s);
+
+       return ret;
+}
+
+static int
+lws_urldecode_spa_lookup(struct lws_spa *spa, const char *name)
+{
+       const char * const *pp = spa->i.param_names;
+       int n;
+
+       for (n = 0; n < spa->i.count_params; n++) {
+               if (!strcmp(*pp, name))
+                       return n;
+
+               if (spa->i.param_names_stride)
+                       pp = (const char * const *)(((char *)pp) + spa->i.param_names_stride);
+               else
+                       pp++;
+       }
+
+       return -1;
+}
+
+static int
+lws_urldecode_spa_cb(struct lws_spa *spa, const char *name, char **buf, int len,
+                    int final)
+{
+       int n;
+
+       if (final == LWS_UFS_CLOSE || spa->s->content_disp_filename[0]) {
+               if (spa->i.opt_cb) {
+                       n = spa->i.opt_cb(spa->i.opt_data, name,
+                                       spa->s->content_disp_filename,
+                                       buf ? *buf : NULL, len, final);
+
+                       if (n < 0)
+                               return -1;
+               }
+               return 0;
+       }
+       n = lws_urldecode_spa_lookup(spa, name);
+       if (n == -1 || !len) /* unrecognized */
+               return 0;
+
+       if (!spa->i.ac) {
+               if (!spa->params[n])
+                       spa->params[n] = *buf;
+
+               if ((*buf) + len >= spa->end) {
+                       lwsl_info("%s: exceeded storage\n", __func__);
+                       return -1;
+               }
+
+               /* move it on inside storage */
+               (*buf) += len;
+               *((*buf)++) = '\0';
+
+               spa->s->out_len -= len + 1;
+       } else {
+               spa->params[n] = lwsac_use(spa->i.ac, len + 1,
+                                          spa->i.ac_chunk_size);
+               if (!spa->params[n])
+                       return -1;
+
+               memcpy(spa->params[n], *buf, len);
+               spa->params[n][len] = '\0';
+       }
+
+       spa->param_length[n] += len;
+
+       return 0;
+}
+
+struct lws_spa *
+lws_spa_create_via_info(struct lws *wsi, const lws_spa_create_info_t *i)
+{
+       struct lws_spa *spa;
+
+       if (i->ac)
+               spa = lwsac_use_zero(i->ac, sizeof(*spa), i->ac_chunk_size);
+       else
+               spa = lws_zalloc(sizeof(*spa), "spa");
+
+       if (!spa)
+               return NULL;
+
+       spa->i = *i;
+       if (!spa->i.max_storage)
+               spa->i.max_storage = 512;
+
+       if (i->ac)
+               spa->storage = lwsac_use(i->ac, spa->i.max_storage,
+                                        i->ac_chunk_size);
+       else
+               spa->storage = lws_malloc(spa->i.max_storage, "spa");
+
+       if (!spa->storage)
+               goto bail2;
+
+       spa->end = spa->storage + i->max_storage - 1;
+
+       if (i->count_params) {
+               if (i->ac)
+                       spa->params = lwsac_use_zero(i->ac,
+                               sizeof(char *) * i->count_params, i->ac_chunk_size);
+               else
+                       spa->params = lws_zalloc(sizeof(char *) * i->count_params,
+                                        "spa params");
+               if (!spa->params)
+                       goto bail3;
+       }
+
+       spa->s = lws_urldecode_s_create(spa, wsi, spa->storage, i->max_storage,
+                                       lws_urldecode_spa_cb);
+       if (!spa->s)
+               goto bail4;
+
+       if (i->count_params) {
+               if (i->ac)
+                       spa->param_length = lwsac_use_zero(i->ac,
+                               sizeof(int) * i->count_params, i->ac_chunk_size);
+               else
+                       spa->param_length = lws_zalloc(sizeof(int) * i->count_params,
+                                               "spa param len");
+               if (!spa->param_length)
+                       goto bail5;
+       }
+
+       lwsl_info("%s: Created SPA %p\n", __func__, spa);
+
+       return spa;
+
+bail5:
+       lws_urldecode_s_destroy(spa, spa->s);
+bail4:
+       if (!i->ac)
+               lws_free(spa->params);
+bail3:
+       if (!i->ac)
+               lws_free(spa->storage);
+bail2:
+       if (!i->ac)
+               lws_free(spa);
+
+       if (i->ac)
+               lwsac_free(i->ac);
+
+       return NULL;
+}
+
+struct lws_spa *
+lws_spa_create(struct lws *wsi, const char * const *param_names,
+              int count_params, int max_storage,
+              lws_spa_fileupload_cb opt_cb, void *opt_data)
+{
+       lws_spa_create_info_t i;
+
+       memset(&i, 0, sizeof(i));
+       i.count_params = count_params;
+       i.max_storage = max_storage;
+       i.opt_cb = opt_cb;
+       i.opt_data = opt_data;
+       i.param_names = param_names;
+
+       return lws_spa_create_via_info(wsi, &i);
+}
+
+int
+lws_spa_process(struct lws_spa *spa, const char *in, int len)
+{
+       if (!spa) {
+               lwsl_err("%s: NULL spa\n", __func__);
+               return -1;
+       }
+       /* we reject any junk after the last part arrived and we finalized */
+       if (spa->finalized)
+               return 0;
+
+       return lws_urldecode_s_process(spa->s, in, len);
+}
+
+int
+lws_spa_get_length(struct lws_spa *spa, int n)
+{
+       if (n >= spa->i.count_params)
+               return 0;
+
+       return spa->param_length[n];
+}
+
+const char *
+lws_spa_get_string(struct lws_spa *spa, int n)
+{
+       if (n >= spa->i.count_params)
+               return NULL;
+
+       return spa->params[n];
+}
+
+int
+lws_spa_finalize(struct lws_spa *spa)
+{
+       if (!spa)
+               return 0;
+
+       if (spa->s) {
+               lws_urldecode_s_destroy(spa, spa->s);
+               spa->s = NULL;
+       }
+
+       spa->finalized = 1;
+
+       return 0;
+}
+
+int
+lws_spa_destroy(struct lws_spa *spa)
+{
+       int n = 0;
+
+       lwsl_info("%s: destroy spa %p\n", __func__, spa);
+
+       if (spa->s)
+               lws_urldecode_s_destroy(spa, spa->s);
+
+       if (spa->i.ac)
+               lwsac_free(spa->i.ac);
+       else {
+               lws_free(spa->param_length);
+               lws_free(spa->params);
+               lws_free(spa->storage);
+               lws_free(spa);
+       }
+
+       return n;
+}
diff --git a/lib/roles/http/server/parsers.c b/lib/roles/http/server/parsers.c
new file mode 100644 (file)
index 0000000..795b53b
--- /dev/null
@@ -0,0 +1,1420 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static const unsigned char lextable[] = {
+#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
+       #include "roles/http/lextable.h"
+#else
+       #include "../lextable.h"
+#endif
+};
+
+#define FAIL_CHAR 0x08
+
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+
+#define UHO_NLEN       0
+#define UHO_VLEN       2
+#define UHO_LL         4
+#define UHO_NAME       8
+
+static uint16_t
+lws_un16be_get(const void *_p)
+{
+       const uint8_t *p = _p;
+
+       return ((uint16_t)p[0] << 8) | p[1];
+}
+
+static void
+lws_un16be_set(void *_p, uint16_t v)
+{
+       uint8_t *p = _p;
+
+       *p++ = (uint8_t)(v >> 8);
+       *p++ = (uint8_t)v;
+}
+
+static uint32_t
+lws_un32be_get(const void *_p)
+{
+       const uint8_t *p = _p;
+
+       return (uint32_t)((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]);
+}
+
+static void
+lws_un32be_set(void *_p, uint32_t v)
+{
+       uint8_t *p = _p;
+
+       *p++ = (uint8_t)(v >> 24);
+       *p++ = (uint8_t)(v >> 16);
+       *p++ = (uint8_t)(v >> 8);
+       *p = (uint8_t)v;
+}
+#endif
+
+static struct allocated_headers *
+_lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size)
+{
+       struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct");
+
+       if (!ah)
+               return NULL;
+
+       ah->data = lws_malloc(data_size, "ah data");
+       if (!ah->data) {
+               lws_free(ah);
+
+               return NULL;
+       }
+       ah->next = pt->http.ah_list;
+       pt->http.ah_list = ah;
+       ah->data_length = data_size;
+       pt->http.ah_pool_length++;
+
+       lwsl_info("%s: created ah %p (size %d): pool length %ld\n", __func__,
+                   ah, (int)data_size, (unsigned long)pt->http.ah_pool_length);
+
+       return ah;
+}
+
+int
+_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah)
+{
+       lws_start_foreach_llp(struct allocated_headers **, a, pt->http.ah_list) {
+               if ((*a) == ah) {
+                       *a = ah->next;
+                       pt->http.ah_pool_length--;
+                       lwsl_info("%s: freed ah %p : pool length %ld\n",
+                                   __func__, ah,
+                                   (unsigned long)pt->http.ah_pool_length);
+                       if (ah->data)
+                               lws_free(ah->data);
+                       lws_free(ah);
+
+                       return 0;
+               }
+       } lws_end_foreach_llp(a, next);
+
+       return 1;
+}
+
+void
+_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;
+       ah->parser_state = WSI_TOKEN_NAME_PART;
+       ah->lextable_pos = 0;
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+       ah->unk_pos = 0;
+       ah->unk_ll_head = 0;
+       ah->unk_ll_tail = 0;
+#endif
+}
+
+// doesn't scrub the ah rxbuffer by default, parent must do if needed
+
+void
+__lws_header_table_reset(struct lws *wsi, int autoservice)
+{
+       struct allocated_headers *ah = wsi->http.ah;
+       struct lws_context_per_thread *pt;
+       struct lws_pollfd *pfd;
+
+       /* if we have the idea we're resetting 'our' ah, must be bound to one */
+       assert(ah);
+       /* ah also concurs with ownership */
+       assert(ah->wsi == wsi);
+
+       _lws_header_table_reset(ah);
+
+       /* since we will restart the ah, our new headers are not completed */
+       wsi->hdr_parsing_completed = 0;
+
+       /* while we hold the ah, keep a timeout on the wsi */
+       __lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH,
+                         wsi->vhost->timeout_secs_ah_idle);
+
+       time(&ah->assigned);
+
+       if (wsi->position_in_fds_table != LWS_NO_FDS_POS &&
+           lws_buflist_next_segment_len(&wsi->buflist, NULL) &&
+           autoservice) {
+               lwsl_debug("%s: service on readbuf ah\n", __func__);
+
+               pt = &wsi->context->pt[(int)wsi->tsi];
+               /*
+                * Unlike a normal connect, we have the headers already
+                * (or the first part of them anyway)
+                */
+               pfd = &pt->fds[wsi->position_in_fds_table];
+               pfd->revents |= LWS_POLLIN;
+               lwsl_err("%s: calling service\n", __func__);
+               lws_service_fd_tsi(wsi->context, pfd, wsi->tsi);
+       }
+}
+
+void
+lws_header_table_reset(struct lws *wsi, int autoservice)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_pt_lock(pt, __func__);
+
+       __lws_header_table_reset(wsi, autoservice);
+
+       lws_pt_unlock(pt);
+}
+
+static void
+_lws_header_ensure_we_are_on_waiting_list(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws_pollargs pa;
+       struct lws **pwsi = &pt->http.ah_wait_list;
+
+       while (*pwsi) {
+               if (*pwsi == wsi)
+                       return;
+               pwsi = &(*pwsi)->http.ah_wait_list;
+       }
+
+       lwsl_info("%s: wsi: %p\n", __func__, wsi);
+       wsi->http.ah_wait_list = pt->http.ah_wait_list;
+       pt->http.ah_wait_list = wsi;
+       pt->http.ah_wait_list_length++;
+
+       /* we cannot accept input then */
+
+       _lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa);
+}
+
+static int
+__lws_remove_from_ah_waiting_list(struct lws *wsi)
+{
+        struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws **pwsi =&pt->http.ah_wait_list;
+
+       while (*pwsi) {
+               if (*pwsi == wsi) {
+                       lwsl_info("%s: wsi %p\n", __func__, wsi);
+                       /* point prev guy to our next */
+                       *pwsi = wsi->http.ah_wait_list;
+                       /* we shouldn't point anywhere now */
+                       wsi->http.ah_wait_list = NULL;
+                       pt->http.ah_wait_list_length--;
+
+                       return 1;
+               }
+               pwsi = &(*pwsi)->http.ah_wait_list;
+       }
+
+       return 0;
+}
+
+int LWS_WARN_UNUSED_RESULT
+lws_header_table_attach(struct lws *wsi, int autoservice)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws_pollargs pa;
+       int n;
+
+       lwsl_info("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__,
+                 (void *)wsi, (void *)wsi->http.ah, wsi->tsi,
+                 pt->http.ah_count_in_use);
+
+       if (!lwsi_role_http(wsi)) {
+               lwsl_err("%s: bad role %s\n", __func__, wsi->role_ops->name);
+               assert(0);
+               return -1;
+       }
+
+       lws_pt_lock(pt, __func__);
+
+       /* if we are already bound to one, just clear it down */
+       if (wsi->http.ah) {
+               lwsl_info("%s: cleardown\n", __func__);
+               goto reset;
+       }
+
+       n = pt->http.ah_count_in_use == context->max_http_header_pool;
+#if defined(LWS_WITH_PEER_LIMITS)
+       if (!n) {
+               n = lws_peer_confirm_ah_attach_ok(context, wsi->peer);
+               if (n)
+                       lws_stats_bump(pt, LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1);
+       }
+#endif
+       if (n) {
+               /*
+                * Pool is either all busy, or we don't want to give this
+                * particular guy an ah right now...
+                *
+                * Make sure we are on the waiting list, and return that we
+                * weren't able to provide the ah
+                */
+               _lws_header_ensure_we_are_on_waiting_list(wsi);
+
+               goto bail;
+       }
+
+       __lws_remove_from_ah_waiting_list(wsi);
+
+       wsi->http.ah = _lws_create_ah(pt, context->max_http_header_data);
+       if (!wsi->http.ah) { /* we could not create an ah */
+               _lws_header_ensure_we_are_on_waiting_list(wsi);
+
+               goto bail;
+       }
+
+       wsi->http.ah->in_use = 1;
+       wsi->http.ah->wsi = wsi; /* mark our owner */
+       pt->http.ah_count_in_use++;
+
+#if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || \
+    defined(LWS_ROLE_H2))
+       lws_context_lock(context, "ah attach"); /* <========================= */
+       if (wsi->peer)
+               wsi->peer->http.count_ah++;
+       lws_context_unlock(context); /* ====================================> */
+#endif
+
+       _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa);
+
+       lwsl_info("%s: did attach wsi %p: ah %p: count %d (on exit)\n", __func__,
+                 (void *)wsi, (void *)wsi->http.ah, pt->http.ah_count_in_use);
+
+reset:
+       __lws_header_table_reset(wsi, autoservice);
+
+       lws_pt_unlock(pt);
+
+#ifndef LWS_NO_CLIENT
+       if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED)
+               if (!lws_http_client_connect_via_info2(wsi))
+                       /* our client connect has failed, the wsi
+                        * has been closed
+                        */
+                       return -1;
+#endif
+
+       return 0;
+
+bail:
+       lws_pt_unlock(pt);
+
+       return 1;
+}
+
+int __lws_header_table_detach(struct lws *wsi, int autoservice)
+{
+       struct lws_context *context = wsi->context;
+       struct allocated_headers *ah = wsi->http.ah;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws_pollargs pa;
+       struct lws **pwsi, **pwsi_eligible;
+       time_t now;
+
+       __lws_remove_from_ah_waiting_list(wsi);
+
+       if (!ah)
+               return 0;
+
+       lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__,
+                 (void *)wsi, (void *)ah, wsi->tsi,
+                 pt->http.ah_count_in_use);
+
+       /* we did have an ah attached */
+       time(&now);
+       if (ah->assigned && now - ah->assigned > 3) {
+               /*
+                * we're detaching the ah, but it was held an
+                * unreasonably long time
+                */
+               lwsl_debug("%s: wsi %p: ah held %ds, role/state 0x%lx 0x%x,"
+                           "\n", __func__, wsi, (int)(now - ah->assigned),
+                           (unsigned long)lwsi_role(wsi), lwsi_state(wsi));
+       }
+
+       ah->assigned = 0;
+
+       /* if we think we're detaching one, there should be one in use */
+       assert(pt->http.ah_count_in_use > 0);
+       /* and this specific one should have been in use */
+       assert(ah->in_use);
+       memset(&wsi->http.ah, 0, sizeof(wsi->http.ah));
+
+#if defined(LWS_WITH_PEER_LIMITS)
+       if (ah->wsi)
+               lws_peer_track_ah_detach(context, wsi->peer);
+#endif
+       ah->wsi = NULL; /* no owner */
+
+       pwsi = &pt->http.ah_wait_list;
+
+       /* oh there is nobody on the waiting list... leave the ah unattached */
+       if (!*pwsi)
+               goto nobody_usable_waiting;
+
+       /*
+        * at least one wsi on the same tsi is waiting, give it to oldest guy
+        * who is allowed to take it (if any)
+        */
+       lwsl_info("pt wait list %p\n", *pwsi);
+       wsi = NULL;
+       pwsi_eligible = NULL;
+
+       while (*pwsi) {
+#if defined(LWS_WITH_PEER_LIMITS)
+               /* are we willing to give this guy an ah? */
+               if (!lws_peer_confirm_ah_attach_ok(context, (*pwsi)->peer))
+#endif
+               {
+                       wsi = *pwsi;
+                       pwsi_eligible = pwsi;
+               }
+#if defined(LWS_WITH_PEER_LIMITS)
+               else
+                       if (!(*pwsi)->http.ah_wait_list)
+                               lws_stats_bump(pt,
+                                       LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1);
+#endif
+               pwsi = &(*pwsi)->http.ah_wait_list;
+       }
+
+       if (!wsi) /* everybody waiting already has too many ah... */
+               goto nobody_usable_waiting;
+
+       lwsl_info("%s: transferring ah to last eligible wsi in wait list "
+                 "%p (wsistate 0x%lx)\n", __func__, wsi,
+                 (unsigned long)wsi->wsistate);
+
+       wsi->http.ah = ah;
+       ah->wsi = wsi; /* new owner */
+
+       __lws_header_table_reset(wsi, autoservice);
+#if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || \
+    defined(LWS_ROLE_H2))
+       lws_context_lock(context, "ah detach"); /* <========================= */
+       if (wsi->peer)
+               wsi->peer->http.count_ah++;
+       lws_context_unlock(context); /* ====================================> */
+#endif
+
+       /* clients acquire the ah and then insert themselves in fds table... */
+       if (wsi->position_in_fds_table != LWS_NO_FDS_POS) {
+               lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi);
+
+               /* 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);
+       }
+
+       /* point prev guy to next guy in list instead */
+       *pwsi_eligible = wsi->http.ah_wait_list;
+       /* the guy who got one is out of the list */
+       wsi->http.ah_wait_list = NULL;
+       pt->http.ah_wait_list_length--;
+
+#ifndef LWS_NO_CLIENT
+       if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) {
+               lws_pt_unlock(pt);
+
+               if (!lws_http_client_connect_via_info2(wsi)) {
+                       /* our client connect has failed, the wsi
+                        * has been closed
+                        */
+
+                       return -1;
+               }
+               return 0;
+       }
+#endif
+
+       assert(!!pt->http.ah_wait_list_length ==
+                       !!(lws_intptr_t)pt->http.ah_wait_list);
+bail:
+       lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__,
+                 (void *)wsi, (void *)ah, pt->tid, pt->http.ah_count_in_use);
+
+       return 0;
+
+nobody_usable_waiting:
+       lwsl_info("%s: nobody usable waiting\n", __func__);
+       _lws_destroy_ah(pt, ah);
+       pt->http.ah_count_in_use--;
+
+       goto bail;
+}
+
+int lws_header_table_detach(struct lws *wsi, int autoservice)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       int n;
+
+       lws_pt_lock(pt, __func__);
+       n = __lws_header_table_detach(wsi, autoservice);
+       lws_pt_unlock(pt);
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx)
+{
+       int n;
+
+       if (!wsi->http.ah)
+               return 0;
+
+       n = wsi->http.ah->frag_index[h];
+       if (!n)
+               return 0;
+       do {
+               if (!frag_idx)
+                       return wsi->http.ah->frags[n].len;
+               n = wsi->http.ah->frags[n].nfrag;
+       } while (frag_idx-- && n);
+
+       return 0;
+}
+
+LWS_VISIBLE int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h)
+{
+       int n;
+       int len = 0;
+
+       if (!wsi->http.ah)
+               return 0;
+
+       n = wsi->http.ah->frag_index[h];
+       if (!n)
+               return 0;
+       do {
+               len += wsi->http.ah->frags[n].len;
+               n = wsi->http.ah->frags[n].nfrag;
+
+               if (n && h != WSI_TOKEN_HTTP_COOKIE)
+                       ++len;
+
+       } while (n);
+
+       return len;
+}
+
+LWS_VISIBLE int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len,
+                                     enum lws_token_indexes h, int frag_idx)
+{
+       int n = 0;
+       int f;
+
+       if (!wsi->http.ah)
+               return -1;
+
+       f = wsi->http.ah->frag_index[h];
+
+       if (!f)
+               return -1;
+
+       while (n < frag_idx) {
+               f = wsi->http.ah->frags[f].nfrag;
+               if (!f)
+                       return -1;
+               n++;
+       }
+
+       if (wsi->http.ah->frags[f].len >= len)
+               return -1;
+
+       memcpy(dst, wsi->http.ah->data + wsi->http.ah->frags[f].offset,
+              wsi->http.ah->frags[f].len);
+       dst[wsi->http.ah->frags[f].len] = '\0';
+
+       return wsi->http.ah->frags[f].len;
+}
+
+LWS_VISIBLE int lws_hdr_copy(struct lws *wsi, char *dst, int len,
+                            enum lws_token_indexes h)
+{
+       int toklen = lws_hdr_total_length(wsi, h);
+       int n;
+       int comma;
+
+       *dst = '\0';
+       if (!toklen)
+               return 0;
+
+       if (toklen >= len)
+               return -1;
+
+       if (!wsi->http.ah)
+               return -1;
+
+       n = wsi->http.ah->frag_index[h];
+       if (!n)
+               return 0;
+
+       do {
+               comma = (wsi->http.ah->frags[n].nfrag &&
+                       h != WSI_TOKEN_HTTP_COOKIE) ? 1 : 0;
+
+               if (wsi->http.ah->frags[n].len + comma >= len)
+                       return -1;
+               strncpy(dst, &wsi->http.ah->data[wsi->http.ah->frags[n].offset],
+                       wsi->http.ah->frags[n].len);
+               dst += wsi->http.ah->frags[n].len;
+               len -= wsi->http.ah->frags[n].len;
+               n = wsi->http.ah->frags[n].nfrag;
+
+               if (comma)
+                       *dst++ = ',';
+                               
+       } while (n);
+       *dst = '\0';
+
+       return toklen;
+}
+
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+LWS_VISIBLE int
+lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen)
+{
+       ah_data_idx_t ll;
+
+       if (!wsi->http.ah || wsi->http2_substream)
+               return -1;
+
+       ll = wsi->http.ah->unk_ll_head;
+       while (ll) {
+               if (ll >= wsi->http.ah->data_length)
+                       return -1;
+               if (nlen == lws_un16be_get(&wsi->http.ah->data[ll + UHO_NLEN]) &&
+                   !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], nlen))
+                       return lws_un16be_get(&wsi->http.ah->data[ll + UHO_VLEN]);
+
+               ll = lws_un32be_get(&wsi->http.ah->data[ll + UHO_LL]);
+       }
+
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_hdr_custom_copy(struct lws *wsi, char *dst, int len, const char *name,
+                   int nlen)
+{
+       ah_data_idx_t ll;
+       int n;
+
+       if (!wsi->http.ah || wsi->http2_substream)
+               return -1;
+
+       *dst = '\0';
+
+       ll = wsi->http.ah->unk_ll_head;
+       while (ll) {
+               if (ll >= wsi->http.ah->data_length)
+                       return -1;
+               if (nlen == lws_un16be_get(&wsi->http.ah->data[ll + UHO_NLEN]) &&
+                   !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], nlen)) {
+                       n = lws_un16be_get(&wsi->http.ah->data[ll + UHO_VLEN]);
+                       if (n + 1 > len)
+                               return -1;
+                       strncpy(dst, &wsi->http.ah->data[ll + UHO_NAME + nlen], n);
+                       dst[n] = '\0';
+
+                       return n;
+               }
+               ll = lws_un32be_get(&wsi->http.ah->data[ll + UHO_LL]);
+       }
+
+       return -1;
+}
+#endif
+
+char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h)
+{
+       int n;
+
+       if (!wsi->http.ah)
+               return NULL;
+
+       n = wsi->http.ah->frag_index[h];
+       if (!n)
+               return NULL;
+
+       return wsi->http.ah->data + wsi->http.ah->frags[n].offset;
+}
+
+static int LWS_WARN_UNUSED_RESULT
+lws_pos_in_bounds(struct lws *wsi)
+{
+       if (!wsi->http.ah)
+               return -1;
+
+       if (wsi->http.ah->pos <
+           (unsigned int)wsi->context->max_http_header_data)
+               return 0;
+
+       if ((int)wsi->http.ah->pos == wsi->context->max_http_header_data) {
+               lwsl_err("Ran out of header data space\n");
+               return 1;
+       }
+
+       /*
+        * with these tests everywhere, it should never be able to exceed
+        * the limit, only meet it
+        */
+       lwsl_err("%s: pos %ld, limit %ld\n", __func__,
+                (unsigned long)wsi->http.ah->pos,
+                (unsigned long)wsi->context->max_http_header_data);
+       assert(0);
+
+       return 1;
+}
+
+int LWS_WARN_UNUSED_RESULT
+lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s)
+{
+       wsi->http.ah->nfrag++;
+       if (wsi->http.ah->nfrag == LWS_ARRAY_SIZE(wsi->http.ah->frags)) {
+               lwsl_warn("More hdr frags than we can deal with, dropping\n");
+               return -1;
+       }
+
+       wsi->http.ah->frag_index[h] = wsi->http.ah->nfrag;
+
+       wsi->http.ah->frags[wsi->http.ah->nfrag].offset = wsi->http.ah->pos;
+       wsi->http.ah->frags[wsi->http.ah->nfrag].len = 0;
+       wsi->http.ah->frags[wsi->http.ah->nfrag].nfrag = 0;
+
+       do {
+               if (lws_pos_in_bounds(wsi))
+                       return -1;
+
+               wsi->http.ah->data[wsi->http.ah->pos++] = *s;
+               if (*s)
+                       wsi->http.ah->frags[wsi->http.ah->nfrag].len++;
+       } while (*s++);
+
+       return 0;
+}
+
+static int LWS_WARN_UNUSED_RESULT
+issue_char(struct lws *wsi, unsigned char c)
+{
+       unsigned short frag_len;
+
+       if (lws_pos_in_bounds(wsi))
+               return -1;
+
+       frag_len = wsi->http.ah->frags[wsi->http.ah->nfrag].len;
+       /*
+        * If we haven't hit the token limit, just copy the character into
+        * the header
+        */
+       if (!wsi->http.ah->current_token_limit ||
+           frag_len < wsi->http.ah->current_token_limit) {
+               wsi->http.ah->data[wsi->http.ah->pos++] = c;
+               if (c)
+                       wsi->http.ah->frags[wsi->http.ah->nfrag].len++;
+               return 0;
+       }
+
+       /* Insert a null character when we *hit* the limit: */
+       if (frag_len == wsi->http.ah->current_token_limit) {
+               if (lws_pos_in_bounds(wsi))
+                       return -1;
+
+               wsi->http.ah->data[wsi->http.ah->pos++] = '\0';
+               lwsl_warn("header %li exceeds limit %ld\n",
+                         (long)wsi->http.ah->parser_state,
+                         (long)wsi->http.ah->current_token_limit);
+       }
+
+       return 1;
+}
+
+int
+lws_parse_urldecode(struct lws *wsi, uint8_t *_c)
+{
+       struct allocated_headers *ah = wsi->http.ah;
+       unsigned int enc = 0;
+       uint8_t c = *_c;
+
+       // lwsl_notice("ah->ups %d\n", ah->ups);
+
+       /*
+        * PRIORITY 1
+        * special URI processing... convert %xx
+        */
+       switch (ah->ues) {
+       case URIES_IDLE:
+               if (c == '%') {
+                       ah->ues = URIES_SEEN_PERCENT;
+                       goto swallow;
+               }
+               break;
+       case URIES_SEEN_PERCENT:
+               if (char_to_hex(c) < 0)
+                       /* illegal post-% char */
+                       goto forbid;
+
+               ah->esc_stash = c;
+               ah->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(ah->esc_stash) << 4) |
+                               char_to_hex(c);
+               c = *_c;
+               enc = 1;
+               ah->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 (ah->ups) {
+       case URIPS_IDLE:
+               if (!c)
+                       return -1;
+               /* genuine delimiter */
+               if ((c == '&' || c == ';') && !enc) {
+                       if (issue_char(wsi, '\0') < 0)
+                               return -1;
+                       /* link to next fragment */
+                       ah->frags[ah->nfrag].nfrag = ah->nfrag + 1;
+                       ah->nfrag++;
+                       if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frags))
+                               goto excessive;
+                       /* start next fragment after the & */
+                       ah->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] &&
+                   !ah->post_literal_equal) {
+                       c = '_';
+                       *_c =c;
+               }
+
+               /* after the real =, we don't care how many = */
+               if (c == '=' && !enc)
+                       ah->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])
+                       ah->ups = URIPS_SEEN_SLASH;
+               break;
+       case URIPS_SEEN_SLASH:
+               /* swallow subsequent slashes */
+               if (c == '/')
+                       goto swallow;
+               /* track and swallow the first . after / */
+               if (c == '.') {
+                       ah->ups = URIPS_SEEN_SLASH_DOT;
+                       goto swallow;
+               }
+               ah->ups = URIPS_IDLE;
+               break;
+       case URIPS_SEEN_SLASH_DOT:
+               /* swallow second . */
+               if (c == '.') {
+                       ah->ups = URIPS_SEEN_SLASH_DOT_DOT;
+                       goto swallow;
+               }
+               /* change /./ to / */
+               if (c == '/') {
+                       ah->ups = URIPS_SEEN_SLASH;
+                       goto swallow;
+               }
+               /* it was like /.dir ... regurgitate the . */
+               ah->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] != '/');
+                       }
+                       ah->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;
+               ah->ups = URIPS_IDLE;
+               break;
+       }
+
+       if (c == '?' && !enc &&
+           !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI args */
+               if (ah->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 >= LWS_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;
+
+               ah->post_literal_equal = 0;
+               ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag;
+               ah->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,
+};
+
+/*
+ * possible returns:, -1 fail, 0 ok or 2, transition to raw
+ */
+
+int LWS_WARN_UNUSED_RESULT
+lws_parse(struct lws *wsi, unsigned char *buf, int *len)
+{
+       struct allocated_headers *ah = wsi->http.ah;
+       struct lws_context *context = wsi->context;
+       unsigned int n, m;
+       unsigned char c;
+       int r, pos;
+
+       assert(wsi->http.ah);
+
+       do {
+               (*len)--;
+               c = *buf++;
+
+               switch (ah->parser_state) {
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+               case WSI_TOKEN_UNKNOWN_VALUE_PART:
+
+                       if (c == '\r')
+                               break;
+                       if (c == '\n') {
+                               lws_un16be_set(&ah->data[ah->unk_pos + 2],
+                                              ah->pos - ah->unk_value_pos);
+                               ah->parser_state = WSI_TOKEN_NAME_PART;
+                               ah->unk_pos = 0;
+                               ah->lextable_pos = 0;
+                               break;
+                       }
+
+                       /* trim leading whitespace */
+                       if (ah->pos != ah->unk_value_pos ||
+                           (c != ' ' && c != '\t')) {
+
+                               if (lws_pos_in_bounds(wsi))
+                                       return -1;
+
+                               ah->data[ah->pos++] = c;
+                       }
+                       pos = ah->lextable_pos;
+                       break;
+#endif
+               default:
+
+                       lwsl_parser("WSI_TOK_(%d) '%c'\n", ah->parser_state, c);
+
+                       /* collect into malloc'd buffers */
+                       /* optional initial space swallow */
+                       if (!ah->frags[ah->frag_index[ah->parser_state]].len &&
+                           c == ' ')
+                               break;
+
+                       for (m = 0; m < LWS_ARRAY_SIZE(methods); m++)
+                               if (ah->parser_state == methods[m])
+                                       break;
+                       if (m == LWS_ARRAY_SIZE(methods))
+                               /* it was not any of the methods */
+                               goto check_eol;
+
+                       /* special URI processing... end at space */
+
+                       if (c == ' ') {
+                               /* enforce starting with / */
+                               if (!ah->frags[ah->nfrag].len)
+                                       if (issue_char(wsi, '/') < 0)
+                                               return -1;
+
+                               if (ah->ups == URIPS_SEEN_SLASH_DOT_DOT) {
+                                       /*
+                                        * 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] != '/');
+                                       }
+                               }
+
+                               /* begin parsing HTTP version: */
+                               if (issue_char(wsi, '\0') < 0)
+                                       return -1;
+                               ah->parser_state = WSI_TOKEN_HTTP;
+                               goto start_fragment;
+                       }
+
+                       r = lws_parse_urldecode(wsi, &c);
+                       switch (r) {
+                       case LPUR_CONTINUE:
+                               break;
+                       case LPUR_SWALLOW:
+                               goto swallow;
+                       case LPUR_FORBID:
+                               goto forbid;
+                       case LPUR_EXCESSIVE:
+                               goto excessive;
+                       default:
+                               return LPR_FAIL;
+                       }
+check_eol:
+                       /* bail at EOL */
+                       if (ah->parser_state != WSI_TOKEN_CHALLENGE &&
+                           c == '\x0d') {
+                               if (ah->ues != URIES_IDLE)
+                                       goto forbid;
+
+                               c = '\0';
+                               ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
+                               lwsl_parser("*\n");
+                       }
+
+                       n = issue_char(wsi, c);
+                       if ((int)n < 0)
+                               return LPR_FAIL;
+                       if (n > 0)
+                               ah->parser_state = WSI_TOKEN_SKIPPING;
+
+swallow:
+                       /* per-protocol end of headers management */
+
+                       if (ah->parser_state == WSI_TOKEN_CHALLENGE)
+                               goto set_parsing_complete;
+                       break;
+
+                       /* collecting and checking a name part */
+               case WSI_TOKEN_NAME_PART:
+                       lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X "
+                                   "(role=0x%lx) "
+                                   "wsi->lextable_pos=%d\n", c, c,
+                                   (unsigned long)lwsi_role(wsi),
+                                   ah->lextable_pos);
+
+                       if (c >= 'A' && c <= 'Z')
+                               c += 'a' - 'A';
+
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                       /*
+                        * ...in case it's an unknown header, speculatively
+                        * store it as the name comes in.  If we recognize it as
+                        * a known header, we'll snip this.
+                        */
+
+                       if (!ah->unk_pos) {
+                               ah->unk_pos = ah->pos;
+                               /*
+                                * Prepare new unknown header linked-list entry
+                                *
+                                *  - 16-bit BE: name part length
+                                *  - 16-bit BE: value part length
+                                *  - 32-bit BE: data offset of next, or 0
+                                */
+                               for (n = 0; n < 8; n++)
+                                       if (!lws_pos_in_bounds(wsi))
+                                               ah->data[ah->pos++] = 0;
+                       }
+#endif
+
+                       if (lws_pos_in_bounds(wsi))
+                               return -1;
+
+                       ah->data[ah->pos++] = c;
+                       pos = ah->lextable_pos;
+
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                       if (pos < 0 && c == ':') {
+                               /*
+                                * process unknown headers
+                                *
+                                * register us in the unknown hdr ll
+                                */
+
+                               if (!ah->unk_ll_head)
+                                       ah->unk_ll_head = ah->unk_pos;
+
+                               if (ah->unk_ll_tail)
+                                       lws_un32be_set(&ah->data[ah->unk_ll_tail + UHO_LL],
+                                                      ah->unk_pos);
+
+                               ah->unk_ll_tail = ah->unk_pos;
+
+                               lwsl_debug("%s: unk header %d '%.*s'\n",
+                                          __func__,
+                                          ah->pos - (ah->unk_pos + UHO_NAME),
+                                          ah->pos - (ah->unk_pos + UHO_NAME),
+                                          &ah->data[ah->unk_pos + UHO_NAME]);
+
+                               /* set the unknown header name part length */
+
+                               lws_un16be_set(&ah->data[ah->unk_pos],
+                                              (ah->pos - ah->unk_pos) - UHO_NAME);
+
+                               ah->unk_value_pos = ah->pos;
+
+                               /*
+                                * collect whatever's coming for the unknown header
+                                * argument until the next CRLF
+                                */
+                               ah->parser_state = WSI_TOKEN_UNKNOWN_VALUE_PART;
+                               break;
+                       }
+#endif
+                       if (pos < 0)
+                               break;
+
+                       while (1) {
+                               if (lextable[pos] & (1 << 7)) {
+                                       /* 1-byte, fail on mismatch */
+                                       if ((lextable[pos] & 0x7f) != c) {
+nope:
+                                               ah->lextable_pos = -1;
+                                               break;
+                                       }
+                                       /* fall thru */
+                                       pos++;
+                                       if (lextable[pos] == FAIL_CHAR)
+                                               goto nope;
+
+                                       ah->lextable_pos = pos;
+                                       break;
+                               }
+
+                               if (lextable[pos] == FAIL_CHAR)
+                                       goto nope;
+
+                               /* b7 = 0, end or 3-byte */
+                               if (lextable[pos] < FAIL_CHAR) {
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                                       /*
+                                        * We hit a terminal marker, so we
+                                        * recognized this header... drop the
+                                        * speculative name part storage
+                                        */
+                                       ah->pos = ah->unk_pos;
+                                       ah->unk_pos = 0;
+#endif
+                                       ah->lextable_pos = pos;
+                                       break;
+                               }
+
+                               if (lextable[pos] == c) { /* goto */
+                                       ah->lextable_pos = pos +
+                                               (lextable[pos + 1]) +
+                                               (lextable[pos + 2] << 8);
+                                       break;
+                               }
+
+                               /* fall thru goto */
+                               pos += 3;
+                               /* continue */
+                       }
+
+                       /*
+                        * If it's h1, server needs to be on the look out for
+                        * unknown methods...
+                        */
+                       if (ah->lextable_pos < 0 && lwsi_role_h1(wsi) &&
+                           lwsi_role_server(wsi)) {
+                               /*
+                                * this is not a header we know about... did
+                                * we get a valid method (GET, POST etc)
+                                * already, or is this the bogus method?
+                                */
+                               for (m = 0; m < LWS_ARRAY_SIZE(methods); m++)
+                                       if (ah->frag_index[methods[m]]) {
+                                               /*
+                                                * already had the method
+                                                */
+#if !defined(LWS_WITH_CUSTOM_HEADERS)
+                                               ah->parser_state = WSI_TOKEN_SKIPPING;
+#endif
+                                               break;
+                                       }
+
+                               if (m != LWS_ARRAY_SIZE(methods))
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                                       /*
+                                        * We have the method, this is just an
+                                        * unknown header then
+                                        */
+                                       goto unknown_hdr;
+#else
+                                       break;
+#endif
+                               /*
+                                * ...it's an unknown http method from a client
+                                * in fact, it cannot be valid http.
+                                *
+                                * Are we set up to transition to another role
+                                * in these cases?
+                                */
+                               if (lws_check_opt(wsi->vhost->options,
+                   LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG)) {
+                                       lwsl_notice("%s: http fail fallback\n",
+                                                   __func__);
+                                        /* transition to other role */
+                                       return LPR_DO_FALLBACK;
+                               }
+
+                               lwsl_info("Unknown method - dropping\n");
+                               goto forbid;
+                       }
+                       if (ah->lextable_pos < 0) {
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                               goto unknown_hdr;
+#else
+                               /*
+                                * ...otherwise for a client, let him ignore
+                                * unknown headers coming from the server
+                                */
+                               ah->parser_state = WSI_TOKEN_SKIPPING;
+                               break;
+#endif
+                       }
+
+                       if (lextable[ah->lextable_pos] < FAIL_CHAR) {
+                               /* terminal state */
+
+                               n = ((unsigned int)lextable[ah->lextable_pos] << 8) |
+                                               lextable[ah->lextable_pos + 1];
+
+                               lwsl_parser("known hdr %d\n", n);
+                               for (m = 0; m < LWS_ARRAY_SIZE(methods); m++)
+                                       if (n == methods[m] &&
+                                           ah->frag_index[methods[m]]) {
+                                               lwsl_warn("Duplicated method\n");
+                                               return LPR_FAIL;
+                                       }
+
+                               /*
+                                * WSORIGIN is protocol equiv to ORIGIN,
+                                * JWebSocket likes to send it, map to ORIGIN
+                                */
+                               if (n == WSI_TOKEN_SWORIGIN)
+                                       n = WSI_TOKEN_ORIGIN;
+
+                               ah->parser_state = (enum lws_token_indexes)
+                                                       (WSI_TOKEN_GET_URI + n);
+                               ah->ups = URIPS_IDLE;
+
+                               if (context->token_limits)
+                                       ah->current_token_limit = context->
+                                               token_limits->token_limit[
+                                                             ah->parser_state];
+                               else
+                                       ah->current_token_limit =
+                                               wsi->context->max_http_header_data;
+
+                               if (ah->parser_state == WSI_TOKEN_CHALLENGE)
+                                       goto set_parsing_complete;
+
+                               goto start_fragment;
+                       }
+                       break;
+
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+unknown_hdr:
+                       //ah->parser_state = WSI_TOKEN_SKIPPING;
+                       //break;
+                       break;
+#endif
+
+start_fragment:
+                       ah->nfrag++;
+excessive:
+                       if (ah->nfrag == LWS_ARRAY_SIZE(ah->frags)) {
+                               lwsl_warn("More hdr frags than we can deal with\n");
+                               return LPR_FAIL;
+                       }
+
+                       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[ah->parser_state];
+                       if (!n) { /* first fragment */
+                               ah->frag_index[ah->parser_state] = ah->nfrag;
+                               ah->hdr_token_idx = ah->parser_state;
+                               break;
+                       }
+                       /* continuation */
+                       while (ah->frags[n].nfrag)
+                               n = ah->frags[n].nfrag;
+                       ah->frags[n].nfrag = ah->nfrag;
+
+                       if (issue_char(wsi, ' ') < 0)
+                               return LPR_FAIL;
+                       break;
+
+                       /* skipping arg part of a name we didn't recognize */
+               case WSI_TOKEN_SKIPPING:
+                       lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c);
+
+                       if (c == '\x0d')
+                               ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
+                       break;
+
+               case WSI_TOKEN_SKIPPING_SAW_CR:
+                       lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c);
+                       if (ah->ues != URIES_IDLE)
+                               goto forbid;
+                       if (c == '\x0a') {
+                               ah->parser_state = WSI_TOKEN_NAME_PART;
+#if defined(LWS_WITH_CUSTOM_HEADERS)
+                               ah->unk_pos = 0;
+#endif
+                               ah->lextable_pos = 0;
+                       } else
+                               ah->parser_state = WSI_TOKEN_SKIPPING;
+                       break;
+                       /* we're done, ignore anything else */
+
+               case WSI_PARSING_COMPLETE:
+                       lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c);
+                       break;
+               }
+
+       } while (*len);
+
+       return LPR_OK;
+
+set_parsing_complete:
+       if (ah->ues != URIES_IDLE)
+               goto forbid;
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) {
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION))
+                       wsi->rx_frame_type = /* temp for ws version index */
+                              atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION));
+
+               lwsl_parser("v%02d hdrs done\n", wsi->rx_frame_type);
+       }
+       ah->parser_state = WSI_PARSING_COMPLETE;
+       wsi->hdr_parsing_completed = 1;
+
+       return LPR_OK;
+
+forbid:
+       lwsl_notice(" forbidding on uri sanitation\n");
+       lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+
+       return LPR_FORBIDDEN;
+}
+
similarity index 96%
rename from lib/ranges.c
rename to lib/roles/http/server/ranges.c
index 4d29540..56ba748 100644 (file)
@@ -21,7 +21,7 @@
  *  MA  02110-1301  USA
  */
 
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
 /*
  * RFC7233 examples
@@ -67,11 +67,10 @@ int
 lws_ranges_next(struct lws_range_parsing *rp)
 {
        static const char * const beq = "bytes=";
-       char c;
 
        while (1) {
 
-               c = rp->buf[rp->pos];
+               char c = rp->buf[rp->pos];
 
                switch (rp->state) {
                case LWSRS_SYNTAX:
@@ -121,7 +120,10 @@ lws_ranges_next(struct lws_range_parsing *rp)
                                if (c == ',')
                                        rp->pos++;
 
-                               /* by the end of this, start and end are always valid if the range still is */
+                               /*
+                                * By the end of this, start and end are
+                                * always valid if the range still is
+                                */
 
                                if (!rp->start_valid) { /* eg, -500 */
                                        if (rp->end > rp->extent)
similarity index 78%
rename from lib/rewrite.c
rename to lib/roles/http/server/rewrite.c
index 60a813d..eca443a 100644 (file)
@@ -1,10 +1,12 @@
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
+#if defined(LWS_WITH_HUBBUB)
 
 LWS_EXTERN struct lws_rewrite *
-lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to)
+lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from,
+                  const char *to)
 {
-       struct lws_rewrite *r = lws_malloc(sizeof(*r));
+       struct lws_rewrite *r = lws_malloc(sizeof(*r), "rewrite");
 
        if (!r) {
                lwsl_err("OOM\n");
@@ -37,7 +39,7 @@ LWS_EXTERN int
 lws_rewrite_parse(struct lws_rewrite *r,
                  const unsigned char *in, int in_len)
 {
-       if (hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK)
+       if (r && hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK)
                return -1;
 
        return 0;
@@ -50,3 +52,4 @@ lws_rewrite_destroy(struct lws_rewrite *r)
        lws_free(r);
 }
 
+#endif
diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c
new file mode 100644 (file)
index 0000000..3ecda73
--- /dev/null
@@ -0,0 +1,2974 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+const char * const method_names[] = {
+       "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD",
+#ifdef LWS_WITH_HTTP2
+       ":path",
+#endif
+       };
+
+static const char * const intermediates[] = { "private", "public" };
+
+/*
+ * return 0: all done
+ *        1: nonfatal error
+ *       <0: fatal error
+ *
+ *       REQUIRES CONTEXT LOCK HELD
+ */
+
+#ifndef LWS_NO_SERVER
+int
+_lws_vhost_init_server(const struct lws_context_creation_info *info,
+                      struct lws_vhost *vhost)
+{
+       int n, opt = 1, limit = 1;
+       lws_sockfd_type sockfd;
+       struct lws_vhost *vh;
+       struct lws *wsi;
+       int m = 0, is;
+
+       (void)method_names;
+       (void)opt;
+
+       if (info) {
+               vhost->iface = info->iface;
+               vhost->listen_port = info->port;
+       }
+
+       /* set up our external listening socket we serve on */
+
+       if (vhost->listen_port == CONTEXT_PORT_NO_LISTEN ||
+           vhost->listen_port == CONTEXT_PORT_NO_LISTEN_SERVER)
+               return 0;
+
+       vh = vhost->context->vhost_list;
+       while (vh) {
+               if (vh->listen_port == vhost->listen_port) {
+                       if (((!vhost->iface && !vh->iface) ||
+                           (vhost->iface && vh->iface &&
+                           !strcmp(vhost->iface, vh->iface))) &&
+                          vh->lserv_wsi
+                       ) {
+                               lwsl_notice(" using listen skt from vhost %s\n",
+                                           vh->name);
+                               return 0;
+                       }
+               }
+               vh = vh->vhost_next;
+       }
+
+       if (vhost->iface) {
+               /*
+                * let's check before we do anything else about the disposition
+                * of the interface he wants to bind to...
+                */
+               is = lws_socket_bind(vhost, LWS_SOCK_INVALID, vhost->listen_port,
+                               vhost->iface, 1);
+               lwsl_debug("initial if check says %d\n", is);
+
+               if (is == LWS_ITOSA_BUSY)
+                       /* treat as fatal */
+                       return -1;
+
+deal:
+
+               lws_start_foreach_llp(struct lws_vhost **, pv,
+                                     vhost->context->no_listener_vhost_list) {
+                       if (is >= LWS_ITOSA_USABLE && *pv == vhost) {
+                               /* on the list and shouldn't be: remove it */
+                               lwsl_debug("deferred iface: removing vh %s\n",
+                                               (*pv)->name);
+                               *pv = vhost->no_listener_vhost_list;
+                               vhost->no_listener_vhost_list = NULL;
+                               goto done_list;
+                       }
+                       if (is < LWS_ITOSA_USABLE && *pv == vhost)
+                               goto done_list;
+               } lws_end_foreach_llp(pv, no_listener_vhost_list);
+
+               /* not on the list... */
+
+               if (is < LWS_ITOSA_USABLE) {
+
+                       /* ... but needs to be: so add it */
+
+                       lwsl_debug("deferred iface: adding vh %s\n", vhost->name);
+                       vhost->no_listener_vhost_list =
+                                       vhost->context->no_listener_vhost_list;
+                       vhost->context->no_listener_vhost_list = vhost;
+               }
+
+done_list:
+
+               switch (is) {
+               default:
+                       break;
+               case LWS_ITOSA_NOT_EXIST:
+                       /* can't add it */
+                       if (info) /* first time */
+                               lwsl_err("VH %s: iface %s port %d DOESN'T EXIST\n",
+                                vhost->name, vhost->iface, vhost->listen_port);
+                       return (info->options & LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND) == LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND?
+                               -1 : 1;
+               case LWS_ITOSA_NOT_USABLE:
+                       /* can't add it */
+                       if (info) /* first time */
+                               lwsl_err("VH %s: iface %s port %d NOT USABLE\n",
+                                vhost->name, vhost->iface, vhost->listen_port);
+                       return (info->options & LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND) == LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND?
+                               -1 : 1;
+               }
+       }
+
+       (void)n;
+#if defined(__linux__)
+#ifdef LWS_WITH_UNIX_SOCK
+       /*
+        * A Unix domain sockets cannot be bound for several times, even if we set
+        * the SO_REUSE* options on.
+        * However, fortunately, each thread is able to independently listen when
+        * running on a reasonably new Linux kernel. So we can safely assume
+        * creating just one listening socket for a multi-threaded environment won't
+        * fail in most cases.
+        */
+       if (!LWS_UNIX_SOCK_ENABLED(vhost))
+#endif
+       limit = vhost->context->count_threads;
+#endif
+
+       for (m = 0; m < limit; m++) {
+#ifdef LWS_WITH_UNIX_SOCK
+               if (LWS_UNIX_SOCK_ENABLED(vhost))
+                       sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+               else
+#endif
+#ifdef LWS_WITH_IPV6
+               if (LWS_IPV6_ENABLED(vhost))
+                       sockfd = socket(AF_INET6, SOCK_STREAM, 0);
+               else
+#endif
+                       sockfd = socket(AF_INET, SOCK_STREAM, 0);
+
+               if (sockfd == LWS_SOCK_INVALID) {
+                       lwsl_err("ERROR opening socket\n");
+                       return 1;
+               }
+#if !defined(LWS_WITH_ESP32)
+#if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE)
+               /*
+                * only accept that we are the only listener on the port
+                * https://msdn.microsoft.com/zh-tw/library/
+                *    windows/desktop/ms740621(v=vs.85).aspx
+                *
+                * for lws, to match Linux, we default to exclusive listen
+                */
+               if (!lws_check_opt(vhost->options,
+                               LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) {
+                       if (setsockopt(sockfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+                                      (const void *)&opt, sizeof(opt)) < 0) {
+                               lwsl_err("reuseaddr failed\n");
+                               compatible_close(sockfd);
+                               return -1;
+                       }
+               } else
+#endif
+
+               /*
+                * allow us to restart even if old sockets in TIME_WAIT
+                */
+               if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
+                              (const void *)&opt, sizeof(opt)) < 0) {
+                       lwsl_err("reuseaddr failed\n");
+                       compatible_close(sockfd);
+                       return -1;
+               }
+
+#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY)
+               if (LWS_IPV6_ENABLED(vhost) &&
+                   vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) {
+                       int value = (vhost->options &
+                               LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE) ? 1 : 0;
+                       if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                     (const void*)&value, sizeof(value)) < 0) {
+                               compatible_close(sockfd);
+                               return -1;
+                       }
+               }
+#endif
+
+#if defined(__linux__) && defined(SO_REUSEPORT)
+               /* keep coverity happy */
+#if LWS_MAX_SMP > 1
+               n = 1;
+#else
+               n = lws_check_opt(vhost->options,
+                                 LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE);
+#endif
+               if (n && vhost->context->count_threads > 1)
+                       if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT,
+                                       (const void *)&opt, sizeof(opt)) < 0) {
+                               compatible_close(sockfd);
+                               return -1;
+                       }
+#endif
+#endif
+               lws_plat_set_socket_options(vhost, sockfd, 0);
+
+               is = lws_socket_bind(vhost, sockfd, vhost->listen_port, vhost->iface, 1);
+               if (is == LWS_ITOSA_BUSY) {
+                       /* treat as fatal */
+                       compatible_close(sockfd);
+
+                       return -1;
+               }
+
+               /*
+                * There is a race where the network device may come up and then
+                * go away and fail here.  So correctly handle unexpected failure
+                * here despite we earlier confirmed it.
+                */
+               if (is < 0) {
+                       lwsl_info("%s: lws_socket_bind says %d\n", __func__, is);
+                       compatible_close(sockfd);
+                       goto deal;
+               }
+
+               wsi = lws_zalloc(sizeof(struct lws), "listen wsi");
+               if (wsi == NULL) {
+                       lwsl_err("Out of mem\n");
+                       goto bail;
+               }
+
+#ifdef LWS_WITH_UNIX_SOCK
+               if (!LWS_UNIX_SOCK_ENABLED(vhost))
+#endif
+               {
+                       wsi->unix_skt = 1;
+                       vhost->listen_port = is;
+
+                       lwsl_debug("%s: lws_socket_bind says %d\n", __func__, is);
+               }
+
+               wsi->context = vhost->context;
+               wsi->desc.sockfd = sockfd;
+               lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_listen);
+               wsi->protocol = vhost->protocols;
+               wsi->tsi = m;
+               lws_vhost_bind_wsi(vhost, wsi);
+               wsi->listener = 1;
+
+               if (wsi->context->event_loop_ops->init_vhost_listen_wsi)
+                       wsi->context->event_loop_ops->init_vhost_listen_wsi(wsi);
+
+               if (__insert_wsi_socket_into_fds(vhost->context, wsi)) {
+                       lwsl_notice("inserting wsi socket into fds failed\n");
+                       goto bail;
+               }
+
+               vhost->context->count_wsi_allocated++;
+               vhost->lserv_wsi = wsi;
+
+               n = listen(wsi->desc.sockfd, LWS_SOMAXCONN);
+               if (n < 0) {
+                       lwsl_err("listen failed with error %d\n", LWS_ERRNO);
+                       vhost->lserv_wsi = NULL;
+                       vhost->context->count_wsi_allocated--;
+                       __remove_wsi_socket_from_fds(wsi);
+                       goto bail;
+               }
+       } /* for each thread able to independently listen */
+
+       if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) {
+#ifdef LWS_WITH_UNIX_SOCK
+               if (LWS_UNIX_SOCK_ENABLED(vhost))
+                       lwsl_info(" Listening on \"%s\"\n", vhost->iface);
+               else
+#endif
+                       lwsl_info(" Listening on port %d\n", vhost->listen_port);
+        }
+
+       // info->port = vhost->listen_port;
+
+       return 0;
+
+bail:
+       compatible_close(sockfd);
+
+       return -1;
+}
+#endif
+
+struct lws_vhost *
+lws_select_vhost(struct lws_context *context, int port, const char *servername)
+{
+       struct lws_vhost *vhost = context->vhost_list;
+       const char *p;
+       int n, colon;
+
+       n = (int)strlen(servername);
+       colon = n;
+       p = strchr(servername, ':');
+       if (p)
+               colon = lws_ptr_diff(p, servername);
+
+       /* Priotity 1: first try exact matches */
+
+       while (vhost) {
+               if (port == vhost->listen_port &&
+                   !strncmp(vhost->name, servername, colon)) {
+                       lwsl_info("SNI: Found: %s\n", servername);
+                       return vhost;
+               }
+               vhost = vhost->vhost_next;
+       }
+
+       /*
+        * Priority 2: if no exact matches, try matching *.vhost-name
+        * unintentional matches are possible but resolve to x.com for *.x.com
+        * which is reasonable.  If exact match exists we already chose it and
+        * never reach here.  SSL will still fail it if the cert doesn't allow
+        * *.x.com.
+        */
+       vhost = context->vhost_list;
+       while (vhost) {
+               int m = (int)strlen(vhost->name);
+               if (port && port == vhost->listen_port &&
+                   m <= (colon - 2) &&
+                   servername[colon - m - 1] == '.' &&
+                   !strncmp(vhost->name, servername + colon - m, m)) {
+                       lwsl_info("SNI: Found %s on wildcard: %s\n",
+                                   servername, vhost->name);
+                       return vhost;
+               }
+               vhost = vhost->vhost_next;
+       }
+
+       /* Priority 3: match the first vhost on our port */
+
+       vhost = context->vhost_list;
+       while (vhost) {
+               if (port && port == vhost->listen_port) {
+                       lwsl_info("%s: vhost match to %s based on port %d\n",
+                                       __func__, vhost->name, port);
+                       return vhost;
+               }
+               vhost = vhost->vhost_next;
+       }
+
+       /* no match */
+
+       return NULL;
+}
+
+static const struct lws_mimetype {
+       const char *extension;
+       const char *mimetype;
+} server_mimetypes[] = {
+       { ".html", "text/html" },
+       { ".htm", "text/html" },
+       { ".js", "text/javascript" },
+       { ".css", "text/css" },
+       { ".png", "image/png" },
+       { ".jpg", "image/jpeg" },
+       { ".jpeg", "image/jpeg" },
+       { ".ico", "image/x-icon" },
+       { ".gif", "image/gif" },
+       { ".svg", "image/svg+xml" },
+       { ".ttf", "application/x-font-ttf" },
+       { ".otf", "application/font-woff" },
+       { ".woff", "application/font-woff" },
+       { ".woff2", "application/font-woff2" },
+       { ".gz", "application/gzip" },
+       { ".txt", "text/plain" },
+       { ".xml", "application/xml" },
+       { ".json", "application/json" },
+};
+
+LWS_VISIBLE LWS_EXTERN const char *
+lws_get_mimetype(const char *file, const struct lws_http_mount *m)
+{
+       const struct lws_protocol_vhost_options *pvo;
+       size_t n = strlen(file), len, i;
+       const char *fallback_mimetype = NULL;
+       const struct lws_mimetype *mt;
+
+       /* prioritize user-defined mimetypes */
+       for (pvo = m ? m->extra_mimetypes : NULL; pvo; pvo = pvo->next) {
+               /* ie, match anything */
+               if (!fallback_mimetype && pvo->name[0] == '*') {
+                       fallback_mimetype = pvo->value;
+                       continue;
+               }
+
+               len = strlen(pvo->name);
+               if (n > len && !strcasecmp(&file[n - len], pvo->name)) {
+                       lwsl_info("%s: match to user mimetype: %s\n", __func__, pvo->value);
+                       return pvo->value;
+               }
+       }
+
+       /* fallback to server-defined mimetypes */
+       for (i = 0; i < LWS_ARRAY_SIZE(server_mimetypes); ++i) {
+               mt = &server_mimetypes[i];
+
+               len = strlen(mt->extension);
+               if (n > len && !strcasecmp(&file[n - len], mt->extension)) {
+                       lwsl_info("%s: match to server mimetype: %s\n", __func__, mt->mimetype);
+                       return mt->mimetype;
+               }
+       }
+
+       /* fallback to '*' if defined */
+       if (fallback_mimetype) {
+               lwsl_info("%s: match to any mimetype: %s\n", __func__, fallback_mimetype);
+               return fallback_mimetype;
+       }
+
+       return NULL;
+}
+
+static lws_fop_flags_t
+lws_vfs_prepare_flags(struct lws *wsi)
+{
+       lws_fop_flags_t f = 0;
+
+       if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING))
+               return f;
+
+       if (strstr(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING),
+                  "gzip")) {
+               lwsl_info("client indicates GZIP is acceptable\n");
+               f |= LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP;
+       }
+
+       return f;
+}
+
+#if !defined(LWS_AMAZON_RTOS)
+static int
+lws_http_serve(struct lws *wsi, char *uri, const char *origin,
+              const struct lws_http_mount *m)
+{
+       const struct lws_protocol_vhost_options *pvo = m->interpret;
+       struct lws_process_html_args args;
+       const char *mimetype;
+#if !defined(_WIN32_WCE)
+       const struct lws_plat_file_ops *fops;
+       const char *vpath;
+       lws_fop_flags_t fflags = LWS_O_RDONLY;
+#if defined(WIN32) && defined(LWS_HAVE__STAT32I64)
+       struct _stat32i64 st;
+#else
+       struct stat st;
+#endif
+       int spin = 0;
+#endif
+       char path[256], sym[2048];
+       unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p;
+       unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE;
+#if !defined(WIN32) && !defined(LWS_WITH_ESP32)
+       size_t len;
+#endif
+       int n;
+
+       wsi->handling_404 = 0;
+       if (!wsi->vhost)
+               return -1;
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (wsi->vhost->http.error_document_404 &&
+           !strcmp(uri, wsi->vhost->http.error_document_404))
+               wsi->handling_404 = 1;
+#endif
+
+       lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri);
+
+#if !defined(_WIN32_WCE)
+
+       fflags |= lws_vfs_prepare_flags(wsi);
+
+       do {
+               spin++;
+               fops = lws_vfs_select_fops(wsi->context->fops, path, &vpath);
+
+               if (wsi->http.fop_fd)
+                       lws_vfs_file_close(&wsi->http.fop_fd);
+
+               wsi->http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
+                                                       path, vpath, &fflags);
+               if (!wsi->http.fop_fd) {
+                       lwsl_info("%s: Unable to open '%s': errno %d\n",
+                                 __func__, path, errno);
+
+                       return 1;
+               }
+
+               /* if it can't be statted, don't try */
+               if (fflags & LWS_FOP_FLAG_VIRTUAL)
+                       break;
+#if defined(LWS_WITH_ESP32)
+               break;
+#endif
+#if !defined(WIN32)
+               if (fstat(wsi->http.fop_fd->fd, &st)) {
+                       lwsl_info("unable to stat %s\n", path);
+                       goto notfound;
+               }
+#else
+#if defined(LWS_HAVE__STAT32I64)
+               if (_stat32i64(path, &st)) {
+                       lwsl_info("unable to stat %s\n", path);
+                       goto notfound;
+               }
+#else
+               if (stat(path, &st)) {
+                       lwsl_info("unable to stat %s\n", path);
+                       goto notfound;
+               }
+#endif
+#endif
+
+               wsi->http.fop_fd->mod_time = (uint32_t)st.st_mtime;
+               fflags |= LWS_FOP_FLAG_MOD_TIME_VALID;
+
+#if !defined(WIN32) && !defined(LWS_WITH_ESP32)
+               if ((S_IFMT & st.st_mode) == S_IFLNK) {
+                       len = readlink(path, sym, sizeof(sym) - 1);
+                       if (len) {
+                               lwsl_err("Failed to read link %s\n", path);
+                               goto notfound;
+                       }
+                       sym[len] = '\0';
+                       lwsl_debug("symlink %s -> %s\n", path, sym);
+                       lws_snprintf(path, sizeof(path) - 1, "%s", sym);
+               }
+#endif
+               if ((S_IFMT & st.st_mode) == S_IFDIR) {
+                       lwsl_debug("default filename append to dir\n");
+                       lws_snprintf(path, sizeof(path) - 1, "%s/%s/index.html",
+                                origin, uri);
+               }
+
+       } while ((S_IFMT & st.st_mode) != S_IFREG && spin < 5);
+
+       if (spin == 5)
+               lwsl_err("symlink loop %s \n", path);
+
+       n = sprintf(sym, "%08llX%08lX",
+                   (unsigned long long)lws_vfs_get_length(wsi->http.fop_fd),
+                   (unsigned long)lws_vfs_get_mod_time(wsi->http.fop_fd));
+
+       /* disable ranges if IF_RANGE token invalid */
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_RANGE))
+               if (strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_RANGE)))
+                       /* differs - defeat Range: */
+                       wsi->http.ah->frag_index[WSI_TOKEN_HTTP_RANGE] = 0;
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH)) {
+               /*
+                * he thinks he has some version of it already,
+                * check if the tag matches
+                */
+               if (!strcmp(sym, lws_hdr_simple_ptr(wsi,
+                                       WSI_TOKEN_HTTP_IF_NONE_MATCH))) {
+
+                       char cache_control[50], *cc = "no-store";
+                       int cclen = 8;
+
+                       lwsl_debug("%s: ETAG match %s %s\n", __func__,
+                                  uri, origin);
+
+                       /* we don't need to send the payload */
+                       if (lws_add_http_header_status(wsi,
+                                       HTTP_STATUS_NOT_MODIFIED, &p, end)) {
+                               lwsl_err("%s: failed adding not modified\n",
+                                               __func__);
+                               return -1;
+                       }
+
+                       if (lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_ETAG,
+                                       (unsigned char *)sym, n, &p, end))
+                               return -1;
+
+                       /* but we still need to send cache control... */
+
+                       if (m->cache_max_age && m->cache_reusable) {
+                               if (!m->cache_revalidate) {
+                                       cc = cache_control;
+                                       cclen = sprintf(cache_control,
+                                               "%s, max-age=%u",
+                                               intermediates[wsi->cache_intermediaries],
+                                               m->cache_max_age);
+                               } else {
+                                       cc = cache_control;
+                                        cclen = sprintf(cache_control,
+                                               "must-revalidate, %s, max-age=%u",
+                                                intermediates[wsi->cache_intermediaries],
+                                                m->cache_max_age);
+                               }
+                       }
+
+                       if (lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_CACHE_CONTROL,
+                                       (unsigned char *)cc, cclen, &p, end))
+                               return -1;
+
+                       if (lws_finalize_http_header(wsi, &p, end))
+                               return -1;
+
+                       n = lws_write(wsi, start, p - start,
+                                     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));
+                               return -1;
+                       }
+
+                       lws_vfs_file_close(&wsi->http.fop_fd);
+
+                       if (lws_http_transaction_completed(wsi))
+                               return -1;
+
+                       return 0;
+               }
+       }
+
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ETAG,
+                       (unsigned char *)sym, n, &p, end))
+               return -1;
+#endif
+
+       mimetype = lws_get_mimetype(path, m);
+       if (!mimetype) {
+               lwsl_info("unknown mimetype for %s\n", path);
+               if (lws_return_http_status(wsi,
+                               HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL) ||
+                   lws_http_transaction_completed(wsi))
+                       return -1;
+
+               return 0;
+       }
+       if (!mimetype[0])
+               lwsl_debug("sending no mimetype for %s\n", path);
+
+       wsi->sending_chunked = 0;
+       wsi->interpreting = 0;
+
+       /*
+        * check if this is in the list of file suffixes to be interpreted by
+        * a protocol
+        */
+       while (pvo) {
+               n = (int)strlen(path);
+               if (n > (int)strlen(pvo->name) &&
+                   !strcmp(&path[n - strlen(pvo->name)], pvo->name)) {
+                       wsi->interpreting = 1;
+                       if (!wsi->http2_substream)
+                               wsi->sending_chunked = 1;
+
+                       wsi->protocol_interpret_idx =
+                               lws_vhost_name_to_protocol(wsi->vhost,
+                                                          pvo->value) -
+                               &lws_get_vhost(wsi)->protocols[0];
+
+                       lwsl_debug("want %s interpreted by %s (pcol is %s)\n", path,
+                                   wsi->vhost->protocols[
+                                            (int)wsi->protocol_interpret_idx].name,
+                                            wsi->protocol->name);
+                       if (lws_bind_protocol(wsi, &wsi->vhost->protocols[
+                                 (int)wsi->protocol_interpret_idx], __func__))
+                               return -1;
+
+                       if (lws_ensure_user_space(wsi))
+                               return -1;
+                       break;
+               }
+               pvo = pvo->next;
+       }
+
+       if (wsi->sending_chunked) {
+               if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+                               (unsigned char *)"chunked", 7,
+                               &p, end))
+                       return -1;
+       }
+
+       if (m->protocol) {
+               const struct lws_protocols *pp = lws_vhost_name_to_protocol(
+                                                      wsi->vhost, m->protocol);
+
+               if (lws_bind_protocol(wsi, pp, __func__))
+                       return -1;
+               args.p = (char *)p;
+               args.max_len = lws_ptr_diff(end, p);
+               if (pp->callback(wsi, LWS_CALLBACK_ADD_HEADERS,
+                                         wsi->user_space, &args, 0))
+                       return -1;
+               p = (unsigned char *)args.p;
+       }
+
+       *p = '\0';
+       n = lws_serve_http_file(wsi, path, mimetype, (char *)start,
+                               lws_ptr_diff(p, start));
+
+       if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi)))
+               return -1; /* error or can't reuse connection: close the socket */
+
+       return 0;
+
+notfound:
+
+       return 1;
+}
+#endif
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+const struct lws_http_mount *
+lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len)
+{
+       const struct lws_http_mount *hm, *hit = NULL;
+       int best = 0;
+
+       hm = wsi->vhost->http.mount_list;
+       while (hm) {
+               if (uri_len >= hm->mountpoint_len &&
+                   !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) &&
+                   (uri_ptr[hm->mountpoint_len] == '\0' ||
+                    uri_ptr[hm->mountpoint_len] == '/' ||
+                    hm->mountpoint_len == 1)
+                   ) {
+                       if (hm->origin_protocol == LWSMPRO_CALLBACK ||
+                           ((hm->origin_protocol == LWSMPRO_CGI ||
+                            lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) ||
+                            lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) ||
+                            lws_hdr_total_length(wsi, WSI_TOKEN_HEAD_URI) ||
+                            (wsi->http2_substream &&
+                               lws_hdr_total_length(wsi,
+                                               WSI_TOKEN_HTTP_COLON_PATH)) ||
+                            hm->protocol) &&
+                           hm->mountpoint_len > best)) {
+                               best = hm->mountpoint_len;
+                               hit = hm;
+                       }
+               }
+               hm = hm->mount_next;
+       }
+
+       return hit;
+}
+#endif
+
+#if !defined(LWS_WITH_ESP32)
+static int
+lws_find_string_in_file(const char *filename, const char *string, int stringlen)
+{
+       char buf[128];
+       int fd, match = 0, pos = 0, n = 0, hit = 0;
+
+       fd = lws_open(filename, O_RDONLY);
+       if (fd < 0) {
+               lwsl_err("can't open auth file: %s\n", filename);
+               return 0;
+       }
+
+       while (1) {
+               if (pos == n) {
+                       n = read(fd, buf, sizeof(buf));
+                       if (n <= 0) {
+                               if (match == stringlen)
+                                       hit = 1;
+                               break;
+                       }
+                       pos = 0;
+               }
+
+               if (match == stringlen) {
+                       if (buf[pos] == '\r' || buf[pos] == '\n') {
+                               hit = 1;
+                               break;
+                       }
+                       match = 0;
+               }
+
+               if (buf[pos] == string[match])
+                       match++;
+               else
+                       match = 0;
+
+               pos++;
+       }
+
+       close(fd);
+
+       return hit;
+}
+#endif
+
+int
+lws_unauthorised_basic_auth(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       unsigned char *start = pt->serv_buf + LWS_PRE,
+                     *p = start, *end = p + 2048;
+       char buf[64];
+       int n;
+
+       /* no auth... tell him it is required */
+
+       if (lws_add_http_header_status(wsi, HTTP_STATUS_UNAUTHORIZED, &p, end))
+               return -1;
+
+       n = lws_snprintf(buf, sizeof(buf), "Basic realm=\"lwsws\"");
+       if (lws_add_http_header_by_token(wsi,
+                       WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
+                       (unsigned char *)buf, n, &p, end))
+               return -1;
+
+       if (lws_add_http_header_content_length(wsi, 0, &p, end))
+               return -1;
+
+       if (lws_finalize_http_header(wsi, &p, end))
+               return -1;
+
+       n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS |
+                                            LWS_WRITE_H2_STREAM_END);
+       if (n < 0)
+               return -1;
+
+       return lws_http_transaction_completed(wsi);
+
+}
+
+int lws_clean_url(char *p)
+{
+       if (p[0] == 'h' && p[1] == 't' && p[2] == 't' && p[3] == 'p') {
+               p += 4;
+               if (*p == 's')
+               p++;
+               if (*p == ':') {
+                       p++;
+                       if (*p == '/')
+                       p++;
+               }
+       }
+
+       while (*p) {
+               if (p[0] == '/' && p[1] == '/') {
+                       char *p1 = p;
+                       while (*p1) {
+                               *p1 = p1[1];
+                               p1++;
+                       }
+                       continue;
+               }
+               p++;
+       }
+
+       return 0;
+}
+
+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,
+#ifdef LWS_WITH_HTTP2
+       WSI_TOKEN_HTTP_COLON_PATH,
+#endif
+};
+
+int
+lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len)
+{
+       int n, count = 0;
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(methods); n++)
+               if (lws_hdr_total_length(wsi, methods[n]))
+                       count++;
+       if (!count) {
+               lwsl_warn("Missing URI in HTTP request\n");
+               return -1;
+       }
+
+       if (count != 1 &&
+           !((wsi->http2_substream || wsi->h2_stream_carries_ws) &&
+             lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH))) {
+               lwsl_warn("multiple methods?\n");
+               return -1;
+       }
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(methods); n++)
+               if (lws_hdr_total_length(wsi, methods[n])) {
+                       *puri_ptr = lws_hdr_simple_ptr(wsi, methods[n]);
+                       *puri_len = lws_hdr_total_length(wsi, methods[n]);
+                       return n;
+               }
+
+       return -1;
+}
+
+enum lws_check_basic_auth_results
+lws_check_basic_auth(struct lws *wsi, const char *basic_auth_login_file)
+{
+       char b64[160], plain[(sizeof(b64) * 3) / 4], *pcolon;
+       int m, ml, fi;
+
+       if (!basic_auth_login_file)
+               return LCBA_CONTINUE;
+
+       /* Did he send auth? */
+       ml = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
+       if (!ml)
+               return LCBA_FAILED_AUTH;
+
+       /* Disallow fragmentation monkey business */
+
+       fi = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_AUTHORIZATION];
+       if (wsi->http.ah->frags[fi].nfrag) {
+               lwsl_err("fragmented basic auth header not allowed\n");
+               return LCBA_FAILED_AUTH;
+       }
+
+       m = lws_hdr_copy(wsi, b64, sizeof(b64),
+                        WSI_TOKEN_HTTP_AUTHORIZATION);
+       if (m < 7) {
+               lwsl_err("b64 auth too long\n");
+               return LCBA_END_TRANSACTION;
+       }
+
+       b64[5] = '\0';
+       if (strcasecmp(b64, "Basic")) {
+               lwsl_err("auth missing basic: %s\n", b64);
+               return LCBA_END_TRANSACTION;
+       }
+
+       /* It'll be like Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l */
+
+       m = lws_b64_decode_string(b64 + 6, plain, sizeof(plain) - 1);
+       if (m < 0) {
+               lwsl_err("plain auth too long\n");
+               return LCBA_END_TRANSACTION;
+       }
+
+       plain[m] = '\0';
+       pcolon = strchr(plain, ':');
+       if (!pcolon) {
+               lwsl_err("basic auth format broken\n");
+               return LCBA_END_TRANSACTION;
+       }
+       if (!lws_find_string_in_file(basic_auth_login_file, plain, m)) {
+               lwsl_err("basic auth lookup failed\n");
+               return LCBA_FAILED_AUTH;
+       }
+
+       /*
+        * Rewrite WSI_TOKEN_HTTP_AUTHORIZATION so it is just the
+        * authorized username
+        */
+
+       *pcolon = '\0';
+       wsi->http.ah->frags[fi].len = lws_ptr_diff(pcolon, plain);
+       pcolon = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
+       strncpy(pcolon, plain, ml - 1);
+       pcolon[ml - 1] = '\0';
+       lwsl_info("%s: basic auth accepted for %s\n", __func__,
+                lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_AUTHORIZATION));
+
+       return LCBA_CONTINUE;
+}
+
+#if defined(LWS_WITH_HTTP_PROXY)
+/*
+ * Set up an onward http proxy connection according to the mount this
+ * uri falls under.  Notice this can also be starting the proxying of what was
+ * originally an incoming h1 upgrade, or an h2 ws "upgrade".
+ */
+int
+lws_http_proxy_start(struct lws *wsi, const struct lws_http_mount *hit,
+                    char *uri_ptr, char ws)
+{
+       char ads[96], rpath[256], host[96], *pcolon, *pslash, unix_skt = 0;
+       struct lws_client_connect_info i;
+       struct lws *cwsi;
+       int n, na;
+
+       if (ws)
+               /*
+                * Neither our inbound ws upgrade request side, nor our onward
+                * ws client connection on our side can bind to the actual
+                * protocol that only the remote inbound side and the remote
+                * onward side understand.
+                *
+                * Instead these are both bound to our built-in "lws-ws-proxy"
+                * protocol, which understands how to proxy between the two
+                * sides.
+                *
+                * We bind the parent, inbound part here and our side of the
+                * onward client connection is bound to the same handler using
+                * the .local_protocol_name.
+                */
+               lws_bind_protocol(wsi, &lws_ws_proxy, __func__);
+
+       memset(&i, 0, sizeof(i));
+       i.context = lws_get_context(wsi);
+
+       if (hit->origin[0] == '+')
+               unix_skt = 1;
+
+       pcolon = strchr(hit->origin, ':');
+       pslash = strchr(hit->origin, '/');
+       if (!pslash) {
+               lwsl_err("Proxy mount origin '%s' must have /\n", hit->origin);
+               return -1;
+       }
+
+       if (unix_skt) {
+               if (!pcolon) {
+                       lwsl_err("Proxy mount origin for unix skt must "
+                                "have address delimited by :\n");
+
+                       return -1;
+               }
+               n = lws_ptr_diff(pcolon, hit->origin);
+               pslash = pcolon;
+       } else {
+               if (pcolon > pslash)
+                       pcolon = NULL;
+
+               if (pcolon)
+                       n = (int)(pcolon - hit->origin);
+               else
+                       n = (int)(pslash - hit->origin);
+
+               if (n >= (int)sizeof(ads) - 2)
+                       n = sizeof(ads) - 2;
+       }
+
+       memcpy(ads, hit->origin, n);
+       ads[n] = '\0';
+
+       i.address = ads;
+       i.port = 80;
+       if (hit->origin_protocol == LWSMPRO_HTTPS) {
+               i.port = 443;
+               i.ssl_connection = 1;
+       }
+       if (pcolon)
+               i.port = atoi(pcolon + 1);
+
+       n = lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s",
+                        pslash + 1, uri_ptr + hit->mountpoint_len) - 2;
+       lws_clean_url(rpath);
+       na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS);
+       if (na) {
+               char *p = rpath + n;
+
+               if (na >= (int)sizeof(rpath) - n - 2) {
+                       lwsl_info("%s: query string %d longer "
+                                 "than we can handle\n", __func__,
+                                 na);
+
+                       return -1;
+               }
+
+               *p++ = '?';
+               if (lws_hdr_copy(wsi, p,
+                            (int)(&rpath[sizeof(rpath) - 1] - p),
+                            WSI_TOKEN_HTTP_URI_ARGS) > 0)
+                       while (na--) {
+                               if (*p == '\0')
+                                       *p = '&';
+                               p++;
+                       }
+               *p = '\0';
+       }
+
+       i.path = rpath;
+
+       /* incoming may be h1 or h2... if he sends h1 HOST, use that
+        * directly, otherwise we must convert h2 :authority to h1
+        * host */
+
+       i.host = NULL;
+       n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY);
+       if (n > 0)
+               i.host = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_AUTHORITY);
+       else {
+               n = lws_hdr_total_length(wsi, WSI_TOKEN_HOST);
+               if (n > 0) {
+                       i.host = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST);
+               }
+       }
+
+#if 0
+       if (i.address[0] != '+' ||
+           !lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST))
+               i.host = i.address;
+       else
+               i.host = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST);
+#endif
+       i.origin = NULL;
+       if (!ws) {
+               if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_POST_URI)
+#if defined(LWS_WITH_HTTP2)
+                                                               || (
+                       lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD) &&
+                       !strcmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD), "post")
+                       )
+#endif
+               )
+                       i.method = "POST";
+               else
+                       i.method = "GET";
+       }
+
+       if (i.host)
+               lws_snprintf(host, sizeof(host), "%s:%u", i.host,
+                                       wsi->vhost->listen_port);
+       else
+               lws_snprintf(host, sizeof(host), "%s:%d", i.address, i.port);
+
+       i.host = host;
+
+       i.alpn = "http/1.1";
+       i.parent_wsi = wsi;
+       i.pwsi = &cwsi;
+       i.protocol = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
+       if (ws)
+               i.local_protocol_name = "lws-ws-proxy";
+
+//     i.uri_replace_from = hit->origin;
+//     i.uri_replace_to = hit->mountpoint;
+
+       lwsl_info("proxying to %s port %d url %s, ssl %d, from %s, to %s\n",
+                  i.address, i.port, i.path, i.ssl_connection,
+                  i.uri_replace_from, i.uri_replace_to);
+
+       if (!lws_client_connect_via_info(&i)) {
+               lwsl_err("proxy connect fail\n");
+
+               /*
+                * ... we can't do the proxy action, but we can
+                * cleanly return him a 503 and a description
+                */
+
+               lws_return_http_status(wsi,
+                       HTTP_STATUS_SERVICE_UNAVAILABLE,
+                       "<h1>Service Temporarily Unavailable</h1>"
+                       "The server is temporarily unable to service "
+                       "your request due to maintenance downtime or "
+                       "capacity problems. Please try again later.");
+
+               return 1;
+       }
+
+       lwsl_info("%s: setting proxy clientside on %p (parent %p)\n",
+                 __func__, cwsi, lws_get_parent(cwsi));
+
+       cwsi->http.proxy_clientside = 1;
+       if (ws) {
+               wsi->proxied_ws_parent = 1;
+               cwsi->h1_ws_proxied = 1;
+               if (i.protocol) {
+                       lwsl_debug("%s: (requesting '%s')\n", __func__, i.protocol);
+               }
+       }
+
+       return 0;
+}
+#endif
+
+static const char * const oprot[] = {
+       "http://", "https://"
+};
+
+int
+lws_http_action(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       const struct lws_http_mount *hit = NULL;
+       enum http_version request_version;
+       struct lws_process_html_args args;
+       enum http_conn_type conn_type;
+       char content_length_str[32];
+       char http_version_str[12];
+       char *uri_ptr = NULL, *s;
+       int uri_len = 0, meth, m;
+       char http_conn_str[25];
+       int http_version_len;
+       unsigned int n;
+
+       meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len);
+       if (meth < 0 || meth >= (int)LWS_ARRAY_SIZE(method_names))
+               goto bail_nuke_ah;
+
+       /* we insist on absolute paths */
+
+       if (!uri_ptr || uri_ptr[0] != '/') {
+               lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+
+               goto bail_nuke_ah;
+       }
+
+       lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth],
+                 meth, uri_ptr);
+
+       if (wsi->role_ops && wsi->role_ops->check_upgrades)
+               switch (wsi->role_ops->check_upgrades(wsi)) {
+               case LWS_UPG_RET_DONE:
+                       return 0;
+               case LWS_UPG_RET_CONTINUE:
+                       break;
+               case LWS_UPG_RET_BAIL:
+                       goto bail_nuke_ah;
+               }
+
+       if (lws_ensure_user_space(wsi))
+               goto bail_nuke_ah;
+
+       /* HTTP header had a content length? */
+
+       wsi->http.rx_content_length = 0;
+       wsi->http.content_length_explicitly_zero = 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->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) > 0) {
+               wsi->http.rx_content_remain = wsi->http.rx_content_length =
+                                               atoll(content_length_str);
+               if (!wsi->http.rx_content_length) {
+                       wsi->http.content_length_explicitly_zero = 1;
+                       lwsl_debug("%s: explicit 0 content-length\n", __func__);
+               }
+       }
+
+       if (wsi->http2_substream) {
+               wsi->http.request_version = HTTP_VERSION_2;
+       } else {
+               /* http_version? Default to 1.0, override with token: */
+               request_version = HTTP_VERSION_1_0;
+
+               /* Works for single digit HTTP versions. : */
+               http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP);
+               if (http_version_len > 7 &&
+                   lws_hdr_copy(wsi, http_version_str,
+                                sizeof(http_version_str) - 1,
+                                WSI_TOKEN_HTTP) > 0 &&
+                   http_version_str[5] == '1' && http_version_str[7] == '1')
+                       request_version = HTTP_VERSION_1_1;
+
+               wsi->http.request_version = request_version;
+
+               /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */
+               if (request_version == HTTP_VERSION_1_1)
+                       conn_type = HTTP_CONNECTION_KEEP_ALIVE;
+               else
+                       conn_type = HTTP_CONNECTION_CLOSE;
+
+               /* Override default if http "Connection:" header: */
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION) &&
+                   lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1,
+                                WSI_TOKEN_CONNECTION) > 0) {
+                       http_conn_str[sizeof(http_conn_str) - 1] = '\0';
+                       if (!strcasecmp(http_conn_str, "keep-alive"))
+                               conn_type = HTTP_CONNECTION_KEEP_ALIVE;
+                       else
+                               if (!strcasecmp(http_conn_str, "close"))
+                                       conn_type = HTTP_CONNECTION_CLOSE;
+               }
+               wsi->http.conn_type = conn_type;
+       }
+
+       n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION,
+                                   wsi->user_space, uri_ptr, uri_len);
+       if (n) {
+               lwsl_info("LWS_CALLBACK_HTTP closing\n");
+
+               return 1;
+       }
+       /*
+        * if there is content supposed to be coming,
+        * put a timeout on it having arrived
+        */
+       lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
+                       wsi->context->timeout_secs);
+#ifdef LWS_WITH_TLS
+       if (wsi->tls.redirect_to_https) {
+               /*
+                * we accepted http:// only so we could redirect to
+                * https://, so issue the redirect.  Create the redirection
+                * URI from the host: header and ignore the path part
+                */
+               unsigned char *start = pt->serv_buf + LWS_PRE, *p = start,
+                             *end = p + wsi->context->pt_serv_buf_size - LWS_PRE;
+
+               n = lws_hdr_total_length(wsi, WSI_TOKEN_HOST);
+               if (!n || n > 128)
+                       goto bail_nuke_ah;
+
+               p += lws_snprintf((char *)p, lws_ptr_diff(end, p), "https://");
+               memcpy(p, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), n);
+               p += n;
+               *p++ = '/';
+               *p = '\0';
+               n = lws_ptr_diff(p, start);
+
+               p += LWS_PRE;
+               n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
+                                     start, n, &p, end);
+               if ((int)n < 0)
+                       goto bail_nuke_ah;
+
+               return lws_http_transaction_completed(wsi);
+       }
+#endif
+
+#ifdef LWS_WITH_ACCESS_LOG
+       lws_prepare_access_log_info(wsi, uri_ptr, uri_len, meth);
+#endif
+
+       /* can we serve it from the mount list? */
+
+       hit = lws_find_mount(wsi, uri_ptr, uri_len);
+       if (!hit) {
+               /* deferred cleanup and reset to protocols[0] */
+
+               lwsl_info("no hit\n");
+
+               if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0],
+                                     "no mount hit"))
+                       return 1;
+
+               lwsi_set_state(wsi, LRS_DOING_TRANSACTION);
+
+               m = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
+                                   wsi->user_space, uri_ptr, uri_len);
+
+               goto after;
+       }
+
+       s = uri_ptr + hit->mountpoint_len;
+
+       /*
+        * if we have a mountpoint like https://xxx.com/yyy
+        * there is an implied / at the end for our purposes since
+        * we can only mount on a "directory".
+        *
+        * But if we just go with that, the browser cannot understand
+        * that he is actually looking down one "directory level", so
+        * even though we give him /yyy/abc.html he acts like the
+        * current directory level is /.  So relative urls like "x.png"
+        * wrongly look outside the mountpoint.
+        *
+        * Therefore if we didn't come in on a url with an explicit
+        * / at the end, we must redirect to add it so the browser
+        * understands he is one "directory level" down.
+        */
+       if ((hit->mountpoint_len > 1 ||
+            (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
+             hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
+           (*s != '/' ||
+            (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
+             hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
+           (hit->origin_protocol != LWSMPRO_CGI &&
+            hit->origin_protocol != LWSMPRO_CALLBACK)) {
+               unsigned char *start = pt->serv_buf + LWS_PRE, *p = start,
+                             *end = p + wsi->context->pt_serv_buf_size -
+                                    LWS_PRE - 512;
+
+               lwsl_info("Doing 301 '%s' org %s\n", s, hit->origin);
+
+               /* > 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 {
+                       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,
+                                     end, n, &p, end);
+               if ((int)n < 0)
+                       goto bail_nuke_ah;
+
+               return lws_http_transaction_completed(wsi);
+       }
+
+       /* basic auth? */
+
+       switch(lws_check_basic_auth(wsi, hit->basic_auth_login_file)) {
+       case LCBA_CONTINUE:
+               break;
+       case LCBA_FAILED_AUTH:
+               return lws_unauthorised_basic_auth(wsi);
+       case LCBA_END_TRANSACTION:
+               lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+               return lws_http_transaction_completed(wsi);
+       }
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       /*
+        * The mount is a reverse proxy?
+        */
+
+       // if (hit)
+       // lwsl_notice("%s: origin_protocol: %d\n", __func__, hit->origin_protocol);
+       //else
+       //      lwsl_notice("%s: no hit\n", __func__);
+
+       if (hit->origin_protocol == LWSMPRO_HTTPS ||
+           hit->origin_protocol == LWSMPRO_HTTP) {
+               n = lws_http_proxy_start(wsi, hit, uri_ptr, 0);
+               // lwsl_notice("proxy start says %d\n", n);
+               if (n)
+                       return n;
+
+               goto deal_body;
+       }
+#endif
+
+       /*
+        * A particular protocol callback is mounted here?
+        *
+        * For the duration of this http transaction, bind us to the
+        * associated protocol
+        */
+       if (hit->origin_protocol == LWSMPRO_CALLBACK || hit->protocol) {
+               const struct lws_protocols *pp;
+               const char *name = hit->origin;
+               if (hit->protocol)
+                       name = hit->protocol;
+
+               pp = lws_vhost_name_to_protocol(wsi->vhost, name);
+               if (!pp) {
+                       n = -1;
+                       lwsl_err("Unable to find plugin '%s'\n",
+                                hit->origin);
+                       return 1;
+               }
+
+               if (lws_bind_protocol(wsi, pp, "http action CALLBACK bind"))
+                       return 1;
+
+               lwsl_notice("%s: %s, checking access rights for mask 0x%x\n",
+                               __func__, hit->origin, hit->auth_mask);
+
+               args.p = uri_ptr;
+               args.len = uri_len;
+               args.max_len = hit->auth_mask;
+               args.final = 0; /* used to signal callback dealt with it */
+               args.chunked = 0;
+
+               n = wsi->protocol->callback(wsi,
+                                           LWS_CALLBACK_CHECK_ACCESS_RIGHTS,
+                                           wsi->user_space, &args, 0);
+               if (n) {
+                       lws_return_http_status(wsi, HTTP_STATUS_UNAUTHORIZED,
+                                              NULL);
+                       goto bail_nuke_ah;
+               }
+               if (args.final) /* callback completely handled it well */
+                       return 0;
+
+               if (hit->cgienv && wsi->protocol->callback(wsi,
+                               LWS_CALLBACK_HTTP_PMO,
+                               wsi->user_space, (void *)hit->cgienv, 0))
+                       return 1;
+
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) {
+                       m = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
+                                           wsi->user_space,
+                                           uri_ptr + hit->mountpoint_len,
+                                           uri_len - hit->mountpoint_len);
+                       goto after;
+               }
+       }
+
+#ifdef LWS_WITH_CGI
+       /* did we hit something with a cgi:// origin? */
+       if (hit->origin_protocol == LWSMPRO_CGI) {
+               const char *cmd[] = {
+                       NULL, /* replace with cgi path */
+                       NULL
+               };
+
+               lwsl_debug("%s: cgi\n", __func__);
+               cmd[0] = hit->origin;
+
+               n = 5;
+               if (hit->cgi_timeout)
+                       n = hit->cgi_timeout;
+
+               n = lws_cgi(wsi, cmd, hit->mountpoint_len, n,
+                           hit->cgienv);
+               if (n) {
+                       lwsl_err("%s: cgi failed\n", __func__);
+                       return -1;
+               }
+
+               goto deal_body;
+       }
+#endif
+
+       n = uri_len - lws_ptr_diff(s, uri_ptr); // (int)strlen(s);
+       if (s[0] == '\0' || (n == 1 && s[n - 1] == '/'))
+               s = (char *)hit->def;
+       if (!s)
+               s = "index.html";
+
+       wsi->cache_secs = hit->cache_max_age;
+       wsi->cache_reuse = hit->cache_reusable;
+       wsi->cache_revalidate = hit->cache_revalidate;
+       wsi->cache_intermediaries = hit->cache_intermediaries;
+
+       m = 1;
+#if !defined(LWS_AMAZON_RTOS)
+       if (hit->origin_protocol == LWSMPRO_FILE)
+               m = lws_http_serve(wsi, s, hit->origin, hit);
+#endif
+
+       if (m > 0) {
+               /*
+                * lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
+                */
+               if (hit->protocol) {
+                       const struct lws_protocols *pp =
+                                       lws_vhost_name_to_protocol(
+                                               wsi->vhost, hit->protocol);
+
+                       lwsi_set_state(wsi, LRS_DOING_TRANSACTION);
+
+                       if (lws_bind_protocol(wsi, pp, "http_action HTTP"))
+                               return 1;
+
+                       m = pp->callback(wsi, LWS_CALLBACK_HTTP,
+                                        wsi->user_space,
+                                        uri_ptr + hit->mountpoint_len,
+                                        uri_len - hit->mountpoint_len);
+               } else
+                       m = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
+                                   wsi->user_space, uri_ptr, uri_len);
+       }
+
+after:
+       if (m) {
+               lwsl_info("LWS_CALLBACK_HTTP closing\n");
+
+               return 1;
+       }
+
+#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY)
+deal_body:
+#endif
+       /*
+        * If we're not issuing a file, check for content_length or
+        * HTTP keep-alive. No keep-alive header allocation for
+        * ISSUING_FILE, as this uses HTTP/1.0.
+        *
+        * In any case, return 0 and let lws_read decide how to
+        * proceed based on state
+        */
+       if (lwsi_state(wsi) != LRS_ISSUING_FILE) {
+               /* Prepare to read body if we have a content length: */
+               lwsl_debug("wsi->http.rx_content_length %lld %d %d\n",
+                          (long long)wsi->http.rx_content_length,
+                          wsi->upgraded_to_http2, wsi->http2_substream);
+
+               if (wsi->http.content_length_explicitly_zero &&
+                   lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) {
+
+                       /*
+                        * POST with an explicit content-length of zero
+                        *
+                        * If we don't give the user code the empty HTTP_BODY
+                        * callback, he may become confused to hear the
+                        * HTTP_BODY_COMPLETION (due to, eg, instantiation of
+                        * lws_spa never happened).
+                        *
+                        * HTTP_BODY_COMPLETION is responsible for sending the
+                        * result status code and result body if any, and
+                        * do the transaction complete processing.
+                        */
+                       if (wsi->protocol->callback(wsi,
+                                       LWS_CALLBACK_HTTP_BODY,
+                                       wsi->user_space, NULL, 0))
+                               return 1;
+                       if (wsi->protocol->callback(wsi,
+                                       LWS_CALLBACK_HTTP_BODY_COMPLETION,
+                                       wsi->user_space, NULL, 0))
+                               return 1;
+
+                       return 0;
+               }
+
+               if (wsi->http.rx_content_length > 0) {
+
+                       if (lwsi_state(wsi) != LRS_DISCARD_BODY) {
+                               lwsi_set_state(wsi, LRS_BODY);
+                               lwsl_info("%s: %p: LRS_BODY state set (0x%x)\n",
+                                   __func__, wsi, wsi->wsistate);
+                       }
+                       wsi->http.rx_content_remain =
+                                       wsi->http.rx_content_length;
+
+                       /*
+                        * At this point we have transitioned from deferred
+                        * action to expecting BODY on the stream wsi, if it's
+                        * in a bundle like h2.  So if the stream wsi has its
+                        * own buflist, we need to deal with that first.
+                        */
+
+                       while (1) {
+                               struct lws_tokens ebuf;
+                               int m;
+
+                               ebuf.len = (int)lws_buflist_next_segment_len(
+                                               &wsi->buflist,
+                                               &ebuf.token);
+                               if (!ebuf.len)
+                                       break;
+                               lwsl_debug("%s: consuming %d\n", __func__,
+                                                       (int)ebuf.len);
+                               m = lws_read_h1(wsi, ebuf.token,
+                                               ebuf.len);
+                               if (m < 0)
+                                       return -1;
+
+                               if (lws_buflist_aware_consume(wsi, &ebuf, m, 1))
+                                       return -1;
+                       }
+               }
+       }
+
+       return 0;
+
+bail_nuke_ah:
+       lws_header_table_detach(wsi, 1);
+
+       return 1;
+}
+
+int
+lws_confirm_host_header(struct lws *wsi)
+{
+       struct lws_tokenize ts;
+       lws_tokenize_elem e;
+       char buf[128];
+       int port = 80;
+
+       /*
+        * this vhost wants us to validate what the
+        * client sent against our vhost name
+        */
+
+       if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
+               lwsl_info("%s: missing host on upgrade\n", __func__);
+
+               return 1;
+       }
+
+#if defined(LWS_WITH_TLS)
+       if (wsi->tls.ssl)
+               port = 443;
+#endif
+
+       lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_DOT_NONTERM /* server.com */|
+                                   LWS_TOKENIZE_F_NO_FLOATS /* 1.server.com */|
+                                   LWS_TOKENIZE_F_MINUS_NONTERM /* a-b.com */);
+       ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_HOST);
+       if (ts.len <= 0) {
+               lwsl_info("%s: missing or oversize host header\n", __func__);
+               return 1;
+       }
+
+       if (lws_tokenize(&ts) != LWS_TOKZE_TOKEN)
+               goto bad_format;
+
+       if (strncmp(ts.token, wsi->vhost->name, ts.token_len)) {
+               buf[(ts.token - buf) + ts.token_len] = '\0';
+               lwsl_info("%s: '%s' in host hdr but vhost name %s\n",
+                         __func__, ts.token, wsi->vhost->name);
+               return 1;
+       }
+
+       e = lws_tokenize(&ts);
+       if (e == LWS_TOKZE_DELIMITER && ts.token[0] == ':') {
+               if (lws_tokenize(&ts) != LWS_TOKZE_INTEGER)
+                       goto bad_format;
+               else
+                       port = atoi(ts.token);
+       } else
+               if (e != LWS_TOKZE_ENDED)
+                       goto bad_format;
+
+       if (wsi->vhost->listen_port != port) {
+               lwsl_info("%s: host port %d mismatches vhost port %d\n",
+                         __func__, port, wsi->vhost->listen_port);
+               return 1;
+       }
+
+       lwsl_debug("%s: host header OK\n", __func__);
+
+       return 0;
+
+bad_format:
+       lwsl_info("%s: bad host header format\n", __func__);
+
+       return 1;
+}
+
+#if !defined(LWS_NO_SERVER)
+int
+lws_http_to_fallback(struct lws *wsi, unsigned char *obuf, size_t olen)
+{
+       const struct lws_role_ops *role = &role_ops_raw_skt;
+       const struct lws_protocols *p1, *protocol =
+                        &wsi->vhost->protocols[wsi->vhost->raw_protocol_index];
+       char ipbuf[64];
+       int n;
+
+       if (wsi->vhost->listen_accept_role &&
+           lws_role_by_name(wsi->vhost->listen_accept_role))
+               role = lws_role_by_name(wsi->vhost->listen_accept_role);
+
+       if (wsi->vhost->listen_accept_protocol) {
+               p1 = lws_vhost_name_to_protocol(wsi->vhost,
+                           wsi->vhost->listen_accept_protocol);
+               if (p1)
+                       protocol = p1;
+       }
+
+       lws_bind_protocol(wsi, protocol, __func__);
+
+       lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, role);
+
+       lws_header_table_detach(wsi, 0);
+       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+       n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
+       if (wsi->role_ops->adoption_cb[lwsi_role_server(wsi)])
+               n = wsi->role_ops->adoption_cb[lwsi_role_server(wsi)];
+
+       ipbuf[0] = '\0';
+#if !defined(LWS_PLAT_OPTEE)
+       lws_get_peer_simple(wsi, ipbuf, sizeof(ipbuf));
+#endif
+
+       lwsl_notice("%s: vh %s, peer: %s, role %s, "
+                   "protocol %s, cb %d, ah %p\n", __func__, wsi->vhost->name,
+                   ipbuf, role->name, protocol->name, n, wsi->http.ah);
+
+       if ((wsi->protocol->callback)(wsi, n, wsi->user_space, NULL, 0))
+               return 1;
+
+       n = LWS_CALLBACK_RAW_RX;
+       if (wsi->role_ops->rx_cb[lwsi_role_server(wsi)])
+               n = wsi->role_ops->rx_cb[lwsi_role_server(wsi)];
+       if (wsi->protocol->callback(wsi, n, wsi->user_space, obuf, olen))
+               return 1;
+
+       return 0;
+}
+
+int
+lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len)
+{
+       struct lws_context *context = lws_get_context(wsi);
+#if defined(LWS_WITH_HTTP2)
+       struct allocated_headers *ah;
+#endif
+       unsigned char *obuf = *buf;
+#if defined(LWS_WITH_HTTP2)
+       char tbuf[128], *p;
+#endif
+       size_t olen = len;
+       int n = 0, m, i;
+
+       if (len >= 10000000) {
+               lwsl_err("%s: assert: len %ld\n", __func__, (long)len);
+               assert(0);
+       }
+
+       if (!wsi->http.ah) {
+               lwsl_err("%s: assert: NULL ah\n", __func__);
+               assert(0);
+       }
+
+       while (len) {
+               if (!lwsi_role_server(wsi) || !lwsi_role_http(wsi)) {
+                       lwsl_err("%s: bad wsi role 0x%x\n", __func__,
+                                       lwsi_role(wsi));
+                       goto bail_nuke_ah;
+               }
+
+               i = (int)len;
+               m = lws_parse(wsi, *buf, &i);
+               lwsl_info("%s: parsed count %d\n", __func__, (int)len - i);
+               (*buf) += (int)len - i;
+               len = i;
+
+               if (m == LPR_DO_FALLBACK) {
+
+                       /*
+                        * http parser went off the rails and
+                        * LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_
+                        * ACCEPT_CONFIG is set on this vhost.
+                        *
+                        * We are transitioning from http with an AH, to
+                        * a backup role (raw-skt, by default).  Drop
+                        * the ah, bind to the role with mode as
+                        * ESTABLISHED.
+                        */
+raw_transition:
+
+                       if (lws_http_to_fallback(wsi, obuf, olen)) {
+                               lwsl_info("%s: fallback -> close\n", __func__);
+                               goto bail_nuke_ah;
+                       }
+
+                       (*buf) = obuf + olen;
+
+                       return 0;
+               }
+               if (m) {
+                       lwsl_info("lws_parse failed\n");
+                       goto bail_nuke_ah;
+               }
+
+               if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE)
+                       continue;
+
+               lwsl_parser("%s: lws_parse sees parsing complete\n", __func__);
+
+               /* select vhost */
+
+               if (wsi->vhost->listen_port &&
+                   lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
+                       struct lws_vhost *vhost = lws_select_vhost(
+                               context, wsi->vhost->listen_port,
+                               lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
+
+                       if (vhost)
+                               lws_vhost_bind_wsi(vhost, wsi);
+               } else
+                       lwsl_info("no host\n");
+
+               if (!lwsi_role_h2(wsi) || !lwsi_role_server(wsi)) {
+                       wsi->vhost->conn_stats.h1_trans++;
+                       if (!wsi->conn_stat_done) {
+                               wsi->vhost->conn_stats.h1_conn++;
+                               wsi->conn_stat_done = 1;
+                       }
+               }
+
+               /* check for unwelcome guests */
+
+               if (wsi->context->reject_service_keywords) {
+                       const struct lws_protocol_vhost_options *rej =
+                                       wsi->context->reject_service_keywords;
+                       char ua[384], *msg = NULL;
+
+                       if (lws_hdr_copy(wsi, ua, sizeof(ua) - 1,
+                                        WSI_TOKEN_HTTP_USER_AGENT) > 0) {
+#ifdef LWS_WITH_ACCESS_LOG
+                               char *uri_ptr = NULL;
+                               int meth, uri_len;
+#endif
+                               ua[sizeof(ua) - 1] = '\0';
+                               while (rej) {
+                                       if (!strstr(ua, rej->name)) {
+                                               rej = rej->next;
+                                               continue;
+                                       }
+
+                                       msg = strchr(rej->value, ' ');
+                                       if (msg)
+                                               msg++;
+                                       lws_return_http_status(wsi,
+                                               atoi(rej->value), msg);
+#ifdef LWS_WITH_ACCESS_LOG
+                                       meth = lws_http_get_uri_and_method(wsi,
+                                                       &uri_ptr, &uri_len);
+                                       if (meth >= 0)
+                                               lws_prepare_access_log_info(wsi,
+                                                       uri_ptr, uri_len, meth);
+
+                                       /* wsi close will do the log */
+#endif
+                                       wsi->vhost->conn_stats.rejected++;
+                                       /*
+                                        * We don't want anything from
+                                        * this rejected guy.  Follow
+                                        * the close flow, not the
+                                        * transaction complete flow.
+                                        */
+                                       goto bail_nuke_ah;
+                               }
+                       }
+               }
+
+
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECT)) {
+                       lwsl_info("Changing to RAW mode\n");
+                       m = 0;
+                       goto raw_transition;
+               }
+
+               lwsi_set_state(wsi, LRS_PRE_WS_SERVING_ACCEPT);
+               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) {
+
+                       const char *up = lws_hdr_simple_ptr(wsi,
+                                                           WSI_TOKEN_UPGRADE);
+
+                       if (strcasecmp(up, "websocket") &&
+                           strcasecmp(up, "h2c")) {
+                               lwsl_info("Unknown upgrade '%s'\n", up);
+
+                               if (lws_return_http_status(wsi,
+                                               HTTP_STATUS_FORBIDDEN, NULL) ||
+                                   lws_http_transaction_completed(wsi))
+                                       goto bail_nuke_ah;
+                       }
+
+                       n = user_callback_handle_rxflow(wsi->protocol->callback,
+                                       wsi, LWS_CALLBACK_HTTP_CONFIRM_UPGRADE,
+                                       wsi->user_space, (char *)up, 0);
+
+                       /* just hang up? */
+
+                       if (n < 0)
+                               goto bail_nuke_ah;
+
+                       /* callback returned headers already, do t_c? */
+
+                       if (n > 0) {
+                               if (lws_http_transaction_completed(wsi))
+                                       goto bail_nuke_ah;
+
+                               /* continue on */
+
+                               return 0;
+                       }
+
+                       /* callback said 0, it was allowed */
+
+                       if (wsi->vhost->options &
+                           LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK &&
+                           lws_confirm_host_header(wsi))
+                               goto bail_nuke_ah;
+
+                       if (!strcasecmp(up, "websocket")) {
+#if defined(LWS_ROLE_WS)
+                               wsi->vhost->conn_stats.ws_upg++;
+                               lwsl_info("Upgrade to ws\n");
+                               goto upgrade_ws;
+#endif
+                       }
+#if defined(LWS_WITH_HTTP2)
+                       if (!strcasecmp(up, "h2c")) {
+                               wsi->vhost->conn_stats.h2_upg++;
+                               lwsl_info("Upgrade to h2c\n");
+                               goto upgrade_h2c;
+                       }
+#endif
+               }
+
+               /* no upgrade ack... he remained as HTTP */
+
+               lwsl_info("%s: %p: No upgrade\n", __func__, wsi);
+
+               lwsi_set_state(wsi, LRS_ESTABLISHED);
+               wsi->http.fop_fd = NULL;
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+               lws_http_compression_validate(wsi);
+#endif
+
+               lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi,
+                          (void *)wsi->http.ah);
+
+               n = lws_http_action(wsi);
+
+               return n;
+
+#if defined(LWS_WITH_HTTP2)
+upgrade_h2c:
+               if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) {
+                       lwsl_info("missing http2_settings\n");
+                       goto bail_nuke_ah;
+               }
+
+               lwsl_info("h2c upgrade...\n");
+
+               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS);
+               /* convert the peer's HTTP-Settings */
+               n = lws_b64_decode_string(p, tbuf, sizeof(tbuf));
+               if (n < 0) {
+                       lwsl_parser("HTTP2_SETTINGS too long\n");
+                       return 1;
+               }
+
+               wsi->upgraded_to_http2 = 1;
+
+               /* adopt the header info */
+
+               ah = wsi->http.ah;
+               lws_role_transition(wsi, LWSIFR_SERVER, LRS_H2_AWAIT_PREFACE,
+                                   &role_ops_h2);
+
+               /* http2 union member has http union struct at start */
+               wsi->http.ah = ah;
+
+               if (!wsi->h2.h2n) {
+                       wsi->h2.h2n = lws_zalloc(sizeof(*wsi->h2.h2n),
+                                                  "h2n");
+                       if (!wsi->h2.h2n)
+                               return 1;
+               }
+
+               lws_h2_init(wsi);
+
+               /* HTTP2 union */
+
+               lws_h2_settings(wsi, &wsi->h2.h2n->set, (unsigned char *)tbuf, n);
+
+               lws_hpack_dynamic_size(wsi, wsi->h2.h2n->set.s[
+                                                     H2SET_HEADER_TABLE_SIZE]);
+
+               strcpy(tbuf, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
+                             "Connection: Upgrade\x0d\x0a"
+                             "Upgrade: h2c\x0d\x0a\x0d\x0a");
+               m = (int)strlen(tbuf);
+               n = lws_issue_raw(wsi, (unsigned char *)tbuf, m);
+               if (n != m) {
+                       lwsl_debug("http2 switch: ERROR writing to socket\n");
+                       return 1;
+               }
+
+               return 0;
+#endif
+#if defined(LWS_ROLE_WS)
+upgrade_ws:
+               if (lws_process_ws_upgrade(wsi))
+                       goto bail_nuke_ah;
+
+               return 0;
+#endif
+       } /* while all chars are handled */
+
+       return 0;
+
+bail_nuke_ah:
+       /* drop the header info */
+       lws_header_table_detach(wsi, 1);
+
+       return 1;
+}
+#endif
+
+LWS_VISIBLE int LWS_WARN_UNUSED_RESULT
+lws_http_transaction_completed(struct lws *wsi)
+{
+       int n = NO_PENDING_TIMEOUT;
+
+       if (lws_has_buffered_out(wsi)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+                       || wsi->http.comp_ctx.buflist_comp ||
+           wsi->http.comp_ctx.may_have_more
+#endif
+       ) {
+               /*
+                * ...so he tried to send something large as the http reply,
+                * it went as a partial, but he immediately said the
+                * transaction was completed.
+                *
+                * Defer the transaction completed until the last part of the
+                * partial is sent.
+                */
+               lwsl_debug("%s: %p: deferring due to partial\n", __func__, wsi);
+               wsi->http.deferred_transaction_completed = 1;
+               lws_callback_on_writable(wsi);
+
+               return 0;
+       }
+       /*
+        * Are we finishing the transaction before we have consumed any body?
+        *
+        * For h1 this would kill keepalive pipelining, and for h2, considering
+        * it can extend over multiple DATA frames, it would kill the network
+        * connection.
+        */
+       if (wsi->http.rx_content_length && wsi->http.rx_content_remain) {
+               /*
+                * are we already in LRS_DISCARD_BODY and didn't clear the
+                * remaining before trying to complete the transaction again?
+                */
+               if (lwsi_state(wsi) == LRS_DISCARD_BODY)
+                       return -1;
+               /*
+                * let's defer transaction completed processing until we
+                * discarded the remaining body
+                */
+               lwsi_set_state(wsi, LRS_DISCARD_BODY);
+
+               return 0;
+       }
+
+       lwsl_info("%s: wsi %p\n", __func__, wsi);
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       lws_http_compression_destroy(wsi);
+#endif
+       lws_access_log(wsi);
+
+       if (!wsi->hdr_parsing_completed) {
+               char peer[64];
+
+#if !defined(LWS_PLAT_OPTEE)
+               lws_get_peer_simple(wsi, peer, sizeof(peer) - 1);
+#else
+               peer[0] = '\0';
+#endif
+               peer[sizeof(peer) - 1] = '\0';
+               lwsl_notice("%s: (from %s) ignoring, ah parsing incomplete\n",
+                               __func__, peer);
+               return 0;
+       }
+
+       /* if we can't go back to accept new headers, drop the connection */
+       if (wsi->http2_substream)
+               return 1;
+
+       if (wsi->seen_zero_length_recv)
+               return 1;
+
+       if (wsi->http.conn_type != HTTP_CONNECTION_KEEP_ALIVE) {
+               lwsl_info("%s: %p: close connection\n", __func__, wsi);
+               return 1;
+       }
+
+       if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0], __func__))
+               return 1;
+
+       /*
+        * otherwise set ourselves up ready to go again, but because we have no
+        * idea about the wsi writability, we make put it in a holding state
+        * until we can verify POLLOUT.  The part of this that confirms POLLOUT
+        * with no partials is in lws_server_socket_service() below.
+        */
+       lwsl_debug("%s: %p: setting DEF_ACT from 0x%x\n", __func__,
+                  wsi, wsi->wsistate);
+       lwsi_set_state(wsi, LRS_DEFERRING_ACTION);
+       wsi->http.tx_content_length = 0;
+       wsi->http.tx_content_remain = 0;
+       wsi->hdr_parsing_completed = 0;
+       wsi->sending_chunked = 0;
+#ifdef LWS_WITH_ACCESS_LOG
+       wsi->http.access_log.sent = 0;
+#endif
+
+       if (wsi->vhost->keepalive_timeout)
+               n = PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE;
+       lws_set_timeout(wsi, n, wsi->vhost->keepalive_timeout);
+
+       /*
+        * We already know we are on http1.1 / keepalive and the next thing
+        * coming will be another header set.
+        *
+        * If there is no pending rx and we still have the ah, drop it and
+        * reacquire a new ah when the new headers start to arrive.  (Otherwise
+        * we needlessly hog an ah indefinitely.)
+        *
+        * However if there is pending rx and we know from the keepalive state
+        * that is already at least the start of another header set, simply
+        * reset the existing header table and keep it.
+        */
+       if (wsi->http.ah) {
+               // lws_buflist_describe(&wsi->buflist, wsi);
+               if (!lws_buflist_next_segment_len(&wsi->buflist, NULL)) {
+                       lwsl_debug("%s: %p: nothing in buflist, detaching ah\n",
+                                 __func__, wsi);
+                       lws_header_table_detach(wsi, 1);
+#ifdef LWS_WITH_TLS
+                       /*
+                        * additionally... if we are hogging an SSL instance
+                        * with no pending pipelined headers (or ah now), and
+                        * SSL is scarce, drop this connection without waiting
+                        */
+
+                       if (wsi->vhost->tls.use_ssl &&
+                           wsi->context->simultaneous_ssl_restriction &&
+                           wsi->context->simultaneous_ssl ==
+                                  wsi->context->simultaneous_ssl_restriction) {
+                               lwsl_info("%s: simultaneous_ssl_restriction\n",
+                                         __func__);
+                               return 1;
+                       }
+#endif
+               } else {
+                       lwsl_info("%s: %p: resetting/keeping ah as pipeline\n",
+                                 __func__, wsi);
+                       lws_header_table_reset(wsi, 0);
+                       /*
+                        * If we kept the ah, we should restrict the amount
+                        * of time we are willing to keep it.  Otherwise it
+                        * will be bound the whole time the connection remains
+                        * open.
+                        */
+                       lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH,
+                                       wsi->vhost->keepalive_timeout);
+               }
+               /* If we're (re)starting on headers, need other implied init */
+               if (wsi->http.ah)
+                       wsi->http.ah->ues = URIES_IDLE;
+
+               //lwsi_set_state(wsi, LRS_ESTABLISHED); // !!!
+       } else
+               if (lws_buflist_next_segment_len(&wsi->buflist, NULL))
+                       if (lws_header_table_attach(wsi, 0))
+                               lwsl_debug("acquired ah\n");
+
+       lwsl_debug("%s: %p: keep-alive await new transaction (state 0x%x)\n",
+                       __func__, wsi, wsi->wsistate);
+       lws_callback_on_writable(wsi);
+
+       return 0;
+}
+
+#if !defined(LWS_AMAZON_RTOS)
+LWS_VISIBLE int
+lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
+                   const char *other_headers, int other_headers_len)
+{
+       struct lws_context *context = lws_get_context(wsi);
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       unsigned char *response = pt->serv_buf + LWS_PRE;
+#if defined(LWS_WITH_RANGES)
+       struct lws_range_parsing *rp = &wsi->http.range;
+#endif
+       int ret = 0, cclen = 8, n = HTTP_STATUS_OK;
+       char cache_control[50], *cc = "no-store";
+       lws_fop_flags_t fflags = LWS_O_RDONLY;
+       const struct lws_plat_file_ops *fops;
+       lws_filepos_t total_content_length;
+       unsigned char *p = response;
+       unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE;
+       const char *vpath;
+#if defined(LWS_WITH_RANGES)
+       int ranges;
+#endif
+
+       if (wsi->handling_404)
+               n = HTTP_STATUS_NOT_FOUND;
+
+       /*
+        * We either call the platform fops .open with first arg platform fops,
+        * or we call fops_zip .open with first arg platform fops, and fops_zip
+        * open will decide whether to switch to fops_zip or stay with fops_def.
+        *
+        * If wsi->http.fop_fd is already set, the caller already opened it
+        */
+       if (!wsi->http.fop_fd) {
+               fops = lws_vfs_select_fops(wsi->context->fops, file, &vpath);
+               fflags |= lws_vfs_prepare_flags(wsi);
+               wsi->http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
+                                                       file, vpath, &fflags);
+               if (!wsi->http.fop_fd) {
+                       lwsl_info("%s: Unable to open: '%s': errno %d\n",
+                                 __func__, file, errno);
+                       if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND,
+                                                  NULL))
+                                               return -1;
+                       return !wsi->http2_substream;
+               }
+       }
+       wsi->http.filelen = lws_vfs_get_length(wsi->http.fop_fd);
+       total_content_length = wsi->http.filelen;
+
+#if defined(LWS_WITH_RANGES)
+       ranges = lws_ranges_init(wsi, rp, wsi->http.filelen);
+
+       lwsl_debug("Range count %d\n", ranges);
+       /*
+        * no ranges -> 200;
+        *  1 range  -> 206 + Content-Type: normal; Content-Range;
+        *  more     -> 206 + Content-Type: multipart/byteranges
+        *              Repeat the true Content-Type in each multipart header
+        *              along with Content-Range
+        */
+       if (ranges < 0) {
+               /* it means he expressed a range in Range:, but it was illegal */
+               lws_return_http_status(wsi,
+                               HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, NULL);
+               if (lws_http_transaction_completed(wsi))
+                       return -1; /* <0 means just hang up */
+
+               lws_vfs_file_close(&wsi->http.fop_fd);
+
+               return 0; /* == 0 means we did the transaction complete */
+       }
+       if (ranges)
+               n = HTTP_STATUS_PARTIAL_CONTENT;
+#endif
+
+       if (lws_add_http_header_status(wsi, n, &p, end))
+               return -1;
+
+       if ((wsi->http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP |
+                      LWS_FOP_FLAG_COMPR_IS_GZIP)) ==
+           (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) {
+               if (lws_add_http_header_by_token(wsi,
+                       WSI_TOKEN_HTTP_CONTENT_ENCODING,
+                       (unsigned char *)"gzip", 4, &p, end))
+                       return -1;
+               lwsl_info("file is being provided in gzip\n");
+       }
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       else {
+               /*
+                * if we know its very compressible, and we can use
+                * compression, then use the most preferred compression
+                * method that the client said he will accept
+                */
+
+               if (!wsi->interpreting && (
+                    !strncmp(content_type, "text/", 5) ||
+                    !strcmp(content_type, "application/javascript") ||
+                    !strcmp(content_type, "image/svg+xml")))
+                       lws_http_compression_apply(wsi, NULL, &p, end, 0);
+       }
+#endif
+
+       if (
+#if defined(LWS_WITH_RANGES)
+           ranges < 2 &&
+#endif
+           content_type && content_type[0])
+               if (lws_add_http_header_by_token(wsi,
+                                                WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                                (unsigned char *)content_type,
+                                                (int)strlen(content_type),
+                                                &p, end))
+                       return -1;
+
+#if defined(LWS_WITH_RANGES)
+       if (ranges >= 2) { /* multipart byteranges */
+               lws_strncpy(wsi->http.multipart_content_type, content_type,
+                       sizeof(wsi->http.multipart_content_type));
+
+               if (lws_add_http_header_by_token(wsi,
+                                                WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                                (unsigned char *)
+                                                "multipart/byteranges; "
+                                                "boundary=_lws",
+                                                20, &p, end))
+                       return -1;
+
+               /*
+                *  our overall content length has to include
+                *
+                *  - (n + 1) x "_lws\r\n"
+                *  - n x Content-Type: xxx/xxx\r\n
+                *  - n x Content-Range: bytes xxx-yyy/zzz\r\n
+                *  - n x /r/n
+                *  - the actual payloads (aggregated in rp->agg)
+                *
+                *  Precompute it for the main response header
+                */
+
+               total_content_length = (lws_filepos_t)rp->agg +
+                                      6 /* final _lws\r\n */;
+
+               lws_ranges_reset(rp);
+               while (lws_ranges_next(rp)) {
+                       n = lws_snprintf(cache_control, sizeof(cache_control),
+                                       "bytes %llu-%llu/%llu",
+                                       rp->start, rp->end, rp->extent);
+
+                       total_content_length +=
+                               6 /* header _lws\r\n */ +
+                               /* Content-Type: xxx/xxx\r\n */
+                               14 + strlen(content_type) + 2 +
+                               /* Content-Range: xxxx\r\n */
+                               15 + n + 2 +
+                               2; /* /r/n */
+               }
+
+               lws_ranges_reset(rp);
+               lws_ranges_next(rp);
+       }
+
+       if (ranges == 1) {
+               total_content_length = (lws_filepos_t)rp->agg;
+               n = lws_snprintf(cache_control, sizeof(cache_control),
+                                "bytes %llu-%llu/%llu",
+                                rp->start, rp->end, rp->extent);
+
+               if (lws_add_http_header_by_token(wsi,
+                                                WSI_TOKEN_HTTP_CONTENT_RANGE,
+                                                (unsigned char *)cache_control,
+                                                n, &p, end))
+                       return -1;
+       }
+
+       wsi->http.range.inside = 0;
+
+       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT_RANGES,
+                                        (unsigned char *)"bytes", 5, &p, end))
+               return -1;
+#endif
+
+       if (!wsi->http2_substream) {
+               /* for http/1.1 ... */
+               if (!wsi->sending_chunked
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+                               && !wsi->http.lcs
+#endif
+               ) {
+                       /* ... if not already using chunked and not using an
+                        * http compression translation, then send the naive
+                        * content length
+                        */
+                       if (lws_add_http_header_content_length(wsi,
+                                               total_content_length, &p, end))
+                               return -1;
+               } else {
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+                       if (wsi->http.lcs) {
+
+                               /* ...otherwise, for http 1 it must go chunked.
+                                * For the compression case, the reason is we
+                                * compress on the fly and do not know the
+                                * compressed content-length until it has all
+                                * been sent.  Http/1.1 pipelining must be able
+                                * to know where the transaction boundaries are
+                                * ... so chunking...
+                                */
+                               if (lws_add_http_header_by_token(wsi,
+                                               WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+                                               (unsigned char *)"chunked", 7,
+                                               &p, end))
+                                       return -1;
+
+                               /*
+                                * ...this is fun, isn't it :-)  For h1 that is
+                                * using an http compression translation, the
+                                * compressor must chunk its output privately.
+                                *
+                                * h2 doesn't need (or support) any of this
+                                * crap.
+                                */
+                               lwsl_debug("setting chunking\n");
+                               wsi->http.comp_ctx.chunking = 1;
+                       }
+#endif
+               }
+       }
+
+       if (wsi->cache_secs && wsi->cache_reuse) {
+               if (!wsi->cache_revalidate) {
+                       cc = cache_control;
+                       cclen = sprintf(cache_control, "%s, max-age=%u",
+                                   intermediates[wsi->cache_intermediaries],
+                                   wsi->cache_secs);
+               } else {
+                       cc = cache_control;
+                       cclen = sprintf(cache_control,
+                                       "must-revalidate, %s, max-age=%u",
+                                intermediates[wsi->cache_intermediaries],
+                                                    wsi->cache_secs);
+
+               }
+       }
+
+       /* Only add cache control if its not specified by any other_headers. */
+       if (!other_headers ||
+           (!strstr(other_headers, "cache-control") &&
+            !strstr(other_headers, "Cache-Control"))) {
+               if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_CACHE_CONTROL,
+                               (unsigned char *)cc, cclen, &p, end))
+                       return -1;
+       }
+
+       if (other_headers) {
+               if ((end - p) < other_headers_len)
+                       return -1;
+               memcpy(p, other_headers, other_headers_len);
+               p += other_headers_len;
+       }
+
+       if (lws_finalize_http_header(wsi, &p, end))
+               return -1;
+
+       ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS);
+       if (ret != (p - response)) {
+               lwsl_err("_write returned %d from %ld\n", ret,
+                        (long)(p - response));
+               return -1;
+       }
+
+       wsi->http.filepos = 0;
+       lwsi_set_state(wsi, LRS_ISSUING_FILE);
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_HEAD_URI)) {
+               /* we do not emit the body */
+               if (lws_http_transaction_completed(wsi))
+                       return -1;
+
+               return 0;
+       }
+
+       lws_callback_on_writable(wsi);
+
+       return 0;
+}
+#endif
+
+LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws_process_html_args args;
+       lws_filepos_t amount, poss;
+       unsigned char *p, *pstart;
+#if defined(LWS_WITH_RANGES)
+       unsigned char finished = 0;
+#endif
+       int n, m;
+
+       lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream);
+
+       do {
+
+               /* priority 1: buffered output */
+
+               if (lws_has_buffered_out(wsi)) {
+                       if (lws_issue_raw(wsi, NULL, 0) < 0) {
+                               lwsl_info("%s: closing\n", __func__);
+                               goto file_had_it;
+                       }
+                       break;
+               }
+
+               /* priority 2: buffered pre-compression-transform */
+
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+       if (wsi->http.comp_ctx.buflist_comp ||
+           wsi->http.comp_ctx.may_have_more) {
+               enum lws_write_protocol wp = LWS_WRITE_HTTP;
+
+               lwsl_info("%s: completing comp partial (buflist %p, may %d)\n",
+                          __func__, wsi->http.comp_ctx.buflist_comp,
+                          wsi->http.comp_ctx.may_have_more);
+
+               if (wsi->role_ops->write_role_protocol(wsi, NULL, 0, &wp) < 0) {
+                       lwsl_info("%s signalling to close\n", __func__);
+                       goto file_had_it;
+               }
+               lws_callback_on_writable(wsi);
+
+               break;
+       }
+#endif
+
+               if (wsi->http.filepos == wsi->http.filelen)
+                       goto all_sent;
+
+               n = 0;
+
+               pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH;
+
+               p = pstart;
+
+#if defined(LWS_WITH_RANGES)
+               if (wsi->http.range.count_ranges && !wsi->http.range.inside) {
+
+                       lwsl_notice("%s: doing range start %llu\n", __func__,
+                                   wsi->http.range.start);
+
+                       if ((long long)lws_vfs_file_seek_cur(wsi->http.fop_fd,
+                                                  wsi->http.range.start -
+                                                  wsi->http.filepos) < 0)
+                               goto file_had_it;
+
+                       wsi->http.filepos = wsi->http.range.start;
+
+                       if (wsi->http.range.count_ranges > 1) {
+                               n =  lws_snprintf((char *)p,
+                                               context->pt_serv_buf_size -
+                                               LWS_H2_FRAME_HEADER_LENGTH,
+                                       "_lws\x0d\x0a"
+                                       "Content-Type: %s\x0d\x0a"
+                                       "Content-Range: bytes "
+                                               "%llu-%llu/%llu\x0d\x0a"
+                                       "\x0d\x0a",
+                                       wsi->http.multipart_content_type,
+                                       wsi->http.range.start,
+                                       wsi->http.range.end,
+                                       wsi->http.range.extent);
+                               p += n;
+                       }
+
+                       wsi->http.range.budget = wsi->http.range.end -
+                                                  wsi->http.range.start + 1;
+                       wsi->http.range.inside = 1;
+               }
+#endif
+
+               poss = context->pt_serv_buf_size - n -
+                               LWS_H2_FRAME_HEADER_LENGTH;
+
+               if (wsi->http.tx_content_length)
+                       if (poss > wsi->http.tx_content_remain)
+                               poss = wsi->http.tx_content_remain;
+
+               /*
+                * if there is a hint about how much we will do well to send at
+                * one time, restrict ourselves to only trying to send that.
+                */
+               if (wsi->protocol->tx_packet_size &&
+                   poss > wsi->protocol->tx_packet_size)
+                       poss = wsi->protocol->tx_packet_size;
+
+               if (wsi->role_ops->tx_credit) {
+                       lws_filepos_t txc = wsi->role_ops->tx_credit(wsi);
+
+                       if (!txc) {
+                               lwsl_info("%s: came here with no tx credit\n",
+                                               __func__);
+                               return 0;
+                       }
+                       if (txc < poss)
+                               poss = txc;
+
+                       /*
+                        * consumption of the actual payload amount sent will be
+                        * handled when the role data frame is sent
+                        */
+               }
+
+#if defined(LWS_WITH_RANGES)
+               if (wsi->http.range.count_ranges) {
+                       if (wsi->http.range.count_ranges > 1)
+                               poss -= 7; /* allow for final boundary */
+                       if (poss > wsi->http.range.budget)
+                               poss = wsi->http.range.budget;
+               }
+#endif
+               if (wsi->sending_chunked) {
+                       /* we need to drop the chunk size in here */
+                       p += 10;
+                       /* allow for the chunk to grow by 128 in translation */
+                       poss -= 10 + 128;
+               }
+
+               if (lws_vfs_file_read(wsi->http.fop_fd, &amount, p, poss) < 0)
+                       goto file_had_it; /* caller will close */
+
+               if (wsi->sending_chunked)
+                       n = (int)amount;
+               else
+                       n = lws_ptr_diff(p, pstart) + (int)amount;
+
+               lwsl_debug("%s: sending %d\n", __func__, n);
+
+               if (n) {
+                       lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
+                                       context->timeout_secs);
+
+                       if (wsi->interpreting) {
+                               args.p = (char *)p;
+                               args.len = n;
+                               args.max_len = (unsigned int)poss + 128;
+                               args.final = wsi->http.filepos + n ==
+                                            wsi->http.filelen;
+                               args.chunked = wsi->sending_chunked;
+                               if (user_callback_handle_rxflow(
+                                    wsi->vhost->protocols[
+                                    (int)wsi->protocol_interpret_idx].callback,
+                                    wsi, LWS_CALLBACK_PROCESS_HTML,
+                                    wsi->user_space, &args, 0) < 0)
+                                       goto file_had_it;
+                               n = args.len;
+                               p = (unsigned char *)args.p;
+                       } else
+                               p = pstart;
+
+#if defined(LWS_WITH_RANGES)
+                       if (wsi->http.range.send_ctr + 1 ==
+                               wsi->http.range.count_ranges && // last range
+                           wsi->http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart)
+                           wsi->http.range.budget - amount == 0) {// final part
+                               n += lws_snprintf((char *)pstart + n, 6,
+                                       "_lws\x0d\x0a"); // append trailing boundary
+                               lwsl_debug("added trailing boundary\n");
+                       }
+#endif
+                       m = lws_write(wsi, p, n, wsi->http.filepos + amount ==
+                                       wsi->http.filelen ?
+                                        LWS_WRITE_HTTP_FINAL : LWS_WRITE_HTTP);
+                       if (m < 0)
+                               goto file_had_it;
+
+                       wsi->http.filepos += amount;
+
+#if defined(LWS_WITH_RANGES)
+                       if (wsi->http.range.count_ranges >= 1) {
+                               wsi->http.range.budget -= amount;
+                               if (wsi->http.range.budget == 0) {
+                                       lwsl_notice("range budget exhausted\n");
+                                       wsi->http.range.inside = 0;
+                                       wsi->http.range.send_ctr++;
+
+                                       if (lws_ranges_next(&wsi->http.range) < 1) {
+                                               finished = 1;
+                                               goto all_sent;
+                                       }
+                               }
+                       }
+#endif
+
+                       if (m != n) {
+                               /* adjust for what was not sent */
+                               if (lws_vfs_file_seek_cur(wsi->http.fop_fd,
+                                                          m - n) ==
+                                                            (lws_fileofs_t)-1)
+                                       goto file_had_it;
+                       }
+               }
+
+all_sent:
+               if ((!lws_has_buffered_out(wsi)
+#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
+                               && !wsi->http.comp_ctx.buflist_comp &&
+                   !wsi->http.comp_ctx.may_have_more
+#endif
+                   ) && (wsi->http.filepos >= wsi->http.filelen
+#if defined(LWS_WITH_RANGES)
+                   || finished)
+#else
+               )
+#endif
+               ) {
+                       lwsi_set_state(wsi, LRS_ESTABLISHED);
+                       /* we might be in keepalive, so close it off here */
+                       lws_vfs_file_close(&wsi->http.fop_fd);
+
+                       lwsl_debug("file completed\n");
+
+                       if (wsi->protocol->callback &&
+                           user_callback_handle_rxflow(wsi->protocol->callback,
+                                       wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION,
+                                       wsi->user_space, NULL, 0) < 0) {
+                                       /*
+                                        * For http/1.x, the choices from
+                                        * transaction_completed are either
+                                        * 0 to use the connection for pipelined
+                                        * or nonzero to hang it up.
+                                        *
+                                        * However for http/2. while we are
+                                        * still interested in hanging up the
+                                        * nwsi if there was a network-level
+                                        * fatal error, simply completing the
+                                        * transaction is a matter of the stream
+                                        * state, not the root connection at the
+                                        * network level
+                                        */
+                                       if (wsi->http2_substream)
+                                               return 1;
+                                       else
+                                               return -1;
+                               }
+
+                       return 1;  /* >0 indicates completed */
+               }
+               /*
+                * while(1) here causes us to spam the whole file contents into
+                * a hugely bloated output buffer if it ever can't send the
+                * whole chunk...
+                */
+       } while (!lws_send_pipe_choked(wsi));
+
+       lws_callback_on_writable(wsi);
+
+       return 0; /* indicates further processing must be done */
+
+file_had_it:
+       lws_vfs_file_close(&wsi->http.fop_fd);
+
+       return -1;
+}
+
+#ifndef LWS_NO_SERVER
+LWS_VISIBLE void
+lws_server_get_canonical_hostname(struct lws_context *context,
+                                 const struct lws_context_creation_info *info)
+{
+       if (lws_check_opt(info->options,
+                       LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME))
+               return;
+#if !defined(LWS_WITH_ESP32)
+       /* find canonical hostname */
+       gethostname((char *)context->canonical_hostname,
+                   sizeof(context->canonical_hostname) - 1);
+
+       lwsl_info(" canonical_hostname = %s\n", context->canonical_hostname);
+#else
+       (void)context;
+#endif
+}
+#endif
+
+LWS_VISIBLE LWS_EXTERN int
+lws_chunked_html_process(struct lws_process_html_args *args,
+                        struct lws_process_html_state *s)
+{
+       char *sp, buffer[32];
+       const char *pc;
+       int old_len, n;
+
+       /* do replacements */
+       sp = args->p;
+       old_len = args->len;
+       args->len = 0;
+       s->start = sp;
+       while (sp < args->p + old_len) {
+
+               if (args->len + 7 >= args->max_len) {
+                       lwsl_err("Used up interpret padding\n");
+                       return -1;
+               }
+
+               if ((!s->pos && *sp == '$') || s->pos) {
+                       int hits = 0, hit = 0;
+
+                       if (!s->pos)
+                               s->start = sp;
+                       s->swallow[s->pos++] = *sp;
+                       if (s->pos == sizeof(s->swallow) - 1)
+                               goto skip;
+                       for (n = 0; n < s->count_vars; n++)
+                               if (!strncmp(s->swallow, s->vars[n], s->pos)) {
+                                       hits++;
+                                       hit = n;
+                               }
+                       if (!hits) {
+skip:
+                               s->swallow[s->pos] = '\0';
+                               memcpy(s->start, s->swallow, s->pos);
+                               args->len++;
+                               s->pos = 0;
+                               sp = s->start + 1;
+                               continue;
+                       }
+                       if (hits == 1 && s->pos == (int)strlen(s->vars[hit])) {
+                               pc = s->replace(s->data, hit);
+                               if (!pc)
+                                       pc = "NULL";
+                               n = (int)strlen(pc);
+                               s->swallow[s->pos] = '\0';
+                               if (n != s->pos) {
+                                       memmove(s->start + n, s->start + s->pos,
+                                               old_len - (sp - args->p) - 1);
+                                       old_len += (n - s->pos) + 1;
+                               }
+                               memcpy(s->start, pc, n);
+                               args->len++;
+                               sp = s->start + 1;
+
+                               s->pos = 0;
+                       }
+                       sp++;
+                       continue;
+               }
+
+               args->len++;
+               sp++;
+       }
+
+       if (args->chunked) {
+               /* no space left for final chunk trailer */
+               if (args->final && args->len + 7 >= args->max_len)
+                       return -1;
+
+               n = sprintf(buffer, "%X\x0d\x0a", args->len);
+
+               args->p -= n;
+               memcpy(args->p, buffer, n);
+               args->len += n;
+
+               if (args->final) {
+                       sp = args->p + args->len;
+                       *sp++ = '\x0d';
+                       *sp++ = '\x0a';
+                       *sp++ = '0';
+                       *sp++ = '\x0d';
+                       *sp++ = '\x0a';
+                       *sp++ = '\x0d';
+                       *sp++ = '\x0a';
+                       args->len += 7;
+               } else {
+                       sp = args->p + args->len;
+                       *sp++ = '\x0d';
+                       *sp++ = '\x0a';
+                       args->len += 2;
+               }
+       }
+
+       return 0;
+}
diff --git a/lib/roles/listen/ops-listen.c b/lib/roles/listen/ops-listen.c
new file mode 100644 (file)
index 0000000..5d87f4b
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+static int
+rops_handle_POLLIN_listen(struct lws_context_per_thread *pt, struct lws *wsi,
+                         struct lws_pollfd *pollfd)
+{
+       struct lws_context *context = wsi->context;
+       lws_sockfd_type accept_fd = LWS_SOCK_INVALID;
+       lws_sock_file_fd_type fd;
+       int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL;
+       struct sockaddr_storage cli_addr;
+       socklen_t clilen;
+
+       /* if our vhost is going down, ignore it */
+
+       if (wsi->vhost->being_destroyed)
+               return LWS_HPI_RET_HANDLED;
+
+       /* pollin means a client has connected to us then
+        *
+        * pollout is a hack on esp32 for background accepts signalling
+        * they completed
+        */
+
+       do {
+               struct lws *cwsi;
+
+               if (!(pollfd->revents & (LWS_POLLIN | LWS_POLLOUT)) ||
+                   !(pollfd->events & LWS_POLLIN))
+                       break;
+
+#if defined(LWS_WITH_TLS)
+               /*
+                * can we really accept it, with regards to SSL limit?
+                * another vhost may also have had POLLIN on his
+                * listener this round and used it up already
+                */
+               if (wsi->vhost->tls.use_ssl &&
+                   context->simultaneous_ssl_restriction &&
+                   context->simultaneous_ssl ==
+                                 context->simultaneous_ssl_restriction)
+                       /*
+                        * no... ignore it, he won't come again until
+                        * we are below the simultaneous_ssl_restriction
+                        * limit and POLLIN is enabled on him again
+                        */
+                       break;
+#endif
+               /* listen socket got an unencrypted connection... */
+
+               clilen = sizeof(cli_addr);
+               lws_latency_pre(context, wsi);
+
+               /*
+                * We cannot identify the peer who is in the listen
+                * socket connect queue before we accept it; even if
+                * we could, not accepting it due to PEER_LIMITS would
+                * block the connect queue for other legit peers.
+                */
+
+               accept_fd = accept((int)pollfd->fd,
+                                  (struct sockaddr *)&cli_addr, &clilen);
+               lws_latency(context, wsi, "listener accept",
+                           (int)accept_fd, accept_fd != LWS_SOCK_INVALID);
+               if (accept_fd == LWS_SOCK_INVALID) {
+                       if (LWS_ERRNO == LWS_EAGAIN ||
+                           LWS_ERRNO == LWS_EWOULDBLOCK) {
+                               break;
+                       }
+                       lwsl_err("accept: %s\n", strerror(LWS_ERRNO));
+                       return LWS_HPI_RET_HANDLED;
+               }
+
+               if (context->being_destroyed) {
+                       compatible_close(accept_fd);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               lws_plat_set_socket_options(wsi->vhost, accept_fd, 0);
+
+#if defined(LWS_WITH_IPV6)
+               lwsl_debug("accepted new conn port %u on fd=%d\n",
+                       ((cli_addr.ss_family == AF_INET6) ?
+                       ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) :
+                       ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)),
+                       accept_fd);
+#else
+               {
+               struct sockaddr_in sain;
+               memcpy(&sain, &cli_addr, sizeof(sain));
+               lwsl_debug("accepted new conn port %u on fd=%d\n",
+                          ntohs(sain.sin_port),
+                          accept_fd);
+               }
+#endif
+
+               /*
+                * look at who we connected to and give user code a
+                * chance to reject based on client IP.  There's no
+                * protocol selected yet so we issue this to
+                * protocols[0]
+                */
+               if ((wsi->vhost->protocols[0].callback)(wsi,
+                               LWS_CALLBACK_FILTER_NETWORK_CONNECTION,
+                               NULL,
+                               (void *)(lws_intptr_t)accept_fd, 0)) {
+                       lwsl_debug("Callback denied net connection\n");
+                       compatible_close(accept_fd);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               if (!(wsi->vhost->options &
+                       LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG))
+                       opts |= LWS_ADOPT_HTTP;
+
+               fd.sockfd = accept_fd;
+               cwsi = lws_adopt_descriptor_vhost(wsi->vhost, opts, fd,
+                                                 NULL, NULL);
+               if (!cwsi) {
+                       lwsl_err("%s: lws_adopt_descriptor_vhost failed\n",
+                                       __func__);
+                       /* already closed cleanly as necessary */
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+               }
+/*
+               if (lws_server_socket_service_ssl(cwsi, accept_fd)) {
+                       lws_close_free_wsi(cwsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                          "listen svc fail");
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+               }
+
+               lwsl_info("%s: new wsi %p: wsistate 0x%lx, role_ops %s\n",
+                           __func__, cwsi, (unsigned long)cwsi->wsistate,
+                           cwsi->role_ops->name);
+*/
+
+       } while (pt->fds_count < context->fd_limit_per_thread - 1 &&
+                wsi->position_in_fds_table != LWS_NO_FDS_POS &&
+                lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0);
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+int rops_handle_POLLOUT_listen(struct lws *wsi)
+{
+       return LWS_HP_RET_USER_SERVICE;
+}
+
+struct lws_role_ops role_ops_listen = {
+       /* role name */                 "listen",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_listen,
+       /* handle_POLLOUT */            rops_handle_POLLOUT_listen,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              NULL,
+       /* adoption_bind */             NULL,
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { 0, 0 },
+       /* rx_cb clnt, srv */           { 0, 0 },
+       /* writeable cb clnt, srv */    { 0, 0 },
+       /* close cb clnt, srv */        { 0, 0 },
+       /* protocol_bind_cb c,s */      { 0, 0 },
+       /* protocol_unbind_cb c,s */    { 0, 0 },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/pipe/ops-pipe.c b/lib/roles/pipe/ops-pipe.c
new file mode 100644 (file)
index 0000000..012050b
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+static int
+rops_handle_POLLIN_pipe(struct lws_context_per_thread *pt, struct lws *wsi,
+                       struct lws_pollfd *pollfd)
+{
+#if !defined(WIN32) && !defined(_WIN32)
+       char s[100];
+       int n;
+
+       /*
+        * discard the byte(s) that signaled us
+        * We really don't care about the number of bytes, but coverity
+        * thinks we should.
+        */
+       n = read(wsi->desc.sockfd, s, sizeof(s));
+       (void)n;
+       if (n < 0)
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+#endif
+
+#if defined(LWS_WITH_THREADPOOL)
+       /*
+        * threadpools that need to call for on_writable callbacks do it by
+        * marking the task as needing one for its wsi, then cancelling service.
+        *
+        * Each tsi will call this to perform the actual callback_on_writable
+        * from the correct service thread context
+        */
+       lws_threadpool_tsi_context(pt->context, pt->tid);
+#endif
+
+       /*
+        * the poll() wait, or the event loop for libuv etc is a
+        * process-wide resource that we interrupted.  So let every
+        * protocol that may be interested in the pipe event know that
+        * it happened.
+        */
+       if (lws_broadcast(pt, LWS_CALLBACK_EVENT_WAIT_CANCELLED, NULL, 0)) {
+               lwsl_info("closed in event cancel\n");
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+struct lws_role_ops role_ops_pipe = {
+       /* role name */                 "pipe",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_pipe,
+       /* handle_POLLOUT */            NULL,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              NULL,
+       /* adoption_bind */             NULL,
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { 0, 0 },
+       /* rx_cb clnt, srv */           { 0, 0 },
+       /* writeable cb clnt, srv */    { 0, 0 },
+       /* close cb clnt, srv */        { 0, 0 },
+       /* protocol_bind_cb c,s */      { 0, 0 },
+       /* protocol_unbind_cb c,s */    { 0, 0 },
+       /* file_handle */               1,
+};
diff --git a/lib/roles/private.h b/lib/roles/private.h
new file mode 100644 (file)
index 0000000..55d9550
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h
+ */
+
+typedef uint32_t lws_wsi_state_t;
+
+/*
+ * The wsi->role_ops pointer decides almost everything about what role the wsi
+ * will play, h2, raw, ws, etc.
+ *
+ * However there are a few additional flags needed that vary, such as if the
+ * role is a client or server side, if it has that concept.  And the connection
+ * fulfilling the role, has a separate dynamic state.
+ *
+ *   31           16 15      0
+ *   [  role flags ] [ state ]
+ *
+ * The role flags part is generally invariant for the lifetime of the wsi,
+ * although it can change if the connection role itself does, eg, if the
+ * connection upgrades from H1 -> WS1 the role flags may be changed at that
+ * point.
+ *
+ * The state part reflects the dynamic connection state, and the states are
+ * reused between roles.
+ *
+ * None of the internal role or state representations are made available outside
+ * of lws internals.  Even for lws internals, if you add stuff here, please keep
+ * the constants inside this header only by adding necessary helpers here and
+ * use the helpers in the actual code.  This is to ease any future refactors.
+ *
+ * Notice LWSIFR_ENCAP means we have a parent wsi that actually carries our
+ * data as a stream inside a different protocol.
+ */
+
+#define _RS 16
+
+#define LWSIFR_CLIENT          (0x1000 << _RS) /* client side */
+#define LWSIFR_SERVER          (0x2000 << _RS) /* server side */
+
+#define LWSIFR_P_ENCAP_H2      (0x0100 << _RS) /* we are encapsulated by h2 */
+
+enum lwsi_role {
+       LWSI_ROLE_MASK          =                            (0xffff << _RS),
+       LWSI_ROLE_ENCAP_MASK    =                            (0x0f00 << _RS),
+};
+
+#define lwsi_role(wsi) (wsi->wsistate & LWSI_ROLE_MASK)
+#if !defined (_DEBUG)
+#define lwsi_set_role(wsi, role) wsi->wsistate = \
+                               (wsi->wsistate & (~LWSI_ROLE_MASK)) | role
+#else
+void lwsi_set_role(struct lws *wsi, lws_wsi_state_t role);
+#endif
+
+#define lwsi_role_client(wsi) (!!(wsi->wsistate & LWSIFR_CLIENT))
+#define lwsi_role_server(wsi) (!!(wsi->wsistate & LWSIFR_SERVER))
+#define lwsi_role_h2_ENCAPSULATION(wsi) \
+               ((wsi->wsistate & LWSI_ROLE_ENCAP_MASK) == LWSIFR_P_ENCAP_H2)
+
+/* Pollout wants a callback in this state */
+#define LWSIFS_POCB            (0x100)
+/* Before any protocol connection was established */
+#define LWSIFS_NOT_EST         (0x200)
+
+enum lwsi_state {
+
+       /* Phase 1: pre-transport */
+
+       LRS_UNCONNECTED                         = LWSIFS_NOT_EST | 0,
+       LRS_WAITING_CONNECT                     = LWSIFS_NOT_EST | 1,
+
+       /* Phase 2: establishing intermediaries on top of transport */
+
+       LRS_WAITING_PROXY_REPLY                 = LWSIFS_NOT_EST | 2,
+       LRS_WAITING_SSL                         = LWSIFS_NOT_EST | 3,
+       LRS_WAITING_SOCKS_GREETING_REPLY        = LWSIFS_NOT_EST | 4,
+       LRS_WAITING_SOCKS_CONNECT_REPLY         = LWSIFS_NOT_EST | 5,
+       LRS_WAITING_SOCKS_AUTH_REPLY            = LWSIFS_NOT_EST | 6,
+
+       /* Phase 3: establishing tls tunnel */
+
+       LRS_SSL_INIT                            = LWSIFS_NOT_EST | 7,
+       LRS_SSL_ACK_PENDING                     = LWSIFS_NOT_EST | 8,
+       LRS_PRE_WS_SERVING_ACCEPT               = LWSIFS_NOT_EST | 9,
+
+       /* Phase 4: connected */
+
+       LRS_WAITING_SERVER_REPLY                = LWSIFS_NOT_EST | 10,
+       LRS_H2_AWAIT_PREFACE                    = LWSIFS_NOT_EST | 11,
+       LRS_H2_AWAIT_SETTINGS                   = LWSIFS_NOT_EST |
+                                                 LWSIFS_POCB | 12,
+
+       /* Phase 5: protocol logically established */
+
+       LRS_H2_CLIENT_SEND_SETTINGS             = LWSIFS_POCB | 13,
+       LRS_H2_WAITING_TO_SEND_HEADERS          = LWSIFS_POCB | 14,
+       LRS_DEFERRING_ACTION                    = LWSIFS_POCB | 15,
+       LRS_IDLING                              = 16,
+       LRS_H1C_ISSUE_HANDSHAKE                 = 17,
+       LRS_H1C_ISSUE_HANDSHAKE2                = 18,
+       LRS_ISSUE_HTTP_BODY                     = 19,
+       LRS_ISSUING_FILE                        = 20,
+       LRS_HEADERS                             = 21,
+       LRS_BODY                                = 22,
+       LRS_DISCARD_BODY                        = 31,
+       LRS_ESTABLISHED                         = LWSIFS_POCB | 23,
+       /* we are established, but we have embarked on serving a single
+        * transaction.  Other transaction input may be pending, but we will
+        * not service it while we are busy dealing with the current
+        * transaction.
+        *
+        * When we complete the current transaction, we would reset our state
+        * back to ESTABLISHED and start to process the next transaction.
+        */
+       LRS_DOING_TRANSACTION                   = LWSIFS_POCB | 24,
+
+       /* Phase 6: finishing */
+
+       LRS_WAITING_TO_SEND_CLOSE               = LWSIFS_POCB | 25,
+       LRS_RETURNED_CLOSE                      = LWSIFS_POCB | 26,
+       LRS_AWAITING_CLOSE_ACK                  = LWSIFS_POCB | 27,
+       LRS_FLUSHING_BEFORE_CLOSE               = LWSIFS_POCB | 28,
+       LRS_SHUTDOWN                            = 29,
+
+       /* Phase 7: dead */
+
+       LRS_DEAD_SOCKET                         = 30,
+
+       LRS_MASK                                = 0xffff
+};
+
+#define lwsi_state(wsi) ((enum lwsi_state)(wsi->wsistate & LRS_MASK))
+#define lwsi_state_PRE_CLOSE(wsi) \
+               ((enum lwsi_state)(wsi->wsistate_pre_close & LRS_MASK))
+#define lwsi_state_est(wsi) (!(wsi->wsistate & LWSIFS_NOT_EST))
+#define lwsi_state_est_PRE_CLOSE(wsi) \
+               (!(wsi->wsistate_pre_close & LWSIFS_NOT_EST))
+#define lwsi_state_can_handle_POLLOUT(wsi) (wsi->wsistate & LWSIFS_POCB)
+#if !defined (_DEBUG)
+#define lwsi_set_state(wsi, lrs) wsi->wsistate = \
+                         (wsi->wsistate & (~LRS_MASK)) | lrs
+#else
+void lwsi_set_state(struct lws *wsi, lws_wsi_state_t lrs);
+#endif
+
+#define _LWS_ADOPT_FINISH (1 << 24)
+
+/*
+ * internal role-specific ops
+ */
+struct lws_context_per_thread;
+struct lws_role_ops {
+       const char *name;
+       const char *alpn;
+       /*
+        * After http headers have parsed, this is the last chance for a role
+        * to upgrade the connection to something else using the headers.
+        * ws-over-h2 is upgraded from h2 like this.
+        */
+       int (*check_upgrades)(struct lws *wsi);
+       /* role-specific context init during context creation */
+       int (*init_context)(struct lws_context *context,
+                           const struct lws_context_creation_info *info);
+       /* role-specific per-vhost init during vhost creation */
+       int (*init_vhost)(struct lws_vhost *vh,
+                         const struct lws_context_creation_info *info);
+       /* role-specific per-vhost destructor during vhost destroy */
+       int (*destroy_vhost)(struct lws_vhost *vh);
+       /* generic 1Hz callback for the role itself */
+       int (*periodic_checks)(struct lws_context *context, int tsi,
+                              time_t now);
+       /* chance for the role to force POLLIN without network activity */
+       int (*service_flag_pending)(struct lws_context *context, int tsi);
+       /* an fd using this role has POLLIN signalled */
+       int (*handle_POLLIN)(struct lws_context_per_thread *pt, struct lws *wsi,
+                            struct lws_pollfd *pollfd);
+       /* an fd using the role wanted a POLLOUT callback and now has it */
+       int (*handle_POLLOUT)(struct lws *wsi);
+       /* perform user pollout */
+       int (*perform_user_POLLOUT)(struct lws *wsi);
+       /* do effective callback on writeable */
+       int (*callback_on_writable)(struct lws *wsi);
+       /* connection-specific tx credit in bytes */
+       lws_fileofs_t (*tx_credit)(struct lws *wsi);
+       /* role-specific write formatting */
+       int (*write_role_protocol)(struct lws *wsi, unsigned char *buf,
+                                  size_t len, enum lws_write_protocol *wp);
+
+       /* get encapsulation parent */
+       struct lws * (*encapsulation_parent)(struct lws *wsi);
+
+       /* role-specific destructor */
+       int (*alpn_negotiated)(struct lws *wsi, const char *alpn);
+
+       /* chance for the role to handle close in the protocol */
+       int (*close_via_role_protocol)(struct lws *wsi,
+                                      enum lws_close_status reason);
+       /* role-specific close processing */
+       int (*close_role)(struct lws_context_per_thread *pt, struct lws *wsi);
+       /* role-specific connection close processing */
+       int (*close_kill_connection)(struct lws *wsi,
+                                    enum lws_close_status reason);
+       /* role-specific destructor */
+       int (*destroy_role)(struct lws *wsi);
+
+       /* role-specific socket-adopt */
+       int (*adoption_bind)(struct lws *wsi, int type, const char *prot);
+       /* role-specific client-bind:
+        * ret 1 = bound, 0 = not bound, -1 = fail out
+        * i may be NULL, indicating client_bind is being called after
+        * a successful bind earlier, to finalize the binding.  In that
+        * case ret 0 = OK, 1 = fail, wsi needs freeing, -1 = fail, wsi freed */
+       int (*client_bind)(struct lws *wsi,
+                          const struct lws_client_connect_info *i);
+
+       /*
+        * the callback reasons for adoption for client, server
+        * (just client applies if no concept of client or server)
+        */
+       uint16_t adoption_cb[2];
+       /*
+        * the callback reasons for adoption for client, server
+        * (just client applies if no concept of client or server)
+        */
+       uint16_t rx_cb[2];
+       /*
+        * the callback reasons for WRITEABLE for client, server
+        * (just client applies if no concept of client or server)
+        */
+       uint16_t writeable_cb[2];
+       /*
+        * the callback reasons for CLOSE for client, server
+        * (just client applies if no concept of client or server)
+        */
+       uint16_t close_cb[2];
+       /*
+        * the callback reasons for protocol bind for client, server
+        * (just client applies if no concept of client or server)
+        */
+       uint16_t protocol_bind_cb[2];
+       /*
+        * the callback reasons for protocol unbind for client, server
+        * (just client applies if no concept of client or server)
+        */
+       uint16_t protocol_unbind_cb[2];
+
+       unsigned int file_handle:1; /* role operates on files not sockets */
+};
+
+/* core roles */
+extern struct lws_role_ops role_ops_raw_skt, role_ops_raw_file, role_ops_listen,
+                          role_ops_pipe;
+
+/* bring in role private declarations */
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+ #include "roles/http/private.h"
+#else
+ #define lwsi_role_http(wsi) (0)
+#endif
+
+#if defined(LWS_ROLE_H1)
+ #include "roles/h1/private.h"
+#else
+ #define lwsi_role_h1(wsi) (0)
+#endif
+
+#if defined(LWS_ROLE_H2)
+ #include "roles/h2/private.h"
+#else
+ #define lwsi_role_h2(wsi) (0)
+#endif
+
+#if defined(LWS_ROLE_WS)
+ #include "roles/ws/private.h"
+#else
+ #define lwsi_role_ws(wsi) (0)
+#endif
+
+#if defined(LWS_ROLE_CGI)
+ #include "roles/cgi/private.h"
+#else
+ #define lwsi_role_cgi(wsi) (0)
+#endif
+
+#if defined(LWS_ROLE_DBUS)
+ #include "roles/dbus/private.h"
+#else
+ #define lwsi_role_dbus(wsi) (0)
+#endif
+
+#if defined(LWS_ROLE_RAW_PROXY)
+ #include "roles/raw-proxy/private.h"
+#else
+ #define lwsi_role_raw_proxy(wsi) (0)
+#endif
+
+enum {
+       LWS_HP_RET_BAIL_OK,
+       LWS_HP_RET_BAIL_DIE,
+       LWS_HP_RET_USER_SERVICE,
+
+       LWS_HPI_RET_WSI_ALREADY_DIED,   /* we closed it */
+       LWS_HPI_RET_HANDLED,            /* no probs */
+       LWS_HPI_RET_PLEASE_CLOSE_ME,    /* close it for us */
+
+       LWS_UPG_RET_DONE,
+       LWS_UPG_RET_CONTINUE,
+       LWS_UPG_RET_BAIL
+};
+
+int
+lws_role_call_adoption_bind(struct lws *wsi, int type, const char *prot);
+
+struct lws *
+lws_client_connect_3(struct lws *wsi, struct lws *wsi_piggyback, ssize_t plen);
diff --git a/lib/roles/raw-file/ops-raw-file.c b/lib/roles/raw-file/ops-raw-file.c
new file mode 100644 (file)
index 0000000..e19bd2a
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+static int
+rops_handle_POLLIN_raw_file(struct lws_context_per_thread *pt, struct lws *wsi,
+                           struct lws_pollfd *pollfd)
+{
+       int n;
+
+       if (pollfd->revents & LWS_POLLOUT) {
+               n = lws_callback_as_writeable(wsi);
+               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+                       lwsl_info("failed at set pollfd\n");
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+               }
+               if (n)
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       if (pollfd->revents & LWS_POLLIN) {
+               if (user_callback_handle_rxflow(wsi->protocol->callback,
+                                               wsi, LWS_CALLBACK_RAW_RX_FILE,
+                                               wsi->user_space, NULL, 0)) {
+                       lwsl_debug("raw rx callback closed it\n");
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+       }
+
+       if (pollfd->revents & LWS_POLLHUP)
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+       return LWS_HPI_RET_HANDLED;
+}
+
+//#if !defined(LWS_NO_SERVER)
+static int
+rops_adoption_bind_raw_file(struct lws *wsi, int type, const char *vh_prot_name)
+{
+       /* no socket or http: it can only be a raw file */
+       if ((type & LWS_ADOPT_HTTP) || (type & LWS_ADOPT_SOCKET) ||
+           (type & _LWS_ADOPT_FINISH))
+               return 0; /* no match */
+
+       lws_role_transition(wsi, 0, LRS_ESTABLISHED, &role_ops_raw_file);
+
+       if (!vh_prot_name) {
+               if (wsi->vhost->default_protocol_index >=
+                   wsi->vhost->count_protocols)
+                       return 0;
+
+               wsi->protocol = &wsi->vhost->protocols[
+                                       wsi->vhost->default_protocol_index];
+       }
+
+       return 1; /* bound */
+}
+//#endif
+
+struct lws_role_ops role_ops_raw_file = {
+       /* role name */                 "raw-file",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_raw_file,
+       /* handle_POLLOUT */            NULL,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              NULL,
+//#if !defined(LWS_NO_SERVER)
+       /* adoption_bind */             rops_adoption_bind_raw_file,
+//#else
+//                                     NULL,
+//#endif
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { LWS_CALLBACK_RAW_ADOPT_FILE,
+                                         LWS_CALLBACK_RAW_ADOPT_FILE },
+       /* rx_cb clnt, srv */           { LWS_CALLBACK_RAW_RX_FILE,
+                                         LWS_CALLBACK_RAW_RX_FILE },
+       /* writeable cb clnt, srv */    { LWS_CALLBACK_RAW_WRITEABLE_FILE,
+                                         LWS_CALLBACK_RAW_WRITEABLE_FILE},
+       /* close cb clnt, srv */        { LWS_CALLBACK_RAW_CLOSE_FILE,
+                                         LWS_CALLBACK_RAW_CLOSE_FILE},
+       /* protocol_bind cb c, srv */   { LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL,
+                                         LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL },
+       /* protocol_unbind cb c, srv */ { LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL,
+                                         LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL },
+       /* file_handle */               1,
+};
diff --git a/lib/roles/raw-proxy/ops-raw-proxy.c b/lib/roles/raw-proxy/ops-raw-proxy.c
new file mode 100644 (file)
index 0000000..9deb9c1
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+static int
+rops_handle_POLLIN_raw_proxy(struct lws_context_per_thread *pt, struct lws *wsi,
+                            struct lws_pollfd *pollfd)
+{
+       struct lws_tokens ebuf;
+       int n, buffered;
+
+       /* pending truncated sends have uber priority */
+
+       if (lws_has_buffered_out(wsi)) {
+               if (!(pollfd->revents & LWS_POLLOUT))
+                       return LWS_HPI_RET_HANDLED;
+
+               /* drain the output buflist */
+               if (lws_issue_raw(wsi, NULL, 0) < 0)
+                       goto fail;
+               /*
+                * we can't afford to allow input processing to send
+                * something new, so spin around he event loop until
+                * he doesn't have any partials
+                */
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       if ((pollfd->revents & pollfd->events & LWS_POLLIN) &&
+           /* any tunnel has to have been established... */
+           lwsi_state(wsi) != LRS_SSL_ACK_PENDING &&
+           !(wsi->favoured_pollin &&
+             (pollfd->revents & pollfd->events & LWS_POLLOUT))) {
+
+               buffered = lws_buflist_aware_read(pt, wsi, &ebuf);
+               switch (ebuf.len) {
+               case 0:
+                       lwsl_info("%s: read 0 len\n", __func__);
+                       wsi->seen_zero_length_recv = 1;
+                       if (lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                               goto fail;
+
+                       /*
+                        * we need to go to fail here, since it's the only
+                        * chance we get to understand that the socket has
+                        * closed
+                        */
+                       // goto try_pollout;
+                       goto fail;
+
+               case LWS_SSL_CAPABLE_ERROR:
+                       goto fail;
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       goto try_pollout;
+               }
+               n = user_callback_handle_rxflow(wsi->protocol->callback,
+                                               wsi, lwsi_role_client(wsi) ?
+                                                LWS_CALLBACK_RAW_PROXY_CLI_RX :
+                                                LWS_CALLBACK_RAW_PROXY_SRV_RX,
+                                               wsi->user_space, ebuf.token,
+                                               ebuf.len);
+               if (n < 0) {
+                       lwsl_info("LWS_CALLBACK_RAW_PROXY_*_RX fail\n");
+                       goto fail;
+               }
+
+               if (lws_buflist_aware_consume(wsi, &ebuf, ebuf.len, buffered))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       } else
+               if (wsi->favoured_pollin &&
+                   (pollfd->revents & pollfd->events & LWS_POLLOUT))
+                       /* we balanced the last favouring of pollin */
+                       wsi->favoured_pollin = 0;
+
+try_pollout:
+
+       if (!(pollfd->revents & LWS_POLLOUT))
+               return LWS_HPI_RET_HANDLED;
+
+       if (lws_handle_POLLOUT_event(wsi, pollfd)) {
+               lwsl_debug("POLLOUT event closed it\n");
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+#if !defined(LWS_NO_CLIENT)
+       if (lws_client_socket_service(wsi, pollfd, NULL))
+               return LWS_HPI_RET_WSI_ALREADY_DIED;
+#endif
+
+       return LWS_HPI_RET_HANDLED;
+
+fail:
+       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "raw svc fail");
+
+       return LWS_HPI_RET_WSI_ALREADY_DIED;
+}
+
+static int
+rops_adoption_bind_raw_proxy(struct lws *wsi, int type,
+                            const char *vh_prot_name)
+{
+       /* no http but socket... must be raw skt */
+       if ((type & LWS_ADOPT_HTTP) || !(type & LWS_ADOPT_SOCKET) ||
+           (!(type & LWS_ADOPT_FLAG_RAW_PROXY)) || (type & _LWS_ADOPT_FINISH))
+               return 0; /* no match */
+
+       if (type & LWS_ADOPT_FLAG_UDP)
+               /*
+                * these can be >128 bytes, so just alloc for UDP
+                */
+               wsi->udp = lws_malloc(sizeof(*wsi->udp), "udp struct");
+
+       lws_role_transition(wsi, LWSIFR_SERVER, (type & LWS_ADOPT_ALLOW_SSL) ?
+                                   LRS_SSL_INIT : LRS_ESTABLISHED,
+                           &role_ops_raw_proxy);
+
+       if (vh_prot_name)
+               lws_bind_protocol(wsi, wsi->protocol, __func__);
+       else
+               /* this is the only time he will transition */
+               lws_bind_protocol(wsi,
+                       &wsi->vhost->protocols[wsi->vhost->raw_protocol_index],
+                       __func__);
+
+       return 1; /* bound */
+}
+
+static int
+rops_client_bind_raw_proxy(struct lws *wsi,
+                          const struct lws_client_connect_info *i)
+{
+       if (!i) {
+
+               /* finalize */
+
+               if (!wsi->user_space && wsi->stash->method)
+                       if (lws_ensure_user_space(wsi))
+                               return 1;
+
+               return 0;
+       }
+
+       /* we are a fallback if nothing else matched */
+
+//     lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED,
+//                         &role_ops_raw_proxy);
+
+       return 0;
+}
+
+static int
+rops_handle_POLLOUT_raw_proxy(struct lws *wsi)
+{
+       if (lwsi_state(wsi) == LRS_ESTABLISHED)
+               return LWS_HP_RET_USER_SERVICE;
+
+       if (lwsi_role_client(wsi))
+               return LWS_HP_RET_USER_SERVICE;
+
+       return LWS_HP_RET_BAIL_OK;
+}
+
+struct lws_role_ops role_ops_raw_proxy = {
+       /* role name */                 "raw-proxy",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_raw_proxy,
+       /* handle_POLLOUT */            rops_handle_POLLOUT_raw_proxy,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              NULL,
+       /* adoption_bind */             rops_adoption_bind_raw_proxy,
+       /* client_bind */               rops_client_bind_raw_proxy,
+       /* adoption_cb clnt, srv */     { LWS_CALLBACK_RAW_PROXY_CLI_ADOPT,
+                                         LWS_CALLBACK_RAW_PROXY_SRV_ADOPT },
+       /* rx_cb clnt, srv */           { LWS_CALLBACK_RAW_PROXY_CLI_RX,
+                                         LWS_CALLBACK_RAW_PROXY_SRV_RX },
+       /* writeable cb clnt, srv */    { LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE,
+                                         LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE, },
+       /* close cb clnt, srv */        { LWS_CALLBACK_RAW_PROXY_CLI_CLOSE,
+                                         LWS_CALLBACK_RAW_PROXY_SRV_CLOSE },
+       /* protocol_bind cb c, srv */   { LWS_CALLBACK_RAW_PROXY_CLI_BIND_PROTOCOL,
+                                         LWS_CALLBACK_RAW_PROXY_SRV_BIND_PROTOCOL },
+       /* protocol_unbind cb c, srv */ { LWS_CALLBACK_RAW_PROXY_CLI_DROP_PROTOCOL,
+                                         LWS_CALLBACK_RAW_PROXY_SRV_DROP_PROTOCOL },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/raw-proxy/private.h b/lib/roles/raw-proxy/private.h
new file mode 100644 (file)
index 0000000..6f169d9
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_RAW_PROXY
+ */
+
+extern struct lws_role_ops role_ops_raw_proxy;
+
+#define lwsi_role_raw_proxy(wsi) (wsi->role_ops == &role_ops_raw_proxy)
+
+#if 0
+struct lws_vhost_role_ws {
+       const struct lws_extension *extensions;
+};
+
+struct lws_pt_role_ws {
+       struct lws *rx_draining_ext_list;
+       struct lws *tx_draining_ext_list;
+};
+
+struct _lws_raw_proxy_related {
+       struct lws *wsi_onward;
+};
+#endif
diff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c
new file mode 100644 (file)
index 0000000..ab3429c
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+static int
+rops_handle_POLLIN_raw_skt(struct lws_context_per_thread *pt, struct lws *wsi,
+                          struct lws_pollfd *pollfd)
+{
+       struct lws_tokens ebuf;
+       int n, buffered;
+
+       /* pending truncated sends have uber priority */
+
+       if (lws_has_buffered_out(wsi)) {
+               if (!(pollfd->revents & LWS_POLLOUT))
+                       return LWS_HPI_RET_HANDLED;
+
+               /* drain the output buflist */
+               if (lws_issue_raw(wsi, NULL, 0) < 0)
+                       goto fail;
+               /*
+                * we can't afford to allow input processing to send
+                * something new, so spin around he event loop until
+                * he doesn't have any partials
+                */
+               return LWS_HPI_RET_HANDLED;
+       }
+
+
+#if !defined(LWS_NO_SERVER)
+       if (!lwsi_role_client(wsi) &&  lwsi_state(wsi) != LRS_ESTABLISHED) {
+
+               lwsl_debug("%s: %p: wsistate 0x%x\n", __func__, wsi,
+                          wsi->wsistate);
+
+               if (lwsi_state(wsi) != LRS_SSL_INIT)
+                       if (lws_server_socket_service_ssl(wsi,
+                                                         LWS_SOCK_INVALID))
+                               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+       if ((pollfd->revents & pollfd->events & LWS_POLLIN) &&
+           /* any tunnel has to have been established... */
+           lwsi_state(wsi) != LRS_SSL_ACK_PENDING &&
+           !(wsi->favoured_pollin &&
+             (pollfd->revents & pollfd->events & LWS_POLLOUT))) {
+
+               buffered = lws_buflist_aware_read(pt, wsi, &ebuf);
+               switch (ebuf.len) {
+               case 0:
+                       lwsl_info("%s: read 0 len\n", __func__);
+                       wsi->seen_zero_length_recv = 1;
+                       if (lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                               goto fail;
+
+                       /*
+                        * we need to go to fail here, since it's the only
+                        * chance we get to understand that the socket has
+                        * closed
+                        */
+                       // goto try_pollout;
+                       goto fail;
+
+               case LWS_SSL_CAPABLE_ERROR:
+                       goto fail;
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       goto try_pollout;
+               }
+
+               n = user_callback_handle_rxflow(wsi->protocol->callback,
+                                               wsi, LWS_CALLBACK_RAW_RX,
+                                               wsi->user_space, ebuf.token,
+                                               ebuf.len);
+               if (n < 0) {
+                       lwsl_info("LWS_CALLBACK_RAW_RX_fail\n");
+                       goto fail;
+               }
+
+               if (lws_buflist_aware_consume(wsi, &ebuf, ebuf.len, buffered))
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       } else
+               if (wsi->favoured_pollin &&
+                   (pollfd->revents & pollfd->events & LWS_POLLOUT))
+                       /* we balanced the last favouring of pollin */
+                       wsi->favoured_pollin = 0;
+
+try_pollout:
+
+       if (!(pollfd->revents & LWS_POLLOUT))
+               return LWS_HPI_RET_HANDLED;
+
+#if !defined(LWS_WITHOUT_CLIENT)
+       if (lwsi_state(wsi) == LRS_WAITING_CONNECT)
+               lws_client_connect_3(wsi, NULL, 0);
+#endif
+
+       /* one shot */
+       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+               lwsl_notice("%s a\n", __func__);
+               goto fail;
+       }
+
+       /* clear back-to-back write detection */
+       wsi->could_have_pending = 0;
+
+       lws_stats_bump(pt, LWSSTATS_C_WRITEABLE_CB, 1);
+#if defined(LWS_WITH_STATS)
+       if (wsi->active_writable_req_us) {
+               uint64_t ul = lws_now_usecs() -
+                               wsi->active_writable_req_us;
+
+               lws_stats_bump(pt, LWSSTATS_US_WRITABLE_DELAY_AVG, ul);
+               lws_stats_max(pt,
+                         LWSSTATS_US_WORST_WRITABLE_DELAY, ul);
+               wsi->active_writable_req_us = 0;
+       }
+#endif
+       n = user_callback_handle_rxflow(wsi->protocol->callback,
+                       wsi, LWS_CALLBACK_RAW_WRITEABLE,
+                       wsi->user_space, NULL, 0);
+       if (n < 0) {
+               lwsl_info("writeable_fail\n");
+               goto fail;
+       }
+
+       return LWS_HPI_RET_HANDLED;
+
+fail:
+       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "raw svc fail");
+
+       return LWS_HPI_RET_WSI_ALREADY_DIED;
+}
+
+#if !defined(LWS_NO_SERVER)
+static int
+rops_adoption_bind_raw_skt(struct lws *wsi, int type, const char *vh_prot_name)
+{
+       /* no http but socket... must be raw skt */
+       if ((type & LWS_ADOPT_HTTP) || !(type & LWS_ADOPT_SOCKET) ||
+           (type & _LWS_ADOPT_FINISH))
+               return 0; /* no match */
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
+       if (type & LWS_ADOPT_FLAG_UDP)
+               /*
+                * these can be >128 bytes, so just alloc for UDP
+                */
+               wsi->udp = lws_malloc(sizeof(*wsi->udp), "udp struct");
+#endif
+
+       lws_role_transition(wsi, 0, (type & LWS_ADOPT_ALLOW_SSL) ? LRS_SSL_INIT :
+                               LRS_ESTABLISHED, &role_ops_raw_skt);
+
+       if (vh_prot_name)
+               lws_bind_protocol(wsi, wsi->protocol, __func__);
+       else
+               /* this is the only time he will transition */
+               lws_bind_protocol(wsi,
+                       &wsi->vhost->protocols[wsi->vhost->raw_protocol_index],
+                       __func__);
+
+       return 1; /* bound */
+}
+#endif
+
+#if !defined(LWS_NO_CLIENT)
+static int
+rops_client_bind_raw_skt(struct lws *wsi,
+                        const struct lws_client_connect_info *i)
+{
+       if (!i) {
+
+               /* finalize */
+
+               if (!wsi->user_space && wsi->stash->method)
+                       if (lws_ensure_user_space(wsi))
+                               return 1;
+
+               return 0;
+       }
+
+       /* we are a fallback if nothing else matched */
+
+       lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED,
+                           &role_ops_raw_skt);
+
+       return 1; /* matched */
+}
+#endif
+
+struct lws_role_ops role_ops_raw_skt = {
+       /* role name */                 "raw-skt",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                NULL,
+       /* destroy_vhost */             NULL,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      NULL,
+       /* handle_POLLIN */             rops_handle_POLLIN_raw_skt,
+       /* handle_POLLOUT */            NULL,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      NULL,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       NULL,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   NULL,
+       /* close_role */                NULL,
+       /* close_kill_connection */     NULL,
+       /* destroy_role */              NULL,
+#if !defined(LWS_NO_SERVER)
+       /* adoption_bind */             rops_adoption_bind_raw_skt,
+#else
+                                       NULL,
+#endif
+#if !defined(LWS_NO_CLIENT)
+       /* client_bind */               rops_client_bind_raw_skt,
+#else
+                                       NULL,
+#endif
+       /* adoption_cb clnt, srv */     { LWS_CALLBACK_RAW_CONNECTED,
+                                         LWS_CALLBACK_RAW_ADOPT },
+       /* rx_cb clnt, srv */           { LWS_CALLBACK_RAW_RX,
+                                         LWS_CALLBACK_RAW_RX },
+       /* writeable cb clnt, srv */    { LWS_CALLBACK_RAW_WRITEABLE,
+                                         LWS_CALLBACK_RAW_WRITEABLE},
+       /* close cb clnt, srv */        { LWS_CALLBACK_RAW_CLOSE,
+                                         LWS_CALLBACK_RAW_CLOSE },
+       /* protocol_bind cb c, srv */   { LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL,
+                                         LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL },
+       /* protocol_unbind cb c, srv */ { LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL,
+                                         LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL },
+       /* file_handle */               0,
+};
diff --git a/lib/roles/ws/client-parser-ws.c b/lib/roles/ws/client-parser-ws.c
new file mode 100644 (file)
index 0000000..299ba03
--- /dev/null
@@ -0,0 +1,703 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * parsers.c: lws_ws_rx_sm() needs to be roughly kept in
+ *   sync with changes here, esp related to ext draining
+ */
+
+int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int callback_action = LWS_CALLBACK_CLIENT_RECEIVE;
+       struct lws_ext_pm_deflate_rx_ebufs pmdrx;
+       unsigned short close_code;
+       unsigned char *pp;
+       int handled, m, n;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       int rx_draining_ext = 0;
+#endif
+
+       pmdrx.eb_in.token = NULL;
+       pmdrx.eb_in.len = 0;
+       pmdrx.eb_out.token = NULL;
+       pmdrx.eb_out.len = 0;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       if (wsi->ws->rx_draining_ext) {
+               assert(!c);
+
+               lws_remove_wsi_from_draining_ext_list(wsi);
+               rx_draining_ext = 1;
+               lwsl_debug("%s: doing draining flow\n", __func__);
+
+               goto drain_extension;
+       }
+#endif
+
+       if (wsi->socket_is_permanently_unusable)
+               return -1;
+
+       switch (wsi->lws_rx_parse_state) {
+       case LWS_RXPS_NEW:
+               /* control frames (PING) may interrupt checkable sequences */
+               wsi->ws->defeat_check_utf8 = 0;
+
+               switch (wsi->ws->ietf_spec_revision) {
+               case 13:
+                       wsi->ws->opcode = c & 0xf;
+                       /* revisit if an extension wants them... */
+                       switch (wsi->ws->opcode) {
+                       case LWSWSOPC_TEXT_FRAME:
+                               wsi->ws->rsv_first_msg = (c & 0x70);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                               /*
+                                * set the expectation that we will have to
+                                * fake up the zlib trailer to the inflator for
+                                * this frame
+                                */
+                               wsi->ws->pmd_trailer_application = !!(c & 0x40);
+#endif
+                               wsi->ws->continuation_possible = 1;
+                               wsi->ws->check_utf8 = lws_check_opt(
+                                       wsi->context->options,
+                                       LWS_SERVER_OPTION_VALIDATE_UTF8);
+                               wsi->ws->utf8 = 0;
+                               wsi->ws->first_fragment = 1;
+                               break;
+                       case LWSWSOPC_BINARY_FRAME:
+                               wsi->ws->rsv_first_msg = (c & 0x70);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                               /*
+                                * set the expectation that we will have to
+                                * fake up the zlib trailer to the inflator for
+                                * this frame
+                                */
+                               wsi->ws->pmd_trailer_application = !!(c & 0x40);
+#endif
+                               wsi->ws->check_utf8 = 0;
+                               wsi->ws->continuation_possible = 1;
+                               wsi->ws->first_fragment = 1;
+                               break;
+                       case LWSWSOPC_CONTINUATION:
+                               if (!wsi->ws->continuation_possible) {
+                                       lwsl_info("disordered continuation\n");
+                                       return -1;
+                               }
+                               wsi->ws->first_fragment = 0;
+                               break;
+                       case LWSWSOPC_CLOSE:
+                               wsi->ws->check_utf8 = 0;
+                               wsi->ws->utf8 = 0;
+                               break;
+                       case 3:
+                       case 4:
+                       case 5:
+                       case 6:
+                       case 7:
+                       case 0xb:
+                       case 0xc:
+                       case 0xd:
+                       case 0xe:
+                       case 0xf:
+                               lwsl_info("illegal opcode\n");
+                               return -1;
+                       default:
+                               wsi->ws->defeat_check_utf8 = 1;
+                               break;
+                       }
+                       wsi->ws->rsv = (c & 0x70);
+                       /* revisit if an extension wants them... */
+                       if (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                               !wsi->ws->count_act_ext &&
+#endif
+                               wsi->ws->rsv) {
+                               lwsl_info("illegal rsv bits set\n");
+                               return -1;
+                       }
+                       wsi->ws->final = !!((c >> 7) & 1);
+                       lwsl_ext("%s:    This RX frame Final %d\n", __func__,
+                                wsi->ws->final);
+
+                       if (wsi->ws->owed_a_fin &&
+                           (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+                            wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) {
+                               lwsl_info("hey you owed us a FIN\n");
+                               return -1;
+                       }
+                       if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) {
+                               wsi->ws->continuation_possible = 0;
+                               wsi->ws->owed_a_fin = 0;
+                       }
+
+                       if ((wsi->ws->opcode & 8) && !wsi->ws->final) {
+                               lwsl_info("control msg can't be fragmented\n");
+                               return -1;
+                       }
+                       if (!wsi->ws->final)
+                               wsi->ws->owed_a_fin = 1;
+
+                       switch (wsi->ws->opcode) {
+                       case LWSWSOPC_TEXT_FRAME:
+                       case LWSWSOPC_BINARY_FRAME:
+                               wsi->ws->frame_is_binary = wsi->ws->opcode ==
+                                                LWSWSOPC_BINARY_FRAME;
+                               break;
+                       }
+                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
+                       break;
+
+               default:
+                       lwsl_err("unknown spec version %02d\n",
+                                wsi->ws->ietf_spec_revision);
+                       break;
+               }
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN:
+
+               wsi->ws->this_frame_masked = !!(c & 0x80);
+
+               switch (c & 0x7f) {
+               case 126:
+                       /* control frames are not allowed to have big lengths */
+                       if (wsi->ws->opcode & 8)
+                               goto illegal_ctl_length;
+                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
+                       break;
+               case 127:
+                       /* control frames are not allowed to have big lengths */
+                       if (wsi->ws->opcode & 8)
+                               goto illegal_ctl_length;
+                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
+                       break;
+               default:
+                       wsi->ws->rx_packet_length = c & 0x7f;
+                       if (wsi->ws->this_frame_masked)
+                               wsi->lws_rx_parse_state =
+                                               LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+                       else {
+                               if (wsi->ws->rx_packet_length) {
+                                       wsi->lws_rx_parse_state =
+                                       LWS_RXPS_WS_FRAME_PAYLOAD;
+                               } else {
+                                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                                       goto spill;
+                               }
+                       }
+                       break;
+               }
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN16_2:
+               wsi->ws->rx_packet_length = c << 8;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN16_1:
+               wsi->ws->rx_packet_length |= c;
+               if (wsi->ws->this_frame_masked)
+                       wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+               else {
+                       if (wsi->ws->rx_packet_length)
+                               wsi->lws_rx_parse_state =
+                                       LWS_RXPS_WS_FRAME_PAYLOAD;
+                       else {
+                               wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                               goto spill;
+                       }
+               }
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_8:
+               if (c & 0x80) {
+                       lwsl_warn("b63 of length must be zero\n");
+                       /* kill the connection */
+                       return -1;
+               }
+#if defined __LP64__
+               wsi->ws->rx_packet_length = ((size_t)c) << 56;
+#else
+               wsi->ws->rx_packet_length = 0;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_7:
+#if defined __LP64__
+               wsi->ws->rx_packet_length |= ((size_t)c) << 48;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_6:
+#if defined __LP64__
+               wsi->ws->rx_packet_length |= ((size_t)c) << 40;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_5:
+#if defined __LP64__
+               wsi->ws->rx_packet_length |= ((size_t)c) << 32;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_4:
+               wsi->ws->rx_packet_length |= ((size_t)c) << 24;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_3:
+               wsi->ws->rx_packet_length |= ((size_t)c) << 16;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_2:
+               wsi->ws->rx_packet_length |= ((size_t)c) << 8;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_1:
+               wsi->ws->rx_packet_length |= (size_t)c;
+               if (wsi->ws->this_frame_masked)
+                       wsi->lws_rx_parse_state =
+                                       LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+               else {
+                       if (wsi->ws->rx_packet_length)
+                               wsi->lws_rx_parse_state =
+                                       LWS_RXPS_WS_FRAME_PAYLOAD;
+                       else {
+                               wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                               goto spill;
+                       }
+               }
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
+               wsi->ws->mask[0] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
+               wsi->ws->mask[1] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
+               wsi->ws->mask[2] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
+               wsi->ws->mask[3] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+
+               if (wsi->ws->rx_packet_length)
+                       wsi->lws_rx_parse_state =
+                                       LWS_RXPS_WS_FRAME_PAYLOAD;
+               else {
+                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                       goto spill;
+               }
+               break;
+
+       case LWS_RXPS_WS_FRAME_PAYLOAD:
+
+               assert(wsi->ws->rx_ubuf);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws->rx_draining_ext)
+                       goto drain_extension;
+#endif
+               if (wsi->ws->this_frame_masked && !wsi->ws->all_zero_nonce)
+                       c ^= wsi->ws->mask[(wsi->ws->mask_idx++) & 3];
+
+               /*
+                * unmask and collect the payload body in
+                * rx_ubuf_head + LWS_PRE
+                */
+
+               wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] = c;
+
+               if (--wsi->ws->rx_packet_length == 0) {
+                       /* spill because we have the whole frame */
+                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                       lwsl_debug("%s: spilling as we have the whole frame\n",
+                                       __func__);
+                       goto spill;
+               }
+
+               /*
+                * if there's no protocol max frame size given, we are
+                * supposed to default to context->pt_serv_buf_size
+                */
+               if (!wsi->protocol->rx_buffer_size &&
+                   wsi->ws->rx_ubuf_head != wsi->context->pt_serv_buf_size)
+                       break;
+
+               if (wsi->protocol->rx_buffer_size &&
+                   wsi->ws->rx_ubuf_head != wsi->protocol->rx_buffer_size)
+                       break;
+
+               /* spill because we filled our rx buffer */
+
+               lwsl_debug("%s: spilling as we filled our rx buffer\n",
+                               __func__);
+spill:
+
+               handled = 0;
+
+               /*
+                * is this frame a control packet we should take care of at this
+                * layer?  If so service it and hide it from the user callback
+                */
+
+               switch (wsi->ws->opcode) {
+               case LWSWSOPC_CLOSE:
+                       pp = &wsi->ws->rx_ubuf[LWS_PRE];
+                       if (lws_check_opt(wsi->context->options,
+                                         LWS_SERVER_OPTION_VALIDATE_UTF8) &&
+                           wsi->ws->rx_ubuf_head > 2 &&
+                           lws_check_utf8(&wsi->ws->utf8, pp + 2,
+                                          wsi->ws->rx_ubuf_head - 2))
+                               goto utf8_fail;
+
+                       /* is this an acknowledgment of our close? */
+                       if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
+                               /*
+                                * fine he has told us he is closing too, let's
+                                * finish our close
+                                */
+                               lwsl_parser("seen server's close ack\n");
+                               return -1;
+                       }
+
+                       lwsl_parser("client sees server close len = %d\n",
+                                                wsi->ws->rx_ubuf_head);
+                       if (wsi->ws->rx_ubuf_head >= 2) {
+                               close_code = (pp[0] << 8) | pp[1];
+                               if (close_code < 1000 ||
+                                   close_code == 1004 ||
+                                   close_code == 1005 ||
+                                   close_code == 1006 ||
+                                   close_code == 1012 ||
+                                   close_code == 1013 ||
+                                   close_code == 1014 ||
+                                   close_code == 1015 ||
+                                   (close_code >= 1016 && close_code < 3000)
+                               ) {
+                                       pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
+                                       pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
+                               }
+                       }
+                       if (user_callback_handle_rxflow(
+                                       wsi->protocol->callback, wsi,
+                                       LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
+                                       wsi->user_space, pp,
+                                       wsi->ws->rx_ubuf_head))
+                               return -1;
+
+                       memcpy(wsi->ws->ping_payload_buf + LWS_PRE, pp,
+                              wsi->ws->rx_ubuf_head);
+                       wsi->ws->close_in_ping_buffer_len =
+                                       wsi->ws->rx_ubuf_head;
+
+                       lwsl_info("%s: scheduling return close as ack\n",
+                                 __func__);
+                       __lws_change_pollfd(wsi, LWS_POLLIN, 0);
+                       lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 3);
+                       wsi->waiting_to_send_close_frame = 1;
+                       wsi->close_needs_ack = 0;
+                       lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE);
+                       lws_callback_on_writable(wsi);
+                       handled = 1;
+                       break;
+
+               case LWSWSOPC_PING:
+                       lwsl_info("received %d byte ping, sending pong\n",
+                                 wsi->ws->rx_ubuf_head);
+
+                       /* he set a close reason on this guy, ignore PING */
+                       if (wsi->ws->close_in_ping_buffer_len)
+                               goto ping_drop;
+
+                       if (wsi->ws->ping_pending_flag) {
+                               /*
+                                * there is already a pending ping payload
+                                * we should just log and drop
+                                */
+                               lwsl_parser("DROP PING since one pending\n");
+                               goto ping_drop;
+                       }
+
+                       /* control packets can only be < 128 bytes long */
+                       if (wsi->ws->rx_ubuf_head > 128 - 3) {
+                               lwsl_parser("DROP PING payload too large\n");
+                               goto ping_drop;
+                       }
+
+                       /* stash the pong payload */
+                       memcpy(wsi->ws->ping_payload_buf + LWS_PRE,
+                              &wsi->ws->rx_ubuf[LWS_PRE],
+                              wsi->ws->rx_ubuf_head);
+
+                       wsi->ws->ping_payload_len = wsi->ws->rx_ubuf_head;
+                       wsi->ws->ping_pending_flag = 1;
+
+                       /* get it sent as soon as possible */
+                       lws_callback_on_writable(wsi);
+ping_drop:
+                       wsi->ws->rx_ubuf_head = 0;
+                       handled = 1;
+                       break;
+
+               case LWSWSOPC_PONG:
+                       lwsl_info("%s: client %p received pong\n", __func__, wsi);
+                       lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE],
+                                    wsi->ws->rx_ubuf_head);
+
+                       if (wsi->ws->await_pong) {
+                               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+                               wsi->ws->await_pong = 0;
+
+                               /*
+                                * prepare to send the ping again if nothing
+                                * sent to countermand it
+                                */
+
+                               __lws_sul_insert(&pt->pt_sul_owner,
+                                                &wsi->sul_ping,
+                                       (lws_usec_t)wsi->context->ws_ping_pong_interval *
+                                        LWS_USEC_PER_SEC);
+                       }
+                       /* issue it */
+                       callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG;
+                       break;
+
+               case LWSWSOPC_CONTINUATION:
+               case LWSWSOPC_TEXT_FRAME:
+               case LWSWSOPC_BINARY_FRAME:
+                       break;
+
+               default:
+                       /* not handled or failed */
+                       lwsl_ext("Unhandled ext opc 0x%x\n", wsi->ws->opcode);
+                       wsi->ws->rx_ubuf_head = 0;
+
+                       return -1;
+               }
+
+               /*
+                * No it's real payload, pass it up to the user callback.
+                *
+                * We have been statefully collecting it in the
+                * LWS_RXPS_WS_FRAME_PAYLOAD clause above.
+                *
+                * It's nicely buffered with the pre-padding taken care of
+                * so it can be sent straight out again using lws_write.
+                *
+                * However, now we have a chunk of it, we want to deal with it
+                * all here.  Since this may be input to permessage-deflate and
+                * there are block limits on that for input and output, we may
+                * need to iterate.
+                */
+               if (handled)
+                       goto already_done;
+
+               pmdrx.eb_in.token = &wsi->ws->rx_ubuf[LWS_PRE];
+               pmdrx.eb_in.len = wsi->ws->rx_ubuf_head;
+
+               /* for the non-pm-deflate case */
+
+               pmdrx.eb_out = pmdrx.eb_in;
+
+               lwsl_debug("%s: starting disbursal of %d deframed rx\n",
+                               __func__, wsi->ws->rx_ubuf_head);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+drain_extension:
+#endif
+               do {
+
+               //      lwsl_notice("%s: pmdrx.eb_in.len: %d\n", __func__,
+               //                  (int)pmdrx.eb_in.len);
+
+                       n = PMDR_DID_NOTHING;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       lwsl_ext("%s: +++ passing %d %p to ext\n", __func__,
+                                pmdrx.eb_in.len, pmdrx.eb_in.token);
+
+                       n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX,
+                                             &pmdrx, 0);
+                       lwsl_ext("Ext RX returned %d\n", n);
+                       if (n < 0) {
+                               wsi->socket_is_permanently_unusable = 1;
+                               return -1;
+                       }
+                       if (n == PMDR_DID_NOTHING)
+                               break;
+#endif
+                       lwsl_ext("%s: post inflate ebuf in len %d / out len %d\n",
+                                   __func__, pmdrx.eb_in.len, pmdrx.eb_out.len);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       if (rx_draining_ext && !pmdrx.eb_out.len) {
+                               lwsl_debug("   --- ending drain on 0 read result\n");
+                               goto already_done;
+                       }
+
+                       if (n == PMDR_HAS_PENDING) {    /* 1 means stuff to drain */
+                               /* extension had more... main loop will come back */
+                               lwsl_ext("%s: adding to draining ext list\n",
+                                           __func__);
+                               lws_add_wsi_to_draining_ext_list(wsi);
+                       } else {
+                               lwsl_ext("%s: removing from draining ext list\n",
+                                           __func__);
+                               lws_remove_wsi_from_draining_ext_list(wsi);
+                       }
+                       rx_draining_ext = wsi->ws->rx_draining_ext;
+#endif
+
+                       if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+
+                               if (lws_check_utf8(&wsi->ws->utf8,
+                                                  pmdrx.eb_out.token,
+                                                  pmdrx.eb_out.len)) {
+                                       lws_close_reason(wsi,
+                                               LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+                                               (uint8_t *)"bad utf8", 8);
+                                       goto utf8_fail;
+                               }
+
+                               /* we are ending partway through utf-8 character? */
+                               if (!wsi->ws->rx_packet_length &&
+                                   wsi->ws->final && wsi->ws->utf8
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                                   /* if ext not negotiated, going to be UNKNOWN */
+                                   && (n == PMDR_EMPTY_FINAL || n == PMDR_UNKNOWN)
+#endif
+                                   ) {
+                                       lwsl_info("FINAL utf8 error\n");
+                                       lws_close_reason(wsi,
+                                               LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+                                               (uint8_t *)"partial utf8", 12);
+utf8_fail:
+                                       lwsl_info("utf8 error\n");
+                                       lwsl_hexdump_info(pmdrx.eb_out.token,
+                                                         pmdrx.eb_out.len);
+
+                                       return -1;
+                               }
+                       }
+
+                       if (pmdrx.eb_out.len < 0 &&
+                           callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG)
+                               goto already_done;
+
+                       if (!pmdrx.eb_out.token)
+                               goto already_done;
+
+                       pmdrx.eb_out.token[pmdrx.eb_out.len] = '\0';
+
+                       if (!wsi->protocol->callback)
+                               goto already_done;
+
+                       if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG)
+                               lwsl_info("Client doing pong callback\n");
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       if (n == PMDR_HAS_PENDING)
+                               /* extension had more... main loop will come back
+                                * we want callback to be done with this set, if so,
+                                * because lws_is_final() hides it was final until the
+                                * last chunk
+                                */
+                               lws_add_wsi_to_draining_ext_list(wsi);
+                       else
+                               lws_remove_wsi_from_draining_ext_list(wsi);
+#endif
+
+                       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+                           lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE ||
+                           lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK)
+                               goto already_done;
+
+                       /* if pmd not enabled, in == out */
+
+                       if (n == PMDR_DID_NOTHING
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                           || n == PMDR_UNKNOWN
+#endif
+                       )
+                               pmdrx.eb_in.len -= pmdrx.eb_out.len;
+
+                       m = wsi->protocol->callback(wsi,
+                                       (enum lws_callback_reasons)callback_action,
+                                       wsi->user_space, pmdrx.eb_out.token,
+                                       pmdrx.eb_out.len);
+
+                       wsi->ws->first_fragment = 0;
+
+                       lwsl_debug("%s: bulk ws rx: inp used %d, output %d\n",
+                                   __func__, wsi->ws->rx_ubuf_head,
+                                   pmdrx.eb_out.len);
+
+                       /* if user code wants to close, let caller know */
+                       if (m)
+                               return 1;
+
+               } while (pmdrx.eb_in.len
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       || rx_draining_ext
+#endif
+               );
+
+already_done:
+               wsi->ws->rx_ubuf_head = 0;
+               break;
+       default:
+               lwsl_err("client rx illegal state\n");
+               return 1;
+       }
+
+       return 0;
+
+illegal_ctl_length:
+       lwsl_warn("Control frame asking for extended length is illegal\n");
+
+       /* kill the connection */
+       return -1;
+}
+
+
diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c
new file mode 100644 (file)
index 0000000..61a59d5
--- /dev/null
@@ -0,0 +1,685 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+/*
+ * In-place str to lower case
+ */
+
+static void
+strtolower(char *s)
+{
+       while (*s) {
+#ifdef LWS_PLAT_OPTEE
+               int tolower_optee(int c);
+               *s = tolower_optee((int)*s);
+#else
+               *s = tolower((int)*s);
+#endif
+               s++;
+       }
+}
+
+int
+lws_create_client_ws_object(const struct lws_client_connect_info *i,
+                           struct lws *wsi)
+{
+       int v = SPEC_LATEST_SUPPORTED;
+
+       /* allocate the ws struct for the wsi */
+       wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct");
+       if (!wsi->ws) {
+               lwsl_notice("OOM\n");
+               return 1;
+       }
+
+       /* -1 means just use latest supported */
+       if (i->ietf_version_or_minus_one != -1 &&
+           i->ietf_version_or_minus_one)
+               v = i->ietf_version_or_minus_one;
+
+       wsi->ws->ietf_spec_revision = v;
+
+       return 0;
+}
+
+#if !defined(LWS_NO_CLIENT)
+int
+lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
+{
+       unsigned char *bufin = *buf;
+
+       if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) &&
+           (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) &&
+           (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) &&
+           !lwsi_role_client(wsi))
+               return 0;
+
+       lwsl_debug("%s: hs client feels it has %d in\n", __func__, (int)len);
+
+       while (len) {
+               /*
+                * we were accepting input but now we stopped doing so
+                */
+               if (lws_is_flowcontrolled(wsi)) {
+                       lwsl_debug("%s: caching %ld\n", __func__, (long)len);
+                       /*
+                        * Since we cached the remaining available input, we
+                        * can say we "consumed" it.
+                        *
+                        * But what about the case where the available input
+                        * came out of the rxflow cache already?  If we are
+                        * effectively "putting it back in the cache", we have
+                        * to place it at the cache head, not the tail as usual.
+                        */
+                       if (lws_rxflow_cache(wsi, *buf, 0, (int)len) ==
+                                                       LWSRXFC_TRIMMED) {
+                               /*
+                                * we dealt with it by trimming the existing
+                                * rxflow cache HEAD to account for what we used.
+                                *
+                                * indicate we didn't use anything to the caller
+                                * so he doesn't do any consumed processing
+                                */
+                               lwsl_info("%s: trimming inside rxflow cache\n",
+                                               __func__);
+                               *buf = bufin;
+                       } else
+                               *buf += len;
+
+                       return 0;
+               }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws->rx_draining_ext) {
+                       int m;
+
+                       lwsl_info("%s: draining ext\n", __func__);
+                       if (lwsi_role_client(wsi))
+                               m = lws_ws_client_rx_sm(wsi, 0);
+                       else
+                               m = lws_ws_rx_sm(wsi, 0, 0);
+                       if (m < 0)
+                               return -1;
+                       continue;
+               }
+#endif
+               /*
+                * caller will account for buflist usage by studying what
+                * happened to *buf
+                */
+
+               if (lws_ws_client_rx_sm(wsi, *(*buf)++)) {
+                       lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n",
+                                   __func__, (int)len);
+                       return -1;
+               }
+               len--;
+       }
+       // lwsl_notice("%s: finished with %ld\n", __func__, (long)len);
+
+       return 0;
+}
+#endif
+
+char *
+lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1)
+{
+       char buf[128], hash[20], key_b64[40];
+       int n;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       const struct lws_extension *ext;
+       int ext_count = 0;
+#endif
+
+       /*
+        * create the random key
+        */
+       n = lws_get_random(wsi->context, hash, 16);
+       if (n != 16) {
+               lwsl_err("Unable to read from random dev %s\n",
+                        SYSTEM_RANDOM_FILEPATH);
+               return NULL;
+       }
+
+       lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64));
+
+       p += sprintf(p, "Upgrade: websocket\x0d\x0a"
+                       "Connection: %sUpgrade\x0d\x0a"
+                       "Sec-WebSocket-Key: ", conn1);
+       strcpy(p, key_b64);
+       p += strlen(key_b64);
+       p += sprintf(p, "\x0d\x0a");
+       if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS))
+               p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a",
+                    lws_hdr_simple_ptr(wsi,
+                                    _WSI_TOKEN_CLIENT_SENT_PROTOCOLS));
+
+       /* tell the server what extensions we could support */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       ext = wsi->vhost->ws.extensions;
+       while (ext && ext->callback) {
+
+               n = wsi->vhost->protocols[0].callback(wsi,
+                       LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
+                               wsi->user_space, (char *)ext->name, 0);
+
+               /*
+                * zero return from callback means go ahead and allow
+                * the extension, it's what we get if the callback is
+                * unhandled
+                */
+
+               if (n) {
+                       ext++;
+                       continue;
+               }
+
+               /* apply it */
+
+               if (ext_count)
+                       *p++ = ',';
+               else
+                       p += sprintf(p, "Sec-WebSocket-Extensions: ");
+               p += sprintf(p, "%s", ext->client_offer);
+               ext_count++;
+
+               ext++;
+       }
+       if (ext_count)
+               p += sprintf(p, "\x0d\x0a");
+#endif
+
+       if (wsi->ws->ietf_spec_revision)
+               p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a",
+                            wsi->ws->ietf_spec_revision);
+
+       /* prepare the expected server accept response */
+
+       key_b64[39] = '\0'; /* enforce composed length below buf sizeof */
+       n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
+                         key_b64);
+
+       lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash);
+
+       lws_b64_encode_string(hash, 20,
+                 wsi->http.ah->initial_handshake_hash_base64,
+                 sizeof(wsi->http.ah->initial_handshake_hash_base64));
+
+       return p;
+}
+
+int
+lws_client_ws_upgrade(struct lws *wsi, const char **cce)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws_context *context = wsi->context;
+       struct lws_tokenize ts;
+       int n, len, okay = 0;
+       lws_tokenize_elem e;
+       char *p, buf[64];
+       const char *pc;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       char *sb = (char *)&pt->serv_buf[0];
+       const struct lws_ext_options *opts;
+       const struct lws_extension *ext;
+       char ext_name[128];
+       const char *c, *a;
+       int more = 1;
+       char ignore;
+#endif
+
+       if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */
+               lwsl_warn("%s: client ws-over-h2 upgrade not supported yet\n",
+                         __func__);
+               *cce = "HS: h2 / ws upgrade unsupported";
+               goto bail3;
+       }
+
+       if (wsi->http.ah->http_response == 401) {
+               lwsl_warn(
+                      "lws_client_handshake: got bad HTTP response '%d'\n",
+                      wsi->http.ah->http_response);
+               *cce = "HS: ws upgrade unauthorized";
+               goto bail3;
+       }
+
+       if (wsi->http.ah->http_response != 101) {
+               lwsl_warn(
+                      "lws_client_handshake: got bad HTTP response '%d'\n",
+                      wsi->http.ah->http_response);
+               *cce = "HS: ws upgrade response not 101";
+               goto bail3;
+       }
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
+               lwsl_info("no ACCEPT\n");
+               *cce = "HS: ACCEPT missing";
+               goto bail3;
+       }
+
+       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
+       if (!p) {
+               lwsl_info("no UPGRADE\n");
+               *cce = "HS: UPGRADE missing";
+               goto bail3;
+       }
+       strtolower(p);
+       if (strcmp(p, "websocket")) {
+               lwsl_warn(
+                     "lws_client_handshake: got bad Upgrade header '%s'\n", p);
+               *cce = "HS: Upgrade to something other than websocket";
+               goto bail3;
+       }
+
+       /* connection: must have "upgrade" */
+
+       lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+                                   LWS_TOKENIZE_F_MINUS_NONTERM);
+       ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION);
+       if (ts.len <= 0) /* won't fit, or absent */
+               goto bad_conn_format;
+
+       do {
+               e = lws_tokenize(&ts);
+               switch (e) {
+               case LWS_TOKZE_TOKEN:
+                       if (!strncasecmp(ts.token, "upgrade", ts.token_len))
+                               e = LWS_TOKZE_ENDED;
+                       break;
+
+               case LWS_TOKZE_DELIMITER:
+                       break;
+
+               default: /* includes ENDED found by the tokenizer itself */
+bad_conn_format:
+                       lwsl_info("%s: malfored connection '%s'\n",
+                                 __func__, buf);
+                       *cce = "HS: UPGRADE malformed";
+                       goto bail3;
+               }
+       } while (e > 0);
+
+       pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
+       if (!pc) {
+               lwsl_parser("lws_client_int_s_hs: no protocol list\n");
+       } else
+               lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc);
+
+       /*
+        * confirm the protocol the server wants to talk was in the list
+        * of protocols we offered
+        */
+
+       len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
+       if (!len) {
+               lwsl_info("%s: WSI_TOKEN_PROTOCOL is null\n", __func__);
+               /*
+                * no protocol name to work from, if we don't already have one
+                * default to first protocol
+                */
+
+               if (wsi->protocol) {
+                       p = (char *)wsi->protocol->name;
+                       goto identify_protocol;
+               }
+
+               /* no choice but to use the default protocol */
+
+               n = 0;
+               wsi->protocol = &wsi->vhost->protocols[0];
+               goto check_extensions;
+       }
+
+       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
+       len = (int)strlen(p);
+
+       while (pc && *pc && !okay) {
+               if (!strncmp(pc, p, len) &&
+                   (pc[len] == ',' || pc[len] == '\0')) {
+                       okay = 1;
+                       continue;
+               }
+               while (*pc && *pc++ != ',')
+                       ;
+               while (*pc == ' ')
+                       pc++;
+       }
+
+       if (!okay) {
+               lwsl_info("%s: got bad protocol %s\n", __func__, p);
+               *cce = "HS: PROTOCOL malformed";
+               goto bail2;
+       }
+
+identify_protocol:
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       lws_strncpy(wsi->ws->actual_protocol, p,
+                   sizeof(wsi->ws->actual_protocol));
+#endif
+
+       /*
+        * identify the selected protocol struct and set it
+        */
+       n = 0;
+       /* keep client connection pre-bound protocol */
+       if (!lwsi_role_client(wsi))
+               wsi->protocol = NULL;
+
+       while (wsi->vhost->protocols[n].callback) {
+               if (!wsi->protocol &&
+                   strcmp(p, wsi->vhost->protocols[n].name) == 0) {
+                       wsi->protocol = &wsi->vhost->protocols[n];
+                       break;
+               }
+               n++;
+       }
+
+       if (!wsi->vhost->protocols[n].callback) { /* no match */
+               /* if server, that's already fatal */
+               if (!lwsi_role_client(wsi)) {
+                       lwsl_info("%s: fail protocol %s\n", __func__, p);
+                       *cce = "HS: Cannot match protocol";
+                       goto bail2;
+               }
+
+               /* for client, find the index of our pre-bound protocol */
+
+               n = 0;
+               while (wsi->vhost->protocols[n].callback) {
+                       if (wsi->protocol && strcmp(wsi->protocol->name,
+                                  wsi->vhost->protocols[n].name) == 0) {
+                               wsi->protocol = &wsi->vhost->protocols[n];
+                               break;
+                       }
+                       n++;
+               }
+
+               if (!wsi->vhost->protocols[n].callback) {
+                       if (wsi->protocol)
+                               lwsl_err("Failed to match protocol %s\n",
+                                               wsi->protocol->name);
+                       else
+                               lwsl_err("No protocol on client\n");
+                       *cce = "ws protocol no match";
+                       goto bail2;
+               }
+       }
+
+       lwsl_debug("Selected protocol %s\n", wsi->protocol->name);
+
+check_extensions:
+       /*
+        * stitch protocol choice into the vh protocol linked list
+        * We always insert ourselves at the start of the list
+        *
+        * X <-> B
+        * X <-> pAn <-> pB
+        */
+
+       lws_same_vh_protocol_insert(wsi, n);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       /* instantiate the accepted extensions */
+
+       if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
+               lwsl_ext("no client extensions allowed by server\n");
+               goto check_accept;
+       }
+
+       /*
+        * break down the list of server accepted extensions
+        * and go through matching them or identifying bogons
+        */
+
+       if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size,
+                        WSI_TOKEN_EXTENSIONS) < 0) {
+               lwsl_warn("ext list from server failed to copy\n");
+               *cce = "HS: EXT: list too big";
+               goto bail2;
+       }
+
+       c = sb;
+       n = 0;
+       ignore = 0;
+       a = NULL;
+       while (more) {
+
+               if (*c && (*c != ',' && *c != '\t')) {
+                       if (*c == ';') {
+                               ignore = 1;
+                               if (!a)
+                                       a = c + 1;
+                       }
+                       if (ignore || *c == ' ') {
+                               c++;
+                               continue;
+                       }
+
+                       ext_name[n] = *c++;
+                       if (n < (int)sizeof(ext_name) - 1)
+                               n++;
+                       continue;
+               }
+               ext_name[n] = '\0';
+               ignore = 0;
+               if (!*c)
+                       more = 0;
+               else {
+                       c++;
+                       if (!n)
+                               continue;
+               }
+
+               /* check we actually support it */
+
+               lwsl_notice("checking client ext %s\n", ext_name);
+
+               n = 0;
+               ext = wsi->vhost->ws.extensions;
+               while (ext && ext->callback) {
+                       if (strcmp(ext_name, ext->name)) {
+                               ext++;
+                               continue;
+                       }
+
+                       n = 1;
+                       lwsl_notice("instantiating client ext %s\n", ext_name);
+
+                       /* instantiate the extension on this conn */
+
+                       wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext;
+
+                       /* allow him to construct his ext instance */
+
+                       if (ext->callback(lws_get_context(wsi), ext, wsi,
+                                  LWS_EXT_CB_CLIENT_CONSTRUCT,
+                                  (void *)&wsi->ws->act_ext_user[
+                                                       wsi->ws->count_act_ext],
+                                  (void *)&opts, 0)) {
+                               lwsl_info(" ext %s failed construction\n",
+                                         ext_name);
+                               ext++;
+                               continue;
+                       }
+
+                       /*
+                        * allow the user code to override ext defaults if it
+                        * wants to
+                        */
+                       ext_name[0] = '\0';
+                       if (user_callback_handle_rxflow(wsi->protocol->callback,
+                                       wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
+                                       (char *)ext->name, ext_name,
+                                       sizeof(ext_name))) {
+                               *cce = "HS: EXT: failed setting defaults";
+                               goto bail2;
+                       }
+
+                       if (ext_name[0] &&
+                           lws_ext_parse_options(ext, wsi,
+                                                 wsi->ws->act_ext_user[
+                                                       wsi->ws->count_act_ext],
+                                                 opts, ext_name,
+                                                 (int)strlen(ext_name))) {
+                               lwsl_err("%s: unable to parse user defaults '%s'",
+                                        __func__, ext_name);
+                               *cce = "HS: EXT: failed parsing defaults";
+                               goto bail2;
+                       }
+
+                       /*
+                        * give the extension the server options
+                        */
+                       if (a && lws_ext_parse_options(ext, wsi,
+                                       wsi->ws->act_ext_user[
+                                                       wsi->ws->count_act_ext],
+                                       opts, a, lws_ptr_diff(c, a))) {
+                               lwsl_err("%s: unable to parse remote def '%s'",
+                                        __func__, a);
+                               *cce = "HS: EXT: failed parsing options";
+                               goto bail2;
+                       }
+
+                       if (ext->callback(lws_get_context(wsi), ext, wsi,
+                                       LWS_EXT_CB_OPTION_CONFIRM,
+                                     wsi->ws->act_ext_user[wsi->ws->count_act_ext],
+                                     NULL, 0)) {
+                               lwsl_err("%s: ext %s rejects server options %s",
+                                        __func__, ext->name, a);
+                               *cce = "HS: EXT: Rejects server options";
+                               goto bail2;
+                       }
+
+                       wsi->ws->count_act_ext++;
+
+                       ext++;
+               }
+
+               if (n == 0) {
+                       lwsl_warn("Unknown ext '%s'!\n", ext_name);
+                       *cce = "HS: EXT: unknown ext";
+                       goto bail2;
+               }
+
+               a = NULL;
+               n = 0;
+       }
+
+check_accept:
+#endif
+
+       /*
+        * Confirm his accept token is the one we precomputed
+        */
+
+       p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
+       if (strcmp(p, wsi->http.ah->initial_handshake_hash_base64)) {
+               lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p,
+                                 wsi->http.ah->initial_handshake_hash_base64);
+               *cce = "HS: Accept hash wrong";
+               goto bail2;
+       }
+
+       /* allocate the per-connection user memory (if any) */
+       if (lws_ensure_user_space(wsi)) {
+               lwsl_err("Problem allocating wsi user mem\n");
+               *cce = "HS: OOM";
+               goto bail2;
+       }
+
+       /*
+        * we seem to be good to go, give client last chance to check
+        * headers and OK it
+        */
+       if (wsi->protocol->callback(wsi,
+                                   LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
+                                   wsi->user_space, NULL, 0)) {
+               *cce = "HS: Rejected by filter cb";
+               goto bail2;
+       }
+
+       /* clear his proxy connection timeout */
+       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+       /* free up his parsing allocations */
+       lws_header_table_detach(wsi, 0);
+
+       lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED,
+                           &role_ops_ws);
+
+       if (wsi->context->ws_ping_pong_interval && !wsi->http2_substream ) {
+               wsi->sul_ping.cb = lws_sul_wsping_cb;
+               __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping,
+                                (lws_usec_t)wsi->context->ws_ping_pong_interval *
+                                LWS_USEC_PER_SEC);
+       }
+
+       wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
+
+       /*
+        * create the frame buffer for this connection according to the
+        * size mentioned in the protocol definition.  If 0 there, then
+        * use a big default for compatibility
+        */
+       n = (int)wsi->protocol->rx_buffer_size;
+       if (!n)
+               n = context->pt_serv_buf_size;
+       n += LWS_PRE;
+       wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */,
+                               "client frame buffer");
+       if (!wsi->ws->rx_ubuf) {
+               lwsl_err("Out of Mem allocating rx buffer %d\n", n);
+               *cce = "HS: OOM";
+               goto bail2;
+       }
+       wsi->ws->rx_ubuf_alloc = n;
+       lwsl_info("Allocating client RX buffer %d\n", n);
+
+#if !defined(LWS_WITH_ESP32)
+       if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
+                      (const char *)&n, sizeof n)) {
+               lwsl_warn("Failed to set SNDBUF to %d", n);
+               *cce = "HS: SO_SNDBUF failed";
+               goto bail3;
+       }
+#endif
+
+       lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name);
+
+       /* call him back to inform him he is up */
+
+       if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
+                                   wsi->user_space, NULL, 0)) {
+               *cce = "HS: Rejected at CLIENT_ESTABLISHED";
+               goto bail3;
+       }
+
+       return 0;
+
+bail3:
+       return 3;
+
+bail2:
+       return 2;
+}
diff --git a/lib/roles/ws/ext/extension-permessage-deflate.c b/lib/roles/ws/ext/extension-permessage-deflate.c
new file mode 100644 (file)
index 0000000..b18efe1
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+ * ./lib/extension-permessage-deflate.c
+ *
+ *  Copyright (C) 2016 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "extension-permessage-deflate.h"
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#define LWS_ZLIB_MEMLEVEL 8
+
+const struct lws_ext_options lws_ext_pm_deflate_options[] = {
+       /* public RFC7692 settings */
+       { "server_no_context_takeover", EXTARG_NONE },
+       { "client_no_context_takeover", EXTARG_NONE },
+       { "server_max_window_bits",     EXTARG_OPT_DEC },
+       { "client_max_window_bits",     EXTARG_OPT_DEC },
+       /* ones only user code can set */
+       { "rx_buf_size",                EXTARG_DEC },
+       { "tx_buf_size",                EXTARG_DEC },
+       { "compression_level",          EXTARG_DEC },
+       { "mem_level",                  EXTARG_DEC },
+       { NULL, 0 }, /* sentinel */
+};
+
+static void
+lws_extension_pmdeflate_restrict_args(struct lws *wsi,
+                                     struct lws_ext_pm_deflate_priv *priv)
+{
+       int n, extra;
+
+       /* cap the RX buf at the nearest power of 2 to protocol rx buf */
+
+       n = wsi->context->pt_serv_buf_size;
+       if (wsi->protocol->rx_buffer_size)
+               n = (int)wsi->protocol->rx_buffer_size;
+
+       extra = 7;
+       while (n >= 1 << (extra + 1))
+               extra++;
+
+       if (extra < priv->args[PMD_RX_BUF_PWR2]) {
+               priv->args[PMD_RX_BUF_PWR2] = extra;
+               lwsl_info(" Capping pmd rx to %d\n", 1 << extra);
+       }
+}
+
+static unsigned char trail[] = { 0, 0, 0xff, 0xff };
+
+LWS_VISIBLE int
+lws_extension_callback_pm_deflate(struct lws_context *context,
+                                 const struct lws_extension *ext,
+                                 struct lws *wsi,
+                                 enum lws_extension_callback_reasons reason,
+                                 void *user, void *in, size_t len)
+{
+       struct lws_ext_pm_deflate_priv *priv =
+                                    (struct lws_ext_pm_deflate_priv *)user;
+       struct lws_ext_pm_deflate_rx_ebufs *pmdrx =
+                               (struct lws_ext_pm_deflate_rx_ebufs *)in;
+       struct lws_ext_option_arg *oa;
+       int n, ret = 0, was_fin = 0, m;
+       unsigned int pen = 0;
+       int penbits = 0;
+
+       switch (reason) {
+       case LWS_EXT_CB_NAMED_OPTION_SET:
+               oa = in;
+               if (!oa->option_name)
+                       break;
+               lwsl_ext("%s: named option set: %s\n", __func__,
+                        oa->option_name);
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(lws_ext_pm_deflate_options);
+                    n++)
+                       if (!strcmp(lws_ext_pm_deflate_options[n].name,
+                                   oa->option_name))
+                               break;
+
+               if (n == (int)LWS_ARRAY_SIZE(lws_ext_pm_deflate_options))
+                       break;
+               oa->option_index = n;
+
+               /* fallthru */
+
+       case LWS_EXT_CB_OPTION_SET:
+               oa = in;
+               lwsl_ext("%s: option set: idx %d, %s, len %d\n", __func__,
+                        oa->option_index, oa->start, oa->len);
+               if (oa->start)
+                       priv->args[oa->option_index] = atoi(oa->start);
+               else
+                       priv->args[oa->option_index] = 1;
+
+               if (priv->args[PMD_CLIENT_MAX_WINDOW_BITS] == 8)
+                       priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 9;
+
+               lws_extension_pmdeflate_restrict_args(wsi, priv);
+               break;
+
+       case LWS_EXT_CB_OPTION_CONFIRM:
+               if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 ||
+                   priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 ||
+                   priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 ||
+                   priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15)
+                       return -1;
+               break;
+
+       case LWS_EXT_CB_CLIENT_CONSTRUCT:
+       case LWS_EXT_CB_CONSTRUCT:
+
+               n = context->pt_serv_buf_size;
+               if (wsi->protocol->rx_buffer_size)
+                       n = (int)wsi->protocol->rx_buffer_size;
+
+               if (n < 128) {
+                       lwsl_info(" permessage-deflate requires the protocol "
+                                 "(%s) to have an RX buffer >= 128\n",
+                                 wsi->protocol->name);
+                       return -1;
+               }
+
+               /* fill in **user */
+               priv = lws_zalloc(sizeof(*priv), "pmd priv");
+               *((void **)user) = priv;
+               lwsl_ext("%s: LWS_EXT_CB_*CONSTRUCT\n", __func__);
+               memset(priv, 0, sizeof(*priv));
+
+               /* fill in pointer to options list */
+               if (in)
+                       *((const struct lws_ext_options **)in) =
+                                       lws_ext_pm_deflate_options;
+
+               /* fallthru */
+
+       case LWS_EXT_CB_OPTION_DEFAULT:
+
+               /* set the public, RFC7692 defaults... */
+
+               priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0,
+               priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0;
+               priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15;
+               priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15;
+
+               /* ...and the ones the user code can override */
+
+               priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */
+               priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */
+               priv->args[PMD_COMP_LEVEL] = 1;
+               priv->args[PMD_MEM_LEVEL] = 8;
+
+               lws_extension_pmdeflate_restrict_args(wsi, priv);
+               break;
+
+       case LWS_EXT_CB_DESTROY:
+               lwsl_ext("%s: LWS_EXT_CB_DESTROY\n", __func__);
+               lws_free(priv->buf_rx_inflated);
+               lws_free(priv->buf_tx_deflated);
+               if (priv->rx_init)
+                       (void)inflateEnd(&priv->rx);
+               if (priv->tx_init)
+                       (void)deflateEnd(&priv->tx);
+               lws_free(priv);
+
+               return ret;
+
+
+       case LWS_EXT_CB_PAYLOAD_RX:
+               lwsl_ext(" %s: LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d\n",
+                        __func__, pmdrx->eb_in.len, priv->rx.avail_in);
+
+               /* if this frame is not marked as compressed, we ignore it */
+
+               if (!(wsi->ws->rsv_first_msg & 0x40) || (wsi->ws->opcode & 8))
+                       return PMDR_DID_NOTHING;
+
+               /*
+                * we shouldn't come back in here if we already applied the
+                * trailer for this compressed packet
+                */
+               if (!wsi->ws->pmd_trailer_application)
+                       return PMDR_DID_NOTHING;
+
+               pmdrx->eb_out.len = 0;
+
+               lwsl_ext("%s: LWS_EXT_CB_PAYLOAD_RX: in %d, "
+                           "existing avail in %d, pkt fin: %d\n", __func__,
+                                   pmdrx->eb_in.len, priv->rx.avail_in,
+                                   wsi->ws->final);
+
+               /* if needed, initialize the inflator */
+
+               if (!priv->rx_init) {
+                       if (inflateInit2(&priv->rx,
+                            -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) {
+                               lwsl_err("%s: iniflateInit failed\n", __func__);
+                               return PMDR_FAILED;
+                       }
+                       priv->rx_init = 1;
+                       if (!priv->buf_rx_inflated)
+                               priv->buf_rx_inflated = lws_malloc(
+                                       LWS_PRE + 7 + 5 +
+                                           (1 << priv->args[PMD_RX_BUF_PWR2]),
+                                           "pmd rx inflate buf");
+                       if (!priv->buf_rx_inflated) {
+                               lwsl_err("%s: OOM\n", __func__);
+                               return PMDR_FAILED;
+                       }
+               }
+
+#if 0
+               /*
+                * don't give us new input while we still work through
+                * the last input
+                */
+
+               if (priv->rx.avail_in && pmdrx->eb_in.token &&
+                                        pmdrx->eb_in.len) {
+                       lwsl_warn("%s: priv->rx.avail_in %d while getting new in\n",
+                                       __func__, priv->rx.avail_in);
+       //              assert(0);
+               }
+#endif
+               if (!priv->rx.avail_in && pmdrx->eb_in.token && pmdrx->eb_in.len) {
+                       priv->rx.next_in = (unsigned char *)pmdrx->eb_in.token;
+                       priv->rx.avail_in = pmdrx->eb_in.len;
+               }
+
+               priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE;
+               pmdrx->eb_out.token = priv->rx.next_out;
+               priv->rx.avail_out = 1 << priv->args[PMD_RX_BUF_PWR2];
+
+               pen = penbits = 0;
+               deflatePending(&priv->rx, &pen, &penbits);
+               pen |= penbits;
+
+               /* so... if...
+                *
+                *  - he has no remaining input content for this message, and
+                *  - and this is the final fragment, and
+                *  - we used everything that could be drained on the input side
+                *
+                * ...then put back the 00 00 FF FF the sender stripped as our
+                * input to zlib
+                */
+               if (!priv->rx.avail_in &&
+                   wsi->ws->final &&
+                   !wsi->ws->rx_packet_length &&
+                   wsi->ws->pmd_trailer_application) {
+                       lwsl_ext("%s: trailer apply 1\n", __func__);
+                       was_fin = 1;
+                       wsi->ws->pmd_trailer_application = 0;
+                       priv->rx.next_in = trail;
+                       priv->rx.avail_in = sizeof(trail);
+               }
+
+               /*
+                * if after all that there's nothing pending and nothing to give
+                * him right now, bail without having done anything
+                */
+
+               if (!priv->rx.avail_in && !pen)
+                       return PMDR_DID_NOTHING;
+
+               n = inflate(&priv->rx, was_fin ? Z_SYNC_FLUSH : Z_NO_FLUSH);
+               lwsl_ext("inflate ret %d, avi %d, avo %d, wsifinal %d\n", n,
+                        priv->rx.avail_in, priv->rx.avail_out, wsi->ws->final);
+               switch (n) {
+               case Z_NEED_DICT:
+               case Z_STREAM_ERROR:
+               case Z_DATA_ERROR:
+               case Z_MEM_ERROR:
+                       lwsl_err("%s: zlib error inflate %d: \"%s\"\n",
+                                 __func__, n, priv->rx.msg);
+                       return PMDR_FAILED;
+               }
+
+               /*
+                * track how much input was used, and advance it
+                */
+
+               pmdrx->eb_in.token = pmdrx->eb_in.token +
+                                        (pmdrx->eb_in.len - priv->rx.avail_in);
+               pmdrx->eb_in.len = priv->rx.avail_in;
+
+               pen = penbits = 0;
+               deflatePending(&priv->rx, &pen, &penbits);
+               pen |= penbits;
+
+               lwsl_debug("%s: %d %d %d %d %d %d\n", __func__,
+                               priv->rx.avail_in,
+                               wsi->ws->final,
+                               (int)wsi->ws->rx_packet_length,
+                               was_fin,
+                               wsi->ws->pmd_trailer_application,
+                               pen);
+
+               if (!priv->rx.avail_in &&
+                   wsi->ws->final &&
+                   !wsi->ws->rx_packet_length &&
+                   !was_fin &&
+                   wsi->ws->pmd_trailer_application &&
+                   !pen
+               ) {
+                       lwsl_ext("%s: RX trailer apply 2\n", __func__);
+
+                       /* we overallocated just for this situation where
+                        * we might issue something */
+                       priv->rx.avail_out += 5;
+
+                       was_fin = 1;
+                       wsi->ws->pmd_trailer_application = 0;
+                       priv->rx.next_in = trail;
+                       priv->rx.avail_in = sizeof(trail);
+                       n = inflate(&priv->rx, Z_SYNC_FLUSH);
+                       lwsl_ext("RX trailer infl ret %d, avi %d, avo %d\n",
+                                   n, priv->rx.avail_in, priv->rx.avail_out);
+                       switch (n) {
+                       case Z_NEED_DICT:
+                       case Z_STREAM_ERROR:
+                       case Z_DATA_ERROR:
+                       case Z_MEM_ERROR:
+                               lwsl_info("zlib error inflate %d: %s\n",
+                                         n, priv->rx.msg);
+                               return -1;
+                       }
+
+                       assert(priv->rx.avail_out);
+
+                       pen = penbits = 0;
+                       deflatePending(&priv->rx, &pen, &penbits);
+                       pen |= penbits;
+               }
+
+               pmdrx->eb_out.len = lws_ptr_diff(priv->rx.next_out,
+                                                pmdrx->eb_out.token);
+               priv->count_rx_between_fin += pmdrx->eb_out.len;
+
+               lwsl_ext("  %s: RX leaving with new effbuff len %d, "
+                        "rx.avail_in=%d, TOTAL RX since FIN %lu\n",
+                        __func__, pmdrx->eb_out.len, priv->rx.avail_in,
+                        (unsigned long)priv->count_rx_between_fin);
+
+               if (was_fin && !pen) {
+                       lwsl_ext("%s: was_fin\n", __func__);
+                       priv->count_rx_between_fin = 0;
+                       if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) {
+                               lwsl_ext("PMD_SERVER_NO_CONTEXT_TAKEOVER\n");
+                               (void)inflateEnd(&priv->rx);
+                               priv->rx_init = 0;
+                       }
+
+                       return PMDR_EMPTY_FINAL;
+               }
+
+               if (pen || priv->rx.avail_in)
+                       return PMDR_HAS_PENDING;
+
+               return PMDR_EMPTY_NONFINAL;
+
+       case LWS_EXT_CB_PAYLOAD_TX:
+
+               /* initialize us if needed */
+
+               if (!priv->tx_init) {
+                       n = deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL],
+                                        Z_DEFLATED,
+                                        -priv->args[PMD_SERVER_MAX_WINDOW_BITS +
+                                               (wsi->vhost->listen_port <= 0)],
+                                        priv->args[PMD_MEM_LEVEL],
+                                        Z_DEFAULT_STRATEGY);
+                       if (n != Z_OK) {
+                               lwsl_ext("inflateInit2 failed %d\n", n);
+                               return PMDR_FAILED;
+                       }
+                       priv->tx_init = 1;
+               }
+
+               if (!priv->buf_tx_deflated)
+                       priv->buf_tx_deflated = lws_malloc(LWS_PRE + 7 + 5 +
+                                           (1 << priv->args[PMD_TX_BUF_PWR2]),
+                                           "pmd tx deflate buf");
+               if (!priv->buf_tx_deflated) {
+                       lwsl_err("%s: OOM\n", __func__);
+                       return PMDR_FAILED;
+               }
+
+               /* hook us up with any deflated input that the caller has */
+
+               if (pmdrx->eb_in.token) {
+
+                       assert(!priv->tx.avail_in);
+
+                       priv->count_tx_between_fin += pmdrx->eb_in.len;
+                       lwsl_ext("%s: TX: eb_in length %d, "
+                                   "TOTAL TX since FIN: %d\n", __func__,
+                                   pmdrx->eb_in.len,
+                                   (int)priv->count_tx_between_fin);
+                       priv->tx.next_in = (unsigned char *)pmdrx->eb_in.token;
+                       priv->tx.avail_in = pmdrx->eb_in.len;
+               }
+
+               priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5;
+               pmdrx->eb_out.token = priv->tx.next_out;
+               priv->tx.avail_out = 1 << priv->args[PMD_TX_BUF_PWR2];
+
+               pen = penbits = 0;
+               deflatePending(&priv->tx, &pen, &penbits);
+               pen |= penbits;
+
+               if (!priv->tx.avail_in && (len & LWS_WRITE_NO_FIN)) {
+                       lwsl_ext("%s: no available in, pen: %u\n", __func__, pen);
+
+                       if (!pen)
+                               return PMDR_DID_NOTHING;
+               }
+
+               m = Z_NO_FLUSH;
+               if (!(len & LWS_WRITE_NO_FIN)) {
+                       lwsl_ext("%s: deflate with SYNC_FLUSH, pkt len %d\n",
+                                       __func__, (int)wsi->ws->rx_packet_length);
+                       m = Z_SYNC_FLUSH;
+               }
+
+               n = deflate(&priv->tx, m);
+               if (n == Z_STREAM_ERROR) {
+                       lwsl_notice("%s: Z_STREAM_ERROR\n", __func__);
+                       return PMDR_FAILED;
+               }
+
+               pen = (!priv->tx.avail_out) && n != Z_STREAM_END;
+
+               lwsl_ext("%s: deflate ret %d, len 0x%x\n", __func__, n,
+                               (unsigned int)len);
+
+               if ((len & 0xf) == LWS_WRITE_TEXT)
+                       priv->tx_first_frame_type = LWSWSOPC_TEXT_FRAME;
+               if ((len & 0xf) == LWS_WRITE_BINARY)
+                       priv->tx_first_frame_type = LWSWSOPC_BINARY_FRAME;
+
+               pmdrx->eb_out.len = lws_ptr_diff(priv->tx.next_out,
+                                                pmdrx->eb_out.token);
+
+               if (m == Z_SYNC_FLUSH && !(len & LWS_WRITE_NO_FIN) && !pen &&
+                   pmdrx->eb_out.len < 4) {
+                       lwsl_err("%s: FAIL want to trim out length %d\n",
+                                       __func__, (int)pmdrx->eb_out.len);
+                       assert(0);
+               }
+
+               if (!(len & LWS_WRITE_NO_FIN) &&
+                   m == Z_SYNC_FLUSH &&
+                   !pen &&
+                   pmdrx->eb_out.len >= 4) {
+                       // lwsl_err("%s: Trimming 4 from end of write\n", __func__);
+                       priv->tx.next_out -= 4;
+                       priv->tx.avail_out += 4;
+                       priv->count_tx_between_fin = 0;
+
+                       assert(priv->tx.next_out[0] == 0x00 &&
+                              priv->tx.next_out[1] == 0x00 &&
+                              priv->tx.next_out[2] == 0xff &&
+                              priv->tx.next_out[3] == 0xff);
+               }
+
+
+               /*
+                * track how much input was used and advance it
+                */
+
+               pmdrx->eb_in.token = pmdrx->eb_in.token +
+                                       (pmdrx->eb_in.len - priv->tx.avail_in);
+               pmdrx->eb_in.len = priv->tx.avail_in;
+
+               priv->compressed_out = 1;
+               pmdrx->eb_out.len = lws_ptr_diff(priv->tx.next_out,
+                                                pmdrx->eb_out.token);
+
+               lwsl_ext("  TX rewritten with new eb_in len %d, "
+                               "eb_out len %d, deflatePending %d\n",
+                               pmdrx->eb_in.len, pmdrx->eb_out.len, pen);
+
+               if (pmdrx->eb_in.len || pen)
+                       return PMDR_HAS_PENDING;
+
+               if (!(len & LWS_WRITE_NO_FIN))
+                       return PMDR_EMPTY_FINAL;
+
+               return PMDR_EMPTY_NONFINAL;
+
+       case LWS_EXT_CB_PACKET_TX_PRESEND:
+               if (!priv->compressed_out)
+                       break;
+               priv->compressed_out = 0;
+
+               /*
+                * we may have not produced any output for the actual "first"
+                * write... in that case, we need to fix up the inappropriate
+                * use of CONTINUATION when the first real write does come.
+                */
+               if (priv->tx_first_frame_type & 0xf) {
+                       *pmdrx->eb_in.token = ((*pmdrx->eb_in.token) & ~0xf) |
+                                       (priv->tx_first_frame_type & 0xf);
+                       /*
+                        * We have now written the "first" fragment, only
+                        * do that once
+                        */
+                       priv->tx_first_frame_type = 0;
+               }
+
+               n = *(pmdrx->eb_in.token) & 15;
+
+               /* set RSV1, but not on CONTINUATION */
+               if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME)
+                       *pmdrx->eb_in.token |= 0x40;
+
+               lwsl_ext("%s: PRESEND compressed: ws frame 0x%02X, len %d\n",
+                           __func__, ((*pmdrx->eb_in.token) & 0xff),
+                           pmdrx->eb_in.len);
+
+               if (((*pmdrx->eb_in.token) & 0x80) &&   /* fin */
+                   priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) {
+                       lwsl_debug("PMD_CLIENT_NO_CONTEXT_TAKEOVER\n");
+                       (void)deflateEnd(&priv->tx);
+                       priv->tx_init = 0;
+               }
+
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
similarity index 79%
rename from lib/extension-permessage-deflate.h
rename to lib/roles/ws/ext/extension-permessage-deflate.h
index 8737736..7c56020 100644 (file)
@@ -1,5 +1,9 @@
 
+#if defined(LWS_WITH_MINIZ)
+#include <miniz.h>
+#else
 #include <zlib.h>
+#endif
 
 #define DEFLATE_FRAME_COMPRESSION_LEVEL_SERVER 1
 #define DEFLATE_FRAME_COMPRESSION_LEVEL_CLIENT Z_DEFAULT_COMPRESSION
@@ -24,18 +28,19 @@ struct lws_ext_pm_deflate_priv {
        unsigned char *buf_rx_inflated; /* RX inflated output buffer */
        unsigned char *buf_tx_deflated; /* TX deflated output buffer */
 
+       unsigned char *buf_tx_holding;
+
        size_t count_rx_between_fin;
+       size_t count_tx_between_fin;
+
+       size_t len_tx_holding;
 
        unsigned char args[PMD_ARG_COUNT];
-       unsigned char tx_held[5];
-       unsigned char rx_held;
+
+       unsigned char tx_first_frame_type;
 
        unsigned char tx_init:1;
        unsigned char rx_init:1;
        unsigned char compressed_out:1;
-       unsigned char rx_held_valid:1;
-       unsigned char tx_held_valid:1;
-       unsigned char rx_append_trailer:1;
-       unsigned char pending_tx_trailer:1;
 };
 
similarity index 70%
rename from lib/extension.c
rename to lib/roles/ws/ext/extension.c
index ac28204..a8bb1c7 100644 (file)
@@ -1,9 +1,9 @@
-#include "private-libwebsockets.h"
+#include "core/private.h"
 
 #include "extension-permessage-deflate.h"
 
 LWS_VISIBLE void
-lws_context_init_extensions(struct lws_context_creation_info *info,
+lws_context_init_extensions(const struct lws_context_creation_info *info,
                            struct lws_context *context)
 {
        lwsl_info(" LWS_MAX_EXTENSIONS_ACTIVE: %u\n", LWS_MAX_EXTENSIONS_ACTIVE);
@@ -54,26 +54,31 @@ lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi,
                        n = 0;
                        pending_close_quote = 0;
                        while (m) {
-                               if (m & 1) {
-                                       lwsl_ext("    m=%d, n=%d, w=%d\n", m, n, w);
-
-                                       if (*in == opts[n].name[w]) {
-                                               if (!opts[n].name[w + 1]) {
-                                                       oa.option_index = n;
-                                                       lwsl_ext("hit %d\n", oa.option_index);
-                                                       leap = LEAPS_SEEK_VAL;
-                                                       if (len == 1)
-                                                               goto set_arg;
-                                                       break;
-                                               }
-                                       } else {
-                                               match_map &= ~(1 << n);
-                                               if (!match_map) {
-                                                       lwsl_ext("empty match map\n");
-                                                       return -1;
-                                               }
+                               if (!(m & 1)) {
+                                       m >>= 1;
+                                       n++;
+                                       continue;
+                               }
+                               lwsl_ext("    m=%d, n=%d, w=%d\n", m, n, w);
+
+                               if (*in == opts[n].name[w]) {
+                                       if (!opts[n].name[w + 1]) {
+                                               oa.option_index = n;
+                                               lwsl_ext("hit %d\n",
+                                                        oa.option_index);
+                                               leap = LEAPS_SEEK_VAL;
+                                               if (len == 1)
+                                                       goto set_arg;
+                                               break;
+                                       }
+                               } else {
+                                       match_map &= ~(1 << n);
+                                       if (!match_map) {
+                                               lwsl_ext("empty match map\n");
+                                               return -1;
                                        }
                                }
+
                                m >>= 1;
                                n++;
                        }
@@ -121,7 +126,7 @@ lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi,
                                return -1;
                        leap = LEAPS_SEEK_ARG_TERM;
                        if (oa.start)
-                               oa.len = in - oa.start;
+                               oa.len = lws_ptr_diff(in, oa.start);
                        if (len == 1)
                                oa.len++;
 
@@ -163,18 +168,21 @@ int lws_ext_cb_active(struct lws *wsi, int reason, void *arg, int len)
 {
        int n, m, handled = 0;
 
-       for (n = 0; n < wsi->count_act_ext; n++) {
-               m = wsi->active_extensions[n]->callback(lws_get_context(wsi),
-                       wsi->active_extensions[n], wsi, reason,
-                       wsi->act_ext_user[n], arg, len);
+       if (!wsi->ws)
+               return 0;
+
+       for (n = 0; n < wsi->ws->count_act_ext; n++) {
+               m = wsi->ws->active_extensions[n]->callback(
+                       lws_get_context(wsi), wsi->ws->active_extensions[n],
+                       wsi, reason, wsi->ws->act_ext_user[n], arg, len);
                if (m < 0) {
                        lwsl_ext("Ext '%s' failed to handle callback %d!\n",
-                                wsi->active_extensions[n]->name, reason);
+                                wsi->ws->active_extensions[n]->name, reason);
                        return -1;
                }
                /* valgrind... */
                if (reason == LWS_EXT_CB_DESTROY)
-                       wsi->act_ext_user[n] = NULL;
+                       wsi->ws->act_ext_user[n] = NULL;
                if (m > handled)
                        handled = m;
        }
@@ -188,17 +196,17 @@ int lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi,
        int n = 0, m, handled = 0;
        const struct lws_extension *ext;
 
-       if (!wsi || !wsi->vhost)
+       if (!wsi || !wsi->vhost || !wsi->ws)
                return 0;
 
-       ext = wsi->vhost->extensions;
+       ext = wsi->vhost->ws.extensions;
 
        while (ext && ext->callback && !handled) {
                m = ext->callback(context, ext, wsi, reason,
                                  (void *)(lws_intptr_t)n, arg, len);
                if (m < 0) {
                        lwsl_ext("Ext '%s' failed to handle callback %d!\n",
-                                wsi->active_extensions[n]->name, reason);
+                                wsi->ws->active_extensions[n]->name, reason);
                        return -1;
                }
                if (m)
@@ -214,11 +222,11 @@ int lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi,
 int
 lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len)
 {
-       struct lws_tokens eff_buf;
+       struct lws_tokens ebuf;
        int ret, m, n = 0;
 
-       eff_buf.token = (char *)buf;
-       eff_buf.token_len = len;
+       ebuf.token = buf;
+       ebuf.len = (int)len;
 
        /*
         * while we have original buf to spill ourselves, or extensions report
@@ -233,36 +241,36 @@ lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len)
                ret = 0;
 
                /* show every extension the new incoming data */
-               m = lws_ext_cb_active(wsi,
-                              LWS_EXT_CB_PACKET_TX_PRESEND, &eff_buf, 0);
+               m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND,
+                                     &ebuf, 0);
                if (m < 0)
                        return -1;
                if (m) /* handled */
                        ret = 1;
 
-               if ((char *)buf != eff_buf.token)
+               if (buf != ebuf.token)
                        /*
                         * extension recreated it:
                         * need to buffer this if not all sent
                         */
-                       wsi->u.ws.clean_buffer = 0;
+                       wsi->ws->clean_buffer = 0;
 
                /* assuming they left us something to send, send it */
 
-               if (eff_buf.token_len) {
-                       n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token,
-                                                           eff_buf.token_len);
+               if (ebuf.len) {
+                       n = lws_issue_raw(wsi, ebuf.token, ebuf.len);
                        if (n < 0) {
                                lwsl_info("closing from ext access\n");
                                return -1;
                        }
 
                        /* always either sent it all or privately buffered */
-                       if (wsi->u.ws.clean_buffer)
+                       if (wsi->ws->clean_buffer)
                                len = n;
-               }
 
-               lwsl_parser("written %d bytes to client\n", n);
+                       lwsl_ext("%s: written %d bytes to client\n",
+                                __func__, n);
+               }
 
                /* no extension has more to spill?  Then we can go */
 
@@ -271,15 +279,15 @@ lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len)
 
                /* we used up what we had */
 
-               eff_buf.token = NULL;
-               eff_buf.token_len = 0;
+               ebuf.token = NULL;
+               ebuf.len = 0;
 
                /*
                 * Did that leave the pipe choked?
                 * Or we had to hold on to some of it?
                 */
 
-               if (!lws_send_pipe_choked(wsi) && !wsi->trunc_len)
+               if (!lws_send_pipe_choked(wsi) && !lws_has_buffered_out(wsi))
                        /* no we could add more, lets's do that */
                        continue;
 
@@ -290,11 +298,11 @@ lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len)
                 * when he is ready to send and take care of it there
                 */
                lws_callback_on_writable(wsi);
-               wsi->extension_data_pending = 1;
+               wsi->ws->extension_data_pending = 1;
                ret = 0;
        }
 
-       return len;
+       return (int)len;
 }
 
 int
@@ -304,15 +312,18 @@ lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r
        struct lws_context *context = wsi->context;
        int n, handled = 0;
 
+       if (!wsi->ws)
+               return 0;
+
        /* maybe an extension will take care of it for us */
 
-       for (n = 0; n < wsi->count_act_ext && !handled; n++) {
-               if (!wsi->active_extensions[n]->callback)
+       for (n = 0; n < wsi->ws->count_act_ext && !handled; n++) {
+               if (!wsi->ws->active_extensions[n]->callback)
                        continue;
 
-               handled |= wsi->active_extensions[n]->callback(context,
-                       wsi->active_extensions[n], wsi,
-                       r, wsi->act_ext_user[n], v, len);
+               handled |= wsi->ws->active_extensions[n]->callback(context,
+                       wsi->ws->active_extensions[n], wsi,
+                       r, wsi->ws->act_ext_user[n], v, len);
        }
 
        return handled;
@@ -325,12 +336,15 @@ lws_set_extension_option(struct lws *wsi, const char *ext_name,
        struct lws_ext_option_arg oa;
        int idx = 0;
 
+       if (!wsi->ws)
+               return 0;
+
        /* first identify if the ext is active on this wsi */
-       while (idx < wsi->count_act_ext &&
-              strcmp(wsi->active_extensions[idx]->name, ext_name))
+       while (idx < wsi->ws->count_act_ext &&
+              strcmp(wsi->ws->active_extensions[idx]->name, ext_name))
                idx++;
 
-       if (idx == wsi->count_act_ext)
+       if (idx == wsi->ws->count_act_ext)
                return -1; /* request ext not active on this wsi */
 
        oa.option_name = opt_name;
@@ -338,7 +352,8 @@ lws_set_extension_option(struct lws *wsi, const char *ext_name,
        oa.start = opt_val;
        oa.len = 0;
 
-       return wsi->active_extensions[idx]->callback(
-                       wsi->context, wsi->active_extensions[idx], wsi,
-                       LWS_EXT_CB_NAMED_OPTION_SET, wsi->act_ext_user[idx], &oa, 0);
+       return wsi->ws->active_extensions[idx]->callback(wsi->context,
+                       wsi->ws->active_extensions[idx], wsi,
+                       LWS_EXT_CB_NAMED_OPTION_SET, wsi->ws->act_ext_user[idx],
+                       &oa, 0);
 }
diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c
new file mode 100644 (file)
index 0000000..ef0d8a6
--- /dev/null
@@ -0,0 +1,2122 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
+
+/*
+ * client-parser.c: lws_ws_client_rx_sm() needs to be roughly kept in
+ *   sync with changes here, esp related to ext draining
+ */
+
+int
+lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int callback_action = LWS_CALLBACK_RECEIVE;
+       struct lws_ext_pm_deflate_rx_ebufs pmdrx;
+       unsigned short close_code;
+       unsigned char *pp;
+       int ret = 0;
+       int n = 0;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       int rx_draining_ext = 0;
+       int lin;
+#endif
+
+       pmdrx.eb_in.token = NULL;
+       pmdrx.eb_in.len = 0;
+       pmdrx.eb_out.token = NULL;
+       pmdrx.eb_out.len = 0;
+
+       if (wsi->socket_is_permanently_unusable)
+               return -1;
+
+       switch (wsi->lws_rx_parse_state) {
+       case LWS_RXPS_NEW:
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws->rx_draining_ext) {
+                       pmdrx.eb_in.token = NULL;
+                       pmdrx.eb_in.len = 0;
+                       pmdrx.eb_out.token = NULL;
+                       pmdrx.eb_out.len = 0;
+                       lws_remove_wsi_from_draining_ext_list(wsi);
+                       rx_draining_ext = 1;
+                       lwsl_debug("%s: doing draining flow\n", __func__);
+
+                       goto drain_extension;
+               }
+#endif
+               switch (wsi->ws->ietf_spec_revision) {
+               case 13:
+                       /*
+                        * no prepended frame key any more
+                        */
+                       wsi->ws->all_zero_nonce = 1;
+                       goto handle_first;
+
+               default:
+                       lwsl_warn("lws_ws_rx_sm: unknown spec version %d\n",
+                                 wsi->ws->ietf_spec_revision);
+                       break;
+               }
+               break;
+       case LWS_RXPS_04_mask_1:
+               wsi->ws->mask[1] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_mask_2;
+               break;
+       case LWS_RXPS_04_mask_2:
+               wsi->ws->mask[2] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_mask_3;
+               break;
+       case LWS_RXPS_04_mask_3:
+               wsi->ws->mask[3] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+
+               /*
+                * start from the zero'th byte in the XOR key buffer since
+                * this is the start of a frame with a new key
+                */
+
+               wsi->ws->mask_idx = 0;
+
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1;
+               break;
+
+       /*
+        *  04 logical framing from the spec (all this is masked when incoming
+        *  and has to be unmasked)
+        *
+        * We ignore the possibility of extension data because we don't
+        * negotiate any extensions at the moment.
+        *
+        *    0                   1                   2                   3
+        *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+        *   +-+-+-+-+-------+-+-------------+-------------------------------+
+        *   |F|R|R|R| opcode|R| Payload len |    Extended payload length    |
+        *   |I|S|S|S|  (4)  |S|     (7)     |             (16/63)           |
+        *   |N|V|V|V|       |V|             |   (if payload len==126/127)   |
+        *   | |1|2|3|       |4|             |                               |
+        *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+        *   |     Extended payload length continued, if payload len == 127  |
+        *   + - - - - - - - - - - - - - - - +-------------------------------+
+        *   |                               |         Extension data        |
+        *   +-------------------------------+ - - - - - - - - - - - - - - - +
+        *   :                                                               :
+        *   +---------------------------------------------------------------+
+        *   :                       Application data                        :
+        *   +---------------------------------------------------------------+
+        *
+        *  We pass payload through to userland as soon as we get it, ignoring
+        *  FIN.  It's up to userland to buffer it up if it wants to see a
+        *  whole unfragmented block of the original size (which may be up to
+        *  2^63 long!)
+        */
+
+       case LWS_RXPS_04_FRAME_HDR_1:
+handle_first:
+
+               wsi->ws->opcode = c & 0xf;
+               wsi->ws->rsv = c & 0x70;
+               wsi->ws->final = !!((c >> 7) & 1);
+               wsi->ws->defeat_check_utf8 = 0;
+
+               if (((wsi->ws->opcode) & 8) && !wsi->ws->final) {
+                       lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+                                       (uint8_t *)"frag ctl", 8);
+                       return -1;
+               }
+
+               switch (wsi->ws->opcode) {
+               case LWSWSOPC_TEXT_FRAME:
+                       wsi->ws->check_utf8 = lws_check_opt(
+                               wsi->context->options,
+                               LWS_SERVER_OPTION_VALIDATE_UTF8);
+                       /* fallthru */
+               case LWSWSOPC_BINARY_FRAME:
+                       if (wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)
+                               wsi->ws->check_utf8 = 0;
+                       if (wsi->ws->continuation_possible) {
+                               lws_close_reason(wsi,
+                                       LWS_CLOSE_STATUS_PROTOCOL_ERR,
+                                       (uint8_t *)"bad cont", 8);
+                               return -1;
+                       }
+                       wsi->ws->rsv_first_msg = (c & 0x70);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       /*
+                        *  set the expectation that we will have to
+                        * fake up the zlib trailer to the inflator for this
+                        * frame
+                        */
+                       wsi->ws->pmd_trailer_application = !!(c & 0x40);
+#endif
+                       wsi->ws->frame_is_binary =
+                            wsi->ws->opcode == LWSWSOPC_BINARY_FRAME;
+                       wsi->ws->first_fragment = 1;
+                       wsi->ws->continuation_possible = !wsi->ws->final;
+                       break;
+               case LWSWSOPC_CONTINUATION:
+                       if (!wsi->ws->continuation_possible) {
+                               lws_close_reason(wsi,
+                                       LWS_CLOSE_STATUS_PROTOCOL_ERR,
+                                       (uint8_t *)"bad cont", 8);
+                               return -1;
+                       }
+                       break;
+               case LWSWSOPC_CLOSE:
+                       wsi->ws->check_utf8 = 0;
+                       wsi->ws->utf8 = 0;
+                       break;
+               case 3:
+               case 4:
+               case 5:
+               case 6:
+               case 7:
+               case 0xb:
+               case 0xc:
+               case 0xd:
+               case 0xe:
+               case 0xf:
+                       lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+                                       (uint8_t *)"bad opc", 7);
+                       lwsl_info("illegal opcode\n");
+                       return -1;
+               }
+
+               if (wsi->ws->owed_a_fin &&
+                   (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+                    wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) {
+                       lwsl_info("hey you owed us a FIN\n");
+                       lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+                                       (uint8_t *)"bad fin", 7);
+                       return -1;
+               }
+               if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) {
+                       wsi->ws->continuation_possible = 0;
+                       wsi->ws->owed_a_fin = 0;
+               }
+
+               if (!wsi->ws->final)
+                       wsi->ws->owed_a_fin = 1;
+
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
+               if (wsi->ws->rsv &&
+                   (
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                                   !wsi->ws->count_act_ext ||
+#endif
+                                   (wsi->ws->rsv & ~0x40))) {
+                       lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
+                                        (uint8_t *)"rsv bits", 8);
+                       return -1;
+               }
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN:
+
+               wsi->ws->this_frame_masked = !!(c & 0x80);
+
+               switch (c & 0x7f) {
+               case 126:
+                       /* control frames are not allowed to have big lengths */
+                       if (wsi->ws->opcode & 8)
+                               goto illegal_ctl_length;
+
+                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
+                       break;
+               case 127:
+                       /* control frames are not allowed to have big lengths */
+                       if (wsi->ws->opcode & 8)
+                               goto illegal_ctl_length;
+
+                       wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
+                       break;
+               default:
+                       wsi->ws->rx_packet_length = c & 0x7f;
+
+
+                       if (wsi->ws->this_frame_masked)
+                               wsi->lws_rx_parse_state =
+                                               LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+                       else
+                               if (wsi->ws->rx_packet_length) {
+                                       wsi->lws_rx_parse_state =
+                                       LWS_RXPS_WS_FRAME_PAYLOAD;
+                               } else {
+                                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                                       goto spill;
+                               }
+                       break;
+               }
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN16_2:
+               wsi->ws->rx_packet_length = c << 8;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN16_1:
+               wsi->ws->rx_packet_length |= c;
+               if (wsi->ws->this_frame_masked)
+                       wsi->lws_rx_parse_state =
+                                       LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+               else {
+                       wsi->lws_rx_parse_state =
+                               LWS_RXPS_WS_FRAME_PAYLOAD;
+               }
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_8:
+               if (c & 0x80) {
+                       lwsl_warn("b63 of length must be zero\n");
+                       /* kill the connection */
+                       return -1;
+               }
+#if defined __LP64__
+               wsi->ws->rx_packet_length = ((size_t)c) << 56;
+#else
+               wsi->ws->rx_packet_length = 0;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_7:
+#if defined __LP64__
+               wsi->ws->rx_packet_length |= ((size_t)c) << 48;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_6:
+#if defined __LP64__
+               wsi->ws->rx_packet_length |= ((size_t)c) << 40;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_5:
+#if defined __LP64__
+               wsi->ws->rx_packet_length |= ((size_t)c) << 32;
+#endif
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_4:
+               wsi->ws->rx_packet_length |= ((size_t)c) << 24;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_3:
+               wsi->ws->rx_packet_length |= ((size_t)c) << 16;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_2:
+               wsi->ws->rx_packet_length |= ((size_t)c) << 8;
+               wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
+               break;
+
+       case LWS_RXPS_04_FRAME_HDR_LEN64_1:
+               wsi->ws->rx_packet_length |= ((size_t)c);
+               if (wsi->ws->this_frame_masked)
+                       wsi->lws_rx_parse_state =
+                                       LWS_RXPS_07_COLLECT_FRAME_KEY_1;
+               else
+                       wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_1:
+               wsi->ws->mask[0] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_2:
+               wsi->ws->mask[1] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_3:
+               wsi->ws->mask[2] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4;
+               break;
+
+       case LWS_RXPS_07_COLLECT_FRAME_KEY_4:
+               wsi->ws->mask[3] = c;
+               if (c)
+                       wsi->ws->all_zero_nonce = 0;
+               wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD;
+               wsi->ws->mask_idx = 0;
+               if (wsi->ws->rx_packet_length == 0) {
+                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                       goto spill;
+               }
+               break;
+
+
+       case LWS_RXPS_WS_FRAME_PAYLOAD:
+               assert(wsi->ws->rx_ubuf);
+
+               if (wsi->ws->rx_ubuf_head + LWS_PRE >= wsi->ws->rx_ubuf_alloc) {
+                       lwsl_err("Attempted overflow \n");
+                       return -1;
+               }
+               if (!(already_processed & ALREADY_PROCESSED_IGNORE_CHAR)) {
+                       if (wsi->ws->all_zero_nonce)
+                               wsi->ws->rx_ubuf[LWS_PRE +
+                                                (wsi->ws->rx_ubuf_head++)] = c;
+                       else
+                               wsi->ws->rx_ubuf[LWS_PRE +
+                                                (wsi->ws->rx_ubuf_head++)] =
+                                  c ^ wsi->ws->mask[(wsi->ws->mask_idx++) & 3];
+
+                       --wsi->ws->rx_packet_length;
+               }
+
+               if (!wsi->ws->rx_packet_length) {
+                       lwsl_debug("%s: ws fragment length exhausted\n",
+                                  __func__);
+                       /* spill because we have the whole frame */
+                       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+                       goto spill;
+               }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws->rx_draining_ext) {
+                       lwsl_debug("%s: UNTIL_EXHAUSTED draining\n", __func__);
+                       goto drain_extension;
+               }
+#endif
+               /*
+                * if there's no protocol max frame size given, we are
+                * supposed to default to context->pt_serv_buf_size
+                */
+               if (!wsi->protocol->rx_buffer_size &&
+                   wsi->ws->rx_ubuf_head != wsi->context->pt_serv_buf_size)
+                       break;
+
+               if (wsi->protocol->rx_buffer_size &&
+                   wsi->ws->rx_ubuf_head != wsi->protocol->rx_buffer_size)
+                       break;
+
+               /* spill because we filled our rx buffer */
+spill:
+               /*
+                * is this frame a control packet we should take care of at this
+                * layer?  If so service it and hide it from the user callback
+                */
+
+               lwsl_parser("spill on %s\n", wsi->protocol->name);
+
+               switch (wsi->ws->opcode) {
+               case LWSWSOPC_CLOSE:
+
+                       if (wsi->ws->peer_has_sent_close)
+                               break;
+
+                       wsi->ws->peer_has_sent_close = 1;
+
+                       pp = &wsi->ws->rx_ubuf[LWS_PRE];
+                       if (lws_check_opt(wsi->context->options,
+                                         LWS_SERVER_OPTION_VALIDATE_UTF8) &&
+                           wsi->ws->rx_ubuf_head > 2 &&
+                           lws_check_utf8(&wsi->ws->utf8, pp + 2,
+                                          wsi->ws->rx_ubuf_head - 2))
+                               goto utf8_fail;
+
+                       /* is this an acknowledgment of our close? */
+                       if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
+                               /*
+                                * fine he has told us he is closing too, let's
+                                * finish our close
+                                */
+                               lwsl_parser("seen client close ack\n");
+                               return -1;
+                       }
+                       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+                               /* if he sends us 2 CLOSE, kill him */
+                               return -1;
+
+                       if (lws_partial_buffered(wsi)) {
+                               /*
+                                * if we're in the middle of something,
+                                * we can't do a normal close response and
+                                * have to just close our end.
+                                */
+                               wsi->socket_is_permanently_unusable = 1;
+                               lwsl_parser("Closing on peer close "
+                                           "due to pending tx\n");
+                               return -1;
+                       }
+
+                       if (wsi->ws->rx_ubuf_head >= 2) {
+                               close_code = (pp[0] << 8) | pp[1];
+                               if (close_code < 1000 ||
+                                   close_code == 1004 ||
+                                   close_code == 1005 ||
+                                   close_code == 1006 ||
+                                   close_code == 1012 ||
+                                   close_code == 1013 ||
+                                   close_code == 1014 ||
+                                   close_code == 1015 ||
+                                   (close_code >= 1016 && close_code < 3000)
+                               ) {
+                                       pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff;
+                                       pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff;
+                               }
+                       }
+
+                       if (user_callback_handle_rxflow(
+                                       wsi->protocol->callback, wsi,
+                                       LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
+                                       wsi->user_space,
+                                       &wsi->ws->rx_ubuf[LWS_PRE],
+                                       wsi->ws->rx_ubuf_head))
+                               return -1;
+
+                       lwsl_parser("server sees client close packet\n");
+                       lwsi_set_state(wsi, LRS_RETURNED_CLOSE);
+                       /* deal with the close packet contents as a PONG */
+                       wsi->ws->payload_is_close = 1;
+                       goto process_as_ping;
+
+               case LWSWSOPC_PING:
+                       lwsl_info("received %d byte ping, sending pong\n",
+                                                wsi->ws->rx_ubuf_head);
+
+                       if (wsi->ws->ping_pending_flag) {
+                               /*
+                                * there is already a pending ping payload
+                                * we should just log and drop
+                                */
+                               lwsl_parser("DROP PING since one pending\n");
+                               goto ping_drop;
+                       }
+process_as_ping:
+                       /* control packets can only be < 128 bytes long */
+                       if (wsi->ws->rx_ubuf_head > 128 - 3) {
+                               lwsl_parser("DROP PING payload too large\n");
+                               goto ping_drop;
+                       }
+
+                       /* stash the pong payload */
+                       memcpy(wsi->ws->ping_payload_buf + LWS_PRE,
+                              &wsi->ws->rx_ubuf[LWS_PRE],
+                               wsi->ws->rx_ubuf_head);
+
+                       wsi->ws->ping_payload_len = wsi->ws->rx_ubuf_head;
+                       wsi->ws->ping_pending_flag = 1;
+
+                       /* get it sent as soon as possible */
+                       lws_callback_on_writable(wsi);
+ping_drop:
+                       wsi->ws->rx_ubuf_head = 0;
+                       return 0;
+
+               case LWSWSOPC_PONG:
+                       lwsl_info("received pong\n");
+                       lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE],
+                                    wsi->ws->rx_ubuf_head);
+
+                       if (wsi->ws->await_pong) {
+                               lwsl_info("received expected PONG on wsi %p\n",
+                                               wsi);
+                               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+                               wsi->ws->await_pong = 0;
+
+                               /*
+                                * prepare to send the ping again if nothing
+                                * sent to countermand it
+                                */
+
+                               __lws_sul_insert(&pt->pt_sul_owner,
+                                                &wsi->sul_ping,
+                                       (lws_usec_t)wsi->context->ws_ping_pong_interval *
+                                        LWS_USEC_PER_SEC);
+                       }
+
+                       /* issue it */
+                       callback_action = LWS_CALLBACK_RECEIVE_PONG;
+                       break;
+
+               case LWSWSOPC_TEXT_FRAME:
+               case LWSWSOPC_BINARY_FRAME:
+               case LWSWSOPC_CONTINUATION:
+                       break;
+
+               default:
+                       lwsl_parser("unknown opc %x\n", wsi->ws->opcode);
+
+                       return -1;
+               }
+
+               /*
+                * No it's real payload, pass it up to the user callback.
+                *
+                * We have been statefully collecting it in the
+                * LWS_RXPS_WS_FRAME_PAYLOAD clause above.
+                *
+                * It's nicely buffered with the pre-padding taken care of
+                * so it can be sent straight out again using lws_write.
+                *
+                * However, now we have a chunk of it, we want to deal with it
+                * all here.  Since this may be input to permessage-deflate and
+                * there are block limits on that for input and output, we may
+                * need to iterate.
+                */
+
+               pmdrx.eb_in.token = &wsi->ws->rx_ubuf[LWS_PRE];
+               pmdrx.eb_in.len = wsi->ws->rx_ubuf_head;
+
+               /* for the non-pm-deflate case */
+
+               pmdrx.eb_out = pmdrx.eb_in;
+
+               if (wsi->ws->opcode == LWSWSOPC_PONG && !pmdrx.eb_in.len)
+                       goto already_done;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+drain_extension:
+#endif
+
+               do {
+
+//                     lwsl_notice("%s: pmdrx.eb_in.len: %d\n", __func__,
+//                                     (int)pmdrx.eb_in.len);
+
+                       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+                           lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK)
+                               goto already_done;
+
+                       n = PMDR_DID_NOTHING;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       lin = pmdrx.eb_in.len;
+                       //if (lin)
+                       //      lwsl_hexdump_notice(ebuf.token, ebuf.len);
+                       lwsl_ext("%s: +++ passing %d %p to ext\n", __func__,
+                                       pmdrx.eb_in.len, pmdrx.eb_in.token);
+
+                       n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &pmdrx, 0);
+                       lwsl_debug("%s: ext says %d / ebuf.len %d\n", __func__,
+                                  n, pmdrx.eb_out.len);
+                       if (wsi->ws->rx_draining_ext)
+                               already_processed &= ~ALREADY_PROCESSED_NO_CB;
+#endif
+
+                       /*
+                        * ebuf may be pointing somewhere completely different
+                        * now, it's the output
+                        */
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       if (n < 0) {
+                               /*
+                                * we may rely on this to get RX, just drop
+                                * connection
+                                */
+                               wsi->socket_is_permanently_unusable = 1;
+                               return -1;
+                       }
+                       if (n == PMDR_DID_NOTHING)
+                               break;
+#endif
+                       lwsl_debug("%s: post ext ret %d, ebuf in %d / out %d\n",
+                                   __func__, n, pmdrx.eb_in.len,
+                                   pmdrx.eb_out.len);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       if (rx_draining_ext && !pmdrx.eb_out.len) {
+                               lwsl_debug("   --- ending drain on 0 read\n");
+                               goto already_done;
+                       }
+
+                       if (n == PMDR_HAS_PENDING)
+                               /*
+                                * extension had more...
+                                * main loop will come back
+                                */
+                               lws_add_wsi_to_draining_ext_list(wsi);
+                       else
+                               lws_remove_wsi_from_draining_ext_list(wsi);
+
+                       rx_draining_ext = wsi->ws->rx_draining_ext;
+#endif
+
+                       if (pmdrx.eb_out.len &&
+                           wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+                               if (lws_check_utf8(&wsi->ws->utf8,
+                                                  pmdrx.eb_out.token,
+                                                  pmdrx.eb_out.len)) {
+                                       lws_close_reason(wsi,
+                                               LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+                                               (uint8_t *)"bad utf8", 8);
+                                       goto utf8_fail;
+                               }
+
+                               /* we are ending partway through utf-8 character? */
+                               if (!wsi->ws->rx_packet_length &&
+                                   wsi->ws->final && wsi->ws->utf8
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                                   /* if ext not negotiated, going to be UNKNOWN */
+                                   && (n == PMDR_EMPTY_FINAL || n == PMDR_UNKNOWN)
+#endif
+                               ) {
+                                       lwsl_info("FINAL utf8 error\n");
+                                       lws_close_reason(wsi,
+                                               LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+                                               (uint8_t *)"partial utf8", 12);
+utf8_fail:
+                                       lwsl_notice("utf8 error\n");
+                                       lwsl_hexdump_notice(pmdrx.eb_out.token,
+                                                           pmdrx.eb_out.len);
+
+                                       return -1;
+                               }
+                       }
+
+                       /* if pmd not enabled, in == out */
+
+                       if (n == PMDR_DID_NOTHING
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                                       ||
+                           n == PMDR_UNKNOWN
+#endif
+                           )
+                               pmdrx.eb_in.len -= pmdrx.eb_out.len;
+
+       if (!wsi->wsistate_pre_close &&
+                           (pmdrx.eb_out.len >= 0 ||
+                            callback_action == LWS_CALLBACK_RECEIVE_PONG ||
+                                                      n == PMDR_EMPTY_FINAL)) {
+                               if (pmdrx.eb_out.len)
+                                       pmdrx.eb_out.token[pmdrx.eb_out.len] = '\0';
+
+                               if (wsi->protocol->callback &&
+                                   !(already_processed & ALREADY_PROCESSED_NO_CB)) {
+                                       if (callback_action ==
+                                                     LWS_CALLBACK_RECEIVE_PONG)
+                                               lwsl_info("Doing pong callback\n");
+
+                                       ret = user_callback_handle_rxflow(
+                                               wsi->protocol->callback, wsi,
+                                               (enum lws_callback_reasons)
+                                                            callback_action,
+                                               wsi->user_space,
+                                               pmdrx.eb_out.token,
+                                               pmdrx.eb_out.len);
+                               }
+                               wsi->ws->first_fragment = 0;
+                       }
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       if (!lin)
+                               break;
+#endif
+
+               } while (pmdrx.eb_in.len
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                               || rx_draining_ext
+#endif
+               );
+
+already_done:
+               wsi->ws->rx_ubuf_head = 0;
+               break;
+       }
+
+       return ret;
+
+illegal_ctl_length:
+
+       lwsl_warn("Control frame with xtended length is illegal\n");
+       /* kill the connection */
+       return -1;
+}
+
+
+LWS_VISIBLE size_t
+lws_remaining_packet_payload(struct lws *wsi)
+{
+       return wsi->ws->rx_packet_length;
+}
+
+LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi)
+{
+       return wsi->ws->frame_is_binary;
+}
+
+void
+lws_add_wsi_to_draining_ext_list(struct lws *wsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       if (wsi->ws->rx_draining_ext)
+               return;
+
+       lwsl_debug("%s: RX EXT DRAINING: Adding to list\n", __func__);
+
+       wsi->ws->rx_draining_ext = 1;
+       wsi->ws->rx_draining_ext_list = pt->ws.rx_draining_ext_list;
+       pt->ws.rx_draining_ext_list = wsi;
+#endif
+}
+
+void
+lws_remove_wsi_from_draining_ext_list(struct lws *wsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       struct lws **w = &pt->ws.rx_draining_ext_list;
+
+       if (!wsi->ws->rx_draining_ext)
+               return;
+
+       lwsl_debug("%s: RX EXT DRAINING: Removing from list\n", __func__);
+
+       wsi->ws->rx_draining_ext = 0;
+
+       /* remove us from context draining ext list */
+       while (*w) {
+               if (*w == wsi) {
+                       /* if us, point it instead to who we were pointing to */
+                       *w = wsi->ws->rx_draining_ext_list;
+                       break;
+               }
+               w = &((*w)->ws->rx_draining_ext_list);
+       }
+       wsi->ws->rx_draining_ext_list = NULL;
+#endif
+}
+
+static int
+lws_0405_frame_mask_generate(struct lws *wsi)
+{
+       int n;
+       /* fetch the per-frame nonce */
+
+       n = lws_get_random(lws_get_context(wsi), wsi->ws->mask, 4);
+       if (n != 4) {
+               lwsl_parser("Unable to read from random device %s %d\n",
+                           SYSTEM_RANDOM_FILEPATH, n);
+               return 1;
+       }
+
+       /* start masking from first byte of masking key buffer */
+       wsi->ws->mask_idx = 0;
+
+       return 0;
+}
+
+void
+lws_sul_wsping_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws *wsi = lws_container_of(sul, struct lws, sul_ping);
+
+       /*
+        * The sul_ping timer came up... either it's time to send a PING
+        * (!wsi->ws->send_check_ping), or we didn't get the PONG in time
+        * (wsi->ws->send_check_ping)
+        */
+
+       if (!wsi->ws->send_check_ping) {
+               lwsl_info("%s: req pp on wsi %p\n", __func__, wsi);
+
+               wsi->ws->send_check_ping = 1;
+               lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING,
+                               wsi->context->timeout_secs);
+               lws_callback_on_writable(wsi);
+
+               return;
+       }
+
+       if (wsi->ws->await_pong) {
+               /* it didn't return the PONG in time */
+
+               lwsl_info("%s: wsi %p: failed to send PONG\n", __func__, wsi);
+               __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+                                    "PONG timeout");
+       }
+}
+
+int
+lws_server_init_wsi_for_ws(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int n;
+
+       lwsi_set_state(wsi, LRS_ESTABLISHED);
+
+       if (wsi->context->ws_ping_pong_interval && !wsi->http2_substream ) {
+               wsi->sul_ping.cb = lws_sul_wsping_cb;
+               __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping,
+                                (lws_usec_t)wsi->context->ws_ping_pong_interval *
+                                LWS_USEC_PER_SEC);
+       }
+
+       /*
+        * create the frame buffer for this connection according to the
+        * size mentioned in the protocol definition.  If 0 there, use
+        * a big default for compatibility
+        */
+
+       n = (int)wsi->protocol->rx_buffer_size;
+       if (!n)
+               n = wsi->context->pt_serv_buf_size;
+       n += LWS_PRE;
+       wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf");
+       if (!wsi->ws->rx_ubuf) {
+               lwsl_err("Out of Mem allocating rx buffer %d\n", n);
+               return 1;
+       }
+       wsi->ws->rx_ubuf_alloc = n;
+       lwsl_debug("Allocating RX buffer %d\n", n);
+
+#if !defined(LWS_WITH_ESP32)
+       if (!wsi->h2_stream_carries_ws)
+               if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
+                      (const char *)&n, sizeof n)) {
+                       lwsl_warn("Failed to set SNDBUF to %d", n);
+                       return 1;
+               }
+#endif
+
+       /* notify user code that we're ready to roll */
+
+       if (wsi->protocol->callback)
+               if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED,
+                                           wsi->user_space,
+#ifdef LWS_WITH_TLS
+                                           wsi->tls.ssl,
+#else
+                                           NULL,
+#endif
+                                           wsi->h2_stream_carries_ws))
+                       return 1;
+
+       lwsl_debug("ws established\n");
+
+       return 0;
+}
+
+
+
+LWS_VISIBLE int
+lws_is_final_fragment(struct lws *wsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       lwsl_debug("%s: final %d, rx pk length %ld, draining %ld\n", __func__,
+                  wsi->ws->final, (long)wsi->ws->rx_packet_length,
+                  (long)wsi->ws->rx_draining_ext);
+       return wsi->ws->final && !wsi->ws->rx_packet_length &&
+              !wsi->ws->rx_draining_ext;
+#else
+       return wsi->ws->final && !wsi->ws->rx_packet_length;
+#endif
+}
+
+LWS_VISIBLE int
+lws_is_first_fragment(struct lws *wsi)
+{
+       return wsi->ws->first_fragment;
+}
+
+LWS_VISIBLE unsigned char
+lws_get_reserved_bits(struct lws *wsi)
+{
+       return wsi->ws->rsv;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_get_close_length(struct lws *wsi)
+{
+       return wsi->ws->close_in_ping_buffer_len;
+}
+
+LWS_VISIBLE LWS_EXTERN unsigned char *
+lws_get_close_payload(struct lws *wsi)
+{
+       return &wsi->ws->ping_payload_buf[LWS_PRE];
+}
+
+LWS_VISIBLE LWS_EXTERN void
+lws_close_reason(struct lws *wsi, enum lws_close_status status,
+                unsigned char *buf, size_t len)
+{
+       unsigned char *p, *start;
+       int budget = sizeof(wsi->ws->ping_payload_buf) - LWS_PRE;
+
+       assert(lwsi_role_ws(wsi));
+
+       start = p = &wsi->ws->ping_payload_buf[LWS_PRE];
+
+       *p++ = (((int)status) >> 8) & 0xff;
+       *p++ = ((int)status) & 0xff;
+
+       if (buf)
+               while (len-- && p < start + budget)
+                       *p++ = *buf++;
+
+       wsi->ws->close_in_ping_buffer_len = lws_ptr_diff(p, start);
+}
+
+static int
+lws_is_ws_with_ext(struct lws *wsi)
+{
+#if defined(LWS_WITHOUT_EXTENSIONS)
+       return 0;
+#else
+       return lwsi_role_ws(wsi) && !!wsi->ws->count_act_ext;
+#endif
+}
+
+static int
+rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
+                      struct lws_pollfd *pollfd)
+{
+       unsigned int pending = 0;
+       struct lws_tokens ebuf;
+       char buffered = 0;
+       int n = 0, m;
+#if defined(LWS_WITH_HTTP2)
+       struct lws *wsi1;
+#endif
+
+       if (!wsi->ws) {
+               lwsl_err("ws role wsi with no ws\n");
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       // lwsl_notice("%s: %s\n", __func__, wsi->protocol->name);
+
+       //lwsl_info("%s: wsistate 0x%x, pollout %d\n", __func__,
+       //         wsi->wsistate, pollfd->revents & LWS_POLLOUT);
+
+       /*
+        * something went wrong with parsing the handshake, and
+        * we ended up back in the event loop without completing it
+        */
+       if (lwsi_state(wsi) == LRS_PRE_WS_SERVING_ACCEPT) {
+               wsi->socket_is_permanently_unusable = 1;
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       ebuf.token = NULL;
+       ebuf.len = 0;
+
+       if (lwsi_state(wsi) == LRS_WAITING_CONNECT) {
+#if !defined(LWS_NO_CLIENT)
+               if ((pollfd->revents & LWS_POLLOUT) &&
+                   lws_handle_POLLOUT_event(wsi, pollfd)) {
+                       lwsl_debug("POLLOUT event closed it\n");
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               n = lws_client_socket_service(wsi, pollfd, NULL);
+               if (n)
+                       return LWS_HPI_RET_WSI_ALREADY_DIED;
+#endif
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       /* 1: something requested a callback when it was OK to write */
+
+       if ((pollfd->revents & LWS_POLLOUT) &&
+           lwsi_state_can_handle_POLLOUT(wsi) &&
+           lws_handle_POLLOUT_event(wsi, pollfd)) {
+               if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+                       lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE);
+
+               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+       }
+
+       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
+           lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) {
+               /*
+                * we stopped caring about anything except control
+                * packets.  Force flow control off, defeat tx
+                * draining.
+                */
+               lws_rx_flow_control(wsi, 1);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws)
+                       wsi->ws->tx_draining_ext = 0;
+#endif
+       }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       if (wsi->ws->tx_draining_ext) {
+               lws_handle_POLLOUT_event(wsi, pollfd);
+               //lwsl_notice("%s: tx drain\n", __func__);
+               /*
+                * We cannot deal with new RX until the TX ext path has
+                * been drained.  It's because new rx will, eg, crap on
+                * the wsi rx buf that may be needed to retain state.
+                *
+                * TX ext drain path MUST go through event loop to avoid
+                * blocking.
+                */
+               lws_callback_on_writable(wsi);
+               return LWS_HPI_RET_HANDLED;
+       }
+#endif
+       if ((pollfd->revents & LWS_POLLIN) && lws_is_flowcontrolled(wsi)) {
+               /* We cannot deal with any kind of new RX because we are
+                * RX-flowcontrolled.
+                */
+               lwsl_info("%s: flowcontrolled, ignoring rx\n", __func__);
+
+               if (__lws_change_pollfd(wsi, LWS_POLLIN, 0))
+                       return -1;
+
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       if (lws_is_flowcontrolled(wsi))
+               return LWS_HPI_RET_HANDLED;
+
+#if defined(LWS_WITH_HTTP2)
+       if (wsi->http2_substream || wsi->upgraded_to_http2) {
+               wsi1 = lws_get_network_wsi(wsi);
+               if (wsi1 && lws_has_buffered_out(wsi1))
+                       /* We cannot deal with any kind of new RX
+                        * because we are dealing with a partial send
+                        * (new RX may trigger new http_action() that
+                        * expect to be able to send)
+                        */
+                       return LWS_HPI_RET_HANDLED;
+       }
+#endif
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       /* 2: RX Extension needs to be drained
+        */
+
+       if (wsi->ws->rx_draining_ext) {
+
+               lwsl_debug("%s: RX EXT DRAINING: Service\n", __func__);
+#ifndef LWS_NO_CLIENT
+               if (lwsi_role_client(wsi)) {
+                       n = lws_ws_client_rx_sm(wsi, 0);
+                       if (n < 0)
+                               /* we closed wsi */
+                               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               } else
+#endif
+                       n = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
+
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       if (wsi->ws->rx_draining_ext)
+               /*
+                * We have RX EXT content to drain, but can't do it
+                * right now.  That means we cannot do anything lower
+                * priority either.
+                */
+               return LWS_HPI_RET_HANDLED;
+#endif
+
+       /* 3: buflist needs to be drained
+        */
+read:
+       //lws_buflist_describe(&wsi->buflist, wsi);
+       ebuf.len = (int)lws_buflist_next_segment_len(&wsi->buflist,
+                                                    &ebuf.token);
+       if (ebuf.len) {
+               lwsl_info("draining buflist (len %d)\n", ebuf.len);
+               buffered = 1;
+               goto drain;
+       }
+
+       if (!(pollfd->revents & pollfd->events & LWS_POLLIN) && !wsi->http.ah)
+               return LWS_HPI_RET_HANDLED;
+
+       if (lws_is_flowcontrolled(wsi)) {
+               lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n",
+                           __func__, wsi, wsi->rxflow_bitmap);
+               return LWS_HPI_RET_HANDLED;
+       }
+
+       if (!(lwsi_role_client(wsi) &&
+             (lwsi_state(wsi) != LRS_ESTABLISHED &&
+              lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK &&
+              lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) {
+               /*
+                * In case we are going to react to this rx by scheduling
+                * writes, we need to restrict the amount of rx to the size
+                * the protocol reported for rx buffer.
+                *
+                * Otherwise we get a situation we have to absorb possibly a
+                * lot of reads before we get a chance to drain them by writing
+                * them, eg, with echo type tests in autobahn.
+                */
+
+               buffered = 0;
+               ebuf.token = pt->serv_buf;
+               if (lwsi_role_ws(wsi))
+                       ebuf.len = wsi->ws->rx_ubuf_alloc;
+               else
+                       ebuf.len = wsi->context->pt_serv_buf_size;
+
+               if ((unsigned int)ebuf.len > wsi->context->pt_serv_buf_size)
+                       ebuf.len = wsi->context->pt_serv_buf_size;
+
+               if ((int)pending > ebuf.len)
+                       pending = ebuf.len;
+
+               ebuf.len = lws_ssl_capable_read(wsi, ebuf.token,
+                                               pending ? (int)pending :
+                                               ebuf.len);
+               switch (ebuf.len) {
+               case 0:
+                       lwsl_info("%s: zero length read\n",
+                                 __func__);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       lwsl_info("SSL Capable more service\n");
+                       return LWS_HPI_RET_HANDLED;
+               case LWS_SSL_CAPABLE_ERROR:
+                       lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n",
+                                       __func__);
+                       return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               /*
+                * coverity thinks ssl_capable_read() may read over
+                * 2GB.  Dissuade it...
+                */
+               ebuf.len &= 0x7fffffff;
+       }
+
+drain:
+
+       /*
+        * give any active extensions a chance to munge the buffer
+        * before parse.  We pass in a pointer to an lws_tokens struct
+        * prepared with the default buffer and content length that's in
+        * there.  Rather than rewrite the default buffer, extensions
+        * that expect to grow the buffer can adapt .token to
+        * point to their own per-connection buffer in the extension
+        * user allocation.  By default with no extensions or no
+        * extension callback handling, just the normal input buffer is
+        * used then so it is efficient.
+        */
+       m = 0;
+       do {
+
+               /* service incoming data */
+               //lws_buflist_describe(&wsi->buflist, wsi);
+               if (ebuf.len) {
+#if defined(LWS_ROLE_H2)
+                       if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY &&
+                           lwsi_state(wsi) != LRS_DISCARD_BODY)
+                               n = lws_read_h2(wsi, ebuf.token,
+                                            ebuf.len);
+                       else
+#endif
+                               n = lws_read_h1(wsi, ebuf.token,
+                                            ebuf.len);
+
+                       if (n < 0) {
+                               /* we closed wsi */
+                               n = 0;
+                               return LWS_HPI_RET_WSI_ALREADY_DIED;
+                       }
+                       //lws_buflist_describe(&wsi->buflist, wsi);
+                       //lwsl_notice("%s: consuming %d / %d\n", __func__, n, ebuf.len);
+                       if (lws_buflist_aware_consume(wsi, &ebuf, n, buffered))
+                               return LWS_HPI_RET_PLEASE_CLOSE_ME;
+               }
+
+               ebuf.token = NULL;
+               ebuf.len = 0;
+       } while (m);
+
+       if (wsi->http.ah
+#if !defined(LWS_NO_CLIENT)
+                       && !wsi->client_h2_alpn
+#endif
+                       ) {
+               lwsl_info("%s: %p: detaching ah\n", __func__, wsi);
+               lws_header_table_detach(wsi, 0);
+       }
+
+       pending = lws_ssl_pending(wsi);
+       if (pending) {
+               if (lws_is_ws_with_ext(wsi))
+                       pending = pending > wsi->ws->rx_ubuf_alloc ?
+                               wsi->ws->rx_ubuf_alloc : pending;
+               else
+                       pending = pending > wsi->context->pt_serv_buf_size ?
+                               wsi->context->pt_serv_buf_size : pending;
+               goto read;
+       }
+
+       if (buffered && /* were draining, now nothing left */
+           !lws_buflist_next_segment_len(&wsi->buflist, NULL)) {
+               lwsl_info("%s: %p flow buf: drained\n", __func__, wsi);
+               /* having drained the rxflow buffer, can rearm POLLIN */
+#ifdef LWS_NO_SERVER
+               n =
+#endif
+               __lws_rx_flow_control(wsi);
+               /* n ignored, needed for NO_SERVER case */
+       }
+
+       /* n = 0 */
+       return LWS_HPI_RET_HANDLED;
+}
+
+
+int rops_handle_POLLOUT_ws(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       int write_type = LWS_WRITE_PONG;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       struct lws_ext_pm_deflate_rx_ebufs pmdrx;
+       int ret, m;
+#endif
+       int n;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       lwsl_debug("%s: %s: wsi->ws->tx_draining_ext %d\n", __func__,
+                       wsi->protocol->name, wsi->ws->tx_draining_ext);
+#endif
+
+       /* Priority 3: pending control packets (pong or close)
+        *
+        * 3a: close notification packet requested from close api
+        */
+
+       if (lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) {
+               lwsl_debug("sending close packet\n");
+               lwsl_hexdump_debug(&wsi->ws->ping_payload_buf[LWS_PRE],
+                                  wsi->ws->close_in_ping_buffer_len);
+               wsi->waiting_to_send_close_frame = 0;
+               n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
+                             wsi->ws->close_in_ping_buffer_len,
+                             LWS_WRITE_CLOSE);
+               if (n >= 0) {
+                       if (wsi->close_needs_ack) {
+                               lwsi_set_state(wsi, LRS_AWAITING_CLOSE_ACK);
+                               lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK,
+                                               5);
+                               lwsl_debug("sent close, await ack\n");
+
+                               return LWS_HP_RET_BAIL_OK;
+                       }
+                       wsi->close_needs_ack = 0;
+                       lwsi_set_state(wsi, LRS_RETURNED_CLOSE);
+               }
+
+               return LWS_HP_RET_BAIL_DIE;
+       }
+
+       /* else, the send failed and we should just hang up */
+
+       if ((lwsi_role_ws(wsi) && wsi->ws->ping_pending_flag) ||
+           (lwsi_state(wsi) == LRS_RETURNED_CLOSE &&
+            wsi->ws->payload_is_close)) {
+
+               if (wsi->ws->payload_is_close)
+                       write_type = LWS_WRITE_CLOSE;
+               else {
+                       if (wsi->wsistate_pre_close) {
+                               /* we started close flow, forget pong */
+                               wsi->ws->ping_pending_flag = 0;
+                               return LWS_HP_RET_BAIL_OK;
+                       }
+                       lwsl_info("issuing pong %d on wsi %p\n",
+                                 wsi->ws->ping_payload_len, wsi);
+               }
+
+               n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
+                             wsi->ws->ping_payload_len, write_type);
+               if (n < 0)
+                       return LWS_HP_RET_BAIL_DIE;
+
+               /* well he is sent, mark him done */
+               wsi->ws->ping_pending_flag = 0;
+               if (wsi->ws->payload_is_close) {
+                       // assert(0);
+                       /* oh... a close frame was it... then we are done */
+                       return LWS_HP_RET_BAIL_DIE;
+               }
+
+               /* otherwise for PING, leave POLLOUT active either way */
+               return LWS_HP_RET_BAIL_OK;
+       }
+
+       if (!wsi->socket_is_permanently_unusable &&
+           wsi->ws->send_check_ping && wsi->context->ws_ping_pong_interval) {
+
+               lwsl_info("%s: issuing ping on wsi %p: %s %s h2: %d\n", __func__, wsi,
+                               wsi->role_ops->name, wsi->protocol->name,
+                               wsi->http2_substream);
+               wsi->ws->send_check_ping = 0;
+               wsi->ws->await_pong = 1;
+               n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE],
+                             0, LWS_WRITE_PING);
+               if (n < 0)
+                       return LWS_HP_RET_BAIL_DIE;
+
+               /* give it a few seconds to respond with the PONG */
+
+               __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping,
+                                (lws_usec_t)wsi->context->timeout_secs *
+                                LWS_USEC_PER_SEC);
+
+               return LWS_HP_RET_BAIL_OK;
+       }
+
+       /* Priority 4: if we are closing, not allowed to send more data frags
+        *             which means user callback or tx ext flush banned now
+        */
+       if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
+               return LWS_HP_RET_USER_SERVICE;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       /* Priority 5: Tx path extension with more to send
+        *
+        *             These are handled as new fragments each time around
+        *             So while we must block new writeable callback to enforce
+        *             payload ordering, but since they are always complete
+        *             fragments control packets can interleave OK.
+        */
+       if (wsi->ws->tx_draining_ext) {
+               lwsl_ext("SERVICING TX EXT DRAINING\n");
+               if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0)
+                       return LWS_HP_RET_BAIL_DIE;
+               /* leave POLLOUT active */
+               return LWS_HP_RET_BAIL_OK;
+       }
+
+       /* Priority 6: extensions
+        */
+       if (!wsi->ws->extension_data_pending && !wsi->ws->tx_draining_ext) {
+               lwsl_ext("%s: !wsi->ws->extension_data_pending\n", __func__);
+               return LWS_HP_RET_USER_SERVICE;
+       }
+
+       /*
+        * Check in on the active extensions, see if they had pending stuff to
+        * spill... they need to get the first look-in otherwise sequence will
+        * be disordered.
+        *
+        * coming here with a NULL, zero-length ebuf means just spill pending
+        */
+
+       ret = 1;
+       if (wsi->role_ops == &role_ops_raw_skt ||
+           wsi->role_ops == &role_ops_raw_file)
+               ret = 0;
+
+       while (ret == 1) {
+
+               /* default to nobody has more to spill */
+
+               ret = 0;
+               pmdrx.eb_in.token = NULL;
+               pmdrx.eb_in.len = 0;
+
+               /* give every extension a chance to spill */
+
+               m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND,
+                                     &pmdrx, 0);
+               if (m < 0) {
+                       lwsl_err("ext reports fatal error\n");
+                       return LWS_HP_RET_BAIL_DIE;
+               }
+               if (m)
+                       /*
+                        * at least one extension told us he has more
+                        * to spill, so we will go around again after
+                        */
+                       ret = 1;
+
+               /* assuming they gave us something to send, send it */
+
+               if (pmdrx.eb_in.len) {
+                       n = lws_issue_raw(wsi, (unsigned char *)pmdrx.eb_in.token,
+                                       pmdrx.eb_in.len);
+                       if (n < 0) {
+                               lwsl_info("closing from POLLOUT spill\n");
+                               return LWS_HP_RET_BAIL_DIE;
+                       }
+                       /*
+                        * Keep amount spilled small to minimize chance of this
+                        */
+                       if (n != pmdrx.eb_in.len) {
+                               lwsl_err("Unable to spill ext %d vs %d\n",
+                                               pmdrx.eb_in.len, n);
+                               return LWS_HP_RET_BAIL_DIE;
+                       }
+               } else
+                       continue;
+
+               /* no extension has more to spill */
+
+               if (!ret)
+                       continue;
+
+               /*
+                * There's more to spill from an extension, but we just sent
+                * something... did that leave the pipe choked?
+                */
+
+               if (!lws_send_pipe_choked(wsi))
+                       /* no we could add more */
+                       continue;
+
+               lwsl_info("choked in POLLOUT service\n");
+
+               /*
+                * Yes, he's choked.  Leave the POLLOUT masked on so we will
+                * come back here when he is unchoked.  Don't call the user
+                * callback to enforce ordering of spilling, he'll get called
+                * when we come back here and there's nothing more to spill.
+                */
+
+               return LWS_HP_RET_BAIL_OK;
+       }
+
+       wsi->ws->extension_data_pending = 0;
+#endif
+
+       return LWS_HP_RET_USER_SERVICE;
+}
+
+static int
+rops_service_flag_pending_ws(struct lws_context *context, int tsi)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       struct lws_context_per_thread *pt = &context->pt[tsi];
+       struct lws *wsi;
+       int forced = 0;
+
+       /* POLLIN faking (the pt lock is taken by the parent) */
+
+       /*
+        * 1) For all guys with already-available ext data to drain, if they are
+        * not flowcontrolled, fake their POLLIN status
+        */
+       wsi = pt->ws.rx_draining_ext_list;
+       while (wsi && wsi->position_in_fds_table != LWS_NO_FDS_POS) {
+               pt->fds[wsi->position_in_fds_table].revents |=
+                       pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
+               if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN)
+                       forced = 1;
+
+               wsi = wsi->ws->rx_draining_ext_list;
+       }
+
+       return forced;
+#else
+       return 0;
+#endif
+}
+
+static int
+rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason)
+{
+       if (!wsi->ws)
+               return 0;
+
+       if (!wsi->ws->close_in_ping_buffer_len && /* already a reason */
+            (reason == LWS_CLOSE_STATUS_NOSTATUS ||
+             reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY))
+               return 0;
+
+       lwsl_debug("%s: sending close indication...\n", __func__);
+
+       /* if no prepared close reason, use 1000 and no aux data */
+
+       if (!wsi->ws->close_in_ping_buffer_len) {
+               wsi->ws->close_in_ping_buffer_len = 2;
+               wsi->ws->ping_payload_buf[LWS_PRE] = (reason >> 8) & 0xff;
+               wsi->ws->ping_payload_buf[LWS_PRE + 1] = reason & 0xff;
+       }
+
+       wsi->waiting_to_send_close_frame = 1;
+       wsi->close_needs_ack = 1;
+       lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE);
+       __lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5);
+
+       lws_callback_on_writable(wsi);
+
+       return 1;
+}
+
+static int
+rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
+{
+       if (!wsi->ws)
+               return 0;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+
+       if (wsi->ws->rx_draining_ext) {
+               struct lws **w = &pt->ws.rx_draining_ext_list;
+
+               wsi->ws->rx_draining_ext = 0;
+               /* remove us from context draining ext list */
+               while (*w) {
+                       if (*w == wsi) {
+                               *w = wsi->ws->rx_draining_ext_list;
+                               break;
+                       }
+                       w = &((*w)->ws->rx_draining_ext_list);
+               }
+               wsi->ws->rx_draining_ext_list = NULL;
+       }
+
+       if (wsi->ws->tx_draining_ext) {
+               struct lws **w = &pt->ws.tx_draining_ext_list;
+               lwsl_ext("%s: CLEARING tx_draining_ext\n", __func__);
+               wsi->ws->tx_draining_ext = 0;
+               /* remove us from context draining ext list */
+               while (*w) {
+                       if (*w == wsi) {
+                               *w = wsi->ws->tx_draining_ext_list;
+                               break;
+                       }
+                       w = &((*w)->ws->tx_draining_ext_list);
+               }
+               wsi->ws->tx_draining_ext_list = NULL;
+       }
+#endif
+       lws_free_set_NULL(wsi->ws->rx_ubuf);
+
+       wsi->ws->ping_payload_len = 0;
+       wsi->ws->ping_pending_flag = 0;
+
+       /* deallocate any active extension contexts */
+
+       if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
+               lwsl_warn("extension destruction failed\n");
+
+       return 0;
+}
+
+static int
+rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
+                           enum lws_write_protocol *wp)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       enum lws_write_protocol wpt;
+#endif
+       struct lws_ext_pm_deflate_rx_ebufs pmdrx;
+       int masked7 = lwsi_role_client(wsi);
+       unsigned char is_masked_bit = 0;
+       unsigned char *dropmask = NULL;
+       size_t orig_len = len;
+       int pre = 0, n = 0;
+
+       // lwsl_err("%s: wp 0x%x len %d\n", __func__, *wp, (int)len);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       if (wsi->ws->tx_draining_ext) {
+               /* remove us from the list */
+               struct lws **w = &pt->ws.tx_draining_ext_list;
+
+               lwsl_ext("%s: CLEARING tx_draining_ext\n", __func__);
+               wsi->ws->tx_draining_ext = 0;
+               /* remove us from context draining ext list */
+               while (*w) {
+                       if (*w == wsi) {
+                               *w = wsi->ws->tx_draining_ext_list;
+                               break;
+                       }
+                       w = &((*w)->ws->tx_draining_ext_list);
+               }
+               wsi->ws->tx_draining_ext_list = NULL;
+
+               wpt = *wp;
+               *wp = (wsi->ws->tx_draining_stashed_wp & 0xc0) |
+                               LWS_WRITE_CONTINUATION;
+
+               /*
+                * When we are just flushing (len == 0), we can trust the
+                * stashed wp info completely.  Otherwise adjust it to the
+                * FIN status of the incoming packet.
+                */
+
+               if (!(wpt & LWS_WRITE_NO_FIN) && len)
+                       *wp &= ~LWS_WRITE_NO_FIN;
+
+               lwsl_ext("FORCED draining wp to 0x%02X "
+                        "(stashed 0x%02X, incoming 0x%02X)\n", *wp,
+                        wsi->ws->tx_draining_stashed_wp, wpt);
+               // assert(0);
+       }
+#endif
+       /* reset the ping wait */
+       if (wsi->context->ws_ping_pong_interval) {
+               wsi->sul_ping.cb = lws_sul_wsping_cb;
+               __lws_sul_insert(&pt->pt_sul_owner, &wsi->sul_ping,
+                               (lws_usec_t)wsi->context->ws_ping_pong_interval *
+                                                               LWS_USEC_PER_SEC);
+       }
+       if (((*wp) & 0x1f) == LWS_WRITE_HTTP ||
+           ((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL ||
+           ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION ||
+           ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS)
+               goto send_raw;
+
+
+
+       /* if we are continuing a frame that already had its header done */
+
+       if (wsi->ws->inside_frame) {
+               lwsl_debug("INSIDE FRAME\n");
+               goto do_more_inside_frame;
+       }
+
+       wsi->ws->clean_buffer = 1;
+
+       /*
+        * give a chance to the extensions to modify payload
+        * the extension may decide to produce unlimited payload erratically
+        * (eg, compression extension), so we require only that if he produces
+        * something, it will be a complete fragment of the length known at
+        * the time (just the fragment length known), and if he has
+        * more we will come back next time he is writeable and allow him to
+        * produce more fragments until he's drained.
+        *
+        * This allows what is sent each time it is writeable to be limited to
+        * a size that can be sent without partial sends or blocking, allows
+        * interleaving of control frames and other connection service.
+        */
+
+       pmdrx.eb_in.token = buf;
+       pmdrx.eb_in.len = (int)len;
+
+       /* for the non-pm-deflate case */
+
+       pmdrx.eb_out = pmdrx.eb_in;
+
+       switch ((int)*wp) {
+       case LWS_WRITE_PING:
+       case LWS_WRITE_PONG:
+       case LWS_WRITE_CLOSE:
+               break;
+       default:
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &pmdrx, *wp);
+               if (n < 0)
+                       return -1;
+               lwsl_ext("%s: defl ext ret %d, ext in remaining %d, "
+                           "out %d compressed (wp 0x%x)\n", __func__, n,
+                           (int)pmdrx.eb_in.len, (int)pmdrx.eb_out.len, *wp);
+
+               if (n == PMDR_HAS_PENDING) {
+                       lwsl_ext("%s: HAS PENDING: write drain len %d "
+                                   "(wp 0x%x) SETTING tx_draining_ext "
+                                   "(remaining in %d)\n", __func__,
+                                   (int)pmdrx.eb_out.len, *wp,
+                                   (int)pmdrx.eb_in.len);
+                       /* extension requires further draining */
+                       wsi->ws->tx_draining_ext = 1;
+                       wsi->ws->tx_draining_ext_list =
+                                       pt->ws.tx_draining_ext_list;
+                       pt->ws.tx_draining_ext_list = wsi;
+                       /* we must come back to do more */
+                       lws_callback_on_writable(wsi);
+                       /*
+                        * keep a copy of the write type for the overall
+                        * action that has provoked generation of these
+                        * fragments, so the last guy can use its FIN state.
+                        */
+                       wsi->ws->tx_draining_stashed_wp = *wp;
+                       /*
+                        * Despite what we may have thought, this is definitely
+                        * NOT the last fragment, because the extension asserted
+                        * he has more coming.  For example, the extension may
+                        * be compressing, and has saved up everything until the
+                        * end, where the output is larger than one chunk.
+                        *
+                        * Make sure this intermediate one doesn't actually
+                        * go out with a FIN.
+                        */
+                       *wp |= LWS_WRITE_NO_FIN;
+               }
+#endif
+               if (pmdrx.eb_out.len && wsi->ws->stashed_write_pending) {
+                       wsi->ws->stashed_write_pending = 0;
+                       *wp = ((*wp) & 0xc0) | (int)wsi->ws->stashed_write_type;
+               }
+       }
+
+       /*
+        * an extension did something we need to keep... for example, if
+        * compression extension, it has already updated its state according
+        * to this being issued
+        */
+       if (buf != pmdrx.eb_out.token) {
+               /*
+                * ext might eat it, but not have anything to issue yet.
+                * In that case we have to follow his lead, but stash and
+                * replace the write type that was lost here the first time.
+                */
+               if (len && !pmdrx.eb_out.len) {
+                       if (!wsi->ws->stashed_write_pending)
+                               wsi->ws->stashed_write_type =
+                                               (char)(*wp) & 0x3f;
+                       wsi->ws->stashed_write_pending = 1;
+                       return (int)len;
+               }
+               /*
+                * extension recreated it:
+                * need to buffer this if not all sent
+                */
+               wsi->ws->clean_buffer = 0;
+       }
+
+       buf = pmdrx.eb_out.token;
+       len = pmdrx.eb_out.len;
+
+       if (!buf) {
+               lwsl_err("null buf (%d)\n", (int)len);
+               return -1;
+       }
+
+       switch (wsi->ws->ietf_spec_revision) {
+       case 13:
+               if (masked7) {
+                       pre += 4;
+                       dropmask = &buf[0 - pre];
+                       is_masked_bit = 0x80;
+               }
+
+               switch ((*wp) & 0xf) {
+               case LWS_WRITE_TEXT:
+                       n = LWSWSOPC_TEXT_FRAME;
+                       break;
+               case LWS_WRITE_BINARY:
+                       n = LWSWSOPC_BINARY_FRAME;
+                       break;
+               case LWS_WRITE_CONTINUATION:
+                       n = LWSWSOPC_CONTINUATION;
+                       break;
+
+               case LWS_WRITE_CLOSE:
+                       n = LWSWSOPC_CLOSE;
+                       break;
+               case LWS_WRITE_PING:
+                       n = LWSWSOPC_PING;
+                       break;
+               case LWS_WRITE_PONG:
+                       n = LWSWSOPC_PONG;
+                       break;
+               default:
+                       lwsl_warn("lws_write: unknown write opc / wp\n");
+                       return -1;
+               }
+
+               if (!((*wp) & LWS_WRITE_NO_FIN))
+                       n |= 1 << 7;
+
+               if (len < 126) {
+                       pre += 2;
+                       buf[-pre] = n;
+                       buf[-pre + 1] = (unsigned char)(len | is_masked_bit);
+               } else {
+                       if (len < 65536) {
+                               pre += 4;
+                               buf[-pre] = n;
+                               buf[-pre + 1] = 126 | is_masked_bit;
+                               buf[-pre + 2] = (unsigned char)(len >> 8);
+                               buf[-pre + 3] = (unsigned char)len;
+                       } else {
+                               pre += 10;
+                               buf[-pre] = n;
+                               buf[-pre + 1] = 127 | is_masked_bit;
+#if defined __LP64__
+                                       buf[-pre + 2] = (len >> 56) & 0x7f;
+                                       buf[-pre + 3] = len >> 48;
+                                       buf[-pre + 4] = len >> 40;
+                                       buf[-pre + 5] = len >> 32;
+#else
+                                       buf[-pre + 2] = 0;
+                                       buf[-pre + 3] = 0;
+                                       buf[-pre + 4] = 0;
+                                       buf[-pre + 5] = 0;
+#endif
+                               buf[-pre + 6] = (unsigned char)(len >> 24);
+                               buf[-pre + 7] = (unsigned char)(len >> 16);
+                               buf[-pre + 8] = (unsigned char)(len >> 8);
+                               buf[-pre + 9] = (unsigned char)len;
+                       }
+               }
+               break;
+       }
+
+do_more_inside_frame:
+
+       /*
+        * Deal with masking if we are in client -> server direction and
+        * the wp demands it
+        */
+
+       if (masked7) {
+               if (!wsi->ws->inside_frame)
+                       if (lws_0405_frame_mask_generate(wsi)) {
+                               lwsl_err("frame mask generation failed\n");
+                               return -1;
+                       }
+
+               /*
+                * in v7, just mask the payload
+                */
+               if (dropmask) { /* never set if already inside frame */
+                       for (n = 4; n < (int)len + 4; n++)
+                               dropmask[n] = dropmask[n] ^ wsi->ws->mask[
+                                       (wsi->ws->mask_idx++) & 3];
+
+                       /* copy the frame nonce into place */
+                       memcpy(dropmask, wsi->ws->mask, 4);
+               }
+       }
+
+       if (lwsi_role_h2_ENCAPSULATION(wsi)) {
+               struct lws *encap = lws_get_network_wsi(wsi);
+
+               assert(encap != wsi);
+               return encap->role_ops->write_role_protocol(wsi, buf - pre,
+                                                           len + pre, wp);
+       }
+
+       switch ((*wp) & 0x1f) {
+       case LWS_WRITE_TEXT:
+       case LWS_WRITE_BINARY:
+       case LWS_WRITE_CONTINUATION:
+               if (!wsi->h2_stream_carries_ws) {
+
+                       /*
+                        * give any active extensions a chance to munge the
+                        * buffer before send.  We pass in a pointer to an
+                        * lws_tokens struct prepared with the default buffer
+                        * and content length that's in there.  Rather than
+                        * rewrite the default buffer, extensions that expect
+                        * to grow the buffer can adapt .token to point to their
+                        * own per-connection buffer in the extension user
+                        * allocation.  By default with no extensions or no
+                        * extension callback handling, just the normal input
+                        * buffer is used then so it is efficient.
+                        *
+                        * callback returns 1 in case it wants to spill more
+                        * buffers
+                        *
+                        * This takes care of holding the buffer if send is
+                        * incomplete, ie, if wsi->ws->clean_buffer is 0
+                        * (meaning an extension meddled with the buffer).  If
+                        * wsi->ws->clean_buffer is 1, it will instead return
+                        * to the user code how much OF THE USER BUFFER was
+                        * consumed.
+                        */
+
+                       n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre);
+                       wsi->ws->inside_frame = 1;
+                       if (n <= 0)
+                               return n;
+
+                       if (n == (int)len + pre) {
+                               /* everything in the buffer was handled
+                                * (or rebuffered...) */
+                               wsi->ws->inside_frame = 0;
+                               return (int)orig_len;
+                       }
+
+                       /*
+                        * it is how many bytes of user buffer got sent... may
+                        * be < orig_len in which case callback when writable
+                        * has already been arranged and user code can call
+                        * lws_write() again with the rest later.
+                        */
+
+                       return n - pre;
+               }
+               break;
+       default:
+               break;
+       }
+
+send_raw:
+       return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre);
+}
+
+static int
+rops_close_kill_connection_ws(struct lws *wsi, enum lws_close_status reason)
+{
+       lws_dll2_remove(&wsi->sul_ping.list);
+       /* deal with ws encapsulation in h2 */
+#if defined(LWS_WITH_HTTP2)
+       if (wsi->http2_substream && wsi->h2_stream_carries_ws)
+               return role_ops_h2.close_kill_connection(wsi, reason);
+
+       return 0;
+#else
+       return 0;
+#endif
+}
+
+static int
+rops_callback_on_writable_ws(struct lws *wsi)
+{
+#if defined(LWS_WITH_HTTP2)
+       if (lwsi_role_h2_ENCAPSULATION(wsi)) {
+               /* we know then that it has an h2 parent */
+               struct lws *enc = role_ops_h2.encapsulation_parent(wsi);
+
+               assert(enc);
+               if (enc->role_ops->callback_on_writable(wsi))
+                       return 1;
+       }
+#endif
+       return 0;
+}
+
+static int
+rops_init_vhost_ws(struct lws_vhost *vh,
+                  const struct lws_context_creation_info *info)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+#ifdef LWS_WITH_PLUGINS
+       struct lws_plugin *plugin = vh->context->plugin_list;
+       int m;
+
+       if (vh->context->plugin_extension_count) {
+
+               m = 0;
+               while (info->extensions && info->extensions[m].callback)
+                       m++;
+
+               /*
+                * give the vhost a unified list of extensions including the
+                * ones that came from plugins
+                */
+               vh->ws.extensions = lws_zalloc(sizeof(struct lws_extension) *
+                                    (m + vh->context->plugin_extension_count + 1),
+                                    "extensions");
+               if (!vh->ws.extensions)
+                       return 1;
+
+               memcpy((struct lws_extension *)vh->ws.extensions, info->extensions,
+                      sizeof(struct lws_extension) * m);
+               plugin = vh->context->plugin_list;
+               while (plugin) {
+                       memcpy((struct lws_extension *)&vh->ws.extensions[m],
+                               plugin->caps.extensions,
+                              sizeof(struct lws_extension) *
+                              plugin->caps.count_extensions);
+                       m += plugin->caps.count_extensions;
+                       plugin = plugin->list;
+               }
+       } else
+#endif
+               vh->ws.extensions = info->extensions;
+#endif
+
+       return 0;
+}
+
+static int
+rops_destroy_vhost_ws(struct lws_vhost *vh)
+{
+#ifdef LWS_WITH_PLUGINS
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       if (vh->context->plugin_extension_count)
+               lws_free((void *)vh->ws.extensions);
+#endif
+#endif
+
+       return 0;
+}
+
+#if defined(LWS_WITH_HTTP_PROXY)
+static int
+ws_destroy_proxy_buf(struct lws_dll2 *d, void *user)
+{
+       lws_free(d);
+
+       return 0;
+}
+#endif
+
+static int
+rops_destroy_role_ws(struct lws *wsi)
+{
+#if defined(LWS_WITH_HTTP_PROXY)
+       lws_dll2_foreach_safe(&wsi->ws->proxy_owner, NULL, ws_destroy_proxy_buf);
+#endif
+
+       lws_free_set_NULL(wsi->ws);
+
+       return 0;
+}
+
+struct lws_role_ops role_ops_ws = {
+       /* role name */                 "ws",
+       /* alpn id */                   NULL,
+       /* check_upgrades */            NULL,
+       /* init_context */              NULL,
+       /* init_vhost */                rops_init_vhost_ws,
+       /* destroy_vhost */             rops_destroy_vhost_ws,
+       /* periodic_checks */           NULL,
+       /* service_flag_pending */      rops_service_flag_pending_ws,
+       /* handle_POLLIN */             rops_handle_POLLIN_ws,
+       /* handle_POLLOUT */            rops_handle_POLLOUT_ws,
+       /* perform_user_POLLOUT */      NULL,
+       /* callback_on_writable */      rops_callback_on_writable_ws,
+       /* tx_credit */                 NULL,
+       /* write_role_protocol */       rops_write_role_protocol_ws,
+       /* encapsulation_parent */      NULL,
+       /* alpn_negotiated */           NULL,
+       /* close_via_role_protocol */   rops_close_via_role_protocol_ws,
+       /* close_role */                rops_close_role_ws,
+       /* close_kill_connection */     rops_close_kill_connection_ws,
+       /* destroy_role */              rops_destroy_role_ws,
+       /* adoption_bind */             NULL,
+       /* client_bind */               NULL,
+       /* adoption_cb clnt, srv */     { LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED,
+                                         LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED },
+       /* rx_cb clnt, srv */           { LWS_CALLBACK_CLIENT_RECEIVE,
+                                         LWS_CALLBACK_RECEIVE },
+       /* writeable cb clnt, srv */    { LWS_CALLBACK_CLIENT_WRITEABLE,
+                                         LWS_CALLBACK_SERVER_WRITEABLE },
+       /* close cb clnt, srv */        { LWS_CALLBACK_CLIENT_CLOSED,
+                                         LWS_CALLBACK_CLOSED },
+       /* protocol_bind cb c, srv */   { LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL,
+                                         LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL },
+       /* protocol_unbind cb c, srv */ { LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL,
+                                         LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL },
+       /* file handles */              0
+};
diff --git a/lib/roles/ws/private.h b/lib/roles/ws/private.h
new file mode 100644 (file)
index 0000000..001b8f1
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_ROLE_WS
+ */
+
+extern struct lws_role_ops role_ops_ws;
+
+#define lwsi_role_ws(wsi) (wsi->role_ops == &role_ops_ws)
+
+enum lws_rx_parse_state {
+       LWS_RXPS_NEW,
+
+       LWS_RXPS_04_mask_1,
+       LWS_RXPS_04_mask_2,
+       LWS_RXPS_04_mask_3,
+
+       LWS_RXPS_04_FRAME_HDR_1,
+       LWS_RXPS_04_FRAME_HDR_LEN,
+       LWS_RXPS_04_FRAME_HDR_LEN16_2,
+       LWS_RXPS_04_FRAME_HDR_LEN16_1,
+       LWS_RXPS_04_FRAME_HDR_LEN64_8,
+       LWS_RXPS_04_FRAME_HDR_LEN64_7,
+       LWS_RXPS_04_FRAME_HDR_LEN64_6,
+       LWS_RXPS_04_FRAME_HDR_LEN64_5,
+       LWS_RXPS_04_FRAME_HDR_LEN64_4,
+       LWS_RXPS_04_FRAME_HDR_LEN64_3,
+       LWS_RXPS_04_FRAME_HDR_LEN64_2,
+       LWS_RXPS_04_FRAME_HDR_LEN64_1,
+
+       LWS_RXPS_07_COLLECT_FRAME_KEY_1,
+       LWS_RXPS_07_COLLECT_FRAME_KEY_2,
+       LWS_RXPS_07_COLLECT_FRAME_KEY_3,
+       LWS_RXPS_07_COLLECT_FRAME_KEY_4,
+
+       LWS_RXPS_WS_FRAME_PAYLOAD
+};
+
+enum lws_websocket_opcodes_07 {
+       LWSWSOPC_CONTINUATION = 0,
+       LWSWSOPC_TEXT_FRAME = 1,
+       LWSWSOPC_BINARY_FRAME = 2,
+
+       LWSWSOPC_NOSPEC__MUX = 7,
+
+       /* control extensions 8+ */
+
+       LWSWSOPC_CLOSE = 8,
+       LWSWSOPC_PING = 9,
+       LWSWSOPC_PONG = 0xa,
+};
+
+/* this is not usable directly by user code any more, lws_close_reason() */
+#define LWS_WRITE_CLOSE 4
+
+#define ALREADY_PROCESSED_IGNORE_CHAR 1
+#define ALREADY_PROCESSED_NO_CB 2
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+struct lws_vhost_role_ws {
+       const struct lws_extension *extensions;
+};
+
+struct lws_pt_role_ws {
+       struct lws *rx_draining_ext_list;
+       struct lws *tx_draining_ext_list;
+};
+#endif
+
+struct _lws_websocket_related {
+       unsigned char *rx_ubuf;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE];
+       void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE];
+       struct lws *rx_draining_ext_list;
+       struct lws *tx_draining_ext_list;
+#endif
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       struct lws_dll2_owner proxy_owner;
+       char actual_protocol[16];
+       size_t proxy_buffered;
+#endif
+
+       /* Also used for close content... control opcode == < 128 */
+       uint8_t ping_payload_buf[128 - 3 + LWS_PRE];
+       uint8_t mask[4];
+
+       size_t rx_packet_length;
+       uint32_t rx_ubuf_head;
+       uint32_t rx_ubuf_alloc;
+
+       uint8_t ping_payload_len;
+       uint8_t mask_idx;
+       uint8_t opcode;
+       uint8_t rsv;
+       uint8_t rsv_first_msg;
+       /* zero if no info, or length including 2-byte close code */
+       uint8_t close_in_ping_buffer_len;
+       uint8_t utf8;
+       uint8_t stashed_write_type;
+       uint8_t tx_draining_stashed_wp;
+       uint8_t ietf_spec_revision;
+
+       unsigned int final:1;
+       unsigned int frame_is_binary:1;
+       unsigned int all_zero_nonce:1;
+       unsigned int this_frame_masked:1;
+       unsigned int inside_frame:1; /* next write will be more of frame */
+       unsigned int clean_buffer:1; /* buffer not rewritten by extension */
+       unsigned int payload_is_close:1; /* process as PONG, but it is close */
+       unsigned int ping_pending_flag:1;
+       unsigned int continuation_possible:1;
+       unsigned int owed_a_fin:1;
+       unsigned int check_utf8:1;
+       unsigned int defeat_check_utf8:1;
+       unsigned int stashed_write_pending:1;
+       unsigned int send_check_ping:1;
+       unsigned int first_fragment:1;
+       unsigned int peer_has_sent_close:1;
+       unsigned int await_pong;
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       unsigned int extension_data_pending:1;
+       unsigned int rx_draining_ext:1;
+       unsigned int tx_draining_ext:1;
+       unsigned int pmd_trailer_application:1;
+
+       uint8_t count_act_ext;
+#endif
+};
+
+/*
+ * we need to separately track what's happening with both compressed rx in
+ * and with inflated rx out that will be passed to the user code
+ */
+
+struct lws_ext_pm_deflate_rx_ebufs {
+       struct lws_tokens eb_in;
+       struct lws_tokens eb_out;
+};
+
+int
+lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len);
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+LWS_VISIBLE void
+lws_context_init_extensions(const struct lws_context_creation_info *info,
+                           struct lws_context *context);
+LWS_EXTERN int
+lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r,
+                         void *v, size_t len);
+
+LWS_EXTERN int
+lws_ext_cb_active(struct lws *wsi, int reason, void *buf, int len);
+LWS_EXTERN int
+lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, int reason,
+                   void *arg, int len);
+#endif
+
+int
+handshake_0405(struct lws_context *context, struct lws *wsi);
+int
+lws_process_ws_upgrade(struct lws *wsi);
+
+int
+lws_process_ws_upgrade2(struct lws *wsi);
+
+extern const struct lws_protocols lws_ws_proxy;
+
+int
+lws_server_init_wsi_for_ws(struct lws *wsi);
+
+void
+lws_sul_wsping_cb(lws_sorted_usec_list_t *sul);
diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c
new file mode 100644 (file)
index 0000000..680c2e6
--- /dev/null
@@ -0,0 +1,1000 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <core/private.h>
+
+#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+static int
+lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       char ext_name[64], *args, *end = (*p) + budget - 1;
+       const struct lws_ext_options *opts, *po;
+       const struct lws_extension *ext;
+       struct lws_ext_option_arg oa;
+       int n, m, more = 1;
+       int ext_count = 0;
+       char ignore;
+       char *c;
+
+       /*
+        * Figure out which extensions the client has that we want to
+        * enable on this connection, and give him back the list
+        */
+       if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS))
+               return 0;
+
+       /*
+        * break down the list of client extensions
+        * and go through them
+        */
+
+       if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size,
+                        WSI_TOKEN_EXTENSIONS) < 0)
+               return 1;
+
+       c = (char *)pt->serv_buf;
+       lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c);
+       wsi->ws->count_act_ext = 0;
+       ignore = 0;
+       n = 0;
+       args = NULL;
+
+       /*
+        * We may get a simple request
+        *
+        * Sec-WebSocket-Extensions: permessage-deflate
+        *
+        * or an elaborated one with requested options
+        *
+        * Sec-WebSocket-Extensions: permessage-deflate; \
+        *                           server_no_context_takeover; \
+        *                           client_no_context_takeover
+        */
+
+       while (more) {
+
+               if (c >= (char *)pt->serv_buf + 255)
+                       return -1;
+
+               if (*c && (*c != ',' && *c != '\t')) {
+                       if (*c == ';') {
+                               ignore = 1;
+                               if (!args)
+                                       args = c + 1;
+                       }
+                       if (ignore || *c == ' ') {
+                               c++;
+                               continue;
+                       }
+                       ext_name[n] = *c++;
+                       if (n < (int)sizeof(ext_name) - 1)
+                               n++;
+                       continue;
+               }
+               ext_name[n] = '\0';
+
+               ignore = 0;
+               if (!*c)
+                       more = 0;
+               else {
+                       c++;
+                       if (!n)
+                               continue;
+               }
+
+               while (args && *args == ' ')
+                       args++;
+
+               /* check a client's extension against our support */
+
+               ext = wsi->vhost->ws.extensions;
+
+               while (ext && ext->callback) {
+
+                       if (strcmp(ext_name, ext->name)) {
+                               ext++;
+                               continue;
+                       }
+
+                       /*
+                        * oh, we do support this one he asked for... but let's
+                        * confirm he only gave it once
+                        */
+                       for (m = 0; m < wsi->ws->count_act_ext; m++)
+                               if (wsi->ws->active_extensions[m] == ext) {
+                                       lwsl_info("ext mentioned twice\n");
+                                       return 1; /* shenanigans */
+                               }
+
+                       /*
+                        * ask user code if it's OK to apply it on this
+                        * particular connection + protocol
+                        */
+                       m = (wsi->protocol->callback)(wsi,
+                               LWS_CALLBACK_CONFIRM_EXTENSION_OKAY,
+                               wsi->user_space, ext_name, 0);
+
+                       /*
+                        * zero return from callback means go ahead and allow
+                        * the extension, it's what we get if the callback is
+                        * unhandled
+                        */
+                       if (m) {
+                               ext++;
+                               continue;
+                       }
+
+                       /* apply it */
+
+                       ext_count++;
+
+                       /* instantiate the extension on this conn */
+
+                       wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext;
+
+                       /* allow him to construct his context */
+
+                       if (ext->callback(lws_get_context(wsi), ext, wsi,
+                                         LWS_EXT_CB_CONSTRUCT,
+                                         (void *)&wsi->ws->act_ext_user[
+                                                       wsi->ws->count_act_ext],
+                                         (void *)&opts, 0)) {
+                               lwsl_info("ext %s failed construction\n",
+                                           ext_name);
+                               ext_count--;
+                               ext++;
+
+                               continue;
+                       }
+
+                       if (ext_count > 1)
+                               *(*p)++ = ',';
+                       else
+                               LWS_CPYAPP(*p,
+                                         "\x0d\x0aSec-WebSocket-Extensions: ");
+                       *p += lws_snprintf(*p, (end - *p), "%s", ext_name);
+
+                       /*
+                        * The client may send a bunch of different option
+                        * sets for the same extension, we are supposed to
+                        * pick one we like the look of.  The option sets are
+                        * separated by comma.
+                        *
+                        * Actually we just either accept the first one or
+                        * nothing.
+                        *
+                        * Go through the options trying to apply the
+                        * recognized ones
+                        */
+
+                       lwsl_info("ext args %s\n", args);
+
+                       while (args && *args && *args != ',') {
+                               while (*args == ' ')
+                                       args++;
+                               po = opts;
+                               while (po->name) {
+                                       /* only support arg-less options... */
+                                       if (po->type != EXTARG_NONE ||
+                                           strncmp(args, po->name,
+                                                   strlen(po->name))) {
+                                               po++;
+                                               continue;
+                                       }
+                                       oa.option_name = NULL;
+                                       oa.option_index = (int)(po - opts);
+                                       oa.start = NULL;
+                                       oa.len = 0;
+                                       lwsl_info("setting '%s'\n", po->name);
+                                       if (!ext->callback(lws_get_context(wsi),
+                                                          ext, wsi,
+                                               LWS_EXT_CB_OPTION_SET,
+                                               wsi->ws->act_ext_user[
+                                                       wsi->ws->count_act_ext],
+                                                         &oa, (end - *p))) {
+
+                                               *p += lws_snprintf(*p,
+                                                                  (end - *p),
+                                                             "; %s", po->name);
+                                               lwsl_debug("adding option %s\n",
+                                                          po->name);
+                                       }
+                                       po++;
+                               }
+                               while (*args && *args != ',' && *args != ';')
+                                       args++;
+
+                               if (*args == ';')
+                                       args++;
+                       }
+
+                       wsi->ws->count_act_ext++;
+                       lwsl_parser("cnt_act_ext <- %d\n",
+                                   wsi->ws->count_act_ext);
+
+                       if (args && *args == ',')
+                               more = 0;
+
+                       ext++;
+               }
+
+               n = 0;
+               args = NULL;
+       }
+
+       return 0;
+}
+#endif
+
+int
+lws_process_ws_upgrade2(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       const struct lws_protocol_vhost_options *pvos = NULL;
+       const char *ws_prot_basic_auth = NULL;
+
+       /*
+        * Allow basic auth a look-in now we bound the wsi to the protocol.
+        *
+        * For vhost ws basic auth, it is "basic-auth": "path" as usual but
+        * applied to the protocol's entry in the vhost's "ws-protocols":
+        * section, as a pvo.
+        */
+
+       pvos = lws_vhost_protocol_options(wsi->vhost, wsi->protocol->name);
+       if (pvos && pvos->options &&
+           !lws_pvo_get_str((void *)pvos->options, "basic-auth",
+                            &ws_prot_basic_auth)) {
+               lwsl_info("%s: ws upgrade requires basic auth\n", __func__);
+               switch(lws_check_basic_auth(wsi, ws_prot_basic_auth)) {
+               case LCBA_CONTINUE:
+                       break;
+               case LCBA_FAILED_AUTH:
+                       return lws_unauthorised_basic_auth(wsi);
+               case LCBA_END_TRANSACTION:
+                       lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+                       return lws_http_transaction_completed(wsi);
+               }
+       }
+
+       /*
+        * We are upgrading to ws, so http/1.1 + h2 and keepalive + pipelined
+        * header considerations about keeping the ah around no longer apply.
+        *
+        * However it's common for the first ws protocol data to have been
+        * coalesced with the browser upgrade request and to already be in the
+        * ah rx buffer.
+        */
+
+       lws_pt_lock(pt, __func__);
+
+       if (!wsi->h2_stream_carries_ws)
+               lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED,
+                                   &role_ops_ws);
+
+       lws_pt_unlock(pt);
+
+       /* allocate the ws struct for the wsi */
+
+       wsi->ws = lws_zalloc(sizeof(*wsi->ws), "ws struct");
+       if (!wsi->ws) {
+               lwsl_notice("OOM\n");
+               return 1;
+       }
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION))
+               wsi->ws->ietf_spec_revision =
+                              atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION));
+
+       /* allocate wsi->user storage */
+       if (lws_ensure_user_space(wsi)) {
+               lwsl_notice("problem with user space\n");
+               return 1;
+       }
+
+       /*
+        * Give the user code a chance to study the request and
+        * have the opportunity to deny it
+        */
+       if ((wsi->protocol->callback)(wsi,
+                       LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
+                       wsi->user_space,
+                     lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) {
+               lwsl_warn("User code denied connection\n");
+               return 1;
+       }
+
+       /*
+        * Perform the handshake according to the protocol version the
+        * client announced
+        */
+
+       switch (wsi->ws->ietf_spec_revision) {
+       default:
+               lwsl_notice("Unknown client spec version %d\n",
+                         wsi->ws->ietf_spec_revision);
+               wsi->ws->ietf_spec_revision = 13;
+               //return 1;
+               /* fallthru */
+       case 13:
+#if defined(LWS_WITH_HTTP2)
+               if (wsi->h2_stream_carries_ws) {
+                       if (lws_h2_ws_handshake(wsi)) {
+                               lwsl_notice("h2 ws handshake failed\n");
+                               return 1;
+                       }
+                       lws_role_transition(wsi,
+                                           LWSIFR_SERVER | LWSIFR_P_ENCAP_H2,
+                                           LRS_ESTABLISHED, &role_ops_ws);
+               } else
+#endif
+               {
+                       lwsl_parser("lws_parse calling handshake_04\n");
+                       if (handshake_0405(wsi->context, wsi)) {
+                               lwsl_notice("hs0405 has failed the connection\n");
+                               return 1;
+                       }
+               }
+               break;
+       }
+
+       lws_server_init_wsi_for_ws(wsi);
+       lwsl_parser("accepted v%02d connection\n", wsi->ws->ietf_spec_revision);
+
+#if defined(LWS_WITH_ACCESS_LOG)
+       {
+               char *uptr = NULL, combo[128];
+               int l, meth = lws_http_get_uri_and_method(wsi, &uptr, &l);
+
+               if (wsi->h2_stream_carries_ws)
+                       wsi->http.request_version = HTTP_VERSION_2;
+
+               wsi->http.access_log.response = 101;
+
+               l = lws_snprintf(combo, sizeof(combo), "%.*s (%s)", l, uptr,
+                                wsi->protocol->name);
+
+               lws_prepare_access_log_info(wsi, combo, l, meth);
+               lws_access_log(wsi);
+       }
+#endif
+
+       lwsl_info("%s: %p: dropping ah on ws upgrade\n", __func__, wsi);
+       lws_header_table_detach(wsi, 1);
+
+       return 0;
+}
+
+int
+lws_process_ws_upgrade(struct lws *wsi)
+{
+       const struct lws_protocols *pcol = NULL;
+       char buf[128], name[64];
+       struct lws_tokenize ts;
+       lws_tokenize_elem e;
+
+       if (!wsi->protocol)
+               lwsl_err("NULL protocol at lws_read\n");
+
+       /*
+        * It's either websocket or h2->websocket
+        *
+        * If we are on h1, confirm we got the required "connection: upgrade"
+        * header.  h2 / ws-over-h2 does not have this.
+        */
+
+#if defined(LWS_WITH_HTTP2)
+       if (!wsi->http2_substream) {
+#endif
+
+               lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+                                           LWS_TOKENIZE_F_DOT_NONTERM |
+                                           LWS_TOKENIZE_F_RFC7230_DELIMS |
+                                           LWS_TOKENIZE_F_MINUS_NONTERM);
+               ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1,
+                                     WSI_TOKEN_CONNECTION);
+               if (ts.len <= 0)
+                       goto bad_conn_format;
+
+               do {
+                       e = lws_tokenize(&ts);
+                       switch (e) {
+                       case LWS_TOKZE_TOKEN:
+                               if (!strncasecmp(ts.token, "upgrade", ts.token_len))
+                                       e = LWS_TOKZE_ENDED;
+                               break;
+
+                       case LWS_TOKZE_DELIMITER:
+                               break;
+
+                       default: /* includes ENDED */
+       bad_conn_format:
+                               lwsl_err("%s: malformed or absent conn hdr\n",
+                                        __func__);
+
+                               return 1;
+                       }
+               } while (e > 0);
+
+#if defined(LWS_WITH_HTTP2)
+       }
+#endif
+
+#if defined(LWS_WITH_HTTP_PROXY)
+       {
+               const struct lws_http_mount *hit;
+               int uri_len = 0, meth;
+               char *uri_ptr;
+
+               meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len);
+               hit = lws_find_mount(wsi, uri_ptr, uri_len);
+
+               if (hit && (meth == 0 || meth == 8) &&
+                   (hit->origin_protocol == LWSMPRO_HTTPS ||
+                    hit->origin_protocol == LWSMPRO_HTTP))
+                       /*
+                        * We are an h1 ws upgrade on a urlpath that corresponds
+                        * to a proxying mount.  Don't try to deal with it
+                        * locally, eg, we won't even have the right protocol
+                        * handler since we're not the guy handling it, just a
+                        * conduit.
+                        *
+                        * Instead open the related ongoing h1 connection
+                        * according to the mount configuration and proxy
+                        * whatever that has to say from now on.
+                        */
+                       return lws_http_proxy_start(wsi, hit, uri_ptr, 1);
+       }
+#endif
+
+       /*
+        * Select the first protocol we support from the list
+        * the client sent us.
+        */
+
+       lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+                                   LWS_TOKENIZE_F_MINUS_NONTERM |
+                                   LWS_TOKENIZE_F_DOT_NONTERM |
+                                   LWS_TOKENIZE_F_RFC7230_DELIMS);
+       ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_PROTOCOL);
+       if (ts.len < 0) {
+               lwsl_err("%s: protocol list too long\n", __func__);
+               return 1;
+       }
+       if (!ts.len) {
+               int n = wsi->vhost->default_protocol_index;
+               /*
+                * Some clients only have one protocol and do not send the
+                * protocol list header... allow it and match to the vhost's
+                * default protocol (which itself defaults to zero).
+                *
+                * Setting the vhost default protocol index to -1 or anything
+                * more than the actual number of protocols on the vhost causes
+                * these "no protocol" ws connections to be rejected.
+                */
+
+               if (n >= wsi->vhost->count_protocols) {
+                       lwsl_notice("%s: rejecting ws upg with no protocol\n",
+                                   __func__);
+
+                       return 1;
+               }
+
+               lwsl_info("%s: defaulting to prot handler %d\n", __func__, n);
+
+               lws_bind_protocol(wsi, &wsi->vhost->protocols[n],
+                                 "ws upgrade default pcol");
+
+               goto alloc_ws;
+       }
+
+       /* otherwise go through the user-provided protocol list */
+
+       do {
+               e = lws_tokenize(&ts);
+               switch (e) {
+               case LWS_TOKZE_TOKEN:
+
+                       if (lws_tokenize_cstr(&ts, name, sizeof(name))) {
+                               lwsl_err("%s: pcol name too long\n", __func__);
+
+                               return 1;
+                       }
+                       lwsl_debug("checking %s\n", name);
+                       pcol = lws_vhost_name_to_protocol(wsi->vhost, name);
+                       if (pcol) {
+                               /* if we know it, bind to it and stop looking */
+                               lws_bind_protocol(wsi, pcol, "ws upg pcol");
+                               e = LWS_TOKZE_ENDED;
+                       }
+                       break;
+
+               case LWS_TOKZE_DELIMITER:
+               case LWS_TOKZE_ENDED:
+                       break;
+
+               default:
+                       lwsl_err("%s: malformatted protocol list", __func__);
+
+                       return 1;
+               }
+       } while (e > 0);
+
+       /* we didn't find a protocol he wanted? */
+
+       if (!pcol) {
+               lwsl_notice("No supported protocol \"%s\"\n", buf);
+
+               return 1;
+       }
+
+alloc_ws:
+
+       return lws_process_ws_upgrade2(wsi);
+}
+
+int
+handshake_0405(struct lws_context *context, struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws_process_html_args args;
+       unsigned char hash[20];
+       int n, accept_len;
+       char *response;
+       char *p;
+
+       if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) ||
+           !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) {
+               lwsl_info("handshake_04 missing pieces\n");
+               /* completed header processing, but missing some bits */
+               goto bail;
+       }
+
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >=
+           MAX_WEBSOCKET_04_KEY_LEN) {
+               lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN);
+               goto bail;
+       }
+
+       /*
+        * since key length is restricted above (currently 128), cannot
+        * overflow
+        */
+       n = sprintf((char *)pt->serv_buf,
+                   "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
+                   lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY));
+
+       lws_SHA1(pt->serv_buf, n, hash);
+
+       accept_len = lws_b64_encode_string((char *)hash, 20,
+                       (char *)pt->serv_buf, context->pt_serv_buf_size);
+       if (accept_len < 0) {
+               lwsl_warn("Base64 encoded hash too long\n");
+               goto bail;
+       }
+
+       /* allocate the per-connection user memory (if any) */
+       if (lws_ensure_user_space(wsi))
+               goto bail;
+
+       /* create the response packet */
+
+       /* make a buffer big enough for everything */
+
+       response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN +
+                  256 + LWS_PRE;
+       p = response;
+       LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
+                     "Upgrade: WebSocket\x0d\x0a"
+                     "Connection: Upgrade\x0d\x0a"
+                     "Sec-WebSocket-Accept: ");
+       strcpy(p, (char *)pt->serv_buf);
+       p += accept_len;
+
+       /* we can only return the protocol header if:
+        *  - one came in, and ... */
+       if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
+           /*  - it is not an empty string */
+           wsi->protocol->name &&
+           wsi->protocol->name[0]) {
+               const char *prot = wsi->protocol->name;
+
+#if defined(LWS_WITH_HTTP_PROXY)
+               if (wsi->proxied_ws_parent && wsi->child_list)
+                       prot = wsi->child_list->ws->actual_protocol;
+#endif
+
+               LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: ");
+               p += lws_snprintf(p, 128, "%s", prot);
+       }
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       /*
+        * Figure out which extensions the client has that we want to
+        * enable on this connection, and give him back the list.
+        *
+        * Give him a limited write bugdet
+        */
+       if (lws_extension_server_handshake(wsi, &p, 192))
+               goto bail;
+#endif
+       LWS_CPYAPP(p, "\x0d\x0a");
+
+       args.p = p;
+       args.max_len = lws_ptr_diff((char *)pt->serv_buf +
+                                   context->pt_serv_buf_size, p);
+       if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                       LWS_CALLBACK_ADD_HEADERS,
+                                       wsi->user_space, &args, 0))
+               goto bail;
+
+       p = args.p;
+
+       /* end of response packet */
+
+       LWS_CPYAPP(p, "\x0d\x0a");
+
+       /* okay send the handshake response accepting the connection */
+
+       lwsl_parser("issuing resp pkt %d len\n",
+                   lws_ptr_diff(p, response));
+#if defined(DEBUG)
+       fwrite(response, 1,  p - response, stderr);
+#endif
+       n = lws_write(wsi, (unsigned char *)response, p - response,
+                     LWS_WRITE_HTTP_HEADERS);
+       if (n != (p - response)) {
+               lwsl_info("%s: ERROR writing to socket %d\n", __func__, n);
+               goto bail;
+       }
+
+       /* alright clean up and set ourselves into established state */
+
+       lwsi_set_state(wsi, LRS_ESTABLISHED);
+       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+
+       {
+               const char * uri_ptr =
+                       lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI);
+               int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
+               const struct lws_http_mount *hit =
+                       lws_find_mount(wsi, uri_ptr, uri_len);
+               if (hit && hit->cgienv &&
+                   wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO,
+                       wsi->user_space, (void *)hit->cgienv, 0))
+                       return 1;
+       }
+
+       return 0;
+
+bail:
+       /* caller will free up his parsing allocations */
+       return -1;
+}
+
+
+
+/*
+ * Once we reach LWS_RXPS_WS_FRAME_PAYLOAD, we know how much
+ * to expect in that state and can deal with it in bulk more efficiently.
+ */
+
+static int
+lws_ws_frame_rest_is_payload(struct lws *wsi, uint8_t **buf, size_t len)
+{
+       struct lws_ext_pm_deflate_rx_ebufs pmdrx;
+       unsigned int avail = (unsigned int)len;
+       uint8_t *buffer = *buf, mask[4];
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       unsigned int old_packet_length = (int)wsi->ws->rx_packet_length;
+#endif
+       int n = 0;
+
+       /*
+        * With zlib, we can give it as much input as we like.  The pmd
+        * extension will draw it down in chunks (default 1024).
+        *
+        * If we try to restrict how much we give it, because we must go
+        * back to the event loop each time, we will drop the remainder...
+        */
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       if (!wsi->ws->count_act_ext)
+#endif
+       {
+               if (wsi->protocol->rx_buffer_size)
+                       avail = (int)wsi->protocol->rx_buffer_size;
+               else
+                       avail = wsi->context->pt_serv_buf_size;
+       }
+
+       /* do not consume more than we should */
+       if (avail > wsi->ws->rx_packet_length)
+               avail = (unsigned int)wsi->ws->rx_packet_length;
+
+       /* do not consume more than what is in the buffer */
+       if (avail > len)
+               avail = (unsigned int)len;
+
+       if (!avail)
+               return 0;
+
+       pmdrx.eb_in.token = buffer;
+       pmdrx.eb_in.len = avail;
+       pmdrx.eb_out.token = buffer;
+       pmdrx.eb_out.len = avail;
+
+       if (!wsi->ws->all_zero_nonce) {
+
+               for (n = 0; n < 4; n++)
+                       mask[n] = wsi->ws->mask[(wsi->ws->mask_idx + n) & 3];
+
+               /* deal with 4-byte chunks using unwrapped loop */
+               n = avail >> 2;
+               while (n--) {
+                       *(buffer) = *(buffer) ^ mask[0];
+                       buffer++;
+                       *(buffer) = *(buffer) ^ mask[1];
+                       buffer++;
+                       *(buffer) = *(buffer) ^ mask[2];
+                       buffer++;
+                       *(buffer) = *(buffer) ^ mask[3];
+                       buffer++;
+               }
+               /* and the remaining bytes bytewise */
+               for (n = 0; n < (int)(avail & 3); n++) {
+                       *(buffer) = *(buffer) ^ mask[n];
+                       buffer++;
+               }
+
+               wsi->ws->mask_idx = (wsi->ws->mask_idx + avail) & 3;
+       }
+
+       lwsl_info("%s: using %d of raw input (total %d on offer)\n", __func__,
+                   avail, (int)len);
+
+       (*buf) += avail;
+       len -= avail;
+       wsi->ws->rx_packet_length -= avail;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &pmdrx, 0);
+       lwsl_info("%s: ext says %d / ebuf_out.len %d\n", __func__,  n,
+                       pmdrx.eb_out.len);
+
+       /*
+        * ebuf may be pointing somewhere completely different now,
+        * it's the output
+        */
+
+       if (n < 0) {
+               /*
+                * we may rely on this to get RX, just drop connection
+                */
+               lwsl_notice("%s: LWS_EXT_CB_PAYLOAD_RX blew out\n", __func__);
+               wsi->socket_is_permanently_unusable = 1;
+
+               return -1;
+       }
+
+       /*
+        * if we had an rx fragment right at the last compressed byte of the
+        * message, we can get a zero length inflated output, where no prior
+        * rx inflated output marked themselves with FIN, since there was
+        * raw ws payload still to drain at that time.
+        *
+        * Then we need to generate a zero length ws rx that can be understood
+        * as the message completion.
+        */
+
+       if (!pmdrx.eb_out.len &&              /* zero-length inflation output */
+           n == PMDR_EMPTY_FINAL &&    /* nothing to drain from the inflator */
+           old_packet_length &&            /* we gave the inflator new input */
+           !wsi->ws->rx_packet_length &&   /* raw ws packet payload all gone */
+           wsi->ws->final &&               /* the raw ws packet is a FIN guy */
+           wsi->protocol->callback &&
+           !wsi->wsistate_pre_close) {
+
+               lwsl_ext("%s: issuing zero length FIN pkt\n", __func__);
+
+               if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                               LWS_CALLBACK_RECEIVE,
+                                               wsi->user_space, NULL, 0))
+                       return -1;
+
+               return avail;
+       }
+
+       /*
+        * If doing permessage-deflate, above was the only way to get a zero
+        * length receive.  Otherwise we're more willing.
+        */
+       if (wsi->ws->count_act_ext && !pmdrx.eb_out.len)
+               return avail;
+
+       if (n == PMDR_HAS_PENDING)
+               /* extension had more... main loop will come back */
+               lws_add_wsi_to_draining_ext_list(wsi);
+       else
+               lws_remove_wsi_from_draining_ext_list(wsi);
+#endif
+
+       if (pmdrx.eb_out.len &&
+           wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) {
+               if (lws_check_utf8(&wsi->ws->utf8,
+                                  pmdrx.eb_out.token,
+                                  pmdrx.eb_out.len)) {
+                       lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+                                        (uint8_t *)"bad utf8", 8);
+                       goto utf8_fail;
+               }
+
+               /* we are ending partway through utf-8 character? */
+               if (!wsi->ws->rx_packet_length && wsi->ws->final &&
+                   wsi->ws->utf8 && !n) {
+                       lwsl_info("FINAL utf8 error\n");
+                       lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD,
+                                        (uint8_t *)"partial utf8", 12);
+
+utf8_fail:
+                       lwsl_info("utf8 error\n");
+                       lwsl_hexdump_info(pmdrx.eb_out.token, pmdrx.eb_out.len);
+
+                       return -1;
+               }
+       }
+
+       if (wsi->protocol->callback && !wsi->wsistate_pre_close)
+               if (user_callback_handle_rxflow(wsi->protocol->callback, wsi,
+                                               LWS_CALLBACK_RECEIVE,
+                                               wsi->user_space,
+                                               pmdrx.eb_out.token,
+                                               pmdrx.eb_out.len))
+                       return -1;
+
+       wsi->ws->first_fragment = 0;
+
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+       lwsl_info("%s: input used %d, output %d, rem len %d, rx_draining_ext %d\n",
+                 __func__, avail, pmdrx.eb_out.len, (int)len,
+                 wsi->ws->rx_draining_ext);
+#endif
+
+       return avail; /* how much we used from the input */
+}
+
+
+int
+lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len)
+{
+       unsigned char *bufin = *buf;
+       int m, bulk = 0;
+
+       lwsl_debug("%s: received %d byte packet\n", __func__, (int)len);
+
+       //lwsl_hexdump_notice(*buf, len);
+
+       /* let the rx protocol state machine have as much as it needs */
+
+       while (len) {
+               /*
+                * we were accepting input but now we stopped doing so
+                */
+               if (wsi->rxflow_bitmap) {
+                       lwsl_info("%s: doing rxflow, caching %d\n", __func__,
+                               (int)len);
+                       /*
+                        * Since we cached the remaining available input, we
+                        * can say we "consumed" it.
+                        *
+                        * But what about the case where the available input
+                        * came out of the rxflow cache already?  If we are
+                        * effectively "putting it back in the cache", we have
+                        * leave it where it is, already pointed to by the head.
+                        */
+                       if (lws_rxflow_cache(wsi, *buf, 0, (int)len) ==
+                                                       LWSRXFC_TRIMMED) {
+                               /*
+                                * We dealt with it by trimming the existing
+                                * rxflow cache HEAD to account for what we used.
+                                *
+                                * indicate we didn't use anything to the caller
+                                * so he doesn't do any consumed processing
+                                */
+                               lwsl_info("%s: trimming inside rxflow cache\n",
+                                         __func__);
+                               *buf = bufin;
+                       } else
+                               *buf += len;
+
+                       return 1;
+               }
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+               if (wsi->ws->rx_draining_ext) {
+                       lwsl_debug("%s: draining rx ext\n", __func__);
+                       m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
+                       if (m < 0)
+                               return -1;
+                       continue;
+               }
+#endif
+
+               /* consume payload bytes efficiently */
+               while (wsi->lws_rx_parse_state == LWS_RXPS_WS_FRAME_PAYLOAD &&
+                               (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME ||
+                                wsi->ws->opcode == LWSWSOPC_BINARY_FRAME ||
+                                wsi->ws->opcode == LWSWSOPC_CONTINUATION) &&
+                      len) {
+                       uint8_t *bin = *buf;
+
+                       bulk = 1;
+                       m = lws_ws_frame_rest_is_payload(wsi, buf, len);
+                       assert((int)lws_ptr_diff(*buf, bin) <= (int)len);
+                       len -= lws_ptr_diff(*buf, bin);
+
+                       if (!m) {
+
+                               break;
+                       }
+                       if (m < 0) {
+                               lwsl_info("%s: rest_is_payload bailed\n",
+                                         __func__);
+                               return -1;
+                       }
+               }
+
+               if (!bulk) {
+                       /* process the byte */
+                       m = lws_ws_rx_sm(wsi, 0, *(*buf)++);
+                       len--;
+               } else {
+                       /*
+                        * We already handled this byte in bulk, just deal
+                        * with the ramifications
+                        */
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+                       lwsl_debug("%s: coming out of bulk with len %d, "
+                                  "wsi->ws->rx_draining_ext %d\n",
+                                  __func__, (int)len,
+                                  wsi->ws->rx_draining_ext);
+#endif
+                       m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR |
+                                             ALREADY_PROCESSED_NO_CB, 0);
+               }
+
+               if (m < 0) {
+                       lwsl_info("%s: lws_ws_rx_sm bailed %d\n", __func__,
+                                 bulk);
+
+                       return -1;
+               }
+
+               bulk = 0;
+       }
+
+       lwsl_debug("%s: exit with %d unused\n", __func__, (int)len);
+
+       return 0;
+}
diff --git a/lib/server-handshake.c b/lib/server-handshake.c
deleted file mode 100644 (file)
index ec9b14e..0000000
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); }
-
-#ifndef LWS_NO_EXTENSIONS
-static int
-lws_extension_server_handshake(struct lws *wsi, char **p, int budget)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       char ext_name[64], *args, *end = (*p) + budget - 1;
-       const struct lws_ext_options *opts, *po;
-       const struct lws_extension *ext;
-       struct lws_ext_option_arg oa;
-       int n, m, more = 1;
-       int ext_count = 0;
-       char ignore;
-       char *c;
-
-       /*
-        * Figure out which extensions the client has that we want to
-        * enable on this connection, and give him back the list
-        */
-       if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS))
-               return 0;
-
-       /*
-        * break down the list of client extensions
-        * and go through them
-        */
-
-       if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size,
-                        WSI_TOKEN_EXTENSIONS) < 0)
-               return 1;
-
-       c = (char *)pt->serv_buf;
-       lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c);
-       wsi->count_act_ext = 0;
-       ignore = 0;
-       n = 0;
-       args = NULL;
-
-       /*
-        * We may get a simple request
-        *
-        * Sec-WebSocket-Extensions: permessage-deflate
-        *
-        * or an elaborated one with requested options
-        *
-        * Sec-WebSocket-Extensions: permessage-deflate; \
-        *                           server_no_context_takeover; \
-        *                           client_no_context_takeover
-        */
-
-       while (more) {
-
-               if (*c && (*c != ',' && *c != '\t')) {
-                       if (*c == ';') {
-                               ignore = 1;
-                               args = c + 1;
-                       }
-                       if (ignore || *c == ' ') {
-                               c++;
-                               continue;
-                       }
-                       ext_name[n] = *c++;
-                       if (n < sizeof(ext_name) - 1)
-                               n++;
-                       continue;
-               }
-               ext_name[n] = '\0';
-
-               ignore = 0;
-               if (!*c)
-                       more = 0;
-               else {
-                       c++;
-                       if (!n)
-                               continue;
-               }
-
-               while (args && *args && *args == ' ')
-                       args++;
-
-               /* check a client's extension against our support */
-
-               ext = wsi->vhost->extensions;
-
-               while (ext && ext->callback) {
-
-                       if (strcmp(ext_name, ext->name)) {
-                               ext++;
-                               continue;
-                       }
-
-                       /*
-                        * oh, we do support this one he asked for... but let's
-                        * confirm he only gave it once
-                        */
-                       for (m = 0; m < wsi->count_act_ext; m++)
-                               if (wsi->active_extensions[m] == ext) {
-                                       lwsl_info("extension mentioned twice\n");
-                                       return 1; /* shenanigans */
-                               }
-
-                       /*
-                        * ask user code if it's OK to apply it on this
-                        * particular connection + protocol
-                        */
-                       m = (wsi->protocol->callback)(wsi,
-                               LWS_CALLBACK_CONFIRM_EXTENSION_OKAY,
-                               wsi->user_space, ext_name, 0);
-
-                       /*
-                        * zero return from callback means go ahead and allow
-                        * the extension, it's what we get if the callback is
-                        * unhandled
-                        */
-                       if (m) {
-                               ext++;
-                               continue;
-                       }
-
-                       /* apply it */
-
-                       ext_count++;
-
-                       /* instantiate the extension on this conn */
-
-                       wsi->active_extensions[wsi->count_act_ext] = ext;
-
-                       /* allow him to construct his context */
-
-                       if (ext->callback(lws_get_context(wsi), ext, wsi,
-                                         LWS_EXT_CB_CONSTRUCT,
-                                         (void *)&wsi->act_ext_user[
-                                                           wsi->count_act_ext],
-                                         (void *)&opts, 0)) {
-                               lwsl_notice("ext %s failed construction\n",
-                                           ext_name);
-                               ext_count--;
-                               ext++;
-
-                               continue;
-                       }
-
-                       if (ext_count > 1)
-                               *(*p)++ = ',';
-                       else
-                               LWS_CPYAPP(*p,
-                                         "\x0d\x0aSec-WebSocket-Extensions: ");
-                       *p += lws_snprintf(*p, (end - *p), "%s", ext_name);
-
-                       /*
-                        *  go through the options trying to apply the
-                        * recognized ones
-                        */
-
-                       lwsl_debug("ext args %s", args);
-
-                       while (args && *args && *args != ',') {
-                               while (*args == ' ')
-                                       args++;
-                               po = opts;
-                               while (po->name) {
-                                       lwsl_debug("'%s' '%s'\n", po->name, args);
-                                       /* only support arg-less options... */
-                                       if (po->type == EXTARG_NONE &&
-                                           !strncmp(args, po->name,
-                                                           strlen(po->name))) {
-                                               oa.option_name = NULL;
-                                               oa.option_index = po - opts;
-                                               oa.start = NULL;
-                                               lwsl_debug("setting %s\n", po->name);
-                                               if (!ext->callback(
-                                                               lws_get_context(wsi), ext, wsi,
-                                                                 LWS_EXT_CB_OPTION_SET,
-                                                                 wsi->act_ext_user[
-                                                                        wsi->count_act_ext],
-                                                                 &oa, (end - *p))) {
-
-                                                       *p += lws_snprintf(*p, (end - *p), "; %s", po->name);
-                                                       lwsl_debug("adding option %s\n", po->name);
-                                               }
-                                       }
-                                       po++;
-                               }
-                               while (*args && *args != ',' && *args != ';')
-                                       args++;
-                       }
-
-                       wsi->count_act_ext++;
-                       lwsl_parser("count_act_ext <- %d\n",
-                                   wsi->count_act_ext);
-
-                       ext++;
-               }
-
-               n = 0;
-               args = NULL;
-       }
-
-       return 0;
-}
-#endif
-int
-handshake_0405(struct lws_context *context, struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       unsigned char hash[20];
-       int n, accept_len;
-       char *response;
-       char *p;
-
-       if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) ||
-           !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) {
-               lwsl_parser("handshake_04 missing pieces\n");
-               /* completed header processing, but missing some bits */
-               goto bail;
-       }
-
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >= MAX_WEBSOCKET_04_KEY_LEN) {
-               lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN);
-               goto bail;
-       }
-
-       /*
-        * since key length is restricted above (currently 128), cannot
-        * overflow
-        */
-       n = sprintf((char *)pt->serv_buf,
-                   "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
-                   lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY));
-
-       lws_SHA1(pt->serv_buf, n, hash);
-
-       accept_len = lws_b64_encode_string((char *)hash, 20,
-                       (char *)pt->serv_buf, context->pt_serv_buf_size);
-       if (accept_len < 0) {
-               lwsl_warn("Base64 encoded hash too long\n");
-               goto bail;
-       }
-
-       /* allocate the per-connection user memory (if any) */
-       if (lws_ensure_user_space(wsi))
-               goto bail;
-
-       /* create the response packet */
-
-       /* make a buffer big enough for everything */
-
-       response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + LWS_PRE;
-       p = response;
-       LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
-                     "Upgrade: WebSocket\x0d\x0a"
-                     "Connection: Upgrade\x0d\x0a"
-                     "Sec-WebSocket-Accept: ");
-       strcpy(p, (char *)pt->serv_buf);
-       p += accept_len;
-
-       /* we can only return the protocol header if:
-        *  - one came in, and ... */
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
-           /*  - it is not an empty string */
-           wsi->protocol->name &&
-           wsi->protocol->name[0]) {
-               LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: ");
-               p += lws_snprintf(p, 128, "%s", wsi->protocol->name);
-       }
-
-#ifndef LWS_NO_EXTENSIONS
-       /*
-        * Figure out which extensions the client has that we want to
-        * enable on this connection, and give him back the list.
-        *
-        * Give him a limited write bugdet
-        */
-       if (lws_extension_server_handshake(wsi, &p, 192))
-               goto bail;
-#endif
-
-       //LWS_CPYAPP(p, "\x0d\x0a""An-unknown-header: blah");
-
-       /* end of response packet */
-
-       LWS_CPYAPP(p, "\x0d\x0a\x0d\x0a");
-
-       if (!lws_any_extension_handled(wsi, LWS_EXT_CB_HANDSHAKE_REPLY_TX,
-                                      response, p - response)) {
-
-               /* okay send the handshake response accepting the connection */
-
-               lwsl_parser("issuing resp pkt %d len\n", (int)(p - response));
-#if defined(DEBUG) && ! defined(LWS_WITH_ESP8266)
-               fwrite(response, 1,  p - response, stderr);
-#endif
-               n = lws_write(wsi, (unsigned char *)response,
-                             p - response, LWS_WRITE_HTTP_HEADERS);
-               if (n != (p - response)) {
-                       lwsl_debug("handshake_0405: ERROR writing to socket\n");
-                       goto bail;
-               }
-
-       }
-
-       /* alright clean up and set ourselves into established state */
-
-       wsi->state = LWSS_ESTABLISHED;
-       wsi->lws_rx_parse_state = LWS_RXPS_NEW;
-
-       {
-               const char * uri_ptr =
-                       lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI);
-               int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI);
-               const struct lws_http_mount *hit =
-                       lws_find_mount(wsi, uri_ptr, uri_len);
-               if (hit && hit->cgienv &&
-                   wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO,
-                       wsi->user_space, (void *)hit->cgienv, 0))
-                       return 1;
-       }
-
-       return 0;
-
-
-bail:
-       /* caller will free up his parsing allocations */
-       return -1;
-}
-
diff --git a/lib/server.c b/lib/server.c
deleted file mode 100644 (file)
index 6e7c8b0..0000000
+++ /dev/null
@@ -1,3507 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-
-#include "private-libwebsockets.h"
-
-#if defined (LWS_WITH_ESP8266)
-#undef memcpy
-void *memcpy(void *dest, const void *src, size_t n)
-{
-       return ets_memcpy(dest, src, n);
-}
-#endif
-
-int
-lws_context_init_server(struct lws_context_creation_info *info,
-                       struct lws_vhost *vhost)
-{
-#if LWS_POSIX
-       int n, opt = 1, limit = 1;
-#endif
-       lws_sockfd_type sockfd;
-       struct lws_vhost *vh;
-       struct lws *wsi;
-       int m = 0;
-
-       (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)
-               return 0;
-
-       vh = vhost->context->vhost_list;
-       while (vh) {
-               if (vh->listen_port == info->port) {
-                       if ((!info->iface && !vh->iface) ||
-                           (info->iface && vh->iface &&
-                           !strcmp(info->iface, vh->iface))) {
-                               vhost->listen_port = info->port;
-                               vhost->iface = info->iface;
-                               lwsl_notice(" using listen skt from vhost %s\n",
-                                           vh->name);
-                               return 0;
-                       }
-               }
-               vh = vh->vhost_next;
-       }
-
-#if LWS_POSIX
-       (void)n;
-#if defined(__linux__)
-       limit = vhost->context->count_threads;
-#endif
-
-       for (m = 0; m < limit; m++) {
-#ifdef LWS_USE_UNIX_SOCK
-       if (LWS_UNIX_SOCK_ENABLED(vhost))
-               sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
-       else
-#endif
-#ifdef LWS_USE_IPV6
-       if (LWS_IPV6_ENABLED(vhost))
-               sockfd = socket(AF_INET6, SOCK_STREAM, 0);
-       else
-#endif
-               sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
-       if (sockfd == -1) {
-#else
-#if defined(LWS_WITH_ESP8266)
-       sockfd = esp8266_create_tcp_listen_socket(vhost);
-       if (!lws_sockfd_valid(sockfd)) {
-#endif
-#endif
-               lwsl_err("ERROR opening socket\n");
-               return 1;
-       }
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-
-#if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE)
-       /*
-        * only accept that we are the only listener on the port
-        * https://msdn.microsoft.com/zh-tw/library/windows/desktop/ms740621(v=vs.85).aspx
-        *
-        * for lws, to match Linux, we default to exclusive listen
-        */
-       if (!lws_check_opt(vhost->options, LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) {
-               if (setsockopt(sockfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
-                              (const void *)&opt, sizeof(opt)) < 0) {
-                       lwsl_err("reuseaddr failed\n");
-                       compatible_close(sockfd);
-                       return 1;
-               }
-       } else
-#endif
-
-       /*
-        * allow us to restart even if old sockets in TIME_WAIT
-        */
-       if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
-                      (const void *)&opt, sizeof(opt)) < 0) {
-               lwsl_err("reuseaddr failed\n");
-               compatible_close(sockfd);
-               return 1;
-       }
-
-#if defined(LWS_USE_IPV6) && defined(IPV6_V6ONLY)
-       if (LWS_IPV6_ENABLED(vhost)) {
-               if (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) {
-                       int value = (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE) ? 1 : 0;
-                       if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
-                                       (const void*)&value, sizeof(value)) < 0) {
-                               compatible_close(sockfd);
-                               return 1;
-                       }
-               }
-       }
-#endif
-
-#if defined(__linux__) && defined(SO_REUSEPORT)
-       n = lws_check_opt(vhost->options, LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE);
-#if LWS_MAX_SMP > 1
-       n = 1;
-#endif
-
-       if (n)
-               if (vhost->context->count_threads > 1)
-                       if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT,
-                                       (const void *)&opt, sizeof(opt)) < 0) {
-                               compatible_close(sockfd);
-                               return 1;
-                       }
-#endif
-#endif
-       lws_plat_set_socket_options(vhost, sockfd);
-
-#if LWS_POSIX
-       n = lws_socket_bind(vhost, sockfd, info->port, info->iface);
-       if (n < 0)
-               goto bail;
-       info->port = n;
-#endif
-       vhost->listen_port = info->port;
-       vhost->iface = info->iface;
-
-       wsi = lws_zalloc(sizeof(struct lws));
-       if (wsi == NULL) {
-               lwsl_err("Out of mem\n");
-               goto bail;
-       }
-       wsi->context = vhost->context;
-       wsi->desc.sockfd = sockfd;
-       wsi->mode = LWSCM_SERVER_LISTENER;
-       wsi->protocol = vhost->protocols;
-       wsi->tsi = m;
-       wsi->vhost = vhost;
-       wsi->listener = 1;
-
-#ifdef LWS_USE_LIBUV
-       if (LWS_LIBUV_ENABLED(vhost->context))
-               lws_uv_initvhost(vhost, wsi);
-#endif
-
-       if (insert_wsi_socket_into_fds(vhost->context, wsi))
-               goto bail;
-
-       vhost->context->count_wsi_allocated++;
-       vhost->lserv_wsi = wsi;
-
-#if LWS_POSIX
-       n = listen(wsi->desc.sockfd, LWS_SOMAXCONN);
-       if (n < 0) {
-               lwsl_err("listen failed with error %d\n", LWS_ERRNO);
-               vhost->lserv_wsi = NULL;
-               vhost->context->count_wsi_allocated--;
-               remove_wsi_socket_from_fds(wsi);
-               goto bail;
-       }
-       } /* for each thread able to independently listen */
-#else
-#if defined(LWS_WITH_ESP8266)
-       esp8266_tcp_stream_bind(wsi->desc.sockfd, info->port, wsi);
-#endif
-#endif
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) {
-#ifdef LWS_USE_UNIX_SOCK
-               if (LWS_UNIX_SOCK_ENABLED(vhost))
-                       lwsl_notice(" Listening on \"%s\"\n", info->iface);
-               else
-#endif
-                       lwsl_notice(" Listening on port %d\n", info->port);
-        }
-
-       return 0;
-
-bail:
-       compatible_close(sockfd);
-
-       return 1;
-}
-
-#if defined(LWS_WITH_ESP8266)
-#undef strchr
-#define strchr ets_strchr
-#endif
-
-struct lws_vhost *
-lws_select_vhost(struct lws_context *context, int port, const char *servername)
-{
-       struct lws_vhost *vhost = context->vhost_list;
-       const char *p;
-       int n, m, colon;
-
-       n = strlen(servername);
-       colon = n;
-       p = strchr(servername, ':');
-       if (p)
-               colon = p - servername;
-
-       /* Priotity 1: first try exact matches */
-
-       while (vhost) {
-               if (port == vhost->listen_port &&
-                   !strncmp(vhost->name, servername, colon)) {
-                       lwsl_info("SNI: Found: %s\n", servername);
-                       return vhost;
-               }
-               vhost = vhost->vhost_next;
-       }
-
-       /*
-        * Priority 2: if no exact matches, try matching *.vhost-name
-        * unintentional matches are possible but resolve to x.com for *.x.com
-        * which is reasonable.  If exact match exists we already chose it and
-        * never reach here.  SSL will still fail it if the cert doesn't allow
-        * *.x.com.
-        */
-
-       vhost = context->vhost_list;
-       while (vhost) {
-               m = strlen(vhost->name);
-               if (port == vhost->listen_port &&
-                   m <= (colon - 2) &&
-                   servername[colon - m - 1] == '.' &&
-                   !strncmp(vhost->name, servername + colon - m, m)) {
-                       lwsl_info("SNI: Found %s on wildcard: %s\n",
-                                   servername, vhost->name);
-                       return vhost;
-               }
-               vhost = vhost->vhost_next;
-       }
-
-       /* Priority 3: match the first vhost on our port */
-
-       vhost = context->vhost_list;
-       while (vhost) {
-               if (port == vhost->listen_port) {
-                       lwsl_info("vhost match to %s based on port %d\n",
-                                       vhost->name, port);
-                       return vhost;
-               }
-               vhost = vhost->vhost_next;
-       }
-
-       /* no match */
-
-       return NULL;
-}
-
-LWS_VISIBLE LWS_EXTERN const char *
-lws_get_mimetype(const char *file, const struct lws_http_mount *m)
-{
-       int n = strlen(file);
-       const struct lws_protocol_vhost_options *pvo = NULL;
-
-       if (m)
-               pvo = m->extra_mimetypes;
-
-       if (n < 5)
-               return NULL;
-
-       if (!strcmp(&file[n - 4], ".ico"))
-               return "image/x-icon";
-
-       if (!strcmp(&file[n - 4], ".gif"))
-               return "image/gif";
-
-       if (!strcmp(&file[n - 3], ".js"))
-               return "text/javascript";
-
-       if (!strcmp(&file[n - 4], ".png"))
-               return "image/png";
-
-       if (!strcmp(&file[n - 4], ".jpg"))
-               return "image/jpeg";
-
-       if (!strcmp(&file[n - 3], ".gz"))
-               return "application/gzip";
-
-       if (!strcmp(&file[n - 4], ".JPG"))
-               return "image/jpeg";
-
-       if (!strcmp(&file[n - 5], ".html"))
-               return "text/html";
-
-       if (!strcmp(&file[n - 4], ".css"))
-               return "text/css";
-
-       if (!strcmp(&file[n - 4], ".txt"))
-               return "text/plain";
-
-       if (!strcmp(&file[n - 4], ".svg"))
-               return "image/svg+xml";
-
-       if (!strcmp(&file[n - 4], ".ttf"))
-               return "application/x-font-ttf";
-
-       if (!strcmp(&file[n - 4], ".otf"))
-               return "application/font-woff";
-
-       if (!strcmp(&file[n - 5], ".woff"))
-               return "application/font-woff";
-
-       if (!strcmp(&file[n - 4], ".xml"))
-               return "application/xml";
-
-       while (pvo) {
-               if (pvo->name[0] == '*') /* ie, match anything */
-                       return pvo->value;
-
-               if (!strcmp(&file[n - strlen(pvo->name)], pvo->name))
-                       return pvo->value;
-
-               pvo = pvo->next;
-       }
-
-       return NULL;
-}
-static lws_fop_flags_t
-lws_vfs_prepare_flags(struct lws *wsi)
-{
-       lws_fop_flags_t f = 0;
-
-       if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING))
-               return f;
-
-       if (strstr(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING),
-                  "gzip")) {
-               lwsl_info("client indicates GZIP is acceptable\n");
-               f |= LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP;
-       }
-
-       return f;
-}
-
-static int
-lws_http_serve(struct lws *wsi, char *uri, const char *origin,
-              const struct lws_http_mount *m)
-{
-       const struct lws_protocol_vhost_options *pvo = m->interpret;
-       struct lws_process_html_args args;
-       const char *mimetype;
-#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266)
-       const struct lws_plat_file_ops *fops;
-       const char *vpath;
-       lws_fop_flags_t fflags = LWS_O_RDONLY;
-#if defined(WIN32) && defined(LWS_HAVE__STAT32I64)
-       struct _stat32i64 st;
-#else
-       struct stat st;
-#endif
-       int spin = 0;
-#endif
-       char path[256], sym[512];
-       unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p;
-       unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE;
-#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32)
-       size_t len;
-#endif
-       int n;
-
-       lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri);
-
-#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266)
-
-       fflags |= lws_vfs_prepare_flags(wsi);
-
-       do {
-               spin++;
-               fops = lws_vfs_select_fops(wsi->context->fops, path, &vpath);
-
-               if (wsi->u.http.fop_fd)
-                       lws_vfs_file_close(&wsi->u.http.fop_fd);
-
-               wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
-                                                       path, vpath, &fflags);
-               if (!wsi->u.http.fop_fd) {
-                       lwsl_err("Unable to open '%s'\n", path);
-
-                       return -1;
-               }
-
-               /* if it can't be statted, don't try */
-               if (fflags & LWS_FOP_FLAG_VIRTUAL)
-                       break;
-#if defined(LWS_WITH_ESP32)
-               break;
-#endif
-#if !defined(WIN32)
-               if (fstat(wsi->u.http.fop_fd->fd, &st)) {
-                       lwsl_info("unable to stat %s\n", path);
-                       goto bail;
-               }
-#else
-#if defined(LWS_HAVE__STAT32I64)
-               if (_stat32i64(path, &st)) {
-                       lwsl_info("unable to stat %s\n", path);
-                       goto bail;
-               }
-#else
-               if (stat(path, &st)) {
-                       lwsl_info("unable to stat %s\n", path);
-                       goto bail;
-               }
-#endif
-#endif
-
-               wsi->u.http.fop_fd->mod_time = (uint32_t)st.st_mtime;
-               fflags |= LWS_FOP_FLAG_MOD_TIME_VALID;
-
-               lwsl_debug(" %s mode %d\n", path, S_IFMT & st.st_mode);
-#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32)
-               if ((S_IFMT & st.st_mode) == S_IFLNK) {
-                       len = readlink(path, sym, sizeof(sym) - 1);
-                       if (len) {
-                               lwsl_err("Failed to read link %s\n", path);
-                               goto bail;
-                       }
-                       sym[len] = '\0';
-                       lwsl_debug("symlink %s -> %s\n", path, sym);
-                       lws_snprintf(path, sizeof(path) - 1, "%s", sym);
-               }
-#endif
-               if ((S_IFMT & st.st_mode) == S_IFDIR) {
-                       lwsl_debug("default filename append to dir\n");
-                       lws_snprintf(path, sizeof(path) - 1, "%s/%s/index.html",
-                                origin, uri);
-               }
-
-       } while ((S_IFMT & st.st_mode) != S_IFREG && spin < 5);
-
-       if (spin == 5)
-               lwsl_err("symlink loop %s \n", path);
-
-       n = sprintf(sym, "%08llX%08lX",
-                   (unsigned long long)lws_vfs_get_length(wsi->u.http.fop_fd),
-                   (unsigned long)lws_vfs_get_mod_time(wsi->u.http.fop_fd));
-
-       /* disable ranges if IF_RANGE token invalid */
-
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_RANGE))
-               if (strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_RANGE)))
-                       /* differs - defeat Range: */
-                       wsi->u.http.ah->frag_index[WSI_TOKEN_HTTP_RANGE] = 0;
-
-       if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH)) {
-               /*
-                * he thinks he has some version of it already,
-                * check if the tag matches
-                */
-               if (!strcmp(sym, lws_hdr_simple_ptr(wsi,
-                                       WSI_TOKEN_HTTP_IF_NONE_MATCH))) {
-
-                       lwsl_debug("%s: ETAG match %s %s\n", __func__,
-                                  uri, origin);
-
-                       /* we don't need to send the payload */
-                       if (lws_add_http_header_status(wsi,
-                                       HTTP_STATUS_NOT_MODIFIED, &p, end))
-                               return -1;
-
-                       if (lws_add_http_header_by_token(wsi,
-                                       WSI_TOKEN_HTTP_ETAG,
-                                       (unsigned char *)sym, n, &p, end))
-                               return -1;
-
-                       if (lws_finalize_http_header(wsi, &p, end))
-                               return -1;
-
-                       n = lws_write(wsi, start, p - start,
-                                     LWS_WRITE_HTTP_HEADERS);
-                       if (n != (p - start)) {
-                               lwsl_err("_write returned %d from %ld\n", n,
-                                        (long)(p - start));
-                               return -1;
-                       }
-
-                       lws_vfs_file_close(&wsi->u.http.fop_fd);
-
-                       return lws_http_transaction_completed(wsi);
-               }
-       }
-
-       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ETAG,
-                       (unsigned char *)sym, n, &p, end))
-               return -1;
-#endif
-
-       mimetype = lws_get_mimetype(path, m);
-       if (!mimetype) {
-               lwsl_err("unknown mimetype for %s\n", path);
-               goto bail;
-       }
-       if (!mimetype[0])
-               lwsl_debug("sending no mimetype for %s\n", path);
-
-       wsi->sending_chunked = 0;
-
-       /*
-        * check if this is in the list of file suffixes to be interpreted by
-        * a protocol
-        */
-       while (pvo) {
-               n = strlen(path);
-               if (n > (int)strlen(pvo->name) &&
-                   !strcmp(&path[n - strlen(pvo->name)], pvo->name)) {
-                       wsi->sending_chunked = 1;
-                       wsi->protocol_interpret_idx = (char)(lws_intptr_t)pvo->value;
-                       lwsl_info("want %s interpreted by %s\n", path,
-                                   wsi->vhost->protocols[(int)(lws_intptr_t)(pvo->value)].name);
-                       wsi->protocol = &wsi->vhost->protocols[(int)(lws_intptr_t)(pvo->value)];
-                       if (lws_ensure_user_space(wsi))
-                               return -1;
-                       break;
-               }
-               pvo = pvo->next;
-       }
-
-       if (m->protocol) {
-               const struct lws_protocols *pp = lws_vhost_name_to_protocol(
-                                                       wsi->vhost, m->protocol);
-
-               if (lws_bind_protocol(wsi, pp))
-                       return 1;
-               args.p = (char *)p;
-               args.max_len = end - p;
-               if (pp->callback(wsi, LWS_CALLBACK_ADD_HEADERS,
-                                         wsi->user_space, &args, 0))
-                       return -1;
-               p = (unsigned char *)args.p;
-       }
-
-       n = lws_serve_http_file(wsi, path, mimetype, (char *)start, p - start);
-
-       if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi)))
-               return -1; /* error or can't reuse connection: close the socket */
-
-       return 0;
-bail:
-
-       return -1;
-}
-
-const struct lws_http_mount *
-lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len)
-{
-       const struct lws_http_mount *hm, *hit = NULL;
-       int best = 0;
-
-       hm = wsi->vhost->mount_list;
-       while (hm) {
-               if (uri_len >= hm->mountpoint_len &&
-                   !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) &&
-                   (uri_ptr[hm->mountpoint_len] == '\0' ||
-                    uri_ptr[hm->mountpoint_len] == '/' ||
-                    hm->mountpoint_len == 1)
-                   ) {
-                       if (hm->origin_protocol == LWSMPRO_CALLBACK ||
-                           ((hm->origin_protocol == LWSMPRO_CGI ||
-                            lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) ||
-                            hm->protocol) &&
-                           hm->mountpoint_len > best)) {
-                               best = hm->mountpoint_len;
-                               hit = hm;
-                       }
-               }
-               hm = hm->mount_next;
-       }
-
-       return hit;
-}
-
-#if LWS_POSIX
-
-static int
-lws_find_string_in_file(const char *filename, const char *string, int stringlen)
-{
-       char buf[128];
-       int fd, match = 0, pos = 0, n = 0, hit = 0;
-
-       fd = open(filename, O_RDONLY);
-       if (fd < 0) {
-               lwsl_err("can't open auth file: %s\n", filename);
-               return 1;
-       }
-
-       while (1) {
-               if (pos == n) {
-                       n = read(fd, buf, sizeof(buf));
-                       if (n <= 0) {
-                               if (match == stringlen)
-                                       hit = 1;
-                               break;
-                       }
-                       pos = 0;
-               }
-
-               if (match == stringlen) {
-                       if (buf[pos] == '\r' || buf[pos] == '\n') {
-                               hit = 1;
-                               break;
-                       }
-                       match = 0;
-               }
-
-               if (buf[pos] == string[match])
-                       match++;
-               else
-                       match = 0;
-
-               pos++;
-       }
-
-       close(fd);
-
-       return hit;
-}
-
-static int
-lws_unauthorised_basic_auth(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       unsigned char *start = pt->serv_buf + LWS_PRE,
-                     *p = start, *end = p + 512;
-       char buf[64];
-       int n;
-
-       /* no auth... tell him it is required */
-
-       if (lws_add_http_header_status(wsi, HTTP_STATUS_UNAUTHORIZED, &p, end))
-               return -1;
-
-       n = lws_snprintf(buf, sizeof(buf), "Basic realm=\"lwsws\"");
-       if (lws_add_http_header_by_token(wsi,
-                       WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
-                       (unsigned char *)buf, n, &p, end))
-               return -1;
-
-       if (lws_finalize_http_header(wsi, &p, end))
-               return -1;
-
-       n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
-       if (n < 0)
-               return -1;
-
-       return lws_http_transaction_completed(wsi);
-
-}
-
-#endif
-
-int lws_clean_url(char *p)
-{
-       if (p[0] == 'h' && p[1] == 't' && p[2] == 't' && p[3] == 'p') {
-               p += 4;
-               if (*p == 's')
-               p++;
-               if (*p == ':') {
-                       p++;
-                       if (*p == '/')
-                       p++;
-               }
-       }
-
-       while (*p) {
-               if (p[0] == '/' && p[1] == '/') {
-                       char *p1 = p;
-                       while (*p1) {
-                               *p1 = p1[1];
-                               p1++;
-                       }
-                       continue;
-               }
-               p++;
-       }
-
-       return 0;
-}
-
-int
-lws_http_action(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       enum http_connection_type connection_type;
-       enum http_version request_version;
-       char content_length_str[32];
-       struct lws_process_html_args args;
-       const struct lws_http_mount *hit = NULL;
-       unsigned int n, count = 0;
-       char http_version_str[10];
-       char http_conn_str[20];
-       int http_version_len;
-       char *uri_ptr = NULL, *s;
-       int uri_len = 0;
-       int meth = -1;
-
-       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,
-#ifdef LWS_USE_HTTP2
-               WSI_TOKEN_HTTP_COLON_PATH,
-#endif
-       };
-#if defined(_DEBUG) || defined(LWS_WITH_ACCESS_LOG)
-       static const char * const method_names[] = {
-               "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT",
-#ifdef LWS_USE_HTTP2
-               ":path",
-#endif
-       };
-#endif
-       static const char * const oprot[] = {
-               "http://", "https://"
-       };
-
-       /* it's not websocket.... shall we accept it as http? */
-
-       for (n = 0; n < ARRAY_SIZE(methods); n++)
-               if (lws_hdr_total_length(wsi, methods[n]))
-                       count++;
-       if (!count) {
-               lwsl_warn("Missing URI in HTTP request\n");
-               goto bail_nuke_ah;
-       }
-
-       if (count != 1) {
-               lwsl_warn("multiple methods?\n");
-               goto bail_nuke_ah;
-       }
-
-       if (lws_ensure_user_space(wsi))
-               goto bail_nuke_ah;
-
-       for (n = 0; n < ARRAY_SIZE(methods); n++)
-               if (lws_hdr_total_length(wsi, methods[n])) {
-                       uri_ptr = lws_hdr_simple_ptr(wsi, methods[n]);
-                       uri_len = lws_hdr_total_length(wsi, methods[n]);
-                       lwsl_info("Method: %s request for '%s'\n",
-                                       method_names[n], uri_ptr);
-                       meth = n;
-                       break;
-               }
-
-       (void)meth;
-
-       /* we insist on absolute paths */
-
-       if (!uri_ptr || uri_ptr[0] != '/') {
-               lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
-
-               goto bail_nuke_ah;
-       }
-
-       /* HTTP header had a content length? */
-
-       wsi->u.http.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;
-
-       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);
-       }
-
-       if (wsi->http2_substream) {
-               wsi->u.http.request_version = HTTP_VERSION_2;
-       } else {
-               /* http_version? Default to 1.0, override with token: */
-               request_version = HTTP_VERSION_1_0;
-
-               /* Works for single digit HTTP versions. : */
-               http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP);
-               if (http_version_len > 7) {
-                       lws_hdr_copy(wsi, http_version_str,
-                                       sizeof(http_version_str) - 1, WSI_TOKEN_HTTP);
-                       if (http_version_str[5] == '1' && http_version_str[7] == '1')
-                               request_version = HTTP_VERSION_1_1;
-               }
-               wsi->u.http.request_version = request_version;
-
-               /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */
-               if (request_version == HTTP_VERSION_1_1)
-                       connection_type = HTTP_CONNECTION_KEEP_ALIVE;
-               else
-                       connection_type = HTTP_CONNECTION_CLOSE;
-
-               /* Override default if http "Connection:" header: */
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) {
-                       lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1,
-                                    WSI_TOKEN_CONNECTION);
-                       http_conn_str[sizeof(http_conn_str) - 1] = '\0';
-                       if (!strcasecmp(http_conn_str, "keep-alive"))
-                               connection_type = HTTP_CONNECTION_KEEP_ALIVE;
-                       else
-                               if (!strcasecmp(http_conn_str, "close"))
-                                       connection_type = HTTP_CONNECTION_CLOSE;
-               }
-               wsi->u.http.connection_type = connection_type;
-       }
-
-       n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION,
-                                   wsi->user_space, uri_ptr, uri_len);
-       if (n) {
-               lwsl_info("LWS_CALLBACK_HTTP closing\n");
-
-               return 1;
-       }
-       /*
-        * if there is content supposed to be coming,
-        * put a timeout on it having arrived
-        */
-       lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
-                       wsi->context->timeout_secs);
-#ifdef LWS_OPENSSL_SUPPORT
-       if (wsi->redirect_to_https) {
-               /*
-                * we accepted http:// only so we could redirect to
-                * https://, so issue the redirect.  Create the redirection
-                * URI from the host: header and ignore the path part
-                */
-               unsigned char *start = pt->serv_buf + LWS_PRE, *p = start,
-                             *end = p + 512;
-
-               if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST))
-                       goto bail_nuke_ah;
-
-               n = sprintf((char *)end, "https://%s/",
-                           lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
-
-               n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
-                                     end, n, &p, end);
-               if ((int)n < 0)
-                       goto bail_nuke_ah;
-
-               return lws_http_transaction_completed(wsi);
-       }
-#endif
-
-#ifdef LWS_WITH_ACCESS_LOG
-       /*
-        * Produce Apache-compatible log string for wsi, like this:
-        *
-        * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800]
-        * "GET /aep-screen.png HTTP/1.1"
-        * 200 152987 "https://libwebsockets.org/index.html"
-        * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36"
-        *
-        */
-       {
-               static const char * const hver[] = {
-                       "http/1.0", "http/1.1", "http/2"
-               };
-#ifdef LWS_USE_IPV6
-               char ads[INET6_ADDRSTRLEN];
-#else
-               char ads[INET_ADDRSTRLEN];
-#endif
-               char da[64];
-               const char *pa, *me;
-               struct tm *tmp;
-               time_t t = time(NULL);
-               int l = 256;
-
-               if (wsi->access_log_pending)
-                       lws_access_log(wsi);
-
-               wsi->access_log.header_log = lws_malloc(l);
-               if (wsi->access_log.header_log) {
-
-                       tmp = localtime(&t);
-                       if (tmp)
-                               strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp);
-                       else
-                               strcpy(da, "01/Jan/1970:00:00:00 +0000");
-
-                       pa = lws_get_peer_simple(wsi, ads, sizeof(ads));
-                       if (!pa)
-                               pa = "(unknown)";
-
-                       me = method_names[meth];
-
-                       lws_snprintf(wsi->access_log.header_log, l,
-                                "%s - - [%s] \"%s %s %s\"",
-                                pa, da, me, uri_ptr,
-                                hver[wsi->u.http.request_version]);
-
-                       l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT);
-                       if (l) {
-                               wsi->access_log.user_agent = lws_malloc(l + 2);
-                               if (wsi->access_log.user_agent)
-                                       lws_hdr_copy(wsi, wsi->access_log.user_agent,
-                                                       l + 1, WSI_TOKEN_HTTP_USER_AGENT);
-                               else
-                                       lwsl_err("OOM getting user agent\n");
-                       }
-                       wsi->access_log_pending = 1;
-               }
-       }
-#endif
-
-       /* can we serve it from the mount list? */
-
-       hit = lws_find_mount(wsi, uri_ptr, uri_len);
-       if (!hit) {
-               /* deferred cleanup and reset to protocols[0] */
-
-               lwsl_info("no hit\n");
-
-               if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0]))
-                       return 1;
-
-               n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
-                                   wsi->user_space, uri_ptr, uri_len);
-
-               goto after;
-       }
-
-       s = uri_ptr + hit->mountpoint_len;
-
-       /*
-        * if we have a mountpoint like https://xxx.com/yyy
-        * there is an implied / at the end for our purposes since
-        * we can only mount on a "directory".
-        *
-        * But if we just go with that, the browser cannot understand
-        * that he is actually looking down one "directory level", so
-        * even though we give him /yyy/abc.html he acts like the
-        * current directory level is /.  So relative urls like "x.png"
-        * wrongly look outside the mountpoint.
-        *
-        * Therefore if we didn't come in on a url with an explicit
-        * / at the end, we must redirect to add it so the browser
-        * understands he is one "directory level" down.
-        */
-       if ((hit->mountpoint_len > 1 ||
-            (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
-             hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
-           (*s != '/' ||
-            (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
-             hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
-           (hit->origin_protocol != LWSMPRO_CGI &&
-            hit->origin_protocol != LWSMPRO_CALLBACK //&&
-            //hit->protocol == NULL
-            )) {
-               unsigned char *start = pt->serv_buf + LWS_PRE,
-                             *p = start, *end = p + 512;
-
-               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);
-
-               lwsl_notice("%s\n", end);
-               lws_clean_url((char *)end);
-               lwsl_notice("%s\n", end);
-
-               n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
-                                     end, n, &p, end);
-               if ((int)n < 0)
-                       goto bail_nuke_ah;
-
-               return lws_http_transaction_completed(wsi);
-       }
-
-#if LWS_POSIX
-       /* basic auth? */
-
-       if (hit->basic_auth_login_file) {
-               char b64[160], plain[(sizeof(b64) * 3) / 4];
-               int m;
-
-               /* Did he send auth? */
-               if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AUTHORIZATION))
-                       return lws_unauthorised_basic_auth(wsi);
-
-               n = HTTP_STATUS_FORBIDDEN;
-
-               m = lws_hdr_copy(wsi, b64, sizeof(b64), WSI_TOKEN_HTTP_AUTHORIZATION);
-               if (m < 7) {
-                       lwsl_err("b64 auth too long\n");
-                       goto transaction_result_n;
-               }
-
-               b64[5] = '\0';
-               if (strcasecmp(b64, "Basic")) {
-                       lwsl_err("auth missing basic: %s\n", b64);
-                       goto transaction_result_n;
-               }
-
-               /* It'll be like Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l */
-
-               m = lws_b64_decode_string(b64 + 6, plain, sizeof(plain));
-               if (m < 0) {
-                       lwsl_err("plain auth too long\n");
-                       goto transaction_result_n;
-               }
-
-//             lwsl_notice(plain);
-
-               if (!lws_find_string_in_file(hit->basic_auth_login_file, plain, m)) {
-                       lwsl_err("basic auth lookup failed\n");
-                       return lws_unauthorised_basic_auth(wsi);
-               }
-
-               lwsl_notice("basic auth accepted\n");
-
-               /* accept the auth */
-       }
-#endif
-
-#if defined(LWS_WITH_HTTP_PROXY)
-       /*
-        * The mount is a reverse proxy?
-        */
-
-       if (hit->origin_protocol == LWSMPRO_HTTPS ||
-           hit->origin_protocol == LWSMPRO_HTTP)  {
-               struct lws_client_connect_info i;
-               char ads[96], rpath[256], *pcolon, *pslash, *p;
-               int n, na;
-
-               memset(&i, 0, sizeof(i));
-               i.context = lws_get_context(wsi);
-
-               pcolon = strchr(hit->origin, ':');
-               pslash = strchr(hit->origin, '/');
-               if (!pslash) {
-                       lwsl_err("Proxy mount origin '%s' must have /\n", hit->origin);
-                       return -1;
-               }
-               if (pcolon > pslash)
-                       pcolon = NULL;
-               
-               if (pcolon)
-                       n = pcolon - hit->origin;
-               else
-                       n = pslash - hit->origin;
-
-               if (n >= sizeof(ads) - 2)
-                       n = sizeof(ads) - 2;
-
-               memcpy(ads, hit->origin, n);
-               ads[n] = '\0';
-
-               i.address = ads;
-               i.port = 80;
-               if (hit->origin_protocol == LWSMPRO_HTTPS) { 
-                       i.port = 443;
-                       i.ssl_connection = 1;
-               }
-               if (pcolon)
-                       i.port = atoi(pcolon + 1);
-               
-               lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", pslash + 1, uri_ptr + hit->mountpoint_len);
-               lws_clean_url(rpath);
-               na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS);
-               if (na) {
-                       p = rpath + strlen(rpath);
-                       *p++ = '?';
-                       lws_hdr_copy(wsi, p, &rpath[sizeof(rpath) - 1] - p, WSI_TOKEN_HTTP_URI_ARGS);
-                       while (--na) {
-                               if (*p == '\0')
-                                       *p = '&';
-                               p++;
-                       }
-               }
-                               
-
-               i.path = rpath;
-               i.host = i.address;
-               i.origin = NULL;
-               i.method = "GET";
-               i.parent_wsi = wsi;
-               i.uri_replace_from = hit->origin;
-               i.uri_replace_to = hit->mountpoint;
-
-               lwsl_notice("proxying to %s port %d url %s, ssl %d, from %s, to %s\n",
-                               i.address, i.port, i.path, i.ssl_connection, i.uri_replace_from, i.uri_replace_to);
-       
-               if (!lws_client_connect_via_info(&i)) {
-                       lwsl_err("proxy connect fail\n");
-                       return 1;
-               }
-
-               return 0;
-       }
-#endif
-
-       /*
-        * A particular protocol callback is mounted here?
-        *
-        * For the duration of this http transaction, bind us to the
-        * associated protocol
-        */
-       if (hit->origin_protocol == LWSMPRO_CALLBACK || hit->protocol) {
-               const struct lws_protocols *pp;
-               const char *name = hit->origin;
-               if (hit->protocol)
-                       name = hit->protocol;
-
-               pp = lws_vhost_name_to_protocol(wsi->vhost, name);
-               if (!pp) {
-                       n = -1;
-                       lwsl_err("Unable to find plugin '%s'\n",
-                                hit->origin);
-                       return 1;
-               }
-
-               if (lws_bind_protocol(wsi, pp))
-                       return 1;
-
-               args.p = uri_ptr;
-               args.len = uri_len;
-               args.max_len = hit->auth_mask;
-               args.final = 0; /* used to signal callback dealt with it */
-
-               n = wsi->protocol->callback(wsi, LWS_CALLBACK_CHECK_ACCESS_RIGHTS,
-                                           wsi->user_space, &args, 0);
-               if (n) {
-                       lws_return_http_status(wsi, HTTP_STATUS_UNAUTHORIZED,
-                                              NULL);
-                       goto bail_nuke_ah;
-               }
-               if (args.final) /* callback completely handled it well */
-                       return 0;
-
-               if (hit->cgienv && wsi->protocol->callback(wsi,
-                               LWS_CALLBACK_HTTP_PMO,
-                               wsi->user_space, (void *)hit->cgienv, 0))
-                       return 1;
-
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) {
-                       n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
-                                           wsi->user_space,
-                                           uri_ptr + hit->mountpoint_len,
-                                           uri_len - hit->mountpoint_len);
-                       goto after;
-               }
-       }
-
-#ifdef LWS_WITH_CGI
-       /* did we hit something with a cgi:// origin? */
-       if (hit->origin_protocol == LWSMPRO_CGI) {
-               const char *cmd[] = {
-                       NULL, /* replace with cgi path */
-                       NULL
-               };
-
-               lwsl_debug("%s: cgi\n", __func__);
-               cmd[0] = hit->origin;
-
-               n = 5;
-               if (hit->cgi_timeout)
-                       n = hit->cgi_timeout;
-
-               n = lws_cgi(wsi, cmd, hit->mountpoint_len, n,
-                           hit->cgienv);
-               if (n) {
-                       lwsl_err("%s: cgi failed\n", __func__);
-                       return -1;
-               }
-
-               goto deal_body;
-       }
-#endif
-
-       n = strlen(s);
-       if (s[0] == '\0' || (n == 1 && s[n - 1] == '/'))
-               s = (char *)hit->def;
-       if (!s)
-               s = "index.html";
-
-       wsi->cache_secs = hit->cache_max_age;
-       wsi->cache_reuse = hit->cache_reusable;
-       wsi->cache_revalidate = hit->cache_revalidate;
-       wsi->cache_intermediaries = hit->cache_intermediaries;
-
-       n = lws_http_serve(wsi, s, hit->origin, hit);
-       if (n) {
-               /*
-                *      lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
-                */
-               if (hit->protocol) {
-                       const struct lws_protocols *pp = lws_vhost_name_to_protocol(
-                                       wsi->vhost, hit->protocol);
-
-                       if (lws_bind_protocol(wsi, pp))
-                               return 1;
-
-                       n = pp->callback(wsi, LWS_CALLBACK_HTTP,
-                                        wsi->user_space,
-                                        uri_ptr + hit->mountpoint_len,
-                                        uri_len - hit->mountpoint_len);
-               } else
-                       n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
-                                   wsi->user_space, uri_ptr, uri_len);
-       }
-
-after:
-       if (n) {
-               lwsl_info("LWS_CALLBACK_HTTP closing\n");
-
-               return 1;
-       }
-
-#ifdef LWS_WITH_CGI
-deal_body:
-#endif
-       /*
-        * If we're not issuing a file, check for content_length or
-        * HTTP keep-alive. No keep-alive header allocation for
-        * ISSUING_FILE, as this uses HTTP/1.0.
-        *
-        * In any case, return 0 and let lws_read decide how to
-        * proceed based on state
-        */
-       if (wsi->state != LWSS_HTTP_ISSUING_FILE)
-               /* Prepare to read body if we have a content length: */
-               if (wsi->u.http.content_length > 0)
-                       wsi->state = LWSS_HTTP_BODY;
-
-       return 0;
-
-bail_nuke_ah:
-       /* we're closing, losing some rx is OK */
-       lws_header_table_force_to_detachable_state(wsi);
-       // lwsl_notice("%s: drop1\n", __func__);
-       lws_header_table_detach(wsi, 1);
-
-       return 1;
-#if LWS_POSIX
-transaction_result_n:
-       lws_return_http_status(wsi, n, NULL);
-
-       return lws_http_transaction_completed(wsi);
-#endif
-}
-
-static int
-lws_server_init_wsi_for_ws(struct lws *wsi)
-{
-       int n;
-
-       wsi->state = LWSS_ESTABLISHED;
-       lws_restart_ws_ping_pong_timer(wsi);
-
-       /*
-        * create the frame buffer for this connection according to the
-        * size mentioned in the protocol definition.  If 0 there, use
-        * a big default for compatibility
-        */
-
-       n = wsi->protocol->rx_buffer_size;
-       if (!n)
-               n = wsi->context->pt_serv_buf_size;
-       n += LWS_PRE;
-       wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */);
-       if (!wsi->u.ws.rx_ubuf) {
-               lwsl_err("Out of Mem allocating rx buffer %d\n", n);
-               return 1;
-       }
-       wsi->u.ws.rx_ubuf_alloc = n;
-       lwsl_debug("Allocating RX buffer %d\n", n);
-
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-       if (!wsi->parent_carries_io)
-               if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
-                      (const char *)&n, sizeof n)) {
-                       lwsl_warn("Failed to set SNDBUF to %d", n);
-                       return 1;
-               }
-#endif
-
-       /* notify user code that we're ready to roll */
-
-       if (wsi->protocol->callback)
-               if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED,
-                                           wsi->user_space,
-#ifdef LWS_OPENSSL_SUPPORT
-                                           wsi->ssl,
-#else
-                                           NULL,
-#endif
-                                           0))
-                       return 1;
-
-       return 0;
-}
-
-int
-lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len)
-{
-       int protocol_len, n = 0, hit, non_space_char_found = 0, m;
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       struct _lws_header_related hdr;
-       struct allocated_headers *ah;
-       unsigned char *obuf = *buf;
-       char protocol_list[128];
-       char protocol_name[64];
-       size_t olen = len;
-       char *p;
-
-       if (len >= 10000000) {
-               lwsl_err("%s: assert: len %ld\n", __func__, (long)len);
-               assert(0);
-       }
-
-       if (!wsi->u.hdr.ah) {
-               lwsl_err("%s: assert: NULL ah\n", __func__);
-               assert(0);
-       }
-
-       while (len--) {
-               wsi->more_rx_waiting = !!len;
-
-               if (wsi->mode != LWSCM_HTTP_SERVING &&
-                   wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED) {
-                       lwsl_err("%s: bad wsi mode %d\n", __func__, wsi->mode);
-                       goto bail_nuke_ah;
-               }
-
-               m = lws_parse(wsi, *(*buf)++);
-               if (m) {
-                       if (m == 2) {
-                               /*
-                                * we are transitioning from http with
-                                * an AH, to raw.  Drop the ah and set
-                                * the mode.
-                                */
-raw_transition:
-                               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-                               lws_bind_protocol(wsi, &wsi->vhost->protocols[
-                                                       wsi->vhost->
-                                                       raw_protocol_index]);
-                               lwsl_info("transition to raw vh %s prot %d\n",
-                                         wsi->vhost->name,
-                                         wsi->vhost->raw_protocol_index);
-                               if ((wsi->protocol->callback)(wsi,
-                                               LWS_CALLBACK_RAW_ADOPT,
-                                               wsi->user_space, NULL, 0))
-                                       goto bail_nuke_ah;
-
-                               lws_header_table_force_to_detachable_state(wsi);
-                               lws_union_transition(wsi, LWSCM_RAW);
-                               lws_header_table_detach(wsi, 1);
-
-                               if (m == 2 && (wsi->protocol->callback)(wsi,
-                                               LWS_CALLBACK_RAW_RX,
-                                               wsi->user_space, obuf, olen))
-                                       return 1;
-
-                               return 0;
-                       }
-                       lwsl_info("lws_parse failed\n");
-                       goto bail_nuke_ah;
-               }
-
-               if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE)
-                       continue;
-
-               lwsl_parser("%s: lws_parse sees parsing complete\n", __func__);
-               lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__,
-                               wsi->more_rx_waiting);
-
-               /* check for unwelcome guests */
-
-               if (wsi->context->reject_service_keywords) {
-                       const struct lws_protocol_vhost_options *rej =
-                                       wsi->context->reject_service_keywords;
-                       char ua[384], *msg = NULL;
-
-                       if (lws_hdr_copy(wsi, ua, sizeof(ua) - 1,
-                                         WSI_TOKEN_HTTP_USER_AGENT) > 0) {
-                               ua[sizeof(ua) - 1] = '\0';
-                               while (rej) {
-                                       if (strstr(ua, rej->name)) {
-                                               msg = strchr(rej->value, ' ');
-                                               if (msg)
-                                                       msg++;
-                                               lws_return_http_status(wsi, atoi(rej->value), msg);
-
-                                               wsi->vhost->conn_stats.rejected++;
-
-                                               goto bail_nuke_ah;
-                                       }
-                                       rej = rej->next;
-                               }
-                       }
-               }
-
-               /* select vhost */
-
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
-                       struct lws_vhost *vhost = lws_select_vhost(
-                               context, wsi->vhost->listen_port,
-                               lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
-
-                       if (vhost)
-                               wsi->vhost = vhost;
-               } 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 (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECT)) {
-                       lwsl_info("Changing to RAW mode\n");
-                       m = 0;
-                       goto raw_transition;
-               }
-
-               wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT;
-               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
-
-               /* is this websocket protocol or normal http 1.0? */
-
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) {
-                       if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE),
-                                       "websocket")) {
-                               wsi->vhost->conn_stats.ws_upg++;
-                               lwsl_info("Upgrade to ws\n");
-                               goto upgrade_ws;
-                       }
-#ifdef LWS_USE_HTTP2
-                       if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE),
-                                       "h2c")) {
-                               wsi->vhost->conn_stats.http2_upg++;
-                               lwsl_info("Upgrade to h2c\n");
-                               goto upgrade_h2c;
-                       }
-#endif
-                       lwsl_info("Unknown upgrade\n");
-                       /* dunno what he wanted to upgrade to */
-                       goto bail_nuke_ah;
-               }
-
-               /* no upgrade ack... he remained as HTTP */
-
-               lwsl_info("No upgrade\n");
-               ah = wsi->u.hdr.ah;
-
-               lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED);
-               wsi->state = LWSS_HTTP;
-               wsi->u.http.fop_fd = NULL;
-
-               /* expose it at the same offset as u.hdr */
-               wsi->u.http.ah = ah;
-               lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi,
-                          (void *)wsi->u.hdr.ah);
-
-               n = lws_http_action(wsi);
-
-               return n;
-
-#ifdef LWS_USE_HTTP2
-upgrade_h2c:
-               if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) {
-                       lwsl_info("missing http2_settings\n");
-                       goto bail_nuke_ah;
-               }
-
-               lwsl_info("h2c upgrade...\n");
-
-               p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS);
-               /* convert the peer's HTTP-Settings */
-               n = lws_b64_decode_string(p, protocol_list,
-                                         sizeof(protocol_list));
-               if (n < 0) {
-                       lwsl_parser("HTTP2_SETTINGS too long\n");
-                       return 1;
-               }
-
-               /* adopt the header info */
-
-               ah = wsi->u.hdr.ah;
-
-               lws_union_transition(wsi, LWSCM_HTTP2_SERVING);
-
-               /* 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);
-
-               /* HTTP2 union */
-
-               lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings,
-                               (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");
-               n = lws_issue_raw(wsi, (unsigned char *)protocol_list,
-                                       strlen(protocol_list));
-               if (n != strlen(protocol_list)) {
-                       lwsl_debug("http2 switch: ERROR writing to socket\n");
-                       return 1;
-               }
-
-               wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE;
-
-               return 0;
-#endif
-
-upgrade_ws:
-               if (!wsi->protocol)
-                       lwsl_err("NULL protocol at lws_read\n");
-
-               /*
-                * It's websocket
-                *
-                * Select the first protocol we support from the list
-                * the client sent us.
-                *
-                * Copy it to remove header fragmentation
-                */
-
-               if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1,
-                                WSI_TOKEN_PROTOCOL) < 0) {
-                       lwsl_err("protocol list too long");
-                       goto bail_nuke_ah;
-               }
-
-               protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
-               protocol_list[protocol_len] = '\0';
-               p = protocol_list;
-               hit = 0;
-
-               while (*p && !hit) {
-                       n = 0;
-                       non_space_char_found = 0;
-                       while (n < sizeof(protocol_name) - 1 && *p &&
-                              *p != ',') {
-                               // ignore leading spaces
-                               if (!non_space_char_found && *p == ' ') {
-                                       n++;
-                                       continue;
-                               }
-                               non_space_char_found = 1;
-                               protocol_name[n++] = *p++;
-                       }
-                       protocol_name[n] = '\0';
-                       if (*p)
-                               p++;
-
-                       lwsl_info("checking %s\n", protocol_name);
-
-                       n = 0;
-                       while (wsi->vhost->protocols[n].callback) {
-                               lwsl_info("try %s\n", wsi->vhost->protocols[n].name);
-
-                               if (wsi->vhost->protocols[n].name &&
-                                   !strcmp(wsi->vhost->protocols[n].name,
-                                           protocol_name)) {
-                                       wsi->protocol = &wsi->vhost->protocols[n];
-                                       hit = 1;
-                                       break;
-                               }
-
-                               n++;
-                       }
-               }
-
-               /* we didn't find a protocol he wanted? */
-
-               if (!hit) {
-                       if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) {
-                               lwsl_info("No protocol from \"%s\" supported\n",
-                                        protocol_list);
-                               goto bail_nuke_ah;
-                       }
-                       /*
-                        * some clients only have one protocol and
-                        * do not send the protocol list header...
-                        * allow it and match to the vhost's default
-                        * protocol (which itself defaults to zero)
-                        */
-                       lwsl_info("defaulting to prot handler %d\n",
-                               wsi->vhost->default_protocol_index);
-                       n = wsi->vhost->default_protocol_index;
-                       wsi->protocol = &wsi->vhost->protocols[
-                                     (int)wsi->vhost->default_protocol_index];
-               }
-
-               /* allocate wsi->user storage */
-               if (lws_ensure_user_space(wsi))
-                       goto bail_nuke_ah;
-
-               /*
-                * Give the user code a chance to study the request and
-                * have the opportunity to deny it
-                */
-               if ((wsi->protocol->callback)(wsi,
-                               LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
-                               wsi->user_space,
-                             lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) {
-                       lwsl_warn("User code denied connection\n");
-                       goto bail_nuke_ah;
-               }
-
-               /*
-                * Perform the handshake according to the protocol version the
-                * client announced
-                */
-
-               switch (wsi->ietf_spec_revision) {
-               case 13:
-                       lwsl_parser("lws_parse calling handshake_04\n");
-                       if (handshake_0405(context, wsi)) {
-                               lwsl_info("hs0405 has failed the connection\n");
-                               goto bail_nuke_ah;
-                       }
-                       break;
-
-               default:
-                       lwsl_info("Unknown client spec version %d\n",
-                                 wsi->ietf_spec_revision);
-                       goto bail_nuke_ah;
-               }
-
-               lws_same_vh_protocol_insert(wsi, n);
-
-               /* we are upgrading to ws, so http/1.1 and keepalive +
-                * pipelined header considerations about keeping the ah around
-                * no longer apply.  However it's common for the first ws
-                * protocol data to have been coalesced with the browser
-                * upgrade request and to already be in the ah rx buffer.
-                */
-
-               lwsl_info("%s: %p: inheriting ah in ws mode (rxpos:%d, rxlen:%d)\n",
-                         __func__, wsi, wsi->u.hdr.ah->rxpos,
-                         wsi->u.hdr.ah->rxlen);
-               lws_pt_lock(pt);
-               hdr = wsi->u.hdr;
-
-               lws_union_transition(wsi, LWSCM_WS_SERVING);
-               /*
-                * first service is WS mode will notice this, use the RX and
-                * then detach the ah (caution: we are not in u.hdr union
-                * mode any more then... ah_temp member is at start the same
-                * though)
-                *
-                * Because rxpos/rxlen shows something in the ah, we will get
-                * service guaranteed next time around the event loop
-                *
-                * All union members begin with hdr, so we can use it even
-                * though we transitioned to ws union mode (the ah detach
-                * code uses it anyway).
-                */
-               wsi->u.hdr = hdr;
-               lws_pt_unlock(pt);
-
-               lws_server_init_wsi_for_ws(wsi);
-               lwsl_parser("accepted v%02d connection\n",
-                           wsi->ietf_spec_revision);
-
-               /* !!! drop ah unreservedly after ESTABLISHED */
-               if (!wsi->more_rx_waiting) {
-                       lws_header_table_force_to_detachable_state(wsi);
-
-                       //lwsl_notice("%p: dropping ah EST\n", wsi);
-                       lws_header_table_detach(wsi, 1);
-               }
-
-               return 0;
-       } /* while all chars are handled */
-
-       return 0;
-
-bail_nuke_ah:
-       /* drop the header info */
-       /* we're closing, losing some rx is OK */
-       lws_header_table_force_to_detachable_state(wsi);
-       //lwsl_notice("%s: drop2\n", __func__);
-       lws_header_table_detach(wsi, 1);
-
-       return 1;
-}
-
-static int
-lws_get_idlest_tsi(struct lws_context *context)
-{
-       unsigned int lowest = ~0;
-       int n = 0, hit = -1;
-
-       for (; n < context->count_threads; n++) {
-               if ((unsigned int)context->pt[n].fds_count !=
-                   context->fd_limit_per_thread - 1 &&
-                   (unsigned int)context->pt[n].fds_count < lowest) {
-                       lowest = context->pt[n].fds_count;
-                       hit = n;
-               }
-       }
-
-       return hit;
-}
-
-struct lws *
-lws_create_new_server_wsi(struct lws_vhost *vhost)
-{
-       struct lws *new_wsi;
-       int n = lws_get_idlest_tsi(vhost->context);
-
-       if (n < 0) {
-               lwsl_err("no space for new conn\n");
-               return NULL;
-       }
-
-       new_wsi = lws_zalloc(sizeof(struct lws));
-       if (new_wsi == NULL) {
-               lwsl_err("Out of memory for new connection\n");
-               return NULL;
-       }
-
-       new_wsi->tsi = n;
-       lwsl_debug("Accepted wsi %p to context %p, tsi %d\n", new_wsi,
-                   vhost->context, new_wsi->tsi);
-
-       new_wsi->vhost = vhost;
-       new_wsi->context = vhost->context;
-       new_wsi->pending_timeout = NO_PENDING_TIMEOUT;
-       new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
-
-       /* initialize the instance struct */
-
-       new_wsi->state = LWSS_HTTP;
-       new_wsi->mode = LWSCM_HTTP_SERVING;
-       new_wsi->hdr_parsing_completed = 0;
-
-#ifdef LWS_OPENSSL_SUPPORT
-       new_wsi->use_ssl = LWS_SSL_ENABLED(vhost);
-#endif
-
-       /*
-        * these can only be set once the protocol is known
-        * we set an unestablished connection's protocol pointer
-        * to the start of the supported list, so it can look
-        * for matching ones during the handshake
-        */
-       new_wsi->protocol = vhost->protocols;
-       new_wsi->user_space = NULL;
-       new_wsi->ietf_spec_revision = 0;
-       new_wsi->desc.sockfd = LWS_SOCK_INVALID;
-       new_wsi->position_in_fds_table = -1;
-
-       vhost->context->count_wsi_allocated++;
-
-       /*
-        * outermost create notification for wsi
-        * no user_space because no protocol selection
-        */
-       vhost->protocols[0].callback(new_wsi, LWS_CALLBACK_WSI_CREATE,
-                                      NULL, NULL, 0);
-
-       return new_wsi;
-}
-
-LWS_VISIBLE int LWS_WARN_UNUSED_RESULT
-lws_http_transaction_completed(struct lws *wsi)
-{
-       int n = NO_PENDING_TIMEOUT;
-
-       lws_access_log(wsi);
-
-       if (!wsi->hdr_parsing_completed) {
-               lwsl_notice("%s: ignoring, ah parsing incomplete\n", __func__);
-               return 0;
-       }
-
-       lwsl_debug("%s: wsi %p\n", __func__, wsi);
-       /* if we can't go back to accept new headers, drop the connection */
-       if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) {
-               lwsl_info("%s: %p: close connection\n", __func__, wsi);
-               return 1;
-       }
-
-       if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0]))
-               return 1;
-
-       /* 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->hdr_parsing_completed = 0;
-#ifdef LWS_WITH_ACCESS_LOG
-       wsi->access_log.sent = 0;
-#endif
-
-       if (wsi->vhost->keepalive_timeout)
-               n = PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE;
-       lws_set_timeout(wsi, n, wsi->vhost->keepalive_timeout);
-
-       /*
-        * We already know we are on http1.1 / keepalive and the next thing
-        * coming will be another header set.
-        *
-        * If there is no pending rx and we still have the ah, drop it and
-        * reacquire a new ah when the new headers start to arrive.  (Otherwise
-        * we needlessly hog an ah indefinitely.)
-        *
-        * However if there is pending rx and we know from the keepalive state
-        * that is already at least the start of another header set, simply
-        * reset the existing header table and keep it.
-        */
-       if (wsi->u.hdr.ah) {
-               lwsl_info("%s: wsi->more_rx_waiting=%d\n", __func__,
-                               wsi->more_rx_waiting);
-
-               if (!wsi->more_rx_waiting) {
-                       lws_header_table_force_to_detachable_state(wsi);
-                       lws_header_table_detach(wsi, 1);
-#ifdef LWS_OPENSSL_SUPPORT
-                       /*
-                        * additionally... if we are hogging an SSL instance
-                        * with no pending pipelined headers (or ah now), and
-                        * SSL is scarce, drop this connection without waiting
-                        */
-
-                       if (wsi->vhost->use_ssl &&
-                           wsi->context->simultaneous_ssl_restriction &&
-                           wsi->context->simultaneous_ssl ==
-                                  wsi->context->simultaneous_ssl_restriction) {
-                               lwsl_info("%s: simultaneous_ssl_restriction and nothing pipelined\n", __func__);
-                               return 1;
-                       }
-#endif
-               } else {
-                       lws_header_table_reset(wsi, 1);
-                       /*
-                        * If we kept the ah, we should restrict the amount
-                        * of time we are willing to keep it.  Otherwise it
-                        * will be bound the whole time the connection remains
-                        * open.
-                        */
-                       lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH,
-                                       wsi->vhost->keepalive_timeout);
-               }
-       }
-
-       /* If we're (re)starting on headers, need other implied init */
-       wsi->u.hdr.ues = URIES_IDLE;
-
-       lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi);
-
-       return 0;
-}
-
-/* if not a socket, it's a raw, non-ssl file descriptor */
-
-LWS_VISIBLE struct lws *
-lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
-                          lws_sock_file_fd_type fd, const char *vh_prot_name,
-                          struct lws *parent)
-{
-       struct lws_context *context = vh->context;
-       struct lws *new_wsi = lws_create_new_server_wsi(vh);
-       struct lws_context_per_thread *pt;
-       int n, ssl = 0;
-
-       if (!new_wsi) {
-               if (type & LWS_ADOPT_SOCKET && !(type & LWS_ADOPT_WS_PARENTIO))
-                       compatible_close(fd.sockfd);
-               return NULL;
-       }
-       pt = &context->pt[(int)new_wsi->tsi];
-       lws_stats_atomic_bump(context, pt, LWSSTATS_C_CONNECTIONS, 1);
-
-       if (parent) {
-               new_wsi->parent = parent;
-               new_wsi->sibling_list = parent->child_list;
-               parent->child_list = new_wsi;
-
-               if (type & LWS_ADOPT_WS_PARENTIO)
-                       new_wsi->parent_carries_io = 1;
-       }
-
-       new_wsi->desc = fd;
-
-       if (vh_prot_name) {
-               new_wsi->protocol = lws_vhost_name_to_protocol(new_wsi->vhost,
-                                                              vh_prot_name);
-               if (!new_wsi->protocol) {
-                       lwsl_err("Protocol %s not enabled on vhost %s\n",
-                                vh_prot_name, new_wsi->vhost->name);
-                       goto bail;
-               }
-               if (lws_ensure_user_space(new_wsi)) {
-                       lwsl_notice("OOM trying to get user_space\n");
-                       goto bail;
-               }
-               if (type & LWS_ADOPT_WS_PARENTIO) {
-                       new_wsi->desc.sockfd = LWS_SOCK_INVALID;
-                       lwsl_debug("binding to %s\n", new_wsi->protocol->name);
-                       lws_bind_protocol(new_wsi, new_wsi->protocol);
-                       lws_union_transition(new_wsi, LWSCM_WS_SERVING);
-                       lws_server_init_wsi_for_ws(new_wsi);
-
-                       return new_wsi;
-               }
-       } else
-               if (type & LWS_ADOPT_HTTP) /* he will transition later */
-                       new_wsi->protocol =
-                               &vh->protocols[vh->default_protocol_index];
-               else { /* this is the only time he will transition */
-                       lws_bind_protocol(new_wsi,
-                               &vh->protocols[vh->raw_protocol_index]);
-                       lws_union_transition(new_wsi, LWSCM_RAW);
-               }
-
-       if (type & LWS_ADOPT_SOCKET) { /* socket desc */
-               lwsl_debug("%s: new wsi %p, sockfd %d\n", __func__, new_wsi,
-                          (int)(lws_intptr_t)fd.sockfd);
-
-               if (type & LWS_ADOPT_HTTP)
-                       /* the transport is accepted...
-                        * give him time to negotiate */
-                       lws_set_timeout(new_wsi,
-                                       PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
-                                       context->timeout_secs);
-
-#if LWS_POSIX == 0
-#if defined(LWS_WITH_ESP8266)
-               esp8266_tcp_stream_accept(accept_fd, new_wsi);
-#endif
-#endif
-       } else /* file desc */
-               lwsl_debug("%s: new wsi %p, filefd %d\n", __func__, new_wsi,
-                          (int)(lws_intptr_t)fd.filefd);
-
-       /*
-        * A new connection was accepted. Give the user a chance to
-        * set properties of the newly created wsi. There's no protocol
-        * selected yet so we issue this to the vhosts's default protocol,
-        * itself by default protocols[0]
-        */
-       n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
-       if (!(type & LWS_ADOPT_HTTP)) {
-               if (!(type & LWS_ADOPT_SOCKET))
-                       n = LWS_CALLBACK_RAW_ADOPT_FILE;
-               else
-                       n = LWS_CALLBACK_RAW_ADOPT;
-       }
-
-       if (!LWS_SSL_ENABLED(new_wsi->vhost) || !(type & LWS_ADOPT_ALLOW_SSL) ||
-           !(type & LWS_ADOPT_SOCKET)) {
-               /* non-SSL */
-               if (!(type & LWS_ADOPT_HTTP)) {
-                       if (!(type & LWS_ADOPT_SOCKET))
-                               new_wsi->mode = LWSCM_RAW_FILEDESC;
-                       else
-                               new_wsi->mode = LWSCM_RAW;
-               }
-       } else {
-               /* SSL */
-               if (!(type & LWS_ADOPT_HTTP))
-                       new_wsi->mode = LWSCM_SSL_INIT_RAW;
-               else
-                       new_wsi->mode = LWSCM_SSL_INIT;
-
-               ssl = 1;
-       }
-
-       lws_libev_accept(new_wsi, new_wsi->desc);
-       lws_libuv_accept(new_wsi, new_wsi->desc);
-       lws_libevent_accept(new_wsi, new_wsi->desc);
-
-       if (!ssl) {
-               if (insert_wsi_socket_into_fds(context, new_wsi)) {
-                       lwsl_err("%s: fail inserting socket\n", __func__);
-                       goto fail;
-               }
-       } else
-               if (lws_server_socket_service_ssl(new_wsi, fd.sockfd)) {
-                       lwsl_err("%s: fail ssl negotiation\n", __func__);
-                       goto fail;
-               }
-
-       /*
-        *  by deferring callback to this point, after insertion to fds,
-        * lws_callback_on_writable() can work from the callback
-        */
-       if ((new_wsi->protocol->callback)(
-                       new_wsi, n, new_wsi->user_space, NULL, 0))
-               goto fail;
-
-       if (type & LWS_ADOPT_HTTP) {
-               if (!lws_header_table_attach(new_wsi, 0))
-                       lwsl_debug("Attached ah immediately\n");
-               else
-                       lwsl_info("%s: waiting for ah\n", __func__);
-       }
-
-       return new_wsi;
-
-fail:
-       if (type & LWS_ADOPT_SOCKET)
-               lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS);
-
-       return NULL;
-
-bail:
-       lwsl_notice("%s: exiting on bail\n", __func__);
-       if (parent)
-               parent->child_list = new_wsi->sibling_list;
-       if (new_wsi->user_space)
-               lws_free(new_wsi->user_space);
-       lws_free(new_wsi);
-       compatible_close(fd.sockfd);
-
-       return NULL;
-}
-
-LWS_VISIBLE struct lws *
-lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd)
-{
-       lws_sock_file_fd_type fd;
-
-       fd.sockfd = accept_fd;
-       return lws_adopt_descriptor_vhost(vh, LWS_ADOPT_SOCKET |
-                       LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL, fd, NULL, NULL);
-}
-
-LWS_VISIBLE struct lws *
-lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
-{
-       return lws_adopt_socket_vhost(context->vhost_list, accept_fd);
-}
-
-/* Common read-buffer adoption for lws_adopt_*_readbuf */
-static struct lws*
-adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len)
-{
-       struct lws_context_per_thread *pt;
-       struct allocated_headers *ah;
-       struct lws_pollfd *pfd;
-
-       if (!wsi)
-               return NULL;
-
-       if (!readbuf || len == 0)
-               return wsi;
-
-       if (len > sizeof(ah->rx)) {
-               lwsl_err("%s: rx in too big\n", __func__);
-               goto bail;
-       }
-
-       /*
-        * we can't process the initial read data until we can attach an ah.
-        *
-        * if one is available, get it and place the data in his ah rxbuf...
-        * wsi with ah that have pending rxbuf get auto-POLLIN service.
-        *
-        * no autoservice because we didn't get a chance to attach the
-        * readbuf data to wsi or ah yet, and we will do it next if we get
-        * the ah.
-        */
-       if (wsi->u.hdr.ah || !lws_header_table_attach(wsi, 0)) {
-               ah = wsi->u.hdr.ah;
-               memcpy(ah->rx, readbuf, len);
-               ah->rxpos = 0;
-               ah->rxlen = len;
-
-               lwsl_notice("%s: calling service on readbuf ah\n", __func__);
-               pt = &wsi->context->pt[(int)wsi->tsi];
-
-               /* unlike a normal connect, we have the headers already
-                * (or the first part of them anyway).
-                * libuv won't come back and service us without a network
-                * event, so we need to do the header service right here.
-                */
-               pfd = &pt->fds[wsi->position_in_fds_table];
-               pfd->revents |= LWS_POLLIN;
-               lwsl_err("%s: calling service\n", __func__);
-               if (lws_service_fd_tsi(wsi->context, pfd, wsi->tsi))
-                       /* service closed us */
-                       return NULL;
-
-               return wsi;
-       }
-       lwsl_err("%s: deferring handling ah\n", __func__);
-       /*
-        * hum if no ah came, we are on the wait list and must defer
-        * dealing with this until the ah arrives.
-        *
-        * later successful lws_header_table_attach() will apply the
-        * below to the rx buffer (via lws_header_table_reset()).
-        */
-       wsi->u.hdr.preamble_rx = lws_malloc(len);
-       if (!wsi->u.hdr.preamble_rx) {
-               lwsl_err("OOM\n");
-               goto bail;
-       }
-       memcpy(wsi->u.hdr.preamble_rx, readbuf, len);
-       wsi->u.hdr.preamble_rx_len = len;
-
-       return wsi;
-
-bail:
-       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-
-       return NULL;
-}
-
-LWS_VISIBLE struct lws *
-lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd,
-                        const char *readbuf, size_t len)
-{
-        return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd), readbuf, len);
-}
-
-LWS_VISIBLE struct lws *
-lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, lws_sockfd_type accept_fd,
-                        const char *readbuf, size_t len)
-{
-        return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd), readbuf, len);
-}
-
-LWS_VISIBLE int
-lws_server_socket_service(struct lws_context *context, struct lws *wsi,
-                         struct lws_pollfd *pollfd)
-{
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       lws_sockfd_type accept_fd = LWS_SOCK_INVALID;
-       struct allocated_headers *ah;
-       lws_sock_file_fd_type fd;
-       int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL;
-#if LWS_POSIX
-       struct sockaddr_storage cli_addr;
-       socklen_t clilen;
-#endif
-       int n, len;
-       
-       // lwsl_notice("%s: mode %d\n", __func__, wsi->mode);
-
-       switch (wsi->mode) {
-
-       case LWSCM_HTTP_SERVING:
-       case LWSCM_HTTP_SERVING_ACCEPTED:
-       case LWSCM_HTTP2_SERVING:
-       case LWSCM_RAW:
-
-               /* handle http headers coming in */
-
-               /* pending truncated sends have uber priority */
-
-               if (wsi->trunc_len) {
-                       if (!(pollfd->revents & LWS_POLLOUT))
-                               break;
-
-                       if (lws_issue_raw(wsi, wsi->trunc_alloc +
-                                              wsi->trunc_offset,
-                                         wsi->trunc_len) < 0)
-                               goto fail;
-                       /*
-                        * we can't afford to allow input processing to send
-                        * something new, so spin around he event loop until
-                        * he doesn't have any partials
-                        */
-                       break;
-               }
-
-               /* any incoming data ready? */
-
-               if (!(pollfd->revents & pollfd->events & LWS_POLLIN))
-                       goto try_pollout;
-
-               /*
-                * If we previously just did POLLIN when IN and OUT were
-                * signalled (because POLLIN processing may have used up
-                * the POLLOUT), don't let that happen twice in a row...
-                * next time we see the situation favour POLLOUT
-                */
-#if !defined(LWS_WITH_ESP8266)
-               if (wsi->favoured_pollin &&
-                   (pollfd->revents & pollfd->events & LWS_POLLOUT)) {
-                       wsi->favoured_pollin = 0;
-                       goto try_pollout;
-               }
-#endif
-
-               /* these states imply we MUST have an ah attached */
-
-               if (wsi->mode != LWSCM_RAW && (wsi->state == LWSS_HTTP ||
-                   wsi->state == LWSS_HTTP_ISSUING_FILE ||
-                   wsi->state == LWSS_HTTP_HEADERS)) {
-                       if (!wsi->u.hdr.ah) {
-                               
-                               //lwsl_err("wsi %p: missing ah\n", wsi);
-                               /* no autoservice beacuse we will do it next */
-                               if (lws_header_table_attach(wsi, 0)) {
-                                       lwsl_info("wsi %p: failed to acquire ah\n", wsi);
-                                       goto try_pollout;
-                               }
-                       }
-                       ah = wsi->u.hdr.ah;
-
-                       //lwsl_notice("%s: %p: rxpos:%d rxlen:%d\n", __func__, wsi,
-                       //         ah->rxpos, ah->rxlen);
-
-                       /* if nothing in ah rx buffer, get some fresh rx */
-                       if (ah->rxpos == ah->rxlen) {
-                               ah->rxlen = lws_ssl_capable_read(wsi, ah->rx,
-                                                  sizeof(ah->rx));
-                               ah->rxpos = 0;
-                               //lwsl_notice("%s: wsi %p, ah->rxlen = %d\r\n",
-                               //         __func__, wsi, ah->rxlen);
-                               switch (ah->rxlen) {
-                               case 0:
-                                       lwsl_info("%s: read 0 len\n", __func__);
-                                       /* lwsl_info("   state=%d\n", wsi->state); */
-//                                     if (!wsi->hdr_parsing_completed)
-//                                             lws_header_table_detach(wsi);
-                                       /* fallthru */
-                               case LWS_SSL_CAPABLE_ERROR:
-                                       goto fail;
-                               case LWS_SSL_CAPABLE_MORE_SERVICE:
-                                       ah->rxlen = ah->rxpos = 0;
-                                       goto try_pollout;
-                               }
-
-                               /*
-                                *  make sure ah does not get detached if we
-                                * have live data in the rx
-                                */
-                               if (ah->rxlen)
-                                       wsi->more_rx_waiting = 1;
-                       }
-
-                       if (!(ah->rxpos != ah->rxlen && ah->rxlen)) {
-                               lwsl_err("%s: assert: rxpos %d, rxlen %d\n",
-                                        __func__, ah->rxpos, ah->rxlen);
-
-                               assert(0);
-                       }
-                       
-                       /* just ignore incoming if waiting for close */
-                       if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE &&
-                           wsi->state != LWSS_HTTP_ISSUING_FILE) {
-                               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 (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;
-                       }
-
-                       goto try_pollout;
-               }
-
-               len = lws_ssl_capable_read(wsi, pt->serv_buf,
-                                          context->pt_serv_buf_size);
-               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("   state=%d\n", wsi->state); */
-//                     if (!wsi->hdr_parsing_completed)
-//                             lws_header_table_detach(wsi);
-                       /* fallthru */
-               case LWS_SSL_CAPABLE_ERROR:
-                       goto fail;
-               case LWS_SSL_CAPABLE_MORE_SERVICE:
-                       goto try_pollout;
-               }
-               
-               if (wsi->mode == LWSCM_RAW) {
-                       n = user_callback_handle_rxflow(wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_RAW_RX,
-                                       wsi->user_space, pt->serv_buf, len);
-                       if (n < 0) {
-                               lwsl_info("LWS_CALLBACK_RAW_RX_fail\n");
-                               goto fail;
-                       }
-                       goto try_pollout;
-               }
-
-               /* just ignore incoming if waiting for close */
-               if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE &&
-                   wsi->state != LWSS_HTTP_ISSUING_FILE) {
-                       /*
-                        * this may want to send
-                        * (via HTTP callback for example)
-                        */
-                       n = lws_read(wsi, pt->serv_buf, len);
-                       if (n < 0) /* we closed wsi */
-                               return 1;
-                       /*
-                        *  he may have used up the
-                        * writability above, if we will defer POLLOUT
-                        * processing in favour of POLLIN, note it
-                        */
-                       if (pollfd->revents & LWS_POLLOUT)
-                               wsi->favoured_pollin = 1;
-                       break;
-               }
-
-try_pollout:
-               
-               /* this handles POLLOUT for http serving fragments */
-
-               if (!(pollfd->revents & LWS_POLLOUT))
-                       break;
-
-               /* one shot */
-               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
-                       lwsl_notice("%s a\n", __func__);
-                       goto fail;
-               }
-
-               if (wsi->mode == LWSCM_RAW) {
-                       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1);
-#if defined(LWS_WITH_STATS)
-                       {
-                               uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us;
-
-                               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul);
-                               lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul);
-                               wsi->active_writable_req_us = 0;
-                       }
-#endif
-                       n = user_callback_handle_rxflow(wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_RAW_WRITEABLE,
-                                       wsi->user_space, NULL, 0);
-                       if (n < 0) {
-                               lwsl_info("writeable_fail\n");
-                               goto fail;
-                       }
-                       break;
-               }
-
-               if (!wsi->hdr_parsing_completed)
-                       break;
-
-               if (wsi->state != LWSS_HTTP_ISSUING_FILE) {
-
-                       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1);
-#if defined(LWS_WITH_STATS)
-                       {
-                               uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us;
-
-                               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul);
-                               lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul);
-                               wsi->active_writable_req_us = 0;
-                       }
-#endif
-
-                       n = user_callback_handle_rxflow(wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_HTTP_WRITEABLE,
-                                       wsi->user_space, NULL, 0);
-                       if (n < 0) {
-                               lwsl_info("writeable_fail\n");
-                               goto fail;
-                       }
-                       break;
-               }
-
-               /* >0 == completion, <0 == error
-                *
-                * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
-                * it's done.  That's the case even if we just completed the
-                * send, so wait for that.
-                */
-               n = lws_serve_http_file_fragment(wsi);
-               if (n < 0)
-                       goto fail;
-
-               break;
-
-       case LWSCM_SERVER_LISTENER:
-
-#if LWS_POSIX
-               /* pollin means a client has connected to us then */
-
-               do {
-                       if (!(pollfd->revents & LWS_POLLIN) || !(pollfd->events & LWS_POLLIN))
-                               break;
-
-#ifdef LWS_OPENSSL_SUPPORT
-                       /*
-                        * can we really accept it, with regards to SSL limit?
-                        * another vhost may also have had POLLIN on his listener this
-                        * round and used it up already
-                        */
-
-                       if (wsi->vhost->use_ssl &&
-                           context->simultaneous_ssl_restriction &&
-                           context->simultaneous_ssl ==
-                                         context->simultaneous_ssl_restriction)
-                               /* no... ignore it, he won't come again until we are
-                                * below the simultaneous_ssl_restriction limit and
-                                * POLLIN is enabled on him again
-                                */
-                               break;
-#endif
-                       /* listen socket got an unencrypted connection... */
-
-                       clilen = sizeof(cli_addr);
-                       lws_latency_pre(context, wsi);
-                       accept_fd  = accept(pollfd->fd, (struct sockaddr *)&cli_addr,
-                                           &clilen);
-                       lws_latency(context, wsi, "listener accept", accept_fd,
-                                   accept_fd >= 0);
-                       if (accept_fd < 0) {
-                               if (LWS_ERRNO == LWS_EAGAIN ||
-                                   LWS_ERRNO == LWS_EWOULDBLOCK) {
-//                                     lwsl_err("accept asks to try again\n");
-                                       break;
-                               }
-                               lwsl_err("ERROR on accept: %s\n", strerror(LWS_ERRNO));
-                               break;
-                       }
-
-                       lws_plat_set_socket_options(wsi->vhost, accept_fd);
-
-#if defined(LWS_USE_IPV6)
-                       lwsl_debug("accepted new conn port %u on fd=%d\n",
-                                         ((cli_addr.ss_family == AF_INET6) ?
-                                         ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) :
-                                         ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)),
-                                         accept_fd);
-#else
-                       lwsl_debug("accepted new conn port %u on fd=%d\n",
-                                         ntohs(((struct sockaddr_in *) &cli_addr)->sin_port),
-                                         accept_fd);
-#endif
-
-#else
-                       /* not very beautiful... */
-                       accept_fd = (lws_sockfd_type)pollfd;
-#endif
-                       /*
-                        * look at who we connected to and give user code a chance
-                        * to reject based on client IP.  There's no protocol selected
-                        * yet so we issue this to protocols[0]
-                        */
-                       if ((wsi->vhost->protocols[0].callback)(wsi,
-                                       LWS_CALLBACK_FILTER_NETWORK_CONNECTION,
-                                       NULL, (void *)(lws_intptr_t)accept_fd, 0)) {
-                               lwsl_debug("Callback denied network connection\n");
-                               compatible_close(accept_fd);
-                               break;
-                       }
-
-                       if (!(wsi->vhost->options & LWS_SERVER_OPTION_ONLY_RAW))
-                               opts |= LWS_ADOPT_HTTP;
-
-                       fd.sockfd = accept_fd;
-                       if (!lws_adopt_descriptor_vhost(wsi->vhost, opts, fd,
-                                                       NULL, NULL))
-                               /* already closed cleanly as necessary */
-                               return 1;
-
-#if LWS_POSIX
-               } while (pt->fds_count < context->fd_limit_per_thread - 1 &&
-                        lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0);
-#endif
-               return 0;
-
-       default:
-               break;
-       }
-
-       if (!lws_server_socket_service_ssl(wsi, accept_fd))
-               return 0;
-
-fail:
-       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-
-       return 1;
-}
-
-LWS_VISIBLE int
-lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
-                   const char *other_headers, int other_headers_len)
-{
-       static const char * const intermediates[] = { "private", "public" };
-       struct lws_context *context = lws_get_context(wsi);
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-#if defined(LWS_WITH_RANGES)
-       struct lws_range_parsing *rp = &wsi->u.http.range;
-#endif
-       char cache_control[50], *cc = "no-store";
-       unsigned char *response = pt->serv_buf + LWS_PRE;
-       unsigned char *p = response;
-       unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE;
-       lws_filepos_t computed_total_content_length;
-       int ret = 0, cclen = 8, n = HTTP_STATUS_OK;
-       lws_fop_flags_t fflags = LWS_O_RDONLY;
-#if defined(LWS_WITH_RANGES)
-       int ranges;
-#endif
-       const struct lws_plat_file_ops *fops;
-       const char *vpath;
-
-       /*
-        * We either call the platform fops .open with first arg platform fops,
-        * or we call fops_zip .open with first arg platform fops, and fops_zip
-        * open will decide whether to switch to fops_zip or stay with fops_def.
-        *
-        * If wsi->u.http.fop_fd is already set, the caller already opened it
-        */
-       if (!wsi->u.http.fop_fd) {
-               fops = lws_vfs_select_fops(wsi->context->fops, file, &vpath);
-               fflags |= lws_vfs_prepare_flags(wsi);
-               wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
-                                                       file, vpath, &fflags);
-               if (!wsi->u.http.fop_fd) {
-                       lwsl_err("Unable to open '%s'\n", file);
-
-                       return -1;
-               }
-       }
-       wsi->u.http.filelen = lws_vfs_get_length(wsi->u.http.fop_fd);
-       computed_total_content_length = wsi->u.http.filelen;
-
-#if defined(LWS_WITH_RANGES)
-       ranges = lws_ranges_init(wsi, rp, wsi->u.http.filelen);
-
-       lwsl_debug("Range count %d\n", ranges);
-       /*
-        * no ranges -> 200;
-        *  1 range  -> 206 + Content-Type: normal; Content-Range;
-        *  more     -> 206 + Content-Type: multipart/byteranges
-        *              Repeat the true Content-Type in each multipart header
-        *              along with Content-Range
-        */
-       if (ranges < 0) {
-               /* it means he expressed a range in Range:, but it was illegal */
-               lws_return_http_status(wsi, HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, NULL);
-               if (lws_http_transaction_completed(wsi))
-                       return -1; /* <0 means just hang up */
-
-               lws_vfs_file_close(&wsi->u.http.fop_fd);
-
-               return 0; /* == 0 means we dealt with the transaction complete */
-       }
-       if (ranges)
-               n = HTTP_STATUS_PARTIAL_CONTENT;
-#endif
-
-       if (lws_add_http_header_status(wsi, n, &p, end))
-               return -1;
-
-       if ((wsi->u.http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP |
-                      LWS_FOP_FLAG_COMPR_IS_GZIP)) ==
-           (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) {
-               if (lws_add_http_header_by_token(wsi,
-                       WSI_TOKEN_HTTP_CONTENT_ENCODING,
-                       (unsigned char *)"gzip", 4, &p, end))
-                       return -1;
-               lwsl_info("file is being provided in gzip\n");
-       }
-
-       if (
-#if defined(LWS_WITH_RANGES)
-           ranges < 2 &&
-#endif
-           content_type && content_type[0])
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
-                                                (unsigned char *)content_type,
-                                                strlen(content_type), &p, end))
-                       return -1;
-
-#if defined(LWS_WITH_RANGES)
-       if (ranges >= 2) { /* multipart byteranges */
-               strncpy(wsi->u.http.multipart_content_type, content_type,
-                       sizeof(wsi->u.http.multipart_content_type) - 1);
-               wsi->u.http.multipart_content_type[
-                        sizeof(wsi->u.http.multipart_content_type) - 1] = '\0';
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
-                                                (unsigned char *)"multipart/byteranges; boundary=_lws",
-                                                20, &p, end))
-                       return -1;
-
-               /*
-                *  our overall content length has to include
-                *
-                *  - (n + 1) x "_lws\r\n"
-                *  - n x Content-Type: xxx/xxx\r\n
-                *  - n x Content-Range: bytes xxx-yyy/zzz\r\n
-                *  - n x /r/n
-                *  - the actual payloads (aggregated in rp->agg)
-                *
-                *  Precompute it for the main response header
-                */
-
-               computed_total_content_length = (lws_filepos_t)rp->agg +
-                                               6 /* final _lws\r\n */;
-
-               lws_ranges_reset(rp);
-               while (lws_ranges_next(rp)) {
-                       n = lws_snprintf(cache_control, sizeof(cache_control),
-                                       "bytes %llu-%llu/%llu",
-                                       rp->start, rp->end, rp->extent);
-
-                       computed_total_content_length +=
-                                       6 /* header _lws\r\n */ +
-                                       14 + strlen(content_type) + 2 + /* Content-Type: xxx/xxx\r\n */
-                                       15 + n + 2 + /* Content-Range: xxxx\r\n */
-                                       2; /* /r/n */
-               }
-
-               lws_ranges_reset(rp);
-               lws_ranges_next(rp);
-       }
-
-       if (ranges == 1) {
-               computed_total_content_length = (lws_filepos_t)rp->agg;
-               n = lws_snprintf(cache_control, sizeof(cache_control), "bytes %llu-%llu/%llu",
-                               rp->start, rp->end, rp->extent);
-
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_RANGE,
-                                                (unsigned char *)cache_control,
-                                                n, &p, end))
-                       return -1;
-       }
-
-       wsi->u.http.range.inside = 0;
-
-       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT_RANGES,
-                                        (unsigned char *)"bytes", 5, &p, end))
-               return -1;
-#endif
-
-       if (!wsi->sending_chunked) {
-               if (lws_add_http_header_content_length(wsi,
-                                                      computed_total_content_length,
-                                                      &p, end))
-                       return -1;
-       } else {
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING,
-                                                (unsigned char *)"chunked",
-                                                7, &p, end))
-                       return -1;
-       }
-
-       if (wsi->cache_secs && wsi->cache_reuse) {
-               if (wsi->cache_revalidate) {
-                       cc = cache_control;
-                       cclen = sprintf(cache_control, "%s max-age: %u",
-                                   intermediates[wsi->cache_intermediaries],
-                                   wsi->cache_secs);
-               } else {
-                       cc = "no-cache";
-                       cclen = 8;
-               }
-       }
-
-       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CACHE_CONTROL,
-                       (unsigned char *)cc, cclen, &p, end))
-               return -1;
-
-       if (wsi->u.http.connection_type == HTTP_CONNECTION_KEEP_ALIVE)
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION,
-                               (unsigned char *)"keep-alive", 10, &p, end))
-                       return -1;
-
-       if (other_headers) {
-               if ((end - p) < other_headers_len)
-                       return -1;
-               memcpy(p, other_headers, other_headers_len);
-               p += other_headers_len;
-       }
-
-       if (lws_finalize_http_header(wsi, &p, end))
-               return -1;
-
-       ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS);
-       if (ret != (p - response)) {
-               lwsl_err("_write returned %d from %ld\n", ret, (long)(p - response));
-               return -1;
-       }
-
-       wsi->u.http.filepos = 0;
-       wsi->state = LWSS_HTTP_ISSUING_FILE;
-
-       return lws_serve_http_file_fragment(wsi);
-}
-
-int
-lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len)
-{
-       int m;
-
-       lwsl_parser("%s: received %d byte packet\n", __func__, (int)len);
-#if 0
-       lwsl_hexdump(*buf, len);
-#endif
-
-       /* let the rx protocol state machine have as much as it needs */
-
-       while (len) {
-               /*
-                * we were accepting input but now we stopped doing so
-                */
-               if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) {
-                       lws_rxflow_cache(wsi, *buf, 0, len);
-                       lwsl_parser("%s: cached %ld\n", __func__, (long)len);
-                       return 1;
-               }
-
-               if (wsi->u.ws.rx_draining_ext) {
-                       // lwsl_notice("draining with 0\n");
-                       m = lws_rx_sm(wsi, 0);
-                       if (m < 0)
-                               return -1;
-                       continue;
-               }
-
-               /* account for what we're using in rxflow buffer */
-               if (wsi->rxflow_buffer)
-                       wsi->rxflow_pos++;
-
-               /* consume payload bytes efficiently */
-               if (
-                   wsi->lws_rx_parse_state ==
-                   LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED) {
-                       m = lws_payload_until_length_exhausted(wsi, buf, &len);
-                       if (wsi->rxflow_buffer)
-                               wsi->rxflow_pos += m;
-               }
-
-               /* process the byte */
-               m = lws_rx_sm(wsi, *(*buf)++);
-               if (m < 0)
-                       return -1;
-               len--;
-       }
-
-       lwsl_parser("%s: exit with %d unused\n", __func__, (int)len);
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_server_get_canonical_hostname(struct lws_context *context,
-                                 struct lws_context_creation_info *info)
-{
-       if (lws_check_opt(info->options, LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME))
-               return;
-#if LWS_POSIX && !defined(LWS_WITH_ESP32)
-       /* find canonical hostname */
-       gethostname((char *)context->canonical_hostname,
-                   sizeof(context->canonical_hostname) - 1);
-
-       lwsl_notice(" canonical_hostname = %s\n", context->canonical_hostname);
-#else
-       (void)context;
-#endif
-}
-
-#define LWS_MAX_ELEM_NAME 32
-
-enum urldecode_stateful {
-       US_NAME,
-       US_IDLE,
-       US_PC1,
-       US_PC2,
-
-       MT_LOOK_BOUND_IN,
-       MT_HNAME,
-       MT_DISP,
-       MT_TYPE,
-       MT_IGNORE1,
-       MT_IGNORE2,
-};
-
-static const char * const mp_hdr[] = {
-       "content-disposition: ",
-       "content-type: ",
-       "\x0d\x0a"
-};
-
-typedef int (*lws_urldecode_stateful_cb)(void *data,
-               const char *name, char **buf, int len, int final);
-
-struct lws_urldecode_stateful {
-       char *out;
-       void *data;
-       char name[LWS_MAX_ELEM_NAME];
-       char temp[LWS_MAX_ELEM_NAME];
-       char content_type[32];
-       char content_disp[32];
-       char content_disp_filename[256];
-       char mime_boundary[128];
-       int out_len;
-       int pos;
-       int hdr_idx;
-       int mp;
-       int sum;
-
-       unsigned int multipart_form_data:1;
-       unsigned int inside_quote:1;
-       unsigned int subname:1;
-       unsigned int boundary_real_crlf:1;
-
-       enum urldecode_stateful state;
-
-       lws_urldecode_stateful_cb output;
-};
-
-static struct lws_urldecode_stateful *
-lws_urldecode_s_create(struct lws *wsi, char *out, int out_len, void *data,
-                      lws_urldecode_stateful_cb output)
-{
-       struct lws_urldecode_stateful *s = lws_zalloc(sizeof(*s));
-       char buf[200], *p;
-       int m = 0;
-
-       if (!s)
-               return NULL;
-
-       s->out = out;
-       s->out_len  = out_len;
-       s->output = output;
-       s->pos = 0;
-       s->sum = 0;
-       s->mp = 0;
-       s->state = US_NAME;
-       s->name[0] = '\0';
-       s->data = data;
-
-       if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_CONTENT_TYPE) > 0) {
-               /* multipart/form-data; boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */
-
-               if (!strncmp(buf, "multipart/form-data", 19)) {
-                       s->multipart_form_data = 1;
-                       s->state = MT_LOOK_BOUND_IN;
-                       s->mp = 2;
-                       p = strstr(buf, "boundary=");
-                       if (p) {
-                               p += 9;
-                               s->mime_boundary[m++] = '\x0d';
-                               s->mime_boundary[m++] = '\x0a';
-                               s->mime_boundary[m++] = '-';
-                               s->mime_boundary[m++] = '-';
-                               while (m < sizeof(s->mime_boundary) - 1 &&
-                                      *p && *p != ' ')
-                                       s->mime_boundary[m++] = *p++;
-
-                               s->mime_boundary[m] = '\0';
-
-                               lwsl_notice("boundary '%s'\n", s->mime_boundary);
-                       }
-               }
-       }
-
-       return s;
-}
-
-static int
-lws_urldecode_s_process(struct lws_urldecode_stateful *s, const char *in, int len)
-{
-       int n, m, hit = 0;
-       char c, was_end = 0;
-
-       while (len--) {
-               if (s->pos == s->out_len - s->mp - 1) {
-                       if (s->output(s->data, s->name, &s->out, s->pos, 0))
-                               return -1;
-
-                       was_end = s->pos;
-                       s->pos = 0;
-               }
-               switch (s->state) {
-
-               /* states for url arg style */
-
-               case US_NAME:
-                       s->inside_quote = 0;
-                       if (*in == '=') {
-                               s->name[s->pos] = '\0';
-                               s->pos = 0;
-                               s->state = US_IDLE;
-                               in++;
-                               continue;
-                       }
-                       if (*in == '&') {
-                               s->name[s->pos] = '\0';
-                               if (s->output(s->data, s->name, &s->out, s->pos, 1))
-                                       return -1;
-                               s->pos = 0;
-                               s->state = US_IDLE;
-                               in++;
-                               continue;
-                       }
-                       if (s->pos >= sizeof(s->name) - 1) {
-                               lwsl_notice("Name too long\n");
-                               return -1;
-                       }
-                       s->name[s->pos++] = *in++;
-                       break;
-               case US_IDLE:
-                       if (*in == '%') {
-                               s->state++;
-                               in++;
-                               continue;
-                       }
-                       if (*in == '&') {
-                               s->out[s->pos] = '\0';
-                               if (s->output(s->data, s->name, &s->out, s->pos, 1))
-                                       return -1;
-                               s->pos = 0;
-                               s->state = US_NAME;
-                               in++;
-                               continue;
-                       }
-                       if (*in == '+') {
-                               in++;
-                               s->out[s->pos++] = ' ';
-                               continue;
-                       }
-                       s->out[s->pos++] = *in++;
-                       break;
-               case US_PC1:
-                       n = char_to_hex(*in);
-                       if (n < 0)
-                               return -1;
-
-                       in++;
-                       s->sum = n << 4;
-                       s->state++;
-                       break;
-
-               case US_PC2:
-                       n = char_to_hex(*in);
-                       if (n < 0)
-                               return -1;
-
-                       in++;
-                       s->out[s->pos++] = s->sum | n;
-                       s->state = US_IDLE;
-                       break;
-
-
-               /* states for multipart / mime style */
-
-               case MT_LOOK_BOUND_IN:
-retry_as_first:
-                       if (*in == s->mime_boundary[s->mp] &&
-                           s->mime_boundary[s->mp]) {
-                               in++;
-                               s->mp++;
-                               if (!s->mime_boundary[s->mp]) {
-                                       s->mp = 0;
-                                       s->state = MT_IGNORE1;
-
-                                       if (s->pos || was_end)
-                                               if (s->output(s->data, s->name,
-                                                     &s->out, s->pos, 1))
-                                                       return -1;
-
-                                       s->pos = 0;
-
-                                       s->content_disp[0] = '\0';
-                                       s->name[0] = '\0';
-                                       s->content_disp_filename[0] = '\0';
-                                       s->boundary_real_crlf = 1;
-                               }
-                               continue;
-                       }
-                       if (s->mp) {
-                               n = 0;
-                               if (!s->boundary_real_crlf)
-                                       n = 2;
-
-                               memcpy(s->out + s->pos, s->mime_boundary + n, s->mp - n);
-                               s->pos += s->mp;
-                               s->mp = 0;
-                               goto retry_as_first;
-                       }
-
-                       s->out[s->pos++] = *in;
-                       in++;
-                       s->mp = 0;
-                       break;
-
-               case MT_HNAME:
-                       m = 0;
-                       c =*in;
-                       if (c >= 'A' && c <= 'Z')
-                               c += 'a' - 'A';
-                       for (n = 0; n < ARRAY_SIZE(mp_hdr); n++)
-                               if (c == mp_hdr[n][s->mp]) {
-                                       m++;
-                                       hit = n;
-                               }
-                       in++;
-                       if (!m) {
-                               s->mp = 0;
-                               continue;
-                       }
-
-                       s->mp++;
-                       if (m != 1)
-                               continue;
-
-                       if (mp_hdr[hit][s->mp])
-                               continue;
-
-                       s->mp = 0;
-                       s->temp[0] = '\0';
-                       s->subname = 0;
-
-                       if (hit == 2)
-                               s->state = MT_LOOK_BOUND_IN;
-                       else
-                               s->state += hit + 1;
-                       break;
-
-               case MT_DISP:
-                       /* form-data; name="file"; filename="t.txt" */
-
-                       if (*in == '\x0d') {
-//                             lwsl_notice("disp: '%s', '%s', '%s'\n",
-//                                s->content_disp, s->name,
-//                                s->content_disp_filename);
-
-                               if (s->content_disp_filename[0])
-                                       if (s->output(s->data, s->name,
-                                                     &s->out, s->pos, LWS_UFS_OPEN))
-                                               return -1;
-                               s->state = MT_IGNORE2;
-                               goto done;
-                       }
-                       if (*in == ';') {
-                               s->subname = 1;
-                               s->temp[0] = '\0';
-                               s->mp = 0;
-                               goto done;
-                       }
-
-                       if (*in == '\"') {
-                               s->inside_quote ^= 1;
-                               goto done;
-                       }
-
-                       if (s->subname) {
-                               if (*in == '=') {
-                                       s->temp[s->mp] = '\0';
-                                       s->subname = 0;
-                                       s->mp = 0;
-                                       goto done;
-                               }
-                               if (s->mp < sizeof(s->temp) - 1 &&
-                                   (*in != ' ' || s->inside_quote))
-                                       s->temp[s->mp++] = *in;
-                               goto done;
-                       }
-
-                       if (!s->temp[0]) {
-                               if (s->mp < sizeof(s->content_disp) - 1)
-                                       s->content_disp[s->mp++] = *in;
-                               s->content_disp[s->mp] = '\0';
-                               goto done;
-                       }
-
-                       if (!strcmp(s->temp, "name")) {
-                               if (s->mp < sizeof(s->name) - 1)
-                                       s->name[s->mp++] = *in;
-                               s->name[s->mp] = '\0';
-                               goto done;
-                       }
-
-                       if (!strcmp(s->temp, "filename")) {
-                               if (s->mp < sizeof(s->content_disp_filename) - 1)
-                                       s->content_disp_filename[s->mp++] = *in;
-                               s->content_disp_filename[s->mp] = '\0';
-                               goto done;
-                       }
-done:
-                       in++;
-                       break;
-
-               case MT_TYPE:
-                       if (*in == '\x0d')
-                               s->state = MT_IGNORE2;
-                       else {
-                               if (s->mp < sizeof(s->content_type) - 1)
-                                       s->content_type[s->mp++] = *in;
-                               s->content_type[s->mp] = '\0';
-                       }
-                       in++;
-                       break;
-
-               case MT_IGNORE1:
-                       if (*in == '\x0d')
-                               s->state = MT_IGNORE2;
-                       in++;
-                       break;
-
-               case MT_IGNORE2:
-                       s->mp = 0;
-                       if (*in == '\x0a')
-                               s->state = MT_HNAME;
-                       in++;
-                       break;
-               }
-       }
-
-       return 0;
-}
-
-static int
-lws_urldecode_s_destroy(struct lws_urldecode_stateful *s)
-{
-       int ret = 0;
-
-       if (s->state != US_IDLE)
-               ret = -1;
-
-       if (!ret)
-               if (s->output(s->data, s->name, &s->out, s->pos, 1))
-                       ret = -1;
-
-       lws_free(s);
-
-       return ret;
-}
-
-struct lws_spa {
-       struct lws_urldecode_stateful *s;
-       lws_spa_fileupload_cb opt_cb;
-       const char * const *param_names;
-       int count_params;
-       char **params;
-       int *param_length;
-       void *opt_data;
-
-       char *storage;
-       char *end;
-       int max_storage;
-
-       char finalized;
-};
-
-static int
-lws_urldecode_spa_lookup(struct lws_spa *spa,
-                        const char *name)
-{
-       int n;
-
-       for (n = 0; n < spa->count_params; n++)
-               if (!strcmp(spa->param_names[n], name))
-                       return n;
-
-       return -1;
-}
-
-static int
-lws_urldecode_spa_cb(void *data, const char *name, char **buf, int len,
-                    int final)
-{
-       struct lws_spa *spa =
-                       (struct lws_spa *)data;
-       int n;
-
-       if (spa->s->content_disp_filename[0]) {
-               if (spa->opt_cb) {
-                       n = spa->opt_cb(spa->opt_data, name,
-                                       spa->s->content_disp_filename,
-                                       *buf, len, final);
-
-                       if (n < 0)
-                               return -1;
-               }
-               return 0;
-       }
-       n = lws_urldecode_spa_lookup(spa, name);
-
-       if (n == -1 || !len) /* unrecognized */
-               return 0;
-
-       if (!spa->params[n])
-               spa->params[n] = *buf;
-
-       if ((*buf) + len >= spa->end) {
-               lwsl_notice("%s: exceeded storage\n", __func__);
-               return -1;
-       }
-
-       spa->param_length[n] += len;
-
-       /* move it on inside storage */
-       (*buf) += len;
-       *((*buf)++) = '\0';
-
-       spa->s->out_len -= len + 1;
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN struct lws_spa *
-lws_spa_create(struct lws *wsi, const char * const *param_names,
-                        int count_params, int max_storage,
-                        lws_spa_fileupload_cb opt_cb, void *opt_data)
-{
-       struct lws_spa *spa = lws_zalloc(sizeof(*spa));
-
-       if (!spa)
-               return NULL;
-
-       spa->param_names = param_names;
-       spa->count_params = count_params;
-       spa->max_storage = max_storage;
-       spa->opt_cb = opt_cb;
-       spa->opt_data = opt_data;
-
-       spa->storage = lws_malloc(max_storage);
-       if (!spa->storage)
-               goto bail2;
-       spa->end = spa->storage + max_storage - 1;
-
-       spa->params = lws_zalloc(sizeof(char *) * count_params);
-       if (!spa->params)
-               goto bail3;
-
-       spa->s = lws_urldecode_s_create(wsi, spa->storage, max_storage, spa,
-                                       lws_urldecode_spa_cb);
-       if (!spa->s)
-               goto bail4;
-
-       spa->param_length = lws_zalloc(sizeof(int) * count_params);
-       if (!spa->param_length)
-               goto bail5;
-
-       lwsl_info("%s: Created SPA %p\n", __func__, spa);
-
-       return spa;
-
-bail5:
-       lws_urldecode_s_destroy(spa->s);
-bail4:
-       lws_free(spa->params);
-bail3:
-       lws_free(spa->storage);
-bail2:
-       lws_free(spa);
-
-       return NULL;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_process(struct lws_spa *ludspa, const char *in, int len)
-{
-       if (!ludspa) {
-               lwsl_err("%s: NULL spa\n", __func__);
-               return -1;
-       }
-       /* we reject any junk after the last part arrived and we finalized */
-       if (ludspa->finalized)
-               return 0;
-
-       return lws_urldecode_s_process(ludspa->s, in, len);
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_get_length(struct lws_spa *ludspa, int n)
-{
-       if (n >= ludspa->count_params)
-               return 0;
-
-       return ludspa->param_length[n];
-}
-
-LWS_VISIBLE LWS_EXTERN const char *
-lws_spa_get_string(struct lws_spa *ludspa, int n)
-{
-       if (n >= ludspa->count_params)
-               return NULL;
-
-       return ludspa->params[n];
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_finalize(struct lws_spa *spa)
-{
-       if (spa->s) {
-               lws_urldecode_s_destroy(spa->s);
-               spa->s = NULL;
-       }
-
-       spa->finalized = 1;
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_destroy(struct lws_spa *spa)
-{
-       int n = 0;
-
-       lwsl_notice("%s: destroy spa %p\n", __func__, spa);
-
-       if (spa->s)
-               lws_urldecode_s_destroy(spa->s);
-
-       lwsl_debug("%s %p %p %p %p\n", __func__,
-                       spa->param_length,
-                       spa->params,
-                       spa->storage,
-                       spa
-                       );
-
-       lws_free(spa->param_length);
-       lws_free(spa->params);
-       lws_free(spa->storage);
-       lws_free(spa);
-
-       return n;
-}
-
-#if 0
-LWS_VISIBLE LWS_EXTERN int
-lws_spa_destroy(struct lws_spa *spa)
-{
-       int n = 0;
-
-       lwsl_info("%s: destroy spa %p\n", __func__, spa);
-
-       if (spa->s)
-               lws_urldecode_s_destroy(spa->s);
-
-       lwsl_debug("%s\n", __func__);
-
-       lws_free(spa->param_length);
-       lws_free(spa->params);
-       lws_free(spa->storage);
-       lws_free(spa);
-
-       return n;
-}
-#endif
-LWS_VISIBLE LWS_EXTERN int
-lws_chunked_html_process(struct lws_process_html_args *args,
-                        struct lws_process_html_state *s)
-{
-       char *sp, buffer[32];
-       const char *pc;
-       int old_len, n;
-
-       /* do replacements */
-       sp = args->p;
-       old_len = args->len;
-       args->len = 0;
-       s->start = sp;
-       while (sp < args->p + old_len) {
-
-               if (args->len + 7 >= args->max_len) {
-                       lwsl_err("Used up interpret padding\n");
-                       return -1;
-               }
-
-               if ((!s->pos && *sp == '$') || s->pos) {
-                       int hits = 0, hit = 0;
-
-                       if (!s->pos)
-                               s->start = sp;
-                       s->swallow[s->pos++] = *sp;
-                       if (s->pos == sizeof(s->swallow) - 1)
-                               goto skip;
-                       for (n = 0; n < s->count_vars; n++)
-                               if (!strncmp(s->swallow, s->vars[n], s->pos)) {
-                                       hits++;
-                                       hit = n;
-                               }
-                       if (!hits) {
-skip:
-                               s->swallow[s->pos] = '\0';
-                               memcpy(s->start, s->swallow, s->pos);
-                               args->len++;
-                               s->pos = 0;
-                               sp = s->start + 1;
-                               continue;
-                       }
-                       if (hits == 1 && s->pos == strlen(s->vars[hit])) {
-                               pc = s->replace(s->data, hit);
-                               if (!pc)
-                                       pc = "NULL";
-                               n = strlen(pc);
-                               s->swallow[s->pos] = '\0';
-                               if (n != s->pos) {
-                                       memmove(s->start + n,
-                                               s->start + s->pos,
-                                               old_len - (sp - args->p));
-                                       old_len += (n - s->pos) + 1;
-                               }
-                               memcpy(s->start, pc, n);
-                               args->len++;
-                               sp = s->start + 1;
-
-                               s->pos = 0;
-                       }
-                       sp++;
-                       continue;
-               }
-
-               args->len++;
-               sp++;
-       }
-
-       /* no space left for final chunk trailer */
-       if (args->final && args->len + 7 >= args->max_len)
-               return -1;
-
-       n = sprintf(buffer, "%X\x0d\x0a", args->len);
-
-       args->p -= n;
-       memcpy(args->p, buffer, n);
-       args->len += n;
-
-       if (args->final) {
-               sp = args->p + args->len;
-               *sp++ = '\x0d';
-               *sp++ = '\x0a';
-               *sp++ = '0';
-               *sp++ = '\x0d';
-               *sp++ = '\x0a';
-               *sp++ = '\x0d';
-               *sp++ = '\x0a';
-               args->len += 7;
-       } else {
-               sp = args->p + args->len;
-               *sp++ = '\x0d';
-               *sp++ = '\x0a';
-               args->len += 2;
-       }
-
-       return 0;
-}
diff --git a/lib/service.c b/lib/service.c
deleted file mode 100644 (file)
index 2f703f8..0000000
+++ /dev/null
@@ -1,1415 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-static int
-lws_calllback_as_writeable(struct lws *wsi)
-{
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       int n;
-
-       lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1);
-#if defined(LWS_WITH_STATS)
-       {
-               uint64_t ul = time_in_microseconds() - wsi->active_writable_req_us;
-
-               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_WRITABLE_DELAY, ul);
-               lws_stats_atomic_max(wsi->context, pt, LWSSTATS_MS_WORST_WRITABLE_DELAY, ul);
-               wsi->active_writable_req_us = 0;
-       }
-#endif
-
-       switch (wsi->mode) {
-       case LWSCM_RAW:
-               n = LWS_CALLBACK_RAW_WRITEABLE;
-               break;
-       case LWSCM_RAW_FILEDESC:
-               n = LWS_CALLBACK_RAW_WRITEABLE_FILE;
-               break;
-       case LWSCM_WS_CLIENT:
-               n = LWS_CALLBACK_CLIENT_WRITEABLE;
-               break;
-       case LWSCM_WSCL_ISSUE_HTTP_BODY:
-               n = LWS_CALLBACK_CLIENT_HTTP_WRITEABLE;
-               break;
-       case LWSCM_WS_SERVING:
-               n = LWS_CALLBACK_SERVER_WRITEABLE;
-               break;
-       default:
-               n = LWS_CALLBACK_HTTP_WRITEABLE;
-               break;
-       }
-
-       return user_callback_handle_rxflow(wsi->protocol->callback,
-                                          wsi, (enum lws_callback_reasons) n,
-                                          wsi->user_space, NULL, 0);
-}
-
-LWS_VISIBLE int
-lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
-{
-       int write_type = LWS_WRITE_PONG;
-       struct lws_tokens eff_buf;
-#ifdef LWS_USE_HTTP2
-       struct lws *wsi2;
-#endif
-       int ret, m, n;
-
-//     lwsl_err("%s: %p\n", __func__, wsi);
-
-       wsi->leave_pollout_active = 0;
-       wsi->handling_pollout = 1;
-       /*
-        * if another thread wants POLLOUT on us, from here on while
-        * handling_pollout is set, he will only set leave_pollout_active.
-        * If we are going to disable POLLOUT, we will check that first.
-        */
-
-       /*
-        * user callback is lowest priority to get these notifications
-        * actually, since other pending things cannot be disordered
-        */
-
-       /* Priority 1: pending truncated sends are incomplete ws fragments
-        *             If anything else sent first the protocol would be
-        *             corrupted.
-        */
-       if (wsi->trunc_len) {
-               if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset,
-                                 wsi->trunc_len) < 0) {
-                       lwsl_info("%s signalling to close\n", __func__);
-                       goto bail_die;
-               }
-               /* leave POLLOUT active either way */
-               goto bail_ok;
-       } else
-               if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) {
-                       wsi->socket_is_permanently_unusable = 1;
-                       goto bail_die; /* retry closing now */
-               }
-
-       if (wsi->mode == LWSCM_WSCL_ISSUE_HTTP_BODY)
-               goto user_service;
-
-
-#ifdef LWS_USE_HTTP2
-       /* 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;
-               }
-               wsi->pps = LWS_PPS_NONE;
-               lws_rx_flow_control(wsi, 1);
-
-               goto bail_ok; /* leave POLLOUT active */
-       }
-#endif
-
-#ifdef LWS_WITH_CGI
-       if (wsi->cgi) {
-               /* also one shot */
-               if (pollfd)
-                       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
-                               lwsl_info("failed at set pollfd\n");
-                               return 1;
-                       }
-               goto user_service_go_again;
-       }
-#endif
-
-       /* Priority 3: pending control packets (pong or close)
-        *
-        * 3a: close notification packet requested from close api
-        */
-
-       if (wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION) {
-               lwsl_debug("sending close packet\n");
-               wsi->waiting_to_send_close_frame = 0;
-               n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE],
-                             wsi->u.ws.close_in_ping_buffer_len,
-                             LWS_WRITE_CLOSE);
-               if (n >= 0) {
-                       wsi->state = LWSS_AWAITING_CLOSE_ACK;
-                       lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 1);
-                       lwsl_debug("sent close indication, awaiting ack\n");
-
-                       goto bail_ok;
-               }
-
-               goto bail_die;
-       }
-
-       /* else, the send failed and we should just hang up */
-
-       if ((wsi->state == LWSS_ESTABLISHED &&
-            wsi->u.ws.ping_pending_flag) ||
-           (wsi->state == LWSS_RETURNED_CLOSE_ALREADY &&
-            wsi->u.ws.payload_is_close)) {
-
-               if (wsi->u.ws.payload_is_close)
-                       write_type = LWS_WRITE_CLOSE;
-
-               n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE],
-                             wsi->u.ws.ping_payload_len, write_type);
-               if (n < 0)
-                       goto bail_die;
-
-               /* well he is sent, mark him done */
-               wsi->u.ws.ping_pending_flag = 0;
-               if (wsi->u.ws.payload_is_close)
-                       /* oh... a close frame was it... then we are done */
-                       goto bail_die;
-
-               /* otherwise for PING, leave POLLOUT active either way */
-               goto bail_ok;
-       }
-
-       if (wsi->state == LWSS_ESTABLISHED &&
-           !wsi->socket_is_permanently_unusable &&
-           wsi->u.ws.send_check_ping) {
-
-               lwsl_info("issuing ping on wsi %p\n", wsi);
-               wsi->u.ws.send_check_ping = 0;
-               n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE],
-                             0, LWS_WRITE_PING);
-               if (n < 0)
-                       goto bail_die;
-
-               /*
-                * we apparently were able to send the PING in a reasonable time
-                * now reset the clock on our peer to be able to send the
-                * PONG in a reasonable time.
-                */
-
-               lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG,
-                               wsi->context->timeout_secs);
-
-               goto bail_ok;
-       }
-
-       /* Priority 4: if we are closing, not allowed to send more data frags
-        *             which means user callback or tx ext flush banned now
-        */
-       if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY)
-               goto user_service;
-
-       /* Priority 5: Tx path extension with more to send
-        *
-        *             These are handled as new fragments each time around
-        *             So while we must block new writeable callback to enforce
-        *             payload ordering, but since they are always complete
-        *             fragments control packets can interleave OK.
-        */
-       if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) {
-               lwsl_ext("SERVICING TX EXT DRAINING\n");
-               if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0)
-                       goto bail_die;
-               /* leave POLLOUT active */
-               goto bail_ok;
-       }
-
-       /* Priority 6: user can get the callback
-        */
-       m = lws_ext_cb_active(wsi, LWS_EXT_CB_IS_WRITEABLE, NULL, 0);
-       if (m)
-               goto bail_die;
-#ifndef LWS_NO_EXTENSIONS
-       if (!wsi->extension_data_pending)
-               goto user_service;
-#endif
-       /*
-        * check in on the active extensions, see if they
-        * had pending stuff to spill... they need to get the
-        * first look-in otherwise sequence will be disordered
-        *
-        * NULL, zero-length eff_buf means just spill pending
-        */
-
-       ret = 1;
-       if (wsi->mode == LWSCM_RAW || wsi->mode == LWSCM_RAW_FILEDESC)
-               ret = 0;
-       while (ret == 1) {
-
-               /* default to nobody has more to spill */
-
-               ret = 0;
-               eff_buf.token = NULL;
-               eff_buf.token_len = 0;
-
-               /* give every extension a chance to spill */
-
-               m = lws_ext_cb_active(wsi,
-                                       LWS_EXT_CB_PACKET_TX_PRESEND,
-                                              &eff_buf, 0);
-               if (m < 0) {
-                       lwsl_err("ext reports fatal error\n");
-                       goto bail_die;
-               }
-               if (m)
-                       /*
-                        * at least one extension told us he has more
-                        * to spill, so we will go around again after
-                        */
-                       ret = 1;
-
-               /* assuming they gave us something to send, send it */
-
-               if (eff_buf.token_len) {
-                       n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token,
-                                         eff_buf.token_len);
-                       if (n < 0) {
-                               lwsl_info("closing from POLLOUT spill\n");
-                               goto bail_die;
-                       }
-                       /*
-                        * Keep amount spilled small to minimize chance of this
-                        */
-                       if (n != eff_buf.token_len) {
-                               lwsl_err("Unable to spill ext %d vs %d\n",
-                                                         eff_buf.token_len, n);
-                               goto bail_die;
-                       }
-               } else
-                       continue;
-
-               /* no extension has more to spill */
-
-               if (!ret)
-                       continue;
-
-               /*
-                * There's more to spill from an extension, but we just sent
-                * something... did that leave the pipe choked?
-                */
-
-               if (!lws_send_pipe_choked(wsi))
-                       /* no we could add more */
-                       continue;
-
-               lwsl_info("choked in POLLOUT service\n");
-
-               /*
-                * Yes, he's choked.  Leave the POLLOUT masked on so we will
-                * come back here when he is unchoked.  Don't call the user
-                * callback to enforce ordering of spilling, he'll get called
-                * when we come back here and there's nothing more to spill.
-                */
-
-               goto bail_ok;
-       }
-#ifndef LWS_NO_EXTENSIONS
-       wsi->extension_data_pending = 0;
-#endif
-user_service:
-       /* one shot */
-
-       if (wsi->parent_carries_io) {
-               wsi->handling_pollout = 0;
-               wsi->leave_pollout_active = 0;
-
-               return lws_calllback_as_writeable(wsi);
-       }
-
-       if (pollfd) {
-               int eff = wsi->leave_pollout_active;
-
-               if (!eff)
-                       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
-                               lwsl_info("failed at set pollfd\n");
-                               goto bail_die;
-                       }
-
-               wsi->handling_pollout = 0;
-
-               /* cannot get leave_pollout_active set after the above */
-               if (!eff && wsi->leave_pollout_active)
-                       /* got set inbetween sampling eff and clearing
-                        * handling_pollout, force POLLOUT on */
-                       lws_calllback_as_writeable(wsi);
-
-               wsi->leave_pollout_active = 0;
-       }
-
-       if (wsi->mode != LWSCM_WSCL_ISSUE_HTTP_BODY &&
-           !wsi->hdr_parsing_completed)
-               goto bail_ok;
-
-
-#ifdef LWS_WITH_CGI
-user_service_go_again:
-#endif
-
-#ifdef LWS_USE_HTTP2
-       /*
-        * we are the 'network wsi' for potentially many muxed child wsi with
-        * no network connection of their own, who have to use us for all their
-        * network actions.  So we use a round-robin scheme to share out the
-        * POLLOUT notifications to our children.
-        *
-        * But because any child could exhaust the socket's ability to take
-        * writes, we can only let one child get notified each time.
-        *
-        * In addition children may be closed / deleted / added between POLLOUT
-        * notifications, so we can't hold pointers
-        */
-
-       if (wsi->mode != LWSCM_HTTP2_SERVING) {
-               lwsl_info("%s: non http2\n", __func__);
-               goto notify;
-       }
-
-       wsi->u.http2.requested_POLLOUT = 0;
-       if (!wsi->u.http2.initialized) {
-               lwsl_info("pollout on uninitialized http2 conn\n");
-               goto bail_ok;
-       }
-
-       lwsl_info("%s: doing children\n", __func__);
-
-       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);
-               }
-               wsi2 = wsi;
-       } while (wsi2 != NULL && !lws_send_pipe_choked(wsi));
-
-       lwsl_info("%s: completed\n", __func__);
-
-       goto bail_ok;
-notify:
-#endif
-       wsi->handling_pollout = 0;
-       wsi->leave_pollout_active = 0;
-
-       return lws_calllback_as_writeable(wsi);
-
-       /*
-        * since these don't disable the POLLOUT, they are always doing the
-        * right thing for leave_pollout_active whether it was set or not.
-        */
-
-bail_ok:
-       wsi->handling_pollout = 0;
-       wsi->leave_pollout_active = 0;
-
-       return 0;
-
-bail_die:
-       wsi->handling_pollout = 0;
-       wsi->leave_pollout_active = 0;
-
-       return -1;
-}
-
-int
-lws_service_timeout_check(struct lws *wsi, unsigned int sec)
-{
-//#if LWS_POSIX
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       int n = 0;
-//#endif
-
-       (void)n;
-
-       /*
-        * if extensions want in on it (eg, we are a mux parent)
-        * give them a chance to service child timeouts
-        */
-       if (lws_ext_cb_active(wsi, LWS_EXT_CB_1HZ, NULL, sec) < 0)
-               return 0;
-
-       if (!wsi->pending_timeout)
-               return 0;
-
-       /*
-        * if we went beyond the allowed time, kill the
-        * connection
-        */
-       if ((time_t)sec > wsi->pending_timeout_limit) {
-//#if LWS_POSIX
-               if (wsi->desc.sockfd != LWS_SOCK_INVALID && wsi->position_in_fds_table >= 0)
-                       n = pt->fds[wsi->position_in_fds_table].events;
-
-               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_TIMEOUTS, 1);
-
-               /* no need to log normal idle keepalive timeout */
-               if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE)
-                       lwsl_notice("wsi %p: TIMEDOUT WAITING on %d (did hdr %d, ah %p, wl %d, pfd events %d) %llu vs %llu\n",
-                           (void *)wsi, wsi->pending_timeout,
-                           wsi->hdr_parsing_completed, wsi->u.hdr.ah,
-                           pt->ah_wait_list_length, n, (unsigned long long)sec, (unsigned long long)wsi->pending_timeout_limit);
-//#endif
-               /*
-                * Since he failed a timeout, he already had a chance to do
-                * something and was unable to... that includes situations like
-                * half closed connections.  So process this "failed timeout"
-                * close as a violent death and don't try to do protocol
-                * cleanup like flush partials.
-                */
-               wsi->socket_is_permanently_unusable = 1;
-               if (wsi->mode == LWSCM_WSCL_WAITING_SSL)
-                       wsi->vhost->protocols[0].callback(wsi,
-                               LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
-                               wsi->user_space, (void *)"Timed out waiting SSL", 21);
-
-               lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-
-               return 1;
-       }
-
-       return 0;
-}
-
-int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len)
-{
-       /* 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;
-       }
-
-       /* a new rxflow, buffer it and warn caller */
-       lwsl_info("new rxflow input buffer len %d\n", len - n);
-       wsi->rxflow_buffer = lws_malloc(len - n);
-       if (!wsi->rxflow_buffer)
-               return -1;
-       wsi->rxflow_len = len - n;
-       wsi->rxflow_pos = 0;
-       memcpy(wsi->rxflow_buffer, buf + n, len - n);
-
-       return 0;
-}
-
-/* this is used by the platform service code to stop us waiting for network
- * activity in poll() when we have something that already needs service
- */
-
-LWS_VISIBLE LWS_EXTERN int
-lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[tsi];
-       int n;
-
-       /* Figure out if we really want to wait in poll()
-        * We only need to wait if really nothing already to do and we have
-        * to wait for something from network
-        */
-
-       /* 1) if we know we are draining rx ext, do not wait in poll */
-       if (pt->rx_draining_ext_list)
-               return 0;
-
-#ifdef LWS_OPENSSL_SUPPORT
-       /* 2) if we know we have non-network pending data, do not wait in poll */
-       if (lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) {
-               lwsl_info("ssl buffered read\n");
-               return 0;
-       }
-#endif
-
-       /* 3) if any ah has pending rx, do not wait in poll */
-       for (n = 0; n < context->max_http_header_pool; n++)
-               if (pt->ah_pool[n].rxpos != pt->ah_pool[n].rxlen) {
-                       /* any ah with pending rx must be attached to someone */
-                       if (!pt->ah_pool[n].wsi) {
-                               lwsl_err("%s: assert: no wsi attached to ah\n", __func__);
-                               assert(0);
-                       }
-                       return 0;
-               }
-
-       return timeout_ms;
-}
-
-/*
- * guys that need POLLIN service again without waiting for network action
- * can force POLLIN here if not flowcontrolled, so they will get service.
- *
- * Return nonzero if anybody got their POLLIN faked
- */
-int
-lws_service_flag_pending(struct lws_context *context, int tsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[tsi];
-#ifdef LWS_OPENSSL_SUPPORT
-       struct lws *wsi_next;
-#endif
-       struct lws *wsi;
-       int forced = 0;
-       int n;
-
-       /* POLLIN faking */
-
-       /*
-        * 1) For all guys with already-available ext data to drain, if they are
-        * not flowcontrolled, fake their POLLIN status
-        */
-       wsi = pt->rx_draining_ext_list;
-       while (wsi) {
-               pt->fds[wsi->position_in_fds_table].revents |=
-                       pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
-               if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) {
-                       forced = 1;
-                       break;
-               }
-               wsi = wsi->u.ws.rx_draining_ext_list;
-       }
-
-#ifdef LWS_OPENSSL_SUPPORT
-       /*
-        * 2) For all guys with buffered SSL read data already saved up, if they
-        * are not flowcontrolled, fake their POLLIN status so they'll get
-        * service to use up the buffered incoming data, even though their
-        * network socket may have nothing
-        */
-       wsi = pt->pending_read_list;
-       while (wsi) {
-               wsi_next = wsi->pending_read_list_next;
-               pt->fds[wsi->position_in_fds_table].revents |=
-                       pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
-               if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) {
-                       forced = 1;
-                       /*
-                        * he's going to get serviced now, take him off the
-                        * list of guys with buffered SSL.  If he still has some
-                        * at the end of the service, he'll get put back on the
-                        * list then.
-                        */
-                       lws_ssl_remove_wsi_from_buffered_list(wsi);
-               }
-
-               wsi = wsi_next;
-       }
-#endif
-       /*
-        * 3) For any wsi who have an ah with pending RX who did not
-        * complete their current headers, and are not flowcontrolled,
-        * fake their POLLIN status so they will be able to drain the
-        * rx buffered in the ah
-        */
-       for (n = 0; n < context->max_http_header_pool; n++)
-               if (pt->ah_pool[n].rxpos != pt->ah_pool[n].rxlen &&
-                   !pt->ah_pool[n].wsi->hdr_parsing_completed) {
-                       pt->fds[pt->ah_pool[n].wsi->position_in_fds_table].revents |=
-                               pt->fds[pt->ah_pool[n].wsi->position_in_fds_table].events &
-                                       LWS_POLLIN;
-                       if (pt->fds[pt->ah_pool[n].wsi->position_in_fds_table].revents &
-                           LWS_POLLIN)
-                               forced = 1;
-               }
-
-       return forced;
-}
-
-#ifndef LWS_NO_CLIENT
-
-LWS_VISIBLE int
-lws_http_client_read(struct lws *wsi, char **buf, int *len)
-{
-       int rlen, n;
-
-       rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len);
-       *len = 0;
-
-       /* allow the source to signal he has data again next time */
-       lws_change_pollfd(wsi, 0, LWS_POLLIN);
-
-       if (rlen == LWS_SSL_CAPABLE_ERROR) {
-               lwsl_notice("%s: SSL capable error\n", __func__);
-               return -1;
-       }
-
-       if (rlen == 0)
-               return -1;
-
-       if (rlen < 0)
-               return 0;
-
-       *len = rlen;
-       wsi->client_rx_avail = 0;
-
-       /*
-        * server may insist on transfer-encoding: chunked,
-        * so http client must deal with it
-        */
-spin_chunks:
-       while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) {
-               switch (wsi->chunk_parser) {
-               case ELCP_HEX:
-                       if ((*buf)[0] == '\x0d') {
-                               wsi->chunk_parser = ELCP_CR;
-                               break;
-                       }
-                       n = char_to_hex((*buf)[0]);
-                       if (n < 0) {
-                               lwsl_debug("chunking failure\n");
-                               return -1;
-                       }
-                       wsi->chunk_remaining <<= 4;
-                       wsi->chunk_remaining |= n;
-                       break;
-               case ELCP_CR:
-                       if ((*buf)[0] != '\x0a') {
-                               lwsl_debug("chunking failure\n");
-                               return -1;
-                       }
-                       wsi->chunk_parser = ELCP_CONTENT;
-                       lwsl_info("chunk %d\n", wsi->chunk_remaining);
-                       if (wsi->chunk_remaining)
-                               break;
-                       lwsl_info("final chunk\n");
-                       goto completed;
-
-               case ELCP_CONTENT:
-                       break;
-
-               case ELCP_POST_CR:
-                       if ((*buf)[0] != '\x0d') {
-                               lwsl_debug("chunking failure\n");
-
-                               return -1;
-                       }
-
-                       wsi->chunk_parser = ELCP_POST_LF;
-                       break;
-
-               case ELCP_POST_LF:
-                       if ((*buf)[0] != '\x0a')
-                               return -1;
-
-                       wsi->chunk_parser = ELCP_HEX;
-                       wsi->chunk_remaining = 0;
-                       break;
-               }
-               (*buf)++;
-               (*len)--;
-       }
-
-       if (wsi->chunked && !wsi->chunk_remaining)
-               return 0;
-
-       if (wsi->u.http.content_remain &&
-           wsi->u.http.content_remain < *len)
-               n = (int)wsi->u.http.content_remain;
-       else
-               n = *len;
-
-       if (wsi->chunked && wsi->chunk_remaining &&
-           wsi->chunk_remaining < n)
-               n = wsi->chunk_remaining;
-
-#ifdef LWS_WITH_HTTP_PROXY
-       /* hubbub */
-       if (wsi->perform_rewrite)
-               lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n);
-       else
-#endif
-               if (user_callback_handle_rxflow(wsi->protocol->callback,
-                               wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
-                               wsi->user_space, *buf, n)) {
-                       lwsl_debug("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ returned -1\n", __func__);
-
-                       return -1;
-               }
-
-       if (wsi->chunked && wsi->chunk_remaining) {
-               (*buf) += n;
-               wsi->chunk_remaining -= n;
-               *len -= n;
-       }
-
-       if (wsi->chunked && !wsi->chunk_remaining)
-               wsi->chunk_parser = ELCP_POST_CR;
-
-       if (wsi->chunked && *len)
-               goto spin_chunks;
-
-       if (wsi->chunked)
-               return 0;
-
-       /* if we know the content length, decrement the content remaining */
-       if (wsi->u.http.content_length > 0)
-               wsi->u.http.content_remain -= n;
-
-       if (wsi->u.http.content_remain || !wsi->u.http.content_length)
-               return 0;
-
-completed:
-       if (user_callback_handle_rxflow(wsi->protocol->callback,
-                       wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
-                       wsi->user_space, NULL, 0)) {
-               lwsl_debug("Completed call returned -1\n");
-               return -1;
-       }
-
-       if (lws_http_transaction_completed_client(wsi)) {
-               lwsl_notice("%s: transaction completed says -1\n", __func__);
-               return -1;
-       }
-
-       return 0;
-}
-#endif
-
-static int
-lws_is_ws_with_ext(struct lws *wsi)
-{
-#if defined(LWS_NO_EXTENSIONS)
-       return 0;
-#else
-       return wsi->state == LWSS_ESTABLISHED &&
-              !!wsi->count_act_ext;
-#endif
-}
-
-LWS_VISIBLE int
-lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int tsi)
-{
-       struct lws_context_per_thread *pt = &context->pt[tsi];
-       lws_sockfd_type our_fd = 0, tmp_fd;
-       struct lws_tokens eff_buf;
-       unsigned int pending = 0;
-       struct lws *wsi, *wsi1;
-       char draining_flow = 0;
-       int timed_out = 0;
-       time_t now;
-       int n = 0, m;
-       int more;
-
-       if (!context->protocol_init_done)
-               lws_protocol_init(context);
-
-       time(&now);
-
-       /*
-        * handle case that system time was uninitialized when lws started
-        * at boot, and got initialized a little later
-        */
-       if (context->time_up < 1464083026 && now > 1464083026)
-               context->time_up = now;
-
-       /* TODO: if using libev, we should probably use timeout watchers... */
-       if (context->last_timeout_check_s != now) {
-               context->last_timeout_check_s = now;
-
-#if defined(LWS_WITH_STATS)
-               if (!tsi && now - context->last_dump > 10) {
-                       lws_stats_log_dump(context);
-                       context->last_dump = now;
-               }
-#endif
-
-               lws_plat_service_periodic(context);
-
-               lws_check_deferred_free(context, 0);
-
-               /* retire unused deprecated context */
-#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_ESP32)
-#if LWS_POSIX && !defined(_WIN32)
-               if (context->deprecated && !context->count_wsi_allocated) {
-                       lwsl_notice("%s: ending deprecated context\n", __func__);
-                       kill(getpid(), SIGINT);
-                       return 0;
-               }
-#endif
-#endif
-               /* global timeout check once per second */
-
-               if (pollfd)
-                       our_fd = pollfd->fd;
-
-               wsi = context->pt[tsi].timeout_list;
-               while (wsi) {
-                       /* we have to take copies, because he may be deleted */
-                       wsi1 = wsi->timeout_list;
-                       tmp_fd = wsi->desc.sockfd;
-                       if (lws_service_timeout_check(wsi, (unsigned int)now)) {
-                               /* he did time out... */
-                               if (tmp_fd == our_fd)
-                                       /* it was the guy we came to service! */
-                                       timed_out = 1;
-                                       /* he's gone, no need to mark as handled */
-                       }
-                       wsi = wsi1;
-               }
-#ifdef LWS_WITH_CGI
-               lws_cgi_kill_terminated(pt);
-#endif
-#if 0
-               {
-                       char s[300], *p = s;
-
-                       for (n = 0; n < context->count_threads; n++)
-                               p += sprintf(p, " %7lu (%5d), ",
-                                            context->pt[n].count_conns,
-                                            context->pt[n].fds_count);
-
-                       lwsl_notice("load: %s\n", s);
-               }
-#endif
-       }
-
-       /*
-        * at intervals, check for ws connections needing ping-pong checks
-        */
-
-       if (context->ws_ping_pong_interval &&
-           context->last_ws_ping_pong_check_s < now + 10) {
-               struct lws_vhost *vh = context->vhost_list;
-               context->last_ws_ping_pong_check_s = now;
-
-               while (vh) {
-                       for (n = 0; n < vh->count_protocols; n++) {
-                               wsi = vh->same_vh_protocol_list[n];
-
-                               while (wsi) {
-                                       if (wsi->state == LWSS_ESTABLISHED &&
-                                           !wsi->socket_is_permanently_unusable &&
-                                           !wsi->u.ws.send_check_ping &&
-                                           wsi->u.ws.time_next_ping_check &&
-                                           wsi->u.ws.time_next_ping_check < now) {
-
-                                               lwsl_info("requesting ping-pong on wsi %p\n", wsi);
-                                               wsi->u.ws.send_check_ping = 1;
-                                               lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING,
-                                                               context->timeout_secs);
-                                               lws_callback_on_writable(wsi);
-                                               wsi->u.ws.time_next_ping_check = now +
-                                                               wsi->context->ws_ping_pong_interval;
-                                       }
-                                       wsi = wsi->same_vh_protocol_next;
-                               }
-                       }
-                       vh = vh->vhost_next;
-               }
-       }
-
-       /* the socket we came to service timed out, nothing to do */
-       if (timed_out)
-               return 0;
-
-       /* just here for timeout management? */
-       if (!pollfd)
-               return 0;
-
-       /* no, here to service a socket descriptor */
-       wsi = wsi_from_fd(context, pollfd->fd);
-       if (!wsi)
-               /* not lws connection ... leave revents alone and return */
-               return 0;
-
-       /*
-        * so that caller can tell we handled, past here we need to
-        * zero down pollfd->revents after handling
-        */
-
-#if LWS_POSIX
-       /* handle session socket closed */
-
-       if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) &&
-           (pollfd->revents & LWS_POLLHUP)) {
-               wsi->socket_is_permanently_unusable = 1;
-               lwsl_debug("Session Socket %p (fd=%d) dead\n",
-                                                      (void *)wsi, pollfd->fd);
-
-               goto close_and_handled;
-       }
-
-#ifdef _WIN32
-       if (pollfd->revents & LWS_POLLOUT)
-               wsi->sock_send_blocking = FALSE;
-#endif
-
-#endif
-
-//       lwsl_debug("fd=%d, revents=%d, mode=%d, state=%d\n", pollfd->fd, pollfd->revents, (int)wsi->mode, (int)wsi->state);
-       if (pollfd->revents & LWS_POLLHUP) {
-               lwsl_debug("pollhup\n");
-               wsi->socket_is_permanently_unusable = 1;
-               goto close_and_handled;
-       }
-
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if ((wsi->state == LWSS_SHUTDOWN) && lws_is_ssl(wsi) && wsi->ssl)
-       {
-               n = SSL_shutdown(wsi->ssl);
-               lwsl_debug("SSL_shutdown=%d for fd %d\n", n, wsi->desc.sockfd);
-               if (n == 1)
-               {
-                       n = shutdown(wsi->desc.sockfd, SHUT_WR);
-                       goto close_and_handled;
-               }
-               else if (n == 0)
-               {
-                       lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN);
-                       n = 0;
-                       goto handled;
-               }
-               else /* n < 0 */
-               {
-                       int shutdown_error = SSL_get_error(wsi->ssl, n);
-                       lwsl_debug("SSL_shutdown returned %d, SSL_get_error: %d\n", n, shutdown_error);
-                       if (shutdown_error == SSL_ERROR_WANT_READ) {
-                               lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN);
-                               n = 0;
-                               goto handled;
-                       } else if (shutdown_error == SSL_ERROR_WANT_WRITE) {
-                               lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLOUT);
-                               n = 0;
-                               goto handled;
-                       }
-
-                       // actual error occurred, just close the connection
-                       n = shutdown(wsi->desc.sockfd, SHUT_WR);
-                       goto close_and_handled;
-               }
-       }
-#endif
-
-       /* okay, what we came here to do... */
-
-       switch (wsi->mode) {
-       case LWSCM_HTTP_SERVING:
-       case LWSCM_HTTP_CLIENT:
-       case LWSCM_HTTP_SERVING_ACCEPTED:
-       case LWSCM_SERVER_LISTENER:
-       case LWSCM_SSL_ACK_PENDING:
-       case LWSCM_SSL_ACK_PENDING_RAW:
-               if (wsi->state == LWSS_CLIENT_HTTP_ESTABLISHED)
-                       goto handled;
-
-#ifdef LWS_WITH_CGI
-               if (wsi->cgi && (pollfd->revents & LWS_POLLOUT)) {
-                       n = lws_handle_POLLOUT_event(wsi, pollfd);
-                       if (n)
-                               goto close_and_handled;
-                       goto handled;
-               }
-#endif
-               /* fallthru */
-       case LWSCM_RAW:
-               n = lws_server_socket_service(context, wsi, pollfd);
-               if (n) /* closed by above */
-                       return 1;
-               goto handled;
-
-       case LWSCM_RAW_FILEDESC:
-
-               if (pollfd->revents & LWS_POLLOUT) {
-                       n = lws_calllback_as_writeable(wsi);
-                       if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
-                               lwsl_info("failed at set pollfd\n");
-                               return 1;
-                       }
-                       if (n)
-                               goto close_and_handled;
-               }
-               n = LWS_CALLBACK_RAW_RX;
-               if (wsi->mode == LWSCM_RAW_FILEDESC)
-                       n = LWS_CALLBACK_RAW_RX_FILE;
-
-               if (pollfd->revents & LWS_POLLIN) {
-                       if (user_callback_handle_rxflow(
-                                       wsi->protocol->callback,
-                                       wsi, n,
-                                       wsi->user_space, NULL, 0)) {
-                               lwsl_debug("raw rx callback closed it\n");
-                               goto close_and_handled;
-                       }
-               }
-
-               if (pollfd->revents & LWS_POLLHUP)
-                       goto close_and_handled;
-               n = 0;
-               goto handled;
-
-       case LWSCM_WS_SERVING:
-       case LWSCM_WS_CLIENT:
-       case LWSCM_HTTP2_SERVING:
-       case LWSCM_HTTP_CLIENT_ACCEPTED:
-
-               /* 1: something requested a callback when it was OK to write */
-
-               if (wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION)
-                       lwsl_notice("xxx\n");
-
-               if ((pollfd->revents & LWS_POLLOUT) &&
-                   ((wsi->state == LWSS_ESTABLISHED ||
-                    wsi->state == LWSS_HTTP2_ESTABLISHED ||
-                    wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS ||
-                    wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
-                    wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION ||
-                    wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE)) &&
-                   lws_handle_POLLOUT_event(wsi, pollfd)) {
-                       if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY)
-                               wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE;
-                       lwsl_info("lws_service_fd: closing\n");
-                       goto close_and_handled;
-               }
-
-               if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
-                   wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION ||
-                   wsi->state == LWSS_AWAITING_CLOSE_ACK) {
-                       /*
-                        * we stopped caring about anything except control
-                        * packets.  Force flow control off, defeat tx
-                        * draining.
-                        */
-                       lws_rx_flow_control(wsi, 1);
-                       wsi->u.ws.tx_draining_ext = 0;
-               }
-
-               if (wsi->u.ws.tx_draining_ext)
-                       /* we cannot deal with new RX until the TX ext
-                        * path has been drained.  It's because new
-                        * rx will, eg, crap on the wsi rx buf that
-                        * may be needed to retain state.
-                        *
-                        * TX ext drain path MUST go through event loop
-                        * to avoid blocking.
-                        */
-                       break;
-
-               if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW))
-                       /* We cannot deal with any kind of new RX
-                        * because we are RX-flowcontrolled.
-                        */
-                       break;
-
-               /* 2: RX Extension needs to be drained
-                */
-
-               if (wsi->state == LWSS_ESTABLISHED &&
-                   wsi->u.ws.rx_draining_ext) {
-
-                       lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__);
-#ifndef LWS_NO_CLIENT
-                       if (wsi->mode == LWSCM_WS_CLIENT) {
-                               n = lws_client_rx_sm(wsi, 0);
-                               if (n < 0)
-                                       /* we closed wsi */
-                                       n = 0;
-                       } else
-#endif
-                               n = lws_rx_sm(wsi, 0);
-
-                       goto handled;
-               }
-
-               if (wsi->u.ws.rx_draining_ext)
-                       /*
-                        * We have RX EXT content to drain, but can't do it
-                        * right now.  That means we cannot do anything lower
-                        * priority either.
-                        */
-                       break;
-
-               /* 3: RX Flowcontrol buffer needs to be drained
-                */
-
-               if (wsi->rxflow_buffer) {
-                       lwsl_info("draining rxflow (len %d)\n",
-                               wsi->rxflow_len - wsi->rxflow_pos
-                       );
-                       /* well, drain it */
-                       eff_buf.token = (char *)wsi->rxflow_buffer +
-                                               wsi->rxflow_pos;
-                       eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos;
-                       draining_flow = 1;
-                       goto drain;
-               }
-
-               /* 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:
-               /* all the union members start with hdr, so even in ws mode
-                * we can deal with the ah via u.hdr
-                */
-               if (wsi->u.hdr.ah) {
-                       lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi);
-                       eff_buf.token_len = wsi->u.hdr.ah->rxlen -
-                                           wsi->u.hdr.ah->rxpos;
-                       eff_buf.token = (char *)wsi->u.hdr.ah->rx +
-                                       wsi->u.hdr.ah->rxpos;
-               } else {
-                       if (wsi->mode != LWSCM_HTTP_CLIENT_ACCEPTED) {
-                               /*
-                                * extension may not consume everything (eg, pmd may be constrained
-                                * as to what it can output...) has to go in per-wsi rx buf area.
-                                * Otherwise in large temp serv_buf area.
-                                */
-                               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;
-
-                               eff_buf.token_len = lws_ssl_capable_read(wsi,
-                                       (unsigned char *)eff_buf.token, pending ? pending :
-                                       eff_buf.token_len);
-                               switch (eff_buf.token_len) {
-                               case 0:
-                                       lwsl_info("%s: zero length read\n", __func__);
-                                       goto close_and_handled;
-                               case LWS_SSL_CAPABLE_MORE_SERVICE:
-                                       lwsl_info("SSL Capable more service\n");
-                                       n = 0;
-                                       goto handled;
-                               case LWS_SSL_CAPABLE_ERROR:
-                                       lwsl_info("Closing when error\n");
-                                       goto close_and_handled;
-                               }
-                               // lwsl_notice("Actual RX %d\n", eff_buf.token_len);
-                       }
-               }
-
-drain:
-#ifndef LWS_NO_CLIENT
-               if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED &&
-                   !wsi->told_user_closed) {
-
-                       /*
-                        * In SSL mode we get POLLIN notification about
-                        * encrypted data in.
-                        *
-                        * But that is not necessarily related to decrypted
-                        * data out becoming available; in may need to perform
-                        * other in or out before that happens.
-                        *
-                        * simply mark ourselves as having readable data
-                        * and turn off our POLLIN
-                        */
-                       wsi->client_rx_avail = 1;
-                       lws_change_pollfd(wsi, LWS_POLLIN, 0);
-
-                       /* let user code know, he'll usually ask for writeable
-                        * callback and drain / re-enable it there
-                        */
-                       if (user_callback_handle_rxflow(
-                                       wsi->protocol->callback,
-                                       wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
-                                       wsi->user_space, NULL, 0)) {
-                               lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP closed it\n");
-                               goto close_and_handled;
-                       }
-
-                       n = 0;
-                       goto handled;
-               }
-#endif
-               /*
-                * give any active extensions a chance to munge the buffer
-                * before parse.  We pass in a pointer to an lws_tokens struct
-                * prepared with the default buffer and content length that's in
-                * there.  Rather than rewrite the default buffer, extensions
-                * that expect to grow the buffer can adapt .token to
-                * point to their own per-connection buffer in the extension
-                * user allocation.  By default with no extensions or no
-                * extension callback handling, just the normal input buffer is
-                * used then so it is efficient.
-                */
-               do {
-                       more = 0;
-
-                       m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE,
-                                             &eff_buf, 0);
-                       if (m < 0)
-                               goto close_and_handled;
-                       if (m)
-                               more = 1;
-
-                       /* service incoming data */
-
-                       if (eff_buf.token_len) {
-                               /*
-                                * if draining from rxflow buffer, not
-                                * critical to track what was used since at the
-                                * use it bumps wsi->rxflow_pos.  If we come
-                                * around again it will pick up from where it
-                                * left off.
-                                */
-                               // lwsl_notice("doing lws_read from pt->serv_buf %p %p for len %d\n", pt->serv_buf, eff_buf.token, (int)eff_buf.token_len);
-
-                               n = lws_read(wsi, (unsigned char *)eff_buf.token,
-                                            eff_buf.token_len);
-                               if (n < 0) {
-                                       /* we closed wsi */
-                                       n = 0;
-                                       goto handled;
-                               }
-                       }
-
-                       eff_buf.token = NULL;
-                       eff_buf.token_len = 0;
-               } while (more);
-
-               if (wsi->u.hdr.ah) {
-                       lwsl_notice("%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
-                        * start with hdr */
-                       lws_header_table_detach(wsi, 0);
-               }
-
-               pending = lws_ssl_pending(wsi);
-               if (pending) {
-                       if (lws_is_ws_with_ext(wsi))
-                               pending = pending > wsi->u.ws.rx_ubuf_alloc ?
-                                       wsi->u.ws.rx_ubuf_alloc : pending;
-                       else
-                               pending = pending > context->pt_serv_buf_size ?
-                                       context->pt_serv_buf_size : pending;
-                       goto read;
-               }
-
-               if (draining_flow && wsi->rxflow_buffer &&
-                   wsi->rxflow_pos == wsi->rxflow_len) {
-                       lwsl_info("flow buffer: drained\n");
-                       lws_free_set_NULL(wsi->rxflow_buffer);
-                       /* having drained the rxflow buffer, can rearm POLLIN */
-#ifdef LWS_NO_SERVER
-                       n =
-#endif
-                       _lws_rx_flow_control(wsi);
-                       /* n ignored, needed for NO_SERVER case */
-               }
-
-               break;
-#ifdef LWS_WITH_CGI
-       case LWSCM_CGI: /* we exist to handle a cgi's stdin/out/err data...
-                        * do the callback on our master wsi
-                        */
-               {
-                       struct lws_cgi_args args;
-
-                       if (wsi->cgi_channel >= LWS_STDOUT &&
-                           !(pollfd->revents & pollfd->events & LWS_POLLIN))
-                               break;
-                       if (wsi->cgi_channel == LWS_STDIN &&
-                           !(pollfd->revents & pollfd->events & LWS_POLLOUT))
-                               break;
-
-                       if (wsi->cgi_channel == LWS_STDIN)
-                               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
-                                       lwsl_info("failed at set pollfd\n");
-                                       return 1;
-                               }
-
-                       args.ch = wsi->cgi_channel;
-                       args.stdwsi = &wsi->parent->cgi->stdwsi[0];
-                       args.hdr_state = wsi->hdr_state;
-
-                       //lwsl_err("CGI LWS_STDOUT waiting wsi %p mode %d state %d\n",
-                       //       wsi->parent, wsi->parent->mode, wsi->parent->state);
-
-                       if (user_callback_handle_rxflow(
-                                       wsi->parent->protocol->callback,
-                                       wsi->parent, LWS_CALLBACK_CGI,
-                                       wsi->parent->user_space,
-                                       (void *)&args, 0))
-                               return 1;
-
-                       break;
-               }
-#endif
-       default:
-#ifdef LWS_NO_CLIENT
-               break;
-#else
-               if ((pollfd->revents & LWS_POLLOUT) &&
-                   lws_handle_POLLOUT_event(wsi, pollfd)) {
-                       lwsl_debug("POLLOUT event closed it\n");
-                       goto close_and_handled;
-               }
-
-               n = lws_client_socket_service(context, wsi, pollfd);
-               if (n)
-                       return 1;
-               goto handled;
-#endif
-       }
-
-       n = 0;
-       goto handled;
-
-close_and_handled:
-       lwsl_debug("%p: Close and handled\n", wsi);
-       lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
-       /*
-        * pollfd may point to something else after the close
-        * due to pollfd swapping scheme on delete on some platforms
-        * we can't clear revents now because it'd be the wrong guy's revents
-        */
-       return 1;
-
-handled:
-       pollfd->revents = 0;
-       return n;
-}
-
-LWS_VISIBLE int
-lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd)
-{
-       return lws_service_fd_tsi(context, pollfd, 0);
-}
-
-LWS_VISIBLE int
-lws_service(struct lws_context *context, int timeout_ms)
-{
-       return lws_plat_service(context, timeout_ms);
-}
-
-LWS_VISIBLE int
-lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
-{
-       return _lws_plat_service_tsi(context, timeout_ms, tsi);
-}
-
diff --git a/lib/smtp.c b/lib/smtp.c
deleted file mode 100644 (file)
index 69a4bf0..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * SMTP support for libwebsockets
- *
- * Copyright (C) 2016 Andy Green <andy@warmcat.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation:
- * version 2.1 of the License.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-static unsigned int
-lwsgs_now_secs(void)
-{
-       struct timeval tv;
-
-       gettimeofday(&tv, NULL);
-
-       return tv.tv_sec;
-}
-
-static void
-ccb(uv_handle_t* handle)
-{
-
-}
-
-static void
-alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
-{
-       struct lws_email *email = (struct lws_email *)handle->data;
-
-       *buf = uv_buf_init(email->email_buf, sizeof(email->email_buf) - 1);
-}
-
-static void
-on_write_end(uv_write_t *req, int status) {
-       lwsl_notice("%s\n", __func__);
-       if (status == -1) {
-               fprintf(stderr, "error on_write_end");
-               return;
-       }
-}
-
-static void
-lwsgs_email_read(struct uv_stream_s *s, ssize_t nread, const uv_buf_t *buf)
-{
-       struct lws_email *email = (struct lws_email *)s->data;
-       static const short retcodes[] = {
-               0,      /* idle */
-               0,      /* connecting */
-               220,    /* connected */
-               250,    /* helo */
-               250,    /* from */
-               250,    /* to */
-               354,    /* data */
-               250,    /* body */
-               221,    /* quit */
-       };
-       uv_write_t write_req;
-       uv_buf_t wbuf;
-       int n;
-
-       if (nread >= 0)
-               email->email_buf[nread] = '\0';
-       lwsl_notice("%s: %s\n", __func__, buf->base);
-       if (nread == -1) {
-               lwsl_err("%s: failed\n", __func__);
-               return;
-       }
-
-       n = atoi(buf->base);
-       if (n != retcodes[email->estate]) {
-               lwsl_err("%s: bad response from server\n", __func__);
-               goto close_conn;
-       }
-
-       switch (email->estate) {
-       case LGSSMTP_CONNECTED:
-               n = sprintf(email->content, "HELO %s\n", email->email_helo);
-               email->estate = LGSSMTP_SENT_HELO;
-               break;
-       case LGSSMTP_SENT_HELO:
-               n = sprintf(email->content, "MAIL FROM: <%s>\n", email->email_from);
-               email->estate = LGSSMTP_SENT_FROM;
-               break;
-       case LGSSMTP_SENT_FROM:
-               n = sprintf(email->content, "RCPT TO: <%s>\n", email->email_to);
-               email->estate = LGSSMTP_SENT_TO;
-               break;
-       case LGSSMTP_SENT_TO:
-               n = sprintf(email->content, "DATA\n");
-               email->estate = LGSSMTP_SENT_DATA;
-               break;
-       case LGSSMTP_SENT_DATA:
-               if (email->on_get_body(email, email->content, email->max_content_size))
-                       return;
-               n = strlen(email->content);
-               email->estate = LGSSMTP_SENT_BODY;
-               break;
-       case LGSSMTP_SENT_BODY:
-               n = sprintf(email->content, "quit\n");
-               email->estate = LGSSMTP_SENT_QUIT;
-               break;
-       case LGSSMTP_SENT_QUIT:
-               lwsl_notice("%s: done\n", __func__);
-               email->on_sent(email);
-               email->estate = LGSSMTP_IDLE;
-               goto close_conn;
-       default:
-               return;
-       }
-
-       puts(email->content);
-       wbuf = uv_buf_init(email->content, n);
-       uv_write(&write_req, s, &wbuf, 1, on_write_end);
-
-       return;
-
-close_conn:
-
-       uv_close((uv_handle_t *)s, ccb);
-}
-
-static void
-lwsgs_email_on_connect(uv_connect_t *req, int status)
-{
-       struct lws_email *email = (struct lws_email *)req->data;
-
-       lwsl_notice("%s\n", __func__);
-
-       if (status == -1) {
-               lwsl_err("%s: failed\n", __func__);
-               return;
-       }
-
-       uv_read_start(req->handle, alloc_buffer, lwsgs_email_read);
-       email->estate = LGSSMTP_CONNECTED;
-}
-
-
-static void
-uv_timeout_cb_email(uv_timer_t *w
-#if UV_VERSION_MAJOR == 0
-               , int status
-#endif
-)
-{
-       struct lws_email *email = lws_container_of(w, struct lws_email,
-                                                  timeout_email);
-       time_t now = lwsgs_now_secs();
-       struct sockaddr_in req_addr;
-
-       switch (email->estate) {
-       case LGSSMTP_IDLE:
-
-               if (email->on_next(email))
-                       break;
-
-               email->estate = LGSSMTP_CONNECTING;
-
-               uv_tcp_init(email->loop, &email->email_client);
-               if (uv_ip4_addr(email->email_smtp_ip, 25, &req_addr)) {
-                       lwsl_err("Unable to convert mailserver ads\n");
-                       return;
-               }
-
-               lwsl_notice("LGSSMTP_IDLE: connecting\n");
-
-               email->email_connect_started = now;
-               email->email_connect_req.data = email;
-               email->email_client.data = email;
-               uv_tcp_connect(&email->email_connect_req, &email->email_client,
-                              (struct sockaddr *)&req_addr,
-                              lwsgs_email_on_connect);
-
-               uv_timer_start(&email->timeout_email,
-                              uv_timeout_cb_email, 5000, 0);
-
-               break;
-
-       case LGSSMTP_CONNECTING:
-               if (email->email_connect_started - now > 5) {
-                       lwsl_err("mail session timed out\n");
-                       /* !!! kill the connection */
-                       uv_close((uv_handle_t *) &email->email_connect_req, ccb);
-                       email->estate = LGSSMTP_IDLE;
-               }
-               break;
-
-       default:
-               break;
-       }
-}
-
-LWS_VISIBLE LWS_EXTERN int
-lws_email_init(struct lws_email *email, uv_loop_t *loop, int max_content)
-{
-       email->content = lws_malloc(max_content);
-       if (!email->content)
-               return 1;
-
-       email->max_content_size = max_content;
-       uv_timer_init(loop, &email->timeout_email);
-
-       email->loop = loop;
-
-       /* trigger him one time in a bit */
-       uv_timer_start(&email->timeout_email, uv_timeout_cb_email, 2000, 0);
-
-       return 0;
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_email_check(struct lws_email *email)
-{
-       uv_timer_start(&email->timeout_email, uv_timeout_cb_email, 1000, 0);
-}
-
-LWS_VISIBLE LWS_EXTERN void
-lws_email_destroy(struct lws_email *email)
-{
-       if (email->content)
-               lws_free_set_NULL(email->content);
-
-       uv_timer_stop(&email->timeout_email);
-       uv_close((uv_handle_t *)&email->timeout_email, NULL);
-}
-
diff --git a/lib/ssl-client.c b/lib/ssl-client.c
deleted file mode 100644 (file)
index 0c75738..0000000
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-extern int openssl_websocket_private_data_index,
-    openssl_SSL_CTX_private_data_index;
-
-extern void
-lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info);
-
-extern int lws_ssl_get_error(struct lws *wsi, int n);
-
-#if defined(USE_WOLFSSL)
-#else
-
-static int
-OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
-{
-#if defined(LWS_WITH_ESP32)
-//     long gvr = ssl_pm_get_verify_result(
-       lwsl_notice("%s\n", __func__);
-
-       return 0;
-#else
-       SSL *ssl;
-       int n;
-       struct lws *wsi;
-
-       /* keep old behaviour accepting self-signed server certs */
-       if (!preverify_ok) {
-               int err = X509_STORE_CTX_get_error(x509_ctx);
-
-               if (err != X509_V_OK) {
-                       ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
-                       wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
-
-                       if ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
-                                       err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
-                                       wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED) {
-                               lwsl_notice("accepting self-signed certificate (verify_callback)\n");
-                               X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
-                               return 1;       // ok
-                       } else if ((err == X509_V_ERR_CERT_NOT_YET_VALID ||
-                                       err == X509_V_ERR_CERT_HAS_EXPIRED) &&
-                                       wsi->use_ssl & LCCSCF_ALLOW_EXPIRED) {
-                               if (err == X509_V_ERR_CERT_NOT_YET_VALID)
-                                       lwsl_notice("accepting not yet valid certificate (verify_callback)\n");
-                               else if (err == X509_V_ERR_CERT_HAS_EXPIRED)
-                                       lwsl_notice("accepting expired certificate (verify_callback)\n");
-                               X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
-                               return 1;       // ok
-                       }
-               }
-       }
-
-       ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
-       wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
-
-       n = lws_get_context_protocol(wsi->context, 0).callback(wsi, LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION, x509_ctx, ssl, preverify_ok);
-
-       /* keep old behaviour if something wrong with server certs */
-       /* if ssl error is overruled in callback and cert is ok,
-        * X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); must be set and
-        * return value is 0 from callback */
-       if (!preverify_ok) {
-               int err = X509_STORE_CTX_get_error(x509_ctx);
-
-               if (err != X509_V_OK) { /* cert validation error was not handled in callback */
-                       int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
-                       const char* msg = X509_verify_cert_error_string(err);
-                       lwsl_err("SSL error: %s (preverify_ok=%d;err=%d;depth=%d)\n", msg, preverify_ok, err, depth);
-                       return preverify_ok;    // not ok
-               }
-       }
-       /* convert callback return code from 0 = OK to verify callback return value 1 = OK */
-       return !n;
-#endif
-}
-#endif
-
-int
-lws_ssl_client_bio_create(struct lws *wsi)
-{
-       char hostname[128], *p;
-
-       if (lws_hdr_copy(wsi, hostname, sizeof(hostname),
-                        _WSI_TOKEN_CLIENT_HOST) <= 0) {
-               lwsl_err("%s: Unable to get hostname\n", __func__);
-
-               return -1;
-       }
-
-       /*
-        * remove any :port part on the hostname... necessary for network
-        * connection but typical certificates do not contain it
-        */
-       p = hostname;
-       while (*p) {
-               if (*p == ':') {
-                       *p = '\0';
-                       break;
-               }
-               p++;
-       }
-
-       wsi->ssl = SSL_new(wsi->vhost->ssl_client_ctx);
-       if (!wsi->ssl) {
-               lwsl_err("SSL_new failed: %s\n",
-                        ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-               lws_ssl_elaborate_error();
-               return -1;
-       }
-
-#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
-       if (wsi->vhost->ssl_info_event_mask)
-               SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback);
-#endif
-
-#if defined LWS_HAVE_X509_VERIFY_PARAM_set1_host
-       X509_VERIFY_PARAM *param;
-       (void)param;
-
-       if (!(wsi->use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) {
-               param = SSL_get0_param(wsi->ssl);
-               /* Enable automatic hostname checks */
-               X509_VERIFY_PARAM_set_hostflags(param,
-                                               X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
-               X509_VERIFY_PARAM_set1_host(param, hostname, 0);
-       }
-
-#endif
-
-#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_ESP32)
-#ifndef USE_OLD_CYASSL
-       /* OpenSSL_client_verify_callback will be called @ SSL_connect() */
-       SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback);
-#endif
-#endif
-
-#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_ESP32)
-       SSL_set_mode(wsi->ssl,  SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
-#endif
-       /*
-        * use server name indication (SNI), if supported,
-        * when establishing connection
-        */
-#ifdef USE_WOLFSSL
-#ifdef USE_OLD_CYASSL
-#ifdef CYASSL_SNI_HOST_NAME
-       CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname));
-#endif
-#else
-#ifdef WOLFSSL_SNI_HOST_NAME
-       wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname));
-#endif
-#endif
-#else
-#if defined(LWS_WITH_ESP32)
-// esp-idf openssl shim does not seem ready for this
-//     SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback);
-       SSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, OpenSSL_client_verify_callback);
-
-#else
-#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
-       SSL_set_tlsext_host_name(wsi->ssl, hostname);
-#endif
-#endif
-#endif
-
-#ifdef USE_WOLFSSL
-       /*
-        * wolfSSL/CyaSSL does certificate verification differently
-        * from OpenSSL.
-        * If we should ignore the certificate, we need to set
-        * this before SSL_new and SSL_connect is called.
-        * Otherwise the connect will simply fail with error code -155
-        */
-#ifdef USE_OLD_CYASSL
-       if (wsi->use_ssl == 2)
-               CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL);
-#else
-       if (wsi->use_ssl == 2)
-               wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL);
-#endif
-#endif /* USE_WOLFSSL */
-
-#if !defined(LWS_WITH_ESP32)
-       wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE);
-       SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
-#else
-       SSL_set_fd(wsi->ssl, wsi->desc.sockfd);
-#endif
-
-#ifdef USE_WOLFSSL
-#ifdef USE_OLD_CYASSL
-       CyaSSL_set_using_nonblock(wsi->ssl, 1);
-#else
-       wolfSSL_set_using_nonblock(wsi->ssl, 1);
-#endif
-#else
-#if !defined(LWS_WITH_ESP32)
-       BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */
-#endif
-#endif
-
-#if !defined(LWS_WITH_ESP32)
-       SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index,
-                       wsi);
-#endif
-
-       return 0;
-}
-
-#if defined(LWS_WITH_ESP32)
-int ERR_get_error(void)
-{
-       return 0;
-}
-#endif
-
-int
-lws_ssl_client_connect1(struct lws *wsi)
-{
-       struct lws_context *context = wsi->context;
-       int n = 0;
-
-       lws_latency_pre(context, wsi);
-
-       n = SSL_connect(wsi->ssl);
-
-       lws_latency(context, wsi,
-         "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0);
-
-       if (n < 0) {
-               n = lws_ssl_get_error(wsi, n);
-
-               if (n == SSL_ERROR_WANT_READ)
-                       goto some_wait;
-
-               if (n == SSL_ERROR_WANT_WRITE) {
-                       /*
-                        * wants us to retry connect due to
-                        * state of the underlying ssl layer...
-                        * but since it may be stalled on
-                        * blocked write, no incoming data may
-                        * arrive to trigger the retry.
-                        * Force (possibly many times if the SSL
-                        * state persists in returning the
-                        * condition code, but other sockets
-                        * are getting serviced inbetweentimes)
-                        * us to get called back when writable.
-                        */
-                       lwsl_info("%s: WANT_WRITE... retrying\n", __func__);
-                       lws_callback_on_writable(wsi);
-some_wait:
-                       wsi->mode = LWSCM_WSCL_WAITING_SSL;
-
-                       return 0; /* no error */
-               }
-
-               {
-                       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-                       char *p = (char *)&pt->serv_buf[0];
-                       char *sb = p;
-
-                       lwsl_err("ssl hs1 error, X509_V_ERR = %d: %s\n",
-                                n, ERR_error_string(n, sb));
-                       lws_ssl_elaborate_error();
-               }
-
-               n = -1;
-       }
-
-       if (n <= 0) {
-               /*
-                * retry if new data comes until we
-                * run into the connection timeout or win
-                */
-
-               unsigned long error = ERR_get_error();
-
-               if (error != SSL_ERROR_NONE) {
-                       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-                       char *p = (char *)&pt->serv_buf[0];
-                       char *sb = p;
-                       lwsl_err("SSL connect error %lu: %s\n",
-                               error, ERR_error_string(error, sb));
-                       return -1;
-               }
-       }
-
-       return 1;
-}
-
-int
-lws_ssl_client_connect2(struct lws *wsi)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-       char *p = (char *)&pt->serv_buf[0];
-       char *sb = p;
-       int n = 0;
-
-       if (wsi->mode == LWSCM_WSCL_WAITING_SSL) {
-               lws_latency_pre(context, wsi);
-               n = SSL_connect(wsi->ssl);
-               lwsl_debug("%s: SSL_connect says %d\n", __func__, n);
-
-               lws_latency(context, wsi,
-                           "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0);
-
-               if (n < 0) {
-                       n = lws_ssl_get_error(wsi, n);
-
-                       if (n == SSL_ERROR_WANT_READ) {
-                               lwsl_info("SSL_connect WANT_READ... retrying\n");
-
-                               wsi->mode = LWSCM_WSCL_WAITING_SSL;
-
-                               return 0; /* no error */
-                       }
-
-                       if (n == SSL_ERROR_WANT_WRITE) {
-                               /*
-                                * wants us to retry connect due to
-                                * state of the underlying ssl layer...
-                                * but since it may be stalled on
-                                * blocked write, no incoming data may
-                                * arrive to trigger the retry.
-                                * Force (possibly many times if the SSL
-                                * state persists in returning the
-                                * condition code, but other sockets
-                                * are getting serviced inbetweentimes)
-                                * us to get called back when writable.
-                                */
-                               lwsl_info("SSL_connect WANT_WRITE... retrying\n");
-                               lws_callback_on_writable(wsi);
-
-                               wsi->mode = LWSCM_WSCL_WAITING_SSL;
-
-                               return 0; /* no error */
-                       }
-
-                       n = -1;
-               }
-
-               if (n <= 0) {
-                       /*
-                        * retry if new data comes until we
-                        * run into the connection timeout or win
-                        */
-                       unsigned long error = ERR_get_error();
-                       if (error != SSL_ERROR_NONE) {
-                               lwsl_err("SSL connect error %lu: %s\n",
-                                        error, ERR_error_string(error, sb));
-                               return -1;
-                       }
-               }
-       }
-
-#if defined(LWS_WITH_ESP32)
-       {
-               X509 *peer = SSL_get_peer_certificate(wsi->ssl);
-
-               if (!peer) {
-                       lwsl_notice("peer did not provide cert\n");
-
-                       return -1;
-               }
-               lwsl_notice("peer provided cert\n");
-       }
-#endif
-
-#ifndef USE_WOLFSSL
-       /*
-        * See comment above about wolfSSL certificate
-        * verification
-        */
-       lws_latency_pre(context, wsi);
-       n = SSL_get_verify_result(wsi->ssl);
-       lws_latency(context, wsi,
-               "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0);
-
-       lwsl_debug("get_verify says %d\n", n);
-
-       if (n != X509_V_OK) {
-               if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
-                    n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
-                    (wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED)) {
-                       lwsl_notice("accepting self-signed certificate\n");
-               } else if ((n == X509_V_ERR_CERT_NOT_YET_VALID ||
-                           n == X509_V_ERR_CERT_HAS_EXPIRED) &&
-                    (wsi->use_ssl & LCCSCF_ALLOW_EXPIRED)) {
-                       lwsl_notice("accepting expired certificate\n");
-               } else if (n == X509_V_ERR_CERT_NOT_YET_VALID) {
-                       lwsl_notice("Cert is from the future... "
-                                   "probably our clock... accepting...\n");
-               } else {
-                       lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n",
-                                n, ERR_error_string(n, sb));
-                       lws_ssl_elaborate_error();
-                       return -1;
-               }
-       }
-
-#endif /* USE_WOLFSSL */
-
-       return 1;
-}
-
-
-int lws_context_init_client_ssl(struct lws_context_creation_info *info,
-                               struct lws_vhost *vhost)
-{
-       SSL_METHOD *method = NULL;
-       struct lws wsi;
-       unsigned long error;
-#if !defined(LWS_WITH_ESP32)
-       const char *cipher_list = info->ssl_cipher_list;
-       const char *ca_filepath = info->ssl_ca_filepath;
-       const char *private_key_filepath = info->ssl_private_key_filepath;
-       const char *cert_filepath = info->ssl_cert_filepath;
-
-       int n;
-
-       /*
-        *  for backwards-compatibility default to using ssl_... members, but
-        * if the newer client-specific ones are given, use those
-        */
-       if (info->client_ssl_cipher_list)
-               cipher_list = info->client_ssl_cipher_list;
-       if (info->client_ssl_ca_filepath)
-               ca_filepath = info->client_ssl_ca_filepath;
-       if (info->client_ssl_cert_filepath)
-               cert_filepath = info->client_ssl_cert_filepath;
-       if (info->client_ssl_private_key_filepath)
-               private_key_filepath = info->client_ssl_private_key_filepath;
-#endif
-
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
-               return 0;
-
-       if (vhost->ssl_client_ctx)
-               return 0;
-
-       if (info->provided_client_ssl_ctx) {
-               /* use the provided OpenSSL context if given one */
-               vhost->ssl_client_ctx = info->provided_client_ssl_ctx;
-               /* nothing for lib to delete */
-               vhost->user_supplied_ssl_ctx = 1;
-
-               return 0;
-       }
-
-       /* basic openssl init already happened in context init */
-
-       /* choose the most recent spin of the api */
-#if defined(LWS_HAVE_TLS_CLIENT_METHOD)
-       method = (SSL_METHOD *)TLS_client_method();
-#elif defined(LWS_HAVE_TLSV1_2_CLIENT_METHOD)
-       method = (SSL_METHOD *)TLSv1_2_client_method();
-#else
-       method = (SSL_METHOD *)SSLv23_client_method();
-#endif
-       if (!method) {
-               error = ERR_get_error();
-               lwsl_err("problem creating ssl method %lu: %s\n",
-                       error, ERR_error_string(error,
-                                     (char *)vhost->context->pt[0].serv_buf));
-               return 1;
-       }
-       /* create context */
-       vhost->ssl_client_ctx = SSL_CTX_new(method);
-       if (!vhost->ssl_client_ctx) {
-               error = ERR_get_error();
-               lwsl_err("problem creating ssl context %lu: %s\n",
-                       error, ERR_error_string(error,
-                                     (char *)vhost->context->pt[0].serv_buf));
-               return 1;
-       }
-
-#ifdef SSL_OP_NO_COMPRESSION
-       SSL_CTX_set_options(vhost->ssl_client_ctx, SSL_OP_NO_COMPRESSION);
-#endif
-
-#if !defined(LWS_WITH_ESP32)
-       SSL_CTX_set_options(vhost->ssl_client_ctx,
-                           SSL_OP_CIPHER_SERVER_PREFERENCE);
-
-       if (cipher_list)
-               SSL_CTX_set_cipher_list(vhost->ssl_client_ctx, cipher_list);
-
-#ifdef LWS_SSL_CLIENT_USE_OS_CA_CERTS
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS))
-               /* loads OS default CA certs */
-               SSL_CTX_set_default_verify_paths(vhost->ssl_client_ctx);
-#endif
-
-       /* openssl init for cert verification (for client sockets) */
-       if (!ca_filepath) {
-               if (!SSL_CTX_load_verify_locations(
-                       vhost->ssl_client_ctx, NULL,
-                                            LWS_OPENSSL_CLIENT_CERTS))
-                       lwsl_err(
-                           "Unable to load SSL Client certs from %s "
-                           "(set by --with-client-cert-dir= "
-                           "in configure) --  client ssl isn't "
-                           "going to work\n", LWS_OPENSSL_CLIENT_CERTS);
-       } else
-               if (!SSL_CTX_load_verify_locations(
-                       vhost->ssl_client_ctx, ca_filepath, NULL)) {
-                       lwsl_err(
-                               "Unable to load SSL Client certs "
-                               "file from %s -- client ssl isn't "
-                               "going to work\n", info->client_ssl_ca_filepath);
-                       lws_ssl_elaborate_error();
-               }
-               else
-                       lwsl_info("loaded ssl_ca_filepath\n");
-#endif
-       /*
-        * callback allowing user code to load extra verification certs
-        * helping the client to verify server identity
-        */
-#if !defined(LWS_WITH_ESP32)
-
-       /* support for client-side certificate authentication */
-       if (cert_filepath) {
-               lwsl_notice("%s: doing cert filepath\n", __func__);
-               n = SSL_CTX_use_certificate_chain_file(vhost->ssl_client_ctx,
-                                                      cert_filepath);
-               if (n < 1) {
-                       lwsl_err("problem %d getting cert '%s'\n", n,
-                                cert_filepath);
-                       lws_ssl_elaborate_error();
-                       return 1;
-               }
-               lwsl_notice("Loaded client cert %s\n", cert_filepath);
-       }
-       if (private_key_filepath) {
-               lwsl_notice("%s: doing private key filepath\n", __func__);
-               lws_ssl_bind_passphrase(vhost->ssl_client_ctx, info);
-               /* set the private key from KeyFile */
-               if (SSL_CTX_use_PrivateKey_file(vhost->ssl_client_ctx,
-                   private_key_filepath, SSL_FILETYPE_PEM) != 1) {
-                       lwsl_err("use_PrivateKey_file '%s'\n",
-                                private_key_filepath);
-                       lws_ssl_elaborate_error();
-                       return 1;
-               }
-               lwsl_notice("Loaded client cert private key %s\n",
-                           private_key_filepath);
-
-               /* verify private key */
-               if (!SSL_CTX_check_private_key(vhost->ssl_client_ctx)) {
-                       lwsl_err("Private SSL key doesn't match cert\n");
-                       return 1;
-               }
-       }
-#endif
-       /*
-        * give him a fake wsi with context set, so he can use
-        * lws_get_context() in the callback
-        */
-       memset(&wsi, 0, sizeof(wsi));
-       wsi.vhost = vhost;
-       wsi.context = vhost->context;
-
-       vhost->protocols[0].callback(&wsi,
-                       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS,
-                                      vhost->ssl_client_ctx, NULL, 0);
-
-       return 0;
-}
diff --git a/lib/ssl-http2.c b/lib/ssl-http2.c
deleted file mode 100644 (file)
index eb3d208..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- *
- * Some or all of this file is based on code from nghttp2, which has the
- * following license.  Since it's more liberal than lws license, you're also
- * at liberty to get the original code from
- * https://github.com/tatsuhiro-t/nghttp2 under his liberal terms alone.
- *
- * nghttp2 - HTTP/2.0 C Library
- *
- * Copyright (c) 2012 Tatsuhiro Tsujikawa
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include "private-libwebsockets.h"
-
-#ifndef LWS_NO_SERVER
-#ifdef LWS_OPENSSL_SUPPORT
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
-
-struct alpn_ctx {
-       unsigned char *data;
-       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)
-{
-       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;
-
-       return SSL_TLSEXT_ERR_OK;
-}
-#endif
-
-LWS_VISIBLE void
-lws_context_init_http2_ssl(struct lws_vhost *vhost)
-{
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
-       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
-       lwsl_notice(
-               " HTTP2 / ALPN configured but not supported by OpenSSL 0x%lx\n",
-                   OPENSSL_VERSION_NUMBER);
-#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
-}
-
-void lws_http2_configure_if_upgraded(struct lws *wsi)
-{
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
-       struct allocated_headers *ah;
-       const char *method = "alpn";
-       const unsigned char *name;
-       unsigned len;
-
-       SSL_get0_alpn_selected(wsi->ssl, &name, &len);
-
-       if (!len) {
-               SSL_get0_next_proto_negotiated(wsi->ssl, &name, &len);
-               method = "npn";
-       }
-
-       if (!len) {
-               lwsl_info("no npn/alpn upgrade\n");
-               return;
-       }
-
-       (void)method;
-       lwsl_info("negotiated %s using %s\n", name, method);
-       wsi->use_ssl = 1;
-       if (strncmp((char *)name, "http/1.1", 8) == 0)
-               return;
-
-       /* http2 */
-
-       /* adopt the header info */
-
-       ah = wsi->u.hdr.ah;
-
-       lws_union_transition(wsi, LWSCM_HTTP2_SERVING);
-       wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE;
-
-       /* 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);
-
-       /* HTTP2 union */
-#endif
-}
-
-#endif
-#endif
diff --git a/lib/ssl-server.c b/lib/ssl-server.c
deleted file mode 100644 (file)
index ea87ee5..0000000
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-extern int openssl_websocket_private_data_index,
-    openssl_SSL_CTX_private_data_index;
-
-extern void
-lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info);
-
-#if !defined(LWS_WITH_ESP32)
-static int
-OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
-{
-       SSL *ssl;
-       int n;
-       struct lws *wsi;
-
-       ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
-               SSL_get_ex_data_X509_STORE_CTX_idx());
-
-       /*
-        * !!! nasty openssl requires the index to come as a library-scope
-        * static
-        */
-       wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
-
-       n = wsi->vhost->protocols[0].callback(wsi,
-                       LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION,
-                                          x509_ctx, ssl, preverify_ok);
-
-       /* convert return code from 0 = OK to 1 = OK */
-       return !n;
-}
-#endif
-
-static int
-lws_context_ssl_init_ecdh(struct lws_vhost *vhost)
-{
-#ifdef LWS_SSL_SERVER_WITH_ECDH_CERT
-       EC_KEY *EC_key = NULL;
-       EVP_PKEY *pkey;
-       int KeyType;
-       X509 *x;
-
-       if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH))
-               return 0;
-
-       lwsl_notice(" Using ECDH certificate support\n");
-
-       /* Get X509 certificate from ssl context */
-       x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0);
-       if (!x) {
-               lwsl_err("%s: x is NULL\n", __func__);
-               return 1;
-       }
-       /* Get the public key from certificate */
-       pkey = X509_get_pubkey(x);
-       if (!pkey) {
-               lwsl_err("%s: pkey is NULL\n", __func__);
-
-               return 1;
-       }
-       /* Get the key type */
-       KeyType = EVP_PKEY_type(pkey->type);
-
-       if (EVP_PKEY_EC != KeyType) {
-               lwsl_notice("Key type is not EC\n");
-               return 0;
-       }
-       /* Get the key */
-       EC_key = EVP_PKEY_get1_EC_KEY(pkey);
-       /* Set ECDH parameter */
-       if (!EC_key) {
-               lwsl_err("%s: ECDH key is NULL \n", __func__);
-               return 1;
-       }
-       SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, EC_key);
-       EC_KEY_free(EC_key);
-#endif
-       return 0;
-}
-
-static int
-lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info,
-                               struct lws_vhost *vhost)
-{
-#ifdef LWS_HAVE_OPENSSL_ECDH_H
-       EC_KEY *ecdh;
-       int ecdh_nid;
-       const char *ecdh_curve = "prime256v1";
-
-       if (info->ecdh_curve)
-               ecdh_curve = info->ecdh_curve;
-
-       ecdh_nid = OBJ_sn2nid(ecdh_curve);
-       if (NID_undef == ecdh_nid) {
-               lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve);
-               return 1;
-       }
-
-       ecdh = EC_KEY_new_by_curve_name(ecdh_nid);
-       if (NULL == ecdh) {
-               lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve);
-               return 1;
-       }
-       SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, ecdh);
-       EC_KEY_free(ecdh);
-
-       SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_ECDH_USE);
-
-       lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve);
-#else
-#if !defined(LWS_WITH_ESP32)
-       lwsl_notice(" OpenSSL doesn't support ECDH\n");
-#endif
-#endif
-       return 0;
-}
-
-#ifndef OPENSSL_NO_TLSEXT
-static int
-lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg)
-{
-       struct lws_context *context;
-       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
-        * same port.
-        */
-       vh = context->vhost_list;
-       while (vh) {
-               if (!vh->being_destroyed && vh->ssl_ctx == SSL_get_SSL_CTX(ssl))
-                       break;
-               vh = vh->vhost_next;
-       }
-
-       assert(vh); /* we cannot get an ssl without using a vhost ssl_ctx */
-       port = vh->listen_port;
-
-       servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-
-       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;
-}
-#endif
-
-LWS_VISIBLE int
-lws_context_init_server_ssl(struct lws_context_creation_info *info,
-                           struct lws_vhost *vhost)
-{
-       struct lws_context *context = vhost->context;
-       struct lws wsi;
-       unsigned long error;
-       int n;
-
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) {
-               vhost->use_ssl = 0;
-               return 0;
-       }
-       if (info->port != CONTEXT_PORT_NO_LISTEN) {
-
-               vhost->use_ssl = info->ssl_cert_filepath != NULL;
-
-               if (vhost->use_ssl && info->ssl_cipher_list)
-                       lwsl_notice(" SSL ciphers: '%s'\n", info->ssl_cipher_list);
-
-               if (vhost->use_ssl)
-                       lwsl_notice(" Using SSL mode\n");
-               else
-                       lwsl_notice(" Using non-SSL mode\n");
-       }
-
-       /*
-        * give him a fake wsi with context + vhost set, so he can use
-        * lws_get_context() in the callback
-        */
-       memset(&wsi, 0, sizeof(wsi));
-       wsi.vhost = vhost;
-       wsi.context = context;
-
-       (void)n;
-       (void)error;
-
-       /*
-        * Firefox insists on SSLv23 not SSLv3
-        * Konq disables SSLv2 by default now, SSLv23 works
-        *
-        * SSLv23_server_method() is the openssl method for "allow all TLS
-        * versions", compared to e.g. TLSv1_2_server_method() which only allows
-        * tlsv1.2. Unwanted versions must be disabled using SSL_CTX_set_options()
-        */
-#if !defined(LWS_WITH_ESP32)
-       {
-               SSL_METHOD *method;
-
-               method = (SSL_METHOD *)SSLv23_server_method();
-               if (!method) {
-                       error = ERR_get_error();
-                       lwsl_err("problem creating ssl method %lu: %s\n",
-                                       error, ERR_error_string(error,
-                                             (char *)context->pt[0].serv_buf));
-                       return 1;
-               }
-               vhost->ssl_ctx = SSL_CTX_new(method);   /* create context */
-               if (!vhost->ssl_ctx) {
-                       error = ERR_get_error();
-                       lwsl_err("problem creating ssl context %lu: %s\n",
-                                       error, ERR_error_string(error,
-                                             (char *)context->pt[0].serv_buf));
-                       return 1;
-               }
-       }
-#else
-       {
-               const SSL_METHOD *method = TLSv1_2_server_method();
-
-               vhost->ssl_ctx = SSL_CTX_new(method);   /* create context */
-               if (!vhost->ssl_ctx) {
-                       lwsl_err("problem creating ssl context\n");
-                       return 1;
-               }
-
-       }
-#endif
-#if !defined(LWS_WITH_ESP32)
-
-       /* associate the lws context with the SSL_CTX */
-
-       SSL_CTX_set_ex_data(vhost->ssl_ctx,
-                       openssl_SSL_CTX_private_data_index, (char *)vhost->context);
-       /* Disable SSLv2 and SSLv3 */
-       SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
-#ifdef SSL_OP_NO_COMPRESSION
-       SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_COMPRESSION);
-#endif
-       SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_DH_USE);
-       SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
-
-       if (info->ssl_cipher_list)
-               SSL_CTX_set_cipher_list(vhost->ssl_ctx,
-                                               info->ssl_cipher_list);
-#endif
-
-       /* as a server, are we requiring clients to identify themselves? */
-
-       if (lws_check_opt(info->options,
-                         LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) {
-               int verify_options = SSL_VERIFY_PEER;
-
-               if (!lws_check_opt(info->options,
-                                  LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
-                       verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
-
-#if !defined(LWS_WITH_ESP32)
-               SSL_CTX_set_session_id_context(vhost->ssl_ctx,
-                               (unsigned char *)context, sizeof(void *));
-
-               /* absolutely require the client cert */
-
-               SSL_CTX_set_verify(vhost->ssl_ctx,
-                      verify_options, OpenSSL_verify_callback);
-#endif
-       }
-
-#ifndef OPENSSL_NO_TLSEXT
-       SSL_CTX_set_tlsext_servername_callback(vhost->ssl_ctx,
-                                              lws_ssl_server_name_cb);
-#endif
-
-       /*
-        * give user code a chance to load certs into the server
-        * allowing it to verify incoming client certs
-        */
-#if !defined(LWS_WITH_ESP32)
-       if (info->ssl_ca_filepath &&
-           !SSL_CTX_load_verify_locations(vhost->ssl_ctx,
-                                          info->ssl_ca_filepath, NULL)) {
-               lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n", __func__);
-       }
-#endif
-       if (vhost->use_ssl) {
-               if (lws_context_ssl_init_ecdh_curve(info, vhost))
-                       return -1;
-
-               vhost->protocols[0].callback(&wsi,
-                       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS,
-                       vhost->ssl_ctx, NULL, 0);
-       }
-
-       if (lws_check_opt(info->options, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT))
-               /* Normally SSL listener rejects non-ssl, optionally allow */
-               vhost->allow_non_ssl_on_ssl_port = 1;
-
-       if (info->ssl_options_set)
-               SSL_CTX_set_options(vhost->ssl_ctx, info->ssl_options_set);
-
-/* SSL_clear_options introduced in 0.9.8m */
-#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
-       if (info->ssl_options_clear)
-               SSL_CTX_clear_options(vhost->ssl_ctx, info->ssl_options_clear);
-#endif
-
-       lwsl_info(" SSL options 0x%lX\n",
-                   SSL_CTX_get_options(vhost->ssl_ctx));
-
-       if (vhost->use_ssl) {
-               /* openssl init for server sockets */
-#if !defined(LWS_WITH_ESP32)
-               /* set the local certificate from CertFile */
-               n = SSL_CTX_use_certificate_chain_file(vhost->ssl_ctx,
-                                       info->ssl_cert_filepath);
-               if (n != 1) {
-                       error = ERR_get_error();
-                       lwsl_err("problem getting cert '%s' %lu: %s\n",
-                               info->ssl_cert_filepath,
-                               error,
-                               ERR_error_string(error,
-                                             (char *)context->pt[0].serv_buf));
-                       return 1;
-               }
-               lws_ssl_bind_passphrase(vhost->ssl_ctx, info);
-#else
-               uint8_t *p;
-               lws_filepos_t flen;
-               int err;
-
-               if (alloc_pem_to_der_file(vhost->context, info->ssl_cert_filepath, &p,
-                                               &flen)) {
-                       lwsl_err("couldn't find cert file %s\n",
-                                info->ssl_cert_filepath);
-
-                       return 1;
-               }
-               err = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, flen, p);
-               if (!err) {
-                       lwsl_err("Problem loading cert\n");
-                       return 1;
-               }
-
-               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;
-               }
-
-//             free(p);
-#endif
-               if (info->ssl_private_key_filepath != NULL) {
-#if !defined(LWS_WITH_ESP32)
-                       /* set the private key from KeyFile */
-                       if (SSL_CTX_use_PrivateKey_file(vhost->ssl_ctx,
-                                    info->ssl_private_key_filepath,
-                                                      SSL_FILETYPE_PEM) != 1) {
-                               error = ERR_get_error();
-                               lwsl_err("ssl problem getting key '%s' %lu: %s\n",
-                                        info->ssl_private_key_filepath, error,
-                                        ERR_error_string(error,
-                                             (char *)context->pt[0].serv_buf));
-                               return 1;
-                       }
-#endif
-               } else
-                       if (vhost->protocols[0].callback(&wsi,
-                               LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
-                               vhost->ssl_ctx, NULL, 0)) {
-                               lwsl_err("ssl private key not set\n");
-
-                               return 1;
-                       }
-#if !defined(LWS_WITH_ESP32)
-               /* verify private key */
-               if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) {
-                       lwsl_err("Private SSL key doesn't match cert\n");
-                       return 1;
-               }
-#endif
-               if (lws_context_ssl_init_ecdh(vhost))
-                       return 1;
-
-               /*
-                * SSL is happy and has a cert it's content with
-                * If we're supporting HTTP2, initialize that
-                */
-
-               lws_context_init_http2_ssl(vhost);
-       }
-
-       return 0;
-}
-
diff --git a/lib/ssl.c b/lib/ssl.c
deleted file mode 100644 (file)
index c517267..0000000
--- a/lib/ssl.c
+++ /dev/null
@@ -1,892 +0,0 @@
-/*
- * libwebsockets - small server side websockets and web server implementation
- *
- * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#include "private-libwebsockets.h"
-
-/* workaround for mingw */
-#if !defined(ECONNABORTED)
-#define ECONNABORTED 103
-#endif
-
-int lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf,
-               lws_filepos_t *amount)
-{
-       lws_filepos_t len;
-       lws_fop_flags_t flags = LWS_O_RDONLY;
-       lws_fop_fd_t fops_fd = lws_vfs_file_open(
-                               lws_get_fops(context), filename, &flags);
-       int ret = 1;
-
-       if (!fops_fd)
-               return 1;
-
-       len = lws_vfs_get_length(fops_fd);
-
-       *buf = malloc((size_t)len);
-       if (!*buf)
-               goto bail;
-
-       if (lws_vfs_file_read(fops_fd, amount, *buf, len))
-               goto bail;
-
-       ret = 0;
-bail:
-       lws_vfs_file_close(&fops_fd);
-
-       return ret;
-}
-
-#if defined(LWS_WITH_ESP32)
-int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
-              lws_filepos_t *amount)
-{
-       nvs_handle nvh;
-       size_t s;
-       int n = 0;
-
-       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
-       if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) {
-               n = 1;
-               goto bail;
-       }
-       *buf = malloc(s);
-       if (!*buf) {
-               n = 2;
-               goto bail;
-       }
-       if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) {
-               free(*buf);
-               n = 1;
-               goto bail;
-       }
-
-       *amount = s;
-
-bail:
-       nvs_close(nvh);
-
-       return n;
-}
-int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf,
-              lws_filepos_t *amount)
-{
-       uint8_t *pem, *p, *q, *end;
-       lws_filepos_t len;
-       int n;
-
-       n = alloc_file(context, filename, &pem, &len);
-       if (n)
-               return n;
-
-       /* trim the first line */
-
-       p = pem;
-       end = p + len;
-       if (strncmp((char *)p, "-----", 5))
-               goto bail;
-       p += 5;
-       while (p < end && *p != '\n' && *p != '-')
-               p++;
-
-       if (*p != '-')
-               goto bail;
-
-       while (p < end && *p != '\n')
-               p++;
-
-       if (p >= end)
-               goto bail;
-
-       p++;
-
-       /* trim the last line */
-
-       q = end - 2;
-
-       while (q > pem && *q != '\n')
-               q--;
-
-       if (*q != '\n')
-               goto bail;
-
-       *q = '\0';
-
-       *amount = lws_b64_decode_string((char *)p, (char *)pem, len);
-       *buf = pem;
-
-       return 0;
-
-bail:
-       free(pem);
-
-       return 4;
-}
-#endif
-
-int openssl_websocket_private_data_index,
-    openssl_SSL_CTX_private_data_index;
-
-int lws_ssl_get_error(struct lws *wsi, int n)
-{
-       if (!wsi->ssl)
-               return 99;
-       lwsl_debug("%s: %p %d\n", __func__, wsi->ssl, n);
-       return SSL_get_error(wsi->ssl, n);
-}
-
-/* Copies a string describing the code returned by lws_ssl_get_error(),
- * which may also contain system error information in the case of SSL_ERROR_SYSCALL,
- * into buf up to len.
- * Returns a pointer to buf.
- *
- * Note: the lws_ssl_get_error() code is *not* an error code that can be passed
- * to ERR_error_string(),
- *
- * ret is the return value originally passed to lws_ssl_get_error(), needed to disambiguate
- * SYS_ERROR_SYSCALL.
- *
- * See man page for SSL_get_error().
- *
- * Not thread safe, uses strerror()
- */
-char* lws_ssl_get_error_string(int status, int ret, char *buf, size_t len) {
-       switch (status) {
-       case SSL_ERROR_NONE: return strncpy(buf, "SSL_ERROR_NONE", len);
-       case SSL_ERROR_ZERO_RETURN: return strncpy(buf, "SSL_ERROR_ZERO_RETURN", len);
-       case SSL_ERROR_WANT_READ: return strncpy(buf, "SSL_ERROR_WANT_READ", len);
-       case SSL_ERROR_WANT_WRITE: return strncpy(buf, "SSL_ERROR_WANT_WRITE", len);
-       case SSL_ERROR_WANT_CONNECT: return strncpy(buf, "SSL_ERROR_WANT_CONNECT", len);
-       case SSL_ERROR_WANT_ACCEPT: return strncpy(buf, "SSL_ERROR_WANT_ACCEPT", len);
-       case SSL_ERROR_WANT_X509_LOOKUP: return strncpy(buf, "SSL_ERROR_WANT_X509_LOOKUP", len);
-       case SSL_ERROR_SYSCALL:
-               switch (ret) {
-                case 0:
-                        lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: EOF");
-                        return buf;
-                case -1:
-#ifndef LWS_PLAT_OPTEE
-                       lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %s", strerror(errno));
-#else
-                       lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %d", errno);
-#endif
-                       return buf;
-                default:
-                        return strncpy(buf, "SSL_ERROR_SYSCALL", len);
-       }
-       case SSL_ERROR_SSL: return "SSL_ERROR_SSL";
-       default: return "SSL_ERROR_UNKNOWN";
-       }
-}
-
-void
-lws_ssl_elaborate_error(void)
-{
-#if defined(LWS_WITH_ESP32)
-#else
-       char buf[256];
-       u_long err;
-
-       while ((err = ERR_get_error()) != 0) {
-               ERR_error_string_n(err, buf, sizeof(buf));
-               lwsl_err("*** %s\n", buf);
-       }
-#endif
-}
-
-#if !defined(LWS_WITH_ESP32)
-
-static int
-lws_context_init_ssl_pem_passwd_cb(char * buf, int size, int rwflag, void *userdata)
-{
-       struct lws_context_creation_info * info =
-                       (struct lws_context_creation_info *)userdata;
-
-       strncpy(buf, info->ssl_private_key_password, size);
-       buf[size - 1] = '\0';
-
-       return strlen(buf);
-}
-
-void
-lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info)
-{
-       if (!info->ssl_private_key_password)
-               return;
-       /*
-        * password provided, set ssl callback and user data
-        * for checking password which will be trigered during
-        * SSL_CTX_use_PrivateKey_file function
-        */
-       SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)info);
-       SSL_CTX_set_default_passwd_cb(ssl_ctx, lws_context_init_ssl_pem_passwd_cb);
-}
-#endif
-
-int
-lws_context_init_ssl_library(struct lws_context_creation_info *info)
-{
-#ifdef USE_WOLFSSL
-#ifdef USE_OLD_CYASSL
-       lwsl_notice(" Compiled with CyaSSL support\n");
-#else
-       lwsl_notice(" Compiled with wolfSSL support\n");
-#endif
-#else
-#if defined(LWS_USE_BORINGSSL)
-       lwsl_notice(" Compiled with BoringSSL support\n");
-#else
-       lwsl_notice(" Compiled with OpenSSL support\n");
-#endif
-#endif
-       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) {
-               lwsl_notice(" SSL disabled: no LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT\n");
-               return 0;
-       }
-
-       /* basic openssl init */
-
-       lwsl_notice("Doing SSL library init\n");
-
-#if !defined(LWS_WITH_ESP32)
-       SSL_library_init();
-       OpenSSL_add_all_algorithms();
-       SSL_load_error_strings();
-
-       openssl_websocket_private_data_index =
-               SSL_get_ex_new_index(0, "lws", NULL, NULL, NULL);
-
-       openssl_SSL_CTX_private_data_index = SSL_CTX_get_ex_new_index(0,
-                       NULL, NULL, NULL, NULL);
-#endif
-
-       return 0;
-}
-
-LWS_VISIBLE void
-lws_ssl_destroy(struct lws_vhost *vhost)
-{
-       if (!lws_check_opt(vhost->context->options,
-                          LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
-               return;
-
-       if (vhost->ssl_ctx)
-               SSL_CTX_free(vhost->ssl_ctx);
-       if (!vhost->user_supplied_ssl_ctx && vhost->ssl_client_ctx)
-               SSL_CTX_free(vhost->ssl_client_ctx);
-#if !defined(LWS_WITH_ESP32)
-
-// after 1.1.0 no need
-#if (OPENSSL_VERSION_NUMBER <  0x10100000)
-// <= 1.0.1f = old api, 1.0.1g+ = new api
-#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL)
-       ERR_remove_state(0);
-#else
-#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \
-    !defined(LIBRESSL_VERSION_NUMBER) && \
-    !defined(OPENSSL_IS_BORINGSSL)
-       ERR_remove_thread_state();
-#else
-       ERR_remove_thread_state(NULL);
-#endif
-#endif
-       // after 1.1.0 no need
-#if  (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000)
-       SSL_COMP_free_compression_methods();
-#endif
-       ERR_free_strings();
-       EVP_cleanup();
-       CRYPTO_cleanup_all_ex_data();
-#endif
-#endif
-}
-
-LWS_VISIBLE void
-lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-
-       if (!wsi->pending_read_list_prev &&
-           !wsi->pending_read_list_next &&
-           pt->pending_read_list != wsi)
-               /* we are not on the list */
-               return;
-
-       /* point previous guy's next to our next */
-       if (!wsi->pending_read_list_prev)
-               pt->pending_read_list = wsi->pending_read_list_next;
-       else
-               wsi->pending_read_list_prev->pending_read_list_next =
-                       wsi->pending_read_list_next;
-
-       /* point next guy's previous to our previous */
-       if (wsi->pending_read_list_next)
-               wsi->pending_read_list_next->pending_read_list_prev =
-                       wsi->pending_read_list_prev;
-
-       wsi->pending_read_list_prev = NULL;
-       wsi->pending_read_list_next = NULL;
-}
-
-LWS_VISIBLE int
-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_ESP32)
-       int ssl_read_errno = 0;
-#endif
-
-       if (!wsi->ssl)
-               return lws_ssl_capable_read_no_ssl(wsi, buf, len);
-
-       lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1);
-
-       errno = 0;
-       n = SSL_read(wsi->ssl, buf, len);
-#if defined(LWS_WITH_ESP32)
-       if (!n && errno == ENOTCONN) {
-               lwsl_debug("%p: SSL_read ENOTCONN\n", wsi);
-               return LWS_SSL_CAPABLE_ERROR;
-       }
-#endif
-#if defined(LWS_WITH_STATS)
-       if (!wsi->seen_rx) {
-                lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_RX_DELAY,
-                               time_in_microseconds() - wsi->accept_start_us);
-                lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1);
-               wsi->seen_rx = 1;
-       }
-#endif
-
-
-       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_ESP32)
-                       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_err("%s failed: %s\n",__func__,
-                        ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-               lws_ssl_elaborate_error();
-
-               return LWS_SSL_CAPABLE_ERROR;
-       }
-
-       if (n < 0) {
-               n = lws_ssl_get_error(wsi, n);
-               // lwsl_notice("get_ssl_err result %d\n", n);
-               if (n ==  SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) {
-                       lwsl_debug("%s: WANT_READ\n", __func__);
-                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
-                       return LWS_SSL_CAPABLE_MORE_SERVICE;
-               }
-               if (n ==  SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->ssl)) {
-                       lwsl_debug("%s: WANT_WRITE\n", __func__);
-                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
-                       return LWS_SSL_CAPABLE_MORE_SERVICE;
-               }
-
-
-               lwsl_err("%s failed2: %s\n",__func__,
-                                ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-                       lws_ssl_elaborate_error();
-
-               return LWS_SSL_CAPABLE_ERROR;
-       }
-
-       lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n);
-
-       if (wsi->vhost)
-               wsi->vhost->conn_stats.rx += n;
-
-       lws_restart_ws_ping_pong_timer(wsi);
-
-       /*
-        * if it was our buffer that limited what we read,
-        * check if SSL has additional data pending inside SSL buffers.
-        *
-        * Because these won't signal at the network layer with POLLIN
-        * and if we don't realize, this data will sit there forever
-        */
-       if (n != len)
-               goto bail;
-       if (!wsi->ssl)
-               goto bail;
-
-       if (!SSL_pending(wsi->ssl))
-               goto bail;
-
-       if (wsi->pending_read_list_next)
-               return n;
-       if (wsi->pending_read_list_prev)
-               return n;
-       if (pt->pending_read_list == wsi)
-               return n;
-
-       /* add us to the linked list of guys with pending ssl */
-       if (pt->pending_read_list)
-               pt->pending_read_list->pending_read_list_prev = wsi;
-
-       wsi->pending_read_list_next = pt->pending_read_list;
-       wsi->pending_read_list_prev = NULL;
-       pt->pending_read_list = wsi;
-
-       return n;
-bail:
-       lws_ssl_remove_wsi_from_buffered_list(wsi);
-
-       return n;
-}
-
-LWS_VISIBLE int
-lws_ssl_pending(struct lws *wsi)
-{
-       if (!wsi->ssl)
-               return 0;
-
-       return SSL_pending(wsi->ssl);
-}
-
-LWS_VISIBLE int
-lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len)
-{
-       int n;
-#if !defined(LWS_WITH_ESP32)
-               int ssl_read_errno = 0;
-#endif
-
-       if (!wsi->ssl)
-               return lws_ssl_capable_write_no_ssl(wsi, buf, len);
-
-       n = SSL_write(wsi->ssl, buf, 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);
-               }
-               return LWS_SSL_CAPABLE_MORE_SERVICE;
-       }
-
- if (n == SSL_ERROR_ZERO_RETURN)
-  return LWS_SSL_CAPABLE_ERROR;
-
-#if !defined(LWS_WITH_ESP32)
- 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_err("%s failed: %s\n",__func__,
-   ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
-       lws_ssl_elaborate_error();
-
-       return LWS_SSL_CAPABLE_ERROR;
-}
-
-static int
-lws_gate_accepts(struct lws_context *context, int on)
-{
-       struct lws_vhost *v = context->vhost_list;
-
-       lwsl_info("gating accepts %d\n", on);
-       context->ssl_gate_accepts = !on;
-#if defined(LWS_WITH_STATS)
-       context->updated = 1;
-#endif
-
-       while (v) {
-               if (v->use_ssl &&  v->lserv_wsi) /* gate ability to accept incoming connections */
-                       if (lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on, (LWS_POLLIN) * on))
-                               lwsl_err("Unable to set accept POLLIN %d\n", on);
-
-               v = v->vhost_next;
-       }
-
-       return 0;
-}
-
-void
-lws_ssl_info_callback(const SSL *ssl, int where, int ret)
-{
-       struct lws *wsi;
-       struct lws_context *context;
-       struct lws_ssl_info si;
-
-       context = (struct lws_context *)SSL_CTX_get_ex_data(
-                                       SSL_get_SSL_CTX(ssl),
-                                       openssl_SSL_CTX_private_data_index);
-       if (!context)
-               return;
-       wsi = wsi_from_fd(context, SSL_get_fd(ssl));
-       if (!wsi)
-               return;
-
-       if (!(where & wsi->vhost->ssl_info_event_mask))
-               return;
-
-       si.where = where;
-       si.ret = ret;
-
-       if (user_callback_handle_rxflow(wsi->protocol->callback,
-                                                  wsi, LWS_CALLBACK_SSL_INFO,
-                                                  wsi->user_space, &si, 0))
-               lws_set_timeout(wsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, -1);
-}
-
-
-LWS_VISIBLE int
-lws_ssl_close(struct lws *wsi)
-{
-       lws_sockfd_type n;
-
-       if (!wsi->ssl)
-               return 0; /* not handled */
-
-#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
-       /* kill ssl callbacks, becausse we will remove the fd from the
-        * table linking it to the wsi
-        */
-       if (wsi->vhost->ssl_info_event_mask)
-               SSL_set_info_callback(wsi->ssl, NULL);
-#endif
-
-       n = SSL_get_fd(wsi->ssl);
-       SSL_shutdown(wsi->ssl);
-       compatible_close(n);
-       SSL_free(wsi->ssl);
-       wsi->ssl = NULL;
-
-       if (wsi->context->simultaneous_ssl_restriction &&
-           wsi->context->simultaneous_ssl-- ==
-                           wsi->context->simultaneous_ssl_restriction)
-               /* we made space and can do an accept */
-               lws_gate_accepts(wsi->context, 1);
-#if defined(LWS_WITH_STATS)
-       wsi->context->updated = 1;
-#endif
-
-       return 1; /* handled */
-}
-
-/* leave all wsi close processing to the caller */
-
-LWS_VISIBLE int
-lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd)
-{
-       struct lws_context *context = wsi->context;
-       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
-       int n, m;
-#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_ESP32)
-       BIO *bio;
-#endif
-        char buf[256];
-
-       if (!LWS_SSL_ENABLED(wsi->vhost))
-               return 0;
-
-       switch (wsi->mode) {
-       case LWSCM_SSL_INIT:
-       case LWSCM_SSL_INIT_RAW:
-               if (wsi->ssl)
-                       lwsl_err("%s: leaking ssl\n", __func__);
-               if (accept_fd == LWS_SOCK_INVALID)
-                       assert(0);
-               if (context->simultaneous_ssl_restriction &&
-                   context->simultaneous_ssl >= context->simultaneous_ssl_restriction) {
-                       lwsl_notice("unable to deal with SSL connection\n");
-                       return 1;
-               }
-               errno = 0;
-               wsi->ssl = SSL_new(wsi->vhost->ssl_ctx);
-               if (wsi->ssl == NULL) {
-                       lwsl_err("SSL_new failed: %d (errno %d)\n",
-                                lws_ssl_get_error(wsi, 0), errno);
-
-                       lws_ssl_elaborate_error();
-                       if (accept_fd != LWS_SOCK_INVALID)
-                               compatible_close(accept_fd);
-                       goto fail;
-               }
-#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
-               if (wsi->vhost->ssl_info_event_mask)
-                       SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback);
-#endif
-               if (context->simultaneous_ssl_restriction &&
-                   ++context->simultaneous_ssl == context->simultaneous_ssl_restriction)
-                       /* that was the last allowed SSL connection */
-                       lws_gate_accepts(context, 0);
-#if defined(LWS_WITH_STATS)
-       context->updated = 1;
-#endif
-
-#if !defined(LWS_WITH_ESP32)
-               SSL_set_ex_data(wsi->ssl,
-                       openssl_websocket_private_data_index, wsi);
-#endif
-               SSL_set_fd(wsi->ssl, accept_fd);
-
-#ifdef USE_WOLFSSL
-#ifdef USE_OLD_CYASSL
-               CyaSSL_set_using_nonblock(wsi->ssl, 1);
-#else
-               wolfSSL_set_using_nonblock(wsi->ssl, 1);
-#endif
-#else
-#if defined(LWS_WITH_ESP32)
-               lws_plat_set_socket_options(wsi->vhost, accept_fd);
-#else
-               SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
-               bio = SSL_get_rbio(wsi->ssl);
-               if (bio)
-                       BIO_set_nbio(bio, 1); /* nonblocking */
-               else
-                       lwsl_notice("NULL rbio\n");
-               bio = SSL_get_wbio(wsi->ssl);
-               if (bio)
-                       BIO_set_nbio(bio, 1); /* nonblocking */
-               else
-                       lwsl_notice("NULL rbio\n");
-#endif
-#endif
-
-               /*
-                * we are not accepted yet, but we need to enter ourselves
-                * as a live connection.  That way we can retry when more
-                * pieces come if we're not sorted yet
-                */
-
-               if (wsi->mode == LWSCM_SSL_INIT)
-                       wsi->mode = LWSCM_SSL_ACK_PENDING;
-               else
-                       wsi->mode = LWSCM_SSL_ACK_PENDING_RAW;
-
-               if (insert_wsi_socket_into_fds(context, wsi)) {
-                       lwsl_err("%s: failed to insert into fds\n", __func__);
-                       goto fail;
-               }
-
-               lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT,
-                               context->timeout_secs);
-
-               lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n");
-
-               /* fallthru */
-
-       case LWSCM_SSL_ACK_PENDING:
-       case LWSCM_SSL_ACK_PENDING_RAW:
-               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
-                       lwsl_err("%s: lws_change_pollfd failed\n", __func__);
-                       goto fail;
-               }
-
-               lws_latency_pre(context, wsi);
-
-               if (wsi->vhost->allow_non_ssl_on_ssl_port) {
-
-                       n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, context->pt_serv_buf_size,
-                                MSG_PEEK);
-
-               /*
-                * optionally allow non-SSL connect on SSL listening socket
-                * This is disabled by default, if enabled it goes around any
-                * SSL-level access control (eg, client-side certs) so leave
-                * it disabled unless you know it's not a problem for you
-                */
-
-                       if (n >= 1 && pt->serv_buf[0] >= ' ') {
-                               /*
-                               * TLS content-type for Handshake is 0x16, and
-                               * for ChangeCipherSpec Record, it's 0x14
-                               *
-                               * A non-ssl session will start with the HTTP
-                               * method in ASCII.  If we see it's not a legit
-                               * SSL handshake kill the SSL for this
-                               * connection and try to handle as a HTTP
-                               * connection upgrade directly.
-                               */
-                               wsi->use_ssl = 0;
-
-                               SSL_shutdown(wsi->ssl);
-                               SSL_free(wsi->ssl);
-                               wsi->ssl = NULL;
-                               if (lws_check_opt(context->options,
-                                   LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS))
-                                       wsi->redirect_to_https = 1;
-                               goto accepted;
-                       }
-                       if (!n) /*
-                                * connection is gone, or nothing to read
-                                * if it's gone, we will timeout on
-                                * PENDING_TIMEOUT_SSL_ACCEPT
-                                */
-                               break;
-                       if (n < 0 && (LWS_ERRNO == LWS_EAGAIN ||
-                                     LWS_ERRNO == LWS_EWOULDBLOCK)) {
-                               /*
-                                * well, we get no way to know ssl or not
-                                * so go around again waiting for something
-                                * to come and give us a hint, or timeout the
-                                * connection.
-                                */
-                               m = SSL_ERROR_WANT_READ;
-                               goto go_again;
-                       }
-               }
-
-               /* normal SSL connection processing path */
-
-#if defined(LWS_WITH_STATS)
-               if (!wsi->accept_start_us)
-                       wsi->accept_start_us = time_in_microseconds();
-#endif
-
-               n = SSL_accept(wsi->ssl);
-               lws_latency(context, wsi,
-                       "SSL_accept LWSCM_SSL_ACK_PENDING\n", n, n == 1);
-               lwsl_info("SSL_accept says %d\n", n);
-               if (n == 1)
-                       goto accepted;
-
-               m = lws_ssl_get_error(wsi, n);
-
-#if defined(LWS_WITH_ESP32)
-               if (m == 5 && errno == 11)
-                       m = SSL_ERROR_WANT_READ;
-#endif
-
-go_again:
-               if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) {
-                       if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
-                               lwsl_err("%s: WANT_READ change_pollfd failed\n", __func__);
-                               goto fail;
-                       }
-
-                       lwsl_info("SSL_ERROR_WANT_READ\n");
-                       break;
-               }
-               if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->ssl)) {
-                       lwsl_debug("%s: WANT_WRITE\n", __func__);
-
-                       if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) {
-                               lwsl_err("%s: WANT_WRITE change_pollfd failed\n", __func__);
-                               goto fail;
-                       }
-
-                       break;
-               }
-               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1);
-                lwsl_err("SSL_accept failed socket %u: %s\n", wsi->desc.sockfd,
-                         lws_ssl_get_error_string(m, n, buf, sizeof(buf)));
-               lws_ssl_elaborate_error();
-               goto fail;
-
-accepted:
-               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1);
-#if defined(LWS_WITH_STATS)
-               lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, time_in_microseconds() - wsi->accept_start_us);
-               wsi->accept_start_us = time_in_microseconds();
-#endif
-
-               /* OK, we are accepted... give him some time to negotiate */
-               lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
-                               context->timeout_secs);
-
-               if (wsi->mode == LWSCM_SSL_ACK_PENDING_RAW)
-                       wsi->mode = LWSCM_RAW;
-               else
-                       wsi->mode = LWSCM_HTTP_SERVING;
-
-               lws_http2_configure_if_upgraded(wsi);
-
-               lwsl_debug("accepted new SSL conn\n");
-               break;
-       }
-
-       return 0;
-
-fail:
-       return 1;
-}
-
-void
-lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost)
-{
-       if (vhost->ssl_ctx)
-               SSL_CTX_free(vhost->ssl_ctx);
-
-       if (!vhost->user_supplied_ssl_ctx && vhost->ssl_client_ctx)
-               SSL_CTX_free(vhost->ssl_client_ctx);
-}
-
-void
-lws_ssl_context_destroy(struct lws_context *context)
-{
-
-#if !defined(LWS_WITH_ESP32)
-
-// after 1.1.0 no need
-#if (OPENSSL_VERSION_NUMBER <  0x10100000)
-// <= 1.0.1f = old api, 1.0.1g+ = new api
-#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL)
-       ERR_remove_state(0);
-#else
-#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \
-    !defined(LIBRESSL_VERSION_NUMBER) && \
-    !defined(OPENSSL_IS_BORINGSSL)
-       ERR_remove_thread_state();
-#else
-       ERR_remove_thread_state(NULL);
-#endif
-#endif
-       // after 1.1.0 no need
-#if  (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000)
-       SSL_COMP_free_compression_methods();
-#endif
-       ERR_free_strings();
-       EVP_cleanup();
-       CRYPTO_cleanup_all_ex_data();
-#endif
-#endif
-}
diff --git a/lib/tls/lws-gencrypto-common.c b/lib/tls/lws-gencrypto-common.c
new file mode 100644 (file)
index 0000000..468b530
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ * libwebsockets - generic crypto hiding the backend - common parts
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+#include "core/private.h"
+
+/*
+ * These came from RFC7518 (JSON Web Algorithms) Section 3
+ *
+ * Cryptographic Algorithms for Digital Signatures and MACs
+ */
+
+static const struct lws_jose_jwe_alg lws_gencrypto_jws_alg_map[] = {
+
+       /*
+        * JWSs MAY also be created that do not provide integrity protection.
+        * Such a JWS is called an Unsecured JWS.  An Unsecured JWS uses the
+        * "alg" value "none" and is formatted identically to other JWSs, but
+        * MUST use the empty octet sequence as its JWS Signature value.
+        * Recipients MUST verify that the JWS Signature value is the empty
+        * octet sequence.
+        *
+        * Implementations that support Unsecured JWSs MUST NOT accept such
+        * objects as valid unless the application specifies that it is
+        * acceptable for a specific object to not be integrity protected.
+        * Implementations MUST NOT accept Unsecured JWSs by default.  In order
+        * to mitigate downgrade attacks, applications MUST NOT signal
+        * acceptance of Unsecured JWSs at a global level, and SHOULD signal
+        * acceptance on a per-object basis.  See Section 8.5 for security
+        * considerations associated with using this algorithm.
+        */
+       {       /* optional */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_NONE,
+               "none", NULL, 0, 0, 0
+       },
+
+       /*
+        * HMAC with SHA-2 Functions
+        *
+        * The HMAC SHA-256 MAC for a JWS is validated by computing an HMAC
+        * value per RFC 2104, using SHA-256 as the hash algorithm "H", using
+        * the received JWS Signing Input as the "text" value, and using the
+        * shared key.  This computed HMAC value is then compared to the result
+        * of base64url decoding the received encoded JWS Signature value.  The
+        * comparison of the computed HMAC value to the JWS Signature value MUST
+        * be done in a constant-time manner to thwart timing attacks.
+        *
+        * Alternatively, the computed HMAC value can be base64url encoded and
+        * compared to the received encoded JWS Signature value (also in a
+        * constant-time manner), as this comparison produces the same result as
+        * comparing the unencoded values.  In either case, if the values match,
+        * the HMAC has been validated.
+        */
+
+       {       /* required: HMAC using SHA-256 */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_SHA256,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_NONE,
+               "HS256", NULL, 0, 0, 0
+       },
+       {       /* optional: HMAC using SHA-384 */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_SHA384,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_NONE,
+               "HS384", NULL, 0, 0, 0
+       },
+       {       /* optional: HMAC using SHA-512 */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_SHA512,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_NONE,
+               "HS512", NULL, 0, 0, 0
+       },
+
+       /*
+        * Digital Signature with RSASSA-PKCS1-v1_5
+        *
+        * This section defines the use of the RSASSA-PKCS1-v1_5 digital
+        * signature algorithm as defined in Section 8.2 of RFC 3447 [RFC3447]
+        * (commonly known as PKCS #1), using SHA-2 [SHS] hash functions.
+        *
+        * A key of size 2048 bits or larger MUST be used with these algorithms.
+        *
+        * The RSASSA-PKCS1-v1_5 SHA-256 digital signature is generated as
+        * follows: generate a digital signature of the JWS Signing Input using
+        * RSASSA-PKCS1-v1_5-SIGN and the SHA-256 hash function with the desired
+        * private key.  This is the JWS Signature value.
+        *
+        * The RSASSA-PKCS1-v1_5 SHA-256 digital signature for a JWS is
+        * validated as follows: submit the JWS Signing Input, the JWS
+        * Signature, and the public key corresponding to the private key used
+        * by the signer to the RSASSA-PKCS1-v1_5-VERIFY algorithm using SHA-256
+        * as the hash function.
+        */
+
+       {       /* recommended: RSASSA-PKCS1-v1_5 using SHA-256 */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5,
+               LWS_JOSE_ENCTYPE_NONE,
+               "RS256", NULL, 2048, 4096, 0
+       },
+       {       /* optional: RSASSA-PKCS1-v1_5 using SHA-384 */
+               LWS_GENHASH_TYPE_SHA384,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5,
+               LWS_JOSE_ENCTYPE_NONE,
+               "RS384", NULL, 2048, 4096, 0
+       },
+       {       /* optional: RSASSA-PKCS1-v1_5 using SHA-512 */
+               LWS_GENHASH_TYPE_SHA512,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5,
+               LWS_JOSE_ENCTYPE_NONE,
+               "RS512", NULL, 2048, 4096, 0
+       },
+
+       /*
+        * Digital Signature with ECDSA
+        *
+        * The ECDSA P-256 SHA-256 digital signature is generated as follows:
+        *
+        * 1.  Generate a digital signature of the JWS Signing Input using ECDSA
+        *     P-256 SHA-256 with the desired private key.  The output will be
+        *     the pair (R, S), where R and S are 256-bit unsigned integers.
+        * 2.  Turn R and S into octet sequences in big-endian order, with each
+        *     array being be 32 octets long.  The octet sequence
+        *     representations MUST NOT be shortened to omit any leading zero
+        *     octets contained in the values.
+        *
+        * 3.  Concatenate the two octet sequences in the order R and then S.
+        *     (Note that many ECDSA implementations will directly produce this
+        *     concatenation as their output.)
+        *
+        * 4.  The resulting 64-octet sequence is the JWS Signature value.
+        *
+        * The ECDSA P-256 SHA-256 digital signature for a JWS is validated as
+        * follows:
+        *
+        * 1.  The JWS Signature value MUST be a 64-octet sequence.  If it is
+        *     not a 64-octet sequence, the validation has failed.
+        *
+        * 2.  Split the 64-octet sequence into two 32-octet sequences.  The
+        *     first octet sequence represents R and the second S.  The values R
+        *     and S are represented as octet sequences using the Integer-to-
+        *     OctetString Conversion defined in Section 2.3.7 of SEC1 [SEC1]
+        *     (in big-endian octet order).
+        * 3.  Submit the JWS Signing Input, R, S, and the public key (x, y) to
+        *     the ECDSA P-256 SHA-256 validator.
+        */
+
+       {       /* Recommended+: ECDSA using P-256 and SHA-256 */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDSA,
+               LWS_JOSE_ENCTYPE_NONE,
+               "ES256", "P-256", 256, 256, 0
+       },
+       {       /* optional: ECDSA using P-384 and SHA-384 */
+               LWS_GENHASH_TYPE_SHA384,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDSA,
+               LWS_JOSE_ENCTYPE_NONE,
+               "ES384", "P-384", 384, 384, 0
+       },
+       {       /* optional: ECDSA using P-521 and SHA-512 */
+               LWS_GENHASH_TYPE_SHA512,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDSA,
+               LWS_JOSE_ENCTYPE_NONE,
+               "ES512", "P-521", 521, 521, 0
+       },
+#if 0
+       Not yet supported
+
+       /*
+        * Digital Signature with RSASSA-PSS
+        *
+        * A key of size 2048 bits or larger MUST be used with this algorithm.
+        *
+        * The RSASSA-PSS SHA-256 digital signature is generated as follows:
+        * generate a digital signature of the JWS Signing Input using RSASSA-
+        * PSS-SIGN, the SHA-256 hash function, and the MGF1 mask generation
+        * function with SHA-256 with the desired private key.  This is the JWS
+        * Signature value.
+        *
+        * The RSASSA-PSS SHA-256 digital signature for a JWS is validated as
+        * follows: submit the JWS Signing Input, the JWS Signature, and the
+        * public key corresponding to the private key used by the signer to the
+        * RSASSA-PSS-VERIFY algorithm using SHA-256 as the hash function and
+        * using MGF1 as the mask generation function with SHA-256.
+        *
+        */
+       {       /* optional: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS,
+               LWS_JOSE_ENCTYPE_NONE,
+               "PS256", NULL, 2048, 4096, 0
+       },
+       {       /* optional: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 */
+               LWS_GENHASH_TYPE_SHA384,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS,
+               LWS_JOSE_ENCTYPE_NONE,
+               "PS384", NULL, 2048, 4096, 0
+       },
+       {       /* optional: RSASSA-PSS using SHA-512 and MGF1 with SHA-512*/
+               LWS_GENHASH_TYPE_SHA512,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_PSS,
+               LWS_JOSE_ENCTYPE_NONE,
+               "PS512", NULL, 2048, 4096, 0
+       },
+#endif
+};
+
+/*
+ * These came from RFC7518 (JSON Web Algorithms) Section 4
+ *
+ * Cryptographic Algorithms for Key Management
+ *
+ * JWE uses cryptographic algorithms to encrypt or determine the Content
+ * Encryption Key (CEK).
+ */
+
+static const struct lws_jose_jwe_alg lws_gencrypto_jwe_alg_map[] = {
+
+       /*
+        * This section defines the specifics of encrypting a JWE CEK with
+        * RSAES-PKCS1-v1_5 [RFC3447].  The "alg" (algorithm) Header Parameter
+        * value "RSA1_5" is used for this algorithm.
+        *
+        * A key of size 2048 bits or larger MUST be used with this algorithm.
+        */
+
+       {       /* recommended-: RSAES-PKCS1-v1_5 */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5,
+               LWS_JOSE_ENCTYPE_NONE,
+               "RSA1_5", NULL, 2048, 4096, 0
+       },
+       {       /* recommended+: RSAES OAEP using default parameters */
+               LWS_GENHASH_TYPE_SHA1,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP,
+               LWS_JOSE_ENCTYPE_NONE,
+               "RSA-OAEP", NULL, 2048, 4096, 0
+       },
+       {       /* recommended+: RSAES OAEP using SHA-256 and MGF1 SHA-256 */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP,
+               LWS_JOSE_ENCTYPE_NONE,
+               "RSA-OAEP-256", NULL, 2048, 4096, 0
+       },
+
+       /*
+        * Key Wrapping with AES Key Wrap
+        *
+        * This section defines the specifics of encrypting a JWE CEK with the
+        * Advanced Encryption Standard (AES) Key Wrap Algorithm [RFC3394] using
+        * the default initial value specified in Section 2.2.3.1 of that
+        * document.
+        *
+        *
+        */
+       {       /* recommended: AES Key Wrap with AES Key Wrap with defaults
+                               using 128-bit key  */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               LWS_JOSE_ENCTYPE_NONE,
+               "A128KW", NULL, 128, 128, 64
+       },
+
+       {       /* optional: AES Key Wrap with AES Key Wrap with defaults
+                               using 192-bit key */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               LWS_JOSE_ENCTYPE_NONE,
+               "A192KW", NULL, 192, 192, 64
+       },
+
+       {       /* recommended: AES Key Wrap with AES Key Wrap with defaults
+                               using 256-bit key */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               LWS_JOSE_ENCTYPE_NONE,
+               "A256KW", NULL, 256, 256, 64
+       },
+
+       /*
+        * This section defines the specifics of directly performing symmetric
+        * key encryption without performing a key wrapping step.  In this case,
+        * the shared symmetric key is used directly as the Content Encryption
+        * Key (CEK) value for the "enc" algorithm.  An empty octet sequence is
+        * used as the JWE Encrypted Key value.  The "alg" (algorithm) Header
+        * Parameter value "dir" is used in this case.
+        */
+       {       /* recommended */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_NONE,
+               "dir", NULL, 0, 0, 0
+       },
+
+       /*
+        * Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static
+        * (ECDH-ES)
+        *
+        * This section defines the specifics of key agreement with Elliptic
+        * Curve Diffie-Hellman Ephemeral Static [RFC6090], in combination with
+        * the Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A].  The
+        * key agreement result can be used in one of two ways:
+        *
+        * 1.  directly as the Content Encryption Key (CEK) for the "enc"
+        *     algorithm, in the Direct Key Agreement mode, or
+        *
+        * 2.  as a symmetric key used to wrap the CEK with the "A128KW",
+        *     "A192KW", or "A256KW" algorithms, in the Key Agreement with Key
+        *     Wrapping mode.
+        *
+        * A new ephemeral public key value MUST be generated for each key
+        * agreement operation.
+        *
+        * In Direct Key Agreement mode, the output of the Concat KDF MUST be a
+        * key of the same length as that used by the "enc" algorithm.  In this
+        * case, the empty octet sequence is used as the JWE Encrypted Key
+        * value.  The "alg" (algorithm) Header Parameter value "ECDH-ES" is
+        * used in the Direct Key Agreement mode.
+        *
+        * In Key Agreement with Key Wrapping mode, the output of the Concat KDF
+        * MUST be a key of the length needed for the specified key wrapping
+        * algorithm.  In this case, the JWE Encrypted Key is the CEK wrapped
+        * with the agreed-upon key.
+        */
+
+       {       /* recommended+: ECDH Ephemeral Static Key agreement Concat KDF */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDHES,
+               LWS_JOSE_ENCTYPE_NONE,
+               "ECDH-ES", NULL, 128, 128, 0
+       },
+       {       /* recommended: ECDH-ES + Concat KDF + wrapped by AES128KW */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDHES,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               "ECDH-ES+A128KW", NULL, 128, 128, 0
+       },
+       {       /* optional: ECDH-ES + Concat KDF + wrapped by AES192KW */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDHES,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               "ECDH-ES+A192KW", NULL, 192, 192, 0
+       },
+       {       /* recommended: ECDH-ES + Concat KDF + wrapped by AES256KW */
+               LWS_GENHASH_TYPE_SHA256,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_ECDHES,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               "ECDH-ES+A256KW", NULL, 256, 256, 0
+       },
+
+       /*
+        * Key Encryption with AES GCM
+        *
+        *  This section defines the specifics of encrypting a JWE Content
+        *  Encryption Key (CEK) with Advanced Encryption Standard (AES) in
+        *  Galois/Counter Mode (GCM) ([AES] and [NIST.800-38D]).
+        *
+        * Use of an Initialization Vector (IV) of size 96 bits is REQUIRED with
+        * this algorithm.  The IV is represented in base64url-encoded form as
+        * the "iv" (initialization vector) Header Parameter value.
+        *
+        * The Additional Authenticated Data value used is the empty octet
+        * string.
+        *
+        * The requested size of the Authentication Tag output MUST be 128 bits,
+        * regardless of the key size.
+        *
+        * The JWE Encrypted Key value is the ciphertext output.
+        *
+        * The Authentication Tag output is represented in base64url-encoded
+        * form as the "tag" (authentication tag) Header Parameter value.
+        *
+        *
+        * "iv" (Initialization Vector) Header Parameter
+        *
+        * The "iv" (initialization vector) Header Parameter value is the
+        * base64url-encoded representation of the 96-bit IV value used for the
+        * key encryption operation.  This Header Parameter MUST be present and
+        * MUST be understood and processed by implementations when these
+        * algorithms are used.
+        *
+        * "tag" (Authentication Tag) Header Parameter
+        *
+        * The "tag" (authentication tag) Header Parameter value is the
+        * base64url-encoded representation of the 128-bit Authentication Tag
+        * value resulting from the key encryption operation.  This Header
+        * Parameter MUST be present and MUST be understood and processed by
+        * implementations when these algorithms are used.
+        */
+       {       /* optional: Key wrapping with AES GCM using 128-bit key  */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               LWS_JOSE_ENCTYPE_NONE,
+               "A128GCMKW", NULL, 128, 128, 96
+       },
+
+       {       /* optional: Key wrapping with AES GCM using 192-bit key */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               LWS_JOSE_ENCTYPE_NONE,
+               "A192GCMKW", NULL, 192, 192, 96
+       },
+
+       {       /* optional: Key wrapping with AES GCM using 256-bit key */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_AES_ECB,
+               LWS_JOSE_ENCTYPE_NONE,
+               "A256GCMKW", NULL, 256, 256, 96
+       },
+
+       /* list terminator */
+       { 0, 0, 0, 0, NULL, NULL }
+};
+
+/*
+ * The "enc" (encryption algorithm) Header Parameter identifies the
+ * content encryption algorithm used to perform authenticated encryption
+ * on the plaintext to produce the ciphertext and the Authentication
+ * Tag.  This algorithm MUST be an AEAD algorithm with a specified key
+ * length.  The encrypted content is not usable if the "enc" value does
+ * not represent a supported algorithm.  "enc" values should either be
+ * registered in the IANA "JSON Web Signature and Encryption Algorithms"
+ * registry established by [JWA] or be a value that contains a
+ * Collision-Resistant Name.  The "enc" value is a case-sensitive ASCII
+ * string containing a StringOrURI value.  This Header Parameter MUST be
+ * present and MUST be understood and processed by implementations.
+ */
+
+static const struct lws_jose_jwe_alg lws_gencrypto_jwe_enc_map[] = {
+       /*
+        * AES_128_CBC_HMAC_SHA_256 / 512
+        *
+        * It uses the HMAC message authentication code [RFC2104] with the
+        * SHA-256 hash function [SHS] to provide message authentication, with
+        * the HMAC output truncated to 128 bits, corresponding to the
+        * HMAC-SHA-256-128 algorithm defined in [RFC4868].  For encryption, it
+        * uses AES in the CBC mode of operation as defined in Section 6.2 of
+        * [NIST.800-38A], with PKCS #7 padding and a 128-bit IV value.
+        *
+        * The AES_CBC_HMAC_SHA2 parameters specific to AES_128_CBC_HMAC_SHA_256
+        * are:
+        *
+        * The input key K is 32 octets long.
+        *       ENC_KEY_LEN is 16 octets.
+        *       MAC_KEY_LEN is 16 octets.
+        *       The SHA-256 hash algorithm is used for the HMAC.
+        *       The HMAC-SHA-256 output is truncated to T_LEN=16 octets, by
+        *       stripping off the final 16 octets.
+        */
+       {       /* required */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_SHA256,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_AES_CBC,
+               "A128CBC-HS256", NULL, 256, 256, 128
+       },
+       /*
+        * AES_192_CBC_HMAC_SHA_384 is based on AES_128_CBC_HMAC_SHA_256, but
+        * with the following differences:
+        *
+        * The input key K is 48 octets long instead of 32.
+        * ENC_KEY_LEN is 24 octets instead of 16.
+        * MAC_KEY_LEN is 24 octets instead of 16.
+        * SHA-384 is used for the HMAC instead of SHA-256.
+        * The HMAC SHA-384 value is truncated to T_LEN=24 octets instead of 16.
+        */
+       {       /* required */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_SHA384,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_AES_CBC,
+               "A192CBC-HS384", NULL, 384, 384, 192
+       },
+       /*
+        * AES_256_CBC_HMAC_SHA_512 is based on AES_128_CBC_HMAC_SHA_256, but
+        * with the following differences:
+        *
+        * The input key K is 64 octets long instead of 32.
+        * ENC_KEY_LEN is 32 octets instead of 16.
+        * MAC_KEY_LEN is 32 octets instead of 16.
+        * SHA-512 is used for the HMAC instead of SHA-256.
+        * The HMAC SHA-512 value is truncated to T_LEN=32 octets instead of 16.
+        */
+       {       /* required */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_SHA512,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_AES_CBC,
+               "A256CBC-HS512", NULL, 512, 512, 256
+       },
+
+       /*
+        * The CEK is used as the encryption key.
+        *
+        * Use of an IV of size 96 bits is REQUIRED with this algorithm.
+        *
+        * The requested size of the Authentication Tag output MUST be 128 bits,
+        * regardless of the key size.
+        */
+       {       /* recommended: AES GCM using 128-bit key  */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_AES_GCM,
+               "A128GCM", NULL, 128, 128, 96
+       },
+       {       /* optional: AES GCM using 192-bit key  */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_AES_GCM,
+               "A192GCM", NULL, 192, 192, 96
+       },
+       {       /* recommended: AES GCM using 256-bit key */
+               LWS_GENHASH_TYPE_UNKNOWN,
+               LWS_GENHMAC_TYPE_UNKNOWN,
+               LWS_JOSE_ENCTYPE_NONE,
+               LWS_JOSE_ENCTYPE_AES_GCM,
+               "A256GCM", NULL, 256, 256, 96
+       },
+       { 0, 0, 0, 0, NULL, NULL, 0, 0, 0 } /* sentinel */
+};
+
+LWS_VISIBLE int
+lws_gencrypto_jws_alg_to_definition(const char *alg,
+                                   const struct lws_jose_jwe_alg **jose)
+{
+       const struct lws_jose_jwe_alg *a = lws_gencrypto_jws_alg_map;
+
+       while (a->alg) {
+               if (!strcmp(alg, a->alg)) {
+                       *jose = a;
+
+                       return 0;
+               }
+               a++;
+       }
+
+       return 1;
+}
+
+LWS_VISIBLE int
+lws_gencrypto_jwe_alg_to_definition(const char *alg,
+                                   const struct lws_jose_jwe_alg **jose)
+{
+       const struct lws_jose_jwe_alg *a = lws_gencrypto_jwe_alg_map;
+
+       while (a->alg) {
+               if (!strcmp(alg, a->alg)) {
+                       *jose = a;
+
+                       return 0;
+               }
+               a++;
+       }
+
+       return 1;
+}
+
+LWS_VISIBLE int
+lws_gencrypto_jwe_enc_to_definition(const char *enc,
+                                   const struct lws_jose_jwe_alg **jose)
+{
+       const struct lws_jose_jwe_alg *e = lws_gencrypto_jwe_enc_map;
+
+       while (e->alg) {
+               if (!strcmp(enc, e->alg)) {
+                       *jose = e;
+
+                       return 0;
+               }
+               e++;
+       }
+
+       return 1;
+}
+
+size_t
+lws_genhash_size(enum lws_genhash_types type)
+{
+       switch(type) {
+       case LWS_GENHASH_TYPE_UNKNOWN:
+               return 0;
+       case LWS_GENHASH_TYPE_MD5:
+               return 16;
+       case LWS_GENHASH_TYPE_SHA1:
+               return 20;
+       case LWS_GENHASH_TYPE_SHA256:
+               return 32;
+       case LWS_GENHASH_TYPE_SHA384:
+               return 48;
+       case LWS_GENHASH_TYPE_SHA512:
+               return 64;
+       }
+
+       return 0;
+}
+
+size_t
+lws_genhmac_size(enum lws_genhmac_types type)
+{
+       switch(type) {
+       case LWS_GENHMAC_TYPE_UNKNOWN:
+               return 0;
+       case LWS_GENHMAC_TYPE_SHA256:
+               return 32;
+       case LWS_GENHMAC_TYPE_SHA384:
+               return 48;
+       case LWS_GENHMAC_TYPE_SHA512:
+               return 64;
+       }
+
+       return 0;
+}
+
+int
+lws_gencrypto_bits_to_bytes(int bits)
+{
+       if (bits & 7)
+               return (bits / 8) + 1;
+
+       return bits / 8;
+}
+
+int
+lws_base64_size(int bytes)
+{
+       return ((bytes * 4) / 3) + 6;
+}
+
+void
+lws_gencrypto_destroy_elements(struct lws_gencrypto_keyelem *el, int m)
+{
+       int n;
+
+       for (n = 0; n < m; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+}
diff --git a/lib/tls/lws-genec-common.c b/lib/tls/lws-genec-common.c
new file mode 100644 (file)
index 0000000..0df1c8b
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * libwebsockets - generic EC api hiding the backend - common parts
+ *
+ * Copyright (C) 2017 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genec provides an EC abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls crypto functions underneath.
+ */
+#include "core/private.h"
+
+const struct lws_ec_curves *
+lws_genec_curve(const struct lws_ec_curves *table, const char *name)
+{
+       const struct lws_ec_curves *c = lws_ec_curves;
+
+       if (table)
+               c = table;
+
+       while (c->name) {
+               if (!strcmp(name, c->name))
+                       return c;
+               c++;
+       }
+
+       return NULL;
+}
+
+//extern const struct lws_ec_curves *lws_ec_curves;
+
+int
+lws_genec_confirm_curve_allowed_by_tls_id(const char *allowed, int id,
+                                         struct lws_jwk *jwk)
+{
+       struct lws_tokenize ts;
+       lws_tokenize_elem e;
+       int n, len;
+
+       lws_tokenize_init(&ts, allowed, LWS_TOKENIZE_F_COMMA_SEP_LIST |
+                                      LWS_TOKENIZE_F_MINUS_NONTERM);
+       ts.len = strlen(allowed);
+       do {
+               e = lws_tokenize(&ts);
+               switch (e) {
+               case LWS_TOKZE_TOKEN:
+                       n = 0;
+                       while (lws_ec_curves[n].name) {
+                               if (id != lws_ec_curves[n].tls_lib_nid) {
+                                       n++;
+                                       continue;
+                               }
+                               lwsl_info("match curve %s\n",
+                                         lws_ec_curves[n].name);
+                               len = strlen(lws_ec_curves[n].name);
+                               jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].len = len;
+                               jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf =
+                                               lws_malloc(len + 1, "cert crv");
+                               if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf) {
+                                       lwsl_err("%s: OOM\n", __func__);
+                                       return 1;
+                               }
+                               memcpy(jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf,
+                                      lws_ec_curves[n].name, len + 1);
+                               return 0;
+                       }
+                       break;
+
+               case LWS_TOKZE_DELIMITER:
+                       break;
+
+               default: /* includes ENDED */
+                       lwsl_err("%s: malformed or curve name in list\n",
+                                __func__);
+
+                       return -1;
+               }
+       } while (e > 0);
+
+       lwsl_err("%s: unsupported curve group nid %d\n", __func__, n);
+
+       return -1;
+}
+
+LWS_VISIBLE void
+lws_genec_destroy_elements(struct lws_gencrypto_keyelem *el)
+{
+       int n;
+
+       for (n = 0; n < LWS_GENCRYPTO_EC_KEYEL_COUNT; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+}
+
+static const char *enames[] = { "crv", "x", "d", "y" };
+
+LWS_VISIBLE int
+lws_genec_dump(struct lws_gencrypto_keyelem *el)
+{
+       int n;
+
+       (void)enames;
+
+       lwsl_info("  genec %p: crv: '%s'\n", el,
+                 !!el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf ?
+                 (char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf: "no curve name");
+
+       for (n = LWS_GENCRYPTO_EC_KEYEL_X; n < LWS_GENCRYPTO_EC_KEYEL_COUNT;
+            n++) {
+               lwsl_info("  e: %s\n", enames[n]);
+               lwsl_hexdump_info(el[n].buf, el[n].len);
+       }
+
+       lwsl_info("\n");
+
+       return 0;
+}
diff --git a/lib/tls/mbedtls/lws-genaes.c b/lib/tls/mbedtls/lws-genaes.c
new file mode 100644 (file)
index 0000000..0e4da81
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * libwebsockets - generic AES api hiding the backend
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genaes provides an abstraction api for AES in lws that works the
+ *  same whether you are using openssl or mbedtls hash functions underneath.
+ */
+#include "core/private.h"
+#include "../../jose/private.h"
+
+static int operation_map[] = { MBEDTLS_AES_ENCRYPT, MBEDTLS_AES_DECRYPT };
+
+LWS_VISIBLE int
+lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op,
+                 enum enum_aes_modes mode, struct lws_gencrypto_keyelem *el,
+                 enum enum_aes_padding padding, void *engine)
+{
+       int n = 0;
+
+       ctx->mode = mode;
+       ctx->k = el;
+       ctx->op = operation_map[op];
+       ctx->underway = 0;
+
+       switch (ctx->mode) {
+       case LWS_GAESM_XTS:
+#if defined(MBEDTLS_CIPHER_MODE_XTS)
+               mbedtls_aes_xts_init(&ctx->u.ctx_xts);
+               break;
+#else
+               return -1;
+#endif
+       case LWS_GAESM_GCM:
+               mbedtls_gcm_init(&ctx->u.ctx_gcm);
+               n = mbedtls_gcm_setkey(&ctx->u.ctx_gcm, MBEDTLS_CIPHER_ID_AES,
+                                      ctx->k->buf, ctx->k->len * 8);
+               if (n) {
+                       lwsl_notice("%s: mbedtls_gcm_setkey: -0x%x\n",
+                                   __func__, -n);
+                       return n;
+               }
+               return n;
+       default:
+               mbedtls_aes_init(&ctx->u.ctx);
+               break;
+       }
+
+       switch (op) {
+       case LWS_GAESO_ENC:
+               if (ctx->mode == LWS_GAESM_XTS)
+#if defined(MBEDTLS_CIPHER_MODE_XTS)
+                       n = mbedtls_aes_xts_setkey_enc(&ctx->u.ctx_xts,
+                                                      ctx->k->buf,
+                                                      ctx->k->len * 8);
+#else
+                       return -1;
+#endif
+               else
+                       n = mbedtls_aes_setkey_enc(&ctx->u.ctx, ctx->k->buf,
+                                                  ctx->k->len * 8);
+               break;
+       case LWS_GAESO_DEC:
+               switch (ctx->mode) {
+               case LWS_GAESM_XTS:
+#if defined(MBEDTLS_CIPHER_MODE_XTS)
+                       n = mbedtls_aes_xts_setkey_dec(&ctx->u.ctx_xts,
+                                                      ctx->k->buf,
+                                                      ctx->k->len * 8);
+                       break;
+#else
+                       return -1;
+#endif
+
+               case LWS_GAESM_CFB128:
+               case LWS_GAESM_CFB8:
+               case LWS_GAESM_CTR:
+               case LWS_GAESM_OFB:
+                       n = mbedtls_aes_setkey_enc(&ctx->u.ctx, ctx->k->buf,
+                                                  ctx->k->len * 8);
+                       break;
+               default:
+                       n = mbedtls_aes_setkey_dec(&ctx->u.ctx, ctx->k->buf,
+                                                  ctx->k->len * 8);
+                       break;
+               }
+               break;
+       }
+
+       if (n)
+               lwsl_notice("%s: setting key: -0x%x\n", __func__, -n);
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genaes_destroy(struct lws_genaes_ctx *ctx, unsigned char *tag, size_t tlen)
+{
+       int n = 0;
+
+       if (ctx->mode == LWS_GAESM_GCM) {
+               n = mbedtls_gcm_finish(&ctx->u.ctx_gcm, tag, tlen);
+               if (n)
+                       lwsl_notice("%s: mbedtls_gcm_finish: -0x%x\n",
+                                   __func__, -n);
+               if (tag && ctx->op == MBEDTLS_AES_DECRYPT && !n) {
+                       if (lws_timingsafe_bcmp(ctx->tag, tag, ctx->taglen)) {
+                               lwsl_err("%s: lws_genaes_crypt tag "
+                                        "mismatch (bad first)\n",
+                                               __func__);
+                               lwsl_hexdump_notice(tag, tlen);
+                               lwsl_hexdump_notice(ctx->tag, ctx->taglen);
+                               n = -1;
+                       }
+               }
+               mbedtls_gcm_free(&ctx->u.ctx_gcm);
+               return n;
+       }
+       if (ctx->mode == LWS_GAESM_XTS)
+#if defined(MBEDTLS_CIPHER_MODE_XTS)
+               mbedtls_aes_xts_free(&ctx->u.ctx_xts);
+#else
+               return -1;
+#endif
+       else
+               mbedtls_aes_free(&ctx->u.ctx);
+
+       return 0;
+}
+
+static int
+lws_genaes_rfc3394_wrap(int wrap, int cek_bits, const uint8_t *kek,
+                       int kek_bits, const uint8_t *in, uint8_t *out)
+{
+       int n, m, ret = -1, c64 = cek_bits / 64;
+       mbedtls_aes_context ctx;
+       uint8_t a[8], b[16];
+
+       /*
+        * notice the KEK key used to perform the wrapping or unwrapping is
+        * always the size of the AES key used, eg, A128KW == 128 bits.  The
+        * key being wrapped or unwrapped may be larger and is set by the
+        * 'bits' parameter.
+        *
+        * If it's larger than the KEK key size bits, we iterate over it
+        */
+
+       mbedtls_aes_init(&ctx);
+
+       if (wrap) {
+               /*
+                * The inputs to the key wrapping process are the KEK and the
+                * plaintext to be wrapped.  The plaintext consists of n 64-bit
+                * blocks, containing the key data being wrapped.
+                *
+                * Inputs:      Plaintext, n 64-bit values {P1, P2, ..., Pn},
+                *              and Key, K (the KEK).
+                * Outputs:     Ciphertext, (n+1) 64-bit values
+                *              {C0, C1, ..., Cn}.
+                *
+                * The default initial value (IV) is defined to be the
+                * hexadecimal constant:
+                *
+                * A[0] = IV = A6A6A6A6A6A6A6A6
+                */
+               memset(out, 0xa6, 8);
+               memcpy(out + 8, in, 8 * c64);
+               n = mbedtls_aes_setkey_enc(&ctx, kek, kek_bits);
+       } else {
+               /*
+                * 2.2.2 Key Unwrap
+                *
+                * The inputs to the unwrap process are the KEK and (n+1)
+                * 64-bit blocks of ciphertext consisting of previously
+                * wrapped key.  It returns n blocks of plaintext consisting
+                * of the n 64-bit blocks of the decrypted key data.
+                *
+                * Inputs:  Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn},
+                * and Key, K (the KEK).
+                *
+                * Outputs: Plaintext, n 64-bit values {P1, P2, ..., Pn}.
+                */
+               memcpy(a, in, 8);
+               memcpy(out, in + 8, 8 * c64);
+               n = mbedtls_aes_setkey_dec(&ctx, kek, kek_bits);
+       }
+
+       if (n < 0) {
+               lwsl_err("%s: setkey failed\n", __func__);
+               goto bail;
+       }
+
+       if (wrap) {
+               for (n = 0; n <= 5; n++) {
+                       uint8_t *r = out + 8;
+                       for (m = 1; m <= c64; m++) {
+                               memcpy(b, out, 8);
+                               memcpy(b + 8, r, 8);
+                               if (mbedtls_internal_aes_encrypt(&ctx, b, b))
+                                       goto bail;
+
+                               memcpy(out, b, 8);
+                               out[7] ^= c64 * n + m;
+                               memcpy(r, b + 8, 8);
+                               r += 8;
+                       }
+               }
+               ret = 0;
+       } else {
+               /*
+                *
+                */
+               for (n = 5; n >= 0; n--) {
+                       uint8_t *r = out + (c64 - 1) * 8;
+                       for (m = c64; m >= 1; m--) {
+                               memcpy(b, a, 8);
+                               b[7] ^= c64 * n + m;
+                               memcpy(b + 8, r, 8);
+                               if (mbedtls_internal_aes_decrypt(&ctx, b, b))
+                                       goto bail;
+
+                               memcpy(a, b, 8);
+                               memcpy(r, b + 8, 8);
+                               r -= 8;
+                       }
+               }
+
+               ret = 0;
+               for (n = 0; n < 8; n++)
+                       if (a[n] != 0xa6)
+                               ret = -1;
+       }
+
+bail:
+       if (ret)
+               lwsl_notice("%s: failed\n", __func__);
+       mbedtls_aes_free(&ctx);
+
+       return ret;
+}
+
+LWS_VISIBLE int
+lws_genaes_crypt(struct lws_genaes_ctx *ctx, const uint8_t *in, size_t len,
+                uint8_t *out, uint8_t *iv_or_nonce_ctr_or_data_unit_16,
+                uint8_t *stream_block_16, size_t *nc_or_iv_off, int taglen)
+{
+       uint8_t iv[LWS_JWE_AES_IV_BYTES], sb[16];
+       int n = 0;
+
+       switch (ctx->mode) {
+       case LWS_GAESM_KW:
+               /* a key of length ctx->k->len is wrapped by a 128-bit KEK */
+               n = lws_genaes_rfc3394_wrap(ctx->op == MBEDTLS_AES_ENCRYPT,
+                               ctx->op == MBEDTLS_AES_ENCRYPT ? len * 8 :
+                                               (len - 8) * 8, ctx->k->buf,
+                                               ctx->k->len * 8,
+                               in, out);
+               break;
+       case LWS_GAESM_CBC:
+               memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
+               n = mbedtls_aes_crypt_cbc(&ctx->u.ctx, ctx->op, len, iv,
+                                         in, out);
+               break;
+
+       case LWS_GAESM_CFB128:
+               memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
+               n = mbedtls_aes_crypt_cfb128(&ctx->u.ctx, ctx->op, len,
+                                            nc_or_iv_off, iv, in, out);
+               break;
+
+       case LWS_GAESM_CFB8:
+               memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
+               n = mbedtls_aes_crypt_cfb8(&ctx->u.ctx, ctx->op, len, iv,
+                                          in, out);
+               break;
+
+       case LWS_GAESM_CTR:
+               memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
+               memcpy(sb, stream_block_16, 16);
+               n = mbedtls_aes_crypt_ctr(&ctx->u.ctx, len, nc_or_iv_off,
+                                         iv, sb, in, out);
+               memcpy(iv_or_nonce_ctr_or_data_unit_16, iv, 16);
+               memcpy(stream_block_16, sb, 16);
+               break;
+
+       case LWS_GAESM_ECB:
+               n = mbedtls_aes_crypt_ecb(&ctx->u.ctx, ctx->op, in, out);
+               break;
+
+       case LWS_GAESM_OFB:
+#if defined(MBEDTLS_CIPHER_MODE_OFB)
+               memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
+               n = mbedtls_aes_crypt_ofb(&ctx->u.ctx, len, nc_or_iv_off, iv,
+                                         in, out);
+               break;
+#else
+               return -1;
+#endif
+
+       case LWS_GAESM_XTS:
+#if defined(MBEDTLS_CIPHER_MODE_XTS)
+               memcpy(iv, iv_or_nonce_ctr_or_data_unit_16, 16);
+               n = mbedtls_aes_crypt_xts(&ctx->u.ctx_xts, ctx->op, len, iv,
+                                         in, out);
+               break;
+#else
+               return -1;
+#endif
+       case LWS_GAESM_GCM:
+               if (!ctx->underway) {
+                       ctx->underway = 1;
+
+                       memcpy(ctx->tag, stream_block_16, taglen);
+                       ctx->taglen = taglen;
+
+                       /*
+                        * iv:                   iv_or_nonce_ctr_or_data_unit_16
+                        * iv_len:               *nc_or_iv_off
+                        * stream_block_16:      pointer to tag
+                        * additional data:      in
+                        * additional data len:  len
+                        */
+
+                       n = mbedtls_gcm_starts(&ctx->u.ctx_gcm, ctx->op,
+                                              iv_or_nonce_ctr_or_data_unit_16,
+                                              *nc_or_iv_off, in, len);
+                       if (n) {
+                               lwsl_notice("%s: mbedtls_gcm_starts: -0x%x\n",
+                                           __func__, -n);
+
+                               return -1;
+                       }
+                       break;
+               }
+
+               n = mbedtls_gcm_update(&ctx->u.ctx_gcm, len, in, out);
+               if (n) {
+                       lwsl_notice("%s: mbedtls_gcm_update: -0x%x\n",
+                                   __func__, -n);
+
+                       return -1;
+               }
+               break;
+       }
+
+       if (n) {
+               lwsl_notice("%s: failed: -0x%x, len %d\n", __func__, -n, (int)len);
+
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/lib/tls/mbedtls/lws-gencrypto.c b/lib/tls/mbedtls/lws-gencrypto.c
new file mode 100644 (file)
index 0000000..dd91ad5
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * libwebsockets - generic crypto api hiding the backend
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws-gencrypto openssl-specific common code
+ */
+
+#include "core/private.h"
+#include "tls/mbedtls/private.h"
+
+mbedtls_md_type_t
+lws_gencrypto_mbedtls_hash_to_MD_TYPE(enum lws_genhash_types hash_type)
+{
+       mbedtls_md_type_t h = -1;
+
+       switch (hash_type) {
+       case LWS_GENHASH_TYPE_MD5:
+               h = MBEDTLS_MD_MD5;
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               h = MBEDTLS_MD_SHA1;
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               h = MBEDTLS_MD_SHA256;
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               h = MBEDTLS_MD_SHA384;
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               h = MBEDTLS_MD_SHA512;
+               break;
+       default:
+               break;
+       }
+
+       return h;
+}
+
+int
+lws_gencrypto_mbedtls_rngf(void *context, unsigned char *buf, size_t len)
+{
+       if ((size_t)lws_get_random(context, buf, len) == len) {
+               // lwsl_hexdump_err(buf, len);
+               return 0;
+       }
+       lwsl_err("%s: rng failed\n", __func__);
+       return -1;
+}
diff --git a/lib/tls/mbedtls/lws-genec.c b/lib/tls/mbedtls/lws-genec.c
new file mode 100644 (file)
index 0000000..c7ff5e2
--- /dev/null
@@ -0,0 +1,517 @@
+/*
+ * libwebsockets - generic EC api hiding the backend - mbedtls implementation
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genec provides an EC abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls crypto functions underneath.
+ */
+#include "core/private.h"
+#include "tls/mbedtls/private.h"
+
+const struct lws_ec_curves lws_ec_curves[] = {
+       /*
+        * These are the curves we are willing to use by default...
+        *
+        * The 3 recommended+ (P-256) and optional curves in RFC7518 7.6
+        *
+        * Specific keys lengths from RFC8422 p20
+        */
+       { "P-256", MBEDTLS_ECP_DP_SECP256R1, 32 },
+       { "P-384", MBEDTLS_ECP_DP_SECP384R1, 48 },
+       { "P-521", MBEDTLS_ECP_DP_SECP521R1, 66 },
+
+       { NULL, 0, 0 }
+};
+
+static int
+lws_genec_keypair_import(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side,
+                        struct lws_gencrypto_keyelem *el)
+{
+       const struct lws_ec_curves *curve;
+       mbedtls_ecp_keypair kp;
+       int ret = -1;
+
+       if (el[LWS_GENCRYPTO_EC_KEYEL_CRV].len < 4) {
+               lwsl_notice("%s: crv '%s' (%d)\n", __func__,
+                           el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf ?
+                                   (char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf :
+                                           "null",
+                           el[LWS_GENCRYPTO_EC_KEYEL_CRV].len);
+               return -21;
+       }
+
+       curve = lws_genec_curve(ctx->curve_table,
+                               (char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf);
+       if (!curve)
+               return -22;
+
+       /*
+        * d (the private part) may be missing, otherwise it and everything
+        * else must match the expected bignum size
+        */
+
+       if ((el[LWS_GENCRYPTO_EC_KEYEL_D].len &&
+            el[LWS_GENCRYPTO_EC_KEYEL_D].len != curve->key_bytes) ||
+           el[LWS_GENCRYPTO_EC_KEYEL_X].len != curve->key_bytes ||
+           el[LWS_GENCRYPTO_EC_KEYEL_Y].len != curve->key_bytes)
+               return -23;
+
+       mbedtls_ecp_keypair_init(&kp);
+       if (mbedtls_ecp_group_load(&kp.grp, curve->tls_lib_nid))
+               goto bail1;
+
+       ctx->has_private = !!el[LWS_GENCRYPTO_EC_KEYEL_D].len;
+
+       /* d (the private key) is directly an mpi */
+
+       if (ctx->has_private &&
+           mbedtls_mpi_read_binary(&kp.d, el[LWS_GENCRYPTO_EC_KEYEL_D].buf,
+                                   el[LWS_GENCRYPTO_EC_KEYEL_D].len))
+               goto bail1;
+
+       mbedtls_ecp_set_zero(&kp.Q);
+
+       if (mbedtls_mpi_read_binary(&kp.Q.X, el[LWS_GENCRYPTO_EC_KEYEL_X].buf,
+                                   el[LWS_GENCRYPTO_EC_KEYEL_X].len))
+               goto bail1;
+
+       if (mbedtls_mpi_read_binary(&kp.Q.Y, el[LWS_GENCRYPTO_EC_KEYEL_Y].buf,
+                                   el[LWS_GENCRYPTO_EC_KEYEL_Y].len))
+               goto bail1;
+
+       mbedtls_mpi_lset(&kp.Q.Z, 1);
+
+       switch (ctx->genec_alg) {
+       case LEGENEC_ECDH:
+               if (mbedtls_ecdh_get_params(ctx->u.ctx_ecdh, &kp, side))
+                       goto bail1;
+               /* verify the key is consistent with the claimed curve */
+               if (ctx->has_private &&
+                   mbedtls_ecp_check_privkey(&ctx->u.ctx_ecdh->grp,
+                                             &ctx->u.ctx_ecdh->d))
+                       goto bail1;
+               if (mbedtls_ecp_check_pubkey(&ctx->u.ctx_ecdh->grp,
+                                            &ctx->u.ctx_ecdh->Q))
+                       goto bail1;
+               break;
+       case LEGENEC_ECDSA:
+               if (mbedtls_ecdsa_from_keypair(ctx->u.ctx_ecdsa, &kp))
+                       goto bail1;
+               /* verify the key is consistent with the claimed curve */
+               if (ctx->has_private &&
+                   mbedtls_ecp_check_privkey(&ctx->u.ctx_ecdsa->grp,
+                                             &ctx->u.ctx_ecdsa->d))
+                       goto bail1;
+               if (mbedtls_ecp_check_pubkey(&ctx->u.ctx_ecdsa->grp,
+                                            &ctx->u.ctx_ecdsa->Q))
+                       goto bail1;
+               break;
+       default:
+               goto bail1;
+       }
+
+       ret = 0;
+
+bail1:
+       mbedtls_ecp_keypair_free(&kp);
+
+       return ret;
+}
+
+LWS_VISIBLE int
+lws_genecdh_create(struct lws_genec_ctx *ctx, struct lws_context *context,
+                  const struct lws_ec_curves *curve_table)
+{
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->context = context;
+       ctx->curve_table = curve_table;
+       ctx->genec_alg = LEGENEC_ECDH;
+
+       ctx->u.ctx_ecdh = lws_zalloc(sizeof(*ctx->u.ctx_ecdh), "genecdh");
+       if (!ctx->u.ctx_ecdh)
+               return 1;
+
+       mbedtls_ecdh_init(ctx->u.ctx_ecdh);
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_genecdsa_create(struct lws_genec_ctx *ctx, struct lws_context *context,
+                   const struct lws_ec_curves *curve_table)
+{
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->context = context;
+       ctx->curve_table = curve_table;
+       ctx->genec_alg = LEGENEC_ECDSA;
+
+       ctx->u.ctx_ecdsa = lws_zalloc(sizeof(*ctx->u.ctx_ecdsa), "genecdsa");
+       if (!ctx->u.ctx_ecdsa)
+               return 1;
+
+       mbedtls_ecdsa_init(ctx->u.ctx_ecdsa);
+
+       return 0;
+}
+
+
+LWS_VISIBLE int
+lws_genecdh_set_key(struct lws_genec_ctx *ctx, struct lws_gencrypto_keyelem *el,
+                   enum enum_lws_dh_side side)
+{
+       if (ctx->genec_alg != LEGENEC_ECDH)
+               return -1;
+
+       return lws_genec_keypair_import(ctx, side, el);
+}
+
+LWS_VISIBLE int
+lws_genecdsa_set_key(struct lws_genec_ctx *ctx,
+                    struct lws_gencrypto_keyelem *el)
+{
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       return lws_genec_keypair_import(ctx, 0, el);
+}
+
+LWS_VISIBLE void
+lws_genec_destroy(struct lws_genec_ctx *ctx)
+{
+       switch (ctx->genec_alg) {
+       case LEGENEC_ECDH:
+               if (ctx->u.ctx_ecdh) {
+                       mbedtls_ecdh_free(ctx->u.ctx_ecdh);
+                       lws_free(ctx->u.ctx_ecdh);
+                       ctx->u.ctx_ecdh = NULL;
+               }
+               break;
+       case LEGENEC_ECDSA:
+               if (ctx->u.ctx_ecdsa) {
+                       mbedtls_ecdsa_free(ctx->u.ctx_ecdsa);
+                       lws_free(ctx->u.ctx_ecdsa);
+                       ctx->u.ctx_ecdsa = NULL;
+               }
+               break;
+       default:
+               break;
+       }
+}
+
+LWS_VISIBLE int
+lws_genecdh_new_keypair(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side,
+                       const char *curve_name,
+                       struct lws_gencrypto_keyelem *el)
+{
+       const struct lws_ec_curves *curve;
+       mbedtls_ecdsa_context ecdsa;
+       mbedtls_ecp_keypair *kp;
+       mbedtls_mpi *mpi[3];
+       int n;
+
+       if (ctx->genec_alg != LEGENEC_ECDH)
+               return -1;
+
+       curve = lws_genec_curve(ctx->curve_table, curve_name);
+       if (!curve) {
+               lwsl_err("%s: curve '%s' not supported\n",
+                        __func__, curve_name);
+
+               return -22;
+       }
+
+       mbedtls_ecdsa_init(&ecdsa);
+       n = mbedtls_ecdsa_genkey(&ecdsa, curve->tls_lib_nid,
+                                lws_gencrypto_mbedtls_rngf,
+                                ctx->context);
+       if (n) {
+               lwsl_err("mbedtls_ecdsa_genkey failed 0x%x\n", -n);
+               goto bail1;
+       }
+
+       kp = (mbedtls_ecp_keypair *)&ecdsa;
+
+       n = mbedtls_ecdh_get_params(ctx->u.ctx_ecdh, kp, side);
+       if (n) {
+               lwsl_err("mbedtls_ecdh_get_params failed 0x%x\n", -n);
+               goto bail1;
+       }
+
+       /*
+        * we need to capture the individual element BIGNUMs into
+        * lws_gencrypto_keyelem, so they can be serialized, used in jwk etc
+        */
+
+       mpi[0] = &kp->Q.X;
+       mpi[1] = &kp->d;
+       mpi[2] = &kp->Q.Y;
+
+       el[LWS_GENCRYPTO_EC_KEYEL_CRV].len = strlen(curve_name) + 1;
+       el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf =
+                       lws_malloc(el[LWS_GENCRYPTO_EC_KEYEL_CRV].len, "ec");
+       if (!el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
+               goto bail1;
+       strcpy((char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf, curve_name);
+
+       for (n = LWS_GENCRYPTO_EC_KEYEL_X; n < LWS_GENCRYPTO_EC_KEYEL_COUNT;
+            n++) {
+               el[n].len = curve->key_bytes;
+               el[n].buf = lws_malloc(curve->key_bytes, "ec");
+               if (!el[n].buf)
+                       goto bail2;
+
+               if (mbedtls_mpi_write_binary(mpi[n - 1], el[n].buf,
+                                            curve->key_bytes))
+                       goto bail2;
+       }
+
+       mbedtls_ecdsa_free(&ecdsa);
+
+       return 0;
+
+bail2:
+       for (n = 0; n < LWS_GENCRYPTO_EC_KEYEL_COUNT; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+bail1:
+       mbedtls_ecdsa_free(&ecdsa);
+
+       lws_free_set_NULL(ctx->u.ctx_ecdh);
+
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_genecdsa_new_keypair(struct lws_genec_ctx *ctx, const char *curve_name,
+                        struct lws_gencrypto_keyelem *el)
+{
+       const struct lws_ec_curves *curve;
+       mbedtls_ecp_keypair *kp;
+       mbedtls_mpi *mpi[3];
+       int n;
+
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       curve = lws_genec_curve(ctx->curve_table, curve_name);
+       if (!curve) {
+               lwsl_err("%s: curve '%s' not supported\n",
+                        __func__, curve_name);
+
+               return -22;
+       }
+
+       //mbedtls_ecdsa_init(ctx->u.ctx_ecdsa);
+       n = mbedtls_ecdsa_genkey(ctx->u.ctx_ecdsa, curve->tls_lib_nid,
+                                lws_gencrypto_mbedtls_rngf, ctx->context);
+       if (n) {
+               lwsl_err("mbedtls_ecdsa_genkey failed 0x%x\n", -n);
+               goto bail1;
+       }
+
+       /*
+        * we need to capture the individual element BIGNUMs into
+        * lws_gencrypto_keyelems, so they can be serialized, used in jwk etc
+        */
+
+       kp = (mbedtls_ecp_keypair *)ctx->u.ctx_ecdsa;
+
+       mpi[0] = &kp->Q.X;
+       mpi[1] = &kp->d;
+       mpi[2] = &kp->Q.Y;
+
+       el[LWS_GENCRYPTO_EC_KEYEL_CRV].len = strlen(curve_name) + 1;
+       el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf =
+                       lws_malloc(el[LWS_GENCRYPTO_EC_KEYEL_CRV].len, "ec");
+       if (!el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf)
+               goto bail1;
+       strcpy((char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf, curve_name);
+
+       for (n = LWS_GENCRYPTO_EC_KEYEL_X; n < LWS_GENCRYPTO_EC_KEYEL_COUNT;
+            n++) {
+               el[n].len = curve->key_bytes;
+               el[n].buf = lws_malloc(curve->key_bytes, "ec");
+               if (!el[n].buf)
+                       goto bail2;
+
+
+               if (mbedtls_mpi_write_binary(mpi[n - 1], el[n].buf, el[n].len)) {
+                       lwsl_err("%s: mbedtls_mpi_write_binary failed\n", __func__);
+                       goto bail2;
+               }
+       }
+
+       return 0;
+
+bail2:
+       for (n = 0; n < LWS_GENCRYPTO_EC_KEYEL_COUNT; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+bail1:
+
+       lws_free_set_NULL(ctx->u.ctx_ecdsa);
+
+       return -1;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in,
+                          enum lws_genhash_types hash_type, int keybits,
+                          uint8_t *sig, size_t sig_len)
+{
+       int n, keybytes = lws_gencrypto_bits_to_bytes(keybits);
+       size_t hlen = lws_genhash_size(hash_type);
+       mbedtls_mpi mpi_r, mpi_s;
+       size_t slen = sig_len;
+
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       /*
+        * The ECDSA P-256 SHA-256 digital signature is generated as follows:
+        *
+        * 1.  Generate a digital signature of the JWS Signing Input using ECDSA
+        *     P-256 SHA-256 with the desired private key.  The output will be
+        *     the pair (R, S), where R and S are 256-bit unsigned integers.
+        *
+        * 2.  Turn R and S into octet sequences in big-endian order, with each
+        *     array being be 32 octets long.  The octet sequence
+        *     representations MUST NOT be shortened to omit any leading zero
+        *     octets contained in the values.
+        *
+        * 3.  Concatenate the two octet sequences in the order R and then S.
+        *     (Note that many ECDSA implementations will directly produce this
+        *     concatenation as their output.)
+        *
+        * 4.  The resulting 64-octet sequence is the JWS Signature value.
+        */
+
+       mbedtls_mpi_init(&mpi_r);
+       mbedtls_mpi_init(&mpi_s);
+
+       n = mbedtls_ecdsa_sign(&ctx->u.ctx_ecdsa->grp, &mpi_r, &mpi_s,
+                              &ctx->u.ctx_ecdsa->d, in, hlen,
+                       lws_gencrypto_mbedtls_rngf, ctx->context);
+       if (n) {
+               lwsl_err("%s: mbedtls_ecdsa_sign failed: -0x%x\n",
+                        __func__, -n);
+
+               goto bail2;
+       }
+
+       if (mbedtls_mpi_write_binary(&mpi_r, sig, keybytes))
+               goto bail2;
+       mbedtls_mpi_free(&mpi_r);
+       if (mbedtls_mpi_write_binary(&mpi_s, sig + keybytes, keybytes))
+               goto bail1;
+       mbedtls_mpi_free(&mpi_s);
+
+       return (int)slen;
+
+bail2:
+       mbedtls_mpi_free(&mpi_r);
+bail1:
+       mbedtls_mpi_free(&mpi_s);
+
+       return -3;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sig_verify_jws(struct lws_genec_ctx *ctx, const uint8_t *in,
+                                enum lws_genhash_types hash_type, int keybits,
+                                const uint8_t *sig, size_t sig_len)
+{
+       int n, keybytes = lws_gencrypto_bits_to_bytes(keybits);
+       size_t hlen = lws_genhash_size(hash_type);
+       mbedtls_mpi mpi_r, mpi_s;
+
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       if ((int)sig_len != keybytes * 2)
+               return -1;
+
+       /*
+        * 1.  The JWS Signature value MUST be a 64-octet sequence.  If it is
+        *     not a 64-octet sequence, the validation has failed.
+        *
+        * 2.  Split the 64-octet sequence into two 32-octet sequences.  The
+        *     first octet sequence represents R and the second S.  The values R
+        *     and S are represented as octet sequences using the Integer-to-
+        *     OctetString Conversion defined in Section 2.3.7 of SEC1 [SEC1]
+        *     (in big-endian octet order).
+        *
+        * 3.  Submit the JWS Signing Input, R, S, and the public key (x, y) to
+        *     the ECDSA P-256 SHA-256 validator.
+        */
+
+       mbedtls_mpi_init(&mpi_r);
+       mbedtls_mpi_init(&mpi_s);
+
+       if (mbedtls_mpi_read_binary(&mpi_r, sig, keybytes))
+               return -1;
+       if (mbedtls_mpi_read_binary(&mpi_s, sig + keybytes, keybytes))
+               goto bail1;
+
+       n = mbedtls_ecdsa_verify(&ctx->u.ctx_ecdsa->grp, in, hlen,
+                                &ctx->u.ctx_ecdsa->Q, &mpi_r, &mpi_s);
+
+       mbedtls_mpi_free(&mpi_s);
+       mbedtls_mpi_free(&mpi_r);
+
+       if (n) {
+               lwsl_err("%s: mbedtls_ecdsa_verify failed: -0x%x\n",
+                        __func__, -n);
+
+               goto bail;
+       }
+
+       return 0;
+bail1:
+       mbedtls_mpi_free(&mpi_r);
+
+bail:
+
+       return -3;
+}
+
+int
+lws_genecdh_compute_shared_secret(struct lws_genec_ctx *ctx, uint8_t *ss,
+                                 int *ss_len)
+{
+       int n;
+       size_t st;
+       if (mbedtls_ecp_check_pubkey(&ctx->u.ctx_ecdh->grp, &ctx->u.ctx_ecdh->Q) ||
+           mbedtls_ecp_check_pubkey(&ctx->u.ctx_ecdh->grp, &ctx->u.ctx_ecdh->Qp)) {
+               lwsl_err("%s: both sides must be set up\n", __func__);
+
+               return -1;
+       }
+
+       n = mbedtls_ecdh_calc_secret(ctx->u.ctx_ecdh, &st, ss, *ss_len,
+                       lws_gencrypto_mbedtls_rngf, ctx->context);
+       if (n)
+               return -1;
+
+       *ss_len = (int)st;
+
+       return 0;
+}
diff --git a/lib/tls/mbedtls/lws-genhash.c b/lib/tls/mbedtls/lws-genhash.c
new file mode 100644 (file)
index 0000000..7fee589
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * libwebsockets - generic hash and HMAC api hiding the backend
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genhash provides a hash / hmac abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls hash functions underneath.
+ */
+#include "libwebsockets.h"
+#include <mbedtls/version.h>
+
+#if (MBEDTLS_VERSION_NUMBER >= 0x02070000)
+#define MBA(fn) fn##_ret
+#else
+#define MBA(fn) fn
+#endif
+
+int
+lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type)
+{
+       ctx->type = type;
+
+       switch (ctx->type) {
+       case LWS_GENHASH_TYPE_MD5:
+               mbedtls_md5_init(&ctx->u.md5);
+               MBA(mbedtls_md5_starts)(&ctx->u.md5);
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               mbedtls_sha1_init(&ctx->u.sha1);
+               MBA(mbedtls_sha1_starts)(&ctx->u.sha1);
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               mbedtls_sha256_init(&ctx->u.sha256);
+               MBA(mbedtls_sha256_starts)(&ctx->u.sha256, 0);
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               mbedtls_sha512_init(&ctx->u.sha512);
+               MBA(mbedtls_sha512_starts)(&ctx->u.sha512, 1 /* is384 */);
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               mbedtls_sha512_init(&ctx->u.sha512);
+               MBA(mbedtls_sha512_starts)(&ctx->u.sha512, 0);
+               break;
+       default:
+               return 1;
+       }
+
+       return 0;
+}
+
+int
+lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len)
+{
+       if (!len)
+               return 0;
+
+       switch (ctx->type) {
+       case LWS_GENHASH_TYPE_MD5:
+               MBA(mbedtls_md5_update)(&ctx->u.md5, in, len);
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               MBA(mbedtls_sha1_update)(&ctx->u.sha1, in, len);
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               MBA(mbedtls_sha256_update)(&ctx->u.sha256, in, len);
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               MBA(mbedtls_sha512_update)(&ctx->u.sha512, in, len);
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               MBA(mbedtls_sha512_update)(&ctx->u.sha512, in, len);
+               break;
+       }
+
+       return 0;
+}
+
+int
+lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result)
+{
+       switch (ctx->type) {
+       case LWS_GENHASH_TYPE_MD5:
+               MBA(mbedtls_md5_finish)(&ctx->u.md5, result);
+               mbedtls_md5_free(&ctx->u.md5);
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               MBA(mbedtls_sha1_finish)(&ctx->u.sha1, result);
+               mbedtls_sha1_free(&ctx->u.sha1);
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               MBA(mbedtls_sha256_finish)(&ctx->u.sha256, result);
+               mbedtls_sha256_free(&ctx->u.sha256);
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               MBA(mbedtls_sha512_finish)(&ctx->u.sha512, result);
+               mbedtls_sha512_free(&ctx->u.sha512);
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               MBA(mbedtls_sha512_finish)(&ctx->u.sha512, result);
+               mbedtls_sha512_free(&ctx->u.sha512);
+               break;
+       }
+
+       return 0;
+}
+
+int
+lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type,
+                const uint8_t *key, size_t key_len)
+{
+       int t;
+
+       ctx->type = type;
+
+       switch (type) {
+       case LWS_GENHMAC_TYPE_SHA256:
+               t = MBEDTLS_MD_SHA256;
+               break;
+       case LWS_GENHMAC_TYPE_SHA384:
+               t = MBEDTLS_MD_SHA384;
+               break;
+       case LWS_GENHMAC_TYPE_SHA512:
+               t = MBEDTLS_MD_SHA512;
+               break;
+       default:
+               return -1;
+       }
+
+       ctx->hmac = mbedtls_md_info_from_type(t);
+       if (!ctx->hmac)
+               return -1;
+
+       if (mbedtls_md_init_ctx(&ctx->ctx, ctx->hmac))
+               return -1;
+
+       if (mbedtls_md_hmac_starts(&ctx->ctx, key, key_len)) {
+               mbedtls_md_free(&ctx->ctx);
+               ctx->hmac = NULL;
+
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len)
+{
+       if (!len)
+               return 0;
+
+       if (mbedtls_md_hmac_update(&ctx->ctx, in, len))
+               return -1;
+
+       return 0;
+}
+
+int
+lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result)
+{
+       int n = 0;
+
+       if (result)
+               n = mbedtls_md_hmac_finish(&ctx->ctx, result);
+
+       mbedtls_md_free(&ctx->ctx);
+       ctx->hmac = NULL;
+       if (n)
+               return -1;
+
+       return 0;
+}
diff --git a/lib/tls/mbedtls/lws-genrsa.c b/lib/tls/mbedtls/lws-genrsa.c
new file mode 100644 (file)
index 0000000..2589064
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * libwebsockets - generic RSA api hiding the backend
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genrsa provides an RSA abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls crypto functions underneath.
+ */
+#include "core/private.h"
+#include "tls/mbedtls/private.h"
+#include <mbedtls/rsa.h>
+
+LWS_VISIBLE void
+lws_genrsa_destroy_elements(struct lws_gencrypto_keyelem *el)
+{
+       int n;
+
+       for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+}
+
+static int mode_map[] = { MBEDTLS_RSA_PKCS_V15, MBEDTLS_RSA_PKCS_V21 };
+
+LWS_VISIBLE int
+lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_gencrypto_keyelem *el,
+                 struct lws_context *context, enum enum_genrsa_mode mode,
+                 enum lws_genhash_types oaep_hashid)
+{
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->ctx = lws_zalloc(sizeof(*ctx->ctx), "genrsa");
+       if (!ctx->ctx)
+               return 1;
+
+       ctx->context = context;
+       ctx->mode = mode;
+
+       if (mode >= LGRSAM_COUNT)
+               return -1;
+
+       mbedtls_rsa_init(ctx->ctx, mode_map[mode], 0);
+
+       ctx->ctx->padding = mode_map[mode];
+       ctx->ctx->hash_id = lws_gencrypto_mbedtls_hash_to_MD_TYPE(oaep_hashid);
+
+       {
+               int n;
+
+               mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT] = {
+                       &ctx->ctx->E, &ctx->ctx->N, &ctx->ctx->D, &ctx->ctx->P,
+                       &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ,
+                       &ctx->ctx->QP,
+               };
+
+               for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++)
+                       if (el[n].buf &&
+                           mbedtls_mpi_read_binary(mpi[n], el[n].buf,
+                                                   el[n].len)) {
+                               lwsl_notice("mpi load failed\n");
+                               lws_free_set_NULL(ctx->ctx);
+
+                               return -1;
+                       }
+
+               /* mbedtls... compute missing P & Q */
+
+               if ( el[LWS_GENCRYPTO_RSA_KEYEL_D].len &&
+                   !el[LWS_GENCRYPTO_RSA_KEYEL_P].len &&
+                   !el[LWS_GENCRYPTO_RSA_KEYEL_Q].len) {
+                       if (mbedtls_rsa_complete(ctx->ctx)) {
+                               lwsl_notice("mbedtls_rsa_complete failed\n");
+                               lws_free_set_NULL(ctx->ctx);
+
+                               return -1;
+                       }
+
+               }
+       }
+
+       ctx->ctx->len = el[LWS_GENCRYPTO_RSA_KEYEL_N].len;
+
+       return 0;
+}
+
+static int
+_rngf(void *context, unsigned char *buf, size_t len)
+{
+       if ((size_t)lws_get_random(context, buf, len) == len)
+               return 0;
+
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx,
+                      enum enum_genrsa_mode mode, struct lws_gencrypto_keyelem *el,
+                      int bits)
+{
+       int n;
+
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->ctx = lws_zalloc(sizeof(*ctx->ctx), "genrsa");
+       if (!ctx->ctx)
+               return -1;
+
+       ctx->context = context;
+       ctx->mode = mode;
+
+       if (mode >= LGRSAM_COUNT)
+               return -1;
+
+       mbedtls_rsa_init(ctx->ctx, mode_map[mode], 0);
+
+       n = mbedtls_rsa_gen_key(ctx->ctx, _rngf, context, bits, 65537);
+       if (n) {
+               lwsl_err("mbedtls_rsa_gen_key failed 0x%x\n", -n);
+               goto cleanup_1;
+       }
+
+       {
+               mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT] = {
+                       &ctx->ctx->E, &ctx->ctx->N, &ctx->ctx->D, &ctx->ctx->P,
+                       &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ,
+                       &ctx->ctx->QP,
+               };
+
+               for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++)
+                       if (mbedtls_mpi_size(mpi[n])) {
+                               el[n].buf = lws_malloc(
+                                       mbedtls_mpi_size(mpi[n]), "genrsakey");
+                               if (!el[n].buf)
+                                       goto cleanup;
+                               el[n].len = mbedtls_mpi_size(mpi[n]);
+                               if (mbedtls_mpi_write_binary(mpi[n], el[n].buf,
+                                                        el[n].len))
+                                       goto cleanup;
+                       }
+       }
+
+       return 0;
+
+cleanup:
+       for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+cleanup_1:
+       lws_free(ctx->ctx);
+
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                         size_t in_len, uint8_t *out, size_t out_max)
+{
+       size_t olen = 0;
+       int n;
+
+       ctx->ctx->len = in_len;
+
+       mbedtls_rsa_complete(ctx->ctx);
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = mbedtls_rsa_rsaes_pkcs1_v15_decrypt(ctx->ctx, _rngf,
+                                                       ctx->context,
+                                                       MBEDTLS_RSA_PUBLIC,
+                                                       &olen, in, out,
+                                                       out_max);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               n = mbedtls_rsa_rsaes_oaep_decrypt(ctx->ctx, _rngf,
+                                                  ctx->context,
+                                                  MBEDTLS_RSA_PUBLIC,
+                                                  NULL, 0,
+                                                  &olen, in, out, out_max);
+               break;
+       default:
+               return -1;
+       }
+       if (n) {
+               lwsl_notice("%s: -0x%x\n", __func__, -n);
+
+               return -1;
+       }
+
+       return olen;
+}
+
+LWS_VISIBLE int
+lws_genrsa_private_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          size_t in_len, uint8_t *out, size_t out_max)
+{
+       size_t olen = 0;
+       int n;
+
+       ctx->ctx->len = in_len;
+
+       mbedtls_rsa_complete(ctx->ctx);
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = mbedtls_rsa_rsaes_pkcs1_v15_decrypt(ctx->ctx, _rngf,
+                                                       ctx->context,
+                                                       MBEDTLS_RSA_PRIVATE,
+                                                       &olen, in, out,
+                                                       out_max);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               n = mbedtls_rsa_rsaes_oaep_decrypt(ctx->ctx, _rngf,
+                                                  ctx->context,
+                                                  MBEDTLS_RSA_PRIVATE,
+                                                  NULL, 0,
+                                                  &olen, in, out, out_max);
+               break;
+       default:
+               return -1;
+       }
+       if (n) {
+               lwsl_notice("%s: -0x%x\n", __func__, -n);
+
+               return -1;
+       }
+
+       return olen;
+}
+
+LWS_VISIBLE int
+lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                         size_t in_len, uint8_t *out)
+{
+       int n;
+
+       mbedtls_rsa_complete(ctx->ctx);
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = mbedtls_rsa_rsaes_pkcs1_v15_encrypt(ctx->ctx, _rngf,
+                                                       ctx->context,
+                                                       MBEDTLS_RSA_PUBLIC,
+                                                       in_len, in, out);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               n = mbedtls_rsa_rsaes_oaep_encrypt(ctx->ctx, _rngf,
+                                                  ctx->context,
+                                                  MBEDTLS_RSA_PUBLIC,
+                                                  NULL, 0,
+                                                  in_len, in, out);
+               break;
+       default:
+               return -1;
+       }
+       if (n < 0) {
+               lwsl_notice("%s: -0x%x: in_len: %d\n", __func__, -n,
+                               (int)in_len);
+
+               return -1;
+       }
+
+       return mbedtls_mpi_size(&ctx->ctx->N);
+}
+
+LWS_VISIBLE int
+lws_genrsa_private_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          size_t in_len, uint8_t *out)
+{
+       int n;
+
+       mbedtls_rsa_complete(ctx->ctx);
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = mbedtls_rsa_rsaes_pkcs1_v15_encrypt(ctx->ctx, _rngf,
+                                                       ctx->context,
+                                                       MBEDTLS_RSA_PRIVATE,
+                                                       in_len, in, out);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               n = mbedtls_rsa_rsaes_oaep_encrypt(ctx->ctx, _rngf,
+                                                  ctx->context,
+                                                  MBEDTLS_RSA_PRIVATE,
+                                                  NULL, 0,
+                                                  in_len, in, out);
+               break;
+       default:
+               return -1;
+       }
+       if (n) {
+               lwsl_notice("%s: -0x%x: in_len: %d\n", __func__, -n,
+                               (int)in_len);
+
+               return -1;
+       }
+
+       return mbedtls_mpi_size(&ctx->ctx->N);
+}
+
+LWS_VISIBLE int
+lws_genrsa_hash_sig_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                        enum lws_genhash_types hash_type, const uint8_t *sig,
+                        size_t sig_len)
+{
+       int n, h = lws_gencrypto_mbedtls_hash_to_MD_TYPE(hash_type);
+
+       if (h < 0)
+               return -1;
+
+       mbedtls_rsa_complete(ctx->ctx);
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = mbedtls_rsa_rsassa_pkcs1_v15_verify(ctx->ctx, NULL, NULL,
+                                                       MBEDTLS_RSA_PUBLIC,
+                                                       h, 0, in, sig);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               n = mbedtls_rsa_rsassa_pss_verify(ctx->ctx, NULL, NULL,
+                                                 MBEDTLS_RSA_PUBLIC,
+                                                 h, 0, in, sig);
+               break;
+       default:
+               return -1;
+       }
+       if (n < 0) {
+               lwsl_notice("%s: -0x%x\n", __func__, -n);
+
+               return -1;
+       }
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genrsa_hash_sign(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                      enum lws_genhash_types hash_type, uint8_t *sig,
+                      size_t sig_len)
+{
+       int n, h = lws_gencrypto_mbedtls_hash_to_MD_TYPE(hash_type);
+
+       if (h < 0)
+               return -1;
+
+       mbedtls_rsa_complete(ctx->ctx);
+
+       /*
+        * The "sig" buffer must be as large as the size of ctx->N
+        * (eg. 128 bytes if RSA-1024 is used).
+        */
+       if (sig_len < ctx->ctx->len)
+               return -1;
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = mbedtls_rsa_rsassa_pkcs1_v15_sign(ctx->ctx, NULL, NULL,
+                                                     MBEDTLS_RSA_PRIVATE,
+                                                     h, 0, in, sig);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               n = mbedtls_rsa_rsassa_pss_sign(ctx->ctx, NULL, NULL,
+                                               MBEDTLS_RSA_PRIVATE,
+                                               h, 0, in, sig);
+               break;
+       default:
+               return -1;
+       }
+
+       if (n < 0) {
+               lwsl_notice("%s: -0x%x\n", __func__, -n);
+
+               return -1;
+       }
+
+       return ctx->ctx->len;
+}
+
+LWS_VISIBLE int
+lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private,
+                           uint8_t *pkey_asn1, size_t pkey_asn1_len)
+{
+       uint8_t *p = pkey_asn1, *totlen, *end = pkey_asn1 + pkey_asn1_len - 1;
+       mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT] = {
+               &ctx->ctx->N, &ctx->ctx->E, &ctx->ctx->D, &ctx->ctx->P,
+               &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ,
+               &ctx->ctx->QP,
+       };
+       int n;
+
+       /* 30 82  - sequence
+        *   09 29  <-- length(0x0929) less 4 bytes
+        * 02 01 <- length (1)
+        *  00
+        * 02 82
+        *  02 01 <- length (513)  N
+        *  ...
+        *
+        *  02 03 <- length (3) E
+        *    01 00 01
+        *
+        * 02 82
+        *   02 00 <- length (512) D P Q EXP1 EXP2 COEFF
+        *
+        *  */
+
+       *p++ = 0x30;
+       *p++ = 0x82;
+       totlen = p;
+       p += 2;
+
+       *p++ = 0x02;
+       *p++ = 0x01;
+       *p++ = 0x00;
+
+       for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++) {
+               int m = mbedtls_mpi_size(mpi[n]);
+               uint8_t *elen;
+
+               *p++ = 0x02;
+               elen = p;
+               if (m < 0x7f)
+                       *p++ = m;
+               else {
+                       *p++ = 0x82;
+                       *p++ = m >> 8;
+                       *p++ = m & 0xff;
+               }
+
+               if (p + m > end)
+                       return -1;
+
+               if (mbedtls_mpi_write_binary(mpi[n], p, m))
+                       return -1;
+               if (p[0] & 0x80) {
+                       p[0] = 0x00;
+                       if (mbedtls_mpi_write_binary(mpi[n], &p[1], m))
+                               return -1;
+                       m++;
+               }
+               if (m < 0x7f)
+                       *elen = m;
+               else {
+                       *elen++ = 0x82;
+                       *elen++ = m >> 8;
+                       *elen = m & 0xff;
+               }
+               p += m;
+       }
+
+       n = lws_ptr_diff(p, pkey_asn1);
+
+       *totlen++ = (n - 4) >> 8;
+       *totlen = (n - 4) & 0xff;
+
+       return n;
+}
+
+LWS_VISIBLE void
+lws_genrsa_destroy(struct lws_genrsa_ctx *ctx)
+{
+       if (!ctx->ctx)
+               return;
+       mbedtls_rsa_free(ctx->ctx);
+       lws_free(ctx->ctx);
+       ctx->ctx = NULL;
+}
diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c
new file mode 100644 (file)
index 0000000..d3de991
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * libwebsockets - mbedtls-specific client TLS code
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+static int
+OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+       return 0;
+}
+
+int
+lws_ssl_client_bio_create(struct lws *wsi)
+{
+       char hostname[128], *p;
+       const char *alpn_comma = wsi->context->tls.alpn_default;
+       struct alpn_ctx protos;
+
+       if (lws_hdr_copy(wsi, hostname, sizeof(hostname),
+                        _WSI_TOKEN_CLIENT_HOST) <= 0) {
+               lwsl_err("%s: Unable to get hostname\n", __func__);
+
+               return -1;
+       }
+
+       /*
+        * remove any :port part on the hostname... necessary for network
+        * connection but typical certificates do not contain it
+        */
+       p = hostname;
+       while (*p) {
+               if (*p == ':') {
+                       *p = '\0';
+                       break;
+               }
+               p++;
+       }
+
+       wsi->tls.ssl = SSL_new(wsi->vhost->tls.ssl_client_ctx);
+       if (!wsi->tls.ssl) {
+               lwsl_info("%s: SSL_new() failed\n", __func__);
+               return -1;
+       }
+
+       if (wsi->vhost->tls.ssl_info_event_mask)
+               SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback);
+
+       if (!(wsi->tls.use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) {
+               X509_VERIFY_PARAM *param = SSL_get0_param(wsi->tls.ssl);
+               /* Enable automatic hostname checks */
+       //      X509_VERIFY_PARAM_set_hostflags(param,
+       //                              X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+               X509_VERIFY_PARAM_set1_host(param, hostname, 0);
+       }
+
+       if (wsi->vhost->tls.alpn)
+               alpn_comma = wsi->vhost->tls.alpn;
+
+       if (lws_hdr_copy(wsi, hostname, sizeof(hostname),
+                        _WSI_TOKEN_CLIENT_ALPN) > 0)
+               alpn_comma = hostname;
+
+       lwsl_info("%s: %p: client conn sending ALPN list '%s'\n",
+                 __func__, wsi, alpn_comma);
+
+       protos.len = lws_alpn_comma_to_openssl(alpn_comma, protos.data,
+                                              sizeof(protos.data) - 1);
+
+       /* with mbedtls, protos is not pointed to after exit from this call */
+       SSL_set_alpn_select_cb(wsi->tls.ssl, &protos);
+
+       /*
+        * use server name indication (SNI), if supported,
+        * when establishing connection
+        */
+       SSL_set_verify(wsi->tls.ssl, SSL_VERIFY_PEER,
+                      OpenSSL_client_verify_callback);
+
+       SSL_set_fd(wsi->tls.ssl, wsi->desc.sockfd);
+
+       return 0;
+}
+
+int ERR_get_error(void)
+{
+       return 0;
+}
+
+enum lws_ssl_capable_status
+lws_tls_client_connect(struct lws *wsi)
+{
+       int m, n = SSL_connect(wsi->tls.ssl);
+       const unsigned char *prot;
+       unsigned int len;
+
+       if (n == 1) {
+               SSL_get0_alpn_selected(wsi->tls.ssl, &prot, &len);
+               lws_role_call_alpn_negotiated(wsi, (const char *)prot);
+               lwsl_info("client connect OK\n");
+               return LWS_SSL_CAPABLE_DONE;
+       }
+
+       m = SSL_get_error(wsi->tls.ssl, n);
+
+       if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl))
+               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+
+       if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl))
+               return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;
+
+       if (!n) /* we don't know what he wants, but he says to retry */
+               return LWS_SSL_CAPABLE_MORE_SERVICE;
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+int
+lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, int ebuf_len)
+{
+       int n;
+       X509 *peer = SSL_get_peer_certificate(wsi->tls.ssl);
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       char *sb = (char *)&pt->serv_buf[0];
+
+       if (!peer) {
+               lwsl_info("peer did not provide cert\n");
+               lws_snprintf(ebuf, ebuf_len, "no peer cert");
+
+               return -1;
+       }
+       lwsl_info("peer provided cert\n");
+
+       n = SSL_get_verify_result(wsi->tls.ssl);
+       lws_latency(wsi->context, wsi,
+                       "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0);
+
+        lwsl_debug("get_verify says %d\n", n);
+
+       if (n == X509_V_OK)
+               return 0;
+
+       if (n == X509_V_ERR_HOSTNAME_MISMATCH &&
+           (wsi->tls.use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) {
+               lwsl_info("accepting certificate for invalid hostname\n");
+               return 0;
+       }
+
+       if (n == X509_V_ERR_INVALID_CA &&
+           (wsi->tls.use_ssl & LCCSCF_ALLOW_SELFSIGNED)) {
+               lwsl_info("accepting certificate from untrusted CA\n");
+               return 0;
+       }
+
+       if ((n == X509_V_ERR_CERT_NOT_YET_VALID ||
+            n == X509_V_ERR_CERT_HAS_EXPIRED) &&
+            (wsi->tls.use_ssl & LCCSCF_ALLOW_EXPIRED)) {
+               lwsl_info("accepting expired or not yet valid certificate\n");
+
+               return 0;
+       }
+       lws_snprintf(ebuf, ebuf_len,
+               "server's cert didn't look good, X509_V_ERR = %d: %s\n",
+                n, ERR_error_string(n, sb));
+       lwsl_info("%s\n", ebuf);
+       lws_tls_err_describe_clear();
+
+       return -1;
+}
+
+int
+lws_tls_client_create_vhost_context(struct lws_vhost *vh,
+                                   const struct lws_context_creation_info *info,
+                                   const char *cipher_list,
+                                   const char *ca_filepath,
+                                   const void *ca_mem,
+                                   unsigned int ca_mem_len,
+                                   const char *cert_filepath,
+                                   const void *cert_mem,
+                                   unsigned int cert_mem_len,
+                                   const char *private_key_filepath)
+{
+       X509 *d2i_X509(X509 **cert, const unsigned char *buffer, long len);
+       SSL_METHOD *method = (SSL_METHOD *)TLS_client_method();
+       unsigned long error;
+       int n;
+
+       if (!method) {
+               error = ERR_get_error();
+               lwsl_err("problem creating ssl method %lu: %s\n",
+                       error, ERR_error_string(error,
+                                     (char *)vh->context->pt[0].serv_buf));
+               return 1;
+       }
+       /* create context */
+       vh->tls.ssl_client_ctx = SSL_CTX_new(method);
+       if (!vh->tls.ssl_client_ctx) {
+               error = ERR_get_error();
+               lwsl_err("problem creating ssl context %lu: %s\n",
+                       error, ERR_error_string(error,
+                                     (char *)vh->context->pt[0].serv_buf));
+               return 1;
+       }
+
+       if (!ca_filepath && (!ca_mem || !ca_mem_len))
+               return 0;
+
+       if (ca_filepath) {
+#if !defined(LWS_PLAT_OPTEE)
+               uint8_t *buf;
+               lws_filepos_t len;
+
+               if (alloc_file(vh->context, ca_filepath, &buf, &len)) {
+                       lwsl_err("Load CA cert file %s failed\n", ca_filepath);
+                       return 1;
+               }
+               vh->tls.x509_client_CA = d2i_X509(NULL, buf, len);
+               free(buf);
+               lwsl_notice("Loading client CA for verification %s\n", ca_filepath);
+#endif
+       } else {
+               vh->tls.x509_client_CA = d2i_X509(NULL, (uint8_t*)ca_mem, ca_mem_len);
+               lwsl_notice("%s: using mem client CA cert %d\n",
+                           __func__, ca_mem_len);
+       }
+
+       if (!vh->tls.x509_client_CA) {
+               lwsl_err("client CA: x509 parse failed\n");
+               return 1;
+       }
+
+       if (!vh->tls.ssl_ctx)
+               SSL_CTX_add_client_CA(vh->tls.ssl_client_ctx, vh->tls.x509_client_CA);
+       else
+               SSL_CTX_add_client_CA(vh->tls.ssl_ctx, vh->tls.x509_client_CA);
+
+       /* support for client-side certificate authentication */
+       if (cert_filepath) {
+#if !defined(LWS_PLAT_OPTEE)
+               uint8_t *buf;
+               lws_filepos_t amount;
+
+               if (lws_tls_use_any_upgrade_check_extant(cert_filepath) !=
+                               LWS_TLS_EXTANT_YES &&
+                   (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT))
+                       return 0;
+
+               lwsl_notice("%s: doing cert filepath %s\n", __func__,
+                               cert_filepath);
+
+               if (alloc_file(vh->context, cert_filepath, &buf, &amount))
+                       return 1;
+
+               buf[amount++] = '\0';
+
+               SSL_CTX_use_PrivateKey_ASN1(0, vh->tls.ssl_client_ctx,
+                               buf, amount);
+
+               n = SSL_CTX_use_certificate_ASN1(vh->tls.ssl_client_ctx,
+                               amount, buf);
+               lws_free(buf);
+               if (n < 1) {
+                       lwsl_err("problem %d getting cert '%s'\n", n,
+                                cert_filepath);
+                       lws_tls_err_describe_clear();
+                       return 1;
+               }
+
+               lwsl_notice("Loaded client cert %s\n", cert_filepath);
+#endif
+       } else if (cert_mem && cert_mem_len) {
+               // lwsl_hexdump_notice(cert_mem, cert_mem_len - 1);
+               SSL_CTX_use_PrivateKey_ASN1(0, vh->tls.ssl_client_ctx,
+                               cert_mem, cert_mem_len - 1);
+               n = SSL_CTX_use_certificate_ASN1(vh->tls.ssl_client_ctx,
+                                                cert_mem_len, cert_mem);
+               if (n < 1) {
+                       lwsl_err("%s: problem interpreting client cert\n",
+                                __func__);
+                       lws_tls_err_describe_clear();
+                       return 1;
+               }
+               lwsl_notice("%s: using mem client cert %d\n",
+                           __func__, cert_mem_len);
+       }
+
+       return 0;
+}
diff --git a/lib/tls/mbedtls/mbedtls-server.c b/lib/tls/mbedtls/mbedtls-server.c
new file mode 100644 (file)
index 0000000..f993a54
--- /dev/null
@@ -0,0 +1,712 @@
+/*
+ * libwebsockets - mbedTLS-specific server functions
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include <mbedtls/x509_csr.h>
+
+int
+lws_tls_server_client_cert_verify_config(struct lws_vhost *vh)
+{
+       int verify_options = SSL_VERIFY_PEER;
+
+       /* as a server, are we requiring clients to identify themselves? */
+       if (!lws_check_opt(vh->options,
+                         LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) {
+               lwsl_notice("no client cert required\n");
+               return 0;
+       }
+
+       /*
+        * The wrapper has this messed-up mapping:
+        *
+        *         else if (ctx->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+        *     mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+        *
+        * ie the meaning is inverted.  So where we should test for ! we don't
+        */
+       if (lws_check_opt(vh->options, LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
+               verify_options = SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+
+       lwsl_notice("%s: vh %s requires client cert %d\n", __func__, vh->name,
+                   verify_options);
+
+       SSL_CTX_set_verify(vh->tls.ssl_ctx, verify_options, NULL);
+
+       return 0;
+}
+
+static int
+lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx,
+                  const unsigned char *servername, size_t len)
+{
+       SSL *ssl = SSL_SSL_from_mbedtls_ssl_context(mbedtls_ctx);
+       struct lws_context *context = (struct lws_context *)arg;
+       struct lws_vhost *vhost, *vh;
+
+       lwsl_notice("%s: %s\n", __func__, servername);
+
+       /*
+        * 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
+        * same port.
+        */
+       vh = context->vhost_list;
+       while (vh) {
+               if (!vh->being_destroyed &&
+                   vh->tls.ssl_ctx == SSL_get_SSL_CTX(ssl))
+                       break;
+               vh = vh->vhost_next;
+       }
+
+       if (!vh) {
+               assert(vh); /* can't match the incoming vh? */
+               return 0;
+       }
+
+       vhost = lws_select_vhost(context, vh->listen_port,
+                                (const char *)servername);
+       if (!vhost) {
+               lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port);
+
+               return 0;
+       }
+
+       lwsl_info("SNI: Found: %s:%d at vhost '%s'\n", servername,
+                                       vh->listen_port, vhost->name);
+
+       if (!vhost->tls.ssl_ctx) {
+               lwsl_err("%s: vhost %s matches SNI but no valid cert\n",
+                               __func__, vh->name);
+
+               return 1;
+       }
+
+       /* select the ssl ctx from the selected vhost for this conn */
+       SSL_set_SSL_CTX(ssl, vhost->tls.ssl_ctx);
+
+       return 0;
+}
+
+int
+lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi,
+                         const char *cert, const char *private_key,
+                         const char *mem_cert, size_t mem_cert_len,
+                         const char *mem_privkey, size_t mem_privkey_len)
+{
+       lws_filepos_t flen;
+       uint8_t *p = NULL;
+       long err;
+       int n;
+
+       if ((!cert || !private_key) && (!mem_cert || !mem_privkey)) {
+               lwsl_notice("%s: no usable input\n", __func__);
+               return 0;
+       }
+
+       n = lws_tls_generic_cert_checks(vhost, cert, private_key);
+
+       if (n == LWS_TLS_EXTANT_NO && (!mem_cert || !mem_privkey))
+               return 0;
+
+       /*
+        * we can't read the root-privs files.  But if mem_cert is provided,
+        * we should use that.
+        */
+       if (n == LWS_TLS_EXTANT_NO)
+               n = LWS_TLS_EXTANT_ALTERNATIVE;
+
+       if (n == LWS_TLS_EXTANT_ALTERNATIVE && (!mem_cert || !mem_privkey))
+               return 1; /* no alternative */
+
+       if (n == LWS_TLS_EXTANT_ALTERNATIVE) {
+               /*
+                * Although we have prepared update certs, we no longer have
+                * the rights to read our own cert + key we saved.
+                *
+                * If we were passed copies in memory buffers, use those
+                * instead.
+                *
+                * The passed memory-buffer cert image is in DER, and the
+                * memory-buffer private key image is PEM.
+                */
+               cert = NULL;
+               private_key = NULL;
+
+               if (!mem_cert)
+                       return 1;
+       }
+       if (lws_tls_alloc_pem_to_der_file(vhost->context, cert, mem_cert,
+                                         mem_cert_len, &p, &flen)) {
+               lwsl_err("couldn't find cert file %s\n", cert);
+
+               return 1;
+       }
+
+       err = SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx, flen, p);
+       lws_free_set_NULL(p);
+       if (!err) {
+               lwsl_err("Problem loading cert\n");
+               return 1;
+       }
+
+       if (lws_tls_alloc_pem_to_der_file(vhost->context, private_key,
+                                         (char *)mem_privkey, mem_privkey_len,
+                                         &p, &flen)) {
+               lwsl_err("couldn't find private key\n");
+
+               return 1;
+       }
+
+       err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->tls.ssl_ctx, p, flen);
+       lws_free_set_NULL(p);
+       if (!err) {
+               lwsl_err("Problem loading key\n");
+
+               return 1;
+       }
+
+       if (!private_key && !mem_privkey && vhost->protocols[0].callback(wsi,
+                       LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
+                       vhost->tls.ssl_ctx, NULL, 0)) {
+               lwsl_err("ssl private key not set\n");
+
+               return 1;
+       }
+
+       vhost->tls.skipped_certs = 0;
+
+       return 0;
+}
+
+int
+lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info,
+                                 struct lws_vhost *vhost, struct lws *wsi)
+{
+       const SSL_METHOD *method = TLS_server_method();
+       uint8_t *p;
+       lws_filepos_t flen;
+       int n;
+
+       vhost->tls.ssl_ctx = SSL_CTX_new(method);       /* create context */
+       if (!vhost->tls.ssl_ctx) {
+               lwsl_err("problem creating ssl context\n");
+               return 1;
+       }
+
+       if (!vhost->tls.use_ssl ||
+           (!info->ssl_cert_filepath && !info->server_ssl_cert_mem))
+               return 0;
+
+       if (info->ssl_ca_filepath) {
+               lwsl_notice("%s: vh %s: loading CA filepath %s\n", __func__,
+                           vhost->name, info->ssl_ca_filepath);
+               if (lws_tls_alloc_pem_to_der_file(vhost->context,
+                               info->ssl_ca_filepath, NULL, 0, &p, &flen)) {
+                       lwsl_err("couldn't find client CA file %s\n",
+                                       info->ssl_ca_filepath);
+
+                       return 1;
+               }
+
+               if (SSL_CTX_add_client_CA_ASN1(vhost->tls.ssl_ctx, (int)flen, p) != 1) {
+                       lwsl_err("%s: SSL_CTX_add_client_CA_ASN1 unhappy\n",
+                                __func__);
+                       free(p);
+                       return 1;
+               }
+               free(p);
+       } else {
+               if (info->server_ssl_ca_mem && info->server_ssl_ca_mem_len &&
+                   SSL_CTX_add_client_CA_ASN1(vhost->tls.ssl_ctx,
+                                              (int)info->server_ssl_ca_mem_len,
+                                              info->server_ssl_ca_mem) != 1) {
+                       lwsl_err("%s: mem SSL_CTX_add_client_CA_ASN1 unhappy\n",
+                                __func__);
+                       return 1;
+               }
+               lwsl_notice("%s: vh %s: mem CA OK\n", __func__, vhost->name);
+       }
+
+       n = lws_tls_server_certs_load(vhost, wsi, info->ssl_cert_filepath,
+                                     info->ssl_private_key_filepath,
+                                     info->server_ssl_cert_mem,
+                                     info->server_ssl_cert_mem_len,
+                                     info->server_ssl_private_key_mem,
+                                     info->server_ssl_private_key_mem_len);
+       if (n)
+               return n;
+
+       return 0;
+}
+
+int
+lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd)
+{
+       errno = 0;
+       wsi->tls.ssl = SSL_new(wsi->vhost->tls.ssl_ctx);
+       if (wsi->tls.ssl == NULL) {
+               lwsl_err("SSL_new failed: errno %d\n", errno);
+
+               lws_tls_err_describe_clear();
+               return 1;
+       }
+
+       SSL_set_fd(wsi->tls.ssl, accept_fd);
+
+       if (wsi->vhost->tls.ssl_info_event_mask)
+               SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback);
+
+       SSL_set_sni_callback(wsi->tls.ssl, lws_mbedtls_sni_cb, wsi->context);
+
+       return 0;
+}
+
+#if defined(LWS_AMAZON_RTOS)
+enum lws_ssl_capable_status
+#else
+int
+#endif
+lws_tls_server_abort_connection(struct lws *wsi)
+{
+       __lws_tls_shutdown(wsi);
+       SSL_free(wsi->tls.ssl);
+
+       return 0;
+}
+
+enum lws_ssl_capable_status
+lws_tls_server_accept(struct lws *wsi)
+{
+       union lws_tls_cert_info_results ir;
+       int m, n;
+
+       n = SSL_accept(wsi->tls.ssl);
+       if (n == 1) {
+
+               if (strstr(wsi->vhost->name, ".invalid")) {
+                       lwsl_notice("%s: vhost has .invalid, "
+                                   "rejecting accept\n", __func__);
+
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+
+               n = lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME,
+                                          &ir, sizeof(ir.ns.name));
+               if (!n)
+                       lwsl_notice("%s: client cert CN '%s'\n",
+                                   __func__, ir.ns.name);
+               else
+                       lwsl_info("%s: couldn't get client cert CN\n",
+                                 __func__);
+               return LWS_SSL_CAPABLE_DONE;
+       }
+
+       m = SSL_get_error(wsi->tls.ssl, n);
+       lwsl_debug("%s: %p: accept SSL_get_error %d errno %d\n", __func__,
+                  wsi, m, errno);
+
+       // mbedtls wrapper only
+       if (m == SSL_ERROR_SYSCALL && errno == 11)
+               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+
+#if defined(WIN32)
+       if (m == SSL_ERROR_SYSCALL && errno == 0)
+               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+#endif
+
+       if (m == SSL_ERROR_SYSCALL || m == SSL_ERROR_SSL)
+               return LWS_SSL_CAPABLE_ERROR;
+
+       if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) {
+               if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
+                       lwsl_info("%s: WANT_READ change_pollfd failed\n",
+                                 __func__);
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+
+               lwsl_info("SSL_ERROR_WANT_READ\n");
+               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+       }
+       if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) {
+               lwsl_debug("%s: WANT_WRITE\n", __func__);
+
+               if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) {
+                       lwsl_info("%s: WANT_WRITE change_pollfd failed\n",
+                                 __func__);
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+               return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;
+       }
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+#if defined(LWS_WITH_ACME)
+/*
+ * mbedtls doesn't support SAN for cert creation.  So we use a known-good
+ * tls-sni-01 cert from OpenSSL that worked on Let's Encrypt, and just replace
+ * the pubkey n part and the signature part.
+ *
+ * This will need redoing for tls-sni-02...
+ */
+
+static uint8_t ss_cert_leadin[] = {
+       0x30, 0x82,
+         0x05, 0x56, /* total length: LEN1 (+2 / +3) (correct for 513 + 512)*/
+
+       0x30, 0x82, /* length: LEN2  (+6 / +7) (correct for 513) */
+               0x03, 0x3e,
+
+       /* addition: v3 cert (+5 bytes)*/
+       0xa0, 0x03,
+               0x02, 0x01, 0x02,
+
+       0x02, 0x01, 0x01,
+       0x30, 0x0d, 0x06, 0x09, 0x2a,
+       0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x3f,
+       0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47,
+       0x42, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b,
+       0x73, 0x6f, 0x6d, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x31,
+       0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x11, 0x74, 0x65,
+       0x6d, 0x70, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, 0x6e, 0x76, 0x61,
+       0x6c, 0x69, 0x64, 0x30, 0x1e, 0x17, 0x0d,
+
+       /* from 2017-10-29 ... */
+       0x31, 0x37, 0x31, 0x30, 0x32, 0x39, 0x31, 0x31, 0x34, 0x39, 0x34, 0x35,
+       0x5a, 0x17, 0x0d,
+
+       /* thru 2049-10-29 we immediately discard the private key, no worries */
+       0x34, 0x39, 0x31, 0x30, 0x32, 0x39, 0x31, 0x32, 0x34, 0x39, 0x34, 0x35,
+       0x5a,
+
+       0x30, 0x3f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+       0x02, 0x47, 0x42, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a,
+       0x0c, 0x0b, 0x73, 0x6f, 0x6d, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e,
+       0x79, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x11,
+       0x74, 0x65, 0x6d, 0x70, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, 0x6e,
+       0x76, 0x61, 0x6c, 0x69, 0x64, 0x30,
+
+       0x82,
+               0x02, 0x22, /* LEN3 (+C3 / C4) */
+       0x30, 0x0d, 0x06,
+       0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+       0x03,
+
+       0x82,
+               0x02, 0x0f, /* LEN4 (+D6 / D7) */
+
+       0x00, 0x30, 0x82,
+
+               0x02, 0x0a, /* LEN5 (+ DB / DC) */
+
+       0x02, 0x82,
+
+       //0x02, 0x01, /* length of n in bytes (including leading 00 if any) */
+       },
+
+       /* 1 + (keybits / 8) bytes N */
+
+       ss_cert_san_leadin[] = {
+               /* e - fixed */
+               0x02, 0x03, 0x01, 0x00, 0x01,
+
+               0xa3, 0x5d, 0x30, 0x5b, 0x30, 0x59, 0x06, 0x03, 0x55, 0x1d,
+               0x11, 0x04, 0x52, 0x30, 0x50, /* <-- SAN length + 2 */
+
+               0x82, 0x4e, /* <-- SAN length */
+       },
+
+       /* 78 bytes of SAN (tls-sni-01)
+       0x61, 0x64, 0x34, 0x31, 0x61, 0x66, 0x62, 0x65, 0x30, 0x63, 0x61, 0x34,
+       0x36, 0x34, 0x32, 0x66, 0x30, 0x61, 0x34, 0x34, 0x39, 0x64, 0x39, 0x63,
+       0x61, 0x37, 0x36, 0x65, 0x62, 0x61, 0x61, 0x62, 0x2e, 0x32, 0x38, 0x39,
+       0x34, 0x64, 0x34, 0x31, 0x36, 0x63, 0x39, 0x38, 0x33, 0x66, 0x31, 0x32,
+       0x65, 0x64, 0x37, 0x33, 0x31, 0x61, 0x33, 0x30, 0x66, 0x35, 0x63, 0x34,
+       0x34, 0x37, 0x37, 0x66, 0x65, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69,
+       0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, */
+
+       /* end of LEN2 area */
+
+       ss_cert_sig_leadin[] = {
+               /* it's saying that the signature is SHA256 + RSA */
+               0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+               0x01, 0x01, 0x0b, 0x05, 0x00, 0x03,
+
+               0x82,
+                       0x02, 0x01,
+               0x00,
+       };
+
+       /* (keybits / 8) bytes signature to end of LEN1 area */
+
+#define SAN_A_LENGTH 78
+
+LWS_VISIBLE int
+lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a,
+                            const char *san_b)
+{
+       int buflen = 0x560;
+       uint8_t *buf = lws_malloc(buflen, "tmp cert buf"), *p = buf, *pkey_asn1;
+       struct lws_genrsa_ctx ctx;
+       struct lws_gencrypto_keyelem el;
+       uint8_t digest[32];
+       struct lws_genhash_ctx hash_ctx;
+       int pkey_asn1_len = 3 * 1024;
+       int n, m, keybits = lws_plat_recommended_rsa_bits(), adj;
+
+       if (!buf)
+               return 1;
+
+       n = lws_genrsa_new_keypair(vhost->context, &ctx, &el, keybits);
+       if (n < 0) {
+               lws_genrsa_destroy_elements(&el);
+               goto bail1;
+       }
+
+       n = sizeof(ss_cert_leadin);
+       memcpy(p, ss_cert_leadin, n);
+       p += n;
+
+       adj = (0x0556 - 0x401) + (keybits / 4) + 1;
+       buf[2] = adj >> 8;
+       buf[3] = adj & 0xff;
+
+       adj = (0x033e - 0x201) + (keybits / 8) + 1;
+       buf[6] = adj >> 8;
+       buf[7] = adj & 0xff;
+
+       adj = (0x0222 - 0x201) + (keybits / 8) + 1;
+       buf[0xc3] = adj >> 8;
+       buf[0xc4] = adj & 0xff;
+
+       adj = (0x020f - 0x201) + (keybits / 8) + 1;
+       buf[0xd6] = adj >> 8;
+       buf[0xd7] = adj & 0xff;
+
+       adj = (0x020a - 0x201) + (keybits / 8) + 1;
+       buf[0xdb] = adj >> 8;
+       buf[0xdc] = adj & 0xff;
+
+       *p++ = ((keybits / 8) + 1) >> 8;
+       *p++ = ((keybits / 8) + 1) & 0xff;
+
+       /* we need to drop 1 + (keybits / 8) bytes of n in here, 00 + key */
+
+       *p++ = 0x00;
+       memcpy(p, el.e[LWS_GENCRYPTO_RSA_KEYEL_N].buf, el.e[LWS_GENCRYPTO_RSA_KEYEL_N].len);
+       p += el.e[LWS_GENCRYPTO_RSA_KEYEL_N].len;
+
+       memcpy(p, ss_cert_san_leadin, sizeof(ss_cert_san_leadin));
+       p += sizeof(ss_cert_san_leadin);
+
+       /* drop in 78 bytes of san_a */
+
+       memcpy(p, san_a, SAN_A_LENGTH);
+       p += SAN_A_LENGTH;
+       memcpy(p, ss_cert_sig_leadin, sizeof(ss_cert_sig_leadin));
+
+       p[17] = ((keybits / 8) + 1) >> 8;
+       p[18] = ((keybits / 8) + 1) & 0xff;
+
+       p += sizeof(ss_cert_sig_leadin);
+
+       /* hash the cert plaintext */
+
+       if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))
+               goto bail2;
+
+       if (lws_genhash_update(&hash_ctx, buf, lws_ptr_diff(p, buf))) {
+               lws_genhash_destroy(&hash_ctx, NULL);
+
+               goto bail2;
+       }
+       if (lws_genhash_destroy(&hash_ctx, digest))
+               goto bail2;
+
+       /* sign the hash */
+
+       n = lws_genrsa_hash_sign(&ctx, digest, LWS_GENHASH_TYPE_SHA256, p,
+                                buflen - lws_ptr_diff(p, buf));
+       if (n < 0)
+               goto bail2;
+       p += n;
+
+       pkey_asn1 = lws_malloc(pkey_asn1_len, "mbed crt tmp");
+       if (!pkey_asn1)
+               goto bail2;
+
+       m = lws_genrsa_render_pkey_asn1(&ctx, 1, pkey_asn1, pkey_asn1_len);
+       if (m < 0) {
+               lws_free(pkey_asn1);
+               goto bail2;
+       }
+
+//     lwsl_hexdump_level(LLL_DEBUG, buf, lws_ptr_diff(p, buf));
+       n = SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx,
+                                lws_ptr_diff(p, buf), buf);
+       if (n != 1) {
+               lws_free(pkey_asn1);
+               lwsl_err("%s: generated cert failed to load 0x%x\n",
+                               __func__, -n);
+       } else {
+               //lwsl_debug("private key\n");
+               //lwsl_hexdump_level(LLL_DEBUG, pkey_asn1, n);
+
+               /* and to use our generated private key */
+               n = SSL_CTX_use_PrivateKey_ASN1(0, vhost->tls.ssl_ctx,
+                                               pkey_asn1, m);
+               lws_free(pkey_asn1);
+               if (n != 1) {
+                       lwsl_err("%s: SSL_CTX_use_PrivateKey_ASN1 failed\n",
+                                   __func__);
+               }
+       }
+
+       lws_genrsa_destroy(&ctx);
+       lws_genrsa_destroy_elements(&el);
+
+       lws_free(buf);
+
+       return n != 1;
+
+bail2:
+       lws_genrsa_destroy(&ctx);
+       lws_genrsa_destroy_elements(&el);
+bail1:
+       lws_free(buf);
+
+       return -1;
+}
+
+void
+lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost)
+{
+}
+
+#if defined(LWS_WITH_JOSE)
+static int
+_rngf(void *context, unsigned char *buf, size_t len)
+{
+       if ((size_t)lws_get_random(context, buf, len) == len)
+               return 0;
+
+       return -1;
+}
+
+static const char *x5[] = { "C", "ST", "L", "O", "CN" };
+
+/*
+ * CSR is output formatted as b64url(DER)
+ * Private key is output as a PEM in memory
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[],
+                           uint8_t *dcsr, size_t csr_len, char **privkey_pem,
+                           size_t *privkey_len)
+{
+       mbedtls_x509write_csr csr;
+       mbedtls_pk_context mpk;
+       int buf_size = 4096, n;
+       char subject[200], *p = subject, *end = p + sizeof(subject) - 1;
+       uint8_t *buf = malloc(buf_size); /* malloc because given to user code */
+
+       if (!buf)
+               return -1;
+
+       mbedtls_x509write_csr_init(&csr);
+
+       mbedtls_pk_init(&mpk);
+       if (mbedtls_pk_setup(&mpk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) {
+               lwsl_notice("%s: pk_setup failed\n", __func__);
+               goto fail;
+       }
+
+       n = mbedtls_rsa_gen_key(mbedtls_pk_rsa(mpk), _rngf, context,
+                               lws_plat_recommended_rsa_bits(), 65537);
+       if (n) {
+               lwsl_notice("%s: failed to generate keys\n", __func__);
+
+               goto fail1;
+       }
+
+       /* subject must be formatted like "C=TW,O=warmcat,CN=myserver" */
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(x5); n++) {
+               if (p != subject)
+                       *p++ = ',';
+               if (elements[n])
+                       p += lws_snprintf(p, end - p, "%s=%s", x5[n],
+                                         elements[n]);
+       }
+
+       if (mbedtls_x509write_csr_set_subject_name(&csr, subject))
+               goto fail1;
+
+       mbedtls_x509write_csr_set_key(&csr, &mpk);
+       mbedtls_x509write_csr_set_md_alg(&csr, MBEDTLS_MD_SHA256);
+
+       /*
+        * data is written at the end of the buffer! Use the
+        * return value to determine where you should start
+        * using the buffer
+        */
+       n = mbedtls_x509write_csr_der(&csr, buf, buf_size, _rngf, context);
+       if (n < 0) {
+               lwsl_notice("%s: write csr der failed\n", __func__);
+               goto fail1;
+       }
+
+       /* we have it in DER, we need it in b64URL */
+
+       n = lws_jws_base64_enc((char *)(buf + buf_size) - n, n,
+                              (char *)dcsr, csr_len);
+       if (n < 0)
+               goto fail1;
+
+       /*
+        * okay, the CSR is done, last we need the private key in PEM
+        * re-use the DER CSR buf as the result buffer since we cn do it in
+        * one step
+        */
+
+       if (mbedtls_pk_write_key_pem(&mpk, buf, buf_size)) {
+               lwsl_notice("write key pem failed\n");
+               goto fail1;
+       }
+
+       *privkey_pem = (char *)buf;
+       *privkey_len = strlen((const char *)buf);
+
+       mbedtls_pk_free(&mpk);
+       mbedtls_x509write_csr_free(&csr);
+
+       return n;
+
+fail1:
+       mbedtls_pk_free(&mpk);
+fail:
+       mbedtls_x509write_csr_free(&csr);
+       free(buf);
+
+       return -1;
+}
+#endif
+#endif
diff --git a/lib/tls/mbedtls/private.h b/lib/tls/mbedtls/private.h
new file mode 100644 (file)
index 0000000..a87a4a4
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  gencrypto mbedtls-specific helper declarations
+ */
+
+#include <mbedtls/x509_crl.h>
+
+struct lws_x509_cert {
+       mbedtls_x509_crt cert; /* has a .next for linked-list / chain */
+};
+
+mbedtls_md_type_t
+lws_gencrypto_mbedtls_hash_to_MD_TYPE(enum lws_genhash_types hash_type);
+
+int
+lws_gencrypto_mbedtls_rngf(void *context, unsigned char *buf, size_t len);
diff --git a/lib/tls/mbedtls/ssl.c b/lib/tls/mbedtls/ssl.c
new file mode 100644 (file)
index 0000000..4e8d20b
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * libwebsockets - mbedTLS-specific lws apis
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/mbedtls/private.h"
+
+
+LWS_VISIBLE void
+lws_ssl_destroy(struct lws_vhost *vhost)
+{
+       if (!lws_check_opt(vhost->context->options,
+                          LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
+               return;
+
+       if (vhost->tls.ssl_ctx)
+               SSL_CTX_free(vhost->tls.ssl_ctx);
+       if (!vhost->tls.user_supplied_ssl_ctx && vhost->tls.ssl_client_ctx)
+               SSL_CTX_free(vhost->tls.ssl_client_ctx);
+
+       if (vhost->tls.x509_client_CA)
+               X509_free(vhost->tls.x509_client_CA);
+}
+
+LWS_VISIBLE int
+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, m;
+
+       if (!wsi->tls.ssl)
+               return lws_ssl_capable_read_no_ssl(wsi, buf, len);
+
+       lws_stats_bump(pt, LWSSTATS_C_API_READ, 1);
+
+       errno = 0;
+       n = SSL_read(wsi->tls.ssl, buf, len);
+#if defined(LWS_WITH_ESP32)
+       if (!n && errno == LWS_ENOTCONN) {
+               lwsl_debug("%p: SSL_read ENOTCONN\n", wsi);
+               return LWS_SSL_CAPABLE_ERROR;
+       }
+#endif
+#if defined(LWS_WITH_STATS)
+       if (!wsi->seen_rx && wsi->accept_start_us) {
+                lws_stats_bump(pt, LWSSTATS_US_SSL_RX_DELAY_AVG,
+                       lws_now_usecs() - wsi->accept_start_us);
+                lws_stats_bump(pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1);
+               wsi->seen_rx = 1;
+       }
+#endif
+
+
+       lwsl_debug("%p: SSL_read says %d\n", wsi, n);
+       /* manpage: returning 0 means connection shut down */
+       if (!n) {
+               wsi->socket_is_permanently_unusable = 1;
+
+               return LWS_SSL_CAPABLE_ERROR;
+       }
+
+       if (n < 0) {
+               m = SSL_get_error(wsi->tls.ssl, n);
+               lwsl_debug("%p: ssl err %d errno %d\n", wsi, m, errno);
+               if (errno == LWS_ENOTCONN) {
+                       /* If the socket isn't connected anymore, bail out. */
+                       wsi->socket_is_permanently_unusable = 1;
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+               if (m == SSL_ERROR_ZERO_RETURN ||
+                   m == SSL_ERROR_SYSCALL)
+                       return LWS_SSL_CAPABLE_ERROR;
+
+               if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) {
+                       lwsl_debug("%s: WANT_READ\n", __func__);
+                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+               if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) {
+                       lwsl_debug("%s: WANT_WRITE\n", __func__);
+                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+               wsi->socket_is_permanently_unusable = 1;
+
+               return LWS_SSL_CAPABLE_ERROR;
+       }
+
+       lws_stats_bump(pt, LWSSTATS_B_READ, n);
+
+       if (wsi->vhost)
+               wsi->vhost->conn_stats.rx += n;
+
+       /*
+        * if it was our buffer that limited what we read,
+        * check if SSL has additional data pending inside SSL buffers.
+        *
+        * Because these won't signal at the network layer with POLLIN
+        * and if we don't realize, this data will sit there forever
+        */
+       if (n != len)
+               goto bail;
+       if (!wsi->tls.ssl)
+               goto bail;
+
+       if (SSL_pending(wsi->tls.ssl) &&
+           lws_dll2_is_detached(&wsi->tls.dll_pending_tls))
+               lws_dll2_add_head(&wsi->tls.dll_pending_tls,
+                                &pt->tls.dll_pending_tls_owner);
+
+       return n;
+bail:
+       lws_ssl_remove_wsi_from_buffered_list(wsi);
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_ssl_pending(struct lws *wsi)
+{
+       if (!wsi->tls.ssl)
+               return 0;
+
+       return SSL_pending(wsi->tls.ssl);
+}
+
+LWS_VISIBLE int
+lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len)
+{
+       int n, m;
+
+       if (!wsi->tls.ssl)
+               return lws_ssl_capable_write_no_ssl(wsi, buf, len);
+
+       n = SSL_write(wsi->tls.ssl, buf, len);
+       if (n > 0)
+               return n;
+
+       m = SSL_get_error(wsi->tls.ssl, n);
+       if (m != SSL_ERROR_SYSCALL) {
+               if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) {
+                       lwsl_notice("%s: want read\n", __func__);
+
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+
+               if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) {
+                       lws_set_blocking_send(wsi);
+                       lwsl_debug("%s: want write\n", __func__);
+
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+       }
+
+       lwsl_debug("%s failed: %d\n",__func__, m);
+       wsi->socket_is_permanently_unusable = 1;
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+int openssl_SSL_CTX_private_data_index;
+
+void
+lws_ssl_info_callback(const SSL *ssl, int where, int ret)
+{
+       struct lws *wsi;
+       struct lws_context *context;
+       struct lws_ssl_info si;
+
+       context = (struct lws_context *)SSL_CTX_get_ex_data(
+                                       SSL_get_SSL_CTX(ssl),
+                                       openssl_SSL_CTX_private_data_index);
+       if (!context)
+               return;
+       wsi = wsi_from_fd(context, SSL_get_fd(ssl));
+       if (!wsi)
+               return;
+
+       if (!(where & wsi->vhost->tls.ssl_info_event_mask))
+               return;
+
+       si.where = where;
+       si.ret = ret;
+
+       if (user_callback_handle_rxflow(wsi->protocol->callback,
+                                       wsi, LWS_CALLBACK_SSL_INFO,
+                                       wsi->user_space, &si, 0))
+               lws_set_timeout(wsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, -1);
+}
+
+
+LWS_VISIBLE int
+lws_ssl_close(struct lws *wsi)
+{
+       lws_sockfd_type n;
+
+       if (!wsi->tls.ssl)
+               return 0; /* not handled */
+
+#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
+       /* kill ssl callbacks, becausse we will remove the fd from the
+        * table linking it to the wsi
+        */
+       if (wsi->vhost->tls.ssl_info_event_mask)
+               SSL_set_info_callback(wsi->tls.ssl, NULL);
+#endif
+
+       n = SSL_get_fd(wsi->tls.ssl);
+       if (!wsi->socket_is_permanently_unusable)
+               SSL_shutdown(wsi->tls.ssl);
+       compatible_close(n);
+       SSL_free(wsi->tls.ssl);
+       wsi->tls.ssl = NULL;
+
+       if (!lwsi_role_client(wsi) &&
+           wsi->context->simultaneous_ssl_restriction &&
+           wsi->context->simultaneous_ssl-- ==
+                           wsi->context->simultaneous_ssl_restriction)
+               /* we made space and can do an accept */
+               lws_gate_accepts(wsi->context, 1);
+
+#if defined(LWS_WITH_STATS)
+       wsi->context->updated = 1;
+#endif
+
+       return 1; /* handled */
+}
+
+void
+lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost)
+{
+       if (vhost->tls.ssl_ctx)
+               SSL_CTX_free(vhost->tls.ssl_ctx);
+
+       if (!vhost->tls.user_supplied_ssl_ctx && vhost->tls.ssl_client_ctx)
+               SSL_CTX_free(vhost->tls.ssl_client_ctx);
+#if defined(LWS_WITH_ACME)
+       lws_tls_acme_sni_cert_destroy(vhost);
+#endif
+}
+
+void
+lws_ssl_context_destroy(struct lws_context *context)
+{
+}
+
+lws_tls_ctx *
+lws_tls_ctx_from_wsi(struct lws *wsi)
+{
+       if (!wsi->tls.ssl)
+               return NULL;
+
+       return SSL_get_SSL_CTX(wsi->tls.ssl);
+}
+
+enum lws_ssl_capable_status
+__lws_tls_shutdown(struct lws *wsi)
+{
+       int n = SSL_shutdown(wsi->tls.ssl);
+
+       lwsl_debug("SSL_shutdown=%d for fd %d\n", n, wsi->desc.sockfd);
+
+       switch (n) {
+       case 1: /* successful completion */
+               n = shutdown(wsi->desc.sockfd, SHUT_WR);
+               return LWS_SSL_CAPABLE_DONE;
+
+       case 0: /* needs a retry */
+               __lws_change_pollfd(wsi, 0, LWS_POLLIN);
+               return LWS_SSL_CAPABLE_MORE_SERVICE;
+
+       default: /* fatal error, or WANT */
+               n = SSL_get_error(wsi->tls.ssl, n);
+               if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) {
+                       if (SSL_want_read(wsi->tls.ssl)) {
+                               lwsl_debug("(wants read)\n");
+                               __lws_change_pollfd(wsi, 0, LWS_POLLIN);
+                               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+                       }
+                       if (SSL_want_write(wsi->tls.ssl)) {
+                               lwsl_debug("(wants write)\n");
+                               __lws_change_pollfd(wsi, 0, LWS_POLLOUT);
+                               return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;
+                       }
+               }
+               return LWS_SSL_CAPABLE_ERROR;
+       }
+}
+
+
+static int
+tops_fake_POLLIN_for_buffered_mbedtls(struct lws_context_per_thread *pt)
+{
+       return lws_tls_fake_POLLIN_for_buffered(pt);
+}
+
+const struct lws_tls_ops tls_ops_mbedtls = {
+       /* fake_POLLIN_for_buffered */  tops_fake_POLLIN_for_buffered_mbedtls,
+};
diff --git a/lib/tls/mbedtls/tls.c b/lib/tls/mbedtls/tls.c
new file mode 100644 (file)
index 0000000..ecb0949
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * libwebsockets - mbedTLS-specific lws apis
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/mbedtls/private.h"
+
+void
+lws_tls_err_describe_clear(void)
+{
+}
+
+int
+lws_context_init_ssl_library(const struct lws_context_creation_info *info)
+{
+       lwsl_info(" Compiled with MbedTLS support\n");
+
+       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
+               lwsl_info(" SSL disabled: no "
+                         "LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT\n");
+
+       return 0;
+}
+
+void
+lws_context_deinit_ssl_library(struct lws_context *context)
+{
+
+}
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl3.h b/lib/tls/mbedtls/wrapper/include/internal/ssl3.h
new file mode 100644 (file)
index 0000000..007b392
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL3_H_
+#define _SSL3_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+# define SSL3_AD_CLOSE_NOTIFY             0
+# define SSL3_AD_UNEXPECTED_MESSAGE      10/* fatal */
+# define SSL3_AD_BAD_RECORD_MAC          20/* fatal */
+# define SSL3_AD_DECOMPRESSION_FAILURE   30/* fatal */
+# define SSL3_AD_HANDSHAKE_FAILURE       40/* fatal */
+# define SSL3_AD_NO_CERTIFICATE          41
+# define SSL3_AD_BAD_CERTIFICATE         42
+# define SSL3_AD_UNSUPPORTED_CERTIFICATE 43
+# define SSL3_AD_CERTIFICATE_REVOKED     44
+# define SSL3_AD_CERTIFICATE_EXPIRED     45
+# define SSL3_AD_CERTIFICATE_UNKNOWN     46
+# define SSL3_AD_ILLEGAL_PARAMETER       47/* fatal */
+
+# define SSL3_AL_WARNING                  1
+# define SSL3_AL_FATAL                    2
+
+#define SSL3_VERSION                 0x0300
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_cert.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_cert.h
new file mode 100644 (file)
index 0000000..86cf31a
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_CERT_H_
+#define _SSL_CERT_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "ssl_types.h"
+
+/**
+ * @brief create a certification object include private key object according to input certification
+ *
+ * @param ic - input certification point
+ *
+ * @return certification object point
+ */
+CERT *__ssl_cert_new(CERT *ic);
+
+/**
+ * @brief create a certification object include private key object
+ *
+ * @param none
+ *
+ * @return certification object point
+ */
+CERT* ssl_cert_new(void);
+
+/**
+ * @brief free a certification object
+ *
+ * @param cert - certification object point
+ *
+ * @return none
+ */
+void ssl_cert_free(CERT *cert);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_code.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_code.h
new file mode 100644 (file)
index 0000000..80fdbb2
--- /dev/null
@@ -0,0 +1,124 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_CODE_H_
+#define _SSL_CODE_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "ssl3.h"
+#include "tls1.h"
+#include "x509_vfy.h"
+
+/* Used in SSL_set_shutdown()/SSL_get_shutdown(); */
+# define SSL_SENT_SHUTDOWN       1
+# define SSL_RECEIVED_SHUTDOWN   2
+
+# define SSL_VERIFY_NONE                 0x00
+# define SSL_VERIFY_PEER                 0x01
+# define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
+# define SSL_VERIFY_CLIENT_ONCE          0x04
+
+/*
+ * The following 3 states are kept in ssl->rlayer.rstate when reads fail, you
+ * should not need these
+ */
+# define SSL_ST_READ_HEADER                      0xF0
+# define SSL_ST_READ_BODY                        0xF1
+# define SSL_ST_READ_DONE                        0xF2
+
+# define SSL_NOTHING            1
+# define SSL_WRITING            2
+# define SSL_READING            3
+# define SSL_X509_LOOKUP        4
+# define SSL_ASYNC_PAUSED       5
+# define SSL_ASYNC_NO_JOBS      6
+
+
+# define SSL_ERROR_NONE                  0
+# define SSL_ERROR_SSL                   1
+# define SSL_ERROR_WANT_READ             2
+# define SSL_ERROR_WANT_WRITE            3
+# define SSL_ERROR_WANT_X509_LOOKUP      4
+# define SSL_ERROR_SYSCALL               5/* look at error stack/return value/errno */
+# define SSL_ERROR_ZERO_RETURN           6
+# define SSL_ERROR_WANT_CONNECT          7
+# define SSL_ERROR_WANT_ACCEPT           8
+# define SSL_ERROR_WANT_ASYNC            9
+# define SSL_ERROR_WANT_ASYNC_JOB       10
+
+/* Message flow states */
+typedef enum {
+    /* No handshake in progress */
+    MSG_FLOW_UNINITED,
+    /* A permanent error with this connection */
+    MSG_FLOW_ERROR,
+    /* We are about to renegotiate */
+    MSG_FLOW_RENEGOTIATE,
+    /* We are reading messages */
+    MSG_FLOW_READING,
+    /* We are writing messages */
+    MSG_FLOW_WRITING,
+    /* Handshake has finished */
+    MSG_FLOW_FINISHED
+} MSG_FLOW_STATE;
+
+/* SSL subsystem states */
+typedef enum {
+    TLS_ST_BEFORE,
+    TLS_ST_OK,
+    DTLS_ST_CR_HELLO_VERIFY_REQUEST,
+    TLS_ST_CR_SRVR_HELLO,
+    TLS_ST_CR_CERT,
+    TLS_ST_CR_CERT_STATUS,
+    TLS_ST_CR_KEY_EXCH,
+    TLS_ST_CR_CERT_REQ,
+    TLS_ST_CR_SRVR_DONE,
+    TLS_ST_CR_SESSION_TICKET,
+    TLS_ST_CR_CHANGE,
+    TLS_ST_CR_FINISHED,
+    TLS_ST_CW_CLNT_HELLO,
+    TLS_ST_CW_CERT,
+    TLS_ST_CW_KEY_EXCH,
+    TLS_ST_CW_CERT_VRFY,
+    TLS_ST_CW_CHANGE,
+    TLS_ST_CW_NEXT_PROTO,
+    TLS_ST_CW_FINISHED,
+    TLS_ST_SW_HELLO_REQ,
+    TLS_ST_SR_CLNT_HELLO,
+    DTLS_ST_SW_HELLO_VERIFY_REQUEST,
+    TLS_ST_SW_SRVR_HELLO,
+    TLS_ST_SW_CERT,
+    TLS_ST_SW_KEY_EXCH,
+    TLS_ST_SW_CERT_REQ,
+    TLS_ST_SW_SRVR_DONE,
+    TLS_ST_SR_CERT,
+    TLS_ST_SR_KEY_EXCH,
+    TLS_ST_SR_CERT_VRFY,
+    TLS_ST_SR_NEXT_PROTO,
+    TLS_ST_SR_CHANGE,
+    TLS_ST_SR_FINISHED,
+    TLS_ST_SW_SESSION_TICKET,
+    TLS_ST_SW_CERT_STATUS,
+    TLS_ST_SW_CHANGE,
+    TLS_ST_SW_FINISHED
+} OSSL_HANDSHAKE_STATE;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_dbg.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_dbg.h
new file mode 100644 (file)
index 0000000..ad32cb9
--- /dev/null
@@ -0,0 +1,190 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_DEBUG_H_
+#define _SSL_DEBUG_H_
+
+#include "platform/ssl_port.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#ifdef CONFIG_OPENSSL_DEBUG_LEVEL
+    #define SSL_DEBUG_LEVEL CONFIG_OPENSSL_DEBUG_LEVEL
+#else
+    #define SSL_DEBUG_LEVEL 0
+#endif
+
+#define SSL_DEBUG_ON  (SSL_DEBUG_LEVEL + 1)
+#define SSL_DEBUG_OFF (SSL_DEBUG_LEVEL - 1)
+
+#ifdef CONFIG_OPENSSL_DEBUG
+    #ifndef SSL_DEBUG_LOG
+        #error "SSL_DEBUG_LOG is not defined"
+    #endif
+
+    #ifndef SSL_DEBUG_FL
+        #define SSL_DEBUG_FL "\n"
+    #endif
+
+    #define SSL_SHOW_LOCATION()                         \
+        SSL_DEBUG_LOG("SSL assert : %s %d\n",           \
+            __FILE__, __LINE__)
+
+    #define SSL_DEBUG(level, fmt, ...)                  \
+    {                                                   \
+        if (level > SSL_DEBUG_LEVEL) {                  \
+            SSL_DEBUG_LOG(fmt SSL_DEBUG_FL, ##__VA_ARGS__); \
+        }                                               \
+    }
+#else /* CONFIG_OPENSSL_DEBUG */
+    #define SSL_SHOW_LOCATION()
+
+    #define SSL_DEBUG(level, fmt, ...)
+#endif /* CONFIG_OPENSSL_DEBUG */
+
+/**
+ * OpenSSL assert function
+ *
+ * if select "CONFIG_OPENSSL_ASSERT_DEBUG", SSL_ASSERT* will show error file name and line
+ * if select "CONFIG_OPENSSL_ASSERT_EXIT", SSL_ASSERT* will just return error code.
+ * if select "CONFIG_OPENSSL_ASSERT_DEBUG_EXIT" SSL_ASSERT* will show error file name and line,
+ * then return error code.
+ * if select "CONFIG_OPENSSL_ASSERT_DEBUG_BLOCK", SSL_ASSERT* will show error file name and line,
+ * then block here with "while (1)"
+ *
+ * SSL_ASSERT1 may will return "-1", so function's return argument is integer.
+ * SSL_ASSERT2 may will return "NULL", so function's return argument is a point.
+ * SSL_ASSERT2 may will return nothing, so function's return argument is "void".
+ */
+#if defined(CONFIG_OPENSSL_ASSERT_DEBUG)
+    #define SSL_ASSERT1(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+        }                                               \
+    }
+
+    #define SSL_ASSERT2(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+        }                                               \
+    }
+
+    #define SSL_ASSERT3(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+        }                                               \
+    }
+#elif defined(CONFIG_OPENSSL_ASSERT_EXIT)
+    #define SSL_ASSERT1(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            return -1;                                  \
+        }                                               \
+    }
+
+    #define SSL_ASSERT2(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            return NULL;                                \
+        }                                               \
+    }
+
+    #define SSL_ASSERT3(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            return ;                                    \
+        }                                               \
+    }
+#elif defined(CONFIG_OPENSSL_ASSERT_DEBUG_EXIT)
+    #define SSL_ASSERT1(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+            return -1;                                  \
+        }                                               \
+    }
+
+    #define SSL_ASSERT2(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+            return NULL;                                \
+        }                                               \
+    }
+
+    #define SSL_ASSERT3(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+            return ;                                    \
+        }                                               \
+    }
+#elif defined(CONFIG_OPENSSL_ASSERT_DEBUG_BLOCK)
+    #define SSL_ASSERT1(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+            while (1);                                  \
+        }                                               \
+    }
+
+    #define SSL_ASSERT2(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+            while (1);                                  \
+        }                                               \
+    }
+
+    #define SSL_ASSERT3(s)                              \
+    {                                                   \
+        if (!(s)) {                                     \
+            SSL_SHOW_LOCATION();                        \
+            while (1);                                  \
+        }                                               \
+    }
+#else
+    #define SSL_ASSERT1(s)
+    #define SSL_ASSERT2(s)
+    #define SSL_ASSERT3(s)
+#endif
+
+#define SSL_PLATFORM_DEBUG_LEVEL SSL_DEBUG_OFF
+#define SSL_PLATFORM_ERROR_LEVEL SSL_DEBUG_ON
+
+#define SSL_CERT_DEBUG_LEVEL     SSL_DEBUG_OFF
+#define SSL_CERT_ERROR_LEVEL     SSL_DEBUG_ON
+
+#define SSL_PKEY_DEBUG_LEVEL     SSL_DEBUG_OFF
+#define SSL_PKEY_ERROR_LEVEL     SSL_DEBUG_ON
+
+#define SSL_X509_DEBUG_LEVEL     SSL_DEBUG_OFF
+#define SSL_X509_ERROR_LEVEL     SSL_DEBUG_ON
+
+#define SSL_LIB_DEBUG_LEVEL      SSL_DEBUG_OFF
+#define SSL_LIB_ERROR_LEVEL      SSL_DEBUG_ON
+
+#define SSL_STACK_DEBUG_LEVEL    SSL_DEBUG_OFF
+#define SSL_STACK_ERROR_LEVEL    SSL_DEBUG_ON
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_lib.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_lib.h
new file mode 100644 (file)
index 0000000..42b2de7
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_LIB_H_
+#define _SSL_LIB_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "ssl_types.h"
+
+ void _ssl_set_alpn_list(const SSL *ssl);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_methods.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_methods.h
new file mode 100644 (file)
index 0000000..cd2f8c0
--- /dev/null
@@ -0,0 +1,121 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_METHODS_H_
+#define _SSL_METHODS_H_
+
+#include "ssl_types.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/**
+ * TLS method function implement
+ */
+#define IMPLEMENT_TLS_METHOD_FUNC(func_name, \
+                    new, free, \
+                    handshake, shutdown, clear, \
+                    read, send, pending, \
+                    set_fd, get_fd, \
+                    set_bufflen, \
+                    get_verify_result, \
+                    get_state) \
+        static const SSL_METHOD_FUNC func_name LOCAL_ATRR = { \
+                new, \
+                free, \
+                handshake, \
+                shutdown, \
+                clear, \
+                read, \
+                send, \
+                pending, \
+                set_fd, \
+                get_fd, \
+                set_bufflen, \
+                get_verify_result, \
+                get_state \
+        };
+
+#define IMPLEMENT_TLS_METHOD(ver, mode, fun, func_name) \
+    const SSL_METHOD* func_name(void) { \
+        static const SSL_METHOD func_name##_data LOCAL_ATRR = { \
+                ver, \
+                mode, \
+                &(fun), \
+        }; \
+        return &func_name##_data; \
+    }
+
+#define IMPLEMENT_SSL_METHOD(ver, mode, fun, func_name) \
+    const SSL_METHOD* func_name(void) { \
+        static const SSL_METHOD func_name##_data LOCAL_ATRR = { \
+                ver, \
+                mode, \
+                &(fun), \
+        }; \
+        return &func_name##_data; \
+    }
+
+#define IMPLEMENT_X509_METHOD(func_name, \
+                new, \
+                free, \
+                load, \
+                show_info) \
+    const X509_METHOD* func_name(void) { \
+        static const X509_METHOD func_name##_data LOCAL_ATRR = { \
+                new, \
+                free, \
+                load, \
+                show_info \
+        }; \
+        return &func_name##_data; \
+    }
+
+#define IMPLEMENT_PKEY_METHOD(func_name, \
+                new, \
+                free, \
+                load) \
+    const PKEY_METHOD* func_name(void) { \
+        static const PKEY_METHOD func_name##_data LOCAL_ATRR = { \
+                new, \
+                free, \
+                load \
+        }; \
+        return &func_name##_data; \
+    }
+
+/**
+ * @brief get X509 object method
+ *
+ * @param none
+ *
+ * @return X509 object method point
+ */
+const X509_METHOD* X509_method(void);
+
+/**
+ * @brief get private key object method
+ *
+ * @param none
+ *
+ * @return private key object method point
+ */
+const PKEY_METHOD* EVP_PKEY_method(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_pkey.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_pkey.h
new file mode 100644 (file)
index 0000000..e790fcc
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_PKEY_H_
+#define _SSL_PKEY_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "ssl_types.h"
+
+/**
+ * @brief create a private key object according to input private key
+ *
+ * @param ipk - input private key point
+ *
+ * @return new private key object point
+ */
+EVP_PKEY* __EVP_PKEY_new(EVP_PKEY *ipk);
+
+/**
+ * @brief create a private key object
+ *
+ * @param none
+ *
+ * @return private key object point
+ */
+EVP_PKEY* EVP_PKEY_new(void);
+
+/**
+ * @brief load a character key context into system context. If '*a' is pointed to the
+ *        private key, then load key into it. Or create a new private key object
+ *
+ * @param type   - private key type
+ * @param a      - a point pointed to a private key point
+ * @param pp     - a point pointed to the key context memory point
+ * @param length - key bytes
+ *
+ * @return private key object point
+ */
+EVP_PKEY* d2i_PrivateKey(int type,
+                         EVP_PKEY **a,
+                         const unsigned char **pp,
+                         long length);
+
+/**
+ * @brief free a private key object
+ *
+ * @param pkey - private key object point
+ *
+ * @return none
+ */
+void EVP_PKEY_free(EVP_PKEY *x);
+
+/**
+ * @brief load private key into the SSL
+ *
+ * @param type - private key type
+ * @param ssl  - SSL point
+ * @param len  - data bytes
+ * @param d    - data point
+ *
+ * @return result
+ *     0 : failed
+ *     1 : OK 
+ */
+ int SSL_use_PrivateKey_ASN1(int type, SSL *ssl, const unsigned char *d, long len);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_stack.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_stack.h
new file mode 100644 (file)
index 0000000..7a7051a
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef _SSL_STACK_H_
+#define _SSL_STACK_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "ssl_types.h"
+
+#define STACK_OF(type)  struct stack_st_##type
+
+#define SKM_DEFINE_STACK_OF(t1, t2, t3) \
+    STACK_OF(t1); \
+    static ossl_inline STACK_OF(t1) *sk_##t1##_new_null(void) \
+    { \
+        return (STACK_OF(t1) *)OPENSSL_sk_new_null(); \
+    } \
+
+#define DEFINE_STACK_OF(t) SKM_DEFINE_STACK_OF(t, t, t)
+
+/**
+ * @brief create a openssl stack object
+ *
+ * @param c - stack function
+ *
+ * @return openssl stack object point
+ */
+OPENSSL_STACK* OPENSSL_sk_new(OPENSSL_sk_compfunc c);
+
+/**
+ * @brief create a NULL function openssl stack object
+ *
+ * @param none
+ *
+ * @return openssl stack object point
+ */
+OPENSSL_STACK *OPENSSL_sk_new_null(void);
+
+/**
+ * @brief free openssl stack object
+ *
+ * @param openssl stack object point
+ *
+ * @return none
+ */
+void OPENSSL_sk_free(OPENSSL_STACK *stack);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_types.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_types.h
new file mode 100644 (file)
index 0000000..1f5f948
--- /dev/null
@@ -0,0 +1,311 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_TYPES_H_
+#define _SSL_TYPES_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//#include "core/private.h"
+#include <lws_config.h>
+#if defined(LWS_WITH_ESP32)
+ /* AMAZON RTOS has its own setting via MTK_MBEDTLS_CONFIG_FILE */
+ #if !defined(LWS_AMAZON_RTOS)
+  #undef MBEDTLS_CONFIG_FILE
+  #define MBEDTLS_CONFIG_FILE <mbedtls/esp_config.h>
+ #endif
+#endif
+
+#include "ssl_code.h"
+
+typedef void SSL_CIPHER;
+
+typedef void X509_STORE_CTX;
+typedef void X509_STORE;
+
+typedef void RSA;
+
+typedef void STACK;
+typedef void BIO;
+
+#if defined(WIN32) || defined(_WIN32)
+#define ossl_inline __inline
+#else
+#define ossl_inline inline
+#endif
+
+#define SSL_METHOD_CALL(f, s, ...)        s->method->func->ssl_##f(s, ##__VA_ARGS__)
+#define X509_METHOD_CALL(f, x, ...)       x->method->x509_##f(x, ##__VA_ARGS__)
+#define EVP_PKEY_METHOD_CALL(f, k, ...)   k->method->pkey_##f(k, ##__VA_ARGS__)
+
+typedef int (*OPENSSL_sk_compfunc)(const void *, const void *);
+
+struct stack_st;
+typedef struct stack_st OPENSSL_STACK;
+
+struct ssl_method_st;
+typedef struct ssl_method_st SSL_METHOD;
+
+struct ssl_method_func_st;
+typedef struct ssl_method_func_st SSL_METHOD_FUNC;
+
+struct record_layer_st;
+typedef struct record_layer_st RECORD_LAYER;
+
+struct ossl_statem_st;
+typedef struct ossl_statem_st OSSL_STATEM;
+
+struct ssl_session_st;
+typedef struct ssl_session_st SSL_SESSION;
+
+struct ssl_ctx_st;
+typedef struct ssl_ctx_st SSL_CTX;
+
+struct ssl_st;
+typedef struct ssl_st SSL;
+
+struct cert_st;
+typedef struct cert_st CERT;
+
+struct x509_st;
+typedef struct x509_st X509;
+
+struct X509_VERIFY_PARAM_st;
+typedef struct X509_VERIFY_PARAM_st X509_VERIFY_PARAM;
+
+struct evp_pkey_st;
+typedef struct evp_pkey_st EVP_PKEY;
+
+struct x509_method_st;
+typedef struct x509_method_st X509_METHOD;
+
+struct pkey_method_st;
+typedef struct pkey_method_st PKEY_METHOD;
+
+struct stack_st {
+
+    char **data;
+
+    int num_alloc;
+
+    OPENSSL_sk_compfunc c;
+};
+
+struct evp_pkey_st {
+
+    void *pkey_pm;
+
+    const PKEY_METHOD *method;
+};
+
+struct x509_st {
+
+    /* X509 certification platform private point */
+    void *x509_pm;
+
+    const X509_METHOD *method;
+};
+
+struct cert_st {
+
+    int sec_level;
+
+    X509 *x509;
+
+    EVP_PKEY *pkey;
+
+};
+
+struct ossl_statem_st {
+
+    MSG_FLOW_STATE state;
+
+    int hand_state;
+};
+
+struct record_layer_st {
+
+    int rstate;
+
+    int read_ahead;
+};
+
+struct ssl_session_st {
+
+    long timeout;
+
+    long time;
+
+    X509 *peer;
+};
+
+struct X509_VERIFY_PARAM_st {
+
+    int depth;
+
+};
+
+typedef int (*next_proto_cb)(SSL *ssl, const unsigned char **out,
+                             unsigned char *outlen, const unsigned char *in,
+                             unsigned int inlen, void *arg);
+
+
+struct ssl_ctx_st
+{
+    int version;
+
+    int references;
+
+    unsigned long options;
+
+    const SSL_METHOD *method;
+
+    CERT *cert;
+
+    X509 *client_CA;
+
+    const char **alpn_protos;
+
+    next_proto_cb alpn_cb;
+
+    int verify_mode;
+
+    int (*default_verify_callback) (int ok, X509_STORE_CTX *ctx);
+
+    long session_timeout;
+
+    int read_ahead;
+
+    int read_buffer_len;
+
+    X509_VERIFY_PARAM param;
+};
+
+struct ssl_st
+{
+    /* protocol version(one of SSL3.0, TLS1.0, etc.) */
+    int version;
+
+    unsigned long options;
+
+    /* shut things down(0x01 : sent, 0x02 : received) */
+    int shutdown;
+
+    CERT *cert;
+
+    X509 *client_CA;
+
+    SSL_CTX  *ctx;
+
+    const SSL_METHOD *method;
+
+    const char **alpn_protos;
+
+    RECORD_LAYER rlayer;
+
+    /* where we are */
+    OSSL_STATEM statem;
+
+    SSL_SESSION *session;
+
+    int verify_mode;
+
+    int (*verify_callback) (int ok, X509_STORE_CTX *ctx);
+
+    int rwstate;
+    int interrupted_remaining_write;
+
+    long verify_result;
+
+    X509_VERIFY_PARAM param;
+
+    int err;
+
+    void (*info_callback) (const SSL *ssl, int type, int val);
+
+    /* SSL low-level system arch point */
+    void *ssl_pm;
+};
+
+struct ssl_method_st {
+    /* protocol version(one of SSL3.0, TLS1.0, etc.) */
+    int version;
+
+    /* SSL mode(client(0) , server(1), not known(-1)) */
+    int endpoint;
+
+    const SSL_METHOD_FUNC *func;
+};
+
+struct ssl_method_func_st {
+
+    int (*ssl_new)(SSL *ssl);
+
+    void (*ssl_free)(SSL *ssl);
+
+    int (*ssl_handshake)(SSL *ssl);
+
+    int (*ssl_shutdown)(SSL *ssl);
+
+    int (*ssl_clear)(SSL *ssl);
+
+    int (*ssl_read)(SSL *ssl, void *buffer, int len);
+
+    int (*ssl_send)(SSL *ssl, const void *buffer, int len);
+
+    int (*ssl_pending)(const SSL *ssl);
+
+    void (*ssl_set_fd)(SSL *ssl, int fd, int mode);
+
+    int (*ssl_get_fd)(const SSL *ssl, int mode);
+
+    void (*ssl_set_bufflen)(SSL *ssl, int len);
+
+    long (*ssl_get_verify_result)(const SSL *ssl);
+
+    OSSL_HANDSHAKE_STATE (*ssl_get_state)(const SSL *ssl);
+};
+
+struct x509_method_st {
+
+    int (*x509_new)(X509 *x, X509 *m_x);
+
+    void (*x509_free)(X509 *x);
+
+    int (*x509_load)(X509 *x, const unsigned char *buf, int len);
+
+    int (*x509_show_info)(X509 *x);
+};
+
+struct pkey_method_st {
+
+    int (*pkey_new)(EVP_PKEY *pkey, EVP_PKEY *m_pkey);
+
+    void (*pkey_free)(EVP_PKEY *pkey);
+
+    int (*pkey_load)(EVP_PKEY *pkey, const unsigned char *buf, int len);
+};
+
+#define OPENSSL_NPN_NEGOTIATED 1
+
+int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx);
+int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/ssl_x509.h b/lib/tls/mbedtls/wrapper/include/internal/ssl_x509.h
new file mode 100644 (file)
index 0000000..7594d06
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_X509_H_
+#define _SSL_X509_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "ssl_types.h"
+#include "ssl_stack.h"
+
+DEFINE_STACK_OF(X509_NAME)
+
+/**
+ * @brief create a X509 certification object according to input X509 certification
+ *
+ * @param ix - input X509 certification point
+ *
+ * @return new X509 certification object point
+ */
+X509* __X509_new(X509 *ix);
+
+/**
+ * @brief create a X509 certification object
+ *
+ * @param none
+ *
+ * @return X509 certification object point
+ */
+X509* X509_new(void);
+
+/**
+ * @brief load a character certification context into system context. If '*cert' is pointed to the
+ *        certification, then load certification into it. Or create a new X509 certification object
+ *
+ * @param cert   - a point pointed to X509 certification
+ * @param buffer - a point pointed to the certification context memory point
+ * @param length - certification bytes
+ *
+ * @return X509 certification object point
+ */
+X509* d2i_X509(X509 **cert, const unsigned char *buffer, long len);
+
+/**
+ * @brief free a X509 certification object
+ *
+ * @param x - X509 certification object point
+ *
+ * @return none
+ */
+void X509_free(X509 *x);
+
+/**
+ * @brief set SSL context client CA certification
+ *
+ * @param ctx - SSL context point
+ * @param x   - X509 certification point
+ *
+ * @return result
+ *     0 : failed
+ *     1 : OK
+ */
+int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x);
+
+/**
+ * @brief add CA client certification into the SSL
+ *
+ * @param ssl - SSL point
+ * @param x   - X509 certification point
+ *
+ * @return result
+ *     0 : failed
+ *     1 : OK
+ */
+int SSL_add_client_CA(SSL *ssl, X509 *x);
+
+/**
+ * @brief load certification into the SSL
+ *
+ * @param ssl - SSL point
+ * @param len - data bytes
+ * @param d   - data point
+ *
+ * @return result
+ *     0 : failed
+ *     1 : OK
+ *
+ */
+int SSL_use_certificate_ASN1(SSL *ssl, int len, const unsigned char *d);
+
+const char *X509_verify_cert_error_string(long n);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/tls1.h b/lib/tls/mbedtls/wrapper/include/internal/tls1.h
new file mode 100644 (file)
index 0000000..7af1b01
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _TLS1_H_
+#define _TLS1_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+# define TLS1_AD_DECRYPTION_FAILED       21
+# define TLS1_AD_RECORD_OVERFLOW         22
+# define TLS1_AD_UNKNOWN_CA              48/* fatal */
+# define TLS1_AD_ACCESS_DENIED           49/* fatal */
+# define TLS1_AD_DECODE_ERROR            50/* fatal */
+# define TLS1_AD_DECRYPT_ERROR           51
+# define TLS1_AD_EXPORT_RESTRICTION      60/* fatal */
+# define TLS1_AD_PROTOCOL_VERSION        70/* fatal */
+# define TLS1_AD_INSUFFICIENT_SECURITY   71/* fatal */
+# define TLS1_AD_INTERNAL_ERROR          80/* fatal */
+# define TLS1_AD_INAPPROPRIATE_FALLBACK  86/* fatal */
+# define TLS1_AD_USER_CANCELLED          90
+# define TLS1_AD_NO_RENEGOTIATION        100
+/* codes 110-114 are from RFC3546 */
+# define TLS1_AD_UNSUPPORTED_EXTENSION   110
+# define TLS1_AD_CERTIFICATE_UNOBTAINABLE 111
+# define TLS1_AD_UNRECOGNIZED_NAME       112
+# define TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE 113
+# define TLS1_AD_BAD_CERTIFICATE_HASH_VALUE 114
+# define TLS1_AD_UNKNOWN_PSK_IDENTITY    115/* fatal */
+# define TLS1_AD_NO_APPLICATION_PROTOCOL 120 /* fatal */
+
+/* Special value for method supporting multiple versions */
+#define TLS_ANY_VERSION                 0x10000
+
+#define TLS1_VERSION                    0x0301
+#define TLS1_1_VERSION                  0x0302
+#define TLS1_2_VERSION                  0x0303
+
+#define SSL_TLSEXT_ERR_OK 0
+#define SSL_TLSEXT_ERR_NOACK 3
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/internal/x509_vfy.h b/lib/tls/mbedtls/wrapper/include/internal/x509_vfy.h
new file mode 100644 (file)
index 0000000..d5b0d1a
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD\r
+//\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
+\r
+//     http://www.apache.org/licenses/LICENSE-2.0\r
+//\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
+\r
+#ifndef _X509_VFY_H_\r
+#define _X509_VFY_H_\r
+\r
+#ifdef __cplusplus\r
+ extern "C" {\r
+#endif\r
+\r
+#define         X509_V_OK                                       0\r
+#define         X509_V_ERR_UNSPECIFIED                          1\r
+#define         X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT            2\r
+#define         X509_V_ERR_UNABLE_TO_GET_CRL                    3\r
+#define         X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE     4\r
+#define         X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE      5\r
+#define         X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY   6\r
+#define         X509_V_ERR_CERT_SIGNATURE_FAILURE               7\r
+#define         X509_V_ERR_CRL_SIGNATURE_FAILURE                8\r
+#define         X509_V_ERR_CERT_NOT_YET_VALID                   9\r
+#define         X509_V_ERR_CERT_HAS_EXPIRED                     10\r
+#define         X509_V_ERR_CRL_NOT_YET_VALID                    11\r
+#define         X509_V_ERR_CRL_HAS_EXPIRED                      12\r
+#define         X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD       13\r
+#define         X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD        14\r
+#define         X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD       15\r
+#define         X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD       16\r
+#define         X509_V_ERR_OUT_OF_MEM                           17\r
+#define         X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT          18\r
+#define         X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN            19\r
+#define         X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY    20\r
+#define         X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE      21\r
+#define         X509_V_ERR_CERT_CHAIN_TOO_LONG                  22\r
+#define         X509_V_ERR_CERT_REVOKED                         23\r
+#define         X509_V_ERR_INVALID_CA                           24\r
+#define         X509_V_ERR_PATH_LENGTH_EXCEEDED                 25\r
+#define         X509_V_ERR_INVALID_PURPOSE                      26\r
+#define         X509_V_ERR_CERT_UNTRUSTED                       27\r
+#define         X509_V_ERR_CERT_REJECTED                        28\r
+/* These are 'informational' when looking for issuer cert */\r
+#define         X509_V_ERR_SUBJECT_ISSUER_MISMATCH              29\r
+#define         X509_V_ERR_AKID_SKID_MISMATCH                   30\r
+#define         X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH          31\r
+#define         X509_V_ERR_KEYUSAGE_NO_CERTSIGN                 32\r
+#define         X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER             33\r
+#define         X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION         34\r
+#define         X509_V_ERR_KEYUSAGE_NO_CRL_SIGN                 35\r
+#define         X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION     36\r
+#define         X509_V_ERR_INVALID_NON_CA                       37\r
+#define         X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED           38\r
+#define         X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE        39\r
+#define         X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED       40\r
+#define         X509_V_ERR_INVALID_EXTENSION                    41\r
+#define         X509_V_ERR_INVALID_POLICY_EXTENSION             42\r
+#define         X509_V_ERR_NO_EXPLICIT_POLICY                   43\r
+#define         X509_V_ERR_DIFFERENT_CRL_SCOPE                  44\r
+#define         X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE        45\r
+#define         X509_V_ERR_UNNESTED_RESOURCE                    46\r
+#define         X509_V_ERR_PERMITTED_VIOLATION                  47\r
+#define         X509_V_ERR_EXCLUDED_VIOLATION                   48\r
+#define         X509_V_ERR_SUBTREE_MINMAX                       49\r
+/* The application is not happy */\r
+#define         X509_V_ERR_APPLICATION_VERIFICATION             50\r
+#define         X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE          51\r
+#define         X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX        52\r
+#define         X509_V_ERR_UNSUPPORTED_NAME_SYNTAX              53\r
+#define         X509_V_ERR_CRL_PATH_VALIDATION_ERROR            54\r
+/* Another issuer check debug option */\r
+#define         X509_V_ERR_PATH_LOOP                            55\r
+/* Suite B mode algorithm violation */\r
+#define         X509_V_ERR_SUITE_B_INVALID_VERSION              56\r
+#define         X509_V_ERR_SUITE_B_INVALID_ALGORITHM            57\r
+#define         X509_V_ERR_SUITE_B_INVALID_CURVE                58\r
+#define         X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM  59\r
+#define         X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED              60\r
+#define         X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256 61\r
+/* Host, email and IP check errors */\r
+#define         X509_V_ERR_HOSTNAME_MISMATCH                    62\r
+#define         X509_V_ERR_EMAIL_MISMATCH                       63\r
+#define         X509_V_ERR_IP_ADDRESS_MISMATCH                  64\r
+/* DANE TLSA errors */\r
+#define         X509_V_ERR_DANE_NO_MATCH                        65\r
+/* security level errors */\r
+#define         X509_V_ERR_EE_KEY_TOO_SMALL                     66\r
+#define         X509_V_ERR_CA_KEY_TOO_SMALL                     67\r
+#define         X509_V_ERR_CA_MD_TOO_WEAK                       68\r
+/* Caller error */\r
+#define         X509_V_ERR_INVALID_CALL                         69\r
+/* Issuer lookup error */\r
+#define         X509_V_ERR_STORE_LOOKUP                         70\r
+/* Certificate transparency */\r
+#define         X509_V_ERR_NO_VALID_SCTS                        71\r
+\r
+#define         X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION         72\r
+\r
+#ifdef __cplusplus\r
+}\r
+#endif\r
+\r
+#endif\r
diff --git a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
new file mode 100755 (executable)
index 0000000..9427283
--- /dev/null
@@ -0,0 +1,1827 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD\r
+//\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
+\r
+//     http://www.apache.org/licenses/LICENSE-2.0\r
+//\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
+\r
+#ifndef _SSL_H_\r
+#define _SSL_H_\r
+\r
+#ifdef __cplusplus\r
+ extern "C" {\r
+#endif\r
+\r
+#include <stdlib.h>\r
+#include "internal/ssl_x509.h"\r
+#include "internal/ssl_pkey.h"\r
+\r
+/*\r
+{\r
+*/\r
+\r
+#define SSL_CB_ALERT 0x4000\r
+\r
+#define X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT           (1 << 0)\r
+#define X509_CHECK_FLAG_NO_WILDCARDS                   (1 << 1)\r
+#define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS           (1 << 2)\r
+#define X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS          (1 << 3)\r
+#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS                (1 << 4)\r
+\r
+ mbedtls_x509_crt *\r
+ ssl_ctx_get_mbedtls_x509_crt(SSL_CTX *ssl_ctx);\r
+\r
+ mbedtls_x509_crt *\r
+ ssl_get_peer_mbedtls_x509_crt(SSL *ssl);\r
+\r
+ int SSL_set_sni_callback(SSL *ssl, int(*cb)(void *, mbedtls_ssl_context *,\r
+                               const unsigned char *, size_t), void *param);\r
+\r
+ void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx);\r
+\r
+ int SSL_CTX_add_client_CA_ASN1(SSL_CTX *ssl, int len,\r
+                 const unsigned char *d);\r
+\r
+ SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc);\r
+\r
+/**\r
+ * @brief create a SSL context\r
+ *\r
+ * @param method - the SSL context method point\r
+ *\r
+ * @return the context point\r
+ */\r
+SSL_CTX* SSL_CTX_new(const SSL_METHOD *method);\r
+\r
+/**\r
+ * @brief free a SSL context\r
+ *\r
+ * @param method - the SSL context point\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_free(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief create a SSL\r
+ *\r
+ * @param ctx - the SSL context point\r
+ *\r
+ * @return the SSL point\r
+ */\r
+SSL* SSL_new(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief free the SSL\r
+ *\r
+ * @param ssl - the SSL point\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_free(SSL *ssl);\r
+\r
+/**\r
+ * @brief connect to the remote SSL server\r
+ *\r
+ * @param ssl - the SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *    -1 : failed\r
+ */\r
+int SSL_connect(SSL *ssl);\r
+\r
+/**\r
+ * @brief accept the remote connection\r
+ *\r
+ * @param ssl - the SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *    -1 : failed\r
+ */\r
+int SSL_accept(SSL *ssl);\r
+\r
+/**\r
+ * @brief read data from to remote\r
+ *\r
+ * @param ssl    - the SSL point which has been connected\r
+ * @param buffer - the received data buffer point\r
+ * @param len    - the received data length\r
+ *\r
+ * @return result\r
+ *     > 0 : OK, and return received data bytes\r
+ *     = 0 : connection is closed\r
+ *     < 0 : an error catch\r
+ */\r
+int SSL_read(SSL *ssl, void *buffer, int len);\r
+\r
+/**\r
+ * @brief send the data to remote\r
+ *\r
+ * @param ssl    - the SSL point which has been connected\r
+ * @param buffer - the send data buffer point\r
+ * @param len    - the send data length\r
+ *\r
+ * @return result\r
+ *     > 0 : OK, and return sent data bytes\r
+ *     = 0 : connection is closed\r
+ *     < 0 : an error catch\r
+ */\r
+int SSL_write(SSL *ssl, const void *buffer, int len);\r
+\r
+/**\r
+ * @brief get the verifying result of the SSL certification\r
+ *\r
+ * @param ssl - the SSL point\r
+ *\r
+ * @return the result of verifying\r
+ */\r
+long SSL_get_verify_result(const SSL *ssl);\r
+\r
+/**\r
+ * @brief shutdown the connection\r
+ *\r
+ * @param ssl - the SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : shutdown is not finished\r
+ *    -1 : an error catch\r
+ */\r
+int SSL_shutdown(SSL *ssl);\r
+\r
+/**\r
+ * @brief bind the socket file description into the SSL\r
+ *\r
+ * @param ssl - the SSL point\r
+ * @param fd  - socket handle\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_set_fd(SSL *ssl, int fd);\r
+\r
+/**\r
+ * @brief These functions load the private key into the SSL_CTX or SSL object\r
+ *\r
+ * @param ctx  - the SSL context point\r
+ * @param pkey - private key object point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey);\r
+\r
+/**\r
+ * @brief These functions load the certification into the SSL_CTX or SSL object\r
+ *\r
+ * @param ctx  - the SSL context point\r
+ * @param pkey - certification object point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x);\r
+\r
+/**\r
+ * @brief create the target SSL context client method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the SSLV2.3 version SSL context client method\r
+ */\r
+const SSL_METHOD* SSLv23_client_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context client method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLSV1.0 version SSL context client method\r
+ */\r
+const SSL_METHOD* TLSv1_client_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context client method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the SSLV1.0 version SSL context client method\r
+ */\r
+const SSL_METHOD* SSLv3_client_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context client method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLSV1.1 version SSL context client method\r
+ */\r
+const SSL_METHOD* TLSv1_1_client_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context client method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLSV1.2 version SSL context client method\r
+ */\r
+const SSL_METHOD* TLSv1_2_client_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLS any version SSL context client method\r
+ */\r
+const SSL_METHOD* TLS_client_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the SSLV2.3 version SSL context server method\r
+ */\r
+const SSL_METHOD* SSLv23_server_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLSV1.1 version SSL context server method\r
+ */\r
+const SSL_METHOD* TLSv1_1_server_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLSV1.2 version SSL context server method\r
+ */\r
+const SSL_METHOD* TLSv1_2_server_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLSV1.0 version SSL context server method\r
+ */\r
+const SSL_METHOD* TLSv1_server_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the SSLV3.0 version SSL context server method\r
+ */\r
+const SSL_METHOD* SSLv3_server_method(void);\r
+\r
+/**\r
+ * @brief create the target SSL context server method\r
+ *\r
+ * @param none\r
+ *\r
+ * @return the TLS any version SSL context server method\r
+ */\r
+const SSL_METHOD* TLS_server_method(void);\r
+\r
+\r
+/**\r
+ * @brief set the SSL context ALPN select callback function\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param cb  - ALPN select callback function\r
+ * @param arg - ALPN select callback function entry private data point\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, next_proto_cb cb,\r
+                                void *arg);\r
+\r
+void SSL_set_alpn_select_cb(SSL *ssl, void *arg);\r
+\r
+/**\r
+ * @brief set the SSL context ALPN select protocol\r
+ *\r
+ * @param ctx        - SSL context point\r
+ * @param protos     - ALPN protocol name\r
+ * @param protos_len - ALPN protocol name bytes\r
+ *\r
+ * @return result\r
+ *     0 : OK\r
+ *     1 : failed\r
+ */\r
+int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos, unsigned int protos_len);\r
+\r
+/**\r
+ * @brief set the SSL context next ALPN select callback function\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param cb  - ALPN select callback function\r
+ * @param arg - ALPN select callback function entry private data point\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_next_proto_select_cb(SSL_CTX *ctx,\r
+                                      int (*cb) (SSL *ssl,\r
+                                                 unsigned char **out,\r
+                                                 unsigned char *outlen,\r
+                                                 const unsigned char *in,\r
+                                                 unsigned int inlen,\r
+                                                 void *arg),\r
+                                      void *arg);\r
+\r
+void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,\r
+                             unsigned int *len);\r
+\r
+void _ssl_set_alpn_list(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL error code\r
+ *\r
+ * @param ssl       - SSL point\r
+ * @param ret_code  - SSL return code\r
+ *\r
+ * @return SSL error number\r
+ */\r
+int SSL_get_error(const SSL *ssl, int ret_code);\r
+\r
+/**\r
+ * @brief clear the SSL error code\r
+ *\r
+ * @param none\r
+ *\r
+ * @return none\r
+ */\r
+void ERR_clear_error(void);\r
+\r
+/**\r
+ * @brief get the current SSL error code\r
+ *\r
+ * @param none\r
+ *\r
+ * @return current SSL error number\r
+ */\r
+int ERR_get_error(void);\r
+\r
+/**\r
+ * @brief register the SSL error strings\r
+ *\r
+ * @param none\r
+ *\r
+ * @return none\r
+ */\r
+void ERR_load_SSL_strings(void);\r
+\r
+/**\r
+ * @brief initialize the SSL library\r
+ *\r
+ * @param none\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_library_init(void);\r
+\r
+/**\r
+ * @brief generates a human-readable string representing the error code e\r
+ *        and store it into the "ret" point memory\r
+ *\r
+ * @param e   - error code\r
+ * @param ret - memory point to store the string\r
+ *\r
+ * @return the result string point\r
+ */\r
+char *ERR_error_string(unsigned long e, char *ret);\r
+\r
+/**\r
+ * @brief add the SSL context option\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param opt - new SSL context option\r
+ *\r
+ * @return the SSL context option\r
+ */\r
+unsigned long SSL_CTX_set_options(SSL_CTX *ctx, unsigned long opt);\r
+\r
+/**\r
+ * @brief add the SSL context mode\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param mod - new SSL context mod\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_mode(SSL_CTX *ctx, int mod);\r
+\r
+/*\r
+}\r
+*/\r
+\r
+/**\r
+ * @brief perform the SSL handshake\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ *    -1 : a error catch\r
+ */\r
+int SSL_do_handshake(SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL current version\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return the version string\r
+ */\r
+const char *SSL_get_version(const SSL *ssl);\r
+\r
+/**\r
+ * @brief set  the SSL context version\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param meth - SSL method point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_ssl_version(SSL_CTX *ctx, const SSL_METHOD *meth);\r
+\r
+/**\r
+ * @brief get the bytes numbers which are to be read\r
+ *\r
+ * @param ssl  - SSL point\r
+ *\r
+ * @return bytes number\r
+ */\r
+int SSL_pending(const SSL *ssl);\r
+\r
+/**\r
+ * @brief check if SSL want nothing\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     0 : false\r
+ *     1 : true\r
+ */\r
+int SSL_want_nothing(const SSL *ssl);\r
+\r
+/**\r
+ * @brief check if SSL want to read\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     0 : false\r
+ *     1 : true\r
+ */\r
+int SSL_want_read(const SSL *ssl);\r
+\r
+/**\r
+ * @brief check if SSL want to write\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     0 : false\r
+ *     1 : true\r
+ */\r
+int SSL_want_write(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL context current method\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return the SSL context current method\r
+ */\r
+const SSL_METHOD *SSL_CTX_get_ssl_method(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief get the SSL current method\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return the SSL current method\r
+ */\r
+const SSL_METHOD *SSL_get_ssl_method(SSL *ssl);\r
+\r
+/**\r
+ * @brief set the SSL method\r
+ *\r
+ * @param ssl  - SSL point\r
+ * @param meth - SSL method point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_set_ssl_method(SSL *ssl, const SSL_METHOD *method);\r
+\r
+/**\r
+ * @brief add CA client certification into the SSL\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param x   - CA certification point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_add_client_CA(SSL *ssl, X509 *x);\r
+\r
+/**\r
+ * @brief add CA client certification into the SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param x   - CA certification point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x);\r
+\r
+/**\r
+ * @brief set the SSL CA certification list\r
+ *\r
+ * @param ssl       - SSL point\r
+ * @param name_list - CA certification list\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_client_CA_list(SSL *ssl, STACK_OF(X509_NAME) *name_list);\r
+\r
+/**\r
+ * @brief set the SSL context CA certification list\r
+ *\r
+ * @param ctx       - SSL context point\r
+ * @param name_list - CA certification list\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_client_CA_list(SSL_CTX *ctx, STACK_OF(X509_NAME) *name_list);\r
+\r
+/**\r
+ * @briefget the SSL CA certification list\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return CA certification list\r
+ */\r
+STACK_OF(X509_NAME) *SSL_get_client_CA_list(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL context CA certification list\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return CA certification list\r
+ */\r
+STACK_OF(X509_NAME) *SSL_CTX_get_client_CA_list(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief get the SSL certification point\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL certification point\r
+ */\r
+X509 *SSL_get_certificate(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL private key point\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL private key point\r
+ */\r
+EVP_PKEY *SSL_get_privatekey(const SSL *ssl);\r
+\r
+/**\r
+ * @brief set the SSL information callback function\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param cb  - information callback function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val));\r
+\r
+/**\r
+ * @brief get the SSL state\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL state\r
+ */\r
+OSSL_HANDSHAKE_STATE SSL_get_state(const SSL *ssl);\r
+\r
+/**\r
+ * @brief set the SSL context read buffer length\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param len - read buffer length\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_default_read_buffer_len(SSL_CTX *ctx, size_t len);\r
+\r
+/**\r
+ * @brief set the SSL read buffer length\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param len - read buffer length\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_default_read_buffer_len(SSL *ssl, size_t len);\r
+\r
+/**\r
+ * @brief set the SSL security level\r
+ *\r
+ * @param ssl   - SSL point\r
+ * @param level - security level\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_security_level(SSL *ssl, int level);\r
+\r
+/**\r
+ * @brief get the SSL security level\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return security level\r
+ */\r
+int SSL_get_security_level(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL verifying mode of the SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return verifying mode\r
+ */\r
+int SSL_CTX_get_verify_mode(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief get the SSL verifying depth of the SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return verifying depth\r
+ */\r
+int SSL_CTX_get_verify_depth(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set the SSL context verifying of the SSL context\r
+ *\r
+ * @param ctx             - SSL context point\r
+ * @param mode            - verifying mode\r
+ * @param verify_callback - verifying callback function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *));\r
+\r
+/**\r
+ * @brief set the SSL verifying of the SSL context\r
+ *\r
+ * @param ctx             - SSL point\r
+ * @param mode            - verifying mode\r
+ * @param verify_callback - verifying callback function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_verify(SSL *s, int mode, int (*verify_callback)(int, X509_STORE_CTX *));\r
+\r
+/**\r
+ * @brief set the SSL verify depth of the SSL context\r
+ *\r
+ * @param ctx   - SSL context point\r
+ * @param depth - verifying depth\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth);\r
+\r
+/**\r
+ * @brief certification verifying callback function\r
+ *\r
+ * @param preverify_ok - verifying result\r
+ * @param x509_ctx     - X509 certification point\r
+ *\r
+ * @return verifying result\r
+ */\r
+int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx);\r
+\r
+/**\r
+ * @brief set the session timeout time\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param t   - new session timeout time\r
+ *\r
+ * @return old session timeout time\r
+ */\r
+long SSL_CTX_set_timeout(SSL_CTX *ctx, long t);\r
+\r
+/**\r
+ * @brief get the session timeout time\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return current session timeout time\r
+ */\r
+long SSL_CTX_get_timeout(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set the SSL context cipher through the list string\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param str - cipher controller list string\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);\r
+\r
+/**\r
+ * @brief set the SSL cipher through the list string\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param str - cipher controller list string\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_set_cipher_list(SSL *ssl, const char *str);\r
+\r
+/**\r
+ * @brief get the SSL cipher list string\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return cipher controller list string\r
+ */\r
+const char *SSL_get_cipher_list(const SSL *ssl, int n);\r
+\r
+/**\r
+ * @brief get the SSL cipher\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return current cipher\r
+ */\r
+const SSL_CIPHER *SSL_get_current_cipher(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL cipher string\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return cipher string\r
+ */\r
+const char *SSL_get_cipher(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL context object X509 certification storage\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return x509 certification storage\r
+ */\r
+X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set the SSL context object X509 certification store\r
+ *\r
+ * @param ctx   - SSL context point\r
+ * @param store - X509 certification store\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_cert_store(SSL_CTX *ctx, X509_STORE *store);\r
+\r
+/**\r
+ * @brief get the SSL specifical statement\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return specifical statement\r
+ */\r
+int SSL_want(const SSL *ssl);\r
+\r
+/**\r
+ * @brief check if the SSL is SSL_X509_LOOKUP state\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_want_x509_lookup(const SSL *ssl);\r
+\r
+/**\r
+ * @brief reset the SSL\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_clear(SSL *ssl);\r
+\r
+/**\r
+ * @brief get the socket handle of the SSL\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     >= 0 : yes, and return socket handle\r
+ *      < 0 : a error catch\r
+ */\r
+int SSL_get_fd(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the read only socket handle of the SSL\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     >= 0 : yes, and return socket handle\r
+ *      < 0 : a error catch\r
+ */\r
+int SSL_get_rfd(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the write only socket handle of the SSL\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     >= 0 : yes, and return socket handle\r
+ *      < 0 : a error catch\r
+ */\r
+int SSL_get_wfd(const SSL *ssl);\r
+\r
+/**\r
+ * @brief set the SSL if we can read as many as data\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param yes - enable the function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_read_ahead(SSL *s, int yes);\r
+\r
+/**\r
+ * @brief set the SSL context if we can read as many as data\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param yes - enbale the function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes);\r
+\r
+/**\r
+ * @brief get the SSL ahead signal if we can read as many as data\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL context ahead signal\r
+ */\r
+int SSL_get_read_ahead(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get the SSL context ahead signal if we can read as many as data\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return SSL context ahead signal\r
+ */\r
+long SSL_CTX_get_read_ahead(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief check if some data can be read\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return\r
+ *         1 : there are bytes to be read\r
+ *         0 : no data\r
+ */\r
+int SSL_has_pending(const SSL *ssl);\r
+\r
+/**\r
+ * @brief load the X509 certification into SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param x   - X509 certification point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x);//loads the certificate x into ctx\r
+\r
+/**\r
+ * @brief load the ASN1 certification into SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param len - certification length\r
+ * @param d   - data point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, int len, const unsigned char *d);\r
+\r
+/**\r
+ * @brief load the certification file into SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param file - certification file name\r
+ * @param type - certification encoding type\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);\r
+\r
+/**\r
+ * @brief load the certification chain file into SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param file - certification chain file name\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_certificate_chain_file(SSL_CTX *ctx, const char *file);\r
+\r
+\r
+/**\r
+ * @brief load the ASN1 private key into SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param d   - data point\r
+ * @param len - private key length\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_PrivateKey_ASN1(int pk, SSL_CTX *ctx, const unsigned char *d,  long len);//adds the private key of type pk stored at memory location d (length len) to ctx\r
+\r
+/**\r
+ * @brief load the private key file into SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param file - private key file name\r
+ * @param type - private key encoding type\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);\r
+\r
+/**\r
+ * @brief load the RSA private key into SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param x   - RSA private key point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_RSAPrivateKey(SSL_CTX *ctx, RSA *rsa);\r
+\r
+/**\r
+ * @brief load the RSA ASN1 private key into SSL context\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param d   - data point\r
+ * @param len - RSA private key length\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, const unsigned char *d, long len);\r
+\r
+/**\r
+ * @brief load the RSA private key file into SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param file - RSA private key file name\r
+ * @param type - private key encoding type\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_RSAPrivateKey_file(SSL_CTX *ctx, const char *file, int type);\r
+\r
+\r
+/**\r
+ * @brief check if the private key and certification is matched\r
+ *\r
+ * @param ctx  - SSL context point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_check_private_key(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set the SSL context server information\r
+ *\r
+ * @param ctx               - SSL context point\r
+ * @param serverinfo        - server information string\r
+ * @param serverinfo_length - server information length\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_serverinfo(SSL_CTX *ctx, const unsigned char *serverinfo, size_t serverinfo_length);\r
+\r
+/**\r
+ * @brief load  the SSL context server infomation file into SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param file - server information file\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_serverinfo_file(SSL_CTX *ctx, const char *file);\r
+\r
+/**\r
+ * @brief SSL select next function\r
+ *\r
+ * @param out        - point of output data point\r
+ * @param outlen     - output data length\r
+ * @param in         - input data\r
+ * @param inlen      - input data length\r
+ * @param client     - client data point\r
+ * @param client_len -client data length\r
+ *\r
+ * @return NPN state\r
+ *         OPENSSL_NPN_UNSUPPORTED : not support\r
+ *         OPENSSL_NPN_NEGOTIATED  : negotiated\r
+ *         OPENSSL_NPN_NO_OVERLAP  : no overlap\r
+ */\r
+int SSL_select_next_proto(unsigned char **out, unsigned char *outlen,\r
+                          const unsigned char *in, unsigned int inlen,\r
+                          const unsigned char *client, unsigned int client_len);\r
+\r
+/**\r
+ * @brief load the extra certification chain into the SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param x509 - X509 certification\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+long SSL_CTX_add_extra_chain_cert(SSL_CTX *ctx, X509 *);\r
+\r
+/**\r
+ * @brief control the SSL context\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param cmd  - command\r
+ * @param larg - parameter length\r
+ * @param parg - parameter point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, char *parg);\r
+\r
+/**\r
+ * @brief get the SSL context cipher\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return SSL context cipher\r
+ */\r
+STACK *SSL_CTX_get_ciphers(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief check if the SSL context can read as many as data\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+long SSL_CTX_get_default_read_ahead(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief get the SSL context extra data\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param idx - index\r
+ *\r
+ * @return data point\r
+ */\r
+void *SSL_CTX_get_ex_data(const SSL_CTX *ctx, int idx);\r
+\r
+/**\r
+ * @brief get the SSL context quiet shutdown option\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return quiet shutdown option\r
+ */\r
+int SSL_CTX_get_quiet_shutdown(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief load the SSL context CA file\r
+ *\r
+ * @param ctx    - SSL context point\r
+ * @param CAfile - CA certification file\r
+ * @param CApath - CA certification file path\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);\r
+\r
+/**\r
+ * @brief add SSL context reference count by '1'\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_up_ref(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set SSL context application private data\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param arg - private data\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_app_data(SSL_CTX *ctx, void *arg);\r
+\r
+/**\r
+ * @brief set SSL context client certification callback function\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param cb  - callback function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_client_cert_cb(SSL_CTX *ctx, int (*cb)(SSL *ssl, X509 **x509, EVP_PKEY **pkey));\r
+\r
+/**\r
+ * @brief set the SSL context if we can read as many as data\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param m   - enable the fuction\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_default_read_ahead(SSL_CTX *ctx, int m);\r
+\r
+/**\r
+ * @brief set SSL context default verifying path\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set SSL context default verifying directory\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_default_verify_dir(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set SSL context default verifying file\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_default_verify_file(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set SSL context extra data\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param idx - data index\r
+ * @param arg - data point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_set_ex_data(SSL_CTX *s, int idx, char *arg);\r
+\r
+/**\r
+ * @brief clear the SSL context option bit of "op"\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param op  - option\r
+ *\r
+ * @return SSL context option\r
+ */\r
+unsigned long SSL_CTX_clear_options(SSL_CTX *ctx, unsigned long op);\r
+\r
+/**\r
+ * @brief get the SSL context option\r
+ *\r
+ * @param ctx - SSL context point\r
+ * @param op  - option\r
+ *\r
+ * @return SSL context option\r
+ */\r
+unsigned long SSL_CTX_get_options(SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set the SSL context quiet shutdown mode\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param mode - mode\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_quiet_shutdown(SSL_CTX *ctx, int mode);\r
+\r
+/**\r
+ * @brief get the SSL context X509 certification\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return X509 certification\r
+ */\r
+X509 *SSL_CTX_get0_certificate(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief get the SSL context private key\r
+ *\r
+ * @param ctx - SSL context point\r
+ *\r
+ * @return private key\r
+ */\r
+EVP_PKEY *SSL_CTX_get0_privatekey(const SSL_CTX *ctx);\r
+\r
+/**\r
+ * @brief set SSL context PSK identity hint\r
+ *\r
+ * @param ctx  - SSL context point\r
+ * @param hint - PSK identity hint\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint);\r
+\r
+/**\r
+ * @brief set SSL context PSK server callback function\r
+ *\r
+ * @param ctx      - SSL context point\r
+ * @param callback - callback function\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_CTX_set_psk_server_callback(SSL_CTX *ctx,\r
+                                     unsigned int (*callback)(SSL *ssl,\r
+                                                              const char *identity,\r
+                                                              unsigned char *psk,\r
+                                                              int max_psk_len));\r
+/**\r
+ * @brief get alert description string\r
+ *\r
+ * @param value - alert value\r
+ *\r
+ * @return alert description string\r
+ */\r
+const char *SSL_alert_desc_string(int value);\r
+\r
+/**\r
+ * @brief get alert description long string\r
+ *\r
+ * @param value - alert value\r
+ *\r
+ * @return alert description long string\r
+ */\r
+const char *SSL_alert_desc_string_long(int value);\r
+\r
+/**\r
+ * @brief get alert type string\r
+ *\r
+ * @param value - alert value\r
+ *\r
+ * @return alert type string\r
+ */\r
+const char *SSL_alert_type_string(int value);\r
+\r
+/**\r
+ * @brief get alert type long string\r
+ *\r
+ * @param value - alert value\r
+ *\r
+ * @return alert type long string\r
+ */\r
+const char *SSL_alert_type_string_long(int value);\r
+\r
+/**\r
+ * @brief get SSL context of the SSL\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL context\r
+ */\r
+SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL application data\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return application data\r
+ */\r
+char *SSL_get_app_data(SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL cipher bits\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param alg_bits - algorithm bits\r
+ *\r
+ * @return strength bits\r
+ */\r
+int SSL_get_cipher_bits(const SSL *ssl, int *alg_bits);\r
+\r
+/**\r
+ * @brief get SSL cipher name\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL cipher name\r
+ */\r
+char *SSL_get_cipher_name(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL cipher version\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL cipher version\r
+ */\r
+char *SSL_get_cipher_version(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL extra data\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param idx - data index\r
+ *\r
+ * @return extra data\r
+ */\r
+char *SSL_get_ex_data(const SSL *ssl, int idx);\r
+\r
+/**\r
+ * @brief get index of the SSL extra data X509 storage context\r
+ *\r
+ * @param none\r
+ *\r
+ * @return data index\r
+ */\r
+int SSL_get_ex_data_X509_STORE_CTX_idx(void);\r
+\r
+/**\r
+ * @brief get peer certification chain\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return certification chain\r
+ */\r
+STACK *SSL_get_peer_cert_chain(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get peer certification\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return certification\r
+ */\r
+X509 *SSL_get_peer_certificate(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL quiet shutdown mode\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return quiet shutdown mode\r
+ */\r
+int SSL_get_quiet_shutdown(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL read only IO handle\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return IO handle\r
+ */\r
+BIO *SSL_get_rbio(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL shared ciphers\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param buf - buffer to store the ciphers\r
+ * @param len - buffer len\r
+ *\r
+ * @return shared ciphers\r
+ */\r
+char *SSL_get_shared_ciphers(const SSL *ssl, char *buf, int len);\r
+\r
+/**\r
+ * @brief get SSL shutdown mode\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return shutdown mode\r
+ */\r
+int SSL_get_shutdown(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL session time\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return session time\r
+ */\r
+long SSL_get_time(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL session timeout time\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return session timeout time\r
+ */\r
+long SSL_get_timeout(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL verifying mode\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return verifying mode\r
+ */\r
+int SSL_get_verify_mode(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL verify parameters\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return verify parameters\r
+ */\r
+X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl);\r
+\r
+/**\r
+ * @brief set expected hostname the peer cert CN should have\r
+ *\r
+ * @param param - verify parameters from SSL_get0_param()\r
+ *\r
+ * @param name - the expected hostname\r
+ *\r
+ * @param namelen - the length of the hostname, or 0 if NUL terminated\r
+ *\r
+ * @return verify parameters\r
+ */\r
+int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param,\r
+                                const char *name, size_t namelen);\r
+\r
+/**\r
+ * @brief set parameters for X509 host verify action\r
+ *\r
+ * @param param -verify parameters from SSL_get0_param()\r
+ *\r
+ * @param flags - bitfield of X509_CHECK_FLAG_... parameters to set\r
+ *\r
+ * @return 1 for success, 0 for failure\r
+ */\r
+int X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param,\r
+                                   unsigned long flags);\r
+\r
+/**\r
+ * @brief clear parameters for X509 host verify action\r
+ *\r
+ * @param param -verify parameters from SSL_get0_param()\r
+ *\r
+ * @param flags - bitfield of X509_CHECK_FLAG_... parameters to clear\r
+ *\r
+ * @return 1 for success, 0 for failure\r
+ */\r
+int X509_VERIFY_PARAM_clear_hostflags(X509_VERIFY_PARAM *param,\r
+                                     unsigned long flags);\r
+\r
+/**\r
+ * @brief get SSL write only IO handle\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return IO handle\r
+ */\r
+BIO *SSL_get_wbio(const SSL *ssl);\r
+\r
+/**\r
+ * @brief load SSL client CA certification file\r
+ *\r
+ * @param file - file name\r
+ *\r
+ * @return certification loading object\r
+ */\r
+STACK *SSL_load_client_CA_file(const char *file);\r
+\r
+/**\r
+ * @brief add SSL reference by '1'\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_up_ref(SSL *ssl);\r
+\r
+/**\r
+ * @brief read and put data into buf, but not clear the SSL low-level storage\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param buf - storage buffer point\r
+ * @param num - data bytes\r
+ *\r
+ * @return result\r
+ *     > 0 : OK, and return read bytes\r
+ *     = 0 : connect is closed\r
+ *     < 0 : a error catch\r
+ */\r
+int SSL_peek(SSL *ssl, void *buf, int num);\r
+\r
+/**\r
+ * @brief make SSL renegotiate\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_renegotiate(SSL *ssl);\r
+\r
+/**\r
+ * @brief get the state string where SSL is reading\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return state string\r
+ */\r
+const char *SSL_rstate_string(SSL *ssl);\r
+\r
+/**\r
+ * @brief get the statement long string where SSL is reading\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return statement long string\r
+ */\r
+const char *SSL_rstate_string_long(SSL *ssl);\r
+\r
+/**\r
+ * @brief set SSL accept statement\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_accept_state(SSL *ssl);\r
+\r
+/**\r
+ * @brief set SSL application data\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param arg - SSL application data point\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_app_data(SSL *ssl, char *arg);\r
+\r
+/**\r
+ * @brief set SSL BIO\r
+ *\r
+ * @param ssl  - SSL point\r
+ * @param rbio - read only IO\r
+ * @param wbio - write only IO\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio);\r
+\r
+/**\r
+ * @brief clear SSL option\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param op  - clear option\r
+ *\r
+ * @return SSL option\r
+ */\r
+unsigned long SSL_clear_options(SSL *ssl, unsigned long op);\r
+\r
+/**\r
+ * @brief get SSL option\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL option\r
+ */\r
+unsigned long SSL_get_options(SSL *ssl);\r
+\r
+/**\r
+ * @brief clear SSL option\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param op  - setting option\r
+ *\r
+ * @return SSL option\r
+ */\r
+unsigned long SSL_set_options(SSL *ssl, unsigned long op);\r
+\r
+/**\r
+ * @brief set SSL quiet shutdown mode\r
+ *\r
+ * @param ssl  - SSL point\r
+ * @param mode - quiet shutdown mode\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_quiet_shutdown(SSL *ssl, int mode);\r
+\r
+/**\r
+ * @brief set SSL shutdown mode\r
+ *\r
+ * @param ssl  - SSL point\r
+ * @param mode - shutdown mode\r
+ *\r
+ * @return none\r
+ */\r
+void SSL_set_shutdown(SSL *ssl, int mode);\r
+\r
+/**\r
+ * @brief set SSL session time\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param t   - session time\r
+ *\r
+ * @return session time\r
+ */\r
+long SSL_set_time(SSL *ssl, long t);\r
+\r
+/**\r
+ * @brief set SSL session timeout time\r
+ *\r
+ * @param ssl - SSL point\r
+ * @param t   - session timeout time\r
+ *\r
+ * @return session timeout time\r
+ */\r
+long SSL_set_timeout(SSL *ssl, long t);\r
+\r
+/**\r
+ * @brief get SSL statement string\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL statement string\r
+ */\r
+char *SSL_state_string(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL statement long string\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL statement long string\r
+ */\r
+char *SSL_state_string_long(const SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL renegotiation count\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return renegotiation count\r
+ */\r
+long SSL_total_renegotiations(SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL version\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return SSL version\r
+ */\r
+int SSL_version(const SSL *ssl);\r
+\r
+/**\r
+ * @brief set SSL PSK identity hint\r
+ *\r
+ * @param ssl  - SSL point\r
+ * @param hint - identity hint\r
+ *\r
+ * @return result\r
+ *     1 : OK\r
+ *     0 : failed\r
+ */\r
+int SSL_use_psk_identity_hint(SSL *ssl, const char *hint);\r
+\r
+/**\r
+ * @brief get SSL PSK identity hint\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return identity hint\r
+ */\r
+const char *SSL_get_psk_identity_hint(SSL *ssl);\r
+\r
+/**\r
+ * @brief get SSL PSK identity\r
+ *\r
+ * @param ssl - SSL point\r
+ *\r
+ * @return identity\r
+ */\r
+const char *SSL_get_psk_identity(SSL *ssl);\r
+\r
+#ifdef __cplusplus\r
+}\r
+#endif\r
+\r
+#endif\r
diff --git a/lib/tls/mbedtls/wrapper/include/platform/ssl_pm.h b/lib/tls/mbedtls/wrapper/include/platform/ssl_pm.h
new file mode 100644 (file)
index 0000000..cbbe3aa
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_PM_H_
+#define _SSL_PM_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include <string.h>
+#include "ssl_types.h"
+#include "ssl_port.h"
+
+#define LOCAL_ATRR
+
+int ssl_pm_new(SSL *ssl);
+void ssl_pm_free(SSL *ssl);
+
+int ssl_pm_handshake(SSL *ssl);
+int ssl_pm_shutdown(SSL *ssl);
+int ssl_pm_clear(SSL *ssl);
+
+int ssl_pm_read(SSL *ssl, void *buffer, int len);
+int ssl_pm_send(SSL *ssl, const void *buffer, int len);
+int ssl_pm_pending(const SSL *ssl);
+
+void ssl_pm_set_fd(SSL *ssl, int fd, int mode);
+int ssl_pm_get_fd(const SSL *ssl, int mode);
+
+OSSL_HANDSHAKE_STATE ssl_pm_get_state(const SSL *ssl);
+
+void ssl_pm_set_bufflen(SSL *ssl, int len);
+
+int x509_pm_show_info(X509 *x);
+int x509_pm_new(X509 *x, X509 *m_x);
+void x509_pm_free(X509 *x);
+int x509_pm_load(X509 *x, const unsigned char *buffer, int len);
+
+int pkey_pm_new(EVP_PKEY *pk, EVP_PKEY *m_pk);
+void pkey_pm_free(EVP_PKEY *pk);
+int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len);
+
+long ssl_pm_get_verify_result(const SSL *ssl);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/include/platform/ssl_port.h b/lib/tls/mbedtls/wrapper/include/platform/ssl_port.h
new file mode 100644 (file)
index 0000000..74c7634
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _SSL_PORT_H_
+#define _SSL_PORT_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "string.h"
+#include "stdlib.h"
+#if defined(LWS_HAVE_MALLOC_H)
+#include "malloc.h"
+#endif
+
+void *ssl_mem_zalloc(size_t size);
+
+#define ssl_mem_malloc malloc
+#define ssl_mem_free   free
+
+#define ssl_memcpy     memcpy
+#define ssl_strlen     strlen
+
+#define ssl_speed_up_enter()
+#define ssl_speed_up_exit()
+
+#define SSL_DEBUG_FL
+#define SSL_DEBUG_LOG(fmt, ...) ESP_LOGI("openssl", fmt, ##__VA_ARGS__)
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_cert.c b/lib/tls/mbedtls/wrapper/library/ssl_cert.c
new file mode 100644 (file)
index 0000000..5c60812
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_cert.h"
+#include "ssl_pkey.h"
+#include "ssl_x509.h"
+#include "ssl_dbg.h"
+#include "ssl_port.h"
+
+/**
+ * @brief create a certification object according to input certification
+ */
+CERT *__ssl_cert_new(CERT *ic)
+{
+    CERT *cert;
+
+    X509 *ix;
+    EVP_PKEY *ipk;
+
+    cert = ssl_mem_zalloc(sizeof(CERT));
+    if (!cert) {
+        SSL_DEBUG(SSL_CERT_ERROR_LEVEL, "no enough memory > (cert)");
+        goto no_mem;
+    }
+
+    if (ic) {
+        ipk = ic->pkey;
+        ix = ic->x509;
+    } else {
+        ipk = NULL;
+        ix = NULL;
+    }
+
+    cert->pkey = __EVP_PKEY_new(ipk);
+    if (!cert->pkey) {
+        SSL_DEBUG(SSL_CERT_ERROR_LEVEL, "__EVP_PKEY_new() return NULL");
+        goto pkey_err;
+    }
+
+    cert->x509 = __X509_new(ix);
+    if (!cert->x509) {
+        SSL_DEBUG(SSL_CERT_ERROR_LEVEL, "__X509_new() return NULL");
+        goto x509_err;
+    }
+
+    return cert;
+
+x509_err:
+    EVP_PKEY_free(cert->pkey);
+pkey_err:
+    ssl_mem_free(cert);
+no_mem:
+    return NULL;
+}
+
+/**
+ * @brief create a certification object include private key object
+ */
+CERT *ssl_cert_new(void)
+{
+    return __ssl_cert_new(NULL);
+}
+
+/**
+ * @brief free a certification object
+ */
+void ssl_cert_free(CERT *cert)
+{
+    SSL_ASSERT3(cert);
+
+    X509_free(cert->x509);
+
+    EVP_PKEY_free(cert->pkey);
+
+    ssl_mem_free(cert);
+}
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_lib.c b/lib/tls/mbedtls/wrapper/library/ssl_lib.c
new file mode 100644 (file)
index 0000000..ec46fd8
--- /dev/null
@@ -0,0 +1,1734 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_lib.h"
+#include "ssl_pkey.h"
+#include "ssl_x509.h"
+#include "ssl_cert.h"
+#include "ssl_dbg.h"
+#include "ssl_port.h"
+
+#include "core/private.h"
+
+char *
+lws_strncpy(char *dest, const char *src, size_t size);
+
+#define SSL_SEND_DATA_MAX_LENGTH 1460
+
+/**
+ * @brief create a new SSL session object
+ */
+static SSL_SESSION* SSL_SESSION_new(void)
+{
+    SSL_SESSION *session;
+
+    session = ssl_mem_zalloc(sizeof(SSL_SESSION));
+    if (!session) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "no enough memory > (session)");
+        goto failed1;
+    }
+
+    session->peer = X509_new();
+    if (!session->peer) {
+       SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "X509_new() return NULL");
+       goto failed2;
+    }
+
+    return session;
+
+failed2:
+    ssl_mem_free(session);
+failed1:
+    return NULL;
+}
+
+/**
+ * @brief free a new SSL session object
+ */
+static void SSL_SESSION_free(SSL_SESSION *session)
+{
+    X509_free(session->peer);
+    ssl_mem_free(session);
+}
+
+/**
+ * @brief Discover whether the current connection is in the error state
+ */
+int ossl_statem_in_error(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    if (ssl->statem.state == MSG_FLOW_ERROR)
+        return 1;
+
+    return 0;
+}
+
+/**
+ * @brief get the SSL specifical statement
+ */
+int SSL_want(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->rwstate;
+}
+
+/**
+ * @brief check if SSL want nothing
+ */
+int SSL_want_nothing(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    if (ssl->err)
+           return 1;
+
+    return (SSL_want(ssl) == SSL_NOTHING);
+}
+
+/**
+ * @brief check if SSL want to read
+ */
+int SSL_want_read(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    if (ssl->err)
+           return 0;
+
+    return (SSL_want(ssl) == SSL_READING);
+}
+
+/**
+ * @brief check if SSL want to write
+ */
+int SSL_want_write(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    if (ssl->err)
+           return 0;
+
+    return (SSL_want(ssl) == SSL_WRITING);
+}
+
+/**
+ * @brief check if SSL want to lookup X509 certification
+ */
+int SSL_want_x509_lookup(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return (SSL_want(ssl) == SSL_WRITING);
+}
+
+/**
+ * @brief get SSL error code
+ */
+int SSL_get_error(const SSL *ssl, int ret_code)
+{
+    int ret = SSL_ERROR_SYSCALL;
+
+    SSL_ASSERT1(ssl);
+
+    if (ret_code > 0)
+        ret = SSL_ERROR_NONE;
+    else if (ret_code < 0)
+    {
+        if (ssl->err == SSL_ERROR_WANT_READ || SSL_want_read(ssl))
+            ret = SSL_ERROR_WANT_READ;
+        else if (ssl->err == SSL_ERROR_WANT_WRITE || SSL_want_write(ssl))
+            ret = SSL_ERROR_WANT_WRITE;
+        else
+            ret = SSL_ERROR_SYSCALL; //unknown
+    }
+    else // ret_code == 0
+    {
+        if (ssl->shutdown & SSL_RECEIVED_SHUTDOWN)
+            ret = SSL_ERROR_ZERO_RETURN;
+        else
+            ret = SSL_ERROR_SYSCALL;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief get the SSL state
+ */
+OSSL_HANDSHAKE_STATE SSL_get_state(const SSL *ssl)
+{
+    OSSL_HANDSHAKE_STATE state;
+
+    SSL_ASSERT1(ssl);
+
+    state = SSL_METHOD_CALL(get_state, ssl);
+
+    return state;
+}
+
+/**
+ * @brief create a SSL context
+ */
+SSL_CTX* SSL_CTX_new(const SSL_METHOD *method)
+{
+    SSL_CTX *ctx;
+    CERT *cert;
+    X509 *client_ca;
+
+    if (!method) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "no no_method");
+        return NULL;
+    }
+
+    client_ca = X509_new();
+    if (!client_ca) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "X509_new() return NULL");
+        goto failed1;
+    }
+
+    cert = ssl_cert_new();
+    if (!cert) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "ssl_cert_new() return NULL");
+        goto failed2;
+    }
+
+    ctx = (SSL_CTX *)ssl_mem_zalloc(sizeof(SSL_CTX));
+    if (!ctx) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "no enough memory > (ctx)");
+        goto failed3;
+    }
+
+    ctx->method = method;
+    ctx->client_CA = client_ca;
+    ctx->cert = cert;
+
+    ctx->version = method->version;
+
+    return ctx;
+
+failed3:
+    ssl_cert_free(cert);
+failed2:
+    X509_free(client_ca);
+failed1:
+    return NULL;
+}
+
+/**
+ * @brief free a SSL context
+ */
+void SSL_CTX_free(SSL_CTX* ctx)
+{
+    SSL_ASSERT3(ctx);
+
+    ssl_cert_free(ctx->cert);
+
+    X509_free(ctx->client_CA);
+
+    if (ctx->alpn_protos)
+           ssl_mem_free(ctx->alpn_protos);
+
+    ssl_mem_free(ctx);
+}
+
+/**
+ * @brief set  the SSL context version
+ */
+int SSL_CTX_set_ssl_version(SSL_CTX *ctx, const SSL_METHOD *meth)
+{
+    SSL_ASSERT1(ctx);
+    SSL_ASSERT1(meth);
+
+    ctx->method = meth;
+
+    ctx->version = meth->version;
+
+    return 1;
+}
+
+/**
+ * @brief get the SSL context current method
+ */
+const SSL_METHOD *SSL_CTX_get_ssl_method(SSL_CTX *ctx)
+{
+    SSL_ASSERT2(ctx);
+
+    return ctx->method;
+}
+
+/**
+ * @brief create a SSL
+ */
+SSL *SSL_new(SSL_CTX *ctx)
+{
+    int ret = 0;
+    SSL *ssl;
+
+    if (!ctx) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "no ctx");
+        return NULL;
+    }
+
+    ssl = (SSL *)ssl_mem_zalloc(sizeof(SSL));
+    if (!ssl) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "no enough memory > (ssl)");
+        goto failed1;
+    }
+
+    ssl->session = SSL_SESSION_new();
+    if (!ssl->session) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "SSL_SESSION_new() return NULL");
+        goto failed2;
+    }
+
+    ssl->cert = __ssl_cert_new(ctx->cert);
+    if (!ssl->cert) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "__ssl_cert_new() return NULL");
+        goto failed3;
+    }
+
+    ssl->client_CA = __X509_new(ctx->client_CA);
+    if (!ssl->client_CA) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "__X509_new() return NULL");
+        goto failed4;
+    }
+
+    ssl->ctx = ctx;
+    ssl->method = ctx->method;
+
+    ssl->version = ctx->version;
+    ssl->options = ctx->options;
+
+    ssl->verify_mode = ctx->verify_mode;
+
+    ret = SSL_METHOD_CALL(new, ssl);
+    if (ret) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "SSL_METHOD_CALL(new) return %d", ret);
+        goto failed5;
+    }
+
+   _ssl_set_alpn_list(ssl);
+
+    ssl->rwstate = SSL_NOTHING;
+
+    return ssl;
+
+failed5:
+    X509_free(ssl->client_CA);
+failed4:
+    ssl_cert_free(ssl->cert);
+failed3:
+    SSL_SESSION_free(ssl->session);
+failed2:
+    ssl_mem_free(ssl);
+failed1:
+    return NULL;
+}
+
+/**
+ * @brief free the SSL
+ */
+void SSL_free(SSL *ssl)
+{
+    SSL_ASSERT3(ssl);
+
+    SSL_METHOD_CALL(free, ssl);
+
+    X509_free(ssl->client_CA);
+
+    ssl_cert_free(ssl->cert);
+
+    SSL_SESSION_free(ssl->session);
+
+    if (ssl->alpn_protos)
+           ssl_mem_free(ssl->alpn_protos);
+
+    ssl_mem_free(ssl);
+}
+
+/**
+ * @brief perform the SSL handshake
+ */
+int SSL_do_handshake(SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    ret = SSL_METHOD_CALL(handshake, ssl);
+
+    return ret;
+}
+
+/**
+ * @brief connect to the remote SSL server
+ */
+int SSL_connect(SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return SSL_do_handshake(ssl);
+}
+
+/**
+ * @brief accept the remote connection
+ */
+int SSL_accept(SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return SSL_do_handshake(ssl);
+}
+
+/**
+ * @brief shutdown the connection
+ */
+int SSL_shutdown(SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    if (SSL_get_state(ssl) != TLS_ST_OK) return 1;
+
+    ret = SSL_METHOD_CALL(shutdown, ssl);
+
+    return ret;
+}
+
+/**
+ * @brief reset the SSL
+ */
+int SSL_clear(SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    ret = SSL_shutdown(ssl);
+    if (1 != ret) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "SSL_shutdown return %d", ret);
+        goto failed1;
+    }
+
+    SSL_METHOD_CALL(free, ssl);
+
+    ret = SSL_METHOD_CALL(new, ssl);
+    if (!ret) {
+        SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "SSL_METHOD_CALL(new) return %d", ret);
+        goto failed1;
+    }
+
+    return 1;
+
+failed1:
+    return ret;
+}
+
+/**
+ * @brief read data from to remote
+ */
+int SSL_read(SSL *ssl, void *buffer, int len)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(buffer);
+    SSL_ASSERT1(len);
+
+    ssl->rwstate = SSL_READING;
+
+    ret = SSL_METHOD_CALL(read, ssl, buffer, len);
+
+    if (ret == len)
+        ssl->rwstate = SSL_NOTHING;
+
+    return ret;
+}
+
+/**
+ * @brief send the data to remote
+ */
+int SSL_write(SSL *ssl, const void *buffer, int len)
+{
+    int ret;
+    int send_bytes, bytes;
+    const unsigned char *pbuf;
+
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(buffer);
+    SSL_ASSERT1(len);
+
+    ssl->rwstate = SSL_WRITING;
+
+    send_bytes = len;
+    pbuf = (const unsigned char *)buffer;
+
+    do {
+        if (send_bytes > SSL_SEND_DATA_MAX_LENGTH)
+            bytes = SSL_SEND_DATA_MAX_LENGTH;
+        else
+            bytes = send_bytes;
+
+       if (ssl->interrupted_remaining_write) {
+               bytes = ssl->interrupted_remaining_write;
+               ssl->interrupted_remaining_write = 0;
+       }
+
+        ret = SSL_METHOD_CALL(send, ssl, pbuf, bytes);
+       //printf("%s: ssl_pm said %d for %d requested (cum %d)\n", __func__, ret, bytes, len -send_bytes);
+        /* the return is a NEGATIVE OpenSSL error code, or the length sent */
+        if (ret > 0) {
+            pbuf += ret;
+            send_bytes -= ret;
+        } else
+               ssl->interrupted_remaining_write = bytes;
+    } while (ret > 0 && send_bytes && ret == bytes);
+
+    if (ret >= 0) {
+        ret = len - send_bytes;
+       if (!ret)
+               ssl->rwstate = SSL_NOTHING;
+    } else {
+           if (send_bytes == len)
+               ret = -1;
+           else
+                   ret = len - send_bytes;
+    }
+
+    return ret;
+}
+
+/**
+ * @brief get SSL context of the SSL
+ */
+SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl)
+{
+    SSL_ASSERT2(ssl);
+
+    return ssl->ctx;
+}
+
+/**
+ * @brief get the SSL current method
+ */
+const SSL_METHOD *SSL_get_ssl_method(SSL *ssl)
+{
+    SSL_ASSERT2(ssl);
+
+    return ssl->method;
+}
+
+/**
+ * @brief set the SSL method
+ */
+int SSL_set_ssl_method(SSL *ssl, const SSL_METHOD *method)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(method);
+
+    if (ssl->version != method->version) {
+
+        ret = SSL_shutdown(ssl);
+        if (1 != ret) {
+            SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "SSL_shutdown return %d", ret);
+            goto failed1;
+        }
+
+        SSL_METHOD_CALL(free, ssl);
+
+        ssl->method = method;
+
+        ret = SSL_METHOD_CALL(new, ssl);
+        if (!ret) {
+            SSL_DEBUG(SSL_LIB_ERROR_LEVEL, "SSL_METHOD_CALL(new) return %d", ret);
+            goto failed1;
+        }
+    } else {
+        ssl->method = method;
+    }
+
+
+    return 1;
+
+failed1:
+    return ret;
+}
+
+/**
+ * @brief get SSL shutdown mode
+ */
+int SSL_get_shutdown(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->shutdown;
+}
+
+/**
+ * @brief set SSL shutdown mode
+ */
+void SSL_set_shutdown(SSL *ssl, int mode)
+{
+    SSL_ASSERT3(ssl);
+
+    ssl->shutdown = mode;
+}
+
+
+/**
+ * @brief get the number of the bytes to be read
+ */
+int SSL_pending(const SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    ret = SSL_METHOD_CALL(pending, ssl);
+
+    return ret;
+}
+
+/**
+ * @brief check if some data can be read
+ */
+int SSL_has_pending(const SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    if (SSL_pending(ssl))
+        ret = 1;
+    else
+        ret = 0;
+
+    return ret;
+}
+
+/**
+ * @brief clear the SSL context option bit of "op"
+ */
+unsigned long SSL_CTX_clear_options(SSL_CTX *ctx, unsigned long op)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->options &= ~op;
+}
+
+/**
+ * @brief get the SSL context option
+ */
+unsigned long SSL_CTX_get_options(SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->options;
+}
+
+/**
+ * @brief set the option of the SSL context
+ */
+unsigned long SSL_CTX_set_options(SSL_CTX *ctx, unsigned long opt)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->options |= opt;
+}
+
+/**
+ * @brief clear SSL option
+ */
+unsigned long SSL_clear_options(SSL *ssl, unsigned long op)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->options & ~op;
+}
+
+/**
+ * @brief get SSL option
+ */
+unsigned long SSL_get_options(SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->options;
+}
+
+/**
+ * @brief clear SSL option
+ */
+unsigned long SSL_set_options(SSL *ssl, unsigned long op)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->options |= op;
+}
+
+/**
+ * @brief get the socket handle of the SSL
+ */
+int SSL_get_fd(const SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    ret = SSL_METHOD_CALL(get_fd, ssl, 0);
+
+    return ret;
+}
+
+/**
+ * @brief get the read only socket handle of the SSL
+ */
+int SSL_get_rfd(const SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    ret = SSL_METHOD_CALL(get_fd, ssl, 0);
+
+    return ret;
+}
+
+/**
+ * @brief get the write only socket handle of the SSL
+ */
+int SSL_get_wfd(const SSL *ssl)
+{
+    int ret;
+
+    SSL_ASSERT1(ssl);
+
+    ret = SSL_METHOD_CALL(get_fd, ssl, 0);
+
+    return ret;
+}
+
+/**
+ * @brief bind the socket file description into the SSL
+ */
+int SSL_set_fd(SSL *ssl, int fd)
+{
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(fd >= 0);
+
+    SSL_METHOD_CALL(set_fd, ssl, fd, 0);
+
+    return 1;
+}
+
+/**
+ * @brief bind the read only socket file description into the SSL
+ */
+int SSL_set_rfd(SSL *ssl, int fd)
+{
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(fd >= 0);
+
+    SSL_METHOD_CALL(set_fd, ssl, fd, 0);
+
+    return 1;
+}
+
+/**
+ * @brief bind the write only socket file description into the SSL
+ */
+int SSL_set_wfd(SSL *ssl, int fd)
+{
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(fd >= 0);
+
+    SSL_METHOD_CALL(set_fd, ssl, fd, 0);
+
+    return 1;
+}
+
+/**
+ * @brief get SSL version
+ */
+int SSL_version(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->version;
+}
+
+/**
+ * @brief get the SSL version string
+ */
+static const char* ssl_protocol_to_string(int version)
+{
+    const char *str;
+
+    if (version == TLS1_2_VERSION)
+        str = "TLSv1.2";
+    else if (version == TLS1_1_VERSION)
+        str = "TLSv1.1";
+    else if (version == TLS1_VERSION)
+        str = "TLSv1";
+    else if (version == SSL3_VERSION)
+        str = "SSLv3";
+    else
+        str = "unknown";
+
+    return str;
+}
+
+/**
+ * @brief get the SSL current version
+ */
+const char *SSL_get_version(const SSL *ssl)
+{
+    SSL_ASSERT2(ssl);
+
+    return ssl_protocol_to_string(SSL_version(ssl));
+}
+
+/**
+ * @brief get alert description string
+ */
+const char* SSL_alert_desc_string(int value)
+{
+    const char *str;
+
+    switch (value & 0xff)
+    {
+        case SSL3_AD_CLOSE_NOTIFY:
+            str = "CN";
+            break;
+        case SSL3_AD_UNEXPECTED_MESSAGE:
+            str = "UM";
+            break;
+        case SSL3_AD_BAD_RECORD_MAC:
+            str = "BM";
+            break;
+        case SSL3_AD_DECOMPRESSION_FAILURE:
+            str = "DF";
+            break;
+        case SSL3_AD_HANDSHAKE_FAILURE:
+            str = "HF";
+            break;
+        case SSL3_AD_NO_CERTIFICATE:
+            str = "NC";
+            break;
+        case SSL3_AD_BAD_CERTIFICATE:
+            str = "BC";
+            break;
+        case SSL3_AD_UNSUPPORTED_CERTIFICATE:
+            str = "UC";
+            break;
+        case SSL3_AD_CERTIFICATE_REVOKED:
+            str = "CR";
+            break;
+        case SSL3_AD_CERTIFICATE_EXPIRED:
+            str = "CE";
+            break;
+        case SSL3_AD_CERTIFICATE_UNKNOWN:
+            str = "CU";
+            break;
+        case SSL3_AD_ILLEGAL_PARAMETER:
+            str = "IP";
+            break;
+        case TLS1_AD_DECRYPTION_FAILED:
+            str = "DC";
+            break;
+        case TLS1_AD_RECORD_OVERFLOW:
+            str = "RO";
+            break;
+        case TLS1_AD_UNKNOWN_CA:
+            str = "CA";
+            break;
+        case TLS1_AD_ACCESS_DENIED:
+            str = "AD";
+            break;
+        case TLS1_AD_DECODE_ERROR:
+            str = "DE";
+            break;
+        case TLS1_AD_DECRYPT_ERROR:
+            str = "CY";
+            break;
+        case TLS1_AD_EXPORT_RESTRICTION:
+            str = "ER";
+            break;
+        case TLS1_AD_PROTOCOL_VERSION:
+            str = "PV";
+            break;
+        case TLS1_AD_INSUFFICIENT_SECURITY:
+            str = "IS";
+            break;
+        case TLS1_AD_INTERNAL_ERROR:
+            str = "IE";
+            break;
+        case TLS1_AD_USER_CANCELLED:
+            str = "US";
+            break;
+        case TLS1_AD_NO_RENEGOTIATION:
+            str = "NR";
+            break;
+        case TLS1_AD_UNSUPPORTED_EXTENSION:
+            str = "UE";
+            break;
+        case TLS1_AD_CERTIFICATE_UNOBTAINABLE:
+            str = "CO";
+            break;
+        case TLS1_AD_UNRECOGNIZED_NAME:
+            str = "UN";
+            break;
+        case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE:
+            str = "BR";
+            break;
+        case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE:
+            str = "BH";
+            break;
+        case TLS1_AD_UNKNOWN_PSK_IDENTITY:
+            str = "UP";
+            break;
+        default:
+            str = "UK";
+            break;
+    }
+
+    return str;
+}
+
+/**
+ * @brief get alert description long string
+ */
+const char* SSL_alert_desc_string_long(int value)
+{
+    const char *str;
+
+    switch (value & 0xff)
+    {
+        case SSL3_AD_CLOSE_NOTIFY:
+            str = "close notify";
+            break;
+        case SSL3_AD_UNEXPECTED_MESSAGE:
+            str = "unexpected_message";
+            break;
+        case SSL3_AD_BAD_RECORD_MAC:
+            str = "bad record mac";
+            break;
+        case SSL3_AD_DECOMPRESSION_FAILURE:
+            str = "decompression failure";
+            break;
+        case SSL3_AD_HANDSHAKE_FAILURE:
+            str = "handshake failure";
+            break;
+        case SSL3_AD_NO_CERTIFICATE:
+            str = "no certificate";
+            break;
+        case SSL3_AD_BAD_CERTIFICATE:
+            str = "bad certificate";
+            break;
+        case SSL3_AD_UNSUPPORTED_CERTIFICATE:
+            str = "unsupported certificate";
+            break;
+        case SSL3_AD_CERTIFICATE_REVOKED:
+            str = "certificate revoked";
+            break;
+        case SSL3_AD_CERTIFICATE_EXPIRED:
+            str = "certificate expired";
+            break;
+        case SSL3_AD_CERTIFICATE_UNKNOWN:
+            str = "certificate unknown";
+            break;
+        case SSL3_AD_ILLEGAL_PARAMETER:
+            str = "illegal parameter";
+            break;
+        case TLS1_AD_DECRYPTION_FAILED:
+            str = "decryption failed";
+            break;
+        case TLS1_AD_RECORD_OVERFLOW:
+            str = "record overflow";
+            break;
+        case TLS1_AD_UNKNOWN_CA:
+            str = "unknown CA";
+            break;
+        case TLS1_AD_ACCESS_DENIED:
+            str = "access denied";
+            break;
+        case TLS1_AD_DECODE_ERROR:
+            str = "decode error";
+            break;
+        case TLS1_AD_DECRYPT_ERROR:
+            str = "decrypt error";
+            break;
+        case TLS1_AD_EXPORT_RESTRICTION:
+            str = "export restriction";
+            break;
+        case TLS1_AD_PROTOCOL_VERSION:
+            str = "protocol version";
+            break;
+        case TLS1_AD_INSUFFICIENT_SECURITY:
+            str = "insufficient security";
+            break;
+        case TLS1_AD_INTERNAL_ERROR:
+            str = "internal error";
+            break;
+        case TLS1_AD_USER_CANCELLED:
+            str = "user canceled";
+            break;
+        case TLS1_AD_NO_RENEGOTIATION:
+            str = "no renegotiation";
+            break;
+        case TLS1_AD_UNSUPPORTED_EXTENSION:
+            str = "unsupported extension";
+            break;
+        case TLS1_AD_CERTIFICATE_UNOBTAINABLE:
+            str = "certificate unobtainable";
+            break;
+        case TLS1_AD_UNRECOGNIZED_NAME:
+            str = "unrecognized name";
+            break;
+        case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE:
+            str = "bad certificate status response";
+            break;
+        case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE:
+            str = "bad certificate hash value";
+            break;
+        case TLS1_AD_UNKNOWN_PSK_IDENTITY:
+            str = "unknown PSK identity";
+            break;
+        default:
+            str = "unknown";
+            break;
+    }
+
+    return str;
+}
+
+/**
+ * @brief get alert type string
+ */
+const char *SSL_alert_type_string(int value)
+{
+    const char *str;
+
+    switch (value >> 8)
+    {
+    case SSL3_AL_WARNING:
+        str = "W";
+        break;
+    case SSL3_AL_FATAL:
+        str = "F";
+        break;
+    default:
+        str = "U";
+        break;
+    }
+
+    return str;
+}
+
+/**
+ * @brief get alert type long string
+ */
+const char *SSL_alert_type_string_long(int value)
+{
+    const char *str;
+
+    switch (value >> 8)
+    {
+        case SSL3_AL_WARNING:
+            str = "warning";
+            break;
+        case SSL3_AL_FATAL:
+            str = "fatal";
+            break;
+        default:
+            str = "unknown";
+            break;
+    }
+
+    return str;
+}
+
+/**
+ * @brief get the state string where SSL is reading
+ */
+const char *SSL_rstate_string(SSL *ssl)
+{
+    const char *str;
+
+    SSL_ASSERT2(ssl);
+
+    switch (ssl->rlayer.rstate)
+    {
+        case SSL_ST_READ_HEADER:
+            str = "RH";
+            break;
+        case SSL_ST_READ_BODY:
+            str = "RB";
+            break;
+        case SSL_ST_READ_DONE:
+            str = "RD";
+            break;
+        default:
+            str = "unknown";
+            break;
+    }
+
+    return str;
+}
+
+/**
+ * @brief get the statement long string where SSL is reading
+ */
+const char *SSL_rstate_string_long(SSL *ssl)
+{
+    const char *str = "unknown";
+
+    SSL_ASSERT2(ssl);
+
+    switch (ssl->rlayer.rstate)
+    {
+        case SSL_ST_READ_HEADER:
+            str = "read header";
+            break;
+        case SSL_ST_READ_BODY:
+            str = "read body";
+            break;
+        case SSL_ST_READ_DONE:
+            str = "read done";
+            break;
+        default:
+            break;
+    }
+
+    return str;
+}
+
+/**
+ * @brief get SSL statement string
+ */
+char *SSL_state_string(const SSL *ssl)
+{
+    char *str = "UNKWN ";
+
+    SSL_ASSERT2(ssl);
+
+    if (ossl_statem_in_error(ssl))
+        str = "SSLERR";
+    else
+    {
+        switch (SSL_get_state(ssl))
+        {
+            case TLS_ST_BEFORE:
+                str = "PINIT ";
+                break;
+            case TLS_ST_OK:
+                str =  "SSLOK ";
+                break;
+            case TLS_ST_CW_CLNT_HELLO:
+                str = "TWCH";
+                break;
+            case TLS_ST_CR_SRVR_HELLO:
+                str = "TRSH";
+                break;
+            case TLS_ST_CR_CERT:
+                str = "TRSC";
+                break;
+            case TLS_ST_CR_KEY_EXCH:
+                str = "TRSKE";
+                break;
+            case TLS_ST_CR_CERT_REQ:
+                str = "TRCR";
+                break;
+            case TLS_ST_CR_SRVR_DONE:
+                str = "TRSD";
+                break;
+            case TLS_ST_CW_CERT:
+                str = "TWCC";
+                break;
+            case TLS_ST_CW_KEY_EXCH:
+                str = "TWCKE";
+                break;
+            case TLS_ST_CW_CERT_VRFY:
+                str = "TWCV";
+                break;
+            case TLS_ST_SW_CHANGE:
+            case TLS_ST_CW_CHANGE:
+                str = "TWCCS";
+                break;
+            case TLS_ST_SW_FINISHED:
+            case TLS_ST_CW_FINISHED:
+                str = "TWFIN";
+                break;
+            case TLS_ST_SR_CHANGE:
+            case TLS_ST_CR_CHANGE:
+                str = "TRCCS";
+                break;
+            case TLS_ST_SR_FINISHED:
+            case TLS_ST_CR_FINISHED:
+                str = "TRFIN";
+                break;
+            case TLS_ST_SW_HELLO_REQ:
+                str = "TWHR";
+                break;
+            case TLS_ST_SR_CLNT_HELLO:
+                str = "TRCH";
+                break;
+            case TLS_ST_SW_SRVR_HELLO:
+                str = "TWSH";
+                break;
+            case TLS_ST_SW_CERT:
+                str = "TWSC";
+                break;
+            case TLS_ST_SW_KEY_EXCH:
+                str = "TWSKE";
+                break;
+            case TLS_ST_SW_CERT_REQ:
+                str = "TWCR";
+                break;
+            case TLS_ST_SW_SRVR_DONE:
+                str = "TWSD";
+                break;
+            case TLS_ST_SR_CERT:
+                str = "TRCC";
+                break;
+            case TLS_ST_SR_KEY_EXCH:
+                str = "TRCKE";
+                break;
+            case TLS_ST_SR_CERT_VRFY:
+                str = "TRCV";
+                break;
+            case DTLS_ST_CR_HELLO_VERIFY_REQUEST:
+                str = "DRCHV";
+                break;
+            case DTLS_ST_SW_HELLO_VERIFY_REQUEST:
+                str = "DWCHV";
+                break;
+            default:
+                break;
+        }
+    }
+
+    return str;
+}
+
+/**
+ * @brief get SSL statement long string
+ */
+char *SSL_state_string_long(const SSL *ssl)
+{
+    char *str = "UNKWN ";
+
+    SSL_ASSERT2(ssl);
+
+    if (ossl_statem_in_error(ssl))
+        str = "SSLERR";
+    else
+    {
+        switch (SSL_get_state(ssl))
+        {
+            case TLS_ST_BEFORE:
+                str = "before SSL initialization";
+                break;
+            case TLS_ST_OK:
+                str = "SSL negotiation finished successfully";
+                break;
+            case TLS_ST_CW_CLNT_HELLO:
+                str = "SSLv3/TLS write client hello";
+                break;
+            case TLS_ST_CR_SRVR_HELLO:
+                str = "SSLv3/TLS read server hello";
+                break;
+            case TLS_ST_CR_CERT:
+                str = "SSLv3/TLS read server certificate";
+                break;
+            case TLS_ST_CR_KEY_EXCH:
+                str = "SSLv3/TLS read server key exchange";
+                break;
+            case TLS_ST_CR_CERT_REQ:
+                str = "SSLv3/TLS read server certificate request";
+                break;
+            case TLS_ST_CR_SESSION_TICKET:
+                str = "SSLv3/TLS read server session ticket";
+                break;
+            case TLS_ST_CR_SRVR_DONE:
+                str = "SSLv3/TLS read server done";
+                break;
+            case TLS_ST_CW_CERT:
+                str = "SSLv3/TLS write client certificate";
+                break;
+            case TLS_ST_CW_KEY_EXCH:
+                str = "SSLv3/TLS write client key exchange";
+                break;
+            case TLS_ST_CW_CERT_VRFY:
+                str = "SSLv3/TLS write certificate verify";
+                break;
+            case TLS_ST_CW_CHANGE:
+            case TLS_ST_SW_CHANGE:
+                str = "SSLv3/TLS write change cipher spec";
+                break;
+            case TLS_ST_CW_FINISHED:
+            case TLS_ST_SW_FINISHED:
+                str = "SSLv3/TLS write finished";
+                break;
+            case TLS_ST_CR_CHANGE:
+            case TLS_ST_SR_CHANGE:
+                str = "SSLv3/TLS read change cipher spec";
+                break;
+            case TLS_ST_CR_FINISHED:
+            case TLS_ST_SR_FINISHED:
+                str = "SSLv3/TLS read finished";
+                break;
+            case TLS_ST_SR_CLNT_HELLO:
+                str = "SSLv3/TLS read client hello";
+                break;
+            case TLS_ST_SW_HELLO_REQ:
+                str = "SSLv3/TLS write hello request";
+                break;
+            case TLS_ST_SW_SRVR_HELLO:
+                str = "SSLv3/TLS write server hello";
+                break;
+            case TLS_ST_SW_CERT:
+                str = "SSLv3/TLS write certificate";
+                break;
+            case TLS_ST_SW_KEY_EXCH:
+                str = "SSLv3/TLS write key exchange";
+                break;
+            case TLS_ST_SW_CERT_REQ:
+                str = "SSLv3/TLS write certificate request";
+                break;
+            case TLS_ST_SW_SESSION_TICKET:
+                str = "SSLv3/TLS write session ticket";
+                break;
+            case TLS_ST_SW_SRVR_DONE:
+                str = "SSLv3/TLS write server done";
+                break;
+            case TLS_ST_SR_CERT:
+                str = "SSLv3/TLS read client certificate";
+                break;
+            case TLS_ST_SR_KEY_EXCH:
+                str = "SSLv3/TLS read client key exchange";
+                break;
+            case TLS_ST_SR_CERT_VRFY:
+                str = "SSLv3/TLS read certificate verify";
+                break;
+            case DTLS_ST_CR_HELLO_VERIFY_REQUEST:
+                str = "DTLS1 read hello verify request";
+                break;
+            case DTLS_ST_SW_HELLO_VERIFY_REQUEST:
+                str = "DTLS1 write hello verify request";
+                break;
+            default:
+                break;
+        }
+    }
+
+    return str;
+}
+
+/**
+ * @brief set the SSL context read buffer length
+ */
+void SSL_CTX_set_default_read_buffer_len(SSL_CTX *ctx, size_t len)
+{
+    SSL_ASSERT3(ctx);
+
+    ctx->read_buffer_len = len;
+}
+
+/**
+ * @brief set the SSL read buffer length
+ */
+void SSL_set_default_read_buffer_len(SSL *ssl, size_t len)
+{
+    SSL_ASSERT3(ssl);
+    SSL_ASSERT3(len);
+
+    SSL_METHOD_CALL(set_bufflen, ssl, len);
+}
+
+/**
+ * @brief set the SSL information callback function
+ */
+void SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val))
+{
+    SSL_ASSERT3(ssl);
+
+    ssl->info_callback = cb;
+}
+
+/**
+ * @brief add SSL context reference count by '1'
+ */
+int SSL_CTX_up_ref(SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    /**
+     * no support multi-thread SSL here
+     */
+    ctx->references++;
+
+    return 1;
+}
+
+/**
+ * @brief set the SSL security level
+ */
+void SSL_set_security_level(SSL *ssl, int level)
+{
+    SSL_ASSERT3(ssl);
+
+    ssl->cert->sec_level = level;
+}
+
+/**
+ * @brief get the SSL security level
+ */
+int SSL_get_security_level(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->cert->sec_level;
+}
+
+/**
+ * @brief get the SSL verifying mode of the SSL context
+ */
+int SSL_CTX_get_verify_mode(const SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->verify_mode;
+}
+
+/**
+ * @brief set the session timeout time
+ */
+long SSL_CTX_set_timeout(SSL_CTX *ctx, long t)
+{
+    long l;
+
+    SSL_ASSERT1(ctx);
+
+    l = ctx->session_timeout;
+    ctx->session_timeout = t;
+
+    return l;
+}
+
+/**
+ * @brief get the session timeout time
+ */
+long SSL_CTX_get_timeout(const SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->session_timeout;
+}
+
+/**
+ * @brief set the SSL if we can read as many as data
+ */
+void SSL_set_read_ahead(SSL *ssl, int yes)
+{
+    SSL_ASSERT3(ssl);
+
+    ssl->rlayer.read_ahead = yes;
+}
+
+/**
+ * @brief set the SSL context if we can read as many as data
+ */
+void SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes)
+{
+    SSL_ASSERT3(ctx);
+
+    ctx->read_ahead = yes;
+}
+
+/**
+ * @brief get the SSL ahead signal if we can read as many as data
+ */
+int SSL_get_read_ahead(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->rlayer.read_ahead;
+}
+
+/**
+ * @brief get the SSL context ahead signal if we can read as many as data
+ */
+long SSL_CTX_get_read_ahead(SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->read_ahead;
+}
+
+/**
+ * @brief check if the SSL context can read as many as data
+ */
+long SSL_CTX_get_default_read_ahead(SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->read_ahead;
+}
+
+/**
+ * @brief set SSL session time
+ */
+long SSL_set_time(SSL *ssl, long t)
+{
+    SSL_ASSERT1(ssl);
+
+    ssl->session->time = t;
+
+    return t;
+}
+
+/**
+ * @brief set SSL session timeout time
+ */
+long SSL_set_timeout(SSL *ssl, long t)
+{
+    SSL_ASSERT1(ssl);
+
+    ssl->session->timeout = t;
+
+    return t;
+}
+
+/**
+ * @brief get the verifying result of the SSL certification
+ */
+long SSL_get_verify_result(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return SSL_METHOD_CALL(get_verify_result, ssl);
+}
+
+/**
+ * @brief get the SSL verifying depth of the SSL context
+ */
+int SSL_CTX_get_verify_depth(const SSL_CTX *ctx)
+{
+    SSL_ASSERT1(ctx);
+
+    return ctx->param.depth;
+}
+
+/**
+ * @brief set the SSL verify depth of the SSL context
+ */
+void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth)
+{
+    SSL_ASSERT3(ctx);
+
+    ctx->param.depth = depth;
+}
+
+/**
+ * @brief get the SSL verifying depth of the SSL
+ */
+int SSL_get_verify_depth(const SSL *ssl)
+{
+    SSL_ASSERT1(ssl);
+
+    return ssl->param.depth;
+}
+
+/**
+ * @brief set the SSL verify depth of the SSL
+ */
+void SSL_set_verify_depth(SSL *ssl, int depth)
+{
+    SSL_ASSERT3(ssl);
+
+    ssl->param.depth = depth;
+}
+
+/**
+ * @brief set the SSL context verifying of the SSL context
+ */
+void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *))
+{
+    SSL_ASSERT3(ctx);
+
+    ctx->verify_mode = mode;
+    ctx->default_verify_callback = verify_callback;
+}
+
+/**
+ * @brief set the SSL verifying of the SSL context
+ */
+void SSL_set_verify(SSL *ssl, int mode, int (*verify_callback)(int, X509_STORE_CTX *))
+{
+    SSL_ASSERT3(ssl);
+
+    ssl->verify_mode = mode;
+    ssl->verify_callback = verify_callback;
+}
+
+void ERR_error_string_n(unsigned long e, char *buf, size_t len)
+{
+       lws_strncpy(buf, "unknown", len);
+}
+
+void ERR_free_strings(void)
+{
+}
+
+char *ERR_error_string(unsigned long e, char *buf)
+{
+       if (!buf)
+               return "unknown";
+
+       switch(e) {
+               case X509_V_ERR_INVALID_CA:
+                       strcpy(buf, "CA is not trusted");
+                       break;
+               case X509_V_ERR_HOSTNAME_MISMATCH:
+                       strcpy(buf, "Hostname mismatch");
+                       break;
+               case X509_V_ERR_CA_KEY_TOO_SMALL:
+                       strcpy(buf, "CA key too small");
+                       break;
+               case X509_V_ERR_CA_MD_TOO_WEAK:
+                       strcpy(buf, "MD key too weak");
+                       break;
+               case X509_V_ERR_CERT_NOT_YET_VALID:
+                       strcpy(buf, "Cert from the future");
+                       break;
+               case X509_V_ERR_CERT_HAS_EXPIRED:
+                       strcpy(buf, "Cert expired");
+                       break;
+               default:
+                       strcpy(buf, "unknown");
+                       break;
+       }
+
+       return buf;
+}
+
+void *SSL_CTX_get_ex_data(const SSL_CTX *ctx, int idx)
+{
+       return NULL;
+}
+
+/*
+ * Openssl wants the valid protocol names supplied like this:
+ *
+ * (unsigned char *)"\x02h2\x08http/1.1", 6 + 9
+ *
+ * Mbedtls wants this:
+ *
+ * Pointer to a NULL-terminated list of supported protocols, in decreasing
+ * preference order. The pointer to the list is recorded by the library for
+ * later reference as required, so the lifetime of the table must be at least
+ * as long as the lifetime of the SSL configuration structure.
+ *
+ * So accept the OpenSSL style and convert to mbedtls style
+ */
+
+
+static void
+_openssl_alpn_to_mbedtls(struct alpn_ctx *ac, char ***palpn_protos)
+{
+       unsigned char *p = ac->data, *q;
+       unsigned char len;
+       char **alpn_protos;
+       int count = 0;
+
+       /* find out how many entries he gave us */
+
+       len = *p++;
+       while (p - ac->data < ac->len) {
+               if (len--) {
+                       p++;
+                       continue;
+               }
+               count++;
+               len = *p++;
+               if (!len)
+                       break;
+       }
+
+       if (!len)
+               count++;
+
+       if (!count)
+               return;
+
+       /* allocate space for count + 1 pointers and the data afterwards */
+
+       alpn_protos = ssl_mem_zalloc((count + 1) * sizeof(char *) + ac->len + 1);
+       if (!alpn_protos)
+               return;
+
+       *palpn_protos = alpn_protos;
+
+       /* convert to mbedtls format */
+
+       q = (unsigned char *)alpn_protos + (count + 1) * sizeof(char *);
+       p = ac->data;
+       count = 0;
+
+       len = *p++;
+       alpn_protos[count] = (char *)q;
+       while (p - ac->data < ac->len) {
+               if (len--) {
+                       *q++ = *p++;
+                       continue;
+               }
+               *q++ = '\0';
+               count++;
+               len = *p++;
+               alpn_protos[count] = (char *)q;
+               if (!len)
+                       break;
+       }
+       if (!len) {
+               *q++ = '\0';
+               count++;
+               /* len = *p++; */
+               alpn_protos[count] = (char *)q;
+       }
+       alpn_protos[count] = NULL; /* last pointer ends list with NULL */
+}
+
+void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, next_proto_cb cb, void *arg)
+{
+       struct alpn_ctx *ac = arg;
+
+       ctx->alpn_cb = cb;
+
+       _openssl_alpn_to_mbedtls(ac, (char ***)&ctx->alpn_protos);
+}
+
+void SSL_set_alpn_select_cb(SSL *ssl, void *arg)
+{
+       struct alpn_ctx *ac = arg;
+
+       _openssl_alpn_to_mbedtls(ac, (char ***)&ssl->alpn_protos);
+
+       _ssl_set_alpn_list(ssl);
+}
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_methods.c b/lib/tls/mbedtls/wrapper/library/ssl_methods.c
new file mode 100644 (file)
index 0000000..0002360
--- /dev/null
@@ -0,0 +1,81 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_methods.h"
+#include "ssl_pm.h"
+
+/**
+ * TLS method function collection
+ */
+IMPLEMENT_TLS_METHOD_FUNC(TLS_method_func,
+        ssl_pm_new, ssl_pm_free,
+        ssl_pm_handshake, ssl_pm_shutdown, ssl_pm_clear,
+        ssl_pm_read, ssl_pm_send, ssl_pm_pending,
+        ssl_pm_set_fd, ssl_pm_get_fd,
+        ssl_pm_set_bufflen,
+        ssl_pm_get_verify_result,
+        ssl_pm_get_state);
+
+/**
+ * TLS or SSL client method collection
+ */
+IMPLEMENT_TLS_METHOD(TLS_ANY_VERSION, 0, TLS_method_func, TLS_client_method);
+
+IMPLEMENT_TLS_METHOD(TLS1_2_VERSION, 0, TLS_method_func, TLSv1_2_client_method);
+
+IMPLEMENT_TLS_METHOD(TLS1_1_VERSION, 0, TLS_method_func, TLSv1_1_client_method);
+
+IMPLEMENT_TLS_METHOD(TLS1_VERSION, 0, TLS_method_func, TLSv1_client_method);
+
+IMPLEMENT_SSL_METHOD(SSL3_VERSION, 0, TLS_method_func, SSLv3_client_method);
+
+/**
+ * TLS or SSL server method collection
+ */
+IMPLEMENT_TLS_METHOD(TLS_ANY_VERSION, 1, TLS_method_func, TLS_server_method);
+
+IMPLEMENT_TLS_METHOD(TLS1_1_VERSION, 1, TLS_method_func, TLSv1_1_server_method);
+
+IMPLEMENT_TLS_METHOD(TLS1_2_VERSION, 1, TLS_method_func, TLSv1_2_server_method);
+
+IMPLEMENT_TLS_METHOD(TLS1_VERSION, 0, TLS_method_func, TLSv1_server_method);
+
+IMPLEMENT_SSL_METHOD(SSL3_VERSION, 1, TLS_method_func, SSLv3_server_method);
+
+/**
+ * TLS or SSL method collection
+ */
+IMPLEMENT_TLS_METHOD(TLS_ANY_VERSION, -1, TLS_method_func, TLS_method);
+
+IMPLEMENT_SSL_METHOD(TLS1_2_VERSION, -1, TLS_method_func, TLSv1_2_method);
+
+IMPLEMENT_SSL_METHOD(TLS1_1_VERSION, -1, TLS_method_func, TLSv1_1_method);
+
+IMPLEMENT_SSL_METHOD(TLS1_VERSION, -1, TLS_method_func, TLSv1_method);
+
+IMPLEMENT_SSL_METHOD(SSL3_VERSION, -1, TLS_method_func, SSLv3_method);
+
+/**
+ * @brief get X509 object method
+ */
+IMPLEMENT_X509_METHOD(X509_method,
+            x509_pm_new, x509_pm_free,
+            x509_pm_load, x509_pm_show_info);
+
+/**
+ * @brief get private key object method
+ */
+IMPLEMENT_PKEY_METHOD(EVP_PKEY_method,
+            pkey_pm_new, pkey_pm_free,
+            pkey_pm_load);
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_pkey.c b/lib/tls/mbedtls/wrapper/library/ssl_pkey.c
new file mode 100644 (file)
index 0000000..567a33e
--- /dev/null
@@ -0,0 +1,239 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_pkey.h"
+#include "ssl_methods.h"
+#include "ssl_dbg.h"
+#include "ssl_port.h"
+
+/**
+ * @brief create a private key object according to input private key
+ */
+EVP_PKEY* __EVP_PKEY_new(EVP_PKEY *ipk)
+{
+    int ret;
+    EVP_PKEY *pkey;
+
+    pkey = ssl_mem_zalloc(sizeof(EVP_PKEY));
+    if (!pkey) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "no enough memory > (pkey)");
+        goto no_mem;
+    }
+
+    if (ipk) {
+        pkey->method = ipk->method;
+    } else {
+        pkey->method = EVP_PKEY_method();
+    }
+
+    ret = EVP_PKEY_METHOD_CALL(new, pkey, ipk);
+    if (ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "EVP_PKEY_METHOD_CALL(new) return %d", ret);
+        goto failed;
+    }
+
+    return pkey;
+
+failed:
+    ssl_mem_free(pkey);
+no_mem:
+    return NULL;
+}
+
+/**
+ * @brief create a private key object
+ */
+EVP_PKEY* EVP_PKEY_new(void)
+{
+    return __EVP_PKEY_new(NULL);
+}
+
+/**
+ * @brief free a private key object
+ */
+void EVP_PKEY_free(EVP_PKEY *pkey)
+{
+    SSL_ASSERT3(pkey);
+
+    EVP_PKEY_METHOD_CALL(free, pkey);
+
+    ssl_mem_free(pkey);
+}
+
+/**
+ * @brief load a character key context into system context. If '*a' is pointed to the
+ *        private key, then load key into it. Or create a new private key object
+ */
+EVP_PKEY *d2i_PrivateKey(int type,
+                         EVP_PKEY **a,
+                         const unsigned char **pp,
+                         long length)
+{
+    int m = 0;
+    int ret;
+    EVP_PKEY *pkey;
+
+    SSL_ASSERT2(pp);
+    SSL_ASSERT2(*pp);
+    SSL_ASSERT2(length);
+
+    if (a && *a) {
+        pkey = *a;
+    } else {
+        pkey = EVP_PKEY_new();;
+        if (!pkey) {
+            SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "EVP_PKEY_new() return NULL");
+            goto failed1;
+        }
+
+        m = 1;
+    }
+
+    ret = EVP_PKEY_METHOD_CALL(load, pkey, *pp, length);
+    if (ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "EVP_PKEY_METHOD_CALL(load) return %d", ret);
+        goto failed2;
+    }
+
+    if (a)
+        *a = pkey;
+
+    return pkey;
+
+failed2:
+    if (m)
+        EVP_PKEY_free(pkey);
+failed1:
+    return NULL;
+}
+
+/**
+ * @brief set the SSL context private key
+ */
+int SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey)
+{
+    SSL_ASSERT1(ctx);
+    SSL_ASSERT1(pkey);
+
+    if (ctx->cert->pkey == pkey)
+        return 1;
+
+    if (ctx->cert->pkey)
+        EVP_PKEY_free(ctx->cert->pkey);
+
+    ctx->cert->pkey = pkey;
+
+    return 1;
+}
+
+/**
+ * @brief set the SSL private key
+ */
+int SSL_use_PrivateKey(SSL *ssl, EVP_PKEY *pkey)
+{
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(pkey);
+
+    if (ssl->cert->pkey == pkey)
+        return 1;
+
+    if (ssl->cert->pkey)
+        EVP_PKEY_free(ssl->cert->pkey);
+
+    ssl->cert->pkey = pkey;
+
+    return 1;
+}
+
+/**
+ * @brief load private key into the SSL context
+ */
+int SSL_CTX_use_PrivateKey_ASN1(int type, SSL_CTX *ctx,
+                                const unsigned char *d, long len)
+{
+    int ret;
+    EVP_PKEY *pk;
+
+    pk = d2i_PrivateKey(0, NULL, &d, len);
+    if (!pk) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_PrivateKey() return NULL");
+        goto failed1;
+    }
+
+    ret = SSL_CTX_use_PrivateKey(ctx, pk);
+    if (!ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "SSL_CTX_use_PrivateKey() return %d", ret);
+        goto failed2;
+    }
+
+    return 1;
+
+failed2:
+    EVP_PKEY_free(pk);
+failed1:
+    return 0;
+}
+
+/**
+ * @brief load private key into the SSL
+ */
+int SSL_use_PrivateKey_ASN1(int type, SSL *ssl,
+                                const unsigned char *d, long len)
+{
+    int ret;
+    EVP_PKEY *pk;
+
+    pk = d2i_PrivateKey(0, NULL, &d, len);
+    if (!pk) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_PrivateKey() return NULL");
+        goto failed1;
+    }
+
+    ret = SSL_use_PrivateKey(ssl, pk);
+    if (!ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "SSL_use_PrivateKey() return %d", ret);
+        goto failed2;
+    }
+
+    return 1;
+
+failed2:
+    EVP_PKEY_free(pk);
+failed1:
+    return 0;
+}
+
+/**
+ * @brief load the private key file into SSL context
+ */
+int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type)
+{
+    return 0;
+}
+
+/**
+ * @brief load the private key file into SSL
+ */
+int SSL_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type)
+{
+    return 0;
+}
+
+/**
+ * @brief load the RSA ASN1 private key into SSL context
+ */
+int SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, const unsigned char *d, long len)
+{
+    return SSL_CTX_use_PrivateKey_ASN1(0, ctx, d, len);
+}
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_stack.c b/lib/tls/mbedtls/wrapper/library/ssl_stack.c
new file mode 100644 (file)
index 0000000..da836da
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_stack.h"
+#include "ssl_dbg.h"
+#include "ssl_port.h"
+
+#ifndef CONFIG_MIN_NODES
+    #define MIN_NODES 4
+#else
+    #define MIN_NODES CONFIG_MIN_NODES
+#endif
+
+/**
+ * @brief create a openssl stack object
+ */
+OPENSSL_STACK* OPENSSL_sk_new(OPENSSL_sk_compfunc c)
+{
+    OPENSSL_STACK *stack;
+    char **data;
+
+    stack = ssl_mem_zalloc(sizeof(OPENSSL_STACK));
+    if (!stack) {
+        SSL_DEBUG(SSL_STACK_ERROR_LEVEL, "no enough memory > (stack)");
+        goto no_mem1;
+    }
+
+    data = ssl_mem_zalloc(sizeof(*data) * MIN_NODES);
+    if (!data) {
+        SSL_DEBUG(SSL_STACK_ERROR_LEVEL, "no enough memory > (data)");
+        goto no_mem2;
+    }
+
+    stack->data = data;
+    stack->num_alloc = MIN_NODES;
+    stack->c = c;
+
+    return stack;
+
+no_mem2:
+    ssl_mem_free(stack);
+no_mem1:
+    return NULL;
+}
+
+/**
+ * @brief create a NULL function openssl stack object
+ */
+OPENSSL_STACK *OPENSSL_sk_new_null(void)
+{
+    return OPENSSL_sk_new((OPENSSL_sk_compfunc)NULL);
+}
+
+/**
+ * @brief free openssl stack object
+ */
+void OPENSSL_sk_free(OPENSSL_STACK *stack)
+{
+    SSL_ASSERT3(stack);
+
+    ssl_mem_free(stack->data);
+    ssl_mem_free(stack);
+}
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_x509.c b/lib/tls/mbedtls/wrapper/library/ssl_x509.c
new file mode 100644 (file)
index 0000000..ed79150
--- /dev/null
@@ -0,0 +1,354 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_x509.h"
+#include "ssl_methods.h"
+#include "ssl_dbg.h"
+#include "ssl_port.h"
+
+#include <assert.h>
+
+/**
+ * @brief show X509 certification information
+ */
+int __X509_show_info(X509 *x)
+{
+    return X509_METHOD_CALL(show_info, x);
+}
+
+/**
+ * @brief create a X509 certification object according to input X509 certification
+ */
+X509* __X509_new(X509 *ix)
+{
+    int ret;
+    X509 *x;
+
+    x = ssl_mem_zalloc(sizeof(X509));
+    if (!x) {
+        SSL_DEBUG(SSL_X509_ERROR_LEVEL, "no enough memory > (x)");
+        goto no_mem;
+    }
+
+    if (ix)
+        x->method = ix->method;
+    else
+        x->method = X509_method();
+
+    ret = X509_METHOD_CALL(new, x, ix);
+    if (ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "X509_METHOD_CALL(new) return %d", ret);
+        goto failed;
+    }
+
+    return x;
+
+failed:
+    ssl_mem_free(x);
+no_mem:
+    return NULL;
+}
+
+/**
+ * @brief create a X509 certification object
+ */
+X509* X509_new(void)
+{
+    return __X509_new(NULL);
+}
+
+/**
+ * @brief free a X509 certification object
+ */
+void X509_free(X509 *x)
+{
+    SSL_ASSERT3(x);
+
+    X509_METHOD_CALL(free, x);
+
+    ssl_mem_free(x);
+};
+
+/**
+ * @brief load a character certification context into system context. If '*cert' is pointed to the
+ *        certification, then load certification into it. Or create a new X509 certification object
+ */
+X509* d2i_X509(X509 **cert, const unsigned char *buffer, long len)
+{
+    int m = 0;
+    int ret;
+    X509 *x;
+
+    SSL_ASSERT2(buffer);
+    SSL_ASSERT2(len);
+
+    if (cert && *cert) {
+        x = *cert;
+    } else {
+        x = X509_new();
+        if (!x) {
+            SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "X509_new() return NULL");
+            goto failed1;
+        }
+        m = 1;
+    }
+
+    ret = X509_METHOD_CALL(load, x, buffer, len);
+    if (ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "X509_METHOD_CALL(load) return %d", ret);
+        goto failed2;
+    }
+
+    return x;
+
+failed2:
+    if (m)
+        X509_free(x);
+failed1:
+    return NULL;
+}
+
+/**
+ * @brief return SSL X509 verify parameters
+ */
+
+X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl)
+{
+       return &ssl->param;
+}
+
+/**
+ * @brief set X509 host verification flags
+ */
+
+int X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param,
+                                   unsigned long flags)
+{
+       /* flags not supported yet */
+       return 0;
+}
+
+/**
+ * @brief clear X509 host verification flags
+ */
+
+int X509_VERIFY_PARAM_clear_hostflags(X509_VERIFY_PARAM *param,
+                                     unsigned long flags)
+{
+       /* flags not supported yet */
+       return 0;
+}
+
+/**
+ * @brief set SSL context client CA certification
+ */
+int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x)
+{
+    SSL_ASSERT1(ctx);
+    SSL_ASSERT1(x);
+    assert(ctx);
+    if (ctx->client_CA == x)
+        return 1;
+
+    X509_free(ctx->client_CA);
+
+    ctx->client_CA = x;
+
+    return 1;
+}
+
+/**
+ * @brief add CA client certification into the SSL
+ */
+int SSL_CTX_add_client_CA_ASN1(SSL_CTX *ctx, int len,
+                const unsigned char *d)
+{
+       X509 *x;
+
+       x = d2i_X509(NULL, d, len);
+       if (!x) {
+               SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_X509() return NULL");
+               return 0;
+       }
+    SSL_ASSERT1(ctx);
+
+    X509_free(ctx->client_CA);
+
+    ctx->client_CA = x;
+
+    return 1;
+}
+
+/**
+ * @brief add CA client certification into the SSL
+ */
+int SSL_add_client_CA(SSL *ssl, X509 *x)
+{
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(x);
+
+    if (ssl->client_CA == x)
+        return 1;
+
+    X509_free(ssl->client_CA);
+
+    ssl->client_CA = x;
+
+    return 1;
+}
+
+/**
+ * @brief set the SSL context certification
+ */
+int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x)
+{
+    SSL_ASSERT1(ctx);
+    SSL_ASSERT1(x);
+
+    if (ctx->cert->x509 == x)
+        return 1;
+
+    X509_free(ctx->cert->x509);
+
+    ctx->cert->x509 = x;
+
+    return 1;
+}
+
+/**
+ * @brief set the SSL certification
+ */
+int SSL_use_certificate(SSL *ssl, X509 *x)
+{
+    SSL_ASSERT1(ssl);
+    SSL_ASSERT1(x);
+
+    if (ssl->cert->x509 == x)
+        return 1;
+
+    X509_free(ssl->cert->x509);
+
+    ssl->cert->x509 = x;
+
+    return 1;
+}
+
+/**
+ * @brief get the SSL certification point
+ */
+X509 *SSL_get_certificate(const SSL *ssl)
+{
+    SSL_ASSERT2(ssl);
+
+    return ssl->cert->x509;
+}
+
+/**
+ * @brief load certification into the SSL context
+ */
+int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, int len,
+                                 const unsigned char *d)
+{
+    int ret;
+    X509 *x;
+
+    x = d2i_X509(NULL, d, len);
+    if (!x) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_X509() return NULL");
+        goto failed1;
+    }
+
+    ret = SSL_CTX_use_certificate(ctx, x);
+    if (!ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "SSL_CTX_use_certificate() return %d", ret);
+        goto failed2;
+    }
+
+    return 1;
+
+failed2:
+    X509_free(x);
+failed1:
+    return 0;
+}
+
+/**
+ * @brief load certification into the SSL
+ */
+int SSL_use_certificate_ASN1(SSL *ssl, int len,
+                             const unsigned char *d)
+{
+    int ret;
+    X509 *x;
+
+    x = d2i_X509(NULL, d, len);
+    if (!x) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_X509() return NULL");
+        goto failed1;
+    }
+
+    ret = SSL_use_certificate(ssl, x);
+    if (!ret) {
+        SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "SSL_use_certificate() return %d", ret);
+        goto failed2;
+    }
+
+    return 1;
+
+failed2:
+    X509_free(x);
+failed1:
+    return 0;
+}
+
+/**
+ * @brief load the certification file into SSL context
+ */
+int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type)
+{
+    return 0;
+}
+
+/**
+ * @brief load the certification file into SSL
+ */
+int SSL_use_certificate_file(SSL *ssl, const char *file, int type)
+{
+    return 0;
+}
+
+/**
+ * @brief get peer certification
+ */
+X509 *SSL_get_peer_certificate(const SSL *ssl)
+{
+    SSL_ASSERT2(ssl);
+
+    return ssl->session->peer;
+}
+
+int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx)
+{
+       return X509_V_ERR_UNSPECIFIED;
+}
+
+int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx)
+{
+       return 0;
+}
+
+const char *X509_verify_cert_error_string(long n)
+{
+       return "unknown";
+}
diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
new file mode 100755 (executable)
index 0000000..dea6894
--- /dev/null
@@ -0,0 +1,950 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_pm.h"
+#include "ssl_port.h"
+#include "ssl_dbg.h"
+
+/* mbedtls include */
+#include "mbedtls/platform.h"
+#include "mbedtls/net_sockets.h"
+#include "mbedtls/debug.h"
+#include "mbedtls/entropy.h"
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/error.h"
+#include "mbedtls/certs.h"
+
+#include "core/private.h"
+
+#define X509_INFO_STRING_LENGTH 8192
+
+struct ssl_pm
+{
+    /* local socket file description */
+    mbedtls_net_context fd;
+    /* remote client socket file description */
+    mbedtls_net_context cl_fd;
+
+    mbedtls_ssl_config conf;
+
+    mbedtls_ctr_drbg_context ctr_drbg;
+
+    mbedtls_ssl_context ssl;
+
+    mbedtls_entropy_context entropy;
+
+    SSL *owner;
+};
+
+struct x509_pm
+{
+    mbedtls_x509_crt *x509_crt;
+
+    mbedtls_x509_crt *ex_crt;
+};
+
+struct pkey_pm
+{
+    mbedtls_pk_context *pkey;
+
+    mbedtls_pk_context *ex_pkey;
+};
+
+unsigned int max_content_len;
+
+/*********************************************************************************************/
+/************************************ SSL arch interface *************************************/
+
+//#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
+
+/* mbedtls debug level */
+#define MBEDTLS_DEBUG_LEVEL 4
+
+/**
+ * @brief mbedtls debug function
+ */
+static void ssl_platform_debug(void *ctx, int level,
+                     const char *file, int line,
+                     const char *str)
+{
+    /* Shorten 'file' from the whole file path to just the filename
+
+       This is a bit wasteful because the macros are compiled in with
+       the full _FILE_ path in each case.
+    */
+//    char *file_sep = rindex(file, '/');
+  //  if(file_sep)
+    //    file = file_sep + 1;
+
+    printf("%s:%d %s", file, line, str);
+}
+//#endif
+
+/**
+ * @brief create SSL low-level object
+ */
+int ssl_pm_new(SSL *ssl)
+{
+    struct ssl_pm *ssl_pm;
+    int ret;
+
+    const unsigned char pers[] = "OpenSSL PM";
+    size_t pers_len = sizeof(pers);
+
+    int endpoint;
+    int version;
+
+    const SSL_METHOD *method = ssl->method;
+
+    ssl_pm = ssl_mem_zalloc(sizeof(struct ssl_pm));
+    if (!ssl_pm) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (ssl_pm)");
+        goto no_mem;
+    }
+
+    ssl_pm->owner = ssl;
+
+    if (!ssl->ctx->read_buffer_len)
+           ssl->ctx->read_buffer_len = 2048;
+
+    max_content_len = ssl->ctx->read_buffer_len;
+    // printf("ssl->ctx->read_buffer_len = %d ++++++++++++++++++++\n", ssl->ctx->read_buffer_len);
+
+    mbedtls_net_init(&ssl_pm->fd);
+    mbedtls_net_init(&ssl_pm->cl_fd);
+
+    mbedtls_ssl_config_init(&ssl_pm->conf);
+    mbedtls_ctr_drbg_init(&ssl_pm->ctr_drbg);
+    mbedtls_entropy_init(&ssl_pm->entropy);
+    mbedtls_ssl_init(&ssl_pm->ssl);
+
+    ret = mbedtls_ctr_drbg_seed(&ssl_pm->ctr_drbg, mbedtls_entropy_func, &ssl_pm->entropy, pers, pers_len);
+    if (ret) {
+        lwsl_notice("%s: mbedtls_ctr_drbg_seed() return -0x%x", __func__, -ret);
+        //goto mbedtls_err1;
+    }
+
+    if (method->endpoint) {
+        endpoint = MBEDTLS_SSL_IS_SERVER;
+    } else {
+        endpoint = MBEDTLS_SSL_IS_CLIENT;
+    }
+    ret = mbedtls_ssl_config_defaults(&ssl_pm->conf, endpoint, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
+    if (ret) {
+           lwsl_err("%s: mbedtls_ssl_config_defaults() return -0x%x", __func__, -ret);
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_config_defaults() return -0x%x", -ret);
+        goto mbedtls_err2;
+    }
+
+    if (TLS_ANY_VERSION != ssl->version) {
+        if (TLS1_2_VERSION == ssl->version)
+            version = MBEDTLS_SSL_MINOR_VERSION_3;
+        else if (TLS1_1_VERSION == ssl->version)
+            version = MBEDTLS_SSL_MINOR_VERSION_2;
+        else if (TLS1_VERSION == ssl->version)
+            version = MBEDTLS_SSL_MINOR_VERSION_1;
+        else
+            version = MBEDTLS_SSL_MINOR_VERSION_0;
+
+        mbedtls_ssl_conf_max_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, version);
+        mbedtls_ssl_conf_min_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, version);
+    } else {
+        mbedtls_ssl_conf_max_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
+        mbedtls_ssl_conf_min_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0);
+    }
+
+    mbedtls_ssl_conf_rng(&ssl_pm->conf, mbedtls_ctr_drbg_random, &ssl_pm->ctr_drbg);
+
+//#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
+ //   mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL);
+//    mbedtls_ssl_conf_dbg(&ssl_pm->conf, ssl_platform_debug, NULL);
+//#else
+    mbedtls_ssl_conf_dbg(&ssl_pm->conf, ssl_platform_debug, NULL);
+//#endif
+
+    ret = mbedtls_ssl_setup(&ssl_pm->ssl, &ssl_pm->conf);
+    if (ret) {
+           lwsl_err("%s: mbedtls_ssl_setup() return -0x%x", __func__, -ret);
+
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_setup() return -0x%x", -ret);
+        goto mbedtls_err2;
+    }
+
+    mbedtls_ssl_set_bio(&ssl_pm->ssl, &ssl_pm->fd, mbedtls_net_send, mbedtls_net_recv, NULL);
+
+    ssl->ssl_pm = ssl_pm;
+
+    return 0;
+
+mbedtls_err2:
+    mbedtls_ssl_config_free(&ssl_pm->conf);
+    mbedtls_ctr_drbg_free(&ssl_pm->ctr_drbg);
+//mbedtls_err1:
+    mbedtls_entropy_free(&ssl_pm->entropy);
+    ssl_mem_free(ssl_pm);
+no_mem:
+    return -1;
+}
+
+/**
+ * @brief free SSL low-level object
+ */
+void ssl_pm_free(SSL *ssl)
+{
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    mbedtls_ctr_drbg_free(&ssl_pm->ctr_drbg);
+    mbedtls_entropy_free(&ssl_pm->entropy);
+    mbedtls_ssl_config_free(&ssl_pm->conf);
+    mbedtls_ssl_free(&ssl_pm->ssl);
+
+    ssl_mem_free(ssl_pm);
+    ssl->ssl_pm = NULL;
+}
+
+/**
+ * @brief reload SSL low-level certification object
+ */
+static int ssl_pm_reload_crt(SSL *ssl)
+{
+    int ret;
+    int mode;
+    struct ssl_pm *ssl_pm = ssl->ssl_pm;
+    struct x509_pm *ca_pm = (struct x509_pm *)ssl->client_CA->x509_pm;
+
+    struct pkey_pm *pkey_pm = (struct pkey_pm *)ssl->cert->pkey->pkey_pm;
+    struct x509_pm *crt_pm = (struct x509_pm *)ssl->cert->x509->x509_pm;
+
+    if (ssl->verify_mode == SSL_VERIFY_PEER)
+        mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+    else if (ssl->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+        mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+    else if (ssl->verify_mode == SSL_VERIFY_CLIENT_ONCE)
+        mode = MBEDTLS_SSL_VERIFY_UNSET;
+    else
+        mode = MBEDTLS_SSL_VERIFY_NONE;
+
+    mbedtls_ssl_conf_authmode(&ssl_pm->conf, mode);
+
+    if (ca_pm->x509_crt) {
+        mbedtls_ssl_conf_ca_chain(&ssl_pm->conf, ca_pm->x509_crt, NULL);
+    } else if (ca_pm->ex_crt) {
+        mbedtls_ssl_conf_ca_chain(&ssl_pm->conf, ca_pm->ex_crt, NULL);
+    }
+
+    if (crt_pm->x509_crt && pkey_pm->pkey) {
+        ret = mbedtls_ssl_conf_own_cert(&ssl_pm->conf, crt_pm->x509_crt, pkey_pm->pkey);
+    } else if (crt_pm->ex_crt && pkey_pm->ex_pkey) {
+        ret = mbedtls_ssl_conf_own_cert(&ssl_pm->conf, crt_pm->ex_crt, pkey_pm->ex_pkey);
+    } else {
+        ret = 0;
+    }
+
+    if (ret) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_conf_own_cert() return -0x%x", -ret);
+        ret = -1;
+    }
+
+    return ret;
+}
+
+/*
+ * Perform the mbedtls SSL handshake instead of mbedtls_ssl_handshake.
+ * We can add debug here.
+ */
+static int mbedtls_handshake( mbedtls_ssl_context *ssl )
+{
+    int ret = 0;
+
+    while (ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) {
+        ret = mbedtls_ssl_handshake_step(ssl);
+
+        lwsl_info("%s: ssl ret -%x state %d\n", __func__, -ret, ssl->state);
+
+        if (ret != 0)
+            break;
+    }
+
+    return ret;
+}
+
+#if !defined(LWS_PLAT_OPTEE)
+#include <errno.h>
+#endif
+
+int ssl_pm_handshake(SSL *ssl)
+{
+    int ret;
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    ssl->err = 0;
+    errno = 0;
+
+    ret = ssl_pm_reload_crt(ssl);
+    if (ret) {
+           printf("%s: cert reload failed\n", __func__);
+        return 0;
+    }
+
+    if (ssl_pm->ssl.state != MBEDTLS_SSL_HANDSHAKE_OVER) {
+           ssl_speed_up_enter();
+
+          /* mbedtls return codes
+           * 0 = successful, or MBEDTLS_ERR_SSL_WANT_READ/WRITE
+           * anything else = death
+           */
+           ret = mbedtls_handshake(&ssl_pm->ssl);
+           ssl_speed_up_exit();
+    } else
+           ret = 0;
+
+    /*
+     * OpenSSL return codes:
+     *   0 = did not complete, but may be retried
+     *   1 = successfully completed
+     *   <0 = death
+     */
+    if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+           ssl->err = ret;
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_handshake() return -0x%x", -ret);
+        return 0; /* OpenSSL: did not complete but may be retried */
+    }
+
+    if (ret == 0) { /* successful */
+        struct x509_pm *x509_pm = (struct x509_pm *)ssl->session->peer->x509_pm;
+
+        x509_pm->ex_crt = (mbedtls_x509_crt *)mbedtls_ssl_get_peer_cert(&ssl_pm->ssl);
+        return 1; /* openssl successful */
+    }
+
+    if (errno == 11) {
+           ssl->err = ret == MBEDTLS_ERR_SSL_WANT_READ;
+
+           return 0;
+    }
+
+    lwsl_info("%s: mbedtls_ssl_handshake() returned -0x%x\n", __func__, -ret);
+
+    /* it's had it */
+
+    ssl->err = SSL_ERROR_SYSCALL;
+
+    return -1; /* openssl death */
+}
+
+mbedtls_x509_crt *
+ssl_ctx_get_mbedtls_x509_crt(SSL_CTX *ssl_ctx)
+{
+       struct x509_pm *x509_pm = (struct x509_pm *)ssl_ctx->cert->x509->x509_pm;
+
+       if (!x509_pm)
+               return NULL;
+
+       return x509_pm->x509_crt;
+}
+
+mbedtls_x509_crt *
+ssl_get_peer_mbedtls_x509_crt(SSL *ssl)
+{
+       struct x509_pm *x509_pm = (struct x509_pm *)ssl->session->peer->x509_pm;
+
+       if (!x509_pm)
+               return NULL;
+
+       return x509_pm->ex_crt;
+}
+
+int ssl_pm_shutdown(SSL *ssl)
+{
+    int ret;
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    ret = mbedtls_ssl_close_notify(&ssl_pm->ssl);
+    if (ret) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_close_notify() return -0x%x", -ret);
+        if (ret == MBEDTLS_ERR_NET_CONN_RESET)
+               ssl->err = SSL_ERROR_SYSCALL;
+        ret = -1; /* OpenSSL: "Call SSL_get_error with the return value to find the reason */
+    } else {
+        struct x509_pm *x509_pm = (struct x509_pm *)ssl->session->peer->x509_pm;
+
+        x509_pm->ex_crt = NULL;
+        ret = 1; /* OpenSSL: "The shutdown was successfully completed"
+                    ...0 means retry */
+    }
+
+    return ret;
+}
+
+int ssl_pm_clear(SSL *ssl)
+{
+    return ssl_pm_shutdown(ssl);
+}
+
+
+int ssl_pm_read(SSL *ssl, void *buffer, int len)
+{
+    int ret;
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    ret = mbedtls_ssl_read(&ssl_pm->ssl, buffer, len);
+    if (ret < 0) {
+        //   lwsl_notice("%s: mbedtls_ssl_read says -0x%x\n", __func__, -ret);
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_read() return -0x%x", -ret);
+        if (ret == MBEDTLS_ERR_NET_CONN_RESET ||
+            ret <= MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE) /* fatal errors */
+               ssl->err = SSL_ERROR_SYSCALL;
+        ret = -1;
+    }
+
+    return ret;
+}
+
+/*
+ * This returns -1, or the length sent.
+ * If -1, then you need to find out if the error was
+ * fatal or recoverable using SSL_get_error()
+ */
+int ssl_pm_send(SSL *ssl, const void *buffer, int len)
+{
+    int ret;
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    ret = mbedtls_ssl_write(&ssl_pm->ssl, buffer, len);
+    /*
+     * We can get a positive number, which may be less than len... that
+     * much was sent successfully and you can call again to send more.
+     *
+     * We can get a negative mbedtls error code... if WANT_WRITE or WANT_READ,
+     * it's nonfatal and means it should be retried as-is.  If something else,
+     * it's fatal actually.
+     *
+     * If this function returns something other than a positive value or
+     * MBEDTLS_ERR_SSL_WANT_READ/WRITE, the ssl context becomes unusable, and
+     * you should either free it or call mbedtls_ssl_session_reset() on it
+     * before re-using it for a new connection; the current connection must
+     * be closed.
+     *
+     * When this function returns MBEDTLS_ERR_SSL_WANT_WRITE/READ, it must be
+     * called later with the same arguments, until it returns a positive value.
+     */
+
+    if (ret < 0) {
+           SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_write() return -0x%x", -ret);
+       switch (ret) {
+       case MBEDTLS_ERR_NET_SEND_FAILED:
+       case MBEDTLS_ERR_NET_CONN_RESET:
+               ssl->err = SSL_ERROR_SYSCALL;
+               break;
+       case MBEDTLS_ERR_SSL_WANT_WRITE:
+               ssl->err = SSL_ERROR_WANT_WRITE;
+               break;
+       case MBEDTLS_ERR_SSL_WANT_READ:
+               ssl->err = SSL_ERROR_WANT_READ;
+               break;
+       default:
+               break;
+       }
+
+       ret = -1;
+    }
+
+    return ret;
+}
+
+int ssl_pm_pending(const SSL *ssl)
+{
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    return mbedtls_ssl_get_bytes_avail(&ssl_pm->ssl);
+}
+
+void ssl_pm_set_fd(SSL *ssl, int fd, int mode)
+{
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    ssl_pm->fd.fd = fd;
+}
+
+int ssl_pm_get_fd(const SSL *ssl, int mode)
+{
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    return ssl_pm->fd.fd;
+}
+
+OSSL_HANDSHAKE_STATE ssl_pm_get_state(const SSL *ssl)
+{
+    OSSL_HANDSHAKE_STATE state;
+
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    switch (ssl_pm->ssl.state)
+    {
+        case MBEDTLS_SSL_CLIENT_HELLO:
+            state = TLS_ST_CW_CLNT_HELLO;
+            break;
+        case MBEDTLS_SSL_SERVER_HELLO:
+            state = TLS_ST_SW_SRVR_HELLO;
+            break;
+        case MBEDTLS_SSL_SERVER_CERTIFICATE:
+            state = TLS_ST_SW_CERT;
+            break;
+        case MBEDTLS_SSL_SERVER_HELLO_DONE:
+            state = TLS_ST_SW_SRVR_DONE;
+            break;
+        case MBEDTLS_SSL_CLIENT_KEY_EXCHANGE:
+            state = TLS_ST_CW_KEY_EXCH;
+            break;
+        case MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC:
+            state = TLS_ST_CW_CHANGE;
+            break;
+        case MBEDTLS_SSL_CLIENT_FINISHED:
+            state = TLS_ST_CW_FINISHED;
+            break;
+        case MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC:
+            state = TLS_ST_SW_CHANGE;
+            break;
+        case MBEDTLS_SSL_SERVER_FINISHED:
+            state = TLS_ST_SW_FINISHED;
+            break;
+        case MBEDTLS_SSL_CLIENT_CERTIFICATE:
+            state = TLS_ST_CW_CERT;
+            break;
+        case MBEDTLS_SSL_SERVER_KEY_EXCHANGE:
+            state = TLS_ST_SR_KEY_EXCH;
+            break;
+        case MBEDTLS_SSL_SERVER_NEW_SESSION_TICKET:
+            state = TLS_ST_SW_SESSION_TICKET;
+            break;
+        case MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT:
+            state = TLS_ST_SW_CERT_REQ;
+            break;
+        case MBEDTLS_SSL_HANDSHAKE_OVER:
+            state = TLS_ST_OK;
+            break;
+        default :
+            state = TLS_ST_BEFORE;
+            break;
+    }
+
+    return state;
+}
+
+int x509_pm_show_info(X509 *x)
+{
+    int ret;
+    char *buf;
+    mbedtls_x509_crt *x509_crt;
+    struct x509_pm *x509_pm = x->x509_pm;
+
+    if (x509_pm->x509_crt)
+        x509_crt = x509_pm->x509_crt;
+    else if (x509_pm->ex_crt)
+        x509_crt = x509_pm->ex_crt;
+    else
+        x509_crt = NULL;
+
+    if (!x509_crt)
+        return -1;
+
+    buf = ssl_mem_malloc(X509_INFO_STRING_LENGTH);
+    if (!buf) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (buf)");
+        goto no_mem;
+    }
+
+    ret = mbedtls_x509_crt_info(buf, X509_INFO_STRING_LENGTH - 1, "", x509_crt);
+    if (ret <= 0) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_x509_crt_info() return -0x%x", -ret);
+        goto mbedtls_err1;
+    }
+
+    buf[ret] = 0;
+
+    ssl_mem_free(buf);
+
+    SSL_DEBUG(SSL_DEBUG_ON, "%s", buf);
+
+    return 0;
+
+mbedtls_err1:
+    ssl_mem_free(buf);
+no_mem:
+    return -1;
+}
+
+int x509_pm_new(X509 *x, X509 *m_x)
+{
+    struct x509_pm *x509_pm;
+
+    x509_pm = ssl_mem_zalloc(sizeof(struct x509_pm));
+    if (!x509_pm) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (x509_pm)");
+        goto failed1;
+    }
+
+    x->x509_pm = x509_pm;
+
+    if (m_x) {
+        struct x509_pm *m_x509_pm = (struct x509_pm *)m_x->x509_pm;
+
+        x509_pm->ex_crt = m_x509_pm->x509_crt;
+    }
+
+    return 0;
+
+failed1:
+    return -1;
+}
+
+void x509_pm_free(X509 *x)
+{
+    struct x509_pm *x509_pm = (struct x509_pm *)x->x509_pm;
+
+    if (x509_pm->x509_crt) {
+        mbedtls_x509_crt_free(x509_pm->x509_crt);
+
+        ssl_mem_free(x509_pm->x509_crt);
+        x509_pm->x509_crt = NULL;
+    }
+
+    ssl_mem_free(x->x509_pm);
+    x->x509_pm = NULL;
+}
+
+int x509_pm_load(X509 *x, const unsigned char *buffer, int len)
+{
+    int ret;
+    unsigned char *load_buf;
+    struct x509_pm *x509_pm = (struct x509_pm *)x->x509_pm;
+
+       if (x509_pm->x509_crt)
+        mbedtls_x509_crt_free(x509_pm->x509_crt);
+
+    if (!x509_pm->x509_crt) {
+        x509_pm->x509_crt = ssl_mem_malloc(sizeof(mbedtls_x509_crt));
+        if (!x509_pm->x509_crt) {
+            SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (x509_pm->x509_crt)");
+            goto no_mem;
+        }
+    }
+
+    mbedtls_x509_crt_init(x509_pm->x509_crt);
+    if (buffer[0] != 0x30) {
+           load_buf = ssl_mem_malloc(len + 1);
+           if (!load_buf) {
+               SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (load_buf)");
+               goto failed;
+           }
+
+           ssl_memcpy(load_buf, buffer, len);
+           load_buf[len] = '\0';
+
+           ret = mbedtls_x509_crt_parse(x509_pm->x509_crt, load_buf, len + 1);
+           ssl_mem_free(load_buf);
+    } else {
+           // printf("parsing as der\n");
+
+           ret = mbedtls_x509_crt_parse_der(x509_pm->x509_crt, buffer, len);
+    }
+
+    if (ret) {
+        printf("mbedtls_x509_crt_parse return -0x%x", -ret);
+        goto failed;
+    }
+
+    return 0;
+
+failed:
+    mbedtls_x509_crt_free(x509_pm->x509_crt);
+    ssl_mem_free(x509_pm->x509_crt);
+    x509_pm->x509_crt = NULL;
+no_mem:
+    return -1;
+}
+
+int pkey_pm_new(EVP_PKEY *pk, EVP_PKEY *m_pkey)
+{
+    struct pkey_pm *pkey_pm;
+
+    pkey_pm = ssl_mem_zalloc(sizeof(struct pkey_pm));
+    if (!pkey_pm)
+        return -1;
+
+    pk->pkey_pm = pkey_pm;
+
+    if (m_pkey) {
+        struct pkey_pm *m_pkey_pm = (struct pkey_pm *)m_pkey->pkey_pm;
+
+        pkey_pm->ex_pkey = m_pkey_pm->pkey;
+    }
+
+    return 0;
+}
+
+void pkey_pm_free(EVP_PKEY *pk)
+{
+    struct pkey_pm *pkey_pm = (struct pkey_pm *)pk->pkey_pm;
+
+    if (pkey_pm->pkey) {
+        mbedtls_pk_free(pkey_pm->pkey);
+
+        ssl_mem_free(pkey_pm->pkey);
+        pkey_pm->pkey = NULL;
+    }
+
+    ssl_mem_free(pk->pkey_pm);
+    pk->pkey_pm = NULL;
+}
+
+int pkey_pm_load(EVP_PKEY *pk, const unsigned char *buffer, int len)
+{
+    int ret;
+    unsigned char *load_buf;
+    struct pkey_pm *pkey_pm = (struct pkey_pm *)pk->pkey_pm;
+
+    if (pkey_pm->pkey)
+        mbedtls_pk_free(pkey_pm->pkey);
+
+    if (!pkey_pm->pkey) {
+        pkey_pm->pkey = ssl_mem_malloc(sizeof(mbedtls_pk_context));
+        if (!pkey_pm->pkey) {
+            SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (pkey_pm->pkey)");
+            goto no_mem;
+        }
+    }
+
+    load_buf = ssl_mem_malloc(len + 1);
+    if (!load_buf) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (load_buf)");
+        goto failed;
+    }
+
+    ssl_memcpy(load_buf, buffer, len);
+    load_buf[len] = '\0';
+
+    mbedtls_pk_init(pkey_pm->pkey);
+
+    ret = mbedtls_pk_parse_key(pkey_pm->pkey, load_buf, len + 1, NULL, 0);
+    ssl_mem_free(load_buf);
+
+    if (ret) {
+        SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_pk_parse_key return -0x%x", -ret);
+        goto failed;
+    }
+
+    return 0;
+
+failed:
+    mbedtls_pk_free(pkey_pm->pkey);
+    ssl_mem_free(pkey_pm->pkey);
+    pkey_pm->pkey = NULL;
+no_mem:
+    return -1;
+}
+
+
+
+void ssl_pm_set_bufflen(SSL *ssl, int len)
+{
+    max_content_len = len;
+}
+
+long ssl_pm_get_verify_result(const SSL *ssl)
+{
+       uint32_t ret;
+       long verify_result;
+       struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+       ret = mbedtls_ssl_get_verify_result(&ssl_pm->ssl);
+       if (!ret)
+               return X509_V_OK;
+
+       if (ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED ||
+               (ret & MBEDTLS_X509_BADCRL_NOT_TRUSTED))
+               verify_result = X509_V_ERR_INVALID_CA;
+
+       else if (ret & MBEDTLS_X509_BADCERT_CN_MISMATCH)
+               verify_result = X509_V_ERR_HOSTNAME_MISMATCH;
+
+       else if ((ret & MBEDTLS_X509_BADCERT_BAD_KEY) ||
+               (ret & MBEDTLS_X509_BADCRL_BAD_KEY))
+               verify_result = X509_V_ERR_CA_KEY_TOO_SMALL;
+
+       else if ((ret & MBEDTLS_X509_BADCERT_BAD_MD) ||
+               (ret & MBEDTLS_X509_BADCRL_BAD_MD))
+               verify_result = X509_V_ERR_CA_MD_TOO_WEAK;
+
+       else if ((ret & MBEDTLS_X509_BADCERT_FUTURE) ||
+               (ret & MBEDTLS_X509_BADCRL_FUTURE))
+               verify_result = X509_V_ERR_CERT_NOT_YET_VALID;
+
+       else if ((ret & MBEDTLS_X509_BADCERT_EXPIRED) ||
+               (ret & MBEDTLS_X509_BADCRL_EXPIRED))
+               verify_result = X509_V_ERR_CERT_HAS_EXPIRED;
+
+       else
+               verify_result = X509_V_ERR_UNSPECIFIED;
+
+       SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL,
+                 "mbedtls_ssl_get_verify_result() return 0x%x", ret);
+
+       return verify_result;
+}
+
+/**
+ * @brief set expected hostname on peer cert CN
+ */
+
+int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param,
+                                const char *name, size_t namelen)
+{
+       SSL *ssl = (SSL *)((char *)param - offsetof(SSL, param));
+       struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+       char *name_cstr = NULL;
+
+       if (namelen) {
+               name_cstr = malloc(namelen + 1);
+               if (!name_cstr)
+                       return 0;
+               memcpy(name_cstr, name, namelen);
+               name_cstr[namelen] = '\0';
+               name = name_cstr;
+       }
+
+       mbedtls_ssl_set_hostname(&ssl_pm->ssl, name);
+
+       if (namelen)
+               free(name_cstr);
+
+       return 1;
+}
+
+void _ssl_set_alpn_list(const SSL *ssl)
+{
+#if defined(LWS_HAVE_mbedtls_ssl_conf_alpn_protocols)
+       if (ssl->alpn_protos) {
+               if (mbedtls_ssl_conf_alpn_protocols(&((struct ssl_pm *)(ssl->ssl_pm))->conf, ssl->alpn_protos))
+                       fprintf(stderr, "mbedtls_ssl_conf_alpn_protocols failed\n");
+
+               return;
+       }
+       if (!ssl->ctx->alpn_protos)
+               return;
+       if (mbedtls_ssl_conf_alpn_protocols(&((struct ssl_pm *)(ssl->ssl_pm))->conf, ssl->ctx->alpn_protos))
+               fprintf(stderr, "mbedtls_ssl_conf_alpn_protocols failed\n");
+#endif
+}
+
+void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
+                            unsigned int *len)
+{
+#if defined(LWS_HAVE_mbedtls_ssl_get_alpn_protocol)
+       const char *alp = mbedtls_ssl_get_alpn_protocol(&((struct ssl_pm *)(ssl->ssl_pm))->ssl);
+
+       *data = (const unsigned char *)alp;
+       if (alp)
+               *len = strlen(alp);
+       else
+               *len = 0;
+#endif
+}
+
+int SSL_set_sni_callback(SSL *ssl, int(*cb)(void *, mbedtls_ssl_context *,
+                        const unsigned char *, size_t), void *param)
+{
+#if defined(LWS_HAVE_mbedtls_ssl_conf_sni)
+       struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+       mbedtls_ssl_conf_sni(&ssl_pm->conf, cb, param);
+#endif
+       return 0;
+}
+
+SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc)
+{
+       struct ssl_pm *ssl_pm = (struct ssl_pm *)((char *)msc - offsetof(struct ssl_pm, ssl));
+
+       return ssl_pm->owner;
+}
+
+#include "ssl_cert.h"
+
+void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx)
+{
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_authmode) || \
+    defined(LWS_HAVE_mbedtls_ssl_set_hs_ca_chain) || \
+    defined(LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       struct ssl_pm *ssl_pm = ssl->ssl_pm;
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       struct x509_pm *x509_pm;
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_ca_chain)
+       struct x509_pm *x509_pm_ca;
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       struct pkey_pm *pkey_pm;
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_authmode)
+       int mode;
+#endif
+
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       if (!ctx->cert || !ctx->cert->x509)
+               return;
+       x509_pm = (struct x509_pm *)ctx->cert->x509->x509_pm;
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_ca_chain)
+       if (!ctx->client_CA)
+               return;
+       x509_pm_ca = (struct x509_pm *)ctx->client_CA->x509_pm;
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       if (!ctx->cert || !ctx->cert->pkey)
+               return;
+       pkey_pm = (struct pkey_pm *)ctx->cert->pkey->pkey_pm;
+#endif
+
+
+       if (ssl->cert)
+               ssl_cert_free(ssl->cert);
+       ssl->ctx = ctx;
+       ssl->cert = __ssl_cert_new(ctx->cert);
+
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_authmode)
+       if (ctx->verify_mode == SSL_VERIFY_PEER)
+               mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+       else if (ctx->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+               mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+       else if (ctx->verify_mode == SSL_VERIFY_CLIENT_ONCE)
+               mode = MBEDTLS_SSL_VERIFY_UNSET;
+       else
+               mode = MBEDTLS_SSL_VERIFY_NONE;
+#endif
+
+       /* apply new ctx cert to ssl */
+
+       ssl->verify_mode = ctx->verify_mode;
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_ca_chain)
+       mbedtls_ssl_set_hs_ca_chain(&ssl_pm->ssl, x509_pm_ca->x509_crt, NULL);
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_own_cert)
+       mbedtls_ssl_set_hs_own_cert(&ssl_pm->ssl, x509_pm->x509_crt, pkey_pm->pkey);
+#endif
+#if defined(LWS_HAVE_mbedtls_ssl_set_hs_authmode)
+       mbedtls_ssl_set_hs_authmode(&ssl_pm->ssl, mode);
+#endif
+}
diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_port.c b/lib/tls/mbedtls/wrapper/platform/ssl_port.c
new file mode 100644 (file)
index 0000000..8c7a313
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ssl_port.h"
+
+/*********************************************************************************************/
+/********************************* SSL general interface *************************************/
+
+void *ssl_mem_zalloc(size_t size)
+{
+    void *p = malloc(size);
+
+    if (p)
+        memset(p, 0, size);
+
+    return p;
+}
+
diff --git a/lib/tls/mbedtls/x509.c b/lib/tls/mbedtls/x509.c
new file mode 100644 (file)
index 0000000..9c0ac05
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * libwebsockets - mbedTLS-specific lws apis
+ *
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/mbedtls/private.h"
+#include <mbedtls/oid.h>
+
+#if defined(LWS_PLAT_OPTEE) || defined(OPTEE_DEV_KIT)
+struct tm {
+int    tm_sec; //   seconds [0,61]
+int    tm_min; //   minutes [0,59]
+int    tm_hour; //  hour [0,23]
+int    tm_mday; //  day of month [1,31]
+int    tm_mon; //   month of year [0,11]
+int    tm_year; //  years since 1900
+int    tm_wday; //  day of week [0,6] (Sunday = 0)
+int    tm_yday; //  day of year [0,365]
+int    tm_isdst; // daylight savings flag
+};
+time_t mktime(struct tm *t)
+{
+       return (time_t)0;
+}
+#endif
+
+static time_t
+lws_tls_mbedtls_time_to_unix(mbedtls_x509_time *xtime)
+{
+       struct tm t;
+
+       if (!xtime || !xtime->year || xtime->year < 0)
+               return (time_t)(long long)-1;
+
+       memset(&t, 0, sizeof(t));
+
+       t.tm_year = xtime->year - 1900;
+       t.tm_mon = xtime->mon - 1; /* mbedtls months are 1+, tm are 0+ */
+       t.tm_mday = xtime->day - 1; /* mbedtls days are 1+, tm are 0+ */
+       t.tm_hour = xtime->hour;
+       t.tm_min = xtime->min;
+       t.tm_sec = xtime->sec;
+       t.tm_isdst = -1;
+
+       return mktime(&t);
+}
+
+static int
+lws_tls_mbedtls_get_x509_name(mbedtls_x509_name *name,
+                             union lws_tls_cert_info_results *buf, size_t len)
+{
+       while (name) {
+               if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid)) {
+                       name = name->next;
+                       continue;
+               }
+
+               if (len - 1 < name->val.len)
+                       return -1;
+
+               memcpy(&buf->ns.name[0], name->val.p, name->val.len);
+               buf->ns.name[name->val.len] = '\0';
+               buf->ns.len = name->val.len;
+
+               return 0;
+       }
+
+       return -1;
+}
+
+static int
+lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type,
+                         union lws_tls_cert_info_results *buf, size_t len)
+{
+       if (!x509)
+               return -1;
+
+       switch (type) {
+       case LWS_TLS_CERT_INFO_VALIDITY_FROM:
+               buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_from);
+               if (buf->time == (time_t)(long long)-1)
+                       return -1;
+               break;
+
+       case LWS_TLS_CERT_INFO_VALIDITY_TO:
+               buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_to);
+               if (buf->time == (time_t)(long long)-1)
+                       return -1;
+               break;
+
+       case LWS_TLS_CERT_INFO_COMMON_NAME:
+               return lws_tls_mbedtls_get_x509_name(&x509->subject, buf, len);
+
+       case LWS_TLS_CERT_INFO_ISSUER_NAME:
+               return lws_tls_mbedtls_get_x509_name(&x509->issuer, buf, len);
+
+       case LWS_TLS_CERT_INFO_USAGE:
+               buf->usage = x509->key_usage;
+               break;
+
+       case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY:
+       {
+               char *p = buf->ns.name;
+               size_t r = len, u;
+
+               switch (mbedtls_pk_get_type(&x509->pk)) {
+               case MBEDTLS_PK_RSA:
+               {
+                       mbedtls_rsa_context *rsa = mbedtls_pk_rsa(x509->pk);
+
+                       if (mbedtls_mpi_write_string(&rsa->N, 16, p, r, &u))
+                               return -1;
+                       r -= u;
+                       p += u;
+                       if (mbedtls_mpi_write_string(&rsa->E, 16, p, r, &u))
+                               return -1;
+
+                       p += u;
+                       buf->ns.len = lws_ptr_diff(p, buf->ns.name);
+                       break;
+               }
+               case MBEDTLS_PK_ECKEY:
+               {
+                       mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(x509->pk);
+
+                       if (mbedtls_mpi_write_string(&ecp->Q.X, 16, p, r, &u))
+                                return -1;
+                       r -= u;
+                       p += u;
+                       if (mbedtls_mpi_write_string(&ecp->Q.Y, 16, p, r, &u))
+                                return -1;
+                       r -= u;
+                       p += u;
+                       if (mbedtls_mpi_write_string(&ecp->Q.Z, 16, p, r, &u))
+                                return -1;
+                       p += u;
+                       buf->ns.len = lws_ptr_diff(p, buf->ns.name);
+                       break;
+               }
+               default:
+                       lwsl_notice("%s: x509 has unsupported pubkey type %d\n",
+                                   __func__,
+                                   mbedtls_pk_get_type(&x509->pk));
+
+                       return -1;
+               }
+               break;
+       }
+
+       default:
+               return -1;
+       }
+
+       return 0;
+}
+
+#if defined(LWS_WITH_NETWORK)
+int
+lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
+                       union lws_tls_cert_info_results *buf, size_t len)
+{
+       mbedtls_x509_crt *x509;
+
+       x509 = ssl_ctx_get_mbedtls_x509_crt(vhost->tls.ssl_ctx);
+
+       return lws_tls_mbedtls_cert_info(x509, type, buf, len);
+}
+
+int
+lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
+                      union lws_tls_cert_info_results *buf, size_t len)
+{
+       mbedtls_x509_crt *x509;
+
+       wsi = lws_get_network_wsi(wsi);
+
+       x509 = ssl_get_peer_mbedtls_x509_crt(wsi->tls.ssl);
+
+       if (!x509)
+               return -1;
+
+       switch (type) {
+       case LWS_TLS_CERT_INFO_VERIFIED:
+               buf->verified = SSL_get_verify_result(wsi->tls.ssl) == X509_V_OK;
+               return 0;
+       default:
+               return lws_tls_mbedtls_cert_info(x509, type, buf, len);
+       }
+
+       return -1;
+}
+#endif
+
+int
+lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type,
+             union lws_tls_cert_info_results *buf, size_t len)
+{
+       return lws_tls_mbedtls_cert_info(&x509->cert, type, buf, len);
+}
+
+int
+lws_x509_create(struct lws_x509_cert **x509)
+{
+       *x509 = lws_malloc(sizeof(**x509), __func__);
+
+       return !(*x509);
+}
+
+/*
+ * Parse one DER-encoded or one or more concatenated PEM-encoded certificates
+ * and add them to the chained list.
+ */
+
+int
+lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len)
+{
+       int ret;
+
+       mbedtls_x509_crt_init(&x509->cert);
+
+       ret = mbedtls_x509_crt_parse(&x509->cert, pem, len);
+       if (ret) {
+               mbedtls_x509_crt_free(&x509->cert);
+               lwsl_err("%s: unable to parse PEM cert: -0x%x\n",
+                        __func__, -ret);
+
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted,
+               const char *common_name)
+{
+       uint32_t flags = 0;
+       int ret;
+
+       ret = mbedtls_x509_crt_verify_with_profile(&x509->cert, &trusted->cert,
+                                                  NULL,
+                                                  &mbedtls_x509_crt_profile_next,
+                                                  common_name, &flags, NULL,
+                                                  NULL);
+
+       if (ret) {
+               lwsl_err("%s: unable to parse PEM cert: -0x%x\n",
+                        __func__, -ret);
+
+               return -1;
+       }
+
+       return 0;
+}
+
+#if defined(LWS_WITH_JOSE)
+
+int
+lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509,
+                      const char *curves, int rsa_min_bits)
+{
+       int kt = mbedtls_pk_get_type(&x509->cert.pk), n, count = 0, ret = -1;
+       mbedtls_rsa_context *rsactx;
+       mbedtls_ecp_keypair *ecpctx;
+       mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT];
+
+       memset(jwk, 0, sizeof(*jwk));
+
+       switch (kt) {
+       case MBEDTLS_PK_RSA:
+               lwsl_notice("%s: RSA key\n", __func__);
+               jwk->kty = LWS_GENCRYPTO_KTY_RSA;
+               rsactx = mbedtls_pk_rsa(x509->cert.pk);
+
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_E] = &rsactx->E;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_N] = &rsactx->N;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = &rsactx->D;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_P] = &rsactx->P;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_Q] = &rsactx->Q;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_DP] = &rsactx->DP;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_DQ] = &rsactx->DQ;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_QI] = &rsactx->QP;
+
+               count = LWS_GENCRYPTO_RSA_KEYEL_COUNT;
+               n = LWS_GENCRYPTO_RSA_KEYEL_E;
+               break;
+
+       case MBEDTLS_PK_ECKEY:
+               lwsl_notice("%s: EC key\n", __func__);
+               jwk->kty = LWS_GENCRYPTO_KTY_EC;
+               ecpctx = mbedtls_pk_ec(x509->cert.pk);
+               mpi[LWS_GENCRYPTO_EC_KEYEL_X] = &ecpctx->Q.X;
+               mpi[LWS_GENCRYPTO_EC_KEYEL_D] = &ecpctx->d;
+               mpi[LWS_GENCRYPTO_EC_KEYEL_Y] = &ecpctx->Q.Y;
+
+               if (lws_genec_confirm_curve_allowed_by_tls_id(curves,
+                               ecpctx->grp.id, jwk))
+                       /* already logged */
+                       goto bail;
+
+               count = LWS_GENCRYPTO_EC_KEYEL_COUNT;
+               n = LWS_GENCRYPTO_EC_KEYEL_X;
+               break;
+       default:
+               lwsl_err("%s: key type %d not supported\n", __func__, kt);
+
+               return -1;
+       }
+
+       for (; n < count; n++) {
+               if (!mbedtls_mpi_size(mpi[n]))
+                       continue;
+
+               jwk->e[n].buf = lws_malloc(mbedtls_mpi_size(mpi[n]), "certjwk");
+               if (!jwk->e[n].buf)
+                       goto bail;
+               jwk->e[n].len = mbedtls_mpi_size(mpi[n]);
+               mbedtls_mpi_write_binary(mpi[n], jwk->e[n].buf, jwk->e[n].len);
+       }
+
+       ret = 0;
+
+bail:
+       /* jwk destroy will clean up partials */
+       if (ret)
+               lws_jwk_destroy(jwk);
+
+       return ret;
+}
+
+int
+lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len,
+                        const char *passphrase)
+{
+       mbedtls_rsa_context *rsactx;
+       mbedtls_ecp_keypair *ecpctx;
+       mbedtls_pk_context pk;
+       mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT];
+       int n, ret = -1, count = 0;
+
+       mbedtls_pk_init(&pk);
+
+       n = 0;
+       if (passphrase)
+               n = strlen(passphrase);
+       n = mbedtls_pk_parse_key(&pk, pem, len, (uint8_t *)passphrase, n);
+       if (n) {
+               lwsl_err("%s: parse PEM key failed: -0x%x\n", __func__, -n);
+
+               return -1;
+       }
+
+       /* the incoming private key type */
+       switch (mbedtls_pk_get_type(&pk)) {
+       case MBEDTLS_PK_RSA:
+               if (jwk->kty != LWS_GENCRYPTO_KTY_RSA) {
+                       lwsl_err("%s: RSA privkey, non-RSA jwk\n", __func__);
+                       goto bail;
+               }
+               rsactx = mbedtls_pk_rsa(pk);
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = &rsactx->D;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_P] = &rsactx->P;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_Q] = &rsactx->Q;
+               n = LWS_GENCRYPTO_RSA_KEYEL_D;
+               count = LWS_GENCRYPTO_RSA_KEYEL_Q + 1;
+               break;
+       case MBEDTLS_PK_ECKEY:
+               if (jwk->kty != LWS_GENCRYPTO_KTY_EC) {
+                       lwsl_err("%s: EC privkey, non-EC jwk\n", __func__);
+                       goto bail;
+               }
+               ecpctx = mbedtls_pk_ec(pk);
+               mpi[LWS_GENCRYPTO_EC_KEYEL_D] = &ecpctx->d;
+               n = LWS_GENCRYPTO_EC_KEYEL_D;
+               count = n + 1;
+               break;
+       default:
+               lwsl_err("%s: unusable key type %d\n", __func__,
+                               mbedtls_pk_get_type(&pk));
+               goto bail;
+       }
+
+       for (; n < count; n++) {
+               if (!mbedtls_mpi_size(mpi[n])) {
+                       lwsl_err("%s: empty privkey\n", __func__);
+                       goto bail;
+               }
+
+               jwk->e[n].buf = lws_malloc(mbedtls_mpi_size(mpi[n]), "certjwk");
+               if (!jwk->e[n].buf)
+                       goto bail;
+               jwk->e[n].len = mbedtls_mpi_size(mpi[n]);
+               mbedtls_mpi_write_binary(mpi[n], jwk->e[n].buf, jwk->e[n].len);
+       }
+
+       ret = 0;
+
+bail:
+       mbedtls_pk_free(&pk);
+
+       return ret;
+}
+#endif
+
+void
+lws_x509_destroy(struct lws_x509_cert **x509)
+{
+       if (!*x509)
+               return;
+
+       mbedtls_x509_crt_free(&(*x509)->cert);
+
+       lws_free_set_NULL(*x509);
+}
diff --git a/lib/tls/openssl/lws-genaes.c b/lib/tls/openssl/lws-genaes.c
new file mode 100644 (file)
index 0000000..cf3ac36
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ * libwebsockets - generic AES api hiding the backend
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genaes provides an AES abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls hash functions underneath.
+ */
+#include "core/private.h"
+#include "../../jose/private.h"
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+LWS_VISIBLE int
+lws_genaes_create(struct lws_genaes_ctx *ctx, enum enum_aes_operation op,
+                 enum enum_aes_modes mode, struct lws_gencrypto_keyelem *el,
+                 enum enum_aes_padding padding, void *engine)
+{
+       int n = 0;
+
+       ctx->ctx = EVP_CIPHER_CTX_new();
+       if (!ctx->ctx)
+               return -1;
+
+       ctx->mode = mode;
+       ctx->k = el;
+       ctx->engine = engine;
+       ctx->init = 0;
+       ctx->op = op;
+       ctx->padding = padding;
+
+       switch (ctx->k->len) {
+       case 128 / 8:
+               switch (mode) {
+               case LWS_GAESM_KW:
+#if defined(LWS_HAVE_EVP_aes_128_wrap)
+                       EVP_CIPHER_CTX_set_flags(ctx->ctx,
+                                               EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+                       ctx->cipher = EVP_aes_128_wrap();
+                       break;
+#else
+                       lwsl_err("%s: your OpenSSL lacks AES wrap apis, update it\n",
+                                __func__);
+                       return -1;
+#endif
+               case LWS_GAESM_CBC:
+                       ctx->cipher = EVP_aes_128_cbc();
+                       break;
+#if defined(LWS_HAVE_EVP_aes_128_cfb128)
+               case LWS_GAESM_CFB128:
+                       ctx->cipher = EVP_aes_128_cfb128();
+                       break;
+#endif
+#if defined(LWS_HAVE_EVP_aes_128_cfb8)
+               case LWS_GAESM_CFB8:
+                       ctx->cipher = EVP_aes_128_cfb8();
+                       break;
+#endif
+               case LWS_GAESM_CTR:
+                       ctx->cipher = EVP_aes_128_ctr();
+                       break;
+               case LWS_GAESM_ECB:
+                       ctx->cipher = EVP_aes_128_ecb();
+                       break;
+               case LWS_GAESM_OFB:
+                       ctx->cipher = EVP_aes_128_ofb();
+                       break;
+               case LWS_GAESM_XTS:
+                       lwsl_err("%s: AES XTS requires double-length key\n",
+                                __func__);
+                       break;
+               case LWS_GAESM_GCM:
+                       ctx->cipher = EVP_aes_128_gcm();
+                       break;
+               default:
+                       goto bail;
+               }
+               break;
+
+       case 192 / 8:
+               switch (mode) {
+               case LWS_GAESM_KW:
+#if defined(LWS_HAVE_EVP_aes_128_wrap)
+                       EVP_CIPHER_CTX_set_flags(ctx->ctx,
+                                               EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+                       ctx->cipher = EVP_aes_192_wrap();
+                       break;
+#else
+                        lwsl_err("%s: your OpenSSL lacks AES wrap apis, update it\n",
+                                 __func__);
+                        return -1;
+#endif
+               case LWS_GAESM_CBC:
+                       ctx->cipher = EVP_aes_192_cbc();
+                       break;
+#if defined(LWS_HAVE_EVP_aes_192_cfb128)
+               case LWS_GAESM_CFB128:
+                       ctx->cipher = EVP_aes_192_cfb128();
+                       break;
+#endif
+#if defined(LWS_HAVE_EVP_aes_192_cfb8)
+               case LWS_GAESM_CFB8:
+                       ctx->cipher = EVP_aes_192_cfb8();
+                       break;
+#endif
+               case LWS_GAESM_CTR:
+                       ctx->cipher = EVP_aes_192_ctr();
+                       break;
+               case LWS_GAESM_ECB:
+                       ctx->cipher = EVP_aes_192_ecb();
+                       break;
+               case LWS_GAESM_OFB:
+                       ctx->cipher = EVP_aes_192_ofb();
+                       break;
+               case LWS_GAESM_XTS:
+                       lwsl_err("%s: AES XTS 192 invalid\n", __func__);
+                       goto bail;
+               case LWS_GAESM_GCM:
+                       ctx->cipher = EVP_aes_192_gcm();
+                       break;
+               default:
+                       goto bail;
+               }
+               break;
+
+       case 256 / 8:
+               switch (mode) {
+               case LWS_GAESM_KW:
+#if defined(LWS_HAVE_EVP_aes_128_wrap)
+                       EVP_CIPHER_CTX_set_flags(ctx->ctx,
+                                               EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+                       ctx->cipher = EVP_aes_256_wrap();
+                       break;
+#else
+                        lwsl_err("%s: your OpenSSL lacks AES wrap apis, update it\n",
+                                 __func__);
+                        return -1;
+#endif
+               case LWS_GAESM_CBC:
+                       ctx->cipher = EVP_aes_256_cbc();
+                       break;
+#if defined(LWS_HAVE_EVP_aes_256_cfb128)
+               case LWS_GAESM_CFB128:
+                       ctx->cipher = EVP_aes_256_cfb128();
+                       break;
+#endif
+#if defined(LWS_HAVE_EVP_aes_256_cfb8)
+               case LWS_GAESM_CFB8:
+                       ctx->cipher = EVP_aes_256_cfb8();
+                       break;
+#endif
+               case LWS_GAESM_CTR:
+                       ctx->cipher = EVP_aes_256_ctr();
+                       break;
+               case LWS_GAESM_ECB:
+                       ctx->cipher = EVP_aes_256_ecb();
+                       break;
+               case LWS_GAESM_OFB:
+                       ctx->cipher = EVP_aes_256_ofb();
+                       break;
+#if defined(LWS_HAVE_EVP_aes_128_xts)
+               case LWS_GAESM_XTS:
+                       ctx->cipher = EVP_aes_128_xts();
+                       break;
+#endif
+               case LWS_GAESM_GCM:
+                       ctx->cipher = EVP_aes_256_gcm();
+                       break;
+               default:
+                       goto bail;
+               }
+               break;
+
+       case 512 / 8:
+               switch (mode) {
+               case LWS_GAESM_XTS:
+                       ctx->cipher = EVP_aes_256_xts();
+                       break;
+               default:
+                       goto bail;
+               }
+       break;
+
+       default:
+               lwsl_err("%s: unsupported AES size %d bits\n", __func__,
+                        ctx->k->len * 8);
+               goto bail;
+       }
+
+       switch (ctx->op) {
+       case LWS_GAESO_ENC:
+               n = EVP_EncryptInit_ex(ctx->ctx, ctx->cipher, ctx->engine,
+                                      NULL, NULL);
+               EVP_CIPHER_CTX_set_padding(ctx->ctx, padding);
+               break;
+       case LWS_GAESO_DEC:
+               n = EVP_DecryptInit_ex(ctx->ctx, ctx->cipher, ctx->engine,
+                                      NULL, NULL);
+               EVP_CIPHER_CTX_set_padding(ctx->ctx, padding);
+               break;
+       }
+       if (!n) {
+               lwsl_err("%s: cipher init failed (cipher %p)\n", __func__,
+                        ctx->cipher);
+               lws_tls_err_describe_clear();
+               goto bail;
+       }
+
+       return 0;
+bail:
+       EVP_CIPHER_CTX_free(ctx->ctx);
+       ctx->ctx = NULL;
+       return -1;
+}
+
+LWS_VISIBLE int
+lws_genaes_destroy(struct lws_genaes_ctx *ctx, unsigned char *tag, size_t tlen)
+{
+       int outl = 0, n = 0;
+       uint8_t buf[256];
+
+       if (!ctx->ctx)
+               return 0;
+
+       if (ctx->init) {
+               switch (ctx->op) {
+               case LWS_GAESO_ENC:
+
+                       if (EVP_EncryptFinal_ex(ctx->ctx, buf, &outl) != 1) {
+                               lwsl_err("%s: enc final failed\n", __func__);
+                               n = -1;
+                       }
+
+                       if (ctx->mode == LWS_GAESM_GCM) {
+                               if (EVP_CIPHER_CTX_ctrl(ctx->ctx,
+                                               EVP_CTRL_GCM_GET_TAG,
+                                                   ctx->taglen, tag) != 1) {
+                                       lwsl_err("get tag ctrl failed\n");
+                                       //lws_tls_err_describe_clear();
+                                       n = 1;
+                               }
+                       }
+                       break;
+               case LWS_GAESO_DEC:
+                       if (EVP_DecryptFinal_ex(ctx->ctx, buf, &outl) != 1) {
+                               lwsl_err("%s: dec final failed\n", __func__);
+                               lws_tls_err_describe_clear();
+                               n = -1;
+                       }
+
+                       break;
+               }
+               if (outl)
+                       lwsl_debug("%s: final len %d\n", __func__, outl);
+       }
+
+       ctx->k = NULL;
+       EVP_CIPHER_CTX_free(ctx->ctx);
+       ctx->ctx = NULL;
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genaes_crypt(struct lws_genaes_ctx *ctx,
+                const uint8_t *in, size_t len, uint8_t *out,
+                uint8_t *iv_or_nonce_ctr_or_data_unit_16,
+                uint8_t *stream_block_16, size_t *nc_or_iv_off, int taglen)
+{
+       int n = 0, outl, olen;
+
+       if (!ctx->init) {
+
+               EVP_CIPHER_CTX_set_key_length(ctx->ctx, ctx->k->len);
+
+               if (ctx->mode == LWS_GAESM_GCM) {
+                       n = EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_SET_IVLEN,
+                                           *nc_or_iv_off, NULL);
+                       if (n != 1) {
+                               lwsl_err("%s: SET_IVLEN failed\n", __func__);
+                               return -1;
+                       }
+                       memcpy(ctx->tag, stream_block_16, taglen);
+                       ctx->taglen = taglen;
+               }
+
+               switch (ctx->op) {
+               case LWS_GAESO_ENC:
+                       n = EVP_EncryptInit_ex(ctx->ctx, NULL, NULL,
+                                              ctx->k->buf,
+                                              iv_or_nonce_ctr_or_data_unit_16);
+                       break;
+               case LWS_GAESO_DEC:
+                       if (ctx->mode == LWS_GAESM_GCM)
+                               EVP_CIPHER_CTX_ctrl(ctx->ctx,
+                                                   EVP_CTRL_GCM_SET_TAG,
+                                                   ctx->taglen, ctx->tag);
+                       n = EVP_DecryptInit_ex(ctx->ctx, NULL, NULL,
+                                              ctx->k->buf,
+                                              iv_or_nonce_ctr_or_data_unit_16);
+                       break;
+               }
+
+               if (!n) {
+                       lws_tls_err_describe_clear();
+                       lwsl_err("%s: init failed (cipher %p)\n",
+                                __func__, ctx->cipher);
+
+                       return -1;
+               }
+               ctx->init = 1;
+       }
+
+       if (ctx->mode == LWS_GAESM_GCM && !out) {
+               /* AAD */
+
+               if (!len)
+                       return 0;
+
+               switch (ctx->op) {
+               case LWS_GAESO_ENC:
+                       n = EVP_EncryptUpdate(ctx->ctx, NULL, &olen, in, len);
+                       break;
+               case LWS_GAESO_DEC:
+                       n = EVP_DecryptUpdate(ctx->ctx, NULL, &olen, in, len);
+                       break;
+               default:
+                       return -1;
+               }
+               if (n != 1) {
+                       lwsl_err("%s: set AAD failed\n",  __func__);
+                       lws_tls_err_describe_clear();
+                       lwsl_hexdump_err(in, len);
+                       return -1;
+               }
+
+               return 0;
+       }
+
+       switch (ctx->op) {
+       case LWS_GAESO_ENC:
+               n = EVP_EncryptUpdate(ctx->ctx, out, &outl, in, len);
+               break;
+       case LWS_GAESO_DEC:
+               n = EVP_DecryptUpdate(ctx->ctx, out, &outl, in, len);
+               break;
+       default:
+               return -1;
+       }
+
+       // lwsl_notice("discarding outl %d\n", (int)outl);
+
+       if (!n) {
+               lwsl_notice("%s: update failed\n", __func__);
+               lws_tls_err_describe_clear();
+
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/lib/tls/openssl/lws-gencrypto.c b/lib/tls/openssl/lws-gencrypto.c
new file mode 100644 (file)
index 0000000..dd74149
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * libwebsockets - generic crypto api hiding the backend
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws-gencrypto openssl-specific common code
+ */
+
+#include "core/private.h"
+#include "tls/openssl/private.h"
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+int
+lws_gencrypto_openssl_hash_to_NID(enum lws_genhash_types hash_type)
+{
+       int h = -1;
+
+       switch (hash_type) {
+       case LWS_GENHASH_TYPE_UNKNOWN:
+               break;
+       case LWS_GENHASH_TYPE_MD5:
+               h = NID_md5;
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               h = NID_sha1;
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               h = NID_sha256;
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               h = NID_sha384;
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               h = NID_sha512;
+               break;
+       }
+
+       return h;
+}
+
+const EVP_MD *
+lws_gencrypto_openssl_hash_to_EVP_MD(enum lws_genhash_types hash_type)
+{
+       const EVP_MD *h = NULL;
+
+       switch (hash_type) {
+       case LWS_GENHASH_TYPE_UNKNOWN:
+               break;
+       case LWS_GENHASH_TYPE_MD5:
+               h = EVP_md5();
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               h = EVP_sha1();
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               h = EVP_sha256();
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               h = EVP_sha384();
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               h = EVP_sha512();
+               break;
+       }
+
+       return h;
+}
diff --git a/lib/tls/openssl/lws-genec.c b/lib/tls/openssl/lws-genec.c
new file mode 100644 (file)
index 0000000..cb62a67
--- /dev/null
@@ -0,0 +1,661 @@
+/*
+ * libwebsockets - generic EC api hiding the backend - openssl implementation
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genec provides an EC abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls crypto functions underneath.
+ */
+#include "core/private.h"
+#include "tls/openssl/private.h"
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+#if !defined(LWS_HAVE_ECDSA_SIG_set0)
+static void
+ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
+{
+    if (pr != NULL)
+        *pr = sig->r;
+    if (ps != NULL)
+        *ps = sig->s;
+}
+
+static int
+ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+{
+       if (r == NULL || s == NULL)
+               return 0;
+       BN_clear_free(sig->r);
+       BN_clear_free(sig->s);
+       sig->r = r;
+       sig->s = s;
+
+       return 1;
+}
+#endif
+#if !defined(LWS_HAVE_BN_bn2binpad)
+int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen)
+{
+    int i;
+    BN_ULONG l;
+
+    bn_check_top(a);
+    i = BN_num_bytes(a);
+
+    /* Add leading zeroes if necessary */
+    if (tolen > i) {
+        memset(to, 0, tolen - i);
+        to += tolen - i;
+    }
+    while (i--) {
+        l = a->d[i / BN_BYTES];
+        *(to++) = (unsigned char)(l >> (8 * (i % BN_BYTES))) & 0xff;
+    }
+    return tolen;
+}
+#endif
+
+const struct lws_ec_curves lws_ec_curves[4] = {
+       /*
+        * These are the curves we are willing to use by default...
+        *
+        * The 3 recommended+ (P-256) and optional curves in RFC7518 7.6
+        *
+        * Specific keys lengths from RFC8422 p20
+        */
+       { "P-256", NID_X9_62_prime256v1, 32 },
+       { "P-384", NID_secp384r1,        48 },
+       { "P-521", NID_secp521r1,        66 },
+
+       { NULL, 0, 0 }
+};
+
+static int
+lws_genec_eckey_import(int nid, EVP_PKEY *pkey, struct lws_gencrypto_keyelem *el)
+{
+       EC_KEY *ec = EC_KEY_new_by_curve_name(nid);
+       BIGNUM *bn_d, *bn_x, *bn_y;
+       int n;
+
+       if (!ec)
+               return -1;
+
+       /*
+        * EC_KEY contains
+        *
+        * EC_GROUP *   group
+        * EC_POINT *   pub_key
+        * BIGNUM *     priv_key  (ie, d)
+        */
+
+       bn_x = BN_bin2bn(el[LWS_GENCRYPTO_EC_KEYEL_X].buf,
+                        el[LWS_GENCRYPTO_EC_KEYEL_X].len, NULL);
+       if (!bn_x) {
+               lwsl_err("%s: BN_bin2bn (x) fail\n", __func__);
+               goto bail;
+       }
+       bn_y = BN_bin2bn(el[LWS_GENCRYPTO_EC_KEYEL_Y].buf,
+                        el[LWS_GENCRYPTO_EC_KEYEL_Y].len, NULL);
+       if (!bn_y) {
+               lwsl_err("%s: BN_bin2bn (y) fail\n", __func__);
+               goto bail1;
+       }
+
+       n = EC_KEY_set_public_key_affine_coordinates(ec, bn_x, bn_y);
+       BN_free(bn_x);
+       BN_free(bn_y);
+       if (n != 1) {
+               lwsl_err("%s: EC_KEY_set_public_key_affine_coordinates fail:\n",
+                        __func__);
+               lws_tls_err_describe_clear();
+               goto bail;
+       }
+
+       if (el[LWS_GENCRYPTO_EC_KEYEL_D].len) {
+               bn_d = BN_bin2bn(el[LWS_GENCRYPTO_EC_KEYEL_D].buf,
+                                el[LWS_GENCRYPTO_EC_KEYEL_D].len, NULL);
+               if (!bn_d) {
+                       lwsl_err("%s: BN_bin2bn (d) fail\n", __func__);
+                       goto bail;
+               }
+
+               n = EC_KEY_set_private_key(ec, bn_d);
+               BN_clear_free(bn_d);
+               if (n != 1) {
+                       lwsl_err("%s: EC_KEY_set_private_key fail\n", __func__);
+                       goto bail;
+               }
+       }
+
+       /* explicitly confirm the key pieces are consistent */
+
+       if (EC_KEY_check_key(ec) != 1) {
+               lwsl_err("%s: EC_KEY_set_private_key fail\n", __func__);
+               goto bail;
+       }
+
+       n = EVP_PKEY_assign_EC_KEY(pkey, ec);
+       if (n != 1) {
+               lwsl_err("%s: EVP_PKEY_set1_EC_KEY failed\n", __func__);
+               return -1;
+       }
+
+       return 0;
+
+bail1:
+       BN_free(bn_x);
+bail:
+       EC_KEY_free(ec);
+
+       return -1;
+}
+
+static int
+lws_genec_keypair_import(struct lws_genec_ctx *ctx,
+                        const struct lws_ec_curves *curve_table,
+                        EVP_PKEY_CTX **pctx, struct lws_gencrypto_keyelem *el)
+{
+       EVP_PKEY *pkey = NULL;
+       const struct lws_ec_curves *curve;
+
+       if (el[LWS_GENCRYPTO_EC_KEYEL_CRV].len < 4)
+               return -2;
+
+       curve = lws_genec_curve(curve_table,
+                               (char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf);
+       if (!curve)
+               return -3;
+
+       if ((el[LWS_GENCRYPTO_EC_KEYEL_D].len &&
+            el[LWS_GENCRYPTO_EC_KEYEL_D].len != curve->key_bytes) ||
+           el[LWS_GENCRYPTO_EC_KEYEL_X].len != curve->key_bytes ||
+           el[LWS_GENCRYPTO_EC_KEYEL_Y].len != curve->key_bytes)
+               return -4;
+
+       ctx->has_private = !!el[LWS_GENCRYPTO_EC_KEYEL_D].len;
+
+       pkey = EVP_PKEY_new();
+       if (!pkey)
+               return -7;
+
+       if (lws_genec_eckey_import(curve->tls_lib_nid, pkey, el)) {
+               lwsl_err("%s: lws_genec_eckey_import fail\n", __func__);
+               goto bail;
+       }
+
+       *pctx = EVP_PKEY_CTX_new(pkey, NULL);
+       EVP_PKEY_free(pkey);
+       pkey = NULL;
+
+       if (!*pctx)
+               goto bail;
+
+       return 0;
+
+bail:
+       if (pkey)
+               EVP_PKEY_free(pkey);
+
+       if (*pctx) {
+               EVP_PKEY_CTX_free(*pctx);
+               *pctx = NULL;
+       }
+
+       return -9;
+}
+
+LWS_VISIBLE int
+lws_genecdh_create(struct lws_genec_ctx *ctx, struct lws_context *context,
+                  const struct lws_ec_curves *curve_table)
+{
+       ctx->context = context;
+       ctx->ctx[0] = NULL;
+       ctx->ctx[1] = NULL;
+       ctx->curve_table = curve_table;
+       ctx->genec_alg = LEGENEC_ECDH;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_genecdsa_create(struct lws_genec_ctx *ctx, struct lws_context *context,
+                   const struct lws_ec_curves *curve_table)
+{
+       ctx->context = context;
+       ctx->ctx[0] = NULL;
+       ctx->ctx[1] = NULL;
+       ctx->curve_table = curve_table;
+       ctx->genec_alg = LEGENEC_ECDSA;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_genecdh_set_key(struct lws_genec_ctx *ctx, struct lws_gencrypto_keyelem *el,
+                   enum enum_lws_dh_side side)
+{
+       if (ctx->genec_alg != LEGENEC_ECDH)
+               return -1;
+
+       return lws_genec_keypair_import(ctx, ctx->curve_table, &ctx->ctx[side], el);
+}
+
+LWS_VISIBLE int
+lws_genecdsa_set_key(struct lws_genec_ctx *ctx,
+                    struct lws_gencrypto_keyelem *el)
+{
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       return lws_genec_keypair_import(ctx, ctx->curve_table, &ctx->ctx[0], el);
+}
+
+static void
+lws_genec_keypair_destroy(EVP_PKEY_CTX **pctx)
+{
+       if (!*pctx)
+               return;
+
+//     lwsl_err("%p\n", EVP_PKEY_get1_EC_KEY(EVP_PKEY_CTX_get0_pkey(*pctx)));
+
+//     EC_KEY_free(EVP_PKEY_get1_EC_KEY(EVP_PKEY_CTX_get0_pkey(*pctx)));
+
+       EVP_PKEY_CTX_free(*pctx);
+       *pctx = NULL;
+}
+
+LWS_VISIBLE void
+lws_genec_destroy(struct lws_genec_ctx *ctx)
+{
+       if (ctx->ctx[0])
+               lws_genec_keypair_destroy(&ctx->ctx[0]);
+       if (ctx->ctx[1])
+               lws_genec_keypair_destroy(&ctx->ctx[1]);
+}
+
+static int
+lws_genec_new_keypair(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side,
+                     const char *curve_name, struct lws_gencrypto_keyelem *el)
+{
+       const struct lws_ec_curves *curve;
+       const EC_POINT *pubkey;
+       EVP_PKEY *pkey = NULL;
+       int ret = -29, n, m;
+       BIGNUM *bn[3];
+       EC_KEY *ec;
+
+       curve = lws_genec_curve(ctx->curve_table, curve_name);
+       if (!curve) {
+               lwsl_err("%s: curve '%s' not supported\n",
+                        __func__, curve_name);
+
+               return -22;
+       }
+
+       ec = EC_KEY_new_by_curve_name(curve->tls_lib_nid);
+       if (!ec) {
+               lwsl_err("%s: unknown nid %d\n", __func__, curve->tls_lib_nid);
+               return -23;
+       }
+
+       if (EC_KEY_generate_key(ec) != 1) {
+               lwsl_err("%s: EC_KEY_generate_key failed\n", __func__);
+               goto bail;
+       }
+
+       pkey = EVP_PKEY_new();
+       if (!pkey)
+               goto bail;
+
+       if (EVP_PKEY_set1_EC_KEY(pkey, ec) != 1) {
+               lwsl_err("%s: EVP_PKEY_assign_EC_KEY failed\n", __func__);
+               goto bail1;
+       }
+
+       ctx->ctx[side] = EVP_PKEY_CTX_new(pkey, NULL);
+       if (!ctx->ctx[side]) {
+               lwsl_err("%s: EVP_PKEY_CTX_new failed\n", __func__);
+               goto bail1;
+       }
+
+       /*
+        * we need to capture the individual element BIGNUMs into
+        * lws_gencrypto_keyelem, so they can be serialized, used in jwk etc
+        */
+
+       pubkey = EC_KEY_get0_public_key(ec);
+       if (!pubkey) {
+               lwsl_err("%s: EC_KEY_get0_public_key failed\n", __func__);
+               goto bail1;
+       }
+
+       bn[0] = BN_new();
+       bn[1] = (BIGNUM *)EC_KEY_get0_private_key(ec);
+       bn[2] = BN_new();
+
+#if defined(LWS_HAVE_EC_POINT_get_affine_coordinates)
+       if (EC_POINT_get_affine_coordinates(EC_KEY_get0_group(ec),
+#else
+       if (EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec),
+#endif
+                       pubkey, bn[0], bn[2], NULL) != 1) {
+               lwsl_err("%s: EC_POINT_get_affine_coordinates_GFp failed\n",
+                        __func__);
+               goto bail2;
+       }
+
+       el[LWS_GENCRYPTO_EC_KEYEL_CRV].len = strlen(curve_name) + 1;
+       el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf =
+                       lws_malloc(el[LWS_GENCRYPTO_EC_KEYEL_CRV].len, "ec");
+       if (!el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf) {
+               lwsl_err("%s: OOM\n", __func__);
+               goto bail2;
+       }
+
+       strcpy((char *)el[LWS_GENCRYPTO_EC_KEYEL_CRV].buf, curve_name);
+
+       for (n = LWS_GENCRYPTO_EC_KEYEL_X; n < LWS_GENCRYPTO_EC_KEYEL_COUNT;
+            n++) {
+               el[n].len = curve->key_bytes;
+               el[n].buf = lws_malloc(curve->key_bytes, "ec");
+               if (!el[n].buf)
+                       goto bail2;
+
+               m = BN_bn2binpad(bn[n - 1], el[n].buf, el[n].len);
+               if ((uint32_t)m != el[n].len)
+                       goto bail2;
+       }
+
+       ctx->has_private = 1;
+
+       ret = 0;
+
+bail2:
+       BN_clear_free(bn[0]);
+       BN_clear_free(bn[2]);
+bail1:
+       EVP_PKEY_free(pkey);
+bail:
+       EC_KEY_free(ec);
+
+       return ret;
+}
+
+LWS_VISIBLE int
+lws_genecdh_new_keypair(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side,
+                       const char *curve_name,
+                       struct lws_gencrypto_keyelem *el)
+{
+       if (ctx->genec_alg != LEGENEC_ECDH)
+               return -1;
+
+       return lws_genec_new_keypair(ctx, side, curve_name, el);
+}
+
+LWS_VISIBLE int
+lws_genecdsa_new_keypair(struct lws_genec_ctx *ctx, const char *curve_name,
+                        struct lws_gencrypto_keyelem *el)
+{
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       return lws_genec_new_keypair(ctx, LDHS_OURS, curve_name, el);
+}
+
+#if 0
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sign(struct lws_genec_ctx *ctx, const uint8_t *in,
+                      enum lws_genhash_types hash_type,
+                      uint8_t *sig, size_t sig_len)
+{
+       const EVP_MD *md = lws_gencrypto_openssl_hash_to_EVP_MD(hash_type);
+       EVP_MD_CTX *mdctx = NULL;
+
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               return -1;
+
+       if (!md)
+               return -1;
+
+       mdctx = EVP_MD_CTX_create();
+       if (!mdctx)
+               goto bail;
+
+       if (EVP_DigestSignInit(mdctx, NULL, md, NULL,
+                              EVP_PKEY_CTX_get0_pkey(ctx->ctx))) {
+               lwsl_err("%s: EVP_DigestSignInit failed\n", __func__);
+
+               goto bail;
+       }
+       if (EVP_DigestSignUpdate(mdctx, in, EVP_MD_size(md))) {
+               lwsl_err("%s: EVP_DigestSignUpdate failed\n", __func__);
+
+               goto bail;
+       }
+       if (EVP_DigestSignFinal(mdctx, sig, &sig_len)) {
+               lwsl_err("%s: EVP_DigestSignFinal failed\n", __func__);
+
+               goto bail;
+       }
+
+       EVP_MD_CTX_free(mdctx);
+
+       return (int)sig_len;
+bail:
+       if (mdctx)
+               EVP_MD_CTX_free(mdctx);
+
+       return -1;
+}
+#endif
+
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in,
+                          enum lws_genhash_types hash_type, int keybits,
+                          uint8_t *sig, size_t sig_len)
+{
+       int ret = -1, n, keybytes = lws_gencrypto_bits_to_bytes(keybits);
+       const BIGNUM *r = NULL, *s = NULL;
+       ECDSA_SIG *ecdsasig;
+       EC_KEY *eckey;
+
+       if (ctx->genec_alg != LEGENEC_ECDSA) {
+               lwsl_notice("%s: ctx alg %d\n", __func__, ctx->genec_alg);
+               return -1;
+       }
+
+       if (!ctx->has_private)
+               return -1;
+
+       if ((int)sig_len < keybytes * 2) {
+               lwsl_notice("%s: sig buff %d < %d\n", __func__,
+                           (int)sig_len, keybytes * 2);
+               return -1;
+       }
+
+       eckey = EVP_PKEY_get1_EC_KEY(EVP_PKEY_CTX_get0_pkey(ctx->ctx[0]));
+
+       /*
+        * The ECDSA P-256 SHA-256 digital signature is generated as follows:
+        *
+        * 1.  Generate a digital signature of the JWS Signing Input using ECDSA
+        *     P-256 SHA-256 with the desired private key.  The output will be
+        *     the pair (R, S), where R and S are 256-bit unsigned integers.
+        *
+        * 2.  Turn R and S into octet sequences in big-endian order, with each
+        *     array being be 32 octets long.  The octet sequence
+        *     representations MUST NOT be shortened to omit any leading zero
+        *     octets contained in the values.
+        *
+        * 3.  Concatenate the two octet sequences in the order R and then S.
+        *     (Note that many ECDSA implementations will directly produce this
+        *     concatenation as their output.)
+        *
+        * 4.  The resulting 64-octet sequence is the JWS Signature value.
+        */
+
+       ecdsasig = ECDSA_do_sign(in, lws_genhash_size(hash_type), eckey);
+       EC_KEY_free(eckey);
+       if (!ecdsasig) {
+               lwsl_notice("%s: ECDSA_do_sign fail\n", __func__);
+               goto bail;
+       }
+
+       ECDSA_SIG_get0(ecdsasig, &r, &s);
+
+       /*
+        * in the 521-bit case, we have to pad the last byte as it only
+        * generates 65 bytes
+        */
+
+       n = BN_bn2binpad(r, sig, keybytes);
+       if (n != keybytes) {
+               lwsl_notice("%s: bignum r fail %d %d\n", __func__, n, keybytes);
+               goto bail;
+       }
+
+       n = BN_bn2binpad(s, sig + keybytes, keybytes);
+       if (n != keybytes) {
+               lwsl_notice("%s: bignum s fail %d %d\n", __func__, n, keybytes);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       if (ecdsasig)
+               ECDSA_SIG_free(ecdsasig);
+
+       return ret;
+}
+
+/* in is the JWS Signing Input hash */
+
+LWS_VISIBLE LWS_EXTERN int
+lws_genecdsa_hash_sig_verify_jws(struct lws_genec_ctx *ctx, const uint8_t *in,
+                                enum lws_genhash_types hash_type, int keybits,
+                                const uint8_t *sig, size_t sig_len)
+{
+       int ret = -1, n, keybytes = lws_gencrypto_bits_to_bytes(keybits),
+           hlen = lws_genhash_size(hash_type);
+       ECDSA_SIG *ecsig = ECDSA_SIG_new();
+       BIGNUM *r = NULL, *s = NULL;
+       EC_KEY *eckey;
+
+       if (!ecsig)
+               return -1;
+
+       if (ctx->genec_alg != LEGENEC_ECDSA)
+               goto bail;
+
+       if ((int)sig_len != keybytes * 2) {
+               lwsl_err("%s: sig buf too small %d vs %d\n", __func__,
+                        (int)sig_len, keybytes * 2);
+               goto bail;
+       }
+       /*
+        * 1.  The JWS Signature value MUST be a 64-octet sequence.  If it is
+        *     not a 64-octet sequence, the validation has failed.
+        *
+        * 2.  Split the 64-octet sequence into two 32-octet sequences.  The
+        *     first octet sequence represents R and the second S.  The values R
+        *     and S are represented as octet sequences using the Integer-to-
+        *     OctetString Conversion defined in Section 2.3.7 of SEC1 [SEC1]
+        *     (in big-endian octet order).
+        *
+        * 3.  Submit the JWS Signing Input, R, S, and the public key (x, y) to
+        *     the ECDSA P-256 SHA-256 validator.
+        */
+
+       r = BN_bin2bn(sig, keybytes, NULL);
+       if (!r) {
+               lwsl_err("%s: BN_bin2bn (r) fail\n", __func__);
+               goto bail;
+       }
+
+       s = BN_bin2bn(sig + keybytes, keybytes, NULL);
+       if (!s) {
+               lwsl_err("%s: BN_bin2bn (s) fail\n", __func__);
+               goto bail1;
+       }
+
+       if (ECDSA_SIG_set0(ecsig, r, s) != 1) {
+               lwsl_err("%s: ECDSA_SIG_set0 fail\n", __func__);
+               goto bail1;
+       }
+
+       eckey = EVP_PKEY_get1_EC_KEY(EVP_PKEY_CTX_get0_pkey(ctx->ctx[0]));
+
+       n = ECDSA_do_verify(in, hlen, ecsig, eckey);
+       EC_KEY_free(eckey);
+       if (n != 1) {
+               lwsl_err("%s: ECDSA_do_verify fail\n", __func__);
+               lws_tls_err_describe_clear();
+               goto bail;
+       }
+
+       ret = 0;
+       goto bail;
+
+bail1:
+       if (r)
+               BN_free(r);
+       if (s)
+               BN_free(s);
+
+bail:
+       ECDSA_SIG_free(ecsig);
+
+       return ret;
+}
+
+int
+lws_genecdh_compute_shared_secret(struct lws_genec_ctx *ctx, uint8_t *ss,
+                                 int *ss_len)
+{
+       int len, ret = -1;
+       EC_KEY *eckey[2];
+
+       if (!ctx->ctx[LDHS_OURS] || !ctx->ctx[LDHS_THEIRS]) {
+               lwsl_err("%s: both sides must be set up\n", __func__);
+
+               return -1;
+       }
+
+       eckey[LDHS_OURS] = EVP_PKEY_get1_EC_KEY(
+                               EVP_PKEY_CTX_get0_pkey(ctx->ctx[LDHS_OURS]));
+       eckey[LDHS_THEIRS] = EVP_PKEY_get1_EC_KEY(
+                               EVP_PKEY_CTX_get0_pkey(ctx->ctx[LDHS_THEIRS]));
+
+       len = (EC_GROUP_get_degree(EC_KEY_get0_group(eckey[LDHS_OURS])) + 7) / 8;
+       if (len <= *ss_len) {
+               *ss_len = ECDH_compute_key(ss, len,
+                               EC_KEY_get0_public_key(eckey[LDHS_THEIRS]),
+                               eckey[LDHS_OURS], NULL);
+               ret = -(*ss_len < 0);
+       }
+
+       EC_KEY_free(eckey[LDHS_OURS]);
+       EC_KEY_free(eckey[LDHS_THEIRS]);
+
+       return ret;
+}
diff --git a/lib/tls/openssl/lws-genhash.c b/lib/tls/openssl/lws-genhash.c
new file mode 100644 (file)
index 0000000..8803904
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * libwebsockets - generic hash and HMAC api hiding the backend
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genhash provides a hash / hmac abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls hash functions underneath.
+ */
+#include "libwebsockets.h"
+#include <openssl/obj_mac.h>
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+int
+lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type)
+{
+       ctx->type = type;
+       ctx->mdctx = EVP_MD_CTX_create();
+       if (!ctx->mdctx)
+               return 1;
+
+       switch (ctx->type) {
+       case LWS_GENHASH_TYPE_MD5:
+               ctx->evp_type = EVP_md5();
+               break;
+       case LWS_GENHASH_TYPE_SHA1:
+               ctx->evp_type = EVP_sha1();
+               break;
+       case LWS_GENHASH_TYPE_SHA256:
+               ctx->evp_type = EVP_sha256();
+               break;
+       case LWS_GENHASH_TYPE_SHA384:
+               ctx->evp_type = EVP_sha384();
+               break;
+       case LWS_GENHASH_TYPE_SHA512:
+               ctx->evp_type = EVP_sha512();
+               break;
+       default:
+               return 1;
+       }
+
+       if (EVP_DigestInit_ex(ctx->mdctx, ctx->evp_type, NULL) != 1) {
+               EVP_MD_CTX_destroy(ctx->mdctx);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+int
+lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len)
+{
+       if (!len)
+               return 0;
+
+       return EVP_DigestUpdate(ctx->mdctx, in, len) != 1;
+}
+
+int
+lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result)
+{
+       unsigned int len;
+       int ret = 0;
+
+       if (result)
+               ret = EVP_DigestFinal_ex(ctx->mdctx, result, &len) != 1;
+
+       (void)len;
+
+       EVP_MD_CTX_destroy(ctx->mdctx);
+
+       return ret;
+}
+
+
+int
+lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type,
+                const uint8_t *key, size_t key_len)
+{
+#if defined(LWS_HAVE_HMAC_CTX_new)
+       ctx->ctx = HMAC_CTX_new();
+       if (!ctx->ctx)
+               return -1;
+#else
+       HMAC_CTX_init(&ctx->ctx);
+#endif
+
+       switch (type) {
+       case LWS_GENHMAC_TYPE_SHA256:
+               ctx->evp_type = EVP_sha256();
+               break;
+       case LWS_GENHMAC_TYPE_SHA384:
+               ctx->evp_type = EVP_sha384();
+               break;
+       case LWS_GENHMAC_TYPE_SHA512:
+               ctx->evp_type = EVP_sha512();
+               break;
+       default:
+               lwsl_err("%s: unknown HMAC type %d\n", __func__, type);
+               goto bail;
+       }
+
+#if defined(LWS_HAVE_HMAC_CTX_new)
+        if (HMAC_Init_ex(ctx->ctx, key, key_len, ctx->evp_type, NULL) != 1)
+#else
+        if (HMAC_Init_ex(&ctx->ctx, key, key_len, ctx->evp_type, NULL) != 1)
+#endif
+               goto bail;
+
+       return 0;
+
+bail:
+#if defined(LWS_HAVE_HMAC_CTX_new)
+       HMAC_CTX_free(ctx->ctx);
+#endif
+
+       return -1;
+}
+
+int
+lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len)
+{
+#if defined(LWS_HAVE_HMAC_CTX_new)
+       if (HMAC_Update(ctx->ctx, in, len) != 1)
+#else
+       if (HMAC_Update(&ctx->ctx, in, len) != 1)
+#endif
+               return -1;
+
+       return 0;
+}
+
+int
+lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result)
+{
+       unsigned int size = lws_genhmac_size(ctx->type);
+#if defined(LWS_HAVE_HMAC_CTX_new)
+       int n = HMAC_Final(ctx->ctx, result, &size);
+
+       HMAC_CTX_free(ctx->ctx);
+#else
+       int n = HMAC_Final(&ctx->ctx, result, &size);
+#endif
+
+       if (n != 1)
+               return -1;
+
+       return 0;
+}
+
diff --git a/lib/tls/openssl/lws-genrsa.c b/lib/tls/openssl/lws-genrsa.c
new file mode 100644 (file)
index 0000000..ec7bde8
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * libwebsockets - generic RSA api hiding the backend
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  lws_genrsa provides an RSA abstraction api in lws that works the
+ *  same whether you are using openssl or mbedtls crypto functions underneath.
+ */
+#include "core/private.h"
+#include "tls/openssl/private.h"
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+LWS_VISIBLE void
+lws_genrsa_destroy_elements(struct lws_gencrypto_keyelem *el)
+{
+       lws_gencrypto_destroy_elements(el, LWS_GENCRYPTO_RSA_KEYEL_COUNT);
+}
+
+static int mode_map_crypt[] = { RSA_PKCS1_PADDING, RSA_PKCS1_OAEP_PADDING },
+          mode_map_sig[]   = { RSA_PKCS1_PADDING, RSA_PKCS1_PSS_PADDING };
+
+static int
+rsa_pkey_wrap(struct lws_genrsa_ctx *ctx, RSA *rsa)
+{
+       EVP_PKEY *pkey;
+
+       /* we have the RSA object filled up... wrap in a PKEY */
+
+       pkey = EVP_PKEY_new();
+       if (!pkey)
+               return 1;
+
+       /* bind the PKEY to the RSA key we just prepared */
+
+       if (EVP_PKEY_assign_RSA(pkey, rsa) != 1) {
+               lwsl_err("%s: EVP_PKEY_assign_RSA_KEY failed\n", __func__);
+               goto bail;
+       }
+
+       /* pepare our PKEY_CTX with the PKEY */
+
+       ctx->ctx = EVP_PKEY_CTX_new(pkey, NULL);
+       EVP_PKEY_free(pkey);
+       pkey = NULL;
+       if (!ctx->ctx)
+               goto bail;
+
+       return 0;
+
+bail:
+       if (pkey)
+               EVP_PKEY_free(pkey);
+
+       return 1;
+}
+
+LWS_VISIBLE int
+lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_gencrypto_keyelem *el,
+                 struct lws_context *context, enum enum_genrsa_mode mode,
+                 enum lws_genhash_types oaep_hashid)
+{
+       int n;
+
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->context = context;
+       ctx->mode = mode;
+
+       /* Step 1:
+        *
+        * convert the MPI for e and n to OpenSSL BIGNUMs
+        */
+
+       for (n = 0; n < 5; n++) {
+               ctx->bn[n] = BN_bin2bn(el[n].buf, el[n].len, NULL);
+               if (!ctx->bn[n]) {
+                       lwsl_notice("mpi load failed\n");
+                       goto bail;
+               }
+       }
+
+       /* Step 2:
+        *
+        * assemble the OpenSSL RSA from the BIGNUMs
+        */
+
+       ctx->rsa = RSA_new();
+       if (!ctx->rsa) {
+               lwsl_notice("Failed to create RSA\n");
+               goto bail;
+       }
+
+#if defined(LWS_HAVE_RSA_SET0_KEY)
+       if (RSA_set0_key(ctx->rsa, ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_N],
+                        ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_E],
+                        ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_D]) != 1) {
+               lwsl_notice("RSA_set0_key failed\n");
+               goto bail;
+       }
+       RSA_set0_factors(ctx->rsa, ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_P],
+                                  ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_Q]);
+#else
+       ctx->rsa->e = ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_E];
+       ctx->rsa->n = ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_N];
+       ctx->rsa->d = ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_D];
+       ctx->rsa->p = ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_P];
+       ctx->rsa->q = ctx->bn[LWS_GENCRYPTO_RSA_KEYEL_Q];
+#endif
+
+       if (!rsa_pkey_wrap(ctx, ctx->rsa))
+               return 0;
+
+bail:
+       for (n = 0; n < 5; n++)
+               if (ctx->bn[n]) {
+                       BN_clear_free(ctx->bn[n]);
+                       ctx->bn[n] = NULL;
+               }
+
+       if (ctx->rsa) {
+               RSA_free(ctx->rsa);
+               ctx->rsa = NULL;
+       }
+
+       return 1;
+}
+
+LWS_VISIBLE int
+lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx,
+                      enum enum_genrsa_mode mode, struct lws_gencrypto_keyelem *el,
+                      int bits)
+{
+       BIGNUM *bn;
+       int n;
+
+       memset(ctx, 0, sizeof(*ctx));
+       ctx->context = context;
+       ctx->mode = mode;
+
+       ctx->rsa = RSA_new();
+       if (!ctx->rsa) {
+               lwsl_notice("Failed to create RSA\n");
+               return -1;
+       }
+
+       bn = BN_new();
+       if (!bn)
+               goto cleanup_1;
+       if (BN_set_word(bn, RSA_F4) != 1) {
+               BN_free(bn);
+               goto cleanup_1;
+       }
+
+       n = RSA_generate_key_ex(ctx->rsa, bits, bn, NULL);
+       BN_clear_free(bn);
+       if (n != 1)
+               goto cleanup_1;
+
+#if defined(LWS_HAVE_RSA_SET0_KEY)
+       {
+               const BIGNUM *mpi[5];
+
+               RSA_get0_key(ctx->rsa, &mpi[LWS_GENCRYPTO_RSA_KEYEL_N],
+                            &mpi[LWS_GENCRYPTO_RSA_KEYEL_E], &mpi[LWS_GENCRYPTO_RSA_KEYEL_D]);
+               RSA_get0_factors(ctx->rsa, &mpi[LWS_GENCRYPTO_RSA_KEYEL_P],
+                                &mpi[LWS_GENCRYPTO_RSA_KEYEL_Q]);
+#else
+       {
+               BIGNUM *mpi[5] = { ctx->rsa->e, ctx->rsa->n, ctx->rsa->d,
+                                  ctx->rsa->p, ctx->rsa->q, };
+#endif
+               for (n = 0; n < 5; n++)
+                       if (BN_num_bytes(mpi[n])) {
+                               el[n].buf = lws_malloc(
+                                       BN_num_bytes(mpi[n]), "genrsakey");
+                               if (!el[n].buf)
+                                       goto cleanup;
+                               el[n].len = BN_num_bytes(mpi[n]);
+                               BN_bn2bin(mpi[n], el[n].buf);
+                       }
+       }
+
+       if (!rsa_pkey_wrap(ctx, ctx->rsa))
+               return 0;
+
+cleanup:
+       for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++)
+               if (el[n].buf)
+                       lws_free_set_NULL(el[n].buf);
+cleanup_1:
+       RSA_free(ctx->rsa);
+       ctx->rsa = NULL;
+
+       return -1;
+}
+
+/*
+ * in_len must be less than RSA_size(rsa) - 11 for the PKCS #1 v1.5
+ * based padding modes
+ */
+
+LWS_VISIBLE int
+lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                         size_t in_len, uint8_t *out)
+{
+       int n = RSA_public_encrypt((int)in_len, in, out, ctx->rsa,
+                                  mode_map_crypt[ctx->mode]);
+       if (n < 0) {
+               lwsl_err("%s: RSA_public_encrypt failed\n", __func__);
+               lws_tls_err_describe_clear();
+               return -1;
+       }
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genrsa_private_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          size_t in_len, uint8_t *out)
+{
+       int n = RSA_private_encrypt((int)in_len, in, out, ctx->rsa,
+                               mode_map_crypt[ctx->mode]);
+       if (n < 0) {
+               lwsl_err("%s: RSA_private_encrypt failed\n", __func__);
+               lws_tls_err_describe_clear();
+               return -1;
+       }
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                         size_t in_len, uint8_t *out, size_t out_max)
+{
+       int n = RSA_public_decrypt((int)in_len, in, out, ctx->rsa,
+                              mode_map_crypt[ctx->mode]);
+       if (n < 0) {
+               lwsl_err("%s: RSA_public_decrypt failed\n", __func__);
+               return -1;
+       }
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genrsa_private_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                          size_t in_len, uint8_t *out, size_t out_max)
+{
+       int n = RSA_private_decrypt((int)in_len, in, out, ctx->rsa,
+                               mode_map_crypt[ctx->mode]);
+       if (n < 0) {
+               lwsl_err("%s: RSA_private_decrypt failed\n", __func__);
+               lws_tls_err_describe_clear();
+               return -1;
+       }
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_genrsa_hash_sig_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                        enum lws_genhash_types hash_type, const uint8_t *sig,
+                        size_t sig_len)
+{
+       int n = lws_gencrypto_openssl_hash_to_NID(hash_type),
+           h = (int)lws_genhash_size(hash_type);
+       const EVP_MD *md = NULL;
+
+       if (n < 0)
+               return -1;
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               n = RSA_verify(n, in, h, (uint8_t *)sig, (int)sig_len, ctx->rsa);
+               break;
+       case LGRSAM_PKCS1_OAEP_PSS:
+               md = lws_gencrypto_openssl_hash_to_EVP_MD(hash_type);
+               if (!md)
+                       return -1;
+
+#if defined(LWS_HAVE_RSA_verify_pss_mgf1)
+               n = RSA_verify_pss_mgf1(ctx->rsa, in, h, md, NULL, -1,
+                                       (uint8_t *)sig,
+#else
+               n = RSA_verify_PKCS1_PSS(ctx->rsa, in, md, (uint8_t *)sig,
+#endif
+                                        (int)sig_len);
+               break;
+       default:
+               return -1;
+       }
+
+       if (n != 1) {
+               lwsl_notice("%s: fail\n", __func__);
+               lws_tls_err_describe_clear();
+
+               return -1;
+       }
+
+       return 0;
+}
+
+LWS_VISIBLE int
+lws_genrsa_hash_sign(struct lws_genrsa_ctx *ctx, const uint8_t *in,
+                      enum lws_genhash_types hash_type, uint8_t *sig,
+                      size_t sig_len)
+{
+       int n = lws_gencrypto_openssl_hash_to_NID(hash_type),
+           h = (int)lws_genhash_size(hash_type);
+       unsigned int used = 0;
+       EVP_MD_CTX *mdctx = NULL;
+       const EVP_MD *md = NULL;
+
+       if (n < 0)
+               return -1;
+
+       switch(ctx->mode) {
+       case LGRSAM_PKCS1_1_5:
+               if (RSA_sign(n, in, h, sig, &used, ctx->rsa) != 1) {
+                       lwsl_err("%s: RSA_sign failed\n", __func__);
+
+                       goto bail;
+               }
+               break;
+
+       case LGRSAM_PKCS1_OAEP_PSS:
+
+               md = lws_gencrypto_openssl_hash_to_EVP_MD(hash_type);
+               if (!md)
+                       return -1;
+
+               if (EVP_PKEY_CTX_set_rsa_padding(ctx->ctx,
+                                                mode_map_sig[ctx->mode]) != 1) {
+                       lwsl_err("%s: set_rsa_padding failed\n", __func__);
+
+                       goto bail;
+               }
+
+               mdctx = EVP_MD_CTX_create();
+               if (!mdctx)
+                       goto bail;
+
+               if (EVP_DigestSignInit(mdctx, NULL, md, NULL,
+                                      EVP_PKEY_CTX_get0_pkey(ctx->ctx))) {
+                       lwsl_err("%s: EVP_DigestSignInit failed\n", __func__);
+
+                       goto bail;
+               }
+               if (EVP_DigestSignUpdate(mdctx, in, EVP_MD_size(md))) {
+                       lwsl_err("%s: EVP_DigestSignUpdate failed\n", __func__);
+
+                       goto bail;
+               }
+               if (EVP_DigestSignFinal(mdctx, sig, &sig_len)) {
+                       lwsl_err("%s: EVP_DigestSignFinal failed\n", __func__);
+
+                       goto bail;
+               }
+               EVP_MD_CTX_free(mdctx);
+               used = (int)sig_len;
+               break;
+
+       default:
+               return -1;
+       }
+
+       return used;
+
+bail:
+       if (mdctx)
+               EVP_MD_CTX_free(mdctx);
+
+       return -1;
+}
+
+LWS_VISIBLE void
+lws_genrsa_destroy(struct lws_genrsa_ctx *ctx)
+{
+       if (!ctx->ctx)
+               return;
+
+       EVP_PKEY_CTX_free(ctx->ctx);
+       ctx->ctx = NULL;
+       ctx->rsa = NULL;
+}
diff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c
new file mode 100644 (file)
index 0000000..f822f63
--- /dev/null
@@ -0,0 +1,667 @@
+/*
+ * libwebsockets - openSSL-specific client tls code
+ *
+ * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#include "tls/openssl/private.h"
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+int lws_openssl_describe_cipher(struct lws *wsi);
+
+extern int openssl_websocket_private_data_index,
+    openssl_SSL_CTX_private_data_index;
+
+#if !defined(USE_WOLFSSL)
+
+static int
+OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+       SSL *ssl;
+       int n;
+       struct lws *wsi;
+
+       /* keep old behaviour accepting self-signed server certs */
+       if (!preverify_ok) {
+               int err = X509_STORE_CTX_get_error(x509_ctx);
+
+               if (err != X509_V_OK) {
+                       ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
+                                       SSL_get_ex_data_X509_STORE_CTX_idx());
+                       wsi = SSL_get_ex_data(ssl,
+                                       openssl_websocket_private_data_index);
+                       if (!wsi) {
+                               lwsl_err("%s: can't get wsi from ssl privdata\n",
+                                        __func__);
+
+                               return 0;
+                       }
+
+                       if ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+                            err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
+                            wsi->tls.use_ssl & LCCSCF_ALLOW_SELFSIGNED) {
+                               lwsl_notice("accepting self-signed "
+                                           "certificate (verify_callback)\n");
+                               X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
+                               return 1;       // ok
+                       } else if ((err == X509_V_ERR_CERT_NOT_YET_VALID ||
+                                   err == X509_V_ERR_CERT_HAS_EXPIRED) &&
+                                   wsi->tls.use_ssl & LCCSCF_ALLOW_EXPIRED) {
+                               if (err == X509_V_ERR_CERT_NOT_YET_VALID)
+                                       lwsl_notice("accepting not yet valid "
+                                                   "certificate (verify_"
+                                                   "callback)\n");
+                               else if (err == X509_V_ERR_CERT_HAS_EXPIRED)
+                                       lwsl_notice("accepting expired "
+                                                   "certificate (verify_"
+                                                   "callback)\n");
+                               X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
+                               return 1;       // ok
+                       }
+               }
+       }
+
+       ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
+                                        SSL_get_ex_data_X509_STORE_CTX_idx());
+       wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
+       if (!wsi) {
+               lwsl_err("%s: can't get wsi from ssl privdata\n",  __func__);
+
+               return 0;
+       }
+
+       n = lws_get_context_protocol(wsi->context, 0).callback(wsi,
+                       LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION,
+                       x509_ctx, ssl, preverify_ok);
+
+       /* keep old behaviour if something wrong with server certs */
+       /* if ssl error is overruled in callback and cert is ok,
+        * X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); must be set and
+        * return value is 0 from callback */
+       if (!preverify_ok) {
+               int err = X509_STORE_CTX_get_error(x509_ctx);
+
+               if (err != X509_V_OK) {
+                       /* cert validation error was not handled in callback */
+                       int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+                       const char *msg = X509_verify_cert_error_string(err);
+
+                       lwsl_err("SSL error: %s (preverify_ok=%d;err=%d;"
+                                "depth=%d)\n", msg, preverify_ok, err, depth);
+
+                       return preverify_ok;    // not ok
+               }
+       }
+       /*
+        * convert callback return code from 0 = OK to verify callback
+        * return value 1 = OK
+        */
+       return !n;
+}
+#endif
+
+
+int
+lws_ssl_client_bio_create(struct lws *wsi)
+{
+       char hostname[128], *p;
+#if defined(LWS_HAVE_SSL_set_alpn_protos) && \
+    defined(LWS_HAVE_SSL_get0_alpn_selected)
+       uint8_t openssl_alpn[40];
+       const char *alpn_comma = wsi->context->tls.alpn_default;
+       int n;
+#endif
+
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (lws_hdr_copy(wsi, hostname, sizeof(hostname),
+                        _WSI_TOKEN_CLIENT_HOST) <= 0)
+#endif
+       {
+               lwsl_err("%s: Unable to get hostname\n", __func__);
+
+               return -1;
+       }
+
+       /*
+        * remove any :port part on the hostname... necessary for network
+        * connection but typical certificates do not contain it
+        */
+       p = hostname;
+       while (*p) {
+               if (*p == ':') {
+                       *p = '\0';
+                       break;
+               }
+               p++;
+       }
+
+       wsi->tls.ssl = SSL_new(wsi->vhost->tls.ssl_client_ctx);
+       if (!wsi->tls.ssl) {
+               lwsl_err("SSL_new failed: %s\n",
+                        ERR_error_string(lws_ssl_get_error(wsi, 0), NULL));
+               lws_tls_err_describe_clear();
+               return -1;
+       }
+
+#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
+       if (wsi->vhost->tls.ssl_info_event_mask)
+               SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback);
+#endif
+
+#if defined LWS_HAVE_X509_VERIFY_PARAM_set1_host
+       if (!(wsi->tls.use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) {
+               X509_VERIFY_PARAM *param = SSL_get0_param(wsi->tls.ssl);
+
+               /* Enable automatic hostname checks */
+               X509_VERIFY_PARAM_set_hostflags(param,
+                                       X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+               // Handle the case where the hostname is an IP address.
+               if (!X509_VERIFY_PARAM_set1_ip_asc(param, hostname))
+                       X509_VERIFY_PARAM_set1_host(param, hostname, 0);
+       }
+#endif
+
+#if !defined(USE_WOLFSSL)
+#ifndef USE_OLD_CYASSL
+       /* OpenSSL_client_verify_callback will be called @ SSL_connect() */
+       SSL_set_verify(wsi->tls.ssl, SSL_VERIFY_PEER,
+                      OpenSSL_client_verify_callback);
+#endif
+#endif
+
+#if !defined(USE_WOLFSSL)
+       SSL_set_mode(wsi->tls.ssl,  SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+#endif
+       /*
+        * use server name indication (SNI), if supported,
+        * when establishing connection
+        */
+#ifdef USE_WOLFSSL
+#ifdef USE_OLD_CYASSL
+#ifdef CYASSL_SNI_HOST_NAME
+       CyaSSL_UseSNI(wsi->tls.ssl, CYASSL_SNI_HOST_NAME, hostname,
+                     strlen(hostname));
+#endif
+#else
+#ifdef WOLFSSL_SNI_HOST_NAME
+       wolfSSL_UseSNI(wsi->tls.ssl, WOLFSSL_SNI_HOST_NAME, hostname,
+                      strlen(hostname));
+#endif
+#endif
+#else
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+       SSL_set_tlsext_host_name(wsi->tls.ssl, hostname);
+#endif
+#endif
+
+#ifdef USE_WOLFSSL
+       /*
+        * wolfSSL/CyaSSL does certificate verification differently
+        * from OpenSSL.
+        * If we should ignore the certificate, we need to set
+        * this before SSL_new and SSL_connect is called.
+        * Otherwise the connect will simply fail with error code -155
+        */
+#ifdef USE_OLD_CYASSL
+       if (wsi->tls.use_ssl == 2)
+               CyaSSL_set_verify(wsi->tls.ssl, SSL_VERIFY_NONE, NULL);
+#else
+       if (wsi->tls.use_ssl == 2)
+               wolfSSL_set_verify(wsi->tls.ssl, SSL_VERIFY_NONE, NULL);
+#endif
+#endif /* USE_WOLFSSL */
+
+       wsi->tls.client_bio = BIO_new_socket((int)(long long)wsi->desc.sockfd,
+                                            BIO_NOCLOSE);
+       SSL_set_bio(wsi->tls.ssl, wsi->tls.client_bio, wsi->tls.client_bio);
+
+#ifdef USE_WOLFSSL
+#ifdef USE_OLD_CYASSL
+       CyaSSL_set_using_nonblock(wsi->tls.ssl, 1);
+#else
+       wolfSSL_set_using_nonblock(wsi->tls.ssl, 1);
+#endif
+#else
+       BIO_set_nbio(wsi->tls.client_bio, 1); /* nonblocking */
+#endif
+
+#if defined(LWS_HAVE_SSL_set_alpn_protos) && \
+    defined(LWS_HAVE_SSL_get0_alpn_selected)
+       if (wsi->vhost->tls.alpn)
+               alpn_comma = wsi->vhost->tls.alpn;
+#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
+       if (lws_hdr_copy(wsi, hostname, sizeof(hostname),
+                        _WSI_TOKEN_CLIENT_ALPN) > 0)
+               alpn_comma = hostname;
+#endif
+
+       lwsl_info("client conn using alpn list '%s'\n", alpn_comma);
+
+       n = lws_alpn_comma_to_openssl(alpn_comma, openssl_alpn,
+                                     sizeof(openssl_alpn) - 1);
+
+       SSL_set_alpn_protos(wsi->tls.ssl, openssl_alpn, n);
+#endif
+
+       SSL_set_ex_data(wsi->tls.ssl, openssl_websocket_private_data_index,
+                       wsi);
+
+       return 0;
+}
+
+enum lws_ssl_capable_status
+lws_tls_client_connect(struct lws *wsi)
+{
+#if defined(LWS_HAVE_SSL_set_alpn_protos) && \
+    defined(LWS_HAVE_SSL_get0_alpn_selected)
+       const unsigned char *prot;
+       char a[32];
+       unsigned int len;
+#endif
+       int m, n;
+
+       errno = 0;
+       ERR_clear_error();
+       n = SSL_connect(wsi->tls.ssl);
+       if (n == 1) {
+#if defined(LWS_HAVE_SSL_set_alpn_protos) && \
+    defined(LWS_HAVE_SSL_get0_alpn_selected)
+               SSL_get0_alpn_selected(wsi->tls.ssl, &prot, &len);
+
+               if (len >= sizeof(a))
+                       len = sizeof(a) - 1;
+               memcpy(a, (const char *)prot, len);
+               a[len] = '\0';
+
+               lws_role_call_alpn_negotiated(wsi, (const char *)a);
+#endif
+               lwsl_info("client connect OK\n");
+               lws_openssl_describe_cipher(wsi);
+               return LWS_SSL_CAPABLE_DONE;
+       }
+
+       m = lws_ssl_get_error(wsi, n);
+
+       if (m == SSL_ERROR_SYSCALL)
+               return LWS_SSL_CAPABLE_ERROR;
+
+       if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl))
+               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+
+       if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl))
+               return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;
+
+       if (!n) /* we don't know what he wants, but he says to retry */
+               return LWS_SSL_CAPABLE_MORE_SERVICE;
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+int
+lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, int ebuf_len)
+{
+#if !defined(USE_WOLFSSL)
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       char *p = (char *)&pt->serv_buf[0];
+       char *sb = p;
+       int n;
+
+       lws_latency_pre(wsi->context, wsi);
+       errno = 0;
+       ERR_clear_error();
+       n = SSL_get_verify_result(wsi->tls.ssl);
+       lws_latency(wsi->context, wsi,
+               "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0);
+
+       lwsl_debug("get_verify says %d\n", n);
+
+       if (n == X509_V_OK)
+               return 0;
+
+       if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+            n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) &&
+            (wsi->tls.use_ssl & LCCSCF_ALLOW_SELFSIGNED)) {
+               lwsl_info("accepting self-signed certificate\n");
+
+               return 0;
+       }
+       if ((n == X509_V_ERR_CERT_NOT_YET_VALID ||
+            n == X509_V_ERR_CERT_HAS_EXPIRED) &&
+            (wsi->tls.use_ssl & LCCSCF_ALLOW_EXPIRED)) {
+               lwsl_info("accepting expired certificate\n");
+               return 0;
+       }
+       if (n == X509_V_ERR_CERT_NOT_YET_VALID) {
+               lwsl_info("Cert is from the future... "
+                           "probably our clock... accepting...\n");
+               return 0;
+       }
+       lws_snprintf(ebuf, ebuf_len,
+               "server's cert didn't look good, X509_V_ERR = %d: %s\n",
+                n, ERR_error_string(n, sb));
+       lwsl_info("%s\n", ebuf);
+       lws_tls_err_describe_clear();
+
+       return -1;
+
+#else /* USE_WOLFSSL */
+       return 0;
+#endif
+}
+
+int
+lws_tls_client_create_vhost_context(struct lws_vhost *vh,
+                                   const struct lws_context_creation_info *info,
+                                   const char *cipher_list,
+                                   const char *ca_filepath,
+                                   const void *ca_mem,
+                                   unsigned int ca_mem_len,
+                                   const char *cert_filepath,
+                                   const void *cert_mem,
+                                   unsigned int cert_mem_len,
+                                   const char *private_key_filepath)
+{
+       struct lws_tls_client_reuse *tcr;
+       const unsigned char *ca_mem_ptr;
+       X509_STORE *x509_store;
+       unsigned long error;
+       SSL_METHOD *method;
+       EVP_MD_CTX *mdctx;
+       unsigned int len;
+       uint8_t hash[32];
+       X509 *client_CA;
+       char c;
+       int n;
+
+       /* basic openssl init already happened in context init */
+
+       /* choose the most recent spin of the api */
+#if defined(LWS_HAVE_TLS_CLIENT_METHOD)
+       method = (SSL_METHOD *)TLS_client_method();
+#elif defined(LWS_HAVE_TLSV1_2_CLIENT_METHOD)
+       method = (SSL_METHOD *)TLSv1_2_client_method();
+#else
+       method = (SSL_METHOD *)SSLv23_client_method();
+#endif
+
+       if (!method) {
+               error = ERR_get_error();
+               lwsl_err("problem creating ssl method %lu: %s\n",
+                       error, ERR_error_string(error,
+                                     (char *)vh->context->pt[0].serv_buf));
+               return 1;
+       }
+
+       /*
+        * OpenSSL client contexts are quite expensive, because they bring in
+        * the system certificate bundle for each one.  So if you have multiple
+        * vhosts, each with a client context, it can add up to several
+        * megabytes of heap.  In the case the client contexts are configured
+        * identically, they could perfectly well have shared just the one.
+        *
+        * For that reason, use a hash to fingerprint the context configuration
+        * and prefer to reuse an existing one with the same fingerprint if
+        * possible.
+        */
+
+        mdctx = EVP_MD_CTX_create();
+        if (!mdctx)
+                return 1;
+
+       if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) {
+               EVP_MD_CTX_destroy(mdctx);
+
+               return 1;
+       }
+
+       if (info->ssl_client_options_set)
+               EVP_DigestUpdate(mdctx, &info->ssl_client_options_set,
+                                sizeof(info->ssl_client_options_set));
+
+#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
+       if (info->ssl_client_options_clear)
+               EVP_DigestUpdate(mdctx, &info->ssl_client_options_clear,
+                                sizeof(info->ssl_client_options_clear));
+#endif
+
+       if (cipher_list)
+               EVP_DigestUpdate(mdctx, cipher_list, strlen(cipher_list));
+
+#if defined(LWS_HAVE_SSL_CTX_set_ciphersuites)
+       if (info->client_tls_1_3_plus_cipher_list)
+               EVP_DigestUpdate(mdctx, info->client_tls_1_3_plus_cipher_list,
+                                strlen(info->client_tls_1_3_plus_cipher_list));
+#endif
+
+       if (!lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS)) {
+               c = 1;
+               EVP_DigestUpdate(mdctx, &c, 1);
+       }
+
+       if (ca_filepath)
+               EVP_DigestUpdate(mdctx, ca_filepath, strlen(ca_filepath));
+
+       if (cert_filepath)
+               EVP_DigestUpdate(mdctx, cert_filepath, strlen(cert_filepath));
+
+       if (private_key_filepath)
+               EVP_DigestUpdate(mdctx, private_key_filepath,
+                                strlen(private_key_filepath));
+       if (ca_mem && ca_mem_len)
+               EVP_DigestUpdate(mdctx, ca_mem, ca_mem_len);
+
+       if (cert_mem && cert_mem_len)
+               EVP_DigestUpdate(mdctx, cert_mem, cert_mem_len);
+
+       len = sizeof(hash);
+       EVP_DigestFinal_ex(mdctx, hash, &len);
+       EVP_MD_CTX_destroy(mdctx);
+
+       /* look for existing client context with same config already */
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+                        lws_dll2_get_head(&vh->context->tls.cc_owner)) {
+               tcr = lws_container_of(p, struct lws_tls_client_reuse, cc_list);
+
+               if (!memcmp(hash, tcr->hash, len)) {
+
+                       /* it's a match */
+
+                       tcr->refcount++;
+                       vh->tls.ssl_client_ctx = tcr->ssl_client_ctx;
+
+                       lwsl_info("%s: vh %s: reusing client ctx %d: use %d\n",
+                                  __func__, vh->name, tcr->index,
+                                  tcr->refcount);
+
+                       return 0;
+               }
+       } lws_end_foreach_dll_safe(p, tp);
+
+       /* no existing one the same... create new client SSL_CTX */
+
+       errno = 0;
+       ERR_clear_error();
+       vh->tls.ssl_client_ctx = SSL_CTX_new(method);
+       if (!vh->tls.ssl_client_ctx) {
+               error = ERR_get_error();
+               lwsl_err("problem creating ssl context %lu: %s\n",
+                       error, ERR_error_string(error,
+                                     (char *)vh->context->pt[0].serv_buf));
+               return 1;
+       }
+
+       tcr = lws_zalloc(sizeof(*tcr), "client ctx tcr");
+       if (!tcr) {
+               SSL_CTX_free(vh->tls.ssl_client_ctx);
+               return 1;
+       }
+
+       tcr->ssl_client_ctx = vh->tls.ssl_client_ctx;
+       tcr->refcount = 1;
+       memcpy(tcr->hash, hash, len);
+       tcr->index = vh->context->tls.count_client_contexts++;
+       lws_dll2_add_head(&tcr->cc_list, &vh->context->tls.cc_owner);
+
+       lwsl_info("%s: vh %s: created new client ctx %d\n", __func__,
+                       vh->name, tcr->index);
+
+       /* bind the tcr to the client context */
+
+       SSL_CTX_set_ex_data(vh->tls.ssl_client_ctx,
+                           openssl_SSL_CTX_private_data_index,
+                           (char *)tcr);
+
+#ifdef SSL_OP_NO_COMPRESSION
+       SSL_CTX_set_options(vh->tls.ssl_client_ctx, SSL_OP_NO_COMPRESSION);
+#endif
+
+       SSL_CTX_set_options(vh->tls.ssl_client_ctx,
+                           SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+       SSL_CTX_set_mode(vh->tls.ssl_client_ctx,
+                        SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
+                        SSL_MODE_RELEASE_BUFFERS);
+
+       if (info->ssl_client_options_set)
+               SSL_CTX_set_options(vh->tls.ssl_client_ctx,
+                                   info->ssl_client_options_set);
+
+       /* SSL_clear_options introduced in 0.9.8m */
+#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
+       if (info->ssl_client_options_clear)
+               SSL_CTX_clear_options(vh->tls.ssl_client_ctx,
+                                     info->ssl_client_options_clear);
+#endif
+
+       if (cipher_list)
+               SSL_CTX_set_cipher_list(vh->tls.ssl_client_ctx, cipher_list);
+
+#if defined(LWS_HAVE_SSL_CTX_set_ciphersuites)
+       if (info->client_tls_1_3_plus_cipher_list)
+               SSL_CTX_set_ciphersuites(vh->tls.ssl_client_ctx,
+                                        info->client_tls_1_3_plus_cipher_list);
+#endif
+
+#ifdef LWS_SSL_CLIENT_USE_OS_CA_CERTS
+       if (!lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS))
+               /* loads OS default CA certs */
+               SSL_CTX_set_default_verify_paths(vh->tls.ssl_client_ctx);
+#endif
+
+       /* openssl init for cert verification (for client sockets) */
+       if (!ca_filepath && (!ca_mem || !ca_mem_len)) {
+               if (!SSL_CTX_load_verify_locations(
+                       vh->tls.ssl_client_ctx, NULL, LWS_OPENSSL_CLIENT_CERTS))
+                       lwsl_err("Unable to load SSL Client certs from %s "
+                           "(set by LWS_OPENSSL_CLIENT_CERTS) -- "
+                           "client ssl isn't going to work\n",
+                           LWS_OPENSSL_CLIENT_CERTS);
+       } else if (ca_filepath) {
+               if (!SSL_CTX_load_verify_locations(
+                       vh->tls.ssl_client_ctx, ca_filepath, NULL)) {
+                       lwsl_err(
+                               "Unable to load SSL Client certs "
+                               "file from %s -- client ssl isn't "
+                               "going to work\n", ca_filepath);
+                       lws_tls_err_describe_clear();
+               }
+               else
+                       lwsl_info("loaded ssl_ca_filepath\n");
+       } else {
+               ca_mem_ptr = (const unsigned char*)ca_mem;
+               client_CA = d2i_X509(NULL, &ca_mem_ptr, ca_mem_len);
+               x509_store = X509_STORE_new();
+               if (!client_CA || !X509_STORE_add_cert(x509_store, client_CA)) {
+                       X509_STORE_free(x509_store);
+                       lwsl_err("Unable to load SSL Client certs from "
+                                "ssl_ca_mem -- client ssl isn't going to "
+                                "work\n");
+                       lws_tls_err_describe_clear();
+               } else {
+                       /* it doesn't increment x509_store ref counter */
+                       SSL_CTX_set_cert_store(vh->tls.ssl_client_ctx,
+                                              x509_store);
+                       lwsl_info("loaded ssl_ca_mem\n");
+               }
+               if (client_CA)
+                       X509_free(client_CA);
+       }
+
+       /*
+        * callback allowing user code to load extra verification certs
+        * helping the client to verify server identity
+        */
+
+       /* support for client-side certificate authentication */
+       if (cert_filepath) {
+               if (lws_tls_use_any_upgrade_check_extant(cert_filepath) !=
+                               LWS_TLS_EXTANT_YES &&
+                   (info->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT))
+                       return 0;
+
+               lwsl_notice("%s: doing cert filepath %s\n", __func__,
+                               cert_filepath);
+               n = SSL_CTX_use_certificate_chain_file(vh->tls.ssl_client_ctx,
+                                                      cert_filepath);
+               if (n < 1) {
+                       lwsl_err("problem %d getting cert '%s'\n", n,
+                                cert_filepath);
+                       lws_tls_err_describe_clear();
+                       return 1;
+               }
+               lwsl_notice("Loaded client cert %s\n", cert_filepath);
+       } else if (cert_mem && cert_mem_len) {
+               n = SSL_CTX_use_certificate_ASN1(vh->tls.ssl_client_ctx,
+                                                cert_mem_len, cert_mem);
+               if (n < 1) {
+                       lwsl_err("%s: problem interpreting client cert\n",
+                                __func__);
+                       lws_tls_err_describe_clear();
+                       return 1;
+               }
+       }
+       if (private_key_filepath) {
+               lwsl_notice("%s: doing private key filepath\n", __func__);
+               lws_ssl_bind_passphrase(vh->tls.ssl_client_ctx, 1, info);
+               /* set the private key from KeyFile */
+               if (SSL_CTX_use_PrivateKey_file(vh->tls.ssl_client_ctx,
+                   private_key_filepath, SSL_FILETYPE_PEM) != 1) {
+                       lwsl_err("use_PrivateKey_file '%s'\n",
+                                private_key_filepath);
+                       lws_tls_err_describe_clear();
+                       return 1;
+               }
+               lwsl_notice("Loaded client cert private key %s\n",
+                           private_key_filepath);
+
+               /* verify private key */
+               if (!SSL_CTX_check_private_key(vh->tls.ssl_client_ctx)) {
+                       lwsl_err("Private SSL key doesn't match cert\n");
+                       return 1;
+               }
+       }
+
+       return 0;
+}
diff --git a/lib/tls/openssl/openssl-server.c b/lib/tls/openssl/openssl-server.c
new file mode 100644 (file)
index 0000000..917c0ea
--- /dev/null
@@ -0,0 +1,1001 @@
+/*
+ * libwebsockets - OpenSSL-specific server functions
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+extern int openssl_websocket_private_data_index,
+          openssl_SSL_CTX_private_data_index;
+
+int lws_openssl_describe_cipher(struct lws *wsi);
+
+static int
+OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+       SSL *ssl;
+       int n;
+       struct lws *wsi;
+       union lws_tls_cert_info_results ir;
+       X509 *topcert = X509_STORE_CTX_get_current_cert(x509_ctx);
+
+       ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
+               SSL_get_ex_data_X509_STORE_CTX_idx());
+
+       /*
+        * !!! nasty openssl requires the index to come as a library-scope
+        * static
+        */
+       wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
+
+       n = lws_tls_openssl_cert_info(topcert, LWS_TLS_CERT_INFO_COMMON_NAME,
+                                     &ir, sizeof(ir.ns.name));
+       if (!n)
+               lwsl_info("%s: client cert CN '%s'\n", __func__, ir.ns.name);
+       else
+               lwsl_info("%s: couldn't get client cert CN\n", __func__);
+
+       n = wsi->vhost->protocols[0].callback(wsi,
+                       LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION,
+                                          x509_ctx, ssl, preverify_ok);
+
+       /* convert return code from 0 = OK to 1 = OK */
+       return !n;
+}
+
+int
+lws_tls_server_client_cert_verify_config(struct lws_vhost *vh)
+{
+       int verify_options = SSL_VERIFY_PEER;
+
+       /* as a server, are we requiring clients to identify themselves? */
+
+       if (!lws_check_opt(vh->options,
+                         LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT))
+               return 0;
+
+       if (!lws_check_opt(vh->options,
+                          LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
+               verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+
+       SSL_CTX_set_session_id_context(vh->tls.ssl_ctx, (uint8_t *)vh->context,
+                                      sizeof(void *));
+
+       /* absolutely require the client cert */
+       SSL_CTX_set_verify(vh->tls.ssl_ctx, verify_options,
+                          OpenSSL_verify_callback);
+
+       return 0;
+}
+
+#if defined(SSL_TLSEXT_ERR_NOACK) && !defined(OPENSSL_NO_TLSEXT)
+static int
+lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg)
+{
+       struct lws_context *context = (struct lws_context *)arg;
+       struct lws_vhost *vhost, *vh;
+       const char *servername;
+
+       if (!ssl)
+               return SSL_TLSEXT_ERR_NOACK;
+
+       /*
+        * 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
+        * same port.
+        */
+       vh = context->vhost_list;
+       while (vh) {
+               if (!vh->being_destroyed &&
+                   vh->tls.ssl_ctx == SSL_get_SSL_CTX(ssl))
+                       break;
+               vh = vh->vhost_next;
+       }
+
+       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\n");
+
+               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->tls.ssl_ctx);
+
+       return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
+/*
+ * this may now get called after the vhost creation, when certs become
+ * available.
+ */
+int
+lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi,
+                         const char *cert, const char *private_key,
+                         const char *mem_cert, size_t mem_cert_len,
+                         const char *mem_privkey, size_t mem_privkey_len)
+{
+#if !defined(OPENSSL_NO_EC)
+       const char *ecdh_curve = "prime256v1";
+#if !defined(LWS_WITH_BORINGSSL) && defined(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS)
+       STACK_OF(X509) *extra_certs = NULL;
+#endif
+       EC_KEY *ecdh, *EC_key = NULL;
+       EVP_PKEY *pkey;
+       X509 *x = NULL;
+       int ecdh_nid;
+       int KeyType;
+#endif
+       unsigned long error;
+       lws_filepos_t flen;
+       uint8_t *p;
+       int ret;
+
+       int n = lws_tls_generic_cert_checks(vhost, cert, private_key), m;
+
+       (void)ret;
+
+       if (!cert && !private_key)
+               n = LWS_TLS_EXTANT_ALTERNATIVE;
+
+       if (n == LWS_TLS_EXTANT_NO && (!mem_cert || !mem_privkey))
+               return 0;
+       if (n == LWS_TLS_EXTANT_NO)
+               n = LWS_TLS_EXTANT_ALTERNATIVE;
+
+       if (n == LWS_TLS_EXTANT_ALTERNATIVE && (!mem_cert || !mem_privkey))
+               return 1; /* no alternative */
+
+       if (n == LWS_TLS_EXTANT_ALTERNATIVE) {
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+
+               /*
+                * Although we have prepared update certs, we no longer have
+                * the rights to read our own cert + key we saved.
+                *
+                * If we were passed copies in memory buffers, use those
+                * in favour of the filepaths we normally want.
+                */
+               cert = NULL;
+               private_key = NULL;
+       }
+
+       /*
+        * use the multi-cert interface for backwards compatibility in the
+        * both simple files case
+        */
+
+       if (n != LWS_TLS_EXTANT_ALTERNATIVE && cert) {
+
+               /* set the local certificate from CertFile */
+               m = SSL_CTX_use_certificate_chain_file(vhost->tls.ssl_ctx, cert);
+               if (m != 1) {
+                       error = ERR_get_error();
+                       lwsl_err("problem getting cert '%s' %lu: %s\n",
+                                cert, error, ERR_error_string(error,
+                                      (char *)vhost->context->pt[0].serv_buf));
+
+                       return 1;
+               }
+
+               if (private_key) {
+                       /* set the private key from KeyFile */
+                       if (SSL_CTX_use_PrivateKey_file(vhost->tls.ssl_ctx, private_key,
+                                                       SSL_FILETYPE_PEM) != 1) {
+                               error = ERR_get_error();
+                               lwsl_err("ssl problem getting key '%s' %lu: %s\n",
+                                        private_key, error,
+                                        ERR_error_string(error,
+                                             (char *)vhost->context->pt[0].serv_buf));
+                               return 1;
+                       }
+               } else {
+                       if (vhost->protocols[0].callback(wsi,
+                                     LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
+                                                        vhost->tls.ssl_ctx, NULL, 0)) {
+                               lwsl_err("ssl private key not set\n");
+
+                               return 1;
+                       }
+               }
+
+               return 0;
+       }
+
+       /* otherwise allow for DER or PEM, file or memory image */
+
+       if (lws_tls_alloc_pem_to_der_file(vhost->context, cert, mem_cert,
+                                         mem_cert_len, &p, &flen)) {
+               lwsl_err("%s: couldn't read cert file\n", __func__);
+
+               return 1;
+       }
+
+#if !defined(USE_WOLFSSL)
+       ret = SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx, (int)flen, p);
+#else
+       ret = wolfSSL_CTX_use_certificate_buffer(vhost->tls.ssl_ctx,
+                                                (uint8_t *)p, (int)flen,
+                                                WOLFSSL_FILETYPE_ASN1);
+#endif
+       lws_free_set_NULL(p);
+       if (ret != 1) {
+               lwsl_err("%s: Problem loading cert\n", __func__);
+
+               return 1;
+       }
+
+       if (lws_tls_alloc_pem_to_der_file(vhost->context, private_key,
+                                         mem_privkey, mem_privkey_len,
+                                         &p, &flen)) {
+               lwsl_notice("unable to convert memory privkey\n");
+
+               return 1;
+       }
+
+#if !defined(USE_WOLFSSL)
+       ret = SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, vhost->tls.ssl_ctx, p,
+                                         (long)(long long)flen);
+       if (ret != 1) {
+               ret = SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC,
+                                                 vhost->tls.ssl_ctx, p,
+                                                 (long)(long long)flen);
+       }
+#else
+       ret = wolfSSL_CTX_use_PrivateKey_buffer(vhost->tls.ssl_ctx, p, flen,
+                                               WOLFSSL_FILETYPE_ASN1);
+#endif
+       lws_free_set_NULL(p);
+       if (ret != 1)  {
+               lwsl_notice("unable to use memory privkey\n");
+
+               return 1;
+       }
+
+#else
+               /*
+                * Although we have prepared update certs, we no longer have
+                * the rights to read our own cert + key we saved.
+                *
+                * If we were passed copies in memory buffers, use those
+                * instead.
+                *
+                * The passed memory-buffer cert image is in DER, and the
+                * memory-buffer private key image is PEM.
+                */
+#ifndef USE_WOLFSSL
+               if (lws_tls_alloc_pem_to_der_file(vhost->context, cert, mem_cert,
+                                                 mem_cert_len, &p, &flen)) {
+                       lwsl_err("%s: couldn't convert pem to der\n", __func__);
+                       return 1;
+               }
+               if (SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx,
+                                                (int)flen,
+                                                (uint8_t *)p) != 1) {
+#else
+               if (wolfSSL_CTX_use_certificate_buffer(vhost->tls.ssl_ctx,
+                                                (uint8_t *)mem_cert,
+                                                (int)mem_cert_len,
+                                                WOLFSSL_FILETYPE_ASN1) != 1) {
+
+#endif
+                       lwsl_err("Problem loading update cert\n");
+
+                       return 1;
+               }
+
+               if (lws_tls_alloc_pem_to_der_file(vhost->context, NULL,
+                                                 mem_privkey, mem_privkey_len,
+                                                 &p, &flen)) {
+                       lwsl_notice("unable to convert memory privkey\n");
+
+                       return 1;
+               }
+#ifndef USE_WOLFSSL
+               if (SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA,
+                                               vhost->tls.ssl_ctx, p,
+                                               (long)(long long)flen) != 1) {
+#else
+               if (wolfSSL_CTX_use_PrivateKey_buffer(vhost->tls.ssl_ctx, p,
+                                           flen, WOLFSSL_FILETYPE_ASN1) != 1) {
+#endif
+                       lwsl_notice("unable to use memory privkey\n");
+
+                       return 1;
+               }
+
+               goto check_key;
+       }
+
+       /* set the local certificate from CertFile */
+       m = SSL_CTX_use_certificate_chain_file(vhost->tls.ssl_ctx, cert);
+       if (m != 1) {
+               error = ERR_get_error();
+               lwsl_err("problem getting cert '%s' %lu: %s\n",
+                        cert, error, ERR_error_string(error,
+                              (char *)vhost->context->pt[0].serv_buf));
+
+               return 1;
+       }
+
+       if (n != LWS_TLS_EXTANT_ALTERNATIVE && private_key) {
+               /* set the private key from KeyFile */
+               if (SSL_CTX_use_PrivateKey_file(vhost->tls.ssl_ctx, private_key,
+                                               SSL_FILETYPE_PEM) != 1) {
+                       error = ERR_get_error();
+                       lwsl_err("ssl problem getting key '%s' %lu: %s\n",
+                                private_key, error,
+                                ERR_error_string(error,
+                                     (char *)vhost->context->pt[0].serv_buf));
+                       return 1;
+               }
+       } else {
+               if (vhost->protocols[0].callback(wsi,
+                             LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
+                                                vhost->tls.ssl_ctx, NULL, 0)) {
+                       lwsl_err("ssl private key not set\n");
+
+                       return 1;
+               }
+       }
+
+check_key:
+#endif
+
+       /* verify private key */
+       if (!SSL_CTX_check_private_key(vhost->tls.ssl_ctx)) {
+               lwsl_err("Private SSL key doesn't match cert\n");
+
+               return 1;
+       }
+
+
+#if !defined(OPENSSL_NO_EC)
+       if (vhost->tls.ecdh_curve[0])
+               ecdh_curve = vhost->tls.ecdh_curve;
+
+       ecdh_nid = OBJ_sn2nid(ecdh_curve);
+       if (NID_undef == ecdh_nid) {
+               lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve);
+               return 1;
+       }
+
+       ecdh = EC_KEY_new_by_curve_name(ecdh_nid);
+       if (NULL == ecdh) {
+               lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve);
+               return 1;
+       }
+       SSL_CTX_set_tmp_ecdh(vhost->tls.ssl_ctx, ecdh);
+       EC_KEY_free(ecdh);
+
+       SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_SINGLE_ECDH_USE);
+
+       lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve);
+
+       if (lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH))
+               lwsl_notice(" Using ECDH certificate support\n");
+
+       /* Get X509 certificate from ssl context */
+#if !defined(LWS_WITH_BORINGSSL)
+#if !defined(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS)
+       x = sk_X509_value(vhost->tls.ssl_ctx->extra_certs, 0);
+#else
+       SSL_CTX_get_extra_chain_certs_only(vhost->tls.ssl_ctx, &extra_certs);
+       if (extra_certs)
+               x = sk_X509_value(extra_certs, 0);
+       else
+               lwsl_info("%s: no extra certs\n", __func__);
+#endif
+       if (!x) {
+               //lwsl_err("%s: x is NULL\n", __func__);
+               goto post_ecdh;
+       }
+#else
+       return 0;
+#endif
+       /* Get the public key from certificate */
+       pkey = X509_get_pubkey(x);
+       if (!pkey) {
+               lwsl_err("%s: pkey is NULL\n", __func__);
+
+               return 1;
+       }
+       /* Get the key type */
+       KeyType = EVP_PKEY_type(EVP_PKEY_id(pkey));
+
+       if (EVP_PKEY_EC != KeyType) {
+               lwsl_notice("Key type is not EC\n");
+               return 0;
+       }
+       /* Get the key */
+       EC_key = EVP_PKEY_get1_EC_KEY(pkey);
+       /* Set ECDH parameter */
+       if (!EC_key) {
+               lwsl_err("%s: ECDH key is NULL \n", __func__);
+               return 1;
+       }
+       SSL_CTX_set_tmp_ecdh(vhost->tls.ssl_ctx, EC_key);
+
+       EC_KEY_free(EC_key);
+#else
+       lwsl_notice(" OpenSSL doesn't support ECDH\n");
+#endif
+#if !defined(OPENSSL_NO_EC) && !defined(LWS_WITH_BORINGSSL)
+post_ecdh:
+#endif
+       vhost->tls.skipped_certs = 0;
+
+       return 0;
+}
+
+int
+lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info,
+                                 struct lws_vhost *vhost, struct lws *wsi)
+{
+       unsigned long error;
+       SSL_METHOD *method = (SSL_METHOD *)SSLv23_server_method();
+
+       if (!method) {
+               error = ERR_get_error();
+               lwsl_err("problem creating ssl method %lu: %s\n",
+                               error, ERR_error_string(error,
+                                     (char *)vhost->context->pt[0].serv_buf));
+               return 1;
+       }
+       vhost->tls.ssl_ctx = SSL_CTX_new(method);       /* create context */
+       if (!vhost->tls.ssl_ctx) {
+               error = ERR_get_error();
+               lwsl_err("problem creating ssl context %lu: %s\n",
+                               error, ERR_error_string(error,
+                                     (char *)vhost->context->pt[0].serv_buf));
+               return 1;
+       }
+
+       SSL_CTX_set_ex_data(vhost->tls.ssl_ctx,
+                           openssl_SSL_CTX_private_data_index,
+                           (char *)vhost->context);
+       /* Disable SSLv2 and SSLv3 */
+       SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_NO_SSLv2 |
+                                               SSL_OP_NO_SSLv3);
+#ifdef SSL_OP_NO_COMPRESSION
+       SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_NO_COMPRESSION);
+#endif
+       SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_SINGLE_DH_USE);
+       SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+       if (info->ssl_cipher_list)
+               SSL_CTX_set_cipher_list(vhost->tls.ssl_ctx, info->ssl_cipher_list);
+
+#if defined(LWS_HAVE_SSL_CTX_set_ciphersuites)
+       if (info->tls1_3_plus_cipher_list)
+               SSL_CTX_set_ciphersuites(vhost->tls.ssl_ctx,
+                                        info->tls1_3_plus_cipher_list);
+#endif
+
+#if !defined(OPENSSL_NO_TLSEXT)
+       SSL_CTX_set_tlsext_servername_callback(vhost->tls.ssl_ctx,
+                                              lws_ssl_server_name_cb);
+       SSL_CTX_set_tlsext_servername_arg(vhost->tls.ssl_ctx, vhost->context);
+#endif
+
+       if (info->ssl_ca_filepath &&
+           !SSL_CTX_load_verify_locations(vhost->tls.ssl_ctx,
+                                          info->ssl_ca_filepath, NULL)) {
+               lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n",
+                        __func__);
+       }
+
+       if (info->ssl_options_set)
+               SSL_CTX_set_options(vhost->tls.ssl_ctx, info->ssl_options_set);
+
+/* SSL_clear_options introduced in 0.9.8m */
+#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
+       if (info->ssl_options_clear)
+               SSL_CTX_clear_options(vhost->tls.ssl_ctx,
+                                     info->ssl_options_clear);
+#endif
+
+       lwsl_info(" SSL options 0x%lX\n",
+                       (unsigned long)SSL_CTX_get_options(vhost->tls.ssl_ctx));
+       if (!vhost->tls.use_ssl ||
+           (!info->ssl_cert_filepath && !info->server_ssl_cert_mem))
+               return 0;
+
+       lws_ssl_bind_passphrase(vhost->tls.ssl_ctx, 0, info);
+
+       return lws_tls_server_certs_load(vhost, wsi, info->ssl_cert_filepath,
+                                        info->ssl_private_key_filepath,
+                                        info->server_ssl_cert_mem,
+                                        info->server_ssl_cert_mem_len,
+                                        info->server_ssl_private_key_mem,
+                                        info->server_ssl_private_key_mem_len);
+}
+
+int
+lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd)
+{
+#if !defined(USE_WOLFSSL)
+       BIO *bio;
+#endif
+
+       errno = 0;
+       ERR_clear_error();
+       wsi->tls.ssl = SSL_new(wsi->vhost->tls.ssl_ctx);
+       if (wsi->tls.ssl == NULL) {
+               lwsl_err("SSL_new failed: %d (errno %d)\n",
+                        lws_ssl_get_error(wsi, 0), errno);
+
+               lws_tls_err_describe_clear();
+               return 1;
+       }
+
+       SSL_set_ex_data(wsi->tls.ssl, openssl_websocket_private_data_index, wsi);
+       SSL_set_fd(wsi->tls.ssl, (int)(long long)accept_fd);
+
+#ifdef USE_WOLFSSL
+#ifdef USE_OLD_CYASSL
+       CyaSSL_set_using_nonblock(wsi->tls.ssl, 1);
+#else
+       wolfSSL_set_using_nonblock(wsi->tls.ssl, 1);
+#endif
+#else
+
+       SSL_set_mode(wsi->tls.ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
+                                  SSL_MODE_RELEASE_BUFFERS);
+       bio = SSL_get_rbio(wsi->tls.ssl);
+       if (bio)
+               BIO_set_nbio(bio, 1); /* nonblocking */
+       else
+               lwsl_notice("NULL rbio\n");
+       bio = SSL_get_wbio(wsi->tls.ssl);
+       if (bio)
+               BIO_set_nbio(bio, 1); /* nonblocking */
+       else
+               lwsl_notice("NULL rbio\n");
+#endif
+
+#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
+               if (wsi->vhost->tls.ssl_info_event_mask)
+                       SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback);
+#endif
+
+       return 0;
+}
+
+int
+lws_tls_server_abort_connection(struct lws *wsi)
+{
+       SSL_shutdown(wsi->tls.ssl);
+       SSL_free(wsi->tls.ssl);
+
+       return 0;
+}
+
+enum lws_ssl_capable_status
+lws_tls_server_accept(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+       union lws_tls_cert_info_results ir;
+       int m, n;
+
+       errno = 0;
+       ERR_clear_error();
+       n = SSL_accept(wsi->tls.ssl);
+
+       if (n == 1) {
+               n = lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
+                                          sizeof(ir.ns.name));
+               if (!n)
+                       lwsl_notice("%s: client cert CN '%s'\n", __func__,
+                                   ir.ns.name);
+               else
+                       lwsl_info("%s: no client cert CN\n", __func__);
+
+               lws_openssl_describe_cipher(wsi);
+
+               if (SSL_pending(wsi->tls.ssl) &&
+                   lws_dll2_is_detached(&wsi->tls.dll_pending_tls))
+                       lws_dll2_add_head(&wsi->tls.dll_pending_tls,
+                                         &pt->tls.dll_pending_tls_owner);
+
+               return LWS_SSL_CAPABLE_DONE;
+       }
+
+       m = lws_ssl_get_error(wsi, n);
+       lws_tls_err_describe_clear();
+
+       if (m == SSL_ERROR_SYSCALL || m == SSL_ERROR_SSL)
+               return LWS_SSL_CAPABLE_ERROR;
+
+       if (m == SSL_ERROR_WANT_READ ||
+           (m != SSL_ERROR_ZERO_RETURN && SSL_want_read(wsi->tls.ssl))) {
+               if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
+                       lwsl_info("%s: WANT_READ change_pollfd failed\n",
+                                 __func__);
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+
+               lwsl_info("SSL_ERROR_WANT_READ: m %d\n", m);
+               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+       }
+       if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) {
+               lwsl_debug("%s: WANT_WRITE\n", __func__);
+
+               if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) {
+                       lwsl_info("%s: WANT_WRITE change_pollfd failed\n",
+                                 __func__);
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+               return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;
+       }
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+#if defined(LWS_WITH_ACME)
+static int
+lws_tls_openssl_rsa_new_key(RSA **rsa, int bits)
+{
+       BIGNUM *bn = BN_new();
+       int n;
+
+       if (!bn)
+               return 1;
+
+       if (BN_set_word(bn, RSA_F4) != 1) {
+               BN_free(bn);
+               return 1;
+       }
+
+       *rsa = RSA_new();
+       if (!*rsa) {
+               BN_free(bn);
+               return 1;
+       }
+
+       n = RSA_generate_key_ex(*rsa, bits, bn, NULL);
+       BN_free(bn);
+       if (n == 1)
+               return 0;
+
+       RSA_free(*rsa);
+       *rsa = NULL;
+
+       return 1;
+}
+
+struct lws_tls_ss_pieces {
+       X509 *x509;
+       EVP_PKEY *pkey;
+       RSA *rsa;
+};
+
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a,
+                            const char *san_b)
+{
+       GENERAL_NAMES *gens = sk_GENERAL_NAME_new_null();
+       GENERAL_NAME *gen = NULL;
+       ASN1_IA5STRING *ia5 = NULL;
+       X509_NAME *name;
+
+       if (!gens)
+               return 1;
+
+       vhost->tls.ss = lws_zalloc(sizeof(*vhost->tls.ss), "sni cert");
+       if (!vhost->tls.ss) {
+               GENERAL_NAMES_free(gens);
+               return 1;
+       }
+
+       vhost->tls.ss->x509 = X509_new();
+       if (!vhost->tls.ss->x509)
+               goto bail;
+
+       ASN1_INTEGER_set(X509_get_serialNumber(vhost->tls.ss->x509), 1);
+       X509_gmtime_adj(X509_get_notBefore(vhost->tls.ss->x509), 0);
+       X509_gmtime_adj(X509_get_notAfter(vhost->tls.ss->x509), 3600);
+
+       vhost->tls.ss->pkey = EVP_PKEY_new();
+       if (!vhost->tls.ss->pkey)
+               goto bail0;
+
+       if (lws_tls_openssl_rsa_new_key(&vhost->tls.ss->rsa, 4096))
+               goto bail1;
+
+       if (!EVP_PKEY_assign_RSA(vhost->tls.ss->pkey, vhost->tls.ss->rsa))
+               goto bail2;
+
+       X509_set_pubkey(vhost->tls.ss->x509, vhost->tls.ss->pkey);
+
+       name = X509_get_subject_name(vhost->tls.ss->x509);
+       X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC,
+                                  (unsigned char *)"GB",          -1, -1, 0);
+       X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC,
+                                  (unsigned char *)"somecompany", -1, -1, 0);
+       if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_UTF8,
+                                  (unsigned char *)"temp.acme.invalid",
+                                                  -1, -1, 0) != 1) {
+               lwsl_notice("failed to add CN\n");
+               goto bail2;
+       }
+       X509_set_issuer_name(vhost->tls.ss->x509, name);
+
+       /* add the SAN payloads */
+
+       gen = GENERAL_NAME_new();
+       ia5 = ASN1_IA5STRING_new();
+       if (!ASN1_STRING_set(ia5, san_a, -1)) {
+               lwsl_notice("failed to set ia5\n");
+               GENERAL_NAME_free(gen);
+               goto bail2;
+       }
+       GENERAL_NAME_set0_value(gen, GEN_DNS, ia5);
+       sk_GENERAL_NAME_push(gens, gen);
+
+       if (X509_add1_ext_i2d(vhost->tls.ss->x509, NID_subject_alt_name,
+                           gens, 0, X509V3_ADD_APPEND) != 1)
+               goto bail2;
+
+       GENERAL_NAMES_free(gens);
+
+       if (san_b && san_b[0]) {
+               gens = sk_GENERAL_NAME_new_null();
+               gen = GENERAL_NAME_new();
+               ia5 = ASN1_IA5STRING_new();
+               if (!ASN1_STRING_set(ia5, san_a, -1)) {
+                       lwsl_notice("failed to set ia5\n");
+                       GENERAL_NAME_free(gen);
+                       goto bail2;
+               }
+               GENERAL_NAME_set0_value(gen, GEN_DNS, ia5);
+               sk_GENERAL_NAME_push(gens, gen);
+
+               if (X509_add1_ext_i2d(vhost->tls.ss->x509, NID_subject_alt_name,
+                                   gens, 0, X509V3_ADD_APPEND) != 1)
+                       goto bail2;
+
+               GENERAL_NAMES_free(gens);
+       }
+
+       /* sign it with our private key */
+       if (!X509_sign(vhost->tls.ss->x509, vhost->tls.ss->pkey, EVP_sha256()))
+               goto bail2;
+
+#if 0
+       {/* useful to take a sample of a working cert for mbedtls to crib */
+               FILE *fp = fopen("/tmp/acme-temp-cert", "w+");
+
+               i2d_X509_fp(fp, vhost->tls.ss->x509);
+               fclose(fp);
+       }
+#endif
+
+       /* tell the vhost to use our crafted certificate */
+       SSL_CTX_use_certificate(vhost->tls.ssl_ctx, vhost->tls.ss->x509);
+       /* and to use our generated private key */
+       SSL_CTX_use_PrivateKey(vhost->tls.ssl_ctx, vhost->tls.ss->pkey);
+
+       return 0;
+
+bail2:
+       RSA_free(vhost->tls.ss->rsa);
+bail1:
+       EVP_PKEY_free(vhost->tls.ss->pkey);
+bail0:
+       X509_free(vhost->tls.ss->x509);
+bail:
+       lws_free(vhost->tls.ss);
+       GENERAL_NAMES_free(gens);
+
+       return 1;
+}
+
+void
+lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost)
+{
+       if (!vhost->tls.ss)
+               return;
+
+       EVP_PKEY_free(vhost->tls.ss->pkey);
+       X509_free(vhost->tls.ss->x509);
+       lws_free_set_NULL(vhost->tls.ss);
+}
+
+static int
+lws_tls_openssl_add_nid(X509_NAME *name, int nid, const char *value)
+{
+       X509_NAME_ENTRY *e;
+       int n;
+
+       if (!value || value[0] == '\0')
+               value = "none";
+
+       e = X509_NAME_ENTRY_create_by_NID(NULL, nid, MBSTRING_ASC,
+                                         (unsigned char *)value, -1);
+       if (!e)
+               return 1;
+       n = X509_NAME_add_entry(name, e, -1, 0);
+       X509_NAME_ENTRY_free(e);
+
+       return n != 1;
+}
+
+static int nid_list[] = {
+       NID_countryName,                /* LWS_TLS_REQ_ELEMENT_COUNTRY */
+       NID_stateOrProvinceName,        /* LWS_TLS_REQ_ELEMENT_STATE */
+       NID_localityName,               /* LWS_TLS_REQ_ELEMENT_LOCALITY */
+       NID_organizationName,           /* LWS_TLS_REQ_ELEMENT_ORGANIZATION */
+       NID_commonName,                 /* LWS_TLS_REQ_ELEMENT_COMMON_NAME */
+       NID_organizationalUnitName,     /* LWS_TLS_REQ_ELEMENT_EMAIL */
+};
+
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[],
+                           uint8_t *csr, size_t csr_len, char **privkey_pem,
+                           size_t *privkey_len)
+{
+       uint8_t *csr_in = csr;
+       RSA *rsakey;
+       X509_REQ *req;
+       X509_NAME *subj;
+       EVP_PKEY *pkey;
+       char *p, *end;
+       BIO *bio;
+       long bio_len;
+       int n, ret = -1;
+
+       if (lws_tls_openssl_rsa_new_key(&rsakey, 4096))
+               return -1;
+
+       pkey = EVP_PKEY_new();
+       if (!pkey)
+               goto bail0;
+       if (!EVP_PKEY_set1_RSA(pkey, rsakey))
+               goto bail1;
+
+       req = X509_REQ_new();
+       if (!req)
+               goto bail1;
+
+       X509_REQ_set_pubkey(req, pkey);
+
+       subj = X509_NAME_new();
+       if (!subj)
+               goto bail2;
+
+       for (n = 0; n < LWS_TLS_REQ_ELEMENT_COUNT; n++)
+               if (lws_tls_openssl_add_nid(subj, nid_list[n], elements[n])) {
+                       lwsl_notice("%s: failed to add element %d\n", __func__,
+                                   n);
+                       goto bail3;
+               }
+
+       if (X509_REQ_set_subject_name(req, subj) != 1)
+               goto bail3;
+
+       if (!X509_REQ_sign(req, pkey, EVP_sha256()))
+               goto bail3;
+
+       /*
+        * issue the CSR as PEM to a BIO, and translate to b64urlenc without
+        * headers, trailers, or whitespace
+        */
+
+       bio = BIO_new(BIO_s_mem());
+       if (!bio)
+               goto bail3;
+
+       if (PEM_write_bio_X509_REQ(bio, req) != 1) {
+               BIO_free(bio);
+               goto bail3;
+       }
+
+       bio_len = BIO_get_mem_data(bio, &p);
+       end = p + bio_len;
+
+       /* strip the header line */
+       while (p < end && *p != '\n')
+               p++;
+
+       while (p < end && csr_len) {
+               if (*p == '\n') {
+                       p++;
+                       continue;
+               }
+
+               if (*p == '-')
+                       break;
+
+               if (*p == '+')
+                       *csr++ = '-';
+               else
+                       if (*p == '/')
+                               *csr++ = '_';
+                       else
+                               *csr++ = *p;
+               p++;
+               csr_len--;
+       }
+       BIO_free(bio);
+       if (!csr_len) {
+               lwsl_notice("%s: need %ld for CSR\n", __func__, bio_len);
+               goto bail3;
+       }
+
+       /*
+        * Also return the private key as a PEM in memory
+        * (platform may not have a filesystem)
+        */
+       bio = BIO_new(BIO_s_mem());
+       if (!bio)
+               goto bail3;
+
+       if (PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, 0, NULL) != 1) {
+               BIO_free(bio);
+               goto bail3;
+       }
+       bio_len = BIO_get_mem_data(bio, &p);
+       *privkey_pem = malloc(bio_len); /* malloc so user code can own / free */
+       *privkey_len = (size_t)bio_len;
+       if (!*privkey_pem) {
+               lwsl_notice("%s: need %ld for private key\n", __func__,
+                           bio_len);
+               BIO_free(bio);
+               goto bail3;
+       }
+       memcpy(*privkey_pem, p, (int)(long long)bio_len);
+       BIO_free(bio);
+
+       ret = lws_ptr_diff(csr, csr_in);
+
+bail3:
+       X509_NAME_free(subj);
+bail2:
+       X509_REQ_free(req);
+bail1:
+       EVP_PKEY_free(pkey);
+bail0:
+       RSA_free(rsakey);
+
+       return ret;
+}
+#endif
diff --git a/lib/tls/openssl/private.h b/lib/tls/openssl/private.h
new file mode 100644 (file)
index 0000000..bd0b174
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  gencrypto openssl-specific helper declarations
+ */
+
+/*
+ * one of these per different client context
+ * cc_owner is in lws_context.lws_context_tls
+ */
+
+struct lws_tls_client_reuse {
+       lws_tls_ctx *ssl_client_ctx;
+       uint8_t hash[32];
+       struct lws_dll2 cc_list;
+       int refcount;
+       int index;
+};
+
+typedef int (*next_proto_cb)(SSL *, const unsigned char **out,
+                             unsigned char *outlen, const unsigned char *in,
+                             unsigned int inlen, void *arg);
+
+struct lws_x509_cert {
+       X509 *cert; /* X509 is opaque, this has to be a pointer */
+};
+
+int
+lws_gencrypto_openssl_hash_to_NID(enum lws_genhash_types hash_type);
+
+const EVP_MD *
+lws_gencrypto_openssl_hash_to_EVP_MD(enum lws_genhash_types hash_type);
+
+#if !defined(LWS_HAVE_BN_bn2binpad)
+int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen);
+#endif
diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c
new file mode 100644 (file)
index 0000000..ad2a76e
--- /dev/null
@@ -0,0 +1,510 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/openssl/private.h"
+#include <errno.h>
+
+int openssl_websocket_private_data_index,
+          openssl_SSL_CTX_private_data_index;
+
+/*
+ * Care: many openssl apis return 1 for success.  These are translated to the
+ * lws convention of 0 for success.
+ */
+
+int lws_openssl_describe_cipher(struct lws *wsi)
+{
+#if !defined(LWS_WITH_NO_LOGS)
+       int np = -1;
+       SSL *s = wsi->tls.ssl;
+
+       SSL_get_cipher_bits(s, &np);
+       lwsl_info("%s: wsi %p: %s, %s, %d bits, %s\n", __func__, wsi,
+                       SSL_get_cipher_name(s), SSL_get_cipher(s), np,
+                       SSL_get_cipher_version(s));
+#endif
+
+       return 0;
+}
+
+int lws_ssl_get_error(struct lws *wsi, int n)
+{
+       int m;
+
+       if (!wsi->tls.ssl)
+               return 99;
+
+       m = SSL_get_error(wsi->tls.ssl, n);
+       lwsl_debug("%s: %p %d -> %d (errno %d)\n", __func__, wsi->tls.ssl, n, m,
+                  errno);
+
+       return m;
+}
+
+static int
+lws_context_init_ssl_pem_passwd_cb(char *buf, int size, int rwflag,
+                                  void *userdata)
+{
+       struct lws_context_creation_info * info =
+                       (struct lws_context_creation_info *)userdata;
+
+       strncpy(buf, info->ssl_private_key_password, size);
+       buf[size - 1] = '\0';
+
+       return (int)strlen(buf);
+}
+
+static int
+lws_context_init_ssl_pem_passwd_client_cb(char *buf, int size, int rwflag,
+                                         void *userdata)
+{
+       struct lws_context_creation_info * info =
+                       (struct lws_context_creation_info *)userdata;
+       const char *p = info->ssl_private_key_password;
+
+       if (info->client_ssl_private_key_password)
+               p = info->client_ssl_private_key_password;
+
+       strncpy(buf, p, size);
+       buf[size - 1] = '\0';
+
+       return (int)strlen(buf);
+}
+
+void
+lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, int is_client,
+                       const struct lws_context_creation_info *info)
+{
+       if (!info->ssl_private_key_password &&
+           !info->client_ssl_private_key_password)
+               return;
+       /*
+        * password provided, set ssl callback and user data
+        * for checking password which will be trigered during
+        * SSL_CTX_use_PrivateKey_file function
+        */
+       SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)info);
+       SSL_CTX_set_default_passwd_cb(ssl_ctx, is_client ?
+                                     lws_context_init_ssl_pem_passwd_client_cb:
+                                     lws_context_init_ssl_pem_passwd_cb);
+}
+
+static void
+lws_ssl_destroy_client_ctx(struct lws_vhost *vhost)
+{
+       struct lws_tls_client_reuse *tcr;
+
+       if (vhost->tls.user_supplied_ssl_ctx || !vhost->tls.ssl_client_ctx)
+               return;
+
+       tcr = SSL_CTX_get_ex_data(vhost->tls.ssl_client_ctx,
+                                 openssl_SSL_CTX_private_data_index);
+
+       if (!tcr || --tcr->refcount)
+               return;
+
+       SSL_CTX_free(vhost->tls.ssl_client_ctx);
+       vhost->tls.ssl_client_ctx = NULL;
+
+       vhost->context->tls.count_client_contexts--;
+
+       lws_dll2_remove(&tcr->cc_list);
+       lws_free(tcr);
+}
+
+LWS_VISIBLE void
+lws_ssl_destroy(struct lws_vhost *vhost)
+{
+       if (!lws_check_opt(vhost->context->options,
+                          LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
+               return;
+
+       if (vhost->tls.ssl_ctx)
+               SSL_CTX_free(vhost->tls.ssl_ctx);
+
+       lws_ssl_destroy_client_ctx(vhost);
+
+// after 1.1.0 no need
+#if (OPENSSL_VERSION_NUMBER <  0x10100000)
+// <= 1.0.1f = old api, 1.0.1g+ = new api
+#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL)
+       ERR_remove_state(0);
+#else
+#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \
+    !defined(LIBRESSL_VERSION_NUMBER) && \
+    !defined(OPENSSL_IS_BORINGSSL)
+       ERR_remove_thread_state();
+#else
+       ERR_remove_thread_state(NULL);
+#endif
+#endif
+       /* not needed after 1.1.0 */
+#if  (OPENSSL_VERSION_NUMBER >= 0x10002000) && \
+     (OPENSSL_VERSION_NUMBER <= 0x10100000)
+       SSL_COMP_free_compression_methods();
+#endif
+       ERR_free_strings();
+       EVP_cleanup();
+       CRYPTO_cleanup_all_ex_data();
+#endif
+}
+
+LWS_VISIBLE int
+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, m;
+
+       if (!wsi->tls.ssl)
+               return lws_ssl_capable_read_no_ssl(wsi, buf, len);
+
+       lws_stats_bump(pt, LWSSTATS_C_API_READ, 1);
+
+       errno = 0;
+       ERR_clear_error();
+       n = SSL_read(wsi->tls.ssl, buf, len);
+#if defined(LWS_WITH_ESP32)
+       if (!n && errno == LWS_ENOTCONN) {
+               lwsl_debug("%p: SSL_read ENOTCONN\n", wsi);
+               return LWS_SSL_CAPABLE_ERROR;
+       }
+#endif
+#if defined(LWS_WITH_STATS)
+       if (!wsi->seen_rx && wsi->accept_start_us) {
+                lws_stats_bump(pt, LWSSTATS_US_SSL_RX_DELAY_AVG,
+                                     lws_now_usecs() -
+                                             wsi->accept_start_us);
+                lws_stats_bump(pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1);
+               wsi->seen_rx = 1;
+       }
+#endif
+
+
+       lwsl_debug("%p: SSL_read says %d\n", wsi, n);
+       /* manpage: returning 0 means connection shut down
+        *
+        * 2018-09-10: https://github.com/openssl/openssl/issues/1903
+        *
+        * So, in summary, if you get a 0 or -1 return from SSL_read() /
+        * SSL_write(), you should call SSL_get_error():
+        *
+        *  - If you get back SSL_ERROR_RETURN_ZERO then you know the connection
+        *    has been cleanly shutdown by the peer. To fully close the
+        *    connection you may choose to call SSL_shutdown() to send a
+        *    close_notify back.
+        *
+        *  - If you get back SSL_ERROR_SSL then some kind of internal or
+        *    protocol error has occurred. More details will be on the SSL error
+        *    queue. You can also call SSL_get_shutdown(). If this indicates a
+        *    state of SSL_RECEIVED_SHUTDOWN then you know a fatal alert has
+        *    been received from the peer (if it had been a close_notify then
+        *    SSL_get_error() would have returned SSL_ERROR_RETURN_ZERO).
+        *    SSL_ERROR_SSL is considered fatal - you should not call
+        *    SSL_shutdown() in this case.
+        *
+        *  - If you get back SSL_ERROR_SYSCALL then some kind of fatal (i.e.
+        *    non-retryable) error has occurred in a system call.
+        */
+       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) /* cleanly shut down */
+                       return LWS_SSL_CAPABLE_ERROR;
+
+               /* hm not retryable.. could be 0 size pkt or error  */
+
+               if (m == SSL_ERROR_SSL || m == SSL_ERROR_SYSCALL ||
+                   errno == LWS_ENOTCONN) {
+
+                       /* unclean, eg closed conn */
+
+                       wsi->socket_is_permanently_unusable = 1;
+
+                       return LWS_SSL_CAPABLE_ERROR;
+               }
+
+               /* retryable? */
+
+               if (SSL_want_read(wsi->tls.ssl)) {
+                       lwsl_debug("%s: WANT_READ\n", __func__);
+                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+               if (SSL_want_write(wsi->tls.ssl)) {
+                       lwsl_debug("%s: WANT_WRITE\n", __func__);
+                       lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+
+               /* keep on trucking it seems */
+       }
+
+       lws_stats_bump(pt, LWSSTATS_B_READ, n);
+
+       if (wsi->vhost)
+               wsi->vhost->conn_stats.rx += n;
+
+       // lwsl_hexdump_err(buf, n);
+
+       /*
+        * if it was our buffer that limited what we read,
+        * check if SSL has additional data pending inside SSL buffers.
+        *
+        * Because these won't signal at the network layer with POLLIN
+        * and if we don't realize, this data will sit there forever
+        */
+       if (n != len)
+               goto bail;
+       if (!wsi->tls.ssl)
+               goto bail;
+
+       if (SSL_pending(wsi->tls.ssl) &&
+           lws_dll2_is_detached(&wsi->tls.dll_pending_tls))
+               lws_dll2_add_head(&wsi->tls.dll_pending_tls,
+                                 &pt->tls.dll_pending_tls_owner);
+
+       return n;
+bail:
+       lws_ssl_remove_wsi_from_buffered_list(wsi);
+
+       return n;
+}
+
+LWS_VISIBLE int
+lws_ssl_pending(struct lws *wsi)
+{
+       if (!wsi->tls.ssl)
+               return 0;
+
+       return SSL_pending(wsi->tls.ssl);
+}
+
+LWS_VISIBLE int
+lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len)
+{
+       int n, m;
+
+       if (!wsi->tls.ssl)
+               return lws_ssl_capable_write_no_ssl(wsi, buf, len);
+
+       errno = 0;
+       ERR_clear_error();
+       n = SSL_write(wsi->tls.ssl, buf, len);
+       if (n > 0)
+               return n;
+
+       m = lws_ssl_get_error(wsi, n);
+       if (m != SSL_ERROR_SYSCALL) {
+               if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) {
+                       lwsl_notice("%s: want read\n", __func__);
+
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+
+               if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) {
+                       lws_set_blocking_send(wsi);
+
+                       lwsl_debug("%s: want write\n", __func__);
+
+                       return LWS_SSL_CAPABLE_MORE_SERVICE;
+               }
+       }
+
+       lwsl_debug("%s failed: %s\n",__func__, ERR_error_string(m, NULL));
+       lws_tls_err_describe_clear();
+
+       wsi->socket_is_permanently_unusable = 1;
+
+       return LWS_SSL_CAPABLE_ERROR;
+}
+
+void
+lws_ssl_info_callback(const SSL *ssl, int where, int ret)
+{
+       struct lws *wsi;
+       struct lws_context *context;
+       struct lws_ssl_info si;
+
+#ifndef USE_WOLFSSL
+       context = (struct lws_context *)SSL_CTX_get_ex_data(
+                                       SSL_get_SSL_CTX(ssl),
+                                       openssl_SSL_CTX_private_data_index);
+#else
+       context = (struct lws_context *)SSL_CTX_get_ex_data(
+                                       SSL_get_SSL_CTX((SSL*) ssl),
+                                       openssl_SSL_CTX_private_data_index);
+#endif
+       if (!context)
+               return;
+       wsi = wsi_from_fd(context, SSL_get_fd(ssl));
+       if (!wsi)
+               return;
+
+       if (!(where & wsi->vhost->tls.ssl_info_event_mask))
+               return;
+
+       si.where = where;
+       si.ret = ret;
+
+       if (user_callback_handle_rxflow(wsi->protocol->callback,
+                                       wsi, LWS_CALLBACK_SSL_INFO,
+                                       wsi->user_space, &si, 0))
+               lws_set_timeout(wsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, -1);
+}
+
+
+LWS_VISIBLE int
+lws_ssl_close(struct lws *wsi)
+{
+       lws_sockfd_type n;
+
+       if (!wsi->tls.ssl)
+               return 0; /* not handled */
+
+#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK)
+       /* kill ssl callbacks, because we will remove the fd from the
+        * table linking it to the wsi
+        */
+       if (wsi->vhost->tls.ssl_info_event_mask)
+               SSL_set_info_callback(wsi->tls.ssl, NULL);
+#endif
+
+       n = SSL_get_fd(wsi->tls.ssl);
+       if (!wsi->socket_is_permanently_unusable)
+               SSL_shutdown(wsi->tls.ssl);
+       compatible_close(n);
+       SSL_free(wsi->tls.ssl);
+       wsi->tls.ssl = NULL;
+
+       if (wsi->context->simultaneous_ssl_restriction &&
+           wsi->context->simultaneous_ssl-- ==
+                           wsi->context->simultaneous_ssl_restriction)
+               /* we made space and can do an accept */
+               lws_gate_accepts(wsi->context, 1);
+
+       // lwsl_notice("%s: ssl restr %d, simul %d\n", __func__,
+       //              wsi->context->simultaneous_ssl_restriction,
+       //              wsi->context->simultaneous_ssl);
+
+#if defined(LWS_WITH_STATS)
+       wsi->context->updated = 1;
+#endif
+
+       return 1; /* handled */
+}
+
+void
+lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost)
+{
+       if (vhost->tls.ssl_ctx)
+               SSL_CTX_free(vhost->tls.ssl_ctx);
+
+       lws_ssl_destroy_client_ctx(vhost);
+
+#if defined(LWS_WITH_ACME)
+       lws_tls_acme_sni_cert_destroy(vhost);
+#endif
+}
+
+void
+lws_ssl_context_destroy(struct lws_context *context)
+{
+// after 1.1.0 no need
+#if (OPENSSL_VERSION_NUMBER <  0x10100000)
+// <= 1.0.1f = old api, 1.0.1g+ = new api
+#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL)
+       ERR_remove_state(0);
+#else
+#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \
+    !defined(LIBRESSL_VERSION_NUMBER) && \
+    !defined(OPENSSL_IS_BORINGSSL)
+       ERR_remove_thread_state();
+#else
+       ERR_remove_thread_state(NULL);
+#endif
+#endif
+       // after 1.1.0 no need
+#if  (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000)
+       SSL_COMP_free_compression_methods();
+#endif
+       ERR_free_strings();
+       EVP_cleanup();
+       CRYPTO_cleanup_all_ex_data();
+#endif
+}
+
+lws_tls_ctx *
+lws_tls_ctx_from_wsi(struct lws *wsi)
+{
+       if (!wsi->tls.ssl)
+               return NULL;
+
+       return SSL_get_SSL_CTX(wsi->tls.ssl);
+}
+
+enum lws_ssl_capable_status
+__lws_tls_shutdown(struct lws *wsi)
+{
+       int n;
+
+       errno = 0;
+       ERR_clear_error();
+       n = SSL_shutdown(wsi->tls.ssl);
+       lwsl_debug("SSL_shutdown=%d for fd %d\n", n, wsi->desc.sockfd);
+       switch (n) {
+       case 1: /* successful completion */
+               n = shutdown(wsi->desc.sockfd, SHUT_WR);
+               return LWS_SSL_CAPABLE_DONE;
+
+       case 0: /* needs a retry */
+               __lws_change_pollfd(wsi, 0, LWS_POLLIN);
+               return LWS_SSL_CAPABLE_MORE_SERVICE;
+
+       default: /* fatal error, or WANT */
+               n = SSL_get_error(wsi->tls.ssl, n);
+               if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) {
+                       if (SSL_want_read(wsi->tls.ssl)) {
+                               lwsl_debug("(wants read)\n");
+                               __lws_change_pollfd(wsi, 0, LWS_POLLIN);
+                               return LWS_SSL_CAPABLE_MORE_SERVICE_READ;
+                       }
+                       if (SSL_want_write(wsi->tls.ssl)) {
+                               lwsl_debug("(wants write)\n");
+                               __lws_change_pollfd(wsi, 0, LWS_POLLOUT);
+                               return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE;
+                       }
+               }
+               return LWS_SSL_CAPABLE_ERROR;
+       }
+}
+
+
+static int
+tops_fake_POLLIN_for_buffered_openssl(struct lws_context_per_thread *pt)
+{
+       return lws_tls_fake_POLLIN_for_buffered(pt);
+}
+
+const struct lws_tls_ops tls_ops_openssl = {
+       /* fake_POLLIN_for_buffered */  tops_fake_POLLIN_for_buffered_openssl,
+};
diff --git a/lib/tls/openssl/tls.c b/lib/tls/openssl/tls.c
new file mode 100644 (file)
index 0000000..d14cd83
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/openssl/private.h"
+
+extern int openssl_websocket_private_data_index,
+openssl_SSL_CTX_private_data_index;
+
+char* lws_ssl_get_error_string(int status, int ret, char *buf, size_t len) {
+       switch (status) {
+       case SSL_ERROR_NONE:
+               return lws_strncpy(buf, "SSL_ERROR_NONE", len);
+       case SSL_ERROR_ZERO_RETURN:
+               return lws_strncpy(buf, "SSL_ERROR_ZERO_RETURN", len);
+       case SSL_ERROR_WANT_READ:
+               return lws_strncpy(buf, "SSL_ERROR_WANT_READ", len);
+       case SSL_ERROR_WANT_WRITE:
+               return lws_strncpy(buf, "SSL_ERROR_WANT_WRITE", len);
+       case SSL_ERROR_WANT_CONNECT:
+               return lws_strncpy(buf, "SSL_ERROR_WANT_CONNECT", len);
+       case SSL_ERROR_WANT_ACCEPT:
+               return lws_strncpy(buf, "SSL_ERROR_WANT_ACCEPT", len);
+       case SSL_ERROR_WANT_X509_LOOKUP:
+               return lws_strncpy(buf, "SSL_ERROR_WANT_X509_LOOKUP", len);
+       case SSL_ERROR_SYSCALL:
+               switch (ret) {
+                case 0:
+                        lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: EOF");
+                        return buf;
+                case -1:
+#ifndef LWS_PLAT_OPTEE
+                       lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %s",
+                                    strerror(errno));
+#else
+                       lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %d", errno);
+#endif
+                       return buf;
+                default:
+                        return strncpy(buf, "SSL_ERROR_SYSCALL", len);
+       }
+       case SSL_ERROR_SSL:
+               return "SSL_ERROR_SSL";
+       default:
+               return "SSL_ERROR_UNKNOWN";
+       }
+}
+
+void
+lws_tls_err_describe_clear(void)
+{
+       char buf[160];
+       unsigned long l;
+
+       do {
+               l = ERR_get_error();
+               if (!l)
+                       break;
+
+               ERR_error_string_n(l, buf, sizeof(buf));
+               lwsl_info("   openssl error: %s\n", buf);
+       } while (l);
+       lwsl_info("\n");
+}
+
+#if LWS_MAX_SMP != 1
+
+static pthread_mutex_t *openssl_mutexes;
+
+static void
+lws_openssl_lock_callback(int mode, int type, const char *file, int line)
+{
+       (void)file;
+       (void)line;
+
+       if (mode & CRYPTO_LOCK)
+               pthread_mutex_lock(&openssl_mutexes[type]);
+       else
+               pthread_mutex_unlock(&openssl_mutexes[type]);
+}
+
+static unsigned long
+lws_openssl_thread_id(void)
+{
+       return (unsigned long)pthread_self();
+}
+#endif
+
+
+int
+lws_context_init_ssl_library(const struct lws_context_creation_info *info)
+{
+#ifdef USE_WOLFSSL
+#ifdef USE_OLD_CYASSL
+       lwsl_info(" Compiled with CyaSSL support\n");
+#else
+       lwsl_info(" Compiled with wolfSSL support\n");
+#endif
+#else
+#if defined(LWS_WITH_BORINGSSL)
+       lwsl_info(" Compiled with BoringSSL support\n");
+#else
+       lwsl_info(" Compiled with OpenSSL support\n");
+#endif
+#endif
+       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) {
+               lwsl_info(" SSL disabled: no "
+                         "LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT\n");
+               return 0;
+       }
+
+       /* basic openssl init */
+
+       lwsl_info("Doing SSL library init\n");
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+       SSL_library_init();
+       OpenSSL_add_all_algorithms();
+       SSL_load_error_strings();
+#else
+       OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
+#endif
+#if defined(LWS_WITH_NETWORK)
+       openssl_websocket_private_data_index =
+               SSL_get_ex_new_index(0, "lws", NULL, NULL, NULL);
+
+       openssl_SSL_CTX_private_data_index = SSL_CTX_get_ex_new_index(0,
+                       NULL, NULL, NULL, NULL);
+#endif
+
+#if LWS_MAX_SMP != 1
+       {
+               int n;
+
+               openssl_mutexes = (pthread_mutex_t *)
+                               OPENSSL_malloc(CRYPTO_num_locks() *
+                                              sizeof(openssl_mutexes[0]));
+
+               for (n = 0; n < CRYPTO_num_locks(); n++)
+                       pthread_mutex_init(&openssl_mutexes[n], NULL);
+
+               /*
+                * These "functions" disappeared in later OpenSSL which is
+                * already threadsafe.
+                */
+
+               (void)lws_openssl_thread_id;
+               (void)lws_openssl_lock_callback;
+
+               CRYPTO_set_id_callback(lws_openssl_thread_id);
+               CRYPTO_set_locking_callback(lws_openssl_lock_callback);
+       }
+#endif
+
+       return 0;
+}
+
+void
+lws_context_deinit_ssl_library(struct lws_context *context)
+{
+#if LWS_MAX_SMP != 1
+       int n;
+
+       if (!lws_check_opt(context->options,
+                          LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
+               return;
+
+       CRYPTO_set_locking_callback(NULL);
+
+       for (n = 0; n < CRYPTO_num_locks(); n++)
+               pthread_mutex_destroy(&openssl_mutexes[n]);
+
+       OPENSSL_free(openssl_mutexes);
+#endif
+}
diff --git a/lib/tls/openssl/x509.c b/lib/tls/openssl/x509.c
new file mode 100644 (file)
index 0000000..d441001
--- /dev/null
@@ -0,0 +1,664 @@
+/*
+ * libwebsockets - OpenSSL-specific lws apis
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/openssl/private.h"
+
+#if !defined(LWS_PLAT_OPTEE)
+static int
+dec(char c)
+{
+       return c - '0';
+}
+#endif
+
+static time_t
+lws_tls_openssl_asn1time_to_unix(ASN1_TIME *as)
+{
+#if !defined(LWS_PLAT_OPTEE)
+
+       const char *p = (const char *)as->data;
+       struct tm t;
+
+       /* [YY]YYMMDDHHMMSSZ */
+
+       memset(&t, 0, sizeof(t));
+
+       if (strlen(p) == 13) {
+               t.tm_year = (dec(p[0]) * 10) + dec(p[1]) + 100;
+               p += 2;
+       } else {
+               t.tm_year = (dec(p[0]) * 1000) + (dec(p[1]) * 100) +
+                           (dec(p[2]) * 10) + dec(p[3]);
+               p += 4;
+       }
+       t.tm_mon = (dec(p[0]) * 10) + dec(p[1]) - 1;
+       p += 2;
+       t.tm_mday = (dec(p[0]) * 10) + dec(p[1]) - 1;
+       p += 2;
+       t.tm_hour = (dec(p[0]) * 10) + dec(p[1]);
+       p += 2;
+       t.tm_min = (dec(p[0]) * 10) + dec(p[1]);
+       p += 2;
+       t.tm_sec = (dec(p[0]) * 10) + dec(p[1]);
+       t.tm_isdst = 0;
+
+       return mktime(&t);
+#else
+       return (time_t)-1;
+#endif
+}
+
+int
+lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type,
+                         union lws_tls_cert_info_results *buf, size_t len)
+{
+       X509_NAME *xn;
+#if !defined(LWS_PLAT_OPTEE)
+       char *p;
+#endif
+
+       if (!x509)
+               return -1;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(X509_get_notBefore)
+#define X509_get_notBefore(x)  X509_getm_notBefore(x)
+#define X509_get_notAfter(x)   X509_getm_notAfter(x)
+#endif
+
+       switch (type) {
+       case LWS_TLS_CERT_INFO_VALIDITY_FROM:
+               buf->time = lws_tls_openssl_asn1time_to_unix(
+                                       X509_get_notBefore(x509));
+               if (buf->time == (time_t)-1)
+                       return -1;
+               break;
+
+       case LWS_TLS_CERT_INFO_VALIDITY_TO:
+               buf->time = lws_tls_openssl_asn1time_to_unix(
+                                       X509_get_notAfter(x509));
+               if (buf->time == (time_t)-1)
+                       return -1;
+               break;
+
+       case LWS_TLS_CERT_INFO_COMMON_NAME:
+#if defined(LWS_PLAT_OPTEE)
+               return -1;
+#else
+               xn = X509_get_subject_name(x509);
+               if (!xn)
+                       return -1;
+               X509_NAME_oneline(xn, buf->ns.name, (int)len - 2);
+               p = strstr(buf->ns.name, "/CN=");
+               if (p)
+                       memmove(buf->ns.name, p + 4, strlen(p + 4) + 1);
+               buf->ns.len = (int)strlen(buf->ns.name);
+               return 0;
+#endif
+       case LWS_TLS_CERT_INFO_ISSUER_NAME:
+               xn = X509_get_issuer_name(x509);
+               if (!xn)
+                       return -1;
+               X509_NAME_oneline(xn, buf->ns.name, (int)len - 1);
+               buf->ns.len = (int)strlen(buf->ns.name);
+               return 0;
+
+       case LWS_TLS_CERT_INFO_USAGE:
+#if defined(LWS_HAVE_X509_get_key_usage)
+               buf->usage = X509_get_key_usage(x509);
+               break;
+#else
+               return -1;
+#endif
+
+       case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY:
+       {
+#ifndef USE_WOLFSSL
+               size_t klen = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL);
+               uint8_t *tmp, *ptmp;
+
+               if (!klen || klen > len)
+                       return -1;
+
+               tmp = (uint8_t *)OPENSSL_malloc(klen);
+               if (!tmp)
+                       return -1;
+
+               ptmp = tmp;
+               if (i2d_X509_PUBKEY(
+                             X509_get_X509_PUBKEY(x509), &ptmp) != (int)klen ||
+                   !ptmp || lws_ptr_diff(ptmp, tmp) != (int)klen) {
+                       lwsl_info("%s: cert public key extraction failed\n",
+                                 __func__);
+                       if (ptmp)
+                               OPENSSL_free(tmp);
+
+                       return -1;
+               }
+
+               buf->ns.len = (int)klen;
+               memcpy(buf->ns.name, tmp, klen);
+               OPENSSL_free(tmp);
+#endif
+               return 0;
+       }
+       default:
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type,
+             union lws_tls_cert_info_results *buf, size_t len)
+{
+       return lws_tls_openssl_cert_info(x509->cert, type, buf, len);
+}
+
+#if defined(LWS_WITH_NETWORK)
+int
+lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
+                       union lws_tls_cert_info_results *buf, size_t len)
+{
+#if defined(LWS_HAVE_SSL_CTX_get0_certificate)
+       X509 *x509 = SSL_CTX_get0_certificate(vhost->tls.ssl_ctx);
+
+       return lws_tls_openssl_cert_info(x509, type, buf, len);
+#else
+       lwsl_notice("openssl is too old to support %s\n", __func__);
+
+       return -1;
+#endif
+}
+
+
+
+int
+lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
+                      union lws_tls_cert_info_results *buf, size_t len)
+{
+       int rc = 0;
+       X509 *x509;
+
+       wsi = lws_get_network_wsi(wsi);
+
+       x509 = SSL_get_peer_certificate(wsi->tls.ssl);
+
+       if (!x509) {
+               lwsl_debug("no peer cert\n");
+
+               return -1;
+       }
+
+       switch (type) {
+       case LWS_TLS_CERT_INFO_VERIFIED:
+               buf->verified = SSL_get_verify_result(wsi->tls.ssl) ==
+                                       X509_V_OK;
+               break;
+       default:
+               rc = lws_tls_openssl_cert_info(x509, type, buf, len);
+       }
+
+       X509_free(x509);
+
+       return rc;
+}
+#endif
+
+int
+lws_x509_create(struct lws_x509_cert **x509)
+{
+       *x509 = lws_malloc(sizeof(**x509), __func__);
+       if (*x509)
+               (*x509)->cert = NULL;
+
+       return !(*x509);
+}
+
+int
+lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len)
+{
+       BIO* bio = BIO_new(BIO_s_mem());
+
+       BIO_write(bio, pem, len);
+       x509->cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+       BIO_free(bio);
+       if (!x509->cert) {
+               lwsl_err("%s: unable to parse PEM cert\n", __func__);
+               lws_tls_err_describe_clear();
+
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted,
+               const char *common_name)
+{
+       char c[32], *p;
+       int ret;
+
+       if (common_name) {
+               X509_NAME *xn = X509_get_subject_name(x509->cert);
+               if (!xn)
+                       return -1;
+               X509_NAME_oneline(xn, c, (int)sizeof(c) - 2);
+               p = strstr(c, "/CN=");
+               if (p)
+                       p = p + 4;
+               else
+                       p = c;
+
+               if (strcmp(p, common_name)) {
+                       lwsl_err("%s: common name mismatch\n", __func__);
+                       return -1;
+               }
+       }
+
+       ret = X509_check_issued(trusted->cert, x509->cert);
+       if (ret != X509_V_OK) {
+               lwsl_err("%s: unable to verify cert relationship\n", __func__);
+               lws_tls_err_describe_clear();
+
+               return -1;
+       }
+
+       return 0;
+}
+
+#if defined(LWS_WITH_JOSE)
+int
+lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509,
+                      const char *curves, int rsa_min_bits)
+{
+       int id, n, ret = -1, count;
+       ASN1_OBJECT *obj = NULL;
+       const EC_POINT *ecpoint;
+       const EC_GROUP *ecgroup;
+       EC_KEY *ecpub = NULL;
+       X509_PUBKEY *pubkey;
+       RSA *rsapub = NULL;
+       BIGNUM *mpi[4];
+       EVP_PKEY *pkey;
+
+       memset(jwk, 0, sizeof(*jwk));
+
+       pubkey = X509_get_X509_PUBKEY(x509->cert);
+       if (!pubkey) {
+               lwsl_err("%s: missing pubkey alg in cert\n", __func__);
+
+               goto bail;
+       }
+
+       if (X509_PUBKEY_get0_param(&obj, NULL, NULL, NULL, pubkey) != 1) {
+               lwsl_err("%s: missing pubkey alg in cert\n", __func__);
+
+               goto bail;
+       }
+
+       id = OBJ_obj2nid(obj);
+       if (id == NID_undef) {
+               lwsl_err("%s: missing pubkey alg in cert\n", __func__);
+
+               goto bail;
+       }
+
+       lwsl_debug("%s: key type %d \"%s\"\n", __func__, id, OBJ_nid2ln(id));
+
+       pkey = X509_get_pubkey(x509->cert);
+       if (!pkey) {
+               lwsl_notice("%s: unable to extract pubkey", __func__);
+
+               goto bail;
+       }
+
+       switch (id) {
+       case NID_X9_62_id_ecPublicKey:
+               lwsl_debug("%s: EC key\n", __func__);
+               jwk->kty = LWS_GENCRYPTO_KTY_EC;
+
+               if (!curves) {
+                       lwsl_err("%s: ec curves not allowed\n", __func__);
+
+                       goto bail1;
+               }
+
+               ecpub = EVP_PKEY_get1_EC_KEY(pkey);
+               if (!ecpub) {
+                       lwsl_notice("%s: missing EC pubkey\n", __func__);
+
+                       goto bail1;
+               }
+
+               ecpoint = EC_KEY_get0_public_key(ecpub);
+               if (!ecpoint) {
+                       lwsl_err("%s: EC_KEY_get0_public_key failed\n", __func__);
+                       goto bail2;
+               }
+
+               ecgroup = EC_KEY_get0_group(ecpub);
+               if (!ecgroup) {
+                       lwsl_err("%s: EC_KEY_get0_group failed\n", __func__);
+                       goto bail2;
+               }
+
+               /* validate the curve against ones we allow */
+
+               if (lws_genec_confirm_curve_allowed_by_tls_id(curves,
+                               EC_GROUP_get_curve_name(ecgroup), jwk))
+                       /* already logged */
+                       goto bail2;
+
+               mpi[LWS_GENCRYPTO_EC_KEYEL_CRV] = NULL;
+               mpi[LWS_GENCRYPTO_EC_KEYEL_X] = BN_new(); /* X */
+               mpi[LWS_GENCRYPTO_EC_KEYEL_D] = NULL;
+               mpi[LWS_GENCRYPTO_EC_KEYEL_Y] = BN_new(); /* Y */
+
+#if defined(LWS_HAVE_EC_POINT_get_affine_coordinates)
+               if (EC_POINT_get_affine_coordinates(ecgroup, ecpoint,
+#else
+               if (EC_POINT_get_affine_coordinates_GFp(ecgroup, ecpoint,
+#endif
+                                                 mpi[LWS_GENCRYPTO_EC_KEYEL_X],
+                                                 mpi[LWS_GENCRYPTO_EC_KEYEL_Y],
+                                                         NULL) != 1) {
+                       BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]);
+                       BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]);
+                       lwsl_err("%s: EC_POINT_get_aff failed\n", __func__);
+                       goto bail2;
+               }
+               count = LWS_GENCRYPTO_EC_KEYEL_COUNT;
+               n = LWS_GENCRYPTO_EC_KEYEL_X;
+               break;
+
+       case NID_rsaEncryption:
+               lwsl_debug("%s: rsa key\n", __func__);
+               jwk->kty = LWS_GENCRYPTO_KTY_RSA;
+
+               rsapub = EVP_PKEY_get1_RSA(pkey);
+               if (!rsapub) {
+                       lwsl_notice("%s: missing RSA pubkey\n", __func__);
+
+                       goto bail1;
+               }
+
+               if ((size_t)RSA_size(rsapub) * 8 < (size_t)rsa_min_bits) {
+                       lwsl_err("%s: key bits %d less than minimum %d\n",
+                                __func__, RSA_size(rsapub) * 8, rsa_min_bits);
+
+                       goto bail2;
+               }
+
+#if defined(LWS_HAVE_RSA_SET0_KEY)
+               /* we don't need d... but the api wants to write it */
+               RSA_get0_key(rsapub,
+                           (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_N],
+                           (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_E],
+                           (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_D]);
+#else
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_E] = rsapub->e;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_N] = rsapub->n;
+               mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = NULL;
+#endif
+               count = LWS_GENCRYPTO_RSA_KEYEL_D;
+               n = LWS_GENCRYPTO_RSA_KEYEL_E;
+               break;
+       default:
+               lwsl_err("%s: unknown NID\n", __func__);
+               goto bail2;
+       }
+
+       for (; n < count; n++) {
+               if (!mpi[n])
+                       continue;
+               jwk->e[n].len = BN_num_bytes(mpi[n]);
+               jwk->e[n].buf = lws_malloc(jwk->e[n].len, "certkeyimp");
+               if (!jwk->e[n].buf) {
+                       if (id == NID_X9_62_id_ecPublicKey) {
+                               BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]);
+                               BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]);
+                       }
+                       goto bail2;
+               }
+               BN_bn2bin(mpi[n], jwk->e[n].buf);
+       }
+
+       if (id == NID_X9_62_id_ecPublicKey) {
+               BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]);
+               BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]);
+       }
+
+       ret = 0;
+
+bail2:
+       if (id == NID_X9_62_id_ecPublicKey)
+               EC_KEY_free(ecpub);
+       else
+               RSA_free(rsapub);
+
+bail1:
+       EVP_PKEY_free(pkey);
+bail:
+       /* jwk destroy will clean any partial state */
+       if (ret)
+               lws_jwk_destroy(jwk);
+
+       return ret;
+}
+
+static int
+lws_x509_jwk_privkey_pem_pp_cb(char *buf, int size, int rwflag, void *u)
+{
+       const char *pp = (const char *)u;
+       int n = strlen(pp);
+
+       if (n > size - 1)
+               return -1;
+
+       memcpy(buf, pp, n + 1);
+
+       return n;
+}
+
+int
+lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len,
+                        const char *passphrase)
+{
+       BIO* bio = BIO_new(BIO_s_mem());
+       BIGNUM *mpi, *dummy[6];
+       EVP_PKEY *pkey = NULL;
+       EC_KEY *ecpriv = NULL;
+       RSA *rsapriv = NULL;
+       const BIGNUM *cmpi;
+       int n, m, ret = -1;
+
+       BIO_write(bio, pem, len);
+       PEM_read_bio_PrivateKey(bio, &pkey, lws_x509_jwk_privkey_pem_pp_cb,
+                               (void *)passphrase);
+       BIO_free(bio);
+       lws_explicit_bzero((void *)pem, len);
+       if (!pkey) {
+               lwsl_err("%s: unable to parse PEM privkey\n", __func__);
+               lws_tls_err_describe_clear();
+
+               return -1;
+       }
+
+       /* confirm the key type matches the existing jwk situation */
+
+       switch (jwk->kty) {
+       case LWS_GENCRYPTO_KTY_EC:
+               if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) {
+                       lwsl_err("%s: jwk is EC but privkey isn't\n", __func__);
+
+                       goto bail;
+               }
+               ecpriv = EVP_PKEY_get1_EC_KEY(pkey);
+               if (!ecpriv) {
+                       lwsl_notice("%s: missing EC key\n", __func__);
+
+                       goto bail;
+               }
+
+               cmpi = EC_KEY_get0_private_key(ecpriv);
+
+               /* quick size check first */
+
+               n = BN_num_bytes(cmpi);
+               if (jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].len != (uint32_t)n) {
+                       lwsl_err("%s: jwk key size doesn't match\n", __func__);
+
+                       goto bail1;
+               }
+
+               /* TODO.. check public curve / group + point */
+
+               jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len = n;
+               jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf = lws_malloc(n, "ec");
+               if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf)
+                       goto bail1;
+
+               m = BN_bn2binpad(cmpi, jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf,
+                                     jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len);
+               if ((unsigned int)m != (unsigned int)BN_num_bytes(cmpi))
+                       goto bail1;
+
+               break;
+
+       case LWS_GENCRYPTO_KTY_RSA:
+               if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_RSA) {
+                       lwsl_err("%s: RSA jwk, non-RSA privkey\n", __func__);
+
+                       goto bail;
+               }
+               rsapriv = EVP_PKEY_get1_RSA(pkey);
+               if (!rsapriv) {
+                       lwsl_notice("%s: missing RSA key\n", __func__);
+
+                       goto bail;
+               }
+
+#if defined(LWS_HAVE_RSA_SET0_KEY)
+               RSA_get0_key(rsapriv, (const BIGNUM **)&dummy[0], /* n */
+                                     (const BIGNUM **)&dummy[1], /* e */
+                                     (const BIGNUM **)&mpi);     /* d */
+               RSA_get0_factors(rsapriv, (const BIGNUM **)&dummy[4],  /* p */
+                                         (const BIGNUM **)&dummy[5]); /* q */
+#else
+               dummy[0] = rsapriv->n;
+               dummy[1] = rsapriv->e;
+               dummy[4] = rsapriv->p;
+               dummy[5] = rsapriv->q;
+               mpi = rsapriv->d;
+#endif
+
+               /* quick size check first */
+
+               n = BN_num_bytes(mpi);
+               if (jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len != (uint32_t)n) {
+                       lwsl_err("%s: jwk key size doesn't match\n", __func__);
+
+                       goto bail1;
+               }
+
+               /* then check that n & e match what we got from the cert */
+
+               dummy[2] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf,
+                                    jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len,
+                                    NULL);
+               dummy[3] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf,
+                                    jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len,
+                                    NULL);
+
+               m = BN_cmp(dummy[2], dummy[0]) | BN_cmp(dummy[3], dummy[1]);
+               BN_clear_free(dummy[2]);
+               BN_clear_free(dummy[3]);
+               if (m) {
+                       lwsl_err("%s: privkey doesn't match jwk pubkey\n",
+                                __func__);
+
+                       goto bail1;
+               }
+
+               /* accept d from the PEM privkey into the JWK */
+
+               jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].len = n;
+               jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf = lws_malloc(n, "privjk");
+               if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf)
+                       goto bail1;
+
+               BN_bn2bin(mpi, jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
+
+               /* accept p and q from the PEM privkey into the JWK */
+
+               jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].len = BN_num_bytes(dummy[4]);
+               jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf = lws_malloc(n, "privjk");
+               if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf) {
+                       lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
+                       goto bail1;
+               }
+               BN_bn2bin(dummy[4], jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf);
+
+               jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].len = BN_num_bytes(dummy[5]);
+               jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf = lws_malloc(n, "privjk");
+               if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf) {
+                       lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
+                       lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf);
+                       goto bail1;
+               }
+               BN_bn2bin(dummy[5], jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf);
+               break;
+       default:
+               lwsl_err("%s: JWK has unknown kty %d\n", __func__, jwk->kty);
+               return -1;
+       }
+
+       ret = 0;
+
+bail1:
+       if (jwk->kty == LWS_GENCRYPTO_KTY_EC)
+               EC_KEY_free(ecpriv);
+       else
+               RSA_free(rsapriv);
+
+bail:
+       EVP_PKEY_free(pkey);
+
+       return ret;
+}
+#endif
+
+void
+lws_x509_destroy(struct lws_x509_cert **x509)
+{
+       if (!*x509)
+               return;
+
+       if ((*x509)->cert) {
+               X509_free((*x509)->cert);
+               (*x509)->cert = NULL;
+       }
+
+       lws_free_set_NULL(*x509);
+}
diff --git a/lib/tls/private-network.h b/lib/tls/private-network.h
new file mode 100644 (file)
index 0000000..806b62d
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_WITH_TLS
+ */
+
+struct lws_context_per_thread;
+struct lws_tls_ops {
+       int (*fake_POLLIN_for_buffered)(struct lws_context_per_thread *pt);
+};
+
+struct lws_context_tls {
+       char alpn_discovered[32];
+       const char *alpn_default;
+       time_t last_cert_check_s;
+       struct lws_dll2_owner cc_owner;
+       int count_client_contexts;
+};
+
+struct lws_pt_tls {
+       struct lws_dll2_owner dll_pending_tls_owner;
+};
+
+struct lws_tls_ss_pieces;
+
+struct alpn_ctx {
+       uint8_t data[23];
+       uint8_t len;
+};
+
+struct lws_vhost_tls {
+       lws_tls_ctx *ssl_ctx;
+       lws_tls_ctx *ssl_client_ctx;
+       const char *alpn;
+       struct lws_tls_ss_pieces *ss; /* for acme tls certs */
+       char *alloc_cert_path;
+       char *key_path;
+#if defined(LWS_WITH_MBEDTLS)
+       lws_tls_x509 *x509_client_CA;
+#endif
+       char ecdh_curve[16];
+       struct alpn_ctx alpn_ctx;
+
+       int use_ssl;
+       int allow_non_ssl_on_ssl_port;
+       int ssl_info_event_mask;
+
+       unsigned int user_supplied_ssl_ctx:1;
+       unsigned int skipped_certs:1;
+};
+
+struct lws_lws_tls {
+       lws_tls_conn *ssl;
+       lws_tls_bio *client_bio;
+       struct lws_dll2 dll_pending_tls;
+       unsigned int use_ssl;
+       unsigned int redirect_to_https:1;
+};
+
+
+LWS_EXTERN void
+lws_context_init_alpn(struct lws_vhost *vhost);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_ssl_pending(struct lws *wsi);
+LWS_EXTERN int LWS_WARN_UNUSED_RESULT
+lws_server_socket_service_ssl(struct lws *new_wsi, lws_sockfd_type accept_fd);
+LWS_EXTERN int
+lws_ssl_close(struct lws *wsi);
+LWS_EXTERN void
+lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost);
+LWS_EXTERN void
+lws_ssl_context_destroy(struct lws_context *context);
+void
+__lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi);
+LWS_VISIBLE void
+lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi);
+LWS_EXTERN int
+lws_ssl_client_bio_create(struct lws *wsi);
+LWS_EXTERN int
+lws_ssl_client_connect1(struct lws *wsi);
+LWS_EXTERN int
+lws_ssl_client_connect2(struct lws *wsi, char *errbuf, int len);
+LWS_EXTERN int
+lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt);
+LWS_EXTERN int
+lws_gate_accepts(struct lws_context *context, int on);
+LWS_EXTERN void
+lws_ssl_bind_passphrase(lws_tls_ctx *ssl_ctx, int is_client,
+                       const struct lws_context_creation_info *info);
+LWS_EXTERN void
+lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret);
+LWS_EXTERN int
+lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi,
+                         const char *cert, const char *private_key,
+                         const char *mem_cert, size_t len_mem_cert,
+                         const char *mem_privkey, size_t mem_privkey_len);
+LWS_EXTERN enum lws_tls_extant
+lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert,
+                           const char *private_key);
+#if !defined(LWS_NO_SERVER)
+ LWS_EXTERN int
+ lws_context_init_server_ssl(const struct lws_context_creation_info *info,
+                            struct lws_vhost *vhost);
+ void
+ lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost);
+#else
+ #define lws_context_init_server_ssl(_a, _b) (0)
+ #define lws_tls_acme_sni_cert_destroy(_a)
+#endif
+
+LWS_EXTERN void
+lws_ssl_destroy(struct lws_vhost *vhost);
+
+/*
+* lws_tls_ abstract backend implementations
+*/
+
+LWS_EXTERN int
+lws_tls_server_client_cert_verify_config(struct lws_vhost *vh);
+LWS_EXTERN int
+lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info,
+                         struct lws_vhost *vhost, struct lws *wsi);
+LWS_EXTERN int
+lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd);
+
+LWS_EXTERN enum lws_ssl_capable_status
+lws_tls_server_accept(struct lws *wsi);
+
+LWS_EXTERN enum lws_ssl_capable_status
+lws_tls_server_abort_connection(struct lws *wsi);
+
+LWS_EXTERN enum lws_ssl_capable_status
+__lws_tls_shutdown(struct lws *wsi);
+
+LWS_EXTERN enum lws_ssl_capable_status
+lws_tls_client_connect(struct lws *wsi);
+LWS_EXTERN int
+lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, int ebuf_len);
+LWS_EXTERN int
+lws_tls_client_create_vhost_context(struct lws_vhost *vh,
+                           const struct lws_context_creation_info *info,
+                           const char *cipher_list,
+                           const char *ca_filepath,
+                           const void *ca_mem,
+                           unsigned int ca_mem_len,
+                           const char *cert_filepath,
+                           const void *cert_mem,
+                           unsigned int cert_mem_len,
+                           const char *private_key_filepath);
+
+LWS_EXTERN lws_tls_ctx *
+lws_tls_ctx_from_wsi(struct lws *wsi);
+LWS_EXTERN int
+lws_ssl_get_error(struct lws *wsi, int n);
+
+LWS_EXTERN int
+lws_context_init_client_ssl(const struct lws_context_creation_info *info,
+                   struct lws_vhost *vhost);
+
+LWS_EXTERN void
+lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret);
+
+int
+lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt);
+
+
+
+
+
diff --git a/lib/tls/private.h b/lib/tls/private.h
new file mode 100644 (file)
index 0000000..16b7d66
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *  This is included from core/private.h if LWS_WITH_TLS
+ */
+
+#if !defined(__LWS_TLS_PRIVATE_H__)
+#define __LWS_TLS_PRIVATE_H__
+
+
+#if defined(LWS_WITH_TLS)
+
+#if defined(USE_WOLFSSL)
+ #if defined(USE_OLD_CYASSL)
+  #if defined(_WIN32)
+   #include <IDE/WIN/user_settings.h>
+   #include <cyassl/ctaocrypt/settings.h>
+  #else
+   #include <cyassl/options.h>
+  #endif
+  #include <cyassl/openssl/ssl.h>
+  #include <cyassl/error-ssl.h>
+ #else
+  #if defined(_WIN32)
+   #include <IDE/WIN/user_settings.h>
+   #include <wolfssl/wolfcrypt/settings.h>
+  #else
+   #include <wolfssl/options.h>
+  #endif
+  #include <wolfssl/openssl/ssl.h>
+  #include <wolfssl/error-ssl.h>
+  #define OPENSSL_NO_TLSEXT
+ #endif /* not USE_OLD_CYASSL */
+#else /* WOLFSSL */
+ #if defined(LWS_WITH_ESP32)
+  #define OPENSSL_NO_TLSEXT
+  #if !defined(LWS_AMAZON_RTOS)
+   /* AMAZON RTOS has its own setting via MTK_MBEDTLS_CONFIG_FILE */
+   #undef MBEDTLS_CONFIG_FILE
+   #define MBEDTLS_CONFIG_FILE <mbedtls/esp_config.h>
+  #endif
+  #include <mbedtls/ssl.h>
+  #include <mbedtls/aes.h>
+  #include <mbedtls/gcm.h>
+  #include <mbedtls/x509_crt.h>
+  #include "tls/mbedtls/wrapper/include/openssl/ssl.h" /* wrapper !!!! */
+ #else /* not esp32 */
+  #if defined(LWS_WITH_MBEDTLS)
+   #include <mbedtls/ssl.h>
+   #include <mbedtls/aes.h>
+   #include <mbedtls/gcm.h>
+   #include <mbedtls/x509_crt.h>
+   #include <mbedtls/x509_csr.h>
+   #include <mbedtls/ecp.h>
+   #include <mbedtls/ecdsa.h>
+   #include "tls/mbedtls/wrapper/include/openssl/ssl.h" /* wrapper !!!! */
+  #else
+   #include <openssl/ssl.h>
+   #include <openssl/evp.h>
+   #include <openssl/err.h>
+   #include <openssl/md5.h>
+   #include <openssl/sha.h>
+   #include <openssl/rsa.h>
+   #include <openssl/bn.h>
+   #include <openssl/aes.h>
+   #ifdef LWS_HAVE_OPENSSL_ECDH_H
+    #include <openssl/ecdh.h>
+   #endif
+   #if !defined(LWS_HAVE_EVP_MD_CTX_free)
+    #define EVP_MD_CTX_free EVP_MD_CTX_destroy
+   #endif
+   #include <openssl/x509v3.h>
+  #endif /* not mbedtls */
+  #if defined(OPENSSL_VERSION_NUMBER)
+   #if (OPENSSL_VERSION_NUMBER < 0x0009080afL)
+/*
+ * later openssl defines this to negate the presence of tlsext... but it was
+ * only introduced at 0.9.8j.  Earlier versions don't know it exists so don't
+ * define it... making it look like the feature exists...
+ */
+    #define OPENSSL_NO_TLSEXT
+   #endif
+  #endif
+ #endif /* not ESP32 */
+#endif /* not USE_WOLFSSL */
+
+#endif /* LWS_WITH_TLS */
+
+enum lws_tls_extant {
+       LWS_TLS_EXTANT_NO,
+       LWS_TLS_EXTANT_YES,
+       LWS_TLS_EXTANT_ALTERNATIVE
+};
+
+
+#if defined(LWS_WITH_TLS)
+
+typedef SSL lws_tls_conn;
+typedef SSL_CTX lws_tls_ctx;
+typedef BIO lws_tls_bio;
+typedef X509 lws_tls_x509;
+
+#if defined(LWS_WITH_NETWORK)
+#include "tls/private-network.h"
+#endif
+
+LWS_EXTERN int
+lws_context_init_ssl_library(const struct lws_context_creation_info *info);
+LWS_EXTERN void
+lws_context_deinit_ssl_library(struct lws_context *context);
+#define LWS_SSL_ENABLED(vh) (vh && vh->tls.use_ssl)
+
+extern const struct lws_tls_ops tls_ops_openssl, tls_ops_mbedtls;
+
+struct lws_ec_valid_curves {
+       int id;
+       const char *jwa_name; /* list terminates with NULL jwa_name */
+};
+
+LWS_EXTERN enum lws_tls_extant
+lws_tls_use_any_upgrade_check_extant(const char *name);
+LWS_EXTERN int openssl_websocket_private_data_index;
+
+
+LWS_EXTERN void
+lws_tls_err_describe_clear(void);
+
+LWS_EXTERN int
+lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type,
+                         union lws_tls_cert_info_results *buf, size_t len);
+LWS_EXTERN int
+lws_tls_check_all_cert_lifetimes(struct lws_context *context);
+
+LWS_EXTERN int
+lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename,
+                             const char *inbuf, lws_filepos_t inlen,
+                             uint8_t **buf, lws_filepos_t *amount);
+LWS_EXTERN char *
+lws_ssl_get_error_string(int status, int ret, char *buf, size_t len);
+
+int
+lws_gencrypto_bits_to_bytes(int bits);
+
+void
+lws_gencrypto_destroy_elements(struct lws_gencrypto_keyelem *el, int m);
+
+/* genec */
+
+struct lws_gencrypto_keyelem;
+struct lws_ec_curves;
+
+LWS_EXTERN const struct lws_ec_curves lws_ec_curves[4];
+const struct lws_ec_curves *
+lws_genec_curve(const struct lws_ec_curves *table, const char *name);
+LWS_VISIBLE void
+lws_genec_destroy_elements(struct lws_gencrypto_keyelem *el);
+int
+lws_gencrypto_mbedtls_rngf(void *context, unsigned char *buf, size_t len);
+
+int
+lws_genec_confirm_curve_allowed_by_tls_id(const char *allowed, int id,
+                                         struct lws_jwk *jwk);
+
+#endif
+#endif
diff --git a/lib/tls/tls-client.c b/lib/tls/tls-client.c
new file mode 100644 (file)
index 0000000..45911bd
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * libwebsockets - client-related ssl code independent of backend
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+int
+lws_ssl_client_connect1(struct lws *wsi)
+{
+       struct lws_context *context = wsi->context;
+       int n = 0;
+
+       lws_latency_pre(context, wsi);
+       n = lws_tls_client_connect(wsi);
+       lws_latency(context, wsi, "SSL_connect hs", n, n > 0);
+
+       switch (n) {
+       case LWS_SSL_CAPABLE_ERROR:
+               return -1;
+       case LWS_SSL_CAPABLE_DONE:
+               return 1; /* connected */
+       case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE:
+               lws_callback_on_writable(wsi);
+               /* fallthru */
+       case LWS_SSL_CAPABLE_MORE_SERVICE_READ:
+               lwsi_set_state(wsi, LRS_WAITING_SSL);
+               break;
+       case LWS_SSL_CAPABLE_MORE_SERVICE:
+               break;
+       }
+
+       return 0; /* retry */
+}
+
+int
+lws_ssl_client_connect2(struct lws *wsi, char *errbuf, int len)
+{
+       int n = 0;
+
+       if (lwsi_state(wsi) == LRS_WAITING_SSL) {
+               lws_latency_pre(wsi->context, wsi);
+
+               n = lws_tls_client_connect(wsi);
+               lwsl_debug("%s: SSL_connect says %d\n", __func__, n);
+               lws_latency(wsi->context, wsi,
+                           "SSL_connect LRS_WAITING_SSL", n, n > 0);
+
+               switch (n) {
+               case LWS_SSL_CAPABLE_ERROR:
+                       lws_snprintf(errbuf, len, "client connect failed");
+                       return -1;
+               case LWS_SSL_CAPABLE_DONE:
+                       break; /* connected */
+               case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE:
+                       lws_callback_on_writable(wsi);
+                       /* fallthru */
+               case LWS_SSL_CAPABLE_MORE_SERVICE_READ:
+                       lwsi_set_state(wsi, LRS_WAITING_SSL);
+                       /* fallthru */
+               case LWS_SSL_CAPABLE_MORE_SERVICE:
+                       return 0;
+               }
+       }
+
+       if (lws_tls_client_confirm_peer_cert(wsi, errbuf, len))
+               return -1;
+
+       return 1;
+}
+
+
+int lws_context_init_client_ssl(const struct lws_context_creation_info *info,
+                               struct lws_vhost *vhost)
+{
+       const char *private_key_filepath = info->ssl_private_key_filepath;
+       const char *cert_filepath = info->ssl_cert_filepath;
+       const char *ca_filepath = info->ssl_ca_filepath;
+       const char *cipher_list = info->ssl_cipher_list;
+       struct lws wsi;
+
+       if (vhost->options & LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG)
+               return 0;
+
+       if (vhost->tls.ssl_ctx) {
+               cert_filepath = NULL;
+               private_key_filepath = NULL;
+               ca_filepath = NULL;
+       }
+
+       /*
+        *  for backwards-compatibility default to using ssl_... members, but
+        * if the newer client-specific ones are given, use those
+        */
+       if (info->client_ssl_cipher_list)
+               cipher_list = info->client_ssl_cipher_list;
+       if (info->client_ssl_cert_filepath)
+               cert_filepath = info->client_ssl_cert_filepath;
+       if (info->client_ssl_private_key_filepath)
+               private_key_filepath = info->client_ssl_private_key_filepath;
+
+       if (info->client_ssl_ca_filepath)
+               ca_filepath = info->client_ssl_ca_filepath;
+
+       if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT))
+               return 0;
+
+       if (vhost->tls.ssl_client_ctx)
+               return 0;
+
+       if (info->provided_client_ssl_ctx) {
+               /* use the provided OpenSSL context if given one */
+               vhost->tls.ssl_client_ctx = info->provided_client_ssl_ctx;
+               /* nothing for lib to delete */
+               vhost->tls.user_supplied_ssl_ctx = 1;
+
+               return 0;
+       }
+
+       if (lws_tls_client_create_vhost_context(vhost, info, cipher_list,
+                                               ca_filepath,
+                                               info->client_ssl_ca_mem,
+                                               info->client_ssl_ca_mem_len,
+                                               cert_filepath,
+                                               info->client_ssl_cert_mem,
+                                               info->client_ssl_cert_mem_len,
+                                               private_key_filepath))
+               return 1;
+
+       lwsl_info("created client ssl context for %s\n", vhost->name);
+
+       /*
+        * give him a fake wsi with context set, so he can use
+        * lws_get_context() in the callback
+        */
+       memset(&wsi, 0, sizeof(wsi));
+       wsi.vhost = vhost; /* not a real bound wsi */
+       wsi.context = vhost->context;
+
+       vhost->protocols[0].callback(&wsi,
+                       LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS,
+                                    vhost->tls.ssl_client_ctx, NULL, 0);
+
+       return 0;
+}
diff --git a/lib/tls/tls-network.c b/lib/tls/tls-network.c
new file mode 100644 (file)
index 0000000..99b5ac8
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+/*
+ * fakes POLLIN on all tls guys with buffered rx
+ *
+ * returns nonzero if any tls guys had POLLIN faked
+ */
+
+int
+lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt)
+{
+       int ret = 0;
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
+                       lws_dll2_get_head(&pt->tls.dll_pending_tls_owner)) {
+               struct lws *wsi = lws_container_of(p, struct lws,
+                                                  tls.dll_pending_tls);
+
+               pt->fds[wsi->position_in_fds_table].revents |=
+                       pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
+               ret |= pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN;
+
+       } lws_end_foreach_dll_safe(p, p1);
+
+       return !!ret;
+}
+
+void
+__lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
+{
+       lws_dll2_remove(&wsi->tls.dll_pending_tls);
+}
+
+void
+lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
+{
+       struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+
+       lws_pt_lock(pt, __func__);
+       __lws_ssl_remove_wsi_from_buffered_list(wsi);
+       lws_pt_unlock(pt);
+}
+
+
+int
+lws_tls_check_cert_lifetime(struct lws_vhost *v)
+{
+       time_t now = (time_t)lws_now_secs(), life = 0;
+       struct lws_acme_cert_aging_args caa;
+       union lws_tls_cert_info_results ir;
+       int n;
+
+       if (v->tls.ssl_ctx && !v->tls.skipped_certs) {
+
+               if (now < 1542933698) /* Nov 23 2018 00:42 UTC */
+                       /* our clock is wrong and we can't judge the certs */
+                       return -1;
+
+               n = lws_tls_vhost_cert_info(v, LWS_TLS_CERT_INFO_VALIDITY_TO,
+                                           &ir, 0);
+               if (n)
+                       return 1;
+
+               life = (ir.time - now) / (24 * 3600);
+               lwsl_notice("   vhost %s: cert expiry: %dd\n", v->name,
+                           (int)life);
+       } else
+               lwsl_info("   vhost %s: no cert\n", v->name);
+
+       memset(&caa, 0, sizeof(caa));
+       caa.vh = v;
+       lws_broadcast(&v->context->pt[0], LWS_CALLBACK_VHOST_CERT_AGING, (void *)&caa,
+                     (size_t)(ssize_t)life);
+
+       return 0;
+}
+
+int
+lws_tls_check_all_cert_lifetimes(struct lws_context *context)
+{
+       struct lws_vhost *v = context->vhost_list;
+
+       while (v) {
+               if (lws_tls_check_cert_lifetime(v) < 0)
+                       return -1;
+               v = v->vhost_next;
+       }
+
+       return 0;
+}
+
+
+/*
+ * LWS_TLS_EXTANT_NO         : skip adding the cert
+ * LWS_TLS_EXTANT_YES        : use the cert and private key paths normally
+ * LWS_TLS_EXTANT_ALTERNATIVE: normal paths not usable, try alternate if poss
+ */
+enum lws_tls_extant
+lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert,
+                           const char *private_key)
+{
+       int n, m;
+
+       /*
+        * The user code can choose to either pass the cert and
+        * key filepaths using the info members like this, or it can
+        * leave them NULL; force the vhost SSL_CTX init using the info
+        * options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and
+        * set up the cert himself using the user callback
+        * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which
+        * happened just above and has the vhost SSL_CTX * in the user
+        * parameter.
+        */
+
+       if (!cert || !private_key)
+               return LWS_TLS_EXTANT_NO;
+
+       n = lws_tls_use_any_upgrade_check_extant(cert);
+       if (n == LWS_TLS_EXTANT_ALTERNATIVE)
+               return LWS_TLS_EXTANT_ALTERNATIVE;
+       m = lws_tls_use_any_upgrade_check_extant(private_key);
+       if (m == LWS_TLS_EXTANT_ALTERNATIVE)
+               return LWS_TLS_EXTANT_ALTERNATIVE;
+
+       if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) &&
+           (vhost->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) {
+               lwsl_notice("Ignoring missing %s or %s\n", cert, private_key);
+               vhost->tls.skipped_certs = 1;
+
+               return LWS_TLS_EXTANT_NO;
+       }
+
+       /*
+        * the cert + key exist
+        */
+
+       return LWS_TLS_EXTANT_YES;
+}
+
+#if !defined(LWS_NO_SERVER)
+/*
+ * update the cert for every vhost using the given path
+ */
+
+LWS_VISIBLE int
+lws_tls_cert_updated(struct lws_context *context, const char *certpath,
+                    const char *keypath,
+                    const char *mem_cert, size_t len_mem_cert,
+                    const char *mem_privkey, size_t len_mem_privkey)
+{
+       struct lws wsi;
+
+       wsi.context = context;
+
+       lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) {
+               wsi.vhost = v; /* not a real bound wsi */
+               if (v->tls.alloc_cert_path && v->tls.key_path &&
+                   !strcmp(v->tls.alloc_cert_path, certpath) &&
+                   !strcmp(v->tls.key_path, keypath)) {
+                       lws_tls_server_certs_load(v, &wsi, certpath, keypath,
+                                                 mem_cert, len_mem_cert,
+                                                 mem_privkey, len_mem_privkey);
+
+                       if (v->tls.skipped_certs)
+                               lwsl_notice("%s: vhost %s: cert unset\n",
+                                           __func__, v->name);
+               }
+       } lws_end_foreach_ll(v, vhost_next);
+
+       return 0;
+}
+#endif
+
+int
+lws_gate_accepts(struct lws_context *context, int on)
+{
+       struct lws_vhost *v = context->vhost_list;
+
+       lwsl_notice("%s: on = %d\n", __func__, on);
+
+#if defined(LWS_WITH_STATS)
+       context->updated = 1;
+#endif
+
+       while (v) {
+               if (v->tls.use_ssl && v->lserv_wsi &&
+                   lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on,
+                                     (LWS_POLLIN) * on))
+                       lwsl_notice("Unable to set accept POLLIN %d\n", on);
+
+               v = v->vhost_next;
+       }
+
+       return 0;
+}
+
+/* comma-separated alpn list, like "h2,http/1.1" to openssl alpn format */
+
+int
+lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len)
+{
+       uint8_t *oos = os, *plen = NULL;
+
+       while (*comma && len > 1) {
+               if (!plen && *comma == ' ') {
+                       comma++;
+                       continue;
+               }
+               if (!plen) {
+                       plen = os++;
+                       len--;
+               }
+
+               if (*comma == ',') {
+                       *plen = lws_ptr_diff(os, plen + 1);
+                       plen = NULL;
+                       comma++;
+               } else {
+                       *os++ = *comma++;
+                       len--;
+               }
+       }
+
+       if (plen)
+               *plen = lws_ptr_diff(os, plen + 1);
+
+       return lws_ptr_diff(os, oos);
+}
+
+
+
diff --git a/lib/tls/tls-server.c b/lib/tls/tls-server.c
new file mode 100644 (file)
index 0000000..db14a6d
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+
+#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \
+                                 OPENSSL_VERSION_NUMBER >= 0x10002000L)
+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 = (struct 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
+
+void
+lws_context_init_alpn(struct lws_vhost *vhost)
+{
+#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \
+                                 OPENSSL_VERSION_NUMBER >= 0x10002000L)
+       const char *alpn_comma = vhost->context->tls.alpn_default;
+
+       if (vhost->tls.alpn)
+               alpn_comma = vhost->tls.alpn;
+
+       lwsl_info(" Server '%s' advertising ALPN: %s\n",
+                   vhost->name, alpn_comma);
+       vhost->tls.alpn_ctx.len = lws_alpn_comma_to_openssl(alpn_comma,
+                                       vhost->tls.alpn_ctx.data,
+                                       sizeof(vhost->tls.alpn_ctx.data) - 1);
+
+       SSL_CTX_set_alpn_select_cb(vhost->tls.ssl_ctx, alpn_cb,
+                                  &vhost->tls.alpn_ctx);
+#else
+       lwsl_err(
+               " HTTP2 / ALPN configured but not supported by OpenSSL 0x%lx\n",
+                   OPENSSL_VERSION_NUMBER);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+}
+
+int
+lws_tls_server_conn_alpn(struct lws *wsi)
+{
+#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \
+                                 OPENSSL_VERSION_NUMBER >= 0x10002000L)
+       const unsigned char *name = NULL;
+       char cstr[10];
+       unsigned len;
+
+       if (!wsi->tls.ssl)
+               return 0;
+
+       SSL_get0_alpn_selected(wsi->tls.ssl, &name, &len);
+       if (!len) {
+               lwsl_info("no ALPN upgrade\n");
+               return 0;
+       }
+
+       if (len > sizeof(cstr) - 1)
+               len = sizeof(cstr) - 1;
+
+       memcpy(cstr, name, len);
+       cstr[len] = '\0';
+
+       lwsl_info("negotiated '%s' using ALPN\n", cstr);
+       wsi->tls.use_ssl |= LCCSCF_USE_SSL;
+
+       return lws_role_call_alpn_negotiated(wsi, (const char *)cstr);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+
+       return 0;
+}
+
+#if !defined(LWS_NO_SERVER)
+
+static void
+lws_sul_tls_cb(lws_sorted_usec_list_t *sul)
+{
+       struct lws_context_per_thread *pt = lws_container_of(sul,
+                       struct lws_context_per_thread, sul_tls);
+
+       lws_tls_check_all_cert_lifetimes(pt->context);
+
+       __lws_sul_insert(&pt->pt_sul_owner, &pt->sul_tls,
+                        (lws_usec_t)24 * 3600 * LWS_US_PER_SEC);
+}
+
+LWS_VISIBLE int
+lws_context_init_server_ssl(const struct lws_context_creation_info *info,
+                           struct lws_vhost *vhost)
+{
+       struct lws_context *context = vhost->context;
+       struct lws wsi;
+
+       if (!lws_check_opt(info->options,
+                          LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) {
+               vhost->tls.use_ssl = 0;
+
+               return 0;
+       }
+
+       /*
+        * If he is giving a server cert, take it as a sign he wants to use
+        * it on this vhost.  User code can leave the cert filepath NULL and
+        * set the LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX option itself, in
+        * which case he's expected to set up the cert himself at
+        * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which
+        * provides the vhost SSL_CTX * in the user parameter.
+        */
+       if (info->ssl_cert_filepath || info->server_ssl_cert_mem)
+               vhost->options |= LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX;
+
+       if (info->port != CONTEXT_PORT_NO_LISTEN) {
+
+               vhost->tls.use_ssl = lws_check_opt(vhost->options,
+                                       LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX);
+
+               if (vhost->tls.use_ssl && info->ssl_cipher_list)
+                       lwsl_notice(" SSL ciphers: '%s'\n",
+                                               info->ssl_cipher_list);
+
+               if (vhost->tls.use_ssl)
+                       lwsl_notice(" Using SSL mode\n");
+               else
+                       lwsl_notice(" Using non-SSL mode\n");
+       }
+
+       /*
+        * give him a fake wsi with context + vhost set, so he can use
+        * lws_get_context() in the callback
+        */
+       memset(&wsi, 0, sizeof(wsi));
+       wsi.vhost = vhost; /* not a real bound wsi */
+       wsi.context = context;
+
+       /*
+        * as a server, if we are requiring clients to identify themselves
+        * then set the backend up for it
+        */
+       if (lws_check_opt(info->options,
+                         LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT))
+               /* Normally SSL listener rejects non-ssl, optionally allow */
+               vhost->tls.allow_non_ssl_on_ssl_port = 1;
+
+       /*
+        * give user code a chance to load certs into the server
+        * allowing it to verify incoming client certs
+        */
+       if (vhost->tls.use_ssl) {
+               if (lws_tls_server_vhost_backend_init(info, vhost, &wsi))
+                       return -1;
+
+               lws_tls_server_client_cert_verify_config(vhost);
+
+               if (vhost->protocols[0].callback(&wsi,
+                           LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS,
+                           vhost->tls.ssl_ctx, vhost, 0))
+                       return -1;
+       }
+
+       if (vhost->tls.use_ssl)
+               lws_context_init_alpn(vhost);
+
+       /* check certs once a day */
+
+       context->pt[0].sul_tls.cb = lws_sul_tls_cb;
+       __lws_sul_insert(&context->pt[0].pt_sul_owner, &context->pt[0].sul_tls,
+                        (lws_usec_t)24 * 3600 * LWS_US_PER_SEC);
+
+       return 0;
+}
+#endif
+
+LWS_VISIBLE int
+lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd)
+{
+       struct lws_context *context = wsi->context;
+       struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+       struct lws_vhost *vh;
+        char buf[256];
+       int n;
+
+        (void)buf;
+
+       if (!LWS_SSL_ENABLED(wsi->vhost))
+               return 0;
+
+       switch (lwsi_state(wsi)) {
+       case LRS_SSL_INIT:
+
+               if (wsi->tls.ssl)
+                       lwsl_err("%s: leaking ssl\n", __func__);
+               if (accept_fd == LWS_SOCK_INVALID)
+                       assert(0);
+               if (context->simultaneous_ssl_restriction &&
+                   context->simultaneous_ssl >=
+                           context->simultaneous_ssl_restriction) {
+                       lwsl_notice("unable to deal with SSL connection\n");
+                       return 1;
+               }
+
+               if (lws_tls_server_new_nonblocking(wsi, accept_fd)) {
+                       if (accept_fd != LWS_SOCK_INVALID)
+                               compatible_close(accept_fd);
+                       goto fail;
+               }
+
+               if (context->simultaneous_ssl_restriction &&
+                   ++context->simultaneous_ssl ==
+                                   context->simultaneous_ssl_restriction)
+                       /* that was the last allowed SSL connection */
+                       lws_gate_accepts(context, 0);
+
+#if defined(LWS_WITH_STATS)
+               context->updated = 1;
+#endif
+               /*
+                * we are not accepted yet, but we need to enter ourselves
+                * as a live connection.  That way we can retry when more
+                * pieces come if we're not sorted yet
+                */
+               lwsi_set_state(wsi, LRS_SSL_ACK_PENDING);
+
+               lws_pt_lock(pt, __func__);
+               if (__insert_wsi_socket_into_fds(context, wsi)) {
+                       lwsl_err("%s: failed to insert into fds\n", __func__);
+                       goto fail;
+               }
+               lws_pt_unlock(pt);
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT,
+                               context->timeout_secs);
+
+               lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n");
+
+               /* fallthru */
+
+       case LRS_SSL_ACK_PENDING:
+
+               if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
+                       lwsl_err("%s: lws_change_pollfd failed\n", __func__);
+                       goto fail;
+               }
+
+               lws_latency_pre(context, wsi);
+
+               if (wsi->vhost->tls.allow_non_ssl_on_ssl_port) {
+
+                       n = recv(wsi->desc.sockfd, (char *)pt->serv_buf,
+                                context->pt_serv_buf_size, MSG_PEEK);
+
+                       /*
+                        * We have LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT..
+                        * this just means don't hang up on him because of no
+                        * tls hello... what happens next is driven by
+                        * additional option flags:
+                        *
+                        * none: fail the connection
+                        *
+                        * LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS:
+                        *     Destroy the TLS, issue a redirect using plaintext
+                        *     http (this may not be accepted by a client that
+                        *     has visited the site before and received an STS
+                        *     header).
+                        *
+                        * LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER:
+                        *     Destroy the TLS, continue and serve normally
+                        *     using http
+                        *
+                        * LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG:
+                        *     Destroy the TLS, apply whatever role and protocol
+                        *     were told in the vhost info struct
+                        *     .listen_accept_role / .listen_accept_protocol and
+                        *     continue with that
+                        */
+
+                       if (n >= 1 && pt->serv_buf[0] >= ' ') {
+                               /*
+                               * TLS content-type for Handshake is 0x16, and
+                               * for ChangeCipherSpec Record, it's 0x14
+                               *
+                               * A non-ssl session will start with the HTTP
+                               * method in ASCII.  If we see it's not a legit
+                               * SSL handshake kill the SSL for this
+                               * connection and try to handle as a HTTP
+                               * connection upgrade directly.
+                               */
+                               wsi->tls.use_ssl = 0;
+
+                               lws_tls_server_abort_connection(wsi);
+                               /*
+                                * care... this creates wsi with no ssl when ssl
+                                * is enabled and normally mandatory
+                                */
+                               wsi->tls.ssl = NULL;
+
+                               if (lws_check_opt(wsi->vhost->options,
+                                   LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS)) {
+                                       lwsl_info("%s: redirecting from http "
+                                                 "to https\n", __func__);
+                                       wsi->tls.redirect_to_https = 1;
+                                       goto notls_accepted;
+                               }
+
+                               if (lws_check_opt(wsi->vhost->options,
+                               LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER)) {
+                                       lwsl_info("%s: allowing unencrypted "
+                                                 "http service on tls port\n",
+                                                 __func__);
+                                       goto notls_accepted;
+                               }
+
+                               if (lws_check_opt(wsi->vhost->options,
+                   LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG)) {
+                                       if (lws_http_to_fallback(wsi, NULL, 0))
+                                               goto fail;
+                                       lwsl_info("%s: allowing non-tls "
+                                                 "fallback\n", __func__);
+                                       goto notls_accepted;
+                               }
+
+                               lwsl_notice("%s: client did not send a valid "
+                                           "tls hello (default vhost %s)\n",
+                                           __func__, wsi->vhost->name);
+                               goto fail;
+                       }
+                       if (!n) {
+                               /*
+                                * connection is gone, fail out
+                                */
+                               lwsl_debug("PEEKed 0\n");
+                               goto fail;
+                       }
+                       if (n < 0 && (LWS_ERRNO == LWS_EAGAIN ||
+                                     LWS_ERRNO == LWS_EWOULDBLOCK)) {
+                               /*
+                                * well, we get no way to know ssl or not
+                                * so go around again waiting for something
+                                * to come and give us a hint, or timeout the
+                                * connection.
+                                */
+                               if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) {
+                                       lwsl_info("%s: change_pollfd failed\n",
+                                                 __func__);
+                                       return -1;
+                               }
+
+                               lwsl_info("SSL_ERROR_WANT_READ\n");
+                               return 0;
+                       }
+               }
+
+               /* normal SSL connection processing path */
+
+#if defined(LWS_WITH_STATS)
+               /* only set this the first time around */
+               if (!wsi->accept_start_us)
+                       wsi->accept_start_us = lws_now_usecs();
+#endif
+               errno = 0;
+               lws_stats_bump(pt, LWSSTATS_C_SSL_ACCEPT_SPIN, 1);
+               n = lws_tls_server_accept(wsi);
+               lws_latency(context, wsi,
+                       "SSL_accept LRS_SSL_ACK_PENDING\n", n, n == 1);
+               lwsl_info("SSL_accept says %d\n", n);
+               switch (n) {
+               case LWS_SSL_CAPABLE_DONE:
+                       break;
+               case LWS_SSL_CAPABLE_ERROR:
+                       lws_stats_bump(pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1);
+                       lwsl_info("SSL_accept failed socket %u: %d\n",
+                                       wsi->desc.sockfd, n);
+                       wsi->socket_is_permanently_unusable = 1;
+                       goto fail;
+
+               default: /* MORE_SERVICE */
+                       return 0;
+               }
+
+               lws_stats_bump(pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1);
+#if defined(LWS_WITH_STATS)
+               if (wsi->accept_start_us)
+                       lws_stats_bump(pt,
+                                     LWSSTATS_US_SSL_ACCEPT_LATENCY_AVG,
+                                     lws_now_usecs() -
+                                             wsi->accept_start_us);
+               wsi->accept_start_us = lws_now_usecs();
+#endif
+
+               /* adapt our vhost to match the SNI SSL_CTX that was chosen */
+               vh = context->vhost_list;
+               while (vh) {
+                       if (!vh->being_destroyed && wsi->tls.ssl &&
+                           vh->tls.ssl_ctx == lws_tls_ctx_from_wsi(wsi)) {
+                               lwsl_info("setting wsi to vh %s\n", vh->name);
+                               lws_vhost_bind_wsi(vh, wsi);
+                               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);
+
+               lwsi_set_state(wsi, LRS_ESTABLISHED);
+               if (lws_tls_server_conn_alpn(wsi))
+                       goto fail;
+               lwsl_debug("accepted new SSL conn\n");
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+
+notls_accepted:
+       lwsi_set_state(wsi, LRS_ESTABLISHED);
+
+       return 0;
+
+fail:
+       return 1;
+}
+
diff --git a/lib/tls/tls.c b/lib/tls/tls.c
new file mode 100644 (file)
index 0000000..0132573
--- /dev/null
@@ -0,0 +1,336 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "core/private.h"
+#include "tls/private.h"
+
+#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
+#if defined(LWS_WITH_ESP32) && !defined(LWS_AMAZON_RTOS)
+int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
+              lws_filepos_t *amount)
+{
+       nvs_handle nvh;
+       size_t s;
+       int n = 0;
+
+       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+       if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) {
+               n = 1;
+               goto bail;
+       }
+       *buf = lws_malloc(s + 1, "alloc_file");
+       if (!*buf) {
+               n = 2;
+               goto bail;
+       }
+       if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) {
+               lws_free(*buf);
+               n = 1;
+               goto bail;
+       }
+
+       *amount = s;
+       (*buf)[s] = '\0';
+
+       lwsl_notice("%s: nvs: read %s, %d bytes\n", __func__, filename, (int)s);
+
+bail:
+       nvs_close(nvh);
+
+       return n;
+}
+#else
+int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
+               lws_filepos_t *amount)
+{
+       FILE *f;
+       size_t s;
+       int n = 0;
+
+       f = fopen(filename, "rb");
+       if (f == NULL) {
+               n = 1;
+               goto bail;
+       }
+
+       if (fseek(f, 0, SEEK_END) != 0) {
+               n = 1;
+               goto bail;
+       }
+
+       s = ftell(f);
+       if (s == (size_t)-1) {
+               n = 1;
+               goto bail;
+       }
+
+       if (fseek(f, 0, SEEK_SET) != 0) {
+               n = 1;
+               goto bail;
+       }
+
+       *buf = lws_malloc(s, "alloc_file");
+       if (!*buf) {
+               n = 2;
+               goto bail;
+       }
+
+       if (fread(*buf, s, 1, f) != 1) {
+               lws_free(*buf);
+               n = 1;
+               goto bail;
+       }
+
+       *amount = s;
+
+bail:
+       if (f)
+               fclose(f);
+
+       return n;
+
+}
+#endif
+
+/*
+ * filename: NULL means use buffer inbuf length inlen directly, otherwise
+ *           load the file "filename" into an allocated buffer.
+ *
+ * Allocates a separate DER output buffer if inbuf / inlen are the input,
+ * since the
+ *
+ * Contents may be PEM or DER: returns with buf pointing to DER and amount
+ * set to the DER length.
+ */
+
+int
+lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename,
+                             const char *inbuf, lws_filepos_t inlen,
+                             uint8_t **buf, lws_filepos_t *amount)
+{
+       uint8_t *pem = NULL, *p, *end, *opem;
+       lws_filepos_t len;
+       uint8_t *q;
+       int n;
+
+       if (filename) {
+               n = alloc_file(context, filename, (uint8_t **)&pem, &len);
+               if (n)
+                       return n;
+       } else {
+               pem = (uint8_t *)inbuf;
+               len = inlen;
+       }
+
+       opem = p = pem;
+       end = p + len;
+
+       if (strncmp((char *)p, "-----", 5)) {
+
+               /* take it as being already DER */
+
+               pem = lws_malloc(inlen, "alloc_der");
+               if (!pem)
+                       return 1;
+
+               memcpy(pem, inbuf, inlen);
+
+               *buf = pem;
+               *amount = inlen;
+
+               return 0;
+       }
+
+       /* PEM -> DER */
+
+       if (!filename) {
+               /* we don't know if it's in const memory... alloc the output */
+               pem = lws_malloc((inlen * 3) / 4, "alloc_der");
+               if (!pem) {
+                       lwsl_err("a\n");
+                       return 1;
+               }
+
+
+       } /* else overwrite the allocated, b64 input with decoded DER */
+
+       /* trim the first line */
+
+       p += 5;
+       while (p < end && *p != '\n' && *p != '-')
+               p++;
+
+       if (*p != '-') {
+               lwsl_err("b\n");
+               goto bail;
+       }
+
+       while (p < end && *p != '\n')
+               p++;
+
+       if (p >= end) {
+               lwsl_err("c\n");
+               goto bail;
+       }
+
+       p++;
+
+       /* trim the last line */
+
+       q = (uint8_t *)end - 2;
+
+       while (q > opem && *q != '\n')
+               q--;
+
+       if (*q != '\n') {
+               lwsl_err("d\n");
+               goto bail;
+       }
+
+       /* we can't write into the input buffer for mem, since it may be in RO
+        * const segment
+        */
+       if (filename)
+               *q = '\0';
+
+       *amount = lws_b64_decode_string((char *)p, (char *)pem,
+                                       (int)(long long)len);
+       *buf = (uint8_t *)pem;
+
+       return 0;
+
+bail:
+       lws_free((uint8_t *)pem);
+
+       return 4;
+}
+
+
+#endif
+
+#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_DEV_KIT)
+
+
+static int
+lws_tls_extant(const char *name)
+{
+       /* it exists if we can open it... */
+       int fd = open(name, O_RDONLY), n;
+       char buf[1];
+
+       if (fd < 0)
+               return 1;
+
+       /* and we can read at least one byte out of it */
+       n = read(fd, buf, 1);
+       close(fd);
+
+       return n != 1;
+}
+#endif
+/*
+ * Returns 0 if the filepath "name" exists and can be read from.
+ *
+ * In addition, if "name".upd exists, backup "name" to "name.old.1"
+ * and rename "name".upd to "name" before reporting its existence.
+ *
+ * There are four situations and three results possible:
+ *
+ * 1) LWS_TLS_EXTANT_NO: There are no certs at all (we are waiting for them to
+ *    be provisioned).  We also feel like this if we need privs we don't have
+ *    any more to look in the directory.
+ *
+ * 2) There are provisioned certs written (xxx.upd) and we still have root
+ *    privs... in this case we rename any existing cert to have a backup name
+ *    and move the upd cert into place with the correct name.  This then becomes
+ *    situation 4 for the caller.
+ *
+ * 3) LWS_TLS_EXTANT_ALTERNATIVE: There are provisioned certs written (xxx.upd)
+ *    but we no longer have the privs needed to read or rename them.  In this
+ *    case, indicate that the caller should use temp copies if any we do have
+ *    rights to access.  This is normal after we have updated the cert.
+ *
+ *    But if we dropped privs, we can't detect the provisioned xxx.upd cert +
+ *    key, because we can't see in the dir.  So we have to upgrade NO to
+ *    ALTERNATIVE when we actually have the in-memory alternative.
+ *
+ * 4) LWS_TLS_EXTANT_YES: The certs are present with the correct name and we
+ *    have the rights to read them.
+ */
+#if !defined(LWS_AMAZON_RTOS)
+enum lws_tls_extant
+lws_tls_use_any_upgrade_check_extant(const char *name)
+{
+#if !defined(LWS_PLAT_OPTEE)
+
+       int n;
+
+#if !defined(LWS_WITH_ESP32)
+       char buf[256];
+
+       lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name);
+       if (!lws_tls_extant(buf)) {
+               /* ah there is an updated file... how about the desired file? */
+               if (!lws_tls_extant(name)) {
+                       /* rename the desired file */
+                       for (n = 0; n < 50; n++) {
+                               lws_snprintf(buf, sizeof(buf) - 1,
+                                            "%s.old.%d", name, n);
+                               if (!rename(name, buf))
+                                       break;
+                       }
+                       if (n == 50) {
+                               lwsl_notice("unable to rename %s\n", name);
+
+                               return LWS_TLS_EXTANT_ALTERNATIVE;
+                       }
+                       lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name);
+               }
+               /* desired file is out of the way, rename the updated file */
+               if (rename(buf, name)) {
+                       lwsl_notice("unable to rename %s to %s\n", buf, name);
+
+                       return LWS_TLS_EXTANT_ALTERNATIVE;
+               }
+       }
+
+       if (lws_tls_extant(name))
+               return LWS_TLS_EXTANT_NO;
+#else
+       nvs_handle nvh;
+       size_t s = 8192;
+
+       if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+               lwsl_notice("%s: can't open nvs\n", __func__);
+               return LWS_TLS_EXTANT_NO;
+       }
+
+       n = nvs_get_blob(nvh, name, NULL, &s);
+       nvs_close(nvh);
+
+       if (n)
+               return LWS_TLS_EXTANT_NO;
+#endif
+#endif
+       return LWS_TLS_EXTANT_YES;
+}
+#endif
+
index e100285..15bd4e8 100644 (file)
@@ -7,7 +7,7 @@ DOXYFILE_ENCODING      = UTF-8
 PROJECT_NAME           = "libwebsockets"
 PROJECT_NUMBER         =
 PROJECT_BRIEF          = "Lightweight C library for HTML5 websockets"
-PROJECT_LOGO           = "./test-server/libwebsockets.org-logo.png"
+PROJECT_LOGO           = "./test-apps/libwebsockets.org-logo.svg"
 OUTPUT_DIRECTORY       = "doc"
 CREATE_SUBDIRS         = NO
 ALLOW_UNICODE_NAMES    = NO
@@ -101,9 +101,66 @@ WARN_LOGFILE           =
 #---------------------------------------------------------------------------
 # Configuration options related to the input files
 #---------------------------------------------------------------------------
-INPUT                  = lib/libwebsockets.h mainpage.md README.build.md README.problems.md README.lwsws.md README.coding.md README.generic-sessions.md README.generic-table.md README.test-apps.md doc-assets 
+INPUT                  = include/libwebsockets.h \
+                        include/libwebsockets/lws-adopt.h \
+                        include/libwebsockets/lws-callbacks.h \
+                        include/libwebsockets/lws-cgi.h \
+                        include/libwebsockets/lws-client.h \
+                        include/libwebsockets/lws-context-vhost.h \
+                        include/libwebsockets/lws-dbus.h \
+                        include/libwebsockets/lws-diskcache.h \
+                        include/libwebsockets/lws-esp32.h \
+                        include/libwebsockets/lws-fts.h \
+                        include/libwebsockets/lws-genhash.h \
+                        include/libwebsockets/lws-genrsa.h \
+                        include/libwebsockets/lws-http.h \
+                        include/libwebsockets/lws-jwk.h \
+                        include/libwebsockets/lws-jws.h \
+                        include/libwebsockets/lws-lejp.h \
+                        include/libwebsockets/lws-logs.h \
+                        include/libwebsockets/lws-lwsac.h \
+                        include/libwebsockets/lws-misc.h \
+                        include/libwebsockets/lws-network-helper.h \
+                        include/libwebsockets/lws-plugin-generic-sessions.h \
+                        include/libwebsockets/lws-protocols-plugins.h \
+                        include/libwebsockets/lws-purify.h \
+                        include/libwebsockets/lws-ring.h \
+                        include/libwebsockets/lws-service.h \
+                        include/libwebsockets/lws-sha1-base64.h \
+                        include/libwebsockets/lws-spa.h \
+                        include/libwebsockets/lws-stats.h \
+                        include/libwebsockets/lws-threadpool.h \
+                        include/libwebsockets/lws-timeout-timer.h \
+                        include/libwebsockets/lws-tokenize.h \
+                        include/libwebsockets/lws-vfs.h \
+                        include/libwebsockets/lws-write.h \
+                        include/libwebsockets/lws-writeable.h \
+                        include/libwebsockets/lws-ws-close.h \
+                        include/libwebsockets/lws-ws-ext.h \
+                        include/libwebsockets/lws-ws-state.h \
+                        include/libwebsockets/lws-x509.h \
+                        plugins/ssh-base/include/lws-plugin-ssh.h \
+                        ./READMEs/mainpage.md \
+                        ./READMEs/README.build.md \
+                        ./READMEs/README.ci.md \
+                        ./READMEs/README.content-security-policy.md \
+                        ./READMEs/README.contributing.md \
+                        ./READMEs/README.http-fallback.md \
+                        ./READMEs/README.release-policy.md \
+                        ./READMEs/README.unix-domain-reverse-proxy.md \
+                        ./READMEs/README.vulnerability-reporting.md \
+                        ./READMEs/README.problems.md \
+                        ./READMEs/README.lwsws.md \
+                        ./READMEs/README.coding.md \
+                        ./READMEs/README.esp32.md \
+                        ./READMEs/README.generic-sessions.md \
+                        ./READMEs/README.generic-table.md \
+                        ./READMEs/README.test-apps.md \
+                        ./READMEs/README-plugin-sshd-base.md \
+                        ./READMEs/README.plugin-acme.md \
+                        ./doc-assets 
 INPUT_ENCODING         = UTF-8
-FILE_PATTERNS          = lib/*.c *.md *.png
+FILE_PATTERNS          = lib/*.c *.md *.png include/*.h
 RECURSIVE              = NO
 EXCLUDE                =
 EXCLUDE_SYMLINKS       = NO
@@ -259,11 +316,11 @@ PERLMOD_MAKEVAR_PREFIX =
 #---------------------------------------------------------------------------
 # Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
-ENABLE_PREPROCESSING   = NO
+ENABLE_PREPROCESSING   = YES
 MACRO_EXPANSION        = NO
 EXPAND_ONLY_PREDEF     = NO
 SEARCH_INCLUDES        = YES
-INCLUDE_PATH           =
+INCLUDE_PATH           = ./include
 INCLUDE_FILE_PATTERNS  =
 PREDEFINED             =
 EXPAND_AS_DEFINED      =
diff --git a/libwebsockets.spec b/libwebsockets.spec
deleted file mode 100644 (file)
index 4b7a97d..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-Name: libwebsockets
-Version: 2.3.0
-Release: 1%{?dist}
-Summary: Websocket Server and Client Library
-
-Group: System Environment/Libraries
-License: LGPLv2 with exceptions
-URL: https://libwebsockets.org
-Source0: %{name}-%{version}.tar.gz
-BuildRoot:     %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
-
-BuildRequires: openssl-devel cmake
-Requires: openssl
-
-%description
-Webserver server and client library
-
-%package devel
-Summary: Development files for libwebsockets
-Group: Development/Libraries
-Requires: %{name} = %{version}-%{release}
-Requires: openssl-devel
-
-%description devel
-Development files for libwebsockets
-
-%prep
-%setup -q
-
-%build
-mkdir -p build
-cd build
-%cmake ..
-make
-
-%install
-rm -rf $RPM_BUILD_ROOT
-cd build
-make install DESTDIR=$RPM_BUILD_ROOT
-
-%post -p /sbin/ldconfig
-%postun -p /sbin/ldconfig
-
-%clean
-rm -rf $RPM_BUILD_ROOT
-
-%files
-%defattr(-,root,root,-)
-%attr(755,root,root)
-/usr/bin/libwebsockets-test-server
-/usr/bin/libwebsockets-test-server-extpoll
-/usr/bin/libwebsockets-test-server-pthreads
-/usr/bin/libwebsockets-test-client
-/usr/bin/libwebsockets-test-ping
-/usr/bin/libwebsockets-test-echo
-/usr/bin/libwebsockets-test-fraggle
-/usr/bin/libwebsockets-test-fuzxy
-/%{_libdir}/libwebsockets.so.11
-/%{_libdir}/libwebsockets.so
-/%{_libdir}/cmake/libwebsockets/LibwebsocketsConfig.cmake
-/%{_libdir}/cmake/libwebsockets/LibwebsocketsConfigVersion.cmake
-/%{_libdir}/cmake/libwebsockets/LibwebsocketsTargets.cmake
-/%{_libdir}/cmake/libwebsockets/LibwebsocketsTargets-release.cmake
-
-/usr/share/libwebsockets-test-server
-%doc
-%files devel
-%defattr(-,root,root,-)
-/usr/include/*
-%attr(755,root,root)
-/%{_libdir}/libwebsockets.a
-/%{_libdir}/pkgconfig/libwebsockets.pc
-
-%changelog
-* Fri Jul 28 2017 Andy Green <andy@warmcat.com> 2.3.0-1
-- MAJOR SONAMEBUMP APICHANGES Upstream 2.3.0 release
-
-* Mon Mar 06 2017 Andy Green <andy@warmcat.com> 2.2.0-1
-- MAJOR SONAMEBUMP APICHANGES Upstream 2.2.0 release
-
-* Thu Oct 06 2016 Andy Green <andy@warmcat.com> 2.1.0-1
-- MAJOR SONAMEBUMP APICHANGES Upstream 2.1.0 release
-
-* Thu May 05 2016 Andy Green <andy@warmcat.com> 2.0.0-1
-- MAJOR SONAMEBUMP APICHANGES Upstream 2.0.0 release
-
-* Tue Feb 16 2016 Andy Green <andy@warmcat.com> 1.7.0-1
-- MAJOR SONAMEBUMP APICHANGES Upstream 1.7.0 release
-
-* Sun Jan 17 2016 Andrew Cooks <acooks@linux.com> 1.6.0-1
-- Bump version to 1.6.0
diff --git a/lws_config.h.in b/lws_config.h.in
deleted file mode 100644 (file)
index 514afeb..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-/* lws_config.h  Generated from lws_config.h.in  */
-
-#ifndef NDEBUG
-       #ifndef _DEBUG
-               #define _DEBUG
-       #endif
-#endif
-
-#define LWS_INSTALL_DATADIR "${CMAKE_INSTALL_PREFIX}/share"
-
-/* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL.
- * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */
-#cmakedefine USE_WOLFSSL
-
-/* Also define to 1 (in addition to USE_WOLFSSL) when using the
-  (older) CyaSSL library */
-#cmakedefine USE_OLD_CYASSL
-#cmakedefine LWS_USE_BORINGSSL
-
-#cmakedefine LWS_USE_MBEDTLS
-#cmakedefine LWS_USE_POLARSSL
-#cmakedefine LWS_WITH_ESP8266
-#cmakedefine LWS_WITH_ESP32
-
-#cmakedefine LWS_WITH_PLUGINS
-#cmakedefine LWS_WITH_NO_LOGS
-
-/* The Libwebsocket version */
-#cmakedefine LWS_LIBRARY_VERSION "${LWS_LIBRARY_VERSION}"
-
-#define LWS_LIBRARY_VERSION_MAJOR ${LWS_LIBRARY_VERSION_MAJOR}
-#define LWS_LIBRARY_VERSION_MINOR ${LWS_LIBRARY_VERSION_MINOR}
-#define LWS_LIBRARY_VERSION_PATCH ${LWS_LIBRARY_VERSION_PATCH}
-/* LWS_LIBRARY_VERSION_NUMBER looks like 1005001 for e.g. version 1.5.1 */
-#define LWS_LIBRARY_VERSION_NUMBER (LWS_LIBRARY_VERSION_MAJOR*1000000)+(LWS_LIBRARY_VERSION_MINOR*1000)+LWS_LIBRARY_VERSION_PATCH
-
-/* The current git commit hash that we're building from */
-#cmakedefine LWS_BUILD_HASH "${LWS_BUILD_HASH}"
-
-/* Build with OpenSSL support */
-#cmakedefine LWS_OPENSSL_SUPPORT
-
-/* The client should load and trust CA root certs it finds in the OS */
-#cmakedefine LWS_SSL_CLIENT_USE_OS_CA_CERTS
-
-/* Sets the path where the client certs should be installed. */
-#cmakedefine LWS_OPENSSL_CLIENT_CERTS "${LWS_OPENSSL_CLIENT_CERTS}"
-
-/* Turn off websocket extensions */
-#cmakedefine LWS_NO_EXTENSIONS
-
-/* Enable libev io loop */
-#cmakedefine LWS_USE_LIBEV
-
-/* Enable libuv io loop */
-#cmakedefine LWS_USE_LIBUV
-
-/* Enable libevent io loop */
-#cmakedefine LWS_USE_LIBEVENT
-
-/* Build with support for ipv6 */
-#cmakedefine LWS_USE_IPV6
-
-/* Build with support for UNIX domain socket */
-#cmakedefine LWS_USE_UNIX_SOCK
-
-/* Build with support for HTTP2 */
-#cmakedefine LWS_USE_HTTP2
-
-/* Turn on latency measuring code */
-#cmakedefine LWS_LATENCY
-
-/* Don't build the daemonizeation api */
-#cmakedefine LWS_NO_DAEMONIZE
-
-/* Build without server support */
-#cmakedefine LWS_NO_SERVER
-
-/* Build without client support */
-#cmakedefine LWS_NO_CLIENT
-
-/* If we should compile with MinGW support */
-#cmakedefine LWS_MINGW_SUPPORT
-
-/* Use the BSD getifaddrs that comes with libwebsocket, for uclibc support */
-#cmakedefine LWS_BUILTIN_GETIFADDRS
-
-/* use SHA1() not internal libwebsockets_SHA1 */
-#cmakedefine LWS_SHA1_USE_OPENSSL_NAME
-
-/* SSL server using ECDH certificate */
-#cmakedefine LWS_SSL_SERVER_WITH_ECDH_CERT
-#cmakedefine LWS_HAVE_SSL_CTX_set1_param
-#cmakedefine LWS_HAVE_X509_VERIFY_PARAM_set1_host
-
-#cmakedefine LWS_HAVE_UV_VERSION_H
-
-/* CGI apis */
-#cmakedefine LWS_WITH_CGI
-
-/* whether the Openssl is recent enough, and / or built with, ecdh */
-#cmakedefine LWS_HAVE_OPENSSL_ECDH_H
-
-/* HTTP Proxy support */
-#cmakedefine LWS_WITH_HTTP_PROXY
-
-/* HTTP Ranges support */
-#cmakedefine LWS_WITH_RANGES
-
-/* Http access log support */
-#cmakedefine LWS_WITH_ACCESS_LOG
-#cmakedefine LWS_WITH_SERVER_STATUS
-
-#cmakedefine LWS_WITH_STATEFUL_URLDECODE
-
-/* Maximum supported service threads */
-#define LWS_MAX_SMP ${LWS_MAX_SMP}
-
-/* Lightweight JSON Parser */
-#cmakedefine LWS_WITH_LEJP
-
-/* SMTP */
-#cmakedefine LWS_WITH_SMTP
-
-/* OPTEE */
-#cmakedefine LWS_PLAT_OPTEE
-
-/* ZIP FOPS */
-#cmakedefine LWS_WITH_ZIP_FOPS
-#cmakedefine LWS_HAVE_STDINT_H
-
-#cmakedefine LWS_AVOID_SIGPIPE_IGN
-
-#cmakedefine LWS_FALLBACK_GETHOSTBYNAME
-
-#cmakedefine LWS_WITH_STATS
-#cmakedefine LWS_WITH_SOCKS5
-
-#cmakedefine LWS_HAVE_SYS_CAPABILITY_H
-#cmakedefine LWS_HAVE_LIBCAP
-
-#cmakedefine LWS_HAVE_ATOLL
-#cmakedefine LWS_HAVE__ATOI64
-#cmakedefine LWS_HAVE__STAT32I64
-
-/* OpenSSL various APIs */
-
-#cmakedefine LWS_HAVE_TLS_CLIENT_METHOD
-#cmakedefine LWS_HAVE_TLSV1_2_CLIENT_METHOD
-#cmakedefine LWS_HAVE_SSL_SET_INFO_CALLBACK
-
-#cmakedefine LWS_HAS_INTPTR_T
-
-${LWS_SIZEOFPTR_CODE}
index 2aa85e5..164e03a 100644 (file)
@@ -2,7 +2,8 @@
  "vhosts": [ {
      "name": "localhost",
      "port": "7681",
-     "interface": "lo",
+# by default, bind to all interfaces, but you can restrict it
+#     "interface": "lo",
 #     "host-ssl-key":  "/etc/pki/tls/private/libwebsockets.org.key",
 #     "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt",
 #     "host-ssl-ca":   "/etc/pki/tls/certs/libwebsockets.org.cer",
index 01a6dc7..4a19307 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets web server application
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
@@ -21,7 +21,9 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
 #include <getopt.h>
+#endif
 #include <signal.h>
 #include <string.h>
 #include <sys/stat.h>
@@ -36,6 +38,7 @@
 #else
 #include <io.h>
 #include "gettimeofday.h"
+#include <uv.h>
 
 int fork(void)
 {
@@ -44,7 +47,7 @@ int fork(void)
 }
 #endif
 
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 
 #include <uv.h>
 
@@ -52,17 +55,20 @@ static struct lws_context *context;
 static char config_dir[128];
 static int opts = 0, do_reload = 1;
 static uv_loop_t loop;
-static uv_signal_t signal_outer;
+static uv_signal_t signal_outer[2];
 static int pids[32];
+void lwsl_emit_stderr(int level, const char *line);
 
 #define LWSWS_CONFIG_STRING_SIZE (32 * 1024)
 
 static const struct lws_extension exts[] = {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
        {
                "permessage-deflate",
                lws_extension_callback_pm_deflate,
                "permessage-deflate"
        },
+#endif
        { NULL, NULL, NULL /* terminator */ }
 };
 
@@ -71,12 +77,14 @@ static const char * const plugin_dirs[] = {
        NULL
 };
 
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
 static struct option options[] = {
        { "help",       no_argument,            NULL, 'h' },
        { "debug",      required_argument,      NULL, 'd' },
        { "configdir",  required_argument,      NULL, 'c' },
        { NULL, 0, 0, 0 }
 };
+#endif
 
 void signal_cb(uv_signal_t *watcher, int signum)
 {
@@ -98,7 +106,9 @@ void signal_cb(uv_signal_t *watcher, int signum)
                break;
        }
        lwsl_err("Signal %d caught\n", watcher->signum);
-       lws_libuv_stop(context);
+       uv_signal_stop(watcher);
+       uv_signal_stop(&signal_outer[1]);
+       lws_context_destroy(context);
 }
 
 static int
@@ -107,6 +117,7 @@ context_creation(void)
        int cs_len = LWSWS_CONFIG_STRING_SIZE - 1;
        struct lws_context_creation_info info;
        char *cs, *config_strings;
+       void *foreign_loops[1];
 
        cs = config_strings = malloc(LWSWS_CONFIG_STRING_SIZE);
        if (!config_strings) {
@@ -117,7 +128,7 @@ context_creation(void)
        memset(&info, 0, sizeof(info));
 
        info.external_baggage_free_on_destroy = config_strings;
-       info.max_http_header_pool = 16;
+       info.pt_serv_buf_size = 8192;
        info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 |
                              LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
                              LWS_SERVER_OPTION_LIBUV;
@@ -131,15 +142,16 @@ context_creation(void)
        if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len))
                goto init_failed;
 
+       foreign_loops[0] = &loop;
+       info.foreign_loops = foreign_loops;
+       info.pcontext = &context;
+
        context = lws_create_context(&info);
        if (context == NULL) {
                lwsl_err("libwebsocket init failed\n");
                goto init_failed;
        }
 
-       lws_uv_sigint_cfg(context, 1, signal_cb);
-       lws_uv_initloop(context, &loop, 0);
-
        /*
         * then create the vhosts... protocols are entirely coming from
         * plugins, so we leave it NULL
@@ -147,8 +159,7 @@ context_creation(void)
 
        info.extensions = exts;
 
-       if (lwsws_get_config_vhosts(context, &info, config_dir,
-                                   &cs, &cs_len))
+       if (lwsws_get_config_vhosts(context, &info, config_dir, &cs, &cs_len))
                return 1;
 
        return 0;
@@ -176,7 +187,7 @@ reload_handler(int signum)
                fprintf(stderr, "root process receives reload\n");
                if (!do_reload) {
                        fprintf(stderr, "passing HUP to child processes\n");
-                       for (m = 0; m < ARRAY_SIZE(pids); m++)
+                       for (m = 0; m < (int)LWS_ARRAY_SIZE(pids); m++)
                                if (pids[m])
                                        kill(pids[m], SIGHUP);
                        sleep(1);
@@ -186,8 +197,10 @@ reload_handler(int signum)
        case SIGINT:
        case SIGTERM:
        case SIGKILL:
+               fprintf(stderr, "master process waiting 2s...\n");
+               sleep(2); /* give children a chance to deal with the signal */
                fprintf(stderr, "killing service processes\n");
-               for (m = 0; m < ARRAY_SIZE(pids); m++)
+               for (m = 0; m < (int)LWS_ARRAY_SIZE(pids); m++)
                        if (pids[m])
                                kill(pids[m], SIGTERM);
                exit(0);
@@ -199,15 +212,19 @@ reload_handler(int signum)
 
 int main(int argc, char **argv)
 {
-       int n = 0, debug_level = 7;
+       int n = 0, budget = 100, debug_level = 1024 + 7;
 #ifndef _WIN32
        int m;
-       int status, syslog_options = LOG_PID | LOG_PERROR;
+       int status;//, syslog_options = LOG_PID | LOG_PERROR;
 #endif
 
        strcpy(config_dir, "/etc/lwsws");
        while (n >= 0) {
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
                n = getopt_long(argc, argv, "hd:c:", options, NULL);
+#else
+               n = getopt(argc, argv, "hd:c:");
+#endif
                if (n < 0)
                        continue;
                switch (n) {
@@ -215,8 +232,7 @@ int main(int argc, char **argv)
                        debug_level = atoi(optarg);
                        break;
                case 'c':
-                       strncpy(config_dir, optarg, sizeof(config_dir) - 1);
-                       config_dir[sizeof(config_dir) - 1] = '\0';
+                       lws_strncpy(config_dir, optarg, sizeof(config_dir));
                        break;
                case 'h':
                        fprintf(stderr, "Usage: lwsws [-c <config dir>] "
@@ -246,9 +262,8 @@ int main(int argc, char **argv)
                                break;
                        /* old */
                        if (n > 0)
-                               for (m = 0; m < ARRAY_SIZE(pids); m++)
+                               for (m = 0; m < (int)LWS_ARRAY_SIZE(pids); m++)
                                        if (!pids[m]) {
-                                               // fprintf(stderr, "added child pid %d\n", n);
                                                pids[m] = n;
                                                break;
                                        }
@@ -258,9 +273,8 @@ int main(int argc, char **argv)
 
                n = waitpid(-1, &status, WNOHANG);
                if (n > 0)
-                       for (m = 0; m < ARRAY_SIZE(pids); m++)
+                       for (m = 0; m < (int)LWS_ARRAY_SIZE(pids); m++)
                                if (pids[m] == n) {
-                                       // fprintf(stderr, "reaped child pid %d\n", pids[m]);
                                        pids[m] = 0;
                                        break;
                                }
@@ -271,16 +285,10 @@ int main(int argc, char **argv)
 #endif
        /* child process */
 
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsws", syslog_options, LOG_DAEMON);
-#endif
-
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
+       lws_set_log_level(debug_level, lwsl_emit_stderr_notimestamp);
 
        lwsl_notice("lwsws libwebsockets web server - license CC0 + LGPL2.1\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
+       lwsl_notice("(C) Copyright 2010-2018 Andy Green <andy@warmcat.com>\n");
 
 #if (UV_VERSION_MAJOR > 0) // Travis...
        uv_loop_init(&loop);
@@ -288,30 +296,33 @@ int main(int argc, char **argv)
        fprintf(stderr, "Your libuv is too old!\n");
        return 0;
 #endif
-       uv_signal_init(&loop, &signal_outer);
-       uv_signal_start(&signal_outer, signal_cb, SIGINT);
-       uv_signal_start(&signal_outer, signal_cb, SIGHUP);
+       uv_signal_init(&loop, &signal_outer[0]);
+       uv_signal_start(&signal_outer[0], signal_cb, SIGINT);
+       uv_signal_init(&loop, &signal_outer[1]);
+       uv_signal_start(&signal_outer[1], signal_cb, SIGHUP);
 
        if (context_creation()) {
                lwsl_err("Context creation failed\n");
                return 1;
        }
 
-       lws_libuv_run(context, 0);
+       lws_service(context, 0);
 
-       uv_signal_stop(&signal_outer);
-       lws_context_destroy(context);
+       lwsl_err("%s: closing\n", __func__);
 
+       for (n = 0; n < 2; n++) {
+               uv_signal_stop(&signal_outer[n]);
+               uv_close((uv_handle_t *)&signal_outer[n], NULL);
+       }
+
+       lws_context_destroy(context);
+       (void)budget;
 #if (UV_VERSION_MAJOR > 0) // Travis...
-       lws_close_all_handles_in_loop(&loop);
-       n = 0;
-       while (n++ < 4096 && uv_loop_close(&loop))
-               uv_run(&loop, UV_RUN_NOWAIT);
+       while ((n = uv_loop_close(&loop)) && --budget)
+               uv_run(&loop, UV_RUN_ONCE);
 #endif
 
-       lws_context_destroy2(context);
-
-       fprintf(stderr, "lwsws exited cleanly\n");
+       fprintf(stderr, "lwsws exited cleanly: %d\n", n);
 
 #ifndef _WIN32
        closelog();
index 13041a3..38c5012 100644 (file)
@@ -6,7 +6,6 @@ After=syslog.target
 ExecStart=/usr/local/bin/lwsws
 ExecReload=/usr/bin/kill -HUP $MAINPID
 ExecStop=/usr/bin/killall lwsws
-StandardError=null
 
 [Install]
 WantedBy=multi-user.target
diff --git a/mainpage.md b/mainpage.md
deleted file mode 100644 (file)
index 9a427b3..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-##Libwebsockets API introduction
-
-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)
- - cookie-based sessions
- - account management (including registration, email verification, lost pw etc)
- - strong ssl PFS support (A+ on SSLlabs test)
-
-You can browse by api category <a href="modules.html">here</a>
-
-A collection of READMEs for build, coding, lwsws etc are <a href="pages.html">here</a>
-
diff --git a/minimal-examples/README.md b/minimal-examples/README.md
new file mode 100644 (file)
index 0000000..2f271db
--- /dev/null
@@ -0,0 +1,88 @@
+|name|demonstrates|
+---|---
+client-server|Minimal examples providing client and server connections simultaneously
+crypto|Minimal examples related to using lws crypto apis
+dbus-server|Minimal examples showing how to integrate DBUS into lws event loop
+http-client|Minimal examples providing an http client
+http-server|Minimal examples providing an http server
+raw|Minimal examples related to adopting raw file or socket descriptors into the event loop
+ws-client|Minimal examples providing a ws client
+ws-server|Minimal examples providing a ws server (and an http server)
+
+## FAQ
+
+### Getting started
+
+Build and install lws itself first (note that after installing lws on \*nix, you need to run `ldconfig` one time so the OS can learn about the new library.  Lws installs in `/usr/local` by default, Debian / Ubuntu ldconfig knows to look there already, but Fedora / CentOS need you to add the line `/usr/local/lib` to `/etc/ld.so.conf` and run ldconfig)
+
+Then start with the simplest:
+
+`http-server/minimal-http-server`
+
+### Why are most of the sources split into a main C file file and a protocol file?
+
+Lws supports three ways to implement the protocol callback code:
+
+ - you can just add it all in the same source file
+
+ - you can separate it as these examples do, and #include it
+   into the main sources
+
+ - you can build it as a standalone plugin that is discovered
+   and loaded at runtime.
+
+The way these examples are structured, you can easily also build
+the protocol callback as a plugin just with a different
+CMakeLists.txt... see https://github.com/warmcat/libwebsockets/tree/master/plugin-standalone
+for an example.
+
+### Why would we want the protocol as a plugin?
+
+You will notice a lot of the main C code is the same boilerplate
+repeated for each example.  The actual interesting part is in
+the protocol callback only.
+
+Lws provides (-DLWS_WITH_LWSWS=1) a generic lightweight server app called 'lwsws' that
+can be configured by JSON.  Combined with your protocol as a plugin,
+it means you don't actually have to make a special server "app"
+part, you can just use lwsws and pass per-vhost configuration
+from JSON into your protocol.  (Of course in some cases you have
+an existing app you are bolting lws on to, then you don't care
+about this for that particular case).
+
+Because lwsws has no dependency on whatever your plugin does, it
+can mix and match different protocols randomly without needing any code
+changes.  It reduces the size of the task to just writing the
+code you care about in your protocol handler, and nothing else to write
+or maintain.
+
+Lwsws supports advanced features like reload, where it starts a new server
+instance with changed config or different plugins, while keeping the old
+instance around until the last connection to it closes.
+
+### I get why there is a pss, but why is there a vhd?
+
+The pss is instantiated per-connection.  But there are almost always
+other variables that have a lifetime longer than a single connection.
+
+You could make these variables "filescope" one-time globals, but that
+means your protocol cannot instantiate multiple times.
+
+Lws supports vhosts (virtual hosts), for example both https://warmcat.com
+and https://libwebsockets are running on the same lwsws instance on the
+same server and same IP... each of these is a separate vhost.
+
+Your protocol may be enabled on multiple vhosts, each of these vhosts
+provides a different vhd specific to the protocol instance on that
+vhost.  For example many of the samples keep a linked-list head to
+a list of live pss in the vhd... that means it's cleanly a list of
+pss opened **on that vhost**.  If another vhost has the protocol
+enabled, connections to that will point to a different vhd, and the
+linked-list head on that vhd will only list connections to his vhost.
+
+The example "ws-server/minimal-ws-server-threads" demonstrates how to deliver
+external configuration data to a specific vhost + protocol
+combination using code.  In lwsws, this is simply a matter of setting
+the desired JSON config.
+
+
diff --git a/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt
new file mode 100644 (file)
index 0000000..43f4246
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-smtp_client)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_SMTP 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/abstract/protocols/smtp-client/README.md b/minimal-examples/abstract/protocols/smtp-client/README.md
new file mode 100644 (file)
index 0000000..a3b3d01
--- /dev/null
@@ -0,0 +1,29 @@
+# lws api test smtp client
+
+Demonstrates how to send email through your local MTA
+
+## build
+
+Requires lws was built with `-DLWS_WITH_SMTP=1` at cmake.
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-r <recipient@whatever.com>|Send the test email to this email address
+
+
+```
+ $ ./lws-api-test-smtp_client -r andy@warmcat.com
+[2019/04/17 05:12:06:5293] USER: LWS API selftest: SMTP client
+[2019/04/17 05:12:06:5635] NOTICE: LGSSMTP_IDLE: connecting to 127.0.0.1:25
+[2019/04/17 05:12:06:6238] NOTICE: email_sent_or_failed: sent OK
+[2019/04/17 05:12:06:6394] USER: Completed: PASS
+
+```
+
diff --git a/minimal-examples/abstract/protocols/smtp-client/main.c b/minimal-examples/abstract/protocols/smtp-client/main.c
new file mode 100644 (file)
index 0000000..11d6b20
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * lws-api-test-smtp_client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+#include <signal.h>
+
+static int interrupted, result = 1;
+static const char *recip;
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+static int
+email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len)
+{
+       /* you could examine email->data here */
+       if (buf)
+               lwsl_notice("%s: %.*s\n", __func__, (int)len, (const char *)buf);
+       else
+               lwsl_notice("%s:\n", __func__);
+
+       /* destroy any allocations in email */
+
+       free((char *)email->payload);
+
+       result = 0;
+       interrupted = 1;
+
+       return 0;
+}
+
+/*
+ * We're going to bind to the raw-skt transport, so tell that what we want it
+ * to connect to
+ */
+
+static const lws_token_map_t smtp_raw_skt_transport_tokens[] = {
+ {
+       .u = { .value = "127.0.0.1" },
+       .name_index = LTMI_PEER_V_DNS_ADDRESS,
+ }, {
+       .u = { .lvalue = 25 },
+       .name_index = LTMI_PEER_LV_PORT,
+ }, {
+ }
+};
+
+static const lws_token_map_t smtp_protocol_tokens[] = {
+ {
+       .u = { .value = "lws-test-client" },
+       .name_index = LTMI_PSMTP_V_HELO,
+ }, {
+ }
+};
+
+
+int main(int argc, const char **argv)
+{
+       int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       lws_abs_t abs, *instance;
+       lws_smtp_email_t email;
+       struct lws_vhost *vh;
+       const char *p;
+
+       /* the normal lws init */
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       p = lws_cmdline_option(argc, argv, "-r");
+       if (!p) {
+               lwsl_err("-r <recipient email> is required\n");
+               return 1;
+       }
+       recip = p;
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: SMTP client\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       vh = lws_create_vhost(context, &info);
+       if (!vh) {
+               lwsl_err("Failed to create first vhost\n");
+               goto bail1;
+       }
+
+       /*
+        * create an smtp client that's hooked up to real sockets
+        */
+
+       memset(&abs, 0, sizeof(abs));
+       abs.vh = vh;
+
+       /* select the protocol and bind its tokens */
+
+       abs.ap = lws_abs_protocol_get_by_name("smtp");
+       if (!abs.ap)
+               goto bail1;
+       abs.ap_tokens = smtp_protocol_tokens;
+
+       /* select the transport and bind its tokens */
+
+       abs.at = lws_abs_transport_get_by_name("raw_skt");
+       if (!abs.at)
+               goto bail1;
+       abs.at_tokens = smtp_raw_skt_transport_tokens;
+
+       instance = lws_abs_bind_and_create_instance(&abs);
+       if (!instance) {
+               lwsl_err("%s: failed to create SMTP client\n", __func__);
+               goto bail1;
+       }
+
+       /* attach an email to it */
+
+       memset(&email, 0, sizeof(email));
+       email.data = NULL /* email specific user data */;
+       email.email_from = "andy@warmcat.com";
+       email.email_to = recip;
+       email.payload = malloc(2048);
+       if (!email.payload) {
+               goto bail1;
+       }
+
+       lws_snprintf((char *)email.payload, 2048,
+                       "From: noreply@example.com\n"
+                       "To: %s\n"
+                       "Subject: Test email for lws smtp-client\n"
+                       "\n"
+                       "Hello this was an api test for lws smtp-client\n"
+                       "\r\n.\r\n", recip);
+       email.done = email_sent_or_failed;
+
+       if (lws_smtp_client_add_email(instance, &email)) {
+               lwsl_err("%s: failed to add email\n", __func__);
+               goto bail;
+       }
+
+       /* the usual lws event loop */
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+
+bail1:
+       lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS");
+
+       lws_context_destroy(context);
+
+       return result;
+}
diff --git a/minimal-examples/api-tests/README.md b/minimal-examples/api-tests/README.md
new file mode 100644 (file)
index 0000000..a28df4f
--- /dev/null
@@ -0,0 +1,12 @@
+These are buildable test apps that run in CI to confirm correct api operation.
+
+|name|tests|
+---|---
+api-test-lwsac|LWS Allocated Chunks api
+api-test-lws_struct-json|Selftests for lws_struct JSON serialization and deserialization
+api-test-lws_tokenize|Generic secure string tokenizer api
+api-test-fts|LWS Full-text Search api
+api-test-gencrypto|LWS Generic Crypto apis
+api-test-jose|LWS JOSE apis
+api-test-smtp_client|SMTP client for sending emails
+
diff --git a/minimal-examples/api-tests/api-test-fts/CMakeLists.txt b/minimal-examples/api-tests/api-test-fts/CMakeLists.txt
new file mode 100644 (file)
index 0000000..023e837
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-fts)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_FTS 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/api-tests/api-test-fts/README.md b/minimal-examples/api-tests/api-test-fts/README.md
new file mode 100644 (file)
index 0000000..fe7881f
--- /dev/null
@@ -0,0 +1,53 @@
+# lws api test fts
+
+Demonstrates how to create indexes and perform full-text searches.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-c / --createindex|Create an index file, instead of searching
+-i / --index <file>|Use this file as the index
+
+The two modes are:
+
+ - create an index: `--createindex inputfile [inputfile...]`
+
+```
+ $ ./lws-api-test-fts -c ./the-picture-of-dorian-gray.txt
+[2018/10/15 07:14:15:1175] USER: LWS API selftest: full-text search
+[2018/10/15 07:14:15:1531] NOTICE: lws_fts_serialize: index 1 files (0MiB) cpu time 32ms, alloc: 1024KiB + 1024KiB, serialize: 3ms, file: 325KiB 
+```
+
+ - perform search[es]: `searchterm [searchterm...]`
+
+```
+ $ ./lws-api-test-fts b
+[2018/10/15 07:15:44:1442] USER: LWS API selftest: full-text search 
+[2018/10/15 07:15:44:1442] NOTICE: lws_fts_search: 'b' Matched: 3 instances, 8 children, 0ms
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC b: 3 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC be: 472 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC bee: 3 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC been: 236 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC beaut: 1 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC beauty: 55 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC because: 40 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC believe: 49 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC better: 54 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC before: 75 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC beg: 5 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC began: 44 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC but: 401 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC basil: 158 agg hits
+[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC broke: 22 agg hits
+[2018/10/15 07:15:44:1444] NOTICE: lws_fts_results_dump: AC by: 242 agg hits
+[2018/10/15 07:15:44:1444] NOTICE: lws_fts_results_dump: AC boy: 36 agg hits
+```
+
diff --git a/minimal-examples/api-tests/api-test-fts/canned-1.txt b/minimal-examples/api-tests/api-test-fts/canned-1.txt
new file mode 100644 (file)
index 0000000..b211f89
--- /dev/null
@@ -0,0 +1,26 @@
+API selftest: full-text search
+AC be: 472 agg hits
+AC but: 401 agg hits
+AC by: 242 agg hits
+AC been: 236 agg hits
+AC basil: 158 agg hits
+AC before: 75 agg hits
+AC beauty: 55 agg hits
+AC better: 54 agg hits
+AC believe: 49 agg hits
+AC began: 44 agg hits
+AC because: 40 agg hits
+AC boy: 36 agg hits
+AC book: 31 agg hits
+AC body: 28 agg hits
+AC both: 26 agg hits
+AC broke: 22 agg hits
+AC beg: 5 agg hits
+AC bore: 5 agg hits
+AC b: 3 agg hits
+AC bee: 3 agg hits
+AC beaut: 1 agg hits
+no filepath results
+
+
+
diff --git a/minimal-examples/api-tests/api-test-fts/canned-2.txt b/minimal-examples/api-tests/api-test-fts/canned-2.txt
new file mode 100644 (file)
index 0000000..579f3ba
--- /dev/null
@@ -0,0 +1,42 @@
+API selftest: full-text search
+no autocomplete results
+../minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt: (8904 lines) 32 hits 
+360
+17482
+393
+18984
+562
+28820
+837
+42903
+1640
+82057
+2037
+102214
+2091
+105019
+2145
+107351
+2725
+137188
+2808
+141127
+2977
+149971
+3429
+173810
+4417
+229186
+4431
+230058
+4656
+241181
+4708
+244372
+../minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt: (14399 lines) 3 hits 
+14106
+14313
+14396
+
+
+
diff --git a/minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt b/minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt
new file mode 100644 (file)
index 0000000..20aa7e3
--- /dev/null
@@ -0,0 +1,14399 @@
+The Project Gutenberg EBook of Les misérables Tome I, by Victor Hugo\r
+\r
+This eBook is for the use of anyone anywhere at no cost and with\r
+almost no restrictions whatsoever.  You may copy it, give it away or\r
+re-use it under the terms of the Project Gutenberg License included\r
+with this eBook or online at www.gutenberg.org\r
+\r
+\r
+Title: Les misérables Tome I\r
+       Fantine\r
+\r
+Author: Victor Hugo\r
+\r
+Release Date: January 10, 2006 [EBook #17489]\r
+[Date last updated: July 28, 2010]\r
+\r
+Language: French\r
+\r
+\r
+*** START OF THIS PROJECT GUTENBERG EBOOK LES MISÉRABLES TOME I ***\r
+\r
+\r
+\r
+\r
+Produced by www.ebooksgratuits.com and Chuck Greif\r
+\r
+\r
+\r
+\r
+Victor Hugo\r
+\r
+LES MISÉRABLES\r
+\r
+Tome I--FANTINE\r
+\r
+(1862)\r
+\r
+\r
+TABLE DES MATIÈRES\r
+\r
+Livre premier--Un juste\r
+\r
+Chapitre I Monsieur Myriel\r
+Chapitre II Monsieur Myriel devient monseigneur Bienvenu\r
+Chapitre III À bon évêque dur évêché\r
+Chapitre IV Les oeuvres semblables aux paroles\r
+Chapitre V Que monseigneur Bienvenu faisait durer trop longtemps ses\r
+     soutanes\r
+Chapitre VI Par qui il faisait garder sa maison\r
+Chapitre VII Cravatte\r
+Chapitre VIII Philosophie après boire\r
+Chapitre IX Le frère raconté par la soeur\r
+Chapitre X L'évêque en présence d'une lumière inconnue\r
+Chapitre XI Une restriction\r
+Chapitre XII Solitude de monseigneur Bienvenu\r
+Chapitre XIII Ce qu'il croyait\r
+Chapitre XIV Ce qu'il pensait\r
+\r
+\r
+Livre deuxième--La chute\r
+\r
+Chapitre I Le soir d'un jour de marche\r
+Chapitre II La prudence conseillée à la sagesse\r
+Chapitre III Héroïsme de l'obéissance passive\r
+Chapitre IV Détails sur les fromageries de Pontarlier\r
+Chapitre V Tranquillité\r
+Chapitre VI Jean Valjean\r
+Chapitre VII Le dedans du désespoir\r
+Chapitre VIII L'onde et l'ombre\r
+Chapitre IX Nouveaux griefs\r
+Chapitre X L'homme réveillé\r
+Chapitre XI Ce qu'il fait\r
+Chapitre XII L'évêque travaille\r
+Chapitre XIII Petit-Gervais\r
+\r
+\r
+Livre troisième--En l'année 1817\r
+\r
+Chapitre I L'année 1817\r
+Chapitre II Double quatuor\r
+Chapitre III Quatre à quatre\r
+Chapitre IV Tholomyès est si joyeux qu'il chante une chanson espagnole\r
+Chapitre V Chez Bombarda\r
+Chapitre VI Chapitre où l'on s'adore\r
+Chapitre VII Sagesse de Tholomyès\r
+Chapitre VIII Mort d'un cheval\r
+Chapitre IX Fin joyeuse de la joie\r
+Livre quatrième--Confier, c'est quelquefois livrer\r
+Chapitre I Une mère qui en rencontre une autre\r
+Chapitre II Première esquisse de deux figures louches\r
+Chapitre III L'Alouette\r
+\r
+\r
+Livre cinquième--La descente\r
+\r
+Chapitre I Histoire d'un progrès dans les verroteries noires\r
+Chapitre II M. Madeleine\r
+Chapitre III Sommes déposées chez Laffitte\r
+Chapitre IV M. Madeleine en deuil\r
+Chapitre V Vagues éclairs à l'horizon\r
+Chapitre VI Le père Fauchelevent\r
+Chapitre VII Fauchelevent devient jardinier à Paris\r
+Chapitre VIII Madame Victurnien dépense trente-cinq francs pour la morale\r
+Chapitre IX Succès de Madame Victurnien\r
+Chapitre X Suite du succès\r
+Chapitre XI _Christus nos liberavit_\r
+Chapitre XII Le désoeuvrement de M. Bamatabois\r
+Chapitre XIII Solution de quelques questions de police municipale\r
+\r
+\r
+Livre sixième--Javert\r
+\r
+Chapitre I Commencement du repos\r
+Chapitre II Comment Jean peut devenir Champ\r
+\r
+\r
+Livre septième--L'affaire Champmathieu\r
+\r
+Chapitre I La soeur Simplice\r
+Chapitre II Perspicacité de maître Scaufflaire\r
+Chapitre III Une tempête sous un crâne\r
+Chapitre IV Formes que prend la souffrance pendant le sommeil\r
+Chapitre V Bâtons dans les roues\r
+Chapitre VI La soeur Simplice mise à l'épreuve\r
+Chapitre VII Le voyageur arrivé prend ses précautions pour repartir\r
+Chapitre VIII Entrée de faveur\r
+Chapitre IX Un lieu où des convictions sont en train de se former\r
+Chapitre X Le système de dénégations\r
+Chapitre XI Champmathieu de plus en plus étonné\r
+\r
+\r
+Livre huitième--Contre-coup\r
+\r
+Chapitre I Dans quel miroir M. Madeleine regarde ses cheveux\r
+Chapitre II Fantine heureuse\r
+Chapitre III Javert content\r
+Chapitre IV L'autorité reprend ses droits\r
+Chapitre V Tombeau convenable\r
+\r
+\r
+\r
+\r
+Livre premier--Un juste\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+Monsieur Myriel\r
+\r
+\r
+En 1815, M. Charles-François-Bienvenu Myriel était évêque de Digne.\r
+C'était un vieillard d'environ soixante-quinze ans; il occupait le siège\r
+de Digne depuis 1806.\r
+\r
+Quoique ce détail ne touche en aucune manière au fond même de ce que\r
+nous avons à raconter, il n'est peut-être pas inutile, ne fût-ce que\r
+pour être exact en tout, d'indiquer ici les bruits et les propos qui\r
+avaient couru sur son compte au moment où il était arrivé dans le\r
+diocèse. Vrai ou faux, ce qu'on dit des hommes tient souvent autant de\r
+place dans leur vie et surtout dans leur destinée que ce qu'ils font. M.\r
+Myriel était fils d'un conseiller au parlement d'Aix; noblesse de robe.\r
+On contait de lui que son père, le réservant pour hériter de sa charge,\r
+l'avait marié de fort bonne heure, à dix-huit ou vingt ans, suivant un\r
+usage assez répandu dans les familles parlementaires. Charles Myriel,\r
+nonobstant ce mariage, avait, disait-on, beaucoup fait parler de lui. Il\r
+était bien fait de sa personne, quoique d'assez petite taille, élégant,\r
+gracieux, spirituel; toute la première partie de sa vie avait été donnée\r
+au monde et aux galanteries. La révolution survint, les événements se\r
+précipitèrent, les familles parlementaires décimées, chassées, traquées,\r
+se dispersèrent. M. Charles Myriel, dès les premiers jours de la\r
+révolution, émigra en Italie. Sa femme y mourut d'une maladie de\r
+poitrine dont elle était atteinte depuis longtemps. Ils n'avaient point\r
+d'enfants. Que se passa-t-il ensuite dans la destinée de M. Myriel?\r
+L'écroulement de l'ancienne société française, la chute de sa propre\r
+famille, les tragiques spectacles de 93, plus effrayants encore\r
+peut-être pour les émigrés qui les voyaient de loin avec le\r
+grossissement de l'épouvante, firent-ils germer en lui des idées de\r
+renoncement et de solitude? Fut-il, au milieu d'une de ces distractions\r
+et de ces affections qui occupaient sa vie, subitement atteint d'un de\r
+ces coups mystérieux et terribles qui viennent quelquefois renverser, en\r
+le frappant au coeur, l'homme que les catastrophes publiques\r
+n'ébranleraient pas en le frappant dans son existence et dans sa\r
+fortune? Nul n'aurait pu le dire; tout ce qu'on savait, c'est que,\r
+lorsqu'il revint d'Italie, il était prêtre.\r
+\r
+En 1804, M. Myriel était curé de Brignolles. Il était déjà vieux, et\r
+vivait dans une retraite profonde.\r
+\r
+Vers l'époque du couronnement, une petite affaire de sa cure, on ne sait\r
+plus trop quoi, l'amena à Paris. Entre autres personnes puissantes, il\r
+alla solliciter pour ses paroissiens M. le cardinal Fesch. Un jour que\r
+l'empereur était venu faire visite à son oncle, le digne curé, qui\r
+attendait dans l'antichambre, se trouva sur le passage de sa majesté.\r
+Napoléon, se voyant regardé avec une certaine curiosité par ce\r
+vieillard, se retourna, et dit brusquement:\r
+\r
+--Quel est ce bonhomme qui me regarde?\r
+\r
+--Sire, dit M. Myriel, vous regardez un bonhomme, et moi je regarde un\r
+grand homme. Chacun de nous peut profiter.\r
+\r
+L'empereur, le soir même, demanda au cardinal le nom de ce curé, et\r
+quelque temps après M. Myriel fut tout surpris d'apprendre qu'il était\r
+nommé évêque de Digne.\r
+\r
+Qu'y avait-il de vrai, du reste, dans les récits qu'on faisait sur la\r
+première partie de la vie de M. Myriel? Personne ne le savait. Peu de\r
+familles avaient connu la famille Myriel avant la révolution.\r
+\r
+M. Myriel devait subir le sort de tout nouveau venu dans une petite\r
+ville où il y a beaucoup de bouches qui parlent et fort peu de têtes qui\r
+pensent. Il devait le subir, quoiqu'il fût évêque et parce qu'il était\r
+évêque. Mais, après tout, les propos auxquels on mêlait son nom\r
+n'étaient peut-être que des propos; du bruit, des mots, des paroles;\r
+moins que des paroles, des _palabres_, comme dit l'énergique langue du\r
+midi.\r
+\r
+Quoi qu'il en fût, après neuf ans d'épiscopat et de résidence à Digne,\r
+tous ces racontages, sujets de conversation qui occupent dans le premier\r
+moment les petites villes et les petites gens, étaient tombés dans un\r
+oubli profond. Personne n'eût osé en parler, personne n'eût même osé\r
+s'en souvenir.\r
+\r
+M. Myriel était arrivé à Digne accompagné d'une vieille fille,\r
+mademoiselle Baptistine, qui était sa soeur et qui avait dix ans de\r
+moins que lui.\r
+\r
+Ils avaient pour tout domestique une servante du même âge que\r
+mademoiselle Baptistine, et appelée madame Magloire, laquelle, après\r
+avoir été _la servante de M. le Curé_, prenait maintenant le double\r
+titre de femme de chambre de mademoiselle et femme de charge de\r
+monseigneur.\r
+\r
+Mademoiselle Baptistine était une personne longue, pâle, mince, douce;\r
+elle réalisait l'idéal de ce qu'exprime le mot «respectable»; car il\r
+semble qu'il soit nécessaire qu'une femme soit mère pour être vénérable.\r
+Elle n'avait jamais été jolie; toute sa vie, qui n'avait été qu'une\r
+suite de saintes oeuvres, avait fini par mettre sur elle une sorte de\r
+blancheur et de clarté; et, en vieillissant, elle avait gagné ce qu'on\r
+pourrait appeler la beauté de la bonté. Ce qui avait été de la maigreur\r
+dans sa jeunesse était devenu, dans sa maturité, de la transparence; et\r
+cette diaphanéité laissait voir l'ange. C'était une âme plus encore que\r
+ce n'était une vierge. Sa personne semblait faite d'ombre; à peine assez\r
+de corps pour qu'il y eût là un sexe; un peu de matière contenant une\r
+lueur; de grands yeux toujours baissés; un prétexte pour qu'une âme\r
+reste sur la terre.\r
+\r
+Madame Magloire était une petite vieille, blanche, grasse, replète,\r
+affairée, toujours haletante, à cause de son activité d'abord, ensuite à\r
+cause d'un asthme.\r
+\r
+À son arrivée, on installa M. Myriel en son palais épiscopal avec les\r
+honneurs voulus par les décrets impériaux qui classent l'évêque\r
+immédiatement après le maréchal de camp. Le maire et le président lui\r
+firent la première visite, et lui de son côté fit la première visite au\r
+général et au préfet.\r
+\r
+L'installation terminée, la ville attendit son évêque à l'oeuvre.\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+Monsieur Myriel devient monseigneur Bienvenu\r
+\r
+\r
+Le palais épiscopal de Digne était attenant à l'hôpital.\r
+\r
+Le palais épiscopal était un vaste et bel hôtel bâti en pierre au\r
+commencement du siècle dernier par monseigneur Henri Puget, docteur en\r
+théologie de la faculté de Paris, abbé de Simore, lequel était évêque de\r
+Digne en 1712. Ce palais était un vrai logis seigneurial. Tout y avait\r
+grand air, les appartements de l'évêque, les salons, les chambres, la\r
+cour d'honneur, fort large, avec promenoirs à arcades, selon l'ancienne\r
+mode florentine, les jardins plantés de magnifiques arbres. Dans la\r
+salle à manger, longue et superbe galerie qui était au rez-de-chaussée\r
+et s'ouvrait sur les jardins, monseigneur Henri Puget avait donné à\r
+manger en cérémonie le 29 juillet 1714 à messeigneurs Charles Brûlart de\r
+Genlis, archevêque-prince d'Embrun, Antoine de Mesgrigny, capucin,\r
+évêque de Grasse, Philippe de Vendôme, grand prieur de France, abbé de\r
+Saint-Honoré de Lérins, François de Berton de Grillon, évêque-baron de\r
+Vence, César de Sabran de Forcalquier, évêque-seigneur de Glandève, et\r
+Jean Soanen, prêtre de l'oratoire, prédicateur ordinaire du roi,\r
+évêque-seigneur de Senez. Les portraits de ces sept révérends\r
+personnages décoraient cette salle, et cette date mémorable, 29 juillet\r
+1714, y était gravée en lettres d'or sur une table de marbre blanc.\r
+\r
+L'hôpital était une maison étroite et basse à un seul étage avec un\r
+petit jardin. Trois jours après son arrivée, l'évêque visita l'hôpital.\r
+La visite terminée, il fit prier le directeur de vouloir bien venir\r
+jusque chez lui.\r
+\r
+--Monsieur le directeur de l'hôpital, lui dit-il, combien en ce moment\r
+avez-vous de malades?\r
+\r
+--Vingt-six, monseigneur.\r
+\r
+--C'est ce que j'avais compté, dit l'évêque.\r
+\r
+--Les lits, reprit le directeur, sont bien serrés les uns contre les\r
+autres.\r
+\r
+--C'est ce que j'avais remarqué.\r
+\r
+--Les salles ne sont que des chambres, et l'air s'y renouvelle\r
+difficilement.\r
+\r
+--C'est ce qui me semble.\r
+\r
+--Et puis, quand il y a un rayon de soleil, le jardin est bien petit\r
+pour les convalescents.\r
+\r
+--C'est ce que je me disais.\r
+\r
+--Dans les épidémies, nous avons eu cette année le typhus, nous avons eu\r
+une suette militaire il y a deux ans, cent malades quelquefois; nous ne\r
+savons que faire.\r
+\r
+--C'est la pensée qui m'était venue.\r
+\r
+--Que voulez-vous, monseigneur? dit le directeur, il faut se résigner.\r
+\r
+Cette conversation avait lieu dans la salle à manger-galerie du\r
+rez-de-chaussée. L'évêque garda un moment le silence, puis il se tourna\r
+brusquement vers le directeur de l'hôpital:\r
+\r
+--Monsieur, dit-il, combien pensez-vous qu'il tiendrait de lits rien que\r
+dans cette salle?\r
+\r
+--La salle à manger de monseigneur! s'écria le directeur stupéfait.\r
+\r
+L'évêque parcourait la salle du regard et semblait y faire avec les yeux\r
+des mesures et des calculs.\r
+\r
+--Il y tiendrait bien vingt lits! dit-il, comme se parlant à lui-même.\r
+\r
+Puis élevant la voix:\r
+\r
+--Tenez, monsieur le directeur de l'hôpital, je vais vous dire. Il y a\r
+évidemment une erreur. Vous êtes vingt-six personnes dans cinq ou six\r
+petites chambres. Nous sommes trois ici, et nous avons place pour\r
+soixante. Il y a erreur, je vous dis. Vous avez mon logis, et j'ai le\r
+vôtre. Rendez-moi ma maison. C'est ici chez vous.\r
+\r
+Le lendemain, les vingt-six pauvres étaient installés dans le palais de\r
+l'évêque et l'évêque était à l'hôpital.\r
+\r
+M. Myriel n'avait point de bien, sa famille ayant été ruinée par la\r
+révolution. Sa soeur touchait une rente viagère de cinq cents francs\r
+qui, au presbytère, suffisait à sa dépense personnelle. M. Myriel\r
+recevait de l'état comme évêque un traitement de quinze mille francs. Le\r
+jour même où il vint se loger dans la maison de l'hôpital, M. Myriel\r
+détermina l'emploi de cette somme une fois pour toutes de la manière\r
+suivante. Nous transcrivons ici une note écrite de sa main.\r
+\r
+_Note pour régler les dépenses de ma maison._\r
+\r
+_Pour le petit séminaire: quinze cents livres_\r
+_Congrégation de la mission: cent livres_\r
+_Pour les lazaristes de Montdidier: cent livres_\r
+_Séminaire des missions étrangères à Paris: deux cents livres_\r
+_Congrégation du Saint-Esprit: cent cinquante livres_\r
+_Établissements religieux de la Terre-Sainte: cent livres_\r
+_Sociétés de charité maternelle: trois cents livres_\r
+_En sus, pour celle d'Arles: cinquante livres_\r
+_OEuvre pour l'amélioration des prisons: quatre cents livres_\r
+_OEuvre pour le soulagement et la délivrance des prisonniers: cinq cents\r
+livres_\r
+_Pour libérer des pères de famille prisonniers pour dettes: mille livres_\r
+_Supplément au traitement des pauvres maîtres d'école du diocèse: deux\r
+mille livres_\r
+_Grenier d'abondance des Hautes-Alpes: cent livres_\r
+_Congrégation des dames de Digne, de Manosque et de Sisteron,\r
+pour l'enseignement gratuit des filles indigentes: quinze cents livres_\r
+_Pour les pauvres: six mille livres_\r
+_Ma dépense personnelle: mille livres_\r
+\r
+Total: _quinze mille livres_\r
+\r
+Pendant tout le temps qu'il occupa le siège de Digne, M. Myriel ne\r
+changea presque rien à cet arrangement. Il appelait cela, comme on voit,\r
+_avoir réglé les dépenses de sa maison_.\r
+\r
+Cet arrangement fut accepté avec une soumission absolue par mademoiselle\r
+Baptistine. Pour cette sainte fille, M. de Digne était tout à la fois\r
+son frère et son évêque, son ami selon la nature et son supérieur selon\r
+l'église. Elle l'aimait et elle le vénérait tout simplement. Quand il\r
+parlait, elle s'inclinait; quand il agissait, elle adhérait. La servante\r
+seule, madame Magloire, murmura un peu. M. l'évêque, on l'a pu\r
+remarquer, ne s'était réservé que mille livres, ce qui, joint à la\r
+pension de mademoiselle Baptistine, faisait quinze cents francs par an.\r
+Avec ces quinze cents francs, ces deux vieilles femmes et ce vieillard\r
+vivaient.\r
+\r
+Et quand un curé de village venait à Digne, M. l'évêque trouvait encore\r
+moyen de le traiter, grâce à la sévère économie de madame Magloire et à\r
+l'intelligente administration de mademoiselle Baptistine.\r
+\r
+Un jour--il était à Digne depuis environ trois mois--l'évêque dit:\r
+\r
+--Avec tout cela je suis bien gêné!\r
+\r
+--Je le crois bien! s'écria madame Magloire, Monseigneur n'a seulement\r
+pas réclamé la rente que le département lui doit pour ses frais de\r
+carrosse en ville et de tournées dans le diocèse. Pour les évêques\r
+d'autrefois c'était l'usage.\r
+\r
+--Tiens! dit l'évêque, vous avez raison, madame Magloire.\r
+\r
+Il fit sa réclamation.\r
+\r
+Quelque temps après, le conseil général, prenant cette demande en\r
+considération, lui vota une somme annuelle de trois mille francs, sous\r
+cette rubrique: _Allocation à M. l'évêque pour frais de carrosse, frais\r
+de poste et frais de tournées pastorales_.\r
+\r
+Cela fit beaucoup crier la bourgeoisie locale, et, à cette occasion, un\r
+sénateur de l'empire, ancien membre du conseil des cinq-cents favorable\r
+au dix-huit brumaire et pourvu près de la ville de Digne d'une\r
+sénatorerie magnifique, écrivit au ministre des cultes, M. Bigot de\r
+Préameneu, un petit billet irrité et confidentiel dont nous extrayons\r
+ces lignes authentiques:\r
+\r
+«--Des frais de carrosse? pourquoi faire dans une ville de moins de\r
+quatre mille habitants? Des frais de poste et de tournées? à quoi bon\r
+ces tournées d'abord? ensuite comment courir la poste dans un pays de\r
+montagnes? Il n'y a pas de routes. On ne va qu'à cheval. Le pont même de\r
+la Durance à Château-Arnoux peut à peine porter des charrettes à boeufs.\r
+Ces prêtres sont tous ainsi. Avides et avares. Celui-ci a fait le bon\r
+apôtre en arrivant. Maintenant il fait comme les autres. Il lui faut\r
+carrosse et chaise de poste. Il lui faut du luxe comme aux anciens\r
+évêques. Oh! toute cette prêtraille! Monsieur le comte, les choses\r
+n'iront bien que lorsque l'empereur nous aura délivrés des calotins. À\r
+bas le pape! (les affaires se brouillaient avec Rome). Quant à moi, je\r
+suis pour César tout seul. Etc., etc.»\r
+\r
+La chose, en revanche, réjouit fort madame Magloire.\r
+\r
+--Bon, dit-elle à mademoiselle Baptistine, Monseigneur a commencé par\r
+les autres, mais il a bien fallu qu'il finît par lui-même. Il a réglé\r
+toutes ses charités. Voilà trois mille livres pour nous. Enfin!\r
+\r
+Le soir même, l'évêque écrivit et remit à sa soeur une note ainsi\r
+conçue:\r
+\r
+_Frais de carrosse et de tournées._\r
+\r
+_Pour donner du bouillon de viande aux malades de l'hôpital: quinze\r
+cents livres_\r
+_Pour la société de charité maternelle d'Aix: deux cent cinquante livres_\r
+_Pour la société de charité maternelle de Draguignan: deux cent cinquante\r
+livres_\r
+_Pour les enfants trouvés: cinq cents livres_\r
+_Pour les orphelins: cinq cents livres_\r
+\r
+Total: _trois mille livres_\r
+\r
+Tel était le budget de M. Myriel.\r
+\r
+Quant au casuel épiscopal, rachats de bans, dispenses, ondoiements,\r
+prédications, bénédictions d'églises ou de chapelles, mariages, etc.,\r
+l'évêque le percevait sur les riches avec d'autant plus d'âpreté qu'il\r
+le donnait aux pauvres.\r
+\r
+Au bout de peu de temps, les offrandes d'argent affluèrent. Ceux qui ont\r
+et ceux qui manquent frappaient à la porte de M. Myriel, les uns venant\r
+chercher l'aumône que les autres venaient y déposer. L'évêque, en moins\r
+d'un an, devint le trésorier de tous les bienfaits et le caissier de\r
+toutes les détresses. Des sommes considérables passaient par ses mains;\r
+mais rien ne put faire qu'il changeât quelque chose à son genre de vie\r
+et qu'il ajoutât le moindre superflu à son nécessaire.\r
+\r
+Loin de là. Comme il y a toujours encore plus de misère en bas que de\r
+fraternité en haut, tout était donné, pour ainsi dire, avant d'être\r
+reçu; c'était comme de l'eau sur une terre sèche; il avait beau recevoir\r
+de l'argent, il n'en avait jamais. Alors il se dépouillait.\r
+\r
+L'usage étant que les évêques énoncent leurs noms de baptême en tête de\r
+leurs mandements et de leurs lettres pastorales, les pauvres gens du\r
+pays avaient choisi, avec une sorte d'instinct affectueux, dans les noms\r
+et prénoms de l'évêque, celui qui leur présentait un sens, et ils ne\r
+l'appelaient que monseigneur Bienvenu. Nous ferons comme eux, et nous le\r
+nommerons ainsi dans l'occasion. Du reste, cette appellation lui\r
+plaisait.\r
+\r
+--J'aime ce nom-là, disait-il. Bienvenu corrige monseigneur.\r
+\r
+Nous ne prétendons pas que le portrait que nous faisons ici soit\r
+vraisemblable; nous nous bornons à dire qu'il est ressemblant.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+À bon évêque dur évêché\r
+\r
+\r
+M. l'évêque, pour avoir converti son carrosse en aumônes, n'en faisait\r
+pas moins ses tournées. C'est un diocèse fatigant que celui de Digne. Il\r
+a fort peu de plaines, beaucoup de montagnes, presque pas de routes, on\r
+l'a vu tout à l'heure; trente-deux cures, quarante et un vicariats et\r
+deux cent quatre-vingt-cinq succursales. Visiter tout cela, c'est une\r
+affaire. M. l'évêque en venait à bout. Il allait à pied quand c'était\r
+dans le voisinage, en carriole dans la plaine, en cacolet dans la\r
+montagne. Les deux vieilles femmes l'accompagnaient. Quand le trajet\r
+était trop pénible pour elles, il allait seul.\r
+\r
+Un jour, il arriva à Senez, qui est une ancienne ville épiscopale, monté\r
+sur un âne. Sa bourse, fort à sec dans ce moment, ne lui avait pas\r
+permis d'autre équipage. Le maire de la ville vint le recevoir à la\r
+porte de l'évêché et le regardait descendre de son âne avec des yeux\r
+scandalisés. Quelques bourgeois riaient autour de lui.\r
+\r
+--Monsieur le maire, dit l'évêque, et messieurs les bourgeois, je vois\r
+ce qui vous scandalise; vous trouvez que c'est bien de l'orgueil à un\r
+pauvre prêtre de monter une monture qui a été celle de Jésus-Christ. Je\r
+l'ai fait par nécessité, je vous assure, non par vanité.\r
+\r
+Dans ses tournées, il était indulgent et doux, et prêchait moins qu'il\r
+ne causait. Il ne mettait aucune vertu sur un plateau inaccessible. Il\r
+n'allait jamais chercher bien loin ses raisonnements et ses modèles.\r
+Aux habitants d'un pays il citait l'exemple du pays voisin. Dans les\r
+cantons où l'on était dur pour les nécessiteux, il disait:\r
+\r
+--Voyez les gens de Briançon. Ils ont donné aux indigents, aux veuves et\r
+aux orphelins le droit de faire faucher leurs prairies trois jours avant\r
+tous les autres. Ils leur rebâtissent gratuitement leurs maisons quand\r
+elles sont en ruines. Aussi est-ce un pays béni de Dieu. Durant tout un\r
+siècle de cent ans, il n'y a pas eu un meurtrier.\r
+\r
+Dans les villages âpres au gain et à la moisson, il disait:\r
+\r
+--Voyez ceux d'Embrun. Si un père de famille, au temps de la récolte, a\r
+ses fils au service à l'armée et ses filles en service à la ville, et\r
+qu'il soit malade et empêché, le curé le recommande au prône; et le\r
+dimanche, après la messe, tous les gens du village, hommes, femmes,\r
+enfants, vont dans le champ du pauvre homme lui faire sa moisson, et lui\r
+rapportent paille et grain dans son grenier.\r
+\r
+Aux familles divisées par des questions d'argent et d'héritage, il\r
+disait:\r
+\r
+--Voyez les montagnards de Devoluy, pays si sauvage qu'on n'y entend pas\r
+le rossignol une fois en cinquante ans. Eh bien, quand le père meurt\r
+dans une famille, les garçons s'en vont chercher fortune, et laissent le\r
+bien aux filles, afin qu'elles puissent trouver des maris.\r
+\r
+Aux cantons qui ont le goût des procès et où les fermiers se ruinent en\r
+papier timbré, il disait:\r
+\r
+--Voyez ces bons paysans de la vallée de Queyras. Ils sont là trois\r
+mille âmes. Mon Dieu! c'est comme une petite république. On n'y connaît\r
+ni le juge, ni l'huissier. Le maire fait tout. Il répartit l'impôt, taxe\r
+chacun en conscience, juge les querelles gratis, partage les patrimoines\r
+sans honoraires, rend des sentences sans frais; et on lui obéit, parce\r
+que c'est un homme juste parmi des hommes simples.\r
+\r
+Aux villages où il ne trouvait pas de maître d'école, il citait encore\r
+ceux de Queyras:\r
+\r
+--Savez-vous comment ils font? disait-il. Comme un petit pays de douze\r
+ou quinze feux ne peut pas toujours nourrir un magister, ils ont des\r
+maîtres d'école payés par toute la vallée qui parcourent les villages,\r
+passant huit jours dans celui-ci, dix dans celui-là, et enseignant. Ces\r
+magisters vont aux foires, où je les ai vus. On les reconnaît à des\r
+plumes à écrire qu'ils portent dans la ganse de leur chapeau. Ceux qui\r
+n'enseignent qu'à lire ont une plume, ceux qui enseignent la lecture et\r
+le calcul ont deux plumes; ceux qui enseignent la lecture, le calcul et\r
+le latin ont trois plumes. Ceux-là sont de grands savants. Mais quelle\r
+honte d'être ignorants! Faites comme les gens de Queyras.\r
+\r
+Il parlait ainsi, gravement et paternellement, à défaut d'exemples\r
+inventant des paraboles, allant droit au but, avec peu de phrases et\r
+beaucoup d'images, ce qui était l'éloquence même de Jésus-Christ,\r
+convaincu et persuadant.\r
+\r
+\r
+\r
+\r
+Chapitre IV\r
+\r
+Les oeuvres semblables aux paroles\r
+\r
+\r
+Sa conversation était affable et gaie. Il se mettait à la portée des\r
+deux vieilles femmes qui passaient leur vie près de lui; quand il riait,\r
+c'était le rire d'un écolier.\r
+\r
+Madame Magloire l'appelait volontiers _Votre Grandeur_. Un jour, il se\r
+leva de son fauteuil et alla à sa bibliothèque chercher un livre. Ce\r
+livre était sur un des rayons d'en haut. Comme l'évêque était d'assez\r
+petite taille, il ne put y atteindre.\r
+\r
+--Madame Magloire, dit-il, apportez-moi une chaise. Ma grandeur ne va\r
+pas jusqu'à cette planche.\r
+\r
+Une de ses parentes éloignées, madame la comtesse de Lô, laissait\r
+rarement échapper une occasion d'énumérer en sa présence ce qu'elle\r
+appelait «les espérances» de ses trois fils. Elle avait plusieurs\r
+ascendants fort vieux et proches de la mort dont ses fils étaient\r
+naturellement les héritiers. Le plus jeune des trois avait à recueillir\r
+d'une grand'tante cent bonnes mille livres de rentes; le deuxième était\r
+substitué au titre de duc de son oncle; l'aîné devait succéder à la\r
+pairie de son aïeul. L'évêque écoutait habituellement en silence ces\r
+innocents et pardonnables étalages maternels. Une fois pourtant, il\r
+paraissait plus rêveur que de coutume, tandis que madame de Lô\r
+renouvelait le détail de toutes ces successions et de toutes ces\r
+«espérances». Elle s'interrompit avec quelque impatience:\r
+\r
+--Mon Dieu, mon cousin! mais à quoi songez-vous donc?\r
+\r
+--Je songe, dit l'évêque, à quelque chose de singulier qui est, je\r
+crois, dans saint Augustin: «Mettez votre espérance dans celui auquel on\r
+ne succède point.»\r
+\r
+Une autre fois, recevant une lettre de faire-part du décès d'un\r
+gentilhomme du pays, où s'étalaient en une longue page, outre les\r
+dignités du défunt, toutes les qualifications féodales et nobiliaires de\r
+tous ses parents:\r
+\r
+--Quel bon dos a la mort! s'écria-t-il. Quelle admirable charge de\r
+titres on lui fait allègrement porter, et comme il faut que les hommes\r
+aient de l'esprit pour employer ainsi la tombe à la vanité!\r
+\r
+Il avait dans l'occasion une raillerie douce qui contenait presque\r
+toujours un sens sérieux. Pendant un carême, un jeune vicaire vint à\r
+Digne et prêcha dans la cathédrale. Il fut assez éloquent. Le sujet de\r
+son sermon était la charité. Il invita les riches à donner aux\r
+indigents, afin d'éviter l'enfer qu'il peignit le plus effroyable qu'il\r
+put et de gagner le paradis qu'il fit désirable et charmant. Il y avait\r
+dans l'auditoire un riche marchand retiré, un peu usurier, nommé M.\r
+Géborand, lequel avait gagné un demi-million à fabriquer de gros draps,\r
+des serges, des cadis et des gasquets. De sa vie M. Géborand n'avait\r
+fait l'aumône à un malheureux. À partir de ce sermon, on remarqua qu'il\r
+donnait tous les dimanches un sou aux vieilles mendiantes du portail de\r
+la cathédrale. Elles étaient six à se partager cela. Un jour, l'évêque\r
+le vit faisant sa charité et dit à sa soeur avec un sourire:\r
+\r
+--Voilà monsieur Géborand qui achète pour un sou de paradis.\r
+\r
+Quand il s'agissait de charité, il ne se rebutait pas, même devant un\r
+refus, et il trouvait alors des mots qui faisaient réfléchir. Une fois,\r
+il quêtait pour les pauvres dans un salon de la ville. Il y avait là le\r
+marquis de Champtercier, vieux, riche, avare, lequel trouvait moyen\r
+d'être tout ensemble ultra-royaliste et ultra-voltairien. Cette variété\r
+a existé. L'évêque, arrivé à lui, lui toucha le bras.\r
+\r
+--Monsieur le marquis, il faut que vous me donniez quelque chose.\r
+\r
+Le marquis se retourna et répondit sèchement:\r
+\r
+--Monseigneur, j'ai mes pauvres.\r
+\r
+--Donnez-les-moi, dit l'évêque.\r
+\r
+Un jour, dans la cathédrale, il fit ce sermon.\r
+\r
+«Mes très chers frères, mes bons amis, il y a en France treize cent\r
+vingt mille maisons de paysans qui n'ont que trois ouvertures, dix-huit\r
+cent dix-sept mille qui ont deux ouvertures, la porte et une fenêtre, et\r
+enfin trois cent quarante-six mille cabanes qui n'ont qu'une ouverture,\r
+la porte. Et cela, à cause d'une chose qu'on appelle l'impôt des portes\r
+et fenêtres. Mettez-moi de pauvres familles, des vieilles femmes, des\r
+petits enfants, dans ces logis-là, et voyez les fièvres et les maladies.\r
+Hélas! Dieu donne l'air aux hommes, la loi le leur vend. Je n'accuse pas\r
+la loi, mais je bénis Dieu. Dans l'Isère, dans le Var, dans les deux\r
+Alpes, les hautes et les basses, les paysans n'ont pas même de\r
+brouettes, ils transportent les engrais à dos d'hommes; ils n'ont pas de\r
+chandelles, et ils brûlent des bâtons résineux et des bouts de corde\r
+trempés dans la poix résine. C'est comme cela dans tout le pays haut du\r
+Dauphiné. Ils font le pain pour six mois, ils le font cuire avec de la\r
+bouse de vache séchée. L'hiver, ils cassent ce pain à coups de hache et\r
+ils le font tremper dans l'eau vingt-quatre heures pour pouvoir le\r
+manger.--Mes frères, ayez pitié! voyez comme on souffre autour de vous.»\r
+\r
+Né provençal, il s'était facilement familiarisé avec tous les patois du\r
+midi. Il disait: «_Eh bé! moussu, sès sagé?_» comme dans le bas\r
+Languedoc. «_Onté anaras passa?_» comme dans les basses Alpes. «_Puerte\r
+un bouen moutou embe un bouen froumage grase_», comme dans le haut\r
+Dauphiné. Ceci plaisait au peuple, et n'avait pas peu contribué à lui\r
+donner accès près de tous les esprits. Il était dans la chaumière et\r
+dans la montagne comme chez lui. Il savait dire les choses les plus\r
+grandes dans les idiomes les plus vulgaires. Parlant toutes les langues,\r
+il entrait dans toutes les âmes. Du reste, il était le même pour les\r
+gens du monde et pour les gens du peuple. Il ne condamnait rien\r
+hâtivement, et sans tenir compte des circonstances environnantes. Il\r
+disait:\r
+\r
+--Voyons le chemin par où la faute a passé.\r
+\r
+Étant, comme il se qualifiait lui-même en souriant, un _ex-pécheur_, il\r
+n'avait aucun des escarpements du rigorisme, et il professait assez\r
+haut, et sans le froncement de sourcil des vertueux féroces, une\r
+doctrine qu'on pourrait résumer à peu près ainsi:\r
+\r
+«L'homme a sur lui la chair qui est tout à la fois son fardeau et sa\r
+tentation. Il la traîne et lui cède.\r
+\r
+«Il doit la surveiller, la contenir, la réprimer, et ne lui obéir qu'à\r
+la dernière extrémité. Dans cette obéissance-là, il peut encore y avoir\r
+de la faute; mais la faute, ainsi faite, est vénielle. C'est une chute,\r
+mais une chute sur les genoux, qui peut s'achever en prière.\r
+\r
+«Être un saint, c'est l'exception; être un juste, c'est la règle. Errez,\r
+défaillez, péchez, mais soyez des justes.\r
+\r
+«Le moins de péché possible, c'est la loi de l'homme. Pas de péché du\r
+tout est le rêve de l'ange. Tout ce qui est terrestre est soumis au\r
+péché. Le péché est une gravitation.»\r
+\r
+Quand il voyait tout le monde crier bien fort et s'indigner bien vite:\r
+\r
+--Oh! oh! disait-il en souriant, il y a apparence que ceci est un gros\r
+crime que tout le monde commet. Voilà les hypocrisies effarées qui se\r
+dépêchent de protester et de se mettre à couvert.\r
+\r
+Il était indulgent pour les femmes et les pauvres sur qui pèse le poids\r
+de la société humaine. Il disait:\r
+\r
+--Les fautes des femmes, des enfants, des serviteurs, des faibles, des\r
+indigents et des ignorants sont la faute des maris, des pères, des\r
+maîtres, des forts, des riches et des savants.\r
+\r
+Il disait encore:\r
+\r
+--À ceux qui ignorent, enseignez-leur le plus de choses que vous\r
+pourrez; la société est coupable de ne pas donner l'instruction gratis;\r
+elle répond de la nuit qu'elle produit. Cette âme est pleine d'ombre, le\r
+péché s'y commet. Le coupable n'est pas celui qui y fait le péché, mais\r
+celui qui y a fait l'ombre.\r
+\r
+Comme on voit, il avait une manière étrange et à lui de juger les\r
+choses. Je soupçonne qu'il avait pris cela dans l'évangile.\r
+\r
+Il entendit un jour conter dans un salon un procès criminel qu'on\r
+instruisait et qu'on allait juger. Un misérable homme, par amour pour\r
+une femme et pour l'enfant qu'il avait d'elle, à bout de ressources,\r
+avait fait de la fausse monnaie. La fausse monnaie était encore punie de\r
+mort à cette époque. La femme avait été arrêtée émettant la première\r
+pièce fausse fabriquée par l'homme. On la tenait, mais on n'avait de\r
+preuves que contre elle. Elle seule pouvait charger son amant et le\r
+perdre en avouant. Elle nia. On insista. Elle s'obstina à nier. Sur ce,\r
+le procureur du roi avait eu une idée. Il avait supposé une infidélité\r
+de l'amant, et était parvenu, avec des fragments de lettres savamment\r
+présentés, à persuader à la malheureuse qu'elle avait une rivale et que\r
+cet homme la trompait. Alors, exaspérée de jalousie, elle avait dénoncé\r
+son amant, tout avoué, tout prouvé. L'homme était perdu. Il allait être\r
+prochainement jugé à Aix avec sa complice. On racontait le fait, et\r
+chacun s'extasiait sur l'habileté du magistrat. En mettant la jalousie\r
+en jeu, il avait fait jaillir la vérité par la colère, il avait fait\r
+sortir la justice de la vengeance. L'évêque écoutait tout cela en\r
+silence. Quand ce fut fini, il demanda:\r
+\r
+--Où jugera-t-on cet homme et cette femme?\r
+\r
+--À la cour d'assises.\r
+\r
+Il reprit:\r
+\r
+--Et où jugera-t-on monsieur le procureur du roi?\r
+\r
+Il arriva à Digne une aventure tragique. Un homme fut condamné à mort\r
+pour meurtre. C'était un malheureux pas tout à fait lettré, pas tout à\r
+fait ignorant, qui avait été bateleur dans les foires et écrivain\r
+public. Le procès occupa beaucoup la ville. La veille du jour fixé pour\r
+l'exécution du condamné, l'aumônier de la prison tomba malade. Il\r
+fallait un prêtre pour assister le patient à ses derniers moments. On\r
+alla chercher le curé. Il paraît qu'il refusa en disant: Cela ne me\r
+regarde pas. Je n'ai que faire de cette corvée et de ce saltimbanque;\r
+moi aussi, je suis malade; d'ailleurs ce n'est pas là ma place. On\r
+rapporta cette réponse à l'évêque qui dit:\r
+\r
+--Monsieur le curé a raison. Ce n'est pas sa place, c'est la mienne.\r
+\r
+Il alla sur-le-champ à la prison, il descendit au cabanon du\r
+«saltimbanque», il l'appela par son nom, lui prit la main et lui parla.\r
+Il passa toute la journée et toute la nuit près de lui, oubliant la\r
+nourriture et le sommeil, priant Dieu pour l'âme du condamné et priant\r
+le condamné pour la sienne propre. Il lui dit les meilleures vérités qui\r
+sont les plus simples. Il fut père, frère, ami; évêque pour bénir\r
+seulement. Il lui enseigna tout, en le rassurant et en le consolant. Cet\r
+homme allait mourir désespéré. La mort était pour lui comme un abîme.\r
+Debout et frémissant sur ce seuil lugubre, il reculait avec horreur. Il\r
+n'était pas assez ignorant pour être absolument indifférent. Sa\r
+condamnation, secousse profonde, avait en quelque sorte rompu çà et là\r
+autour de lui cette cloison qui nous sépare du mystère des choses et que\r
+nous appelons la vie. Il regardait sans cesse au dehors de ce monde par\r
+ces brèches fatales, et ne voyait que des ténèbres. L'évêque lui fit\r
+voir une clarté.\r
+\r
+Le lendemain, quand on vint chercher le malheureux, l'évêque était là.\r
+Il le suivit. Il se montra aux yeux de la foule en camail violet et avec\r
+sa croix épiscopale au cou, côte à côte avec ce misérable lié de cordes.\r
+\r
+Il monta sur la charrette avec lui, il monta sur l'échafaud avec lui. Le\r
+patient, si morne et si accablé la veille, était rayonnant. Il sentait\r
+que son âme était réconciliée et il espérait Dieu. L'évêque l'embrassa,\r
+et, au moment où le couteau allait tomber, il lui dit:\r
+\r
+--Celui que l'homme tue, Dieu le ressuscite; celui que les frères\r
+chassent retrouve le Père. Priez, croyez, entrez dans la vie! le Père\r
+est là.\r
+\r
+Quand il redescendit de l'échafaud, il avait quelque chose dans son\r
+regard qui fit ranger le peuple. On ne savait ce qui était le plus\r
+admirable de sa pâleur ou de sa sérénité. En rentrant à cet humble logis\r
+qu'il appelait en souriant son palais, il dit à sa soeur:\r
+\r
+--Je viens d'officier pontificalement.\r
+\r
+Comme les choses les plus sublimes sont souvent aussi les choses les\r
+moins comprises, il y eut dans la ville des gens qui dirent, en\r
+commentant cette conduite de l'évêque: «C'est de l'affectation.» Ceci ne\r
+fut du reste qu'un propos de salons. Le peuple, qui n'entend pas malice\r
+aux actions saintes, fut attendri et admira.\r
+\r
+Quant à l'évêque, avoir vu la guillotine fut pour lui un choc, et il fut\r
+longtemps à s'en remettre.\r
+\r
+L'échafaud, en effet, quand il est là, dressé et debout, a quelque chose\r
+qui hallucine. On peut avoir une certaine indifférence sur la peine de\r
+mort, ne point se prononcer, dire oui et non, tant qu'on n'a pas vu de\r
+ses yeux une guillotine; mais si l'on en rencontre une, la secousse est\r
+violente, il faut se décider et prendre parti pour ou contre. Les uns\r
+admirent, comme de Maistre; les autres exècrent, comme Beccaria. La\r
+guillotine est la concrétion de la loi; elle se nomme _vindicte;_ elle\r
+n'est pas neutre, et ne vous permet pas de rester neutre. Qui l'aperçoit\r
+frissonne du plus mystérieux des frissons. Toutes les questions sociales\r
+dressent autour de ce couperet leur point d'interrogation. L'échafaud\r
+est vision. L'échafaud n'est pas une charpente, l'échafaud n'est pas une\r
+machine, l'échafaud n'est pas une mécanique inerte faite de bois, de fer\r
+et de cordes. Il semble que ce soit une sorte d'être qui a je ne sais\r
+quelle sombre initiative; on dirait que cette charpente voit, que cette\r
+machine entend, que cette mécanique comprend, que ce bois, ce fer et ces\r
+cordes veulent. Dans la rêverie affreuse où sa présence jette l'âme,\r
+l'échafaud apparaît terrible et se mêlant de ce qu'il fait. L'échafaud\r
+est le complice du bourreau; il dévore; il mange de la chair, il boit du\r
+sang. L'échafaud est une sorte de monstre fabriqué par le juge et par le\r
+charpentier, un spectre qui semble vivre d'une espèce de vie\r
+épouvantable faite de toute la mort qu'il a donnée.\r
+\r
+Aussi l'impression fut-elle horrible et profonde; le lendemain de\r
+l'exécution et beaucoup de jours encore après, l'évêque parut accablé.\r
+La sérénité presque violente du moment funèbre avait disparu: le fantôme\r
+de la justice sociale l'obsédait. Lui qui d'ordinaire revenait de toutes\r
+ses actions avec une satisfaction si rayonnante, il semblait qu'il se\r
+fît un reproche. Par moments, il se parlait à lui-même, et bégayait à\r
+demi-voix des monologues lugubres. En voici un que sa soeur entendit un\r
+soir et recueillit:\r
+\r
+--Je ne croyais pas que cela fût si monstrueux. C'est un tort de\r
+s'absorber dans la loi divine au point de ne plus s'apercevoir de la loi\r
+humaine. La mort n'appartient qu'à Dieu. De quel droit les hommes\r
+touchent-ils à cette chose inconnue?\r
+\r
+Avec le temps ces impressions s'atténuèrent, et probablement\r
+s'effacèrent. Cependant on remarqua que l'évêque évitait désormais de\r
+passer sur la place des exécutions. On pouvait appeler M. Myriel à toute\r
+heure au chevet des malades et des mourants. Il n'ignorait pas que là\r
+était son plus grand devoir et son plus grand travail. Les familles\r
+veuves ou orphelines n'avaient pas besoin de le demander, il arrivait de\r
+lui-même. Il savait s'asseoir et se taire de longues heures auprès de\r
+l'homme qui avait perdu la femme qu'il aimait, de la mère qui avait\r
+perdu son enfant. Comme il savait le moment de se taire, il savait aussi\r
+le moment de parler. Ô admirable consolateur! il ne cherchait pas à\r
+effacer la douleur par l'oubli, mais à l'agrandir et à la dignifier par\r
+l'espérance. Il disait:\r
+\r
+--Prenez garde à la façon dont vous vous tournez vers les morts. Ne\r
+songez pas à ce qui pourrit. Regardez fixement. Vous apercevrez la lueur\r
+vivante de votre mort bien-aimé au fond du ciel.\r
+\r
+Il savait que la croyance est saine. Il cherchait à conseiller et à\r
+calmer l'homme désespéré en lui indiquant du doigt l'homme résigné, et à\r
+transformer la douleur qui regarde une fosse en lui montrant la douleur\r
+qui regarde une étoile.\r
+\r
+\r
+\r
+\r
+Chapitre V\r
+\r
+Que monseigneur Bienvenu faisait durer trop longtemps ses soutanes\r
+\r
+\r
+La vie intérieure de M. Myriel était pleine des mêmes pensées que sa vie\r
+publique. Pour qui eût pu la voir de près, c'eût été un spectacle grave\r
+et charmant que cette pauvreté volontaire dans laquelle vivait M.\r
+l'évêque de Digne.\r
+\r
+Comme tous les vieillards et comme la plupart des penseurs, il dormait\r
+peu. Ce court sommeil était profond. Le matin il se recueillait pendant\r
+une heure, puis il disait sa messe, soit à la cathédrale, soit dans son\r
+oratoire. Sa messe dite, il déjeunait d'un pain de seigle trempé dans le\r
+lait de ses vaches. Puis il travaillait.\r
+\r
+Un évêque est un homme fort occupé; il faut qu'il reçoive tous les jours\r
+le secrétaire de l'évêché, qui est d'ordinaire un chanoine, presque tous\r
+les jours ses grands vicaires. Il a des congrégations à contrôler, des\r
+privilèges à donner, toute une librairie ecclésiastique à examiner,\r
+paroissiens, catéchismes diocésains, livres d'heures, etc., des\r
+mandements à écrire, des prédications à autoriser, des curés et des\r
+maires à mettre d'accord, une correspondance cléricale, une\r
+correspondance administrative, d'un côté l'état, de l'autre le\r
+Saint-Siège, mille affaires.\r
+\r
+Le temps que lui laissaient ces mille affaires, ses offices et son\r
+bréviaire, il le donnait d'abord aux nécessiteux, aux malades et aux\r
+affligés; le temps que les affligés, les malades et les nécessiteux lui\r
+laissaient, il le donnait au travail. Tantôt il bêchait la terre dans\r
+son jardin, tantôt il lisait et écrivait. Il n'avait qu'un mot pour ces\r
+deux sortes de travail; il appelait cela _jardiner_.\r
+\r
+--L'esprit est un jardin, disait-il.\r
+\r
+À midi, il dînait. Le dîner ressemblait au déjeuner.\r
+\r
+Vers deux heures, quand le temps était beau, il sortait et se promenait\r
+à pied dans la campagne ou dans la ville, entrant souvent dans les\r
+masures. On le voyait cheminer seul, tout à ses pensées, l'oeil baissé,\r
+appuyé sur sa longue canne, vêtu de sa douillette violette ouatée et\r
+bien chaude, chaussé de bas violets dans de gros souliers, et coiffé de\r
+son chapeau plat qui laissait passer par ses trois cornes trois glands\r
+d'or à graine d'épinards.\r
+\r
+C'était une fête partout où il paraissait. On eût dit que son passage\r
+avait quelque chose de réchauffant et de lumineux. Les enfants et les\r
+vieillards venaient sur le seuil des portes pour l'évêque comme pour le\r
+soleil. Il bénissait et on le bénissait. On montrait sa maison à\r
+quiconque avait besoin de quelque chose.\r
+\r
+Çà et là, il s'arrêtait, parlait aux petits garçons et aux petites\r
+filles et souriait aux mères. Il visitait les pauvres tant qu'il avait\r
+de l'argent; quand il n'en avait plus, il visitait les riches.\r
+\r
+Comme il faisait durer ses soutanes beaucoup de temps, et qu'il ne\r
+voulait pas qu'on s'en aperçût, il ne sortait jamais dans la ville\r
+autrement qu'avec sa douillette violette. Cela le gênait un peu en été.\r
+\r
+Le soir à huit heures et demie il soupait avec sa soeur, madame Magloire\r
+debout derrière eux et les servant à table. Rien de plus frugal que ce\r
+repas. Si pourtant l'évêque avait un de ses curés à souper, madame\r
+Magloire en profitait pour servir à Monseigneur quelque excellent\r
+poisson des lacs ou quelque fin gibier de la montagne. Tout curé était\r
+un prétexte à bon repas; l'évêque se laissait faire. Hors de là, son\r
+ordinaire ne se composait guère que de légumes cuits dans l'eau et de\r
+soupe à l'huile. Aussi disait-on dans la ville:\r
+\r
+--Quand l'évêque fait pas chère de curé, il fait chère de trappiste.\r
+\r
+Après son souper, il causait pendant une demi-heure avec mademoiselle\r
+Baptistine et madame Magloire; puis il rentrait dans sa chambre et se\r
+remettait à écrire, tantôt sur des feuilles volantes, tantôt sur la\r
+marge de quelque in-folio. Il était lettré et quelque peu savant. Il a\r
+laissé cinq ou six manuscrits assez curieux; entre autres une\r
+dissertation sur le verset de la Genèse: _Au commencement l'esprit de\r
+Dieu flottait sur les eaux_. Il confronte avec ce verset trois textes:\r
+la version arabe qui dit: _Les vents de Dieu soufflaient;_ Flavius\r
+Josèphe qui dit: _Un vent d'en haut se précipitait sur la terre_, et\r
+enfin la paraphrase chaldaïque d'Onkelos qui porte: _Un vent venant de\r
+Dieu soufflait sur la face des eaux_. Dans une autre dissertation, il\r
+examine les oeuvres théologiques de Hugo, évêque de Ptolémaïs,\r
+arrière-grand-oncle de celui qui écrit ce livre, et il établit qu'il\r
+faut attribuer à cet évêque les divers opuscules publiés, au siècle\r
+dernier, sous le pseudonyme de Barleycourt.\r
+\r
+Parfois au milieu d'une lecture, quel que fût le livre qu'il eût entre\r
+les mains, il tombait tout à coup dans une méditation profonde, d'où il\r
+ne sortait que pour écrire quelques lignes sur les pages mêmes du\r
+volume. Ces lignes souvent n'ont aucun rapport avec le livre qui les\r
+contient. Nous avons sous les yeux une note écrite par lui sur une des\r
+marges d'un in-quarto intitulé: _Correspondance du lord Germain avec les\r
+généraux Clinton, Cornwallis et les amiraux de la station de l'Amérique.\r
+À Versailles, chez Poinçot, libraire, et à Paris, chez Pissot, libraire,\r
+quai des Augustins_.\r
+\r
+Voici cette note:\r
+\r
+«Ô vous qui êtes!\r
+\r
+«L'Ecclésiaste vous nomme Toute-Puissance, les Macchabées vous nomment\r
+Créateur, l'Épître aux Éphésiens vous nomme Liberté, Baruch vous nomme\r
+Immensité, les Psaumes vous nomment Sagesse et Vérité, Jean vous nomme\r
+Lumière, les Rois vous nomment Seigneur, l'Exode vous appelle\r
+Providence, le Lévitique Sainteté, Esdras Justice, la création vous\r
+nomme Dieu, l'homme vous nomme Père; mais Salomon vous nomme\r
+Miséricorde, et c'est là le plus beau de tous vos noms.»\r
+\r
+Vers neuf heures du soir, les deux femmes se retiraient et montaient à\r
+leurs chambres au premier, le laissant jusqu'au matin seul au\r
+rez-de-chaussée.\r
+\r
+Ici il est nécessaire que nous donnions une idée exacte du logis de M.\r
+l'évêque de Digne.\r
+\r
+\r
+\r
+\r
+Chapitre VI\r
+\r
+Par qui il faisait garder sa maison\r
+\r
+\r
+La maison qu'il habitait se composait, nous l'avons dit, d'un\r
+rez-de-chaussée et d'un seul étage: trois pièces au rez-de-chaussée,\r
+trois chambres au premier, au-dessus un grenier. Derrière la maison, un\r
+jardin d'un quart d'arpent. Les deux femmes occupaient le premier.\r
+L'évêque logeait en bas. La première pièce, qui s'ouvrait sur la rue,\r
+lui servait de salle à manger, la deuxième de chambre à coucher, et la\r
+troisième d'oratoire. On ne pouvait sortir de cet oratoire sans passer\r
+par la chambre à coucher, et sortir de la chambre à coucher sans passer\r
+par la salle à manger. Dans l'oratoire, au fond, il y avait une alcôve\r
+fermée, avec un lit pour les cas d'hospitalité. M. l'évêque offrait ce\r
+lit aux curés de campagne que des affaires ou les besoins de leur\r
+paroisse amenaient à Digne.\r
+\r
+La pharmacie de l'hôpital, petit bâtiment ajouté à la maison et pris sur\r
+le jardin, avait été transformée en cuisine et en cellier.\r
+\r
+Il y avait en outre dans le jardin une étable qui était l'ancienne\r
+cuisine de l'hospice et où l'évêque entretenait deux vaches. Quelle que\r
+fût la quantité de lait qu'elles lui donnassent, il en envoyait\r
+invariablement tous les matins la moitié aux malades de l'hôpital.--Je\r
+paye ma dîme, disait-il.\r
+\r
+Sa chambre était assez grande et assez difficile à chauffer dans la\r
+mauvaise saison. Comme le bois est très cher à Digne, il avait imaginé\r
+de faire faire dans l'étable à vaches un compartiment fermé d'une\r
+cloison en planches. C'était là qu'il passait ses soirées dans les\r
+grands froids. Il appelait cela son _salon d'hiver_.\r
+\r
+Il n'y avait dans ce salon d'hiver, comme dans la salle à manger,\r
+d'autres meubles qu'une table de bois blanc, carrée, et quatre chaises\r
+de paille. La salle à manger était ornée en outre d'un vieux buffet\r
+peint en rose à la détrempe. Du buffet pareil, convenablement habillé de\r
+napperons blancs et de fausses dentelles, l'évêque avait fait l'autel\r
+qui décorait son oratoire.\r
+\r
+Ses pénitentes riches et les saintes femmes de Digne s'étaient souvent\r
+cotisées pour faire les frais d'un bel autel neuf à l'oratoire de\r
+monseigneur; il avait chaque fois pris l'argent et l'avait donné aux\r
+pauvres.\r
+\r
+--Le plus beau des autels, disait-il, c'est l'âme d'un malheureux\r
+consolé qui remercie Dieu.\r
+\r
+Il avait dans son oratoire deux chaises prie-Dieu en paille, et un\r
+fauteuil à bras également en paille dans sa chambre à coucher. Quand par\r
+hasard il recevait sept ou huit personnes à la fois, le préfet, ou le\r
+général, ou l'état-major du régiment en garnison, ou quelques élèves du\r
+petit séminaire, on était obligé d'aller chercher dans l'étable les\r
+chaises du salon d'hiver, dans l'oratoire les prie-Dieu, et le fauteuil\r
+dans la chambre à coucher; de cette façon, on pouvait réunir jusqu'à\r
+onze sièges pour les visiteurs. À chaque nouvelle visite on démeublait\r
+une pièce.\r
+\r
+Il arrivait parfois qu'on était douze; alors l'évêque dissimulait\r
+l'embarras de la situation en se tenant debout devant la cheminée si\r
+c'était l'hiver, ou en proposant un tour dans le jardin si c'était\r
+l'été.\r
+\r
+Il y avait bien encore dans l'alcôve fermée une chaise, mais elle était\r
+à demi dépaillée et ne portait que sur trois pieds, ce qui faisait\r
+qu'elle ne pouvait servir qu'appuyée contre le mur. Mademoiselle\r
+Baptistine avait bien aussi dans sa chambre une très grande bergère en\r
+bois jadis doré et revêtue de pékin à fleurs, mais on avait été obligé\r
+de monter cette bergère au premier par la fenêtre, l'escalier étant trop\r
+étroit; elle ne pouvait donc pas compter parmi les en-cas du mobilier.\r
+\r
+L'ambition de mademoiselle Baptistine eût été de pouvoir acheter un\r
+meuble de salon en velours d'Utrecht jaune à rosaces et en acajou à cou\r
+de cygne, avec canapé. Mais cela eût coûté au moins cinq cents francs,\r
+et, ayant vu qu'elle n'avait réussi à économiser pour cet objet que\r
+quarante-deux francs dix sous en cinq ans, elle avait fini par y\r
+renoncer. D'ailleurs qui est-ce qui atteint son idéal?\r
+\r
+Rien de plus simple à se figurer que la chambre à coucher de l'évêque.\r
+Une porte-fenêtre donnant sur le jardin, vis-à-vis le lit; un lit\r
+d'hôpital, en fer avec baldaquin de serge verte; dans l'ombre du lit,\r
+derrière un rideau, les ustensiles de toilette trahissant encore les\r
+anciennes habitudes élégantes de l'homme du monde; deux portes, l'une\r
+près de la cheminée, donnant dans l'oratoire; l'autre, près de la\r
+bibliothèque, donnant dans la salle à manger; la bibliothèque, grande\r
+armoire vitrée pleine de livres; la cheminée, de bois peint en marbre,\r
+habituellement sans feu; dans la cheminée, une paire de chenets en fer\r
+ornés de deux vases à guirlandes et cannelures jadis argentés à l'argent\r
+haché, ce qui était un genre de luxe épiscopal; au-dessus, à l'endroit\r
+où d'ordinaire on met la glace, un crucifix de cuivre désargenté fixé\r
+sur un velours noir râpé dans un cadre de bois dédoré. Près de la\r
+porte-fenêtre, une grande table avec un encrier, chargée de papiers\r
+confus et de gros volumes. Devant la table, le fauteuil de paille.\r
+Devant le lit, un prie-Dieu, emprunté à l'oratoire.\r
+\r
+Deux portraits dans des cadres ovales étaient accrochés au mur des deux\r
+côtés du lit. De petites inscriptions dorées sur le fond neutre de la\r
+toile à côté des figures indiquaient que les portraits représentaient,\r
+l'un, l'abbé de Chaliot, évêque de Saint-Claude, l'autre, l'abbé\r
+Tourteau, vicaire général d'Agde, abbé de Grand-Champ, ordre de Cîteaux,\r
+diocèse de Chartres. L'évêque, en succédant dans cette chambre aux\r
+malades de l'hôpital, y avait trouvé ces portraits et les y avait\r
+laissés. C'étaient des prêtres, probablement des donateurs: deux motifs\r
+pour qu'il les respectât. Tout ce qu'il savait de ces deux personnages,\r
+c'est qu'ils avaient été nommés par le roi, l'un à son évêché, l'autre à\r
+son bénéfice, le même jour, le 27 avril 1785. Madame Magloire ayant\r
+décroché les tableaux pour en secouer la poussière, l'évêque avait\r
+trouvé cette particularité écrite d'une encre blanchâtre sur un petit\r
+carré de papier jauni par le temps, collé avec quatre pains à cacheter\r
+derrière le portrait de l'abbé de Grand-Champ.\r
+\r
+Il avait à sa fenêtre un antique rideau de grosse étoffe de laine qui\r
+finit par devenir tellement vieux que, pour éviter la dépense d'un neuf,\r
+madame Magloire fut obligée de faire une grande couture au beau milieu.\r
+Cette couture dessinait une croix. L'évêque le faisait souvent\r
+remarquer.\r
+\r
+--Comme cela fait bien! disait-il.\r
+\r
+Toutes les chambres de la maison, au rez-de-chaussée ainsi qu'au\r
+premier, sans exception, étaient blanchies au lait de chaux, ce qui est\r
+une mode de caserne et d'hôpital.\r
+\r
+Cependant, dans les dernières années, madame Magloire retrouva, comme on\r
+le verra plus loin, sous le papier badigeonné, des peintures qui\r
+ornaient l'appartement de mademoiselle Baptistine. Avant d'être\r
+l'hôpital, cette maison avait été le parloir aux bourgeois. De là cette\r
+décoration. Les chambres étaient pavées de briques rouges qu'on lavait\r
+toutes les semaines, avec des nattes de paille tressée devant tous les\r
+lits. Du reste, ce logis, tenu par deux femmes, était du haut en bas\r
+d'une propreté exquise. C'était le seul luxe que l'évêque permit. Il\r
+disait:\r
+\r
+--Cela ne prend rien aux pauvres.\r
+\r
+Il faut convenir cependant qu'il lui restait de ce qu'il avait possédé\r
+jadis six couverts d'argent et une grande cuiller à soupe que madame\r
+Magloire regardait tous les jours avec bonheur reluire splendidement sur\r
+la grosse nappe de toile blanche. Et comme nous peignons ici l'évêque de\r
+Digne tel qu'il était, nous devons ajouter qu'il lui était arrivé plus\r
+d'une fois de dire:\r
+\r
+--Je renoncerais difficilement à manger dans de l'argenterie.\r
+\r
+Il faut ajouter à cette argenterie deux gros flambeaux d'argent massif\r
+qui lui venaient de l'héritage d'une grand'tante. Ces flambeaux\r
+portaient deux bougies de cire et figuraient habituellement sur la\r
+cheminée de l'évêque. Quand il avait quelqu'un à dîner, madame Magloire\r
+allumait les deux bougies et mettait les deux flambeaux sur la table.\r
+\r
+Il y avait dans la chambre même de l'évêque, à la tête de son lit, un\r
+petit placard dans lequel madame Magloire serrait chaque soir les six\r
+couverts d'argent et la grande cuiller. Il faut dire qu'on n'en ôtait\r
+jamais la clef.\r
+\r
+Le jardin, un peu gâté par les constructions assez laides dont nous\r
+avons parlé, se composait de quatre allées en croix rayonnant autour\r
+d'un puisard; une autre allée faisait tout le tour du jardin et\r
+cheminait le long du mur blanc dont il était enclos. Ces allées\r
+laissaient entre elles quatre carrés bordés de buis. Dans trois, madame\r
+Magloire cultivait des légumes; dans le quatrième, l'évêque avait mis\r
+des fleurs. Il y avait çà et là quelques arbres fruitiers.\r
+\r
+Une fois madame Magloire lui avait dit avec une sorte de malice douce:\r
+\r
+--Monseigneur, vous qui tirez parti de tout, voilà pourtant un carré\r
+inutile. Il vaudrait mieux avoir là des salades que des bouquets.\r
+\r
+--Madame Magloire, répondit l'évêque, vous vous trompez. Le beau est\r
+aussi utile que l'utile.\r
+\r
+Il ajouta après un silence:\r
+\r
+--Plus peut-être.\r
+\r
+Ce carré, composé de trois ou quatre plates-bandes, occupait M. l'évêque\r
+presque autant que ses livres. Il y passait volontiers une heure ou\r
+deux, coupant, sarclant, et piquant çà et là des trous en terre où il\r
+mettait des graines. Il n'était pas aussi hostile aux insectes qu'un\r
+jardinier l'eût voulu. Du reste, aucune prétention à la botanique; il\r
+ignorait les groupes et le solidisme; il ne cherchait pas le moins du\r
+monde à décider entre Tournefort et la méthode naturelle; il ne prenait\r
+parti ni pour les utricules contre les cotylédons, ni pour Jussieu\r
+contre Linné. Il n'étudiait pas les plantes; il aimait les fleurs. Il\r
+respectait beaucoup les savants, il respectait encore plus les\r
+ignorants, et, sans jamais manquer à ces deux respects, il arrosait ses\r
+plates-bandes chaque soir d'été avec un arrosoir de fer-blanc peint en\r
+vert.\r
+\r
+La maison n'avait pas une porte qui fermât à clef. La porte de la salle\r
+à manger qui, nous l'avons dit, donnait de plain-pied sur la place de la\r
+cathédrale, était jadis armée de serrures et de verrous comme une porte\r
+de prison. L'évêque avait fait ôter toutes ces ferrures, et cette porte,\r
+la nuit comme le jour, n'était fermée qu'au loquet. Le premier passant\r
+venu, à quelque heure que ce fût, n'avait qu'à la pousser. Dans les\r
+commencements, les deux femmes avaient été fort tourmentées de cette\r
+porte jamais close; mais M. de Digne leur avait dit:\r
+\r
+--Faites mettre des verrous à vos chambres, si cela vous plaît.\r
+\r
+Elles avaient fini par partager sa confiance ou du moins par faire comme\r
+si elles la partageaient. Madame Magloire seule avait de temps en temps\r
+des frayeurs. Pour ce qui est de l'évêque, on peut trouver sa pensée\r
+expliquée ou du moins indiquée dans ces trois lignes écrites par lui sur\r
+la marge d'une bible: «Voici la nuance: la porte du médecin ne doit\r
+jamais être fermée; la porte du prêtre doit toujours être ouverte.» Sur\r
+un autre livre, intitulé _Philosophie de la science médicale_, il avait\r
+écrit cette autre note: «Est-ce que je ne suis pas médecin comme eux?\r
+Moi aussi j'ai mes malades; d'abord j'ai les leurs, qu'ils appellent les\r
+malades; et puis j'ai les miens, que j'appelle les malheureux.»\r
+\r
+Ailleurs encore il avait écrit: «Ne demandez pas son nom à qui vous\r
+demande un gîte. C'est surtout celui-là que son nom embarrasse qui a\r
+besoin d'asile.»\r
+\r
+Il advint qu'un digne curé, je ne sais plus si c'était le curé de\r
+Couloubroux ou le curé de Pompierry, s'avisa de lui demander un jour,\r
+probablement à l'instigation de madame Magloire, si Monseigneur était\r
+bien sûr de ne pas commettre jusqu'à un certain point une imprudence en\r
+laissant jour et nuit sa porte ouverte à la disposition de qui voulait\r
+entrer, et s'il ne craignait pas enfin qu'il n'arrivât quelque malheur\r
+dans une maison si peu gardée. L'évêque lui toucha l'épaule avec une\r
+gravité douce et lui dit:--_Nisi Dominus custodierit domum, in vanum\r
+vigilant qui custodiunt eam_.\r
+\r
+Puis il parla d'autre chose.\r
+\r
+Il disait assez volontiers:\r
+\r
+--Il y a la bravoure du prêtre comme il y a la bravoure du colonel de\r
+dragons. Seulement, ajoutait-il, la nôtre doit être tranquille.\r
+\r
+\r
+\r
+\r
+Chapitre VII\r
+\r
+Cravatte\r
+\r
+\r
+Ici se place naturellement un fait que nous ne devons pas omettre, car\r
+il est de ceux qui font le mieux voir quel homme c'était que M. l'évêque\r
+de Digne.\r
+\r
+Après la destruction de la bande de Gaspard Bès qui avait infesté les\r
+gorges d'Ollioules, un de ses lieutenants, Cravatte, se réfugia dans la\r
+montagne. Il se cacha quelque temps avec ses bandits, reste de la troupe\r
+de Gaspard Bès, dans le comté de Nice, puis gagna le Piémont, et tout à\r
+coup reparut en France, du côté de Barcelonnette. On le vit à Jauziers\r
+d'abord, puis aux Tuiles. Il se cacha dans les cavernes du\r
+Joug-de-l'Aigle, et de là il descendait vers les hameaux et les villages\r
+par les ravins de l'Ubaye et de l'Ubayette. Il osa même pousser jusqu'à\r
+Embrun, pénétra une nuit dans la cathédrale et dévalisa la sacristie.\r
+Ses brigandages désolaient le pays. On mit la gendarmerie à ses\r
+trousses, mais en vain. Il échappait toujours; quelquefois il résistait\r
+de vive force. C'était un hardi misérable. Au milieu de toute cette\r
+terreur, l'évêque arriva. Il faisait sa tournée. Au Chastelar, le maire\r
+vint le trouver et l'engagea à rebrousser chemin. Cravatte tenait la\r
+montagne jusqu'à l'Arche, et au-delà. Il y avait danger, même avec une\r
+escorte. C'était exposer inutilement trois ou quatre malheureux\r
+gendarmes.\r
+\r
+--Aussi, dit l'évêque, je compte aller sans escorte.\r
+\r
+--Y pensez-vous, monseigneur? s'écria le maire.\r
+\r
+--J'y pense tellement, que je refuse absolument les gendarmes et que je\r
+vais partir dans une heure.\r
+\r
+--Partir?\r
+\r
+--Partir.\r
+\r
+--Seul?\r
+\r
+--Seul.\r
+\r
+--Monseigneur! vous ne ferez pas cela.\r
+\r
+--Il y a là, dans la montagne, reprit l'évêque, une humble petite\r
+commune grande comme ça, que je n'ai pas vue depuis trois ans. Ce sont\r
+mes bons amis. De doux et honnêtes bergers. Ils possèdent une chèvre sur\r
+trente qu'ils gardent. Ils font de fort jolis cordons de laine de\r
+diverses couleurs, et ils jouent des airs de montagne sur de petites\r
+flûtes à six trous. Ils ont besoin qu'on leur parle de temps en temps du\r
+bon Dieu. Que diraient-ils d'un évêque qui a peur? Que diraient-ils si\r
+je n'y allais pas?\r
+\r
+--Mais, monseigneur, les brigands! Si vous rencontrez les brigands!\r
+\r
+--Tiens, dit l'évêque, j'y songe. Vous avez raison. Je puis les\r
+rencontrer. Eux aussi doivent avoir besoin qu'on leur parle du bon Dieu.\r
+\r
+--Monseigneur! mais c'est une bande! c'est un troupeau de loups!\r
+\r
+--Monsieur le maire, c'est peut-être précisément de ce troupeau que\r
+Jésus me fait le pasteur. Qui sait les voies de la Providence?\r
+\r
+--Monseigneur, ils vous dévaliseront.\r
+\r
+--Je n'ai rien.\r
+\r
+--Ils vous tueront.\r
+\r
+--Un vieux bonhomme de prêtre qui passe en marmottant ses momeries? Bah!\r
+à quoi bon?\r
+\r
+--Ah! mon Dieu! si vous alliez les rencontrer!\r
+\r
+--Je leur demanderai l'aumône pour mes pauvres.\r
+\r
+--Monseigneur, n'y allez pas, au nom du ciel! vous exposez votre vie.\r
+\r
+--Monsieur le maire, dit l'évêque, n'est-ce décidément que cela? Je ne\r
+suis pas en ce monde pour garder ma vie, mais pour garder les âmes.\r
+\r
+Il fallut le laisser faire. Il partit, accompagné seulement d'un enfant\r
+qui s'offrit à lui servir de guide. Son obstination fit bruit dans le\r
+pays, et effraya très fort.\r
+\r
+Il ne voulut emmener ni sa soeur ni madame Magloire. Il traversa la\r
+montagne à mulet, ne rencontra personne, et arriva sain et sauf chez ses\r
+«bons amis» les bergers. Il y resta quinze jours, prêchant,\r
+administrant, enseignant, moralisant. Lorsqu'il fut proche de son\r
+départ, il résolut de chanter pontificalement un _Te Deum_. Il en parla\r
+au curé. Mais comment faire? pas d'ornements épiscopaux. On ne pouvait\r
+mettre à sa disposition qu'une chétive sacristie de village avec\r
+quelques vieilles chasubles de damas usé ornées de galons faux.\r
+\r
+--Bah! dit l'évêque. Monsieur le curé, annonçons toujours au prône notre\r
+_Te Deum_. Cela s'arrangera.\r
+\r
+On chercha dans les églises d'alentour. Toutes les magnificences de ces\r
+humbles paroisses réunies n'auraient pas suffi à vêtir convenablement un\r
+chantre de cathédrale. Comme on était dans cet embarras, une grande\r
+caisse fut apportée et déposée au presbytère pour M. l'évêque par deux\r
+cavaliers inconnus qui repartirent sur-le-champ. On ouvrit la caisse;\r
+elle contenait une chape de drap d'or, une mitre ornée de diamants, une\r
+croix archiépiscopale, une crosse magnifique, tous les vêtements\r
+pontificaux volés un mois auparavant au trésor de Notre-Dame d'Embrun.\r
+Dans la caisse, il y avait un papier sur lequel étaient écrits ces mots:\r
+_Cravatte à monseigneur Bienvenu_.\r
+\r
+--Quand je disais que cela s'arrangerait! dit l'évêque.\r
+\r
+Puis il ajouta en souriant:\r
+\r
+--À qui se contente d'un surplis de curé, Dieu envoie une chape\r
+d'archevêque.\r
+\r
+--Monseigneur, murmura le curé en hochant la tête avec un sourire, Dieu,\r
+ou le diable.\r
+\r
+L'évêque regarda fixement le curé et reprit avec autorité:\r
+\r
+--Dieu!\r
+\r
+Quand il revint au Chastelar, et tout le long de la route, on venait le\r
+regarder par curiosité. Il retrouva au presbytère du Chastelar\r
+mademoiselle Baptistine et madame Magloire qui l'attendaient, et il dit\r
+à sa soeur:\r
+\r
+--Eh bien, avais-je raison? Le pauvre prêtre est allé chez ces pauvres\r
+montagnards les mains vides, il en revient les mains pleines. J'étais\r
+parti n'emportant que ma confiance en Dieu; je rapporte le trésor d'une\r
+cathédrale.\r
+\r
+Le soir, avant de se coucher, il dit encore:\r
+\r
+--Ne craignons jamais les voleurs ni les meurtriers. Ce sont là les\r
+dangers du dehors, les petits dangers. Craignons-nous nous-mêmes. Les\r
+préjugés, voilà les voleurs; les vices, voilà les meurtriers. Les grands\r
+dangers sont au dedans de nous. Qu'importe ce qui menace notre tête ou\r
+notre bourse! Ne songeons qu'à ce qui menace notre âme.\r
+\r
+Puis se tournant vers sa soeur:\r
+\r
+--Ma soeur, de la part du prêtre jamais de précaution contre le\r
+prochain. Ce que le prochain fait, Dieu le permet. Bornons-nous à prier\r
+Dieu quand nous croyons qu'un danger arrive sur nous. Prions-le, non\r
+pour nous, mais pour que notre frère ne tombe pas en faute à notre\r
+occasion.\r
+\r
+Du reste, les événements étaient rares dans son existence. Nous\r
+racontons ceux que nous savons; mais d'ordinaire il passait sa vie à\r
+faire toujours les mêmes choses aux mêmes moments. Un mois de son année\r
+ressemblait à une heure de sa journée.\r
+\r
+Quant à ce que devint «le trésor» de la cathédrale d'Embrun, on nous\r
+embarrasserait de nous interroger là-dessus. C'étaient là de bien belles\r
+choses, et bien tentantes, et bien bonnes à voler au profit des\r
+malheureux. Volées, elles l'étaient déjà d'ailleurs. La moitié de\r
+l'aventure était accomplie; il ne restait plus qu'à changer la direction\r
+du vol, et qu'à lui faire faire un petit bout de chemin du côté des\r
+pauvres. Nous n'affirmons rien du reste à ce sujet. Seulement on a\r
+trouvé dans les papiers de l'évêque une note assez obscure qui se\r
+rapporte peut-être à cette affaire, et qui est ainsi conçue: _La\r
+question est de savoir si cela doit faire retour à la cathédrale ou à\r
+l'hôpital_.\r
+\r
+\r
+\r
+\r
+Chapitre VIII\r
+\r
+Philosophie après boire\r
+\r
+\r
+Le sénateur dont il a été parlé plus haut était un homme entendu qui\r
+avait fait son chemin avec une rectitude inattentive à toutes ces\r
+rencontres qui font obstacle et qu'on nomme conscience, foi jurée,\r
+justice, devoir; il avait marché droit à son but et sans broncher une\r
+seule fois dans la ligne de son avancement et de son intérêt. C'était un\r
+ancien procureur, attendri par le succès, pas méchant homme du tout,\r
+rendant tous les petits services qu'il pouvait à ses fils, à ses\r
+gendres, à ses parents, même à des amis; ayant sagement pris de la vie\r
+les bons côtés, les bonnes occasions, les bonnes aubaines. Le reste lui\r
+semblait assez bête. Il était spirituel, et juste assez lettré pour se\r
+croire un disciple d'Épicure en n'étant peut-être qu'un produit de\r
+Pigault-Lebrun. Il riait volontiers, et agréablement, des choses\r
+infinies et éternelles, et des «billevesées du bonhomme évêque». Il en\r
+riait quelquefois, avec une aimable autorité, devant M. Myriel lui-même,\r
+qui écoutait.\r
+\r
+À je ne sais plus quelle cérémonie demi-officielle, le comte*** (ce\r
+sénateur) et M. Myriel durent dîner chez le préfet. Au dessert, le\r
+sénateur, un peu égayé, quoique toujours digne, s'écria:\r
+\r
+--Parbleu, monsieur l'évêque, causons. Un sénateur et un évêque se\r
+regardent difficilement sans cligner de l'oeil. Nous sommes deux\r
+augures. Je vais vous faire un aveu. J'ai ma philosophie.\r
+\r
+--Et vous avez raison, répondit l'évêque. Comme on fait sa philosophie\r
+on se couche. Vous êtes sur le lit de pourpre, monsieur le sénateur.\r
+\r
+Le sénateur, encouragé, reprit:\r
+\r
+--Soyons bons enfants.\r
+\r
+--Bons diables même, dit l'évêque.\r
+\r
+--Je vous déclare, reprit le sénateur, que le marquis d'Argens, Pyrrhon,\r
+Hobbes et M. Naigeon ne sont pas des maroufles. J'ai dans ma\r
+bibliothèque tous mes philosophes dorés sur tranche.\r
+\r
+--Comme vous-même, monsieur le comte, interrompit l'évêque.\r
+\r
+Le sénateur poursuivit:\r
+\r
+--Je hais Diderot; c'est un idéologue, un déclamateur et un\r
+révolutionnaire, au fond croyant en Dieu, et plus bigot que Voltaire.\r
+Voltaire s'est moqué de Needham, et il a eu tort; car les anguilles de\r
+Needham prouvent que Dieu est inutile. Une goutte de vinaigre dans une\r
+cuillerée de pâte de farine supplée le _fiat lux_. Supposez la goutte\r
+plus grosse et la cuillerée plus grande, vous avez le monde. L'homme,\r
+c'est l'anguille. Alors à quoi bon le Père éternel? Monsieur l'évêque,\r
+l'hypothèse Jéhovah me fatigue. Elle n'est bonne qu'à produire des gens\r
+maigres qui songent creux. À bas ce grand Tout qui me tracasse! Vive\r
+Zéro qui me laisse tranquille! De vous à moi, et pour vider mon sac, et\r
+pour me confesser à mon pasteur comme il convient, je vous avoue que\r
+j'ai du bon sens. Je ne suis pas fou de votre Jésus qui prêche à tout\r
+bout de champ le renoncement et le sacrifice. Conseil d'avare à des\r
+gueux. Renoncement! pourquoi? Sacrifice! à quoi? Je ne vois pas qu'un\r
+loup s'immole au bonheur d'un autre loup. Restons donc dans la nature.\r
+Nous sommes au sommet; ayons la philosophie supérieure. Que sert d'être\r
+en haut, si l'on ne voit pas plus loin que le bout du nez des autres?\r
+Vivons gaîment. La vie, c'est tout. Que l'homme ait un autre avenir,\r
+ailleurs, là-haut, là-bas, quelque part, je n'en crois pas un traître\r
+mot. Ah! l'on me recommande le sacrifice et le renoncement, je dois\r
+prendre garde à tout ce que je fais, il faut que je me casse la tête sur\r
+le bien et le mal, sur le juste et l'injuste, sur le _fas_ et le\r
+_nefas_. Pourquoi? parce que j'aurai à rendre compte de mes actions.\r
+Quand? après ma mort. Quel bon rêve! Après ma mort, bien fin qui me\r
+pincera. Faites donc saisir une poignée de cendre par une main d'ombre.\r
+Disons le vrai, nous qui sommes des initiés et qui avons levé la jupe\r
+d'Isis: il n'y a ni bien, ni mal; il y a de la végétation. Cherchons le\r
+réel. Creusons tout à fait. Allons au fond, que diable! Il faut flairer\r
+la vérité, fouiller sous terre, et la saisir. Alors elle vous donne des\r
+joies exquises. Alors vous devenez fort, et vous riez. Je suis carré par\r
+la base, moi. Monsieur l'évêque, l'immortalité de l'homme est un\r
+écoute-s'il-pleut. Oh! la charmante promesse! Fiez-vous-y. Le bon billet\r
+qu'a Adam! On est âme, on sera ange, on aura des ailes bleues aux\r
+omoplates. Aidez-moi donc, n'est-ce pas Tertullien qui dit que les\r
+bienheureux iront d'un astre à l'autre? Soit. On sera les sauterelles\r
+des étoiles. Et puis, on verra Dieu. Ta ta ta. Fadaises que tous ces\r
+paradis. Dieu est une sonnette monstre. Je ne dirais point cela dans le\r
+_Moniteur_, parbleu! mais je le chuchote entre amis. _Inter pocula_.\r
+Sacrifier la terre au paradis, c'est lâcher la proie pour l'ombre. Être\r
+dupe de l'infini! pas si bête. Je suis néant. Je m'appelle monsieur le\r
+comte Néant, sénateur. Étais-je avant ma naissance? Non. Serai-je après\r
+ma mort? Non. Que suis-je? un peu de poussière agrégée par un organisme.\r
+Qu'ai-je à faire sur cette terre? J'ai le choix. Souffrir ou jouir. Où\r
+me mènera la souffrance? Au néant. Mais j'aurai souffert. Où me mènera\r
+la jouissance? Au néant. Mais j'aurai joui. Mon choix est fait. Il faut\r
+être mangeant ou mangé. Je mange. Mieux vaut être la dent que l'herbe.\r
+Telle est ma sagesse. Après quoi, va comme je te pousse, le fossoyeur\r
+est là, le Panthéon pour nous autres, tout tombe dans le grand trou.\r
+Fin. _Finis_. Liquidation totale. Ceci est l'endroit de\r
+l'évanouissement. La mort est morte, croyez-moi. Qu'il y ait là\r
+quelqu'un qui ait quelque chose à me dire, je ris d'y songer. Invention\r
+de nourrices. Croquemitaine pour les enfants, Jéhovah pour les hommes.\r
+Non, notre lendemain est de la nuit. Derrière la tombe, il n'y a plus\r
+que des néants égaux. Vous avez été Sardanapale, vous avez été Vincent\r
+de Paul, cela fait le même rien. Voilà le vrai. Donc vivez, par-dessus\r
+tout. Usez de votre moi pendant que vous le tenez. En vérité, je vous le\r
+dis, monsieur l'évêque, j'ai ma philosophie, et j'ai mes philosophes. Je\r
+ne me laisse pas enguirlander par des balivernes. Après ça, il faut bien\r
+quelque chose à ceux qui sont en bas, aux va-nu-pieds, aux gagne-petit,\r
+aux misérables. On leur donne à gober les légendes, les chimères, l'âme,\r
+l'immortalité, le paradis, les étoiles. Ils mâchent cela. Ils le mettent\r
+sur leur pain sec. Qui n'a rien a le bon Dieu. C'est bien le moins. Je\r
+n'y fais point obstacle, mais je garde pour moi monsieur Naigeon. Le bon\r
+Dieu est bon pour le peuple.\r
+\r
+L'évêque battit des mains.\r
+\r
+--Voilà parler! s'écria-t-il. L'excellente chose, et vraiment\r
+merveilleuse, que ce matérialisme-là! Ne l'a pas qui veut. Ah! quand on\r
+l'a, on n'est plus dupe; on ne se laisse pas bêtement exiler comme\r
+Caton, ni lapider comme Étienne, ni brûler vif comme Jeanne d'Arc. Ceux\r
+qui ont réussi à se procurer ce matérialisme admirable ont la joie de se\r
+sentir irresponsables, et de penser qu'ils peuvent dévorer tout, sans\r
+inquiétude, les places, les sinécures, les dignités, le pouvoir bien ou\r
+mal acquis, les palinodies lucratives, les trahisons utiles, les\r
+savoureuses capitulations de conscience, et qu'ils entreront dans la\r
+tombe, leur digestion faite. Comme c'est agréable! Je ne dis pas cela\r
+pour vous, monsieur le sénateur. Cependant il m'est impossible de ne\r
+point vous féliciter. Vous autres grands seigneurs, vous avez, vous le\r
+dites, une philosophie à vous et pour vous, exquise, raffinée,\r
+accessible aux riches seuls, bonne à toutes les sauces, assaisonnant\r
+admirablement les voluptés de la vie. Cette philosophie est prise dans\r
+les profondeurs et déterrée par des chercheurs spéciaux. Mais vous êtes\r
+bons princes, et vous ne trouvez pas mauvais que la croyance au bon Dieu\r
+soit la philosophie du peuple, à peu près comme l'oie aux marrons est la\r
+dinde aux truffes du pauvre.\r
+\r
+\r
+\r
+\r
+Chapitre IX\r
+\r
+Le frère raconté par la soeur\r
+\r
+\r
+Pour donner une idée du ménage intérieur de M. l'évêque de Digne et de\r
+la façon dont ces deux saintes filles subordonnaient leurs actions,\r
+leurs pensées, même leurs instincts de femmes aisément effrayées, aux\r
+habitudes et aux intentions de l'évêque, sans qu'il eût même à prendre\r
+la peine de parler pour les exprimer, nous ne pouvons mieux faire que de\r
+transcrire ici une lettre de mademoiselle Baptistine à madame la\r
+vicomtesse de Boischevron, son amie d'enfance. Cette lettre est entre\r
+nos mains.\r
+\r
+«Digne, 16 décembre 18....\r
+\r
+«Ma bonne madame, pas un jour ne se passe sans que nous parlions de\r
+vous. C'est assez notre habitude, mais il y a une raison de plus.\r
+Figurez-vous qu'en lavant et époussetant les plafonds et les murs,\r
+madame Magloire a fait des découvertes; maintenant nos deux chambres\r
+tapissées de vieux papier blanchi à la chaux ne dépareraient pas un\r
+château dans le genre du vôtre. Madame Magloire a déchiré tout le\r
+papier. Il y avait des choses dessous. Mon salon, où il n'y a pas de\r
+meubles, et dont nous nous servons pour étendre le linge après les\r
+lessives, a quinze pieds de haut, dix-huit de large carrés, un plafond\r
+peint anciennement avec dorure, des solives comme chez vous. C'était\r
+recouvert d'une toile, du temps que c'était l'hôpital. Enfin des\r
+boiseries du temps de nos grand'mères. Mais c'est ma chambre qu'il faut\r
+voir. Madame Magloire a découvert, sous au moins dix papiers collés\r
+dessus, des peintures, sans être bonnes, qui peuvent se supporter. C'est\r
+Télémaque reçu chevalier par Minerve, c'est lui encore dans les jardins.\r
+Le nom m'échappe. Enfin où les dames romaines se rendaient une seule\r
+nuit. Que vous dirai-je? j'ai des romains, des romaines (_ici un mot\r
+illisible_), et toute la suite. Madame Magloire a débarbouillé tout\r
+cela, et cet été elle va réparer quelques petites avaries, revenir le\r
+tout, et ma chambre sera un vrai musée. Elle a trouvé aussi dans un coin\r
+du grenier deux consoles en bois, genre ancien. On demandait deux écus\r
+de six livres pour les redorer, mais il vaut bien mieux donner cela aux\r
+pauvres; d'ailleurs c'est fort laid, et j'aimerais mieux une table ronde\r
+en acajou.\r
+\r
+«Je suis toujours bien heureuse. Mon frère est si bon. Il donne tout ce\r
+qu'il a aux indigents et aux malades. Nous sommes très gênés. Le pays\r
+est dur l'hiver, et il faut bien faire quelque chose pour ceux qui\r
+manquent. Nous sommes à peu près chauffés et éclairés. Vous voyez que ce\r
+sont de grandes douceurs.\r
+\r
+«Mon frère a ses habitudes à lui. Quand il cause, il dit qu'un évêque\r
+doit être ainsi. Figurez-vous que la porte de la maison n'est jamais\r
+fermée. Entre qui veut, et l'on est tout de suite chez mon frère. Il ne\r
+craint rien, même la nuit. C'est là sa bravoure à lui, comme il dit.\r
+\r
+«Il ne veut pas que je craigne pour lui, ni que madame Magloire craigne.\r
+Il s'expose à tous les dangers, et il ne veut même pas que nous ayons\r
+l'air de nous en apercevoir. Il faut savoir le comprendre.\r
+\r
+«Il sort par la pluie, il marche dans l'eau, il voyage en hiver. Il n'a\r
+pas peur de la nuit, des routes suspectes ni des rencontres.\r
+\r
+«L'an dernier, il est allé tout seul dans un pays de voleurs. Il n'a pas\r
+voulu nous emmener. Il est resté quinze jours absent. À son retour, il\r
+n'avait rien eu, on le croyait mort, et il se portait bien, et il a dit:\r
+"Voilà comme on m'a volé!" Et il a ouvert une malle pleine de tous les\r
+bijoux de la cathédrale d'Embrun, que les voleurs lui avaient donnés.\r
+\r
+«Cette fois-là, en revenant, comme j'étais allée à sa rencontre à deux\r
+lieues avec d'autres de ses amis, je n'ai pu m'empêcher de le gronder un\r
+peu, en ayant soin de ne parler que pendant que la voiture faisait du\r
+bruit, afin que personne autre ne pût entendre.\r
+\r
+«Dans les premiers temps, je me disais: il n'y a pas de dangers qui\r
+l'arrêtent, il est terrible. À présent j'ai fini par m'y accoutumer. Je\r
+fais signe à madame Magloire pour qu'elle ne le contrarie pas. Il se\r
+risque comme il veut. Moi j'emmène madame Magloire, je rentre dans ma\r
+chambre, je prie pour lui, et je m'endors. Je suis tranquille, parce que\r
+je sais bien que s'il lui arrivait malheur, ce serait ma fin. Je m'en\r
+irais au bon Dieu avec mon frère et mon évêque. Madame Magloire a eu\r
+plus de peine que moi à s'habituer à ce qu'elle appelait ses\r
+imprudences. Mais à présent le pli est pris. Nous prions toutes les\r
+deux, nous avons peur ensemble, et nous nous endormons. Le diable\r
+entrerait dans la maison qu'on le laisserait faire. Après tout, que\r
+craignons-nous dans cette maison? Il y a toujours quelqu'un avec nous,\r
+qui est le plus fort. Le diable peut y passer, mais le bon Dieu\r
+l'habite.\r
+\r
+«Voilà qui me suffit. Mon frère n'a plus même besoin de me dire un mot\r
+maintenant. Je le comprends sans qu'il parle, et nous nous abandonnons à\r
+la Providence.\r
+\r
+«Voilà comme il faut être avec un homme qui a du grand dans l'esprit.\r
+\r
+«J'ai questionné mon frère pour le renseignement que vous me demandez\r
+sur la famille de Faux. Vous savez comme il sait tout et comme il a des\r
+souvenirs, car il est toujours très bon royaliste. C'est de vrai une\r
+très ancienne famille normande de la généralité de Caen. Il y a cinq\r
+cents ans d'un Raoul de Faux, d'un Jean de Faux et d'un Thomas de Faux,\r
+qui étaient des gentilshommes, dont un seigneur de Rochefort. Le dernier\r
+était Guy-Étienne-Alexandre, et était maître de camp, et quelque chose\r
+dans les chevaux-légers de Bretagne. Sa fille Marie-Louise a épousé\r
+Adrien-Charles de Gramont, fils du duc Louis de Gramont, pair de France,\r
+colonel des gardes françaises et lieutenant général des armées. On écrit\r
+Faux, Fauq et Faoucq.\r
+\r
+«Bonne madame, recommandez-nous aux prières de votre saint parent, M. le\r
+cardinal. Quant à votre chère Sylvanie, elle a bien fait de ne pas\r
+prendre les courts instants qu'elle passe près de vous pour m'écrire.\r
+Elle se porte bien, travaille selon vos désirs, m'aime toujours. C'est\r
+tout ce que je veux. Son souvenir par vous m'est arrivé. Je m'en trouve\r
+heureuse. Ma santé n'est pas trop mauvaise, et cependant je maigris tous\r
+les jours davantage. Adieu, le papier me manque et me force de vous\r
+quitter. Mille bonnes choses.\r
+\r
+«Baptistine.\r
+\r
+«P. S. Madame votre belle-soeur est toujours ici avec sa jeune famille.\r
+Votre petit-neveu est charmant. Savez-vous qu'il a cinq ans bientôt!\r
+Hier il a vu passer un cheval auquel on avait mis des genouillères, et\r
+il disait: "Qu'est-ce qu'il a donc aux genoux?" Il est si gentil, cet\r
+enfant! Son petit frère traîne un vieux balai dans l'appartement comme\r
+une voiture, et dit: "Hu!"\r
+\r
+»Comme on le voit par cette lettre, ces deux femmes savaient se plier\r
+aux façons d'être de l'évêque avec ce génie particulier de la femme qui\r
+comprend l'homme mieux que l'homme ne se comprend. L'évêque de Digne,\r
+sous cet air doux et candide qui ne se démentait jamais, faisait parfois\r
+des choses grandes, hardies et magnifiques, sans paraître même s'en\r
+douter. Elles en tremblaient, mais elles le laissaient faire.\r
+Quelquefois madame Magloire essayait une remontrance avant; jamais\r
+pendant ni après. Jamais on ne le troublait, ne fût-ce que par un signe,\r
+dans une action commencée. À de certains moments, sans qu'il eût besoin\r
+de le dire, lorsqu'il n'en avait peut-être pas lui-même conscience, tant\r
+sa simplicité était parfaite, elles sentaient vaguement qu'il agissait\r
+comme évêque; alors elles n'étaient plus que deux ombres dans la maison.\r
+Elles le servaient passivement, et, si c'était obéir que de disparaître,\r
+elles disparaissaient. Elles savaient, avec une admirable délicatesse\r
+d'instinct, que certaines sollicitudes peuvent gêner. Aussi, même le\r
+croyant en péril, elles comprenaient, je ne dis pas sa pensée, mais sa\r
+nature, jusqu'au point de ne plus veiller sur lui. Elles le confiaient à\r
+Dieu.\r
+\r
+D'ailleurs Baptistine disait, comme on vient de le lire, que la fin de\r
+son frère serait la sienne. Madame Magloire ne le disait pas, mais elle\r
+le savait.\r
+\r
+\r
+\r
+\r
+Chapitre X\r
+\r
+L'évêque en présence d'une lumière inconnue\r
+\r
+\r
+À une époque un peu postérieure à la date de la lettre citée dans les\r
+pages précédentes, il fit une chose, à en croire toute la ville, plus\r
+risquée encore que sa promenade à travers les montagnes des bandits. Il\r
+y avait près de Digne, dans la campagne, un homme qui vivait solitaire.\r
+Cet homme, disons tout de suite le gros mot, était un ancien\r
+conventionnel. Il se nommait G.\r
+\r
+On parlait du conventionnel G. dans le petit monde de Digne avec une\r
+sorte d'horreur. Un conventionnel, vous figurez-vous cela? Cela existait\r
+du temps qu'on se tutoyait et qu'on disait: citoyen. Cet homme était à\r
+peu près un monstre. Il n'avait pas voté la mort du roi, mais presque.\r
+C'était un quasi-régicide. Il avait été terrible. Comment, au retour des\r
+princes légitimes, n'avait-on pas traduit cet homme-là devant une cour\r
+prévôtale? On ne lui eût pas coupé la tête, si vous voulez, il faut de\r
+la clémence, soit; mais un bon bannissement à vie. Un exemple enfin!\r
+etc., etc. C'était un athée d'ailleurs, comme tous ces\r
+gens-là.--Commérages des oies sur le vautour.\r
+\r
+Était-ce du reste un vautour que G.? Oui, si l'on en jugeait par ce\r
+qu'il y avait de farouche dans sa solitude. N'ayant pas voté la mort du\r
+roi, il n'avait pas été compris dans les décrets d'exil et avait pu\r
+rester en France.\r
+\r
+Il habitait, à trois quarts d'heure de la ville, loin de tout hameau,\r
+loin de tout chemin, on ne sait quel repli perdu d'un vallon très\r
+sauvage. Il avait là, disait-on, une espèce de champ, un trou, un\r
+repaire. Pas de voisins; pas même de passants. Depuis qu'il demeurait\r
+dans ce vallon, le sentier qui y conduisait avait disparu sous l'herbe.\r
+On parlait de cet endroit-là comme de la maison du bourreau. Pourtant\r
+l'évêque songeait, et de temps en temps regardait l'horizon à l'endroit\r
+où un bouquet d'arbres marquait le vallon du vieux conventionnel, et il\r
+disait:\r
+\r
+--Il y a là une âme qui est seule.\r
+\r
+Et au fond de sa pensée il ajoutait: «Je lui dois ma visite.»\r
+\r
+Mais, avouons-le, cette idée, au premier abord naturelle, lui\r
+apparaissait, après un moment de réflexion, comme étrange et impossible,\r
+et presque repoussante. Car, au fond, il partageait l'impression\r
+générale, et le conventionnel lui inspirait, sans qu'il s'en rendît\r
+clairement compte, ce sentiment qui est comme la frontière de la haine\r
+et qu'exprime si bien le mot éloignement.\r
+\r
+Toutefois, la gale de la brebis doit-elle faire reculer le pasteur? Non.\r
+Mais quelle brebis!\r
+\r
+Le bon évêque était perplexe. Quelquefois il allait de ce côté-là, puis\r
+il revenait. Un jour enfin le bruit se répandit dans la ville qu'une\r
+façon de jeune pâtre qui servait le conventionnel G. dans sa bauge était\r
+venu chercher un médecin; que le vieux scélérat se mourait, que la\r
+paralysie le gagnait, et qu'il ne passerait pas la nuit.\r
+\r
+--Dieu merci! ajoutaient quelques-uns.\r
+\r
+L'évêque prit son bâton, mit son pardessus à cause de sa soutane un peu\r
+trop usée, comme nous l'avons dit, et aussi à cause du vent du soir qui\r
+ne devait pas tarder à souffler, et partit.\r
+\r
+Le soleil déclinait et touchait presque à l'horizon, quand l'évêque\r
+arriva à l'endroit excommunié. Il reconnut avec un certain battement de\r
+coeur qu'il était près de la tanière. Il enjamba un fossé, franchit une\r
+haie, leva un échalier, entra dans un courtil délabré, fit quelques pas\r
+assez hardiment, et tout à coup, au fond de la friche, derrière une\r
+haute broussaille, il aperçut la caverne.\r
+\r
+C'était une cabane toute basse, indigente, petite et propre, avec une\r
+treille clouée à la façade.\r
+\r
+Devant la porte, dans une vieille chaise à roulettes, fauteuil du\r
+paysan, il y avait un homme en cheveux blancs qui souriait au soleil.\r
+\r
+Près du vieillard assis se tenait debout un jeune garçon, le petit\r
+pâtre. Il tendait au vieillard une jatte de lait.\r
+\r
+Pendant que l'évêque regardait, le vieillard éleva la voix:\r
+\r
+--Merci, dit-il, je n'ai plus besoin de rien.\r
+\r
+Et son sourire quitta le soleil pour s'arrêter sur l'enfant.\r
+\r
+L'évêque s'avança. Au bruit qu'il fit en marchant, le vieux homme assis\r
+tourna la tête, et son visage exprima toute la quantité de surprise\r
+qu'on peut avoir après une longue vie.\r
+\r
+--Depuis que je suis ici, dit-il, voilà la première fois qu'on entre\r
+chez moi. Qui êtes-vous, monsieur?\r
+\r
+L'évêque répondit:\r
+\r
+--Je me nomme Bienvenu Myriel.\r
+\r
+--Bienvenu Myriel! j'ai entendu prononcer ce nom. Est-ce que c'est vous\r
+que le peuple appelle monseigneur Bienvenu?\r
+\r
+--C'est moi.\r
+\r
+Le vieillard reprit avec un demi-sourire:\r
+\r
+--En ce cas, vous êtes mon évêque?\r
+\r
+--Un peu.\r
+\r
+--Entrez, monsieur.\r
+\r
+Le conventionnel tendit la main à l'évêque, mais l'évêque ne la prit\r
+pas. L'évêque se borna à dire:\r
+\r
+--Je suis satisfait de voir qu'on m'avait trompé. Vous ne me semblez,\r
+certes, pas malade.\r
+\r
+--Monsieur, répondit le vieillard, je vais guérir.\r
+\r
+Il fit une pause et dit:\r
+\r
+--Je mourrai dans trois heures.\r
+\r
+Puis il reprit:\r
+\r
+--Je suis un peu médecin; je sais de quelle façon la dernière heure\r
+vient. Hier, je n'avais que les pieds froids; aujourd'hui, le froid a\r
+gagné les genoux; maintenant je le sens qui monte jusqu'à la ceinture;\r
+quand il sera au coeur, je m'arrêterai. Le soleil est beau, n'est-ce\r
+pas? je me suis fait rouler dehors pour jeter un dernier coup d'oeil sur\r
+les choses, vous pouvez me parler, cela ne me fatigue point. Vous faites\r
+bien de venir regarder un homme qui va mourir. Il est bon que ce\r
+moment-là ait des témoins. On a des manies; j'aurais voulu aller jusqu'à\r
+l'aube. Mais je sais que j'en ai à peine pour trois heures. Il fera\r
+nuit. Au fait, qu'importe! Finir est une affaire simple. On n'a pas\r
+besoin du matin pour cela. Soit. Je mourrai à la belle étoile.\r
+\r
+Le vieillard se tourna vers le pâtre.\r
+\r
+--Toi, va te coucher. Tu as veillé l'autre nuit. Tu es fatigué.\r
+\r
+L'enfant rentra dans la cabane.\r
+\r
+Le vieillard le suivit des yeux et ajouta comme se parlant à lui-même:\r
+\r
+--Pendant qu'il dormira, je mourrai. Les deux sommeils peuvent faire bon\r
+voisinage.\r
+\r
+L'évêque n'était pas ému comme il semble qu'il aurait pu l'être. Il ne\r
+croyait pas sentir Dieu dans cette façon de mourir. Disons tout, car les\r
+petites contradictions des grands coeurs veulent être indiquées comme le\r
+reste, lui qui, dans l'occasion, riait si volontiers de Sa Grandeur, il\r
+était quelque peu choqué de ne pas être appelé monseigneur, et il était\r
+presque tenté de répliquer: citoyen. Il lui vint une velléité de\r
+familiarité bourrue, assez ordinaire aux médecins et aux prêtres, mais\r
+qui ne lui était pas habituelle, à lui. Cet homme, après tout, ce\r
+conventionnel, ce représentant du peuple, avait été un puissant de la\r
+terre; pour la première fois de sa vie peut-être, l'évêque se sentit en\r
+humeur de sévérité.\r
+\r
+Le conventionnel cependant le considérait avec une cordialité modeste,\r
+où l'on eût pu démêler l'humilité qui sied quand on est si près de sa\r
+mise en poussière.\r
+\r
+L'évêque, de son côté, quoiqu'il se gardât ordinairement de la\r
+curiosité, laquelle, selon lui, était contiguë à l'offense, ne pouvait\r
+s'empêcher d'examiner le conventionnel avec une attention qui, n'ayant\r
+pas sa source dans la sympathie, lui eût été probablement reprochée par\r
+sa conscience vis-à-vis de tout autre homme. Un conventionnel lui\r
+faisait un peu l'effet d'être hors la loi, même hors la loi de charité.\r
+\r
+G., calme, le buste presque droit, la voix vibrante, était un de ces\r
+grands octogénaires qui font l'étonnement du physiologiste. La\r
+révolution a eu beaucoup de ces hommes proportionnés à l'époque. On\r
+sentait dans ce vieillard l'homme à l'épreuve. Si près de sa fin, il\r
+avait conservé tous les gestes de la santé. Il y avait dans son coup\r
+d'oeil clair, dans son accent ferme, dans son robuste mouvement\r
+d'épaules, de quoi déconcerter la mort. Azraël, l'ange mahométan du\r
+sépulcre, eût rebroussé chemin et eût cru se tromper de porte. G.\r
+semblait mourir parce qu'il le voulait bien. Il y avait de la liberté\r
+dans son agonie. Les jambes seulement étaient immobiles. Les ténèbres le\r
+tenaient par là. Les pieds étaient morts et froids, et la tête vivait de\r
+toute la puissance de la vie et paraissait en pleine lumière. G., en ce\r
+grave moment, ressemblait à ce roi du conte oriental, chair par en haut,\r
+marbre par en bas.\r
+\r
+Une pierre était là. L'évêque s'y assit. L'exorde fut _ex abrupto_.\r
+\r
+--Je vous félicite, dit-il du ton dont on réprimande. Vous n'avez\r
+toujours pas voté la mort du roi.\r
+\r
+Le conventionnel ne parut pas remarquer le sous-entendu amer caché dans\r
+ce mot: toujours. Il répondit. Tout sourire avait disparu de sa face.\r
+\r
+--Ne me félicitez pas trop, monsieur; j'ai voté la fin du tyran.\r
+\r
+C'était l'accent austère en présence de l'accent sévère.\r
+\r
+--Que voulez-vous dire? reprit l'évêque.\r
+\r
+--Je veux dire que l'homme a un tyran, l'ignorance. J'ai voté la fin de\r
+ce tyran-là. Ce tyran-là a engendré la royauté qui est l'autorité prise\r
+dans le faux, tandis que la science est l'autorité prise dans le vrai.\r
+L'homme ne doit être gouverné que par la science.\r
+\r
+--Et la conscience, ajouta l'évêque.\r
+\r
+--C'est la même chose. La conscience, c'est la quantité de science innée\r
+que nous avons en nous.\r
+\r
+Monseigneur Bienvenu écoutait, un peu étonné, ce langage très nouveau\r
+pour lui. Le conventionnel poursuivit:\r
+\r
+--Quant à Louis XVI, j'ai dit non. Je ne me crois pas le droit de tuer\r
+un homme; mais je me sens le devoir d'exterminer le mal. J'ai voté la\r
+fin du tyran. C'est-à-dire la fin de la prostitution pour la femme, la\r
+fin de l'esclavage pour l'homme, la fin de la nuit pour l'enfant. En\r
+votant la république, j'ai voté cela. J'ai voté la fraternité, la\r
+concorde, l'aurore! J'ai aidé à la chute des préjugés et des erreurs.\r
+Les écroulements des erreurs et des préjugés font de la lumière. Nous\r
+avons fait tomber le vieux monde, nous autres, et le vieux monde, vase\r
+des misères, en se renversant sur le genre humain, est devenu une urne\r
+de joie.\r
+\r
+--Joie mêlée, dit l'évêque.\r
+\r
+--Vous pourriez dire joie troublée, et aujourd'hui, après ce fatal\r
+retour du passé qu'on nomme 1814, joie disparue. Hélas, l'oeuvre a été\r
+incomplète, j'en conviens; nous avons démoli l'ancien régime dans les\r
+faits, nous n'avons pu entièrement le supprimer dans les idées. Détruire\r
+les abus, cela ne suffit pas; il faut modifier les moeurs. Le moulin n'y\r
+est plus, le vent y est encore.\r
+\r
+--Vous avez démoli. Démolir peut être utile; mais je me défie d'une\r
+démolition compliquée de colère.\r
+\r
+--Le droit a sa colère, monsieur l'évêque, et la colère du droit est un\r
+élément du progrès. N'importe, et quoi qu'on en dise, la révolution\r
+française est le plus puissant pas du genre humain depuis l'avènement du\r
+Christ. Incomplète, soit; mais sublime. Elle a dégagé toutes les\r
+inconnues sociales. Elle a adouci les esprits; elle a calmé, apaisé,\r
+éclairé; elle a fait couler sur la terre des flots de civilisation. Elle\r
+a été bonne. La révolution française, c'est le sacre de l'humanité.\r
+\r
+L'évêque ne put s'empêcher de murmurer:\r
+\r
+--Oui? 93!\r
+\r
+Le conventionnel se dressa sur sa chaise avec une solennité presque\r
+lugubre, et, autant qu'un mourant peut s'écrier, il s'écria:\r
+\r
+--Ah! vous y voilà! 93! J'attendais ce mot-là. Un nuage s'est formé\r
+pendant quinze cents ans. Au bout de quinze siècles, il a crevé. Vous\r
+faites le procès au coup de tonnerre.\r
+\r
+L'évêque sentit, sans se l'avouer peut-être, que quelque chose en lui\r
+était atteint. Pourtant il fit bonne contenance. Il répondit:\r
+\r
+--Le juge parle au nom de la justice; le prêtre parle au nom de la\r
+pitié, qui n'est autre chose qu'une justice plus élevée. Un coup de\r
+tonnerre ne doit pas se tromper.\r
+\r
+Et il ajouta en regardant fixement le conventionnel.\r
+\r
+--Louis XVII?\r
+\r
+Le conventionnel étendit la main et saisit le bras de l'évêque:\r
+\r
+--Louis XVII! Voyons, sur qui pleurez-vous? Est-ce sur l'enfant\r
+innocent? alors, soit. Je pleure avec vous. Est-ce sur l'enfant royal?\r
+je demande à réfléchir. Pour moi, le frère de Cartouche, enfant\r
+innocent, pendu sous les aisselles en place de Grève jusqu'à ce que mort\r
+s'ensuive, pour le seul crime d'avoir été le frère de Cartouche, n'est\r
+pas moins douloureux que le petit-fils de Louis XV, enfant innocent,\r
+martyrisé dans la tour du Temple pour le seul crime d'avoir été le\r
+petit-fils de Louis XV.\r
+\r
+--Monsieur, dit l'évêque, je n'aime pas ces rapprochements de noms.\r
+\r
+--Cartouche? Louis XV? pour lequel des deux réclamez-vous?\r
+\r
+Il y eut un moment de silence. L'évêque regrettait presque d'être venu,\r
+et pourtant il se sentait vaguement et étrangement ébranlé.\r
+\r
+Le conventionnel reprit:\r
+\r
+--Ah! monsieur le prêtre, vous n'aimez pas les crudités du vrai. Christ\r
+les aimait, lui. Il prenait une verge et il époussetait le temple. Son\r
+fouet plein d'éclairs était un rude diseur de vérités. Quand il\r
+s'écriait: _Sinite parvulos_..., il ne distinguait pas entre les petits\r
+enfants. Il ne se fût pas gêné de rapprocher le dauphin de Barabbas du\r
+dauphin d'Hérode. Monsieur, l'innocence est sa couronne à elle-même.\r
+L'innocence n'a que faire d'être altesse. Elle est aussi auguste\r
+déguenillée que fleurdelysée.\r
+\r
+--C'est vrai, dit l'évêque à voix basse.\r
+\r
+--J'insiste, continua le conventionnel G. Vous m'avez nommé Louis XVII.\r
+Entendons-nous. Pleurons-nous sur tous les innocents, sur tous les\r
+martyrs, sur tous les enfants, sur ceux d'en bas comme sur ceux d'en\r
+haut? J'en suis. Mais alors, je vous l'ai dit, il faut remonter plus\r
+haut que 93, et c'est avant Louis XVII qu'il faut commencer nos larmes.\r
+Je pleurerai sur les enfants des rois avec vous, pourvu que vous\r
+pleuriez avec moi sur les petits du peuple.\r
+\r
+--Je pleure sur tous, dit l'évêque.\r
+\r
+--Également! s'écria G., et si la balance doit pencher, que ce soit du\r
+côté du peuple. Il y a plus longtemps qu'il souffre.\r
+\r
+Il y eut encore un silence. Ce fut le conventionnel qui le rompit. Il se\r
+souleva sur un coude, prit entre son pouce et son index replié un peu de\r
+sa joue, comme on fait machinalement lorsqu'on interroge et qu'on juge,\r
+et interpella l'évêque avec un regard plein de toutes les énergies de\r
+l'agonie. Ce fut presque une explosion.\r
+\r
+--Oui, monsieur, il y a longtemps que le peuple souffre. Et puis, tenez,\r
+ce n'est pas tout cela, que venez-vous me questionner et me parler de\r
+Louis XVII? Je ne vous connais pas, moi. Depuis que je suis dans ce\r
+pays, j'ai vécu dans cet enclos, seul, ne mettant pas les pieds dehors,\r
+ne vient personne que cet enfant qui m'aide. Votre nom est, il est vrai,\r
+arrivé confusément jusqu'à moi, et, je dois le dire, pas très mal\r
+prononcé; mais cela ne signifie rien; les gens habiles ont tant de\r
+manières d'en faire accroire à ce brave bonhomme de peuple. À propos, je\r
+n'ai pas entendu le bruit de votre voiture, vous l'aurez sans doute\r
+laissée derrière le taillis, là-bas, à l'embranchement de la route. Je\r
+ne vous connais pas, vous dis-je. Vous m'avez dit que vous étiez\r
+l'évêque, mais cela ne me renseigne point sur votre personne morale. En\r
+somme, je vous répète ma question. Qui êtes-vous? Vous êtes un évêque,\r
+c'est-à-dire un prince de l'église, un de ces hommes dorés, armoriés,\r
+rentés, qui ont de grosses prébendes--l'évêché de Digne, quinze mille\r
+francs de fixe, dix mille francs de casuel, total, vingt-cinq mille\r
+francs--, qui ont des cuisines, qui ont des livrées, qui font bonne\r
+chère, qui mangent des poules d'eau le vendredi, qui se pavanent,\r
+laquais devant, laquais derrière, en berline de gala, et qui ont des\r
+palais, et qui roulent carrosse au nom de Jésus-Christ qui allait pieds\r
+nus! Vous êtes un prélat; rentes, palais, chevaux, valets, bonne table,\r
+toutes les sensualités de la vie, vous avez cela comme les autres, et\r
+comme les autres vous en jouissez, c'est bien, mais cela en dit trop ou\r
+pas assez; cela ne m'éclaire pas sur votre valeur intrinsèque et\r
+essentielle, à vous qui venez avec la prétention probable de m'apporter\r
+de la sagesse. À qui est-ce que je parle? Qui êtes-vous?\r
+\r
+L'évêque baissa la tête et répondit:\r
+\r
+--_Vermis sum_.\r
+\r
+--Un ver de terre en carrosse! grommela le conventionnel.\r
+\r
+C'était le tour du conventionnel d'être hautain, et de l'évêque d'être\r
+humble.\r
+\r
+L'évêque reprit avec douceur.\r
+\r
+--Monsieur, soit. Mais expliquez-moi en quoi mon carrosse, qui est là à\r
+deux pas derrière les arbres, en quoi ma bonne table et les poules d'eau\r
+que je mange le vendredi, en quoi mes vingt-cinq mille livres de rentes,\r
+en quoi mon palais et mes laquais prouvent que la pitié n'est pas une\r
+vertu, que la clémence n'est pas un devoir, et que 93 n'a pas été\r
+inexorable.\r
+\r
+Le conventionnel passa la main sur son front comme pour en écarter un\r
+nuage.\r
+\r
+--Avant de vous répondre, dit-il, je vous prie de me pardonner. Je viens\r
+d'avoir un tort, monsieur. Vous êtes chez moi, vous êtes mon hôte. Je\r
+vous dois courtoisie. Vous discutez mes idées, il sied que je me borne à\r
+combattre vos raisonnements. Vos richesses et vos jouissances sont des\r
+avantages que j'ai contre vous dans le débat, mais il est de bon goût de\r
+ne pas m'en servir. Je vous promets de ne plus en user.\r
+\r
+--Je vous remercie, dit l'évêque.\r
+\r
+G. reprit:\r
+\r
+--Revenons à l'explication que vous me demandiez. Où en étions-nous? Que\r
+me disiez-vous? que 93 a été inexorable?\r
+\r
+--Inexorable, oui, dit l'évêque. Que pensez-vous de Marat battant des\r
+mains à la guillotine?\r
+\r
+--Que pensez-vous de Bossuet chantant le _Te Deum_ sur les dragonnades?\r
+\r
+La réponse était dure, mais elle allait au but avec la rigidité d'une\r
+pointe d'acier. L'évêque en tressaillit; il ne lui vint aucune riposte,\r
+mais il était froissé de cette façon de nommer Bossuet. Les meilleurs\r
+esprits ont leurs fétiches, et parfois se sentent vaguement meurtris des\r
+manques de respect de la logique.\r
+\r
+Le conventionnel commençait à haleter; l'asthme de l'agonie, qui se mêle\r
+aux derniers souffles, lui entrecoupait la voix; cependant il avait\r
+encore une parfaite lucidité d'âme dans les yeux. Il continua:\r
+\r
+--Disons encore quelques mots çà et là, je veux bien. En dehors de la\r
+révolution qui, prise dans son ensemble, est une immense affirmation\r
+humaine, 93, hélas! est une réplique. Vous le trouvez inexorable, mais\r
+toute la monarchie, monsieur? Carrier est un bandit; mais quel nom\r
+donnez-vous à Montrevel? Fouquier-Tinville est un gueux, mais quel est\r
+votre avis sur Lamoignon-Bâville? Maillard est affreux, mais\r
+Saulx-Tavannes, s'il vous plaît? Le père Duchêne est féroce, mais quelle\r
+épithète m'accorderez-vous pour le père Letellier? Jourdan-Coupe-Tête\r
+est un monstre, mais moindre que M. le marquis de Louvois. Monsieur,\r
+monsieur, je plains Marie-Antoinette, archiduchesse et reine, mais je\r
+plains aussi cette pauvre femme huguenote qui, en 1685, sous Louis le\r
+Grand, monsieur, allaitant son enfant, fut liée, nue jusqu'à la\r
+ceinture, à un poteau, l'enfant tenu à distance; le sein se gonflait de\r
+lait et le coeur d'angoisse. Le petit, affamé et pâle, voyait ce sein,\r
+agonisait et criait, et le bourreau disait à la femme, mère et nourrice:\r
+«Abjure!» lui donnant à choisir entre la mort de son enfant et la mort\r
+de sa conscience. Que dites-vous de ce supplice de Tantale accommodé à\r
+une mère? Monsieur, retenez bien ceci: la révolution française a eu ses\r
+raisons. Sa colère sera absoute par l'avenir. Son résultat, c'est le\r
+monde meilleur. De ses coups les plus terribles, il sort une caresse\r
+pour le genre humain. J'abrège. Je m'arrête, j'ai trop beau jeu.\r
+D'ailleurs je me meurs.\r
+\r
+Et, cessant de regarder l'évêque, le conventionnel acheva sa pensée en\r
+ces quelques mots tranquilles:\r
+\r
+--Oui, les brutalités du progrès s'appellent révolutions. Quand elles\r
+sont finies, on reconnaît ceci: que le genre humain a été rudoyé, mais\r
+qu'il a marché.\r
+\r
+Le conventionnel ne se doutait pas qu'il venait d'emporter\r
+successivement l'un après l'autre tous les retranchements intérieurs de\r
+l'évêque. Il en restait un pourtant, et de ce retranchement, suprême\r
+ressource de la résistance de monseigneur Bienvenu, sortit cette parole\r
+où reparut presque toute la rudesse du commencement:\r
+\r
+--Le progrès doit croire en Dieu. Le bien ne peut pas avoir de serviteur\r
+impie. C'est un mauvais conducteur du genre humain que celui qui est\r
+athée.\r
+\r
+Le vieux représentant du peuple ne répondit pas. Il eut un tremblement.\r
+Il regarda le ciel, et une larme germa lentement dans ce regard. Quand\r
+la paupière fut pleine, la larme coula le long de sa joue livide, et il\r
+dit presque en bégayant, bas et se parlant à lui-même, l'oeil perdu dans\r
+les profondeurs:\r
+\r
+--O toi! ô idéal! toi seul existes!\r
+\r
+L'évêque eut une sorte d'inexprimable commotion. Après un silence, le\r
+vieillard leva un doigt vers le ciel, et dit:\r
+\r
+--L'infini est. Il est là. Si l'infini n'avait pas de moi, le moi serait\r
+sa borne; il ne serait pas infini; en d'autres termes, il ne serait pas.\r
+Or il est. Donc il a un moi. Ce moi de l'infini, c'est Dieu.\r
+\r
+Le mourant avait prononcé ces dernières paroles d'une voix haute et avec\r
+le frémissement de l'extase, comme s'il voyait quelqu'un. Quand il eut\r
+parlé, ses yeux se fermèrent. L'effort l'avait épuisé. Il était évident\r
+qu'il venait de vivre en une minute les quelques heures qui lui\r
+restaient. Ce qu'il venait de dire l'avait approché de celui qui est\r
+dans la mort. L'instant suprême arrivait.\r
+\r
+L'évêque le comprit, le moment pressait, c'était comme prêtre qu'il\r
+était venu; de l'extrême froideur, il était passé par degrés à l'émotion\r
+extrême; il regarda ces yeux fermés, il prit cette vieille main ridée et\r
+glacée, et se pencha vers le moribond:\r
+\r
+--Cette heure est celle de Dieu. Ne trouvez-vous pas qu'il serait\r
+regrettable que nous nous fussions rencontrés en vain?\r
+\r
+Le conventionnel rouvrit les yeux. Une gravité où il y avait de l'ombre\r
+s'empreignit sur son visage.\r
+\r
+--Monsieur l'évêque, dit-il, avec une lenteur qui venait peut-être plus\r
+encore de la dignité de l'âme que de la défaillance des forces, j'ai\r
+passé ma vie dans la méditation, l'étude et la contemplation. J'avais\r
+soixante ans quand mon pays m'a appelé, et m'a ordonné de me mêler de\r
+ses affaires. J'ai obéi. Il y avait des abus, je les ai combattus; il y\r
+avait des tyrannies, je les ai détruites; il y avait des droits et des\r
+principes, je les ai proclamés et confessés. Le territoire était envahi,\r
+je l'ai défendu; la France était menacée, j'ai offert ma poitrine. Je\r
+n'étais pas riche; je suis pauvre. J'ai été l'un des maîtres de l'État,\r
+les caves du Trésor étaient encombrées d'espèces au point qu'on était\r
+forcé d'étançonner les murs, prêts à se fendre sous le poids de l'or et\r
+de l'argent, je dînais rue de l'Arbre-Sec à vingt-deux sous par tête.\r
+J'ai secouru les opprimés, j'ai soulagé les souffrants. J'ai déchiré la\r
+nappe de l'autel, c'est vrai; mais c'était pour panser les blessures de\r
+la patrie. J'ai toujours soutenu la marche en avant du genre humain vers\r
+la lumière, et j'ai résisté quelquefois au progrès sans pitié. J'ai,\r
+dans l'occasion, protégé mes propres adversaires, vous autres. Et il y a\r
+à Peteghem en Flandre, à l'endroit même où les rois mérovingiens avaient\r
+leur palais d'été, un couvent d'urbanistes, l'abbaye de Sainte-Claire en\r
+Beaulieu, que j'ai sauvé en 1793. J'ai fait mon devoir selon mes forces,\r
+et le bien que j'ai pu. Après quoi j'ai été chassé, traqué, poursuivi,\r
+persécuté, noirci, raillé, conspué, maudit, proscrit. Depuis bien des\r
+années déjà, avec mes cheveux blancs, je sens que beaucoup de gens se\r
+croient sur moi le droit de mépris, j'ai pour la pauvre foule ignorante\r
+visage de damné, et j'accepte, ne haïssant personne, l'isolement de la\r
+haine. Maintenant, j'ai quatre-vingt-six ans; je vais mourir. Qu'est-ce\r
+que vous venez me demander?\r
+\r
+--Votre bénédiction, dit l'évêque.\r
+\r
+Et il s'agenouilla.\r
+\r
+Quand l'évêque releva la tête, la face du conventionnel était devenue\r
+auguste. Il venait d'expirer.\r
+\r
+L'évêque rentra chez lui profondément absorbé dans on ne sait quelles\r
+pensées. Il passa toute la nuit en prière. Le lendemain, quelques braves\r
+curieux essayèrent de lui parler du conventionnel G.; il se borna à\r
+montrer le ciel. À partir de ce moment, il redoubla de tendresse et de\r
+fraternité pour les petits et les souffrants.\r
+\r
+Toute allusion à ce «vieux scélérat de G.» le faisait tomber dans une\r
+préoccupation singulière. Personne ne pourrait dire que le passage de\r
+cet esprit devant le sien et le reflet de cette grande conscience sur la\r
+sienne ne fût pas pour quelque chose dans son approche de la perfection.\r
+\r
+Cette «visite pastorale» fut naturellement une occasion de bourdonnement\r
+pour les petites coteries locales:\r
+\r
+--Était-ce la place d'un évêque que le chevet d'un tel mourant? Il n'y\r
+avait évidemment pas de conversion à attendre. Tous ces révolutionnaires\r
+sont relaps. Alors pourquoi y aller? Qu'a-t-il été regarder là? Il\r
+fallait donc qu'il fût bien curieux d'un emportement d'âme par le\r
+diable.\r
+\r
+Un jour, une douairière, de la variété impertinente qui se croit\r
+spirituelle, lui adressa cette saillie:\r
+\r
+--Monseigneur, on demande quand Votre Grandeur aura le bonnet rouge.\r
+\r
+--Oh! oh! voilà une grosse couleur, répondit l'évêque. Heureusement que\r
+ceux qui la méprisent dans un bonnet la vénèrent dans un chapeau.\r
+\r
+\r
+\r
+\r
+Chapitre XI\r
+\r
+Une restriction\r
+\r
+\r
+On risquerait fort de se tromper si l'on concluait de là que monseigneur\r
+Bienvenu fût «un évêque philosophe» ou «un curé patriote». Sa rencontre,\r
+ce qu'on pourrait presque appeler sa conjonction avec le conventionnel\r
+G., lui laissa une sorte d'étonnement qui le rendit plus doux encore.\r
+Voilà tout.\r
+\r
+Quoique monseigneur Bienvenu n'ait été rien moins qu'un homme politique,\r
+c'est peut-être ici le lieu d'indiquer, très brièvement, quelle fut son\r
+attitude dans les événements d'alors, en supposant que monseigneur\r
+Bienvenu ait jamais songé à avoir une attitude. Remontons donc en\r
+arrière de quelques années.\r
+\r
+Quelque temps après l'élévation de M. Myriel à l'épiscopat, l'empereur\r
+l'avait fait baron de l'empire, en même temps que plusieurs autres\r
+évêques. L'arrestation du pape eut lieu, comme on sait, dans la nuit du\r
+5 au 6 juillet 1809; à cette occasion, M. Myriel fut appelé par Napoléon\r
+au synode des évêques de France et d'Italie convoqué à Paris. Ce synode\r
+se tint à Notre-Dame et s'assembla pour la première fois le 15 juin 1811\r
+sous la présidence de M. le cardinal Fesch. M. Myriel fut du nombre des\r
+quatre-vingt-quinze évêques qui s'y rendirent. Mais il n'assista qu'à\r
+une séance et à trois ou quatre conférences particulières. Évêque d'un\r
+diocèse montagnard, vivant si près de la nature, dans la rusticité et le\r
+dénuement, il paraît qu'il apportait parmi ces personnages éminents des\r
+idées qui changeaient la température de l'assemblée. Il revint bien vite\r
+à Digne. On le questionna sur ce prompt retour, il répondit:\r
+\r
+--Je les gênais. L'air du dehors leur venait par moi. Je leur faisais\r
+l'effet d'une porte ouverte.\r
+\r
+Une autre fois il dit:\r
+\r
+--Que voulez-vous? ces messeigneurs-là sont des princes. Moi, je ne suis\r
+qu'un pauvre évêque paysan.\r
+\r
+Le fait est qu'il avait déplu. Entre autres choses étranges, il lui\r
+serait échappé de dire, un soir qu'il se trouvait chez un de ses\r
+collègues les plus qualifiés:\r
+\r
+--Les belles pendules! les beaux tapis! les belles livrées! Ce doit être\r
+bien importun! Oh! que je ne voudrais pas avoir tout ce superflu-là à me\r
+crier sans cesse aux oreilles: Il y a des gens qui ont faim! il y a des\r
+gens qui ont froid! il y a des pauvres! il y a des pauvres!\r
+\r
+Disons-le en passant, ce ne serait pas une haine intelligente que la\r
+haine du luxe. Cette haine impliquerait la haine des arts. Cependant,\r
+chez les gens d'église, en dehors de la représentation et des\r
+cérémonies, le luxe est un tort. Il semble révéler des habitudes peu\r
+réellement charitables. Un prêtre opulent est un contre-sens. Le prêtre\r
+doit se tenir près des pauvres. Or peut-on toucher sans cesse, et nuit\r
+et jour, à toutes les détresses, à toutes les infortunes, à toutes les\r
+indigences, sans avoir soi-même sur soi un peu de cette sainte misère,\r
+comme la poussière du travail? Se figure-t-on un homme qui est près d'un\r
+brasier, et qui n'a pas chaud? Se figure-t-on un ouvrier qui travaille\r
+sans cesse à une fournaise, et qui n'a ni un cheveu brûlé, ni un ongle\r
+noirci, ni une goutte de sueur, ni un grain de cendre au visage? La\r
+première preuve de la charité chez le prêtre, chez l'évêque surtout,\r
+c'est la pauvreté. C'était là sans doute ce que pensait M. l'évêque de\r
+Digne.\r
+\r
+Il ne faudrait pas croire d'ailleurs qu'il partageait sur certains\r
+points délicats ce que nous appellerions «les idées du siècle». Il se\r
+mêlait peu aux querelles théologiques du moment et se taisait sur les\r
+questions où sont compromis l'Église et l'État; mais si on l'eût\r
+beaucoup pressé, il paraît qu'on l'eût trouvé plutôt ultramontain que\r
+gallican. Comme nous faisons un portrait et que nous ne voulons rien\r
+cacher, nous sommes forcé d'ajouter qu'il fut glacial pour Napoléon\r
+déclinant. À partir de 1813, il adhéra ou il applaudit à toutes les\r
+manifestations hostiles. Il refusa de le voir à son passage au retour de\r
+l'île d'Elbe, et s'abstint d'ordonner dans son diocèse les prières\r
+publiques pour l'empereur pendant les Cent-Jours.\r
+\r
+Outre sa soeur, mademoiselle Baptistine, il avait deux frères: l'un\r
+général, l'autre préfet. Il écrivait assez souvent à tous les deux. Il\r
+tint quelque temps rigueur au premier, parce qu'ayant un commandement en\r
+Provence, à l'époque du débarquement de Cannes, le général s'était mis à\r
+la tête de douze cents hommes et avait poursuivi l'empereur comme\r
+quelqu'un qui veut le laisser échapper. Sa correspondance resta plus\r
+affectueuse pour l'autre frère, l'ancien préfet, brave et digne homme\r
+qui vivait retiré à Paris, rue Cassette.\r
+\r
+Monseigneur Bienvenu eut donc, aussi lui, son heure d'esprit de parti,\r
+son heure d'amertume, son nuage. L'ombre des passions du moment traversa\r
+ce doux et grand esprit occupé des choses éternelles. Certes, un pareil\r
+homme eût mérité de n'avoir pas d'opinions politiques. Qu'on ne se\r
+méprenne pas sur notre pensée, nous ne confondons point ce qu'on appelle\r
+«opinions politiques» avec la grande aspiration au progrès, avec la\r
+sublime foi patriotique, démocratique et humaine, qui, de nos jours,\r
+doit être le fond même de toute intelligence généreuse. Sans approfondir\r
+des questions qui ne touchent qu'indirectement au sujet de ce livre,\r
+nous disons simplement ceci: Il eût été beau que monseigneur Bienvenu\r
+n'eût pas été royaliste et que son regard ne se fût pas détourné un seul\r
+instant de cette contemplation sereine où l'on voit rayonner\r
+distinctement, au-dessus du va-et-vient orageux des choses humaines, ces\r
+trois pures lumières, la Vérité, la Justice, la Charité.\r
+\r
+Tout en convenant que ce n'était point pour une fonction politique que\r
+Dieu avait créé monseigneur Bienvenu, nous eussions compris et admiré la\r
+protestation au nom du droit et de la liberté, l'opposition fière, la\r
+résistance périlleuse et juste à Napoléon tout-puissant. Mais ce qui\r
+nous plaît vis-à-vis de ceux qui montent nous plaît moins vis-à-vis de\r
+ceux qui tombent. Nous n'aimons le combat que tant qu'il y a danger; et,\r
+dans tous les cas, les combattants de la première heure ont seuls le\r
+droit d'être les exterminateurs de la dernière. Qui n'a pas été\r
+accusateur opiniâtre pendant la prospérité doit se taire devant\r
+l'écroulement. Le dénonciateur du succès est le seul légitime justicier\r
+de la chute. Quant à nous, lorsque la Providence s'en mêle et frappe,\r
+nous la laissons faire. 1812 commence à nous désarmer. En 1813, la lâche\r
+rupture de silence de ce corps législatif taciturne enhardi par les\r
+catastrophes n'avait que de quoi indigner, et c'était un tort\r
+d'applaudir; en 1814, devant ces maréchaux trahissant, devant ce sénat\r
+passant d'une fange à l'autre, insultant après avoir divinisé, devant\r
+cette idolâtrie lâchant pied et crachant sur l'idole, c'était un devoir\r
+de détourner la tête; en 1815, comme les suprêmes désastres étaient dans\r
+l'air, comme la France avait le frisson de leur approche sinistre, comme\r
+on pouvait vaguement distinguer Waterloo ouvert devant Napoléon, la\r
+douloureuse acclamation de l'armée et du peuple au condamné du destin\r
+n'avait rien de risible, et, toute réserve faite sur le despote, un\r
+coeur comme l'évêque de Digne n'eût peut-être pas dû méconnaître ce\r
+qu'avait d'auguste et de touchant, au bord de l'abîme, l'étroit\r
+embrassement d'une grande nation et d'un grand homme.\r
+\r
+À cela près, il était et il fut, en toute chose, juste, vrai, équitable,\r
+intelligent, humble et digne; bienfaisant, et bienveillant, ce qui est\r
+une autre bienfaisance. C'était un prêtre, un sage, et un homme. Même,\r
+il faut le dire, dans cette opinion politique que nous venons de lui\r
+reprocher et que nous sommes disposé à juger presque sévèrement, il\r
+était tolérant et facile, peut-être plus que nous qui parlons ici.--Le\r
+portier de la maison de ville avait été placé là par l'empereur. C'était\r
+un vieux sous-officier de la vieille garde, légionnaire d'Austerlitz,\r
+bonapartiste comme l'aigle. Il échappait dans l'occasion à ce pauvre\r
+diable de ces paroles peu réfléchies que la loi d'alors qualifiait\r
+_propos séditieux_. Depuis que le profil impérial avait disparu de la\r
+légion d'honneur, il ne s'habillait jamais _dans l'ordonnance_, comme il\r
+disait, afin de ne pas être forcé de porter sa croix. Il avait ôté\r
+lui-même dévotement l'effigie impériale de la croix que Napoléon lui\r
+avait donnée, cela faisait un trou, et il n'avait rien voulu mettre à la\r
+place. «Plutôt mourir, disait-il, que de porter sur mon coeur les trois\r
+crapauds!» Il raillait volontiers tout haut Louis XVIII. «Vieux goutteux\r
+à guêtres d'anglais!» disait-il, «qu'il s'en aille en Prusse avec son\r
+salsifis!» Heureux de réunir dans la même imprécation les deux choses\r
+qu'il détestait le plus, la Prusse et l'Angleterre. Il en fit tant qu'il\r
+perdit sa place. Le voilà sans pain sur le pavé avec femme et enfants.\r
+L'évêque le fit venir, le gronda doucement, et le nomma suisse de la\r
+cathédrale.\r
+\r
+M. Myriel était dans le diocèse le vrai pasteur, l'ami de tous. En neuf\r
+ans, à force de saintes actions et de douces manières, monseigneur\r
+Bienvenu avait rempli la ville de Digne d'une sorte de vénération tendre\r
+et filiale. Sa conduite même envers Napoléon avait été acceptée et comme\r
+tacitement pardonnée par le peuple, bon troupeau faible, qui adorait son\r
+empereur, mais qui aimait son évêque.\r
+\r
+\r
+\r
+\r
+Chapitre XII\r
+\r
+Solitude de monseigneur Bienvenu\r
+\r
+\r
+Il y a presque toujours autour d'un évêque une escouade de petits abbés\r
+comme autour d'un général une volée de jeunes officiers. C'est là ce que\r
+ce charmant saint François de Sales appelle quelque part «les prêtres\r
+blancs-becs». Toute carrière a ses aspirants qui font cortège aux\r
+arrivés. Pas une puissance qui n'ait son entourage; pas une fortune qui\r
+n'ait sa cour. Les chercheurs d'avenir tourbillonnent autour du présent\r
+splendide. Toute métropole a son état-major. Tout évêque un peu influent\r
+a près de lui sa patrouille de chérubins séminaristes, qui fait la ronde\r
+et maintient le bon ordre dans le palais épiscopal, et qui monte la\r
+garde autour du sourire de monseigneur. Agréer à un évêque, c'est le\r
+pied à l'étrier pour un sous-diacre. Il faut bien faire son chemin;\r
+l'apostolat ne dédaigne pas le canonicat.\r
+\r
+De même qu'il y a ailleurs les gros bonnets, il y a dans l'église les\r
+grosses mitres. Ce sont les évêques bien en cour, riches, rentés,\r
+habiles, acceptés du monde, sachant prier, sans doute, mais sachant\r
+aussi solliciter, peu scrupuleux de faire faire antichambre en leur\r
+personne à tout un diocèse, traits d'union entre la sacristie et la\r
+diplomatie, plutôt abbés que prêtres, plutôt prélats qu'évêques. Heureux\r
+qui les approche! Gens en crédit qu'ils sont, ils font pleuvoir autour\r
+d'eux, sur les empressés et les favorisés, et sur toute cette jeunesse\r
+qui sait plaire, les grasses paroisses, les prébendes, les\r
+archidiaconats, les aumôneries et les fonctions cathédrales, en\r
+attendant les dignités épiscopales. En avançant eux-mêmes, ils font\r
+progresser leurs satellites; c'est tout un système solaire en marche.\r
+Leur rayonnement empourpre leur suite. Leur prospérité s'émiette sur la\r
+cantonade en bonnes petites promotions. Plus grand diocèse au patron,\r
+plus grosse cure au favori. Et puis Rome est là. Un évêque qui sait\r
+devenir archevêque, un archevêque qui sait devenir cardinal, vous emmène\r
+comme conclaviste, vous entrez dans la rote, vous avez le pallium, vous\r
+voilà auditeur, vous voilà camérier, vous voilà monsignor, et de la\r
+Grandeur à Imminence il n'y a qu'un pas, et entre Imminence et la\r
+Sainteté il n'y a que la fumée d'un scrutin. Toute calotte peut rêver la\r
+tiare. Le prêtre est de nos jours le seul homme qui puisse régulièrement\r
+devenir roi; et quel roi! le roi suprême. Aussi quelle pépinière\r
+d'aspirations qu'un séminaire! Que d'enfants de choeur rougissants, que\r
+de jeunes abbés ont sur la tête le pot au lait de Perrette! Comme\r
+l'ambition s'intitule aisément vocation, qui sait? de bonne foi\r
+peut-être et se trompant elle-même, béate qu'elle est!\r
+\r
+Monseigneur Bienvenu, humble, pauvre, particulier, n'était pas compté\r
+parmi les grosses mitres. Cela était visible à l'absence complète de\r
+jeunes prêtres autour de lui. On a vu qu'à Paris «il n'avait pas pris».\r
+Pas un avenir ne songeait à se greffer sur ce vieillard solitaire. Pas\r
+une ambition en herbe ne faisait la folie de verdir à son ombre. Ses\r
+chanoines et ses grands vicaires étaient de bons vieux hommes, un peu\r
+peuple comme lui, murés comme lui dans ce diocèse sans issue sur le\r
+cardinafat, et qui ressemblaient à leur évêque, avec cette différence\r
+qu'eux étaient finis, et que lui était achevé.\r
+\r
+On sentait si bien l'impossibilité de croître près de monseigneur\r
+Bienvenu qu'à peine sortis du séminaire, les jeunes gens ordonnés par\r
+lui se faisaient recommander aux archevêques d'Aix ou d'Auch, et s'en\r
+allaient bien vite. Car enfin, nous le répétons, on veut être poussé. Un\r
+saint qui vit dans un excès d'abnégation est un voisinage dangereux; il\r
+pourrait bien vous communiquer par contagion une pauvreté incurable,\r
+l'ankylose des articulations utiles à l'avancement, et, en somme, plus\r
+de renoncement que vous n'en voulez; et l'on fuit cette vertu galeuse.\r
+De là l'isolement de monseigneur Bienvenu. Nous vivons dans une société\r
+sombre. Réussir, voilà l'enseignement qui tombe goutte à goutte de la\r
+corruption en surplomb.\r
+\r
+Soit dit en passant, c'est une chose assez hideuse que le succès. Sa\r
+fausse ressemblance avec le mérite trompe les hommes. Pour la foule, la\r
+réussite a presque le même profil que la suprématie. Le succès, ce\r
+ménechme du talent, a une dupe: l'histoire. Juvénal et Tacite seuls en\r
+bougonnent. De nos jours, une philosophie à peu près officielle est\r
+entrée en domesticité chez lui, porte la livrée du succès, et fait le\r
+service de son antichambre. Réussissez: théorie. Prospérité suppose\r
+Capacité. Gagnez à la loterie, vous voilà un habile homme. Qui triomphe\r
+est vénéré. Naissez coiffé, tout est là. Ayez de la chance, vous aurez\r
+le reste; soyez heureux, on vous croira grand. En dehors des cinq ou six\r
+exceptions immenses qui font l'éclat d'un siècle, l'admiration\r
+contemporaine n'est guère que myopie. Dorure est or. Être le premier\r
+venu, cela ne gâte rien, pourvu qu'on soit le parvenu. Le vulgaire est\r
+un vieux Narcisse qui s'adore lui-même et qui applaudit le vulgaire.\r
+Cette faculté énorme par laquelle on est Moïse, Eschyle, Dante,\r
+Michel-Ange ou Napoléon, la multitude la décerne d'emblée et par\r
+acclamation à quiconque atteint son but dans quoi que ce soit. Qu'un\r
+notaire se transfigure en député, qu'un faux Corneille fasse _Tiridate_,\r
+qu'un eunuque parvienne à posséder un harem, qu'un Prud'homme militaire\r
+gagne par accident la bataille décisive d'une époque, qu'un apothicaire\r
+invente les semelles de carton pour l'armée de Sambre-et-Meuse et se\r
+construise, avec ce carton vendu pour du cuir, quatre cent mille livres\r
+de rente, qu'un porte-balle épouse l'usure et la fasse accoucher de sept\r
+ou huit millions dont il est le père et dont elle est la mère, qu'un\r
+prédicateur devienne évêque par le nasillement, qu'un intendant de bonne\r
+maison soit si riche en sortant de service qu'on le fasse ministre des\r
+finances, les hommes appellent cela Génie, de même qu'ils appellent\r
+Beauté la figure de Mousqueton et Majesté l'encolure de Claude. Ils\r
+confondent avec les constellations de l'abîme les étoiles que font dans\r
+la vase molle du bourbier les pattes des canards.\r
+\r
+\r
+\r
+\r
+Chapitre XIII\r
+\r
+Ce qu'il croyait\r
+\r
+\r
+Au point de vue de l'orthodoxie, nous n'avons point à sonder M. l'évêque\r
+de Digne. Devant une telle âme, nous ne nous sentons en humeur que de\r
+respect. La conscience du juste doit être crue sur parole. D'ailleurs,\r
+de certaines natures étant données, nous admettons le développement\r
+possible de toutes les beautés de la vertu humaine dans une croyance\r
+différente de la nôtre.\r
+\r
+Que pensait-il de ce dogme-ci ou de ce mystère-là? Ces secrets du for\r
+intérieur ne sont connus que de la tombe où les âmes entrent nues. Ce\r
+dont nous sommes certain, c'est que jamais les difficultés de foi ne se\r
+résolvaient pour lui en hypocrisie. Aucune pourriture n'est possible au\r
+diamant. Il croyait le plus qu'il pouvait. _Credo in Patrem_,\r
+s'écriait-il souvent. Puisant d'ailleurs dans les bonnes oeuvres cette\r
+quantité de satisfaction qui suffit à la conscience, et qui vous dit\r
+tout bas: «Tu es avec Dieu.»\r
+\r
+Ce que nous croyons devoir noter, c'est que, en dehors, pour ainsi dire,\r
+et au-delà de sa foi, l'évêque avait un excès d'amour. C'est par là,\r
+_quia multum amavit_, qu'il était jugé vulnérable par les «hommes\r
+sérieux», les «personnes graves» et les «gens raisonnables»; locutions\r
+favorites de notre triste monde où l'égoïsme reçoit le mot d'ordre du\r
+pédantisme. Qu'était-ce que cet excès d'amour? C'était une bienveillance\r
+sereine, débordant les hommes, comme nous l'avons indiqué déjà, et, dans\r
+l'occasion, s'étendant jusqu'aux choses. Il vivait sans dédain. Il était\r
+indulgent pour la création de Dieu. Tout homme, même le meilleur, a en\r
+lui une dureté irréfléchie qu'il tient en réserve pour l'animal.\r
+L'évêque de Digne n'avait point cette dureté-là, particulière à beaucoup\r
+de prêtres pourtant. Il n'allait pas jusqu'au bramine, mais il semblait\r
+avoir médité cette parole de l'Ecclésiaste: «Sait-on où va l'âme des\r
+animaux?» Les laideurs de l'aspect, les difformités de l'instinct, ne le\r
+troublaient pas et ne l'indignaient pas. Il en était ému, presque\r
+attendri. Il semblait que, pensif, il en allât chercher, au-delà de la\r
+vie apparente, la cause, l'explication ou l'excuse. Il semblait par\r
+moments demander à Dieu des commutations. Il examinait sans colère, et\r
+avec l'oeil du linguiste qui déchiffre un palimpseste, la quantité de\r
+chaos qui est encore dans la nature. Cette rêverie faisait parfois\r
+sortir de lui des mots étranges. Un matin, il était dans son jardin; il\r
+se croyait seul, mais sa soeur marchait derrière lui sans qu'il la vît;\r
+tout à coup, il s'arrêta, et il regarda quelque chose à terre; c'était\r
+une grosse araignée, noire, velue, horrible. Sa soeur l'entendit qui\r
+disait:\r
+\r
+--Pauvre bête! ce n'est pas sa faute.\r
+\r
+Pourquoi ne pas dire ces enfantillages presque divins de la bonté?\r
+Puérilités, soit; mais ces puérilités sublimes ont été celles de saint\r
+François d'Assise et de Marc-Aurèle. Un jour il se donna une entorse\r
+pour n'avoir pas voulu écraser une fourmi.\r
+\r
+Ainsi vivait cet homme juste. Quelquefois, il s'endormait dans son\r
+jardin, et alors il n'était rien de plus vénérable.\r
+\r
+Monseigneur Bienvenu avait été jadis, à en croire les récits sur sa\r
+jeunesse et même sur sa virilité, un homme passionné, peut-être violent.\r
+Sa mansuétude universelle était moins un instinct de nature que le\r
+résultat d'une grande conviction filtrée dans son coeur à travers la vie\r
+et lentement tombée en lui, pensée à pensée; car, dans un caractère\r
+comme dans un rocher, il peut y avoir des trous de gouttes d'eau. Ces\r
+creusements-là sont ineffaçables; ces formations-là sont\r
+indestructibles.\r
+\r
+En 1815, nous croyons l'avoir dit, il atteignit soixante-quinze ans,\r
+mais il n'en paraissait pas avoir plus de soixante. Il n'était pas\r
+grand; il avait quelque embonpoint, et, pour le combattre, il faisait\r
+volontiers de longues marches à pied, il avait le pas ferme et n'était\r
+que fort peu courbé, détail d'où nous ne prétendons rien conclure;\r
+Grégoire XVI, à quatre-vingts ans, se tenait droit et souriant, ce qui\r
+ne l'empêchait pas d'être un mauvais évêque. Monseigneur Bienvenu avait\r
+ce que le peuple appelle «une belle tête», mais si aimable qu'on\r
+oubliait qu'elle était belle.\r
+\r
+Quand il causait avec cette santé enfantine qui était une de ses grâces,\r
+et dont nous avons déjà parlé, on se sentait à l'aise près de lui, il\r
+semblait que de toute sa personne il sortît de la joie. Son teint coloré\r
+et frais, toutes ses dents bien blanches qu'il avait conservées et que\r
+son rire faisait voir, lui donnaient cet air ouvert et facile qui fait\r
+dire d'un homme: «C'est un bon enfant», et d'un vieillard: «C'est un\r
+bonhomme». C'était, on s'en souvient, l'effet qu'il avait fait à\r
+Napoléon. Au premier abord, et pour qui le voyait pour la première fois,\r
+ce n'était guère qu'un bonhomme en effet. Mais si l'on restait quelques\r
+heures près de lui, et pour peu qu'on le vît pensif, le bonhomme se\r
+transfigurait peu à peu et prenait je ne sais quoi d'imposant; son front\r
+large et sérieux, auguste par les cheveux blancs, devenait auguste aussi\r
+par la méditation; la majesté se dégageait de cette bonté, sans que la\r
+bonté cessât de rayonner; on éprouvait quelque chose de l'émotion qu'on\r
+aurait si l'on voyait un ange souriant ouvrir lentement ses ailes sans\r
+cesser de sourire. Le respect, un respect inexprimable, vous pénétrait\r
+par degrés et vous montait au coeur, et l'on sentait qu'on avait devant\r
+soi une de ces âmes fortes, éprouvées et indulgentes, où la pensée est\r
+si grande qu'elle ne peut plus être que douce.\r
+\r
+Comme on l'a vu, la prière, la célébration des offices religieux,\r
+l'aumône, la consolation aux affligés, la culture d'un coin de terre, la\r
+fraternité, la frugalité, l'hospitalité, le renoncement, la confiance,\r
+l'étude, le travail remplissaient chacune des journées de sa vie.\r
+_Remplissaient_ est bien le mot, et certes cette journée de l'évêque\r
+était bien pleine jusqu'aux bords de bonnes pensées, de bonnes paroles\r
+et de bonnes actions. Cependant elle n'était pas complète si le temps\r
+froid ou pluvieux l'empêchait d'aller passer, le soir, quand les deux\r
+femmes s'étaient retirées, une heure ou deux dans son jardin avant de\r
+s'endormir. Il semblait que ce fût une sorte de rite pour lui de se\r
+préparer au sommeil par la méditation en présence des grands spectacles\r
+du ciel nocturne. Quelquefois, à une heure même assez avancée de la\r
+nuit, si les deux vieilles filles ne dormaient pas, elles l'entendaient\r
+marcher lentement dans les allées. Il était là, seul avec lui-même,\r
+recueilli, paisible, adorant, comparant la sérénité de son coeur à la\r
+sérénité de l'éther, ému dans les ténèbres par les splendeurs visibles\r
+des constellations et les splendeurs invisibles de Dieu, ouvrant son âme\r
+aux pensées qui tombent de l'inconnu. Dans ces moments-là, offrant son\r
+coeur à l'heure où les fleurs nocturnes offrent leur parfum, allumé\r
+comme une lampe au centre de la nuit étoilée, se répandant en extase au\r
+milieu du rayonnement universel de la création, il n'eût pu peut-être\r
+dire lui-même ce qui se passait dans son esprit, il sentait quelque\r
+chose s'envoler hors de lui et quelque chose descendre en lui.\r
+Mystérieux échanges des gouffres de l'âme avec les gouffres de\r
+l'univers!\r
+\r
+Il songeait à la grandeur et à la présence de Dieu; à l'éternité future,\r
+étrange mystère; à l'éternité passée, mystère plus étrange encore; à\r
+tous les infinis qui s'enfonçaient sous ses yeux dans tous les sens; et,\r
+sans chercher à comprendre l'incompréhensible, il le regardait. Il\r
+n'étudiait pas Dieu, il s'en éblouissait. Il considérait ces magnifiques\r
+rencontres des atomes qui donnent des aspects à la matière, révèlent les\r
+forces en les constatant, créent les individualités dans l'unité, les\r
+proportions dans l'étendue, l'innombrable dans l'infini, et par la\r
+lumière produisent la beauté. Ces rencontres se nouent et se dénouent\r
+sans cesse; de là la vie et la mort. Il s'asseyait sur un banc de bois\r
+adossé à une treille décrépite, et il regardait les astres à travers les\r
+silhouettes chétives et rachitiques de ses arbres fruitiers. Ce quart\r
+d'arpent, si pauvrement planté, si encombré de masures et de hangars,\r
+lui était cher et lui suffisait.\r
+\r
+Que fallait-il de plus à ce vieillard, qui partageait le loisir de sa\r
+vie, où il y avait si peu de loisir, entre le jardinage le jour et la\r
+contemplation la nuit? Cet étroit enclos, ayant les cieux pour plafond,\r
+n'était-ce pas assez pour pouvoir adorer Dieu tour à tour dans ses\r
+oeuvres les plus charmantes et dans ses oeuvres les plus sublimes?\r
+N'est-ce pas là tout, en effet, et que désirer au-delà? Un petit jardin\r
+pour se promener, et l'immensité pour rêver. À ses pieds ce qu'on peut\r
+cultiver et cueillir; sur sa tête ce qu'on peut étudier et méditer;\r
+quelques fleurs sur la terre et toutes les étoiles dans le ciel.\r
+\r
+\r
+\r
+\r
+Chapitre XIV\r
+\r
+Ce qu'il pensait\r
+\r
+\r
+Un dernier mot.\r
+\r
+Comme cette nature de détails pourrait, particulièrement au moment où\r
+nous sommes, et pour nous servir d'une expression actuellement à la\r
+mode, donner à l'évêque de Digne une certaine physionomie «panthéiste»,\r
+et faire croire, soit à son blâme, soit à sa louange, qu'il y avait en\r
+lui une de ces philosophies personnelles, propres à notre siècle, qui\r
+germent quelquefois dans les esprits solitaires et s'y construisent et y\r
+grandissent jusqu'à y remplacer les religions, nous insistons sur ceci\r
+que pas un de ceux qui ont connu monseigneur Bienvenu ne se fût cru\r
+autorisé à penser rien de pareil. Ce qui éclairait cet homme, c'était le\r
+coeur. Sa sagesse était faite de la lumière qui vient de là.\r
+\r
+Point de systèmes, beaucoup d'oeuvres. Les spéculations abstruses\r
+contiennent du vertige; rien n'indique qu'il hasardât son esprit dans\r
+les apocalypses. L'apôtre peut être hardi, mais l'évêque doit être\r
+timide. Il se fût probablement fait scrupule de sonder trop avant de\r
+certains problèmes réservés en quelque sorte aux grands esprits\r
+terribles. Il y a de l'horreur sacrée sous les porches de l'énigme; ces\r
+ouvertures sombres sont là béantes, mais quelque chose vous dit, à vous\r
+passant de la vie, qu'on n'entre pas. Malheur à qui y pénètre! Les\r
+génies, dans les profondeurs inouïes de l'abstraction et de la\r
+spéculation pure, situés pour ainsi dire au-dessus des dogmes, proposent\r
+leurs idées à Dieu. Leur prière offre audacieusement la discussion. Leur\r
+adoration interroge. Ceci est la religion directe, pleine d'anxiété et\r
+de responsabilité pour qui en tente les escarpements.\r
+\r
+La méditation humaine n'a point de limite. À ses risques et périls, elle\r
+analyse et creuse son propre éblouissement. On pourrait presque dire\r
+que, par une sorte de réaction splendide, elle en éblouit la nature; le\r
+mystérieux monde qui nous entoure rend ce qu'il reçoit, il est probable\r
+que les contemplateurs sont contemplés. Quoi qu'il en soit, il y a sur\r
+la terre des hommes--sont-ce des hommes?--qui aperçoivent distinctement\r
+au fond des horizons du rêve les hauteurs de l'absolu, et qui ont la\r
+vision terrible de la montagne infinie. Monseigneur Bienvenu n'était\r
+point de ces hommes-là, monseigneur Bienvenu n'était pas un génie. Il\r
+eût redouté ces sublimités d'où quelques-uns, très grands même, comme\r
+Swedenborg et Pascal, ont glissé dans la démence. Certes, ces puissantes\r
+rêveries ont leur utilité morale, et par ces routes ardues on s'approche\r
+de la perfection idéale. Lui, il prenait le sentier qui abrège:\r
+l'évangile. Il n'essayait point de faire faire à sa chasuble les plis du\r
+manteau d'Élie, il ne projetait aucun rayon d'avenir sur le roulis\r
+ténébreux des événements, il ne cherchait pas à condenser en flamme la\r
+lueur des choses, il n'avait rien du prophète et rien du mage. Cette âme\r
+simple aimait, voilà tout.\r
+\r
+Qu'il dilatât la prière jusqu'à une aspiration surhumaine, cela est\r
+probable; mais on ne peut pas plus prier trop qu'aimer trop; et, si\r
+c'était une hérésie de prier au-delà des textes, sainte Thérèse et saint\r
+Jérôme seraient des hérétiques.\r
+\r
+Il se penchait sur ce qui gémit et sur ce qui expie. L'univers lui\r
+apparaissait comme une immense maladie; il sentait partout de la fièvre,\r
+il auscultait partout de la souffrance, et, sans chercher à deviner\r
+l'énigme, il tâchait de panser la plaie. Le redoutable spectacle des\r
+choses créées développait en lui l'attendrissement; il n'était occupé\r
+qu'à trouver pour lui-même et à inspirer aux autres la meilleure manière\r
+de plaindre et de soulager. Ce qui existe était pour ce bon et rare\r
+prêtre un sujet permanent de tristesse cherchant à consoler.\r
+\r
+Il y a des hommes qui travaillent à l'extraction de l'or; lui, il\r
+travaillait à l'extraction de la pitié. L'universelle misère était sa\r
+mine. La douleur partout n'était qu'une occasion de bonté toujours.\r
+_Aimez-vous les uns les autres;_ il déclarait cela complet, ne\r
+souhaitait rien de plus, et c'était là toute sa doctrine. Un jour, cet\r
+homme qui se croyait «philosophe», ce sénateur, déjà nommé, dit à\r
+l'évêque:\r
+\r
+--Mais voyez donc le spectacle du monde; guerre de tous contre tous; le\r
+plus fort a le plus d'esprit. Votre _aimez-vous les uns les autres_ est\r
+une bêtise.\r
+\r
+--Eh bien, répondit monseigneur Bienvenu sans disputer, si c'est une\r
+bêtise, l'âme doit s'y enfermer comme la perle dans l'huître.\r
+\r
+Il s'y enfermait donc, il y vivait, il s'en satisfaisait absolument,\r
+laissant de côté les questions prodigieuses qui attirent et qui\r
+épouvantent, les perspectives insondables de l'abstraction, les\r
+précipices de la métaphysique, toutes ces profondeurs convergentes, pour\r
+l'apôtre à Dieu, pour l'athée au néant: la destinée, le bien et le mal,\r
+la guerre de l'être contre l'être, la conscience de l'homme, le\r
+somnambulisme pensif de l'animal, la transformation par la mort, la\r
+récapitulation d'existences que contient le tombeau, la greffe\r
+incompréhensible des amours successifs sur le moi persistant, l'essence,\r
+la substance, le Nil et l'Ens, l'âme, la nature, la liberté, la\r
+nécessité; problèmes à pic, épaisseurs sinistres, où se penchent les\r
+gigantesques archanges de l'esprit humain; formidables abîmes que\r
+Lucrèce, Manou, saint Paul et Dante contemplent avec cet oeil fulgurant\r
+qui semble, en regardant fixement l'infini, y faire éclore des étoiles.\r
+\r
+Monseigneur Bienvenu était simplement un homme qui constatait du dehors\r
+les questions mystérieuses sans les scruter, sans les agiter, et sans en\r
+troubler son propre esprit, et qui avait dans l'âme le grave respect de\r
+l'ombre.\r
+\r
+\r
+\r
+\r
+Livre deuxième--La chute\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+Le soir d'un jour de marche\r
+\r
+\r
+Dans les premiers jours du mois d'octobre 1815, une heure environ avant\r
+le coucher du soleil, un homme qui voyageait à pied entrait dans la\r
+petite ville de Digne. Les rares habitants qui se trouvaient en ce moment\r
+à leurs fenêtres ou sur le seuil de leurs maisons regardaient ce\r
+voyageur avec une sorte d'inquiétude. Il était difficile de rencontrer\r
+un passant d'un aspect plus misérable. C'était un homme de moyenne\r
+taille, trapu et robuste, dans la force de l'âge. Il pouvait avoir\r
+quarante-six ou quarante-huit ans. Une casquette à visière de cuir\r
+rabattue cachait en partie son visage, brûlé par le soleil et le hâle,\r
+et ruisselant de sueur. Sa chemise de grosse toile jaune, rattachée au\r
+col par une petite ancre d'argent, laissait voir sa poitrine velue; il\r
+avait une cravate tordue en corde, un pantalon de coutil bleu, usé et\r
+râpé, blanc à un genou, troué à l'autre, une vieille blouse grise en\r
+haillons, rapiécée à l'un des coudes d'un morceau de drap vert cousu\r
+avec de la ficelle, sur le dos un sac de soldat fort plein, bien bouclé\r
+et tout neuf, à la main un énorme bâton noueux, les pieds sans bas dans\r
+des souliers ferrés, la tête tondue et la barbe longue.\r
+\r
+La sueur, la chaleur, le voyage à pied, la poussière, ajoutaient je ne\r
+sais quoi de sordide à cet ensemble délabré.\r
+\r
+Les cheveux étaient ras, et pourtant hérissés; car ils commençaient à\r
+pousser un peu, et semblaient n'avoir pas été coupés depuis quelque\r
+temps.\r
+\r
+Personne ne le connaissait. Ce n'était évidemment qu'un passant. D'où\r
+venait-il? Du midi. Des bords de la mer peut-être. Car il faisait son\r
+entrée dans Digne par la même rue qui, sept mois auparavant, avait vu\r
+passer l'empereur Napoléon allant de Cannes à Paris. Cet homme avait dû\r
+marcher tout le jour. Il paraissait très fatigué. Des femmes de l'ancien\r
+bourg qui est au bas de la ville l'avaient vu s'arrêter sous les arbres\r
+du boulevard Gassendi et boire à la fontaine qui est à l'extrémité de la\r
+promenade. Il fallait qu'il eût bien soif, car des enfants qui le\r
+suivaient le virent encore s'arrêter, et boire, deux cents pas plus\r
+loin, à la fontaine de la place du marché.\r
+\r
+Arrivé au coin de la rue Poichevert, il tourna à gauche et se dirigea\r
+vers la mairie. Il y entra, puis sortit un quart d'heure après. Un\r
+gendarme était assis près de la porte sur le banc de pierre où le\r
+général Drouot monta le 4 mars pour lire à la foule effarée des\r
+habitants de Digne la proclamation du golfe Juan. L'homme ôta sa\r
+casquette et salua humblement le gendarme.\r
+\r
+Le gendarme, sans répondre à son salut, le regarda avec attention, le\r
+suivit quelque temps des yeux, puis entra dans la maison de ville.\r
+\r
+Il y avait alors à Digne une belle auberge à l'enseigne de _la\r
+Croix-de-Colbas_. Cette auberge avait pour hôtelier un nommé Jacquin\r
+Labarre, homme considéré dans la ville pour sa parenté avec un autre\r
+Labarre, qui tenait à Grenoble l'auberge des _Trois-Dauphins_ et qui\r
+avait servi dans les guides. Lors du débarquement de l'empereur,\r
+beaucoup de bruits avaient couru dans le pays sur cette auberge des\r
+_Trois-Dauphins_. On contait que le général Bertrand, déguisé en\r
+charretier, y avait fait de fréquents voyages au mois de janvier, et\r
+qu'il y avait distribué des croix d'honneur à des soldats et des\r
+poignées de napoléons à des bourgeois. La réalité est que l'empereur,\r
+entré dans Grenoble, avait refusé de s'installer à l'hôtel de la\r
+préfecture; il avait remercié le maire en disant: _Je vais chez un brave\r
+homme que je connais_, et il était allé aux _Trois-Dauphins_. Cette\r
+gloire du Labarre des _Trois-Dauphins_ se reflétait à vingt-cinq lieues\r
+de distance jusque sur le Labarre de la _Croix-de-Colbas_. On disait de\r
+lui dans la ville: _C'est le cousin de celui de Grenoble_.\r
+\r
+L'homme se dirigea vers cette auberge, qui était la meilleure du pays.\r
+Il entra dans la cuisine, laquelle s'ouvrait de plain-pied sur la rue.\r
+Tous les fourneaux étaient allumés; un grand feu flambait gaîment dans\r
+la cheminée. L'hôte, qui était en même temps le chef, allait de l'âtre\r
+aux casseroles, fort occupé et surveillant un excellent dîner destiné à\r
+des rouliers qu'on entendait rire et parler à grand bruit dans une salle\r
+voisine. Quiconque a voyagé sait que personne ne fait meilleure chère\r
+que les rouliers. Une marmotte grasse, flanquée de perdrix blanches et\r
+de coqs de bruyère, tournait sur une longue broche devant le feu; sur\r
+les fourneaux cuisaient deux grosses carpes du lac de Lauzet et une\r
+truite du lac d'Alloz.\r
+\r
+L'hôte, entendant la porte s'ouvrir et entrer un nouveau venu, dit sans\r
+lever les yeux de ses fourneaux:\r
+\r
+--Que veut monsieur?\r
+\r
+--Manger et coucher, dit l'homme.\r
+\r
+--Rien de plus facile, reprit l'hôte.\r
+\r
+En ce moment il tourna la tête, embrassa d'un coup d'oeil tout\r
+l'ensemble du voyageur, et ajouta:\r
+\r
+--... en payant.\r
+\r
+L'homme tira une grosse bourse de cuir de la poche de sa blouse et\r
+répondit:\r
+\r
+--J'ai de l'argent.\r
+\r
+--En ce cas on est à vous, dit l'hôte.\r
+\r
+L'homme remit sa bourse en poche, se déchargea de son sac, le posa à\r
+terre près de la porte, garda son bâton à la main, et alla s'asseoir sur\r
+une escabelle basse près du feu. Digne est dans la montagne. Les soirées\r
+d'octobre y sont froides.\r
+\r
+Cependant, tout en allant et venant, l'homme considérait le voyageur.\r
+\r
+--Dîne-t-on bientôt? dit l'homme.\r
+\r
+--Tout à l'heure, dit l'hôte.\r
+\r
+Pendant que le nouveau venu se chauffait, le dos tourné, le digne\r
+aubergiste Jacquin Labarre tira un crayon de sa poche, puis il déchira\r
+le coin d'un vieux journal qui traînait sur une petite table près de la\r
+fenêtre. Sur la marge blanche il écrivit une ligne ou deux, plia sans\r
+cacheter et remit ce chiffon de papier à un enfant qui paraissait lui\r
+servir tout à la fois de marmiton et de laquais. L'aubergiste dit un mot\r
+à l'oreille du marmiton, et l'enfant partit en courant dans la direction\r
+de la mairie.\r
+\r
+Le voyageur n'avait rien vu de tout cela.\r
+\r
+Il demanda encore une fois:\r
+\r
+--Dîne-t-on bientôt?\r
+\r
+--Tout à l'heure, dit l'hôte.\r
+\r
+L'enfant revint. Il rapportait le papier. L'hôte le déplia avec\r
+empressement, comme quelqu'un qui attend une réponse. Il parut lire\r
+attentivement, puis hocha la tête, et resta un moment pensif. Enfin il\r
+fit un pas vers le voyageur qui semblait plongé dans des réflexions peu\r
+sereines.\r
+\r
+--Monsieur, dit-il, je ne puis vous recevoir.\r
+\r
+L'homme se dressa à demi sur son séant.\r
+\r
+--Comment! Avez-vous peur que je ne paye pas? Voulez-vous que je paye\r
+d'avance? J'ai de l'argent, vous dis-je.\r
+\r
+--Ce n'est pas cela.\r
+\r
+--Quoi donc?\r
+\r
+--Vous avez de l'argent....\r
+\r
+--Oui, dit l'homme.\r
+\r
+--Et moi, dit l'hôte, je n'ai pas de chambre.\r
+\r
+L'homme reprit tranquillement:\r
+\r
+--Mettez-moi à l'écurie.\r
+\r
+--Je ne puis.\r
+\r
+--Pourquoi?\r
+\r
+--Les chevaux prennent toute la place.\r
+\r
+--Eh bien, repartit l'homme, un coin dans le grenier. Une botte de\r
+paille. Nous verrons cela après dîner.\r
+\r
+--Je ne puis vous donner à dîner.\r
+\r
+Cette déclaration, faite d'un ton mesuré, mais ferme, parut grave à\r
+l'étranger. Il se leva.\r
+\r
+--Ah bah! mais je meurs de faim, moi. J'ai marché dès le soleil levé.\r
+J'ai fait douze lieues. Je paye. Je veux manger.\r
+\r
+--Je n'ai rien, dit l'hôte.\r
+\r
+L'homme éclata de rire et se tourna vers la cheminée et les fourneaux.\r
+\r
+--Rien! et tout cela?\r
+\r
+--Tout cela m'est retenu.\r
+\r
+--Par qui?\r
+\r
+--Par ces messieurs les rouliers.\r
+\r
+--Combien sont-ils?\r
+\r
+--Douze.\r
+\r
+--Il y a là à manger pour vingt.\r
+\r
+--Ils ont tout retenu et tout payé d'avance.\r
+\r
+L'homme se rassit et dit sans hausser la voix:\r
+\r
+--Je suis à l'auberge, j'ai faim, et je reste.\r
+\r
+L'hôte alors se pencha à son oreille, et lui dit d'un accent qui le fit\r
+tressaillir:\r
+\r
+--Allez-vous en.\r
+\r
+Le voyageur était courbé en cet instant et poussait quelques braises\r
+dans le feu avec le bout ferré de son bâton, il se retourna vivement,\r
+et, comme il ouvrait la bouche pour répliquer, l'hôte le regarda\r
+fixement et ajouta toujours à voix basse:\r
+\r
+--Tenez, assez de paroles comme cela. Voulez-vous que je vous dise votre\r
+nom? Vous vous appelez Jean Valjean. Maintenant voulez-vous que je vous\r
+dise qui vous êtes? En vous voyant entrer, je me suis douté de quelque\r
+chose, j'ai envoyé à la mairie, et voici ce qu'on m'a répondu.\r
+Savez-vous lire?\r
+\r
+En parlant ainsi il tendait à l'étranger, tout déplié, le papier qui\r
+venait de voyager de l'auberge à la mairie, et de la mairie à l'auberge.\r
+L'homme y jeta un regard. L'aubergiste reprit après un silence:\r
+\r
+--J'ai l'habitude d'être poli avec tout le monde. Allez-vous-en.\r
+\r
+L'homme baissa la tête, ramassa le sac qu'il avait déposé à terre, et\r
+s'en alla. Il prit la grande rue. Il marchait devant lui au hasard,\r
+rasant de près les maisons, comme un homme humilié et triste. Il ne se\r
+retourna pas une seule fois. S'il s'était retourné, il aurait vu\r
+l'aubergiste de la _Croix-de-Colbas_ sur le seuil de sa porte, entouré\r
+de tous les voyageurs de son auberge et de tous les passants de la rue,\r
+parlant vivement et le désignant du doigt, et, aux regards de défiance\r
+et d'effroi du groupe, il aurait deviné qu'avant peu son arrivée serait\r
+l'événement de toute la ville.\r
+\r
+Il ne vit rien de tout cela. Les gens accablés ne regardent pas derrière\r
+eux. Ils ne savent que trop que le mauvais sort les suit.\r
+\r
+Il chemina ainsi quelque temps, marchant toujours, allant à l'aventure\r
+par des rues qu'il ne connaissait pas, oubliant la fatigue, comme cela\r
+arrive dans la tristesse. Tout à coup il sentit vivement la faim. La\r
+nuit approchait. Il regarda autour de lui pour voir s'il ne découvrirait\r
+pas quelque gîte.\r
+\r
+La belle hôtellerie s'était fermée pour lui; il cherchait quelque\r
+cabaret bien humble, quelque bouge bien pauvre.\r
+\r
+Précisément une lumière s'allumait au bout de la rue; une branche de\r
+pin, pendue à une potence en fer, se dessinait sur le ciel blanc du\r
+crépuscule. Il y alla.\r
+\r
+C'était en effet un cabaret. Le cabaret qui est dans la rue de Chaffaut.\r
+\r
+Le voyageur s'arrêta un moment, et regarda par la vitre l'intérieur de\r
+la salle basse du cabaret, éclairée par une petite lampe sur une table\r
+et par un grand feu dans la cheminée. Quelques hommes y buvaient. L'hôte\r
+se chauffait. La flamme faisait bruire une marmite de fer accrochée à la\r
+crémaillère.\r
+\r
+On entre dans ce cabaret, qui est aussi une espèce d'auberge, par deux\r
+portes. L'une donne sur la rue, l'autre s'ouvre sur une petite cour\r
+pleine de fumier.\r
+\r
+Le voyageur n'osa pas entrer par la porte de la rue. Il se glissa dans\r
+la cour, s'arrêta encore, puis leva timidement le loquet et poussa la\r
+porte.\r
+\r
+--Qui va là? dit le maître.\r
+\r
+--Quelqu'un qui voudrait souper et coucher.\r
+\r
+--C'est bon. Ici on soupe et on couche.\r
+\r
+Il entra. Tous les gens qui buvaient se retournèrent. La lampe\r
+l'éclairait d'un côté, le feu de l'autre. On l'examina quelque temps\r
+pendant qu'il défaisait son sac.\r
+\r
+L'hôte lui dit:\r
+\r
+--Voilà du feu. Le souper cuit dans la marmite. Venez vous chauffer,\r
+camarade.\r
+\r
+Il alla s'asseoir près de l'âtre. Il allongea devant le feu ses pieds\r
+meurtris par la fatigue; une bonne odeur sortait de la marmite. Tout ce\r
+qu'on pouvait distinguer de son visage sous sa casquette baissée prit\r
+une vague apparence de bien-être mêlée à cet autre aspect si poignant\r
+que donne l'habitude de la souffrance.\r
+\r
+C'était d'ailleurs un profil ferme, énergique et triste. Cette\r
+physionomie était étrangement composée; elle commençait par paraître\r
+humble et finissait par sembler sévère. L'oeil luisait sous les sourcils\r
+comme un feu sous une broussaille.\r
+\r
+Cependant un des hommes attablés était un poissonnier qui, avant\r
+d'entrer au cabaret de la rue de Chaffaut, était allé mettre son cheval\r
+à l'écurie chez Labarre. Le hasard faisait que le matin même il avait\r
+rencontré cet étranger de mauvaise mine, cheminant entre Bras dasse\r
+et... j'ai oublié le nom. (Je crois que c'est Escoublon). Or, en le\r
+rencontrant, l'homme, qui paraissait déjà très fatigué, lui avait\r
+demandé de le prendre en croupe; à quoi le poissonnier n'avait répondu\r
+qu'en doublant le pas. Ce poissonnier faisait partie, une demi-heure\r
+auparavant, du groupe qui entourait Jacquin Labarre, et lui-même avait\r
+raconté sa désagréable rencontre du matin aux gens de _la\r
+Croix-de-Colbas_. Il fit de sa place au cabaretier un signe\r
+imperceptible. Le cabaretier vint à lui. Ils échangèrent quelques\r
+paroles à voix basse. L'homme était retombé dans ses réflexions.\r
+\r
+Le cabaretier revint à la cheminée, posa brusquement sa main sur\r
+l'épaule de l'homme, et lui dit:\r
+\r
+--Tu vas t'en aller d'ici.\r
+\r
+L'étranger se retourna et répondit avec douceur.\r
+\r
+--Ah! vous savez?\r
+\r
+--Oui.\r
+\r
+--On m'a renvoyé de l'autre auberge.\r
+\r
+--Et l'on te chasse de celle-ci.\r
+\r
+--Où voulez-vous que j'aille?\r
+\r
+--Ailleurs.\r
+\r
+L'homme prit son bâton et son sac, et s'en alla.\r
+\r
+Comme il sortait, quelques enfants, qui l'avaient suivi depuis _la\r
+Croix-de-Colbas_ et qui semblaient l'attendre, lui jetèrent des pierres.\r
+Il revint sur ses pas avec colère et les menaça de son bâton; les\r
+enfants se dispersèrent comme une volée d'oiseaux.\r
+\r
+Il passa devant la prison. À la porte pendait une chaîne de fer attachée\r
+à une cloche. Il sonna.\r
+\r
+Un guichet s'ouvrit.\r
+\r
+--Monsieur le guichetier, dit-il en ôtant respectueusement sa casquette,\r
+voudriez-vous bien m'ouvrir et me loger pour cette nuit?\r
+\r
+Une voix répondit:\r
+\r
+--Une prison n'est pas une auberge. Faites-vous arrêter. On vous\r
+ouvrira.\r
+\r
+Le guichet se referma.\r
+\r
+Il entra dans une petite rue où il y a beaucoup de jardins. Quelques-uns\r
+ne sont enclos que de haies, ce qui égaye la rue. Parmi ces jardins et\r
+ces haies, il vit une petite maison d'un seul étage dont la fenêtre\r
+était éclairée. Il regarda par cette vitre comme il avait fait pour le\r
+cabaret. C'était une grande chambre blanchie à la chaux, avec un lit\r
+drapé d'indienne imprimée, et un berceau dans un coin, quelques chaises\r
+de bois et un fusil à deux coups accroché au mur. Une table était servie\r
+au milieu de la chambre. Une lampe de cuivre éclairait la nappe de\r
+grosse toile blanche, le broc d'étain luisant comme l'argent et plein de\r
+vin et la soupière brune qui fumait. À cette table était assis un homme\r
+d'une quarantaine d'années, à la figure joyeuse et ouverte, qui faisait\r
+sauter un petit enfant sur ses genoux. Près de lui, une femme toute\r
+jeune allaitait un autre enfant. Le père riait, l'enfant riait, la mère\r
+souriait.\r
+\r
+L'étranger resta un moment rêveur devant ce spectacle doux et calmant.\r
+Que se passait-il en lui? Lui seul eût pu le dire. Il est probable qu'il\r
+pensa que cette maison joyeuse serait hospitalière, et que là où il\r
+voyait tant de bonheur il trouverait peut-être un peu de pitié.\r
+\r
+Il frappa au carreau un petit coup très faible.\r
+\r
+On n'entendit pas.\r
+\r
+Il frappa un second coup.\r
+\r
+Il entendit la femme qui disait:\r
+\r
+--Mon homme, il me semble qu'on frappe.\r
+\r
+--Non, répondit le mari.\r
+\r
+Il frappa un troisième coup.\r
+\r
+Le mari se leva, prit la lampe, et alla à la porte qu'il ouvrit.\r
+\r
+C'était un homme de haute taille, demi-paysan, demi-artisan. Il portait\r
+un vaste tablier de cuir qui montait jusqu'à son épaule gauche, et dans\r
+lequel faisaient ventre un marteau, un mouchoir rouge, une poire à\r
+poudre, toutes sortes d'objets que la ceinture retenait comme dans une\r
+poche. Il renversait la tête en arrière; sa chemise largement ouverte et\r
+rabattue montrait son cou de taureau, blanc et nu. Il avait d'épais\r
+sourcils, d'énormes favoris noirs, les yeux à fleur de tête, le bas du\r
+visage en museau, et sur tout cela cet air d'être chez soi qui est une\r
+chose inexprimable.\r
+\r
+--Monsieur, dit le voyageur, pardon. En payant, pourriez-vous me donner\r
+une assiettée de soupe et un coin pour dormir dans ce hangar qui est là\r
+dans ce jardin? Dites, pourriez-vous? En payant?\r
+\r
+--Qui êtes-vous? demanda le maître du logis.\r
+\r
+L'homme répondit:\r
+\r
+--J'arrive de Puy-Moisson. J'ai marché toute la journée. J'ai fait douze\r
+lieues. Pourriez-vous? En payant?\r
+\r
+--Je ne refuserais pas, dit le paysan, de loger quelqu'un de bien qui\r
+payerait. Mais pourquoi n'allez-vous pas à l'auberge.\r
+\r
+--Il n'y a pas de place.\r
+\r
+--Bah! pas possible. Ce n'est pas jour de foire ni de marché. Êtes-vous\r
+allé chez Labarre?\r
+\r
+--Oui.\r
+\r
+--Eh bien?\r
+\r
+Le voyageur répondit avec embarras:\r
+\r
+--Je ne sais pas, il ne m'a pas reçu.\r
+\r
+--Êtes-vous allé chez chose, de la rue de Chaffaut?\r
+\r
+L'embarras de l'étranger croissait. Il balbutia:\r
+\r
+--Il ne m'a pas reçu non plus.\r
+\r
+Le visage du paysan prit une expression de défiance, il regarda le\r
+nouveau venu de la tête aux pieds, et tout à coup il s'écria avec une\r
+sorte de frémissement:\r
+\r
+--Est-ce que vous seriez l'homme?...\r
+\r
+Il jeta un nouveau coup d'oeil sur l'étranger, fit trois pas en arrière,\r
+posa la lampe sur la table et décrocha son fusil du mur.\r
+\r
+Cependant aux paroles du paysan: _Est-ce que vous seriez l'homme?..._ la\r
+femme s'était levée, avait pris ses deux enfants dans ses bras et\r
+s'était réfugiée précipitamment derrière son mari, regardant l'étranger\r
+avec épouvante, la gorge nue, les yeux effarés, en murmurant tout bas:_\r
+Tso-maraude_.\r
+\r
+Tout cela se fit en moins de temps qu'il ne faut pour se le figurer.\r
+Après avoir examiné quelques instants l'homme comme on examine une\r
+vipère, le maître du logis revint à la porte et dit:\r
+\r
+--Va-t'en.\r
+\r
+--Par grâce, reprit l'homme, un verre d'eau.\r
+\r
+--Un coup de fusil! dit le paysan.\r
+\r
+Puis il referma la porte violemment, et l'homme l'entendit tirer deux\r
+gros verrous. Un moment après, la fenêtre se ferma au volet, et un bruit\r
+de barre de fer qu'on posait parvint au dehors.\r
+\r
+La nuit continuait de tomber. Le vent froid des Alpes soufflait. À la\r
+lueur du jour expirant, l'étranger aperçut dans un des jardins qui\r
+bordent la rue une sorte de hutte qui lui parut maçonnée en mottes de\r
+gazon. Il franchit résolument une barrière de bois et se trouva dans le\r
+jardin. Il s'approcha de la hutte; elle avait pour porte une étroite\r
+ouverture très basse et elle ressemblait à ces constructions que les\r
+cantonniers se bâtissent au bord des routes. Il pensa sans doute que\r
+c'était en effet le logis d'un cantonnier; il souffrait du froid et de\r
+la faim; il s'était résigné à la faim, mais c'était du moins là un abri\r
+contre le froid. Ces sortes de logis ne sont habituellement pas occupés\r
+la nuit. Il se coucha à plat ventre et se glissa dans la hutte. Il y\r
+faisait chaud, et il y trouva un assez bon lit de paille. Il resta un\r
+moment étendu sur ce lit, sans pouvoir faire un mouvement tant il était\r
+fatigué. Puis, comme son sac sur son dos le gênait et que c'était\r
+d'ailleurs un oreiller tout trouvé, il se mit à déboucler une des\r
+courroies. En ce moment un grondement farouche se fit entendre. Il leva\r
+les yeux. La tête d'un dogue énorme se dessinait dans l'ombre à\r
+l'ouverture de la hutte.\r
+\r
+C'était la niche d'un chien.\r
+\r
+Il était lui-même vigoureux et redoutable; il s'arma de son bâton, il se\r
+fit de son sac un bouclier, et sortit de la niche comme il put, non sans\r
+élargir les déchirures de ses haillons.\r
+\r
+Il sortit également du jardin, mais à reculons, obligé, pour tenir le\r
+dogue en respect, d'avoir recours à cette manoeuvre du bâton que les\r
+maîtres en ce genre d'escrime appellent _la rose couverte_.\r
+\r
+Quand il eut, non sans peine, repassé la barrière et qu'il se retrouva\r
+dans la rue, seul, sans gîte, sans toit, sans abri, chassé même de ce\r
+lit de paille et de cette niche misérable, il se laissa tomber plutôt\r
+qu'il ne s'assit sur une pierre, et il paraît qu'un passant qui\r
+traversait l'entendit s'écrier:\r
+\r
+--Je ne suis pas même un chien!\r
+\r
+Bientôt il se releva et se remit à marcher. Il sortit de la ville,\r
+espérant trouver quelque arbre ou quelque meule dans les champs, et s'y\r
+abriter.\r
+\r
+Il chemina ainsi quelque temps, la tête toujours baissée. Quand il se\r
+sentit loin de toute habitation humaine, il leva les yeux et chercha\r
+autour de lui. Il était dans un champ; il avait devant lui une de ces\r
+collines basses couvertes de chaume coupé ras, qui après la moisson\r
+ressemblent à des têtes tondues.\r
+\r
+L'horizon était tout noir; ce n'était pas seulement le sombre de la\r
+nuit; c'étaient des nuages très bas qui semblaient s'appuyer sur la\r
+colline même et qui montaient, emplissant tout le ciel. Cependant, comme\r
+la lune allait se lever et qu'il flottait encore au zénith un reste de\r
+clarté crépusculaire, ces nuages formaient au haut du ciel une sorte de\r
+voûte blanchâtre d'où tombait sur la terre une lueur.\r
+\r
+La terre était donc plus éclairée que le ciel, ce qui est un effet\r
+particulièrement sinistre, et la colline, d'un pauvre et chétif contour,\r
+se dessinait vague et blafarde sur l'horizon ténébreux. Tout cet\r
+ensemble était hideux, petit, lugubre et borné. Rien dans le champ ni\r
+sur la colline qu'un arbre difforme qui se tordait en frissonnant à\r
+quelques pas du voyageur.\r
+\r
+Cet homme était évidemment très loin d'avoir de ces délicates habitudes\r
+d'intelligence et d'esprit qui font qu'on est sensible aux aspects\r
+mystérieux des choses; cependant il y avait dans ce ciel, dans cette\r
+colline, dans cette plaine et dans cet arbre, quelque chose de si\r
+profondément désolé qu'après un moment d'immobilité et de rêverie, il\r
+rebroussa chemin brusquement. Il y a des instants où la nature semble\r
+hostile.\r
+\r
+Il revint sur ses pas. Les portes de Digne étaient fermées. Digne, qui a\r
+soutenu des sièges dans les guerres de religion, était encore entourée\r
+en 1815 de vieilles murailles flanquées de tours carrées qu'on a\r
+démolies depuis. Il passa par une brèche et rentra dans la ville.\r
+\r
+Il pouvait être huit heures du soir. Comme il ne connaissait pas les\r
+rues, il recommença sa promenade à l'aventure.\r
+\r
+Il parvint ainsi à la préfecture, puis au séminaire. En passant sur la\r
+place de la cathédrale, il montra le poing à l'église.\r
+\r
+Il y a au coin de cette place une imprimerie. C'est là que furent\r
+imprimées pour la première fois les proclamations de l'empereur et de la\r
+garde impériale à l'armée, apportées de l'île d'Elbe et dictées par\r
+Napoléon lui-même.\r
+\r
+Épuisé de fatigue et n'espérant plus rien, il se coucha sur le banc de\r
+pierre qui est à la porte de cette imprimerie.\r
+\r
+Une vieille femme sortait de l'église en ce moment. Elle vit cet homme\r
+étendu dans l'ombre.\r
+\r
+--Que faites-vous là, mon ami? dit-elle.\r
+\r
+Il répondit durement et avec colère:\r
+\r
+--Vous le voyez, bonne femme, je me couche.\r
+\r
+La bonne femme, bien digne de ce nom en effet, était madame la marquise\r
+de R.\r
+\r
+--Sur ce banc? reprit-elle.\r
+\r
+--J'ai eu pendant dix-neuf ans un matelas de bois, dit l'homme, j'ai\r
+aujourd'hui un matelas de pierre.\r
+\r
+--Vous avez été soldat?\r
+\r
+--Oui, bonne femme. Soldat.\r
+\r
+--Pourquoi n'allez-vous pas à l'auberge?\r
+\r
+--Parce que je n'ai pas d'argent.\r
+\r
+--Hélas, dit madame de R., je n'ai dans ma bourse que quatre sous.\r
+\r
+--Donnez toujours.\r
+\r
+L'homme prit les quatre sous. Madame de R. continua:\r
+\r
+--Vous ne pouvez vous loger avec si peu dans une auberge. Avez-vous\r
+essayé pourtant? Il est impossible que vous passiez ainsi la nuit. Vous\r
+avez sans doute froid et faim. On aurait pu vous loger par charité.\r
+\r
+--J'ai frappé à toutes les portes.\r
+\r
+--Eh bien?\r
+\r
+--Partout on m'a chassé.\r
+\r
+La «bonne femme» toucha le bras de l'homme et lui montra de l'autre côté\r
+de la place une petite maison basse à côté de l'évêché.\r
+\r
+--Vous avez, reprit-elle, frappé à toutes les portes?\r
+\r
+--Oui.\r
+\r
+--Avez-vous frappé à celle-là?\r
+\r
+--Non.\r
+\r
+--Frappez-y.\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+La prudence conseillée à la sagesse\r
+\r
+\r
+Ce soir-là, M. l'évêque de Digne, après sa promenade en ville, était\r
+resté assez tard enfermé dans sa chambre. Il s'occupait d'un grand\r
+travail sur les _Devoirs_, lequel est malheureusement demeuré inachevé.\r
+Il dépouillait soigneusement tout ce que les Pères et les Docteurs ont\r
+dit sur cette grave matière. Son livre était divisé en deux parties;\r
+premièrement les devoirs de tous, deuxièmement les devoirs de chacun,\r
+selon la classe à laquelle il appartient. Les devoirs de tous sont les\r
+grands devoirs. Il y en a quatre. Saint Matthieu les indique: devoirs\r
+envers Dieu (Matth., VI), devoirs envers soi-même (Matth., V, 29, 30),\r
+devoirs envers le prochain (Matth., VII, 12), devoirs envers les\r
+créatures (Matth., VI, 20, 25). Pour les autres devoirs, l'évêque les\r
+avait trouvés indiqués et prescrits ailleurs; aux souverains et aux\r
+sujets, dans l'Épître aux Romains; aux magistrats, aux épouses, aux\r
+mères et aux jeunes hommes, par saint Pierre; aux maris, aux pères, aux\r
+enfants et aux serviteurs, dans l'Épître aux Éphésiens; aux fidèles,\r
+dans l'Épître aux Hébreux; aux vierges, dans l'Épître aux Corinthiens.\r
+Il faisait laborieusement de toutes ces prescriptions un ensemble\r
+harmonieux qu'il voulait présenter aux âmes.\r
+\r
+Il travaillait encore à huit heures, écrivant assez incommodément sur de\r
+petits carrés de papier avec un gros livre ouvert sur ses genoux, quand\r
+madame Magloire entra, selon son habitude, pour prendre l'argenterie\r
+dans le placard près du lit. Un moment après, l'évêque, sentant que le\r
+couvert était mis et que sa soeur l'attendait peut-être, ferma son\r
+livre, se leva de sa table et entra dans la salle à manger.\r
+\r
+La salle à manger était une pièce oblongue à cheminée, avec porte sur la\r
+rue (nous l'avons dit), et fenêtre sur le jardin.\r
+\r
+Madame Magloire achevait en effet de mettre le couvert.\r
+\r
+Tout en vaquant au service, elle causait avec mademoiselle Baptistine.\r
+\r
+Une lampe était sur la table; la table était près de la cheminée. Un\r
+assez bon feu était allumé.\r
+\r
+On peut se figurer facilement ces deux femmes qui avaient toutes deux\r
+passé soixante ans: madame Magloire petite, grasse, vive; mademoiselle\r
+Baptistine, douce, mince, frêle, un peu plus grande que son frère, vêtue\r
+d'une robe de soie puce, couleur à la mode en 1806, qu'elle avait\r
+achetée alors à Paris et qui lui durait encore. Pour emprunter des\r
+locutions vulgaires qui ont le mérite de dire avec un seul mot une idée\r
+qu'une page suffirait à peine à exprimer, madame Magloire avait l'air\r
+d'une _paysanne_ et mademoiselle Baptistine d'une _dame_. Madame\r
+Magloire avait un bonnet blanc à tuyaux, au cou une jeannette d'or, le\r
+seul bijou de femme qu'il y eût dans la maison, un fichu très blanc\r
+sortant de la robe de bure noire à manches larges et courtes, un tablier\r
+de toile de coton à carreaux rouges et verts, noué à la ceinture d'un\r
+ruban vert, avec pièce d'estomac pareille rattachée par deux épingles\r
+aux deux coins d'en haut, aux pieds de gros souliers et des bas jaunes\r
+comme les femmes de Marseille. La robe de mademoiselle Baptistine était\r
+coupée sur les patrons de 1806, taille courte, fourreau étroit, manches\r
+à épaulettes, avec pattes et boutons. Elle cachait ses cheveux gris sous\r
+une perruque frisée dite à _l'enfant_. Madame Magloire avait l'air\r
+intelligent, vif et bon; les deux angles de sa bouche inégalement\r
+relevés et la lèvre supérieure plus grosse que la lèvre inférieure lui\r
+donnaient quelque chose de bourru et d'impérieux. Tant que monseigneur\r
+se taisait, elle lui parlait résolument avec un mélange de respect et de\r
+liberté; mais dès que monseigneur parlait, on a vu cela, elle obéissait\r
+passivement comme mademoiselle. Mademoiselle Baptistine ne parlait même\r
+pas. Elle se bornait à obéir et à complaire. Même quand elle était\r
+jeune, elle n'était pas jolie, elle avait de gros yeux bleus à fleur de\r
+tête et le nez long et busqué; mais tout son visage, toute sa personne,\r
+nous l'avons dit en commençant, respiraient une ineffable bonté. Elle\r
+avait toujours été prédestinée à la mansuétude; mais la foi, la charité,\r
+l'espérance, ces trois vertus qui chauffent doucement l'âme, avaient\r
+élevé peu à peu cette mansuétude jusqu'à la sainteté. La nature n'en\r
+avait fait qu'une brebis, la religion en avait fait un ange. Pauvre\r
+sainte fille! doux souvenir disparu! Mademoiselle Baptistine a depuis\r
+raconté tant de fois ce qui s'était passé à l'évêché cette soirée-là,\r
+que plusieurs personnes qui vivent encore s'en rappellent les moindres\r
+détails.\r
+\r
+Au moment où M. l'évêque entra, madame Magloire parlait avec quelque\r
+vivacité. Elle entretenait _mademoiselle_ d'un sujet qui lui était\r
+familier et auquel l'évêque était accoutumé. Il s'agissait du loquet de\r
+la porte d'entrée.\r
+\r
+Il paraît que, tout en allant faire quelques provisions pour le souper,\r
+madame Magloire avait entendu dire des choses en divers lieux. On\r
+parlait d'un rôdeur de mauvaise mine; qu'un vagabond suspect serait\r
+arrivé, qu'il devait être quelque part dans la ville, et qu'il se\r
+pourrait qu'il y eût de méchantes rencontres pour ceux qui s'aviseraient\r
+de rentrer tard chez eux cette nuit-là. Que la police était bien mal\r
+faite du reste, attendu que M. le préfet et M. le maire ne s'aimaient\r
+pas, et cherchaient à se nuire en faisant arriver des événements. Que\r
+c'était donc aux gens sages à faire la police eux-mêmes et à se bien\r
+garder, et qu'il faudrait avoir soin de dûment clore, verrouiller et\r
+barricader sa maison, _et de bien fermer ses portes_.\r
+\r
+Madame Magloire appuya sur ce dernier mot; mais l'évêque venait de sa\r
+chambre où il avait eu assez froid, il s'était assis devant la cheminée\r
+et se chauffait, et puis il pensait à autre chose. Il ne releva pas le\r
+mot à effet que madame Magloire venait de laisser tomber. Elle le\r
+répéta. Alors, mademoiselle Baptistine, voulant satisfaire madame\r
+Magloire sans déplaire à son frère, se hasarda à dire timidement:\r
+\r
+--Mon frère, entendez-vous ce que dit madame Magloire?\r
+\r
+--J'en ai entendu vaguement quelque chose, répondit l'évêque.\r
+\r
+Puis tournant à demi sa chaise, mettant ses deux mains sur ses genoux,\r
+et levant vers la vieille servante son visage cordial et facilement\r
+joyeux, que le feu éclairait d'en bas:\r
+\r
+--Voyons. Qu'y a-t-il? qu'y a-t-il? Nous sommes donc dans quelque gros\r
+danger?\r
+\r
+Alors madame Magloire recommença toute l'histoire, en l'exagérant\r
+quelque peu, sans s'en douter. Il paraîtrait qu'un bohémien, un\r
+va-nu-pieds, une espèce de mendiant dangereux serait en ce moment dans\r
+la ville. Il s'était présenté pour loger chez Jacquin Labarre qui\r
+n'avait pas voulu le recevoir. On l'avait vu arriver par le boulevard\r
+Gassendi et rôder dans les rues à la brume. Un homme de sac et de corde\r
+avec une figure terrible.\r
+\r
+--Vraiment? dit l'évêque.\r
+\r
+Ce consentement à l'interroger encouragea madame Magloire; cela lui\r
+semblait indiquer que l'évêque n'était pas loin de s'alarmer; elle\r
+poursuivit triomphante:\r
+\r
+--Oui, monseigneur. C'est comme cela. Il y aura quelque malheur cette\r
+nuit dans la ville. Tout le monde le dit. Avec cela que la police est si\r
+mal faite (répétition inutile). Vivre dans un pays de montagnes, et\r
+n'avoir pas même de lanternes la nuit dans les rues! On sort. Des fours,\r
+quoi! Et je dis, monseigneur, et mademoiselle que voilà dit comme moi....\r
+\r
+--Moi, interrompit la soeur, je ne dis rien. Ce que mon frère fait est\r
+bien fait.\r
+\r
+Madame Magloire continua comme s'il n'y avait pas eu de protestation:\r
+\r
+--Nous disons que cette maison-ci n'est pas sûre du tout; que, si\r
+monseigneur le permet, je vais aller dire à Paulin Musebois, le\r
+serrurier, qu'il vienne remettre les anciens verrous de la porte; on les\r
+a là, c'est une minute; et je dis qu'il faut des verrous, monseigneur,\r
+ne serait-ce que pour cette nuit; car je dis qu'une porte qui s'ouvre du\r
+dehors avec un loquet, par le premier passant venu, rien n'est plus\r
+terrible; avec cela que monseigneur a l'habitude de toujours dire\r
+d'entrer, et que d'ailleurs, même au milieu de la nuit, ô mon Dieu! on\r
+n'a pas besoin d'en demander la permission....\r
+\r
+En ce moment, on frappa à la porte un coup assez violent.\r
+\r
+--Entrez, dit l'évêque.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+Héroïsme de l'obéissance passive\r
+\r
+\r
+La porte s'ouvrit.\r
+\r
+Elle s'ouvrit vivement, toute grande, comme si quelqu'un la poussait\r
+avec énergie et résolution.\r
+\r
+Un homme entra.\r
+\r
+Cet homme, nous le connaissons déjà. C'est le voyageur que nous avons vu\r
+tout à l'heure errer cherchant un gîte.\r
+\r
+Il entra, fit un pas, et s'arrêta, laissant la porte ouverte derrière\r
+lui. Il avait son sac sur l'épaule, son bâton à la main, une expression\r
+rude, hardie, fatiguée et violente dans les yeux. Le feu de la cheminée\r
+l'éclairait. Il était hideux. C'était une sinistre apparition.\r
+\r
+Madame Magloire n'eut pas même la force de jeter un cri. Elle\r
+tressaillit, et resta béante.\r
+\r
+Mademoiselle Baptistine se retourna, aperçut l'homme qui entrait et se\r
+dressa à demi d'effarement, puis, ramenant peu à peu sa tête vers la\r
+cheminée, elle se mit à regarder son frère et son visage redevint\r
+profondément calme et serein.\r
+\r
+L'évêque fixait sur l'homme un oeil tranquille.\r
+\r
+Comme il ouvrait la bouche, sans doute pour demander au nouveau venu ce\r
+qu'il désirait, l'homme appuya ses deux mains à la fois sur son bâton,\r
+promena ses yeux tour à tour sur le vieillard et les femmes, et, sans\r
+attendre que l'évêque parlât, dit d'une voix haute:\r
+\r
+--Voici. Je m'appelle Jean Valjean. Je suis un galérien. J'ai passé\r
+dix-neuf ans au bagne. Je suis libéré depuis quatre jours et en route\r
+pour Pontarlier qui est ma destination. Quatre jours et que je marche\r
+depuis Toulon. Aujourd'hui, j'ai fait douze lieues à pied. Ce soir, en\r
+arrivant dans ce pays, j'ai été dans une auberge, on m'a renvoyé à cause\r
+de mon passeport jaune que j'avais montré à la mairie. Il avait fallu.\r
+J'ai été à une autre auberge. On m'a dit: Va-t-en! Chez l'un, chez\r
+l'autre. Personne n'a voulu de moi. J'ai été à la prison, le guichetier\r
+n'a pas ouvert. J'ai été dans la niche d'un chien. Ce chien m'a mordu et\r
+m'a chassé, comme s'il avait été un homme. On aurait dit qu'il savait\r
+qui j'étais. Je m'en suis allé dans les champs pour coucher à la belle\r
+étoile. Il n'y avait pas d'étoile. J'ai pensé qu'il pleuvrait, et qu'il\r
+n'y avait pas de bon Dieu pour empêcher de pleuvoir, et je suis rentré\r
+dans la ville pour y trouver le renfoncement d'une porte. Là, dans la\r
+place, j'allais me coucher sur une pierre. Une bonne femme m'a montré\r
+votre maison et m'a dit: «Frappe là». J'ai frappé. Qu'est-ce que c'est\r
+ici? Êtes-vous une auberge? J'ai de l'argent. Ma masse. Cent neuf francs\r
+quinze sous que j'ai gagnés au bagne par mon travail en dix-neuf ans. Je\r
+payerai. Qu'est-ce que cela me fait? J'ai de l'argent. Je suis très\r
+fatigué, douze lieues à pied, j'ai bien faim. Voulez-vous que je reste?\r
+\r
+--Madame Magloire, dit l'évêque, vous mettrez un couvert de plus.\r
+\r
+L'homme fit trois pas et s'approcha de la lampe qui était sur la table.\r
+\r
+--Tenez, reprit-il, comme s'il n'avait pas bien compris, ce n'est pas\r
+ça. Avez-vous entendu? Je suis un galérien. Un forçat. Je viens des\r
+galères.\r
+\r
+Il tira de sa poche une grande feuille de papier jaune qu'il déplia.\r
+\r
+--Voilà mon passeport. Jaune, comme vous voyez. Cela sert à me faire\r
+chasser de partout où je suis. Voulez-vous lire? Je sais lire, moi. J'ai\r
+appris au bagne. Il y a une école pour ceux qui veulent. Tenez, voilà ce\r
+qu'on a mis sur le passeport: «Jean Valjean, forçat libéré, natif\r
+de...--cela vous est égal...--Est resté dix-neuf ans au bagne. Cinq ans\r
+pour vol avec effraction. Quatorze ans pour avoir tenté de s'évader\r
+quatre fois. Cet homme est très dangereux.»--Voilà! Tout le monde m'a\r
+jeté dehors. Voulez-vous me recevoir, vous? Est-ce une auberge?\r
+Voulez-vous me donner à manger et à coucher? Avez-vous une écurie?\r
+\r
+--Madame Magloire, dit l'évêque, vous mettrez des draps blancs au lit de\r
+l'alcôve.\r
+\r
+Nous avons déjà expliqué de quelle nature était l'obéissance des deux\r
+femmes.\r
+\r
+Madame Magloire sortit pour exécuter ces ordres. L'évêque se tourna vers\r
+l'homme.\r
+\r
+--Monsieur, asseyez-vous et chauffez-vous. Nous allons souper dans un\r
+instant, et l'on fera votre lit pendant que vous souperez.\r
+\r
+Ici l'homme comprit tout à fait. L'expression de son visage, jusqu'alors\r
+sombre et dure, s'empreignit de stupéfaction, de doute, de joie, et\r
+devint extraordinaire. Il se mit à balbutier comme un homme fou:\r
+\r
+--Vrai? quoi? vous me gardez? vous ne me chassez pas! un forçat! Vous\r
+m'appelez monsieur! vous ne me tutoyez pas! Va-t-en, chien! qu'on me dit\r
+toujours. Je croyais bien que vous me chasseriez. Aussi j'avais dit tout\r
+de suite qui je suis. Oh! la brave femme qui m'a enseigné ici! Je vais\r
+souper! un lit! Un lit avec des matelas et des draps! comme tout le\r
+monde! il y a dix-neuf ans que je n'ai couché dans un lit! Vous voulez\r
+bien que je ne m'en aille pas! Vous êtes de dignes gens! D'ailleurs j'ai\r
+de l'argent. Je payerai bien. Pardon, monsieur l'aubergiste, comment\r
+vous appelez-vous? Je payerai tout ce qu'on voudra. Vous êtes un brave\r
+homme. Vous êtes aubergiste, n'est-ce pas?\r
+\r
+--Je suis, dit l'évêque, un prêtre qui demeure ici.\r
+\r
+--Un prêtre! reprit l'homme. Oh! un brave homme de prêtre! Alors vous ne\r
+me demandez pas d'argent? Le curé, n'est-ce pas? le curé de cette grande\r
+église? Tiens! c'est vrai, que je suis bête! je n'avais pas vu votre\r
+calotte!\r
+\r
+Tout en parlant, il avait déposé son sac et son bâton dans un coin, puis\r
+remis son passeport dans sa poche, et il s'était assis. Mademoiselle\r
+Baptistine le considérait avec douceur. Il continua:\r
+\r
+--Vous êtes humain, monsieur le curé. Vous n'avez pas de mépris. C'est\r
+bien bon un bon prêtre. Alors vous n'avez pas besoin que je paye?\r
+\r
+--Non, dit l'évêque, gardez votre argent. Combien avez-vous? ne\r
+m'avez-vous pas dit cent neuf francs?\r
+\r
+--Quinze sous, ajouta l'homme.\r
+\r
+--Cent neuf francs quinze sous. Et combien de temps avez-vous mis à\r
+gagner cela?\r
+\r
+--Dix-neuf ans.\r
+\r
+--Dix-neuf ans!\r
+\r
+L'évêque soupira profondément.\r
+\r
+L'homme poursuivit:\r
+\r
+--J'ai encore tout mon argent. Depuis quatre jours je n'ai dépensé que\r
+vingt-cinq sous que j'ai gagnés en aidant à décharger des voitures à\r
+Grasse. Puisque vous êtes abbé, je vais vous dire, nous avions un\r
+aumônier au bagne. Et puis un jour j'ai vu un évêque. Monseigneur, qu'on\r
+appelle. C'était l'évêque de la Majore, à Marseille. C'est le curé qui\r
+est sur les curés. Vous savez, pardon, je dis mal cela, mais pour moi,\r
+c'est si loin!--Vous comprenez, nous autres! Il a dit la messe au milieu\r
+du bagne, sur un autel, il avait une chose pointue, en or, sur la tête.\r
+Au grand jour de midi, cela brillait. Nous étions en rang. Des trois\r
+côtés. Avec les canons, mèche allumée, en face de nous. Nous ne voyions\r
+pas bien. Il a parlé, mais il était trop au fond, nous n'entendions pas.\r
+Voilà ce que c'est qu'un évêque.\r
+\r
+Pendant qu'il parlait, l'évêque était allé pousser la porte qui était\r
+restée toute grande ouverte.\r
+\r
+Madame Magloire rentra. Elle apportait un couvert qu'elle mit sur la\r
+table.\r
+\r
+--Madame Magloire, dit l'évêque, mettez ce couvert le plus près possible\r
+du feu.\r
+\r
+Et se tournant vers son hôte:\r
+\r
+--Le vent de nuit est dur dans les Alpes. Vous devez avoir froid,\r
+monsieur?\r
+\r
+Chaque fois qu'il disait ce mot monsieur, avec sa voix doucement grave\r
+et de si bonne compagnie, le visage de l'homme s'illuminait. Monsieur à\r
+un forçat, c'est un verre d'eau à un naufragé de la Méduse. L'ignominie\r
+a soif de considération.\r
+\r
+--Voici, reprit l'évêque, une lampe qui éclaire bien mal.\r
+\r
+Madame Magloire comprit, et elle alla chercher sur la cheminée de la\r
+chambre à coucher de monseigneur les deux chandeliers d'argent qu'elle\r
+posa sur la table tout allumés.\r
+\r
+--Monsieur le curé, dit l'homme, vous êtes bon. Vous ne me méprisez pas.\r
+Vous me recevez chez vous. Vous allumez vos cierges pour moi. Je ne vous\r
+ai pourtant pas caché d'où je viens et que je suis un homme malheureux.\r
+\r
+L'évêque, assis près de lui, lui toucha doucement la main.\r
+\r
+--Vous pouviez ne pas me dire qui vous étiez.\r
+\r
+Ce n'est pas ici ma maison, c'est la maison de Jésus-Christ. Cette porte\r
+ne demande pas à celui qui entre s'il a un nom, mais s'il a une douleur.\r
+Vous souffrez; vous avez faim et soif; soyez le bienvenu. Et ne me\r
+remerciez pas, ne me dites pas que je vous reçois chez moi. Personne\r
+n'est ici chez soi, excepté celui qui a besoin d'un asile. Je vous le\r
+dis à vous qui passez, vous êtes ici chez vous plus que moi-même. Tout\r
+ce qui est ici est à vous. Qu'ai-je besoin de savoir votre nom?\r
+D'ailleurs, avant que vous me le disiez, vous en avez un que je savais.\r
+\r
+L'homme ouvrit des yeux étonnés.\r
+\r
+--Vrai? vous saviez comment je m'appelle?\r
+\r
+--Oui, répondit l'évêque, vous vous appelez mon frère.\r
+\r
+--Tenez, monsieur le curé! s'écria l'homme, j'avais bien faim en entrant\r
+ici; mais vous êtes si bon qu'à présent je ne sais plus ce que j'ai;\r
+cela m'a passé.\r
+\r
+L'évêque le regarda et lui dit:\r
+\r
+--Vous avez bien souffert?\r
+\r
+--Oh! la casaque rouge, le boulet au pied, une planche pour dormir, le\r
+chaud, le froid, le travail, la chiourme, les coups de bâton! La double\r
+chaîne pour rien. Le cachot pour un mot. Même malade au lit, la chaîne.\r
+Les chiens, les chiens sont plus heureux! Dix-neuf ans! J'en ai\r
+quarante-six. À présent, le passeport jaune! Voilà.\r
+\r
+--Oui, reprit l'évêque, vous sortez d'un lieu de tristesse. Écoutez. Il\r
+y aura plus de joie au ciel pour le visage en larmes d'un pécheur\r
+repentant que pour la robe blanche de cent justes. Si vous sortez de ce\r
+lieu douloureux avec des pensées de haine et de colère contre les\r
+hommes, vous êtes digne de pitié; si vous en sortez avec des pensées de\r
+bienveillance, de douceur et de paix, vous valez mieux qu'aucun de nous.\r
+\r
+Cependant madame Magloire avait servi le souper. Une soupe faite avec de\r
+l'eau, de l'huile, du pain et du sel, un peu de lard, un morceau de\r
+viande de mouton, des figues, un fromage frais, et un gros pain de\r
+seigle. Elle avait d'elle-même ajouté à l'ordinaire de M. l'évêque une\r
+bouteille de vieux vin de Mauves.\r
+\r
+Le visage de l'évêque prit tout à coup cette expression de gaîté propre\r
+aux natures hospitalières:\r
+\r
+--À table! dit-il vivement.\r
+\r
+Comme il en avait coutume lorsque quelque étranger soupait avec lui, il\r
+fit asseoir l'homme à sa droite. Mademoiselle Baptistine, parfaitement\r
+paisible et naturelle, prit place à sa gauche.\r
+\r
+L'évêque dit le bénédicité, puis servit lui-même la soupe, selon son\r
+habitude. L'homme se mit à manger avidement.\r
+\r
+Tout à coup l'évêque dit:\r
+\r
+--Mais il me semble qu'il manque quelque chose sur cette table.\r
+\r
+Madame Magloire en effet n'avait mis que les trois couverts absolument\r
+nécessaires. Or c'était l'usage de la maison, quand l'évêque avait\r
+quelqu'un à souper, de disposer sur la nappe les six couverts d'argent,\r
+étalage innocent. Ce gracieux semblant de luxe était une sorte\r
+d'enfantillage plein de charme dans cette maison douce et sévère qui\r
+élevait la pauvreté jusqu'à la dignité.\r
+\r
+Madame Magloire comprit l'observation, sortit sans dire un mot, et un\r
+moment après les trois couverts réclamés par l'évêque brillaient sur la\r
+nappe, symétriquement arrangés devant chacun des trois convives.\r
+\r
+\r
+\r
+\r
+Chapitre IV\r
+\r
+Détails sur les fromageries de Pontarlier\r
+\r
+\r
+Maintenant, pour donner une idée de ce qui se passa à cette table, nous\r
+ne saurions mieux faire que de transcrire ici un passage d'une lettre de\r
+mademoiselle Baptistine à madame de Boischevron, où la conversation du\r
+forçat et de l'évêque est racontée avec une minutie naïve:\r
+\r
+      *       *       *       *       *\r
+\r
+«...Cet homme ne faisait aucune attention à personne. Il mangeait avec\r
+une voracité d'affamé. Cependant, après la soupe, il a dit:\r
+\r
+«--Monsieur le curé du bon Dieu, tout ceci est encore bien trop bon pour\r
+moi, mais je dois dire que les rouliers qui n'ont pas voulu me laisser\r
+manger avec eux font meilleure chère que vous.\r
+\r
+«Entre nous, l'observation m'a un peu choquée. Mon frère a répondu:\r
+\r
+«--Ils ont plus de fatigue que moi.\r
+\r
+«--Non, a repris cet homme, ils ont plus d'argent. Vous êtes pauvre. Je\r
+vois bien. Vous n'êtes peut-être pas même curé. Êtes-vous curé\r
+seulement? Ah! par exemple, si le bon Dieu était juste, vous devriez\r
+bien être curé.\r
+\r
+«--Le bon Dieu est plus que juste, a dit mon frère.\r
+\r
+«Un moment après il a ajouté:\r
+\r
+«--Monsieur Jean Valjean, c'est à Pontarlier que vous allez?\r
+\r
+«--Avec itinéraire obligé.\r
+\r
+«Je crois bien que c'est comme cela que l'homme a dit. Puis il a\r
+continué:\r
+\r
+«--Il faut que je sois en route demain à la pointe du jour. Il fait dur\r
+voyager. Si les nuits sont froides, les journées sont chaudes.\r
+\r
+«--Vous allez là, a repris mon frère, dans un bon pays. À la révolution,\r
+ma famille a été ruinée, je me suis réfugié en Franche-Comté d'abord, et\r
+j'y ai vécu quelque temps du travail de mes bras. J'avais de la bonne\r
+volonté. J'ai trouvé à m'y occuper. On n'a qu'à choisir. Il y a des\r
+papeteries, des tanneries, des distilleries, des huileries, des\r
+fabriques d'horlogerie en grand, des fabriques d'acier, des fabriques de\r
+cuivre, au moins vingt usines de fer, dont quatre à Lods, à Châtillon, à\r
+Audincourt et à Beure qui sont très considérables....\r
+\r
+«Je crois ne pas me tromper et que ce sont bien là les noms que mon\r
+frère a cités, puis il s'est interrompu et m'a adressé la parole:\r
+\r
+«--Chère soeur, n'avons-nous pas des parents dans ce pays-là?\r
+\r
+«J'ai répondu:\r
+\r
+«--Nous en avions, entre autres M. de Lucenet qui était capitaine des\r
+portes à Pontarlier dans l'ancien régime.\r
+\r
+«--Oui, a repris mon frère, mais en 93 on n'avait plus de parents, on\r
+n'avait que ses bras. J'ai travaillé. Ils ont dans le pays de\r
+Pontarlier, où vous allez, monsieur Valjean, une industrie toute\r
+patriarcale et toute charmante, ma soeur. Ce sont leurs fromageries\r
+qu'ils appellent fruitières.\r
+\r
+«Alors mon frère, tout en faisant manger cet homme, lui a expliqué très\r
+en détail ce que c'étaient que les fruitières de Pontarlier;--qu'on en\r
+distinguait deux sortes:--les _grosses granges_, qui sont aux riches, et\r
+où il y a quarante ou cinquante vaches, lesquelles produisent sept à\r
+huit milliers de fromages par été; les _fruitières d'association_, qui\r
+sont aux pauvres; ce sont les paysans de la moyenne montagne qui mettent\r
+leurs vaches en commun et partagent les produits.--Ils prennent à leurs\r
+gages un fromager qu'ils appellent le grurin;--le grurin reçoit le lait\r
+des associés trois fois par jour et marque les quantités sur une taille\r
+double;--c'est vers la fin d'avril que le travail des fromageries\r
+commence; c'est vers la mi-juin que les fromagers conduisent leurs\r
+vaches dans la montagne.\r
+\r
+«L'homme se ranimait tout en mangeant. Mon frère lui faisait boire de ce\r
+bon vin de Mauves dont il ne boit pas lui-même parce qu'il dit que c'est\r
+du vin cher. Mon frère lui disait tous ces détails avec cette gaîté\r
+aisée que vous lui connaissez, entremêlant ses paroles de façons\r
+gracieuses pour moi. Il est beaucoup revenu sur ce bon état de grurin,\r
+comme s'il eût souhaité que cet homme comprît, sans le lui conseiller\r
+directement et durement, que ce serait un asile pour lui. Une chose m'a\r
+frappée. Cet homme était ce que je vous ai dit. Eh bien! mon frère,\r
+pendant tout le souper, ni de toute la soirée, à l'exception de quelques\r
+paroles sur Jésus quand il est entré, n'a pas dit un mot qui pût\r
+rappeler à cet homme qui il était ni apprendre à cet homme qui était mon\r
+frère. C'était bien une occasion en apparence de faire un peu de sermon\r
+et d'appuyer l'évêque sur le galérien pour laisser la marque du passage.\r
+Il eût paru peut-être à un autre que c'était le cas, ayant ce malheureux\r
+sous la main, de lui nourrir l'âme en même temps que le corps et de lui\r
+faire quelque reproche assaisonné de morale et de conseil, ou bien un\r
+peu de commisération avec exhortation de se mieux conduire à l'avenir.\r
+Mon frère ne lui a même pas demandé de quel pays il était, ni son\r
+histoire. Car dans son histoire il y a sa faute, et mon frère semblait\r
+éviter tout ce qui pouvait l'en faire souvenir. C'est au point qu'à un\r
+certain moment, comme mon frère parlait des montagnards de Pontarlier,\r
+qui ont _un doux travail près du ciel et qui_, ajoutait-il, _sont\r
+heureux parce qu'ils sont innocents_, il s'est arrêté court, craignant\r
+qu'il n'y eût dans ce mot qui lui échappait quelque chose qui pût\r
+froisser l'homme. À force d'y réfléchir, je crois avoir compris ce qui\r
+se passait dans le coeur de mon frère. Il pensait sans doute que cet\r
+homme, qui s'appelle Jean Valjean, n'avait que trop sa misère présente à\r
+l'esprit, que le mieux était de l'en distraire, et de lui faire croire,\r
+ne fût-ce qu'un moment, qu'il était une personne comme une autre, en\r
+étant pour lui tout ordinaire. N'est-ce pas là en effet bien entendre la\r
+charité? N'y a-t-il pas, bonne madame, quelque chose de vraiment\r
+évangélique dans cette délicatesse qui s'abstient de sermon, de morale\r
+et d'allusion, et la meilleure pitié, quand un homme a un point\r
+douloureux, n'est-ce pas de n'y point toucher du tout? Il m'a semblé que\r
+ce pouvait être là la pensée intérieure de mon frère. Dans tous les cas,\r
+ce que je puis dire, c'est que, s'il a eu toutes ces idées, il n'en a\r
+rien marqué, même pour moi; il a été d'un bout à l'autre le même homme\r
+que tous les soirs, et il a soupé avec ce Jean Valjean du même air et de\r
+la même façon qu'il aurait soupé avec M. Gédéon Le Prévost ou avec M. le\r
+curé de la paroisse.\r
+\r
+«Vers la fin, comme nous étions aux figues, on a cogné à la porte.\r
+C'était la mère Gerbaud avec son petit dans ses bras. Mon frère a baisé\r
+l'enfant au front, et m'a emprunté quinze sous que j'avais sur moi pour\r
+les donner à la mère Gerbaud. L'homme pendant ce temps-là ne faisait pas\r
+grande attention. Il ne parlait plus et paraissait très fatigué. La\r
+pauvre vieille Gerbaud partie, mon frère a dit les grâces, puis il s'est\r
+tourné vers cet homme, et il lui a dit: Vous devez avoir bien besoin de\r
+votre lit. Madame Magloire a enlevé le couvert bien vite. J'ai compris\r
+qu'il fallait nous retirer pour laisser dormir ce voyageur, et nous\r
+sommes montées toutes les deux. J'ai cependant envoyé madame Magloire un\r
+instant après porter sur le lit de cet homme une peau de chevreuil de la\r
+Forêt-Noire qui est dans ma chambre. Les nuits sont glaciales, et cela\r
+tient chaud. C'est dommage que cette peau soit vieille; tout le poil\r
+s'en va. Mon frère l'a achetée du temps qu'il était en Allemagne, à\r
+Tottlingen, près des sources du Danube, ainsi que le petit couteau à\r
+manche d'ivoire dont je me sers à table.\r
+\r
+«Madame Magloire est remontée presque tout de suite, nous nous sommes\r
+mises à prier Dieu dans le salon où l'on étend le linge, et puis nous\r
+sommes rentrées chacune dans notre chambre sans nous rien dire.»\r
+\r
+\r
+\r
+\r
+Chapitre V\r
+\r
+Tranquillité\r
+\r
+\r
+Après avoir donné le bonsoir à sa soeur, monseigneur Bienvenu prit sur\r
+la table un des deux flambeaux d'argent, remit l'autre à son hôte, et\r
+lui dit:\r
+\r
+--Monsieur, je vais vous conduire à votre chambre.\r
+\r
+L'homme le suivit.\r
+\r
+Comme on a pu le remarquer dans ce qui a été dit plus haut, le logis\r
+était distribué de telle sorte que, pour passer dans l'oratoire où était\r
+l'alcôve ou pour en sortir, il fallait traverser la chambre à coucher de\r
+l'évêque.\r
+\r
+Au moment où ils traversaient cette chambre, madame Magloire serrait\r
+l'argenterie dans le placard qui était au chevet du lit. C'était le\r
+dernier soin qu'elle prenait chaque soir avant de s'aller coucher.\r
+\r
+L'évêque installa son hôte dans l'alcôve. Un lit blanc et frais y était\r
+dressé. L'homme posa le flambeau sur une petite table.\r
+\r
+--Allons, dit l'évêque, faites une bonne nuit. Demain matin, avant de\r
+partir, vous boirez une tasse de lait de nos vaches tout chaud.\r
+\r
+--Merci, monsieur l'abbé, dit l'homme.\r
+\r
+À peine eut-il prononcé ces paroles pleines de paix que, tout à coup et\r
+sans transition, il eut un mouvement étrange et qui eût glacé\r
+d'épouvante les deux saintes filles si elles en eussent été témoins.\r
+Aujourd'hui même il nous est difficile de nous rendre compte de ce qui\r
+le poussait en ce moment. Voulait-il donner un avertissement ou jeter\r
+une menace? Obéissait-il simplement à une sorte d'impulsion instinctive\r
+et obscure pour lui-même? Il se tourna brusquement vers le vieillard,\r
+croisa les bras, et, fixant sur son hôte un regard sauvage, il s'écria\r
+d'une voix rauque:\r
+\r
+--Ah çà! décidément! vous me logez chez vous près de vous comme cela!\r
+\r
+Il s'interrompit et ajouta avec un rire où il y avait quelque chose de\r
+monstrueux:\r
+\r
+--Avez-vous bien fait toutes vos réflexions? Qui est-ce qui vous dit que\r
+je n'ai pas assassiné?\r
+\r
+L'évêque leva les yeux vers le plafond et répondit:\r
+\r
+--Cela regarde le bon Dieu.\r
+\r
+Puis, gravement et remuant les lèvres comme quelqu'un qui prie ou qui se\r
+parle à lui-même, il dressa les deux doigts de sa main droite et bénit\r
+l'homme qui ne se courba pas, et, sans tourner la tête et sans regarder\r
+derrière lui, il rentra dans sa chambre.\r
+\r
+Quand l'alcôve était habitée, un grand rideau de serge tiré de part en\r
+part dans l'oratoire cachait l'autel. L'évêque s'agenouilla en passant\r
+devant ce rideau et fit une courte prière.\r
+\r
+Un moment après, il était dans son jardin, marchant, rêvant,\r
+contemplant, l'âme et la pensée tout entières à ces grandes choses\r
+mystérieuses que Dieu montre la nuit aux yeux qui restent ouverts.\r
+\r
+Quant à l'homme, il était vraiment si fatigué qu'il n'avait même pas\r
+profité de ces bons draps blancs. Il avait soufflé sa bougie avec sa\r
+narine à la manière des forçats et s'était laissé tomber tout habillé\r
+sur le lit, où il s'était tout de suite profondément endormi.\r
+\r
+Minuit sonnait comme l'évêque rentrait de son jardin dans son\r
+appartement.\r
+\r
+Quelques minutes après, tout dormait dans la petite maison.\r
+\r
+\r
+\r
+\r
+Chapitre VI\r
+\r
+Jean Valjean\r
+\r
+\r
+Vers le milieu de la nuit, Jean Valjean se réveilla.\r
+\r
+Jean Valjean était d'une pauvre famille de paysans de la Brie. Dans son\r
+enfance, il n'avait pas appris à lire. Quand il eut l'âge d'homme, il\r
+était émondeur à Faverolles. Sa mère s'appelait Jeanne Mathieu; son père\r
+s'appelait Jean Valjean, ou Vlajean, sobriquet probablement, et\r
+contraction de _Voilà Jean_.\r
+\r
+Jean Valjean était d'un caractère pensif sans être triste, ce qui est le\r
+propre des natures affectueuses. Somme toute, pourtant, c'était quelque\r
+chose d'assez endormi et d'assez insignifiant, en apparence du moins,\r
+que Jean Valjean. Il avait perdu en très bas âge son père et sa mère. Sa\r
+mère était morte d'une fièvre de lait mal soignée. Son père, émondeur\r
+comme lui, s'était tué en tombant d'un arbre. Il n'était resté à Jean\r
+Valjean qu'une soeur plus âgée que lui, veuve, avec sept enfants, filles\r
+et garçons. Cette soeur avait élevé Jean Valjean, et tant qu'elle eut\r
+son mari elle logea et nourrit son jeune frère. Le mari mourut. L'aîné\r
+des sept enfants avait huit ans, le dernier un an. Jean Valjean venait\r
+d'atteindre, lui, sa vingt-cinquième année. Il remplaça le père, et\r
+soutint à son tour sa soeur qui l'avait élevé. Cela se fit simplement,\r
+comme un devoir, même avec quelque chose de bourru de la part de Jean\r
+Valjean. Sa jeunesse se dépensait ainsi dans un travail rude et mal\r
+payé. On ne lui avait jamais connu de «bonne amie» dans le pays. Il\r
+n'avait pas eu le temps d'être amoureux.\r
+\r
+Le soir il rentrait fatigué et mangeait sa soupe sans dire un mot. Sa\r
+soeur, mère Jeanne, pendant qu'il mangeait, lui prenait souvent dans son\r
+écuelle le meilleur de son repas, le morceau de viande, la tranche de\r
+lard le coeur de chou, pour le donner à quelqu'un de ses enfants; lui,\r
+mangeant toujours, penché sur la table, presque la tête dans sa soupe,\r
+ses longs cheveux tombant autour de son écuelle et cachant ses yeux,\r
+avait l'air de ne rien voir et laissait faire. Il y avait à Faverolles,\r
+pas loin de la chaumière Valjean, de l'autre côté de la ruelle, une\r
+fermière appelée Marie-Claude; les enfants Valjean, habituellement\r
+affamés, allaient quelquefois emprunter au nom de leur mère une pinte de\r
+lait à Marie-Claude, qu'ils buvaient derrière une haie ou dans quelque\r
+coin d'allée, s'arrachant le pot, et si hâtivement que les petites\r
+filles s'en répandaient sur leur tablier et dans leur goulotte. La mère,\r
+si elle eût su cette maraude, eût sévèrement corrigé les délinquants.\r
+Jean Valjean, brusque et bougon, payait en arrière de la mère la pinte\r
+de lait à Marie-Claude, et les enfants n'étaient pas punis.\r
+\r
+Il gagnait dans la saison de l'émondage vingt-quatre sous par jour, puis\r
+il se louait comme moissonneur, comme manoeuvre, comme garçon de ferme\r
+bouvier, comme homme de peine. Il faisait ce qu'il pouvait. Sa soeur\r
+travaillait de son côté, mais que faire avec sept petits enfants?\r
+C'était un triste groupe que la misère enveloppa et étreignit peu à peu.\r
+Il arriva qu'un hiver fut rude. Jean n'eut pas d'ouvrage. La famille\r
+n'eut pas de pain. Pas de pain. À la lettre. Sept enfants! Un dimanche\r
+soir, Maubert Isabeau, boulanger sur la place de l'Église, à Faverolles,\r
+se disposait à se coucher, lorsqu'il entendit un coup violent dans la\r
+devanture grillée et vitrée de sa boutique. Il arriva à temps pour voir\r
+un bras passé à travers un trou fait d'un coup de poing dans la grille\r
+et dans la vitre. Le bras saisit un pain et l'emporta. Isabeau sortit en\r
+hâte; le voleur s'enfuyait à toutes jambes; Isabeau courut après lui et\r
+l'arrêta. Le voleur avait jeté le pain, mais il avait encore le bras\r
+ensanglanté. C'était Jean Valjean.\r
+\r
+Ceci se passait en 1795. Jean Valjean fut traduit devant les tribunaux\r
+du temps «pour vol avec effraction la nuit dans une maison habitée». Il\r
+avait un fusil dont il se servait mieux que tireur au monde, il était\r
+quelque peu braconnier; ce qui lui nuisit. Il y a contre les braconniers\r
+un préjugé légitime. Le braconnier, de même que le contrebandier, côtoie\r
+de fort près le brigand. Pourtant, disons-le en passant, il y a encore\r
+un abîme entre ces races d'hommes et le hideux assassin des villes. Le\r
+braconnier vit dans la forêt; le contrebandier vit dans la montagne ou\r
+sur la mer. Les villes font des hommes féroces parce qu'elles font des\r
+hommes corrompus. La montagne, la mer, la forêt, font des hommes\r
+sauvages. Elles développent le côté farouche, mais souvent sans détruire\r
+le côté humain.\r
+\r
+Jean Valjean fut déclaré coupable. Les termes du code étaient formels.\r
+Il y a dans notre civilisation des heures redoutables; ce sont les\r
+moments où la pénalité prononce un naufrage. Quelle minute funèbre que\r
+celle où la société s'éloigne et consomme l'irréparable abandon d'un\r
+être pensant! Jean Valjean fut condamné à cinq ans de galères.\r
+\r
+Le 22 avril 1796, on cria dans Paris la victoire de Montenotte remportée\r
+par le général en chef de l'année d'Italie, que le message du Directoire\r
+aux Cinq-Cents, du 2 floréal an IV, appelle Buona-Parte; ce même jour\r
+une grande chaîne fut ferrée à Bicêtre. Jean Valjean fit partie de cette\r
+chaîne. Un ancien guichetier de la prison, qui a près de\r
+quatre-vingt-dix ans aujourd'hui, se souvient encore parfaitement de ce\r
+malheureux qui fut ferré à l'extrémité du quatrième cordon dans l'angle\r
+nord de la cour. Il était assis à terre comme tous les autres. Il\r
+paraissait ne rien comprendre à sa position, sinon qu'elle était\r
+horrible. Il est probable qu'il y démêlait aussi, à travers les vagues\r
+idées d'un pauvre homme ignorant de tout, quelque chose d'excessif.\r
+Pendant qu'on rivait à grands coups de marteau derrière sa tête le\r
+boulon de son carcan, il pleurait, les larmes l'étouffaient, elles\r
+l'empêchaient de parler, il parvenait seulement à dire de temps en\r
+temps: _J'étais émondeur à Faverolles_. Puis, tout en sanglotant, il\r
+élevait sa main droite et l'abaissait graduellement sept fois comme s'il\r
+touchait successivement sept têtes inégales, et par ce geste on devinait\r
+que la chose quelconque qu'il avait faite, il l'avait faite pour vêtir\r
+et nourrir sept petits enfants.\r
+\r
+Il partit pour Toulon. Il y arriva après un voyage de vingt-sept jours,\r
+sur une charrette, la chaîne au cou. À Toulon, il fut revêtu de la\r
+casaque rouge. Tout s'effaça de ce qui avait été sa vie, jusqu'à son\r
+nom; il ne fut même plus Jean Valjean; il fut le numéro 24601. Que\r
+devint la soeur? que devinrent les sept enfants? Qui est-ce qui s'occupe\r
+de cela? Que devient la poignée de feuilles du jeune arbre scié par le\r
+pied?\r
+\r
+C'est toujours la même histoire. Ces pauvres êtres vivants, ces\r
+créatures de Dieu, sans appui désormais, sans guide, sans asile, s'en\r
+allèrent au hasard, qui sait même? chacun de leur côté peut-être, et\r
+s'enfoncèrent peu à peu dans cette froide brume où s'engloutissent les\r
+destinées solitaires, moines ténèbres où disparaissent successivement\r
+tant de têtes infortunées dans la sombre marche du genre humain. Ils\r
+quittèrent le pays. Le clocher de ce qui avait été leur village les\r
+oublia; la borne de ce qui avait été leur champ les oublia; après\r
+quelques années de séjour au bagne, Jean Valjean lui-même les oublia.\r
+Dans ce coeur où il y avait eu une plaie, il y eut une cicatrice. Voilà\r
+tout. À peine, pendant tout le temps qu'il passa à Toulon, entendit-il\r
+parler une seule fois de sa soeur. C'était, je crois, vers la fin de la\r
+quatrième année de sa captivité. Je ne sais plus par quelle voie ce\r
+renseignement lui parvint. Quelqu'un, qui les avait connus au pays,\r
+avait vu sa soeur. Elle était à Paris. Elle habitait une pauvre rue près\r
+de Saint-Sulpice, la rue du Geindre. Elle n'avait plus avec elle qu'un\r
+enfant, un petit garçon, le dernier. Où étaient les six autres? Elle ne\r
+le savait peut-être pas elle-même. Tous les matins elle allait à une\r
+imprimerie rue du Sabot, n° 3, où elle était plieuse et brocheuse. Il\r
+fallait être là à six heures du matin, bien avant le jour l'hiver. Dans\r
+la maison de l'imprimerie il y avait une école, elle menait à cette\r
+école son petit garçon qui avait sept ans. Seulement, comme elle entrait\r
+à l'imprimerie à six heures et que l'école n'ouvrait qu'à sept, il\r
+fallait que l'enfant attendît, dans la cour, que l'école ouvrit, une\r
+heure; l'hiver, une heure de nuit, en plein air. On ne voulait pas que\r
+l'enfant entrât dans l'imprimerie, parce qu'il gênait, disait-on. Les\r
+ouvriers voyaient le matin en passant ce pauvre petit être assis sur le\r
+pavé, tombant de sommeil, et souvent endormi dans l'ombre, accroupi et\r
+plié sur son panier. Quand il pleuvait, une vieille femme, la portière,\r
+en avait pitié; elle le recueillait dans son bouge où il n'y avait qu'un\r
+grabat, un rouet et deux chaises de bois, et le petit dormait là dans un\r
+coin, se serrant contre le chat pour avoir moins froid. À sept heures,\r
+l'école ouvrait et il y entrait. Voilà ce qu'on dit à Jean Valjean. On\r
+l'en entretint un jour, ce fut un moment, un éclair, comme une fenêtre\r
+brusquement ouverte sur la destinée de ces êtres qu'il avait aimés, puis\r
+tout se referma; il n'en entendit plus parler, et ce fut pour jamais.\r
+Plus rien n'arriva d'eux à lui; jamais il ne les revit, jamais il ne les\r
+rencontra, et, dans la suite de cette douloureuse histoire, on ne les\r
+retrouvera plus.\r
+\r
+Vers la fin de cette quatrième année, le tour d'évasion de Jean Valjean\r
+arriva. Ses camarades l'aidèrent comme cela se fait dans ce triste lieu.\r
+Il s'évada. Il erra deux jours en liberté dans les champs; si c'est être\r
+libre que d'être traqué; de tourner la tête à chaque instant; de\r
+tressaillir au moindre bruit; d'avoir peur de tout, du toit qui fume, de\r
+l'homme qui passe, du chien qui aboie, du cheval qui galope, de l'heure\r
+qui sonne, du jour parce qu'on voit, de la nuit parce qu'on ne voit pas,\r
+de la route, du sentier, du buisson, du sommeil. Le soir du second jour,\r
+il fut repris. Il n'avait ni mangé ni dormi depuis trente-six heures. Le\r
+tribunal maritime le condamna pour ce délit à une prolongation de trois\r
+ans, ce qui lui fit huit ans. La sixième année, ce fut encore son tour\r
+de s'évader; il en usa, mais il ne put consommer sa fuite. Il avait\r
+manqué à l'appel. On tira le coup de canon, et à la nuit les gens de\r
+ronde le trouvèrent caché sous la quille d'un vaisseau en construction;\r
+il résista aux gardes-chiourme qui le saisirent. Évasion et rébellion.\r
+Ce fait prévu par le code spécial fut puni d'une aggravation de cinq\r
+ans, dont deux ans de double chaîne. Treize ans. La dixième année, son\r
+tour revint, il en profita encore. Il ne réussit pas mieux. Trois ans\r
+pour cette nouvelle tentative. Seize ans. Enfin, ce fut, je crois,\r
+pendant la treizième année qu'il essaya une dernière fois et ne réussit\r
+qu'à se faire reprendre après quatre heures d'absence. Trois ans pour\r
+ces quatre heures. Dix-neuf ans. En octobre 1815 il fut libéré; il était\r
+entré là en 1796 pour avoir cassé un carreau et pris un pain.\r
+\r
+Place pour une courte parenthèse. C'est la seconde fois que, dans ses\r
+études sur la question pénale et sur la damnation par la loi, l'auteur\r
+de ce livre rencontre le vol d'un pain, comme point de départ du\r
+désastre d'une destinée. Claude Gueux avait volé un pain; Jean Valjean\r
+avait volé un pain. Une statistique anglaise constate qu'à Londres\r
+quatre vols sur cinq ont pour cause immédiate la faim.\r
+\r
+Jean Valjean était entré au bagne sanglotant et frémissant; il en sortit\r
+impassible. Il y était entré désespéré; il en sortit sombre.\r
+\r
+Que s'était-il passé dans cette âme?\r
+\r
+\r
+\r
+\r
+Chapitre VII\r
+\r
+Le dedans du désespoir\r
+\r
+\r
+Essayons de le dire.\r
+\r
+Il faut bien que la société regarde ces choses puisque c'est elle qui\r
+les fait.\r
+\r
+C'était, nous l'avons dit, un ignorant; mais ce n'était pas un imbécile.\r
+La lumière naturelle était allumée en lui. Le malheur, qui a aussi sa\r
+clarté, augmenta le peu de jour qu'il y avait dans cet esprit. Sous le\r
+bâton, sous la chaîne, au cachot, à la fatigue, sous l'ardent soleil du\r
+bagne, sur le lit de planches des forçats, il se replia en sa conscience\r
+et réfléchit.\r
+\r
+Il se constitua tribunal.\r
+\r
+Il commença par se juger lui-même.\r
+\r
+Il reconnut qu'il n'était pas un innocent injustement puni. Il s'avoua\r
+qu'il avait commis une action extrême et blâmable; qu'on ne lui eût\r
+peut-être pas refusé ce pain s'il l'avait demandé; que dans tous les cas\r
+il eût mieux valu l'attendre, soit de la pitié, soit du travail; que ce\r
+n'est pas tout à fait une raison sans réplique de dire: peut-on attendre\r
+quand on a faim? que d'abord il est très rare qu'on meure littéralement\r
+de faim; ensuite que, malheureusement ou heureusement, l'homme est ainsi\r
+fait qu'il peut souffrir longtemps et beaucoup, moralement et\r
+physiquement, sans mourir; qu'il fallait donc de la patience; que cela\r
+eût mieux valu même pour ces pauvres petits enfants; que c'était un acte\r
+de folie, à lui, malheureux homme chétif, de prendre violemment au\r
+collet la société tout entière et de se figurer qu'on sort de la misère\r
+par le vol; que c'était, dans tous les cas, une mauvaise porte pour\r
+sortir de la misère que celle par où l'on entre dans l'infamie; enfin\r
+qu'il avait eu tort.\r
+\r
+Puis il se demanda:\r
+\r
+S'il était le seul qui avait eu tort dans sa fatale histoire? Si d'abord\r
+ce n'était pas une chose grave qu'il eût, lui travailleur, manqué de\r
+travail, lui laborieux, manqué de pain. Si, ensuite, la faute commise et\r
+avouée, le châtiment n'avait pas été féroce et outré. S'il n'y avait pas\r
+plus d'abus de la part de la loi dans la peine qu'il n'y avait eu d'abus\r
+de la part du coupable dans la faute. S'il n'y avait pas excès de poids\r
+dans un des plateaux de la balance, celui où est l'expiation. Si la\r
+surcharge de la peine n'était point l'effacement du délit, et n'arrivait\r
+pas à ce résultat: de retourner la situation, de remplacer la faute du\r
+délinquant par la faute de la répression, de faire du coupable la\r
+victime et du débiteur le créancier, et de mettre définitivement le\r
+droit du côté de celui-là même qui l'avait violé. Si cette peine,\r
+compliquée des aggravations successives pour les tentatives d'évasion,\r
+ne finissait pas par être une sorte d'attentat du plus fort sur le plus\r
+faible, un crime de la société sur l'individu, un crime qui recommençait\r
+tous les jours, un crime qui durait dix-neuf ans.\r
+\r
+Il se demanda si la société humaine pouvait avoir le droit de faire\r
+également subir à ses membres, dans un cas son imprévoyance\r
+déraisonnable, et dans l'autre cas sa prévoyance impitoyable, et de\r
+saisir à jamais un pauvre homme entre un défaut et un excès, défaut de\r
+travail, excès de châtiment. S'il n'était pas exorbitant que la société\r
+traitât ainsi précisément ses membres les plus mal dotés dans la\r
+répartition de biens que fait le hasard, et par conséquent les plus\r
+dignes de ménagements.\r
+\r
+Ces questions faites et résolues, il jugea la société et la condamna.\r
+\r
+Il la condamna sans haine.\r
+\r
+Il la fit responsable du sort qu'il subissait, et se dit qu'il\r
+n'hésiterait peut-être pas à lui en demander compte un jour. Il se\r
+déclara à lui-même qu'il n'y avait pas équilibre entre le dommage qu'il\r
+avait causé et le dommage qu'on lui causait; il conclut enfin que son\r
+châtiment n'était pas, à la vérité, une injustice, mais qu'à coup sûr\r
+c'était une iniquité.\r
+\r
+La colère peut être folle et absurde; on peut être irrité à tort; on\r
+n'est indigné que lorsqu'on a raison au fond par quelque côté. Jean\r
+Valjean se sentait indigné. Et puis, la société humaine ne lui avait\r
+fait que du mal. Jamais il n'avait vu d'elle que ce visage courroucé\r
+qu'elle appelle sa justice et qu'elle montre à ceux qu'elle frappe. Les\r
+hommes ne l'avaient touché que pour le meurtrir. Tout contact avec eux\r
+lui avait été un coup. Jamais, depuis son enfance, depuis sa mère,\r
+depuis sa soeur, jamais il n'avait rencontré une parole amie et un\r
+regard bienveillant. De souffrance en souffrance il arriva peu à peu à\r
+cette conviction que la vie était une guerre; et que dans cette guerre\r
+il était le vaincu. Il n'avait d'autre arme que sa haine. Il résolut de\r
+l'aiguiser au bagne et de l'emporter en s'en allant.\r
+\r
+Il y avait à Toulon une école pour la chiourme tenue par des frères\r
+ignorantins où l'on enseignait le plus nécessaire à ceux de ces\r
+malheureux qui avaient de la bonne volonté. Il fut du nombre des hommes\r
+de bonne volonté. Il alla à l'école à quarante ans, et apprit à lire, à\r
+écrire, à compter. Il sentit que fortifier son intelligence, c'était\r
+fortifier sa haine. Dans certains cas, l'instruction et la lumière\r
+peuvent servir de rallonge au mal.\r
+\r
+Cela est triste à dire, après avoir jugé la société qui avait fait son\r
+malheur, il jugea la providence qui avait fait la société.\r
+\r
+Il la condamna aussi.\r
+\r
+Ainsi, pendant ces dix-neuf ans de torture et d'esclavage, cette âme\r
+monta et tomba en même temps. Il y entra de la lumière d'un côté et des\r
+ténèbres de l'autre.\r
+\r
+Jean Valjean n'était pas, on l'a vu, d'une nature mauvaise. Il était\r
+encore bon lorsqu'il arriva au bagne. Il y condamna la société et sentit\r
+qu'il devenait méchant, il y condamna la providence et sentit qu'il\r
+devenait impie.\r
+\r
+Ici il est difficile de ne pas méditer un instant.\r
+\r
+La nature humaine se transforme-t-elle ainsi de fond en comble et tout à\r
+fait? L'homme créé bon par Dieu peut-il être fait méchant par l'homme?\r
+L'âme peut-elle être refaite tout d'une pièce par la destinée, et\r
+devenir mauvaise, la destinée étant mauvaise? Le coeur peut-il devenir\r
+difforme et contracter des laideurs et des infirmités incurables sous la\r
+pression d'un malheur disproportionné, comme la colonne vertébrale sous\r
+une voûte trop basse? N'y a-t-il pas dans toute âme humaine, n'y\r
+avait-il pas dans l'âme de Jean Valjean en particulier, une première\r
+étincelle, un élément divin, incorruptible dans ce monde, immortel dans\r
+l'autre, que le bien peut développer, attiser, allumer, enflammer et\r
+faire rayonner splendidement, et que le mal ne peut jamais entièrement\r
+éteindre?\r
+\r
+Questions graves et obscures, à la dernière desquelles tout\r
+physiologiste eût probablement répondu non, et sans hésiter, s'il eût vu\r
+à Toulon, aux heures de repos qui étaient pour Jean Valjean des heures\r
+de rêverie, assis, les bras croisés, sur la barre de quelque cabestan,\r
+le bout de sa chaîne enfoncé dans sa poche pour l'empêcher de traîner,\r
+ce galérien morne, sérieux, silencieux et pensif, paria des lois qui\r
+regardait l'homme avec colère, damné de la civilisation qui regardait le\r
+ciel avec sévérité.\r
+\r
+Certes, et nous ne voulons pas le dissimuler, le physiologiste\r
+observateur eût vu là une misère irrémédiable, il eût plaint peut-être\r
+ce malade du fait de la loi, mais il n'eût pas même essayé de\r
+traitement; il eût détourné le regard des cavernes qu'il aurait\r
+entrevues dans cette âme; et, comme Dante de la porte de l'enfer, il eût\r
+effacé de cette existence le mot que le doigt de Dieu écrit pourtant sur\r
+le front de tout homme: _Espérance_!\r
+\r
+Cet état de son âme que nous avons tenté d'analyser était-il aussi\r
+parfaitement clair pour Jean Valjean que nous avons essayé de le rendre\r
+pour ceux qui nous lisent? Jean Valjean voyait-il distinctement, après\r
+leur formation, et avait-il vu distinctement, à mesure qu'ils se\r
+formaient, tous les éléments dont se composait sa misère morale? Cet\r
+homme rude et illettré s'était-il bien nettement rendu compte de la\r
+succession d'idées par laquelle il était, degré à degré, monté et\r
+descendu jusqu'aux lugubres aspects qui étaient depuis tant d'années\r
+déjà l'horizon intérieur de son esprit? Avait-il bien conscience de tout\r
+ce qui s'était passé en lui et de tout ce qui s'y remuait? C'est ce que\r
+nous n'oserions dire; c'est même ce que nous ne croyons pas. Il y avait\r
+trop d'ignorance dans Jean Valjean pour que, même après tant de malheur,\r
+il n'y restât pas beaucoup de vague. Par moments il ne savait pas même\r
+bien au juste ce qu'il éprouvait. Jean Valjean était dans les ténèbres;\r
+il souffrait dans les ténèbres; il haïssait dans les ténèbres; on eût pu\r
+dire qu'il haïssait devant lui. Il vivait habituellement dans cette\r
+ombre, tâtonnant comme un aveugle et comme un rêveur. Seulement, par\r
+intervalles, il lui venait tout à coup, de lui-même ou du dehors, une\r
+secousse de colère, un surcroît de souffrance, un pâle et rapide éclair\r
+qui illuminait toute son âme, et faisait brusquement apparaître partout\r
+autour de lui, en avant et en arrière, aux lueurs d'une lumière\r
+affreuse, les hideux précipices et les sombres perspectives de sa\r
+destinée.\r
+\r
+L'éclair passé, la nuit retombait, et où était-il? il ne le savait plus.\r
+\r
+Le propre des peines de cette nature, dans lesquelles domine ce qui est\r
+impitoyable, c'est-à-dire ce qui est abrutissant, c'est de transformer\r
+peu à peu, par une sorte de transfiguration stupide, un homme en une\r
+bête fauve. Quelquefois en une bête féroce. Les tentatives d'évasion de\r
+Jean Valjean, successives et obstinées, suffiraient à prouver cet\r
+étrange travail fait par la loi sur l'âme humaine. Jean Valjean eût\r
+renouvelé ces tentatives, si parfaitement inutiles et folles, autant de\r
+fois que l'occasion s'en fût présentée, sans songer un instant au\r
+résultat, ni aux expériences déjà faites. Il s'échappait impétueusement\r
+comme le loup qui trouve la cage ouverte. L'instinct lui disait:\r
+sauve-toi! Le raisonnement lui eût dit: reste! Mais, devant une\r
+tentation si violente, le raisonnement avait disparu; il n'y avait plus\r
+que l'instinct. La bête seule agissait. Quand il était repris, les\r
+nouvelles sévérités qu'on lui infligeait ne servaient qu'à l'effarer\r
+davantage.\r
+\r
+Un détail que nous ne devons pas omettre, c'est qu'il était d'une force\r
+physique dont n'approchait pas un des habitants du bagne. À la fatigue,\r
+pour filer un câble, pour virer un cabestan, Jean Valjean valait quatre\r
+hommes. Il soulevait et soutenait parfois d'énormes poids sur son dos,\r
+et remplaçait dans l'occasion cet instrument qu'on appelle cric et qu'on\r
+appelait jadis orgueil, d'où a pris nom, soit dit en passant, la rue\r
+Montorgueil près des halles de Paris. Ses camarades l'avaient surnommé\r
+Jean-le-Cric. Une fois, comme on réparait le balcon de l'hôtel de ville\r
+de Toulon, une des admirables cariatides de Puget qui soutiennent ce\r
+balcon se descella et faillit tomber. Jean Valjean, qui se trouvait là,\r
+soutint de l'épaule la cariatide et donna le temps aux ouvriers\r
+d'arriver.\r
+\r
+Sa souplesse dépassait encore sa vigueur. Certains forçats, rêveurs\r
+perpétuels d'évasions, finissent par faire de la force et de l'adresse\r
+combinées une véritable science. C'est la science des muscles. Toute une\r
+statique mystérieuse est quotidiennement pratiquée par les prisonniers,\r
+ces éternels envieux des mouches et des oiseaux. Gravir une verticale,\r
+et trouver des points d'appui là où l'on voit à peine une saillie, était\r
+un jeu pour Jean Valjean. Étant donné un angle de mur, avec la tension\r
+de son dos et de ses jarrets, avec ses coudes et ses talons emboîtés\r
+dans les aspérités de la pierre, il se hissait comme magiquement à un\r
+troisième étage. Quelquefois il montait ainsi jusqu'au toit du bagne.\r
+\r
+Il parlait peu. Il ne riait pas. Il fallait quelque émotion extrême pour\r
+lui arracher, une ou deux fois l'an, ce lugubre rire du forçat qui est\r
+comme un écho du rire du démon. À le voir, il semblait occupé à regarder\r
+continuellement quelque chose de terrible.\r
+\r
+Il était absorbé en effet.\r
+\r
+À travers les perceptions maladives d'une nature incomplète et d'une\r
+intelligence accablée, il sentait confusément qu'une chose monstrueuse\r
+était sur lui. Dans cette pénombre obscure et blafarde où il rampait,\r
+chaque fois qu'il tournait le cou et qu'il essayait d'élever son regard,\r
+il voyait, avec une terreur mêlée de rage, s'échafauder, s'étager et\r
+monter à perte de vue au-dessus de lui, avec des escarpements horribles,\r
+une sorte d'entassement effrayant de choses, de lois, de préjugés,\r
+d'hommes et de faits, dont les contours lui échappaient, dont la masse\r
+l'épouvantait, et qui n'était autre chose que cette prodigieuse pyramide\r
+que nous appelons la civilisation. Il distinguait çà et là dans cet\r
+ensemble fourmillant et difforme, tantôt près de lui, tantôt loin et sur\r
+des plateaux inaccessibles, quelque groupe, quelque détail vivement\r
+éclairé, ici l'argousin et son bâton, ici le gendarme et son sabre,\r
+là-bas l'archevêque mitré, tout en haut, dans une sorte de soleil,\r
+l'empereur couronné et éblouissant. Il lui semblait que ces splendeurs\r
+lointaines, loin de dissiper sa nuit, la rendaient plus funèbre et plus\r
+noire. Tout cela, lois, préjugés, faits, hommes, choses, allait et\r
+venait au-dessus de lui, selon le mouvement compliqué et mystérieux que\r
+Dieu imprime à la civilisation, marchant sur lui et l'écrasant avec je\r
+ne sais quoi de paisible dans la cruauté et d'inexorable dans\r
+l'indifférence. Âmes tombées au fond de l'infortune possible, malheureux\r
+hommes perdus au plus bas de ces limbes où l'on ne regarde plus, les\r
+réprouvés de la loi sentent peser de tout son poids sur leur tête cette\r
+société humaine, si formidable pour qui est dehors, si effroyable pour\r
+qui est dessous.\r
+\r
+Dans cette situation, Jean Valjean songeait, et quelle pouvait être la\r
+nature de sa rêverie?\r
+\r
+Si le grain de mil sous la meule avait des pensées, il penserait sans\r
+doute ce que pensait Jean Valjean.\r
+\r
+Toutes ces choses, réalités pleines de spectres, fantasmagories pleines\r
+de réalités, avaient fini par lui créer une sorte d'état intérieur\r
+presque inexprimable.\r
+\r
+Par moments, au milieu de son travail du bagne, il s'arrêtait. Il se\r
+mettait à penser. Sa raison, à la fois plus mûre et plus troublée\r
+qu'autrefois, se révoltait. Tout ce qui lui était arrivé lui paraissait\r
+absurde; tout ce qui l'entourait lui paraissait impossible. Il se\r
+disait: c'est un rêve. Il regardait l'argousin debout à quelques pas de\r
+lui; l'argousin lui semblait un fantôme; tout à coup le fantôme lui\r
+donnait un coup de bâton.\r
+\r
+La nature visible existait à peine pour lui. Il serait presque vrai de\r
+dire qu'il n'y avait point pour Jean Valjean de soleil, ni de beaux\r
+jours d'été, ni de ciel rayonnant, ni de fraîches aubes d'avril. Je ne\r
+sais quel jour de soupirail éclairait habituellement son âme.\r
+\r
+Pour résumer, en terminant, ce qui peut être résumé et traduit en\r
+résultats positifs dans tout ce que nous venons d'indiquer, nous nous\r
+bornerons à constater qu'en dix-neuf ans, Jean Valjean, l'inoffensif\r
+émondeur de Faverolles, le redoutable galérien de Toulon, était devenu\r
+capable, grâce à la manière dont le bagne l'avait façonné, de deux\r
+espèces de mauvaises actions: premièrement, d'une mauvaise action\r
+rapide, irréfléchie, pleine d'étourdissement, toute d'instinct, sorte de\r
+représaille pour le mal souffert; deuxièmement, d'une mauvaise action\r
+grave, sérieuse, débattue en conscience et méditée avec les idées\r
+fausses que peut donner un pareil malheur. Ses préméditations passaient\r
+par les trois phases successives que les natures d'une certaine trempe\r
+peuvent seules parcourir, raisonnement, volonté, obstination. Il avait\r
+pour mobiles l'indignation habituelle, l'amertume de l'âme, le profond\r
+sentiment des iniquités subies, la réaction, même contre les bons, les\r
+innocents et les justes, s'il y en a. Le point de départ comme le point\r
+d'arrivée de toutes ses pensées était la haine de la loi humaine; cette\r
+haine qui, si elle n'est arrêtée dans son développement par quelque\r
+incident providentiel, devient, dans un temps donné, la haine de la\r
+société, puis la haine du genre humain, puis la haine de la création, et\r
+se traduit par un vague et incessant et brutal désir de nuire, n'importe\r
+à qui, à un être vivant quelconque. Comme on voit, ce n'était pas sans\r
+raison que le passeport qualifiait Jean Valjean d'_homme très\r
+dangereux_.\r
+\r
+D'année en année, cette âme s'était desséchée de plus en plus,\r
+lentement, mais fatalement. À coeur sec, oeil sec. À sa sortie du bagne,\r
+il y avait dix-neuf ans qu'il n'avait versé une larme.\r
+\r
+\r
+\r
+\r
+Chapitre VIII\r
+\r
+L'onde et l'ombre\r
+\r
+\r
+Un homme à la mer!\r
+\r
+Qu'importe! le navire ne s'arrête pas. Le vent souffle, ce sombre\r
+navire-là a une route qu'il est forcé de continuer. Il passe.\r
+\r
+L'homme disparaît, puis reparaît, il plonge et remonte à la surface, il\r
+appelle, il tend les bras, on ne l'entend pas; le navire, frissonnant\r
+sous l'ouragan, est tout à sa manoeuvre, les matelots et les passagers\r
+ne voient même plus l'homme submergé; sa misérable tête n'est qu'un\r
+point dans l'énormité des vagues. Il jette des cris désespérés dans les\r
+profondeurs. Quel spectre que cette voile qui s'en va! Il la regarde, il\r
+la regarde frénétiquement. Elle s'éloigne, elle blêmit, elle décroît. Il\r
+était là tout à l'heure, il était de l'équipage, il allait et venait sur\r
+le pont avec les autres, il avait sa part de respiration et de soleil,\r
+il était un vivant. Maintenant, que s'est-il donc passé? Il a glissé, il\r
+est tombé, c'est fini.\r
+\r
+Il est dans l'eau monstrueuse. Il n'a plus sous les pieds que de la\r
+fuite et de l'écroulement. Les flots déchirés et déchiquetés par le vent\r
+l'environnent hideusement, les roulis de l'abîme l'emportent, tous les\r
+haillons de l'eau s'agitent autour de sa tête, une populace de vagues\r
+crache sur lui, de confuses ouvertures le dévorent à demi; chaque fois\r
+qu'il enfonce, il entrevoit des précipices pleins de nuit; d'affreuses\r
+végétations inconnues le saisissent, lui nouent les pieds, le tirent à\r
+elles; il sent qu'il devient abîme, il fait partie de l'écume, les flots\r
+se le jettent de l'un à l'autre, il boit l'amertume, l'océan lâche\r
+s'acharne à le noyer, l'énormité joue avec son agonie. Il semble que\r
+toute cette eau soit de la haine.\r
+\r
+Il lutte pourtant, il essaie de se défendre, il essaie de se soutenir,\r
+il fait effort, il nage. Lui, cette pauvre force tout de suite épuisée,\r
+il combat l'inépuisable.\r
+\r
+Où donc est le navire? Là-bas. À peine visible dans les pâles ténèbres\r
+de l'horizon.\r
+\r
+Les rafales soufflent; toutes les écumes l'accablent. Il lève les yeux\r
+et ne voit que les lividités des nuages. Il assiste, agonisant, à\r
+l'immense démence de la mer. Il est supplicié par cette folie. Il entend\r
+des bruits étrangers à l'homme qui semblent venir d'au delà de la terre\r
+et d'on ne sait quel dehors effrayant.\r
+\r
+Il y a des oiseaux dans les nuées, de même qu'il y a des anges au-dessus\r
+des détresses humaines, mais que peuvent-ils pour lui? Cela vole, chante\r
+et plane, et lui, il râle.\r
+\r
+Il se sent enseveli à la fois par ces deux infinis, l'océan et le ciel;\r
+l'un est une tombe, l'autre est un linceul.\r
+\r
+La nuit descend, voilà des heures qu'il nage, ses forces sont à bout; ce\r
+navire, cette chose lointaine où il y avait des hommes, s'est effacé; il\r
+est seul dans le formidable gouffre crépusculaire, il enfonce, il se\r
+roidit, il se tord, il sent au-dessous de lui les vagues monstres de\r
+l'invisible; il appelle.\r
+\r
+Il n'y a plus d'hommes. Où est Dieu?\r
+\r
+Il appelle. Quelqu'un! quelqu'un! Il appelle toujours.\r
+\r
+Rien à l'horizon. Rien au ciel.\r
+\r
+Il implore l'étendue, la vague, l'algue, l'écueil; cela est sourd. Il\r
+supplie la tempête; la tempête imperturbable n'obéit qu'à l'infini.\r
+\r
+Autour de lui, l'obscurité, la brume, la solitude, le tumulte orageux et\r
+inconscient, le plissement indéfini des eaux farouches. En lui l'horreur\r
+et la fatigue. Sous lui la chute. Pas de point d'appui. Il songe aux\r
+aventures ténébreuses du cadavre dans l'ombre illimitée. Le froid sans\r
+fond le paralyse. Ses mains se crispent et se ferment et prennent du\r
+néant. Vents, nuées, tourbillons, souffles, étoiles inutiles! Que faire?\r
+Le désespéré s'abandonne, qui est las prend le parti de mourir, il se\r
+laisse faire, il se laisse aller, il lâche prise, et le voilà qui roule\r
+à jamais dans les profondeurs lugubres de l'engloutissement.\r
+\r
+Ô marche implacable des sociétés humaines! Pertes d'hommes et d'âmes\r
+chemin faisant! Océan où tombe tout ce que laisse tomber la loi!\r
+Disparition sinistre du secours! ô mort morale!\r
+\r
+La mer, c'est l'inexorable nuit sociale où la pénalité jette ses damnés.\r
+La mer, c'est l'immense misère.\r
+\r
+L'âme, à vau-l'eau dans ce gouffre, peut devenir un cadavre. Qui la\r
+ressuscitera?\r
+\r
+\r
+\r
+\r
+Chapitre IX\r
+\r
+Nouveaux griefs\r
+\r
+\r
+Quand vint l'heure de la sortie du bagne, quand Jean Valjean entendit à\r
+son oreille ce mot étrange: _tu es libre_! le moment fut invraisemblable\r
+et inouï, un rayon de vive lumière, un rayon de la vraie lumière des\r
+vivants pénétra subitement en lui. Mais ce rayon ne tarda point à pâlir.\r
+Jean Valjean avait été ébloui de l'idée de la liberté. Il avait cru à\r
+une vie nouvelle. Il vit bien vite ce que c'était qu'une liberté à\r
+laquelle on donne un passeport jaune.\r
+\r
+Et autour de cela bien des amertumes. Il avait calculé que sa masse,\r
+pendant son séjour au bagne, aurait dû s'élever à cent soixante et onze\r
+francs. Il est juste d'ajouter qu'il avait oublié de faire entrer dans\r
+ses calculs le repos forcé des dimanches et fêtes qui, pour dix-neuf\r
+ans, entraînait une diminution de vingt-quatre francs environ. Quoi\r
+qu'il en fût, cette masse avait été réduite, par diverses retenues\r
+locales, à la somme de cent neuf francs quinze sous, qui lui avait été\r
+comptée à sa sortie.\r
+\r
+Il n'y avait rien compris, et se croyait lésé. Disons le mot, volé.\r
+\r
+Le lendemain de sa libération, à Grasse, il vit devant la porte d'une\r
+distillerie de fleurs d'oranger des hommes qui déchargeaient des\r
+ballots. Il offrit ses services. La besogne pressait, on les accepta. Il\r
+se mit à l'ouvrage. Il était intelligent, robuste et adroit; il faisait\r
+de son mieux; le maître paraissait content. Pendant qu'il travaillait,\r
+un gendarme passa, le remarqua, et lui demanda ses papiers. Il fallut\r
+montrer le passeport jaune. Cela fait, Jean Valjean reprit son travail.\r
+Un peu auparavant, il avait questionné l'un des ouvriers sur ce qu'ils\r
+gagnaient à cette besogne par jour; on lui avait répondu: _trente sous_.\r
+Le soir venu, comme il était forcé de repartir le lendemain matin, il se\r
+présenta devant le maître de la distillerie et le pria de le payer. Le\r
+maître ne proféra pas une parole, et lui remit vingt-cinq sous. Il\r
+réclama. On lui répondit: cela est assez bon pour toi. Il insista. Le\r
+maître le regarda entre les deux yeux et lui dit: _Gare le bloc_.\r
+\r
+Là encore il se considéra comme volé.\r
+\r
+La société, l'état, en lui diminuant sa masse, l'avait volé en grand.\r
+Maintenant, c'était le tour de l'individu qui le volait en petit.\r
+\r
+Libération n'est pas délivrance. On sort du bagne, mais non de la\r
+condamnation. Voilà ce qui lui était arrivé à Grasse. On a vu de quelle\r
+façon il avait été accueilli à Digne.\r
+\r
+\r
+\r
+\r
+Chapitre X\r
+\r
+L'homme réveillé\r
+\r
+\r
+Donc, comme deux heures du matin sonnaient à l'horloge de la cathédrale,\r
+Jean Valjean se réveilla.\r
+\r
+Ce qui le réveilla, c'est que le lit était trop bon. Il y avait vingt\r
+ans bientôt qu'il n'avait couché dans un lit, et quoiqu'il ne se fût pas\r
+déshabillé, la sensation était trop nouvelle pour ne pas troubler son\r
+sommeil.\r
+\r
+Il avait dormi plus de quatre heures. Sa fatigue était passée. Il était\r
+accoutumé à ne pas donner beaucoup d'heures au repos.\r
+\r
+Il ouvrit les yeux et regarda un moment dans l'obscurité autour de lui,\r
+puis il les referma pour se rendormir.\r
+\r
+Quand beaucoup de sensations diverses ont agité la journée, quand des\r
+choses préoccupent l'esprit, on s'endort, mais on ne se rendort pas. Le\r
+sommeil vient plus aisément qu'il ne revient. C'est ce qui arriva à Jean\r
+Valjean. Il ne put se rendormir, et il se mit à penser.\r
+\r
+Il était dans un de ces moments où les idées qu'on a dans l'esprit sont\r
+troubles. Il avait une sorte de va-et-vient obscur dans le cerveau. Ses\r
+souvenirs anciens et ses souvenirs immédiats y flottaient pêle-mêle et\r
+s'y croisaient confusément, perdant leurs formes, se grossissant\r
+démesurément, puis disparaissant tout à coup comme dans une eau fangeuse\r
+et agitée. Beaucoup de pensées lui venaient, mais il y en avait une qui\r
+se représentait continuellement et qui chassait toutes les autres. Cette\r
+pensée, nous allons la dire tout de suite:--Il avait remarqué les six\r
+couverts d'argent et la grande cuiller que madame Magloire avait posés\r
+sur la table.\r
+\r
+Ces six couverts d'argent l'obsédaient.--Ils étaient là.--À quelques\r
+pas.--À l'instant où il avait traversé la chambre d'à côté pour venir\r
+dans celle où il était, la vieille servante les mettait dans un petit\r
+placard à la tête du lit.--Il avait bien remarqué ce placard.--À droite,\r
+en entrant par la salle à manger.--Ils étaient massifs.--Et de vieille\r
+argenterie.--Avec la grande cuiller, on en tirerait au moins deux cents\r
+francs.--Le double de ce qu'il avait gagné en dix-neuf ans.--Il est\r
+vrai qu'il eût gagné davantage si l'_administration_ ne l'avait pas\r
+_volé_.\r
+\r
+Son esprit oscilla toute une grande heure dans des fluctuations\r
+auxquelles se mêlait bien quelque lutte. Trois heures sonnèrent. Il\r
+rouvrit les yeux, se dressa brusquement sur son séant, étendit le bras\r
+et tâta son havresac qu'il avait jeté dans le coin de l'alcôve, puis il\r
+laissa pendre ses jambes et poser ses pieds à terre, et se trouva,\r
+presque sans savoir comment, assis sur son lit.\r
+\r
+Il resta un certain temps rêveur dans cette attitude qui eût eu quelque\r
+chose de sinistre pour quelqu'un qui l'eût aperçu ainsi dans cette\r
+ombre, seul éveillé dans la maison endormie. Tout à coup il se baissa,\r
+ôta ses souliers et les posa doucement sur la natte près du lit, puis il\r
+reprit sa posture de rêverie et redevint immobile.\r
+\r
+Au milieu de cette méditation hideuse, les idées que nous venons\r
+d'indiquer remuaient sans relâche son cerveau, entraient, sortaient,\r
+rentraient, faisaient sur lui une sorte de pesée; et puis il songeait\r
+aussi, sans savoir pourquoi, et avec cette obstination machinale de la\r
+rêverie, à un forçat nommé Brevet qu'il avait connu au bagne, et dont le\r
+pantalon n'était retenu que par une seule bretelle de coton tricoté. Le\r
+dessin en damier de cette bretelle lui revenait sans cesse à l'esprit.\r
+\r
+Il demeurait dans cette situation, et y fût peut-être resté indéfiniment\r
+jusqu'au lever du jour, si l'horloge n'eût sonné un coup--le quart ou la\r
+demie. Il sembla que ce coup lui eût dit: allons!\r
+\r
+Il se leva debout, hésita encore un moment, et écouta; tout se taisait\r
+dans la maison; alors il marcha droit et à petits pas vers la fenêtre\r
+qu'il entrevoyait. La nuit n'était pas très obscure; c'était une pleine\r
+lune sur laquelle couraient de larges nuées chassées par le vent. Cela\r
+faisait au dehors des alternatives d'ombre et de clarté, des éclipses,\r
+puis des éclaircies, et au dedans une sorte de crépuscule. Ce\r
+crépuscule, suffisant pour qu'on pût se guider, intermittent à cause des\r
+nuages, ressemblait à l'espèce de lividité qui tombe d'un soupirail de\r
+cave devant lequel vont et viennent des passants. Arrivé à la fenêtre,\r
+Jean Valjean l'examina. Elle était sans barreaux, donnait sur le jardin\r
+et n'était fermée, selon la mode du pays, que d'une petite clavette. Il\r
+l'ouvrit, mais, comme un air froid et vif entra brusquement dans la\r
+chambre, il la referma tout de suite. Il regarda le jardin de ce regard\r
+attentif qui étudie plus encore qu'il ne regarde. Le jardin était enclos\r
+d'un mur blanc assez bas, facile à escalader. Au fond, au-delà, il\r
+distingua des têtes d'arbres également espacées, ce qui indiquait que ce\r
+mur séparait le jardin d'une avenue ou d'une ruelle plantée.\r
+\r
+Ce coup d'oeil jeté, il fit le mouvement d'un homme déterminé, marcha à\r
+son alcôve, prit son havresac, l'ouvrit, le fouilla, en tira quelque\r
+chose qu'il posa sur le lit, mit ses souliers dans une des poches,\r
+referma le tout, chargea le sac sur ses épaules, se couvrit de sa\r
+casquette dont il baissa la visière sur ses yeux, chercha son bâton en\r
+tâtonnant, et l'alla poser dans l'angle de la fenêtre, puis revint au\r
+lit et saisit résolument l'objet qu'il y avait déposé. Cela ressemblait\r
+à une barre de fer courte, aiguisée comme un épieu à l'une de ses\r
+extrémités.\r
+\r
+Il eût été difficile de distinguer dans les ténèbres pour quel emploi\r
+avait pu être façonné ce morceau de fer. C'était peut-être un levier?\r
+C'était peut-être une massue?\r
+\r
+Au jour on eût pu reconnaître que ce n'était autre chose qu'un\r
+chandelier de mineur. On employait alors quelquefois les forçats à\r
+extraire de la roche des hautes collines qui environnent Toulon, et il\r
+n'était pas rare qu'ils eussent à leur disposition des outils de mineur.\r
+Les chandeliers des mineurs sont en fer massif, terminés à leur\r
+extrémité inférieure par une pointe au moyen de laquelle on les enfonce\r
+dans le rocher.\r
+\r
+Il prit ce chandelier dans sa main droite, et retenant son haleine,\r
+assourdissant son pas, il se dirigea vers la porte de la chambre\r
+voisine, celle de l'évêque, comme on sait. Arrivé à cette porte, il la\r
+trouva entrebâillée. L'évêque ne l'avait point fermée.\r
+\r
+\r
+\r
+\r
+Chapitre XI\r
+\r
+Ce qu'il fait\r
+\r
+\r
+Jean Valjean écouta. Aucun bruit.\r
+\r
+Il poussa la porte.\r
+\r
+Il la poussa du bout du doigt, légèrement, avec cette douceur furtive et\r
+inquiète d'un chat qui veut entrer.\r
+\r
+La porte céda à la pression et fit un mouvement imperceptible et\r
+silencieux qui élargit un peu l'ouverture.\r
+\r
+Il attendit un moment, puis poussa la porte une seconde fois, plus\r
+hardiment. Elle continua de céder en silence. L'ouverture était assez\r
+grande maintenant pour qu'il pût passer. Mais il y avait près de la\r
+porte une petite table qui faisait avec elle un angle gênant et qui\r
+barrait l'entrée.\r
+\r
+Jean Valjean reconnut la difficulté. Il fallait à toute force que\r
+l'ouverture fût encore élargie.\r
+\r
+Il prit son parti, et poussa une troisième fois la porte, plus\r
+énergiquement que les deux premières. Cette fois il y eut un gond mal\r
+huilé qui jeta tout à coup dans cette obscurité un cri rauque et\r
+prolongé.\r
+\r
+Jean Valjean tressaillit. Le bruit de ce gond sonna dans son oreille\r
+avec quelque chose d'éclatant et de formidable comme le clairon du\r
+jugement dernier. Dans les grossissements fantastiques de la première\r
+minute, il se figura presque que ce gond venait de s'animer et de\r
+prendre tout à coup une vie terrible, et qu'il aboyait comme un chien\r
+pour avertir tout le monde et réveiller les gens endormis.\r
+\r
+Il s'arrêta, frissonnant, éperdu, et retomba de la pointe du pied sur le\r
+talon. Il entendait ses artères battre dans ses tempes comme deux\r
+marteaux de forge, et il lui semblait que son souffle sortait de sa\r
+poitrine avec le bruit du vent qui sort d'une caverne. Il lui paraissait\r
+impossible que l'horrible clameur de ce gond irrité n'eût pas ébranlé\r
+toute la maison comme une secousse de tremblement de terre; la porte,\r
+poussée par lui, avait pris l'alarme et avait appelé; le vieillard\r
+allait se lever, les deux vieilles femmes allaient crier, on viendrait à\r
+l'aide; avant un quart d'heure, la ville serait en rumeur et la\r
+gendarmerie sur pied. Un moment il se crut perdu.\r
+\r
+Il demeura où il était, pétrifié comme la statue de sel, n'osant faire\r
+un mouvement.\r
+\r
+Quelques minutes s'écoulèrent. La porte s'était ouverte toute grande. Il\r
+se hasarda à regarder dans la chambre. Rien n'y avait bougé. Il prêta\r
+l'oreille. Rien ne remuait dans la maison. Le bruit du gond rouillé\r
+n'avait éveillé personne. Ce premier danger était passé, mais il y avait\r
+encore en lui un affreux tumulte. Il ne recula pas pourtant. Même quand\r
+il s'était cru perdu, il n'avait pas reculé. Il ne songea plus qu'à\r
+finir vite. Il fit un pas et entra dans la chambre.\r
+\r
+Cette chambre était dans un calme parfait. On y distinguait çà et là des\r
+formes confuses et vagues qui, au jour, étaient des papiers épars sur\r
+une table, des in-folio ouverts, des volumes empilés sur un tabouret, un\r
+fauteuil chargé de vêtements, un prie-Dieu, et qui à cette heure\r
+n'étaient plus que des coins ténébreux et des places blanchâtres. Jean\r
+Valjean avança avec précaution en évitant de se heurter aux meubles. Il\r
+entendait au fond de la chambre la respiration égale et tranquille de\r
+l'évêque endormi.\r
+\r
+Il s'arrêta tout à coup. Il était près du lit. Il y était arrivé plus\r
+tôt qu'il n'aurait cru.\r
+\r
+La nature mêle quelquefois ses effets et ses spectacles à nos actions\r
+avec une espèce d'à-propos sombre et intelligent, comme si elle voulait\r
+nous faire réfléchir. Depuis près d'une demi-heure un grand nuage\r
+couvrait le ciel. Au moment où Jean Valjean s'arrêta en face du lit, ce\r
+nuage se déchira, comme s'il l'eût fait exprès, et un rayon de lune,\r
+traversant la longue fenêtre, vint éclairer subitement le visage pâle de\r
+l'évêque. Il dormait paisiblement. Il était presque vêtu dans son lit, à\r
+cause des nuits froides des Basses-Alpes, d'un vêtement de laine brune\r
+qui lui couvrait les bras jusqu'aux poignets. Sa tête était renversée\r
+sur l'oreiller dans l'attitude abandonnée du repos; il laissait pendre\r
+hors du lit sa main ornée de l'anneau pastoral et d'où étaient tombées\r
+tant de bonnes oeuvres et de saintes actions. Toute sa face s'illuminait\r
+d'une vague expression de satisfaction, d'espérance et de béatitude.\r
+C'était plus qu'un sourire et presque un rayonnement. Il y avait sur son\r
+front l'inexprimable réverbération d'une lumière qu'on ne voyait pas.\r
+L'âme des justes pendant le sommeil contemple un ciel mystérieux.\r
+\r
+Un reflet de ce ciel était sur l'évêque.\r
+\r
+C'était en même temps une transparence lumineuse, car ce ciel était au\r
+dedans de lui. Ce ciel, c'était sa conscience.\r
+\r
+Au moment où le rayon de lune vint se superposer, pour ainsi dire, à\r
+cette clarté intérieure, l'évêque endormi apparut comme dans une gloire.\r
+Cela pourtant resta doux et voilé d'un demi-jour ineffable. Cette lune\r
+dans le ciel, cette nature assoupie, ce jardin sans un frisson, cette\r
+maison si calme, l'heure, le moment, le silence, ajoutaient je ne sais\r
+quoi de solennel et d'indicible au vénérable repos de ce sage, et\r
+enveloppaient d'une sorte d'auréole majestueuse et sereine ces cheveux\r
+blancs et ces yeux fermés, cette figure où tout était espérance et où\r
+tout était confiance, cette tête de vieillard et ce sommeil d'enfant.\r
+\r
+Il y avait presque de la divinité dans cet homme ainsi auguste à son\r
+insu. Jean Valjean, lui, était dans l'ombre, son chandelier de fer à la\r
+main, debout, immobile, effaré de ce vieillard lumineux. Jamais il\r
+n'avait rien vu de pareil. Cette confiance l'épouvantait. Le monde moral\r
+n'a pas de plus grand spectacle que celui-là: une conscience troublée et\r
+inquiète, parvenue au bord d'une mauvaise action, et contemplant le\r
+sommeil d'un juste.\r
+\r
+Ce sommeil, dans cet isolement, et avec un voisin tel que lui, avait\r
+quelque chose de sublime qu'il sentait vaguement, mais impérieusement.\r
+\r
+Nul n'eût pu dire ce qui se passait en lui, pas même lui. Pour essayer\r
+de s'en rendre compte, il faut rêver ce qu'il y a de plus violent en\r
+présence de ce qu'il y a de plus doux. Sur son visage même on n'eût rien\r
+pu distinguer avec certitude. C'était une sorte d'étonnement hagard. Il\r
+regardait cela. Voilà tout. Mais quelle était sa pensée? Il eût été\r
+impossible de le deviner. Ce qui était évident, c'est qu'il était ému et\r
+bouleversé. Mais de quelle nature était cette émotion?\r
+\r
+Son oeil ne se détachait pas du vieillard. La seule chose qui se\r
+dégageât clairement de son attitude et de sa physionomie, c'était une\r
+étrange indécision. On eût dit qu'il hésitait entre les deux abîmes,\r
+celui où l'on se perd et celui où l'on se sauve. Il semblait prêt à\r
+briser ce crâne ou à baiser cette main.\r
+\r
+Au bout de quelques instants, son bras gauche se leva lentement vers son\r
+front, et il ôta sa casquette, puis son bras retomba avec la même\r
+lenteur, et Jean Valjean rentra dans sa contemplation, sa casquette dans\r
+la main gauche, sa massue dans la main droite, ses cheveux hérissés sur\r
+sa tête farouche.\r
+\r
+L'évêque continuait de dormir dans une paix profonde sous ce regard\r
+effrayant. Un reflet de lune faisait confusément visible au-dessus de la\r
+cheminée le crucifix qui semblait leur ouvrir les bras à tous les deux,\r
+avec une bénédiction pour l'un et un pardon pour l'autre.\r
+\r
+Tout à coup Jean Valjean remit sa casquette sur son front, puis marcha\r
+rapidement, le long du lit, sans regarder l'évêque, droit au placard\r
+qu'il entrevoyait près du chevet; il leva le chandelier de fer comme\r
+pour forcer la serrure; la clef y était; il l'ouvrit; la première chose\r
+qui lui apparut fut le panier d'argenterie; il le prit, traversa la\r
+chambre à grands pas sans précaution et sans s'occuper du bruit, gagna\r
+la porte, rentra dans l'oratoire, ouvrit la fenêtre, saisit un bâton,\r
+enjamba l'appui du rez-de-chaussée, mit l'argenterie dans son sac, jeta\r
+le panier, franchit le jardin, sauta par-dessus le mur comme un tigre,\r
+et s'enfuit.\r
+\r
+\r
+\r
+\r
+Chapitre XII\r
+\r
+L'évêque travaille\r
+\r
+\r
+Le lendemain, au soleil levant, monseigneur Bienvenu se promenait dans\r
+son jardin. Madame Magloire accourut vers lui toute bouleversée.\r
+\r
+--Monseigneur, monseigneur, cria-t-elle, votre grandeur sait-elle où est\r
+le panier d'argenterie?\r
+\r
+--Oui, dit l'évêque.\r
+\r
+--Jésus-Dieu soit béni! reprit-elle. Je ne savais ce qu'il était devenu.\r
+\r
+L'évêque venait de ramasser le panier dans une plate-bande. Il le\r
+présenta à madame Magloire.\r
+\r
+--Le voilà.\r
+\r
+--Eh bien? dit-elle. Rien dedans! et l'argenterie?\r
+\r
+--Ah! repartit l'évêque. C'est donc l'argenterie qui vous occupe? Je ne\r
+sais où elle est.\r
+\r
+--Grand bon Dieu! elle est volée! C'est l'homme d'hier soir qui l'a\r
+volée!\r
+\r
+En un clin d'oeil, avec toute sa vivacité de vieille alerte, madame\r
+Magloire courut à l'oratoire, entra dans l'alcôve et revint vers\r
+l'évêque. L'évêque venait de se baisser et considérait en soupirant un\r
+plant de cochléaria des Guillons que le panier avait brisé en tombant à\r
+travers la plate-bande. Il se redressa au cri de madame Magloire.\r
+\r
+--Monseigneur, l'homme est parti! l'argenterie est volée!\r
+\r
+Tout en poussant cette exclamation, ses yeux tombaient sur un angle du\r
+jardin où l'on voyait des traces d'escalade. Le chevron du mur avait été\r
+arraché.\r
+\r
+--Tenez! c'est par là qu'il s'en est allé. Il a sauté dans la ruelle\r
+Cochefilet! Ah! l'abomination! Il nous a volé notre argenterie!\r
+\r
+L'évêque resta un moment silencieux, puis leva son oeil sérieux, et dit\r
+à madame Magloire avec douceur:\r
+\r
+--Et d'abord, cette argenterie était-elle à nous?\r
+\r
+Madame Magloire resta interdite. Il y eut encore un silence, puis\r
+l'évêque continua:\r
+\r
+--Madame Magloire, je détenais à tort et depuis longtemps cette\r
+argenterie. Elle était aux pauvres. Qu'était-ce que cet homme? Un pauvre\r
+évidemment.\r
+\r
+--Hélas Jésus! repartit madame Magloire. Ce n'est pas pour moi ni pour\r
+mademoiselle. Cela nous est bien égal. Mais c'est pour monseigneur. Dans\r
+quoi monseigneur va-t-il manger maintenant?\r
+\r
+L'évêque la regarda d'un air étonné.\r
+\r
+--Ah çà mais! est-ce qu'il n'y a pas des couverts d'étain?\r
+\r
+Madame Magloire haussa les épaules.\r
+\r
+--L'étain a une odeur.\r
+\r
+--Alors, des couverts de fer.\r
+\r
+Madame Magloire fit une grimace significative.\r
+\r
+--Le fer a un goût.\r
+\r
+--Eh bien, dit l'évêque, des couverts de bois.\r
+\r
+Quelques instants après, il déjeunait à cette même table où Jean Valjean\r
+s'était assis la veille. Tout en déjeunant, monseigneur Bienvenu faisait\r
+gaîment remarquer à sa soeur qui ne disait rien et à madame Magloire qui\r
+grommelait sourdement qu'il n'est nullement besoin d'une cuiller ni\r
+d'une fourchette, même en bois, pour tremper un morceau de pain dans une\r
+tasse de lait.\r
+\r
+--Aussi a-t-on idée! disait madame Magloire toute seule en allant et\r
+venant, recevoir un homme comme cela! et le loger à côté de soi! et quel\r
+bonheur encore qu'il n'ait fait que voler! Ah mon Dieu! cela fait frémir\r
+quand on songe!\r
+\r
+Comme le frère et la soeur allaient se lever de table, on frappa à la\r
+porte.\r
+\r
+--Entrez, dit l'évêque.\r
+\r
+La porte s'ouvrit. Un groupe étrange et violent apparut sur le seuil.\r
+Trois hommes en tenaient un quatrième au collet. Les trois hommes\r
+étaient des gendarmes; l'autre était Jean Valjean.\r
+\r
+Un brigadier de gendarmerie, qui semblait conduire le groupe, était près\r
+de la porte. Il entra et s'avança vers l'évêque en faisant le salut\r
+militaire.\r
+\r
+--Monseigneur... dit-il.\r
+\r
+À ce mot Jean Valjean, qui était morne et semblait abattu, releva la\r
+tête d'un air stupéfait.\r
+\r
+--Monseigneur! murmura-t-il. Ce n'est donc pas le curé?...\r
+\r
+--Silence! dit un gendarme. C'est monseigneur l'évêque.\r
+\r
+Cependant monseigneur Bienvenu s'était approché aussi vivement que son\r
+grand âge le lui permettait.\r
+\r
+--Ah! vous voilà! s'écria-t-il en regardant Jean Valjean. Je suis aise\r
+de vous voir. Et bien mais! je vous avais donné les chandeliers aussi,\r
+qui sont en argent comme le reste et dont vous pourrez bien avoir deux\r
+cents francs. Pourquoi ne les avez-vous pas emportés avec vos couverts?\r
+\r
+Jean Valjean ouvrit les yeux et regarda le vénérable évêque avec une\r
+expression qu'aucune langue humaine ne pourrait rendre.\r
+\r
+--Monseigneur, dit le brigadier de gendarmerie, ce que cet homme disait\r
+était donc vrai? Nous l'avons rencontré. Il allait comme quelqu'un qui\r
+s'en va. Nous l'avons arrêté pour voir. Il avait cette argenterie....\r
+\r
+--Et il vous a dit, interrompit l'évêque en souriant, qu'elle lui avait\r
+été donnée par un vieux bonhomme de prêtre chez lequel il avait passé la\r
+nuit? Je vois la chose. Et vous l'avez ramené ici? C'est une méprise.\r
+\r
+--Comme cela, reprit le brigadier, nous pouvons le laisser aller?\r
+\r
+--Sans doute, répondit l'évêque.\r
+\r
+Les gendarmes lâchèrent Jean Valjean qui recula.\r
+\r
+--Est-ce que c'est vrai qu'on me laisse? dit-il d'une voix presque\r
+inarticulée et comme s'il parlait dans le sommeil.\r
+\r
+--Oui, on te laisse, tu n'entends donc pas? dit un gendarme.\r
+\r
+--Mon ami, reprit l'évêque, avant de vous en aller, voici vos\r
+chandeliers. Prenez-les.\r
+\r
+Il alla à la cheminée, prit les deux flambeaux d'argent et les apporta à\r
+Jean Valjean. Les deux femmes le regardaient faire sans un mot, sans un\r
+geste, sans un regard qui pût déranger l'évêque.\r
+\r
+Jean Valjean tremblait de tous ses membres. Il prit les deux chandeliers\r
+machinalement et d'un air égaré.\r
+\r
+--Maintenant, dit l'évêque, allez en paix.\r
+\r
+--À propos, quand vous reviendrez, mon ami, il est inutile de passer par\r
+le jardin. Vous pourrez toujours entrer et sortir par la porte de la\r
+rue. Elle n'est fermée qu'au loquet jour et nuit.\r
+\r
+Puis se tournant vers la gendarmerie:\r
+\r
+--Messieurs, vous pouvez vous retirer.\r
+\r
+Les gendarmes s'éloignèrent.\r
+\r
+Jean Valjean était comme un homme qui va s'évanouir.\r
+\r
+L'évêque s'approcha de lui, et lui dit à voix basse:\r
+\r
+--N'oubliez pas, n'oubliez jamais que vous m'avez promis d'employer cet\r
+argent à devenir honnête homme.\r
+\r
+Jean Valjean, qui n'avait aucun souvenir d'avoir rien promis, resta\r
+interdit. L'évêque avait appuyé sur ces paroles en les prononçant. Il\r
+reprit avec une sorte de solennité:\r
+\r
+--Jean Valjean, mon frère, vous n'appartenez plus au mal, mais au bien.\r
+C'est votre âme que je vous achète; je la retire aux pensées noires et à\r
+l'esprit de perdition, et je la donne à Dieu.\r
+\r
+\r
+\r
+\r
+Chapitre XIII\r
+\r
+Petit-Gervais\r
+\r
+\r
+Jean Valjean sortit de la ville comme s'il s'échappait. Il se mit à\r
+marcher en toute hâte dans les champs, prenant les chemins et les\r
+sentiers qui se présentaient sans s'apercevoir qu'il revenait à chaque\r
+instant sur ses pas. Il erra ainsi toute la matinée, n'ayant pas mangé\r
+et n'ayant pas faim. Il était en proie à une foule de sensations\r
+nouvelles. Il se sentait une sorte de colère; il ne savait contre qui.\r
+Il n'eût pu dire s'il était touché ou humilié. Il lui venait par moments\r
+un attendrissement étrange qu'il combattait et auquel il opposait\r
+l'endurcissement de ses vingt dernières années. Cet état le fatiguait.\r
+Il voyait avec inquiétude s'ébranler au dedans de lui l'espèce de calme\r
+affreux que l'injustice de son malheur lui avait donné. Il se demandait\r
+qu'est-ce qui remplacerait cela. Parfois il eût vraiment mieux aimé être\r
+en prison avec les gendarmes, et que les choses ne se fussent point\r
+passées ainsi; cela l'eût moins agité. Bien que la saison fut assez\r
+avancée, il y avait encore çà et là dans les haies quelques fleurs\r
+tardives dont l'odeur, qu'il traversait en marchant, lui rappelait des\r
+souvenirs d'enfance. Ces souvenirs lui étaient presque insupportables,\r
+tant il y avait longtemps qu'ils ne lui étaient apparus.\r
+\r
+Des pensées inexprimables s'amoncelèrent ainsi en lui toute la journée.\r
+\r
+Comme le soleil déclinait au couchant, allongeant sur le sol l'ombre du\r
+moindre caillou, Jean Valjean était assis derrière un buisson dans une\r
+grande plaine rousse absolument déserte. Il n'y avait à l'horizon que\r
+les Alpes. Pas même le clocher d'un village lointain. Jean Valjean\r
+pouvait être à trois lieues de Digne. Un sentier qui coupait la plaine\r
+passait à quelques pas du buisson.\r
+\r
+Au milieu de cette méditation qui n'eût pas peu contribué à rendre ses\r
+haillons effrayants pour quelqu'un qui l'eût rencontré, il entendit un\r
+bruit joyeux.\r
+\r
+Il tourna la tête, et vit venir par le sentier un petit savoyard d'une\r
+dizaine d'années qui chantait, sa vielle au flanc et sa boîte à marmotte\r
+sur le dos; un de ces doux et gais enfants qui vont de pays en pays,\r
+laissant voir leurs genoux par les trous de leur pantalon.\r
+\r
+Tout en chantant l'enfant interrompait de temps en temps sa marche et\r
+jouait aux osselets avec quelques pièces de monnaie qu'il avait dans sa\r
+main, toute sa fortune probablement. Parmi cette monnaie il y avait une\r
+pièce de quarante sous. L'enfant s'arrêta à côté du buisson sans voir\r
+Jean Valjean et fit sauter sa poignée de sous que jusque-là il avait\r
+reçue avec assez d'adresse tout entière sur le dos de sa main.\r
+\r
+Cette fois la pièce de quarante sous lui échappa, et vint rouler vers la\r
+broussaille jusqu'à Jean Valjean.\r
+\r
+Jean Valjean posa le pied dessus.\r
+\r
+Cependant l'enfant avait suivi sa pièce du regard, et l'avait vu.\r
+\r
+Il ne s'étonna point et marcha droit à l'homme.\r
+\r
+C'était un lieu absolument solitaire. Aussi loin que le regard pouvait\r
+s'étendre, il n'y avait personne dans la plaine ni dans le sentier. On\r
+n'entendait que les petits cris faibles d'une nuée d'oiseaux de passage\r
+qui traversaient le ciel à une hauteur immense. L'enfant tournait le dos\r
+au soleil qui lui mettait des fils d'or dans les cheveux et qui\r
+empourprait d'une lueur sanglante la face sauvage de Jean Valjean.\r
+\r
+--Monsieur, dit le petit savoyard, avec cette confiance de l'enfance qui\r
+se compose d'ignorance et d'innocence,--ma pièce?\r
+\r
+--Comment t'appelles-tu? dit Jean Valjean.\r
+\r
+--Petit-Gervais, monsieur.\r
+\r
+--Va-t'en, dit Jean Valjean.\r
+\r
+--Monsieur, reprit l'enfant, rendez-moi ma pièce.\r
+\r
+Jean Valjean baissa la tête et ne répondit pas.\r
+\r
+L'enfant recommença:\r
+\r
+--Ma pièce, monsieur!\r
+\r
+L'oeil de Jean Valjean resta fixé à terre.\r
+\r
+--Ma pièce! cria l'enfant, ma pièce blanche! mon argent! Il semblait que\r
+Jean Valjean n'entendit point. L'enfant le prit au collet de sa blouse\r
+et le secoua. Et en même temps il faisait effort pour déranger le gros\r
+soulier ferré posé sur son trésor.\r
+\r
+--Je veux ma pièce! ma pièce de quarante sous!\r
+\r
+L'enfant pleurait. La tête de Jean Valjean se releva. Il était toujours\r
+assis. Ses yeux étaient troubles. Il considéra l'enfant avec une sorte\r
+d'étonnement, puis il étendit la main vers son bâton et cria d'une voix\r
+terrible:\r
+\r
+--Qui est là?\r
+\r
+--Moi, monsieur, répondit l'enfant. Petit-Gervais! moi! moi! Rendez-moi\r
+mes quarante sous, s'il vous plaît! Ôtez votre pied, monsieur, s'il vous\r
+plaît!\r
+\r
+Puis irrité, quoique tout petit, et devenant presque menaçant:\r
+\r
+--Ah, çà, ôterez-vous votre pied? Ôtez donc votre pied, voyons.\r
+\r
+--Ah! c'est encore toi! dit Jean Valjean, et se dressant brusquement\r
+tout debout, le pied toujours sur la pièce d'argent, il ajouta:--Veux-tu\r
+bien te sauver!\r
+\r
+L'enfant effaré le regarda, puis commença à trembler de la tête aux\r
+pieds, et, après quelques secondes de stupeur, se mit à s'enfuir en\r
+courant de toutes ses forces sans oser tourner le cou ni jeter un cri.\r
+\r
+Cependant à une certaine distance l'essoufflement le força de s'arrêter,\r
+et Jean Valjean, à travers sa rêverie, l'entendit qui sanglotait.\r
+\r
+Au bout de quelques instants l'enfant avait disparu. Le soleil s'était\r
+couché. L'ombre se faisait autour de Jean Valjean. Il n'avait pas mangé\r
+de la journée; il est probable qu'il avait la fièvre.\r
+\r
+Il était resté debout, et n'avait pas changé d'attitude depuis que\r
+l'enfant s'était enfui. Son souffle soulevait sa poitrine à des\r
+intervalles longs et inégaux. Son regard, arrêté à dix ou douze pas\r
+devant lui, semblait étudier avec une attention profonde la forme d'un\r
+vieux tesson de faïence bleue tombé dans l'herbe. Tout à coup il\r
+tressaillit; il venait de sentir le froid du soir.\r
+\r
+Il raffermit sa casquette sur son front, chercha machinalement à croiser\r
+et à boutonner sa blouse, fit un pas, et se baissa pour reprendre à\r
+terre son bâton. En ce moment il aperçut la pièce de quarante sous que\r
+son pied avait à demi enfoncée dans la terre et qui brillait parmi les\r
+cailloux.\r
+\r
+Ce fut comme une commotion galvanique. Qu'est-ce que c'est que ça?\r
+dit-il entre ses dents. Il recula de trois pas, puis s'arrêta, sans\r
+pouvoir détacher son regard de ce point que son pied avait foulé\r
+l'instant d'auparavant, comme si cette chose qui luisait là dans\r
+l'obscurité eût été un oeil ouvert fixé sur lui.\r
+\r
+Au bout de quelques minutes, il s'élança convulsivement vers la pièce\r
+d'argent, la saisit, et, se redressant, se mit à regarder au loin dans\r
+la plaine, jetant à la fois ses yeux vers tous les points de l'horizon,\r
+debout et frissonnant comme une bête fauve effarée qui cherche un asile.\r
+\r
+Il ne vit rien. La nuit tombait, la plaine était froide et vague, de\r
+grandes brumes violettes montaient dans la clarté crépusculaire.\r
+\r
+Il dit: «Ah!» et se mit à marcher rapidement dans une certaine\r
+direction, du côté où l'enfant avait disparu. Après une centaine de pas,\r
+il s'arrêta, regarda, et ne vit rien.\r
+\r
+Alors il cria de toute sa force: «Petit-Gervais! Petit-Gervais!»\r
+\r
+Il se tut, et attendit.\r
+\r
+Rien ne répondit.\r
+\r
+La campagne était déserte et morne. Il était environné de l'étendue. Il\r
+n'y avait rien autour de lui qu'une ombre où se perdait son regard et un\r
+silence où sa voix se perdait.\r
+\r
+Une bise glaciale soufflait, et donnait aux choses autour de lui une\r
+sorte de vie lugubre. Des arbrisseaux secouaient leurs petits bras\r
+maigres avec une furie incroyable. On eût dit qu'ils menaçaient et\r
+poursuivaient quelqu'un.\r
+\r
+Il recommença à marcher, puis il se mit à courir, et de temps en temps\r
+il s'arrêtait, et criait dans cette solitude, avec une voix qui était ce\r
+qu'on pouvait entendre de plus formidable et de plus désolé:\r
+«Petit-Gervais! Petit-Gervais!»\r
+\r
+Certes, si l'enfant l'eût entendu, il eût eu peur et se fût bien gardé\r
+de se montrer. Mais l'enfant était sans doute déjà bien loin.\r
+\r
+Il rencontra un prêtre qui était à cheval. Il alla à lui et lui dit:\r
+\r
+--Monsieur le curé, avez-vous vu passer un enfant?\r
+\r
+--Non, dit le prêtre.\r
+\r
+--Un nommé Petit-Gervais?\r
+\r
+--Je n'ai vu personne.\r
+\r
+Il tira deux pièces de cinq francs de sa sacoche et les remit au prêtre.\r
+\r
+--Monsieur le curé, voici pour vos pauvres.--Monsieur le curé, c'est un\r
+petit d'environ dix ans qui a une marmotte, je crois, et une vielle. Il\r
+allait. Un de ces savoyards, vous savez?\r
+\r
+--Je ne l'ai point vu.\r
+\r
+--Petit-Gervais? il n'est point des villages d'ici? pouvez-vous me dire?\r
+\r
+--Si c'est comme vous dites, mon ami, c'est un petit enfant étranger.\r
+Cela passe dans le pays. On ne les connaît pas.\r
+\r
+Jean Valjean prit violemment deux autres écus de cinq francs qu'il donna\r
+au prêtre.\r
+\r
+--Pour vos pauvres, dit-il.\r
+\r
+Puis il ajouta avec égarement:\r
+\r
+--Monsieur l'abbé, faites-moi arrêter. Je suis un voleur.\r
+\r
+Le prêtre piqua des deux et s'enfuit très effrayé.\r
+\r
+Jean Valjean se remit à courir dans la direction qu'il avait d'abord\r
+prise.\r
+\r
+Il fit de la sorte un assez long chemin, regardant, appelant, criant,\r
+mais il ne rencontra plus personne. Deux ou trois fois il courut dans la\r
+plaine vers quelque chose qui lui faisait l'effet d'un être couché ou\r
+accroupi; ce n'étaient que des broussailles ou des roches à fleur de\r
+terre. Enfin, à un endroit où trois sentiers se croisaient, il s'arrêta.\r
+La lune s'était levée. Il promena sa vue au loin et appela une dernière\r
+fois: «Petit-Gervais! Petit-Gervais! Petit-Gervais!» Son cri s'éteignit\r
+dans la brume, sans même éveiller un écho. Il murmura encore:\r
+«Petit-Gervais!» mais d'une voix faible et presque inarticulée. Ce fut\r
+là son dernier effort; ses jarrets fléchirent brusquement sous lui comme\r
+si une puissance invisible l'accablait tout à coup du poids de sa\r
+mauvaise conscience; il tomba épuisé sur une grosse pierre, les poings\r
+dans ses cheveux et le visage dans ses genoux, et il cria: «Je suis un\r
+misérable!»\r
+\r
+Alors son coeur creva et il se mit à pleurer. C'était la première fois\r
+qu'il pleurait depuis dix-neuf ans.\r
+\r
+Quand Jean Valjean était sorti de chez l'évêque, on l'a vu, il était\r
+hors de tout ce qui avait été sa pensée jusque-là. Il ne pouvait se\r
+rendre compte de ce qui se passait en lui. Il se raidissait contre\r
+l'action angélique et contre les douces paroles du vieillard. «Vous\r
+m'avez promis de devenir honnête homme. Je vous achète votre âme. Je la\r
+retire à l'esprit de perversité et je la donne au bon Dieu.» Cela lui\r
+revenait sans cesse. Il opposait à cette indulgence céleste l'orgueil,\r
+qui est en nous comme la forteresse du mal. Il sentait indistinctement\r
+que le pardon de ce prêtre était le plus grand assaut et la plus\r
+formidable attaque dont il eût encore été ébranlé; que son\r
+endurcissement serait définitif s'il résistait à cette clémence; que,\r
+s'il cédait, il faudrait renoncer à cette haine dont les actions des\r
+autres hommes avaient rempli son âme pendant tant d'années, et qui lui\r
+plaisait; que cette fois il fallait vaincre ou être vaincu, et que la\r
+lutte, une lutte colossale et décisive, était engagée entre sa\r
+méchanceté à lui et la bonté de cet homme.\r
+\r
+En présence de toutes ces lueurs, il allait comme un homme ivre. Pendant\r
+qu'il marchait ainsi, les yeux hagards, avait-il une perception\r
+distincte de ce qui pourrait résulter pour lui de son aventure à Digne?\r
+Entendait-il tous ces bourdonnements mystérieux qui avertissent ou\r
+importunent l'esprit à de certains moments de la vie? Une voix lui\r
+disait-elle à l'oreille qu'il venait de traverser l'heure solennelle de\r
+sa destinée, qu'il n'y avait plus de milieu pour lui, que si désormais\r
+il n'était pas le meilleur des hommes il en serait le pire, qu'il\r
+fallait pour ainsi dire que maintenant il montât plus haut que l'évêque\r
+ou retombât plus bas que le galérien, que s'il voulait devenir bon il\r
+fallait qu'il devînt ange; que s'il voulait rester méchant il fallait\r
+qu'il devînt monstre?\r
+\r
+Ici encore il faut se faire ces questions que nous nous sommes déjà\r
+faites ailleurs, recueillait-il confusément quelque ombre de tout ceci\r
+dans sa pensée? Certes, le malheur, nous l'avons dit, fait l'éducation\r
+de l'intelligence; cependant il est douteux que Jean Valjean fût en état\r
+de démêler tout ce que nous indiquons ici. Si ces idées lui arrivaient,\r
+il les entrevoyait plutôt qu'il ne les voyait, et elles ne réussissaient\r
+qu'à le jeter dans un trouble insupportable et presque douloureux. Au\r
+sortir de cette chose difforme et noire qu'on appelle le bagne, l'évêque\r
+lui avait fait mal à l'âme comme une clarté trop vive lui eût fait mal\r
+aux yeux en sortant des ténèbres. La vie future, la vie possible qui\r
+s'offrait désormais à lui toute pure et toute rayonnante le remplissait\r
+de frémissements et d'anxiété. Il ne savait vraiment plus où il en\r
+était. Comme une chouette qui verrait brusquement se lever le soleil, le\r
+forçat avait été ébloui et comme aveuglé par la vertu.\r
+\r
+Ce qui était certain, ce dont il ne se doutait pas, c'est qu'il n'était\r
+déjà plus le même homme, c'est que tout était changé en lui, c'est qu'il\r
+n'était plus en son pouvoir de faire que l'évêque ne lui eût pas parlé\r
+et ne l'eût pas touché.\r
+\r
+Dans cette situation d'esprit, il avait rencontré Petit-Gervais et lui\r
+avait volé ses quarante sous. Pourquoi? Il n'eût assurément pu\r
+l'expliquer; était-ce un dernier effet et comme un suprême effort des\r
+mauvaises pensées qu'il avait apportées du bagne, un reste d'impulsion,\r
+un résultat de ce qu'on appelle en statique la _force acquise_? C'était\r
+cela, et c'était aussi peut-être moins encore que cela. Disons-le\r
+simplement, ce n'était pas lui qui avait volé, ce n'était pas l'homme,\r
+c'était la bête qui, par habitude et par instinct, avait stupidement\r
+posé le pied sur cet argent, pendant que l'intelligence se débattait au\r
+milieu de tant d'obsessions inouïes et nouvelles. Quand l'intelligence\r
+se réveilla et vit cette action de la brute, Jean Valjean recula avec\r
+angoisse et poussa un cri d'épouvante.\r
+\r
+C'est que, phénomène étrange et qui n'était possible que dans la\r
+situation où il était, en volant cet argent à cet enfant, il avait fait\r
+une chose dont il n'était déjà plus capable.\r
+\r
+Quoi qu'il en soit, cette dernière mauvaise action eut sur lui un effet\r
+décisif; elle traversa brusquement ce chaos qu'il avait dans\r
+l'intelligence et le dissipa, mit d'un côté les épaisseurs obscures et\r
+de l'autre la lumière, et agit sur son âme, dans l'état où elle se\r
+trouvait, comme de certains réactifs chimiques agissent sur un mélange\r
+trouble en précipitant un élément et en clarifiant l'autre.\r
+\r
+Tout d'abord, avant même de s'examiner et de réfléchir, éperdu, comme\r
+quelqu'un qui cherche à se sauver, il tâcha de retrouver l'enfant pour\r
+lui rendre son argent, puis, quand il reconnut que cela était inutile et\r
+impossible, il s'arrêta désespéré. Au moment où il s'écria: «je suis un\r
+misérable!» il venait de s'apercevoir tel qu'il était, et il était déjà\r
+à ce point séparé de lui-même, qu'il lui semblait qu'il n'était plus\r
+qu'un fantôme, et qu'il avait là devant lui, en chair et en os, le bâton\r
+à la main, la blouse sur les reins, son sac rempli d'objets volés sur le\r
+dos, avec son visage résolu et morne, avec sa pensée pleine de projets\r
+abominables, le hideux galérien Jean Valjean.\r
+\r
+L'excès du malheur, nous l'avons remarqué, l'avait fait en quelque sorte\r
+visionnaire. Ceci fut donc comme une vision. Il vit véritablement ce\r
+Jean Valjean, cette face sinistre devant lui. Il fut presque au moment\r
+de se demander qui était cet homme, et il en eut horreur.\r
+\r
+Son cerveau était dans un de ces moments violents et pourtant\r
+affreusement calmes où la rêverie est si profonde qu'elle absorbe la\r
+réalité. On ne voit plus les objets qu'on a autour de soi, et l'on voit\r
+comme en dehors de soi les figures qu'on a dans l'esprit.\r
+\r
+Il se contempla donc, pour ainsi dire, face à face, et en même temps, à\r
+travers cette hallucination, il voyait dans une profondeur mystérieuse\r
+une sorte de lumière qu'il prit d'abord pour un flambeau. En regardant\r
+avec plus d'attention cette lumière qui apparaissait à sa conscience, il\r
+reconnut qu'elle avait la forme humaine, et que ce flambeau était\r
+l'évêque.\r
+\r
+Sa conscience considéra tour à tour ces deux hommes ainsi placés devant\r
+elle, l'évêque et Jean Valjean. Il n'avait pas fallu moins que le\r
+premier pour détremper le second. Par un de ces effets singuliers qui\r
+sont propres à ces sortes d'extases, à mesure que sa rêverie se\r
+prolongeait, l'évêque grandissait et resplendissait à ses yeux, Jean\r
+Valjean s'amoindrissait et s'effaçait. À un certain moment il ne fut\r
+plus qu'une ombre. Tout à coup il disparut. L'évêque seul était resté.\r
+\r
+Il remplissait toute l'âme de ce misérable d'un rayonnement magnifique.\r
+Jean Valjean pleura longtemps. Il pleura à chaudes larmes, il pleura à\r
+sanglots, avec plus de faiblesse qu'une femme, avec plus d'effroi qu'un\r
+enfant.\r
+\r
+Pendant qu'il pleurait, le jour se faisait de plus en plus dans son\r
+cerveau, un jour extraordinaire, un jour ravissant et terrible à la\r
+fois. Sa vie passée, sa première faute, sa longue expiation, son\r
+abrutissement extérieur, son endurcissement intérieur, sa mise en\r
+liberté réjouie par tant de plans de vengeance, ce qui lui était arrivé\r
+chez l'évêque, la dernière chose qu'il avait faite, ce vol de quarante\r
+sous à un enfant, crime d'autant plus lâche et d'autant plus monstrueux\r
+qu'il venait après le pardon de l'évêque, tout cela lui revint et lui\r
+apparut, clairement, mais dans une clarté qu'il n'avait jamais vue\r
+jusque-là. Il regarda sa vie, et elle lui parut horrible; son âme, et\r
+elle lui parut affreuse. Cependant un jour doux était sur cette vie et\r
+sur cette âme. Il lui semblait qu'il voyait Satan à la lumière du\r
+paradis.\r
+\r
+Combien d'heures pleura-t-il ainsi? que fit-il après avoir pleuré? où\r
+alla-t-il? on ne l'a jamais su. Il paraît seulement avéré que, dans\r
+cette même nuit, le voiturier qui faisait à cette époque le service de\r
+Grenoble et qui arrivait à Digne vers trois heures du matin, vit en\r
+traversant la rue de l'évêché un homme dans l'attitude de la prière, à\r
+genoux sur le pavé, dans l'ombre, devant la porte de monseigneur\r
+Bienvenu.\r
+\r
+\r
+\r
+\r
+Livre troisième--En l'année 1817\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+L'année 1817\r
+\r
+\r
+1817 est l'année que Louis XVIII, avec un certain aplomb royal qui ne\r
+manquait pas de fierté, qualifiait la vingt-deuxième de son règne. C'est\r
+l'année où M. Bruguière de Sorsum était célèbre. Toutes les boutiques\r
+des perruquiers, espérant la poudre et le retour de l'oiseau royal,\r
+étaient badigeonnées d'azur et fleurdelysées. C'était le temps candide\r
+où le comte Lynch siégeait tous les dimanches comme marguillier au banc\r
+d'oeuvre de Saint-Germain-des-Prés en habit de pair de France, avec son\r
+cordon rouge et son long nez, et cette majesté de profil particulière à\r
+un homme qui a fait une action d'éclat. L'action d'éclat commise par M.\r
+Lynch était ceci: avoir, étant maire de Bordeaux, le 12 mars 1814, donné\r
+la ville un peu trop tôt à M. le duc d'Angoulême. De là sa pairie. En\r
+1817, la mode engloutissait les petits garçons de quatre à six ans sous\r
+de vastes casquettes en cuir maroquiné à oreillons assez ressemblantes à\r
+des mitres d'esquimaux. L'armée française était vêtue de blanc, à\r
+l'autrichienne; les régiments s'appelaient légions; au lieu de chiffres\r
+ils portaient les noms des départements. Napoléon était à Sainte-Hélène,\r
+et, comme l'Angleterre lui refusait du drap vert, il faisait retourner\r
+ses vieux habits. En 1817, Pellegrini chantait, mademoiselle Bigottini\r
+dansait; Potier régnait; Odry n'existait pas encore. Madame Saqui\r
+succédait à Forioso. Il y avait encore des Prussiens en France. M.\r
+Delalot était un personnage. La légitimité venait de s'affirmer en\r
+coupant le poing, puis la tête, à Pleignier, à Carbonneau et à Tolleron.\r
+Le prince de Talleyrand, grand chambellan, et l'abbé Louis, ministre\r
+désigné des finances, se regardaient en riant du rire de deux augures;\r
+tous deux avaient célébré, le 14 juillet 1790, la messe de la Fédération\r
+au Champ de Mars; Talleyrand l'avait dite comme évêque, Louis l'avait\r
+servie comme diacre. En 1817, dans les contre-allées de ce même Champ de\r
+Mars, on apercevait de gros cylindres de bois, gisant sous la pluie,\r
+pourrissant dans l'herbe, peints en bleu avec des traces d'aigles et\r
+d'abeilles dédorées. C'étaient les colonnes qui, deux ans auparavant,\r
+avaient soutenu l'estrade de l'empereur au Champ-de-Mai. Elles étaient\r
+noircies çà et là de la brûlure du bivouac des Autrichiens baraqués près\r
+du Gros-Caillou. Deux ou trois de ces colonnes avaient disparu dans les\r
+feux de ces bivouacs et avaient chauffé les larges mains des\r
+_kaiserlicks_. Le Champ de Mai avait eu cela de remarquable qu'il avait\r
+été tenu au mois de juin et au Champ de Mars. En cette année 1817, deux\r
+choses étaient populaires: le Voltaire-Touquet et la tabatière à la\r
+Charte. L'émotion parisienne la plus récente était le crime de Dautun\r
+qui avait jeté la tête de son frère dans le bassin du Marché-aux-Fleurs.\r
+On commençait à faire au ministère de la marine une enquête sur cette\r
+fatale frégate de la Méduse qui devait couvrir de honte Chaumareix et de\r
+gloire Géricault. Le colonel Selves allait en Égypte pour y devenir\r
+Soliman pacha. Le palais des Thermes, rue de la Harpe, servait de\r
+boutique à un tonnelier. On voyait encore sur la plate-forme de la tour\r
+octogone de l'hôtel de Cluny la petite logette en planches qui avait\r
+servi d'observatoire à Messier, astronome de la marine sous Louis XVI.\r
+La duchesse de Duras lisait à trois ou quatre amis, dans son boudoir\r
+meublé d'X en satin bleu ciel, _Ourika_ inédite. On grattait les N au\r
+Louvre. Le pont d'Austerlitz abdiquait et s'intitulait pont du Jardin du\r
+Roi, double énigme qui déguisait à la fois le pont d'Austerlitz et le\r
+jardin des Plantes. Louis XVIII, préoccupé, tout en annotant du coin de\r
+l'ongle Horace, des héros qui se font empereurs et des sabotiers qui se\r
+font dauphins, avait deux soucis: Napoléon et Mathurin Bruneau.\r
+L'académie française donnait pour sujet de prix: _Le bonheur que procure\r
+l'étude_. M. Bellart était officiellement éloquent. On voyait germer à\r
+son ombre ce futur avocat général de Broè, promis aux sarcasmes de\r
+Paul-Louis Courier. Il y avait un faux Chateaubriand appelé Marchangy,\r
+en attendant qu'il y eut un faux Marchangy appelé d'Arlincourt. _Claire\r
+d'Albe_ et _Malek-Adel_ étaient des chefs-d'oeuvre; madame Cottin était\r
+déclarée le premier écrivain de l'époque. L'institut laissait rayer de\r
+sa liste l'académicien Napoléon Bonaparte. Une ordonnance royale\r
+érigeait Angoulême en école de marine, car, le duc d'Angoulême étant\r
+grand amiral, il était évident que la ville d'Angoulême avait de droit\r
+toutes les qualités d'un port de mer, sans quoi le principe monarchique\r
+eût été entamé. On agitait en conseil des ministres la question de\r
+savoir si l'on devait tolérer les vignettes représentant des voltiges\r
+qui assaisonnaient les affiches de Franconi et qui attroupaient les\r
+polissons des rues. M. Paër, auteur de l'_Agnese_, bonhomme à la face\r
+carrée qui avait une verrue sur la joue, dirigeait les petits concerts\r
+intimes de la marquise de Sassenaye, rue de la Ville-l'Évêque. Toutes\r
+les jeunes filles chantaient _l'Ermite de Saint-Avelle_, paroles\r
+d'Edmond Géraud. _Le Nain jaune_ se transformait en _Miroir_. Le café\r
+Lemblin tenait pour l'empereur contre le café Valois qui tenait pour les\r
+Bourbons. On venait de marier à une princesse de Sicile M. le duc de\r
+Berry, déjà regardé du fond de l'ombre par Louvel. Il y avait un an que\r
+madame de Staël était morte. Les gardes du corps sifflaient mademoiselle\r
+Mars. Les grands journaux étaient tout petits. Le format était\r
+restreint, mais la liberté était grande. _Le Constitutionnel_ était\r
+constitutionnel. _La Minerve_ appelait Chateaubriand _Chateaubriant_. Ce\r
+_t_ faisait beaucoup rire les bourgeois aux dépens du grand écrivain.\r
+Dans des journaux vendus, des journalistes prostitués insultaient les\r
+proscrits de 1815; David n'avait plus de talent, Arnault n'avait plus\r
+d'esprit, Carnot n'avait plus de probité; Soult n'avait gagné aucune\r
+bataille; il est vrai que Napoléon n'avait plus de génie. Personne\r
+n'ignore qu'il est assez rare que les lettres adressées par la poste à\r
+un exilé lui parviennent, les polices se faisant un religieux devoir de\r
+les intercepter. Le fait n'est point nouveau; Descartes, banni, s'en\r
+plaignait. Or, David ayant, dans un journal belge, montré quelque humeur\r
+de ne pas recevoir les lettres qu'on lui écrivait, ceci paraissait\r
+plaisant aux feuilles royalistes qui bafouaient à cette occasion le\r
+proscrit. Dire: _les régicides_, ou dire: _les votants_, dire: _les\r
+ennemis_, ou dire: _les alliés_, dire: _Napoléon_, ou dire: _Buonaparte_,\r
+cela séparait deux hommes plus qu'un abîme. Tous les gens de bons sens\r
+convenaient que l'ère des révolutions était à jamais fermée par le roi\r
+Louis XVIII, surnommé «l'immortel auteur de la charte». Au terre-plein\r
+du Pont-Neuf, on sculptait le mot _Redivivus_, sur le piédestal qui\r
+attendait la statue de Henri IV. M. Piet ébauchait, rue Thérèse, n° 4,\r
+son conciliabule pour consolider la monarchie. Les chefs de la droite\r
+disaient dans les conjonctures graves: «Il faut écrire à Bacot». MM.\r
+Canuel, O'Mahony et de Chappedelaine esquissaient, un peu approuvés de\r
+Monsieur, ce qui devait être plus tard «la conspiration du bord de\r
+l'eau». L'Épingle Noire complotait de son côté. Delaverderie s'abouchait\r
+avec Trogoff. M. Decazes, esprit dans une certaine mesure libéral,\r
+dominait. Chateaubriand, debout tous les matins devant sa fenêtre du n°\r
+27 de la rue Saint-Dominique, en pantalon à pieds et en pantoufles, ses\r
+cheveux gris coiffés d'un madras, les yeux fixés sur un miroir, une\r
+trousse complète de chirurgien dentiste ouverte devant lui, se curait\r
+les dents, qu'il avait charmantes, tout en dictant des variantes de _la\r
+Monarchie selon la Charte_ à M. Pilorge, son secrétaire. La critique\r
+faisant autorité préférait Lafon à Talma. M. de Féletz signait A.; M.\r
+Hoffmann signait Z. Charles Nodier écrivait _Thérèse Aubert_. Le divorce\r
+était aboli. Les lycées s'appelaient collèges. Les collégiens, ornés au\r
+collet d'une fleur de lys d'or, s'y gourmaient à propos du roi de Rome.\r
+La contre-police du château dénonçait à son altesse royale Madame le\r
+portrait, partout exposé, de M. le duc d'Orléans, lequel avait meilleure\r
+mine en uniforme de colonel général des houzards que M. le duc de Berry\r
+en uniforme de colonel général des dragons; grave inconvénient. La ville\r
+de Paris faisait redorer à ses frais le dôme des Invalides. Les hommes\r
+sérieux se demandaient ce que ferait, dans telle ou telle occasion, M.\r
+de Trinquelague; M. Clausel de Montals se séparait, sur divers points,\r
+de M. Clausel de Coussergues; M. de Salaberry n'était pas content. Le\r
+comédien Picard, qui était de l'Académie dont le comédien Molière\r
+n'avait pu être, faisait jouer _les deux Philibert_ à l'Odéon, sur le\r
+fronton duquel l'arrachement des lettres laissait encore lire\r
+distinctement: THÉÂTRE DE L'IMPÉRATRICE. On prenait parti pour ou contre\r
+Cugnet de Montarlot. Fabvier était factieux; Bavoux était\r
+révolutionnaire. Le libraire Pélicier publiait une édition de Voltaire,\r
+sous ce titre: _OEuvres de Voltaire_, de l'Académie française. «Cela\r
+fait venir les acheteurs», disait cet éditeur naïf. L'opinion générale\r
+était que M. Charles Loyson, serait le génie du siècle; l'envie\r
+commençait à le mordre, signe de gloire; et l'on faisait sur lui ce\r
+vers:\r
+\r
+_Même quand Loyson vole, on sent qu'il a des pattes._\r
+\r
+Le cardinal Fesch refusant de se démettre, M. de Pins, archevêque\r
+d'Amasie, administrait le diocèse de Lyon. La querelle de la vallée des\r
+Dappes commençait entre la Suisse et la France par un mémoire du\r
+capitaine Dufour, depuis général. Saint-Simon, ignoré, échafaudait son\r
+rêve sublime. Il y avait à l'académie des sciences un Fourier célèbre\r
+que la postérité a oublié et dans je ne sais quel grenier un Fourier\r
+obscur dont l'avenir se souviendra. Lord Byron commençait à poindre; une\r
+note d'un poème de Millevoye l'annonçait à la France en ces termes: _un\r
+certain lord Baron_. David d'Angers s'essayait à pétrir le marbre.\r
+L'abbé Caron parlait avec éloge, en petit comité de séminaristes, dans\r
+le cul-de-sac des Feuillantines, d'un prêtre inconnu nommé Félicité\r
+Robert qui a été plus tard Lamennais. Une chose qui fumait et clapotait\r
+sur la Seine avec le bruit d'un chien qui nage allait et venait sous les\r
+fenêtres des Tuileries, du pont Royal au pont Louis XV c'était une\r
+mécanique bonne à pas grand'chose, une espèce de joujou, une rêverie\r
+d'inventeur songe-creux, une utopie: un bateau à vapeur. Les Parisiens\r
+regardaient cette inutilité avec indifférence. M. de Vaublanc,\r
+réformateur de l'Institut par coup d'État, ordonnance et fournée, auteur\r
+distingué de plusieurs académiciens, après en avoir fait, ne pouvait\r
+parvenir à l'être. Le faubourg Saint-Germain et la pavillon Marsan\r
+souhaitaient pour préfet de police M. Delaveau, à cause de sa dévotion.\r
+Dupuytren et Récamier se prenaient de querelle à l'amphithéâtre de\r
+l'École de médecine et se menaçaient du poing à propos de la divinité de\r
+Jésus-Christ. Cuvier, un oeil sur la Genèse et l'autre sur la nature,\r
+s'efforçait de plaire à la réaction bigote en mettant les fossiles\r
+d'accord avec les textes et en faisant flatter Moïse par les\r
+mastodontes. M. François de Neufchâteau, louable cultivateur de la\r
+mémoire de Parmentier, faisait mille efforts pour que _pomme de terre_\r
+fût prononcée _parmentière_, et n'y réussissait point. L'abbé Grégoire,\r
+ancien évêque, ancien conventionnel, ancien sénateur, était passé dans\r
+la polémique royaliste à l'état «d'infâme Grégoire». Cette locution que\r
+nous venons d'employer: _passer à l'état de_, était dénoncée comme\r
+néologisme par M. Royer-Collard. On pouvait distinguer encore à sa\r
+blancheur, sous la troisième arche du pont d'Iéna, la pierre neuve avec\r
+laquelle, deux ans auparavant, on avait bouché le trou de mine pratiqué\r
+par Blücher pour faire sauter le pont. La justice appelait à sa barre un\r
+homme qui, en voyant entrer le comte d'Artois à Notre-Dame, avait dit\r
+tout haut: _Sapristi! je regrette le temps où je voyais Bonaparte et\r
+Talma entrer bras dessus bras dessous au Bal-Sauvage_. Propos séditieux.\r
+Six mois de prison. Des traîtres se montraient déboutonnés; des hommes\r
+qui avaient passé à l'ennemi la veille d'une bataille ne cachaient rien\r
+de la récompense et marchaient impudiquement en plein soleil dans le\r
+cynisme des richesses et des dignités; des déserteurs de Ligny et des\r
+Quatre-Bras, dans le débraillé de leur turpitude payée, étalaient leur\r
+dévouement monarchique tout nu; oubliant ce qui est écrit en Angleterre\r
+sur la muraille intérieure des water-closets publics: _Please adjust\r
+your dress before leaving_.\r
+\r
+Voilà, pêle-mêle, ce qui surnage confusément de l'année 1817, oubliée\r
+aujourd'hui. L'histoire néglige presque toutes ces particularités, et ne\r
+peut faire autrement; l'infini l'envahirait. Pourtant ces détails, qu'on\r
+appelle à tort petits--il n'y a ni petits faits dans l'humanité, ni\r
+petites feuilles dans la végétation--sont utiles. C'est de la\r
+physionomie des années que se compose la figure des siècles.\r
+\r
+En cette année 1817, quatre jeunes Parisiens firent «une bonne farce».\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+Double quatuor\r
+\r
+\r
+Ces Parisiens étaient l'un de Toulouse, l'autre de Limoges, le troisième\r
+de Cahors et le quatrième de Montauban; mais ils étaient étudiants, et\r
+qui dit étudiant dit parisien; étudier à Paris, c'est naître à Paris.\r
+\r
+Ces jeunes gens étaient insignifiants; tout le monde a vu ces\r
+figures-là; quatre échantillons du premier venu; ni bons ni mauvais, ni\r
+savants ni ignorants, ni des génies ni des imbéciles; beaux de ce\r
+charmant avril qu'on appelle vingt ans. C'étaient quatre Oscars\r
+quelconques, car à cette époque les Arthurs n'existaient pas encore.\r
+_Brûlez pour lui les parfums d'Arabie_, s'écriait la romance, _Oscar\r
+s'avance, Oscar, je vais le voir!_ On sortait d'Ossian, l'élégance était\r
+scandinave et calédonienne, le genre anglais pur ne devait prévaloir que\r
+plus tard, et le premier des Arthurs, Wellington, venait à peine de\r
+gagner la bataille de Waterloo.\r
+\r
+Ces Oscars s'appelaient l'un Félix Tholomyès, de Toulouse; l'autre\r
+Listolier, de Cahors; l'autre Fameuil, de Limoges; le dernier\r
+Blachevelle, de Montauban. Naturellement chacun avait sa maîtresse.\r
+Blachevelle aimait Favourite, ainsi nommée parce qu'elle était allée en\r
+Angleterre; Listolier adorait Dahlia, qui avait pris pour nom de guerre\r
+un nom de fleur; Fameuil idolâtrait Zéphine, abrégé de Joséphine;\r
+Tholomyès avait Fantine, dite la Blonde à cause de ses beaux cheveux\r
+couleur de soleil.\r
+\r
+Favourite, Dahlia, Zéphine et Fantine étaient quatre ravissantes filles,\r
+parfumées et radieuses, encore un peu ouvrières, n'ayant pas tout à fait\r
+quitté leur aiguille, dérangées par les amourettes, mais ayant sur le\r
+visage un reste de la sérénité du travail et dans l'âme cette fleur\r
+d'honnêteté qui dans la femme survit à la première chute. Il y avait une\r
+des quatre qu'on appelait la jeune, parce qu'elle était la cadette; et\r
+une qu'on appelait la vieille. La vieille avait vingt-trois ans. Pour ne\r
+rien celer, les trois premières étaient plus expérimentées, plus\r
+insouciantes et plus envolées dans le bruit de la vie que Fantine la\r
+Blonde, qui en était à sa première illusion.\r
+\r
+Dahlia, Zéphine, et surtout Favourite, n'en auraient pu dire autant. Il\r
+y avait déjà plus d'un épisode à leur roman à peine commencé, et\r
+l'amoureux, qui s'appelait Adolphe au premier chapitre, se trouvait être\r
+Alphonse au second, et Gustave au troisième. Pauvreté et coquetterie\r
+sont deux conseillères fatales, l'une gronde, l'autre flatte; et les\r
+belles filles du peuple les ont toutes les deux qui leur parlent bas à\r
+l'oreille, chacune de son côté. Ces âmes mal gardées écoutent. De là les\r
+chutes qu'elles font et les pierres qu'on leur jette. On les accable\r
+avec la splendeur de tout ce qui est immaculé et inaccessible. Hélas! si\r
+la _Yungfrau_ avait faim?\r
+\r
+Favourite, ayant été en Angleterre, avait pour admiratrices Zéphine et\r
+Dahlia. Elle avait eu de très bonne heure un chez-soi. Son père était un\r
+vieux professeur de mathématiques brutal et qui gasconnait; point marié,\r
+courant le cachet malgré l'âge. Ce professeur, étant jeune, avait vu un\r
+jour la robe d'une femme de chambre s'accrocher à un garde-cendre; il\r
+était tombé amoureux de cet accident. Il en était résulté Favourite.\r
+Elle rencontrait de temps en temps son père, qui la saluait. Un matin,\r
+une vieille femme à l'air béguin était entrée chez elle et lui avait\r
+dit:\r
+\r
+--Vous ne me connaissez pas, mademoiselle?\r
+\r
+--Non.\r
+\r
+--Je suis ta mère.\r
+\r
+Puis la vieille avait ouvert le buffet, bu et mangé, fait apporter un\r
+matelas qu'elle avait, et s'était installée. Cette mère, grognon et\r
+dévote, ne parlait jamais à Favourite, restait des heures sans souffler\r
+mot, déjeunait, dînait et soupait comme quatre, et descendait faire\r
+salon chez le portier, où elle disait du mal de sa fille.\r
+\r
+Ce qui avait entraîné Dahlia vers Listolier, vers d'autres peut-être,\r
+vers l'oisiveté, c'était d'avoir de trop jolis ongles roses. Comment\r
+faire travailler ces ongles-là? Qui veut rester vertueuse ne doit pas\r
+avoir pitié de ses mains. Quant à Zéphine, elle avait conquis Fameuil\r
+par sa petite manière mutine et caressante de dire: «Oui, monsieur».\r
+\r
+Les jeunes gens étant camarades, les jeunes filles étaient amies. Ces\r
+amours-là sont toujours doublés de ces amitiés-là.\r
+\r
+Sage et philosophe, c'est deux; et ce qui le prouve, c'est que, toutes\r
+réserves faites sur ces petits ménages irréguliers, Favourite, Zéphine\r
+et Dahlia étaient des filles philosophes, et Fantine une fille sage.\r
+\r
+Sage, dira-t-on? et Tholomyès? Salomon répondrait que l'amour fait\r
+partie de la sagesse. Nous nous bornons à dire que l'amour de Fantine\r
+était un premier amour, un amour unique, un amour fidèle.\r
+\r
+Elle était la seule des quatre qui ne fût tutoyée que par un seul.\r
+\r
+Fantine était un de ces êtres comme il en éclôt, pour ainsi dire, au\r
+fond du peuple. Sortie des plus insondables épaisseurs de l'ombre\r
+sociale, elle avait au front le signe de l'anonyme et de l'inconnu. Elle\r
+était née à Montreuil-sur-mer. De quels parents? Qui pourrait le dire?\r
+On ne lui avait jamais connu ni père ni mère. Elle se nommait Fantine.\r
+Pourquoi Fantine? On ne lui avait jamais connu d'autre nom. À l'époque\r
+de sa naissance, le Directoire existait encore. Point de nom de famille,\r
+elle n'avait pas de famille; point de nom de baptême, l'église n'était\r
+plus là. Elle s'appela comme il plut au premier passant qui la rencontra\r
+toute petite, allant pieds nus dans la rue. Elle reçut un nom comme elle\r
+recevait l'eau des nuées sur son front quand il pleuvait. On l'appela la\r
+petite Fantine. Personne n'en savait davantage. Cette créature humaine\r
+était venue dans la vie comme cela. À dix ans, Fantine quitta la ville\r
+et s'alla mettre en service chez des fermiers des environs. À quinze\r
+ans, elle vint à Paris "chercher fortune". Fantine était belle et resta\r
+pure le plus longtemps qu'elle put. C'était une jolie blonde avec de\r
+belles dents. Elle avait de l'or et des perles pour dot, mais son or\r
+était sur sa tête et ses perles étaient dans sa bouche.\r
+\r
+Elle travailla pour vivre; puis, toujours pour vivre, car le coeur a sa\r
+faim aussi, elle aima.\r
+\r
+Elle aima Tholomyès.\r
+\r
+Amourette pour lui, passion pour elle. Les rues du quartier latin,\r
+qu'emplit le fourmillement des étudiants et des grisettes, virent le\r
+commencement de ce songe. Fantine, dans ces dédales de la colline du\r
+Panthéon, où tant d'aventures se nouent et se dénouent, avait fui\r
+longtemps Tholomyès, mais de façon à le rencontrer toujours. Il y a une\r
+manière d'éviter qui ressemble à chercher. Bref, l'églogue eut lieu.\r
+\r
+Blachevelle, Listolier et Fameuil formaient une sorte de groupe dont\r
+Tholomyès était la tête. C'était lui qui avait l'esprit.\r
+\r
+Tholomyès était l'antique étudiant vieux; il était riche; il avait\r
+quatre mille francs de rente; quatre mille francs de rente, splendide\r
+scandale sur la montagne Sainte-Geneviève. Tholomyès était un viveur de\r
+trente ans, mal conservé. Il était ridé et édenté; et il ébauchait une\r
+calvitie dont il disait lui-même sans tristesse: _crâne à trente ans,\r
+genou à quarante_. Il digérait médiocrement, et il lui était venu un\r
+larmoiement à un oeil. Mais à mesure que sa jeunesse s'éteignait, il\r
+allumait sa gaîté; il remplaçait ses dents par des lazzis, ses cheveux\r
+par la joie, sa santé par l'ironie, et son oeil qui pleurait riait sans\r
+cesse. Il était délabré, mais tout en fleurs. Sa jeunesse, pliant bagage\r
+bien avant l'âge, battait en retraite en bon ordre, éclatait de rire, et\r
+l'on n'y voyait que du feu. Il avait eu une pièce refusée au Vaudeville.\r
+Il faisait çà et là des vers quelconques. En outre, il doutait\r
+supérieurement de toute chose, grande force aux yeux des faibles. Donc,\r
+étant ironique et chauve, il était le chef. _Iron_ est un mot anglais\r
+qui veut dire fer. Serait-ce de là que viendrait ironie?\r
+\r
+Un jour Tholomyès prit à part les trois autres, fît un geste d'oracle,\r
+et leur dit:\r
+\r
+--Il y a bientôt un an que Fantine, Dahlia, Zéphine et Favourite nous\r
+demandent de leur faire une surprise. Nous la leur avons promise\r
+solennellement. Elles nous en parlent toujours, à moi surtout. De même\r
+qu'à Naples les vieilles femmes crient à saint Janvier: _Faccia\r
+gialluta, fa o miracolo_. Face jaune, fais ton miracle! nos belles me\r
+disent sans cesse: «Tholomyès, quand accoucheras-tu de ta surprise?» En\r
+même temps nos parents nous écrivent. Scie des deux côtés. Le moment me\r
+semble venu. Causons.\r
+\r
+Sur ce, Tholomyès baissa la voix, et articula mystérieusement quelque\r
+chose de si gai qu'un vaste et enthousiaste ricanement sortit des quatre\r
+bouches à la fois et que Blachevelle s'écria:\r
+\r
+--Ça, c'est une idée!\r
+\r
+Un estaminet plein de fumée se présenta, ils y entrèrent, et le reste de\r
+leur conférence se perdit dans l'ombre.\r
+\r
+Le résultat de ces ténèbres fut une éblouissante partie de plaisir qui\r
+eut lieu le dimanche suivant, les quatre jeunes gens invitant les quatre\r
+jeunes filles.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+Quatre à quatre\r
+\r
+\r
+Ce qu'était une partie de campagne d'étudiants et de grisettes, il y a\r
+quarante-cinq ans, on se le représente malaisément aujourd'hui. Paris\r
+n'a plus les mêmes environs; la figure de ce qu'on pourrait appeler la\r
+vie circumparisienne a complètement changé depuis un demi-siècle; où il\r
+y avait le coucou, il y a le wagon; où il y avait la patache, il y a le\r
+bateau à vapeur; on dit aujourd'hui Fécamp comme on disait Saint-Cloud.\r
+Le Paris de 1862 est une ville qui a la France pour banlieue.\r
+\r
+Les quatre couples accomplirent consciencieusement toutes les folies\r
+champêtres possibles alors. On entrait dans les vacances, et c'était une\r
+chaude et claire journée d'été. La veille, Favourite, la seule qui sût\r
+écrire, avait écrit ceci à Tholomyès au nom des quatre: «C'est un bonne\r
+heure de sortir de bonheur.» C'est pourquoi ils se levèrent à cinq\r
+heures du matin. Puis ils allèrent à Saint-Cloud par le coche,\r
+regardèrent la cascade à sec, et s'écrièrent: «Cela doit être bien beau\r
+quand il y a de l'eau!» déjeunèrent à la _Tête-Noire_, où Castaing\r
+n'avait pas encore passé, se payèrent une partie de bagues au quinconce\r
+du grand bassin, montèrent à la lanterne de Diogène, jouèrent des\r
+macarons à la roulette du pont de Sèvres, cueillirent des bouquets à\r
+Puteaux, achetèrent des mirlitons à Neuilly, mangèrent partout des\r
+chaussons de pommes, furent parfaitement heureux.\r
+\r
+Les jeunes filles bruissaient et bavardaient comme des fauvettes\r
+échappées. C'était un délire. Elles donnaient par moments de petites\r
+tapes aux jeunes gens. Ivresse matinale de la vie! Adorables années!\r
+L'aile des libellules frissonne. Oh! qui que vous soyez, vous\r
+souvenez-vous? Avez-vous marché dans les broussailles, en écartant les\r
+branches à cause de la tête charmante qui vient derrière vous? Avez-vous\r
+glissé en riant sur quelque talus mouillé par la pluie avec une femme\r
+aimée qui vous retient par la main et qui s'écrie: «Ah! mes brodequins\r
+tout neufs! dans quel état ils sont!»\r
+\r
+Disons tout de suite que cette joyeuse contrariété, une ondée, manqua à\r
+cette compagnie de belle humeur, quoique Favourite eût dit en partant,\r
+avec un accent magistral et maternel: _Les limaces se promènent dans les\r
+sentiers. Signe de pluie, mes enfants_.\r
+\r
+Toutes quatre étaient follement jolies. Un bon vieux poète classique,\r
+alors en renom, un bonhomme qui avait une Éléonore, M. le chevalier de\r
+Labouïsse, errant ce jour-là sous les marronniers de Saint-Cloud, les\r
+vit passer vers dix heures du matin; il s'écria: _Il y en a une de\r
+trop_, songeant aux Grâces. Favourite, l'amie de Blachevelle, celle de\r
+vingt-trois ans, la vieille, courait en avant sous les grandes branches\r
+vertes, sautait les fossés, enjambait éperdument les buissons, et\r
+présidait cette gaîté avec une verve de jeune faunesse. Zéphine et\r
+Dahlia, que le hasard avait faites belles de façon qu'elles se faisaient\r
+valoir en se rapprochant et se complétaient, ne se quittaient point, par\r
+instinct de coquetterie plus encore que par amitié, et, appuyées l'une à\r
+l'autre, prenaient des poses anglaises; les premiers _keepsakes_\r
+venaient de paraître, la mélancolie pointait pour les femmes, comme,\r
+plus tard, le byronisme pour les hommes, et les cheveux du sexe tendre\r
+commençaient à s'éplorer. Zéphine et Dahlia étaient coiffées en\r
+rouleaux. Listolier et Fameuil, engagés dans une discussion sur leurs\r
+professeurs, expliquaient à Fantine la différence qu'il y avait entre M.\r
+Delvincourt et M. Blondeau.\r
+\r
+Blachevelle semblait avoir été créé expressément pour porter sur son\r
+bras le dimanche le châle-ternaux boiteux de Favourite.\r
+\r
+Tholomyès suivait, dominant le groupe. Il était très gai, mais on\r
+sentait en lui le gouvernement; il y avait de la dictature dans sa\r
+jovialité; son ornement principal était un pantalon jambes-d'éléphant,\r
+en nankin, avec sous-pieds de tresse de cuivre; il avait un puissant\r
+rotin de deux cents francs à la main, et, comme il se permettait tout,\r
+une chose étrange appelée cigare, à la bouche. Rien n'étant sacré pour\r
+lui, il fumait.\r
+\r
+--Ce Tholomyès est étonnant, disaient les autres avec vénération. Quels\r
+pantalons! quelle énergie!\r
+\r
+Quant à Fantine, c'était la joie. Ses dents splendides avaient\r
+évidemment reçu de Dieu une fonction, le rire. Elle portait à sa main\r
+plus volontiers que sur sa tête son petit chapeau de paille cousue, aux\r
+longues brides blanches. Ses épais cheveux blonds, enclins à flotter et\r
+facilement dénoués et qu'il fallait rattacher sans cesse, semblaient\r
+faits pour la fuite de Galatée sous les saules. Ses lèvres roses\r
+babillaient avec enchantement. Les coins de sa bouche voluptueusement\r
+relevés, comme aux mascarons antiques d'Érigone, avaient l'air\r
+d'encourager les audaces; mais ses longs cils pleins d'ombre\r
+s'abaissaient discrètement sur ce brouhaha du bas du visage comme pour\r
+mettre le holà. Toute sa toilette avait on ne sait quoi de chantant et\r
+de flambant. Elle avait une robe de barège mauve, de petits\r
+souliers-cothurnes mordorés dont les rubans traçaient des X sur son fin\r
+bas blanc à jour, et cette espèce de spencer en mousseline, invention\r
+marseillaise, dont le nom, canezou, corruption du mot _quinze août_\r
+prononcé à la Canebière, signifie beau temps, chaleur et midi. Les trois\r
+autres, moins timides, nous l'avons dit, étaient décolletées tout net,\r
+ce qui, l'été, sous des chapeaux couverts de fleurs, a beaucoup de grâce\r
+et d'agacerie; mais, à côté de ces ajustements hardis, le canezou de la\r
+blonde Fantine, avec ses transparences, ses indiscrétions et ses\r
+réticences, cachant et montrant à la fois, semblait une trouvaille\r
+provocante de la décence, et la fameuse cour d'amour, présidée par la\r
+vicomtesse de Cette aux yeux vert de mer, eût peut-être donné le prix de\r
+la coquetterie à ce canezou qui concourait pour la chasteté. Le plus\r
+naïf est quelquefois le plus savant. Cela arrive.\r
+\r
+Éclatante de face, délicate de profil, les yeux d'un bleu profond, les\r
+paupières grasses, les pieds cambrés et petits, les poignets et les\r
+chevilles admirablement emboîtés, la peau blanche laissant voir çà et là\r
+les arborescences azurées des veines, la joue puérile et franche, le cou\r
+robuste des Junons éginétiques, la nuque forte et souple, les épaules\r
+modelées comme par Coustou, ayant au centre une voluptueuse fossette\r
+visible à travers la mousseline; une gaîté glacée de rêverie;\r
+sculpturale et exquise; telle était Fantine; et l'on devinait sous ces\r
+chiffons une statue, et dans cette statue une âme.\r
+\r
+Fantine était belle, sans trop le savoir. Les rares songeurs, prêtres\r
+mystérieux du beau, qui confrontent silencieusement toute chose à la\r
+perfection, eussent entrevu en cette petite ouvrière, à travers la\r
+transparence de la grâce parisienne, l'antique euphonie sacrée. Cette\r
+fille de l'ombre avait de la race. Elle était belle sous les deux\r
+espèces, qui sont le style et le rythme. Le style est la forme de\r
+l'idéal; le rythme en est le mouvement.\r
+\r
+Nous avons dit que Fantine était la joie, Fantine était aussi la pudeur.\r
+\r
+Pour un observateur qui l'eût étudiée attentivement, ce qui se dégageait\r
+d'elle, à travers toute cette ivresse de l'âge, de la saison et de\r
+l'amourette, c'était une invincible expression de retenue et de\r
+modestie. Elle restait un peu étonnée. Ce chaste étonnement-là est la\r
+nuance qui sépare Psyché de Vénus. Fantine avait les longs doigts blancs\r
+et fins de la vestale qui remue les cendres du feu sacré avec une\r
+épingle d'or. Quoiqu'elle n'eût rien refusé, on ne le verra que trop, à\r
+Tholomyès, son visage, au repos, était souverainement virginal; une\r
+sorte de dignité sérieuse et presque austère l'envahissait soudainement\r
+à de certaines heures, et rien n'était singulier et troublant comme de\r
+voir la gaîté s'y éteindre si vite et le recueillement y succéder sans\r
+transition à l'épanouissement. Cette gravité subite, parfois sévèrement\r
+accentuée, ressemblait au dédain d'une déesse. Son front, son nez et son\r
+menton offraient cet équilibre de ligne, très distinct de l'équilibre de\r
+proportion, et d'où résulte l'harmonie du visage; dans l'intervalle si\r
+caractéristique qui sépare la base du nez de la lèvre supérieure, elle\r
+avait ce pli imperceptible et charmant, signe mystérieux de la chasteté\r
+qui rendit Barberousse amoureux d'une Diane trouvée dans les fouilles\r
+d'Icône.\r
+\r
+L'amour est une faute; soit. Fantine était l'innocence surnageant sur la\r
+faute.\r
+\r
+\r
+\r
+\r
+Chapitre IV\r
+\r
+Tholomyès est si joyeux qu'il chante une chanson espagnole\r
+\r
+\r
+Cette journée-là était d'un bout à l'autre faite d'aurore. Toute la\r
+nature semblait avoir congé, et rire. Les parterres de Saint-Cloud\r
+embaumaient; le souffle de la Seine remuait vaguement les feuilles;\r
+les branches gesticulaient dans le vent; les abeilles mettaient les\r
+jasmins au pillage; toute une bohème de papillons s'ébattait dans les\r
+achillées, les trèfles et les folles avoines; il y avait dans l'auguste\r
+parc du roi de France un tas de vagabonds, les oiseaux.\r
+\r
+Les quatre joyeux couples, mêlés au soleil, aux champs, aux fleurs, aux\r
+arbres, resplendissaient.\r
+\r
+Et, dans cette communauté de paradis, parlant, chantant, courant,\r
+dansant, chassant aux papillons, cueillant des liserons, mouillant leurs\r
+bas à jour roses dans les hautes herbes, fraîches, folles, point\r
+méchantes, toutes recevaient un peu çà et là les baisers de tous,\r
+excepté Fantine, enfermée dans sa vague résistance rêveuse et farouche,\r
+et qui aimait.\r
+\r
+--Toi, lui disait Favourite, tu as toujours l'air chose.\r
+\r
+Ce sont là les joies. Ces passages de couples heureux sont un appel\r
+profond à la vie et à la nature, et font sortir de tout la caresse et la\r
+lumière. Il y avait une fois une fée qui fit les prairies et les arbres\r
+exprès pour les amoureux. De là cette éternelle école buissonnière des\r
+amants qui recommence sans cesse et qui durera tant qu'il y aura des\r
+buissons et des écoliers. De là la popularité du printemps parmi les\r
+penseurs. Le patricien et le gagne-petit, le duc et pair et le robin,\r
+les gens de la cour et les gens de la ville, comme on parlait autrefois,\r
+tous sont sujets de cette fée. On rit, on se cherche, il y a dans l'air\r
+une clarté d'apothéose, quelle transfiguration que d'aimer! Les clercs\r
+de notaire sont des dieux. Et les petits cris, les poursuites dans\r
+l'herbe, les tailles prises au vol, ces jargons qui sont des mélodies,\r
+ces adorations qui éclatent dans la façon de dire une syllabe, ces\r
+cerises arrachées d'une bouche à l'autre, tout cela flamboie et passe\r
+dans des gloires célestes. Les belles filles font un doux gaspillage\r
+d'elles-mêmes. On croit que cela ne finira jamais. Les philosophes, les\r
+poètes, les peintres regardent ces extases et ne savent qu'en faire,\r
+tant cela les éblouit. Le départ pour Cythère! s'écrie Watteau; Lancret,\r
+le peintre de la roture, contemple ses bourgeois envolés dans le bleu;\r
+Diderot tend les bras à toutes ces amourettes, et d'Urfé y mêle des\r
+druides.\r
+\r
+Après le déjeuner les quatre couples étaient allés voir, dans ce qu'on\r
+appelait alors le carré du roi, une plante nouvellement arrivée de\r
+l'Inde, dont le nom nous échappe en ce moment, et qui à cette époque\r
+attirait tout Paris à Saint-Cloud; c'était un bizarre et charmant\r
+arbrisseau haut sur tige, dont les innombrables branches fines comme des\r
+fils, ébouriffées, sans feuilles, étaient couvertes d'un million de\r
+petites rosettes blanches; ce qui faisait que l'arbuste avait l'air\r
+d'une chevelure pouilleuse de fleurs. Il y avait toujours foule à\r
+l'admirer.\r
+\r
+L'arbuste vu, Tholomyès s'était écrié: «J'offre des ânes!» et, prix fait\r
+avec un ânier, ils étaient revenus par Vanves et Issy. À Issy, incident.\r
+Le parc, Bien National possédé à cette époque par le munitionnaire\r
+Bourguin, était d'aventure tout grand ouvert. Ils avaient franchi la\r
+grille, visité l'anachorète mannequin dans sa grotte, essayé les petits\r
+effets mystérieux du fameux cabinet des miroirs, lascif traquenard digne\r
+d'un satyre devenu millionnaire ou de Turcaret métamorphosé en Priape.\r
+Ils avaient robustement secoué le grand filet balançoire attaché aux\r
+deux châtaigniers célébrés par l'abbé de Bernis. Tout en y balançant ces\r
+belles l'une après l'autre, ce qui faisait, parmi les rires universels,\r
+des plis de jupe envolée où Greuze eût trouvé son compte, le toulousain\r
+Tholomyès, quelque peu espagnol, Toulouse est cousine de Tolosa,\r
+chantait, sur une mélopée mélancolique, la vieille chanson _gallega_\r
+probablement inspirée par quelque belle fille lancée à toute volée sur\r
+une corde entre deux arbres:\r
+\r
+          _Soy de Badajoz._\r
+          _Amor me llama._\r
+          _Toda mi alma_\r
+          _Es en mi ojos_\r
+          _Porque enseñas_\r
+          _À tus piernas._\r
+\r
+Fantine seule refusa de se balancer.\r
+\r
+--Je n'aime pas qu'on ait du genre comme ça, murmura assez aigrement\r
+Favourite.\r
+\r
+Les ânes quittés, joie nouvelle; on passa la Seine en bateau, et de\r
+Passy, à pied, ils gagnèrent la barrière de l'Étoile. Ils étaient, on\r
+s'en souvient, debout depuis cinq heures du matin; mais, bah! _il n'y a\r
+pas de lassitude le dimanche_, disait Favourite; _le dimanche, la\r
+fatigue ne travaille pas_. Vers trois heures les quatre couples, effarés\r
+de bonheur, dégringolaient aux montagnes russes, édifice singulier qui\r
+occupait alors les hauteurs Beaujon et dont on apercevait la ligne\r
+serpentante au-dessus des arbres des Champs-Élysées.\r
+\r
+De temps en temps Favourite s'écriait:\r
+\r
+--Et la surprise? je demande la surprise.\r
+\r
+--Patience, répondait Tholomyès.\r
+\r
+\r
+\r
+\r
+Chapitre V\r
+\r
+Chez Bombarda\r
+\r
+\r
+Les montagnes russes épuisées, on avait songé au dîner; et le radieux\r
+huitain, enfin un peu las, s'était échoué au cabaret Bombarda,\r
+succursale qu'avait établie aux Champs-Élysées ce fameux restaurateur\r
+Bombarda, dont on voyait alors l'enseigne rue de Rivoli à côté du\r
+passage Delorme.\r
+\r
+Une chambre grande, mais laide, avec alcôve et lit au fond (vu la\r
+plénitude du cabaret le dimanche, il avait fallu accepter ce gîte); deux\r
+fenêtres d'où l'on pouvait contempler, à travers les ormes, le quai et\r
+la rivière; un magnifique rayon d'août effleurant les fenêtres; deux\r
+tables; sur l'une une triomphante montagne de bouquets mêlés à des\r
+chapeaux d'hommes et de femmes; à l'autre les quatre couples attablés\r
+autour d'un joyeux encombrement de plats, d'assiettes, de verres et de\r
+bouteilles; des cruchons de bière mêlés à des flacons de vin; peu\r
+d'ordre sur la table, quelque désordre dessous;\r
+\r
+    _Ils faisaient sous la table_\r
+    _Un bruit, un trique-trac de pieds épouvantable_\r
+\r
+dit Molière.\r
+\r
+Voilà où en était vers quatre heures et demie du soir la bergerade\r
+commencée à cinq heures du matin. Le soleil déclinait, l'appétit\r
+s'éteignait.\r
+\r
+Les Champs-Élysées, pleins de soleil et de foule, n'étaient que lumière\r
+et poussière, deux choses dont se compose la gloire. Les chevaux de\r
+Marly, ces marbres hennissants, se cabraient dans un nuage d'or. Les\r
+carrosses allaient et venaient. Un escadron de magnifiques gardes du\r
+corps, clairon en tête, descendait l'avenue de Neuilly; le drapeau\r
+blanc, vaguement rose au soleil couchant, flottait sur le dôme des\r
+Tuileries. La place de la Concorde, redevenue alors place Louis XV,\r
+regorgeait de promeneurs contents. Beaucoup portaient la fleur de lys\r
+d'argent suspendue au ruban blanc moiré qui, en 1817, n'avait pas encore\r
+tout à fait disparu des boutonnières. Çà et là au milieu des passants\r
+faisant cercle et applaudissant, des rondes de petites filles jetaient\r
+au vent une bourrée bourbonienne alors célèbre, destinée à foudroyer les\r
+Cent-Jours, et qui avait pour ritournelle:\r
+\r
+          _Rendez-nous notre père de Gand,_\r
+             _Rendez-nous notre père._\r
+\r
+Des tas de faubouriens endimanchés, parfois même fleurdelysés comme les\r
+bourgeois, épars dans le grand carré et dans le carré Marigny, jouaient\r
+aux bagues et tournaient sur les chevaux de bois; d'autres buvaient;\r
+quelques-uns, apprentis imprimeurs, avaient des bonnets de papier; on\r
+entendait leurs rires. Tout était radieux. C'était un temps de paix\r
+incontestable et de profonde sécurité royaliste; c'était l'époque où un\r
+rapport intime et spécial du préfet de police Anglès au roi sur les\r
+faubourgs de Paris se terminait par ces lignes: «Tout bien considéré,\r
+sire, il n'y a rien à craindre de ces gens-là. Ils sont insouciants et\r
+indolents comme des chats. Le bas peuple des provinces est remuant,\r
+celui de Paris ne l'est pas. Ce sont tous petits hommes. Sire, il en\r
+faudrait deux bout à bout pour faire un de vos grenadiers. Il n'y a\r
+point de crainte du côté de la populace de la capitale. Il est\r
+remarquable que la taille a encore décru dans cette population depuis\r
+cinquante ans; et le peuple des faubourgs de Paris est plus petit\r
+qu'avant la révolution. Il n'est point dangereux. En somme, c'est de la\r
+canaille bonne.»\r
+\r
+Qu'un chat puisse se changer en lion, les préfets de police ne le\r
+croient pas possible; cela est pourtant, et c'est là le miracle du\r
+peuple de Paris. Le chat d'ailleurs, si méprisé du comte Anglès, avait\r
+l'estime des républiques antiques; il incarnait à leurs yeux la liberté,\r
+et, comme pour servir de pendant à la Minerve aptère du Pirée, il y\r
+avait sur la place publique de Corinthe le colosse de bronze d'un chat.\r
+La police naïve de la restauration voyait trop «en beau» le peuple de\r
+Paris. Ce n'est point, autant qu'on le croit, de la «canaille bonne». Le\r
+Parisien est au Français ce que l'Athénien était au Grec; personne ne\r
+dort mieux que lui, personne n'est plus franchement frivole et paresseux\r
+que lui, personne mieux que lui n'a l'air d'oublier; qu'on ne s'y fie\r
+pas pourtant; il est propre à toute sorte de nonchalance, mais, quand il\r
+y a de la gloire au bout, il est admirable à toute espèce de furie.\r
+Donnez-lui une pique, il fera le 10 août; donnez-lui un fusil, vous\r
+aurez Austerlitz. Il est le point d'appui de Napoléon et la ressource de\r
+Danton. S'agit-il de la patrie? il s'enrôle; s'agit-il de la liberté? il\r
+dépave. Gare! ses cheveux pleins de colère sont épiques; sa blouse se\r
+drape en chlamyde. Prenez garde. De la première rue Greneta venue, il\r
+fera des fourches caudines. Si l'heure sonne, ce faubourien va grandir,\r
+ce petit homme va se lever, et il regardera d'une façon terrible, et son\r
+souffle deviendra tempête, et il sortira de cette pauvre poitrine grêle\r
+assez de vent pour déranger les plis des Alpes. C'est grâce au\r
+faubourien de Paris que la révolution, mêlée aux armées, conquiert\r
+l'Europe. Il chante, c'est sa joie. Proportionnez sa chanson à sa\r
+nature, et vous verrez! Tant qu'il n'a pour refrain que la Carmagnole,\r
+il ne renverse que Louis XVI; faites-lui chanter la Marseillaise, il\r
+délivrera le monde.\r
+\r
+Cette note écrite en marge du rapport Anglès, nous revenons à nos quatre\r
+couples. Le dîner, comme nous l'avons dit, s'achevait.\r
+\r
+\r
+\r
+\r
+Chapitre VI\r
+\r
+Chapitre où l'on s'adore\r
+\r
+\r
+Propos de table et propos d'amour; les uns sont aussi insaisissables que\r
+les autres; les propos d'amour sont des nuées, les propos de table sont\r
+des fumées.\r
+\r
+Fameuil et Dahlia fredonnaient; Tholomyès buvait; Zéphine riait, Fantine\r
+souriait. Listolier soufflait dans une trompette de bois achetée à\r
+Saint-Cloud. Favourite regardait tendrement Blachevelle et disait:\r
+\r
+--Blachevelle, je t'adore.\r
+\r
+Ceci amena une question de Blachevelle:\r
+\r
+--Qu'est-ce que tu ferais, Favourite, si je cessais de t'aimer?\r
+\r
+--Moi! s'écria Favourite. Ah! ne dis pas cela, même pour rire! Si tu\r
+cessais de m'aimer, je te sauterais après, je te grifferais, je te\r
+gratignerais, je te jetterais de l'eau, je te ferais arrêter.\r
+\r
+Blachevelle sourit avec la fatuité voluptueuse d'un homme chatouillé à\r
+l'amour-propre. Favourite reprit:\r
+\r
+--Oui, je crierais à la garde! Ah! je me gênerais par exemple! Canaille!\r
+\r
+Blachevelle, extasié, se renversa sur sa chaise et ferma\r
+orgueilleusement les deux yeux.\r
+\r
+Dahlia, tout en mangeant, dit bas à Favourite dans le brouhaha:\r
+\r
+--Tu l'idolâtres donc bien, ton Blachevelle?\r
+\r
+--Moi, je le déteste, répondit Favourite du même ton en ressaisissant sa\r
+fourchette. Il est avare. J'aime le petit d'en face de chez moi. Il est\r
+très bien, ce jeune homme-là, le connais-tu? On voit qu'il a le genre\r
+d'être acteur. J'aime les acteurs. Sitôt qu'il rentre, sa mère dit: «Ah!\r
+mon Dieu! ma tranquillité est perdue. Le voilà qui va crier. Mais, mon\r
+ami, tu me casses la tête!» Parce qu'il va dans la maison, dans des\r
+greniers à rats, dans des trous noirs, si haut qu'il peut monter,--et\r
+chanter, et déclamer, est-ce que je sais, moi? qu'on l'entend d'en bas!\r
+Il gagne déjà vingt sous par jour chez un avoué à écrire de la chicane.\r
+Il est fils d'un ancien chantre de Saint-Jacques-du-Haut-Pas. Ah! il est\r
+très bien. Il m'idolâtre tant qu'un jour qu'il me voyait faire de la\r
+pâte pour des crêpes, il m'a dit: _Mamselle, faites des beignets de vos\r
+gants et je les mangerai_. Il n'y a que les artistes pour dire des\r
+choses comme ça. Ah! il est très bien. Je suis en train d'être insensée\r
+de ce petit-là. C'est égal, je dis à Blachevelle que je l'adore. Comme\r
+je mens! Hein? comme je mens!\r
+\r
+Favourite fit une pause, et continua:\r
+\r
+--Dahlia, vois-tu, je suis triste. Il n'a fait que pleuvoir tout l'été,\r
+le vent m'agace, le vent ne décolère pas, Blachevelle est très pingre,\r
+c'est à peine s'il y a des petits pois au marché, on ne sait que manger,\r
+j'ai le spleen, comme disent les Anglais, le beurre est si cher! et\r
+puis, vois, c'est une horreur, nous dînons dans un endroit où il y a un\r
+lit, ça me dégoûte de la vie.\r
+\r
+\r
+\r
+\r
+Chapitre VII\r
+\r
+Sagesse de Tholomyès\r
+\r
+\r
+Cependant, tandis que quelques-uns chantaient, les autres causaient\r
+tumultueusement, et tous ensemble; ce n'était plus que du bruit.\r
+Tholomyès intervint:\r
+\r
+--Ne parlons point au hasard ni trop vite, s'écria-t-il. Méditons si\r
+nous voulons être éblouissants. Trop d'improvisation vide bêtement\r
+l'esprit. Bière qui coule n'amasse point de mousse. Messieurs, pas de\r
+hâte. Mêlons la majesté à la ripaille; mangeons avec recueillement;\r
+festinons lentement. Ne nous pressons pas. Voyez le printemps; s'il se\r
+dépêche, il est flambé, c'est-à-dire gelé. L'excès de zèle perd les\r
+pêchers et les abricotiers. L'excès de zèle tue la grâce et la joie des\r
+bons dîners. Pas de zèle, messieurs! Grimod de la Reynière est de l'avis\r
+de Talleyrand.\r
+\r
+Une sourde rébellion gronda dans le groupe.\r
+\r
+--Tholomyès, laisse-nous tranquilles, dit Blachevelle.\r
+\r
+--À bas le tyran! dit Fameuil.\r
+\r
+--Bombarda, Bombance et Bamboche! cria Listolier.\r
+\r
+--Le dimanche existe, reprit Fameuil.\r
+\r
+--Nous sommes sobres, ajouta Listolier.\r
+\r
+--Tholomyès, fit Blachevelle, contemple mon calme.\r
+\r
+--Tu en es le marquis, répondit Tholomyès.\r
+\r
+Ce médiocre jeu de mots fit l'effet d'une pierre dans une mare. Le\r
+marquis de Montcalm était un royaliste alors célèbre. Toutes les\r
+grenouilles se turent.\r
+\r
+--Amis, s'écria Tholomyès, de l'accent d'un homme qui ressaisit\r
+l'empire, remettez-vous. Il ne faut pas que trop de stupeur accueille ce\r
+calembour tombé du ciel. Tout ce qui tombe de la sorte n'est pas\r
+nécessairement digne d'enthousiasme et de respect. Le calembour est la\r
+fiente de l'esprit qui vole. Le lazzi tombe n'importe où; et l'esprit,\r
+après la ponte d'une bêtise, s'enfonce dans l'azur. Une tache blanchâtre\r
+qui s'aplatit sur le rocher n'empêche pas le condor de planer. Loin de\r
+moi l'insulte au calembour! Je l'honore dans la proportion de ses\r
+mérites; rien de plus. Tout ce qu'il y a de plus auguste, de plus\r
+sublime et de plus charmant dans l'humanité, et peut-être hors de\r
+l'humanité, a fait des jeux de mots. Jésus-Christ a fait un calembour\r
+sur saint Pierre, Moïse sur Isaac, Eschyle sur Polynice, Cléopâtre sur\r
+Octave. Et notez que ce calembour de Cléopâtre a précédé la bataille\r
+d'Actium, et que, sans lui, personne ne se souviendrait de la ville de\r
+Toryne, nom grec qui signifie cuiller à pot. Cela concédé, je reviens à\r
+mon exhortation. Mes frères, je le répète, pas de zèle, pas de\r
+tohu-bohu, pas d'excès, même en pointes, gaîtés, liesses et jeux de\r
+mots. Écoutez-moi, j'ai la prudence d'Amphiaraüs et la calvitie de\r
+César. Il faut une limite, même aux rébus. _Est modus in rebus_. Il faut\r
+une limite, même aux dîners. Vous aimez les chaussons aux pommes,\r
+mesdames, n'en abusez pas. Il faut, même en chaussons, du bon sens et de\r
+l'art. La gloutonnerie châtie le glouton. Gula punit Gulax.\r
+L'indigestion est chargée par le bon Dieu de faire de la morale aux\r
+estomacs. Et, retenez ceci: chacune de nos passions, même l'amour, a un\r
+estomac qu'il ne faut pas trop remplir. En toute chose il faut écrire à\r
+temps le mot _finis_, il faut se contenir, quand cela devient urgent,\r
+tirer le verrou sur son appétit, mettre au violon sa fantaisie et se\r
+mener soi-même au poste. Le sage est celui qui sait à un moment donné\r
+opérer sa propre arrestation. Ayez quelque confiance en moi. Parce que\r
+j'ai fait un peu mon droit, à ce que me disent mes examens, parce que je\r
+sais la différence qu'il y a entre la question mue et la question\r
+pendante, parce que j'ai soutenu une thèse en latin sur la manière dont\r
+on donnait la torture à Rome au temps où Munatius Demens était questeur\r
+du Parricide, parce que je vais être docteur, à ce qu'il paraît, il ne\r
+s'ensuit pas de toute nécessité que je sois un imbécile. Je vous\r
+recommande la modération dans vos désirs. Vrai comme je m'appelle Félix\r
+Tholomyès, je parle bien. Heureux celui qui, lorsque l'heure a sonné,\r
+prend un parti héroïque, et abdique comme Sylla, ou Origène!\r
+\r
+Favourite écoutait avec une attention profonde.\r
+\r
+--Félix! dit-elle, quel joli mot! j'aime ce nom-là. C'est en latin. Ça\r
+veut dire Prosper.\r
+\r
+Tholomyès poursuivit:\r
+\r
+--Quirites, gentlemen, Caballeros, mes amis! voulez-vous ne sentir aucun\r
+aiguillon et vous passer de lit nuptial et braver l'amour? Rien de plus\r
+simple. Voici la recette: la limonade, l'exercice outré, le travail\r
+forcé, éreintez-vous, traînez des blocs, ne dormez pas, veillez,\r
+gorgez-vous de boissons nitreuses et de tisanes de nymphaeas, savourez\r
+des émulsions de pavots et d'agnuscastus, assaisonnez-moi cela d'une\r
+diète sévère, crevez de faim, et joignez-y les bains froids, les\r
+ceintures d'herbes, l'application d'une plaque de plomb, les lotions\r
+avec la liqueur de Saturne et les fomentations avec l'oxycrat.\r
+\r
+--J'aime mieux une femme, dit Listolier.\r
+\r
+--La femme! reprit Tholomyès, méfiez-vous-en. Malheur à celui qui se\r
+livre au coeur changeant de la femme! La femme est perfide et tortueuse.\r
+Elle déteste le serpent par jalousie de métier. Le serpent, c'est la\r
+boutique en face.\r
+\r
+--Tholomyès, cria Blachevelle, tu es ivre!\r
+\r
+--Pardieu! dit Tholomyès.\r
+\r
+--Alors sois gai, reprit Blachevelle.\r
+\r
+Et, remplissant son verre, il se leva:\r
+\r
+--Gloire au vin! _Nunc te, Bacche, canam_! Pardon, mesdemoiselles, c'est\r
+de l'espagnol. Et la preuve, señoras, la voici: tel peuple, telle\r
+futaille. L'arrobe de Castille contient seize litres, le cantaro\r
+d'Alicante douze, l'almude des Canaries vingt-cinq, le cuartin des\r
+Baléares vingt-six, la botte du czar Pierre trente. Vive ce czar qui\r
+était grand, et vive sa botte qui était plus grande encore! Mesdames, un\r
+conseil d'ami: trompez-vous de voisin, si bon vous semble. Le propre de\r
+l'amour, c'est d'errer. L'amourette n'est pas faite pour s'accroupir et\r
+s'abrutir comme une servante anglaise qui a le calus du scrobage aux\r
+genoux. Elle n'est pas faite pour cela, elle erre gaîment, la douce\r
+amourette! On a dit: l'erreur est humaine; moi je dis: l'erreur est\r
+amoureuse. Mesdames, je vous idolâtre toutes. Ô Zéphine, ô Joséphine,\r
+figure plus que chiffonnée, vous seriez charmante, si vous n'étiez de\r
+travers. Vous avez l'air d'un joli visage sur lequel, par mégarde, on\r
+s'est assis. Quant à Favourite, ô nymphes et muses! un jour que\r
+Blachevelle passait le ruisseau de la rue Guérin-Boisseau, il vit une\r
+belle fille aux bas blancs et bien tirés qui montrait ses jambes. Ce\r
+prologue lui plut, et Blachevelle aima. Celle qu'il aima était\r
+Favourite. Ô Favourite, tu as des lèvres ioniennes. Il y avait un\r
+peintre grec, appelé Euphorion, qu'on avait surnommé le peintre des\r
+lèvres. Ce Grec seul eût été digne de peindre ta bouche! Écoute! avant\r
+toi, il n'y avait pas de créature digne de ce nom. Tu es faite pour\r
+recevoir la pomme comme Vénus ou pour la manger comme Ève. La beauté\r
+commence à toi. Je viens de parler d'Ève, c'est toi qui l'as créée. Tu\r
+mérites le brevet d'invention de la jolie femme. Ô Favourite, je cesse\r
+de vous tutoyer, parce que je passe de la poésie à la prose. Vous\r
+parliez de mon nom tout à l'heure. Cela m'a attendri; mais, qui que nous\r
+soyons, méfions-nous des noms. Ils peuvent se tromper. Je me nomme Félix\r
+et ne suis pas heureux. Les mots sont des menteurs. N'acceptons pas\r
+aveuglément les indications qu'ils nous donnent. Ce serait une erreur\r
+d'écrire à Liège pour avoir des bouchons et à Pau pour avoir des gants.\r
+Miss Dahlia, à votre place, je m'appellerais Rosa. Il faut que la fleur\r
+sente bon et que la femme ait de l'esprit. Je ne dis rien de Fantine,\r
+c'est une songeuse, une rêveuse, une pensive, une sensitive; c'est un\r
+fantôme ayant la forme d'une nymphe et la pudeur d'une nonne, qui se\r
+fourvoie dans la vie de grisette, mais qui se réfugie dans les\r
+illusions, et qui chante, et qui prie, et qui regarde l'azur sans trop\r
+savoir ce qu'elle voit ni ce qu'elle fait, et qui, les yeux au ciel,\r
+erre dans un jardin où il y a plus d'oiseaux qu'il n'en existe! Ô\r
+Fantine, sache ceci: moi Tholomyès, je suis une illusion; mais elle ne\r
+m'entend même pas, la blonde fille des chimères! Du reste, tout en elle\r
+est fraîcheur, suavité, jeunesse, douce clarté matinale. Ô Fantine,\r
+fille digne de vous appeler marguerite ou perle, vous êtes une femme du\r
+plus bel orient. Mesdames, un deuxième conseil: ne vous mariez point; le\r
+mariage est une greffe; cela prend bien ou mal; fuyez ce risque. Mais,\r
+bah! qu'est-ce que je chante là? Je perds mes paroles. Les filles sont\r
+incurables sur l'épousaille; et tout ce que nous pouvons dire, nous\r
+autres sages, n'empêchera point les giletières et les piqueuses de\r
+bottines de rêver des maris enrichis de diamants. Enfin, soit; mais,\r
+belles, retenez ceci: vous mangez trop de sucre. Vous n'avez qu'un tort,\r
+ô femmes, c'est de grignoter du sucre. Ô sexe rongeur, tes jolies\r
+petites dents blanches adorent le sucre. Or, écoutez bien, le sucre est\r
+un sel. Tout sel est desséchant. Le sucre est le plus desséchant de tous\r
+les sels. Il pompe à travers les veines les liquides du sang; de là la\r
+coagulation, puis la solidification du sang; de là les tubercules dans\r
+le poumon; de là la mort. Et c'est pourquoi le diabète confine à la\r
+phthisie. Donc ne croquez pas de sucre, et vous vivrez! Je me tourne\r
+vers les hommes. Messieurs, faites des conquêtes. Pillez-vous les uns\r
+aux autres sans remords vos bien-aimées. Chassez-croisez. En amour, il\r
+n'y a pas d'amis. Partout où il y a une jolie femme l'hostilité est\r
+ouverte. Pas de quartier, guerre à outrance! Une jolie femme est un\r
+casus belli; une jolie femme est un flagrant délit. Toutes les invasions\r
+de l'histoire sont déterminées par des cotillons. La femme est le droit\r
+de l'homme. Romulus a enlevé les Sabines, Guillaume a enlevé les\r
+Saxonnes, César a enlevé les Romaines. L'homme qui n'est pas aimé plane\r
+comme un vautour sur les amantes d'autrui; et quant à moi, à tous ces\r
+infortunés qui sont veufs, je jette la proclamation sublime de Bonaparte\r
+à l'armée d'Italie: «Soldats, vous manquez de tout. L'ennemi en a.»\r
+\r
+Tholomyès s'interrompit.\r
+\r
+--Souffle, Tholomyès, dit Blachevelle.\r
+\r
+En même temps, Blachevelle, appuyé de Listolier et de Fameuil, entonna\r
+sur un air de complainte une de ces chansons d'atelier composées des\r
+premiers mots venus, rimées richement et pas du tout, vides de sens\r
+comme le geste de l'arbre et le bruit du vent, qui naissent de la vapeur\r
+des pipes et se dissipent et s'envolent avec elle. Voici par quel\r
+couplet le groupe donna la réplique à la harangue de Tholomyès:\r
+\r
+Les pères dindons donnèrent de l'argent à un agent pour que mons\r
+Clermont-Tonnerre fût fait pape à la Saint-Jean; Mais Clermont ne put\r
+pas être fait pape, n'étant pas prêtre.\r
+\r
+Alors leur agent rageant leur rapporta leur argent.\r
+\r
+Ceci n'était pas fait pour calmer l'improvisation de Tholomyès; il vida\r
+son verre, le remplit, et recommença.\r
+\r
+--À bas la sagesse! oubliez tout ce que j'ai dit. Ne soyons ni prudes,\r
+ni prudents, ni prud'hommes. Je porte un toast à l'allégresse; soyons\r
+allègres! Complétons notre cours de droit par la folie et la nourriture.\r
+Indigestion et digeste. Que Justinien soit le mâle et que Ripaille soit\r
+la femelle! Joie dans les profondeurs! Vis, ô création! Le monde est un\r
+gros diamant! Je suis heureux. Les oiseaux sont étonnants. Quelle fête\r
+partout! Le rossignol est un Elleviou gratis. Été, je te salue. Ô\r
+Luxembourg, ô Géorgiques de la rue Madame et de l'allée de\r
+l'Observatoire! Ô pioupious rêveurs! ô toutes ces bonnes charmantes qui,\r
+tout en gardant des enfants, s'amusent à en ébaucher! Les pampas de\r
+l'Amérique me plairaient, si je n'avais les arcades de l'Odéon. Mon âme\r
+s'envole dans les forêts vierges et dans les savanes. Tout est beau. Les\r
+mouches bourdonnent dans les rayons. Le soleil a éternué le colibri.\r
+Embrasse-moi, Fantine!\r
+\r
+Il se trompa, et embrassa Favourite.\r
+\r
+\r
+\r
+\r
+Chapitre VIII\r
+\r
+Mort d'un cheval\r
+\r
+\r
+--On dîne mieux chez Edon que chez Bombarda, s'écria Zéphine.\r
+\r
+--Je préfère Bombarda à Edon, déclara Blachevelle. Il a plus de luxe.\r
+C'est plus asiatique. Voyez la salle d'en bas. Il y a des glaces sur les\r
+murs.\r
+\r
+--J'en aime mieux dans mon assiette, dit Favourite.\r
+\r
+Blachevelle insista:\r
+\r
+--Regardez les couteaux. Les manches sont en argent chez Bombarda, et en\r
+os chez Edon. Or, l'argent est plus précieux que l'os.\r
+\r
+--Excepté pour ceux qui ont un menton d'argent, observa Tholomyès.\r
+\r
+Il regardait en cet instant-là le dôme des Invalides, visible des\r
+fenêtres de Bombarda.\r
+\r
+Il y eut une pause.\r
+\r
+--Tholomyès, cria Fameuil, tout à l'heure, Listolier et moi, nous avions\r
+une discussion.\r
+\r
+--Une discussion est bonne, répondit Tholomyès, une querelle vaut mieux.\r
+\r
+--Nous disputions philosophie.\r
+\r
+--Soit.\r
+\r
+--Lequel préfères-tu de Descartes ou de Spinosa?\r
+\r
+--Désaugiers, dit Tholomyès.\r
+\r
+Cet arrêt rendu, il but et reprit:\r
+\r
+--Je consens à vivre. Tout n'est pas fini sur la terre, puisqu'on peut\r
+encore déraisonner. J'en rends grâces aux dieux immortels. On ment, mais\r
+on rit. On affirme, mais on doute. L'inattendu jaillit du syllogisme.\r
+C'est beau. Il est encore ici-bas des humains qui savent joyeusement\r
+ouvrir et fermer la boîte à surprises du paradoxe. Ceci, mesdames, que\r
+vous buvez d'un air tranquille, est du vin de Madère, sachez-le, du cru\r
+de Coural das Freiras qui est à trois cent dix-sept toises au-dessus du\r
+niveau de la mer! Attention en buvant! trois cent dix-sept toises! et\r
+monsieur Bombarda, le magnifique restaurateur, vous donne ces trois cent\r
+dix-sept toises pour quatre francs cinquante centimes!\r
+\r
+Fameuil interrompit de nouveau:\r
+\r
+--Tholomyès, tes opinions font loi. Quel est ton auteur favori?\r
+\r
+--Ber....\r
+\r
+--Quin?\r
+\r
+--Non. Choux.\r
+\r
+Et Tholomyès poursuivit:\r
+\r
+--Honneur à Bombarda! il égalerait Munophis d'Elephanta s'il pouvait me\r
+cueillir une almée, et Thygélion de Chéronée s'il pouvait m'apporter une\r
+hétaïre! car, ô mesdames, il y avait des Bombarda en Grèce et en Égypte.\r
+C'est Apulée qui nous l'apprend. Hélas! toujours les mêmes choses et\r
+rien de nouveau. Plus rien d'inédit dans la création du créateur! _Nil\r
+sub sole novum_, dit Salomon; _amor omnibus idem_, dit Virgile; et\r
+Carabine monte avec Carabin dans la galiote de Saint-Cloud, comme\r
+Aspasie s'embarquait avec Périclès sur la flotte de Samos. Un dernier\r
+mot. Savez-vous ce que c'était qu'Aspasie, mesdames? Quoiqu'elle vécût\r
+dans un temps où les femmes n'avaient pas encore d'âme, c'était une âme;\r
+une âme d'une nuance rose et pourpre, plus embrasée que le feu, plus\r
+franche que l'aurore. Aspasie était une créature en qui se touchaient\r
+les deux extrêmes de la femme; c'était la prostituée déesse. Socrate,\r
+plus Manon Lescaut. Aspasie fut créée pour le cas où il faudrait une\r
+catin à Prométhée.\r
+\r
+Tholomyès, lancé, se serait difficilement arrêté, si un cheval ne se fût\r
+abattu sur le quai en cet instant-là même. Du choc, la charrette et\r
+l'orateur restèrent court. C'était une jument beauceronne, vieille et\r
+maigre et digne de l'équarrisseur, qui traînait une charrette fort\r
+lourde. Parvenue devant Bombarda, la bête, épuisée et accablée, avait\r
+refusé d'aller plus loin. Cet incident avait fait de la foule. À peine\r
+le charretier, jurant et indigné, avait-il eu le temps de prononcer avec\r
+l'énergie convenable le mot sacramentel: _mâtin_! appuyé d'un implacable\r
+coup de fouet, que la haridelle était tombée pour ne plus se relever. Au\r
+brouhaha des passants, les gais auditeurs de Tholomyès tournèrent la\r
+tête, et Tholomyès en profita pour clore son allocution par cette\r
+strophe mélancolique:\r
+\r
+          _Elle était de ce monde où coucous et carrosses_\r
+                   _Ont le même destin,_\r
+          _Et, rosse, elle a vécu ce que vivent les rosses,_\r
+                   _L'espace d'un: mâtin!_\r
+\r
+--Pauvre cheval, soupira Fantine.\r
+\r
+Et Dahlia s'écria:\r
+\r
+--Voilà Fantine qui va se mettre à plaindre les chevaux! Peut-on être\r
+fichue bête comme ça!\r
+\r
+En ce moment, Favourite, croisant les bras et renversant la tête en\r
+arrière, regarda résolûment Tholomyès et dit:\r
+\r
+--Ah çà! et la surprise?\r
+\r
+--Justement. L'instant est arrivé, répondit Tholomyès. Messieurs,\r
+l'heure de la surprise a sonné. Mesdames, attendez-nous un moment.\r
+\r
+--Cela commence par un baiser, dit Blachevelle.\r
+\r
+--Sur le front, ajouta Tholomyès.\r
+\r
+Chacun déposa gravement un baiser sur le front de sa maîtresse; puis ils\r
+se dirigèrent vers la porte tous les quatre à la file, en mettant leur\r
+doigt sur la bouche.\r
+\r
+Favourite battit des mains à leur sortie.\r
+\r
+--C'est déjà amusant, dit-elle.\r
+\r
+--Ne soyez pas trop longtemps, murmura Fantine. Nous vous attendons.\r
+\r
+\r
+\r
+\r
+Chapitre IX\r
+\r
+Fin joyeuse de la joie\r
+\r
+\r
+Les jeunes filles, restées seules, s'accoudèrent deux à deux sur l'appui\r
+des fenêtres, jasant, penchant leur tête et se parlant d'une croisée à\r
+l'autre.\r
+\r
+Elles virent les jeunes gens sortir du cabaret Bombarda bras dessus bras\r
+dessous; ils se retournèrent, leur firent des signes en riant, et\r
+disparurent dans cette poudreuse cohue du dimanche qui envahit\r
+hebdomadairement les Champs-Élysées.\r
+\r
+--Ne soyez pas longtemps! cria Fantine.\r
+\r
+--Que vont-ils nous rapporter? dit Zéphine.\r
+\r
+--Pour sûr ce sera joli, dit Dahlia.\r
+\r
+--Moi, reprit Favourite, je veux que ce soit en or.\r
+\r
+Elles furent bientôt distraites par le mouvement du bord de l'eau\r
+qu'elles distinguaient dans les branches des grands arbres et qui les\r
+divertissait fort. C'était l'heure du départ des malles-poste et des\r
+diligences. Presque toutes les messageries du midi et de l'ouest\r
+passaient alors par les Champs-Élysées. La plupart suivaient le quai et\r
+sortaient par la barrière de Passy. De minute en minute, quelque grosse\r
+voiture peinte en jaune et en noir, pesamment chargée, bruyamment\r
+attelée, difforme à force de malles, de bâches et de valises, pleine de\r
+têtes tout de suite disparues, broyant la chaussée, changeant tous les\r
+pavés en briquets, se ruait à travers la foule avec toutes les\r
+étincelles d'une forge, de la poussière pour fumée, et un air de furie.\r
+Ce vacarme réjouissait les jeunes filles. Favourite s'exclamait:\r
+\r
+--Quel tapage! on dirait des tas de chaînes qui s'envolent.\r
+\r
+Il arriva une fois qu'une de ces voitures qu'on distinguait\r
+difficilement dans l'épaisseur des ormes, s'arrêta un moment, puis\r
+repartit au galop. Cela étonna Fantine.\r
+\r
+--C'est particulier! dit-elle. Je croyais que la diligence ne s'arrêtait\r
+jamais. Favourite haussa les épaules.\r
+\r
+--Cette Fantine est surprenante. Je viens la voir par curiosité. Elle\r
+s'éblouit des choses les plus simples. Une supposition; je suis un\r
+voyageur, je dis à la diligence: je vais en avant, vous me prendrez sur\r
+le quai en passant. La diligence passe, me voit, s'arrête, et me prend.\r
+Cela se fait tous les jours. Tu ne connais pas la vie, ma chère.\r
+\r
+Un certain temps s'écoula ainsi. Tout à coup Favourite eut le mouvement\r
+de quelqu'un qui se réveille.\r
+\r
+--Eh bien, fit-elle, et la surprise?\r
+\r
+--À propos, oui, reprit Dahlia, la fameuse surprise?\r
+\r
+--Ils sont bien longtemps! dit Fantine.\r
+\r
+Comme Fantine achevait ce soupir, le garçon qui avait servi le dîner\r
+entra. Il tenait à la main quelque chose qui ressemblait à une lettre.\r
+\r
+--Qu'est-ce que cela? demanda Favourite.\r
+\r
+Le garçon répondit:\r
+\r
+--C'est un papier que ces messieurs ont laissé pour ces dames.\r
+\r
+--Pourquoi ne l'avoir pas apporté tout de suite?\r
+\r
+--Parce que ces messieurs, reprit le garçon, ont commandé de ne le\r
+remettre à ces dames qu'au bout d'une heure.\r
+\r
+Favourite arracha le papier des mains du garçon. C'était une lettre en\r
+effet.\r
+\r
+--Tiens! dit-elle. Il n'y a pas d'adresse. Mais voici ce qui est écrit\r
+dessus:\r
+\r
+Ceci est la surprise.\r
+\r
+Elle décacheta vivement la lettre, l'ouvrit et lut (elle savait lire):\r
+\r
+«Ô nos amantes!\r
+\r
+«Sachez que nous avons des parents. Des parents, vous ne connaissez pas\r
+beaucoup ça. Ça s'appelle des pères et mères dans le code civil, puéril\r
+et honnête. Or, ces parents gémissent, ces vieillards nous réclament,\r
+ces bons hommes et ces bonnes femmes nous appellent enfants prodigues,\r
+ils souhaitent nos retours, et nous offrent de tuer des veaux. Nous leur\r
+obéissons, étant vertueux. À l'heure où vous lirez ceci, cinq chevaux\r
+fougueux nous rapporteront à nos papas et à nos mamans. Nous fichons le\r
+camp, comme dit Bossuet. Nous partons, nous sommes partis. Nous fuyons\r
+dans les bras de Laffitte et sur les ailes de Caillard. La diligence de\r
+Toulouse nous arrache à l'abîme, et l'abîme c'est vous, ô nos belles\r
+petites! Nous rentrons dans la société, dans le devoir et dans l'ordre,\r
+au grand trot, à raison de trois lieues à l'heure. Il importe à la\r
+patrie que nous soyons, comme tout le monde, préfets, pères de famille,\r
+gardes champêtres et conseillers d'État. Vénérez-nous. Nous nous\r
+sacrifions. Pleurez-nous rapidement et remplacez-nous vite. Si cette\r
+lettre vous déchire, rendez-le-lui. Adieu.\r
+\r
+«Pendant près de deux ans, nous vous avons rendues heureuses. Ne nous en\r
+gardez pas rancune.\r
+\r
+«Signé: Blachevelle.\r
+\r
+«Fameuil.\r
+\r
+«Listolier.\r
+\r
+«Félix Tholomyès\r
+\r
+«Post-scriptum. Le dîner est payé.»\r
+\r
+Les quatre jeunes filles se regardèrent.\r
+\r
+Favourite rompit la première le silence.\r
+\r
+--Eh bien! s'écria-t-elle, c'est tout de même une bonne farce.\r
+\r
+--C'est très drôle, dit Zéphine.\r
+\r
+--Ce doit être Blachevelle qui a eu cette idée-là, reprit Favourite. Ça\r
+me rend amoureuse de lui. Sitôt parti, sitôt aimé. Voilà l'histoire.\r
+\r
+--Non, dit Dahlia, c'est une idée à Tholomyès. Ça se reconnaît.\r
+\r
+--En ce cas, reprit Favourite, mort à Blachevelle et vive Tholomyès!\r
+\r
+--Vive Tholomyès! crièrent Dahlia et Zéphine.\r
+\r
+Et elles éclatèrent de rire.\r
+\r
+Fantine rit comme les autres.\r
+\r
+Une heure après, quand elle fut rentrée dans sa chambre, elle pleura.\r
+C'était, nous l'avons dit, son premier amour; elle s'était donnée à ce\r
+Tholomyès comme à un mari, et la pauvre fille avait un enfant.\r
+\r
+\r
+\r
+\r
+Livre quatrième--Confier, c'est quelquefois livrer\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+Une mère qui en rencontre une autre\r
+\r
+\r
+Il y avait, dans le premier quart de ce siècle, à Montfermeil, près de\r
+Paris, une façon de gargote qui n'existe plus aujourd'hui. Cette gargote\r
+était tenue par des gens appelés Thénardier, mari et femme. Elle était\r
+située dans la ruelle du Boulanger. On voyait au-dessus de la porte une\r
+planche clouée à plat sur le mur. Sur cette planche était peint quelque\r
+chose qui ressemblait à un homme portant sur son dos un autre homme,\r
+lequel avait de grosses épaulettes de général dorées avec de larges\r
+étoiles argentées; des taches rouges figuraient du sang; le reste du\r
+tableau était de la fumée et représentait probablement une bataille. Au\r
+bas on lisait cette inscription: _Au Sergent de Waterloo._\r
+\r
+Rien n'est plus ordinaire qu'un tombereau ou une charrette à la porte\r
+d'une auberge. Cependant le véhicule ou, pour mieux dire, le fragment de\r
+véhicule qui encombrait la rue devant la gargote du Sergent de Waterloo,\r
+un soir du printemps de 1818, eût certainement attiré par sa masse\r
+l'attention d'un peintre qui eût passé là.\r
+\r
+C'était l'avant-train d'un de ces fardiers, usités dans les pays de\r
+forêts, et qui servent à charrier des madriers et des troncs d'arbres.\r
+Cet avant-train se composait d'un massif essieu de fer à pivot où\r
+s'emboîtait un lourd timon, et que supportaient deux roues démesurées.\r
+Tout cet ensemble était trapu, écrasant et difforme. On eût dit l'affût\r
+d'un canon géant. Les ornières avaient donné aux roues, aux jantes, aux\r
+moyeux, à l'essieu et au timon, une couche de vase, hideux badigeonnage\r
+jaunâtre assez semblable à celui dont on orne volontiers les\r
+cathédrales. Le bois disparaissait sous la boue et le fer sous la\r
+rouille. Sous l'essieu pendait en draperie une grosse chaîne digne de\r
+Goliath forçat. Cette chaîne faisait songer, non aux poutres qu'elle\r
+avait fonction de transporter, mais aux mastodontes et aux mammons\r
+qu'elle eût pu atteler; elle avait un air de bagne, mais de bagne\r
+cyclopéen et surhumain, et elle semblait détachée de quelque monstre.\r
+Homère y eût lié Polyphème et Shakespeare Caliban.\r
+\r
+Pourquoi cet avant-train de fardier était-il à cette place dans la rue?\r
+D'abord, pour encombrer la rue; ensuite pour achever de se rouiller. Il\r
+y a dans le vieil ordre social une foule d'institutions qu'on trouve de\r
+la sorte sur son passage en plein air et qui n'ont pas pour être là\r
+d'autres raisons.\r
+\r
+Le centre de la chaîne pendait sous l'essieu assez près de terre, et sur\r
+la courbure, comme sur la corde d'une balançoire, étaient assises et\r
+groupées, ce soir-là, dans un entrelacement exquis, deux petites filles,\r
+l'une d'environ deux ans et demi, l'autre de dix-huit mois, la plus\r
+petite dans les bras de la plus grande. Un mouchoir savamment noué les\r
+empêchait de tomber. Une mère avait vu cette effroyable chaîne, et avait\r
+dit: Tiens! voilà un joujou pour mes enfants.\r
+\r
+Les deux enfants, du reste gracieusement attifées, et avec quelque\r
+recherche, rayonnaient; on eût dit deux roses dans de la ferraille;\r
+leurs yeux étaient un triomphe; leurs fraîches joues riaient. L'une\r
+était châtain, l'autre était brune. Leurs naïfs visages étaient deux\r
+étonnements ravis; un buisson fleuri qui était près de là envoyait aux\r
+passants des parfums qui semblaient venir d'elles; celle de dix-huit\r
+mois montrait son gentil ventre nu avec cette chaste indécence de la\r
+petitesse.\r
+\r
+Au-dessus et autour de ces deux têtes délicates, pétries dans le bonheur\r
+et trempées dans la lumière, le gigantesque avant-train, noir de\r
+rouille, presque terrible, tout enchevêtré de courbes et d'angles\r
+farouches, s'arrondissait comme un porche de caverne. À quelques pas,\r
+accroupie sur le seuil de l'auberge, la mère, femme d'un aspect peu\r
+avenant du reste, mais touchante en ce moment-là, balançait les deux\r
+enfants au moyen d'une longue ficelle, les couvant des yeux de peur\r
+d'accident avec cette expression animale et céleste propre à la\r
+maternité; à chaque va-et-vient, les hideux anneaux jetaient un bruit\r
+strident qui ressemblait à un cri de colère; les petites filles\r
+s'extasiaient, le soleil couchant se mêlait à cette joie, et rien\r
+n'était charmant comme ce caprice du hasard, qui avait fait d'une chaîne\r
+de titans une escarpolette de chérubins.\r
+\r
+Tout en berçant ses deux petites, la mère chantonnait d'une voix fausse\r
+une romance alors célèbre:\r
+\r
+          _Il le faut, disait un guerrier._\r
+\r
+Sa chanson et la contemplation de ses filles l'empêchaient d'entendre et\r
+de voir ce qui se passait dans la rue.\r
+\r
+Cependant quelqu'un s'était approché d'elle, comme elle commençait le\r
+premier couplet de la romance, et tout à coup elle entendit une voix qui\r
+disait très près de son oreille:\r
+\r
+--Vous avez là deux jolis enfants, madame, répondit la mère, continuant\r
+sa romance:\r
+\r
+          _À la belle et tendre Imogine._\r
+\r
+répondit la mère, continuant sa romance, puis elle tourna la tête.\r
+\r
+Une femme était devant elle, à quelques pas. Cette femme, elle aussi,\r
+avait un enfant qu'elle portait dans ses bras.\r
+\r
+Elle portait en outre un assez gros sac de nuit qui semblait fort lourd.\r
+\r
+L'enfant de cette femme était un des plus divins êtres qu'on pût voir.\r
+C'était une fille de deux à trois ans. Elle eût pu jouter avec les deux\r
+autres pour la coquetterie de l'ajustement; elle avait un bavolet de\r
+linge fin, des rubans à sa brassière et de la valenciennes à son bonnet.\r
+Le pli de sa jupe relevée laissait voir sa cuisse blanche, potelée et\r
+ferme. Elle était admirablement rose et bien portante. La belle petite\r
+donnait envie de mordre dans les pommes de ses joues. On ne pouvait rien\r
+dire de ses yeux, sinon qu'ils devaient être très grands et qu'ils\r
+avaient des cils magnifiques. Elle dormait.\r
+\r
+Elle dormait de ce sommeil d'absolue confiance propre à son âge. Les\r
+bras des mères sont faits de tendresse; les enfants y dorment\r
+profondément.\r
+\r
+Quant à la mère, l'aspect en était pauvre et triste. Elle avait la mise\r
+d'une ouvrière qui tend à redevenir paysanne. Elle était jeune.\r
+Était-elle belle? peut-être; mais avec cette mise il n'y paraissait pas.\r
+Ses cheveux, d'où s'échappait une mèche blonde, semblaient fort épais,\r
+mais disparaissaient sévèrement sous une coiffe de béguine, laide,\r
+serrée, étroite, et nouée au menton. Le rire montre les belles dents\r
+quand on en a; mais elle ne riait point. Ses yeux ne semblaient pas être\r
+secs depuis très longtemps. Elle était pâle; elle avait l'air très lasse\r
+et un peu malade; elle regardait sa fille endormie dans ses bras avec\r
+cet air particulier d'une mère qui a nourri son enfant. Un large\r
+mouchoir bleu, comme ceux où se mouchent les invalides, plié en fichu,\r
+masquait lourdement sa taille. Elle avait les mains hâlées et toutes\r
+piquées de taches de rousseur, l'index durci et déchiqueté par\r
+l'aiguille, une Mante brune de laine bourrue, une robe de toile et de\r
+gros souliers. C'était Fantine.\r
+\r
+C'était Fantine. Difficile à reconnaître. Pourtant, à l'examiner\r
+attentivement, elle avait toujours sa beauté. Un pli triste, qui\r
+ressemblait à un commencement d'ironie, ridait sa joue droite. Quant à\r
+sa toilette, cette aérienne toilette de mousseline et de rubans qui\r
+semblait faite avec de la gaîté, de la folie et de la musique, pleine de\r
+grelots et parfumée de lilas, elle s'était évanouie comme ces beaux\r
+givres éclatants qu'on prend pour des diamants au soleil; ils fondent et\r
+laissent la branche toute noire.\r
+\r
+Dix mois s'étaient écoulés depuis «la bonne farce».\r
+\r
+Que s'était-il passé pendant ces dix mois? on le devine.\r
+\r
+Après l'abandon, la gêne. Fantine avait tout de suite perdu de vue\r
+Favourite, Zéphine et Dahlia; le lien, brisé du côté des hommes, s'était\r
+défait du côté des femmes; on les eût bien étonnées, quinze jours après,\r
+si on leur eût dit qu'elles étaient amies; cela n'avait plus de raison\r
+d'être. Fantine était restée seule. Le père de son enfant parti,--hélas!\r
+ces ruptures-là sont irrévocables,--elle se trouva absolument isolée,\r
+avec l'habitude du travail de moins et le goût du plaisir de plus.\r
+Entraînée par sa liaison avec Tholomyès à dédaigner le petit métier\r
+qu'elle savait, elle avait négligé ses débouchés; ils s'étaient fermés.\r
+Nulle ressource. Fantine savait à peine lire et ne savait pas écrire; on\r
+lui avait seulement appris dans son enfance à signer son nom; elle avait\r
+fait écrire par un écrivain public une lettre à Tholomyès, puis une\r
+seconde, puis une troisième. Tholomyès n'avait répondu à aucune. Un\r
+jour, Fantine entendit des commères dire en regardant sa fille:\r
+\r
+--Est-ce qu'on prend ces enfants-là au sérieux? on hausse les épaules de\r
+ces enfants-là!\r
+\r
+Alors elle songea à Tholomyès qui haussait les épaules de son enfant et\r
+qui ne prenait pas cet être innocent au sérieux; et son coeur devint\r
+sombre à l'endroit de cet homme. Quel parti prendre pourtant? Elle ne\r
+savait plus à qui s'adresser. Elle avait commis une faute, mais le fond\r
+de sa nature, on s'en souvient, était pudeur et vertu. Elle sentit\r
+vaguement qu'elle était à la veille de tomber dans la détresse, et de\r
+glisser dans le pire. Il fallait du courage; elle en eut, et se roidit.\r
+L'idée lui vint de retourner dans sa ville natale, à Montreuil-sur-mer.\r
+Là quelqu'un peut-être la connaîtrait et lui donnerait du travail. Oui;\r
+mais il faudrait cacher sa faute. Et elle entrevoyait confusément la\r
+nécessité possible d'une séparation plus douloureuse encore que la\r
+première. Son coeur se serra, mais elle prit sa résolution. Fantine, on\r
+le verra, avait la farouche bravoure de la vie.\r
+\r
+Elle avait déjà vaillamment renoncé à la parure, s'était vêtue de toile,\r
+et avait mis toute sa soie, tous ses chiffons, tous ses rubans et toutes\r
+ses dentelles sur sa fille, seule vanité qui lui restât, et sainte\r
+celle-là. Elle vendit tout ce qu'elle avait, ce qui lui produisit deux\r
+cents francs; ses petites dettes payées, elle n'eut plus que\r
+quatre-vingts francs environ. À vingt-deux ans, par une belle matinée de\r
+printemps, elle quittait Paris, emportant son enfant sur son dos.\r
+Quelqu'un qui les eût vues passer toutes les deux eût pitié. Cette femme\r
+n'avait au monde que cet enfant, et cet enfant n'avait au monde que\r
+cette femme. Fantine avait nourri sa fille; cela lui avait fatigué la\r
+poitrine, et elle toussait un peu.\r
+\r
+Nous n'aurons plus occasion de parler de M. Félix Tholomyès.\r
+Bornons-nous à dire que, vingt ans plus tard, sous le roi\r
+Louis-Philippe, c'était un gros avoué de province, influent et riche,\r
+électeur sage et juré très sévère; toujours homme de plaisir.\r
+\r
+Vers le milieu du jour, après avoir, pour se reposer, cheminé de temps\r
+en temps, moyennant trois ou quatre sous par lieue, dans ce qu'on\r
+appelait alors les Petites Voitures des Environs de Paris, Fantine se\r
+trouvait à Montfermeil, dans la ruelle du Boulanger.\r
+\r
+Comme elle passait devant l'auberge Thénardier, les deux petites filles,\r
+enchantées sur leur escarpolette monstre, avaient été pour elle une\r
+sorte d'éblouissement, et elle s'était arrêtée devant cette vision de\r
+joie.\r
+\r
+Il y a des charmes. Ces deux petites filles en furent un pour cette\r
+mère.\r
+\r
+Elle les considérait, toute émue. La présence des anges est une annonce\r
+de paradis. Elle crut voir au dessus de cette auberge le mystérieux ICI\r
+de la providence. Ces deux petites étaient si évidemment heureuses! Elle\r
+les regardait, elle les admirait, tellement attendrie qu'au moment où la\r
+mère reprenait haleine entre deux vers de sa chanson, elle ne put\r
+s'empêcher de lui dire ce mot qu'on vient de lire:\r
+\r
+--Vous avez là deux jolis enfants, madame.\r
+\r
+Les créatures les plus féroces sont désarmées par la caresse à leurs\r
+petits. La mère leva la tête et remercia, et fit asseoir la passante sur\r
+le banc de la porte, elle-même étant sur le seuil. Les deux femmes\r
+causèrent.\r
+\r
+--Je m'appelle madame Thénardier, dit la mère des deux petites. Nous\r
+tenons cette auberge.\r
+\r
+Puis, toujours à sa romance, elle reprit entre ses dents:\r
+\r
+          _Il le faut, je suis chevalier,_\r
+          _Et je pars pour la Palestine._\r
+\r
+Cette madame Thénardier était une femme rousse, charnue, anguleuse; le\r
+type femme-à-soldat dans toute sa disgrâce. Et, chose bizarre, avec un\r
+air penché qu'elle devait à des lectures romanesques. C'était une\r
+minaudière hommasse. De vieux romans qui se sont éraillés sur des\r
+imaginations de gargotières ont de ces effets-là. Elle était jeune\r
+encore; elle avait à peine trente ans. Si cette femme, qui était\r
+accroupie, se fût tenue droite, peut-être sa haute taille et sa carrure\r
+de colosse ambulant propre aux foires, eussent-elles dès l'abord\r
+effarouché la voyageuse, troublé sa confiance, et fait évanouir ce que\r
+nous avons à raconter. Une personne qui est assise au lieu d'être\r
+debout, les destinées tiennent à cela.\r
+\r
+La voyageuse raconta son histoire, un peu modifiée:\r
+\r
+Qu'elle était ouvrière; que son mari était mort; que le travail lui\r
+manquait à Paris, et qu'elle allait en chercher ailleurs; dans son pays;\r
+qu'elle avait quitté Paris, le matin même, à pied; que, comme elle\r
+portait son enfant, se sentant fatiguée, et ayant rencontré la voiture\r
+de Villemomble, elle y était montée; que de Villemomble elle était venue\r
+à Montfermeil à pied, que la petite avait un peu marché, mais pas\r
+beaucoup, c'est si jeune, et qu'il avait fallu la prendre, et que le\r
+bijou s'était endormi.\r
+\r
+Et sur ce mot elle donna à sa fille un baiser passionné qui la réveilla.\r
+L'enfant ouvrit les yeux, de grands yeux bleus comme ceux de sa mère, et\r
+regarda, quoi? rien, tout, avec cet air sérieux et quelquefois sévère\r
+des petits enfants, qui est un mystère de leur lumineuse innocence\r
+devant nos crépuscules de vertus. On dirait qu'ils se sentent anges et\r
+qu'ils nous savent hommes. Puis l'enfant se mit à rire, et, quoique la\r
+mère la retint, glissa à terre avec l'indomptable énergie d'un petit\r
+être qui veut courir. Tout à coup elle aperçut les deux autres sur leur\r
+balançoire, s'arrêta court, et tira la langue, signe d'admiration.\r
+\r
+La mère Thénardier détacha ses filles, les fit descendre de\r
+l'escarpolette, et dit:\r
+\r
+--Amusez-vous toutes les trois.\r
+\r
+Ces âges-là s'apprivoisent vite, et au bout d'une minute les petites\r
+Thénardier jouaient avec la nouvelle venue à faire des trous dans la\r
+terre, plaisir immense.\r
+\r
+Cette nouvelle venue était très gaie; la bonté de la mère est écrite\r
+dans la gaîté du marmot; elle avait pris un brin de bois qui lui servait\r
+de pelle, et elle creusait énergiquement une fosse bonne pour une\r
+mouche. Ce que fait le fossoyeur devient riant, fait par l'enfant.\r
+\r
+Les deux femmes continuaient de causer.\r
+\r
+--Comment s'appelle votre mioche?\r
+\r
+--Cosette.\r
+\r
+Cosette, lisez Euphrasie. La petite se nommait Euphrasie. Mais\r
+d'Euphrasie la mère avait fait Cosette, par ce doux et gracieux instinct\r
+des mères et du peuple qui change Josefa en Pepita et Françoise en\r
+Sillette. C'est là un genre de dérivés qui dérange et déconcerte toute\r
+la science des étymologistes. Nous avons connu une grand'mère qui avait\r
+réussi à faire de Théodore, Gnon.\r
+\r
+--Quel âge a-t-elle?\r
+\r
+--Elle va sur trois ans.\r
+\r
+--C'est comme mon aînée.\r
+\r
+Cependant les trois petites filles étaient groupées dans une posture\r
+d'anxiété profonde et de béatitude; un événement avait lieu; un gros ver\r
+venait de sortir de terre; et elles avaient peur, et elles étaient en\r
+extase.\r
+\r
+Leurs fronts radieux se touchaient; on eût dit trois têtes dans une\r
+auréole.\r
+\r
+--Les enfants, s'écria la mère Thénardier, comme ça se connaît tout de\r
+suite! les voilà qu'on jurerait trois soeurs!\r
+\r
+Ce mot fut l'étincelle qu'attendait probablement l'autre mère. Elle\r
+saisit la main de la Thénardier, la regarda fixement, et lui dit:\r
+\r
+--Voulez-vous me garder mon enfant?\r
+\r
+La Thénardier eut un de ces mouvements surpris qui ne sont ni le\r
+consentement ni le refus.\r
+\r
+La mère de Cosette poursuivit:\r
+\r
+--Voyez-vous, je ne peux pas emmener ma fille au pays. L'ouvrage ne le\r
+permet pas. Avec un enfant, on ne trouve pas à se placer. Ils sont si\r
+ridicules dans ce pays-là. C'est le bon Dieu qui m'a fait passer devant\r
+votre auberge. Quand j'ai vu vos petites si jolies et si propres et si\r
+contentes, cela m'a bouleversée. J'ai dit: voilà une bonne mère. C'est\r
+ça; ça fera trois soeurs. Et puis, je ne serai pas longtemps à revenir.\r
+Voulez-vous me garder mon enfant?\r
+\r
+--Il faudrait voir, dit la Thénardier.\r
+\r
+--Je donnerais six francs par mois.\r
+\r
+Ici une voix d'homme cria du fond de la gargote:\r
+\r
+--Pas à moins de sept francs. Et six mois payés d'avance.\r
+\r
+--Six fois sept quarante-deux, dit la Thénardier.\r
+\r
+--Je les donnerai, dit la mère.\r
+\r
+--Et quinze francs en dehors pour les premiers frais, ajouta la voix\r
+d'homme.\r
+\r
+--Total cinquante-sept francs, dit la madame Thénardier. Et à travers\r
+ces chiffres, elle chantonnait vaguement:\r
+\r
+_Il le faut, disait un guerrier._\r
+\r
+--Je les donnerai, dit la mère, j'ai quatre-vingts francs. Il me restera\r
+de quoi aller au pays. En allant à pied. Je gagnerai de l'argent là-bas,\r
+et dès que j'en aurai un peu, je reviendrai chercher l'amour.\r
+\r
+La voix d'homme reprit:\r
+\r
+--La petite a un trousseau?\r
+\r
+--C'est mon mari, dit la Thénardier.\r
+\r
+--Sans doute elle a un trousseau, le pauvre trésor. J'ai bien vu que\r
+c'était votre mari. Et un beau trousseau encore! un trousseau insensé.\r
+Tout par douzaines; et des robes de soie comme une dame. Il est là dans\r
+mon sac de nuit.\r
+\r
+--Il faudra le donner, repartit la voix d'homme.\r
+\r
+--Je crois bien que je le donnerai! dit la mère. Ce serait cela qui\r
+serait drôle si je laissais ma fille toute nue!\r
+\r
+La face du maître apparut.\r
+\r
+--C'est bon, dit-il.\r
+\r
+Le marché fut conclu. La mère passa la nuit à l'auberge, donna son\r
+argent et laissa son enfant, renoua son sac de nuit dégonflé du\r
+trousseau et léger désormais, et partit le lendemain matin, comptant\r
+revenir bientôt. On arrange tranquillement ces départs-là, mais ce sont\r
+des désespoirs.\r
+\r
+Une voisine des Thénardier rencontra cette mère comme elle s'en allait,\r
+et s'en revint en disant:\r
+\r
+--Je viens de voir une femme qui pleure dans la rue, que c'est un\r
+déchirement.\r
+\r
+Quand la mère de Cosette fut partie, l'homme dit à la femme:\r
+\r
+--Cela va me payer mon effet de cent dix francs qui échoit demain. Il me\r
+manquait cinquante francs. Sais-tu que j'aurais eu l'huissier et un\r
+protêt? Tu as fait là une bonne souricière avec tes petites.\r
+\r
+--Sans m'en douter, dit la femme.\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+Première esquisse de deux figures louches\r
+\r
+\r
+La souris prise était bien chétive; mais le chat se réjouit même d'une\r
+souris maigre. Qu'était-ce que les Thénardier?\r
+\r
+Disons-en un mot dès à présent. Nous compléterons le croquis plus tard.\r
+\r
+Ces êtres appartenaient à cette classe bâtarde composée de gens\r
+grossiers parvenus et de gens intelligents déchus, qui est entre la\r
+classe dite moyenne et la classe dite inférieure, et qui combine\r
+quelques-uns des défauts de la seconde avec presque tous les vices de la\r
+première, sans avoir le généreux élan de l'ouvrier ni l'ordre honnête du\r
+bourgeois.\r
+\r
+C'étaient de ces natures naines qui, si quelque feu sombre les chauffe\r
+par hasard, deviennent facilement monstrueuses. Il y avait dans la femme\r
+le fond d'une brute et dans l'homme l'étoffe d'un gueux. Tous deux\r
+étaient au plus haut degré susceptibles de l'espèce de hideux progrès\r
+qui se fait dans le sens du mal. Il existe des âmes écrevisses reculant\r
+continuellement vers les ténèbres, rétrogradant dans la vie plutôt\r
+qu'elles n'y avancent, employant l'expérience à augmenter leur\r
+difformité, empirant sans cesse, et s'empreignant de plus en plus d'une\r
+noirceur croissante. Cet homme et cette femme étaient de ces âmes-là.\r
+\r
+Le Thénardier particulièrement était gênant pour le physionomiste. On\r
+n'a qu'à regarder certains hommes pour s'en défier, on les sent\r
+ténébreux à leurs deux extrémités. Ils sont inquiets derrière eux et\r
+menaçants devant eux. Il y a en eux de l'inconnu. On ne peut pas plus\r
+répondre de ce qu'ils ont fait que de ce qu'ils feront. L'ombre qu'ils\r
+ont dans le regard les dénonce. Rien qu'en les entendant dire un mot ou\r
+qu'en les voyant faire un geste on entrevoit de sombres secrets dans\r
+leur passé et de sombres mystères dans leur avenir.\r
+\r
+Ce Thénardier, s'il fallait l'en croire, avait été soldat; sergent,\r
+disait-il; il avait fait probablement la campagne de 1815, et s'était\r
+même comporté assez bravement, à ce qu'il paraît. Nous verrons plus tard\r
+ce qu'il en était. L'enseigne de son cabaret était une allusion à l'un\r
+de ses faits d'armes. Il l'avait peinte lui-même, car il savait faire un\r
+peu de tout; mal.\r
+\r
+C'était l'époque où l'antique roman classique, qui, après avoir été\r
+_Clélie_, n'était plus que _Lodoïska_, toujours noble, mais de plus en\r
+plus vulgaire, tombé de mademoiselle de Scudéri à madame\r
+Barthélemy-Hadot, et de madame de Lafayette à madame Bournon-Malarme,\r
+incendiait l'âme aimante des portières de Paris et ravageait même un peu\r
+la banlieue. Madame Thénardier était juste assez intelligente pour lire\r
+ces espèces de livres. Elle s'en nourrissait. Elle y noyait ce qu'elle\r
+avait de cervelle; cela lui avait donné, tant qu'elle avait été très\r
+jeune, et même un peu plus tard, une sorte d'attitude pensive près de\r
+son mari, coquin d'une certaine profondeur, ruffian lettré à la\r
+grammaire près, grossier et fin en même temps, mais, en fait de\r
+sentimentalisme, lisant Pigault-Lebrun, et pour «tout ce qui touche le\r
+sexe», comme il disait dans son jargon, butor correct et sans mélange.\r
+Sa femme avait quelque douze ou quinze ans de moins que lui. Plus tard,\r
+quand les cheveux romanesquement pleureurs commencèrent à grisonner,\r
+quand la Mégère se dégagea de la Paméla, la Thénardier ne fut plus\r
+qu'une grosse méchante femme ayant savouré des romans bêtes. Or on ne\r
+lit pas impunément des niaiseries. Il en résulta que sa fille aînée se\r
+nomma Eponine. Quant à la cadette, la pauvre petite faillit se nommer\r
+Gulnare; elle dut à je ne sais quelle heureuse diversion faite par un\r
+roman de Ducray-Duminil, de ne s'appeler qu'Azelma.\r
+\r
+Au reste, pour le dire en passant, tout n'est pas ridicule et\r
+superficiel dans cette curieuse époque à laquelle nous faisons ici\r
+allusion, et qu'on pourrait appeler l'anarchie des noms de baptême. À\r
+côté de l'élément romanesque, que nous venons d'indiquer, il y a le\r
+symptôme social. Il n'est pas rare aujourd'hui que le garçon bouvier se\r
+nomme Arthur, Alfred ou Alphonse, et que le vicomte--s'il y a encore des\r
+vicomtes--se nomme Thomas, Pierre ou Jacques. Ce déplacement qui met le\r
+nom «élégant» sur le plébéien et le nom campagnard sur l'aristocrate\r
+n'est autre chose qu'un remous d'égalité. L'irrésistible pénétration du\r
+souffle nouveau est là comme en tout. Sous cette discordance apparente,\r
+il y a une chose grande et profonde: la révolution française.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+L'Alouette\r
+\r
+\r
+Il ne suffit pas d'être méchant pour prospérer. La gargote allait mal.\r
+\r
+Grâce aux cinquante-sept francs de la voyageuse, Thénardier avait pu\r
+éviter un protêt et faire honneur à sa signature. Le mois suivant ils\r
+eurent encore besoin d'argent; la femme porta à Paris et engagea au\r
+Mont-de-Piété le trousseau de Cosette pour une somme de soixante francs.\r
+Dès que cette somme fut dépensée, les Thénardier s'accoutumèrent à ne\r
+plus voir dans la petite fille qu'un enfant qu'ils avaient chez eux par\r
+charité, et la traitèrent en conséquence. Comme elle n'avait plus de\r
+trousseau, on l'habilla des vieilles jupes et des vieilles chemises des\r
+petites Thénardier, c'est-à-dire de haillons.\r
+\r
+On la nourrit des restes de tout le monde, un peu mieux que le chien et\r
+un peu plus mal que le chat. Le chat et le chien étaient du reste ses\r
+commensaux habituels; Cosette mangeait avec eux sous la table dans une\r
+écuelle de bois pareille à la leur. La mère qui s'était fixée, comme on\r
+le verra plus tard, à Montreuil-sur-mer, écrivait, ou, pour mieux dire,\r
+faisait écrire tous les mois afin d'avoir des nouvelles de son enfant.\r
+Les Thénardier répondaient invariablement: Cosette est à merveille. Les\r
+six premiers mois révolus, la mère envoya sept francs pour le septième\r
+mois, et continua assez exactement ses envois de mois en mois. L'année\r
+n'était pas finie que le Thénardier dit:\r
+\r
+--Une belle grâce qu'elle nous fait là! que veut-elle que nous fassions\r
+avec ses sept francs?\r
+\r
+Et il écrivit pour exiger douze francs. La mère, à laquelle ils\r
+persuadaient que son enfant était heureuse "et venait bien", se soumit\r
+et envoya les douze francs.\r
+\r
+Certaines natures ne peuvent aimer d'un côté sans haïr de l'autre. La\r
+mère Thénardier aimait passionnément ses deux filles à elle, ce qui fit\r
+qu'elle détesta l'étrangère. Il est triste de songer que l'amour d'une\r
+mère peut avoir de vilains aspects. Si peu de place que Cosette tînt\r
+chez elle, il lui semblait que cela était pris aux siens, et que cette\r
+petite diminuait l'air que ses filles respiraient. Cette femme, comme\r
+beaucoup de femmes de sa sorte, avait une somme de caresses et une somme\r
+de coups et d'injures à dépenser chaque jour. Si elle n'avait pas eu\r
+Cosette, il est certain que ses filles, tout idolâtrées qu'elles\r
+étaient, auraient tout reçu; mais l'étrangère leur rendit le service de\r
+détourner les coups sur elle. Ses filles n'eurent que les caresses.\r
+Cosette ne faisait pas un mouvement qui ne fît pleuvoir sur sa tête une\r
+grêle de châtiments violents et immérités. Doux être faible qui ne\r
+devait rien comprendre à ce monde ni à Dieu, sans cesse punie, grondée,\r
+rudoyée, battue et voyant à côté d'elle deux petites créatures comme\r
+elle, qui vivaient dans un rayon d'aurore!\r
+\r
+La Thénardier étant méchante pour Cosette, Éponine et Azelma furent\r
+méchantes. Les enfants, à cet âge, ne sont que des exemplaires de la\r
+mère. Le format est plus petit, voilà tout.\r
+\r
+Une année s'écoula, puis une autre.\r
+\r
+On disait dans le village:\r
+\r
+--Ces Thénardier sont de braves gens. Ils ne sont pas riches, et ils\r
+élèvent un pauvre enfant qu'on leur a abandonné chez eux!\r
+\r
+On croyait Cosette oubliée par sa mère.\r
+\r
+Cependant le Thénardier, ayant appris par on ne sait quelles voies\r
+obscures que l'enfant était probablement bâtard et que la mère ne\r
+pouvait l'avouer, exigea quinze francs par mois, disant que «la\r
+créature» grandissait et «_mangeait_», et menaçant de la renvoyer.\r
+«Quelle ne m'embête pas! s'écriait-il, je lui bombarde son mioche tout\r
+au beau milieu de ses cachotteries. Il me faut de l'augmentation.» La\r
+mère paya les quinze francs.\r
+\r
+D'année en année, l'enfant grandit, et sa misère aussi.\r
+\r
+Tant que Cosette fut toute petite, elle fut le souffre-douleur des deux\r
+autres enfants; dès qu'elle se mit à se développer un peu, c'est-à-dire\r
+avant même qu'elle eût cinq ans, elle devint la servante de la maison.\r
+\r
+Cinq ans, dira-t-on, c'est invraisemblable. Hélas, c'est vrai. La\r
+souffrance sociale commence à tout âge.\r
+\r
+N'avons-nous pas vu, récemment, le procès d'un nommé Dumolard, orphelin\r
+devenu bandit, qui, dès l'âge de cinq ans, disent les documents\r
+officiels, étant seul au monde «travaillait pour vivre, et volait.»\r
+\r
+On fit faire à Cosette les commissions, balayer les chambres, la cour,\r
+la rue, laver la vaisselle, porter même des fardeaux. Les Thénardier se\r
+crurent d'autant plus autorisés à agir ainsi que la mère qui était\r
+toujours à Montreuil-sur-mer commença à mal payer. Quelques mois\r
+restèrent en souffrance.\r
+\r
+Si cette mère fût revenue à Montfermeil au bout de ces trois années,\r
+elle n'eût point reconnu son enfant. Cosette, si jolie et si fraîche à\r
+son arrivée dans cette maison, était maintenant maigre et blême. Elle\r
+avait je ne sais quelle allure inquiète. Sournoise! disaient les\r
+Thénardier.\r
+\r
+L'injustice l'avait faite hargneuse et la misère l'avait rendue laide.\r
+Il ne lui restait plus que ses beaux yeux qui faisaient peine, parce\r
+que, grands comme ils étaient, il semblait qu'on y vît une plus grande\r
+quantité de tristesse.\r
+\r
+C'était une chose navrante de voir, l'hiver, ce pauvre enfant, qui\r
+n'avait pas encore six ans, grelottant sous de vieilles loques de toile\r
+trouées, balayer la rue avant le jour avec un énorme balai dans ses\r
+petites mains rouges et une larme dans ses grands yeux.\r
+\r
+Dans le pays on l'appelait l'Alouette. Le peuple, qui aime les figures,\r
+s'était plu à nommer de ce nom ce petit être pas plus gros qu'un oiseau,\r
+tremblant, effarouché et frissonnant, éveillé le premier chaque matin\r
+dans la maison et dans le village, toujours dans la rue ou dans les\r
+champs avant l'aube. Seulement la pauvre Alouette ne chantait jamais.\r
+\r
+\r
+\r
+\r
+Livre cinquième--La descente\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+Histoire d'un progrès dans les verroteries noires\r
+\r
+\r
+Cette mère cependant qui, au dire des gens de Montfermeil, semblait\r
+avoir abandonné son enfant, que devenait-elle? où était-elle? que\r
+faisait-elle?\r
+\r
+Après avoir laissé sa petite Cosette aux Thénardier, elle avait continué\r
+son chemin et était arrivée à Montreuil-sur-mer.\r
+\r
+C'était, on se le rappelle, en 1818.\r
+\r
+Fantine avait quitté sa province depuis une dizaine d'années.\r
+Montreuil-sur-mer avait changé d'aspect. Tandis que Fantine descendait\r
+lentement de misère en misère, sa ville natale avait prospéré.\r
+\r
+Depuis deux ans environ, il s'y était accompli un de ces faits\r
+industriels qui sont les grands événements des petits pays.\r
+\r
+Ce détail importe, et nous croyons utile de le développer; nous dirions\r
+presque, de le souligner.\r
+\r
+De temps immémorial, Montreuil-sur-mer avait pour industrie spéciale\r
+l'imitation des jais anglais et des verroteries noires d'Allemagne.\r
+Cette industrie avait toujours végété, à cause de la cherté des matières\r
+premières qui réagissait sur la main-d'oeuvre. Au moment où Fantine\r
+revint à Montreuil-sur-mer, une transformation inouïe s'était opérée\r
+dans cette production des «articles noirs». Vers la fin de 1815, un\r
+homme, un inconnu, était venu s'établir dans la ville et avait eu l'idée\r
+de substituer, dans cette fabrication, la gomme laque à la résine et,\r
+pour les bracelets en particulier, les coulants en tôle simplement\r
+rapprochée aux coulants en tôle soudée. Ce tout petit changement avait\r
+été une révolution.\r
+\r
+Ce tout petit changement en effet avait prodigieusement réduit le prix\r
+de la matière première, ce qui avait permis, premièrement, d'élever le\r
+prix de la main-d'oeuvre, bienfait pour le pays; deuxièmement,\r
+d'améliorer la fabrication, avantage pour le consommateur;\r
+troisièmement, de vendre à meilleur marché tout en triplant le bénéfice,\r
+profit pour le manufacturier.\r
+\r
+Ainsi pour une idée trois résultats.\r
+\r
+En moins de trois ans, l'auteur de ce procédé était devenu riche, ce qui\r
+est bien, et avait tout fait riche autour de lui, ce qui est mieux. Il\r
+était étranger au département. De son origine, on ne savait rien; de ses\r
+commencements, peu de chose.\r
+\r
+On contait qu'il était venu dans la ville avec fort peu d'argent,\r
+quelques centaines de francs tout au plus.\r
+\r
+C'est de ce mince capital, mis au service d'une idée ingénieuse, fécondé\r
+par l'ordre et par la pensée, qu'il avait tiré sa fortune et la fortune\r
+de tout ce pays.\r
+\r
+À son arrivée à Montreuil-sur-mer, il n'avait que les vêtements, la\r
+tournure et le langage d'un ouvrier.\r
+\r
+Il paraît que, le jour même où il faisait obscurément son entrée dans la\r
+petite ville de Montreuil-sur-mer, à la tombée d'un soir de décembre, le\r
+sac au dos et le bâton d'épine à la main, un gros incendie venait\r
+d'éclater à la maison commune. Cet homme s'était jeté dans le feu, et\r
+avait sauvé, au péril de sa vie, deux enfants qui se trouvaient être\r
+ceux du capitaine de gendarmerie; ce qui fait qu'on n'avait pas songé à\r
+lui demander son passeport. Depuis lors, on avait su son nom. Il\r
+s'appelait le _père Madeleine_.\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+M. Madeleine\r
+\r
+\r
+C'était un homme d'environ cinquante ans, qui avait l'air préoccupé et\r
+qui était bon. Voilà tout ce qu'on en pouvait dire.\r
+\r
+Grâce aux progrès rapides de cette industrie qu'il avait si\r
+admirablement remaniée, Montreuil-sur-mer était devenu un centre\r
+d'affaires considérable. L'Espagne, qui consomme beaucoup de jais noir,\r
+y commandait chaque année des achats immenses. Montreuil-sur-mer, pour\r
+ce commerce, faisait presque concurrence à Londres et à Berlin. Les\r
+bénéfices du père Madeleine étaient tels que, dès la deuxième année, il\r
+avait pu bâtir une grande fabrique dans laquelle il y avait deux vastes\r
+ateliers, l'un pour les hommes, l'autre pour les femmes. Quiconque avait\r
+faim pouvait s'y présenter, et était sûr de trouver là de l'emploi et du\r
+pain. Le père Madeleine demandait aux hommes de la bonne volonté, aux\r
+femmes des moeurs pures, à tous de la probité. Il avait divisé les\r
+ateliers afin de séparer les sexes et que les filles et les femmes\r
+pussent rester sages. Sur ce point, il était inflexible. C'était le seul\r
+où il fût en quelque sorte intolérant. Il était d'autant plus fondé à\r
+cette sévérité que, Montreuil-sur-mer étant une ville de garnison, les\r
+occasions de corruption abondaient. Du reste sa venue avait été un\r
+bienfait, et sa présence était une providence. Avant l'arrivée du père\r
+Madeleine, tout languissait dans le pays; maintenant tout y vivait de la\r
+vie saine du travail. Une forte circulation échauffait tout et pénétrait\r
+partout. Le chômage et la misère étaient inconnus. Il n'y avait pas de\r
+poche si obscure où il n'y eût un peu d'argent, pas de logis si pauvre\r
+où il n'y eût un peu de joie.\r
+\r
+Le père Madeleine employait tout le monde. Il n'exigeait qu'une chose:\r
+soyez honnête homme! soyez honnête fille!\r
+\r
+Comme nous l'avons dit, au milieu de cette activité dont il était la\r
+cause et le pivot, le père Madeleine faisait sa fortune, mais, chose\r
+assez singulière dans un simple homme de commerce, il ne paraissait\r
+point que ce fût là son principal souci. Il semblait qu'il songeât\r
+beaucoup aux autres et peu à lui. En 1820, on lui connaissait une somme\r
+de six cent trente mille francs placée à son nom chez Laffitte; mais\r
+avant de se réserver ces six cent trente mille francs, il avait dépensé\r
+plus d'un million pour la ville et pour les pauvres.\r
+\r
+L'hôpital était mal doté; il y avait fondé dix lits. Montreuil-sur-mer\r
+est divisé en ville haute et ville basse. La ville basse, qu'il\r
+habitait, n'avait qu'une école, méchante masure qui tombait en ruine; il\r
+en avait construit deux, une pour les filles, l'autre pour les garçons.\r
+Il allouait de ses deniers aux deux instituteurs une indemnité double de\r
+leur maigre traitement officiel, et un jour, à quelqu'un qui s'en\r
+étonnait, il dit: «Les deux premiers fonctionnaires de l'état, c'est la\r
+nourrice et le maître d'école.» Il avait créé à ses frais une salle\r
+d'asile, chose alors presque inconnue en France, et une caisse de\r
+secours pour les ouvriers vieux et infirmes. Sa manufacture étant un\r
+centre, un nouveau quartier où il y avait bon nombre de familles\r
+indigentes avait rapidement surgi autour de lui; il y avait établi une\r
+pharmacie gratuite.\r
+\r
+Dans les premiers temps, quand on le vit commencer, les bonnes âmes\r
+dirent: C'est un gaillard qui veut s'enrichir. Quand on le vit enrichir\r
+le pays avant de s'enrichir lui-même, les mêmes bonnes âmes dirent:\r
+C'est un ambitieux. Cela semblait d'autant plus probable que cet homme\r
+était religieux, et même pratiquait dans une certaine mesure, chose fort\r
+bien vue à cette époque. Il allait régulièrement entendre une basse\r
+messe tous les dimanches. Le député local, qui flairait partout des\r
+concurrences, ne tarda pas à s'inquiéter de cette religion. Ce député,\r
+qui avait été membre du corps législatif de l'empire, partageait les\r
+idées religieuses d'un père de l'oratoire connu sous le nom de Fouché,\r
+duc d'Otrante, dont il avait été la créature et l'ami. À huis clos il\r
+riait de Dieu doucement. Mais quand il vit le riche manufacturier\r
+Madeleine aller à la basse messe de sept heures, il entrevit un candidat\r
+possible, et résolut de le dépasser; il prit un confesseur jésuite et\r
+alla à la grand'messe et à vêpres. L'ambition en ce temps-là était, dans\r
+l'acception directe du mot, une course au clocher. Les pauvres\r
+profitèrent de cette terreur comme le bon Dieu, car l'honorable député\r
+fonda aussi deux lits à l'hôpital; ce qui fit douze.\r
+\r
+Cependant en 1819 le bruit se répandit un matin dans la ville que, sur\r
+la présentation de M. le préfet, et en considération des services rendus\r
+au pays, le père Madeleine allait être nommé par le roi maire de\r
+Montreuil-sur-mer. Ceux qui avaient déclaré ce nouveau venu «un\r
+ambitieux», saisirent avec transport cette occasion que tous les hommes\r
+souhaitent de s'écrier: «Là! qu'est-ce que nous avions dit?» Tout\r
+Montreuil-sur-mer fut en rumeur. Le bruit était fondé. Quelques jours\r
+après, la nomination parut dans _le Moniteur_. Le lendemain, le père\r
+Madeleine refusa.\r
+\r
+Dans cette même année 1819, les produits du nouveau procédé inventé par\r
+Madeleine figurèrent à l'exposition de l'industrie; sur le rapport du\r
+jury, le roi nomma l'inventeur chevalier de la Légion d'honneur.\r
+Nouvelle rumeur dans la petite ville. Eh bien! c'est la croix qu'il\r
+voulait! Le père Madeleine refusa la croix.\r
+\r
+Décidément cet homme était une énigme. Les bonnes âmes se tirèrent\r
+d'affaire en disant: Après tout, c'est une espèce d'aventurier.\r
+\r
+On l'a vu, le pays lui devait beaucoup, les pauvres lui devaient tout;\r
+il était si utile qu'il avait bien fallu qu'on finît par l'honorer, et\r
+il était si doux qu'il avait bien fallu qu'on finît par l'aimer; ses\r
+ouvriers en particulier l'adoraient, et il portait cette adoration avec\r
+une sorte de gravité mélancolique. Quand il fut constaté riche, «les\r
+personnes de la société» le saluèrent, et on l'appela dans la ville\r
+monsieur Madeleine; ses ouvriers et les enfants continuèrent de\r
+l'appeler _le père Madeleine_, et c'était la chose qui le faisait le\r
+mieux sourire. À mesure qu'il montait, les invitations pleuvaient sur\r
+lui. «La société» le réclamait. Les petits salons guindés de\r
+Montreuil-sur-mer qui, bien entendu, se fussent dans les premiers temps\r
+fermés à l'artisan, s'ouvrirent à deux battants au millionnaire. On lui\r
+fit mille avances. Il refusa.\r
+\r
+Cette fois encore les bonnes âmes ne furent point empêchées.\r
+\r
+--C'est un homme ignorant et de basse éducation. On ne sait d'où cela\r
+sort. Il ne saurait pas se tenir dans le monde. Il n'est pas du tout\r
+prouvé qu'il sache lire.\r
+\r
+Quand on l'avait vu gagner de l'argent, on avait dit: c'est un marchand.\r
+Quand on l'avait vu semer son argent, on avait dit: c'est un ambitieux.\r
+Quand on l'avait vu repousser les honneurs, on avait dit: c'est un\r
+aventurier. Quand on le vit repousser le monde, on dit: c'est une brute.\r
+\r
+En 1820, cinq ans après son arrivée à Montreuil-sur-mer, les services\r
+qu'il avait rendus au pays étaient si éclatants, le voeu de la contrée\r
+fut tellement unanime, que le roi le nomma de nouveau maire de la ville.\r
+Il refusa encore, mais le préfet résista à son refus, tous les notables\r
+vinrent le prier, le peuple en pleine rue le suppliait, l'insistance fut\r
+si vive qu'il finit par accepter. On remarqua que ce qui parut surtout\r
+le déterminer, ce fut l'apostrophe presque irritée d'une vieille femme\r
+du peuple qui lui cria du seuil de sa porte avec humeur: _Un bon maire,\r
+c'est utile. Est-ce qu'on recule devant du bien qu'on peut faire?_\r
+\r
+Ce fut là la troisième phase de son ascension. Le père Madeleine était\r
+devenu monsieur Madeleine, monsieur Madeleine devint monsieur le maire.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+Sommes déposées chez Laffitte\r
+\r
+\r
+Du reste, il était demeuré aussi simple que le premier jour. Il avait\r
+les cheveux gris, l'oeil sérieux, le teint hâlé d'un ouvrier, le visage\r
+pensif d'un philosophe. Il portait habituellement un chapeau à bords\r
+larges et une longue redingote de gros drap, boutonnée jusqu'au menton.\r
+Il remplissait ses fonctions de maire, mais hors de là il vivait\r
+solitaire. Il parlait à peu de monde. Il se dérobait aux politesses,\r
+saluait de côté, s'esquivait vite, souriait pour se dispenser de causer,\r
+donnait pour se dispenser de sourire. Les femmes disaient de lui: Quel\r
+bon ours! Son plaisir était de se promener dans les champs.\r
+\r
+Il prenait ses repas toujours seul, avec un livre ouvert devant lui où\r
+il lisait. Il avait une petite bibliothèque bien faite. Il aimait les\r
+livres; les livres sont des amis froids et sûrs. À mesure que le loisir\r
+lui venait avec la fortune, il semblait qu'il en profitât pour cultiver\r
+son esprit. Depuis qu'il était à Montreuil-sur-mer, on remarquait que\r
+d'année en année son langage devenait plus poli, plus choisi et plus\r
+doux.\r
+\r
+Il emportait volontiers un fusil dans ses promenades, mais il s'en\r
+servait rarement. Quand cela lui arrivait par aventure, il avait un tir\r
+infaillible qui effrayait. Jamais il ne tuait un animal inoffensif.\r
+Jamais il ne tirait un petit oiseau. Quoiqu'il ne fût plus jeune, on\r
+contait qu'il était d'une force prodigieuse. Il offrait un coup de main\r
+à qui en avait besoin, relevait un cheval, poussait à une roue\r
+embourbée, arrêtait par les cornes un taureau échappé. Il avait toujours\r
+ses poches pleines de monnaie en sortant et vides en rentrant. Quand il\r
+passait dans un village, les marmots déguenillés couraient joyeusement\r
+après lui et l'entouraient comme une nuée de moucherons.\r
+\r
+On croyait deviner qu'il avait dû vivre jadis de la vie des champs, car\r
+il avait toutes sortes de secrets utiles qu'il enseignait aux paysans.\r
+Il leur apprenait à détruire la teigne des blés en aspergeant le grenier\r
+et en inondant les fentes du plancher d'une dissolution de sel commun,\r
+et à chasser les charançons en suspendant partout, aux murs et aux\r
+toits, dans les héberges et dans les maisons, de l'orviot en fleur. Il\r
+avait des "recettes" pour extirper d'un champ la luzette, la nielle, la\r
+vesce, la gaverolle, la queue-de-renard, toutes les herbes parasites qui\r
+mangent le blé. Il défendait une lapinière contre les rats rien qu'avec\r
+l'odeur d'un petit cochon de Barbarie qu'il y mettait. Un jour il voyait\r
+des gens du pays très occupés à arracher des orties. Il regarda ce tas\r
+de plantes déracinées et déjà desséchées, et dit:\r
+\r
+--C'est mort. Cela serait pourtant bon si l'on savait s'en servir. Quand\r
+l'ortie est jeune, la feuille est un légume excellent; quand elle\r
+vieillit, elle a des filaments et des fibres comme le chanvre et le lin.\r
+La toile d'ortie vaut la toile de chanvre. Hachée, l'ortie est bonne\r
+pour la volaille; broyée, elle est bonne pour les bêtes à cornes. La\r
+graine de l'ortie mêlée au fourrage donne du luisant au poil des\r
+animaux; la racine mêlée au sel produit une belle couleur jaune. C'est\r
+du reste un excellent foin qu'on peut faucher deux fois. Et que faut-il\r
+à l'ortie? Peu de terre, nul soin, nulle culture. Seulement la graine\r
+tombe à mesure qu'elle mûrit, et est difficile à récolter. Voilà tout.\r
+Avec quelque peine qu'on prendrait, l'ortie serait utile; on la néglige,\r
+elle devient nuisible. Alors on la tue. Que d'hommes ressemblent à\r
+l'ortie!\r
+\r
+Il ajouta après un silence:\r
+\r
+--Mes amis, retenez ceci, il n'y a ni mauvaises herbes ni mauvais\r
+hommes. Il n'y a que de mauvais cultivateurs.\r
+\r
+Les enfants l'aimaient encore parce qu'il savait faire de charmants\r
+petits ouvrages avec de la paille et des noix de coco.\r
+\r
+Quand il voyait la porte d'une église tendue de noir, il entrait; il\r
+recherchait un enterrement comme d'autres recherchent un baptême. Le\r
+veuvage et le malheur d'autrui l'attiraient à cause de sa grande\r
+douceur; il se mêlait aux amis en deuil, aux familles vêtues de noir,\r
+aux prêtres gémissant autour d'un cercueil. Il semblait donner\r
+volontiers pour texte à ses pensées ces psalmodies funèbres pleines de\r
+la vision d'un autre monde. L'oeil au ciel, il écoutait, avec une sorte\r
+d'aspiration vers tous les mystères de l'infini, ces voix tristes qui\r
+chantent sur le bord de l'abîme obscur de la mort.\r
+\r
+Il faisait une foule de bonnes actions en se cachant comme on se cache\r
+pour les mauvaises. Il pénétrait à la dérobée, le soir, dans les\r
+maisons; il montait furtivement des escaliers. Un pauvre diable, en\r
+rentrant dans son galetas, trouvait que sa porte avait été ouverte,\r
+quelquefois même forcée, dans son absence. Le pauvre homme se récriait:\r
+quelque malfaiteur est venu! Il entrait, et la première chose qu'il\r
+voyait, c'était une pièce d'or oubliée sur un meuble. "Le malfaiteur"\r
+qui était venu, c'était le père Madeleine.\r
+\r
+Il était affable et triste. Le peuple disait: «Voilà un homme riche qui\r
+n'a pas l'air fier. Voilà un homme heureux qui n'a pas l'air content.»\r
+\r
+Quelques-uns prétendaient que c'était un personnage mystérieux, et\r
+affirmaient qu'on n'entrait jamais dans sa chambre, laquelle était une\r
+vraie cellule d'anachorète meublée de sabliers ailés et enjolivée de\r
+tibias en croix et de têtes de mort. Cela se disait beaucoup, si bien\r
+que quelques jeunes femmes élégantes et malignes de Montreuil-sur-mer\r
+vinrent chez lui un jour, et lui demandèrent:\r
+\r
+--Monsieur le maire, montrez-nous donc votre chambre. On dit que c'est\r
+une grotte.\r
+\r
+Il sourit, et les introduisit sur-le-champ dans cette «grotte». Elles\r
+furent bien punies de leur curiosité. C'était une chambre garnie tout\r
+bonnement de meubles d'acajou assez laids comme tous les meubles de ce\r
+genre et tapissée de papier à douze sous. Elles n'y purent rien\r
+remarquer que deux flambeaux de forme vieillie qui étaient sur la\r
+cheminée et qui avaient l'air d'être en argent, «car ils étaient\r
+contrôlés». Observation pleine de l'esprit des petites villes.\r
+\r
+On n'en continua pas moins de dire que personne ne pénétrait dans cette\r
+chambre et que c'était une caverne d'ermite, un rêvoir, un trou, un\r
+tombeau.\r
+\r
+On se chuchotait aussi qu'il avait des sommes «immenses» déposées chez\r
+Laffitte, avec cette particularité qu'elles étaient toujours à sa\r
+disposition immédiate, de telle sorte, ajoutait-on, que M. Madeleine\r
+pourrait arriver un matin chez Laffitte, signer un reçu et emporter ses\r
+deux ou trois millions en dix minutes. Dans la réalité ces «deux ou\r
+trois millions» se réduisaient, nous l'avons dit, à six cent trente ou\r
+quarante mille francs.\r
+\r
+\r
+\r
+\r
+Chapitre IV\r
+\r
+M. Madeleine en deuil\r
+\r
+\r
+Au commencement de 1821, les journaux annoncèrent la mort de M. Myriel,\r
+évêque de Digne, «surnommé _monseigneur Bienvenu_», et trépassé en odeur\r
+de sainteté à l'âge de quatre-vingt-deux ans.\r
+\r
+L'évêque de Digne, pour ajouter ici un détail que les journaux omirent,\r
+était, quand il mourut, depuis plusieurs années aveugle, et content\r
+d'être aveugle, sa soeur étant près de lui.\r
+\r
+Disons-le en passant, être aveugle et être aimé, c'est en effet, sur\r
+cette terre où rien n'est complet, une des formes les plus étrangement\r
+exquises du bonheur. Avoir continuellement à ses côtés une femme, une\r
+fille, une soeur, un être charmant, qui est là parce que vous avez\r
+besoin d'elle et parce qu'elle ne peut se passer de vous, se savoir\r
+indispensable à qui nous est nécessaire, pouvoir incessamment mesurer\r
+son affection à la quantité de présence qu'elle nous donne, et se dire:\r
+puisqu'elle me consacre tout son temps, c'est que j'ai tout son coeur;\r
+voir la pensée à défaut de la figure, constater la fidélité d'un être\r
+dans l'éclipse du monde, percevoir le frôlement d'une robe comme un\r
+bruit d'ailes, l'entendre aller et venir, sortir, rentrer, parler,\r
+chanter, et songer qu'on est le centre de ces pas, de cette parole, de\r
+ce chant, manifester à chaque minute sa propre attraction, se sentir\r
+d'autant plus puissant qu'on est plus infirme, devenir dans l'obscurité,\r
+et par l'obscurité, l'astre autour duquel gravite cet ange, peu de\r
+félicités égalent celle-là. Le suprême bonheur de la vie, c'est la\r
+conviction qu'on est aimé; aimé pour soi-même, disons mieux, aimé malgré\r
+soi-même; cette conviction, l'aveugle l'a. Dans cette détresse, être\r
+servi, c'est être caressé. Lui manque-t-il quelque chose? Non. Ce n'est\r
+point perdre la lumière qu'avoir l'amour. Et quel amour! un amour\r
+entièrement fait de vertu. Il n'y a point de cécité où il y a certitude.\r
+L'âme à tâtons cherche l'âme, et la trouve. Et cette âme trouvée et\r
+prouvée est une femme. Une main vous soutient, c'est la sienne; une\r
+bouche effleure votre front, c'est sa bouche; vous entendez une\r
+respiration tout près de vous, c'est elle. Tout avoir d'elle, depuis son\r
+culte jusqu'à sa pitié, n'être jamais quitté, avoir cette douce\r
+faiblesse qui vous secourt, s'appuyer sur ce roseau inébranlable,\r
+toucher de ses mains la providence et pouvoir la prendre dans ses bras,\r
+Dieu palpable, quel ravissement! Le coeur, cette céleste fleur obscure,\r
+entre dans un épanouissement mystérieux. On ne donnerait pas cette ombre\r
+pour toute la clarté. L'âme ange est là, sans cesse là; si elle\r
+s'éloigne, c'est pour revenir; elle s'efface comme le rêve et reparaît\r
+comme la réalité. On sent de la chaleur qui approche, la voilà. On\r
+déborde de sérénité, de gaîté et d'extase; on est un rayonnement dans la\r
+nuit. Et mille petits soins. Des riens qui sont énormes dans ce vide.\r
+Les plus ineffables accents de la voix féminine employés à vous bercer,\r
+et suppléant pour vous à l'univers évanoui. On est caressé avec de\r
+l'âme. On ne voit rien, mais on se sent adoré. C'est un paradis de\r
+ténèbres.\r
+\r
+C'est de ce paradis que monseigneur Bienvenu était passé à l'autre.\r
+\r
+L'annonce de sa mort fut reproduite par le journal local de\r
+Montreuil-sur-mer. M. Madeleine parut le lendemain tout en noir avec un\r
+crêpe à son chapeau.\r
+\r
+On remarqua dans la ville ce deuil, et l'on jasa. Cela parut une lueur\r
+sur l'origine de M. Madeleine. On en conclut qu'il avait quelque\r
+alliance avec le vénérable évêque. _Il drape pour l'évêque de Digne_,\r
+dirent les salons; cela rehaussa fort M. Madeleine, et lui donna\r
+subitement et d'emblée une certaine considération dans le monde noble de\r
+Montreuil-sur-mer. Le microscopique faubourg Saint-Germain de l'endroit\r
+songea à faire cesser la quarantaine de M. Madeleine, parent probable\r
+d'un évêque. M. Madeleine s'aperçut de l'avancement qu'il obtenait à\r
+plus de révérences des vieilles femmes et à plus de sourires des jeunes.\r
+Un soir, une doyenne de ce petit grand monde-là, curieuse par droit\r
+d'ancienneté, se hasarda à lui demander:\r
+\r
+--Monsieur le maire est sans doute cousin du feu évêque de Digne?\r
+\r
+Il dit:\r
+\r
+--Non, madame.\r
+\r
+--Mais, reprit la douairière, vous en portez le deuil?\r
+\r
+Il répondit:\r
+\r
+--C'est que dans ma jeunesse j'ai été laquais dans sa famille.\r
+\r
+Une remarque qu'on faisait encore, c'est que, chaque fois qu'il passait\r
+dans la ville un jeune savoyard courant le pays et cherchant des\r
+cheminées à ramoner, M. le maire le faisait appeler, lui demandait son\r
+nom, et lui donnait de l'argent. Les petits savoyards se le disaient, et\r
+il en passait beaucoup.\r
+\r
+\r
+\r
+\r
+Chapitre V\r
+\r
+Vagues éclairs à l'horizon\r
+\r
+\r
+Peu à peu, et avec le temps, toutes les oppositions étaient tombées. Il\r
+y avait eu d'abord contre M. Madeleine, sorte de loi que subissent\r
+toujours ceux qui s'élèvent, des noirceurs et des calomnies, puis ce ne\r
+fut plus que des méchancetés, puis ce ne fut que des malices, puis cela\r
+s'évanouit tout à fait; le respect devint complet, unanime, cordial, et\r
+il arriva un moment, vers 1821, où ce mot: monsieur le maire, fut\r
+prononcé à Montreuil-sur-mer presque du même accent que ce mot:\r
+monseigneur l'évêque, était prononcé à Digne en 1815. On venait de dix\r
+lieues à la ronde consulter M. Madeleine. Il terminait les différends,\r
+il empêchait les procès, il réconciliait les ennemis. Chacun le prenait\r
+pour juge de son bon droit. Il semblait qu'il eût pour âme le livre de\r
+la loi naturelle. Ce fut comme une contagion de vénération qui, en six\r
+ou sept ans et de proche en proche, gagna tout le pays.\r
+\r
+Un seul homme, dans la ville et dans l'arrondissement, se déroba\r
+absolument à cette contagion, et, quoi que fît le père Madeleine, y\r
+demeura rebelle, comme si une sorte d'instinct, incorruptible et\r
+imperturbable, l'éveillait et l'inquiétait. Il semblerait en effet qu'il\r
+existe dans certains hommes un véritable instinct bestial, pur et\r
+intègre comme tout instinct, qui crée les antipathies et les sympathies,\r
+qui sépare fatalement une nature d'une autre nature, qui n'hésite pas,\r
+qui ne se trouble, ne se tait et ne se dément jamais, clair dans son\r
+obscurité, infaillible, impérieux, réfractaire à tous les conseils de\r
+l'intelligence et à tous les dissolvants de la raison, et qui, de\r
+quelque façon que les destinées soient faites, avertit secrètement\r
+l'homme-chien de la présence de l'homme-chat, et l'homme-renard de la\r
+présence de l'homme-lion.\r
+\r
+Souvent, quand M. Madeleine passait dans une rue, calme, affectueux,\r
+entouré des bénédictions de tous, il arrivait qu'un homme de haute\r
+taille, vêtu d'une redingote gris de fer, armé d'une grosse canne et\r
+coiffé d'un chapeau rabattu, se retournait brusquement derrière lui, et\r
+le suivait des yeux jusqu'à ce qu'il eût disparu, croisant les bras,\r
+secouant lentement la tête, et haussant sa lèvre supérieure avec sa\r
+lèvre inférieure jusqu'à son nez, sorte de grimace significative qui\r
+pourrait se traduire par: «Mais qu'est-ce que c'est que cet\r
+homme-là?--Pour sûr je l'ai vu quelque part.--En tout cas, je ne suis\r
+toujours pas sa dupe.»\r
+\r
+Ce personnage, grave d'une gravité presque menaçante, était de ceux qui,\r
+même rapidement entrevus, préoccupent l'observateur.\r
+\r
+Il se nommait Javert, et il était de la police.\r
+\r
+Il remplissait à Montreuil-sur-mer les fonctions pénibles, mais utiles,\r
+d'inspecteur. Il n'avait pas vu les commencements de Madeleine. Javert\r
+devait le poste qu'il occupait à la protection de M. Chabouillet, le\r
+secrétaire du ministre d'État, comte Anglès, alors préfet de police à\r
+Paris. Quand Javert était arrivé à Montreuil-sur-mer, la fortune du\r
+grand manufacturier était déjà faite, et le père Madeleine était devenu\r
+monsieur Madeleine.\r
+\r
+Certains officiers de police ont une physionomie à part et qui se\r
+complique d'un air de bassesse mêlé à un air d'autorité. Javert avait\r
+cette physionomie, moins la bassesse.\r
+\r
+Dans notre conviction, si les âmes étaient visibles aux yeux, on verrait\r
+distinctement cette chose étrange que chacun des individus de l'espèce\r
+humaine correspond à quelqu'une des espèces de la création animale; et\r
+l'on pourrait reconnaître aisément cette vérité à peine entrevue par le\r
+penseur, que, depuis l'huître jusqu'à l'aigle, depuis le porc jusqu'au\r
+tigre, tous les animaux sont dans l'homme et que chacun d'eux est dans\r
+un homme. Quelquefois même plusieurs d'entre eux à la fois.\r
+\r
+Les animaux ne sont autre chose que les figures de nos vertus et de nos\r
+vices, errantes devant nos yeux, les fantômes visibles de nos âmes. Dieu\r
+nous les montre pour nous faire réfléchir. Seulement, comme les animaux\r
+ne sont que des ombres, Dieu ne les a point faits éducables dans le sens\r
+complet du mot; à quoi bon? Au contraire, nos âmes étant des réalités et\r
+ayant une fin qui leur est propre, Dieu leur a donné l'intelligence,\r
+c'est-à-dire l'éducation possible. L'éducation sociale bien faite peut\r
+toujours tirer d'une âme, quelle qu'elle soit, l'utilité qu'elle\r
+contient.\r
+\r
+Ceci soit dit, bien entendu, au point de vue restreint de la vie\r
+terrestre apparente, et sans préjuger la question profonde de la\r
+personnalité antérieure et ultérieure des êtres qui ne sont pas l'homme.\r
+Le moi visible n'autorise en aucune façon le penseur à nier le moi\r
+latent. Cette réserve faite, passons.\r
+\r
+Maintenant, si l'on admet un moment avec nous que dans tout homme il y a\r
+une des espèces animales de la création, il nous sera facile de dire ce\r
+que c'était que l'officier de paix Javert.\r
+\r
+Les paysans asturiens sont convaincus que dans toute portée de louve il\r
+y a un chien, lequel est tué par la mère, sans quoi en grandissant il\r
+dévorerait les autres petits.\r
+\r
+Donnez une face humaine à ce chien fils d'une louve, et ce sera Javert.\r
+\r
+Javert était né dans une prison d'une tireuse de cartes dont le mari\r
+était aux galères. En grandissant, il pensa qu'il était en dehors de la\r
+société et désespéra d'y rentrer jamais. Il remarqua que la société\r
+maintient irrémissiblement en dehors d'elle deux classes d'hommes, ceux\r
+qui l'attaquent et ceux qui la gardent; il n'avait le choix qu'entre ces\r
+deux classes; en même temps il se sentait je ne sais quel fond de\r
+rigidité, de régularité et de probité, compliqué d'une inexprimable\r
+haine pour cette race de bohèmes dont il était. Il entra dans la police.\r
+\r
+Il y réussit. À quarante ans il était inspecteur.\r
+\r
+Il avait dans sa jeunesse été employé dans les chiourmes du midi.\r
+\r
+Avant d'aller plus loin, entendons-nous sur ce mot face humaine que nous\r
+appliquions tout à l'heure à Javert.\r
+\r
+La face humaine de Javert consistait en un nez camard, avec deux\r
+profondes narines vers lesquelles montaient sur ses deux joues d'énormes\r
+favoris. On se sentait mal à l'aise la première fois qu'on voyait ces\r
+deux forêts et ces deux cavernes. Quand Javert riait, ce qui était rare\r
+et terrible, ses lèvres minces s'écartaient, et laissaient voir, non\r
+seulement ses dents, mais ses gencives, et il se faisait autour de son\r
+nez un plissement épaté et sauvage comme sur un mufle de bête fauve.\r
+Javert sérieux était un dogue; lorsqu'il riait, c'était un tigre. Du\r
+reste, peu de crâne, beaucoup de mâchoire, les cheveux cachant le front\r
+et tombant sur les sourcils, entre les deux yeux un froncement central\r
+permanent comme une étoile de colère, le regard obscur, la bouche pincée\r
+et redoutable, l'air du commandement féroce.\r
+\r
+Cet homme était composé de deux sentiments très simples, et relativement\r
+très bons, mais qu'il faisait presque mauvais à force de les exagérer:\r
+le respect de l'autorité, la haine de la rébellion; et à ses yeux le\r
+vol, le meurtre, tous les crimes, n'étaient que des formes de la\r
+rébellion. Il enveloppait dans une sorte de foi aveugle et profonde tout\r
+ce qui a une fonction dans l'État, depuis le premier ministre jusqu'au\r
+garde champêtre. Il couvrait de mépris, d'aversion et de dégoût tout ce\r
+qui avait franchi une fois le seuil légal du mal. Il était absolu et\r
+n'admettait pas d'exceptions. D'une part il disait:\r
+\r
+--Le fonctionnaire ne peut se tromper; le magistrat n'a jamais tort.\r
+\r
+D'autre part il disait:\r
+\r
+--Ceux-ci sont irrémédiablement perdus. Rien de bon n'en peut sortir.\r
+\r
+Il partageait pleinement l'opinion de ces esprits extrêmes qui\r
+attribuent à la loi humaine je ne sais quel pouvoir de faire ou, si l'on\r
+veut, de constater des damnés, et qui mettent un Styx au bas de la\r
+société. Il était stoïque, sérieux, austère; rêveur triste; humble et\r
+hautain comme les fanatiques. Son regard était une vrille. Cela était\r
+froid et cela perçait. Toute sa vie tenait dans ces deux mots: veiller\r
+et surveiller. Il avait introduit la ligne droite dans ce qu'il y a de\r
+plus tortueux au monde; il avait la conscience de son utilité, la\r
+religion de ses fonctions, et il était espion comme on est prêtre.\r
+Malheur à qui tombait sous sa main! Il eût arrêté son père s'évadant du\r
+bagne et dénoncé sa mère en rupture de ban. Et il l'eût fait avec cette\r
+sorte de satisfaction intérieure que donne la vertu. Avec cela une vie\r
+de privations, l'isolement, l'abnégation, la chasteté, jamais une\r
+distraction. C'était le devoir implacable, la police comprise comme les\r
+Spartiates comprenaient Sparte, un guet impitoyable, une honnêteté\r
+farouche, un mouchard marmoréen, Brutus dans Vidocq.\r
+\r
+Toute la personne de Javert exprimait l'homme qui épie et qui se dérobe.\r
+L'école mystique de Joseph de Maistre, laquelle à cette époque\r
+assaisonnait de haute cosmogonie ce qu'on appelait les journaux ultras,\r
+n'eût pas manqué de dire que Javert était un symbole. On ne voyait pas\r
+son front qui disparaissait sous son chapeau, on ne voyait pas ses yeux\r
+qui se perdaient sous ses sourcils, on ne voyait pas son menton qui\r
+plongeait dans sa cravate, on ne voyait pas ses mains qui rentraient\r
+dans ses manches, on ne voyait pas sa canne qu'il portait sous sa\r
+redingote. Mais l'occasion venue, on voyait tout à coup sortir de toute\r
+cette ombre, comme d'une embuscade, un front anguleux et étroit, un\r
+regard funeste, un menton menaçant, des mains énormes; et un gourdin\r
+monstrueux.\r
+\r
+À ses moments de loisir, qui étaient peu fréquents, tout en haïssant les\r
+livres, il lisait; ce qui fait qu'il n'était pas complètement illettré.\r
+Cela se reconnaissait à quelque emphase dans la parole.\r
+\r
+Il n'avait aucun vice, nous l'avons dit. Quand il était content de lui,\r
+il s'accordait une prise de tabac. Il tenait à l'humanité par là.\r
+\r
+On comprendra sans peine que Javert était l'effroi de toute cette classe\r
+que la statistique annuelle du ministère de la justice désigne sous la\r
+rubrique: _Gens sans aveu_. Le nom de Javert prononcé les mettait en\r
+déroute; la face de Javert apparaissant les pétrifiait.\r
+\r
+Tel était cet homme formidable.\r
+\r
+Javert était comme un oeil toujours fixé sur M. Madeleine. Oeil plein de\r
+soupçon et de conjectures. M. Madeleine avait fini par s'en apercevoir,\r
+mais il sembla que cela fût insignifiant pour lui. Il ne fit pas même\r
+une question à Javert, il ne le cherchait ni ne l'évitait, et il\r
+portait, sans paraître y faire attention, ce regard gênant et presque\r
+pesant. Il traitait Javert comme tout le monde, avec aisance et bonté.\r
+\r
+À quelques paroles échappées à Javert, on devinait qu'il avait recherché\r
+secrètement, avec cette curiosité qui tient à la race et où il entre\r
+autant d'instinct que de volonté, toutes les traces antérieures que le\r
+père Madeleine avait pu laisser ailleurs. Il paraissait savoir, et il\r
+disait parfois à mots couverts, que quelqu'un avait pris certaines\r
+informations dans un certain pays sur une certaine famille disparue. Une\r
+fois il lui arriva de dire, se parlant à lui-même:\r
+\r
+--Je crois que je le tiens!\r
+\r
+Puis il resta trois jours pensif sans prononcer une parole. Il paraît\r
+que le fil qu'il croyait tenir s'était rompu. Du reste, et ceci est le\r
+correctif nécessaire à ce que le sens de certains mots pourrait\r
+présenter de trop absolu, il ne peut y avoir rien de vraiment\r
+infaillible dans une créature humaine, et le propre de l'instinct est\r
+précisément de pouvoir être troublé, dépisté et dérouté. Sans quoi il\r
+serait supérieur à l'intelligence, et la bête se trouverait avoir une\r
+meilleure lumière que l'homme.\r
+\r
+Javert était évidemment quelque peu déconcerté par le complet naturel et\r
+la tranquillité de M. Madeleine.\r
+\r
+Un jour pourtant son étrange manière d'être parut faire impression sur\r
+M. Madeleine. Voici à quelle occasion.\r
+\r
+\r
+\r
+\r
+Chapitre VI\r
+\r
+Le père Fauchelevent\r
+\r
+\r
+M. Madeleine passait un matin dans une ruelle non pavée de\r
+Montreuil-sur-mer. Il entendit du bruit et vit un groupe à quelque\r
+distance. Il y alla. Un vieux homme, nommé le père Fauchelevent, venait\r
+de tomber sous sa charrette dont le cheval s'était abattu.\r
+\r
+Ce Fauchelevent était un des rares ennemis qu'eût encore M. Madeleine à\r
+cette époque. Lorsque Madeleine était arrivé dans le pays, Fauchelevent,\r
+ancien tabellion et paysan presque lettré, avait un commerce qui\r
+commençait à aller mal. Fauchelevent avait vu ce simple ouvrier qui\r
+s'enrichissait, tandis que lui, maître, se ruinait. Cela l'avait rempli\r
+de jalousie, et il avait fait ce qu'il avait pu en toute occasion pour\r
+nuire à Madeleine. Puis la faillite était venue, et, vieux, n'ayant plus\r
+à lui qu'une charrette et un cheval, sans famille et sans enfants du\r
+reste, pour vivre il s'était fait charretier.\r
+\r
+Le cheval avait les deux cuisses cassées et ne pouvait se relever. Le\r
+vieillard était engagé entre les roues. La chute avait été tellement\r
+malheureuse que toute la voiture pesait sur sa poitrine. La charrette\r
+était assez lourdement chargée. Le père Fauchelevent poussait des râles\r
+lamentables. On avait essayé de le tirer, mais en vain. Un effort\r
+désordonné, une aide maladroite, une secousse à faux pouvaient\r
+l'achever. Il était impossible de le dégager autrement qu'en soulevant\r
+la voiture par-dessous. Javert, qui était survenu au moment de\r
+l'accident, avait envoyé chercher un cric.\r
+\r
+M. Madeleine arriva. On s'écarta avec respect.\r
+\r
+--À l'aide! criait le vieux Fauchelevent. Qui est-ce qui est bon enfant\r
+pour sauver le vieux?\r
+\r
+M. Madeleine se tourna vers les assistants:\r
+\r
+--A-t-on un cric?\r
+\r
+--On en est allé quérir un, répondit un paysan.\r
+\r
+--Dans combien de temps l'aura-t-on?\r
+\r
+--On est allé au plus près, au lieu Flachot, où il y a un maréchal; mais\r
+c'est égal, il faudra bien un bon quart d'heure.\r
+\r
+--Un quart d'heure! s'écria Madeleine.\r
+\r
+Il avait plu la veille, le sol était détrempé, la charrette s'enfonçait\r
+dans la terre à chaque instant et comprimait de plus en plus la poitrine\r
+du vieux charretier. Il était évident qu'avant cinq minutes il aurait\r
+les côtes brisées.\r
+\r
+--Il est impossible d'attendre un quart d'heure, dit Madeleine aux\r
+paysans qui regardaient.\r
+\r
+--Il faut bien!\r
+\r
+--Mais il ne sera plus temps! Vous ne voyez donc pas que la charrette\r
+s'enfonce?\r
+\r
+--Dame!\r
+\r
+--Écoutez, reprit Madeleine, il y a encore assez de place sous la\r
+voiture pour qu'un homme s'y glisse et la soulève avec son dos. Rien\r
+qu'une demi-minute, et l'on tirera le pauvre homme. Y a-t-il ici\r
+quelqu'un qui ait des reins et du coeur? Cinq louis d'or à gagner!\r
+\r
+Personne ne bougea dans le groupe.\r
+\r
+--Dix louis, dit Madeleine.\r
+\r
+Les assistants baissaient les yeux. Un d'eux murmura:\r
+\r
+--Il faudrait être diablement fort. Et puis, on risque de se faire\r
+écraser!\r
+\r
+--Allons! recommença Madeleine, vingt louis! Même silence.\r
+\r
+--Ce n'est pas la bonne volonté qui leur manque, dit une voix.\r
+\r
+M. Madeleine se retourna, et reconnut Javert. Il ne l'avait pas aperçu\r
+en arrivant. Javert continua:\r
+\r
+--C'est la force. Il faudrait être un terrible homme pour faire la chose\r
+de lever une voiture comme cela sur son dos.\r
+\r
+Puis, regardant fixement M. Madeleine, il poursuivit en appuyant sur\r
+chacun des mots qu'il prononçait:\r
+\r
+--Monsieur Madeleine, je n'ai jamais connu qu'un seul homme capable de\r
+faire ce que vous demandez là.\r
+\r
+Madeleine tressaillit.\r
+\r
+Javert ajouta avec un air d'indifférence, mais sans quitter des yeux\r
+Madeleine:\r
+\r
+--C'était un forçat.\r
+\r
+--Ah! dit Madeleine.\r
+\r
+--Du bagne de Toulon.\r
+\r
+Madeleine devint pâle.\r
+\r
+Cependant la charrette continuait à s'enfoncer lentement. Le père\r
+Fauchelevent râlait et hurlait:\r
+\r
+--J'étouffe! Ça me brise les côtes! Un cric! quelque chose! Ah!\r
+\r
+Madeleine regarda autour de lui:\r
+\r
+--Il n'y a donc personne qui veuille gagner vingt louis et sauver la vie\r
+à ce pauvre vieux?\r
+\r
+Aucun des assistants ne remua. Javert reprit:\r
+\r
+--Je n'ai jamais connu qu'un homme qui pût remplacer un cric. C'était ce\r
+forçat.\r
+\r
+--Ah! voilà que ça m'écrase! cria le vieillard.\r
+\r
+Madeleine leva la tête, rencontra l'oeil de faucon de Javert toujours\r
+attaché sur lui, regarda les paysans immobiles, et sourit tristement.\r
+Puis, sans dire une parole, il tomba à genoux, et avant même que la\r
+foule eût eu le temps de jeter un cri, il était sous la voiture.\r
+\r
+Il y eut un affreux moment d'attente et de silence.\r
+\r
+On vit Madeleine presque à plat ventre sous ce poids effrayant essayer\r
+deux fois en vain de rapprocher ses coudes de ses genoux. On lui cria:\r
+\r
+--Père Madeleine! retirez-vous de là!\r
+\r
+Le vieux Fauchelevent lui-même lui dit:\r
+\r
+--Monsieur Madeleine! allez-vous-en! C'est qu'il faut que je meure,\r
+voyez-vous! Laissez-moi! Vous allez vous faire écraser aussi!\r
+\r
+Madeleine ne répondit pas.\r
+\r
+Les assistants haletaient. Les roues avaient continué de s'enfoncer, et\r
+il était déjà devenu presque impossible que Madeleine sortît de dessous\r
+la voiture.\r
+\r
+Tout à coup on vit l'énorme masse s'ébranler, la charrette se soulevait\r
+lentement, les roues sortaient à demi de l'ornière. On entendit une voix\r
+étouffée qui criait:\r
+\r
+--Dépêchez-vous! aidez!\r
+\r
+C'était Madeleine qui venait de faire un dernier effort.\r
+\r
+Ils se précipitèrent. Le dévouement d'un seul avait donné de la force et\r
+du courage à tous. La charrette fut enlevée par vingt bras. Le vieux\r
+Fauchelevent était sauvé.\r
+\r
+Madeleine se releva. Il était blême, quoique ruisselant de sueur. Ses\r
+habits étaient déchirés et couverts de boue. Tous pleuraient. Le\r
+vieillard lui baisait les genoux et l'appelait le bon Dieu. Lui, il\r
+avait sur le visage je ne sais quelle expression de souffrance heureuse\r
+et céleste, et il fixait son oeil tranquille sur Javert qui le regardait\r
+toujours.\r
+\r
+\r
+\r
+\r
+Chapitre VII\r
+\r
+Fauchelevent devient jardinier à Paris\r
+\r
+\r
+Fauchelevent s'était démis la rotule dans sa chute. Le père Madeleine le\r
+fit transporter dans une infirmerie qu'il avait établie pour ses\r
+ouvriers dans le bâtiment même de sa fabrique et qui était desservie par\r
+deux soeurs de charité. Le lendemain matin, le vieillard trouva un\r
+billet de mille francs sur sa table de nuit, avec ce mot de la main du\r
+père Madeleine: _Je vous achète votre charrette et votre cheval_. La\r
+charrette était brisée et le cheval était mort. Fauchelevent guérit,\r
+mais son genou resta ankylosé. M. Madeleine, par les recommandations des\r
+soeurs et de son curé, fit placer le bonhomme comme jardinier dans un\r
+couvent de femmes du quartier Saint-Antoine à Paris.\r
+\r
+Quelque temps après, M. Madeleine fut nommé maire. La première fois que\r
+Javert vit M. Madeleine revêtu de l'écharpe qui lui donnait toute\r
+autorité sur la ville, il éprouva cette sorte de frémissement\r
+qu'éprouverait un dogue qui flairerait un loup sous les habits de son\r
+maître. À partir de ce moment, il l'évita le plus qu'il put. Quand les\r
+besoins du service l'exigeaient impérieusement et qu'il ne pouvait faire\r
+autrement que de se trouver avec M. le maire, il lui parlait avec un\r
+respect profond.\r
+\r
+Cette prospérité créée à Montreuil-sur-mer par le père Madeleine avait,\r
+outre les signes visibles que nous avons indiqués, un autre symptôme\r
+qui, pour n'être pas visible, n'était pas moins significatif. Ceci ne\r
+trompe jamais.\r
+\r
+Quand la population souffre, quand le travail manque, quand le commerce\r
+est nul, le contribuable résiste à l'impôt par pénurie, épuise et\r
+dépasse les délais, et l'état dépense beaucoup d'argent en frais de\r
+contrainte et de rentrée. Quand le travail abonde, quand le pays est\r
+heureux et riche, l'impôt se paye aisément et coûte peu à l'état. On\r
+peut dire que la misère et la richesse publiques ont un thermomètre\r
+infaillible, les frais de perception de l'impôt. En sept ans, les frais\r
+de perception de l'impôt s'étaient réduits des trois quarts dans\r
+l'arrondissement de Montreuil-sur-mer, ce qui faisait fréquemment citer\r
+cet arrondissement entre tous par M. de Villèle, alors ministre des\r
+finances.\r
+\r
+Telle était la situation du pays, lorsque Fantine y revint. Personne ne\r
+se souvenait plus d'elle. Heureusement la porte de la fabrique de M.\r
+Madeleine était comme un visage ami. Elle s'y présenta, et fut admise\r
+dans l'atelier des femmes. Le métier était tout nouveau pour Fantine,\r
+elle n'y pouvait être bien adroite, elle ne tirait donc de sa journée de\r
+travail que peu de chose, mais enfin cela suffisait, le problème était\r
+résolu, elle gagnait sa vie.\r
+\r
+\r
+\r
+\r
+Chapitre VIII\r
+\r
+Madame Victurnien dépense trente-cinq francs pour la morale\r
+\r
+\r
+Quand Fantine vit qu'elle vivait, elle eut un moment de joie. Vivre\r
+honnêtement de son travail, quelle grâce du ciel! Le goût du travail lui\r
+revint vraiment. Elle acheta un miroir, se réjouit d'y regarder sa\r
+jeunesse, ses beaux cheveux et ses belles dents, oublia beaucoup de\r
+choses, ne songea plus qu'à sa Cosette et à l'avenir possible, et fut\r
+presque heureuse. Elle loua une petite chambre et la meubla à crédit sur\r
+son travail futur; reste de ses habitudes de désordre.\r
+\r
+Ne pouvant pas dire qu'elle était mariée, elle s'était bien gardée,\r
+comme nous l'avons déjà fait entrevoir, de parler de sa petite fille.\r
+\r
+En ces commencements, on l'a vu, elle payait exactement les Thénardier.\r
+Comme elle ne savait que signer, elle était obligée de leur écrire par\r
+un écrivain public.\r
+\r
+Elle écrivait souvent. Cela fut remarqué. On commença à dire tout bas\r
+dans l'atelier des femmes que Fantine «écrivait des lettres» et qu'«elle\r
+avait des allures».\r
+\r
+Il n'y a rien de tel pour épier les actions des gens que ceux qu'elles\r
+ne regardent pas.--Pourquoi ce monsieur ne vient-il jamais qu'à la\r
+brune? pourquoi monsieur un tel n'accroche-t-il jamais sa clef au clou\r
+le jeudi? pourquoi prend-il toujours les petites rues? pourquoi madame\r
+descend-elle toujours de son fiacre avant d'arriver à la maison?\r
+pourquoi envoie-t-elle acheter un cahier de papier à lettres, quand elle\r
+en a «plein sa papeterie?» etc., etc.--Il existe des êtres qui, pour\r
+connaître le mot de ces énigmes, lesquelles leur sont du reste\r
+parfaitement indifférentes, dépensent plus d'argent, prodiguent plus de\r
+temps, se donnent plus de peine qu'il n'en faudrait pour dix bonnes\r
+actions; et cela, gratuitement, pour le plaisir, sans être payés de la\r
+curiosité autrement que par la curiosité. Ils suivront celui-ci ou\r
+celle-là des jours entiers, feront faction des heures à des coins de\r
+rue, sous des portes d'allées, la nuit, par le froid et par la pluie,\r
+corrompront des commissionnaires, griseront des cochers de fiacre et des\r
+laquais, achèteront une femme de chambre, feront acquisition d'un\r
+portier. Pourquoi? pour rien. Pur acharnement de voir, de savoir et de\r
+pénétrer. Pure démangeaison de dire. Et souvent ces secrets connus, ces\r
+mystères publiés, ces énigmes éclairées du grand jour, entraînent des\r
+catastrophes, des duels, des faillites, des familles ruinées, des\r
+existences brisées, à la grande joie de ceux qui ont «tout découvert»\r
+sans intérêt et par pur instinct. Chose triste.\r
+\r
+Certaines personnes sont méchantes uniquement par besoin de parler. Leur\r
+conversation, causerie dans le salon, bavardage dans l'antichambre, est\r
+comme ces cheminées qui usent vite le bois; il leur faut beaucoup de\r
+combustible; et le combustible, c'est le prochain.\r
+\r
+On observa donc Fantine.\r
+\r
+Avec cela, plus d'une était jalouse de ses cheveux blonds et de ses\r
+dents blanches. On constata que dans l'atelier, au milieu des autres,\r
+elle se détournait souvent pour essuyer une larme. C'étaient les moments\r
+où elle songeait à son enfant; peut-être aussi à l'homme qu'elle avait\r
+aimé.\r
+\r
+C'est un douloureux labeur que la rupture des sombres attaches du passé.\r
+\r
+On constata qu'elle écrivait, au moins deux fois par mois, toujours à la\r
+même adresse, et qu'elle affranchissait la lettre. On parvint à se\r
+procurer l'adresse: _Monsieur, Monsieur Thénardier, aubergiste, à\r
+Montfermeil_. On fit jaser au cabaret l'écrivain public, vieux bonhomme\r
+qui ne pouvait pas emplir son estomac de vin rouge sans vider sa poche\r
+aux secrets. Bref, on sut que Fantine avait un enfant. «Ce devait être\r
+une espèce de fille.» Il se trouva une commère qui fit le voyage de\r
+Montfermeil, parla aux Thénardier, et dit à son retour: «Pour mes\r
+trente-cinq francs, j'en ai eu le coeur net. J'ai vu l'enfant!»\r
+\r
+La commère qui fit cela était une gorgone appelée madame Victurnien,\r
+gardienne et portière de la vertu de tout le monde. Madame Victurnien\r
+avait cinquante-six ans, et doublait le masque de la laideur du masque\r
+de la vieillesse. Voix chevrotante, esprit capricant. Cette vieille\r
+femme avait été jeune, chose étonnante. Dans sa jeunesse, en plein 93,\r
+elle avait épousé un moine échappé du cloître en bonnet rouge et passé\r
+des bernardins aux jacobins. Elle était sèche, rêche, revêche, pointue,\r
+épineuse, presque venimeuse; tout en se souvenant de son moine dont elle\r
+était veuve, et qui l'avait fort domptée et pliée. C'était une ortie où\r
+l'on voyait le froissement du froc. À la restauration, elle s'était\r
+faite bigote, et si énergiquement que les prêtres lui avaient pardonné\r
+son moine. Elle avait un petit bien qu'elle léguait bruyamment à une\r
+communauté religieuse. Elle était fort bien vue à l'évêché d'Arras.\r
+Cette madame Victurnien donc alla à Montfermeil, et revint en disant:\r
+«J'ai vu l'enfant».\r
+\r
+Tout cela prit du temps. Fantine était depuis plus d'un an à la\r
+fabrique, lorsqu'un matin la surveillante de l'atelier lui remit, de la\r
+part de M. le maire, cinquante francs, en lui disant qu'elle ne faisait\r
+plus partie de l'atelier et en l'engageant, de la part de M. le maire, à\r
+quitter le pays.\r
+\r
+C'était précisément dans ce même mois que les Thénardier, après avoir\r
+demandé douze francs au lieu de six, venaient d'exiger quinze francs au\r
+lieu de douze.\r
+\r
+Fantine fut atterrée. Elle ne pouvait s'en aller du pays, elle devait\r
+son loyer et ses meubles. Cinquante francs ne suffisaient pas pour\r
+acquitter cette dette. Elle balbutia quelques mots suppliants. La\r
+surveillante lui signifia qu'elle eût à sortir sur-le-champ de\r
+l'atelier. Fantine n'était du reste qu'une ouvrière médiocre. Accablée\r
+de honte plus encore que de désespoir, elle quitta l'atelier et rentra\r
+dans sa chambre. Sa faute était donc maintenant connue de tous!\r
+\r
+Elle ne se sentit plus la force de dire un mot. On lui conseilla de voir\r
+M. le maire; elle n'osa pas. M. le maire lui donnait cinquante francs,\r
+parce qu'il était bon, et la chassait, parce qu'il était juste. Elle\r
+plia sous cet arrêt.\r
+\r
+\r
+\r
+\r
+Chapitre IX\r
+\r
+Succès de Madame Victurnien\r
+\r
+\r
+La veuve du moine fut donc bonne à quelque chose.\r
+\r
+Du reste, M. Madeleine n'avait rien su de tout cela. Ce sont là de ces\r
+combinaisons d'événements dont la vie est pleine. M. Madeleine avait\r
+pour habitude de n'entrer presque jamais dans l'atelier des femmes. Il\r
+avait mis à la tête de cet atelier une vieille fille, que le curé lui\r
+avait donnée, et il avait toute confiance dans cette surveillante,\r
+personne vraiment respectable, ferme, équitable, intègre, remplie de la\r
+charité qui consiste à donner, mais n'ayant pas au même degré la charité\r
+qui consiste à comprendre et à pardonner. M. Madeleine se remettait de\r
+tout sur elle. Les meilleurs hommes sont souvent forcés de déléguer leur\r
+autorité. C'est dans cette pleine puissance et avec la conviction\r
+qu'elle faisait bien, que la surveillante avait instruit le procès,\r
+jugé, condamné et exécuté Fantine.\r
+\r
+Quant aux cinquante francs, elle les avait donnés sur une somme que M.\r
+Madeleine lui confiait pour aumônes et secours aux ouvrières et dont\r
+elle ne rendait pas compte.\r
+\r
+Fantine s'offrit comme servante dans le pays; elle alla d'une maison à\r
+l'autre. Personne ne voulut d'elle. Elle n'avait pu quitter la ville. Le\r
+marchand fripier auquel elle devait ses meubles, quels meubles! lui\r
+avait dit: «Si vous vous en allez, je vous fais arrêter comme voleuse.»\r
+Le propriétaire auquel elle devait son loyer, lui avait dit:\r
+\r
+«Vous êtes jeune et jolie, vous pouvez payer.» Elle partagea les\r
+cinquante francs entre le propriétaire et le fripier, rendit au marchand\r
+les trois quarts de son mobilier, ne garda que le nécessaire, et se\r
+trouva sans travail, sans état, n'ayant plus que son lit, et devant\r
+encore environ cent francs.\r
+\r
+Elle se mit à coudre de grosses chemises pour les soldats de la\r
+garnison, et gagnait douze sous par jour. Sa fille lui en coûtait dix.\r
+C'est en ce moment qu'elle commença à mal payer les Thénardier.\r
+\r
+Cependant une vieille femme qui lui allumait sa chandelle quand elle\r
+rentrait le soir, lui enseigna l'art de vivre dans la misère. Derrière\r
+vivre de peu, il y a vivre de rien. Ce sont deux chambres; la première\r
+est obscure, la seconde est noire.\r
+\r
+Fantine apprit comment on se passe tout à fait de feu en hiver, comment\r
+on renonce à un oiseau qui vous mange un liard de millet tous les deux\r
+jours, comment on fait de son jupon sa couverture et de sa couverture\r
+son jupon, comment on ménage sa chandelle en prenant son repas à la\r
+lumière de la fenêtre d'en face. On ne sait pas tout ce que certains\r
+êtres faibles, qui ont vieilli dans le dénûment et l'honnêteté, savent\r
+tirer d'un sou. Cela finit par être un talent. Fantine acquit ce sublime\r
+talent et reprit un peu de courage.\r
+\r
+À cette époque, elle disait à une voisine:\r
+\r
+--Bah! je me dis: en ne dormant que cinq heures et en travaillant tout\r
+le reste à mes coutures, je parviendrai bien toujours à gagner à peu\r
+près du pain. Et puis, quand on est triste, on mange moins. Eh bien! des\r
+souffrances, des inquiétudes, un peu de pain d'un côté, des chagrins de\r
+l'autre, tout cela me nourrira.\r
+\r
+Dans cette détresse, avoir sa petite fille eût été un étrange bonheur.\r
+Elle songea à la faire venir. Mais quoi! lui faire partager son\r
+dénûment! Et puis, elle devait aux Thénardier! comment s'acquitter? Et\r
+le voyage! comment le payer?\r
+\r
+La vieille qui lui avait donné ce qu'on pourrait appeler des leçons de\r
+vie indigente était une sainte fille nommée Marguerite, dévote de la\r
+bonne dévotion, pauvre, et charitable pour les pauvres et même pour les\r
+riches, sachant tout juste assez écrire pour signer _Margueritte_, et\r
+croyant en Dieu, ce qui est la science.\r
+\r
+Il y a beaucoup de ces vertus-là en bas; un jour elles seront en haut.\r
+Cette vie a un lendemain.\r
+\r
+Dans les premiers temps, Fantine avait été si honteuse qu'elle n'avait\r
+pas osé sortir. Quand elle était dans la rue, elle devinait qu'on se\r
+retournait derrière elle et qu'on la montrait du doigt; tout le monde la\r
+regardait et personne ne la saluait; le mépris âcre et froid des\r
+passants lui pénétrait dans la chair et dans l'âme comme une bise.\r
+\r
+Dans les petites villes, il semble qu'une malheureuse soit nue sous les\r
+sarcasmes et la curiosité de tous. À Paris, du moins, personne ne vous\r
+connaît, et cette obscurité est un vêtement. Oh! comme elle eût souhaité\r
+venir à Paris! Impossible.\r
+\r
+Il fallut bien s'accoutumer à la déconsidération, comme elle s'était\r
+accoutumée à l'indigence. Peu à peu elle en prit son parti. Après deux\r
+ou trois mois elle secoua la honte et se remit à sortir comme si de rien\r
+n'était.\r
+\r
+--Cela m'est bien égal, dit-elle.\r
+\r
+Elle alla et vint, la tête haute, avec un sourire amer, et sentit\r
+qu'elle devenait effrontée.\r
+\r
+Madame Victurnien quelquefois la voyait passer de sa fenêtre, remarquait\r
+la détresse de «cette créature», grâce à elle "remise à sa place", et se\r
+félicitait. Les méchants ont un bonheur noir.\r
+\r
+L'excès du travail fatiguait Fantine, et la petite toux sèche qu'elle\r
+avait augmenta. Elle disait quelquefois à sa voisine Marguerite: «Tâtez\r
+donc comme mes mains sont chaudes.»\r
+\r
+Cependant le matin, quand elle peignait avec un vieux peigne cassé ses\r
+beaux cheveux qui ruisselaient comme de la soie floche, elle avait une\r
+minute de coquetterie heureuse.\r
+\r
+\r
+\r
+\r
+Chapitre X\r
+\r
+Suite du succès\r
+\r
+\r
+Elle avait été congédiée vers la fin de l'hiver; l'été se passa, mais\r
+l'hiver revint. Jours courts, moins de travail. L'hiver, point de\r
+chaleur, point de lumière, point de midi, le soir touche au matin,\r
+brouillard, crépuscule, la fenêtre est grise, on n'y voit pas clair. Le\r
+ciel est un soupirail. Toute la journée est une cave. Le soleil a l'air\r
+d'un pauvre. L'affreuse saison! L'hiver change en pierre l'eau du ciel\r
+et le coeur de l'homme. Ses créanciers la harcelaient.\r
+\r
+Fantine gagnait trop peu. Ses dettes avaient grossi. Les Thénardier, mal\r
+payés, lui écrivaient à chaque instant des lettres dont le contenu la\r
+désolait et dont le port la ruinait. Un jour ils lui écrivirent que sa\r
+petite Cosette était toute nue par le froid qu'il faisait, qu'elle avait\r
+besoin d'une jupe de laine, et qu'il fallait au moins que la mère\r
+envoyât dix francs pour cela. Elle reçut la lettre, et la froissa dans\r
+ses mains tout le jour. Le soir elle entra chez un barbier qui habitait\r
+le coin de la rue, et défit son peigne. Ses admirables cheveux blonds\r
+lui tombèrent jusqu'aux reins.\r
+\r
+--Les beaux cheveux! s'écria le barbier.\r
+\r
+--Combien m'en donneriez-vous? dit-elle.\r
+\r
+--Dix francs.\r
+\r
+--Coupez-les.\r
+\r
+Elle acheta une jupe de tricot et l'envoya aux Thénardier.\r
+\r
+Cette jupe fit les Thénardier furieux. C'était de l'argent qu'ils\r
+voulaient. Ils donnèrent la jupe à Eponine. La pauvre Alouette continua\r
+de frissonner.\r
+\r
+Fantine pensa: «Mon enfant n'a plus froid. Je l'ai habillée de mes\r
+cheveux.» Elle mettait de petits bonnets ronds qui cachaient sa tête\r
+tondue et avec lesquels elle était encore jolie.\r
+\r
+Un travail ténébreux se faisait dans le coeur de Fantine. Quand elle vit\r
+qu'elle ne pouvait plus se coiffer, elle commença à tout prendre en\r
+haine autour d'elle. Elle avait longtemps partagé la vénération de tous\r
+pour le père Madeleine; cependant, à force de se répéter que c'était lui\r
+qui l'avait chassée, et qu'il était la cause de son malheur, elle en\r
+vint à le haïr lui aussi, lui surtout. Quand elle passait devant la\r
+fabrique aux heures où les ouvriers sont sur la porte, elle affectait de\r
+rire et de chanter.\r
+\r
+Une vieille ouvrière qui la vit une fois chanter et rire de cette façon\r
+dit:\r
+\r
+--Voilà une fille qui finira mal.\r
+\r
+Elle prit un amant, le premier venu, un homme qu'elle n'aimait pas, par\r
+bravade, avec la rage dans le coeur. C'était un misérable, une espèce de\r
+musicien mendiant, un oisif gueux, qui la battait, et qui la quitta\r
+comme elle l'avait pris, avec dégoût. Elle adorait son enfant.\r
+\r
+Plus elle descendait, plus tout devenait sombre autour d'elle plus ce\r
+doux petit ange rayonnait dans le fond de son âme. Elle disait. Quand je\r
+serai riche, j'aurai ma Cosette avec moi; et elle riait. La toux ne la\r
+quittait pas, et elle avait des sueurs dans le dos.\r
+\r
+Un jour elle reçut des Thénardier une lettre ainsi conçue:\r
+\r
+«Cosette est malade d'une maladie qui est dans le pays. Une fièvre\r
+miliaire, qu'ils appellent. Il faut des drogues chères. Cela nous ruine\r
+et nous ne pouvons plus payer. Si vous ne nous envoyez pas quarante\r
+francs avant huit jours, la petite est morte.»\r
+\r
+Elle se mit à rire aux éclats, et elle dit à sa vieille voisine:\r
+\r
+--Ah! ils sont bons! quarante francs! que ça! ça fait deux napoléons! Où\r
+veulent-ils que je les prenne? Sont-ils bêtes, ces paysans!\r
+\r
+Cependant elle alla dans l'escalier près d'une lucarne et relut la\r
+lettre.\r
+\r
+Puis elle descendit l'escalier et sortit en courant et en sautant, riant\r
+toujours. Quelqu'un qui la rencontra lui dit:\r
+\r
+--Qu'est-ce que vous avez donc à être si gaie?\r
+\r
+Elle répondit:\r
+\r
+--C'est une bonne bêtise que viennent de m'écrire des gens de la\r
+campagne. Ils me demandent quarante francs. Paysans, va!\r
+\r
+Comme elle passait sur la place, elle vit beaucoup de monde qui\r
+entourait une voiture de forme bizarre sur l'impériale de laquelle\r
+pérorait tout debout un homme vêtu de rouge. C'était un bateleur\r
+dentiste en tournée, qui offrait au public des râteliers complets, des\r
+opiats, des poudres et des élixirs.\r
+\r
+Fantine se mêla au groupe et se mit à rire comme les autres de cette\r
+harangue où il y avait de l'argot pour la canaille et du jargon pour les\r
+gens comme il faut. L'arracheur de dents vit cette belle fille qui\r
+riait, et s'écria tout à coup:\r
+\r
+--Vous avez de jolies dents, la fille qui riez là. Si vous voulez me\r
+vendre vos deux palettes, je vous donne de chaque un napoléon d'or.\r
+\r
+--Qu'est-ce que c'est que ça, mes palettes? demanda Fantine.\r
+\r
+--Les palettes, reprit le professeur dentiste, c'est les dents de\r
+devant, les deux d'en haut.\r
+\r
+--Quelle horreur! s'écria Fantine.\r
+\r
+--Deux napoléons! grommela une vieille édentée qui était là. Qu'en voilà\r
+une qui est heureuse!\r
+\r
+Fantine s'enfuit, et se boucha les oreilles pour ne pas entendre la voix\r
+enrouée de l'homme qui lui criait: Réfléchissez, la belle! deux\r
+napoléons, ça peut servir. Si le coeur vous en dit, venez ce soir à\r
+l'auberge du _Tillac d'argent_, vous m'y trouverez.\r
+\r
+Fantine rentra, elle était furieuse et conta la chose à sa bonne voisine\r
+Marguerite:\r
+\r
+--Comprenez-vous cela? ne voilà-t-il pas un abominable homme? comment\r
+laisse-t-on des gens comme cela aller dans le pays! M'arracher mes deux\r
+dents de devant! mais je serais horrible! Les cheveux repoussent, mais\r
+les dents! Ah! le monstre d'homme! j'aimerais mieux me jeter d'un\r
+cinquième la tête la première sur le pavé! Il m'a dit qu'il serait ce\r
+soir au _Tillac d'argent_.\r
+\r
+--Et qu'est-ce qu'il offrait? demanda Marguerite.\r
+\r
+--Deux napoléons.\r
+\r
+--Cela fait quarante francs.\r
+\r
+--Oui, dit Fantine, cela fait quarante francs.\r
+\r
+Elle resta pensive, et se mit à son ouvrage. Au bout d'un quart d'heure,\r
+elle quitta sa couture et alla relire la lettre des Thénardier sur\r
+l'escalier.\r
+\r
+En rentrant, elle dit à Marguerite qui travaillait près d'elle:\r
+\r
+--Qu'est-ce que c'est donc que cela, une fièvre miliaire? Savez-vous?\r
+\r
+--Oui, répondit la vieille fille, c'est une maladie.\r
+\r
+--Ça a donc besoin de beaucoup de drogues?\r
+\r
+--Oh! des drogues terribles.\r
+\r
+--Où ça vous prend-il?\r
+\r
+--C'est une maladie qu'on a comme ça.\r
+\r
+--Cela attaque donc les enfants?\r
+\r
+--Surtout les enfants.\r
+\r
+--Est-ce qu'on en meurt?\r
+\r
+--Très bien, dit Marguerite.\r
+\r
+Fantine sortit et alla encore une fois relire la lettre sur l'escalier.\r
+\r
+Le soir elle descendit, et on la vit qui se dirigeait du côté de la rue\r
+de Paris où sont les auberges.\r
+\r
+Le lendemain matin, comme Marguerite entrait dans la chambre de Fantine\r
+avant le jour, car elles travaillaient toujours ensemble et de cette\r
+façon n'allumaient qu'une chandelle pour deux, elle trouva Fantine\r
+assise sur son lit, pâle, glacée. Elle ne s'était pas couchée. Son\r
+bonnet était tombé sur ses genoux. La chandelle avait brûlé toute la\r
+nuit et était presque entièrement consumée.\r
+\r
+Marguerite s'arrêta sur le seuil, pétrifiée de cet énorme désordre, et\r
+s'écria:\r
+\r
+--Seigneur! la chandelle qui est toute brûlée! il s'est passé des\r
+événements!\r
+\r
+Puis elle regarda Fantine qui tournait vers elle sa tête sans cheveux.\r
+\r
+Fantine depuis la veille avait vieilli de dix ans.\r
+\r
+--Jésus! fit Marguerite, qu'est-ce que vous avez, Fantine?\r
+\r
+--Je n'ai rien, répondit Fantine. Au contraire. Mon enfant ne mourra pas\r
+de cette affreuse maladie, faute de secours. Je suis contente.\r
+\r
+En parlant ainsi, elle montrait à la vieille fille deux napoléons qui\r
+brillaient sur la table.\r
+\r
+--Ah, Jésus Dieu! dit Marguerite. Mais c'est une fortune! Où avez-vous\r
+eu ces louis d'or?\r
+\r
+--Je les ai eus, répondit Fantine.\r
+\r
+En même temps elle sourit. La chandelle éclairait son visage. C'était un\r
+sourire sanglant. Une salive rougeâtre lui souillait le coin des lèvres,\r
+et elle avait un trou noir dans la bouche.\r
+\r
+Les deux dents étaient arrachées.\r
+\r
+Elle envoya les quarante francs à Montfermeil.\r
+\r
+Du reste c'était une ruse des Thénardier pour avoir de l'argent. Cosette\r
+n'était pas malade.\r
+\r
+Fantine jeta son miroir par la fenêtre. Depuis longtemps elle avait\r
+quitté sa cellule du second pour une mansarde fermée d'un loquet sous le\r
+toit; un de ces galetas dont le plafond fait angle avec le plancher et\r
+vous heurte à chaque instant la tête. Le pauvre ne peut aller au fond de\r
+sa chambre comme au fond de sa destinée qu'en se courbant de plus en\r
+plus. Elle n'avait plus de lit, il lui restait une loque qu'elle\r
+appelait sa couverture, un matelas à terre et une chaise dépaillée. Un\r
+petit rosier qu'elle avait s'était désséché dans un coin, oublié. Dans\r
+l'autre coin, il y avait un pot à beurre à mettre l'eau, qui gelait\r
+l'hiver, et où les différents niveaux de l'eau restaient longtemps\r
+marqués par des cercles de glace. Elle avait perdu la honte, elle perdit\r
+la coquetterie. Dernier signe. Elle sortait avec des bonnets sales. Soit\r
+faute de temps, soit indifférence, elle ne raccommodait plus son linge.\r
+À mesure que les talons s'usaient, elle tirait ses bas dans ses\r
+souliers. Cela se voyait à de certains plis perpendiculaires. Elle\r
+rapiéçait son corset, vieux et usé, avec des morceaux de calicot qui se\r
+déchiraient au moindre mouvement. Les gens auxquels elle devait, lui\r
+faisaient «des scènes», et ne lui laissaient aucun repos. Elle les\r
+trouvait dans la rue, elle les retrouvait dans son escalier. Elle\r
+passait des nuits à pleurer et à songer. Elle avait les yeux très\r
+brillants, et elle sentait une douleur fixe dans l'épaule, vers le haut\r
+de l'omoplate gauche. Elle toussait beaucoup. Elle haïssait profondément\r
+le père Madeleine, et ne se plaignait pas. Elle cousait dix-sept heures\r
+par jour; mais un entrepreneur du travail des prisons, qui faisait\r
+travailler les prisonnières au rabais, fit tout à coup baisser les prix,\r
+ce qui réduisit la journée des ouvrières libres à neuf sous. Dix-sept\r
+heures de travail, et neuf sous par jour! Ses créanciers étaient plus\r
+impitoyables que jamais. Le fripier, qui avait repris presque tous les\r
+meubles, lui disait sans cesse: Quand me payeras-tu, coquine? Que\r
+voulait-on d'elle, bon Dieu! Elle se sentait traquée et il se\r
+développait en elle quelque chose de la bête farouche. Vers le même\r
+temps, le Thénardier lui écrivit que décidément il avait attendu avec\r
+beaucoup trop de bonté, et qu'il lui fallait cent francs, tout de suite;\r
+sinon qu'il mettrait à la porte la petite Cosette, toute convalescente\r
+de sa grande maladie, par le froid, par les chemins, et qu'elle\r
+deviendrait ce qu'elle pourrait, et qu'elle crèverait, si elle voulait.\r
+«Cent francs, songea Fantine! Mais où y a-t-il un état à gagner cent\r
+sous par jour?»\r
+\r
+--Allons! dit-elle, vendons le reste.\r
+\r
+L'infortunée se fit fille publique.\r
+\r
+\r
+\r
+\r
+Chapitre XI\r
+\r
+_Christus nos liberavit_\r
+\r
+\r
+Qu'est-ce que c'est que cette histoire de Fantine? C'est la société\r
+achetant une esclave.\r
+\r
+À qui? À la misère.\r
+\r
+À la faim, au froid, à l'isolement, à l'abandon, au dénûment. Marché\r
+douloureux. Une âme pour un morceau de pain. La misère offre, la société\r
+accepte.\r
+\r
+La sainte loi de Jésus-Christ gouverne notre civilisation, mais elle ne\r
+la pénètre pas encore. On dit que l'esclavage a disparu de la\r
+civilisation européenne. C'est une erreur. Il existe toujours, mais il\r
+ne pèse plus que sur la femme, et il s'appelle prostitution.\r
+\r
+Il pèse sur la femme, c'est-à-dire sur la grâce, sur la faiblesse, sur\r
+la beauté, sur la maternité. Ceci n'est pas une des moindres hontes de\r
+l'homme.\r
+\r
+Au point de ce douloureux drame où nous sommes arrivés, il ne reste plus\r
+rien à Fantine de ce qu'elle a été autrefois. Elle est devenue marbre en\r
+devenant boue. Qui la touche a froid. Elle passe, elle vous subit et\r
+elle vous ignore; elle est la figure déshonorée et sévère. La vie et\r
+l'ordre social lui ont dit leur dernier mot. Il lui est arrivé tout ce\r
+qui lui arrivera. Elle a tout ressenti, tout supporté, tout éprouvé,\r
+tout souffert, tout perdu, tout pleuré. Elle est résignée de cette\r
+résignation qui ressemble à l'indifférence comme la mort ressemble au\r
+sommeil. Elle n'évite plus rien. Elle ne craint plus rien. Tombe sur\r
+elle toute la nuée et passe sur elle tout l'océan! que lui importe!\r
+c'est une éponge imbibée.\r
+\r
+Elle le croit du moins, mais c'est une erreur de s'imaginer qu'on épuise\r
+le sort et qu'on touche le fond de quoi que ce soit.\r
+\r
+Hélas! qu'est-ce que toutes ces destinées ainsi poussées pêle-mêle? où\r
+vont-elles? pourquoi sont-elles ainsi?\r
+\r
+Celui qui sait cela voit toute l'ombre.\r
+\r
+Il est seul. Il s'appelle Dieu.\r
+\r
+\r
+\r
+\r
+Chapitre XII\r
+\r
+Le désoeuvrement de M. Bamatabois\r
+\r
+\r
+Il y a dans toutes les petites villes, et il y avait à Montreuil-sur-mer\r
+en particulier, une classe de jeunes gens qui grignotent quinze cents\r
+livres de rente en province du même air dont leurs pareils dévorent à\r
+Paris deux cent mille francs par an. Ce sont des êtres de la grande\r
+espèce neutre; hongres, parasites, nuls, qui ont un peu de terre, un peu\r
+de sottise et un peu d'esprit, qui seraient des rustres dans un salon et\r
+se croient des gentilshommes au cabaret, qui disent: mes prés, mes bois,\r
+mes paysans, sifflent les actrices du théâtre pour prouver qu'ils sont\r
+gens de goût, querellent les officiers de la garnison pour montrer\r
+qu'ils sont gens de guerre, chassent, fument, bâillent, boivent, sentent\r
+le tabac, jouent au billard, regardent les voyageurs descendre de\r
+diligence, vivent au café, dînent à l'auberge, ont un chien qui mange\r
+les os sous la table et une maîtresse qui pose les plats dessus,\r
+tiennent à un sou, exagèrent les modes, admirent la tragédie, méprisent\r
+les femmes, usent leurs vieilles bottes, copient Londres à travers Paris\r
+et Paris à travers Pont-à-Mousson, vieillissent hébétés, ne travaillent\r
+pas, ne servent à rien et ne nuisent pas à grand'chose.\r
+\r
+M. Félix Tholomyès, resté dans sa province et n'ayant jamais vu Paris,\r
+serait un de ces hommes-là.\r
+\r
+S'ils étaient plus riches, on dirait: ce sont des élégants; s'ils\r
+étaient plus pauvres, on dirait: ce sont des fainéants. Ce sont tout\r
+simplement des désoeuvrés. Parmi ces désoeuvrés, il y a des ennuyeux,\r
+des ennuyés, des rêvasseurs, et quelques drôles.\r
+\r
+Dans ce temps-là, un élégant se composait d'un grand col, d'une grande\r
+cravate, d'une montre à breloques, de trois gilets superposés de\r
+couleurs différentes, le bleu et le rouge en dedans, d'un habit couleur\r
+olive à taille courte, à queue de morue, à double rangée de boutons\r
+d'argent serrés les uns contre les autres et montant jusque sur\r
+l'épaule, et d'un pantalon olive plus clair, orné sur les deux coutures\r
+d'un nombre de côtes indéterminé, mais toujours impair, variant de une à\r
+onze, limite qui n'était jamais franchie. Ajoutez à cela des\r
+souliers-bottes avec de petits fers au talon, un chapeau à haute forme\r
+et à bords étroits, des cheveux en touffe, une énorme canne, et une\r
+conversation rehaussée des calembours de Potier. Sur le tout des éperons\r
+et des moustaches. À cette époque, des moustaches voulaient dire\r
+bourgeois et des éperons voulaient dire piéton.\r
+\r
+L'élégant de province portait les éperons plus longs et les moustaches\r
+plus farouches. C'était le temps de la lutte des républiques de\r
+l'Amérique méridionale contre le roi d'Espagne, de Bolivar contre\r
+Morillo. Les chapeaux à petits bords étaient royalistes et se nommaient\r
+des morillos; les libéraux portaient des chapeaux à larges bords qui\r
+s'appelaient des bolivars.\r
+\r
+Huit ou dix mois donc après ce qui a été raconté dans les pages\r
+précédentes, vers les premiers jours de janvier 1823, un soir qu'il\r
+avait neigé, un de ces élégants, un de ces désoeuvrés, un "bien\r
+pensant", car il avait un morillo, de plus chaudement enveloppé d'un de\r
+ces grands manteaux qui complétaient dans les temps froids le costume à\r
+la mode, se divertissait à harceler une créature qui rôdait en robe de\r
+bal et toute décolletée avec des fleurs sur la tête devant la vitre du\r
+café des officiers. Cet élégant fumait, car c'était décidément la mode.\r
+\r
+Chaque fois que cette femme passait devant lui, il lui jetait, avec une\r
+bouffée de la fumée de son cigare, quelque apostrophe qu'il croyait\r
+spirituelle et gaie, comme:--Que tu es laide!--Veux-tu te cacher!--Tu\r
+n'as pas de dents! etc., etc.--Ce monsieur s'appelait monsieur\r
+Bamatabois. La femme, triste spectre paré qui allait et venait sur la\r
+neige, ne lui répondait pas, ne le regardait même pas, et n'en\r
+accomplissait pas moins en silence et avec une régularité sombre sa\r
+promenade qui la ramenait de cinq minutes en cinq minutes sous le\r
+sarcasme, comme le soldat condamné qui revient sous les verges. Ce peu\r
+d'effet piqua sans doute l'oisif qui, profitant d'un moment où elle se\r
+retournait, s'avança derrière elle à pas de loup et en étouffant son\r
+rire, se baissa, prit sur le pavé une poignée de neige et la lui plongea\r
+brusquement dans le dos entre ses deux épaules nues. La fille poussa un\r
+rugissement, se tourna, bondit comme une panthère, et se rua sur\r
+l'homme, lui enfonçant ses ongles dans le visage, avec les plus\r
+effroyables paroles qui puissent tomber du corps de garde dans le\r
+ruisseau. Ces injures, vomies d'une voix enrouée par l'eau-de-vie,\r
+sortaient hideusement d'une bouche à laquelle manquaient en effet les\r
+deux dents de devant. C'était la Fantine.\r
+\r
+Au bruit que cela fit, les officiers sortirent en foule du café, les\r
+passants s'amassèrent, et il se forma un grand cercle riant, huant et\r
+applaudissant, autour de ce tourbillon composé de deux êtres où l'on\r
+avait peine à reconnaître un homme et une femme, l'homme se débattant,\r
+son chapeau à terre, la femme frappant des pieds et des poings,\r
+décoiffée, hurlant, sans dents et sans cheveux, livide de colère,\r
+horrible. Tout à coup un homme de haute taille sortit vivement de la\r
+foule, saisit la femme à son corsage de satin couvert de boue, et lui\r
+dit: Suis-moi!\r
+\r
+La femme leva la tête; sa voix furieuse s'éteignit subitement. Ses yeux\r
+étaient vitreux, de livide elle était devenue pâle, et elle tremblait\r
+d'un tremblement de terreur. Elle avait reconnu Javert.\r
+\r
+L'élégant avait profité de l'incident pour s'esquiver.\r
+\r
+\r
+\r
+\r
+Chapitre XIII\r
+\r
+Solution de quelques questions de police municipale\r
+\r
+\r
+Javert écarta les assistants, rompit le cercle et se mit à marcher à grands\r
+pas vers le bureau de police qui est à l'extrémité de la place, traînant\r
+après lui la misérable. Elle se laissait faire machinalement. Ni lui ni\r
+elle ne disaient un mot. La nuée des spectateurs, au paroxysme de la\r
+joie, suivait avec des quolibets. La suprême misère, occasion\r
+d'obscénités. Arrivé au bureau de police qui était une salle basse\r
+chauffée par un poêle et gardée par un poste, avec une porte vitrée et\r
+grillée sur la rue, Javert ouvrit la porte, entra avec Fantine, et\r
+referma la porte derrière lui, au grand désappointement des curieux qui\r
+se haussèrent sur la pointe du pied et allongèrent le cou devant la\r
+vitre trouble du corps de garde, cherchant à voir. La curiosité est une\r
+gourmandise. Voir, c'est dévorer.\r
+\r
+En entrant, la Fantine alla tomber dans un coin, immobile et muette,\r
+accroupie comme une chienne qui a peur.\r
+\r
+Le sergent du poste apporta une chandelle allumée sur une table. Javert\r
+s'assit, tira de sa poche une feuille de papier timbré et se mit à\r
+écrire.\r
+\r
+Ces classes de femmes sont entièrement remises par nos lois à la\r
+discrétion de la police. Elle en fait ce qu'elle veut, les punit comme\r
+bon lui semble, et confisque à son gré ces deux tristes choses qu'elles\r
+appellent leur industrie et leur liberté. Javert était impassible; son\r
+visage sérieux ne trahissait aucune émotion. Pourtant il était gravement\r
+et profondément préoccupé. C'était un de ces moments où il exerçait sans\r
+contrôle, mais avec tous les scrupules d'une conscience sévère, son\r
+redoutable pouvoir discrétionnaire. En cet instant, il le sentait, son\r
+escabeau d'agent de police était un tribunal. Il jugeait. Il jugeait, et\r
+il condamnait. Il appelait tout ce qu'il pouvait avoir d'idées dans\r
+l'esprit autour de la grande chose qu'il faisait. Plus il examinait le\r
+fait de cette fille, plus il se sentait révolté. Il était évident qu'il\r
+venait de voir commettre un crime. Il venait de voir, là dans la rue, la\r
+société, représentée par un propriétaire-électeur, insultée et attaquée\r
+par une créature en dehors de tout. Une prostituée avait attenté à un\r
+bourgeois. Il avait vu cela, lui Javert. Il écrivait en silence.\r
+\r
+Quand il eut fini, il signa, plia le papier et dit au sergent du poste,\r
+en le lui remettant:\r
+\r
+--Prenez trois hommes, et menez cette fille au bloc.\r
+\r
+Puis se tournant vers la Fantine:\r
+\r
+--Tu en as pour six mois.\r
+\r
+La malheureuse tressaillit.\r
+\r
+--Six mois! six mois de prison! Six mois à gagner sept sous par jour!\r
+Mais que deviendra Cosette? ma fille! ma fille! Mais je dois encore plus\r
+de cent francs aux Thénardier, monsieur l'inspecteur, savez-vous cela?\r
+\r
+Elle se traîna sur la dalle mouillée par les bottes boueuses de tous ces\r
+hommes, sans se lever, joignant les mains, faisant de grands pas avec\r
+ses genoux.\r
+\r
+--Monsieur Javert, dit-elle, je vous demande grâce. Je vous assure que\r
+je n'ai pas eu tort. Si vous aviez vu le commencement, vous auriez vu!\r
+je vous jure le bon Dieu que je n'ai pas eu tort. C'est ce monsieur le\r
+bourgeois que je ne connais pas qui m'a mis de la neige dans le dos.\r
+Est-ce qu'on a le droit de nous mettre de la neige dans le dos quand\r
+nous passons comme cela tranquillement sans faire de mal à personne?\r
+Cela m'a saisie. Je suis un peu malade, voyez-vous! Et puis il y avait\r
+déjà un peu de temps qu'il me disait des raisons. Tu es laide! tu n'as\r
+pas de dents! Je le sais bien que je n'ai plus mes dents. Je ne faisais\r
+rien, moi; je disais: c'est un monsieur qui s'amuse. J'étais honnête\r
+avec lui, je ne lui parlais pas. C'est à cet instant-là qu'il m'a mis de\r
+la neige. Monsieur Javert, mon bon monsieur l'inspecteur! est-ce qu'il\r
+n'y a personne là qui ait vu pour vous dire que c'est bien vrai? J'ai\r
+peut-être eu tort de me fâcher. Vous savez, dans le premier moment, on\r
+n'est pas maître. On a des vivacités. Et puis, quelque chose de si froid\r
+qu'on vous met dans le dos à l'heure que vous ne vous y attendez pas!\r
+J'ai eu tort d'abîmer le chapeau de ce monsieur. Pourquoi s'est-il en\r
+allé? Je lui demanderais pardon. Oh! mon Dieu, cela me serait bien égal\r
+de lui demander pardon. Faites-moi grâce pour aujourd'hui cette fois,\r
+monsieur Javert. Tenez, vous ne savez pas ça, dans les prisons on ne\r
+gagne que sept sous, ce n'est pas la faute du gouvernement, mais on\r
+gagne sept sous, et figurez-vous que j'ai cent francs à payer, ou\r
+autrement on me renverra ma petite. Ô mon Dieu! je ne peux pas l'avoir\r
+avec moi. C'est si vilain ce que je fais! Ô ma Cosette, ô mon petit ange\r
+de la bonne sainte Vierge, qu'est-ce qu'elle deviendra, pauvre loup! Je\r
+vais vous dire, c'est les Thénardier, des aubergistes, des paysans, ça\r
+n'a pas de raisonnement. Il leur faut de l'argent. Ne me mettez pas en\r
+prison! Voyez-vous, c'est une petite qu'on mettrait à même sur la grande\r
+route, va comme tu pourras, en plein coeur d'hiver, il faut avoir pitié\r
+de cette chose-là, mon bon monsieur Javert. Si c'était plus grand, ça\r
+gagnerait sa vie, mais ça ne peut pas, à ces âges-là. Je ne suis pas une\r
+mauvaise femme au fond. Ce n'est pas la lâcheté et la gourmandise qui\r
+ont fait de moi ça. J'ai bu de l'eau-de-vie, c'est par misère. Je ne\r
+l'aime pas, mais cela étourdit. Quand j'étais plus heureuse, on n'aurait\r
+eu qu'à regarder dans mes armoires, on aurait bien vu que je n'étais pas\r
+une femme coquette qui a du désordre. J'avais du linge, beaucoup de\r
+linge. Ayez pitié de moi, monsieur Javert!\r
+\r
+Elle parlait ainsi, brisée en deux, secouée par les sanglots, aveuglée\r
+par les larmes, la gorge nue, se tordant les mains, toussant d'une toux\r
+sèche et courte, balbutiant tout doucement avec la voix de l'agonie. La\r
+grande douleur est un rayon divin et terrible qui transfigure les\r
+misérables. À ce moment-là, la Fantine était redevenue belle. À de\r
+certains instants, elle s'arrêtait et baisait tendrement le bas de la\r
+redingote du mouchard. Elle eût attendri un coeur de granit, mais on\r
+n'attendrit pas un coeur de bois.\r
+\r
+--Allons! dit Javert, je t'ai écoutée. As-tu bien tout dit? Marche à\r
+présent! Tu as tes six mois; _le Père éternel en personne n'y pourrait\r
+plus rien_.\r
+\r
+À cette solennelle parole, Le Père éternel en personne n'y pourrait plus\r
+rien, elle comprit que l'arrêt était prononcé. Elle s'affaissa sur\r
+elle-même en murmurant:\r
+\r
+--Grâce!\r
+\r
+Javert tourna le dos.\r
+\r
+Les soldats la saisirent par les bras.\r
+\r
+Depuis quelques minutes, un homme était entré sans qu'on eût pris garde\r
+à lui. Il avait refermé la porte, s'y était adossé, et avait entendu les\r
+prières désespérées de la Fantine. Au moment où les soldats mirent la\r
+main sur la malheureuse, qui ne voulait pas se lever, il fit un pas,\r
+sortit de l'ombre, et dit:\r
+\r
+--Un instant, s'il vous plaît!\r
+\r
+Javert leva les yeux et reconnut M. Madeleine. Il ôta son chapeau, et\r
+saluant avec une sorte de gaucherie fâchée:\r
+\r
+--Pardon, monsieur le maire....\r
+\r
+Ce mot, monsieur le maire, fit sur la Fantine un effet étrange. Elle se\r
+dressa debout tout d'une pièce comme un spectre qui sort de terre,\r
+repoussa les soldats des deux bras, marcha droit à M. Madeleine avant\r
+qu'on eût pu la retenir, et le regardant fixement, l'air égaré, elle\r
+cria:\r
+\r
+--Ah! c'est donc toi qui es monsieur le maire!\r
+\r
+Puis elle éclata de rire et lui cracha au visage.\r
+\r
+M. Madeleine s'essuya le visage, et dit:\r
+\r
+--Inspecteur Javert, mettez cette femme en liberté.\r
+\r
+Javert se sentit au moment de devenir fou. Il éprouvait en cet instant,\r
+coup sur coup, et presque mêlées ensemble, les plus violentes émotions\r
+qu'il eût ressenties de sa vie. Voir une fille publique cracher au\r
+visage d'un maire, cela était une chose si monstrueuse que, dans ses\r
+suppositions les plus effroyables, il eût regardé comme un sacrilège de\r
+le croire possible. D'un autre côté, dans le fond de sa pensée, il\r
+faisait confusément un rapprochement hideux entre ce qu'était cette\r
+femme et ce que pouvait être ce maire, et alors il entrevoyait avec\r
+horreur je ne sais quoi de tout simple dans ce prodigieux attentat. Mais\r
+quand il vit ce maire, ce magistrat, s'essuyer tranquillement le visage\r
+et dire: _mettez cette femme en liberté_, il eut comme un éblouissement\r
+de stupeur; la pensée et la parole lui manquèrent également; la somme de\r
+l'étonnement possible était dépassée pour lui. Il resta muet.\r
+\r
+Ce mot n'avait pas porté un coup moins étrange à la Fantine. Elle leva\r
+son bras nu et se cramponna à la clef du poêle comme une personne qui\r
+chancelle. Cependant elle regardait tout autour d'elle et elle se mit à\r
+parler à voix basse, comme si elle se parlait à elle-même.\r
+\r
+--En liberté! qu'on me laisse aller! que je n'aille pas en prison six\r
+mois! Qui est-ce qui a dit cela? Il n'est pas possible qu'on ait dit\r
+cela. J'ai mal entendu. Ça ne peut pas être ce monstre de maire! Est-ce\r
+que c'est vous, mon bon monsieur Javert, qui avez dit qu'on me mette en\r
+liberté? Oh! voyez-vous! je vais vous dire et vous me laisserez aller.\r
+Ce monstre de maire, ce vieux gredin de maire, c'est lui qui est cause\r
+de tout. Figurez-vous, monsieur Javert, qu'il m'a chassée! à cause d'un\r
+tas de gueuses qui tiennent des propos dans l'atelier. Si ce n'est pas\r
+là une horreur! renvoyer une pauvre fille qui fait honnêtement son\r
+ouvrage! Alors je n'ai plus gagné assez, et tout le malheur est venu.\r
+D'abord il y a une amélioration que ces messieurs de la police devraient\r
+bien faire, ce serait d'empêcher les entrepreneurs des prisons de faire\r
+du tort aux pauvres gens. Je vais vous expliquer cela, voyez-vous. Vous\r
+gagnez douze sous dans les chemises, cela tombe à neuf sous, il n'y a\r
+plus moyen de vivre. Il faut donc devenir ce qu'on peut. Moi, j'avais ma\r
+petite Cosette, j'ai bien été forcée de devenir une mauvaise femme. Vous\r
+comprenez à présent, que c'est ce gueux de maire qui a tout fait le mal.\r
+Après cela, j'ai piétiné le chapeau de ce monsieur bourgeois devant le\r
+café des officiers. Mais lui, il m'avait perdu toute ma robe avec sa\r
+neige. Nous autres, nous n'avons qu'une robe de soie, pour le soir.\r
+Voyez-vous, je n'ai jamais fait de mal exprès, vrai, monsieur Javert, et\r
+je vois partout des femmes bien plus méchantes que moi qui sont bien\r
+plus heureuses. Ô monsieur Javert, c'est vous qui avez dit qu'on me\r
+mette dehors, n'est-ce pas? Prenez des informations, parlez à mon\r
+propriétaire, maintenant je paye mon terme, on vous dira bien que je\r
+suis honnête. Ah! mon Dieu, je vous demande pardon, j'ai touché, sans\r
+faire attention, à la clef du poêle, et cela fait fumer.\r
+\r
+M. Madeleine l'écoutait avec une attention profonde. Pendant qu'elle\r
+parlait, il avait fouillé dans son gilet, en avait tiré sa bourse et\r
+l'avait ouverte. Elle était vide. Il l'avait remise dans sa poche. Il\r
+dit à la Fantine:\r
+\r
+--Combien avez-vous dit que vous deviez?\r
+\r
+La Fantine, qui ne regardait que Javert, se retourna de son côté:\r
+\r
+--Est-ce que je te parle à toi!\r
+\r
+Puis s'adressant aux soldats:\r
+\r
+--Dites donc, vous autres, avez-vous vu comme je te vous lui ai craché à\r
+la figure? Ah! vieux scélérat de maire, tu viens ici pour me faire peur,\r
+mais je n'ai pas peur de toi. J'ai peur de monsieur Javert. J'ai peur de\r
+mon bon monsieur Javert!\r
+\r
+En parlant ainsi elle se retourna vers l'inspecteur:\r
+\r
+--Avec ça, voyez-vous, monsieur l'inspecteur, il faut être juste. Je\r
+comprends que vous êtes juste, monsieur l'inspecteur. Au fait, c'est\r
+tout simple, un homme qui joue à mettre un peu de neige dans le dos\r
+d'une femme, ça les faisait rire, les officiers, il faut bien qu'on se\r
+divertisse à quelque chose, nous autres nous sommes là pour qu'on\r
+s'amuse, quoi! Et puis, vous, vous venez, vous êtes bien forcé de mettre\r
+l'ordre, vous emmenez la femme qui a tort, mais en y réfléchissant,\r
+comme vous êtes bon, vous dites qu'on me mette en liberté, c'est pour la\r
+petite, parce que six mois en prison, cela m'empêcherait de nourrir mon\r
+enfant. Seulement n'y reviens plus, coquine! Oh! je n'y reviendrai plus,\r
+monsieur Javert! on me fera tout ce qu'on voudra maintenant, je ne\r
+bougerai plus. Seulement, aujourd'hui, voyez-vous, j'ai crié parce que\r
+cela m'a fait mal, je ne m'attendais pas du tout à cette neige de ce\r
+monsieur, et puis, je vous ai dit, je ne me porte pas très bien, je\r
+tousse, j'ai là dans l'estomac comme une boule qui me brûle, que le\r
+médecin me dit: soignez-vous. Tenez, tâtez, donnez votre main, n'ayez\r
+pas peur, c'est ici.\r
+\r
+Elle ne pleurait plus, sa voix était caressante, elle appuyait sur sa\r
+gorge blanche et délicate la grosse main rude de Javert, et elle le\r
+regardait en souriant.\r
+\r
+Tout à coup elle rajusta vivement le désordre de ses vêtements, fit\r
+retomber les plis de sa robe qui en se traînant s'était relevée presque\r
+à la hauteur du genou, et marcha vers la porte en disant à demi-voix aux\r
+soldats avec un signe de tête amical:\r
+\r
+--Les enfants, monsieur l'inspecteur a dit qu'on me lâche, je m'en vas.\r
+\r
+Elle mit la main sur le loquet. Un pas de plus, elle était dans la rue.\r
+\r
+Javert jusqu'à cet instant était resté debout, immobile, l'oeil fixé à\r
+terre, posé de travers au milieu de cette scène comme une statue\r
+dérangée qui attend qu'on la mette quelque part.\r
+\r
+Le bruit que fit le loquet le réveilla. Il releva la tête avec une\r
+expression d'autorité souveraine, expression toujours d'autant plus\r
+effrayante que le pouvoir se trouve placé plus bas, féroce chez la bête\r
+fauve, atroce chez l'homme de rien.\r
+\r
+--Sergent, cria-t-il, vous ne voyez pas que cette drôlesse s'en va! Qui\r
+est-ce qui vous a dit de la laisser aller?\r
+\r
+--Moi, dit Madeleine.\r
+\r
+La Fantine à la voix de Javert avait tremblé et lâché le loquet comme un\r
+voleur pris lâche l'objet volé. À la voix de Madeleine, elle se\r
+retourna, et à partir de ce moment, sans qu'elle prononçât un mot, sans\r
+qu'elle osât même laisser sortir son souffle librement, son regard alla\r
+tour à tour de Madeleine à Javert et de Javert à Madeleine, selon que\r
+c'était l'un ou l'autre qui parlait.\r
+\r
+Il était évident qu'il fallait que Javert eût été, comme on dit, «jeté\r
+hors des gonds» pour qu'il se fût permis d'apostropher le sergent comme\r
+il l'avait fait, après l'invitation du maire de mettre Fantine en\r
+liberté. En était-il venu à oublier la présence de monsieur le maire?\r
+Avait-il fini par se déclarer à lui-même qu'il était impossible qu'une\r
+«autorité» eût donné un pareil ordre, et que bien certainement monsieur\r
+le maire avait dû dire sans le vouloir une chose pour une autre? Ou\r
+bien, devant les énormités dont il était témoin depuis deux heures, se\r
+disait-il qu'il fallait revenir aux suprêmes résolutions, qu'il était\r
+nécessaire que le petit se fit grand, que le mouchard se transformât en\r
+magistrat, que l'homme de police devînt homme de justice, et qu'en cette\r
+extrémité prodigieuse l'ordre, la loi, la morale, le gouvernement, la\r
+société tout entière, se personnifiaient en lui Javert?\r
+\r
+Quoi qu'il en soit, quand M. Madeleine eut dit ce moi qu'on vient\r
+d'entendre, on vit l'inspecteur de police Javert se tourner vers\r
+monsieur le maire, pâle, froid, les lèvres bleues, le regard désespéré,\r
+tout le corps agité d'un tremblement imperceptible, et, chose inouïe,\r
+lui dire, l'oeil baissé, mais la voix ferme:\r
+\r
+--Monsieur le maire, cela ne se peut pas.\r
+\r
+--Comment? dit M. Madeleine.\r
+\r
+--Cette malheureuse a insulté un bourgeois.\r
+\r
+--Inspecteur Javert, repartit M. Madeleine avec un accent conciliant et\r
+calme, écoutez. Vous êtes un honnête homme, et je ne fais nulle\r
+difficulté de m'expliquer avec vous. Voici le vrai. Je passais sur la\r
+place comme vous emmeniez cette femme, il y avait encore des groupes, je\r
+me suis informé, j'ai tout su, c'est le bourgeois qui a eu tort et qui,\r
+en bonne police, eût dû être arrêté.\r
+\r
+Javert reprit:\r
+\r
+--Cette misérable vient d'insulter monsieur le maire.\r
+\r
+--Ceci me regarde, dit M. Madeleine. Mon injure est à moi peut-être.\r
+J'en puis faire ce que je veux.\r
+\r
+--Je demande pardon à monsieur le maire. Son injure n'est pas à lui,\r
+elle est à la justice.\r
+\r
+--Inspecteur Javert, répliqua M. Madeleine, la première justice, c'est\r
+la conscience. J'ai entendu cette femme. Je sais ce que je fais.\r
+\r
+--Et moi, monsieur le maire, je ne sais pas ce que je vois.\r
+\r
+--Alors contentez-vous d'obéir.\r
+\r
+--J'obéis à mon devoir. Mon devoir veut que cette femme fasse six mois\r
+de prison.\r
+\r
+M. Madeleine répondit avec douceur:\r
+\r
+--Écoutez bien ceci. Elle n'en fera pas un jour.\r
+\r
+À cette parole décisive, Javert osa regarder le maire fixement, et lui\r
+dit, mais avec un son de voix toujours profondément respectueux:\r
+\r
+--Je suis au désespoir de résister à monsieur le maire, c'est la\r
+première fois de ma vie, mais il daignera me permettre de lui faire\r
+observer que je suis dans la limite de mes attributions. Je reste,\r
+puisque monsieur le maire le veut, dans le fait du bourgeois. J'étais\r
+là. C'est cette fille qui s'est jetée sur monsieur Bamatabois, qui est\r
+électeur et propriétaire de cette belle maison à balcon qui fait le coin\r
+de l'esplanade, à trois étages et toute en pierre de taille. Enfin, il y\r
+a des choses dans ce monde! Quoi qu'il en soit, monsieur le maire, cela,\r
+c'est un fait de police de la rue qui me regarde, et je retiens la femme\r
+Fantine.\r
+\r
+Alors M. Madeleine croisa les bras et dit avec une voix sévère que\r
+personne dans la ville n'avait encore entendue:\r
+\r
+--Le fait dont vous parlez est un fait de police municipale. Aux termes\r
+des articles neuf, onze, quinze et soixante-six du code d'instruction\r
+criminelle, j'en suis juge. J'ordonne que cette femme soit mise en\r
+liberté.\r
+\r
+Javert voulut tenter un dernier effort.\r
+\r
+--Mais, monsieur le maire....\r
+\r
+--Je vous rappelle, à vous, l'article quatre-vingt-un de la loi du 13\r
+décembre 1799 sur la détention arbitraire.\r
+\r
+--Monsieur le maire, permettez....\r
+\r
+--Plus un mot.\r
+\r
+--Pourtant....\r
+\r
+--Sortez, dit M. Madeleine.\r
+\r
+Javert reçut le coup, debout, de face, et en pleine poitrine comme un\r
+soldat russe. Il salua jusqu'à terre monsieur le maire, et sortit.\r
+\r
+Fantine se rangea de la porte et le regarda avec stupeur passer devant\r
+elle.\r
+\r
+Cependant elle aussi était en proie à un bouleversement étrange. Elle\r
+venait de se voir en quelque sorte disputée par deux puissances\r
+opposées. Elle avait vu lutter devant ses yeux deux hommes tenant dans\r
+leurs mains sa liberté, sa vie, son âme, son enfant; l'un de ces hommes\r
+la tirait du côté de l'ombre, l'autre la ramenait vers la lumière. Dans\r
+cette lutte, entrevue à travers les grossissements de l'épouvante, ces\r
+deux hommes lui étaient apparus comme deux géants; l'un parlait comme\r
+son démon, l'autre parlait comme son bon ange. L'ange avait vaincu le\r
+démon, et, chose qui la faisait frissonner de la tête aux pieds, cet\r
+ange, ce libérateur, c'était précisément l'homme qu'elle abhorrait, ce\r
+maire qu'elle avait si longtemps considéré comme l'auteur de tous ses\r
+maux, ce Madeleine! et au moment même où elle venait de l'insulter d'une\r
+façon hideuse, il la sauvait! S'était-elle donc trompée? Devait-elle\r
+donc changer toute son âme?... Elle ne savait, elle tremblait. Elle\r
+écoutait éperdue, elle regardait effarée, et à chaque parole que disait\r
+M. Madeleine, elle sentait fondre et s'écrouler en elle les affreuses\r
+ténèbres de la haine et naître dans son coeur je ne sais quoi de\r
+réchauffant et d'ineffable qui était de la joie, de la confiance et de\r
+l'amour.\r
+\r
+Quand Javert fut sorti, M. Madeleine se tourna vers elle, et lui dit\r
+avec une voix lente, ayant peine à parler comme un homme sérieux qui ne\r
+veut pas pleurer:\r
+\r
+--Je vous ai entendue. Je ne savais rien de ce que vous avez dit. Je\r
+crois que c'est vrai, et je sens que c'est vrai. J'ignorais même que\r
+vous eussiez quitté mes ateliers. Pourquoi ne vous êtes-vous pas\r
+adressée à moi? Mais voici: je payerai vos dettes, je ferai venir votre\r
+enfant, ou vous irez la rejoindre. Vous vivrez ici, à Paris, où vous\r
+voudrez. Je me charge de votre enfant et de vous. Vous ne travaillerez\r
+plus, si vous voulez. Je vous donnerai tout l'argent qu'il vous faudra.\r
+Vous redeviendrez honnête en redevenant heureuse. Et même, écoutez, je\r
+vous le déclare dès à présent, si tout est comme vous le dites, et je\r
+n'en doute pas, vous n'avez jamais cessé d'être vertueuse et sainte\r
+devant Dieu. Oh! pauvre femme!\r
+\r
+C'en était plus que la pauvre Fantine n'en pouvait supporter. Avoir\r
+Cosette! sortir de cette vie infâme! vivre libre, riche, heureuse,\r
+honnête, avec Cosette! voir brusquement s'épanouir au milieu de sa\r
+misère toutes ces réalités du paradis! Elle regarda comme hébétée cet\r
+homme qui lui parlait, et ne put que jeter deux ou trois sanglots: oh!\r
+oh! oh! Ses jarrets plièrent, elle se mit à genoux devant M. Madeleine,\r
+et, avant qu'il eût pu l'en empêcher, il sentit qu'elle lui prenait la\r
+main et que ses lèvres s'y posaient.\r
+\r
+Puis elle s'évanouit.\r
+\r
+\r
+\r
+\r
+Livre sixième--Javert\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+Commencement du repos\r
+\r
+\r
+M. Madeleine fit transporter la Fantine à cette infirmerie qu'il avait\r
+dans sa propre maison. Il la confia aux soeurs qui la mirent au lit. Une\r
+fièvre ardente était survenue. Elle passa une partie de la nuit à\r
+délirer et à parler haut. Cependant elle finit par s'endormir.\r
+\r
+Le lendemain vers midi Fantine se réveilla, elle entendit une\r
+respiration tout près de son lit, elle écarta son rideau et vit M.\r
+Madeleine debout qui regardait quelque chose au-dessus de sa tête. Ce\r
+regard était plein de pitié et d'angoisse et suppliait. Elle en suivit\r
+la direction et vit qu'il s'adressait à un crucifix cloué au mur.\r
+\r
+M. Madeleine était désormais transfiguré aux yeux de Fantine. Il lui\r
+paraissait enveloppé de lumière. Il était absorbé dans une sorte de\r
+prière. Elle le considéra longtemps sans oser l'interrompre. Enfin elle\r
+lui dit timidement:\r
+\r
+--Que faites-vous donc là?\r
+\r
+M. Madeleine était à cette place depuis une heure. Il attendait que\r
+Fantine se réveillât. Il lui prit la main, lui tâta le pouls, et\r
+répondit:\r
+\r
+--Comment êtes-vous?\r
+\r
+--Bien, j'ai dormi, dit-elle, je crois que je vais mieux. Ce ne sera\r
+rien.\r
+\r
+Lui reprit, répondant à la question qu'elle lui avait adressée d'abord,\r
+comme s'il ne faisait que de l'entendre:\r
+\r
+--Je priais le martyr qui est là-haut.\r
+\r
+Et il ajouta dans sa pensée: «Pour la martyre qui est ici-bas.»\r
+\r
+M. Madeleine avait passé la nuit et la matinée à s'informer. Il savait\r
+tout maintenant. Il connaissait dans tous ses poignants détails\r
+l'histoire de Fantine. Il continua:\r
+\r
+--Vous avez bien souffert, pauvre mère. Oh! ne vous plaignez pas, vous\r
+avez à présent la dot des élus. C'est de cette façon que les hommes font\r
+des anges. Ce n'est point leur faute; ils ne savent pas s'y prendre\r
+autrement. Voyez-vous, cet enfer dont vous sortez est la première forme\r
+du ciel. Il fallait commencer par là.\r
+\r
+Il soupira profondément. Elle cependant lui souriait avec ce sublime\r
+sourire auquel il manquait deux dents.\r
+\r
+Javert dans cette même nuit avait écrit une lettre. Il remit lui-même\r
+cette lettre le lendemain matin au bureau de poste de Montreuil-sur-mer.\r
+Elle était pour Paris, et la suscription portait: À _monsieur\r
+Chabouillet, secrétaire de monsieur le préfet de police_. Comme\r
+l'affaire du corps de garde s'était ébruitée, la directrice du bureau de\r
+poste et quelques autres personnes qui virent la lettre avant le départ\r
+et qui reconnurent l'écriture de Javert sur l'adresse, pensèrent que\r
+c'était sa démission qu'il envoyait.\r
+\r
+M. Madeleine se hâta d'écrire aux Thénardier. Fantine leur devait cent\r
+vingt francs. Il leur envoya trois cents francs en leur disant de se\r
+payer sur cette somme, et d'amener tout de suite l'enfant à\r
+Montreuil-sur-mer où sa mère malade la réclamait.\r
+\r
+Ceci éblouit le Thénardier.\r
+\r
+--Diable! dit-il à sa femme, ne lâchons pas l'enfant. Voilà que cette\r
+mauviette va devenir une vache à lait. Je devine. Quelque jocrisse se\r
+sera amouraché de la mère.\r
+\r
+Il riposta par un mémoire de cinq cents et quelques francs fort bien\r
+fait. Dans ce mémoire figuraient pour plus de trois cents francs deux\r
+notes incontestables, l'une d'un médecin, l'autre d'un apothicaire,\r
+lesquels avaient soigné et médicamenté dans deux longues maladies\r
+Éponine et Azelma. Cosette, nous l'avons dit, n'avait pas été malade. Ce\r
+fut l'affaire d'une toute petite substitution de noms. Thénardier mit au\r
+bas du mémoire: _reçu à compte trois cents francs_.\r
+\r
+M. Madeleine envoya tout de suite trois cents autres francs et écrivit:\r
+Dépêchez-vous d'amener Cosette.\r
+\r
+--Christi! dit le Thénardier, ne lâchons pas l'enfant.\r
+\r
+Cependant Fantine ne se rétablissait point. Elle était toujours à\r
+l'infirmerie. Les soeurs n'avaient d'abord reçu et soigné «cette fille»\r
+qu'avec répugnance. Qui a vu les bas-reliefs de Reims se souvient du\r
+gonflement de la lèvre inférieure des vierges sages regardant les\r
+vierges folles. Cet antique mépris des vestales pour les ambulaïes est\r
+un des plus profonds instincts de la dignité féminine; les soeurs\r
+l'avaient éprouvé, avec le redoublement qu'ajoute la religion. Mais, en\r
+peu de jours, Fantine les avait désarmées. Elle avait toutes sortes de\r
+paroles humbles et douces, et la mère qui était en elle attendrissait.\r
+Un jour les soeurs l'entendirent qui disait à travers la fièvre:\r
+\r
+--J'ai été une pécheresse, mais quand j'aurai mon enfant près de moi,\r
+cela voudra dire que Dieu m'a pardonné. Pendant que j'étais dans le mal,\r
+je n'aurais pas voulu avoir ma Cosette avec moi, je n'aurais pas pu\r
+supporter ses yeux étonnés et tristes. C'était pour elle pourtant que je\r
+faisais le mal, et c'est ce qui fait que Dieu me pardonne. Je sentirai\r
+la bénédiction du bon Dieu quand Cosette sera ici. Je la regarderai,\r
+cela me fera du bien de voir cette innocente. Elle ne sait rien du tout.\r
+C'est un ange, voyez-vous, mes soeurs. À cet âge-là, les ailes, ça n'est\r
+pas encore tombé.\r
+\r
+M. Madeleine l'allait voir deux fois par jour, et chaque fois elle lui\r
+demandait:\r
+\r
+--Verrai-je bientôt ma Cosette?\r
+\r
+Il lui répondait:\r
+\r
+--Peut-être demain matin. D'un moment à l'autre elle arrivera, je\r
+l'attends.\r
+\r
+Et le visage pâle de la mère rayonnait.\r
+\r
+--Oh! disait-elle, comme je vais être heureuse!\r
+\r
+Nous venons de dire qu'elle ne se rétablissait pas. Au contraire, son\r
+état semblait s'aggraver de semaine en semaine. Cette poignée de neige\r
+appliquée à nu sur la peau entre les deux omoplates avait déterminé une\r
+suppression subite de transpiration à la suite de laquelle la maladie\r
+qu'elle couvait depuis plusieurs années finit par se déclarer\r
+violemment. On commençait alors à suivre pour l'étude et le traitement\r
+des maladies de poitrine les belles indications de Laennec. Le médecin\r
+ausculta Fantine et hocha la tête.\r
+\r
+M. Madeleine dit au médecin:\r
+\r
+--Eh bien?\r
+\r
+--N'a-t-elle pas un enfant qu'elle désire voir? dit le médecin.\r
+\r
+--Oui.\r
+\r
+--Eh bien, hâtez-vous de le faire venir.\r
+\r
+M. Madeleine eut un tressaillement.\r
+\r
+Fantine lui demanda:\r
+\r
+--Qu'a dit le médecin?\r
+\r
+M. Madeleine s'efforça de sourire.\r
+\r
+--Il a dit de faire venir bien vite votre enfant. Que cela vous rendra\r
+la santé.\r
+\r
+--Oh! reprit-elle, il a raison! Mais qu'est-ce qu'ils ont donc ces\r
+Thénardier à me garder ma Cosette! Oh! elle va venir. Voici enfin que je\r
+vois le bonheur tout près de moi!\r
+\r
+Le Thénardier cependant ne «lâchait pas l'enfant» et donnait cent\r
+mauvaises raisons. Cosette était un peu souffrante pour se mettre en\r
+route l'hiver. Et puis il y avait un reste de petites dettes criardes\r
+dans le pays dont il rassemblait les factures, etc., etc.\r
+\r
+--J'enverrai quelqu'un chercher Cosette, dit le père Madeleine. S'il le\r
+faut, j'irai moi-même.\r
+\r
+Il écrivit sous la dictée de Fantine cette lettre qu'il lui fit signer:\r
+\r
+«Monsieur Thénardier,\r
+\r
+«Vous remettrez Cosette à la personne.\r
+\r
+«On vous payera toutes les petites choses.\r
+\r
+«J'ai l'honneur de vous saluer avec considération.\r
+\r
+«Fantine.»\r
+\r
+Sur ces entrefaites, il survint un grave incident. Nous avons beau\r
+tailler de notre mieux le bloc mystérieux dont notre vie est faite, la\r
+veine noire de la destinée y reparaît toujours.\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+Comment Jean peut devenir Champ\r
+\r
+\r
+Un matin, M. Madeleine était dans son cabinet, occupé à régler d'avance\r
+quelques affaires pressantes de la mairie pour le cas où il se\r
+déciderait à ce voyage de Montfermeil, lorsqu'on vint lui dire que\r
+l'inspecteur de police Javert demandait à lui parler. En entendant\r
+prononcer ce nom, M. Madeleine ne put se défendre d'une impression\r
+désagréable. Depuis l'aventure du bureau de police, Javert l'avait plus\r
+que jamais évité, et M. Madeleine ne l'avait point revu.\r
+\r
+--Faites entrer, dit-il.\r
+\r
+Javert entra.\r
+\r
+M. Madeleine était resté assis près de la cheminée, une plume à la main,\r
+l'oeil sur un dossier qu'il feuilletait et qu'il annotait, et qui\r
+contenait des procès-verbaux de contraventions à la police de la voirie.\r
+Il ne se dérangea point pour Javert. Il ne pouvait s'empêcher de songer\r
+à la pauvre Fantine, et il lui convenait d'être glacial.\r
+\r
+Javert salua respectueusement M. le maire qui lui tournait le dos. M. le\r
+maire ne le regarda pas et continua d'annoter son dossier.\r
+\r
+Javert fit deux ou trois pas dans le cabinet, et s'arrêta sans rompre le\r
+silence. Un physionomiste qui eût été familier avec la nature de Javert,\r
+qui eût étudié depuis longtemps ce sauvage au service de la\r
+civilisation, ce composé bizarre du Romain, du Spartiate, du moine et du\r
+caporal, cet espion incapable d'un mensonge, ce mouchard vierge, un\r
+physionomiste qui eût su sa secrète et ancienne aversion pour M.\r
+Madeleine, son conflit avec le maire au sujet de la Fantine, et qui eût\r
+considéré Javert en ce moment, se fût dit: que s'est-il passé? Il était\r
+évident, pour qui eût connu cette conscience droite, claire, sincère,\r
+probe, austère et féroce, que Javert sortait de quelque grand événement\r
+intérieur. Javert n'avait rien dans l'âme qu'il ne l'eût aussi sur le\r
+visage. Il était, comme les gens violents, sujet aux revirements\r
+brusques. Jamais sa physionomie n'avait été plus étrange et plus\r
+inattendue. En entrant, il s'était incliné devant M. Madeleine avec un\r
+regard où il n'y avait ni rancune, ni colère, ni défiance, il s'était\r
+arrêté à quelques pas derrière le fauteuil du maire; et maintenant il se\r
+tenait là, debout, dans une attitude presque disciplinaire, avec la\r
+rudesse naïve et froide d'un homme qui n'a jamais été doux et qui a\r
+toujours été patient; il attendait, sans dire un mot, sans faire un\r
+mouvement, dans une humilité vraie et dans une résignation tranquille,\r
+qu'il plût à monsieur le maire de se retourner, calme, sérieux, le\r
+chapeau à la main, les yeux baissés, avec une expression qui tenait le\r
+milieu entre le soldat devant son officier et le coupable devant son\r
+juge. Tous les sentiments comme tous les souvenirs qu'on eût pu lui\r
+supposer avaient disparu. Il n'y avait plus rien sur ce visage\r
+impénétrable et simple comme le granit, qu'une morne tristesse. Toute sa\r
+personne respirait l'abaissement et la fermeté, et je ne sais quel\r
+accablement courageux.\r
+\r
+Enfin M. le maire posa sa plume et se tourna à demi.\r
+\r
+--Eh bien! qu'est-ce? qu'y a-t-il, Javert?\r
+\r
+Javert demeura un instant silencieux comme s'il se recueillait, puis\r
+éleva la voix avec une sorte de solennité triste qui n'excluait pourtant\r
+pas la simplicité:\r
+\r
+--Il y a, monsieur le maire, qu'un acte coupable a été commis.\r
+\r
+--Quel acte?\r
+\r
+--Un agent inférieur de l'autorité a manqué de respect à un magistrat de\r
+la façon la plus grave. Je viens, comme c'est mon devoir, porter le fait\r
+à votre connaissance.\r
+\r
+--Quel est cet agent? demanda M. Madeleine.\r
+\r
+--Moi, dit Javert.\r
+\r
+--Vous?\r
+\r
+--Moi.\r
+\r
+--Et quel est le magistrat qui aurait à se plaindre de l'agent?\r
+\r
+--Vous, monsieur le maire.\r
+\r
+M. Madeleine se dressa sur son fauteuil. Javert poursuivit, l'air sévère\r
+et les yeux toujours baissés:\r
+\r
+--Monsieur le maire, je viens vous prier de vouloir bien provoquer près\r
+de l'autorité ma destitution.\r
+\r
+M. Madeleine stupéfait ouvrit la bouche. Javert l'interrompit.\r
+\r
+--Vous direz, j'aurais pu donner ma démission, mais cela ne suffit pas.\r
+Donner sa démission, c'est honorable. J'ai failli, je dois être puni. Il\r
+faut que je sois chassé.\r
+\r
+Et après une pause, il ajouta:\r
+\r
+--Monsieur le maire, vous avez été sévère pour moi l'autre jour\r
+injustement. Soyez-le aujourd'hui justement.\r
+\r
+--Ah çà! pourquoi? s'écria M. Madeleine. Quel est ce galimatias?\r
+qu'est-ce que cela veut dire? où y a-t-il un acte coupable commis contre\r
+moi par vous? qu'est-ce que vous m'avez fait? quels torts avez-vous\r
+envers moi? Vous vous accusez, vous voulez être remplacé....\r
+\r
+--Chassé, dit Javert.\r
+\r
+--Chassé, soit. C'est fort bien. Je ne comprends pas.\r
+\r
+--Vous allez comprendre, monsieur le maire.\r
+\r
+Javert soupira du fond de sa poitrine et reprit toujours froidement et\r
+tristement:\r
+\r
+--Monsieur le maire, il y a six semaines, à la suite de cette scène pour\r
+cette fille, j'étais furieux, je vous ai dénoncé.\r
+\r
+--Dénoncé!\r
+\r
+--À la préfecture de police de Paris.\r
+\r
+M. Madeleine, qui ne riait pas beaucoup plus souvent que Javert, se mit\r
+à rire.\r
+\r
+--Comme maire ayant empiété sur la police?\r
+\r
+--Comme ancien forçat.\r
+\r
+Le maire devint livide.\r
+\r
+Javert, qui n'avait pas levé les yeux, continua:\r
+\r
+--Je le croyais. Depuis longtemps j'avais des idées.\r
+\r
+Une ressemblance, des renseignements que vous avez fait prendre à\r
+Faverolles, votre force des reins, l'aventure du vieux Fauchelevent,\r
+votre adresse au tir, votre jambe qui traîne un peu, est-ce que je sais,\r
+moi? des bêtises! mais enfin je vous prenais pour un nommé Jean Valjean.\r
+\r
+--Un nommé?... Comment dites-vous ce nom-là?\r
+\r
+--Jean Valjean. C'est un forçat que j'avais vu il y a vingt ans quand\r
+j'étais adjudant-garde-chiourme à Toulon. En sortant du bagne, ce Jean\r
+Valjean avait, à ce qu'il paraît, volé chez un évêque, puis il avait\r
+commis un autre vol à main armée, dans un chemin public, sur un petit\r
+savoyard. Depuis huit ans il s'était dérobé, on ne sait comment, et on\r
+le cherchait. Moi je m'étais figuré... Enfin, j'ai fait cette chose! La\r
+colère m'a décidé, je vous ai dénoncé à la préfecture.\r
+\r
+M. Madeleine, qui avait ressaisi le dossier depuis quelques instants,\r
+reprit avec un accent de parfaite indifférence:\r
+\r
+--Et que vous a-t-on répondu?\r
+\r
+--Que j'étais fou.\r
+\r
+--Eh bien?\r
+\r
+--Eh bien, on avait raison.\r
+\r
+--C'est heureux que vous le reconnaissiez!\r
+\r
+--Il faut bien, puisque le véritable Jean Valjean est trouvé.\r
+\r
+La feuille que tenait M. Madeleine lui échappa des mains, il leva la\r
+tête, regarda fixement Javert, et dit avec un accent inexprimable:\r
+\r
+--Ah!\r
+\r
+Javert poursuivit:\r
+\r
+--Voilà ce que c'est, monsieur le maire. Il paraît qu'il y avait dans le\r
+pays, du côté d'Ailly-le-Haut-Clocher, une espèce de bonhomme qu'on\r
+appelait le père Champmathieu. C'était très misérable. On n'y faisait\r
+pas attention. Ces gens-là, on ne sait pas de quoi cela vit.\r
+Dernièrement, cet automne, le père Champmathieu a été arrêté pour un vol\r
+de pommes à cidre, commis chez...--enfin n'importe! Il y a eu vol, mur\r
+escaladé, branches de l'arbre cassées. On a arrêté mon Champmathieu. Il\r
+avait encore la branche de pommier à la main. On coffre le drôle.\r
+Jusqu'ici ce n'est pas beaucoup plus qu'une affaire correctionnelle.\r
+Mais voici qui est de la providence. La geôle étant en mauvais état,\r
+monsieur le juge d'instruction trouve à propos de faire transférer\r
+Champmathieu à Arras où est la prison départementale. Dans cette prison\r
+d'Arras, il y a un ancien forçat nommé Brevet qui est détenu pour je ne\r
+sais quoi et qu'on a fait guichetier de chambrée parce qu'il se conduit\r
+bien. Monsieur le maire, Champmathieu n'est pas plus tôt débarqué que\r
+voilà Brevet qui s'écrie: «Eh mais! je connais cet homme-là. C'est un\r
+fagot. Regardez-moi donc, bonhomme! Vous êtes Jean Valjean!--Jean\r
+Valjean! qui ça Jean Valjean? Le Champmathieu joue l'étonné.--Ne fais\r
+donc pas le sinvre, dit Brevet. Tu es Jean Valjean! Tu as été au bagne\r
+de Toulon. Il y a vingt ans. Nous y étions ensemble.--Le Champmathieu\r
+nie. Parbleu! vous comprenez. On approfondit. On me fouille cette\r
+aventure-là. Voici ce qu'on trouve: ce Champmathieu, il y a une\r
+trentaine d'années, a été ouvrier émondeur d'arbres dans plusieurs pays,\r
+notamment à Faverolles. Là on perd sa trace. Longtemps après, on le\r
+revoit en Auvergne, puis à Paris, où il dit avoir été charron et avoir\r
+eu une fille blanchisseuse, mais cela n'est pas prouvé; enfin dans ce\r
+pays-ci. Or, avant d'aller au bagne pour vol qualifié, qu'était Jean\r
+Valjean? émondeur. Où? à Faverolles. Autre fait. Ce Valjean s'appelait\r
+de son nom de baptême Jean et sa mère se nommait de son nom de famille\r
+Mathieu. Quoi de plus naturel que de penser qu'en sortant du bagne il\r
+aura pris le nom de sa mère pour se cacher et se sera fait appeler Jean\r
+Mathieu? Il va en Auvergne. De _Jean_ la prononciation du pays fait\r
+_Chan_, on l'appelle Chan Mathieu. Notre homme se laisse faire et le\r
+voilà transformé en Champmathieu. Vous me suivez, n'est-ce pas? On\r
+s'informe à Faverolles. La famille de Jean Valjean n'y est plus. On ne\r
+sait plus où elle est. Vous savez, dans ces classes-là, il y a souvent\r
+de ces évanouissements d'une famille. On cherche, on ne trouve plus\r
+rien. Ces gens-là, quand ce n'est pas de la boue, c'est de la poussière.\r
+Et puis, comme le commencement de ces histoires date de trente ans, il\r
+n'y a plus personne à Faverolles qui ait connu Jean Valjean. On\r
+s'informe à Toulon. Avec Brevet, il n'y a plus que deux forçats qui\r
+aient vu Jean Valjean. Ce sont les condamnés à vie Cochepaille et\r
+Chenildieu. On les extrait du bagne et on les fait venir. On les\r
+confronte au prétendu Champmathieu. Ils n'hésitent pas. Pour eux comme\r
+pour Brevet, c'est Jean Valjean. Même âge, il a cinquante-quatre ans,\r
+même taille, même air, même homme enfin, c'est lui. C'est en ce\r
+moment-là même que j'envoyais ma dénonciation à la préfecture de Paris.\r
+On me répond que je perds l'esprit et que Jean Valjean est à Arras au\r
+pouvoir de la justice. Vous concevez si cela m'étonne, moi qui croyais\r
+tenir ici ce même Jean Valjean! J'écris à monsieur le juge\r
+d'instruction. Il me fait venir, on m'amène le Champmathieu....\r
+\r
+--Eh bien? interrompit M. Madeleine.\r
+\r
+Javert répondit avec son visage incorruptible et triste:\r
+\r
+--Monsieur le maire, la vérité est la vérité. J'en suis fâché, mais\r
+c'est cet homme-là qui est Jean Valjean. Moi aussi je l'ai reconnu.\r
+\r
+M. Madeleine reprit d'une voix très basse:\r
+\r
+--Vous êtes sûr?\r
+\r
+Javert se mit à rire de ce rire douloureux qui échappe à une conviction\r
+profonde:\r
+\r
+--Oh, sûr!\r
+\r
+Il demeura un moment pensif, prenant machinalement des pincées de poudre\r
+de bois dans la sébille à sécher l'encre qui était sur la table, et il\r
+ajouta:\r
+\r
+--Et même, maintenant que je vois le vrai Jean Valjean, je ne comprends\r
+pas comment j'ai pu croire autre chose. Je vous demande pardon, monsieur\r
+le maire.\r
+\r
+En adressant cette parole suppliante et grave à celui qui, six semaines\r
+auparavant, l'avait humilié en plein corps de garde et lui avait dit:\r
+«sortez!» Javert, cet homme hautain, était à son insu plein de\r
+simplicité et de dignité. M. Madeleine ne répondit à sa prière que par\r
+cette question brusque:\r
+\r
+--Et que dit cet homme?\r
+\r
+--Ah, dame! monsieur le maire, l'affaire est mauvaise. Si c'est Jean\r
+Valjean, il y a récidive. Enjamber un mur, casser une branche, chiper\r
+des pommes, pour un enfant, c'est une polissonnerie; pour un homme,\r
+c'est un délit; pour un forçat, c'est un crime. Escalade et vol, tout y\r
+est. Ce n'est plus la police correctionnelle, c'est la cour d'assises.\r
+Ce n'est plus quelques jours de prison, ce sont les galères à\r
+perpétuité. Et puis, il y a l'affaire du petit savoyard que j'espère\r
+bien qui reviendra. Diable! il y a de quoi se débattre, n'est-ce pas?\r
+Oui, pour un autre que Jean Valjean. Mais Jean Valjean est un sournois.\r
+C'est encore là que je le reconnais. Un autre sentirait que cela\r
+chauffe; il se démènerait, il crierait, la bouilloire chante devant le\r
+feu, il ne voudrait pas être Jean Valjean, et caetera. Lui, il n'a pas\r
+l'air de comprendre, il dit: Je suis Champmathieu, je ne sors pas de là!\r
+Il a l'air étonné, il fait la brute, c'est bien mieux. Oh! le drôle est\r
+habile. Mais c'est égal, les preuves sont là. Il est reconnu par quatre\r
+personnes, le vieux coquin sera condamné. C'est porté aux assises, à\r
+Arras. Je vais y aller pour témoigner. Je suis cité.\r
+\r
+M. Madeleine s'était remis à son bureau, avait ressaisi son dossier, et\r
+le feuilletait tranquillement, lisant et écrivant tour à tour comme un\r
+homme affairé. Il se tourna vers Javert:\r
+\r
+--Assez, Javert. Au fait, tous ces détails m'intéressent fort peu. Nous\r
+perdons notre temps, et nous avons des affaires pressées. Javert, vous\r
+allez vous rendre sur-le-champ chez la bonne femme Buseaupied qui vend\r
+des herbes là-bas au coin de la rue Saint-Saulve. Vous lui direz de\r
+déposer sa plainte contre le charretier Pierre Chesnelong. Cet homme est\r
+un brutal qui a failli écraser cette femme et son enfant. Il faut qu'il\r
+soit puni. Vous irez ensuite chez M. Charcellay, rue\r
+Montre-de-Champigny. Il se plaint qu'il y a une gouttière de la maison\r
+voisine qui verse l'eau de la pluie chez lui, et qui affouille les\r
+fondations de sa maison. Après vous constaterez des contraventions de\r
+police qu'on me signale rue Guibourg chez la veuve Doris, et rue du\r
+Garraud-Blanc chez madame Renée Le Bossé, et vous dresserez\r
+procès-verbal. Mais je vous donne là beaucoup de besogne. N'allez-vous\r
+pas être absent? ne m'avez-vous pas dit que vous alliez à Arras pour\r
+cette affaire dans huit ou dix jours?...\r
+\r
+--Plus tôt que cela, monsieur le maire.\r
+\r
+--Quel jour donc?\r
+\r
+--Mais je croyais avoir dit à monsieur le maire que cela se jugeait\r
+demain et que je partais par la diligence cette nuit.\r
+\r
+M. Madeleine fit un mouvement imperceptible.\r
+\r
+--Et combien de temps durera l'affaire?\r
+\r
+--Un jour tout au plus. L'arrêt sera prononcé au plus tard demain dans\r
+la nuit. Mais je n'attendrai pas l'arrêt, qui ne peut manquer. Sitôt ma\r
+déposition faite, je reviendrai ici.\r
+\r
+--C'est bon, dit M. Madeleine.\r
+\r
+Et il congédia Javert d'un signe de main. Javert ne s'en alla pas.\r
+\r
+--Pardon, monsieur le maire, dit-il.\r
+\r
+--Qu'est-ce encore? demanda M. Madeleine.\r
+\r
+--Monsieur le maire, il me reste une chose à vous rappeler.\r
+\r
+--Laquelle?\r
+\r
+--C'est que je dois être destitué.\r
+\r
+M. Madeleine se leva.\r
+\r
+--Javert, vous êtes un homme d'honneur, et je vous estime. Vous vous\r
+exagérez votre faute. Ceci d'ailleurs est encore une offense qui me\r
+concerne. Javert, vous êtes digne de monter et non de descendre.\r
+J'entends que vous gardiez votre place.\r
+\r
+Javert regarda M. Madeleine avec sa prunelle candide au fond de laquelle\r
+il semblait qu'on vit cette conscience peu éclairée, mais rigide et\r
+chaste, et il dit d'une voix tranquille:\r
+\r
+--Monsieur le maire, je ne puis vous accorder cela.\r
+\r
+--Je vous répète, répliqua M. Madeleine, que la chose me regarde.\r
+\r
+Mais Javert, attentif à sa seule pensée, continua:\r
+\r
+--Quant à exagérer, je n'exagère point. Voici comment je raisonne. Je\r
+vous ai soupçonné injustement. Cela, ce n'est rien. C'est notre droit à\r
+nous autres de soupçonner, quoiqu'il y ait pourtant abus à soupçonner\r
+au-dessus de soi. Mais, sans preuves, dans un accès de colère, dans le\r
+but de me venger, je vous ai dénoncé comme forçat, vous, un homme\r
+respectable, un maire, un magistrat! ceci est grave. Très grave. J'ai\r
+offensé l'autorité dans votre personne, moi, agent de l'autorité! Si\r
+l'un de mes subordonnés avait fait ce que j'ai fait, je l'aurais déclaré\r
+indigne du service, et chassé. Eh bien?\r
+\r
+Tenez, monsieur le maire, encore un mot. J'ai souvent été sévère dans ma\r
+vie. Pour les autres. C'était juste. Je faisais bien. Maintenant, si je\r
+n'étais pas sévère pour moi, tout ce que j'ai fait de juste deviendrait\r
+injuste.\r
+\r
+Est-ce que je dois m'épargner plus que les autres? Non. Quoi! je\r
+n'aurais été bon qu'à châtier autrui, et pas moi! mais je serais un\r
+misérable! mais ceux qui disent: ce gueux de Javert! auraient raison!\r
+Monsieur le maire, je ne souhaite pas que vous me traitiez avec bonté,\r
+votre bonté m'a fait faire assez de mauvais sang quand elle était pour\r
+les autres. Je n'en veux pas pour moi. La bonté qui consiste à donner\r
+raison à la fille publique contre le bourgeois, à l'agent de police\r
+contre le maire, à celui qui est en bas contre celui qui est en haut,\r
+c'est ce que j'appelle de la mauvaise bonté. C'est avec cette bonté-là\r
+que la société se désorganise. Mon Dieu! c'est bien facile d'être bon,\r
+le malaisé c'est d'être juste. Allez! si vous aviez été ce que je\r
+croyais, je n'aurais pas été bon pour vous, moi! vous auriez vu!\r
+Monsieur le maire, je dois me traiter comme je traiterais tout autre.\r
+Quand je réprimais des malfaiteurs, quand je sévissais sur des gredins,\r
+je me suis souvent dit à moi-même: toi, si tu bronches, si jamais je te\r
+prends en faute, sois tranquille!--J'ai bronché, je me prends en faute,\r
+tant pis! Allons, renvoyé, cassé, chassé! c'est bon. J'ai des bras, je\r
+travaillerai à la terre, cela m'est égal. Monsieur le maire, le bien du\r
+service veut un exemple. Je demande simplement la destitution de\r
+l'inspecteur Javert.\r
+\r
+Tout cela était prononcé d'un accent humble, fier, désespéré et\r
+convaincu qui donnait je ne sais quelle grandeur bizarre à cet étrange\r
+honnête homme.\r
+\r
+--Nous verrons, fit M. Madeleine.\r
+\r
+Et il lui tendit la main.\r
+\r
+Javert recula, et dit d'un ton farouche:\r
+\r
+--Pardon, monsieur le maire, mais cela ne doit pas être. Un maire ne\r
+donne pas la main à un mouchard.\r
+\r
+Il ajouta entre ses dents:\r
+\r
+--Mouchard, oui; du moment où j'ai médusé de la police, je ne suis plus\r
+qu'un mouchard. Puis il salua profondément, et se dirigea vers la porte.\r
+Là il se retourna, et, les yeux toujours baissés:\r
+\r
+--Monsieur le maire, dit-il, je continuerai le service jusqu'à ce que je\r
+sois remplacé.\r
+\r
+Il sortit. M. Madeleine resta rêveur, écoutant ce pas ferme et assuré\r
+qui s'éloignait sur le pavé du corridor.\r
+\r
+\r
+\r
+\r
+Livre septième--L'affaire Champmathieu\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+La soeur Simplice\r
+\r
+\r
+Les incidents qu'on va lire n'ont pas tous été connus à\r
+Montreuil-sur-mer, mais le peu qui en a percé a laissé dans cette ville\r
+un tel souvenir, que ce serait une grave lacune dans ce livre si nous ne\r
+les racontions dans leurs moindres détails.\r
+\r
+Dans ces détails, le lecteur rencontrera deux ou trois circonstances\r
+invraisemblables que nous maintenons par respect pour la vérité.\r
+\r
+Dans l'après-midi qui suivit la visite de Javert, M. Madeleine alla voir\r
+la Fantine comme d'habitude.\r
+\r
+Avant de pénétrer près de Fantine, il fit demander la soeur Simplice.\r
+Les deux religieuses qui faisaient le service de l'infirmerie, dames\r
+lazaristes comme toutes les soeurs de charité, s'appelaient soeur\r
+Perpétue et soeur Simplice.\r
+\r
+La soeur Perpétue était la première villageoise venue, grossièrement\r
+soeur de charité, entrée chez Dieu comme on entre en place. Elle était\r
+religieuse comme on est cuisinière. Ce type n'est point très rare. Les\r
+ordres monastiques acceptent volontiers cette lourde poterie paysanne,\r
+aisément façonnée en capucin ou en ursuline. Ces rusticités s'utilisent\r
+pour les grosses besognes de la dévotion. La transition d'un bouvier à\r
+un carme n'a rien de heurté; l'un devient l'autre sans grand travail; le\r
+fond commun d'ignorance du village et du cloître est une préparation\r
+toute faite, et met tout de suite le campagnard de plain-pied avec le\r
+moine. Un peu d'ampleur au sarrau, et voilà un froc. La soeur Perpétue\r
+était une forte religieuse, de Marines, près Pontoise, patoisant,\r
+psalmodiant, bougonnant, sucrant la tisane selon le bigotisme ou\r
+l'hypocrisie du grabataire, brusquant les malades, bourrue avec les\r
+mourants, leur jetant presque Dieu au visage, lapidant l'agonie avec des\r
+prières en colère, hardie, honnête et rougeaude.\r
+\r
+La soeur Simplice était blanche d'une blancheur de cire. Près de soeur\r
+Perpétue, c'était le cierge à côté de la chandelle. Vincent de Paul a\r
+divinement fixé la figure de la soeur de charité dans ces admirables\r
+paroles où il mêle tant de liberté à tant de servitude: «Elles n'auront\r
+pour monastère que la maison des malades, pour cellule qu'une chambre de\r
+louage, pour chapelle que l'église de leur paroisse, pour cloître que\r
+les rues de la ville ou les salles des hôpitaux, pour clôture que\r
+l'obéissance, pour grille que la crainte de Dieu, pour voile que la\r
+modestie.» Cet idéal était vivant dans la soeur Simplice. Personne n'eût\r
+pu dire l'âge de la soeur Simplice; elle n'avait jamais été jeune et\r
+semblait ne devoir jamais être vieille. C'était une personne--nous\r
+n'osons dire une femme--calme, austère, de bonne compagnie, froide, et\r
+qui n'avait jamais menti. Elle était si douce qu'elle paraissait\r
+fragile; plus solide d'ailleurs que le granit. Elle touchait aux\r
+malheureux avec de charmants doigts fins et purs. Il y avait, pour ainsi\r
+dire, du silence dans sa parole; elle parlait juste le nécessaire, et\r
+elle avait un son de voix qui eût tout à la fois édifié un confessionnal\r
+et enchanté un salon. Cette délicatesse s'accommodait de la robe de\r
+bure, trouvant à ce rude contact un rappel continuel du ciel et de Dieu.\r
+Insistons sur un détail. N'avoir jamais menti, n'avoir jamais dit, pour\r
+un intérêt quelconque, même indifféremment, une chose qui ne fût la\r
+vérité, la sainte vérité, c'était le trait distinctif de la soeur\r
+Simplice; c'était l'accent de sa vertu. Elle était presque célèbre dans\r
+la congrégation pour cette véracité imperturbable. L'abbé Sicard parle\r
+de la soeur Simplice dans une lettre au sourd-muet Massieu. Si sincères,\r
+si loyaux et si purs que nous soyons, nous avons tous sur notre candeur\r
+au moins la fêlure du petit mensonge innocent. Elle, point. Petit\r
+mensonge, mensonge innocent, est-ce que cela existe? Mentir, c'est\r
+l'absolu du mal. Peu mentir n'est pas possible; celui qui ment, ment\r
+tout le mensonge; mentir, c'est la face même du démon; Satan a deux\r
+noms, il s'appelle Satan et il s'appelle Mensonge. Voilà ce qu'elle\r
+pensait. Et comme elle pensait, elle pratiquait. Il en résultait cette\r
+blancheur dont nous avons parlé, blancheur qui couvrait de son\r
+rayonnement même ses lèvres et ses yeux. Son sourire était blanc, son\r
+regard était blanc. Il n'y avait pas une toile d'araignée, pas un grain\r
+de poussière à la vitre de cette conscience. En entrant dans l'obédience\r
+de saint Vincent de Paul, elle avait pris le nom de Simplice par choix\r
+spécial. Simplice de Sicile, on le sait, est cette sainte qui aima mieux\r
+se laisser arracher les deux seins que de répondre, étant née à\r
+Syracuse, qu'elle était née à Ségeste, mensonge qui la sauvait. Cette\r
+patronne convenait à cette âme.\r
+\r
+La soeur Simplice, en entrant dans l'ordre, avait deux défauts dont elle\r
+s'était peu à peu corrigée; elle avait eu le goût des friandises et elle\r
+avait aimé à recevoir des lettres. Elle ne lisait jamais qu'un livre de\r
+prières en gros caractères et en latin. Elle ne comprenait pas le latin,\r
+mais elle comprenait le livre.\r
+\r
+La pieuse fille avait pris en affection Fantine, y sentant probablement\r
+de la vertu latente, et s'était dévouée à la soigner presque\r
+exclusivement.\r
+\r
+M. Madeleine emmena à part la soeur Simplice et lui recommanda Fantine\r
+avec un accent singulier dont la soeur se souvint plus tard.\r
+\r
+En quittant la soeur, il s'approcha de Fantine.\r
+\r
+Fantine attendait chaque jour l'apparition de M. Madeleine comme on\r
+attend un rayon de chaleur et de joie. Elle disait aux soeurs:\r
+\r
+--Je ne vis que lorsque monsieur le maire est là.\r
+\r
+Elle avait ce jour-là beaucoup de fièvre. Dès qu'elle vit M. Madeleine,\r
+elle lui demanda:\r
+\r
+--Et Cosette?\r
+\r
+Il répondit en souriant:\r
+\r
+--Bientôt.\r
+\r
+M. Madeleine fut avec Fantine comme à l'ordinaire. Seulement il resta\r
+une heure au lieu d'une demi-heure, au grand contentement de Fantine. Il\r
+fît mille instances à tout le monde pour que rien ne manquât à la\r
+malade. On remarqua qu'il y eut un moment où son visage devint très\r
+sombre. Mais cela s'expliqua quand on sut que le médecin s'était penché\r
+à son oreille et lui avait dit:\r
+\r
+--Elle baisse beaucoup.\r
+\r
+Puis il rentra à la mairie, et le garçon de bureau le vit examiner avec\r
+attention une carte routière de France qui était suspendue dans son\r
+cabinet. Il écrivit quelques chiffres au crayon sur un papier.\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+Perspicacité de maître Scaufflaire\r
+\r
+\r
+De la mairie il se rendit au bout de la ville chez un Flamand, maître\r
+Scaufflaër, francisé Scaufflaire, qui louait des chevaux et des\r
+«cabriolets à volonté».\r
+\r
+Pour aller chez ce Scaufflaire, le plus court était de prendre une rue\r
+peu fréquentée où était le presbytère de la paroisse que M. Madeleine\r
+habitait. Le curé était, disait-on, un homme digne et respectable, et de\r
+bon conseil. À l'instant où M. Madeleine arriva devant le presbytère, il\r
+n'y avait dans la rue qu'un passant, et ce passant remarqua ceci: M. le\r
+maire, après avoir dépassé la maison curiale, s'arrêta, demeura\r
+immobile, puis revint sur ses pas et rebroussa chemin jusqu'à la porte\r
+du presbytère, qui était une porte bâtarde avec marteau de fer. Il mit\r
+vivement la main au marteau, et le souleva; puis il s'arrêta de nouveau,\r
+et resta court, et comme pensif, et, après quelques secondes, au lieu de\r
+laisser bruyamment retomber le marteau, il le reposa doucement et reprit\r
+son chemin avec une sorte de hâte qu'il n'avait pas auparavant.\r
+\r
+M. Madeleine trouva maître Scaufflaire chez lui occupé à repiquer un\r
+harnais.\r
+\r
+--Maître Scaufflaire, demanda-t-il, avez-vous un bon cheval?\r
+\r
+--Monsieur le maire, dit le Flamand, tous mes chevaux sont bons.\r
+Qu'entendez-vous par un bon cheval?\r
+\r
+--J'entends un cheval qui puisse faire vingt lieues en un jour.\r
+\r
+--Diable! fit le Flamand, vingt lieues!\r
+\r
+--Oui.\r
+\r
+--Attelé à un cabriolet?\r
+\r
+--Oui.\r
+\r
+--Et combien de temps se reposera-t-il après la course?\r
+\r
+--Il faut qu'il puisse au besoin repartir le lendemain.\r
+\r
+--Pour refaire le même trajet?\r
+\r
+--Oui.\r
+\r
+--Diable! diable! et c'est vingt lieues? M. Madeleine tira de sa poche\r
+le papier où il avait crayonné des chiffres. Il les montra au Flamand.\r
+C'étaient les chiffres 5, 6, 8-1/2.\r
+\r
+--Vous voyez, dit-il. Total, dix-neuf et demi, autant dire vingt lieues.\r
+\r
+--Monsieur le maire, reprit le Flamand, j'ai votre affaire. Mon petit\r
+cheval blanc. Vous avez dû le voir passer quelquefois. C'est une petite\r
+bête du bas Boulonnais. C'est plein de feu. On a voulu d'abord en faire\r
+un cheval de selle. Bah! il ruait, il flanquait tout le monde par terre.\r
+On le croyait vicieux, on ne savait qu'en faire. Je l'ai acheté. Je l'ai\r
+mis au cabriolet. Monsieur, c'est cela qu'il voulait; il est doux comme\r
+une fille, il va le vent. Ah! par exemple, il ne faudrait pas lui monter\r
+sur le dos. Ce n'est pas son idée d'être cheval de selle. Chacun a son\r
+ambition. Tirer, oui, porter, non; il faut croire qu'il s'est dit ça.\r
+\r
+--Et il fera la course?\r
+\r
+--Vos vingt lieues. Toujours au grand trot, et en moins de huit heures.\r
+Mais voici à quelles conditions.\r
+\r
+--Dites.\r
+\r
+--Premièrement, vous le ferez souffler une heure à moitié chemin; il\r
+mangera, et on sera là pendant qu'il mangera pour empêcher le garçon de\r
+l'auberge de lui voler son avoine; car j'ai remarqué que dans les\r
+auberges l'avoine est plus souvent bue par les garçons d'écurie que\r
+mangée par les chevaux.\r
+\r
+--On sera là.\r
+\r
+--Deuxièmement.... Est-ce pour monsieur le maire le cabriolet?\r
+\r
+--Oui.\r
+\r
+--Monsieur le maire sait conduire?\r
+\r
+--Oui.\r
+\r
+--Eh bien, monsieur le maire voyagera seul et sans bagage afin de ne\r
+point charger le cheval.\r
+\r
+--Convenu.\r
+\r
+--Mais monsieur le maire, n'ayant personne avec lui, sera obligé de\r
+prendre la peine de surveiller lui-même l'avoine.\r
+\r
+--C'est dit.\r
+\r
+--Il me faudra trente francs par jour. Les jours de repos payés. Pas un\r
+liard de moins, et la nourriture de la bête à la charge de monsieur le\r
+maire.\r
+\r
+M. Madeleine tira trois napoléons de sa bourse et les mit sur la table.\r
+\r
+--Voilà deux jours d'avance.\r
+\r
+--Quatrièmement, pour une course pareille sur cabriolet serait trop\r
+lourd et fatiguerait le cheval. Il faudrait que monsieur le maire\r
+consentît à voyager dans un petit tilbury que j'ai.\r
+\r
+--J'y consens.\r
+\r
+--C'est léger, mais c'est découvert.\r
+\r
+--Cela m'est égal.\r
+\r
+--Monsieur le maire a-t-il réfléchi que nous sommes en hiver?...\r
+\r
+M. Madeleine ne répondit pas. Le Flamand reprit:\r
+\r
+--Qu'il fait très froid?\r
+\r
+M. Madeleine garda le silence. Maître Scaufflaire continua:\r
+\r
+--Qu'il peut pleuvoir?\r
+\r
+M. Madeleine leva la tête et dit:\r
+\r
+--Le tilbury et le cheval seront devant ma porte demain à quatre heures\r
+et demie du matin.\r
+\r
+--C'est entendu, monsieur le maire, répondit Scaufflaire, puis, grattant\r
+avec l'ongle de son pouce une tache qui était dans le bois de la table,\r
+il reprit de cet air insouciant que les Flamands savent si bien mêler à\r
+leur finesse:\r
+\r
+--Mais voilà que j'y songe à présent! monsieur le maire ne me dit pas où\r
+il va. Où est-ce que va monsieur le maire?\r
+\r
+Il ne songeait pas à autre chose depuis le commencement de la\r
+conversation, mais il ne savait pourquoi il n'avait pas osé faire cette\r
+question.\r
+\r
+--Votre cheval a-t-il de bonnes jambes de devant? dit M. Madeleine.\r
+\r
+--Oui, monsieur le maire. Vous le soutiendrez un peu dans les descentes.\r
+Y a-t-il beaucoup de descentes d'ici où vous allez?\r
+\r
+--N'oubliez pas d'être à ma porte à quatre heures et demie du matin,\r
+très précises, répondit M. Madeleine; et il sortit.\r
+\r
+Le Flamand resta «tout bête», comme il disait lui-même quelque temps\r
+après.\r
+\r
+Monsieur le maire était sorti depuis deux ou trois minutes, lorsque la\r
+porte se rouvrit; c'était M. le maire. Il avait toujours le même air\r
+impassible et préoccupé.\r
+\r
+--Monsieur Scaufflaire, dit-il, à quelle somme estimez-vous le cheval et\r
+le tilbury que vous me louerez, l'un portant l'autre?\r
+\r
+--L'un traînant l'autre, monsieur le maire, dit le Flamand avec un gros\r
+rire.\r
+\r
+--Soit. Eh bien!\r
+\r
+--Est-ce que monsieur le maire veut me les acheter?\r
+\r
+--Non, mais à tout événement, je veux vous les garantir. À mon retour\r
+vous me rendrez la somme. Combien estimez-vous cabriolet et cheval?\r
+\r
+--À cinq cents francs, monsieur le maire.\r
+\r
+--Les voici.\r
+\r
+M. Madeleine posa un billet de banque sur la table, puis sortit et cette\r
+fois ne rentra plus.\r
+\r
+Maître Scaufflaire regretta affreusement de n'avoir point dit mille\r
+francs. Du reste le cheval et le tilbury, en bloc, valaient cent écus.\r
+\r
+Le Flamand appela sa femme, et lui conta la chose. Où diable monsieur le\r
+maire peut-il aller? Ils tinrent conseil.\r
+\r
+--Il va à Paris, dit la femme.\r
+\r
+--Je ne crois pas, dit le mari.\r
+\r
+M. Madeleine avait oublié sur la cheminée le papier où il avait tracé\r
+des chiffres. Le Flamand le prit et l'étudia.\r
+\r
+--Cinq, six, huit et demi? cela doit marquer des relais de poste.\r
+\r
+Il se tourna vers sa femme.\r
+\r
+--J'ai trouvé.\r
+\r
+--Comment?\r
+\r
+--Il y a cinq lieues d'ici à Hesdin, six de Hesdin à Saint-Pol, huit et\r
+demie de Saint-Pol à Arras. Il va à Arras.\r
+\r
+Cependant M. Madeleine était rentré chez lui.\r
+\r
+Pour revenir de chez maître Scaufflaire, il avait pris le plus long,\r
+comme si la porte du presbytère avait été pour lui une tentation, et\r
+qu'il eût voulu l'éviter. Il était monté dans sa chambre et s'y était\r
+enfermé, ce qui n'avait rien que de simple, car il se couchait\r
+volontiers de bonne heure. Pourtant la concierge de la fabrique, qui\r
+était en même temps l'unique servante de M. Madeleine, observa que sa\r
+lumière s'éteignit à huit heures et demie, et elle le dit au caissier\r
+qui rentrait, en ajoutant:\r
+\r
+--Est-ce que monsieur le maire est malade? je lui ai trouvé l'air un peu\r
+singulier.\r
+\r
+Ce caissier habitait une chambre située précisément au-dessous de la\r
+chambre de M. Madeleine. Il ne prit point garde aux paroles de la\r
+portière, se coucha et s'endormit. Vers minuit, il se réveilla\r
+brusquement; il avait entendu à travers son sommeil un bruit au-dessus\r
+de sa tête. Il écouta. C'était un pas qui allait et venait, comme si\r
+l'on marchait dans la chambre en haut. Il écouta plus attentivement, et\r
+reconnut le pas de M. Madeleine. Cela lui parut étrange; habituellement\r
+aucun bruit ne se faisait dans la chambre de M. Madeleine avant l'heure\r
+de son lever. Un moment après le caissier entendit quelque chose qui\r
+ressemblait à une armoire qu'on ouvre et qu'on referme. Puis on dérangea\r
+un meuble, il y eut un silence, et le pas recommença. Le caissier se\r
+dressa sur son séant, s'éveilla tout à fait, regarda, et à travers les\r
+vitres de sa croisée aperçut sur le mur d'en face la réverbération\r
+rougeâtre d'une fenêtre éclairée. À la direction des rayons, ce ne\r
+pouvait être que la fenêtre de la chambre de M. Madeleine. La\r
+réverbération tremblait comme si elle venait plutôt d'un feu allumé que\r
+d'une lumière. L'ombre des châssis vitrés ne s'y dessinait pas, ce qui\r
+indiquait que la fenêtre était toute grande ouverte. Par le froid qu'il\r
+faisait, cette fenêtre ouverte était surprenante. Le caissier se\r
+rendormit. Une heure ou deux après, il se réveilla encore. Le même pas,\r
+lent et régulier, allait et venait toujours au-dessus de sa tête.\r
+\r
+La réverbération se dessinait toujours sur le mur, mais elle était\r
+maintenant pâle et paisible comme le reflet d'une lampe ou d'une bougie.\r
+La fenêtre était toujours ouverte. Voici ce qui se passait dans la\r
+chambre de M. Madeleine.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+Une tempête sous un crâne\r
+\r
+\r
+Le lecteur a sans doute deviné que M. Madeleine n'est autre que Jean\r
+Valjean.\r
+\r
+Nous avons déjà regardé dans les profondeurs de cette conscience; le\r
+moment est venu d'y regarder encore. Nous ne le faisons pas sans émotion\r
+et sans tremblement. Il n'existe rien de plus terrifiant que cette sorte\r
+de contemplation. L'oeil de l'esprit ne peut trouver nulle part plus\r
+d'éblouissements ni plus de ténèbres que dans l'homme; il ne peut se\r
+fixer sur aucune chose qui soit plus redoutable, plus compliquée, plus\r
+mystérieuse et plus infinie. Il y a un spectacle plus grand que la mer,\r
+c'est le ciel; il y a un spectacle plus grand que le ciel, c'est\r
+l'intérieur de l'âme.\r
+\r
+Faire le poème de la conscience humaine, ne fût-ce qu'à propos d'un seul\r
+homme, ne fût-ce qu'à propos du plus infime des hommes, ce serait fondre\r
+toutes les épopées dans une épopée supérieure et définitive. La\r
+conscience, c'est le chaos des chimères, des convoitises et des\r
+tentatives, la fournaise des rêves, l'antre des idées dont on a honte;\r
+c'est le pandémonium des sophismes, c'est le champ de bataille des\r
+passions. À de certaines heures, pénétrez à travers la face livide d'un\r
+être humain qui réfléchit, et regardez derrière, regardez dans cette\r
+âme, regardez dans cette obscurité. Il y a là, sous le silence\r
+extérieur, des combats de géants comme dans Homère, des mêlées de\r
+dragons et d'hydres et des nuées de fantômes comme dans Milton, des\r
+spirales visionnaires comme chez Dante. Chose sombre que cet infini que\r
+tout homme porte en soi et auquel il mesure avec désespoir les volontés\r
+de son cerveau et les actions de sa vie!\r
+\r
+Alighieri rencontra un jour une sinistre porte devant laquelle il\r
+hésita. En voici une aussi devant nous, au seuil de laquelle nous\r
+hésitons. Entrons pourtant.\r
+\r
+Nous n'avons que peu de chose à ajouter à ce que le lecteur connaît déjà\r
+de ce qui était arrivé à Jean Valjean depuis l'aventure de\r
+Petit-Gervais. À partir de ce moment, on l'a vu, il fut un autre homme.\r
+Ce que l'évêque avait voulu faire de lui, il l'exécuta. Ce fut plus\r
+qu'une transformation, ce fut une transfiguration.\r
+\r
+Il réussit à disparaître, vendit l'argenterie de l'évêque, ne gardant\r
+que les flambeaux, comme souvenir, se glissa de ville en ville, traversa\r
+la France, vint à Montreuil-sur-mer, eut l'idée que nous avons dite,\r
+accomplit ce que nous avons raconté, parvint à se faire insaisissable et\r
+inaccessible, et désormais, établi à Montreuil-sur-mer, heureux de\r
+sentir sa conscience attristée par son passé et la première moitié de\r
+son existence démentie par la dernière, il vécut paisible, rassuré et\r
+espérant, n'ayant plus que deux pensées: cacher son nom, et sanctifier\r
+sa vie; échapper aux hommes, et revenir à Dieu.\r
+\r
+Ces deux pensées étaient si étroitement mêlées dans son esprit qu'elles\r
+n'en formaient qu'une seule; elles étaient toutes deux également\r
+absorbantes et impérieuses, et dominaient ses moindres actions.\r
+D'ordinaire elles étaient d'accord pour régler la conduite de sa vie;\r
+elles le tournaient vers l'ombre; elles le faisaient bienveillant et\r
+simple; elles lui conseillaient les mêmes choses. Quelquefois cependant\r
+il y avait conflit entre elles. Dans ce cas-là, on s'en souvient,\r
+l'homme que tout le pays de Montreuil-sur-mer appelait M. Madeleine ne\r
+balançait pas à sacrifier la première à la seconde, sa sécurité à sa\r
+vertu. Ainsi, en dépit de toute réserve et de toute prudence, il avait\r
+gardé les chandeliers de l'évêque, porté son deuil, appelé et interrogé\r
+tous les petits savoyards qui passaient, pris des renseignements sur les\r
+familles de Faverolles, et sauvé la vie au vieux Fauchelevent, malgré\r
+les inquiétantes insinuations de Javert. Il semblait, nous l'avons déjà\r
+remarqué, qu'il pensât, à l'exemple de tous ceux qui ont été sages,\r
+saints et justes, que son premier devoir n'était pas envers lui.\r
+\r
+Toutefois, il faut le dire, jamais rien de pareil ne s'était encore\r
+présenté. Jamais les deux idées qui gouvernaient le malheureux homme\r
+dont nous racontons les souffrances n'avaient engagé une lutte si\r
+sérieuse. Il le comprit confusément, mais profondément, dès les\r
+premières paroles que prononça Javert, en entrant dans son cabinet.\r
+\r
+Au moment où fut si étrangement articulé ce nom qu'il avait enseveli\r
+sous tant d'épaisseurs, il fut saisi de stupeur et comme enivré par la\r
+sinistre bizarrerie de sa destinée, et, à travers cette stupeur, il eut\r
+ce tressaillement qui précède les grandes secousses; il se courba comme\r
+un chêne à l'approche d'un orage, comme un soldat à l'approche d'un\r
+assaut. Il sentit venir sur sa tête des ombres pleines de foudres et\r
+d'éclairs. Tout en écoutant parler Javert, il eut une première pensée\r
+d'aller, de courir, de se dénoncer, de tirer ce Champmathieu de prison\r
+et de s'y mettre; cela fut douloureux et poignant comme une incision\r
+dans la chair vive, puis cela passa, et il se dit: «Voyons! voyons!» Il\r
+réprima ce premier mouvement généreux et recula devant l'héroïsme.\r
+\r
+Sans doute, il serait beau qu'après les saintes paroles de l'évêque,\r
+après tant d'années de repentir et d'abnégation, au milieu d'une\r
+pénitence admirablement commencée, cet homme, même en présence d'une si\r
+terrible conjoncture, n'eût pas bronché un instant et eût continué de\r
+marcher du même pas vers ce précipice ouvert au fond duquel était le\r
+ciel; cela serait beau, mais cela ne fut pas ainsi. Il faut bien que\r
+nous rendions compte des choses qui s'accomplissaient dans cette âme, et\r
+nous ne pouvons dire que ce qui y était. Ce qui l'emporta tout d'abord,\r
+ce fut l'instinct de la conservation; il rallia en hâte ses idées,\r
+étouffa ses émotions, considéra la présence de Javert, ce grand péril,\r
+ajourna toute résolution avec la fermeté de l'épouvante, s'étourdit sur\r
+ce qu'il y avait à faire, et reprit son calme comme un lutteur ramasse\r
+son bouclier.\r
+\r
+Le reste de la journée il fut dans cet état, un tourbillon au dedans,\r
+une tranquillité profonde au dehors; il ne prit que ce qu'on pourrait\r
+appeler «les mesures conservatoires». Tout était encore confus et se\r
+heurtait dans son cerveau; le trouble y était tel qu'il ne voyait\r
+distinctement la forme d'aucune idée; et lui-même n'aurait pu rien dire\r
+de lui-même, si ce n'est qu'il venait de recevoir un grand coup. Il se\r
+rendit comme d'habitude près du lit de douleur de Fantine et prolongea\r
+sa visite, par un instinct de bonté, se disant qu'il fallait agir ainsi\r
+et la bien recommander aux soeurs pour le cas où il arriverait qu'il eût\r
+à s'absenter. Il sentit vaguement qu'il faudrait peut-être aller à\r
+Arras, et, sans être le moins du monde décidé à ce voyage, il se dit\r
+qu'à l'abri de tout soupçon comme il l'était, il n'y avait point\r
+d'inconvénient à être témoin de ce qui se passerait, et il retint le\r
+tilbury de Scaufflaire, afin d'être préparé à tout événement.\r
+\r
+Il dîna avec assez d'appétit.\r
+\r
+Rentré dans sa chambre il se recueillit.\r
+\r
+Il examina la situation et la trouva inouïe; tellement inouïe qu'au\r
+milieu de sa rêverie, par je ne sais quelle impulsion d'anxiété presque\r
+inexplicable, il se leva de sa chaise et ferma sa porte au verrou. Il\r
+craignait qu'il n'entrât encore quelque chose. Il se barricadait contre\r
+le possible.\r
+\r
+Un moment après il souffla sa lumière. Elle le gênait.\r
+\r
+Il lui semblait qu'on pouvait le voir.\r
+\r
+Qui, on?\r
+\r
+Hélas! ce qu'il voulait mettre à la porte était entré ce qu'il voulait\r
+aveugler, le regardait. Sa conscience.\r
+\r
+Sa conscience, c'est-à-dire Dieu.\r
+\r
+Pourtant, dans le premier moment, il se fit illusion; il eut un\r
+sentiment de sûreté et de solitude; le verrou tiré, il se crut\r
+imprenable; la chandelle éteinte, il se sentit invisible. Alors il prit\r
+possession de lui-même; il posa ses coudes sur la table, appuya la tête\r
+sur sa main, et se mit à songer dans les ténèbres.\r
+\r
+--Où en suis-je?--Est-ce que je ne rêve pas? Que m'a-t-on dit?--Est-il\r
+bien vrai que j'aie vu ce Javert et qu'il m'ait parlé ainsi?--Que peut\r
+être ce Champmathieu?--Il me ressemble donc?--Est-ce possible?--Quand\r
+je pense qu'hier j'étais si tranquille et si loin de me douter de\r
+rien!--Qu'est-ce que je faisais donc hier à pareille heure?--Qu'y a-t-il\r
+dans cet incident?--Comment se dénouera-t-il?--Que faire?\r
+\r
+Voilà dans quelle tourmente il était. Son cerveau avait perdu la force\r
+de retenir ses idées, elles passaient comme des ondes, et il prenait son\r
+front dans ses deux mains pour les arrêter.\r
+\r
+De ce tumulte qui bouleversait sa volonté et sa raison, et dont il\r
+cherchait à tirer une évidence et une résolution, rien ne se dégageait\r
+que l'angoisse.\r
+\r
+Sa tête était brûlante. Il alla à la fenêtre et l'ouvrit toute grande.\r
+Il n'y avait pas d'étoiles au ciel. Il revint s'asseoir près de la\r
+table.\r
+\r
+La première heure s'écoula ainsi.\r
+\r
+Peu à peu cependant des linéaments vagues commencèrent à se former et à\r
+se fixer dans sa méditation, et il put entrevoir avec la précision de la\r
+réalité, non l'ensemble de la situation, mais quelques détails.\r
+\r
+Il commença par reconnaître que, si extraordinaire et si critique que\r
+fût cette situation, il en était tout à fait le maître.\r
+\r
+Sa stupeur ne fit que s'en accroître.\r
+\r
+Indépendamment du but sévère et religieux que se proposaient ses\r
+actions, tout ce qu'il avait fait jusqu'à ce jour n'était autre chose\r
+qu'un trou qu'il creusait pour y enfouir son nom. Ce qu'il avait\r
+toujours le plus redouté, dans ses heures de repli sur lui-même, dans\r
+ses nuits d'insomnie, c'était d'entendre jamais prononcer ce nom; il se\r
+disait que ce serait là pour lui la fin de tout; que le jour où ce nom\r
+reparaîtrait, il ferait évanouir autour de lui sa vie nouvelle, et qui\r
+sait même peut-être? au dedans de lui sa nouvelle âme. Il frémissait de\r
+la seule pensée que c'était possible. Certes, si quelqu'un lui eût dit\r
+en ces moments-là qu'une heure viendrait où ce nom retentirait à son\r
+oreille, où ce hideux mot, Jean Valjean, sortirait tout à coup de la\r
+nuit et se dresserait devant lui, où cette lumière formidable faite pour\r
+dissiper le mystère dont il s'enveloppait resplendirait subitement sur\r
+sa tête; et que ce nom ne le menacerait pas, que cette lumière ne\r
+produirait qu'une obscurité plus épaisse, que ce voile déchiré\r
+accroîtrait le mystère; que ce tremblement de terre consoliderait son\r
+édifice, que ce prodigieux incident n'aurait d'autre résultat, si bon\r
+lui semblait, à lui, que de rendre son existence à la fois plus claire\r
+et plus impénétrable, et que, de sa confrontation avec le fantôme de\r
+Jean Valjean, le bon et digne bourgeois monsieur Madeleine sortirait\r
+plus honoré, plus paisible et plus respecté que jamais,--si quelqu'un\r
+lui eût dit cela, il eût hoché la tête et regardé ces paroles comme\r
+insensées. Eh bien! tout cela venait précisément d'arriver, tout cet\r
+entassement de l'impossible était un fait, et Dieu avait permis que ces\r
+choses folles devinssent des choses réelles!\r
+\r
+Sa rêverie continuait de s'éclaircir. Il se rendait de plus en plus\r
+compte de sa position. Il lui semblait qu'il venait de s'éveiller de je\r
+ne sais quel sommeil, et qu'il se trouvait glissant sur une pente au\r
+milieu de la nuit, debout, frissonnant, reculant en vain, sur le bord\r
+extrême d'un abîme. Il entrevoyait distinctement dans l'ombre un\r
+inconnu, un étranger, que la destinée prenait pour lui et poussait dans\r
+le gouffre à sa place. Il fallait, pour que le gouffre se refermât, que\r
+quelqu'un y tombât, lui ou l'autre.\r
+\r
+Il n'avait qu'à laisser faire.\r
+\r
+La clarté devint complète, et il s'avoua ceci:--Que sa place était vide\r
+aux galères, qu'il avait beau faire, qu'elle l'y attendait toujours, que\r
+le vol de Petit-Gervais l'y ramenait, que cette place vide l'attendrait\r
+et l'attirerait jusqu'à ce qu'il y fût, que cela était inévitable et\r
+fatal.--Et puis il se dit:--Qu'en ce moment il avait un remplaçant,\r
+qu'il paraissait qu'un nommé Champmathieu avait cette mauvaise chance,\r
+et que, quant à lui, présent désormais au bagne dans la personne de ce\r
+Champmathieu, présent dans la société sous le nom de M. Madeleine, il\r
+n'avait plus rien à redouter, pourvu qu'il n'empêchât pas les hommes de\r
+sceller sur la tête de ce Champmathieu cette pierre de l'infamie qui,\r
+comme la pierre du sépulcre, tombe une fois et ne se relève jamais.\r
+\r
+Tout cela était si violent et si étrange qu'il se fit soudain en lui\r
+cette espèce de mouvement indescriptible qu'aucun homme n'éprouve plus\r
+de deux ou trois fois dans sa vie, sorte de convulsion de la conscience\r
+qui remue tout ce que le coeur a de douteux, qui se compose d'ironie, de\r
+joie et de désespoir, et qu'on pourrait appeler un éclat de rire\r
+intérieur.\r
+\r
+Il ralluma brusquement sa bougie.\r
+\r
+--Eh bien quoi! se dit-il, de quoi est-ce que j'ai peur? qu'est-ce que\r
+j'ai à songer comme cela? Me voilà sauvé. Tout est fini. Je n'avais plus\r
+qu'une porte entr'ouverte par laquelle mon passé pouvait faire irruption\r
+dans ma vie; cette porte, la voilà murée! à jamais! Ce Javert qui me\r
+trouble depuis si longtemps, ce redoutable instinct qui semblait m'avoir\r
+deviné, qui m'avait deviné, pardieu! et qui me suivait partout, cet\r
+affreux chien de chasse toujours en arrêt sur moi, le voilà dérouté,\r
+occupé ailleurs, absolument dépisté! Il est satisfait désormais, il me\r
+laissera tranquille, il tient son Jean Valjean! Qui sait même, il est\r
+probable qu'il voudra quitter la ville! Et tout cela s'est fait sans\r
+moi! Et je n'y suis pour rien! Ah çà, mais! qu'est-ce qu'il y a de\r
+malheureux dans ceci? Des gens qui me verraient, parole d'honneur!\r
+croiraient qu'il m'est arrivé une catastrophe! Après tout, s'il y a du\r
+mal pour quelqu'un, ce n'est aucunement de ma faute. C'est la providence\r
+qui a tout fait. C'est qu'elle veut cela apparemment!\r
+\r
+Ai-je le droit de déranger ce qu'elle arrange? Qu'est-ce que je demande\r
+à présent? De quoi est-ce que je vais me mêler? Cela ne me regarde pas.\r
+Comment! je ne suis pas content! Mais qu'est-ce qu'il me faut donc? Le\r
+but auquel j'aspire depuis tant d'années, le songe de mes nuits, l'objet\r
+de mes prières au ciel, la sécurité, je l'atteins! C'est Dieu qui le\r
+veut. Je n'ai rien à faire contre la volonté de Dieu. Et pourquoi Dieu\r
+le veut-il? Pour que je continue ce que j'ai commencé, pour que je fasse\r
+le bien, pour que je sois un jour un grand et encourageant exemple, pour\r
+qu'il soit dit qu'il y a eu enfin un peu de bonheur attaché à cette\r
+pénitence que j'ai subie et à cette vertu où je suis revenu! Vraiment je\r
+ne comprends pas pourquoi j'ai eu peur tantôt d'entrer chez ce brave\r
+curé et de tout lui raconter comme à un confesseur, et de lui demander\r
+conseil, c'est évidemment là ce qu'il m'aurait dit. C'est décidé,\r
+laissons aller les choses! laissons faire le bon Dieu!\r
+\r
+Il se parlait ainsi dans les profondeurs de sa conscience, penché sur ce\r
+qu'on pourrait appeler son propre abîme. Il se leva de sa chaise, et se\r
+mit à marcher dans la chambre.--Allons, dit-il, n'y pensons plus. Voilà\r
+une résolution prise!--Mais il ne sentit aucune joie.\r
+\r
+Au contraire.\r
+\r
+On n'empêche pas plus la pensée de revenir à une idée que la mer de\r
+revenir à un rivage. Pour le matelot, cela s'appelle la marée; pour le\r
+coupable, cela s'appelle le remords. Dieu soulève l'âme comme l'océan.\r
+\r
+Au bout de peu d'instants, il eut beau faire, il reprit ce sombre\r
+dialogue dans lequel c'était lui qui parlait et lui qui écoutait, disant\r
+ce qu'il eût voulu taire, écoutant ce qu'il n'eût pas voulu entendre,\r
+cédant à cette puissance mystérieuse qui lui disait: pense! comme elle\r
+disait il y a deux mille ans à un autre condamné, marche!\r
+\r
+Avant d'aller plus loin et pour être pleinement compris, insistons sur\r
+une observation nécessaire.\r
+\r
+Il est certain qu'on se parle à soi-même, il n'est pas un être pensant\r
+qui ne l'ait éprouvé. On peut dire même que le verbe n'est jamais un\r
+plus magnifique mystère que lorsqu'il va, dans l'intérieur d'un homme,\r
+de la pensée à la conscience et qu'il retourne de la conscience à la\r
+pensée. C'est dans ce sens seulement qu'il faut entendre les mots\r
+souvent employés dans ce chapitre, il dit, il s'écria. On se dit, on se\r
+parle, on s'écrie en soi-même, sans que le silence extérieur soit rompu.\r
+Il y a un grand tumulte; tout parle en nous, excepté la bouche. Les\r
+réalités de l'âme, pour n'être point visibles et palpables, n'en sont\r
+pas moins des réalités.\r
+\r
+Il se demanda donc où il en était. Il s'interrogea sur cette «résolution\r
+prise». Il se confessa à lui-même que tout ce qu'il venait d'arranger\r
+dans son esprit était monstrueux, que «laisser aller les choses, laisser\r
+faire le bon Dieu», c'était tout simplement horrible. Laisser\r
+s'accomplir cette méprise de la destinée et des hommes, ne pas\r
+l'empêcher, s'y prêter par son silence, ne rien faire enfin, c'était\r
+faire tout! c'était le dernier degré de l'indignité hypocrite! c'était\r
+un crime bas, lâche, sournois, abject, hideux!\r
+\r
+Pour la première fois depuis huit années, le malheureux homme venait de\r
+sentir la saveur amère d'une mauvaise pensée et d'une mauvaise action.\r
+\r
+Il la recracha avec dégoût.\r
+\r
+Il continua de se questionner. Il se demanda sévèrement ce qu'il avait\r
+entendu par ceci: "Mon but est atteint!" Il se déclara que sa vie avait\r
+un but en effet. Mais quel but? cacher son nom? tromper la police?\r
+Était-ce pour une chose si petite qu'il avait fait tout ce qu'il avait\r
+fait? Est-ce qu'il n'avait pas un autre but, qui était le grand, qui\r
+était le vrai? Sauver, non sa personne, mais son âme. Redevenir honnête\r
+et bon. Être un juste! est-ce que ce n'était pas là surtout, là\r
+uniquement, ce qu'il avait toujours voulu, ce que l'évêque lui avait\r
+ordonné?--Fermer la porte à son passé? Mais il ne la fermait pas, grand\r
+Dieu! il la rouvrait en faisant une action infâme! mais il redevenait un\r
+voleur, et le plus odieux des voleurs! il volait à un autre son\r
+existence, sa vie, sa paix, sa place au soleil! il devenait un assassin!\r
+il tuait, il tuait moralement un misérable homme, il lui infligeait\r
+cette affreuse mort vivante, cette mort à ciel ouvert, qu'on appelle le\r
+bagne! Au contraire, se livrer, sauver cet homme frappé d'une si lugubre\r
+erreur, reprendre son nom, redevenir par devoir le forçat Jean Valjean,\r
+c'était là vraiment achever sa résurrection, et fermer à jamais l'enfer\r
+d'où il sortait! Y retomber en apparence, c'était en sortir en réalité!\r
+Il fallait faire cela! il n'avait rien fait s'il ne faisait pas cela!\r
+toute sa vie était inutile, toute sa pénitence était perdue, et il n'y\r
+avait plus qu'à dire: à quoi bon? Il sentait que l'évêque était là, que\r
+l'évêque était d'autant plus présent qu'il était mort, que l'évêque le\r
+regardait fixement, que désormais le maire Madeleine avec toutes ses\r
+vertus lui serait abominable, et que le galérien Jean Valjean serait\r
+admirable et pur devant lui. Que les hommes voyaient son masque, mais\r
+que l'évêque voyait sa face. Que les hommes voyaient sa vie, mais que\r
+l'évêque voyait sa conscience. Il fallait donc aller à Arras, délivrer\r
+le faux Jean Valjean, dénoncer le véritable! Hélas! c'était là le plus\r
+grand des sacrifices, la plus poignante des victoires, le dernier pas à\r
+franchir; mais il le fallait. Douloureuse destinée! il n'entrerait dans\r
+la sainteté aux yeux de Dieu que s'il rentrait dans l'infamie aux yeux\r
+des hommes!\r
+\r
+--Eh bien, dit-il, prenons ce parti! faisons notre devoir! sauvons cet\r
+homme!\r
+\r
+Il prononça ces paroles à haute voix, sans s'apercevoir qu'il parlait\r
+tout haut.\r
+\r
+Il prit ses livres, les vérifia et les mit en ordre. Il jeta au feu une\r
+liasse de créances qu'il avait sur de petits commerçants gênés. Il\r
+écrivit une lettre qu'il cacheta et sur l'enveloppe de laquelle on\r
+aurait pu lire, s'il y avait eu quelqu'un dans sa chambre en cet\r
+instant: _À Monsieur Laffitte, banquier, rue d'Artois, à Paris_.\r
+\r
+Il tira d'un secrétaire un portefeuille qui contenait quelques billets\r
+de banque et le passeport dont il s'était servi cette même année pour\r
+aller aux élections.\r
+\r
+Qui l'eût vu pendant qu'il accomplissait ces divers actes auxquels se\r
+mêlait une méditation si grave, ne se fût pas douté de ce qui se passait\r
+en lui. Seulement par moments ses lèvres remuaient; dans d'autres\r
+instants il relevait la tête et fixait son regard sur un point\r
+quelconque de la muraille, comme s'il y avait précisément là quelque\r
+chose qu'il voulait éclaircir ou interroger.\r
+\r
+La lettre à M. Laffitte terminée, il la mit dans sa poche ainsi que le\r
+portefeuille, et recommença à marcher.\r
+\r
+Sa rêverie n'avait point dévié. Il continuait de voir clairement son\r
+devoir écrit en lettres lumineuses qui flamboyaient devant ses yeux et\r
+se déplaçaient avec son regard:--_Va! nomme-toi! dénonce-toi!_\r
+\r
+Il voyait de même, et comme si elles se fussent mues devant lui avec des\r
+formes sensibles, les deux idées qui avaient été jusque-là la double\r
+règle de sa vie: cacher son nom, sanctifier son âme. Pour la première\r
+fois, elles lui apparaissaient absolument distinctes, et il voyait la\r
+différence qui les séparait. Il reconnaissait que l'une de ces idées\r
+était nécessairement bonne, tandis que l'autre pouvait devenir mauvaise;\r
+que celle-là était le dévouement et que celle-ci était la personnalité;\r
+que l'une disait: le _prochain_, et que l'autre disait: _moi_; que l'une\r
+venait de la lumière et que l'autre venait de la nuit.\r
+\r
+Elles se combattaient, il les voyait se combattre. À mesure qu'il\r
+songeait, elles avaient grandi devant l'oeil de son esprit; elles\r
+avaient maintenant des statures colossales; et il lui semblait qu'il\r
+voyait lutter au dedans de lui-même, dans cet infini dont nous parlions\r
+tout à l'heure, au milieu des obscurités et des lueurs, une déesse et\r
+une géante.\r
+\r
+Il était plein d'épouvante, mais il lui semblait que la bonne pensée\r
+l'emportait.\r
+\r
+Il sentait qu'il touchait à l'autre moment décisif de sa conscience et\r
+de sa destinée; que l'évêque avait marqué la première phase de sa vie\r
+nouvelle, et que ce Champmathieu en marquait la seconde. Après la grande\r
+crise, la grande épreuve.\r
+\r
+Cependant la fièvre, un instant apaisée, lui revenait peu à peu. Mille\r
+pensées le traversaient, mais elles continuaient de le fortifier dans sa\r
+résolution.\r
+\r
+Un moment il s'était dit:--qu'il prenait peut-être la chose trop\r
+vivement, qu'après tout ce Champmathieu n'était pas intéressant, qu'en\r
+somme il avait volé.\r
+\r
+Il se répondit:--Si cet homme a en effet volé quelques pommes, c'est un\r
+mois de prison. Il y a loin de là aux galères. Et qui sait même? a-t-il\r
+volé? est-ce prouvé? Le nom de Jean Valjean l'accable et semble\r
+dispenser de preuves. Les procureurs du roi n'agissent-ils pas\r
+habituellement ainsi? On le croit voleur, parce qu'on le sait forçat.\r
+\r
+Dans un autre instant, cette idée lui vint que, lorsqu'il se serait\r
+dénoncé, peut-être on considérerait l'héroïsme de son action, et sa vie\r
+honnête depuis sept ans, et ce qu'il avait fait pour le pays, et qu'on\r
+lui ferait grâce.\r
+\r
+Mais cette supposition s'évanouit bien vite, et il sourit amèrement en\r
+songeant que le vol des quarante sous à Petit-Gervais le faisait\r
+récidiviste, que cette affaire reparaîtrait certainement et, aux termes\r
+précis de la loi, le ferait passible des travaux forcés à perpétuité.\r
+\r
+Il se détourna de toute illusion, se détacha de plus en plus de la terre\r
+et chercha la consolation et la force ailleurs. Il se dit qu'il fallait\r
+faire son devoir; que peut-être même ne serait-il pas plus malheureux\r
+après avoir fait son devoir qu'après l'avoir éludé; que s'il _laissait\r
+faire_, s'il restait à Montreuil-sur-mer, sa considération, sa bonne\r
+renommée, ses bonnes oeuvres, la déférence, la vénération, sa charité,\r
+sa richesse, sa popularité, sa vertu, seraient assaisonnées d'un crime;\r
+et quel goût auraient toutes ces choses saintes liées à cette chose\r
+hideuse! tandis que, s'il accomplissait son sacrifice, au bagne, au\r
+poteau, au carcan, au bonnet vert, au travail sans relâche, à la honte\r
+sans pitié, il se mêlerait une idée céleste!\r
+\r
+Enfin il se dit qu'il y avait nécessité, que sa destinée était ainsi\r
+faite, qu'il n'était pas maître de déranger les arrangements d'en haut,\r
+que dans tous les cas il fallait choisir: ou la vertu au dehors et\r
+l'abomination au dedans, ou la sainteté au dedans et l'infamie au\r
+dehors.\r
+\r
+À remuer tant d'idées lugubres, son courage ne défaillait pas, mais son\r
+cerveau se fatiguait. Il commençait à penser malgré lui à d'autres\r
+choses, à des choses indifférentes. Ses artères battaient violemment\r
+dans ses tempes. Il allait et venait toujours. Minuit sonna d'abord à la\r
+paroisse, puis à la maison de ville. Il compta les douze coups aux deux\r
+horloges, et il compara le son des deux cloches. Il se rappela à cette\r
+occasion que quelques jours auparavant il avait vu chez un marchand de\r
+ferrailles une vieille cloche à vendre sur laquelle ce nom était écrit:\r
+_Antoine Albin de Romainville_.\r
+\r
+Il avait froid. Il alluma un peu de feu. Il ne songea pas à fermer la\r
+fenêtre.\r
+\r
+Cependant il était retombé dans sa stupeur. Il lui fallait faire un\r
+assez grand effort pour se rappeler à quoi il songeait avant que minuit\r
+sonnât. Il y parvint enfin.\r
+\r
+--Ah! oui, se dit-il, j'avais pris la résolution de me dénoncer.\r
+\r
+Et puis tout à coup il pensa à la Fantine.\r
+\r
+--Tiens! dit-il, et cette pauvre femme!\r
+\r
+Ici une crise nouvelle se déclara.\r
+\r
+Fantine, apparaissant brusquement dans sa rêverie, y fut comme un rayon\r
+d'une lumière inattendue. Il lui sembla que tout changeait d'aspect\r
+autour de lui, il s'écria:\r
+\r
+--Ah çà, mais! jusqu'ici je n'ai considéré que moi! je n'ai eu égard\r
+qu'à ma convenance! Il me convient de me taire ou de me\r
+dénoncer,--cacher ma personne ou sauver mon âme,--être un magistrat\r
+méprisable et respecté ou un galérien infâme et vénérable, c'est moi,\r
+c'est toujours moi, ce n'est que moi! Mais, mon Dieu, c'est de l'égoïsme\r
+tout cela! Ce sont des formes diverses de l'égoïsme, mais c'est de\r
+l'égoïsme! Si je songeais un peu aux autres? La première sainteté est de\r
+penser à autrui. Voyons, examinons. Moi excepté, moi effacé, moi oublié,\r
+qu'arrivera-t-il de tout ceci?--Si je me dénonce? on me prend. On lâche\r
+ce Champmathieu, on me remet aux galères, c'est bien. Et puis? Que se\r
+passe-t-il ici? Ah! ici, il y a un pays, une ville, des fabriques, une\r
+industrie, des ouvriers, des hommes, des femmes, des vieux grands-pères,\r
+des enfants, des pauvres gens! J'ai créé tout ceci, je fais vivre tout\r
+cela; partout où il y a une cheminée qui fume, c'est moi qui ai mis le\r
+tison dans le feu et la viande dans la marmite; j'ai fait l'aisance, la\r
+circulation, le crédit; avant moi il n'y avait rien; j'ai relevé,\r
+vivifié, animé, fécondé, stimulé, enrichi tout le pays; moi de moins,\r
+c'est l'âme de moins. Je m'ôte, tout meurt.--Et cette femme qui a tant\r
+souffert, qui a tant de mérites dans sa chute, dont j'ai causé sans le\r
+vouloir tout le malheur! Et cet enfant que je voulais aller chercher,\r
+que j'ai promis à la mère! Est-ce que je ne dois pas aussi quelque chose\r
+à cette femme, en réparation du mal que je lui ai fait? Si je disparais,\r
+qu'arrive-t-il? La mère meurt. L'enfant devient ce qu'il peut. Voilà ce\r
+qui se passe, si je me dénonce.--Si je ne me dénonce pas? Voyons, si je\r
+ne me dénonce pas? Après s'être fait cette question, il s'arrêta; il eut\r
+comme un moment d'hésitation et de tremblement; mais ce moment dura peu,\r
+et il se répondit avec calme:\r
+\r
+--Eh bien, cet homme va aux galères, c'est vrai, mais, que diable! il a\r
+volé! J'ai beau me dire qu'il n'a pas volé, il a volé! Moi, je reste\r
+ici, je continue. Dans dix ans j'aurai gagné dix millions, je les\r
+répands dans le pays, je n'ai rien à moi, qu'est-ce que cela me fait? Ce\r
+n'est pas pour moi ce que je fais! La prospérité de tous va croissant,\r
+les industries s'éveillent et s'excitent, les manufactures et les usines\r
+se multiplient, les familles, cent familles, mille familles! sont\r
+heureuses; la contrée se peuple; il naît des villages où il n'y a que\r
+des fermes, il naît des fermes où il n'y a rien; la misère disparaît, et\r
+avec la misère disparaissent la débauche, la prostitution, le vol, le\r
+meurtre, tous les vices, tous les crimes! Et cette pauvre mère élève son\r
+enfant! et voilà tout un pays riche et honnête! Ah çà, j'étais fou,\r
+j'étais absurde, qu'est-ce que je parlais donc de me dénoncer? Il faut\r
+faire attention, vraiment, et ne rien précipiter. Quoi! parce qu'il\r
+m'aura plu de faire le grand et le généreux,--c'est du mélodrame, après\r
+tout!--parce que je n'aurai songé qu'à moi, qu'à moi seul, quoi! pour\r
+sauver d'une punition peut-être un peu exagérée, mais juste au fond, on\r
+ne sait qui, un voleur, un drôle évidemment, il faudra que tout un pays\r
+périsse! il faudra qu'une pauvre femme crève à l'hôpital! qu'une pauvre\r
+petite fille crève sur le pavé! comme des chiens! Ah! mais c'est\r
+abominable! Sans même que la mère ait revu son enfant! sans que l'enfant\r
+ait presque connu sa mère! Et tout ça pour ce vieux gredin de voleur de\r
+pommes qui, à coup sûr, a mérité les galères pour autre chose, si ce\r
+n'est pour cela! Beaux scrupules qui sauvent un coupable et qui\r
+sacrifient des innocents, qui sauvent un vieux vagabond, lequel n'a plus\r
+que quelques années à vivre au bout du compte et ne sera guère plus\r
+malheureux au bagne que dans sa masure, et qui sacrifient toute une\r
+population, mères, femmes, enfants! Cette pauvre petite Cosette qui n'a\r
+que moi au monde et qui est sans doute en ce moment toute bleue de froid\r
+dans le bouge de ces Thénardier! Voilà encore des canailles ceux-là! Et\r
+je manquerais à mes devoirs envers tous ces pauvres êtres! Et je m'en\r
+irais me dénoncer! Et je ferais cette inepte sottise! Mettons tout au\r
+pis. Supposons qu'il y ait une mauvaise action pour moi dans ceci et que\r
+ma conscience me la reproche un jour, accepter, pour le bien d'autrui,\r
+ces reproches qui ne chargent que moi, cette mauvaise action qui ne\r
+compromet que mon âme, c'est là qu'est le dévouement, c'est là qu'est la\r
+vertu.\r
+\r
+Il se leva, il se remit à marcher. Cette fois il lui semblait qu'il\r
+était content. On ne trouve les diamants que dans les ténèbres de la\r
+terre; on ne trouve les vérités que dans les profondeurs de la pensée.\r
+Il lui semblait qu'après être descendu dans ces profondeurs, après avoir\r
+longtemps tâtonné au plus noir de ces ténèbres, il venait enfin de\r
+trouver un de ces diamants, une de ces vérités, et qu'il la tenait dans\r
+sa main; et il s'éblouissait à la regarder.\r
+\r
+--Oui, pensa-t-il, c'est cela. Je suis dans le vrai. J'ai la solution.\r
+Il faut finir par s'en tenir à quelque chose. Mon parti est pris.\r
+Laissons faire! Ne vacillons plus, ne reculons plus. Ceci est dans\r
+l'intérêt de tous, non dans le mien. Je suis Madeleine, je reste\r
+Madeleine. Malheur à celui qui est Jean Valjean! Ce n'est plus moi. Je\r
+ne connais pas cet homme, je ne sais plus ce que c'est, s'il se trouve\r
+que quelqu'un est Jean Valjean à cette heure, qu'il s'arrange! cela ne\r
+me regarde pas. C'est un nom de fatalité qui flotte dans la nuit, s'il\r
+s'arrête et s'abat sur une tête, tant pis pour elle!\r
+\r
+Il se regarda dans le petit miroir qui était sur sa cheminée, et dit:\r
+\r
+--Tiens! cela m'a soulagé de prendre une résolution! Je suis tout autre\r
+à présent.\r
+\r
+Il marcha encore quelques pas, puis il s'arrêta court:\r
+\r
+--Allons! dit-il, il ne faut hésiter devant aucune des conséquences de\r
+la résolution prise. Il y a encore des fils qui m'attachent à ce Jean\r
+Valjean. Il faut les briser! Il y a ici, dans cette chambre même, des\r
+objets qui m'accuseraient, des choses muettes qui seraient des témoins,\r
+c'est dit, il faut que tout cela disparaisse.\r
+\r
+Il fouilla dans sa poche, en tira sa bourse, l'ouvrit, et y prit une\r
+petite clef.\r
+\r
+Il introduisit cette clef dans une serrure dont on voyait à peine le\r
+trou, perdu qu'il était dans les nuances les plus sombres du dessin qui\r
+couvrait le papier collé sur le mur. Une cachette s'ouvrit, une espèce\r
+de fausse armoire ménagée entre l'angle de la muraille et le manteau de\r
+la cheminée. Il n'y avait dans cette cachette que quelques guenilles, un\r
+sarrau de toile bleue, un vieux pantalon, un vieux havresac, et un gros\r
+bâton d'épine ferré aux deux bouts. Ceux qui avaient vu Jean Valjean à\r
+l'époque où il traversait Digne, en octobre 1815, eussent aisément\r
+reconnu toutes les pièces de ce misérable accoutrement.\r
+\r
+Il les avait conservées comme il avait conservé les chandeliers\r
+d'argent, pour se rappeler toujours son point de départ. Seulement il\r
+cachait ceci qui venait du bagne, et il laissait voir les flambeaux qui\r
+venaient de l'évêque.\r
+\r
+Il jeta un regard furtif vers la porte, comme s'il eût craint qu'elle ne\r
+s'ouvrît malgré le verrou qui la fermait; puis d'un mouvement vif et\r
+brusque et d'une seule brassée, sans même donner un coup d'oeil à ces\r
+choses qu'il avait si religieusement et si périlleusement gardées\r
+pendant tant d'années, il prit tout, haillons, bâton, havresac, et jeta\r
+tout au feu. Il referma la fausse armoire, et, redoublant de\r
+précautions, désormais inutiles puisqu'elle était vide, en cacha la\r
+porte derrière un gros meuble qu'il y poussa.\r
+\r
+Au bout de quelques secondes, la chambre et le mur d'en face furent\r
+éclairés d'une grande réverbération rouge et tremblante. Tout brûlait.\r
+Le bâton d'épine pétillait et jetait des étincelles jusqu'au milieu de\r
+la chambre.\r
+\r
+Le havresac, en se consumant avec d'affreux chiffons qu'il contenait,\r
+avait mis à nu quelque chose qui brillait dans la cendre. En se\r
+penchant, on eût aisément reconnu une pièce d'argent. Sans doute la\r
+pièce de quarante sous volée au petit savoyard.\r
+\r
+Lui ne regardait pas le feu et marchait, allant et venant toujours du\r
+même pas.\r
+\r
+Tout à coup ses yeux tombèrent sur les deux flambeaux d'argent que la\r
+réverbération faisait reluire vaguement sur la cheminée.\r
+\r
+--Tiens! pensa-t-il, tout Jean Valjean est encore là-dedans. Il faut\r
+aussi détruire cela.\r
+\r
+Il prit les deux flambeaux.\r
+\r
+Il y avait assez de feu pour qu'on pût les déformer promptement et en\r
+faire une sorte de lingot méconnaissable.\r
+\r
+Il se pencha sur le foyer et s'y chauffa un instant. Il eut un vrai\r
+bien-être.--La bonne chaleur! dit-il.\r
+\r
+Il remua le brasier avec un des deux chandeliers. Une minute de plus, et\r
+ils étaient dans le feu. En ce moment il lui sembla qu'il entendait une\r
+voix qui criait au dedans de lui:\r
+\r
+--Jean Valjean! Jean Valjean!\r
+\r
+Ses cheveux se dressèrent, il devint comme un homme qui écoute une chose\r
+terrible.\r
+\r
+--Oui, c'est cela, achève! disait la voix. Complète ce que tu fais!\r
+détruis ces flambeaux! anéantis ce souvenir! oublie l'évêque! oublie\r
+tout! perds ce Champmathieu! va, c'est bien. Applaudis-toi! Ainsi, c'est\r
+convenu, c'est résolu, c'est dit, voilà un homme, voilà un vieillard qui\r
+ne sait ce qu'on lui veut, qui n'a rien fait peut-être, un innocent,\r
+dont ton nom fait tout le malheur, sur qui ton nom pèse comme un crime,\r
+qui va être pris pour toi, qui va être condamné, qui va finir ses jours\r
+dans l'abjection et dans l'horreur! c'est bien. Sois honnête homme, toi.\r
+Reste monsieur le maire, reste honorable et honoré, enrichis la ville,\r
+nourris des indigents, élève des orphelins, vis heureux, vertueux et\r
+admiré, et pendant ce temps-là, pendant que tu seras ici dans la joie et\r
+dans la lumière, il y aura quelqu'un qui aura ta casaque rouge, qui\r
+portera ton nom dans l'ignominie et qui traînera ta chaîne au bagne!\r
+Oui, c'est bien arrangé ainsi! Ah! misérable!\r
+\r
+La sueur lui coulait du front. Il attachait sur les flambeaux un oeil\r
+hagard. Cependant ce qui parlait en lui n'avait pas fini. La voix\r
+continuait:\r
+\r
+--Jean Valjean! il y aura autour de toi beaucoup de voix qui feront un\r
+grand bruit, qui parleront bien haut, et qui te béniront, et une seule\r
+que personne n'entendra et qui te maudira dans les ténèbres. Eh bien!\r
+écoute, infâme! toutes ces bénédictions retomberont avant d'arriver au\r
+ciel, et il n'y aura que la malédiction qui montera jusqu'à Dieu! Cette\r
+voix, d'abord toute faible et qui s'était élevée du plus obscur de sa\r
+conscience, était devenue par degrés éclatante et formidable, et il\r
+l'entendait maintenant à son oreille. Il lui semblait qu'elle était\r
+sortie de lui-même et qu'elle parlait à présent en dehors de lui. Il\r
+crut entendre les dernières paroles si distinctement qu'il regarda dans\r
+la chambre avec une sorte de terreur.\r
+\r
+--Y a-t-il quelqu'un ici? demanda-t-il à haute voix, et tout égaré.\r
+\r
+Puis il reprit avec un rire qui ressemblait au rire d'un idiot:\r
+\r
+--Que je suis bête! il ne peut y avoir personne.\r
+\r
+Il y avait quelqu'un; mais celui qui y était n'était pas de ceux que\r
+l'oeil humain peut voir.\r
+\r
+Il posa les flambeaux sur la cheminée.\r
+\r
+Alors il reprit cette marche monotone et lugubre qui troublait dans ses\r
+rêves et réveillait en sursaut l'homme endormi au-dessous de lui.\r
+\r
+Cette marche le soulageait et l'enivrait en même temps. Il semble que\r
+parfois dans les occasions suprêmes on se remue pour demander conseil à\r
+tout ce qu'on peut rencontrer en se déplaçant. Au bout de quelques\r
+instants il ne savait plus où il en était.\r
+\r
+Il reculait maintenant avec une égale épouvante devant les deux\r
+résolutions qu'il avait prises tour à tour. Les deux idées qui le\r
+conseillaient lui paraissaient aussi funestes l'une que l'autre.--Quelle\r
+fatalité! quelle rencontre que ce Champmathieu pris pour lui! Être\r
+précipité justement par le moyen que la providence paraissait d'abord\r
+avoir employé pour l'affermir!\r
+\r
+Il y eut un moment où il considéra l'avenir. Se dénoncer, grand Dieu! se\r
+livrer! Il envisagea avec un immense désespoir tout ce qu'il faudrait\r
+quitter, tout ce qu'il faudrait reprendre. Il faudrait donc dire adieu à\r
+cette existence si bonne, si pure, si radieuse, à ce respect de tous, à\r
+l'honneur, à la liberté! Il n'irait plus se promener dans les champs, il\r
+n'entendrait plus chanter les oiseaux au mois de mai, il ne ferait plus\r
+l'aumône aux petits enfants! Il ne sentirait plus la douceur des regards\r
+de reconnaissance et d'amour fixés sur lui! Il quitterait cette maison\r
+qu'il avait bâtie, cette chambre, cette petite chambre! Tout lui\r
+paraissait charmant à cette heure. Il ne lirait plus dans ces livres, il\r
+n'écrirait plus sur cette petite table de bois blanc! Sa vieille\r
+portière, la seule servante qu'il eût, ne lui monterait plus son café le\r
+matin. Grand Dieu! au lieu de cela, la chiourme, le carcan, la veste\r
+rouge, la chaîne au pied, la fatigue, le cachot, le lit de camp, toutes\r
+ces horreurs connues! À son âge, après avoir été ce qu'il était! Si\r
+encore il était jeune! Mais, vieux, être tutoyé par le premier venu,\r
+être fouillé par le garde-chiourme, recevoir le coup de bâton de\r
+l'argousin! avoir les pieds nus dans des souliers ferrés! tendre matin\r
+et soir sa jambe au marteau du rondier qui visite la manille! subir la\r
+curiosité des étrangers auxquels on dirait: _Celui-là, c'est le fameux\r
+Jean Valjean, qui a été maire à Montreuil-sur-mer_! Le soir, ruisselant\r
+de sueur, accablé de lassitude, le bonnet vert sur les yeux, remonter\r
+deux à deux, sous le fouet du sergent, l'escalier-échelle du bagne\r
+flottant! Oh! quelle misère! La destinée peut-elle donc être méchante\r
+comme un être intelligent et devenir monstrueuse comme le coeur humain!\r
+\r
+Et, quoi qu'il fît, il retombait toujours sur ce poignant dilemme qui\r
+était au fond de sa rêverie:--rester dans le paradis, et y devenir\r
+démon! rentrer dans l'enfer, et y devenir ange!\r
+\r
+Que faire, grand Dieu! que faire?\r
+\r
+La tourmente dont il était sorti avec tant de peine se déchaîna de\r
+nouveau en lui. Ses idées recommencèrent à se mêler. Elles prirent ce je\r
+ne sais quoi de stupéfié et de machinal qui est propre au désespoir. Ce\r
+nom de Romainville lui revenait sans cesse à l'esprit avec deux vers\r
+d'une chanson qu'il avait entendue autrefois. Il songeait que\r
+Romainville est un petit bois près Paris où les jeunes gens amoureux\r
+vont cueillir des lilas au mois d'avril.\r
+\r
+Il chancelait au dehors comme au dedans. Il marchait comme un petit\r
+enfant qu'on laisse aller seul.\r
+\r
+À de certains moments, luttant contre sa lassitude, il faisait effort\r
+pour ressaisir son intelligence. Il tâchait de se poser une dernière\r
+fois, et définitivement, le problème sur lequel il était en quelque\r
+sorte tombé d'épuisement. Faut-il se dénoncer? Faut-il se taire?--Il ne\r
+réussissait à rien voir de distinct. Les vagues aspects de tous les\r
+raisonnements ébauchés par sa rêverie tremblaient et se dissipaient l'un\r
+après l'autre en fumée. Seulement il sentait que, à quelque parti qu'il\r
+s'arrêtât, nécessairement, et sans qu'il fût possible d'y échapper,\r
+quelque chose de lui allait mourir; qu'il entrait dans un sépulcre à\r
+droite comme à gauche; qu'il accomplissait une agonie, l'agonie de son\r
+bonheur ou l'agonie de sa vertu.\r
+\r
+Hélas! toutes ses irrésolutions l'avaient repris. Il n'était pas plus\r
+avancé qu'au commencement.\r
+\r
+Ainsi se débattait sous l'angoisse cette malheureuse âme. Dix-huit cents\r
+ans avant cet homme infortuné, l'être mystérieux, en qui se résument\r
+toutes les saintetés et toutes les souffrances de l'humanité, avait\r
+aussi lui, pendant que les oliviers frémissaient au vent farouche de\r
+l'infini, longtemps écarté de la main l'effrayant calice qui lui\r
+apparaissait ruisselant d'ombre et débordant de ténèbres dans des\r
+profondeurs pleines d'étoiles.\r
+\r
+\r
+\r
+\r
+Chapitre IV\r
+\r
+Formes que prend la souffrance pendant le sommeil\r
+\r
+\r
+Trois heures du matin venaient de sonner, et il y avait cinq heures\r
+qu'il marchait ainsi, presque sans interruption lorsqu'il se laissa\r
+tomber sur sa chaise.\r
+\r
+Il s'y endormit et fit un rêve.\r
+\r
+Ce rêve, comme la plupart des rêves, ne se rapportait à la situation que\r
+par je ne sais quoi de funeste et de poignant, mais il lui fit\r
+impression. Ce cauchemar le frappa tellement que plus tard il l'a écrit.\r
+C'est un des papiers écrits de sa main qu'il a laissés. Nous croyons\r
+devoir transcrire ici cette chose textuellement.\r
+\r
+Quel que soit ce rêve, l'histoire de cette nuit serait incomplète si\r
+nous l'omettions. C'est la sombre aventure d'une âme malade.\r
+\r
+Le voici. Sur l'enveloppe nous trouvons cette ligne écrite: _Le rêve que\r
+j'ai eu cette nuit-là._\r
+\r
+«J'étais dans une campagne. Une grande campagne triste où il n'y avait\r
+pas d'herbe. Il ne me semblait pas qu'il fît jour ni qu'il fît nuit.\r
+\r
+«Je me promenais avec mon frère, le frère de mes années d'enfance, ce\r
+frère auquel je dois dire que je ne pense jamais et dont je ne me\r
+souviens presque plus.\r
+\r
+«Nous causions, et nous rencontrions des passants. Nous parlions d'une\r
+voisine que nous avions eue autrefois, et qui, depuis qu'elle demeurait\r
+sur la rue, travaillait la fenêtre toujours ouverte. Tout en causant,\r
+nous avions froid à cause de cette fenêtre ouverte.\r
+\r
+«Il n'y avait pas d'arbres dans la campagne.\r
+\r
+«Nous vîmes un homme qui passa près de nous. C'était un homme tout nu,\r
+couleur de cendre, monté sur un cheval couleur de terre. L'homme n'avait\r
+pas de cheveux; on voyait son crâne et des veines sur son crâne. Il\r
+tenait à la main une baguette qui était souple comme un sarment de vigne\r
+et lourde comme du fer. Ce cavalier passa et ne nous dit rien.\r
+\r
+«Mon frère me dit: Prenons par le chemin creux.\r
+\r
+«Il y avait un chemin creux où l'on ne voyait pas une broussaille ni un\r
+brin de mousse. Tout était couleur de terre, même le ciel. Au bout de\r
+quelques pas, on ne me répondit plus quand je parlais. Je m'aperçus que\r
+mon frère n'était plus avec moi.\r
+\r
+«J'entrai dans un village que je vis. Je songeai que ce devait être là\r
+Romainville (pourquoi Romainville?).\r
+\r
+«La première rue où j'entrai était déserte. J'entrai dans une seconde\r
+rue. Derrière l'angle que faisaient les deux rues, il y avait un homme\r
+debout contre le mur. Je dis à cet homme:--Quel est ce pays? où suis-je?\r
+L'homme ne répondit pas. Je vis la porte d'une maison ouverte, j'y\r
+entrai.\r
+\r
+«La première chambre était déserte. J'entrai dans la seconde. Derrière\r
+la porte de cette chambre, il y avait un homme debout contre le mur. Je\r
+demandai à cet homme:--À qui est cette maison? où suis-je? L'homme ne\r
+répondit pas. La maison avait un jardin.\r
+\r
+«Je sortis de la maison et j'entrai dans le jardin. Le jardin était\r
+désert. Derrière le premier arbre, je trouvai un homme qui se tenait\r
+debout. Je dis à cet homme:--Quel est ce jardin? où suis-je? L'homme ne\r
+répondit pas.\r
+\r
+«J'errai dans le village, et je m'aperçus que c'était une ville. Toutes\r
+les rues étaient désertes, toutes les portes étaient ouvertes. Aucun\r
+être vivant ne passait dans les rues, ne marchait dans les chambres ou\r
+ne se promenait dans les jardins. Mais il y avait derrière chaque angle\r
+de mur, derrière chaque porte, derrière chaque arbre, un homme debout\r
+qui se taisait. On n'en voyait jamais qu'un à la fois. Ces hommes me\r
+regardaient passer.\r
+\r
+«Je sortis de la ville et je me mis à marcher dans les champs.\r
+\r
+«Au bout de quelque temps, je me retournai, et je vis une grande foule\r
+qui venait derrière moi. Je reconnus tous les hommes que j'avais vus\r
+dans la ville. Ils avaient des têtes étranges. Ils ne semblaient pas se\r
+hâter, et cependant ils marchaient plus vite que moi. Ils ne faisaient\r
+aucun bruit en marchant. En un instant, cette foule me rejoignit et\r
+m'entoura. Les visages de ces hommes étaient couleur de terre.\r
+\r
+«Alors le premier que j'avais vu et questionné en entrant dans la ville\r
+me dit:--Où allez-vous? Est-ce que vous ne savez pas que vous êtes mort\r
+depuis longtemps?\r
+\r
+«J'ouvris la bouche pour répondre, et je m'aperçus qu'il n'y avait\r
+personne autour de moi.»\r
+\r
+Il se réveilla. Il était glacé. Un vent qui était froid comme le vent du\r
+matin faisait tourner dans leurs gonds les châssis de la croisée restée\r
+ouverte. Le feu s'était éteint. La bougie touchait à sa fin. Il était\r
+encore nuit noire.\r
+\r
+Il se leva, il alla à la fenêtre. Il n'y avait toujours pas d'étoiles au\r
+ciel.\r
+\r
+De sa fenêtre on voyait la cour de la maison et la rue. Un bruit sec et\r
+dur qui résonna tout à coup sur le sol lui fit baisser les yeux.\r
+\r
+Il vit au-dessous de lui deux étoiles rouges dont les rayons\r
+s'allongeaient et se raccourcissaient bizarrement dans l'ombre.\r
+\r
+Comme sa pensée était encore à demi submergée dans la brume des\r
+rêves.--tiens! songea-t-il, il n'y en a pas dans le ciel. Elles sont sur\r
+la terre maintenant.\r
+\r
+Cependant ce trouble se dissipa, un second bruit pareil au premier\r
+acheva de le réveiller; il regarda, et il reconnut que ces deux étoiles\r
+étaient les lanternes d'une voiture. À la clarté qu'elles jetaient, il\r
+put distinguer la forme de cette voiture. C'était un tilbury attelé d'un\r
+petit cheval blanc. Le bruit qu'il avait entendu, c'étaient les coups de\r
+pied du cheval sur le pavé.\r
+\r
+--Qu'est-ce que c'est que cette voiture? se dit-il. Qui est-ce qui vient\r
+donc si matin? En ce moment on frappa un petit coup à la porte de sa\r
+chambre.\r
+\r
+Il frissonna de la tête aux pieds, et cria d'une voix terrible:\r
+\r
+--Qui est là?\r
+\r
+Quelqu'un répondit:\r
+\r
+--Moi, monsieur le maire.\r
+\r
+Il reconnut la voix de la vieille femme, sa portière.\r
+\r
+--Eh bien, reprit-il, qu'est-ce que c'est?\r
+\r
+--Monsieur le maire, il est tout à l'heure cinq heures du matin.\r
+\r
+--Qu'est-ce que cela me fait?\r
+\r
+--Monsieur le maire, c'est le cabriolet.\r
+\r
+--Quel cabriolet?\r
+\r
+--Le tilbury.\r
+\r
+--Quel tilbury?\r
+\r
+--Est-ce que monsieur le maire n'a pas fait demander un tilbury?\r
+\r
+--Non, dit-il.\r
+\r
+--Le cocher dit qu'il vient chercher monsieur le maire.\r
+\r
+--Quel cocher?\r
+\r
+--Le cocher de M. Scaufflaire.\r
+\r
+--M. Scaufflaire?\r
+\r
+Ce nom le fit tressaillir comme si un éclair lui eût passé devant la\r
+face.\r
+\r
+--Ah! oui! reprit-il, M. Scaufflaire.\r
+\r
+Si la vieille femme l'eût pu voir en ce moment, elle eût été épouvantée.\r
+\r
+Il se fit un assez long silence. Il examinait d'un air stupide la flamme\r
+de la bougie et prenait autour de la mèche de la cire brûlante qu'il\r
+roulait dans ses doigts.\r
+\r
+La vieille attendait. Elle se hasarda pourtant à élever encore la voix:\r
+\r
+--Monsieur le maire, que faut-il que je réponde?\r
+\r
+--Dites que c'est bien, et que je descends.\r
+\r
+\r
+\r
+\r
+Chapitre V\r
+\r
+Bâtons dans les roues\r
+\r
+\r
+Le service des postes d'Arras à Montreuil-sur-mer se faisait encore à\r
+cette époque par de petites malles du temps de l'empire. Ces malles\r
+étaient des cabriolets à deux roues, tapissés de cuir fauve au dedans,\r
+suspendus sur des ressorts à pompe, et n'ayant que deux places, l'une\r
+pour le courrier, l'autre pour le voyageur. Les roues étaient armées de\r
+ces longs moyeux offensifs qui tiennent les autres voitures à distance\r
+et qu'on voit encore sur les routes d'Allemagne. Le coffre aux dépêches,\r
+immense boîte oblongue, était placé derrière le cabriolet et faisait\r
+corps avec lui. Ce coffre était peint en noir et le cabriolet en jaune.\r
+\r
+Ces voitures, auxquelles rien ne ressemble aujourd'hui, avaient je ne\r
+sais quoi de difforme et de bossu, et, quand on les voyait passer de\r
+loin et ramper dans quelque route à l'horizon, elles ressemblaient à ces\r
+insectes qu'on appelle, je crois, termites, et qui, avec un petit\r
+corsage, traînent un gros arrière-train. Elles allaient, du reste, fort\r
+vite. La malle partie d'Arras toutes les nuits à une heure, après le\r
+passage du courrier de Paris, arrivait à Montreuil-sur-mer un peu avant\r
+cinq heures du matin.\r
+\r
+Cette nuit-là, la malle qui descendait à Montreuil-sur-mer par la route\r
+de Hesdin accrocha, au tournant d'une rue, au moment où elle entrait\r
+dans la ville, un petit tilbury attelé d'un cheval blanc, qui venait en\r
+sens inverse et dans lequel il n'y avait qu'une personne, un homme\r
+enveloppé d'un manteau. La roue du tilbury reçut un choc assez rude. Le\r
+courrier cria à cet homme d'arrêter, mais le voyageur n'écouta pas, et\r
+continua sa route au grand trot.\r
+\r
+--Voilà un homme diablement pressé! dit le courrier.\r
+\r
+L'homme qui se hâtait ainsi, c'est celui que nous venons de voir se\r
+débattre dans des convulsions dignes à coup sûr de pitié.\r
+\r
+Où allait-il? Il n'eût pu le dire. Pourquoi se hâtait-il? Il ne savait.\r
+Il allait au hasard devant lui. Où? À Arras sans doute; mais il allait\r
+peut-être ailleurs aussi. Par moments il le sentait, et il tressaillait.\r
+\r
+Il s'enfonçait dans cette nuit comme dans un gouffre. Quelque chose le\r
+poussait, quelque chose l'attirait. Ce qui se passait en lui, personne\r
+ne pourrait le dire, tous le comprendront. Quel homme n'est entré, au\r
+moins une fois en sa vie, dans cette obscure caverne de l'inconnu?\r
+\r
+Du reste il n'avait rien résolu, rien décidé, rien arrêté, rien fait.\r
+Aucun des actes de sa conscience n'avait été définitif. Il était plus\r
+que jamais comme au premier moment. Pourquoi allait-il à Arras?\r
+\r
+Il se répétait ce qu'il s'était déjà dit en retenant le cabriolet de\r
+Scaufflaire,--que, quel que dût être le résultat, il n'y avait aucun\r
+inconvénient à voir de ses yeux, à juger les choses par lui-même;--que\r
+cela même était prudent, qu'il fallait savoir ce qui se passerait; qu'on\r
+ne pouvait rien décider sans avoir observé et scruté;--que de loin on se\r
+faisait des montagnes de tout; qu'au bout du compte, lorsqu'il aurait vu\r
+ce Champmathieu, quelque misérable, sa conscience serait probablement\r
+fort soulagée de le laisser aller au bagne à sa place;--qu'à la vérité\r
+il y aurait là Javert, et ce Brevet, ce Chenildieu, ce Cochepaille,\r
+anciens forçats qui l'avaient connu; mais qu'à coup sûr ils ne le\r
+reconnaîtraient pas;--bah! quelle idée!--que Javert en était à cent\r
+lieues;--que toutes les conjectures et toutes les suppositions étaient\r
+fixées sur ce Champmathieu, et que rien n'est entêté comme les\r
+suppositions et les conjectures;--qu'il n'y avait donc aucun danger. Que\r
+sans doute c'était un moment noir, mais qu'il en sortirait;--qu'après\r
+tout il tenait sa destinée, si mauvaise qu'elle voulût être, dans sa\r
+main;--qu'il en était le maître. Il se cramponnait à cette pensée.\r
+\r
+Au fond, pour tout dire, il eût mieux aimé ne point aller à Arras.\r
+\r
+Cependant il y allait.\r
+\r
+Tout en songeant, il fouettait le cheval, lequel trottait de ce bon trot\r
+réglé et sûr qui fait deux lieues et demie à l'heure.\r
+\r
+À mesure que le cabriolet avançait, il sentait quelque chose en lui qui\r
+reculait.\r
+\r
+Au point du jour il était en rase campagne; la ville de\r
+Montreuil-sur-mer était assez loin derrière lui. Il regarda l'horizon\r
+blanchir; il regarda, sans les voir, passer devant ses yeux toutes les\r
+froides figures d'une aube d'hiver. Le matin a ses spectres comme le\r
+soir. Il ne les voyait pas, mais, à son insu, et par une sorte de\r
+pénétration presque physique, ces noires silhouettes d'arbres et de\r
+collines ajoutaient à l'état violent de son âme je ne sais quoi de morne\r
+et de sinistre.\r
+\r
+Chaque fois qu'il passait devant une de ces maisons isolées qui côtoient\r
+parfois les routes, il se disait: il y a pourtant là-dedans des gens qui\r
+dorment!\r
+\r
+Le trot du cheval, les grelots du harnais, les roues sur le pavé,\r
+faisaient un bruit doux et monotone. Ces choses-là sont charmantes quand\r
+on est joyeux et lugubres quand on est triste. Il était grand jour\r
+lorsqu'il arriva à Hesdin. Il s'arrêta devant une auberge pour laisser\r
+souffler le cheval et lui faire donner l'avoine.\r
+\r
+Ce cheval était, comme l'avait dit Scaufflaire, de cette petite race du\r
+Boulonnais qui a trop de tête, trop de ventre et pas assez d'encolure,\r
+mais qui a le poitrail ouvert, la croupe large, la jambe sèche et fine\r
+et le pied solide; race laide, mais robuste et saine. L'excellente bête\r
+avait fait cinq lieues en deux heures et n'avait pas une goutte de sueur\r
+sur la croupe.\r
+\r
+Il n'était pas descendu du tilbury. Le garçon d'écurie qui apportait\r
+l'avoine se baissa tout à coup et examina la roue de gauche.\r
+\r
+--Allez-vous loin comme cela? dit cet homme.\r
+\r
+Il répondit, presque sans sortir de sa rêverie:\r
+\r
+--Pourquoi?\r
+\r
+--Venez-vous de loin? reprit le garçon.\r
+\r
+--De cinq lieues d'ici.\r
+\r
+--Ah!\r
+\r
+--Pourquoi dites-vous: ah?\r
+\r
+Le garçon se pencha de nouveau, resta un moment silencieux, l'oeil fixé\r
+sur la roue, puis se redressa en disant:\r
+\r
+--C'est que voilà une roue qui vient de faire cinq lieues, c'est\r
+possible, mais qui à coup sûr ne fera pas maintenant un quart de lieue.\r
+\r
+Il sauta à bas du tilbury.\r
+\r
+--Que dites-vous là, mon ami?\r
+\r
+--Je dis que c'est un miracle que vous ayez fait cinq lieues sans\r
+rouler, vous et votre cheval, dans quelque fossé de la grande route.\r
+Regardez plutôt.\r
+\r
+La roue en effet était gravement endommagée. Le choc de la malle-poste\r
+avait fendu deux rayons et labouré le moyeu dont l'écrou ne tenait plus.\r
+\r
+--Mon ami, dit-il au garçon d'écurie, il y a un charron ici?\r
+\r
+--Sans doute, monsieur.\r
+\r
+--Rendez-moi le service de l'aller chercher.\r
+\r
+--Il est là, à deux pas. Hé! maître Bourgaillard!\r
+\r
+Maître Bourgaillard, le charron, était sur le seuil de sa porte. Il vint\r
+examiner la roue et fit la grimace d'un chirurgien qui considère une\r
+jambe cassée.\r
+\r
+--Pouvez-vous raccommoder cette roue sur-le-champ?\r
+\r
+--Oui, monsieur.\r
+\r
+--Quand pourrai-je repartir?\r
+\r
+--Demain.\r
+\r
+--Demain!\r
+\r
+--Il y a une grande journée d'ouvrage. Est-ce que monsieur est pressé?\r
+\r
+--Très pressé. Il faut que je reparte dans une heure au plus tard.\r
+\r
+--Impossible, monsieur.\r
+\r
+--Je payerai tout ce qu'on voudra.\r
+\r
+--Impossible.\r
+\r
+--Eh bien! dans deux heures.\r
+\r
+--Impossible pour aujourd'hui. Il faut refaire deux rais et un moyeu.\r
+Monsieur ne pourra repartir avant demain.\r
+\r
+--L'affaire que j'ai ne peut attendre à demain. Si, au lieu de\r
+raccommoder cette roue, on la remplaçait?\r
+\r
+--Comment cela?\r
+\r
+--Vous êtes charron?\r
+\r
+--Sans doute, monsieur.\r
+\r
+--Est-ce que vous n'auriez pas une roue à me vendre? Je pourrais\r
+repartir tout de suite.\r
+\r
+--Une roue de rechange?\r
+\r
+--Oui.\r
+\r
+--Je n'ai pas une roue toute faite pour votre cabriolet. Deux roues font\r
+la paire. Deux roues ne vont pas ensemble au hasard.\r
+\r
+--En ce cas, vendez-moi une paire de roues.\r
+\r
+--Monsieur, toutes les roues ne vont pas à tous les essieux.\r
+\r
+--Essayez toujours.\r
+\r
+--C'est inutile, monsieur. Je n'ai à vendre que des roues de charrette.\r
+Nous sommes un petit pays ici.\r
+\r
+--Auriez-vous un cabriolet à me louer?\r
+\r
+Le maître charron, du premier coup d'oeil, avait reconnu que le tilbury\r
+était une voiture de louage. Il haussa les épaules.\r
+\r
+--Vous les arrangez bien, les cabriolets qu'on vous loue! j'en aurais un\r
+que je ne vous le louerais pas.\r
+\r
+--Eh bien, à me vendre?\r
+\r
+--Je n'en ai pas.\r
+\r
+--Quoi! pas une carriole? Je ne suis pas difficile, comme vous voyez.\r
+\r
+--Nous sommes un petit pays. J'ai bien là sous la remise, ajouta le\r
+charron, une vieille calèche qui est à un bourgeois de la ville qui me\r
+l'a donnée en garde et qui s'en sert tous les trente-six du mois. Je\r
+vous la louerais bien, qu'est-ce que cela me fait? mais il ne faudrait\r
+pas que le bourgeois la vît passer; et puis, c'est une calèche, il\r
+faudrait deux chevaux.\r
+\r
+--Je prendrai des chevaux de poste.\r
+\r
+--Où va monsieur?\r
+\r
+--À Arras.\r
+\r
+--Et monsieur veut arriver aujourd'hui?\r
+\r
+--Mais oui.\r
+\r
+--En prenant des chevaux de poste?\r
+\r
+--Pourquoi pas?\r
+\r
+--Est-il égal à monsieur d'arriver cette nuit à quatre heures du matin?\r
+\r
+--Non certes.\r
+\r
+--C'est que, voyez-vous bien, il y a une chose à dire, en prenant des\r
+chevaux de poste....\r
+\r
+--Monsieur a son passeport?\r
+\r
+--Oui.\r
+\r
+--Eh bien, en prenant des chevaux de poste, monsieur n'arrivera pas à\r
+Arras avant demain. Nous sommes un chemin de traverse. Les relais sont\r
+mal servis, les chevaux sont aux champs. C'est la saison des grandes\r
+charrues qui commence, il faut de forts attelages, et l'on prend les\r
+chevaux partout, à la poste comme ailleurs. Monsieur attendra au moins\r
+trois ou quatre heures à chaque relais. Et puis on va au pas. Il y a\r
+beaucoup de côtes à monter.\r
+\r
+--Allons, j'irai à cheval. Dételez le cabriolet. On me vendra bien une\r
+selle dans le pays.\r
+\r
+--Sans doute. Mais ce cheval-ci endure-t-il la selle?\r
+\r
+--C'est vrai, vous m'y faites penser. Il ne l'endure pas.\r
+\r
+--Alors....\r
+\r
+--Mais je trouverai bien dans le village un cheval à louer?\r
+\r
+--Un cheval pour aller à Arras d'une traite!\r
+\r
+--Oui.\r
+\r
+--Il faudrait un cheval comme on n'en a pas dans nos endroits. Il\r
+faudrait l'acheter d'abord, car on ne vous connaît pas. Mais ni à vendre\r
+ni à louer, ni pour cinq cents francs, ni pour mille, vous ne le\r
+trouveriez pas!\r
+\r
+--Comment faire?\r
+\r
+--Le mieux, là, en honnête homme, c'est que je raccommode la roue et que\r
+vous remettiez votre voyage à demain.\r
+\r
+--Demain il sera trop tard.\r
+\r
+--Dame!\r
+\r
+--N'y a-t-il pas la malle-poste qui va à Arras? Quand passe-t-elle?\r
+\r
+--La nuit prochaine. Les deux malles font le service la nuit, celle qui\r
+monte comme celle qui descend.\r
+\r
+--Comment! il vous faut une journée pour raccommoder cette roue?\r
+\r
+--Une journée, et une bonne!\r
+\r
+--En mettant deux ouvriers?\r
+\r
+--En en mettant dix!\r
+\r
+--Si on liait les rayons avec des cordes?\r
+\r
+--Les rayons, oui; le moyeu, non. Et puis la jante aussi est en mauvais\r
+état.\r
+\r
+--Y a-t-il un loueur de voitures dans la ville?\r
+\r
+--Non.\r
+\r
+--Y a-t-il un autre charron?\r
+\r
+Le garçon d'écurie et le maître charron répondirent en même temps en\r
+hochant la tête.\r
+\r
+--Non.\r
+\r
+Il sentit une immense joie.\r
+\r
+Il était évident que la providence s'en mêlait. C'était elle qui avait\r
+brisé la roue du tilbury et qui l'arrêtait en route. Il ne s'était pas\r
+rendu à cette espèce de première sommation; il venait de faire tous les\r
+efforts possibles pour continuer son voyage; il avait loyalement et\r
+scrupuleusement épuisé tous les moyens; il n'avait reculé ni devant la\r
+saison, ni devant la fatigue, ni devant la dépense; il n'avait rien à se\r
+reprocher. S'il n'allait pas plus loin, cela ne le regardait plus. Ce\r
+n'était plus sa faute, c'était, non le fait de sa conscience, mais le\r
+fait de la providence.\r
+\r
+Il respira. Il respira librement et à pleine poitrine pour la première\r
+fois depuis la visite de Javert. Il lui semblait que le poignet de fer\r
+qui lui serrait le coeur depuis vingt heures venait de le lâcher.\r
+\r
+Il lui paraissait que maintenant Dieu était pour lui, et se déclarait.\r
+\r
+Il se dit qu'il avait fait tout ce qu'il pouvait, et qu'à présent il\r
+n'avait qu'à revenir sur ses pas, tranquillement.\r
+\r
+Si sa conversation avec le charron eût eu lieu dans une chambre de\r
+l'auberge, elle n'eût point eu de témoins, personne ne l'eût entendue,\r
+les choses en fussent restées là, et il est probable que nous n'aurions\r
+eu à raconter aucun des événements qu'on va lire; mais cette\r
+conversation s'était faite dans la rue. Tout colloque dans la rue\r
+produit inévitablement un cercle. Il y a toujours des gens qui ne\r
+demandent qu'à être spectateurs. Pendant qu'il questionnait le charron,\r
+quelques allants et venants s'étaient arrêtés autour d'eux. Après avoir\r
+écouté pendant quelques minutes, un jeune garçon, auquel personne\r
+n'avait pris garde, s'était détaché du groupe en courant.\r
+\r
+Au moment où le voyageur, après la délibération intérieure que nous\r
+venons d'indiquer, prenait la résolution de rebrousser chemin, cet\r
+enfant revenait. Il était accompagné d'une vieille femme.\r
+\r
+--Monsieur, dit la femme, mon garçon me dit que vous avez envie de louer\r
+un cabriolet. Cette simple parole, prononcée par une vieille femme que\r
+conduisait un enfant, lui fit ruisseler la sueur dans les reins. Il crut\r
+voir la main qui l'avait lâché reparaître dans l'ombre derrière lui,\r
+toute prête à le reprendre.\r
+\r
+Il répondit:\r
+\r
+--Oui, bonne femme, je cherche un cabriolet à louer.\r
+\r
+Et il se hâta d'ajouter:\r
+\r
+--Mais il n'y en a pas dans le pays.\r
+\r
+--Si fait, dit la vieille.\r
+\r
+--Où ça donc? reprit le charron.\r
+\r
+--Chez moi, répliqua la vieille.\r
+\r
+Il tressaillit. La main fatale l'avait ressaisi.\r
+\r
+La vieille avait en effet sous un hangar une façon de carriole en osier.\r
+Le charron et le garçon d'auberge, désolés que le voyageur leur\r
+échappât, intervinrent.\r
+\r
+--C'était une affreuse guimbarde,--cela était posé à cru sur\r
+l'essieu,--il est vrai que les banquettes étaient suspendues à\r
+l'intérieur avec des lanières de cuir,--il pleuvait dedans,--les roues\r
+étaient rouillées et rongées d'humidité,--cela n'irait pas beaucoup plus\r
+loin que le tilbury,--une vraie patache!--Ce monsieur aurait bien tort\r
+de s'y embarquer,--etc., etc.\r
+\r
+Tout cela était vrai, mais cette guimbarde, cette patache, cette chose,\r
+quelle qu'elle fût, roulait sur ses deux roues et pouvait aller à Arras.\r
+\r
+Il paya ce qu'on voulut, laissa le tilbury à réparer chez le charron\r
+pour l'y retrouver à son retour, fit atteler le cheval blanc à la\r
+carriole, y monta, et reprit la route qu'il suivait depuis le matin.\r
+\r
+Au moment où la carriole s'ébranla, il s'avoua qu'il avait eu l'instant\r
+d'auparavant une certaine joie de songer qu'il n'irait point où il\r
+allait. Il examina cette joie avec une sorte de colère et la trouva\r
+absurde. Pourquoi de la joie à revenir en arrière? Après tout, il\r
+faisait ce voyage librement. Personne ne l'y forçait. Et, certainement,\r
+rien n'arriverait que ce qu'il voudrait bien.\r
+\r
+Comme il sortait de Hesdin, il entendit une voix qui lui criait:\r
+arrêtez! arrêtez! Il arrêta la carriole d'un mouvement vif dans lequel\r
+il y avait encore je ne sais quoi de fébrile et de convulsif qui\r
+ressemblait à de l'espérance.\r
+\r
+C'était le petit garçon de la vieille.\r
+\r
+--Monsieur, dit-il, c'est moi qui vous ai procuré la carriole.\r
+\r
+--Eh bien!\r
+\r
+--Vous ne m'avez rien donné.\r
+\r
+Lui qui donnait à tous et si facilement, il trouva cette prétention\r
+exorbitante et presque odieuse.\r
+\r
+--Ah! c'est toi, drôle? dit-il, tu n'auras rien!\r
+\r
+Il fouetta le cheval et repartit au grand trot.\r
+\r
+Il avait perdu beaucoup de temps à Hesdin, il eût voulu le rattraper. Le\r
+petit cheval était courageux et tirait comme deux; mais on était au mois\r
+de février, il avait plu, les routes étaient mauvaises. Et puis, ce\r
+n'était plus le tilbury. La carriole était dure et très lourde. Avec\r
+cela force montées.\r
+\r
+Il mit près de quatre heures pour aller de Hesdin à Saint-Pol. Quatre\r
+heures pour cinq lieues.\r
+\r
+À Saint-Pol il détela à la première auberge venue, et fit mener le\r
+cheval à l'écurie. Comme il l'avait promis à Scaufflaire, il se tint\r
+près du râtelier pendant que le cheval mangeait. Il songeait à des\r
+choses tristes et confuses.\r
+\r
+La femme de l'aubergiste entre dans l'écurie.\r
+\r
+--Est-ce que monsieur ne veut pas déjeuner?\r
+\r
+--Tiens, c'est vrai, dit-il, j'ai même bon appétit. Il suivit cette\r
+femme qui avait une figure fraîche et réjouie. Elle le conduisit dans\r
+une salle basse où il y avait des tables ayant pour nappes des toiles\r
+cirées.\r
+\r
+--Dépêchez-vous, reprit-il, il faut que je reparte. Je suis pressé.\r
+\r
+Une grosse servante flamande mit son couvert en toute hâte. Il regardait\r
+cette fille avec un sentiment de bien-être.\r
+\r
+--C'est là ce que j'avais, pensa-t-il. Je n'avais pas déjeuné.\r
+\r
+On le servit. Il se jeta sur le pain, mordit une bouchée, puis le reposa\r
+lentement sur la table et n'y toucha plus.\r
+\r
+Un routier mangeait à une autre table. Il dit à cet homme:\r
+\r
+--Pourquoi leur pain est-il donc si amer?\r
+\r
+Le routier était allemand et n'entendit pas.\r
+\r
+Il retourna dans l'écurie près du cheval.\r
+\r
+Une heure après, il avait quitté Saint-Pol et se dirigeait vers Tinques\r
+qui n'est qu'à cinq lieues d'Arras.\r
+\r
+Que faisait-il pendant ce trajet? À quoi pensait-il? Comme le matin, il\r
+regardait passer les arbres, les toits de chaume, les champs cultivés,\r
+et les évanouissements du paysage qui se disloque à chaque coude du\r
+chemin. C'est là une contemplation qui suffit quelquefois à l'âme et qui\r
+la dispense presque de penser. Voir mille objets pour la première et\r
+pour la dernière fois, quoi de plus mélancolique et de plus profond!\r
+Voyager, c'est naître et mourir à chaque instant. Peut-être, dans la\r
+région la plus vague de son esprit, faisait-il des rapprochements entre\r
+ces horizons changeants et l'existence humaine. Toutes les choses de la\r
+vie sont perpétuellement en fuite devant nous. Les obscurcissements et\r
+les clartés s'entremêlent: après un éblouissement, une éclipse; on\r
+regarde, on se hâte, on tend les mains pour saisir ce qui passe; chaque\r
+événement est un tournant de la route; et tout à coup on est vieux. On\r
+sent comme une secousse, tout est noir, on distingue une porte obscure,\r
+ce sombre cheval de la vie qui vous traînait s'arrête, et l'on voit\r
+quelqu'un de voilé et d'inconnu qui le dételle dans les ténèbres.\r
+\r
+Le crépuscule tombait au moment où des enfants qui sortaient de l'école\r
+regardèrent ce voyageur entrer dans Tinques. Il est vrai qu'on était\r
+encore aux jours courts de l'année. Il ne s'arrêta pas à Tinques. Comme\r
+il débouchait du village, un cantonnier qui empierrait la route dressa\r
+la tête et dit:\r
+\r
+--Voilà un cheval bien fatigué.\r
+\r
+La pauvre bête en effet n'allait plus qu'au pas.\r
+\r
+--Est-ce que vous allez à Arras? ajouta le cantonnier.\r
+\r
+--Oui.\r
+\r
+--Si vous allez de ce train, vous n'y arriverez pas de bonne heure.\r
+\r
+Il arrêta le cheval et demanda au cantonnier:\r
+\r
+--Combien y a-t-il encore d'ici à Arras?\r
+\r
+--Près de sept grandes lieues.\r
+\r
+--Comment cela? le livre de poste ne marque que cinq lieues et un quart.\r
+\r
+--Ah! reprit le cantonnier, vous ne savez donc pas que la route est en\r
+réparation? Vous allez la trouver coupée à un quart d'heure d'ici. Pas\r
+moyen d'aller plus loin.\r
+\r
+--Vraiment.\r
+\r
+--Vous prendrez à gauche, le chemin qui va à Carency, vous passerez la\r
+rivière; et, quand vous serez à Camblin, vous tournerez à droite; c'est\r
+la route de Mont-Saint-Éloy qui va à Arras.\r
+\r
+--Mais voilà la nuit, je me perdrai.\r
+\r
+--Vous n'êtes pas du pays?\r
+\r
+--Non.\r
+\r
+--Avec ça, c'est tout chemins de traverse. Tenez, Monsieur, reprit le\r
+cantonnier, voulez-vous que je vous donne un conseil? Votre cheval est\r
+las, rentrez dans Tinques. Il y a une bonne auberge. Couchez-y. Vous\r
+irez demain à Arras.\r
+\r
+--Il faut que j'y sois ce soir.\r
+\r
+--C'est différent. Alors allez tout de même à cette auberge et prenez-y\r
+un cheval de renfort. Le garçon du cheval vous guidera dans la traverse.\r
+\r
+Il suivit le conseil du cantonnier, rebroussa chemin, et une demi-heure\r
+après il repassait au même endroit, mais au grand trot, avec un bon\r
+cheval de renfort. Un garçon d'écurie qui s'intitulait postillon était\r
+assis sur le brancard de la carriole.\r
+\r
+Cependant il sentait qu'il perdait du temps.\r
+\r
+Il faisait tout à fait nuit.\r
+\r
+Ils s'engagèrent dans la traverse. La route devint affreuse. La carriole\r
+tombait d'une ornière dans l'autre. Il dit au postillon:\r
+\r
+--Toujours au trot, et double pourboire.\r
+\r
+Dans un cahot le palonnier cassa.\r
+\r
+--Monsieur, dit le postillon, voilà le palonnier cassé, je ne sais plus\r
+comment atteler mon cheval, cette route-ci est bien mauvaise la nuit; si\r
+vous vouliez revenir coucher à Tinques, nous pourrions être demain matin\r
+de bonne heure à Arras.\r
+\r
+Il répondit:\r
+\r
+--As-tu un bout de corde et un couteau?\r
+\r
+--Oui, monsieur.\r
+\r
+Il coupa une branche d'arbre et en fit un palonnier.\r
+\r
+Ce fut encore une perte de vingt minutes; mais ils repartirent au galop.\r
+\r
+La plaine était ténébreuse. Des brouillards bas, courts et noirs\r
+rampaient sur les collines et s'en arrachaient comme des fumées. Il y\r
+avait des lueurs blanchâtres dans les nuages. Un grand vent qui venait\r
+de la mer faisait dans tous les coins de l'horizon le bruit de quelqu'un\r
+qui remue des meubles. Tout ce qu'on entrevoyait avait des attitudes de\r
+terreur. Que de choses frissonnent sous ces vastes souffles de la nuit!\r
+\r
+Le froid le pénétrait. Il n'avait pas mangé depuis la veille. Il se\r
+rappelait vaguement son autre course nocturne dans la grande plaine aux\r
+environs de Digne. Il y avait huit ans; et cela lui semblait hier.\r
+\r
+Une heure sonna à quelque clocher lointain. Il demanda au garçon:\r
+\r
+--Quelle est cette heure?\r
+\r
+--Sept heures, monsieur. Nous serons à Arras à huit. Nous n'avons plus\r
+que trois lieues. En ce moment il fit pour la première fois cette\r
+réflexion--en trouvant étrange qu'elle ne lui fût pas venue plus\r
+tôt--que c'était peut-être inutile, toute la peine qu'il prenait; qu'il\r
+ne savait seulement pas l'heure du procès; qu'il aurait dû au moins s'en\r
+informer; qu'il était extravagant d'aller ainsi devant soi sans savoir\r
+si cela servirait à quelque chose.--Puis il ébaucha quelques calculs\r
+dans son esprit:--qu'ordinairement les séances des cours d'assises\r
+commençaient à neuf heures du matin;--que cela ne devait pas être long,\r
+cette affaire-là;--que le vol de pommes, ce serait très court;--qu'il\r
+n'y aurait plus ensuite qu'une question d'identité;--quatre ou cinq\r
+dépositions, peu de chose à dire pour les avocats;--qu'il allait\r
+arriver lorsque tout serait fini!\r
+\r
+Le postillon fouettait les chevaux. Ils avaient passé la rivière et\r
+laissé derrière eux Mont-Saint-Éloy.\r
+\r
+La nuit devenait de plus en plus profonde.\r
+\r
+\r
+\r
+\r
+Chapitre VI\r
+\r
+La soeur Simplice mise à l'épreuve\r
+\r
+\r
+Cependant, en ce moment-là même, Fantine était dans la joie.\r
+\r
+Elle avait passé une très mauvaise nuit. Toux affreuse, redoublement de\r
+fièvre; elle avait eu des songes. Le matin, à la visite du médecin, elle\r
+délirait. Il avait eu l'air alarmé et avait recommandé qu'on le prévînt\r
+dès que M. Madeleine viendrait.\r
+\r
+Toute la matinée elle fut morne, parla peu, et fit des plis à ses draps\r
+en murmurant à voix basse des calculs qui avaient l'air d'être des\r
+calculs de distances. Ses yeux étaient caves et fixes. Ils paraissaient\r
+presque éteints, et puis, par moments, ils se rallumaient et\r
+resplendissaient comme des étoiles. Il semble qu'aux approches d'une\r
+certaine heure sombre, la clarté du ciel emplisse ceux que quitte la\r
+clarté de la terre.\r
+\r
+Chaque fois que la soeur Simplice lui demandait comment elle se\r
+trouvait, elle répondait invariablement:\r
+\r
+--Bien. Je voudrais voir monsieur Madeleine.\r
+\r
+Quelques mois auparavant, à ce moment où Fantine venait de perdre sa\r
+dernière pudeur, sa dernière honte et sa dernière joie, elle était\r
+l'ombre d'elle-même; maintenant elle en était le spectre. Le mal\r
+physique avait complété l'oeuvre du mal moral. Cette créature de\r
+vingt-cinq ans avait le front ridé, les joues flasques, les narines\r
+pincées, les dents déchaussées, le teint plombé, le cou osseux, les\r
+clavicules saillantes, les membres chétifs, la peau terreuse, et ses\r
+cheveux blonds poussaient mêlés de cheveux gris. Hélas! comme la maladie\r
+improvise la vieillesse! À midi, le médecin revint, il fit quelques\r
+prescriptions, s'informa si M. le maire avait paru à l'infirmerie, et\r
+branla la tête.\r
+\r
+M. Madeleine venait d'habitude à trois heures voir la malade. Comme\r
+l'exactitude était de la bonté, il était exact.\r
+\r
+Vers deux heures et demie, Fantine commença à s'agiter. Dans l'espace de\r
+vingt minutes, elle demanda plus de dix fois à la religieuse:\r
+\r
+--Ma soeur, quelle heure est-il?\r
+\r
+Trois heures sonnèrent. Au troisième coup, Fantine se dressa sur son\r
+séant, elle qui d'ordinaire pouvait à peine remuer dans son lit; elle\r
+joignit dans une sorte d'étreinte convulsive ses deux mains décharnées\r
+et jaunes, et la religieuse entendit sortir de sa poitrine un de ces\r
+soupirs profonds qui semblent soulever un accablement. Puis Fantine se\r
+tourna et regarda la porte.\r
+\r
+Personne n'entra; la porte ne s'ouvrit point.\r
+\r
+Elle resta ainsi un quart d'heure, l'oeil attaché sur la porte, immobile\r
+et comme retenant son haleine. La soeur n'osait lui parler. L'église\r
+sonna trois heures un quart. Fantine se laissa retomber sur l'oreiller.\r
+\r
+Elle ne dit rien et se remit à faire des plis à son drap. La demi-heure\r
+passa, puis l'heure. Personne ne vint.\r
+\r
+Chaque fois que l'horloge sonnait, Fantine se dressait et regardait du\r
+côté de la porte, puis elle retombait.\r
+\r
+On voyait clairement sa pensée, mais elle ne prononçait aucun nom, elle\r
+ne se plaignait pas, elle n'accusait pas. Seulement elle toussait d'une\r
+façon lugubre. On eût dit que quelque chose d'obscur s'abaissait sur\r
+elle. Elle était livide et avait les lèvres bleues. Elle souriait par\r
+moments.\r
+\r
+Cinq heures sonnèrent. Alors la soeur l'entendit qui disait très bas et\r
+doucement:\r
+\r
+--Mais puisque je m'en vais demain, il a tort de ne pas venir\r
+aujourd'hui!\r
+\r
+La soeur Simplice elle-même était surprise du retard de M. Madeleine.\r
+\r
+Cependant Fantine regardait le ciel de son lit. Elle avait l'air de\r
+chercher à se rappeler quelque chose. Tout à coup elle se mit à chanter\r
+d'une voix faible comme un souffle. La religieuse écouta. Voici ce que\r
+Fantine chantait:\r
+\r
+          _Nous achèterons de bien belles choses_\r
+         _En nous promenant le long des faubourgs._\r
+       _Les bleuets sont bleus, les roses sont roses,_\r
+         _Les bleuets sont bleus, j'aime mes amours._\r
+           _La vierge Marie auprès de mon poêle_\r
+            _Est venue hier en manteau brodé,_\r
+         _Et m'a dit:--Voici, caché sous mon voile,_\r
+          _Le petit qu'un jour tu m'as demandé._\r
+          _Courez à la ville, ayez de la toile,_\r
+             _Achetez du fil, achetez un dé._\r
+          _Nous achèterons de bien belles choses_\r
+         _En nous promenant le long des faubourgs._\r
+         _Bonne sainte Vierge, auprès de mon poêle_\r
+           _J'ai mis un berceau de rubans orné_\r
+         _Dieu me donnerait sa plus belle étoile,_\r
+         _J'aime mieux l'enfant que tu m'as donné._\r
+          --_Madame, que faire avec cette toile?_\r
+         --_Faites un trousseau pour mon nouveau-né._\r
+        _Les bleuets sont bleus, les roses sont roses,_\r
+          _Les bleuets sont bleus, j'aime mes amours._\r
+                  --_Lavez cette toile._\r
+                --_Où?_--_Dans la rivière._\r
+             _Faites-en, sans rien gâter ni salir,_\r
+              _Une belle jupe avec sa brassière_\r
+            _Que je veux broder et de fleurs emplir._\r
+        --_L'enfant n'est plus là, madame, qu'en faire?_\r
+            --_Faites-en un drap pour m'ensevelir._\r
+             _Nous achèterons de bien belles choses_\r
+           _En nous promenant le long des faubourgs._\r
+         _Les bleuets sont bleus, les roses sont roses,_\r
+          _Les bleuets sont bleus, j'aime mes amours._\r
+\r
+Cette chanson était une vieille romance de berceuse avec laquelle\r
+autrefois elle endormait sa petite Cosette, et qui ne s'était pas\r
+offerte à son esprit depuis cinq ans qu'elle n'avait plus son enfant.\r
+Elle chantait cela d'une voix si triste et sur un air si doux que\r
+c'était à faire pleurer, même une religieuse. La soeur, habituée aux\r
+choses austères, sentit une larme lui venir.\r
+\r
+L'horloge sonna six heures. Fantine ne parut pas entendre. Elle semblait\r
+ne plus faire attention à aucune chose autour d'elle.\r
+\r
+La soeur Simplice envoya une fille de service s'informer près de la\r
+portière de la fabrique si M. le maire était rentré et s'il ne monterait\r
+pas bientôt à l'infirmerie. La fille revint au bout de quelques minutes.\r
+\r
+Fantine était toujours immobile et paraissait attentive à des idées\r
+qu'elle avait.\r
+\r
+La servante raconta très bas à la soeur Simplice que M. le maire était\r
+parti le matin même avant six heures dans un petit tilbury attelé d'un\r
+cheval blanc, par le froid qu'il faisait, qu'il était parti seul, pas\r
+même de cocher, qu'on ne savait pas le chemin qu'il avait pris, que des\r
+personnes disaient l'avoir vu tourner par la route d'Arras, que d'autres\r
+assuraient l'avoir rencontré sur la route de Paris. Qu'en s'en allant il\r
+avait été comme à l'ordinaire très doux, et qu'il avait seulement dit à\r
+la portière qu'on ne l'attendît pas cette nuit.\r
+\r
+Pendant que les deux femmes, le dos tourné au lit de la Fantine,\r
+chuchotaient, la soeur questionnant, la servante conjecturant, la\r
+Fantine, avec cette vivacité fébrile de certaines maladies organiques\r
+qui mêle les mouvements libres de la santé à l'effrayante maigreur de la\r
+mort, s'était mise à genoux sur son lit, ses deux poings crispés appuyés\r
+sur le traversin, et, la tête passée par l'intervalle des rideaux, elle\r
+écoutait. Tout à coup elle cria:\r
+\r
+--Vous parlez là de monsieur Madeleine! pourquoi parlez-vous tout bas?\r
+Qu'est-ce qu'il fait? Pourquoi ne vient-il pas?\r
+\r
+Sa voix était si brusque et si rauque que les deux femmes crurent\r
+entendre une voix d'homme; elles se retournèrent effrayées.\r
+\r
+--Répondez donc! cria Fantine.\r
+\r
+La servante balbutia:\r
+\r
+--La portière m'a dit qu'il ne pourrait pas venir aujourd'hui.\r
+\r
+--Mon enfant, dit la soeur, tenez-vous tranquille, recouchez-vous.\r
+\r
+Fantine, sans changer d'attitude, reprit d'une voix haute et avec un\r
+accent tout à la fois impérieux et déchirant:\r
+\r
+--Il ne pourra venir? Pourquoi cela? Vous savez la raison. Vous la\r
+chuchotiez là entre vous. Je veux la savoir.\r
+\r
+La servante se hâta de dire à l'oreille de la religieuse:\r
+\r
+--Répondez qu'il est occupé au conseil municipal.\r
+\r
+La soeur Simplice rougit légèrement; c'était un mensonge que la servante\r
+lui proposait. D'un autre côté il lui semblait bien que dire la vérité à\r
+la malade ce serait sans doute lui porter un coup terrible et que cela\r
+était grave dans l'état où était Fantine. Cette rougeur dura peu. La\r
+soeur leva sur Fantine son oeil calme et triste, et dit:\r
+\r
+--Monsieur le maire est parti.\r
+\r
+Fantine se redressa et s'assit sur ses talons. Ses yeux étincelèrent.\r
+Une joie inouïe rayonna sur cette physionomie douloureuse.\r
+\r
+--Parti! s'écria-t-elle. Il est allé chercher Cosette!\r
+\r
+Puis elle tendit ses deux mains vers le ciel et tout son visage devint\r
+ineffable. Ses lèvres remuaient; elle priait à voix basse.\r
+\r
+Quand sa prière fut finie:\r
+\r
+--Ma soeur, dit-elle, je veux bien me recoucher, je vais faire tout ce\r
+qu'on voudra; tout à l'heure j'ai été méchante, je vous demande pardon\r
+d'avoir parlé si haut, c'est très mal de parler haut, je le sais bien,\r
+ma bonne soeur, mais voyez-vous, je suis très contente. Le bon Dieu est\r
+bon, monsieur Madeleine est bon, figurez-vous qu'il est allé chercher ma\r
+petite Cosette à Montfermeil.\r
+\r
+Elle se recoucha, aida la religieuse à arranger l'oreiller et baisa une\r
+petite croix d'argent qu'elle avait au cou et que la soeur Simplice lui\r
+avait donnée.\r
+\r
+--Mon enfant, dit la soeur, tâchez de reposer maintenant, et ne parlez\r
+plus.\r
+\r
+Fantine prit dans ses mains moites la main de la soeur, qui souffrait de\r
+lui sentir cette sueur.\r
+\r
+--Il est parti ce matin pour aller à Paris. Au fait il n'a pas même\r
+besoin de passer par Paris. Montfermeil, c'est un peu à gauche en\r
+venant. Vous rappelez-vous comme il me disait hier quand je lui parlais\r
+de Cosette: bientôt, bientôt? C'est une surprise qu'il veut me faire.\r
+Vous savez? il m'avait fait signer une lettre pour la reprendre aux\r
+Thénardier. Ils n'auront rien à dire, pas vrai? Ils rendront Cosette.\r
+Puisqu'ils sont payés. Les autorités ne souffriraient pas qu'on garde un\r
+enfant quand on est payé. Ma soeur, ne me faites pas signe qu'il ne faut\r
+pas que je parle. Je suis extrêmement heureuse, je vais très bien, je\r
+n'ai plus de mal du tout, je vais revoir Cosette, j'ai même très faim.\r
+Il y a près de cinq ans que je ne l'ai vue. Vous ne vous figurez pas,\r
+vous, comme cela vous tient, les enfants! Et puis elle sera si gentille,\r
+vous verrez! Si vous saviez, elle a de si jolis petits doigts roses!\r
+D'abord elle aura de très belles mains. À un an, elle avait des mains\r
+ridicules. Ainsi!--Elle doit être grande à présent. Cela vous a sept\r
+ans. C'est une demoiselle. Je l'appelle Cosette, mais elle s'appelle\r
+Euphrasie. Tenez, ce matin, je regardais de la poussière qui était sur\r
+la cheminée et j'avais bien l'idée comme cela que je reverrais bientôt\r
+Cosette. Mon Dieu! comme on a tort d'être des années sans voir ses\r
+enfants! on devrait bien réfléchir que la vie n'est pas éternelle! Oh!\r
+comme il est bon d'être parti, monsieur le maire! C'est vrai ça, qu'il\r
+fait bien froid? avait-il son manteau au moins? Il sera ici demain,\r
+n'est-ce pas? Ce sera demain fête. Demain matin, ma soeur, vous me ferez\r
+penser à mettre mon petit bonnet qui a de la dentelle. Montfermeil,\r
+c'est un pays. J'ai fait cette route-là, à pied, dans le temps. Il y a\r
+eu bien loin pour moi. Mais les diligences vont très vite! Il sera ici\r
+demain avec Cosette. Combien y a-t-il d'ici Montfermeil?\r
+\r
+La soeur, qui n'avait aucune idée des distances, répondit:\r
+\r
+--Oh! je crois bien qu'il pourra être ici demain.\r
+\r
+--Demain! demain! dit Fantine, je verrai Cosette demain! Voyez-vous,\r
+bonne soeur du bon Dieu, je ne suis plus malade. Je suis folle. Je\r
+danserais, si on voulait.\r
+\r
+Quelqu'un qui l'eût vue un quart d'heure auparavant n'y eût rien\r
+compris. Elle était maintenant toute rose, elle parlait d'une voix vive\r
+et naturelle, toute sa figure n'était qu'un sourire. Par moments elle\r
+riait en se parlant tout bas. Joie de mère, c'est presque joie d'enfant.\r
+\r
+--Eh bien, reprit la religieuse, vous voilà heureuse, obéissez-moi, ne\r
+parlez plus.\r
+\r
+Fantine posa sa tête sur l'oreiller et dit à demi-voix:\r
+\r
+--Oui, recouche-toi, sois sage puisque tu vas avoir ton enfant. Elle a\r
+raison, soeur Simplice. Tous ceux qui sont ici ont raison.\r
+\r
+Et puis, sans bouger, sans remuer la tête, elle se mit à regarder\r
+partout avec ses yeux tout grands ouverts et un air joyeux, et elle ne\r
+dit plus rien.\r
+\r
+La soeur referma ses rideaux, espérant qu'elle s'assoupirait.\r
+\r
+Entre sept et huit heures le médecin vint. N'entendant aucun bruit, il\r
+crut que Fantine dormait, entra doucement et s'approcha du lit sur la\r
+pointe du pied. Il entrouvrit les rideaux, et à la lueur de la veilleuse\r
+il vit les grands yeux calmes de Fantine qui le regardaient.\r
+\r
+Elle lui dit:\r
+\r
+--Monsieur, n'est-ce pas, on me laissera la coucher à côté de moi dans\r
+un petit lit?\r
+\r
+Le médecin crut qu'elle délirait. Elle ajouta:\r
+\r
+--Regardez plutôt, il y a juste de la place.\r
+\r
+Le médecin prit à part la soeur Simplice qui lui expliqua la chose, que\r
+M. Madeleine était absent pour un jour ou deux, et que, dans le doute,\r
+on n'avait pas cru devoir détromper la malade qui croyait monsieur le\r
+maire parti pour Montfermeil; qu'il était possible en somme qu'elle eût\r
+deviné juste. Le médecin approuva.\r
+\r
+Il se rapprocha du lit de Fantine, qui reprit:\r
+\r
+--C'est que, voyez-vous, le matin, quand elle s'éveillera, je lui dirai\r
+bonjour à ce pauvre chat, et la nuit, moi qui ne dors pas, je\r
+l'entendrai dormir. Sa petite respiration si douce, cela me fera du\r
+bien.\r
+\r
+--Donnez-moi votre main, dit le médecin.\r
+\r
+Elle tendit son bras, et s'écria en riant.\r
+\r
+--Ah! tiens! au fait, c'est vrai, vous ne savez pas c'est que je suis\r
+guérie. Cosette arrive demain.\r
+\r
+Le médecin fut surpris. Elle était mieux. L'oppression était moindre. Le\r
+pouls avait repris de la force. Une sorte de vie survenue tout à coup\r
+ranimait ce pauvre être épuisé.\r
+\r
+--Monsieur le docteur, reprit-elle, la soeur vous a-t-elle dit que\r
+monsieur le maire était allé chercher le chiffon?\r
+\r
+Le médecin recommanda le silence et qu'on évitât toute émotion pénible.\r
+Il prescrivit une infusion de quinquina pur, et, pour le cas où la\r
+fièvre reprendrait dans la nuit, une potion calmante. En s'en allant, il\r
+dit à la soeur:\r
+\r
+--Cela va mieux. Si le bonheur voulait qu'en effet monsieur le maire\r
+arrivât demain avec l'enfant, qui sait? il y a des crises si étonnantes,\r
+on a vu de grandes joies arrêter court des maladies; je sais bien que\r
+celle-ci est une maladie organique, et bien avancée, mais c'est un tel\r
+mystère que tout cela! Nous la sauverions peut-être.\r
+\r
+\r
+\r
+\r
+Chapitre VII\r
+\r
+Le voyageur arrivé prend ses précautions pour repartir.\r
+\r
+\r
+Il était près de huit heures du soir quand la carriole que nous avons\r
+laissée en route entra sous la porte cochère de l'hôtel de la Poste\r
+à Arras. L'homme que nous avons suivi jusqu'à ce moment en descendit,\r
+répondit d'un air distrait aux empressements des gens de l'auberge,\r
+renvoya le cheval de renfort, et conduisit lui-même le petit cheval\r
+blanc à l'écurie; puis il poussa la porte d'une salle de billard qui\r
+était au rez-de-chaussée, s'y assit, et s'accouda sur une table. Il\r
+avait mis quatorze heures à ce trajet qu'il comptait faire en six.\r
+Il se rendait la justice que ce n'était pas sa faute; mais au fond il\r
+n'en était pas fâché.\r
+\r
+La maîtresse de l'hôtel entra.\r
+\r
+--Monsieur couche-t-il? monsieur soupe-t-il?\r
+\r
+Il fit un signe de tête négatif.\r
+\r
+--Le garçon d'écurie dit que le cheval de monsieur est bien fatigué!\r
+\r
+Ici il rompit le silence.\r
+\r
+--Est-ce que le cheval ne pourra pas repartir demain matin?\r
+\r
+--Oh! monsieur! il lui faut au moins deux jours de repos.\r
+\r
+Il demanda:\r
+\r
+--N'est-ce pas ici le bureau de poste?\r
+\r
+--Oui, monsieur.\r
+\r
+L'hôtesse le mena à ce bureau; il montra son passeport et s'informa s'il\r
+y avait moyen de revenir cette nuit même à Montreuil-sur-mer par la\r
+malle; la place à côté du courrier était justement vacante; il la retint\r
+et la paya.\r
+\r
+--Monsieur, dit le buraliste, ne manquez pas d'être ici pour partir à\r
+une heure précise du matin.\r
+\r
+Cela fait, il sortit de l'hôtel et se mit à marcher dans la ville.\r
+\r
+Il ne connaissait pas Arras, les rues étaient obscures, et il allait au\r
+hasard. Cependant il semblait s'obstiner à ne pas demander son chemin\r
+aux passants. Il traversa la petite rivière Crinchon et se trouva dans\r
+un dédale de ruelles étroites où il se perdit. Un bourgeois cheminait\r
+avec un falot. Après quelque hésitation, il prit le parti de s'adresser\r
+à ce bourgeois, non sans avoir d'abord regardé devant et derrière lui,\r
+comme s'il craignait que quelqu'un n'entendit la question qu'il allait\r
+faire.\r
+\r
+--Monsieur, dit-il, le palais de justice, s'il vous plaît?\r
+\r
+--Vous n'êtes pas de la ville, monsieur? répondit le bourgeois qui était\r
+un assez vieux homme, eh bien, suivez-moi. Je vais précisément du côté\r
+du palais de justice, c'est-à-dire du côté de l'hôtel de la préfecture.\r
+Car on répare en ce moment le palais, et provisoirement les tribunaux\r
+ont leurs audiences à la préfecture.\r
+\r
+--Est-ce là, demanda-t-il, qu'on tient les assises?\r
+\r
+--Sans doute, monsieur. Voyez-vous, ce qui est la préfecture aujourd'hui\r
+était l'évêché avant la révolution. Monsieur de Conzié, qui était évêque\r
+en quatre-vingt-deux, y a fait bâtir une grande salle. C'est dans cette\r
+grande salle qu'on juge.\r
+\r
+Chemin faisant, le bourgeois lui dit:\r
+\r
+--Si c'est un procès que monsieur veut voir, il est un peu tard.\r
+Ordinairement les séances finissent à six heures.\r
+\r
+Cependant, comme ils arrivaient sur la grande place, le bourgeois lui\r
+montra quatre longues fenêtres éclairées sur la façade d'un vaste\r
+bâtiment ténébreux.\r
+\r
+--Ma foi, monsieur, vous arrivez à temps, vous avez du bonheur.\r
+Voyez-vous ces quatre fenêtres? c'est la cour d'assises. Il y a de la\r
+lumière. Donc ce n'est pas fini. L'affaire aura traîné en longueur et on\r
+fait une audience du soir. Vous vous intéressez à cette affaire? Est-ce\r
+que c'est un procès criminel? Est-ce que vous êtes témoin?\r
+\r
+Il répondit:\r
+\r
+--Je ne viens pour aucune affaire, j'ai seulement à parler à un avocat.\r
+\r
+--C'est différent, dit le bourgeois. Tenez, monsieur, voici la porte. Où\r
+est le factionnaire. Vous n'aurez qu'à monter le grand escalier.\r
+\r
+Il se conforma aux indications du bourgeois, et, quelques minutes après,\r
+il était dans une salle où il y avait beaucoup de monde et où des\r
+groupes mêlés d'avocats en robe chuchotaient çà et là.\r
+\r
+C'est toujours une chose qui serre le coeur de voir ces attroupements\r
+d'hommes vêtus de noir qui murmurent entre eux à voix basse sur le seuil\r
+des chambres de justice. Il est rare que la charité et la pitié sortent\r
+de toutes ces paroles. Ce qui en sort le plus souvent, ce sont des\r
+condamnations faites d'avance. Tous ces groupes semblent à l'observateur\r
+qui passe et qui rêve autant de ruches sombres où des espèces d'esprits\r
+bourdonnants construisent en commun toutes sortes d'édifices ténébreux.\r
+\r
+Cette salle, spacieuse et éclairée d'une seule lampe, était une ancienne\r
+antichambre de l'évêché et servait de salle des pas perdus. Une porte à\r
+deux battants, fermée en ce moment, la séparait de la grande chambre où\r
+siégeait la cour d'assises.\r
+\r
+L'obscurité était telle qu'il ne craignit pas de s'adresser au premier\r
+avocat qu'il rencontra.\r
+\r
+--Monsieur, dit-il, où en est-on?\r
+\r
+--C'est fini, dit l'avocat.\r
+\r
+--Fini!\r
+\r
+Ce mot fut répété d'un tel accent que l'avocat se retourna.\r
+\r
+--Pardon, monsieur, vous êtes peut-être un parent?\r
+\r
+--Non. Je ne connais personne ici. Et y a-t-il eu condamnation?\r
+\r
+--Sans doute. Cela n'était guère possible autrement.\r
+\r
+--Aux travaux forcés?...\r
+\r
+--À perpétuité.\r
+\r
+Il reprit d'une voix tellement faible qu'on l'entendait à peine:\r
+\r
+--L'identité a donc été constatée?\r
+\r
+--Quelle identité? répondit l'avocat. Il n'y avait pas d'identité à\r
+constater. L'affaire était simple. Cette femme avait tué son enfant,\r
+l'infanticide a été prouvé, le jury a écarté la préméditation, on l'a\r
+condamnée à vie.\r
+\r
+--C'est donc une femme? dit-il.\r
+\r
+--Mais sûrement. La fille Limosin. De quoi me parlez-vous donc?\r
+\r
+--De rien. Mais puisque c'est fini, comment se fait-il que la salle soit\r
+encore éclairée?\r
+\r
+--C'est pour l'autre affaire qu'on a commencée il y a à peu près deux\r
+heures.\r
+\r
+--Quelle autre affaire?\r
+\r
+--Oh! celle-là est claire aussi. C'est une espèce de gueux, un\r
+récidiviste, un galérien, qui a volé. Je ne sais plus trop son nom. En\r
+voilà un qui vous a une mine de bandit. Rien que pour avoir cette\r
+figure-là, je l'enverrais aux galères.\r
+\r
+--Monsieur, demanda-t-il, y a-t-il moyen de pénétrer dans la salle?\r
+\r
+--Je ne crois vraiment pas. Il y a beaucoup de foule. Cependant\r
+l'audience est suspendue. Il y a des gens qui sont sortis, et, à la\r
+reprise de l'audience, vous pourrez essayer.\r
+\r
+--Par où entre-t-on?\r
+\r
+--Par cette grande porte.\r
+\r
+L'avocat le quitta. En quelques instants, il avait éprouvé, presque en\r
+même temps, presque mêlées, toutes les émotions possibles. Les paroles\r
+de cet indifférent lui avaient tour à tour traversé le coeur comme des\r
+aiguilles de glace et comme des lames de feu. Quand il vit que rien\r
+n'était terminé, il respira; mais il n'eût pu dire si ce qu'il\r
+ressentait était du contentement ou de la douleur.\r
+\r
+Il s'approcha de plusieurs groupes et il écouta ce qu'on disait. Le rôle\r
+de la session étant très chargé, le président avait indiqué pour ce même\r
+jour deux affaires simples et courtes. On avait commencé par\r
+l'infanticide, et maintenant on en était au forçat, au récidiviste, au\r
+"cheval de retour". Cet homme avait volé des pommes, mais cela ne\r
+paraissait pas bien prouvé; ce qui était prouvé, c'est qu'il avait été\r
+déjà aux galères à Toulon. C'est ce qui faisait son affaire mauvaise. Du\r
+reste, l'interrogatoire de l'homme était terminé et les dépositions des\r
+témoins; mais il y avait encore les plaidoiries de l'avocat et le\r
+réquisitoire du ministère public; cela ne devait guère finir avant\r
+minuit. L'homme serait probablement condamné; l'avocat général était\r
+très bon--et ne manquait pas ses accusés--c'était un garçon d'esprit qui\r
+faisait des vers.\r
+\r
+Un huissier se tenait debout près de la porte qui communiquait avec la\r
+salle des assises. Il demanda à cet huissier:\r
+\r
+--Monsieur, la porte va-t-elle bientôt s'ouvrir?\r
+\r
+--Elle ne s'ouvrira pas, dit l'huissier.\r
+\r
+--Comment! on ne l'ouvrira pas à la reprise de l'audience? est-ce que\r
+l'audience n'est pas suspendue?\r
+\r
+--L'audience vient d'être reprise, répondit l'huissier, mais la porte ne\r
+se rouvrira pas.\r
+\r
+--Pourquoi?\r
+\r
+--Parce que la salle est pleine.\r
+\r
+--Quoi? il n'y a plus une place?\r
+\r
+--Plus une seule. La porte est fermée. Personne ne peut plus entrer.\r
+\r
+L'huissier ajouta après un silence:\r
+\r
+--Il y a bien encore deux ou trois places derrière monsieur le\r
+président, mais monsieur le président n'y admet que les fonctionnaires\r
+publics.\r
+\r
+Cela dit, l'huissier lui tourna le dos.\r
+\r
+Il se retira la tête baissée, traversa l'antichambre et redescendit\r
+l'escalier lentement, comme hésitant à chaque marche. Il est probable\r
+qu'il tenait conseil avec lui-même. Le violent combat qui se livrait en\r
+lui depuis la veille n'était pas fini; et, à chaque instant, il en\r
+traversait quelque nouvelle péripétie. Arrivé sur le palier de\r
+l'escalier, il s'adossa à la rampe et croisa les bras. Tout à coup il\r
+ouvrit sa redingote, prit son portefeuille, en tira un crayon, déchira\r
+une feuille, et écrivit rapidement sur cette feuille à la lueur du\r
+réverbère cette ligne:--_M. Madeleine, maire de Montreuil-sur-mer_.\r
+Puis il remonta l'escalier à grands pas, fendit la foule, marcha droit à\r
+l'huissier, lui remit le papier, et lui dit avec autorité:\r
+\r
+--Portez ceci à monsieur le président.\r
+\r
+L'huissier prit le papier, y jeta un coup d'oeil et obéit.\r
+\r
+\r
+\r
+\r
+Chapitre VIII\r
+\r
+Entrée de faveur\r
+\r
+\r
+Sans qu'il s'en doutât, le maire de Montreuil-sur-mer avait une sorte de\r
+célébrité. Depuis sept ans que sa réputation de vertu remplissait tout\r
+le bas Boulonnais, elle avait fini par franchir les limites d'un petit\r
+pays et s'était répandue dans les deux ou trois départements voisins.\r
+Outre le service considérable qu'il avait rendu au chef-lieu en y\r
+restaurant l'industrie des verroteries noires, il n'était pas une des\r
+cent quarante et une communes de l'arrondissement de Montreuil-sur-mer\r
+qui ne lui dût quelque bienfait. Il avait su même au besoin aider et\r
+féconder les industries des autres arrondissements. C'est ainsi qu'il\r
+avait dans l'occasion soutenu de son crédit et de ses fonds la fabrique\r
+de tulle de Boulogne, la filature de lin à la mécanique de Frévent et la\r
+manufacture hydraulique de toiles de Boubers-sur-Canche. Partout on\r
+prononçait avec vénération le nom de M. Madeleine. Arras et Douai\r
+enviaient son maire à l'heureuse petite ville de Montreuil-sur-mer.\r
+\r
+Le conseiller à la cour royale de Douai, qui présidait cette session des\r
+assises à Arras, connaissait comme tout le monde ce nom si profondément\r
+et si universellement honoré. Quand l'huissier, ouvrant discrètement la\r
+porte qui communiquait de la chambre du conseil à l'audience, se pencha\r
+derrière le fauteuil du président et lui remit le papier où était écrite\r
+la ligne qu'on vient de lire, en ajoutant: _Ce monsieur désire assister\r
+à l'audience_, le président fit un vif mouvement de déférence, saisit\r
+une plume, écrivit quelques mots au bas du papier, et le rendit à\r
+l'huissier en lui disant: Faites entrer.\r
+\r
+L'homme malheureux dont nous racontons l'histoire était resté près de la\r
+porte de la salle à la même place et dans la même attitude où l'huissier\r
+l'avait quitté. Il entendit, à travers sa rêverie, quelqu'un qui lui\r
+disait: Monsieur veut-il bien me faire l'honneur de me suivre? C'était\r
+ce même huissier qui lui avait tourné le dos l'instant d'auparavant et\r
+qui maintenant le saluait jusqu'à terre. L'huissier en même temps lui\r
+remit le papier. Il le déplia, et, comme il se rencontrait qu'il était\r
+près de la lampe, il put lire:\r
+\r
+«Le président de la cour d'assises présente son respect à M. Madeleine.»\r
+\r
+Il froissa le papier entre ses mains, comme si ces quelques mots eussent\r
+eu pour lui un arrière-goût étrange et amer.\r
+\r
+Il suivit l'huissier.\r
+\r
+Quelques minutes après, il se trouvait seul dans une espèce de cabinet\r
+lambrissé, d'un aspect sévère, éclairé par deux bougies posées sur une\r
+table à tapis vert. Il avait encore dans l'oreille les dernières paroles\r
+de l'huissier qui venait de le quitter--«Monsieur, vous voici dans la\r
+chambre du conseil; vous n'avez qu'à tourner le bouton de cuivre de\r
+cette porte, et vous vous trouverez dans l'audience derrière le fauteuil\r
+de monsieur le président.»--Ces paroles se mêlaient dans sa pensée à un\r
+souvenir vague de corridors étroits et d'escaliers noirs qu'il venait de\r
+parcourir.\r
+\r
+L'huissier l'avait laissé seul. Le moment suprême était arrivé. Il\r
+cherchait à se recueillir sans pouvoir y parvenir. C'est surtout aux\r
+heures où l'on aurait le plus besoin de les rattacher aux réalités\r
+poignantes de la vie que tous les fils de la pensée se rompent dans le\r
+cerveau. Il était dans l'endroit même où les juges délibèrent et\r
+condamnent. Il regardait avec une tranquillité stupide cette chambre\r
+paisible et redoutable où tant d'existences avaient été brisées, où son\r
+nom allait retentir tout à l'heure, et que sa destinée traversait en ce\r
+moment. Il regardait la muraille, puis il se regardait lui-même,\r
+s'étonnant que ce fût cette chambre et que ce fût lui.\r
+\r
+Il n'avait pas mangé depuis plus de vingt-quatre heures, il était brisé\r
+par les cahots de la carriole, mais il ne le sentait pas; il lui\r
+semblait qu'il ne sentait rien.\r
+\r
+Il s'approcha d'un cadre noir qui était accroché au mur et qui contenait\r
+sous verre une vieille lettre autographe de Jean-Nicolas Pache, maire de\r
+Paris et ministre, datée, sans doute par erreur, du _9 juin an II_, et\r
+dans laquelle Pache envoyait à la commune la liste des ministres et des\r
+députés tenus en arrestation chez eux. Un témoin qui l'eût pu voir et\r
+qui l'eût observé en cet instant eût sans doute imaginé Fantine et\r
+Cosette.\r
+\r
+Tout en rêvant, il se retourna, et ses yeux rencontrèrent le bouton de\r
+cuivre de la porte qui le séparait de la salle des assises. Il avait\r
+presque oublié cette porte. Son regard, d'abord calme, s'y arrêta, resta\r
+attaché à ce bouton de cuivre, puis devint effaré et fixe, et\r
+s'empreignit peu à peu d'épouvante. Des gouttes de sueur lui sortaient\r
+d'entre les cheveux et ruisselaient sur ses tempes.\r
+\r
+À un certain moment, il fit avec une sorte d'autorité mêlée de rébellion\r
+ce geste indescriptible qui veut dire et qui dit si bien: _Pardieu! qui\r
+est-ce qui m'y force?_ Puis il se tourna vivement, vit devant lui la\r
+porte par laquelle il était entré, y alla, l'ouvrit, et sortit. Il\r
+n'était plus dans cette chambre, il était dehors, dans un corridor, un\r
+corridor long, étroit, coupé de degrés et de guichets, faisant toutes\r
+sortes d'angles, éclairé çà et là de réverbères pareils à des veilleuses\r
+de malades, le corridor par où il était venu. Il respira, il écouta;\r
+aucun bruit derrière lui, aucun bruit devant lui; il se mit à fuir comme\r
+si on le poursuivait.\r
+\r
+Quand il eut doublé plusieurs des coudes de ce couloir, il écouta\r
+encore. C'était toujours le même silence et la même ombre autour de lui.\r
+Il était essoufflé, il chancelait, il s'appuya au mur. La pierre était\r
+froide, sa sueur était glacée sur son front, il se redressa en\r
+frissonnant.\r
+\r
+Alors, là, seul, debout dans cette obscurité, tremblant de froid et\r
+d'autre chose peut-être, il songea.\r
+\r
+Il avait songé toute la nuit, il avait songé toute la journée; il\r
+n'entendait plus en lui qu'une voix qui disait: hélas!\r
+\r
+Un quart d'heure s'écoula ainsi. Enfin, il pencha la tête, soupira avec\r
+angoisse, laissa pendre ses bras, et revint sur ses pas. Il marchait\r
+lentement et comme accablé. Il semblait que quelqu'un l'eût atteint dans\r
+sa fuite et le ramenât.\r
+\r
+Il rentra dans la chambre du conseil. La première chose qu'il aperçut,\r
+ce fut la gâchette de la porte. Cette gâchette, ronde et en cuivre poli,\r
+resplendissait pour lui comme une effroyable étoile. Il la regardait\r
+comme une brebis regarderait l'oeil d'un tigre.\r
+\r
+Ses yeux ne pouvaient s'en détacher.\r
+\r
+De temps en temps il faisait un pas et se rapprochait de la porte.\r
+\r
+S'il eût écouté, il eût entendu, comme une sorte de murmure confus, le\r
+bruit de la salle voisine; mais il n'écoutait pas, et il n'entendait\r
+pas.\r
+\r
+Tout à coup, sans qu'il sût lui-même comment, il se trouva près de la\r
+porte. Il saisit convulsivement le bouton; la porte s'ouvrit.\r
+\r
+Il était dans la salle d'audience.\r
+\r
+\r
+\r
+\r
+Chapitre IX\r
+\r
+Un lieu où des convictions sont en train de se former\r
+\r
+\r
+Il fit un pas, referma machinalement la porte derrière lui, et resta\r
+debout, considérant ce qu'il voyait.\r
+\r
+C'était une assez vaste enceinte à peine éclairée, tantôt pleine de\r
+rumeur, tantôt pleine de silence, où tout l'appareil d'un procès\r
+criminel se développait avec sa gravité mesquine et lugubre au milieu de\r
+la foule.\r
+\r
+À un bout de la salle, celui où il se trouvait, des juges à l'air\r
+distrait, en robe usée, se rongeant les ongles ou fermant les paupières;\r
+à l'autre bout, une foule en haillons; des avocats dans toutes sortes\r
+d'attitudes; des soldats au visage honnête et dur; de vieilles boiseries\r
+tachées, un plafond sale, des tables couvertes d'une serge plutôt jaune\r
+que verte, des portes noircies par les mains; à des clous plantés dans\r
+le lambris, des quinquets d'estaminet donnant plus de fumée que de\r
+clarté; sur les tables, des chandelles dans des chandeliers de cuivre;\r
+l'obscurité, la laideur, la tristesse; et de tout cela se dégageait une\r
+impression austère et auguste, car on y sentait cette grande chose\r
+humaine qu'on appelle la loi et cette grande chose divine qu'on appelle\r
+la justice.\r
+\r
+Personne dans cette foule ne fit attention à lui. Tous les regards\r
+convergeaient vers un point unique, un banc de bois adossé à une petite\r
+porte, le long de la muraille, à gauche du président. Sur ce banc, que\r
+plusieurs chandelles éclairaient, il y avait un homme entre deux\r
+gendarmes.\r
+\r
+Cet homme, c'était l'homme.\r
+\r
+Il ne le chercha pas, il le vit. Ses yeux allèrent là naturellement,\r
+comme s'ils avaient su d'avance où était cette figure.\r
+\r
+Il crut se voir lui-même, vieilli, non pas sans doute absolument\r
+semblable de visage, mais tout pareil d'attitude et d'aspect, avec ces\r
+cheveux hérissés, avec cette prunelle fauve et inquiète, avec cette\r
+blouse, tel qu'il était le jour où il entrait à Digne, plein de haine et\r
+cachant dans son âme ce hideux trésor de pensées affreuses qu'il avait\r
+mis dix-neuf ans à ramasser sur le pavé du bagne.\r
+\r
+Il se dit avec un frémissement:\r
+\r
+--Mon Dieu! est-ce que je redeviendrai ainsi?\r
+\r
+Cet être paraissait au moins soixante ans. Il avait je ne sais quoi de\r
+rude, de stupide et d'effarouché.\r
+\r
+Au bruit de la porte, on s'était rangé pour lui faire place, le\r
+président avait tourné la tête, et comprenant que le personnage qui\r
+venait d'entrer était M. le maire de Montreuil-sur-mer, il l'avait\r
+salué. L'avocat général, qui avait vu M. Madeleine à Montreuil-sur-mer\r
+où des opérations de son ministère l'avaient plus d'une fois appelé, le\r
+reconnut, et salua également. Lui s'en aperçut à peine. Il était en\r
+proie à une sorte d'hallucination; il regardait.\r
+\r
+Des juges, un greffier, des gendarmes, une foule de têtes cruellement\r
+curieuses, il avait déjà vu cela une fois, autrefois, il y avait\r
+vingt-sept ans. Ces choses funestes, il les retrouvait; elles étaient\r
+là, elles remuaient, elles existaient. Ce n'était plus un effort de sa\r
+mémoire, un mirage de sa pensée, c'étaient de vrais gendarmes et de\r
+vrais juges, une vraie foule et de vrais hommes en chair et en os. C'en\r
+était fait, il voyait reparaître et revivre autour de lui, avec tout ce\r
+que la réalité a de formidable, les aspects monstrueux de son passé.\r
+\r
+Tout cela était béant devant lui.\r
+\r
+Il en eut horreur, il ferma les yeux, et s'écria au plus profond de son\r
+âme: jamais!\r
+\r
+Et par un jeu tragique de la destinée qui faisait trembler toutes ses\r
+idées et le rendait presque fou, c'était un autre lui-même qui était là!\r
+Cet homme qu'on jugeait, tous l'appelaient Jean Valjean!\r
+\r
+Il avait sous les yeux, vision inouïe, une sorte de représentation du\r
+moment le plus horrible de sa vie, jouée par son fantôme.\r
+\r
+Tout y était, c'était le même appareil, la même heure de nuit, presque\r
+les mêmes faces de juges, de soldats et de spectateurs. Seulement,\r
+au-dessus de la tête du président, il y avait un crucifix, chose qui\r
+manquait aux tribunaux du temps de sa condamnation. Quand on l'avait\r
+jugé, Dieu était absent.\r
+\r
+Une chaise était derrière lui; il s'y laissa tomber, terrifié de l'idée\r
+qu'on pouvait le voir. Quand il fut assis, il profita d'une pile de\r
+cartons qui était sur le bureau des juges pour dérober son visage à\r
+toute la salle. Il pouvait maintenant voir sans être vu. Peu à peu il se\r
+remit. Il rentra pleinement dans le sentiment du réel; il arriva à cette\r
+phase de calme où l'on peut écouter.\r
+\r
+M. Bamatabois était au nombre des jurés. Il chercha Javert, mais il ne\r
+le vit pas. Le banc des témoins lui était caché par la table du\r
+greffier. Et puis, nous venons de le dire, la salle était à peine\r
+éclairée.\r
+\r
+Au moment où il était entré, l'avocat de l'accusé achevait sa\r
+plaidoirie. L'attention de tous était excitée au plus haut point;\r
+l'affaire durait depuis trois heures. Depuis trois heures, cette foule\r
+regardait plier peu à peu sous le poids d'une vraisemblance terrible un\r
+homme, un inconnu, une espèce d'être misérable, profondément stupide ou\r
+profondément habile. Cet homme, on le sait déjà, était un vagabond qui\r
+avait été trouvé dans un champ, emportant une branche chargée de pommes\r
+mûres, cassée à un pommier dans un clos voisin, appelé le clos Pierron.\r
+Qui était cet homme? Une enquête avait eu lieu; des témoins venaient\r
+d'être entendus, ils avaient été unanimes, des lumières avaient jailli\r
+de tout le débat. L'accusation disait:\r
+\r
+--Nous ne tenons pas seulement un voleur de fruits, un maraudeur; nous\r
+tenons là, dans notre main, un bandit, un relaps en rupture de ban, un\r
+ancien forçat, un scélérat des plus dangereux, un malfaiteur appelé Jean\r
+Valjean que la justice recherche depuis longtemps, et qui, il y a huit\r
+ans, en sortant du bagne de Toulon, a commis un vol de grand chemin à\r
+main armée sur la personne d'un enfant savoyard appelé Petit-Gervais,\r
+crime prévu par l'article 383 du code pénal, pour lequel nous nous\r
+réservons de le poursuivre ultérieurement, quand l'identité sera\r
+judiciairement acquise. Il vient de commettre un nouveau vol. C'est un\r
+cas de récidive. Condamnez-le pour le fait nouveau; il sera jugé plus\r
+tard pour le fait ancien.\r
+\r
+Devant cette accusation, devant l'unanimité des témoins, l'accusé\r
+paraissait surtout étonné. Il faisait des gestes et des signes qui\r
+voulaient dire non, ou bien il considérait le plafond. Il parlait avec\r
+peine, répondait avec embarras, mais de la tête aux pieds toute sa\r
+personne niait. Il était comme un idiot en présence de toutes ces\r
+intelligences rangées en bataille autour de lui, et comme un étranger au\r
+milieu de cette société qui le saisissait. Cependant il y allait pour\r
+lui de l'avenir le plus menaçant, la vraisemblance croissait à chaque\r
+minute, et toute cette foule regardait avec plus d'anxiété que lui-même\r
+cette sentence pleine de calamités qui penchait sur lui de plus en plus.\r
+Une éventualité laissait même entrevoir, outre le bagne, la peine de\r
+mort possible, si l'identité était reconnue et si l'affaire\r
+Petit-Gervais se terminait plus tard par une condamnation. Qu'était-ce\r
+que cet homme? De quelle nature était son apathie? Etait-ce imbécillité\r
+ou ruse? Comprenait-il trop, ou ne comprenait-il pas du tout? Questions\r
+qui divisaient la foule et semblaient partager le jury. Il y avait dans\r
+ce procès ce qui effraye et ce qui intrigue; le drame n'était pas\r
+seulement sombre, il était obscur. Le défenseur avait assez bien plaidé,\r
+dans cette langue de province qui a longtemps constitué l'éloquence du\r
+barreau et dont usaient jadis tous les avocats, aussi bien à Paris qu'à\r
+Romorantin ou à Montbrison, et qui aujourd'hui, étant devenue classique,\r
+n'est plus guère parlée que par les orateurs officiels du parquet,\r
+auxquels elle convient par sa sonorité grave et son allure majestueuse;\r
+langue où un mari s'appelle un époux, une femme, une épouse, Paris, le\r
+centre des arts et de la civilisation, le roi, le monarque, monseigneur\r
+l'évêque, un saint pontife, l'avocat général, l'éloquent interprète de\r
+la vindicte, la plaidoirie, les accents qu'on vient d'entendre, le\r
+siècle de Louis XIV, le grand siècle, un théâtre, le temple de\r
+Melpomène, la famille régnante, l'auguste sang de nos rois, un concert,\r
+une solennité musicale, monsieur le général commandant le département,\r
+l'illustre guerrier qui, etc., les élèves du séminaire, ces tendres\r
+lévites, les erreurs imputées aux journaux, l'imposture qui distille son\r
+venin dans les colonnes de ces organes, etc., etc.--L'avocat donc avait\r
+commencé par s'expliquer sur le vol des pommes,--chose malaisée en beau\r
+style; mais Bénigne Bossuet lui-même a été obligé de faire allusion à\r
+une poule en pleine oraison funèbre, et il s'en est tiré avec pompe.\r
+L'avocat avait établi que le vol de pommes n'était pas matériellement\r
+prouvé.--Son client, qu'en sa qualité de défenseur, il persistait à\r
+appeler Champmathieu, n'avait été vu de personne escaladant le mur ou\r
+cassant la branche. On l'avait arrêté nanti de cette branche (que\r
+l'avocat appelait plus volontiers rameau); mais il disait l'avoir\r
+trouvée à terre et ramassée. Où était la preuve du contraire?--Sans\r
+doute cette branche avait été cassée et dérobée après escalade, puis\r
+jetée là par le maraudeur alarmé; sans doute il y avait un voleur. Mais\r
+qu'est-ce qui prouvait que ce voleur était Champmathieu? Une seule\r
+chose. Sa qualité d'ancien forçat. L'avocat ne niait pas que cette\r
+qualité ne parût malheureusement bien constatée; l'accusé avait résidé à\r
+Faverolles; l'accusé y avait été émondeur; le nom de Champmathieu\r
+pouvait bien avoir pour origine Jean Mathieu; tout cela était vrai;\r
+enfin quatre témoins reconnaissaient sans hésiter et positivement\r
+Champmathieu pour être le galérien Jean Valjean; à ces indications, à\r
+ces témoignages, l'avocat ne pouvait opposer que la dénégation de son\r
+client, dénégation intéressée; mais en supposant qu'il fût le forçat\r
+Jean Valjean, cela prouvait-il qu'il fût le voleur des pommes? C'était\r
+une présomption, tout au plus; non une preuve. L'accusé, cela était\r
+vrai, et le défenseur «dans sa bonne foi» devait en convenir, avait\r
+adopté «un mauvais système de défense»--Il s'obstinait à nier tout, le\r
+vol et sa qualité de forçat. Un aveu sur ce dernier point eût mieux\r
+valu, à coup sûr, et lui eût concilié l'indulgence de ses juges;\r
+l'avocat le lui avait conseillé; mais l'accusé s'y était refusé\r
+obstinément, croyant sans doute sauver tout en n'avouant rien. C'était\r
+un tort; mais ne fallait-il pas considérer la brièveté de cette\r
+intelligence? Cet homme était visiblement stupide. Un long malheur au\r
+bagne, une longue misère hors du bagne, l'avaient abruti, etc., etc. Il\r
+se défendait mal, était-ce une raison pour le condamner? Quant à\r
+l'affaire Petit-Gervais, l'avocat n'avait pas à la discuter, elle\r
+n'était point dans la cause. L'avocat concluait en suppliant le jury et\r
+la cour, si l'identité de Jean Valjean leur paraissait évidente, de lui\r
+appliquer les peines de police qui s'adressent au condamné en rupture de\r
+ban, et non le châtiment épouvantable qui frappe le forçat récidiviste.\r
+\r
+L'avocat général répliqua au défenseur. Il fut violent et fleuri, comme\r
+sont habituellement les avocats généraux.\r
+\r
+Il félicita le défenseur de sa «loyauté», et profita habilement de cette\r
+loyauté. Il atteignit l'accusé par toutes les concessions que l'avocat\r
+avait faites. L'avocat semblait accorder que l'accusé était Jean\r
+Valjean. Il en prit acte. Cet homme était donc Jean Valjean. Ceci était\r
+acquis à l'accusation et ne pouvait plus se contester. Ici, par une\r
+habile antonomase, remontant aux sources et aux causes de la\r
+criminalité, l'avocat général tonna contre l'immoralité de l'école\r
+romantique, alors à son aurore sous le nom d'école satanique que lui\r
+avaient décerné les critiques de l'Oriflamme et de la Quotidienne, il\r
+attribua, non sans vraisemblance, à l'influence de cette littérature\r
+perverse le délit de Champmathieu, ou pour mieux dire, de Jean Valjean.\r
+Ces considérations épuisées, il passa à Jean Valjean lui-même.\r
+Qu'était-ce que Jean Valjean? Description de Jean Valjean. Un monstre\r
+vomi, etc. Le modèle de ces sortes de descriptions est dans le récit de\r
+Théramène, lequel n'est pas utile à la tragédie, mais rend tous les\r
+jours de grands services à l'éloquence judiciaire. L'auditoire et les\r
+jurés «frémirent». La description achevée, l'avocat général reprit, dans\r
+un mouvement oratoire fait pour exciter au plus haut point le lendemain\r
+matin l'enthousiasme du Journal de la Préfecture:\r
+\r
+Et c'est un pareil homme, etc., etc., etc., vagabond, mendiant, sans\r
+moyens d'existence, etc., etc.,--accoutumé par sa vie passée aux actions\r
+coupables et peu corrigé par son séjour au bagne, comme le prouve le\r
+crime commis sur Petit-Gervais, etc., etc.,--c'est un homme pareil qui,\r
+trouvé sur la voie publique en flagrant délit de vol, à quelques pas\r
+d'un mur escaladé, tenant encore à la main l'objet volé, nie le flagrant\r
+délit, le vol, l'escalade, nie tout, nie jusqu'à son nom, nie jusqu'à\r
+son identité! Outre cent autres preuves sur lesquelles nous ne revenons\r
+pas, quatre témoins le reconnaissent, Javert, l'intègre inspecteur de\r
+police Javert, et trois de ses anciens compagnons d'ignominie, les\r
+forçats Brevet, Chenildieu et Cochepaille. Qu'oppose-t-il à cette\r
+unanimité foudroyante? Il nie. Quel endurcissement! Vous ferez justice,\r
+messieurs les jurés, etc., etc.\r
+\r
+Pendant que l'avocat général parlait, l'accusé écoutait, la bouche\r
+ouverte, avec une sorte d'étonnement où il entrait bien quelque\r
+admiration. Il était évidemment surpris qu'un homme pût parler comme\r
+cela. De temps en temps, aux moments les plus «énergiques» du\r
+réquisitoire, dans ces instants où l'éloquence, qui ne peut se contenir,\r
+déborde dans un flux d'épithètes flétrissantes et enveloppe l'accusé\r
+comme un orage, il remuait lentement la tête de droite à gauche et de\r
+gauche à droite, sorte de protestation triste et muette dont il se\r
+contentait depuis le commencement des débats. Deux ou trois fois les\r
+spectateurs placés le plus près de lui l'entendirent dire à demi-voix:\r
+\r
+--Voilà ce que c'est, de n'avoir pas demandé à M. Baloup!\r
+\r
+L'avocat général fit remarquer au jury cette attitude hébétée, calculée\r
+évidemment, qui dénotait, non l'imbécillité, mais l'adresse, la ruse,\r
+l'habitude de tromper la justice, et qui mettait dans tout son jour «la\r
+profonde perversité» de cet homme. Il termina en faisant ses réserves\r
+pour l'affaire Petit-Gervais, et en réclamant une condamnation sévère.\r
+\r
+C'était, pour l'instant, on s'en souvient, les travaux forcés à\r
+perpétuité.\r
+\r
+Le défenseur se leva, commença par complimenter «monsieur l'avocat\r
+général» sur son «admirable parole», puis répliqua comme il put, mais il\r
+faiblissait; le terrain évidemment se dérobait sous lui.\r
+\r
+\r
+\r
+\r
+Chapitre X\r
+\r
+Le système de dénégations\r
+\r
+\r
+L'instant de clore les débats était venu. Le président fit lever\r
+l'accusé et lui adressa la question d'usage:\r
+\r
+--Avez-vous quelque chose à ajouter à votre défense?\r
+\r
+L'homme, debout, roulant dans ses mains un affreux bonnet qu'il avait,\r
+sembla ne pas entendre.\r
+\r
+Le président répéta la question.\r
+\r
+Cette fois l'homme entendit. Il parut comprendre, il fit le mouvement de\r
+quelqu'un qui se réveille, promena ses yeux autour de lui, regarda le\r
+public, les gendarmes, son avocat, les jurés, la cour, posa son poing\r
+monstrueux sur le rebord de la boiserie placée devant son banc, regarda\r
+encore, et tout à coup, fixant sont regard sur l'avocat général, il se\r
+mit à parler. Ce fut comme une éruption. Il sembla, à la façon dont les\r
+paroles s'échappaient de sa bouche, incohérentes, impétueuses, heurtées,\r
+pêle-mêle, qu'elles s'y pressaient toutes à la fois pour sortir en même\r
+temps. Il dit:\r
+\r
+--J'ai à dire ça. Que j'ai été charron à Paris, même que c'était chez\r
+monsieur Baloup. C'est un état dur. Dans la chose de charron, on\r
+travaille toujours en plein air, dans des cours, sous des hangars chez\r
+les bons maîtres, jamais dans des ateliers fermés, parce qu'il faut des\r
+espaces, voyez-vous. L'hiver, on a si froid qu'on se bat les bras pour\r
+se réchauffer; mais les maîtres ne veulent pas, ils disent que cela perd\r
+du temps. Manier du fer quand il y a de la glace entre les pavés, c'est\r
+rude. Ça vous use vite un homme. On est vieux tout jeune dans cet\r
+état-là. À quarante ans, un homme est fini. Moi, j'en avais\r
+cinquante-trois, j'avais bien du mal. Et puis c'est si méchant les\r
+ouvriers! Quand un bonhomme n'est plus jeune, on vous l'appelle pour\r
+tout vieux serin, vieille bête! Je ne gagnais plus que trente sous par\r
+jour, on me payait le moins cher qu'on pouvait, les maîtres profitaient\r
+de mon âge. Avec ça, j'avais ma fille qui était blanchisseuse à la\r
+rivière. Elle gagnait un peu de son côté. À nous deux, cela allait. Elle\r
+avait de la peine aussi. Toute la journée dans un baquet jusqu'à\r
+mi-corps, à la pluie, à la neige, avec le vent qui vous coupe la figure;\r
+quand il gèle, c'est tout de même, il faut laver; il y a des personnes\r
+qui n'ont pas beaucoup de linge et qui attendent après; si on ne lavait\r
+pas, on perdrait des pratiques. Les planches sont mal jointes et il vous\r
+tombe des gouttes d'eau partout. On a ses jupes toutes mouillées, dessus\r
+et dessous. Ça pénètre. Elle a aussi travaillé au lavoir des\r
+Enfants-Rouges, où l'eau arrive par des robinets. On n'est pas dans le\r
+baquet. On lave devant soi au robinet et on rince derrière soi dans le\r
+bassin. Comme c'est fermé, on a moins froid au corps. Mais il y a une\r
+buée d'eau chaude qui est terrible et qui vous perd les yeux. Elle\r
+revenait à sept heures du soir, et se couchait bien vite; elle était si\r
+fatiguée. Son mari la battait. Elle est morte. Nous n'avons pas été bien\r
+heureux. C'était une brave fille qui n'allait pas au bal, qui était bien\r
+tranquille. Je me rappelle un mardi gras où elle était couchée à huit\r
+heures. Voilà. Je dis vrai. Vous n'avez qu'à demander. Ah, bien oui,\r
+demander! que je suis bête! Paris, c'est un gouffre. Qui est-ce qui\r
+connaît le père Champmathieu? Pourtant je vous dis monsieur Baloup.\r
+Voyez chez monsieur Baloup. Après ça, je ne sais pas ce qu'on me veut.\r
+\r
+L'homme se tut, et resta debout. Il avait dit ces choses d'une voix\r
+haute, rapide, rauque, dure et enrouée, avec une sorte de naïveté\r
+irritée et sauvage. Une fois il s'était interrompu pour saluer quelqu'un\r
+dans la foule. Les espèces d'affirmations qu'il semblait jeter au hasard\r
+devant lui, lui venaient comme des hoquets, et il ajoutait à chacune\r
+d'elles le geste d'un bûcheron qui fend du bois. Quand il eut fini,\r
+l'auditoire éclata de rire. Il regarda le public, et voyant qu'on riait,\r
+et ne comprenant pas, il se mit à rire lui-même.\r
+\r
+Cela était sinistre.\r
+\r
+Le président, homme attentif et bienveillant, éleva la voix.\r
+\r
+Il rappela à «messieurs les jurés» que «le sieur Baloup, l'ancien maître\r
+charron chez lequel l'accusé disait avoir servi, avait été inutilement\r
+cité. Il était en faillite, et n'avait pu être retrouvé.» Puis se\r
+tournant vers l'accusé, il l'engagea à écouter ce qu'il allait lui dire\r
+et ajouta:\r
+\r
+--Vous êtes dans une situation où il faut réfléchir. Les présomptions\r
+les plus graves pèsent sur vous et peuvent entraîner des conséquences\r
+capitales. Accusé, dans votre intérêt, je vous interpelle une dernière\r
+fois, expliquez-vous clairement sur ces deux faits:--Premièrement,\r
+avez-vous, oui ou non, franchi le mur du clos Pierron, cassé la branche\r
+et volé les pommes, c'est-à-dire commis le crime de vol avec escalade?\r
+Deuxièmement, oui ou non, êtes-vous le forçat libéré Jean Valjean?\r
+\r
+L'accusé secoua la tête d'un air capable, comme un homme qui a bien\r
+compris et qui sait ce qu'il va répondre. Il ouvrit la bouche, se tourna\r
+vers le président et dit:\r
+\r
+--D'abord....\r
+\r
+Puis il regarda son bonnet, il regarda le plafond, et se tut.\r
+\r
+--Accusé, reprit l'avocat général d'une voix sévère, faites attention.\r
+Vous ne répondez à rien de ce qu'on vous demande. Votre trouble vous\r
+condamne. Il est évident que vous ne vous appelez pas Champmathieu, que\r
+vous êtes le forçat Jean Valjean caché d'abord sous le nom de Jean\r
+Mathieu qui était le nom de sa mère, que vous êtes allé en Auvergne, que\r
+vous êtes né à Faverolles où vous avez été émondeur. Il est évident que\r
+vous avez volé avec escalade des pommes mûres dans le clos Pierron.\r
+Messieurs les jurés apprécieront.\r
+\r
+L'accusé avait fini par se rasseoir; il se leva brusquement quand\r
+l'avocat général eut fini, et s'écria:\r
+\r
+--Vous êtes très méchant, vous! Voilà ce que je voulais dire. Je ne\r
+trouvais pas d'abord. Je n'ai rien volé. Je suis un homme qui ne mange\r
+pas tous les jours. Je venais d'Ailly, je marchais dans le pays après\r
+une ondée qui avait fait la campagne toute jaune, même que les mares\r
+débordaient et qu'il ne sortait plus des sables que de petits brins\r
+d'herbe au bord de la route, j'ai trouvé une branche cassée par terre où\r
+il y avait des pommes, j'ai ramassé la branche sans savoir qu'elle me\r
+ferait arriver de la peine. Il y a trois mois que je suis en prison et\r
+qu'on me trimballe. Après ça, je ne peux pas dire, on parle contre moi,\r
+on me dit: répondez! le gendarme, qui est bon enfant, me pousse le coude\r
+et me dit tout bas: réponds donc. Je ne sais pas expliquer, moi, je n'ai\r
+pas fait les études, je suis un pauvre homme. Voilà ce qu'on a tort de\r
+ne pas voir. Je n'ai pas volé, j'ai ramassé par terre des choses qu'il y\r
+avait. Vous dites Jean Valjean, Jean Mathieu! Je ne connais pas ces\r
+personnes-là. C'est des villageois. J'ai travaillé chez monsieur Baloup,\r
+boulevard de l'Hôpital. Je m'appelle Champmathieu. Vous êtes bien malins\r
+de me dire où je suis né. Moi, je l'ignore. Tout le monde n'a pas des\r
+maisons pour y venir au monde. Ce serait trop commode. Je crois que mon\r
+père et ma mère étaient des gens qui allaient sur les routes. Je ne sais\r
+pas d'ailleurs. Quand j'étais enfant, on m'appelait Petit, maintenant,\r
+on m'appelle Vieux. Voilà mes noms de baptême. Prenez ça comme vous\r
+voudrez. J'ai été en Auvergne, j'ai été à Faverolles, pardi! Eh bien?\r
+est-ce qu'on ne peut pas avoir été en Auvergne et avoir été à Faverolles\r
+sans avoir été aux galères? Je vous dis que je n'ai pas volé, et que je\r
+suis le père Champmathieu. J'ai été chez monsieur Baloup, j'ai été\r
+domicilié. Vous m'ennuyez avec vos bêtises à la fin! Pourquoi donc\r
+est-ce que le monde est après moi comme des acharnés!\r
+\r
+L'avocat général était demeuré debout; il s'adressa au président:\r
+\r
+--Monsieur le président, en présence des dénégations confuses, mais fort\r
+habiles de l'accusé, qui voudrait bien se faire passer pour idiot, mais\r
+qui n'y parviendra pas--nous l'en prévenons--nous requérons qu'il vous\r
+plaise et qu'il plaise à la cour appeler de nouveau dans cette enceinte\r
+les condamnés Brevet, Cochepaille et Chenildieu et l'inspecteur de\r
+police Javert, et les interpeller une dernière fois sur l'identité de\r
+l'accusé avec le forçat Jean Valjean.\r
+\r
+--Je fais remarquer à monsieur l'avocat général, dit le président, que\r
+l'inspecteur de police Javert, rappelé par ses fonctions au chef-lieu\r
+d'un arrondissement voisin, a quitté l'audience et même la ville,\r
+aussitôt sa déposition faite. Nous lui en avons accordé l'autorisation,\r
+avec l'agrément de monsieur l'avocat général et du défenseur de\r
+l'accusé.\r
+\r
+--C'est juste, monsieur le président, reprit l'avocat général. En\r
+l'absence du sieur Javert, je crois devoir rappeler à messieurs les\r
+jurés ce qu'il a dit ici-même, il y a peu d'heures. Javert est un homme\r
+estimé qui honore par sa rigoureuse et stricte probité des fonctions\r
+inférieures, mais importantes. Voici en quels termes il a déposé:--«Je\r
+n'ai pas même besoin des présomptions morales et des preuves matérielles\r
+qui démentent les dénégations de l'accusé. Je le reconnais parfaitement.\r
+Cet homme ne s'appelle pas Champmathieu; c'est un ancien forçat très\r
+méchant et très redouté nommé Jean Valjean. On ne l'a libéré à\r
+l'expiration de sa peine qu'avec un extrême regret. Il a subi dix-neuf\r
+ans de travaux forcés pour vol qualifié. Il avait cinq ou six fois tenté\r
+de s'évader. Outre le vol Petit-Gervais et le vol Pierron, je le\r
+soupçonne encore d'un vol commis chez sa grandeur le défunt évêque de\r
+Digne. Je l'ai souvent vu, à l'époque où j'étais adjudant garde-chiourme\r
+au bagne de Toulon. Je répète que je le reconnais parfaitement.» Cette\r
+déclaration si précise parut produire une vive impression sur le public\r
+et le jury. L'avocat général termina en insistant pour qu'à défaut de\r
+Javert, les trois témoins Brevet, Chenildieu et Cochepaille fussent\r
+entendus de nouveau et interpellés solennellement.\r
+\r
+Le président transmit un ordre à un huissier, et un moment après la\r
+porte de la chambre des témoins s'ouvrit. L'huissier, accompagné d'un\r
+gendarme prêt à lui prêter main-forte, introduisit le condamné Brevet.\r
+L'auditoire était en suspens et toutes les poitrines palpitaient comme\r
+si elles n'eussent eu qu'une seule âme.\r
+\r
+L'ancien forçat Brevet portait la veste noire et grise des maisons\r
+centrales. Brevet était un personnage d'une soixantaine d'années qui\r
+avait une espèce de figure d'homme d'affaires et l'air d'un coquin. Cela\r
+va quelquefois ensemble. Il était devenu, dans la prison où de nouveaux\r
+méfaits l'avaient ramené, quelque chose comme guichetier. C'était un\r
+homme dont les chefs disaient: Il cherche à se rendre utile. Les\r
+aumôniers portaient bon témoignage de ses habitudes religieuses. Il ne\r
+faut pas oublier que ceci se passait sous la restauration.\r
+\r
+--Brevet, dit le président, vous avez subi une condamnation infamante et\r
+vous ne pouvez prêter serment....\r
+\r
+Brevet baissa les yeux.\r
+\r
+--Cependant, reprit le président, même dans l'homme que la loi a\r
+dégradé, il peut rester, quand la pitié divine le permet, un sentiment\r
+d'honneur et d'équité. C'est à ce sentiment que je fais appel à cette\r
+heure décisive. S'il existe encore en vous, et je l'espère, réfléchissez\r
+avant de me répondre, considérez d'une part cet homme qu'un mot de vous\r
+peut perdre, d'autre part la justice qu'un mot de vous peut éclairer.\r
+L'instant est solennel, et il est toujours temps de vous rétracter, si\r
+vous croyez vous être trompé.--Accusé, levez-vous.\r
+\r
+--Brevet, regardez bien l'accusé, recueillez vos souvenirs, et\r
+dites-nous, en votre âme et conscience, si vous persistez à reconnaître\r
+cet homme pour votre ancien camarade de bagne Jean Valjean.\r
+\r
+Brevet regarda l'accusé, puis se retourna vers la cour.\r
+\r
+--Oui, monsieur le président. C'est moi qui l'ai reconnu le premier et\r
+je persiste. Cet homme est Jean Valjean. Entré à Toulon en 1796 et sorti\r
+en 1815. Je suis sorti l'an d'après. Il a l'air d'une brute maintenant,\r
+alors ce serait que l'âge l'a abruti; au bagne il était sournois. Je le\r
+reconnais positivement.\r
+\r
+--Allez vous asseoir, dit le président. Accusé, restez debout.\r
+\r
+On introduisit Chenildieu, forçat à vie, comme l'indiquaient sa casaque\r
+rouge et son bonnet vert. Il subissait sa peine au bagne de Toulon, d'où\r
+on l'avait extrait pour cette affaire. C'était un petit homme d'environ\r
+cinquante ans, vif, ridé, chétif, jaune, effronté, fiévreux, qui avait\r
+dans tous ses membres et dans toute sa personne une sorte de faiblesse\r
+maladive et dans le regard une force immense. Ses compagnons du bagne\r
+l'avaient surnommé Je-nie-Dieu.\r
+\r
+Le président lui adressa à peu près les mêmes paroles qu'à Brevet. Au\r
+moment où il lui rappela que son infamie lui ôtait le droit de prêter\r
+serment, Chenildieu leva la tête et regarda la foule en face. Le\r
+président l'invita à se recueillir et lui demanda, comme à Brevet, s'il\r
+persistait à reconnaître l'accusé.\r
+\r
+Chenildieu éclata de rire.\r
+\r
+--Pardine! si je le reconnais! nous avons été cinq ans attachés à la\r
+même chaîne. Tu boudes donc, mon vieux?\r
+\r
+--Allez vous asseoir, dit le président.\r
+\r
+L'huissier amena Cochepaille. Cet autre condamné à perpétuité, venu du\r
+bagne et vêtu de rouge comme Chenildieu, était un paysan de Lourdes et\r
+un demi-ours des Pyrénées. Il avait gardé des troupeaux dans la\r
+montagne, et de pâtre il avait glissé brigand. Cochepaille n'était pas\r
+moins sauvage et paraissait plus stupide encore que l'accusé. C'était un\r
+de ces malheureux hommes que la nature a ébauchés en bêtes fauves et que\r
+la société termine en galériens.\r
+\r
+Le président essaya de le remuer par quelques paroles pathétiques et\r
+graves et lui demanda, comme aux deux autres, s'il persistait, sans\r
+hésitation et sans trouble, à reconnaître l'homme debout devant lui.\r
+\r
+--C'est Jean Valjean, dit Cochepaille. Même qu'on l'appelait\r
+Jean-le-Cric, tant il était fort.\r
+\r
+Chacune des affirmations de ces trois hommes, évidemment sincères et de\r
+bonne foi, avait soulevé dans l'auditoire un murmure de fâcheux augure\r
+pour l'accusé, murmure qui croissait et se prolongeait plus longtemps\r
+chaque fois qu'une déclaration nouvelle venait s'ajouter à la\r
+précédente. L'accusé, lui, les avait écoutées avec ce visage étonné qui,\r
+selon l'accusation, était son principal moyen de défense. À la première,\r
+les gendarmes ses voisins l'avaient entendu grommeler entre ses dents:\r
+Ah bien! en voilà un! Après la seconde il dit un peu plus haut, d'un air\r
+presque satisfait: Bon! À la troisième il s'écria: Fameux!\r
+\r
+Le président l'interpella.\r
+\r
+--Accusé, vous avez entendu. Qu'avez-vous à dire?\r
+\r
+Il répondit:\r
+\r
+--Je dis--Fameux!\r
+\r
+Une rumeur éclata dans le public et gagna presque le jury. Il était\r
+évident que l'homme était perdu.\r
+\r
+--Huissiers, dit le président, faites faire silence. Je vais clore les\r
+débats.\r
+\r
+En ce moment un mouvement se fit tout à côté du président. On entendit\r
+une voix qui criait:\r
+\r
+--Brevet, Chenildieu, Cochepaille! regardez de ce côté-ci.\r
+\r
+Tous ceux qui entendirent cette voix se sentirent glacés, tant elle\r
+était lamentable et terrible. Les yeux se tournèrent vers le point d'où\r
+elle venait. Un homme, placé parmi les spectateurs privilégiés qui\r
+étaient assis derrière la cour, venait de se lever, avait poussé la\r
+porte à hauteur d'appui qui séparait le tribunal du prétoire, et était\r
+debout au milieu de la salle. Le président, l'avocat général, M.\r
+Bamatabois, vingt personnes, le reconnurent, et s'écrièrent à la fois:\r
+\r
+--Monsieur Madeleine!\r
+\r
+\r
+\r
+\r
+Chapitre XI\r
+\r
+Champmathieu de plus en plus étonné\r
+\r
+\r
+C'était lui en effet. La lampe du greffier éclairait son visage. Il\r
+tenait son chapeau à la main, il n'y avait aucun désordre dans ses\r
+vêtements, sa redingote était boutonnée avec soin. Il était très pâle et\r
+il tremblait légèrement. Ses cheveux, gris encore au moment de son\r
+arrivée à Arras, étaient maintenant tout à fait blancs. Ils avaient\r
+blanchi depuis une heure qu'il était là.\r
+\r
+Toutes les têtes se dressèrent. La sensation fut indescriptible. Il y\r
+eut dans l'auditoire un instant d'hésitation. La voix avait été si\r
+poignante, l'homme qui était là paraissait si calme, qu'au premier abord\r
+on ne comprit pas. On se demanda qui avait crié. On ne pouvait croire\r
+que ce fût cet homme tranquille qui eût jeté ce cri effrayant.\r
+\r
+Cette indécision ne dura que quelques secondes. Avant même que le\r
+président et l'avocat général eussent pu dire un mot, avant que les\r
+gendarmes et les huissiers eussent pu faire un geste, l'homme que tous\r
+appelaient encore en ce moment M. Madeleine s'était avancé vers les\r
+témoins Cochepaille, Brevet et Chenildieu.\r
+\r
+--Vous ne me reconnaissez pas? dit-il.\r
+\r
+Tous trois demeurèrent interdits et indiquèrent par un signe de tête\r
+qu'ils ne le connaissaient point. Cochepaille intimidé fit le salut\r
+militaire. M. Madeleine se tourna vers les jurés et vers la cour et dit\r
+d'une voix douce:\r
+\r
+--Messieurs les jurés, faites relâcher l'accusé. Monsieur le président,\r
+faites-moi arrêter. L'homme que vous cherchez, ce n'est pas lui, c'est\r
+moi. Je suis Jean Valjean. Pas une bouche ne respirait. À la première\r
+commotion de l'étonnement avait succédé un silence de sépulcre. On\r
+sentait dans la salle cette espèce de terreur religieuse qui saisit la\r
+foule lorsque quelque chose de grand s'accomplit.\r
+\r
+Cependant le visage du président s'était empreint de sympathie et de\r
+tristesse; il avait échangé un signe rapide avec l'avocat et quelques\r
+paroles à voix basse avec les conseillers assesseurs. Il s'adressa au\r
+public, et demanda avec un accent qui fut compris de tous:\r
+\r
+--Y a-t-il un médecin ici?\r
+\r
+L'avocat général prit la parole:\r
+\r
+--Messieurs les jurés, l'incident si étrange et si inattendu qui trouble\r
+l'audience ne nous inspire, ainsi qu'à vous, qu'un sentiment que nous\r
+n'avons pas besoin d'exprimer. Vous connaissez tous, au moins de\r
+réputation, l'honorable M. Madeleine, maire de Montreuil-sur-mer. S'il y\r
+a un médecin dans l'auditoire, nous nous joignons à monsieur le\r
+président pour le prier de vouloir bien assister monsieur Madeleine et\r
+le reconduire à sa demeure.\r
+\r
+M. Madeleine ne laissa point achever l'avocat général.\r
+\r
+Il l'interrompit d'un accent plein de mansuétude et d'autorité. Voici\r
+les paroles qu'il prononça; les voici littéralement, telles qu'elles\r
+furent écrites immédiatement après l'audience par un des témoins de\r
+cette scène; telles qu'elles sont encore dans l'oreille de ceux qui les\r
+ont entendues, il y a près de quarante ans aujourd'hui.\r
+\r
+--Je vous remercie, monsieur l'avocat général, mais je ne suis pas fou.\r
+Vous allez voir. Vous étiez sur le point de commettre une grande erreur,\r
+lâchez cet homme, j'accomplis un devoir, je suis ce malheureux condamné.\r
+Je suis le seul qui voie clair ici, et je vous dis la vérité. Ce que je\r
+fais en ce moment, Dieu, qui est là-haut, le regarde, et cela suffit.\r
+Vous pouvez me prendre, puisque me voilà. J'avais pourtant fait de mon\r
+mieux. Je me suis caché sous un nom; je suis devenu riche, je suis\r
+devenu maire; j'ai voulu rentrer parmi les honnêtes gens. Il paraît que\r
+cela ne se peut pas. Enfin, il y a bien des choses que je ne puis pas\r
+dire, je ne vais pas vous raconter ma vie, un jour on saura. J'ai volé\r
+monseigneur l'évêque, cela est vrai; j'ai volé Petit-Gervais, cela est\r
+vrai. On a eu raison de vous dire que Jean Valjean était un malheureux\r
+très méchant. Toute la faute n'est peut-être pas à lui. Écoutez,\r
+messieurs les juges, un homme aussi abaissé que moi n'a pas de\r
+remontrance à faire à la providence ni de conseil à donner à la société;\r
+mais, voyez-vous, l'infamie d'où j'avais essayé de sortir est une chose\r
+nuisible. Les galères font le galérien. Recueillez cela, si vous voulez.\r
+\r
+Avant le bagne, j'étais un pauvre paysan très peu intelligent, une\r
+espèce d'idiot; le bagne m'a changé. J'étais stupide, je suis devenu\r
+méchant; j'étais bûche, je suis devenu tison. Plus tard l'indulgence et\r
+la bonté m'ont sauvé, comme la sévérité m'avait perdu. Mais, pardon,\r
+vous ne pouvez pas comprendre ce que je dis là. Vous trouverez chez moi,\r
+dans les cendres de la cheminée, la pièce de quarante sous que j'ai\r
+volée il y a sept ans à Petit-Gervais. Je n'ai plus rien à ajouter.\r
+Prenez-moi. Mon Dieu! monsieur l'avocat général remue la tête, vous\r
+dites: M. Madeleine est devenu fou, vous ne me croyez pas! Voilà qui est\r
+affligeant. N'allez point condamner cet homme au moins! Quoi! ceux-ci ne\r
+me reconnaissent pas! Je voudrais que Javert fût ici. Il me\r
+reconnaîtrait, lui!\r
+\r
+Rien ne pourrait rendre ce qu'il y avait de mélancolie bienveillante et\r
+sombre dans l'accent qui accompagnait ces paroles.\r
+\r
+Il se tourna vers les trois forçats:\r
+\r
+--Eh bien, je vous reconnais, moi! Brevet! vous rappelez-vous?...\r
+\r
+Il s'interrompit, hésita un moment, et dit:\r
+\r
+--Te rappelles-tu ces bretelles en tricot à damier que tu avais au\r
+bagne?\r
+\r
+Brevet eut comme une secousse de surprise et le regarda de la tête aux\r
+pieds d'un air effrayé. Lui continua:\r
+\r
+--Chenildieu, qui te surnommais toi-même Je-nie-Dieu, tu as toute\r
+l'épaule droite brûlée profondément, parce que tu t'es couché un jour\r
+l'épaule sur un réchaud plein de braise, pour effacer les trois lettres\r
+T. F. P., qu'on y voit toujours cependant. Réponds, est-ce vrai?\r
+\r
+--C'est vrai, dit Chenildieu.\r
+\r
+Il s'adressa à Cochepaille:\r
+\r
+--Cochepaille, tu as près de la saignée du bras gauche une date gravée\r
+en lettres bleues avec de la poudre brûlée. Cette date, c'est celle du\r
+débarquement de l'empereur à Cannes, _1er mars 1815_. Relève ta manche.\r
+\r
+Cochepaille releva sa manche, tous les regards se penchèrent autour de\r
+lui sur son bras nu. Un gendarme approcha une lampe; la date y était.\r
+\r
+Le malheureux homme se tourna vers l'auditoire et vers les juges avec un\r
+sourire dont ceux qui l'ont vu sont encore navrés lorsqu'ils y songent.\r
+C'était le sourire du triomphe, c'était aussi le sourire du désespoir.\r
+\r
+--Vous voyez bien, dit-il, que je suis Jean Valjean.\r
+\r
+Il n'y avait plus dans cette enceinte ni juges, ni accusateurs, ni\r
+gendarmes; il n'y avait que des yeux fixes et des coeurs émus. Personne\r
+ne se rappelait plus le rôle que chacun pouvait avoir à jouer; l'avocat\r
+général oubliait qu'il était là pour requérir, le président qu'il était\r
+là pour présider, le défenseur qu'il était là pour défendre. Chose\r
+frappante, aucune question ne fut faite, aucune autorité n'intervint. Le\r
+propre des spectacles sublimes, c'est de prendre toutes les âmes et de\r
+faire de tous les témoins des spectateurs. Aucun peut-être ne se rendait\r
+compte de ce qu'il éprouvait; aucun, sans doute, ne se disait qu'il\r
+voyait resplendir là une grande lumière; tous intérieurement se\r
+sentaient éblouis.\r
+\r
+Il était évident qu'on avait sous les yeux Jean Valjean. Cela rayonnait.\r
+L'apparition de cet homme avait suffi pour remplir de clarté cette\r
+aventure si obscure le moment d'auparavant. Sans qu'il fût besoin\r
+d'aucune explication désormais, toute cette foule, comme par une sorte\r
+de révélation électrique, comprit tout de suite et d'un seul coup d'oeil\r
+cette simple et magnifique histoire d'un homme qui se livrait pour qu'un\r
+autre homme ne fût pas condamné à sa place. Les détails, les\r
+hésitations, les petites résistances possibles se perdirent dans ce\r
+vaste fait lumineux.\r
+\r
+Impression qui passa vite, mais qui dans l'instant fut irrésistible.\r
+\r
+--Je ne veux pas déranger davantage l'audience, reprit Jean Valjean. Je\r
+m'en vais, puisqu'on ne m'arrête pas. J'ai plusieurs choses à faire.\r
+Monsieur l'avocat général sait qui je suis, il sait où je vais, il me\r
+fera arrêter quand il voudra.\r
+\r
+Il se dirigea vers la porte de sortie. Pas une voix ne s'éleva, pas un\r
+bras ne s'étendit pour l'empêcher. Tous s'écartèrent. Il avait en ce\r
+moment ce je ne sais quoi de divin qui fait que les multitudes reculent\r
+et se rangent devant un homme. Il traversa la foule à pas lents. On n'a\r
+jamais su qui ouvrit la porte, mais il est certain que la porte se\r
+trouva ouverte lorsqu'il y parvint. Arrivé là, il se retourna et dit:\r
+\r
+--Monsieur l'avocat général, je reste à votre disposition.\r
+\r
+Puis il s'adressa à l'auditoire:\r
+\r
+--Vous tous, tous ceux qui sont ici, vous me trouvez digne de pitié,\r
+n'est-ce pas? Mon Dieu! quand je pense à ce que j'ai été sur le point de\r
+faire, je me trouve digne d'envie. Cependant j'aurais mieux aimé que\r
+tout ceci n'arrivât pas.\r
+\r
+Il sortit, et la porte se referma comme elle avait été ouverte, car ceux\r
+qui font de certaines choses souveraines sont toujours sûrs d'être\r
+servis par quelqu'un dans la foule.\r
+\r
+Moins d'une heure après, le verdict du jury déchargeait de toute\r
+accusation le nommé Champmathieu; et Champmathieu, mis en liberté\r
+immédiatement, s'en allait stupéfait, croyant tous les hommes fous et ne\r
+comprenant rien à cette vision.\r
+\r
+\r
+\r
+\r
+Livre huitième--Contre-coup\r
+\r
+\r
+\r
+\r
+Chapitre I\r
+\r
+Dans quel miroir M. Madeleine regarde ses cheveux\r
+\r
+\r
+Le jour commençait à poindre. Fantine avait eu une nuit de fièvre et\r
+d'insomnie, pleine d'ailleurs d'images heureuses; au matin, elle\r
+s'endormit. La soeur Simplice qui l'avait veillée profita de ce sommeil\r
+pour aller préparer une nouvelle potion de quinquina. La digne soeur\r
+était depuis quelques instants dans le laboratoire de l'infirmerie,\r
+penchée sur ses drogues et sur ses fioles et regardant de très près à\r
+cause de cette brume que le crépuscule répand sur les objets. Tout à\r
+coup elle tourna la tête et fit un léger cri. M. Madeleine était devant\r
+elle. Il venait d'entrer silencieusement.\r
+\r
+--C'est vous, monsieur le maire! s'écria-t-elle.\r
+\r
+Il répondit, à voix basse:\r
+\r
+--Comment va cette pauvre femme?\r
+\r
+--Pas mal en ce moment. Mais nous avons été bien inquiets, allez!\r
+\r
+Elle lui expliqua ce qui s'était passé, que Fantine était bien mal la\r
+veille et que maintenant elle était mieux, parce qu'elle croyait que\r
+monsieur le maire était allé chercher son enfant à Montfermeil. La soeur\r
+n'osa pas interroger monsieur le maire, mais elle vit bien à son air que\r
+ce n'était point de là qu'il venait.\r
+\r
+--Tout cela est bien, dit-il, vous avez eu raison de ne pas la\r
+détromper.\r
+\r
+--Oui, reprit la soeur, mais maintenant, monsieur le maire, qu'elle va\r
+vous voir et qu'elle ne verra pas son enfant, que lui dirons-nous?\r
+\r
+Il resta un moment rêveur.\r
+\r
+--Dieu nous inspirera, dit-il.\r
+\r
+--On ne pourrait cependant pas mentir, murmura la soeur à demi-voix.\r
+\r
+Le plein jour s'était fait dans la chambre. Il éclairait en face le\r
+visage de M. Madeleine. Le hasard fit que la soeur leva les yeux.\r
+\r
+--Mon Dieu, monsieur! s'écria-t-elle, que vous est-il donc arrivé? vos\r
+cheveux sont tout blancs!\r
+\r
+--Blancs! dit-il.\r
+\r
+La soeur Simplice n'avait point de miroir; elle fouilla dans une trousse\r
+et en tira une petite glace dont se servait le médecin de l'infirmerie\r
+pour constater qu'un malade était mort et ne respirait plus. M.\r
+Madeleine prit la glace, y considéra ses cheveux, et dit:\r
+\r
+--Tiens!\r
+\r
+Il prononça ce mot avec indifférence et comme s'il pensait à autre\r
+chose.\r
+\r
+La soeur se sentit glacée par je ne sais quoi d'inconnu qu'elle\r
+entrevoyait dans tout ceci.\r
+\r
+Il demanda:\r
+\r
+--Puis-je la voir?\r
+\r
+--Est-ce que monsieur le maire ne lui fera pas revenir son enfant? dit\r
+la soeur, osant à peine hasarder une question.\r
+\r
+--Sans doute, mais il faut au moins deux ou trois jours.\r
+\r
+--Si elle ne voyait pas monsieur le maire d'ici là, reprit timidement la\r
+soeur, elle ne saurait pas que monsieur le maire est de retour, il\r
+serait aisé de lui faire prendre patience, et quand l'enfant arriverait\r
+elle penserait tout naturellement que monsieur le maire est arrivé avec\r
+l'enfant. On n'aurait pas de mensonge à faire.\r
+\r
+M. Madeleine parut réfléchir quelques instants, puis il dit avec sa\r
+gravité calme:\r
+\r
+--Non, ma soeur, il faut que je la voie. Je suis peut-être pressé.\r
+\r
+La religieuse ne sembla pas remarquer ce mot «peut-être», qui donnait un\r
+sens obscur et singulier aux paroles de M. le maire. Elle répondit en\r
+baissant les yeux et la voix respectueusement:\r
+\r
+--En ce cas, elle repose, mais monsieur le maire peut entrer.\r
+\r
+Il fit quelques observations sur une porte qui fermait mal, et dont le\r
+bruit pouvait réveiller la malade, puis il entra dans la chambre de\r
+Fantine, s'approcha du lit et entrouvrit les rideaux. Elle dormait. Son\r
+souffle sortait de sa poitrine avec ce bruit tragique qui est propre à\r
+ces maladies, et qui navre les pauvres mères lorsqu'elles veillent la\r
+nuit près de leur enfant condamné et endormi. Mais cette respiration\r
+pénible troublait à peine une sorte de sérénité ineffable, répandue sur\r
+son visage, qui la transfigurait dans son sommeil. Sa pâleur était\r
+devenue de la blancheur; ses joues étaient vermeilles. Ses longs cils\r
+blonds, la seule beauté qui lui fût restée de sa virginité et de sa\r
+jeunesse, palpitaient tout en demeurant clos et baissés. Toute sa\r
+personne tremblait de je ne sais quel déploiement d'ailes prêtes à\r
+s'entrouvrir et à l'emporter, qu'on sentait frémir, mais qu'on ne voyait\r
+pas. À la voir ainsi, on n'eût jamais pu croire que c'était là une\r
+malade presque désespérée. Elle ressemblait plutôt à ce qui va s'envoler\r
+qu'à ce qui va mourir.\r
+\r
+La branche, lorsqu'une main s'approche pour détacher la fleur,\r
+frissonne, et semble à la fois se dérober et s'offrir. Le corps humain a\r
+quelque chose de ce tressaillement, quand arrive l'instant où les doigts\r
+mystérieux de la mort vont cueillir l'âme.\r
+\r
+M. Madeleine resta quelque temps immobile près de ce lit, regardant tour\r
+à tour la malade et le crucifix, comme il faisait deux mois auparavant,\r
+le jour où il était venu pour la première fois la voir dans cet asile.\r
+Ils étaient encore là tous les deux dans la même attitude, elle dormant,\r
+lui priant; seulement maintenant, depuis ces deux mois écoulés, elle\r
+avait des cheveux gris et lui des cheveux blancs.\r
+\r
+La soeur n'était pas entrée avec lui. Il se tenait près de ce lit,\r
+debout, le doigt sur la bouche, comme s'il y eût eu dans la chambre\r
+quelqu'un à faire taire.\r
+\r
+Elle ouvrit les yeux, le vit, et dit paisiblement, avec un sourire:\r
+\r
+--Et Cosette?\r
+\r
+\r
+\r
+\r
+Chapitre II\r
+\r
+Fantine heureuse\r
+\r
+\r
+Elle n'eut pas un mouvement de surprise, ni un mouvement de joie; elle\r
+était la joie même. Cette simple question: «Et Cosette?» fut faite avec\r
+une foi si profonde, avec tant de certitude, avec une absence si\r
+complète d'inquiétude et de doute, qu'il ne trouva pas une parole. Elle\r
+continua:\r
+\r
+--Je savais que vous étiez là. Je dormais, mais je vous voyais. Il y a\r
+longtemps que je vous vois. Je vous ai suivi des yeux toute la nuit.\r
+Vous étiez dans une gloire et vous aviez autour de vous toutes sortes de\r
+figures célestes.\r
+\r
+Il leva son regard vers le crucifix.\r
+\r
+--Mais, reprit-elle, dites-moi donc où est Cosette? Pourquoi ne l'avoir\r
+pas mise sur mon lit pour le moment où je m'éveillerais?\r
+\r
+Il répondit machinalement quelque chose qu'il n'a jamais pu se rappeler\r
+plus tard.\r
+\r
+Heureusement le médecin, averti, était survenu. Il vint en aide à M.\r
+Madeleine.\r
+\r
+--Mon enfant, dit le médecin, calmez-vous. Votre enfant est là.\r
+\r
+Les yeux de Fantine s'illuminèrent et couvrirent de clarté tout son\r
+visage. Elle joignit les mains avec une expression qui contenait tout ce\r
+que la prière peut avoir à la fois de plus violent et de plus doux.\r
+\r
+--Oh! s'écria-t-elle, apportez-la-moi!\r
+\r
+Touchante illusion de mère! Cosette était toujours pour elle le petit\r
+enfant qu'on apporte.\r
+\r
+--Pas encore, reprit le médecin, pas en ce moment. Vous avez un reste de\r
+fièvre. La vue de votre enfant vous agiterait et vous ferait du mal. Il\r
+faut d'abord vous guérir. Elle l'interrompit impétueusement.\r
+\r
+--Mais je suis guérie! je vous dis que je suis guérie! Est-il âne, ce\r
+médecin! Ah çà! je veux voir mon enfant, moi!\r
+\r
+--Vous voyez, dit le médecin, comme vous vous emportez. Tant que vous\r
+serez ainsi, je m'opposerai à ce que vous ayez votre enfant. Il ne\r
+suffit pas de la voir, il faut vivre pour elle. Quand vous serez\r
+raisonnable, je vous l'amènerai moi-même.\r
+\r
+La pauvre mère courba la tête.\r
+\r
+--Monsieur le médecin, je vous demande pardon, je vous demande vraiment\r
+bien pardon. Autrefois, je n'aurais pas parlé comme je viens de faire,\r
+il m'est arrivé tant de malheurs que quelquefois je ne sais plus ce que\r
+je dis. Je comprends, vous craignez l'émotion, j'attendrai tant que vous\r
+voudrez, mais je vous jure que cela ne m'aurait pas fait de mal de voir\r
+ma fille. Je la vois, je ne la quitte pas des yeux depuis hier au soir.\r
+Savez-vous? on me l'apporterait maintenant que je me mettrais à lui\r
+parler doucement. Voilà tout. Est-ce que ce n'est pas bien naturel que\r
+j'aie envie de voir mon enfant qu'on a été me chercher exprès à\r
+Montfermeil? Je ne suis pas en colère. Je sais bien que je vais être\r
+heureuse. Toute la nuit j'ai vu des choses blanches et des personnes qui\r
+me souriaient. Quand monsieur le médecin voudra, il m'apportera ma\r
+Cosette. Je n'ai plus de fièvre, puisque je suis guérie; je sens bien\r
+que je n'ai plus rien du tout; mais je vais faire comme si j'étais\r
+malade et ne pas bouger pour faire plaisir aux dames d'ici. Quand on\r
+verra que je suis bien tranquille, on dira: il faut lui donner son\r
+enfant.\r
+\r
+M. Madeleine s'était assis sur une chaise qui était à côté du lit. Elle\r
+se tourna vers lui; elle faisait visiblement effort pour paraître calme\r
+et «bien sage», comme elle disait dans cet affaiblissement de la maladie\r
+qui ressemble à l'enfance, afin que, la voyant si paisible, on ne fît\r
+pas difficulté de lui amener Cosette. Cependant, tout en se contenant,\r
+elle ne pouvait s'empêcher d'adresser à M. Madeleine mille questions.\r
+\r
+--Avez-vous fait un bon voyage, monsieur le maire? Oh! comme vous êtes\r
+bon d'avoir été me la chercher! Dites-moi seulement comment elle est.\r
+A-t-elle bien supporté la route? Hélas! elle ne me reconnaîtra pas!\r
+Depuis le temps, elle m'a oubliée, pauvre chou! Les enfants, cela n'a\r
+pas de mémoire. C'est comme des oiseaux. Aujourd'hui cela voit une chose\r
+et demain une autre, et cela ne pense plus à rien. Avait-elle du linge\r
+blanc seulement? Ces Thénardier la tenaient-ils proprement? Comment la\r
+nourrissait-on? Oh! comme j'ai souffert, si vous saviez! de me faire\r
+toutes ces questions-là dans le temps de ma misère! Maintenant, c'est\r
+passé. Je suis joyeuse. Oh! que je voudrais donc la voir! Monsieur le\r
+maire, l'avez-vous trouvée jolie? N'est-ce pas qu'elle est belle, ma\r
+fille? Vous devez avoir eu bien froid dans cette diligence! Est-ce qu'on\r
+ne pourrait pas l'amener rien qu'un petit moment? On la remporterait\r
+tout de suite après. Dites! vous qui êtes le maître, si vous vouliez!\r
+\r
+Il lui prit la main:\r
+\r
+--Cosette est belle, dit-il, Cosette se porte bien, vous la verrez\r
+bientôt, mais apaisez-vous. Vous parlez trop vivement, et puis vous\r
+sortez vos bras du lit, et cela vous fait tousser.\r
+\r
+En effet, des quintes de toux interrompaient Fantine presque à chaque\r
+mot.\r
+\r
+Fantine ne murmura pas, elle craignait d'avoir compromis par quelques\r
+plaintes trop passionnées la confiance qu'elle voulait inspirer, et elle\r
+se mit à dire des paroles indifférentes.\r
+\r
+--C'est assez joli, Montfermeil, n'est-ce-pas? L'été, on va y faire des\r
+parties de plaisir. Ces Thénardier font-ils de bonnes affaires? Il ne\r
+passe pas grand monde dans leur pays. C'est une espèce de gargote que\r
+cette auberge-là.\r
+\r
+M. Madeleine lui tenait toujours la main, il la considérait avec\r
+anxiété; il était évident qu'il était venu pour lui dire des choses\r
+devant lesquelles sa pensée hésitait maintenant. Le médecin, sa visite\r
+faite, s'était retiré. La soeur Simplice était seule restée auprès\r
+d'eux.\r
+\r
+Cependant, au milieu de ce silence, Fantine s'écria:\r
+\r
+--Je l'entends! mon Dieu! je l'entends!\r
+\r
+Elle étendit le bras pour qu'on se tût autour d'elle, retint son\r
+souffle, et se mit à écouter avec ravissement.\r
+\r
+Il y avait un enfant qui jouait dans la cour; l'enfant de la portière ou\r
+d'une ouvrière quelconque. C'est là un de ces hasards qu'on retrouve\r
+toujours et qui semblent faire partie de la mystérieuse mise en scène\r
+des événements lugubres. L'enfant, c'était une petite fille, allait,\r
+venait, courait pour se réchauffer, riait et chantait à haute voix.\r
+Hélas! à quoi les jeux des enfants ne se mêlent-ils pas! C'était cette\r
+petite fille que Fantine entendait chanter.\r
+\r
+--Oh! reprit-elle, c'est ma Cosette! je reconnais sa voix!\r
+\r
+L'enfant s'éloigna comme il était venu, la voix s'éteignit, Fantine\r
+écouta encore quelque temps, puis son visage s'assombrit, et M.\r
+Madeleine l'entendit qui disait à voix basse:\r
+\r
+--Comme ce médecin est méchant de ne pas me laisser voir ma fille! Il a\r
+une mauvaise figure, cet homme-là!\r
+\r
+Cependant le fond riant de ses idées revint. Elle continua de se parler\r
+à elle-même, la tête sur l'oreiller.\r
+\r
+--Comme nous allons être heureuses! Nous aurons un petit jardin,\r
+d'abord! M. Madeleine me l'a promis. Ma fille jouera dans le jardin.\r
+Elle doit savoir ses lettres maintenant. Je la ferai épeler. Elle courra\r
+dans l'herbe après les papillons. Je la regarderai. Et puis elle fera sa\r
+première communion. Ah çà! quand fera-t-elle sa première communion? Elle\r
+se mit à compter sur ses doigts.\r
+\r
+--... Un, deux, trois, quatre... elle a sept ans. Dans cinq ans. Elle\r
+aura un voile blanc, des bas à jour, elle aura l'air d'une petite femme.\r
+Ô ma bonne soeur, vous ne savez pas comme je suis bête, voilà que je\r
+pense à la première communion de ma fille! Et elle se mit à rire.\r
+\r
+Il avait quitté la main de Fantine. Il écoutait ces paroles comme on\r
+écoute un vent qui souffle, les yeux à terre, l'esprit plongé dans des\r
+réflexions sans fond. Tout à coup elle cessa de parler, cela lui fit\r
+lever machinalement la tête. Fantine était devenue effrayante.\r
+\r
+Elle ne parlait plus, elle ne respirait plus; elle s'était soulevée à\r
+demi sur son séant, son épaule maigre sortait de sa chemise, son visage,\r
+radieux le moment d'auparavant, était blême, et elle paraissait fixer\r
+sur quelque chose de formidable, devant elle, à l'autre extrémité de la\r
+chambre, son oeil agrandi par la terreur.\r
+\r
+--Mon Dieu! s'écria-t-il. Qu'avez-vous, Fantine?\r
+\r
+Elle ne répondit pas, elle ne quitta point des yeux l'objet quelconque\r
+qu'elle semblait voir, elle lui toucha le bras d'une main et de l'autre\r
+lui fit signe de regarder derrière lui.\r
+\r
+Il se retourna, et vit Javert.\r
+\r
+\r
+\r
+\r
+Chapitre III\r
+\r
+Javert content\r
+\r
+\r
+Voici ce qui s'était passé.\r
+\r
+Minuit et demi venait de sonner, quand M. Madeleine était sorti de la\r
+salle des assises d'Arras. Il était rentré à son auberge juste à temps\r
+pour repartir par la malle-poste où l'on se rappelle qu'il avait retenu\r
+sa place. Un peu avant six heures du matin, il était arrivé à\r
+Montreuil-sur-mer, et son premier soin avait été de jeter à la poste sa\r
+lettre à M. Laffitte, puis d'entrer à l'infirmerie et de voir Fantine.\r
+\r
+Cependant, à peine avait-il quitté la salle d'audience de la cour\r
+d'assises, que l'avocat général, revenu du premier saisissement, avait\r
+pris la parole pour déplorer l'acte de folie de l'honorable maire de\r
+Montreuil-sur-mer, déclarer que ses convictions n'étaient en rien\r
+modifiées par cet incident bizarre qui s'éclaircirait plus tard, et\r
+requérir, en attendant, la condamnation de ce Champmathieu, évidemment\r
+le vrai Jean Valjean. La persistance de l'avocat général était\r
+visiblement en contradiction avec le sentiment de tous, du public, de la\r
+cour et du jury. Le défenseur avait eu peu de peine à réfuter cette\r
+harangue et à établir que, par suite des révélations de M. Madeleine,\r
+c'est-à-dire du vrai Jean Valjean, la face de l'affaire était\r
+bouleversée de fond en comble, et que le jury n'avait plus devant les\r
+yeux qu'un innocent. L'avocat avait tiré de là quelques épiphonèmes,\r
+malheureusement peu neufs, sur les erreurs judiciaires, etc., etc., le\r
+président dans son résumé s'était joint au défenseur, et le jury en\r
+quelques minutes avait mis hors de cause Champmathieu.\r
+\r
+Cependant il fallait un Jean Valjean à l'avocat général, et, n'ayant\r
+plus Champmathieu, il prit Madeleine.\r
+\r
+Immédiatement après la mise en liberté de Champmathieu, l'avocat général\r
+s'enferma avec le président. Ils conférèrent «de la nécessité de se\r
+saisir de la personne de M. le maire de Montreuil-sur-mer». Cette\r
+phrase, où il y a beaucoup de _de_, est de M. l'avocat général,\r
+entièrement écrite de sa main sur la minute de son rapport au procureur\r
+général. La première émotion passée, le président fit peu d'objections.\r
+Il fallait bien que justice eût son cours. Et puis, pour tout dire,\r
+quoique le président fût homme bon et assez intelligent, il était en\r
+même temps fort royaliste et presque ardent, et il avait été choqué que\r
+le maire de Montreuil-sur-mer, en parlant du débarquement à Cannes, eût\r
+dit l'_empereur_ et non _Buonaparte_.\r
+\r
+L'ordre d'arrestation fut donc expédié. L'avocat général l'envoya à\r
+Montreuil-sur-mer par un exprès, à franc étrier, et en chargea\r
+l'inspecteur de police Javert.\r
+\r
+On sait que Javert était revenu à Montreuil-sur-mer immédiatement après\r
+avoir fait sa déposition.\r
+\r
+Javert se levait au moment où l'exprès lui remit l'ordre d'arrestation\r
+et le mandat d'amener.\r
+\r
+L'exprès était lui-même un homme de police fort entendu qui, en deux\r
+mots, mit Javert au fait de ce qui était arrivé à Arras. L'ordre\r
+d'arrestation, signé de l'avocat général, était ainsi\r
+conçu:--L'inspecteur Javert appréhendera au corps le sieur Madeleine,\r
+maire de Montreuil-sur-mer, qui, dans l'audience de ce jour, a été\r
+reconnu pour être le forçat libéré Jean Valjean.\r
+\r
+Quelqu'un qui n'eût pas connu Javert et qui l'eût vu au moment où il\r
+pénétra dans l'antichambre de l'infirmerie n'eût pu rien deviner de ce\r
+qui se passait, et lui eût trouvé l'air le plus ordinaire du monde. Il\r
+était froid, calme, grave, avait ses cheveux gris parfaitement lissés\r
+sur les tempes et venait de monter l'escalier avec sa lenteur\r
+habituelle. Quelqu'un qui l'eût connu à fond et qui l'eût examiné\r
+attentivement eût frémi. La boucle de son col de cuir, au lieu d'être\r
+sur sa nuque, était sur son oreille gauche. Ceci révélait une agitation\r
+inouïe.\r
+\r
+Javert était un caractère complet, ne laissant faire de pli ni à son\r
+devoir, ni à son uniforme; méthodique avec les scélérats, rigide avec\r
+les boutons de son habit.\r
+\r
+Pour qu'il eût mal mis la boucle de son col, il fallait qu'il y eût en\r
+lui une de ces émotions qu'on pourrait appeler des tremblements de terre\r
+intérieurs.\r
+\r
+Il était venu simplement, avait requis un caporal et quatre soldats au\r
+poste voisin, avait laissé les soldats dans la cour, et s'était fait\r
+indiquer la chambre de Fantine par la portière sans défiance, accoutumée\r
+qu'elle était à voir des gens armés demander monsieur le maire.\r
+\r
+Arrivé à la chambre de Fantine, Javert tourna la clef, poussa la porte\r
+avec une douceur de garde-malade ou de mouchard, et entra.\r
+\r
+À proprement parler, il n'entra pas. Il se tint debout dans la porte\r
+entrebâillée, le chapeau sur la tête, la main gauche dans sa redingote\r
+fermée jusqu'au menton. Dans le pli du coude on pouvait voir le pommeau\r
+de plomb de son énorme canne, laquelle disparaissait derrière lui.\r
+\r
+Il resta ainsi près d'une minute sans qu'on s'aperçût de sa présence.\r
+Tout à coup Fantine leva les yeux, le vit, et fit retourner M.\r
+Madeleine.\r
+\r
+À l'instant où le regard de Madeleine rencontra le regard de Javert,\r
+Javert, sans bouger, sans remuer, sans approcher, devint épouvantable.\r
+Aucun sentiment humain ne réussit à être effroyable comme la joie.\r
+\r
+Ce fut le visage d'un démon qui vient de retrouver son damné.\r
+\r
+La certitude de tenir enfin Jean Valjean fit apparaître sur sa\r
+physionomie tout ce qu'il avait dans l'âme. Le fond remué monta à la\r
+surface. L'humiliation d'avoir un peu perdu la piste et de s'être mépris\r
+quelques minutes sur ce Champmathieu, s'effaçait sous l'orgueil d'avoir\r
+si bien deviné d'abord et d'avoir eu si longtemps un instinct juste. Le\r
+contentement de Javert éclata dans son attitude souveraine. La\r
+difformité du triomphe s'épanouit sur ce front étroit. Ce fut tout le\r
+déploiement d'horreur que peut donner une figure satisfaite.\r
+\r
+Javert en ce moment était au ciel. Sans qu'il s'en rendit nettement\r
+compte, mais pourtant avec une intuition confuse de sa nécessité et de\r
+son succès, il personnifiait, lui Javert, la justice, la lumière et la\r
+vérité dans leur fonction céleste d'écrasement du mal. Il avait derrière\r
+lui et autour de lui, à une profondeur infinie, l'autorité, la raison,\r
+la chose jugée, la conscience légale, la vindicte publique, toutes les\r
+étoiles; il protégeait l'ordre, il faisait sortir de la loi la foudre,\r
+il vengeait la société, il prêtait main-forte à l'absolu; il se dressait\r
+dans une gloire; il y avait dans sa victoire un reste de défi et de\r
+combat; debout, altier, éclatant, il étalait en plein azur la bestialité\r
+surhumaine d'un archange féroce; l'ombre redoutable de l'action qu'il\r
+accomplissait faisait visible à son poing crispé le vague flamboiement\r
+de l'épée sociale; heureux et indigné, il tenait sous son talon le\r
+crime, le vice, la rébellion, la perdition, l'enfer, il rayonnait, il\r
+exterminait, il souriait et il y avait une incontestable grandeur dans\r
+ce saint Michel monstrueux.\r
+\r
+Javert, effroyable, n'avait rien d'ignoble.\r
+\r
+La probité, la sincérité, la candeur, la conviction, l'idée du devoir,\r
+sont des choses qui, en se trompant, peuvent devenir hideuses, mais qui,\r
+même hideuses, restent grandes; leur majesté, propre à la conscience\r
+humaine, persiste dans l'horreur. Ce sont des vertus qui ont un vice,\r
+l'erreur. L'impitoyable joie honnête d'un fanatique en pleine atrocité\r
+conserve on ne sait quel rayonnement lugubrement vénérable. Sans qu'il\r
+s'en doutât, Javert, dans son bonheur formidable, était à plaindre comme\r
+tout ignorant qui triomphe. Rien n'était poignant et terrible comme\r
+cette figure où se montrait ce qu'on pourrait appeler tout le mauvais du\r
+bon.\r
+\r
+\r
+\r
+\r
+Chapitre IV\r
+\r
+L'autorité reprend ses droits\r
+\r
+\r
+La Fantine n'avait point vu Javert depuis le jour où M. le maire l'avait\r
+arrachée à cet homme. Son cerveau malade ne se rendit compte de rien,\r
+seulement elle ne douta pas qu'il ne revint la chercher. Elle ne put\r
+supporter cette figure affreuse, elle se sentit expirer, elle cacha son\r
+visage de ses deux mains et cria avec angoisse:\r
+\r
+--Monsieur Madeleine, sauvez-moi!\r
+\r
+Jean Valjean--nous ne le nommerons plus désormais autrement--s'était\r
+levé. Il dit à Fantine de sa voix la plus douce et la plus calme:\r
+\r
+--Soyez tranquille. Ce n'est pas pour vous qu'il vient.\r
+\r
+Puis il s'adressa à Javert et lui dit:\r
+\r
+--Je sais ce que vous voulez.\r
+\r
+Javert répondit:\r
+\r
+--Allons, vite!\r
+\r
+Il y eut dans l'inflexion qui accompagna ces deux mots je ne sais quoi\r
+de fauve et de frénétique. Javert ne dit pas: «Allons, vite!» il dit:\r
+«Allonouaite!» Aucune orthographe ne pourrait rendre l'accent dont cela\r
+fut prononcé; ce n'était plus une parole humaine, c'était un\r
+rugissement.\r
+\r
+Il ne fit point comme d'habitude; il n'entra point en matière; il\r
+n'exhiba point de mandat d'amener. Pour lui, Jean Valjean était une\r
+sorte de combattant mystérieux et insaisissable, un lutteur ténébreux\r
+qu'il étreignait depuis cinq ans sans pouvoir le renverser. Cette\r
+arrestation n'était pas un commencement, mais une fin. Il se borna à\r
+dire: «Allons, vite!»\r
+\r
+En parlant ainsi, il ne fit point un pas; il lança sur Jean Valjean ce\r
+regard qu'il jetait comme un crampon, et avec lequel il avait coutume de\r
+tirer violemment les misérables à lui.\r
+\r
+C'était ce regard que la Fantine avait senti pénétrer jusque dans la\r
+moelle de ses os deux mois auparavant.\r
+\r
+Au cri de Javert, Fantine avait rouvert les yeux. Mais M. le maire était\r
+là. Que pouvait-elle craindre?\r
+\r
+Javert avança au milieu de la chambre et cria:\r
+\r
+--Ah çà! viendras-tu?\r
+\r
+La malheureuse regarda autour d'elle. Il n'y avait personne que la\r
+religieuse et monsieur le maire. À qui pouvait s'adresser ce tutoiement\r
+abject? elle seulement. Elle frissonna.\r
+\r
+Alors elle vit une chose inouïe, tellement inouïe que jamais rien de\r
+pareil ne lui était apparu dans les plus noirs délires de la fièvre.\r
+\r
+Elle vit le mouchard Javert saisir au collet monsieur le maire; elle vit\r
+monsieur le maire courber la tête. Il lui sembla que le monde\r
+s'évanouissait.\r
+\r
+Javert, en effet, avait pris Jean Valjean au collet.\r
+\r
+--Monsieur le maire! cria Fantine.\r
+\r
+Javert éclata de rire, de cet affreux rire qui lui déchaussait toutes\r
+les dents.\r
+\r
+--Il n'y a plus de monsieur le maire ici!\r
+\r
+Jean Valjean n'essaya pas de déranger la main qui tenait le col de sa\r
+redingote. Il dit:\r
+\r
+--Javert....\r
+\r
+Javert l'interrompit:\r
+\r
+--Appelle-moi monsieur l'inspecteur.\r
+\r
+--Monsieur, reprit Jean Valjean, je voudrais vous dire un mot en\r
+particulier.\r
+\r
+--Tout haut! parle tout haut! répondit Javert; on me parle tout haut à\r
+moi!\r
+\r
+Jean Valjean continua en baissant la voix:\r
+\r
+--C'est une prière que j'ai à vous faire....\r
+\r
+--Je te dis de parler tout haut.\r
+\r
+--Mais cela ne doit être entendu que de vous seul....\r
+\r
+--Qu'est-ce que cela me fait? je n'écoute pas!\r
+\r
+Jean Valjean se tourna vers lui et lui dit rapidement et très bas:\r
+\r
+--Accordez-moi trois jours! trois jours pour aller chercher l'enfant de\r
+cette malheureuse femme! Je payerai ce qu'il faudra. Vous\r
+m'accompagnerez si vous voulez.\r
+\r
+--Tu veux rire! cria Javert. Ah çà! je ne te croyais pas bête! Tu me\r
+demandes trois jours pour t'en aller! Tu dis que c'est pour aller\r
+chercher l'enfant de cette fille! Ah! ah! c'est bon! voilà qui est bon!\r
+Fantine eut un tremblement.\r
+\r
+--Mon enfant! s'écria-t-elle, aller chercher mon enfant! Elle n'est donc\r
+pas ici! Ma soeur, répondez-moi, où est Cosette? Je veux mon enfant!\r
+Monsieur Madeleine! monsieur le maire!\r
+\r
+Javert frappa du pied.\r
+\r
+--Voilà l'autre, à présent! Te tairas-tu, drôlesse! Gredin de pays où\r
+les galériens sont magistrats et où les filles publiques sont soignées\r
+comme des comtesses! Ah mais! tout ça va changer; il était temps!\r
+\r
+Il regarda fixement Fantine et ajouta en reprenant à poignée la cravate,\r
+la chemise et le collet de Jean Valjean:\r
+\r
+--Je te dis qu'il n'y a point de monsieur Madeleine et qu'il n'y a point\r
+de monsieur le maire. Il y a un voleur, il y a un brigand, il y a un\r
+forçat appelé Jean Valjean! c'est lui que je tiens! voilà ce qu'il y a!\r
+\r
+Fantine se dressa en sursaut, appuyée sur ses bras roides et sur ses\r
+deux mains, elle regarda Jean Valjean, elle regarda Javert, elle regarda\r
+la religieuse, elle ouvrit la bouche comme pour parler, un râle sortit\r
+du fond de sa gorge, ses dents claquèrent, elle étendit les bras avec\r
+angoisse, ouvrant convulsivement les mains, et cherchant autour d'elle\r
+comme quelqu'un qui se noie, puis elle s'affaissa subitement sur\r
+l'oreiller. Sa tête heurta le chevet du lit et vint retomber sur sa\r
+poitrine, la bouche béante, les yeux ouverts et éteints.\r
+\r
+Elle était morte.\r
+\r
+Jean Valjean posa sa main sur la main de Javert qui le tenait, et\r
+l'ouvrit comme il eût ouvert la main d'un enfant, puis il dit à Javert:\r
+\r
+--Vous avez tué cette femme.\r
+\r
+--Finirons-nous! cria Javert furieux. Je ne suis pas ici pour entendre\r
+des raisons. Économisons tout ça. La garde est en bas. Marchons tout de\r
+suite, ou les poucettes!\r
+\r
+Il y avait dans un coin de la chambre un vieux lit en fer en assez\r
+mauvais état qui servait de lit de camp aux soeurs quand elles\r
+veillaient. Jean Valjean alla à ce lit, disloqua en un clin d'oeil le\r
+chevet déjà fort délabré, chose facile à des muscles comme les siens,\r
+saisit à poigne-main la maîtresse-tringle, et considéra Javert. Javert\r
+recula vers la porte.\r
+\r
+Jean Valjean, sa barre de fer au poing, marcha lentement vers le lit de\r
+Fantine. Quand il y fut parvenu, il se retourna, et dit à Javert d'une\r
+voix qu'on entendait à peine:\r
+\r
+--Je ne vous conseille pas de me déranger en ce moment.\r
+\r
+Ce qui est certain, c'est que Javert tremblait.\r
+\r
+Il eut l'idée d'aller appeler la garde, mais Jean Valjean pouvait\r
+profiter de cette minute pour s'évader. Il resta donc, saisit sa canne\r
+par le petit bout, et s'adossa au chambranle de la porte sans quitter du\r
+regard Jean Valjean.\r
+\r
+Jean Valjean posa son coude sur la pomme du chevet du lit et son front\r
+sur sa main, et se mit à contempler Fantine immobile et étendue. Il\r
+demeura ainsi, absorbé, muet, et ne songeant évidemment plus à aucune\r
+chose de cette vie. Il n'y avait plus rien sur son visage et dans son\r
+attitude qu'une inexprimable pitié. Après quelques instants de cette\r
+rêverie, il se pencha vers Fantine et lui parla à voix basse.\r
+\r
+Que lui dit-il? Que pouvait dire cet homme qui était réprouvé à cette\r
+femme qui était morte? Qu'était-ce que ces paroles? Personne sur la\r
+terre ne les a entendues. La morte les entendit-elle? Il y a des\r
+illusions touchantes qui sont peut-être des réalités sublimes. Ce qui\r
+est hors de doute, c'est que la soeur Simplice, unique témoin de la\r
+chose qui se passait, a souvent raconté qu'au moment où Jean Valjean\r
+parla à l'oreille de Fantine, elle vit distinctement poindre un\r
+ineffable sourire sur ces lèvres pâles et dans ces prunelles vagues,\r
+pleines de l'étonnement du tombeau.\r
+\r
+Jean Valjean prit dans ses deux mains la tête de Fantine et l'arrangea\r
+sur l'oreiller comme une mère eût fait pour son enfant, il lui rattacha\r
+le cordon de sa chemise et rentra ses cheveux sous son bonnet. Cela\r
+fait, il lui ferma les yeux.\r
+\r
+La face de Fantine en cet instant semblait étrangement éclairée.\r
+\r
+La mort, c'est l'entrée dans la grande lueur.\r
+\r
+La main de Fantine pendait hors du lit. Jean Valjean s'agenouilla devant\r
+cette main, la souleva doucement, et la baisa.\r
+\r
+Puis il se redressa, et, se tournant vers Javert:\r
+\r
+--Maintenant, dit-il, je suis à vous.\r
+\r
+\r
+\r
+\r
+Chapitre V\r
+\r
+Tombeau convenable\r
+\r
+\r
+Javert déposa Jean Valjean à la prison de la ville.\r
+\r
+L'arrestation de M. Madeleine produisit à Montreuil-sur-mer une\r
+sensation, ou pour mieux dire une commotion extraordinaire. Nous sommes\r
+triste de ne pouvoir dissimuler que sur ce seul mot: _c'était un\r
+galérien_, tout le monde à peu près l'abandonna. En moins de deux heures\r
+tout le bien qu'il avait fait fut oublié, et ce ne fut plus «qu'un\r
+galérien». Il est juste de dire qu'on ne connaissait pas encore les\r
+détails de l'événement d'Arras. Toute la journée on entendait dans\r
+toutes les parties de la ville des conversations comme celle-ci:\r
+\r
+--Vous ne savez pas? c'était un forçat libéré! Qui ça?--Le maire.--Bah!\r
+M. Madeleine?--Oui. Vraiment?--Il ne s'appelait pas Madeleine, il a un\r
+affreux nom, Béjean, Bojean, Boujean.--Ah, mon Dieu!--Il est\r
+arrêté.--Arrêté!--En prison à la prison de la ville, en attendant qu'on\r
+le transfère.--Qu'on le transfère! On va le transférer! Où va-t-on le\r
+transférer?--Il va passer aux assises pour un vol de grand chemin qu'il\r
+a fait autrefois.--Eh bien! je m'en doutais. Cet homme était trop bon,\r
+trop parfait, trop confit. Il refusait la croix, il donnait des sous à\r
+tous les petits drôles qu'il rencontrait. J'ai toujours pensé qu'il y\r
+avait là-dessous quelque mauvaise histoire.\r
+\r
+«Les salons» surtout abondèrent dans ce sens.\r
+\r
+Une vieille dame, abonnée au _Drapeau blanc_, fit cette réflexion dont\r
+il est presque impossible de sonder la profondeur:\r
+\r
+--Je n'en suis pas fâchée. Cela apprendra aux buonapartistes!\r
+\r
+C'est ainsi que ce fantôme qui s'était appelé M. Madeleine se dissipa à\r
+Montreuil-sur-mer. Trois ou quatre personnes seulement dans toute la\r
+ville restèrent fidèles à cette mémoire. La vieille portière qui l'avait\r
+servi fut du nombre. Le soir de ce même jour, cette digne vieille était\r
+assise dans sa loge, encore tout effarée et réfléchissant tristement. La\r
+fabrique avait été fermée toute la journée, la porte cochère était\r
+verrouillée, la rue était déserte. Il n'y avait dans la maison que deux\r
+religieuses, soeur Perpétue et soeur Simplice, qui veillaient près du\r
+corps de Fantine.\r
+\r
+Vers l'heure où M. Madeleine avait coutume de rentrer, la brave portière\r
+se leva machinalement, prit la clef de la chambre de M. Madeleine dans\r
+un tiroir et le bougeoir dont il se servait tous les soirs pour monter\r
+chez lui, puis elle accrocha la clef au clou où il la prenait\r
+d'habitude, et plaça le bougeoir à côté, comme si elle l'attendait.\r
+Ensuite elle se rassit sur sa chaise et se remit à songer. La pauvre\r
+bonne vieille avait fait tout cela sans en avoir conscience.\r
+\r
+Ce ne fut qu'au bout de plus de deux heures qu'elle sortit de sa rêverie\r
+et s'écria: «Tiens! mon bon Dieu Jésus! moi qui ai mis sa clef au clou!»\r
+\r
+En ce moment la vitre de la loge s'ouvrit, une main passa par\r
+l'ouverture, saisit la clef et le bougeoir et alluma la bougie à la\r
+chandelle qui brûlait.\r
+\r
+La portière leva les yeux et resta béante, avec un cri dans le gosier\r
+qu'elle retint. Elle connaissait cette main, ce bras, cette manche de\r
+redingote.\r
+\r
+C'était M. Madeleine.\r
+\r
+Elle fut quelques secondes avant de pouvoir parler, saisie, comme elle\r
+le disait elle-même plus tard en racontant son aventure.\r
+\r
+--Mon Dieu, monsieur le maire, s'écria-t-elle enfin, je vous croyais....\r
+\r
+Elle s'arrêta, la fin de sa phrase eût manqué de respect au\r
+commencement. Jean Valjean était toujours pour elle monsieur le maire.\r
+\r
+Il acheva sa pensée.\r
+\r
+--En prison, dit-il. J'y étais. J'ai brisé un barreau d'une fenêtre, je\r
+me suis laissé tomber du haut d'un toit, et me voici. Je monte à ma\r
+chambre, allez me chercher la soeur Simplice. Elle est sans doute près\r
+de cette pauvre femme.\r
+\r
+La vieille obéit en toute hâte.\r
+\r
+Il ne lui fit aucune recommandation; il était bien sûr qu'elle le\r
+garderait mieux qu'il ne se garderait lui-même.\r
+\r
+On n'a jamais su comment il avait réussi à pénétrer dans la cour sans\r
+faire ouvrir la porte cochère. Il avait, et portait toujours sur lui, un\r
+passe-partout qui ouvrait une petite porte latérale; mais on avait dû le\r
+fouiller et lui prendre son passe-partout. Ce point n'a pas été\r
+éclairci.\r
+\r
+Il monta l'escalier qui conduisait à sa chambre. Arrivé en haut, il\r
+laissa son bougeoir sur les dernières marches de l'escalier, ouvrit sa\r
+porte avec peu de bruit, et alla fermer à tâtons sa fenêtre et son\r
+volet, puis il revint prendre sa bougie et rentra dans sa chambre.\r
+\r
+La précaution était utile; on se souvient que sa fenêtre pouvait être\r
+aperçue de la rue. Il jeta un coup d'oeil autour de lui, sur sa table,\r
+sur sa chaise, sur son lit qui n'avait pas été défait depuis trois\r
+jours. Il ne restait aucune trace du désordre de l'avant-dernière nuit.\r
+La portière avait «fait la chambre». Seulement elle avait ramassé dans\r
+les cendres et posé proprement sur la table les deux bouts du bâton\r
+ferré et la pièce de quarante sous noircie par le feu.\r
+\r
+Il prit une feuille de papier sur laquelle il écrivit: _Voici les deux\r
+bouts de mon bâton ferré et la pièce de quarante sous volée à\r
+Petit-Gervais dont j'ai parlé à la cour d'assises_, et il posa sur cette\r
+feuille la pièce d'argent et les deux morceaux de fer, de façon que ce\r
+fût la première chose qu'on aperçût en entrant dans la chambre. Il tira\r
+d'une armoire une vieille chemise à lui qu'il déchira. Cela fit quelques\r
+morceaux de toile dans lesquels il emballa les deux flambeaux d'argent.\r
+Du reste il n'avait ni hâte ni agitation, et, tout en emballant les\r
+chandeliers de l'évêque, il mordait dans un morceau de pain noir. Il est\r
+probable que c'était le pain de la prison qu'il avait emporté en\r
+s'évadant.\r
+\r
+Ceci a été constaté par les miettes de pain qui furent trouvées sur le\r
+carreau de la chambre, lorsque la justice plus tard fit une\r
+perquisition.\r
+\r
+On frappa deux petits coups à la porte.\r
+\r
+--Entrez, dit-il.\r
+\r
+C'était la soeur Simplice.\r
+\r
+Elle était pâle, elle avait les yeux rouges, la chandelle qu'elle tenait\r
+vacillait dans sa main. Les violences de la destinée ont cela de\r
+particulier que, si perfectionnés ou si refroidis que nous soyons, elles\r
+nous tirent du fond des entrailles la nature humaine et la forcent de\r
+reparaître au dehors. Dans les émotions de cette journée, la religieuse\r
+était redevenue femme. Elle avait pleuré, et elle tremblait.\r
+\r
+Jean Valjean venait d'écrire quelques lignes sur un papier qu'il tendit\r
+à la religieuse en disant:\r
+\r
+--Ma soeur, vous remettrez ceci à monsieur le curé.\r
+\r
+Le papier était déplié. Elle y jeta les yeux.\r
+\r
+--Vous pouvez lire, dit-il.\r
+\r
+Elle lut.--«Je prie monsieur le curé de veiller sur tout ce que je\r
+laisse ici. Il voudra bien payer là-dessus les frais de mon procès et\r
+l'enterrement de la femme qui est morte aujourd'hui. Le reste sera aux\r
+pauvres.»\r
+\r
+La soeur voulut parler, mais elle put à peine balbutier quelques sons\r
+inarticulés. Elle parvint cependant à dire:\r
+\r
+--Est-ce que monsieur le maire ne désire pas revoir une dernière fois\r
+cette pauvre malheureuse?\r
+\r
+--Non, dit-il, on est à ma poursuite, on n'aurait qu'à m'arrêter dans sa\r
+chambre, cela la troublerait.\r
+\r
+Il achevait à peine qu'un grand bruit se fit dans l'escalier. Ils\r
+entendirent un tumulte de pas qui montaient, et la vieille portière qui\r
+disait de sa voix la plus haute et la plus perçante:\r
+\r
+--Mon bon monsieur, je vous jure le bon Dieu qu'il n'est entré personne\r
+ici de toute la journée ni de toute la soirée, que même je n'ai pas\r
+quitté ma porte!\r
+\r
+Un homme répondit:\r
+\r
+--Cependant il y a de la lumière dans cette chambre.\r
+\r
+Ils reconnurent la voix de Javert.\r
+\r
+La chambre était disposée de façon que la porte en s'ouvrant masquait\r
+l'angle du mur à droite. Jean Valjean souffla la bougie et se mit dans\r
+cet angle.\r
+\r
+La soeur Simplice tomba à genoux près de la table.\r
+\r
+La porte s'ouvrit.\r
+\r
+Javert entra.\r
+\r
+On entendait le chuchotement de plusieurs hommes et les protestations de\r
+la portière dans le corridor.\r
+\r
+La religieuse ne leva pas les yeux. Elle priait.\r
+\r
+La chandelle était sur la cheminée et ne donnait que peu de clarté.\r
+\r
+Javert aperçut la soeur et s'arrêta interdit.\r
+\r
+On se rappelle que le fond même de Javert, son élément, son milieu\r
+respirable, c'était la vénération de toute autorité. Il était tout d'une\r
+pièce et n'admettait ni objection, ni restriction. Pour lui, bien\r
+entendu, l'autorité ecclésiastique était la première de toutes. Il était\r
+religieux, superficiel et correct sur ce point comme sur tous. À ses\r
+yeux un prêtre était un esprit qui ne se trompe pas, une religieuse\r
+était une créature qui ne pèche pas. C'étaient des âmes murées à ce\r
+monde avec une seule porte qui ne s'ouvrait jamais que pour laisser\r
+sortir la vérité.\r
+\r
+En apercevant la soeur, son premier mouvement fut de se retirer.\r
+\r
+Cependant il y avait aussi un autre devoir qui le tenait, et qui le\r
+poussait impérieusement en sens inverse. Son second mouvement fut de\r
+rester, et de hasarder au moins une question.\r
+\r
+C'était cette soeur Simplice qui n'avait menti de sa vie. Javert le\r
+savait, et la vénérait particulièrement à cause de cela.\r
+\r
+--Ma soeur, dit-il, êtes-vous seule dans cette chambre?\r
+\r
+Il y eut un moment affreux pendant lequel la pauvre portière se sentit\r
+défaillir.\r
+\r
+La soeur leva les yeux et répondit:\r
+\r
+--Oui.\r
+\r
+--Ainsi, reprit Javert, excusez-moi si j'insiste, c'est mon devoir, vous\r
+n'avez pas vu ce soir une personne, un homme. Il s'est évadé, nous le\r
+cherchons, ce nommé Jean Valjean, vous ne l'avez pas vu?\r
+\r
+La soeur répondit:\r
+\r
+--Non.\r
+\r
+Elle mentit. Elle mentit deux fois de suite, coup sur coup, sans\r
+hésiter, rapidement, comme on se dévoue.\r
+\r
+--Pardon, dit Javert, et il se retira en saluant profondément.\r
+\r
+Ô sainte fille! vous n'êtes plus de ce monde depuis beaucoup d'années;\r
+vous avez rejoint dans la lumière vos soeurs les vierges et vos frères\r
+les anges; que ce mensonge vous soit compté dans le paradis!\r
+\r
+L'affirmation de la soeur fut pour Javert quelque chose de si décisif\r
+qu'il ne remarqua même pas la singularité de cette bougie qu'on venait\r
+de souffler et qui fumait sur la table.\r
+\r
+Une heure après, un homme, marchant à travers les arbres et les brumes,\r
+s'éloignait rapidement de Montreuil-sur-mer dans la direction de Paris.\r
+Cet homme était Jean Valjean. Il a été établi, par le témoignage de deux\r
+ou trois rouliers qui l'avaient rencontré, qu'il portait un paquet et\r
+qu'il était vêtu d'une blouse. Où avait-il pris cette blouse? On ne l'a\r
+jamais su. Cependant un vieux ouvrier était mort quelques jours\r
+auparavant à l'infirmerie de la fabrique, ne laissant que sa blouse.\r
+C'était peut-être celle-là.\r
+\r
+Un dernier mot sur Fantine.\r
+\r
+Nous avons tous une mère, la terre. On rendit Fantine à cette mère.\r
+\r
+Le curé crut bien faire, et fit bien peut-être, en réservant, sur ce que\r
+Jean Valjean avait laissé, le plus d'argent possible aux pauvres. Après\r
+tout, de qui s'agissait-il? d'un forçat et d'une fille publique. C'est\r
+pourquoi il simplifia l'enterrement de Fantine, et le réduisit à ce\r
+strict nécessaire qu'on appelle la fosse commune.\r
+\r
+Fantine fut donc enterrée dans ce coin gratis du cimetière qui est à\r
+tous et à personne, et où l'on perd les pauvres. Heureusement Dieu sait\r
+où retrouver l'âme. On coucha Fantine dans les ténèbres parmi les\r
+premiers os venus; elle subit la promiscuité des cendres. Elle fut jetée\r
+à la fosse publique. Sa tombe ressembla à son lit.\r
+\r
+\r
+\r
+\r
+\r
+End of the Project Gutenberg EBook of Les misérables Tome I, by Victor Hugo\r
+\r
+*** END OF THIS PROJECT GUTENBERG EBOOK LES MISÉRABLES TOME I ***\r
+\r
+***** This file should be named 17489-8.txt or 17489-8.zip *****\r
+This and all associated files of various formats will be found in:\r
+        http://www.gutenberg.org/1/7/4/8/17489/\r
+\r
+Produced by www.ebooksgratuits.com and Chuck Greif\r
+\r
+Updated editions will replace the previous one--the old editions\r
+will be renamed.\r
+\r
+Creating the works from public domain print editions means that no\r
+one owns a United States copyright in these works, so the Foundation\r
+(and you!) can copy and distribute it in the United States without\r
+permission and without paying copyright royalties.  Special rules,\r
+set forth in the General Terms of Use part of this license, apply to\r
+copying and distributing Project Gutenberg-tm electronic works to\r
+protect the PROJECT GUTENBERG-tm concept and trademark.  Project\r
+Gutenberg is a registered trademark, and may not be used if you\r
+charge for the eBooks, unless you receive specific permission.  If you\r
+do not charge anything for copies of this eBook, complying with the\r
+rules is very easy.  You may use this eBook for nearly any purpose\r
+such as creation of derivative works, reports, performances and\r
+research.  They may be modified and printed and given away--you may do\r
+practically ANYTHING with public domain eBooks.  Redistribution is\r
+subject to the trademark license, especially commercial\r
+redistribution.\r
+\r
+\r
+\r
+*** START: FULL LICENSE ***\r
+\r
+THE FULL PROJECT GUTENBERG LICENSE\r
+PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\r
+\r
+To protect the Project Gutenberg-tm mission of promoting the free\r
+distribution of electronic works, by using or distributing this work\r
+(or any other work associated in any way with the phrase "Project\r
+Gutenberg"), you agree to comply with all the terms of the Full Project\r
+Gutenberg-tm License (available with this file or online at\r
+http://gutenberg.org/license).\r
+\r
+\r
+Section 1.  General Terms of Use and Redistributing Project Gutenberg-tm\r
+electronic works\r
+\r
+1.A.  By reading or using any part of this Project Gutenberg-tm\r
+electronic work, you indicate that you have read, understand, agree to\r
+and accept all the terms of this license and intellectual property\r
+(trademark/copyright) agreement.  If you do not agree to abide by all\r
+the terms of this agreement, you must cease using and return or destroy\r
+all copies of Project Gutenberg-tm electronic works in your possession.\r
+If you paid a fee for obtaining a copy of or access to a Project\r
+Gutenberg-tm electronic work and you do not agree to be bound by the\r
+terms of this agreement, you may obtain a refund from the person or\r
+entity to whom you paid the fee as set forth in paragraph 1.E.8.\r
+\r
+1.B.  "Project Gutenberg" is a registered trademark.  It may only be\r
+used on or associated in any way with an electronic work by people who\r
+agree to be bound by the terms of this agreement.  There are a few\r
+things that you can do with most Project Gutenberg-tm electronic works\r
+even without complying with the full terms of this agreement.  See\r
+paragraph 1.C below.  There are a lot of things you can do with Project\r
+Gutenberg-tm electronic works if you follow the terms of this agreement\r
+and help preserve free future access to Project Gutenberg-tm electronic\r
+works.  See paragraph 1.E below.\r
+\r
+1.C.  The Project Gutenberg Literary Archive Foundation ("the Foundation"\r
+or PGLAF), owns a compilation copyright in the collection of Project\r
+Gutenberg-tm electronic works.  Nearly all the individual works in the\r
+collection are in the public domain in the United States.  If an\r
+individual work is in the public domain in the United States and you are\r
+located in the United States, we do not claim a right to prevent you from\r
+copying, distributing, performing, displaying or creating derivative\r
+works based on the work as long as all references to Project Gutenberg\r
+are removed.  Of course, we hope that you will support the Project\r
+Gutenberg-tm mission of promoting free access to electronic works by\r
+freely sharing Project Gutenberg-tm works in compliance with the terms of\r
+this agreement for keeping the Project Gutenberg-tm name associated with\r
+the work.  You can easily comply with the terms of this agreement by\r
+keeping this work in the same format with its attached full Project\r
+Gutenberg-tm License when you share it without charge with others.\r
+\r
+1.D.  The copyright laws of the place where you are located also govern\r
+what you can do with this work.  Copyright laws in most countries are in\r
+a constant state of change.  If you are outside the United States, check\r
+the laws of your country in addition to the terms of this agreement\r
+before downloading, copying, displaying, performing, distributing or\r
+creating derivative works based on this work or any other Project\r
+Gutenberg-tm work.  The Foundation makes no representations concerning\r
+the copyright status of any work in any country outside the United\r
+States.\r
+\r
+1.E.  Unless you have removed all references to Project Gutenberg:\r
+\r
+1.E.1.  The following sentence, with active links to, or other immediate\r
+access to, the full Project Gutenberg-tm License must appear prominently\r
+whenever any copy of a Project Gutenberg-tm work (any work on which the\r
+phrase "Project Gutenberg" appears, or with which the phrase "Project\r
+Gutenberg" is associated) is accessed, displayed, performed, viewed,\r
+copied or distributed:\r
+\r
+This eBook is for the use of anyone anywhere at no cost and with\r
+almost no restrictions whatsoever.  You may copy it, give it away or\r
+re-use it under the terms of the Project Gutenberg License included\r
+with this eBook or online at www.gutenberg.org\r
+\r
+1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\r
+from the public domain (does not contain a notice indicating that it is\r
+posted with permission of the copyright holder), the work can be copied\r
+and distributed to anyone in the United States without paying any fees\r
+or charges.  If you are redistributing or providing access to a work\r
+with the phrase "Project Gutenberg" associated with or appearing on the\r
+work, you must comply either with the requirements of paragraphs 1.E.1\r
+through 1.E.7 or obtain permission for the use of the work and the\r
+Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\r
+1.E.9.\r
+\r
+1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\r
+with the permission of the copyright holder, your use and distribution\r
+must comply with both paragraphs 1.E.1 through 1.E.7 and any additional\r
+terms imposed by the copyright holder.  Additional terms will be linked\r
+to the Project Gutenberg-tm License for all works posted with the\r
+permission of the copyright holder found at the beginning of this work.\r
+\r
+1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\r
+License terms from this work, or any files containing a part of this\r
+work or any other work associated with Project Gutenberg-tm.\r
+\r
+1.E.5.  Do not copy, display, perform, distribute or redistribute this\r
+electronic work, or any part of this electronic work, without\r
+prominently displaying the sentence set forth in paragraph 1.E.1 with\r
+active links or immediate access to the full terms of the Project\r
+Gutenberg-tm License.\r
+\r
+1.E.6.  You may convert to and distribute this work in any binary,\r
+compressed, marked up, nonproprietary or proprietary form, including any\r
+word processing or hypertext form.  However, if you provide access to or\r
+distribute copies of a Project Gutenberg-tm work in a format other than\r
+"Plain Vanilla ASCII" or other format used in the official version\r
+posted on the official Project Gutenberg-tm web site (www.gutenberg.org),\r
+you must, at no additional cost, fee or expense to the user, provide a\r
+copy, a means of exporting a copy, or a means of obtaining a copy upon\r
+request, of the work in its original "Plain Vanilla ASCII" or other\r
+form.  Any alternate format must include the full Project Gutenberg-tm\r
+License as specified in paragraph 1.E.1.\r
+\r
+1.E.7.  Do not charge a fee for access to, viewing, displaying,\r
+performing, copying or distributing any Project Gutenberg-tm works\r
+unless you comply with paragraph 1.E.8 or 1.E.9.\r
+\r
+1.E.8.  You may charge a reasonable fee for copies of or providing\r
+access to or distributing Project Gutenberg-tm electronic works provided\r
+that\r
+\r
+- You pay a royalty fee of 20% of the gross profits you derive from\r
+     the use of Project Gutenberg-tm works calculated using the method\r
+     you already use to calculate your applicable taxes.  The fee is\r
+     owed to the owner of the Project Gutenberg-tm trademark, but he\r
+     has agreed to donate royalties under this paragraph to the\r
+     Project Gutenberg Literary Archive Foundation.  Royalty payments\r
+     must be paid within 60 days following each date on which you\r
+     prepare (or are legally required to prepare) your periodic tax\r
+     returns.  Royalty payments should be clearly marked as such and\r
+     sent to the Project Gutenberg Literary Archive Foundation at the\r
+     address specified in Section 4, "Information about donations to\r
+     the Project Gutenberg Literary Archive Foundation."\r
+\r
+- You provide a full refund of any money paid by a user who notifies\r
+     you in writing (or by e-mail) within 30 days of receipt that s/he\r
+     does not agree to the terms of the full Project Gutenberg-tm\r
+     License.  You must require such a user to return or\r
+     destroy all copies of the works possessed in a physical medium\r
+     and discontinue all use of and all access to other copies of\r
+     Project Gutenberg-tm works.\r
+\r
+- You provide, in accordance with paragraph 1.F.3, a full refund of any\r
+     money paid for a work or a replacement copy, if a defect in the\r
+     electronic work is discovered and reported to you within 90 days\r
+     of receipt of the work.\r
+\r
+- You comply with all other terms of this agreement for free\r
+     distribution of Project Gutenberg-tm works.\r
+\r
+1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\r
+electronic work or group of works on different terms than are set\r
+forth in this agreement, you must obtain permission in writing from\r
+both the Project Gutenberg Literary Archive Foundation and Michael\r
+Hart, the owner of the Project Gutenberg-tm trademark.  Contact the\r
+Foundation as set forth in Section 3 below.\r
+\r
+1.F.\r
+\r
+1.F.1.  Project Gutenberg volunteers and employees expend considerable\r
+effort to identify, do copyright research on, transcribe and proofread\r
+public domain works in creating the Project Gutenberg-tm\r
+collection.  Despite these efforts, Project Gutenberg-tm electronic\r
+works, and the medium on which they may be stored, may contain\r
+"Defects," such as, but not limited to, incomplete, inaccurate or\r
+corrupt data, transcription errors, a copyright or other intellectual\r
+property infringement, a defective or damaged disk or other medium, a\r
+computer virus, or computer codes that damage or cannot be read by\r
+your equipment.\r
+\r
+1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right\r
+of Replacement or Refund" described in paragraph 1.F.3, the Project\r
+Gutenberg Literary Archive Foundation, the owner of the Project\r
+Gutenberg-tm trademark, and any other party distributing a Project\r
+Gutenberg-tm electronic work under this agreement, disclaim all\r
+liability to you for damages, costs and expenses, including legal\r
+fees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\r
+LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\r
+PROVIDED IN PARAGRAPH F3.  YOU AGREE THAT THE FOUNDATION, THE\r
+TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\r
+LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\r
+INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\r
+DAMAGE.\r
+\r
+1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\r
+defect in this electronic work within 90 days of receiving it, you can\r
+receive a refund of the money (if any) you paid for it by sending a\r
+written explanation to the person you received the work from.  If you\r
+received the work on a physical medium, you must return the medium with\r
+your written explanation.  The person or entity that provided you with\r
+the defective work may elect to provide a replacement copy in lieu of a\r
+refund.  If you received the work electronically, the person or entity\r
+providing it to you may choose to give you a second opportunity to\r
+receive the work electronically in lieu of a refund.  If the second copy\r
+is also defective, you may demand a refund in writing without further\r
+opportunities to fix the problem.\r
+\r
+1.F.4.  Except for the limited right of replacement or refund set forth\r
+in paragraph 1.F.3, this work is provided to you 'AS-IS', WITH NO OTHER\r
+WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\r
+WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\r
+\r
+1.F.5.  Some states do not allow disclaimers of certain implied\r
+warranties or the exclusion or limitation of certain types of damages.\r
+If any disclaimer or limitation set forth in this agreement violates the\r
+law of the state applicable to this agreement, the agreement shall be\r
+interpreted to make the maximum disclaimer or limitation permitted by\r
+the applicable state law.  The invalidity or unenforceability of any\r
+provision of this agreement shall not void the remaining provisions.\r
+\r
+1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\r
+trademark owner, any agent or employee of the Foundation, anyone\r
+providing copies of Project Gutenberg-tm electronic works in accordance\r
+with this agreement, and any volunteers associated with the production,\r
+promotion and distribution of Project Gutenberg-tm electronic works,\r
+harmless from all liability, costs and expenses, including legal fees,\r
+that arise directly or indirectly from any of the following which you do\r
+or cause to occur: (a) distribution of this or any Project Gutenberg-tm\r
+work, (b) alteration, modification, or additions or deletions to any\r
+Project Gutenberg-tm work, and (c) any Defect you cause.\r
+\r
+\r
+Section  2.  Information about the Mission of Project Gutenberg-tm\r
+\r
+Project Gutenberg-tm is synonymous with the free distribution of\r
+electronic works in formats readable by the widest variety of computers\r
+including obsolete, old, middle-aged and new computers.  It exists\r
+because of the efforts of hundreds of volunteers and donations from\r
+people in all walks of life.\r
+\r
+Volunteers and financial support to provide volunteers with the\r
+assistance they need, is critical to reaching Project Gutenberg-tm's\r
+goals and ensuring that the Project Gutenberg-tm collection will\r
+remain freely available for generations to come.  In 2001, the Project\r
+Gutenberg Literary Archive Foundation was created to provide a secure\r
+and permanent future for Project Gutenberg-tm and future generations.\r
+To learn more about the Project Gutenberg Literary Archive Foundation\r
+and how your efforts and donations can help, see Sections 3 and 4\r
+and the Foundation web page at http://www.pglaf.org.\r
+\r
+\r
+Section 3.  Information about the Project Gutenberg Literary Archive\r
+Foundation\r
+\r
+The Project Gutenberg Literary Archive Foundation is a non profit\r
+501(c)(3) educational corporation organized under the laws of the\r
+state of Mississippi and granted tax exempt status by the Internal\r
+Revenue Service.  The Foundation's EIN or federal tax identification\r
+number is 64-6221541.  Its 501(c)(3) letter is posted at\r
+http://pglaf.org/fundraising.  Contributions to the Project Gutenberg\r
+Literary Archive Foundation are tax deductible to the full extent\r
+permitted by U.S. federal laws and your state's laws.\r
+\r
+The Foundation's principal office is located at 4557 Melan Dr. S.\r
+Fairbanks, AK, 99712., but its volunteers and employees are scattered\r
+throughout numerous locations.  Its business office is located at\r
+809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\r
+business@pglaf.org.  Email contact links and up to date contact\r
+information can be found at the Foundation's web site and official\r
+page at http://pglaf.org\r
+\r
+For additional contact information:\r
+     Dr. Gregory B. Newby\r
+     Chief Executive and Director\r
+     gbnewby@pglaf.org\r
+\r
+Section 4.  Information about Donations to the Project Gutenberg\r
+Literary Archive Foundation\r
+\r
+Project Gutenberg-tm depends upon and cannot survive without wide\r
+spread public support and donations to carry out its mission of\r
+increasing the number of public domain and licensed works that can be\r
+freely distributed in machine readable form accessible by the widest\r
+array of equipment including outdated equipment.  Many small donations\r
+($1 to $5,000) are particularly important to maintaining tax exempt\r
+status with the IRS.\r
+\r
+The Foundation is committed to complying with the laws regulating\r
+charities and charitable donations in all 50 states of the United\r
+States.  Compliance requirements are not uniform and it takes a\r
+considerable effort, much paperwork and many fees to meet and keep up\r
+with these requirements.  We do not solicit donations in locations\r
+where we have not received written confirmation of compliance.  To\r
+SEND DONATIONS or determine the status of compliance for any\r
+particular state visit http://pglaf.org\r
+\r
+While we cannot and do not solicit contributions from states where we\r
+have not met the solicitation requirements, we know of no prohibition\r
+against accepting unsolicited donations from donors in such states who\r
+approach us with offers to donate.\r
+\r
+International donations are gratefully accepted, but we cannot make\r
+any statements concerning tax treatment of donations received from\r
+outside the United States.  U.S. laws alone swamp our small staff.\r
+\r
+Please check the Project Gutenberg Web pages for current donation\r
+methods and addresses.  Donations are accepted in a number of other\r
+ways including checks, online payments and credit card\r
+donations.  To donate, please visit: http://pglaf.org/donate\r
+\r
+\r
+Section 5.  General Information About Project Gutenberg-tm electronic\r
+works.\r
+\r
+Professor Michael S. Hart is the originator of the Project Gutenberg-tm\r
+concept of a library of electronic works that could be freely shared\r
+with anyone.  For thirty years, he produced and distributed Project\r
+Gutenberg-tm eBooks with only a loose network of volunteer support.\r
+\r
+Project Gutenberg-tm eBooks are often created from several printed\r
+editions, all of which are confirmed as Public Domain in the U.S.\r
+unless a copyright notice is included.  Thus, we do not necessarily\r
+keep eBooks in compliance with any particular paper edition.\r
+\r
+Most people start at our Web site which has the main PG search facility:\r
+\r
+     http://www.gutenberg.org\r
+\r
+This Web site includes information about Project Gutenberg-tm,\r
+including how to make donations to the Project Gutenberg Literary\r
+Archive Foundation, how to help produce our new eBooks, and how to\r
+subscribe to our email newsletter to hear about new eBooks.\r
+\r
+*** END: FULL LICENSE ***\r
diff --git a/minimal-examples/api-tests/api-test-fts/main.c b/minimal-examples/api-tests/api-test-fts/main.c
new file mode 100644 (file)
index 0000000..5003f8c
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * lws-api-test-fts - lws full-text search api test
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
+#include <getopt.h>
+#endif
+#include <fcntl.h>
+
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
+static struct option options[] = {
+       { "help",       no_argument,            NULL, 'h' },
+       { "createindex", no_argument,           NULL, 'c' },
+       { "index",      required_argument,      NULL, 'i' },
+       { "debug",      required_argument,      NULL, 'd' },
+       { "file",       required_argument,      NULL, 'f' },
+       { "lines",      required_argument,      NULL, 'l' },
+       { NULL, 0, 0, 0 }
+};
+#endif
+
+static const char *index_filepath = "/tmp/lws-fts-test-index";
+static char filepath[256];
+
+int main(int argc, char **argv)
+{
+       int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       int fd, fi, ft, createindex = 0, flags = LWSFTS_F_QUERY_AUTOCOMPLETE;
+       struct lws_fts_search_params params;
+       struct lws_fts_result *result;
+       struct lws_fts_file *jtf;
+       struct lws_fts *t;
+       char buf[16384];
+
+       do {
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
+               n = getopt_long(argc, argv, "hd:i:cfl", options, NULL);
+#else
+       n = getopt(argc, argv, "hd:i:cfl");
+#endif
+               if (n < 0)
+                       continue;
+               switch (n) {
+               case 'i':
+                       strncpy(filepath, optarg, sizeof(filepath) - 1);
+                       filepath[sizeof(filepath) - 1] = '\0';
+                       index_filepath = filepath;
+                       break;
+               case 'd':
+                       logs = atoi(optarg);
+                       break;
+               case 'c':
+                       createindex = 1;
+                       break;
+               case 'f':
+                       flags &= ~LWSFTS_F_QUERY_AUTOCOMPLETE;
+                       flags |= LWSFTS_F_QUERY_FILES;
+                       break;
+               case 'l':
+                       flags |= LWSFTS_F_QUERY_FILES |
+                                LWSFTS_F_QUERY_FILE_LINES;
+                       break;
+               case 'h':
+                       fprintf(stderr,
+                               "Usage: %s [--createindex]"
+                                       "[--index=<index filepath>] "
+                                       "[-d <log bitfield>] file1 file2 \n",
+                                       argv[0]);
+                       exit(1);
+               }
+       } while (n >= 0);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: full-text search\n");
+
+       if (createindex) {
+
+               lwsl_notice("Creating index\n");
+
+               /*
+                * create an index by shifting through argv and indexing each
+                * file given there into a single combined index
+                */
+
+               ft = open(index_filepath, O_CREAT | O_WRONLY | O_TRUNC, 0600);
+               if (ft < 0) {
+                       lwsl_err("%s: can't open index %s\n", __func__,
+                                index_filepath);
+
+                       goto bail;
+               }
+
+               t = lws_fts_create(ft);
+               if (!t) {
+                       lwsl_err("%s: Unable to allocate trie\n", __func__);
+
+                       goto bail1;
+               }
+
+               while (optind < argc) {
+
+                       fi = lws_fts_file_index(t, argv[optind],
+                                               strlen(argv[optind]), 1);
+                       if (fi < 0) {
+                               lwsl_err("%s: Failed to get file idx for %s\n",
+                                        __func__, argv[optind]);
+
+                               goto bail1;
+                       }
+
+                       fd = open(argv[optind], O_RDONLY);
+                       if (fd < 0) {
+                               lwsl_err("unable to open %s for read\n",
+                                               argv[optind]);
+                               goto bail;
+                       }
+
+                       do {
+                               int n = read(fd, buf, sizeof(buf));
+
+                               if (n <= 0)
+                                       break;
+
+                               if (lws_fts_fill(t, fi, buf, n)) {
+                                       lwsl_err("%s: lws_fts_fill failed\n",
+                                                __func__);
+                                       close(fd);
+
+                                       goto bail;
+                               }
+
+                       } while (1);
+
+                       close(fd);
+                       optind++;
+               }
+
+               if (lws_fts_serialize(t)) {
+                       lwsl_err("%s: serialize failed\n", __func__);
+
+                       goto bail;
+               }
+
+               lws_fts_destroy(&t);
+               close(ft);
+
+               return 0;
+       }
+
+       /*
+        * shift through argv searching for each token
+        */
+
+       jtf = lws_fts_open(index_filepath);
+       if (!jtf)
+               goto bail;
+
+       while (optind < argc) {
+
+               struct lws_fts_result_autocomplete *ac;
+               struct lws_fts_result_filepath *fp;
+               uint32_t *l, n;
+
+               memset(&params, 0, sizeof(params));
+
+               params.needle = argv[optind];
+               params.flags = flags;
+               params.max_autocomplete = 20;
+               params.max_files = 20;
+
+               result = lws_fts_search(jtf, &params);
+
+               if (!result) {
+                       lwsl_err("%s: search failed\n", __func__);
+                       lws_fts_close(jtf);
+                       goto bail;
+               }
+
+               ac = result->autocomplete_head;
+               fp = result->filepath_head;
+
+               if (!ac)
+                       lwsl_notice("%s: no autocomplete results\n", __func__);
+
+               while (ac) {
+                       lwsl_notice("%s: AC %s: %d agg hits\n", __func__,
+                               ((char *)(ac + 1)), ac->instances);
+
+                       ac = ac->next;
+               }
+
+               if (!fp)
+                       lwsl_notice("%s: no filepath results\n", __func__);
+
+               while (fp) {
+                       lwsl_notice("%s: %s: (%d lines) %d hits \n", __func__,
+                               (((char *)(fp + 1)) + fp->matches_length),
+                               fp->lines_in_file, fp->matches);
+
+                       if (fp->matches_length) {
+                               l = (uint32_t *)(fp + 1);
+                               n = 0;
+                               while ((int)n++ < fp->matches)
+                                       lwsl_notice(" %d\n", *l++);
+                       }
+                       fp = fp->next;
+               }
+
+               lwsac_free(&params.results_head);
+
+               optind++;
+       }
+
+       lws_fts_close(jtf);
+
+       return 0;
+
+bail1:
+       close(ft);
+bail:
+       lwsl_user("FAILED\n");
+
+       return 1;
+}
diff --git a/minimal-examples/api-tests/api-test-fts/selftest.sh b/minimal-examples/api-tests/api-test-fts/selftest.sh
new file mode 100755 (executable)
index 0000000..03e7d49
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=4
+
+FAILS=0
+
+#
+# let's make an index with just Dorian first
+#
+dotest $1 $2 apitest -c -i /tmp/lws-fts-dorian.index \
+    "../minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt"
+
+# and let's hear about autocompletes for "b"
+
+dotest $1 $2 apitest -i /tmp/lws-fts-dorian.index b
+cat $2/api-test-fts/apitest.log | cut -d' ' -f5- > /tmp/fts1
+diff -urN /tmp/fts1 "../minimal-examples/api-tests/api-test-fts/canned-1.txt"
+if [ $? -ne 0 ] ; then
+       echo "Test 1 failed"
+       FAILS=$(( $FAILS + 1 ))
+fi
+
+#
+# let's make an index with Dorian + Les Mis in French (ie, UTF-8) as well
+#
+dotest $1 $2 apitest -c -i /tmp/lws-fts-both.index \
+   "../minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt" \
+   "../minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt"
+
+# and let's hear about "help", which appears in both
+
+dotest $1 $2 apitest -i /tmp/lws-fts-both.index -f -l help
+cat $2/api-test-fts/apitest.log | cut -d' ' -f5- > /tmp/fts2
+diff -urN /tmp/fts2 "../minimal-examples/api-tests/api-test-fts/canned-2.txt"
+if [ $? -ne 0 ] ; then
+       echo "Test 1 failed"
+       FAILS=$(( $FAILS + 1 ))
+fi
+
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt b/minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt
new file mode 100644 (file)
index 0000000..f4ffc49
--- /dev/null
@@ -0,0 +1,8904 @@
+The Project Gutenberg EBook of The Picture of Dorian Gray, by Oscar Wilde\r
+\r
+This eBook is for the use of anyone anywhere at no cost and with\r
+almost no restrictions whatsoever.  You may copy it, give it away or\r
+re-use it under the terms of the Project Gutenberg License included\r
+with this eBook or online at www.gutenberg.net\r
+\r
+\r
+Title: The Picture of Dorian Gray\r
+\r
+Author: Oscar Wilde\r
+\r
+Release Date: June 9, 2008 [EBook #174]\r
+[This file last updated on July 2, 2011]\r
+[This file last updated on July 23, 2014]\r
+\r
+\r
+Language: English\r
+\r
+\r
+*** START OF THIS PROJECT GUTENBERG EBOOK THE PICTURE OF DORIAN GRAY ***\r
+\r
+\r
+\r
+\r
+Produced by Judith Boss.  HTML version by Al Haines.\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+The Picture of Dorian Gray\r
+\r
+by\r
+\r
+Oscar Wilde\r
+\r
+\r
+\r
+\r
+THE PREFACE\r
+\r
+The artist is the creator of beautiful things.  To reveal art and\r
+conceal the artist is art's aim.  The critic is he who can translate\r
+into another manner or a new material his impression of beautiful\r
+things.\r
+\r
+The highest as the lowest form of criticism is a mode of autobiography.\r
+Those who find ugly meanings in beautiful things are corrupt without\r
+being charming.  This is a fault.\r
+\r
+Those who find beautiful meanings in beautiful things are the\r
+cultivated.  For these there is hope.  They are the elect to whom\r
+beautiful things mean only beauty.\r
+\r
+There is no such thing as a moral or an immoral book.  Books are well\r
+written, or badly written.  That is all.\r
+\r
+The nineteenth century dislike of realism is the rage of Caliban seeing\r
+his own face in a glass.\r
+\r
+The nineteenth century dislike of romanticism is the rage of Caliban\r
+not seeing his own face in a glass.  The moral life of man forms part\r
+of the subject-matter of the artist, but the morality of art consists\r
+in the perfect use of an imperfect medium.  No artist desires to prove\r
+anything.  Even things that are true can be proved.  No artist has\r
+ethical sympathies.  An ethical sympathy in an artist is an\r
+unpardonable mannerism of style.  No artist is ever morbid.  The artist\r
+can express everything.  Thought and language are to the artist\r
+instruments of an art.  Vice and virtue are to the artist materials for\r
+an art.  From the point of view of form, the type of all the arts is\r
+the art of the musician.  From the point of view of feeling, the\r
+actor's craft is the type.  All art is at once surface and symbol.\r
+Those who go beneath the surface do so at their peril.  Those who read\r
+the symbol do so at their peril.  It is the spectator, and not life,\r
+that art really mirrors.  Diversity of opinion about a work of art\r
+shows that the work is new, complex, and vital.  When critics disagree,\r
+the artist is in accord with himself.  We can forgive a man for making\r
+a useful thing as long as he does not admire it.  The only excuse for\r
+making a useless thing is that one admires it intensely.\r
+\r
+               All art is quite useless.\r
+\r
+                            OSCAR WILDE\r
+\r
+\r
+\r
+\r
+CHAPTER 1\r
+\r
+The studio was filled with the rich odour of roses, and when the light\r
+summer wind stirred amidst the trees of the garden, there came through\r
+the open door the heavy scent of the lilac, or the more delicate\r
+perfume of the pink-flowering thorn.\r
+\r
+From the corner of the divan of Persian saddle-bags on which he was\r
+lying, smoking, as was his custom, innumerable cigarettes, Lord Henry\r
+Wotton could just catch the gleam of the honey-sweet and honey-coloured\r
+blossoms of a laburnum, whose tremulous branches seemed hardly able to\r
+bear the burden of a beauty so flamelike as theirs; and now and then\r
+the fantastic shadows of birds in flight flitted across the long\r
+tussore-silk curtains that were stretched in front of the huge window,\r
+producing a kind of momentary Japanese effect, and making him think of\r
+those pallid, jade-faced painters of Tokyo who, through the medium of\r
+an art that is necessarily immobile, seek to convey the sense of\r
+swiftness and motion.  The sullen murmur of the bees shouldering their\r
+way through the long unmown grass, or circling with monotonous\r
+insistence round the dusty gilt horns of the straggling woodbine,\r
+seemed to make the stillness more oppressive.  The dim roar of London\r
+was like the bourdon note of a distant organ.\r
+\r
+In the centre of the room, clamped to an upright easel, stood the\r
+full-length portrait of a young man of extraordinary personal beauty,\r
+and in front of it, some little distance away, was sitting the artist\r
+himself, Basil Hallward, whose sudden disappearance some years ago\r
+caused, at the time, such public excitement and gave rise to so many\r
+strange conjectures.\r
+\r
+As the painter looked at the gracious and comely form he had so\r
+skilfully mirrored in his art, a smile of pleasure passed across his\r
+face, and seemed about to linger there.  But he suddenly started up,\r
+and closing his eyes, placed his fingers upon the lids, as though he\r
+sought to imprison within his brain some curious dream from which he\r
+feared he might awake.\r
+\r
+"It is your best work, Basil, the best thing you have ever done," said\r
+Lord Henry languidly.  "You must certainly send it next year to the\r
+Grosvenor.  The Academy is too large and too vulgar.  Whenever I have\r
+gone there, there have been either so many people that I have not been\r
+able to see the pictures, which was dreadful, or so many pictures that\r
+I have not been able to see the people, which was worse.  The Grosvenor\r
+is really the only place."\r
+\r
+"I don't think I shall send it anywhere," he answered, tossing his head\r
+back in that odd way that used to make his friends laugh at him at\r
+Oxford.  "No, I won't send it anywhere."\r
+\r
+Lord Henry elevated his eyebrows and looked at him in amazement through\r
+the thin blue wreaths of smoke that curled up in such fanciful whorls\r
+from his heavy, opium-tainted cigarette.  "Not send it anywhere?  My\r
+dear fellow, why?  Have you any reason?  What odd chaps you painters\r
+are!  You do anything in the world to gain a reputation.  As soon as\r
+you have one, you seem to want to throw it away.  It is silly of you,\r
+for there is only one thing in the world worse than being talked about,\r
+and that is not being talked about.  A portrait like this would set you\r
+far above all the young men in England, and make the old men quite\r
+jealous, if old men are ever capable of any emotion."\r
+\r
+"I know you will laugh at me," he replied, "but I really can't exhibit\r
+it.  I have put too much of myself into it."\r
+\r
+Lord Henry stretched himself out on the divan and laughed.\r
+\r
+"Yes, I knew you would; but it is quite true, all the same."\r
+\r
+"Too much of yourself in it! Upon my word, Basil, I didn't know you\r
+were so vain; and I really can't see any resemblance between you, with\r
+your rugged strong face and your coal-black hair, and this young\r
+Adonis, who looks as if he was made out of ivory and rose-leaves. Why,\r
+my dear Basil, he is a Narcissus, and you--well, of course you have an\r
+intellectual expression and all that.  But beauty, real beauty, ends\r
+where an intellectual expression begins.  Intellect is in itself a mode\r
+of exaggeration, and destroys the harmony of any face.  The moment one\r
+sits down to think, one becomes all nose, or all forehead, or something\r
+horrid.  Look at the successful men in any of the learned professions.\r
+How perfectly hideous they are!  Except, of course, in the Church.  But\r
+then in the Church they don't think.  A bishop keeps on saying at the\r
+age of eighty what he was told to say when he was a boy of eighteen,\r
+and as a natural consequence he always looks absolutely delightful.\r
+Your mysterious young friend, whose name you have never told me, but\r
+whose picture really fascinates me, never thinks.  I feel quite sure of\r
+that.  He is some brainless beautiful creature who should be always\r
+here in winter when we have no flowers to look at, and always here in\r
+summer when we want something to chill our intelligence.  Don't flatter\r
+yourself, Basil:  you are not in the least like him."\r
+\r
+"You don't understand me, Harry," answered the artist.  "Of course I am\r
+not like him.  I know that perfectly well.  Indeed, I should be sorry\r
+to look like him.  You shrug your shoulders?  I am telling you the\r
+truth.  There is a fatality about all physical and intellectual\r
+distinction, the sort of fatality that seems to dog through history the\r
+faltering steps of kings.  It is better not to be different from one's\r
+fellows.  The ugly and the stupid have the best of it in this world.\r
+They can sit at their ease and gape at the play.  If they know nothing\r
+of victory, they are at least spared the knowledge of defeat.  They\r
+live as we all should live--undisturbed, indifferent, and without\r
+disquiet.  They neither bring ruin upon others, nor ever receive it\r
+from alien hands.  Your rank and wealth, Harry; my brains, such as they\r
+are--my art, whatever it may be worth; Dorian Gray's good looks--we\r
+shall all suffer for what the gods have given us, suffer terribly."\r
+\r
+"Dorian Gray?  Is that his name?" asked Lord Henry, walking across the\r
+studio towards Basil Hallward.\r
+\r
+"Yes, that is his name.  I didn't intend to tell it to you."\r
+\r
+"But why not?"\r
+\r
+"Oh, I can't explain.  When I like people immensely, I never tell their\r
+names to any one.  It is like surrendering a part of them.  I have\r
+grown to love secrecy.  It seems to be the one thing that can make\r
+modern life mysterious or marvellous to us.  The commonest thing is\r
+delightful if one only hides it.  When I leave town now I never tell my\r
+people where I am going.  If I did, I would lose all my pleasure.  It\r
+is a silly habit, I dare say, but somehow it seems to bring a great\r
+deal of romance into one's life.  I suppose you think me awfully\r
+foolish about it?"\r
+\r
+"Not at all," answered Lord Henry, "not at all, my dear Basil.  You\r
+seem to forget that I am married, and the one charm of marriage is that\r
+it makes a life of deception absolutely necessary for both parties.  I\r
+never know where my wife is, and my wife never knows what I am doing.\r
+When we meet--we do meet occasionally, when we dine out together, or go\r
+down to the Duke's--we tell each other the most absurd stories with the\r
+most serious faces.  My wife is very good at it--much better, in fact,\r
+than I am.  She never gets confused over her dates, and I always do.\r
+But when she does find me out, she makes no row at all.  I sometimes\r
+wish she would; but she merely laughs at me."\r
+\r
+"I hate the way you talk about your married life, Harry," said Basil\r
+Hallward, strolling towards the door that led into the garden.  "I\r
+believe that you are really a very good husband, but that you are\r
+thoroughly ashamed of your own virtues.  You are an extraordinary\r
+fellow.  You never say a moral thing, and you never do a wrong thing.\r
+Your cynicism is simply a pose."\r
+\r
+"Being natural is simply a pose, and the most irritating pose I know,"\r
+cried Lord Henry, laughing; and the two young men went out into the\r
+garden together and ensconced themselves on a long bamboo seat that\r
+stood in the shade of a tall laurel bush.  The sunlight slipped over\r
+the polished leaves.  In the grass, white daisies were tremulous.\r
+\r
+After a pause, Lord Henry pulled out his watch.  "I am afraid I must be\r
+going, Basil," he murmured, "and before I go, I insist on your\r
+answering a question I put to you some time ago."\r
+\r
+"What is that?" said the painter, keeping his eyes fixed on the ground.\r
+\r
+"You know quite well."\r
+\r
+"I do not, Harry."\r
+\r
+"Well, I will tell you what it is.  I want you to explain to me why you\r
+won't exhibit Dorian Gray's picture.  I want the real reason."\r
+\r
+"I told you the real reason."\r
+\r
+"No, you did not.  You said it was because there was too much of\r
+yourself in it.  Now, that is childish."\r
+\r
+"Harry," said Basil Hallward, looking him straight in the face, "every\r
+portrait that is painted with feeling is a portrait of the artist, not\r
+of the sitter.  The sitter is merely the accident, the occasion.  It is\r
+not he who is revealed by the painter; it is rather the painter who, on\r
+the coloured canvas, reveals himself.  The reason I will not exhibit\r
+this picture is that I am afraid that I have shown in it the secret of\r
+my own soul."\r
+\r
+Lord Henry laughed.  "And what is that?" he asked.\r
+\r
+"I will tell you," said Hallward; but an expression of perplexity came\r
+over his face.\r
+\r
+"I am all expectation, Basil," continued his companion, glancing at him.\r
+\r
+"Oh, there is really very little to tell, Harry," answered the painter;\r
+"and I am afraid you will hardly understand it.  Perhaps you will\r
+hardly believe it."\r
+\r
+Lord Henry smiled, and leaning down, plucked a pink-petalled daisy from\r
+the grass and examined it.  "I am quite sure I shall understand it," he\r
+replied, gazing intently at the little golden, white-feathered disk,\r
+"and as for believing things, I can believe anything, provided that it\r
+is quite incredible."\r
+\r
+The wind shook some blossoms from the trees, and the heavy\r
+lilac-blooms, with their clustering stars, moved to and fro in the\r
+languid air.  A grasshopper began to chirrup by the wall, and like a\r
+blue thread a long thin dragon-fly floated past on its brown gauze\r
+wings.  Lord Henry felt as if he could hear Basil Hallward's heart\r
+beating, and wondered what was coming.\r
+\r
+"The story is simply this," said the painter after some time.  "Two\r
+months ago I went to a crush at Lady Brandon's. You know we poor\r
+artists have to show ourselves in society from time to time, just to\r
+remind the public that we are not savages.  With an evening coat and a\r
+white tie, as you told me once, anybody, even a stock-broker, can gain\r
+a reputation for being civilized.  Well, after I had been in the room\r
+about ten minutes, talking to huge overdressed dowagers and tedious\r
+academicians, I suddenly became conscious that some one was looking at\r
+me.  I turned half-way round and saw Dorian Gray for the first time.\r
+When our eyes met, I felt that I was growing pale.  A curious sensation\r
+of terror came over me.  I knew that I had come face to face with some\r
+one whose mere personality was so fascinating that, if I allowed it to\r
+do so, it would absorb my whole nature, my whole soul, my very art\r
+itself.  I did not want any external influence in my life.  You know\r
+yourself, Harry, how independent I am by nature.  I have always been my\r
+own master; had at least always been so, till I met Dorian Gray.\r
+Then--but I don't know how to explain it to you.  Something seemed to\r
+tell me that I was on the verge of a terrible crisis in my life.  I had\r
+a strange feeling that fate had in store for me exquisite joys and\r
+exquisite sorrows.  I grew afraid and turned to quit the room.  It was\r
+not conscience that made me do so:  it was a sort of cowardice.  I take\r
+no credit to myself for trying to escape."\r
+\r
+"Conscience and cowardice are really the same things, Basil.\r
+Conscience is the trade-name of the firm.  That is all."\r
+\r
+"I don't believe that, Harry, and I don't believe you do either.\r
+However, whatever was my motive--and it may have been pride, for I used\r
+to be very proud--I certainly struggled to the door.  There, of course,\r
+I stumbled against Lady Brandon.  'You are not going to run away so\r
+soon, Mr. Hallward?' she screamed out.  You know her curiously shrill\r
+voice?"\r
+\r
+"Yes; she is a peacock in everything but beauty," said Lord Henry,\r
+pulling the daisy to bits with his long nervous fingers.\r
+\r
+"I could not get rid of her.  She brought me up to royalties, and\r
+people with stars and garters, and elderly ladies with gigantic tiaras\r
+and parrot noses.  She spoke of me as her dearest friend.  I had only\r
+met her once before, but she took it into her head to lionize me.  I\r
+believe some picture of mine had made a great success at the time, at\r
+least had been chattered about in the penny newspapers, which is the\r
+nineteenth-century standard of immortality.  Suddenly I found myself\r
+face to face with the young man whose personality had so strangely\r
+stirred me.  We were quite close, almost touching.  Our eyes met again.\r
+It was reckless of me, but I asked Lady Brandon to introduce me to him.\r
+Perhaps it was not so reckless, after all.  It was simply inevitable.\r
+We would have spoken to each other without any introduction.  I am sure\r
+of that.  Dorian told me so afterwards.  He, too, felt that we were\r
+destined to know each other."\r
+\r
+"And how did Lady Brandon describe this wonderful young man?" asked his\r
+companion.  "I know she goes in for giving a rapid _precis_ of all her\r
+guests.  I remember her bringing me up to a truculent and red-faced old\r
+gentleman covered all over with orders and ribbons, and hissing into my\r
+ear, in a tragic whisper which must have been perfectly audible to\r
+everybody in the room, the most astounding details.  I simply fled.  I\r
+like to find out people for myself.  But Lady Brandon treats her guests\r
+exactly as an auctioneer treats his goods.  She either explains them\r
+entirely away, or tells one everything about them except what one wants\r
+to know."\r
+\r
+"Poor Lady Brandon!  You are hard on her, Harry!" said Hallward\r
+listlessly.\r
+\r
+"My dear fellow, she tried to found a _salon_, and only succeeded in\r
+opening a restaurant.  How could I admire her?  But tell me, what did\r
+she say about Mr. Dorian Gray?"\r
+\r
+"Oh, something like, 'Charming boy--poor dear mother and I absolutely\r
+inseparable.  Quite forget what he does--afraid he--doesn't do\r
+anything--oh, yes, plays the piano--or is it the violin, dear Mr.\r
+Gray?'  Neither of us could help laughing, and we became friends at\r
+once."\r
+\r
+"Laughter is not at all a bad beginning for a friendship, and it is far\r
+the best ending for one," said the young lord, plucking another daisy.\r
+\r
+Hallward shook his head.  "You don't understand what friendship is,\r
+Harry," he murmured--"or what enmity is, for that matter.  You like\r
+every one; that is to say, you are indifferent to every one."\r
+\r
+"How horribly unjust of you!" cried Lord Henry, tilting his hat back\r
+and looking up at the little clouds that, like ravelled skeins of\r
+glossy white silk, were drifting across the hollowed turquoise of the\r
+summer sky.  "Yes; horribly unjust of you.  I make a great difference\r
+between people.  I choose my friends for their good looks, my\r
+acquaintances for their good characters, and my enemies for their good\r
+intellects.  A man cannot be too careful in the choice of his enemies.\r
+I have not got one who is a fool.  They are all men of some\r
+intellectual power, and consequently they all appreciate me.  Is that\r
+very vain of me?  I think it is rather vain."\r
+\r
+"I should think it was, Harry.  But according to your category I must\r
+be merely an acquaintance."\r
+\r
+"My dear old Basil, you are much more than an acquaintance."\r
+\r
+"And much less than a friend.  A sort of brother, I suppose?"\r
+\r
+"Oh, brothers!  I don't care for brothers.  My elder brother won't die,\r
+and my younger brothers seem never to do anything else."\r
+\r
+"Harry!" exclaimed Hallward, frowning.\r
+\r
+"My dear fellow, I am not quite serious.  But I can't help detesting my\r
+relations.  I suppose it comes from the fact that none of us can stand\r
+other people having the same faults as ourselves.  I quite sympathize\r
+with the rage of the English democracy against what they call the vices\r
+of the upper orders.  The masses feel that drunkenness, stupidity, and\r
+immorality should be their own special property, and that if any one of\r
+us makes an ass of himself, he is poaching on their preserves.  When\r
+poor Southwark got into the divorce court, their indignation was quite\r
+magnificent.  And yet I don't suppose that ten per cent of the\r
+proletariat live correctly."\r
+\r
+"I don't agree with a single word that you have said, and, what is\r
+more, Harry, I feel sure you don't either."\r
+\r
+Lord Henry stroked his pointed brown beard and tapped the toe of his\r
+patent-leather boot with a tasselled ebony cane.  "How English you are\r
+Basil!  That is the second time you have made that observation.  If one\r
+puts forward an idea to a true Englishman--always a rash thing to\r
+do--he never dreams of considering whether the idea is right or wrong.\r
+The only thing he considers of any importance is whether one believes\r
+it oneself.  Now, the value of an idea has nothing whatsoever to do\r
+with the sincerity of the man who expresses it.  Indeed, the\r
+probabilities are that the more insincere the man is, the more purely\r
+intellectual will the idea be, as in that case it will not be coloured\r
+by either his wants, his desires, or his prejudices.  However, I don't\r
+propose to discuss politics, sociology, or metaphysics with you.  I\r
+like persons better than principles, and I like persons with no\r
+principles better than anything else in the world.  Tell me more about\r
+Mr. Dorian Gray.  How often do you see him?"\r
+\r
+"Every day.  I couldn't be happy if I didn't see him every day.  He is\r
+absolutely necessary to me."\r
+\r
+"How extraordinary!  I thought you would never care for anything but\r
+your art."\r
+\r
+"He is all my art to me now," said the painter gravely.  "I sometimes\r
+think, Harry, that there are only two eras of any importance in the\r
+world's history.  The first is the appearance of a new medium for art,\r
+and the second is the appearance of a new personality for art also.\r
+What the invention of oil-painting was to the Venetians, the face of\r
+Antinous was to late Greek sculpture, and the face of Dorian Gray will\r
+some day be to me.  It is not merely that I paint from him, draw from\r
+him, sketch from him.  Of course, I have done all that.  But he is much\r
+more to me than a model or a sitter.  I won't tell you that I am\r
+dissatisfied with what I have done of him, or that his beauty is such\r
+that art cannot express it.  There is nothing that art cannot express,\r
+and I know that the work I have done, since I met Dorian Gray, is good\r
+work, is the best work of my life.  But in some curious way--I wonder\r
+will you understand me?--his personality has suggested to me an\r
+entirely new manner in art, an entirely new mode of style.  I see\r
+things differently, I think of them differently.  I can now recreate\r
+life in a way that was hidden from me before.  'A dream of form in days\r
+of thought'--who is it who says that?  I forget; but it is what Dorian\r
+Gray has been to me.  The merely visible presence of this lad--for he\r
+seems to me little more than a lad, though he is really over\r
+twenty--his merely visible presence--ah!  I wonder can you realize all\r
+that that means?  Unconsciously he defines for me the lines of a fresh\r
+school, a school that is to have in it all the passion of the romantic\r
+spirit, all the perfection of the spirit that is Greek.  The harmony of\r
+soul and body--how much that is!  We in our madness have separated the\r
+two, and have invented a realism that is vulgar, an ideality that is\r
+void.  Harry! if you only knew what Dorian Gray is to me!  You remember\r
+that landscape of mine, for which Agnew offered me such a huge price\r
+but which I would not part with?  It is one of the best things I have\r
+ever done.  And why is it so?  Because, while I was painting it, Dorian\r
+Gray sat beside me.  Some subtle influence passed from him to me, and\r
+for the first time in my life I saw in the plain woodland the wonder I\r
+had always looked for and always missed."\r
+\r
+"Basil, this is extraordinary!  I must see Dorian Gray."\r
+\r
+Hallward got up from the seat and walked up and down the garden.  After\r
+some time he came back.  "Harry," he said, "Dorian Gray is to me simply\r
+a motive in art.  You might see nothing in him.  I see everything in\r
+him.  He is never more present in my work than when no image of him is\r
+there.  He is a suggestion, as I have said, of a new manner.  I find\r
+him in the curves of certain lines, in the loveliness and subtleties of\r
+certain colours.  That is all."\r
+\r
+"Then why won't you exhibit his portrait?" asked Lord Henry.\r
+\r
+"Because, without intending it, I have put into it some expression of\r
+all this curious artistic idolatry, of which, of course, I have never\r
+cared to speak to him.  He knows nothing about it.  He shall never know\r
+anything about it.  But the world might guess it, and I will not bare\r
+my soul to their shallow prying eyes.  My heart shall never be put\r
+under their microscope.  There is too much of myself in the thing,\r
+Harry--too much of myself!"\r
+\r
+"Poets are not so scrupulous as you are.  They know how useful passion\r
+is for publication.  Nowadays a broken heart will run to many editions."\r
+\r
+"I hate them for it," cried Hallward.  "An artist should create\r
+beautiful things, but should put nothing of his own life into them.  We\r
+live in an age when men treat art as if it were meant to be a form of\r
+autobiography.  We have lost the abstract sense of beauty.  Some day I\r
+will show the world what it is; and for that reason the world shall\r
+never see my portrait of Dorian Gray."\r
+\r
+"I think you are wrong, Basil, but I won't argue with you.  It is only\r
+the intellectually lost who ever argue.  Tell me, is Dorian Gray very\r
+fond of you?"\r
+\r
+The painter considered for a few moments.  "He likes me," he answered\r
+after a pause; "I know he likes me.  Of course I flatter him\r
+dreadfully.  I find a strange pleasure in saying things to him that I\r
+know I shall be sorry for having said.  As a rule, he is charming to\r
+me, and we sit in the studio and talk of a thousand things.  Now and\r
+then, however, he is horribly thoughtless, and seems to take a real\r
+delight in giving me pain.  Then I feel, Harry, that I have given away\r
+my whole soul to some one who treats it as if it were a flower to put\r
+in his coat, a bit of decoration to charm his vanity, an ornament for a\r
+summer's day."\r
+\r
+"Days in summer, Basil, are apt to linger," murmured Lord Henry.\r
+"Perhaps you will tire sooner than he will.  It is a sad thing to think\r
+of, but there is no doubt that genius lasts longer than beauty.  That\r
+accounts for the fact that we all take such pains to over-educate\r
+ourselves.  In the wild struggle for existence, we want to have\r
+something that endures, and so we fill our minds with rubbish and\r
+facts, in the silly hope of keeping our place.  The thoroughly\r
+well-informed man--that is the modern ideal.  And the mind of the\r
+thoroughly well-informed man is a dreadful thing.  It is like a\r
+_bric-a-brac_ shop, all monsters and dust, with everything priced above\r
+its proper value.  I think you will tire first, all the same.  Some day\r
+you will look at your friend, and he will seem to you to be a little\r
+out of drawing, or you won't like his tone of colour, or something.\r
+You will bitterly reproach him in your own heart, and seriously think\r
+that he has behaved very badly to you.  The next time he calls, you\r
+will be perfectly cold and indifferent.  It will be a great pity, for\r
+it will alter you.  What you have told me is quite a romance, a romance\r
+of art one might call it, and the worst of having a romance of any kind\r
+is that it leaves one so unromantic."\r
+\r
+"Harry, don't talk like that.  As long as I live, the personality of\r
+Dorian Gray will dominate me.  You can't feel what I feel.  You change\r
+too often."\r
+\r
+"Ah, my dear Basil, that is exactly why I can feel it.  Those who are\r
+faithful know only the trivial side of love: it is the faithless who\r
+know love's tragedies."  And Lord Henry struck a light on a dainty\r
+silver case and began to smoke a cigarette with a self-conscious and\r
+satisfied air, as if he had summed up the world in a phrase.  There was\r
+a rustle of chirruping sparrows in the green lacquer leaves of the ivy,\r
+and the blue cloud-shadows chased themselves across the grass like\r
+swallows.  How pleasant it was in the garden!  And how delightful other\r
+people's emotions were!--much more delightful than their ideas, it\r
+seemed to him.  One's own soul, and the passions of one's\r
+friends--those were the fascinating things in life.  He pictured to\r
+himself with silent amusement the tedious luncheon that he had missed\r
+by staying so long with Basil Hallward.  Had he gone to his aunt's, he\r
+would have been sure to have met Lord Goodbody there, and the whole\r
+conversation would have been about the feeding of the poor and the\r
+necessity for model lodging-houses. Each class would have preached the\r
+importance of those virtues, for whose exercise there was no necessity\r
+in their own lives.  The rich would have spoken on the value of thrift,\r
+and the idle grown eloquent over the dignity of labour.  It was\r
+charming to have escaped all that!  As he thought of his aunt, an idea\r
+seemed to strike him.  He turned to Hallward and said, "My dear fellow,\r
+I have just remembered."\r
+\r
+"Remembered what, Harry?"\r
+\r
+"Where I heard the name of Dorian Gray."\r
+\r
+"Where was it?" asked Hallward, with a slight frown.\r
+\r
+"Don't look so angry, Basil.  It was at my aunt, Lady Agatha's.  She\r
+told me she had discovered a wonderful young man who was going to help\r
+her in the East End, and that his name was Dorian Gray.  I am bound to\r
+state that she never told me he was good-looking. Women have no\r
+appreciation of good looks; at least, good women have not.  She said\r
+that he was very earnest and had a beautiful nature.  I at once\r
+pictured to myself a creature with spectacles and lank hair, horribly\r
+freckled, and tramping about on huge feet.  I wish I had known it was\r
+your friend."\r
+\r
+"I am very glad you didn't, Harry."\r
+\r
+"Why?"\r
+\r
+"I don't want you to meet him."\r
+\r
+"You don't want me to meet him?"\r
+\r
+"No."\r
+\r
+"Mr. Dorian Gray is in the studio, sir," said the butler, coming into\r
+the garden.\r
+\r
+"You must introduce me now," cried Lord Henry, laughing.\r
+\r
+The painter turned to his servant, who stood blinking in the sunlight.\r
+"Ask Mr. Gray to wait, Parker:  I shall be in in a few moments." The\r
+man bowed and went up the walk.\r
+\r
+Then he looked at Lord Henry.  "Dorian Gray is my dearest friend," he\r
+said.  "He has a simple and a beautiful nature.  Your aunt was quite\r
+right in what she said of him.  Don't spoil him.  Don't try to\r
+influence him.  Your influence would be bad.  The world is wide, and\r
+has many marvellous people in it.  Don't take away from me the one\r
+person who gives to my art whatever charm it possesses:  my life as an\r
+artist depends on him.  Mind, Harry, I trust you."  He spoke very\r
+slowly, and the words seemed wrung out of him almost against his will.\r
+\r
+"What nonsense you talk!" said Lord Henry, smiling, and taking Hallward\r
+by the arm, he almost led him into the house.\r
+\r
+\r
+\r
+CHAPTER 2\r
+\r
+As they entered they saw Dorian Gray.  He was seated at the piano, with\r
+his back to them, turning over the pages of a volume of Schumann's\r
+"Forest Scenes."  "You must lend me these, Basil," he cried.  "I want\r
+to learn them.  They are perfectly charming."\r
+\r
+"That entirely depends on how you sit to-day, Dorian."\r
+\r
+"Oh, I am tired of sitting, and I don't want a life-sized portrait of\r
+myself," answered the lad, swinging round on the music-stool in a\r
+wilful, petulant manner.  When he caught sight of Lord Henry, a faint\r
+blush coloured his cheeks for a moment, and he started up.  "I beg your\r
+pardon, Basil, but I didn't know you had any one with you."\r
+\r
+"This is Lord Henry Wotton, Dorian, an old Oxford friend of mine.  I\r
+have just been telling him what a capital sitter you were, and now you\r
+have spoiled everything."\r
+\r
+"You have not spoiled my pleasure in meeting you, Mr. Gray," said Lord\r
+Henry, stepping forward and extending his hand.  "My aunt has often\r
+spoken to me about you.  You are one of her favourites, and, I am\r
+afraid, one of her victims also."\r
+\r
+"I am in Lady Agatha's black books at present," answered Dorian with a\r
+funny look of penitence.  "I promised to go to a club in Whitechapel\r
+with her last Tuesday, and I really forgot all about it.  We were to\r
+have played a duet together--three duets, I believe.  I don't know what\r
+she will say to me.  I am far too frightened to call."\r
+\r
+"Oh, I will make your peace with my aunt.  She is quite devoted to you.\r
+And I don't think it really matters about your not being there.  The\r
+audience probably thought it was a duet.  When Aunt Agatha sits down to\r
+the piano, she makes quite enough noise for two people."\r
+\r
+"That is very horrid to her, and not very nice to me," answered Dorian,\r
+laughing.\r
+\r
+Lord Henry looked at him.  Yes, he was certainly wonderfully handsome,\r
+with his finely curved scarlet lips, his frank blue eyes, his crisp\r
+gold hair.  There was something in his face that made one trust him at\r
+once.  All the candour of youth was there, as well as all youth's\r
+passionate purity.  One felt that he had kept himself unspotted from\r
+the world.  No wonder Basil Hallward worshipped him.\r
+\r
+"You are too charming to go in for philanthropy, Mr. Gray--far too\r
+charming." And Lord Henry flung himself down on the divan and opened\r
+his cigarette-case.\r
+\r
+The painter had been busy mixing his colours and getting his brushes\r
+ready.  He was looking worried, and when he heard Lord Henry's last\r
+remark, he glanced at him, hesitated for a moment, and then said,\r
+"Harry, I want to finish this picture to-day. Would you think it\r
+awfully rude of me if I asked you to go away?"\r
+\r
+Lord Henry smiled and looked at Dorian Gray.  "Am I to go, Mr. Gray?"\r
+he asked.\r
+\r
+"Oh, please don't, Lord Henry.  I see that Basil is in one of his sulky\r
+moods, and I can't bear him when he sulks.  Besides, I want you to tell\r
+me why I should not go in for philanthropy."\r
+\r
+"I don't know that I shall tell you that, Mr. Gray.  It is so tedious a\r
+subject that one would have to talk seriously about it.  But I\r
+certainly shall not run away, now that you have asked me to stop.  You\r
+don't really mind, Basil, do you?  You have often told me that you\r
+liked your sitters to have some one to chat to."\r
+\r
+Hallward bit his lip.  "If Dorian wishes it, of course you must stay.\r
+Dorian's whims are laws to everybody, except himself."\r
+\r
+Lord Henry took up his hat and gloves.  "You are very pressing, Basil,\r
+but I am afraid I must go.  I have promised to meet a man at the\r
+Orleans.  Good-bye, Mr. Gray.  Come and see me some afternoon in Curzon\r
+Street.  I am nearly always at home at five o'clock. Write to me when\r
+you are coming.  I should be sorry to miss you."\r
+\r
+"Basil," cried Dorian Gray, "if Lord Henry Wotton goes, I shall go,\r
+too.  You never open your lips while you are painting, and it is\r
+horribly dull standing on a platform and trying to look pleasant.  Ask\r
+him to stay.  I insist upon it."\r
+\r
+"Stay, Harry, to oblige Dorian, and to oblige me," said Hallward,\r
+gazing intently at his picture.  "It is quite true, I never talk when I\r
+am working, and never listen either, and it must be dreadfully tedious\r
+for my unfortunate sitters.  I beg you to stay."\r
+\r
+"But what about my man at the Orleans?"\r
+\r
+The painter laughed.  "I don't think there will be any difficulty about\r
+that.  Sit down again, Harry.  And now, Dorian, get up on the platform,\r
+and don't move about too much, or pay any attention to what Lord Henry\r
+says.  He has a very bad influence over all his friends, with the\r
+single exception of myself."\r
+\r
+Dorian Gray stepped up on the dais with the air of a young Greek\r
+martyr, and made a little _moue_ of discontent to Lord Henry, to whom he\r
+had rather taken a fancy.  He was so unlike Basil.  They made a\r
+delightful contrast.  And he had such a beautiful voice.  After a few\r
+moments he said to him, "Have you really a very bad influence, Lord\r
+Henry?  As bad as Basil says?"\r
+\r
+"There is no such thing as a good influence, Mr. Gray.  All influence\r
+is immoral--immoral from the scientific point of view."\r
+\r
+"Why?"\r
+\r
+"Because to influence a person is to give him one's own soul.  He does\r
+not think his natural thoughts, or burn with his natural passions.  His\r
+virtues are not real to him.  His sins, if there are such things as\r
+sins, are borrowed.  He becomes an echo of some one else's music, an\r
+actor of a part that has not been written for him.  The aim of life is\r
+self-development. To realize one's nature perfectly--that is what each\r
+of us is here for.  People are afraid of themselves, nowadays.  They\r
+have forgotten the highest of all duties, the duty that one owes to\r
+one's self.  Of course, they are charitable.  They feed the hungry and\r
+clothe the beggar.  But their own souls starve, and are naked.  Courage\r
+has gone out of our race.  Perhaps we never really had it.  The terror\r
+of society, which is the basis of morals, the terror of God, which is\r
+the secret of religion--these are the two things that govern us.  And\r
+yet--"\r
+\r
+"Just turn your head a little more to the right, Dorian, like a good\r
+boy," said the painter, deep in his work and conscious only that a look\r
+had come into the lad's face that he had never seen there before.\r
+\r
+"And yet," continued Lord Henry, in his low, musical voice, and with\r
+that graceful wave of the hand that was always so characteristic of\r
+him, and that he had even in his Eton days, "I believe that if one man\r
+were to live out his life fully and completely, were to give form to\r
+every feeling, expression to every thought, reality to every dream--I\r
+believe that the world would gain such a fresh impulse of joy that we\r
+would forget all the maladies of mediaevalism, and return to the\r
+Hellenic ideal--to something finer, richer than the Hellenic ideal, it\r
+may be.  But the bravest man amongst us is afraid of himself.  The\r
+mutilation of the savage has its tragic survival in the self-denial\r
+that mars our lives.  We are punished for our refusals.  Every impulse\r
+that we strive to strangle broods in the mind and poisons us.  The body\r
+sins once, and has done with its sin, for action is a mode of\r
+purification.  Nothing remains then but the recollection of a pleasure,\r
+or the luxury of a regret.  The only way to get rid of a temptation is\r
+to yield to it.  Resist it, and your soul grows sick with longing for\r
+the things it has forbidden to itself, with desire for what its\r
+monstrous laws have made monstrous and unlawful.  It has been said that\r
+the great events of the world take place in the brain.  It is in the\r
+brain, and the brain only, that the great sins of the world take place\r
+also.  You, Mr. Gray, you yourself, with your rose-red youth and your\r
+rose-white boyhood, you have had passions that have made you afraid,\r
+thoughts that have filled you with terror, day-dreams and sleeping\r
+dreams whose mere memory might stain your cheek with shame--"\r
+\r
+"Stop!" faltered Dorian Gray, "stop! you bewilder me.  I don't know\r
+what to say.  There is some answer to you, but I cannot find it.  Don't\r
+speak.  Let me think.  Or, rather, let me try not to think."\r
+\r
+For nearly ten minutes he stood there, motionless, with parted lips and\r
+eyes strangely bright.  He was dimly conscious that entirely fresh\r
+influences were at work within him.  Yet they seemed to him to have\r
+come really from himself.  The few words that Basil's friend had said\r
+to him--words spoken by chance, no doubt, and with wilful paradox in\r
+them--had touched some secret chord that had never been touched before,\r
+but that he felt was now vibrating and throbbing to curious pulses.\r
+\r
+Music had stirred him like that.  Music had troubled him many times.\r
+But music was not articulate.  It was not a new world, but rather\r
+another chaos, that it created in us.  Words!  Mere words!  How\r
+terrible they were!  How clear, and vivid, and cruel!  One could not\r
+escape from them.  And yet what a subtle magic there was in them!  They\r
+seemed to be able to give a plastic form to formless things, and to\r
+have a music of their own as sweet as that of viol or of lute.  Mere\r
+words!  Was there anything so real as words?\r
+\r
+Yes; there had been things in his boyhood that he had not understood.\r
+He understood them now.  Life suddenly became fiery-coloured to him.\r
+It seemed to him that he had been walking in fire.  Why had he not\r
+known it?\r
+\r
+With his subtle smile, Lord Henry watched him.  He knew the precise\r
+psychological moment when to say nothing.  He felt intensely\r
+interested.  He was amazed at the sudden impression that his words had\r
+produced, and, remembering a book that he had read when he was sixteen,\r
+a book which had revealed to him much that he had not known before, he\r
+wondered whether Dorian Gray was passing through a similar experience.\r
+He had merely shot an arrow into the air.  Had it hit the mark?  How\r
+fascinating the lad was!\r
+\r
+Hallward painted away with that marvellous bold touch of his, that had\r
+the true refinement and perfect delicacy that in art, at any rate comes\r
+only from strength.  He was unconscious of the silence.\r
+\r
+"Basil, I am tired of standing," cried Dorian Gray suddenly.  "I must\r
+go out and sit in the garden.  The air is stifling here."\r
+\r
+"My dear fellow, I am so sorry.  When I am painting, I can't think of\r
+anything else.  But you never sat better.  You were perfectly still.\r
+And I have caught the effect I wanted--the half-parted lips and the\r
+bright look in the eyes.  I don't know what Harry has been saying to\r
+you, but he has certainly made you have the most wonderful expression.\r
+I suppose he has been paying you compliments.  You mustn't believe a\r
+word that he says."\r
+\r
+"He has certainly not been paying me compliments.  Perhaps that is the\r
+reason that I don't believe anything he has told me."\r
+\r
+"You know you believe it all," said Lord Henry, looking at him with his\r
+dreamy languorous eyes.  "I will go out to the garden with you.  It is\r
+horribly hot in the studio.  Basil, let us have something iced to\r
+drink, something with strawberries in it."\r
+\r
+"Certainly, Harry.  Just touch the bell, and when Parker comes I will\r
+tell him what you want.  I have got to work up this background, so I\r
+will join you later on.  Don't keep Dorian too long.  I have never been\r
+in better form for painting than I am to-day. This is going to be my\r
+masterpiece.  It is my masterpiece as it stands."\r
+\r
+Lord Henry went out to the garden and found Dorian Gray burying his\r
+face in the great cool lilac-blossoms, feverishly drinking in their\r
+perfume as if it had been wine.  He came close to him and put his hand\r
+upon his shoulder.  "You are quite right to do that," he murmured.\r
+"Nothing can cure the soul but the senses, just as nothing can cure the\r
+senses but the soul."\r
+\r
+The lad started and drew back.  He was bareheaded, and the leaves had\r
+tossed his rebellious curls and tangled all their gilded threads.\r
+There was a look of fear in his eyes, such as people have when they are\r
+suddenly awakened.  His finely chiselled nostrils quivered, and some\r
+hidden nerve shook the scarlet of his lips and left them trembling.\r
+\r
+"Yes," continued Lord Henry, "that is one of the great secrets of\r
+life--to cure the soul by means of the senses, and the senses by means\r
+of the soul.  You are a wonderful creation.  You know more than you\r
+think you know, just as you know less than you want to know."\r
+\r
+Dorian Gray frowned and turned his head away.  He could not help liking\r
+the tall, graceful young man who was standing by him.  His romantic,\r
+olive-coloured face and worn expression interested him.  There was\r
+something in his low languid voice that was absolutely fascinating.\r
+His cool, white, flowerlike hands, even, had a curious charm.  They\r
+moved, as he spoke, like music, and seemed to have a language of their\r
+own.  But he felt afraid of him, and ashamed of being afraid.  Why had\r
+it been left for a stranger to reveal him to himself?  He had known\r
+Basil Hallward for months, but the friendship between them had never\r
+altered him.  Suddenly there had come some one across his life who\r
+seemed to have disclosed to him life's mystery.  And, yet, what was\r
+there to be afraid of?  He was not a schoolboy or a girl.  It was\r
+absurd to be frightened.\r
+\r
+"Let us go and sit in the shade," said Lord Henry.  "Parker has brought\r
+out the drinks, and if you stay any longer in this glare, you will be\r
+quite spoiled, and Basil will never paint you again.  You really must\r
+not allow yourself to become sunburnt.  It would be unbecoming."\r
+\r
+"What can it matter?" cried Dorian Gray, laughing, as he sat down on\r
+the seat at the end of the garden.\r
+\r
+"It should matter everything to you, Mr. Gray."\r
+\r
+"Why?"\r
+\r
+"Because you have the most marvellous youth, and youth is the one thing\r
+worth having."\r
+\r
+"I don't feel that, Lord Henry."\r
+\r
+"No, you don't feel it now.  Some day, when you are old and wrinkled\r
+and ugly, when thought has seared your forehead with its lines, and\r
+passion branded your lips with its hideous fires, you will feel it, you\r
+will feel it terribly.  Now, wherever you go, you charm the world.\r
+Will it always be so? ... You have a wonderfully beautiful face, Mr.\r
+Gray.  Don't frown.  You have.  And beauty is a form of genius--is\r
+higher, indeed, than genius, as it needs no explanation.  It is of the\r
+great facts of the world, like sunlight, or spring-time, or the\r
+reflection in dark waters of that silver shell we call the moon.  It\r
+cannot be questioned.  It has its divine right of sovereignty.  It\r
+makes princes of those who have it.  You smile?  Ah! when you have lost\r
+it you won't smile.... People say sometimes that beauty is only\r
+superficial.  That may be so, but at least it is not so superficial as\r
+thought is.  To me, beauty is the wonder of wonders.  It is only\r
+shallow people who do not judge by appearances.  The true mystery of\r
+the world is the visible, not the invisible.... Yes, Mr. Gray, the\r
+gods have been good to you.  But what the gods give they quickly take\r
+away.  You have only a few years in which to live really, perfectly,\r
+and fully.  When your youth goes, your beauty will go with it, and then\r
+you will suddenly discover that there are no triumphs left for you, or\r
+have to content yourself with those mean triumphs that the memory of\r
+your past will make more bitter than defeats.  Every month as it wanes\r
+brings you nearer to something dreadful.  Time is jealous of you, and\r
+wars against your lilies and your roses.  You will become sallow, and\r
+hollow-cheeked, and dull-eyed.  You will suffer horribly.... Ah!\r
+realize your youth while you have it.  Don't squander the gold of your\r
+days, listening to the tedious, trying to improve the hopeless failure,\r
+or giving away your life to the ignorant, the common, and the vulgar.\r
+These are the sickly aims, the false ideals, of our age.  Live!  Live\r
+the wonderful life that is in you!  Let nothing be lost upon you.  Be\r
+always searching for new sensations.  Be afraid of nothing.... A new\r
+Hedonism--that is what our century wants.  You might be its visible\r
+symbol.  With your personality there is nothing you could not do.  The\r
+world belongs to you for a season.... The moment I met you I saw that\r
+you were quite unconscious of what you really are, of what you really\r
+might be.  There was so much in you that charmed me that I felt I must\r
+tell you something about yourself.  I thought how tragic it would be if\r
+you were wasted.  For there is such a little time that your youth will\r
+last--such a little time.  The common hill-flowers wither, but they\r
+blossom again.  The laburnum will be as yellow next June as it is now.\r
+In a month there will be purple stars on the clematis, and year after\r
+year the green night of its leaves will hold its purple stars.  But we\r
+never get back our youth.  The pulse of joy that beats in us at twenty\r
+becomes sluggish.  Our limbs fail, our senses rot.  We degenerate into\r
+hideous puppets, haunted by the memory of the passions of which we were\r
+too much afraid, and the exquisite temptations that we had not the\r
+courage to yield to.  Youth!  Youth!  There is absolutely nothing in\r
+the world but youth!"\r
+\r
+Dorian Gray listened, open-eyed and wondering.  The spray of lilac fell\r
+from his hand upon the gravel.  A furry bee came and buzzed round it\r
+for a moment.  Then it began to scramble all over the oval stellated\r
+globe of the tiny blossoms.  He watched it with that strange interest\r
+in trivial things that we try to develop when things of high import\r
+make us afraid, or when we are stirred by some new emotion for which we\r
+cannot find expression, or when some thought that terrifies us lays\r
+sudden siege to the brain and calls on us to yield.  After a time the\r
+bee flew away.  He saw it creeping into the stained trumpet of a Tyrian\r
+convolvulus.  The flower seemed to quiver, and then swayed gently to\r
+and fro.\r
+\r
+Suddenly the painter appeared at the door of the studio and made\r
+staccato signs for them to come in.  They turned to each other and\r
+smiled.\r
+\r
+"I am waiting," he cried.  "Do come in.  The light is quite perfect,\r
+and you can bring your drinks."\r
+\r
+They rose up and sauntered down the walk together.  Two green-and-white\r
+butterflies fluttered past them, and in the pear-tree at the corner of\r
+the garden a thrush began to sing.\r
+\r
+"You are glad you have met me, Mr. Gray," said Lord Henry, looking at\r
+him.\r
+\r
+"Yes, I am glad now.  I wonder shall I always be glad?"\r
+\r
+"Always!  That is a dreadful word.  It makes me shudder when I hear it.\r
+Women are so fond of using it.  They spoil every romance by trying to\r
+make it last for ever.  It is a meaningless word, too.  The only\r
+difference between a caprice and a lifelong passion is that the caprice\r
+lasts a little longer."\r
+\r
+As they entered the studio, Dorian Gray put his hand upon Lord Henry's\r
+arm.  "In that case, let our friendship be a caprice," he murmured,\r
+flushing at his own boldness, then stepped up on the platform and\r
+resumed his pose.\r
+\r
+Lord Henry flung himself into a large wicker arm-chair and watched him.\r
+The sweep and dash of the brush on the canvas made the only sound that\r
+broke the stillness, except when, now and then, Hallward stepped back\r
+to look at his work from a distance.  In the slanting beams that\r
+streamed through the open doorway the dust danced and was golden.  The\r
+heavy scent of the roses seemed to brood over everything.\r
+\r
+After about a quarter of an hour Hallward stopped painting, looked for\r
+a long time at Dorian Gray, and then for a long time at the picture,\r
+biting the end of one of his huge brushes and frowning.  "It is quite\r
+finished," he cried at last, and stooping down he wrote his name in\r
+long vermilion letters on the left-hand corner of the canvas.\r
+\r
+Lord Henry came over and examined the picture.  It was certainly a\r
+wonderful work of art, and a wonderful likeness as well.\r
+\r
+"My dear fellow, I congratulate you most warmly," he said.  "It is the\r
+finest portrait of modern times.  Mr. Gray, come over and look at\r
+yourself."\r
+\r
+The lad started, as if awakened from some dream.\r
+\r
+"Is it really finished?" he murmured, stepping down from the platform.\r
+\r
+"Quite finished," said the painter.  "And you have sat splendidly\r
+to-day. I am awfully obliged to you."\r
+\r
+"That is entirely due to me," broke in Lord Henry.  "Isn't it, Mr.\r
+Gray?"\r
+\r
+Dorian made no answer, but passed listlessly in front of his picture\r
+and turned towards it.  When he saw it he drew back, and his cheeks\r
+flushed for a moment with pleasure.  A look of joy came into his eyes,\r
+as if he had recognized himself for the first time.  He stood there\r
+motionless and in wonder, dimly conscious that Hallward was speaking to\r
+him, but not catching the meaning of his words.  The sense of his own\r
+beauty came on him like a revelation.  He had never felt it before.\r
+Basil Hallward's compliments had seemed to him to be merely the\r
+charming exaggeration of friendship.  He had listened to them, laughed\r
+at them, forgotten them.  They had not influenced his nature.  Then had\r
+come Lord Henry Wotton with his strange panegyric on youth, his\r
+terrible warning of its brevity.  That had stirred him at the time, and\r
+now, as he stood gazing at the shadow of his own loveliness, the full\r
+reality of the description flashed across him.  Yes, there would be a\r
+day when his face would be wrinkled and wizen, his eyes dim and\r
+colourless, the grace of his figure broken and deformed.  The scarlet\r
+would pass away from his lips and the gold steal from his hair.  The\r
+life that was to make his soul would mar his body.  He would become\r
+dreadful, hideous, and uncouth.\r
+\r
+As he thought of it, a sharp pang of pain struck through him like a\r
+knife and made each delicate fibre of his nature quiver.  His eyes\r
+deepened into amethyst, and across them came a mist of tears.  He felt\r
+as if a hand of ice had been laid upon his heart.\r
+\r
+"Don't you like it?" cried Hallward at last, stung a little by the\r
+lad's silence, not understanding what it meant.\r
+\r
+"Of course he likes it," said Lord Henry.  "Who wouldn't like it?  It\r
+is one of the greatest things in modern art.  I will give you anything\r
+you like to ask for it.  I must have it."\r
+\r
+"It is not my property, Harry."\r
+\r
+"Whose property is it?"\r
+\r
+"Dorian's, of course," answered the painter.\r
+\r
+"He is a very lucky fellow."\r
+\r
+"How sad it is!" murmured Dorian Gray with his eyes still fixed upon\r
+his own portrait.  "How sad it is!  I shall grow old, and horrible, and\r
+dreadful.  But this picture will remain always young.  It will never be\r
+older than this particular day of June.... If it were only the other\r
+way!  If it were I who was to be always young, and the picture that was\r
+to grow old!  For that--for that--I would give everything!  Yes, there\r
+is nothing in the whole world I would not give!  I would give my soul\r
+for that!"\r
+\r
+"You would hardly care for such an arrangement, Basil," cried Lord\r
+Henry, laughing.  "It would be rather hard lines on your work."\r
+\r
+"I should object very strongly, Harry," said Hallward.\r
+\r
+Dorian Gray turned and looked at him.  "I believe you would, Basil.\r
+You like your art better than your friends.  I am no more to you than a\r
+green bronze figure.  Hardly as much, I dare say."\r
+\r
+The painter stared in amazement.  It was so unlike Dorian to speak like\r
+that.  What had happened?  He seemed quite angry.  His face was flushed\r
+and his cheeks burning.\r
+\r
+"Yes," he continued, "I am less to you than your ivory Hermes or your\r
+silver Faun.  You will like them always.  How long will you like me?\r
+Till I have my first wrinkle, I suppose.  I know, now, that when one\r
+loses one's good looks, whatever they may be, one loses everything.\r
+Your picture has taught me that.  Lord Henry Wotton is perfectly right.\r
+Youth is the only thing worth having.  When I find that I am growing\r
+old, I shall kill myself."\r
+\r
+Hallward turned pale and caught his hand.  "Dorian!  Dorian!" he cried,\r
+"don't talk like that.  I have never had such a friend as you, and I\r
+shall never have such another.  You are not jealous of material things,\r
+are you?--you who are finer than any of them!"\r
+\r
+"I am jealous of everything whose beauty does not die.  I am jealous of\r
+the portrait you have painted of me.  Why should it keep what I must\r
+lose?  Every moment that passes takes something from me and gives\r
+something to it.  Oh, if it were only the other way!  If the picture\r
+could change, and I could be always what I am now!  Why did you paint\r
+it?  It will mock me some day--mock me horribly!"  The hot tears welled\r
+into his eyes; he tore his hand away and, flinging himself on the\r
+divan, he buried his face in the cushions, as though he was praying.\r
+\r
+"This is your doing, Harry," said the painter bitterly.\r
+\r
+Lord Henry shrugged his shoulders.  "It is the real Dorian Gray--that\r
+is all."\r
+\r
+"It is not."\r
+\r
+"If it is not, what have I to do with it?"\r
+\r
+"You should have gone away when I asked you," he muttered.\r
+\r
+"I stayed when you asked me," was Lord Henry's answer.\r
+\r
+"Harry, I can't quarrel with my two best friends at once, but between\r
+you both you have made me hate the finest piece of work I have ever\r
+done, and I will destroy it.  What is it but canvas and colour?  I will\r
+not let it come across our three lives and mar them."\r
+\r
+Dorian Gray lifted his golden head from the pillow, and with pallid\r
+face and tear-stained eyes, looked at him as he walked over to the deal\r
+painting-table that was set beneath the high curtained window.  What\r
+was he doing there?  His fingers were straying about among the litter\r
+of tin tubes and dry brushes, seeking for something.  Yes, it was for\r
+the long palette-knife, with its thin blade of lithe steel.  He had\r
+found it at last.  He was going to rip up the canvas.\r
+\r
+With a stifled sob the lad leaped from the couch, and, rushing over to\r
+Hallward, tore the knife out of his hand, and flung it to the end of\r
+the studio.  "Don't, Basil, don't!" he cried.  "It would be murder!"\r
+\r
+"I am glad you appreciate my work at last, Dorian," said the painter\r
+coldly when he had recovered from his surprise.  "I never thought you\r
+would."\r
+\r
+"Appreciate it?  I am in love with it, Basil.  It is part of myself.  I\r
+feel that."\r
+\r
+"Well, as soon as you are dry, you shall be varnished, and framed, and\r
+sent home.  Then you can do what you like with yourself." And he walked\r
+across the room and rang the bell for tea.  "You will have tea, of\r
+course, Dorian?  And so will you, Harry?  Or do you object to such\r
+simple pleasures?"\r
+\r
+"I adore simple pleasures," said Lord Henry.  "They are the last refuge\r
+of the complex.  But I don't like scenes, except on the stage.  What\r
+absurd fellows you are, both of you!  I wonder who it was defined man\r
+as a rational animal.  It was the most premature definition ever given.\r
+Man is many things, but he is not rational.  I am glad he is not, after\r
+all--though I wish you chaps would not squabble over the picture.  You\r
+had much better let me have it, Basil.  This silly boy doesn't really\r
+want it, and I really do."\r
+\r
+"If you let any one have it but me, Basil, I shall never forgive you!"\r
+cried Dorian Gray; "and I don't allow people to call me a silly boy."\r
+\r
+"You know the picture is yours, Dorian.  I gave it to you before it\r
+existed."\r
+\r
+"And you know you have been a little silly, Mr. Gray, and that you\r
+don't really object to being reminded that you are extremely young."\r
+\r
+"I should have objected very strongly this morning, Lord Henry."\r
+\r
+"Ah! this morning!  You have lived since then."\r
+\r
+There came a knock at the door, and the butler entered with a laden\r
+tea-tray and set it down upon a small Japanese table.  There was a\r
+rattle of cups and saucers and the hissing of a fluted Georgian urn.\r
+Two globe-shaped china dishes were brought in by a page.  Dorian Gray\r
+went over and poured out the tea.  The two men sauntered languidly to\r
+the table and examined what was under the covers.\r
+\r
+"Let us go to the theatre to-night," said Lord Henry.  "There is sure\r
+to be something on, somewhere.  I have promised to dine at White's, but\r
+it is only with an old friend, so I can send him a wire to say that I\r
+am ill, or that I am prevented from coming in consequence of a\r
+subsequent engagement.  I think that would be a rather nice excuse:  it\r
+would have all the surprise of candour."\r
+\r
+"It is such a bore putting on one's dress-clothes," muttered Hallward.\r
+"And, when one has them on, they are so horrid."\r
+\r
+"Yes," answered Lord Henry dreamily, "the costume of the nineteenth\r
+century is detestable.  It is so sombre, so depressing.  Sin is the\r
+only real colour-element left in modern life."\r
+\r
+"You really must not say things like that before Dorian, Harry."\r
+\r
+"Before which Dorian?  The one who is pouring out tea for us, or the\r
+one in the picture?"\r
+\r
+"Before either."\r
+\r
+"I should like to come to the theatre with you, Lord Henry," said the\r
+lad.\r
+\r
+"Then you shall come; and you will come, too, Basil, won't you?"\r
+\r
+"I can't, really.  I would sooner not.  I have a lot of work to do."\r
+\r
+"Well, then, you and I will go alone, Mr. Gray."\r
+\r
+"I should like that awfully."\r
+\r
+The painter bit his lip and walked over, cup in hand, to the picture.\r
+"I shall stay with the real Dorian," he said, sadly.\r
+\r
+"Is it the real Dorian?" cried the original of the portrait, strolling\r
+across to him.  "Am I really like that?"\r
+\r
+"Yes; you are just like that."\r
+\r
+"How wonderful, Basil!"\r
+\r
+"At least you are like it in appearance.  But it will never alter,"\r
+sighed Hallward.  "That is something."\r
+\r
+"What a fuss people make about fidelity!" exclaimed Lord Henry.  "Why,\r
+even in love it is purely a question for physiology.  It has nothing to\r
+do with our own will.  Young men want to be faithful, and are not; old\r
+men want to be faithless, and cannot: that is all one can say."\r
+\r
+"Don't go to the theatre to-night, Dorian," said Hallward.  "Stop and\r
+dine with me."\r
+\r
+"I can't, Basil."\r
+\r
+"Why?"\r
+\r
+"Because I have promised Lord Henry Wotton to go with him."\r
+\r
+"He won't like you the better for keeping your promises.  He always\r
+breaks his own.  I beg you not to go."\r
+\r
+Dorian Gray laughed and shook his head.\r
+\r
+"I entreat you."\r
+\r
+The lad hesitated, and looked over at Lord Henry, who was watching them\r
+from the tea-table with an amused smile.\r
+\r
+"I must go, Basil," he answered.\r
+\r
+"Very well," said Hallward, and he went over and laid down his cup on\r
+the tray.  "It is rather late, and, as you have to dress, you had\r
+better lose no time.  Good-bye, Harry.  Good-bye, Dorian.  Come and see\r
+me soon.  Come to-morrow."\r
+\r
+"Certainly."\r
+\r
+"You won't forget?"\r
+\r
+"No, of course not," cried Dorian.\r
+\r
+"And ... Harry!"\r
+\r
+"Yes, Basil?"\r
+\r
+"Remember what I asked you, when we were in the garden this morning."\r
+\r
+"I have forgotten it."\r
+\r
+"I trust you."\r
+\r
+"I wish I could trust myself," said Lord Henry, laughing.  "Come, Mr.\r
+Gray, my hansom is outside, and I can drop you at your own place.\r
+Good-bye, Basil.  It has been a most interesting afternoon."\r
+\r
+As the door closed behind them, the painter flung himself down on a\r
+sofa, and a look of pain came into his face.\r
+\r
+\r
+\r
+CHAPTER 3\r
+\r
+At half-past twelve next day Lord Henry Wotton strolled from Curzon\r
+Street over to the Albany to call on his uncle, Lord Fermor, a genial\r
+if somewhat rough-mannered old bachelor, whom the outside world called\r
+selfish because it derived no particular benefit from him, but who was\r
+considered generous by Society as he fed the people who amused him.\r
+His father had been our ambassador at Madrid when Isabella was young\r
+and Prim unthought of, but had retired from the diplomatic service in a\r
+capricious moment of annoyance on not being offered the Embassy at\r
+Paris, a post to which he considered that he was fully entitled by\r
+reason of his birth, his indolence, the good English of his dispatches,\r
+and his inordinate passion for pleasure.  The son, who had been his\r
+father's secretary, had resigned along with his chief, somewhat\r
+foolishly as was thought at the time, and on succeeding some months\r
+later to the title, had set himself to the serious study of the great\r
+aristocratic art of doing absolutely nothing.  He had two large town\r
+houses, but preferred to live in chambers as it was less trouble, and\r
+took most of his meals at his club.  He paid some attention to the\r
+management of his collieries in the Midland counties, excusing himself\r
+for this taint of industry on the ground that the one advantage of\r
+having coal was that it enabled a gentleman to afford the decency of\r
+burning wood on his own hearth.  In politics he was a Tory, except when\r
+the Tories were in office, during which period he roundly abused them\r
+for being a pack of Radicals.  He was a hero to his valet, who bullied\r
+him, and a terror to most of his relations, whom he bullied in turn.\r
+Only England could have produced him, and he always said that the\r
+country was going to the dogs.  His principles were out of date, but\r
+there was a good deal to be said for his prejudices.\r
+\r
+When Lord Henry entered the room, he found his uncle sitting in a rough\r
+shooting-coat, smoking a cheroot and grumbling over _The Times_.  "Well,\r
+Harry," said the old gentleman, "what brings you out so early?  I\r
+thought you dandies never got up till two, and were not visible till\r
+five."\r
+\r
+"Pure family affection, I assure you, Uncle George.  I want to get\r
+something out of you."\r
+\r
+"Money, I suppose," said Lord Fermor, making a wry face.  "Well, sit\r
+down and tell me all about it.  Young people, nowadays, imagine that\r
+money is everything."\r
+\r
+"Yes," murmured Lord Henry, settling his button-hole in his coat; "and\r
+when they grow older they know it.  But I don't want money.  It is only\r
+people who pay their bills who want that, Uncle George, and I never pay\r
+mine.  Credit is the capital of a younger son, and one lives charmingly\r
+upon it.  Besides, I always deal with Dartmoor's tradesmen, and\r
+consequently they never bother me.  What I want is information:  not\r
+useful information, of course; useless information."\r
+\r
+"Well, I can tell you anything that is in an English Blue Book, Harry,\r
+although those fellows nowadays write a lot of nonsense.  When I was in\r
+the Diplomatic, things were much better.  But I hear they let them in\r
+now by examination.  What can you expect?  Examinations, sir, are pure\r
+humbug from beginning to end.  If a man is a gentleman, he knows quite\r
+enough, and if he is not a gentleman, whatever he knows is bad for him."\r
+\r
+"Mr. Dorian Gray does not belong to Blue Books, Uncle George," said\r
+Lord Henry languidly.\r
+\r
+"Mr. Dorian Gray?  Who is he?" asked Lord Fermor, knitting his bushy\r
+white eyebrows.\r
+\r
+"That is what I have come to learn, Uncle George.  Or rather, I know\r
+who he is.  He is the last Lord Kelso's grandson.  His mother was a\r
+Devereux, Lady Margaret Devereux.  I want you to tell me about his\r
+mother.  What was she like?  Whom did she marry?  You have known nearly\r
+everybody in your time, so you might have known her.  I am very much\r
+interested in Mr. Gray at present.  I have only just met him."\r
+\r
+"Kelso's grandson!" echoed the old gentleman.  "Kelso's grandson! ...\r
+Of course.... I knew his mother intimately.  I believe I was at her\r
+christening.  She was an extraordinarily beautiful girl, Margaret\r
+Devereux, and made all the men frantic by running away with a penniless\r
+young fellow--a mere nobody, sir, a subaltern in a foot regiment, or\r
+something of that kind.  Certainly.  I remember the whole thing as if\r
+it happened yesterday.  The poor chap was killed in a duel at Spa a few\r
+months after the marriage.  There was an ugly story about it.  They\r
+said Kelso got some rascally adventurer, some Belgian brute, to insult\r
+his son-in-law in public--paid him, sir, to do it, paid him--and that\r
+the fellow spitted his man as if he had been a pigeon.  The thing was\r
+hushed up, but, egad, Kelso ate his chop alone at the club for some\r
+time afterwards.  He brought his daughter back with him, I was told,\r
+and she never spoke to him again.  Oh, yes; it was a bad business.  The\r
+girl died, too, died within a year.  So she left a son, did she?  I had\r
+forgotten that.  What sort of boy is he?  If he is like his mother, he\r
+must be a good-looking chap."\r
+\r
+"He is very good-looking," assented Lord Henry.\r
+\r
+"I hope he will fall into proper hands," continued the old man.  "He\r
+should have a pot of money waiting for him if Kelso did the right thing\r
+by him.  His mother had money, too.  All the Selby property came to\r
+her, through her grandfather.  Her grandfather hated Kelso, thought him\r
+a mean dog.  He was, too.  Came to Madrid once when I was there.  Egad,\r
+I was ashamed of him.  The Queen used to ask me about the English noble\r
+who was always quarrelling with the cabmen about their fares.  They\r
+made quite a story of it.  I didn't dare show my face at Court for a\r
+month.  I hope he treated his grandson better than he did the jarvies."\r
+\r
+"I don't know," answered Lord Henry.  "I fancy that the boy will be\r
+well off.  He is not of age yet.  He has Selby, I know.  He told me so.\r
+And ... his mother was very beautiful?"\r
+\r
+"Margaret Devereux was one of the loveliest creatures I ever saw,\r
+Harry.  What on earth induced her to behave as she did, I never could\r
+understand.  She could have married anybody she chose.  Carlington was\r
+mad after her.  She was romantic, though.  All the women of that family\r
+were.  The men were a poor lot, but, egad! the women were wonderful.\r
+Carlington went on his knees to her.  Told me so himself.  She laughed\r
+at him, and there wasn't a girl in London at the time who wasn't after\r
+him.  And by the way, Harry, talking about silly marriages, what is\r
+this humbug your father tells me about Dartmoor wanting to marry an\r
+American?  Ain't English girls good enough for him?"\r
+\r
+"It is rather fashionable to marry Americans just now, Uncle George."\r
+\r
+"I'll back English women against the world, Harry," said Lord Fermor,\r
+striking the table with his fist.\r
+\r
+"The betting is on the Americans."\r
+\r
+"They don't last, I am told," muttered his uncle.\r
+\r
+"A long engagement exhausts them, but they are capital at a\r
+steeplechase.  They take things flying.  I don't think Dartmoor has a\r
+chance."\r
+\r
+"Who are her people?" grumbled the old gentleman.  "Has she got any?"\r
+\r
+Lord Henry shook his head.  "American girls are as clever at concealing\r
+their parents, as English women are at concealing their past," he said,\r
+rising to go.\r
+\r
+"They are pork-packers, I suppose?"\r
+\r
+"I hope so, Uncle George, for Dartmoor's sake.  I am told that\r
+pork-packing is the most lucrative profession in America, after\r
+politics."\r
+\r
+"Is she pretty?"\r
+\r
+"She behaves as if she was beautiful.  Most American women do.  It is\r
+the secret of their charm."\r
+\r
+"Why can't these American women stay in their own country?  They are\r
+always telling us that it is the paradise for women."\r
+\r
+"It is.  That is the reason why, like Eve, they are so excessively\r
+anxious to get out of it," said Lord Henry.  "Good-bye, Uncle George.\r
+I shall be late for lunch, if I stop any longer.  Thanks for giving me\r
+the information I wanted.  I always like to know everything about my\r
+new friends, and nothing about my old ones."\r
+\r
+"Where are you lunching, Harry?"\r
+\r
+"At Aunt Agatha's. I have asked myself and Mr. Gray.  He is her latest\r
+_protege_."\r
+\r
+"Humph! tell your Aunt Agatha, Harry, not to bother me any more with\r
+her charity appeals.  I am sick of them.  Why, the good woman thinks\r
+that I have nothing to do but to write cheques for her silly fads."\r
+\r
+"All right, Uncle George, I'll tell her, but it won't have any effect.\r
+Philanthropic people lose all sense of humanity.  It is their\r
+distinguishing characteristic."\r
+\r
+The old gentleman growled approvingly and rang the bell for his\r
+servant.  Lord Henry passed up the low arcade into Burlington Street\r
+and turned his steps in the direction of Berkeley Square.\r
+\r
+So that was the story of Dorian Gray's parentage.  Crudely as it had\r
+been told to him, it had yet stirred him by its suggestion of a\r
+strange, almost modern romance.  A beautiful woman risking everything\r
+for a mad passion.  A few wild weeks of happiness cut short by a\r
+hideous, treacherous crime.  Months of voiceless agony, and then a\r
+child born in pain.  The mother snatched away by death, the boy left to\r
+solitude and the tyranny of an old and loveless man.  Yes; it was an\r
+interesting background.  It posed the lad, made him more perfect, as it\r
+were.  Behind every exquisite thing that existed, there was something\r
+tragic.  Worlds had to be in travail, that the meanest flower might\r
+blow.... And how charming he had been at dinner the night before, as\r
+with startled eyes and lips parted in frightened pleasure he had sat\r
+opposite to him at the club, the red candleshades staining to a richer\r
+rose the wakening wonder of his face.  Talking to him was like playing\r
+upon an exquisite violin.  He answered to every touch and thrill of the\r
+bow.... There was something terribly enthralling in the exercise of\r
+influence.  No other activity was like it.  To project one's soul into\r
+some gracious form, and let it tarry there for a moment; to hear one's\r
+own intellectual views echoed back to one with all the added music of\r
+passion and youth; to convey one's temperament into another as though\r
+it were a subtle fluid or a strange perfume: there was a real joy in\r
+that--perhaps the most satisfying joy left to us in an age so limited\r
+and vulgar as our own, an age grossly carnal in its pleasures, and\r
+grossly common in its aims.... He was a marvellous type, too, this lad,\r
+whom by so curious a chance he had met in Basil's studio, or could be\r
+fashioned into a marvellous type, at any rate.  Grace was his, and the\r
+white purity of boyhood, and beauty such as old Greek marbles kept for\r
+us.  There was nothing that one could not do with him.  He could be\r
+made a Titan or a toy.  What a pity it was that such beauty was\r
+destined to fade! ...  And Basil?  From a psychological point of view,\r
+how interesting he was!  The new manner in art, the fresh mode of\r
+looking at life, suggested so strangely by the merely visible presence\r
+of one who was unconscious of it all; the silent spirit that dwelt in\r
+dim woodland, and walked unseen in open field, suddenly showing\r
+herself, Dryadlike and not afraid, because in his soul who sought for\r
+her there had been wakened that wonderful vision to which alone are\r
+wonderful things revealed; the mere shapes and patterns of things\r
+becoming, as it were, refined, and gaining a kind of symbolical value,\r
+as though they were themselves patterns of some other and more perfect\r
+form whose shadow they made real:  how strange it all was!  He\r
+remembered something like it in history.  Was it not Plato, that artist\r
+in thought, who had first analyzed it?  Was it not Buonarotti who had\r
+carved it in the coloured marbles of a sonnet-sequence? But in our own\r
+century it was strange.... Yes; he would try to be to Dorian Gray\r
+what, without knowing it, the lad was to the painter who had fashioned\r
+the wonderful portrait.  He would seek to dominate him--had already,\r
+indeed, half done so.  He would make that wonderful spirit his own.\r
+There was something fascinating in this son of love and death.\r
+\r
+Suddenly he stopped and glanced up at the houses.  He found that he had\r
+passed his aunt's some distance, and, smiling to himself, turned back.\r
+When he entered the somewhat sombre hall, the butler told him that they\r
+had gone in to lunch.  He gave one of the footmen his hat and stick and\r
+passed into the dining-room.\r
+\r
+"Late as usual, Harry," cried his aunt, shaking her head at him.\r
+\r
+He invented a facile excuse, and having taken the vacant seat next to\r
+her, looked round to see who was there.  Dorian bowed to him shyly from\r
+the end of the table, a flush of pleasure stealing into his cheek.\r
+Opposite was the Duchess of Harley, a lady of admirable good-nature and\r
+good temper, much liked by every one who knew her, and of those ample\r
+architectural proportions that in women who are not duchesses are\r
+described by contemporary historians as stoutness.  Next to her sat, on\r
+her right, Sir Thomas Burdon, a Radical member of Parliament, who\r
+followed his leader in public life and in private life followed the\r
+best cooks, dining with the Tories and thinking with the Liberals, in\r
+accordance with a wise and well-known rule.  The post on her left was\r
+occupied by Mr. Erskine of Treadley, an old gentleman of considerable\r
+charm and culture, who had fallen, however, into bad habits of silence,\r
+having, as he explained once to Lady Agatha, said everything that he\r
+had to say before he was thirty.  His own neighbour was Mrs. Vandeleur,\r
+one of his aunt's oldest friends, a perfect saint amongst women, but so\r
+dreadfully dowdy that she reminded one of a badly bound hymn-book.\r
+Fortunately for him she had on the other side Lord Faudel, a most\r
+intelligent middle-aged mediocrity, as bald as a ministerial statement\r
+in the House of Commons, with whom she was conversing in that intensely\r
+earnest manner which is the one unpardonable error, as he remarked once\r
+himself, that all really good people fall into, and from which none of\r
+them ever quite escape.\r
+\r
+"We are talking about poor Dartmoor, Lord Henry," cried the duchess,\r
+nodding pleasantly to him across the table.  "Do you think he will\r
+really marry this fascinating young person?"\r
+\r
+"I believe she has made up her mind to propose to him, Duchess."\r
+\r
+"How dreadful!" exclaimed Lady Agatha.  "Really, some one should\r
+interfere."\r
+\r
+"I am told, on excellent authority, that her father keeps an American\r
+dry-goods store," said Sir Thomas Burdon, looking supercilious.\r
+\r
+"My uncle has already suggested pork-packing, Sir Thomas."\r
+\r
+"Dry-goods! What are American dry-goods?" asked the duchess, raising\r
+her large hands in wonder and accentuating the verb.\r
+\r
+"American novels," answered Lord Henry, helping himself to some quail.\r
+\r
+The duchess looked puzzled.\r
+\r
+"Don't mind him, my dear," whispered Lady Agatha.  "He never means\r
+anything that he says."\r
+\r
+"When America was discovered," said the Radical member--and he began to\r
+give some wearisome facts.  Like all people who try to exhaust a\r
+subject, he exhausted his listeners.  The duchess sighed and exercised\r
+her privilege of interruption.  "I wish to goodness it never had been\r
+discovered at all!" she exclaimed.  "Really, our girls have no chance\r
+nowadays.  It is most unfair."\r
+\r
+"Perhaps, after all, America never has been discovered," said Mr.\r
+Erskine; "I myself would say that it had merely been detected."\r
+\r
+"Oh! but I have seen specimens of the inhabitants," answered the\r
+duchess vaguely.  "I must confess that most of them are extremely\r
+pretty.  And they dress well, too.  They get all their dresses in\r
+Paris.  I wish I could afford to do the same."\r
+\r
+"They say that when good Americans die they go to Paris," chuckled Sir\r
+Thomas, who had a large wardrobe of Humour's cast-off clothes.\r
+\r
+"Really!  And where do bad Americans go to when they die?" inquired the\r
+duchess.\r
+\r
+"They go to America," murmured Lord Henry.\r
+\r
+Sir Thomas frowned.  "I am afraid that your nephew is prejudiced\r
+against that great country," he said to Lady Agatha.  "I have travelled\r
+all over it in cars provided by the directors, who, in such matters,\r
+are extremely civil.  I assure you that it is an education to visit it."\r
+\r
+"But must we really see Chicago in order to be educated?" asked Mr.\r
+Erskine plaintively.  "I don't feel up to the journey."\r
+\r
+Sir Thomas waved his hand.  "Mr. Erskine of Treadley has the world on\r
+his shelves. We practical men like to see things, not to read about\r
+them. The Americans are an extremely interesting people. They are\r
+absolutely reasonable. I think that is their distinguishing\r
+characteristic. Yes, Mr. Erskine, an absolutely reasonable people. I\r
+assure you there is no nonsense about the Americans."\r
+\r
+"How dreadful!" cried Lord Henry.  "I can stand brute force, but brute\r
+reason is quite unbearable.  There is something unfair about its use.\r
+It is hitting below the intellect."\r
+\r
+"I do not understand you," said Sir Thomas, growing rather red.\r
+\r
+"I do, Lord Henry," murmured Mr. Erskine, with a smile.\r
+\r
+"Paradoxes are all very well in their way...." rejoined the baronet.\r
+\r
+"Was that a paradox?" asked Mr. Erskine.  "I did not think so.  Perhaps\r
+it was.  Well, the way of paradoxes is the way of truth.  To test\r
+reality we must see it on the tight rope.  When the verities become\r
+acrobats, we can judge them."\r
+\r
+"Dear me!" said Lady Agatha, "how you men argue!  I am sure I never can\r
+make out what you are talking about.  Oh!  Harry, I am quite vexed with\r
+you.  Why do you try to persuade our nice Mr. Dorian Gray to give up\r
+the East End?  I assure you he would be quite invaluable.  They would\r
+love his playing."\r
+\r
+"I want him to play to me," cried Lord Henry, smiling, and he looked\r
+down the table and caught a bright answering glance.\r
+\r
+"But they are so unhappy in Whitechapel," continued Lady Agatha.\r
+\r
+"I can sympathize with everything except suffering," said Lord Henry,\r
+shrugging his shoulders.  "I cannot sympathize with that.  It is too\r
+ugly, too horrible, too distressing.  There is something terribly\r
+morbid in the modern sympathy with pain.  One should sympathize with\r
+the colour, the beauty, the joy of life.  The less said about life's\r
+sores, the better."\r
+\r
+"Still, the East End is a very important problem," remarked Sir Thomas\r
+with a grave shake of the head.\r
+\r
+"Quite so," answered the young lord.  "It is the problem of slavery,\r
+and we try to solve it by amusing the slaves."\r
+\r
+The politician looked at him keenly.  "What change do you propose,\r
+then?" he asked.\r
+\r
+Lord Henry laughed.  "I don't desire to change anything in England\r
+except the weather," he answered.  "I am quite content with philosophic\r
+contemplation.  But, as the nineteenth century has gone bankrupt\r
+through an over-expenditure of sympathy, I would suggest that we should\r
+appeal to science to put us straight.  The advantage of the emotions is\r
+that they lead us astray, and the advantage of science is that it is\r
+not emotional."\r
+\r
+"But we have such grave responsibilities," ventured Mrs. Vandeleur\r
+timidly.\r
+\r
+"Terribly grave," echoed Lady Agatha.\r
+\r
+Lord Henry looked over at Mr. Erskine.  "Humanity takes itself too\r
+seriously.  It is the world's original sin.  If the caveman had known\r
+how to laugh, history would have been different."\r
+\r
+"You are really very comforting," warbled the duchess.  "I have always\r
+felt rather guilty when I came to see your dear aunt, for I take no\r
+interest at all in the East End.  For the future I shall be able to\r
+look her in the face without a blush."\r
+\r
+"A blush is very becoming, Duchess," remarked Lord Henry.\r
+\r
+"Only when one is young," she answered.  "When an old woman like myself\r
+blushes, it is a very bad sign.  Ah!  Lord Henry, I wish you would tell\r
+me how to become young again."\r
+\r
+He thought for a moment.  "Can you remember any great error that you\r
+committed in your early days, Duchess?" he asked, looking at her across\r
+the table.\r
+\r
+"A great many, I fear," she cried.\r
+\r
+"Then commit them over again," he said gravely.  "To get back one's\r
+youth, one has merely to repeat one's follies."\r
+\r
+"A delightful theory!" she exclaimed.  "I must put it into practice."\r
+\r
+"A dangerous theory!" came from Sir Thomas's tight lips.  Lady Agatha\r
+shook her head, but could not help being amused.  Mr. Erskine listened.\r
+\r
+"Yes," he continued, "that is one of the great secrets of life.\r
+Nowadays most people die of a sort of creeping common sense, and\r
+discover when it is too late that the only things one never regrets are\r
+one's mistakes."\r
+\r
+A laugh ran round the table.\r
+\r
+He played with the idea and grew wilful; tossed it into the air and\r
+transformed it; let it escape and recaptured it; made it iridescent\r
+with fancy and winged it with paradox.  The praise of folly, as he went\r
+on, soared into a philosophy, and philosophy herself became young, and\r
+catching the mad music of pleasure, wearing, one might fancy, her\r
+wine-stained robe and wreath of ivy, danced like a Bacchante over the\r
+hills of life, and mocked the slow Silenus for being sober.  Facts fled\r
+before her like frightened forest things.  Her white feet trod the huge\r
+press at which wise Omar sits, till the seething grape-juice rose round\r
+her bare limbs in waves of purple bubbles, or crawled in red foam over\r
+the vat's black, dripping, sloping sides.  It was an extraordinary\r
+improvisation.  He felt that the eyes of Dorian Gray were fixed on him,\r
+and the consciousness that amongst his audience there was one whose\r
+temperament he wished to fascinate seemed to give his wit keenness and\r
+to lend colour to his imagination.  He was brilliant, fantastic,\r
+irresponsible.  He charmed his listeners out of themselves, and they\r
+followed his pipe, laughing.  Dorian Gray never took his gaze off him,\r
+but sat like one under a spell, smiles chasing each other over his lips\r
+and wonder growing grave in his darkening eyes.\r
+\r
+At last, liveried in the costume of the age, reality entered the room\r
+in the shape of a servant to tell the duchess that her carriage was\r
+waiting.  She wrung her hands in mock despair.  "How annoying!" she\r
+cried.  "I must go.  I have to call for my husband at the club, to take\r
+him to some absurd meeting at Willis's Rooms, where he is going to be\r
+in the chair.  If I am late he is sure to be furious, and I couldn't\r
+have a scene in this bonnet.  It is far too fragile.  A harsh word\r
+would ruin it.  No, I must go, dear Agatha.  Good-bye, Lord Henry, you\r
+are quite delightful and dreadfully demoralizing.  I am sure I don't\r
+know what to say about your views.  You must come and dine with us some\r
+night.  Tuesday?  Are you disengaged Tuesday?"\r
+\r
+"For you I would throw over anybody, Duchess," said Lord Henry with a\r
+bow.\r
+\r
+"Ah! that is very nice, and very wrong of you," she cried; "so mind you\r
+come"; and she swept out of the room, followed by Lady Agatha and the\r
+other ladies.\r
+\r
+When Lord Henry had sat down again, Mr. Erskine moved round, and taking\r
+a chair close to him, placed his hand upon his arm.\r
+\r
+"You talk books away," he said; "why don't you write one?"\r
+\r
+"I am too fond of reading books to care to write them, Mr. Erskine.  I\r
+should like to write a novel certainly, a novel that would be as lovely\r
+as a Persian carpet and as unreal.  But there is no literary public in\r
+England for anything except newspapers, primers, and encyclopaedias.\r
+Of all people in the world the English have the least sense of the\r
+beauty of literature."\r
+\r
+"I fear you are right," answered Mr. Erskine.  "I myself used to have\r
+literary ambitions, but I gave them up long ago.  And now, my dear\r
+young friend, if you will allow me to call you so, may I ask if you\r
+really meant all that you said to us at lunch?"\r
+\r
+"I quite forget what I said," smiled Lord Henry.  "Was it all very bad?"\r
+\r
+"Very bad indeed.  In fact I consider you extremely dangerous, and if\r
+anything happens to our good duchess, we shall all look on you as being\r
+primarily responsible.  But I should like to talk to you about life.\r
+The generation into which I was born was tedious.  Some day, when you\r
+are tired of London, come down to Treadley and expound to me your\r
+philosophy of pleasure over some admirable Burgundy I am fortunate\r
+enough to possess."\r
+\r
+"I shall be charmed.  A visit to Treadley would be a great privilege.\r
+It has a perfect host, and a perfect library."\r
+\r
+"You will complete it," answered the old gentleman with a courteous\r
+bow.  "And now I must bid good-bye to your excellent aunt.  I am due at\r
+the Athenaeum.  It is the hour when we sleep there."\r
+\r
+"All of you, Mr. Erskine?"\r
+\r
+"Forty of us, in forty arm-chairs. We are practising for an English\r
+Academy of Letters."\r
+\r
+Lord Henry laughed and rose.  "I am going to the park," he cried.\r
+\r
+As he was passing out of the door, Dorian Gray touched him on the arm.\r
+"Let me come with you," he murmured.\r
+\r
+"But I thought you had promised Basil Hallward to go and see him,"\r
+answered Lord Henry.\r
+\r
+"I would sooner come with you; yes, I feel I must come with you.  Do\r
+let me.  And you will promise to talk to me all the time?  No one talks\r
+so wonderfully as you do."\r
+\r
+"Ah!  I have talked quite enough for to-day," said Lord Henry, smiling.\r
+"All I want now is to look at life.  You may come and look at it with\r
+me, if you care to."\r
+\r
+\r
+\r
+CHAPTER 4\r
+\r
+One afternoon, a month later, Dorian Gray was reclining in a luxurious\r
+arm-chair, in the little library of Lord Henry's house in Mayfair.  It\r
+was, in its way, a very charming room, with its high panelled\r
+wainscoting of olive-stained oak, its cream-coloured frieze and ceiling\r
+of raised plasterwork, and its brickdust felt carpet strewn with silk,\r
+long-fringed Persian rugs.  On a tiny satinwood table stood a statuette\r
+by Clodion, and beside it lay a copy of Les Cent Nouvelles, bound for\r
+Margaret of Valois by Clovis Eve and powdered with the gilt daisies\r
+that Queen had selected for her device.  Some large blue china jars and\r
+parrot-tulips were ranged on the mantelshelf, and through the small\r
+leaded panes of the window streamed the apricot-coloured light of a\r
+summer day in London.\r
+\r
+Lord Henry had not yet come in.  He was always late on principle, his\r
+principle being that punctuality is the thief of time.  So the lad was\r
+looking rather sulky, as with listless fingers he turned over the pages\r
+of an elaborately illustrated edition of Manon Lescaut that he had\r
+found in one of the book-cases. The formal monotonous ticking of the\r
+Louis Quatorze clock annoyed him.  Once or twice he thought of going\r
+away.\r
+\r
+At last he heard a step outside, and the door opened.  "How late you\r
+are, Harry!" he murmured.\r
+\r
+"I am afraid it is not Harry, Mr. Gray," answered a shrill voice.\r
+\r
+He glanced quickly round and rose to his feet.  "I beg your pardon.  I\r
+thought--"\r
+\r
+"You thought it was my husband.  It is only his wife.  You must let me\r
+introduce myself.  I know you quite well by your photographs.  I think\r
+my husband has got seventeen of them."\r
+\r
+"Not seventeen, Lady Henry?"\r
+\r
+"Well, eighteen, then.  And I saw you with him the other night at the\r
+opera."  She laughed nervously as she spoke, and watched him with her\r
+vague forget-me-not eyes.  She was a curious woman, whose dresses\r
+always looked as if they had been designed in a rage and put on in a\r
+tempest.  She was usually in love with somebody, and, as her passion\r
+was never returned, she had kept all her illusions.  She tried to look\r
+picturesque, but only succeeded in being untidy.  Her name was\r
+Victoria, and she had a perfect mania for going to church.\r
+\r
+"That was at Lohengrin, Lady Henry, I think?"\r
+\r
+"Yes; it was at dear Lohengrin.  I like Wagner's music better than\r
+anybody's. It is so loud that one can talk the whole time without other\r
+people hearing what one says.  That is a great advantage, don't you\r
+think so, Mr. Gray?"\r
+\r
+The same nervous staccato laugh broke from her thin lips, and her\r
+fingers began to play with a long tortoise-shell paper-knife.\r
+\r
+Dorian smiled and shook his head:  "I am afraid I don't think so, Lady\r
+Henry.  I never talk during music--at least, during good music.  If one\r
+hears bad music, it is one's duty to drown it in conversation."\r
+\r
+"Ah! that is one of Harry's views, isn't it, Mr. Gray?  I always hear\r
+Harry's views from his friends.  It is the only way I get to know of\r
+them.  But you must not think I don't like good music.  I adore it, but\r
+I am afraid of it.  It makes me too romantic.  I have simply worshipped\r
+pianists--two at a time, sometimes, Harry tells me.  I don't know what\r
+it is about them.  Perhaps it is that they are foreigners.  They all\r
+are, ain't they?  Even those that are born in England become foreigners\r
+after a time, don't they?  It is so clever of them, and such a\r
+compliment to art.  Makes it quite cosmopolitan, doesn't it?  You have\r
+never been to any of my parties, have you, Mr. Gray?  You must come.  I\r
+can't afford orchids, but I spare no expense in foreigners.  They make\r
+one's rooms look so picturesque.  But here is Harry!  Harry, I came in\r
+to look for you, to ask you something--I forget what it was--and I\r
+found Mr. Gray here.  We have had such a pleasant chat about music.  We\r
+have quite the same ideas.  No; I think our ideas are quite different.\r
+But he has been most pleasant.  I am so glad I've seen him."\r
+\r
+"I am charmed, my love, quite charmed," said Lord Henry, elevating his\r
+dark, crescent-shaped eyebrows and looking at them both with an amused\r
+smile.  "So sorry I am late, Dorian.  I went to look after a piece of\r
+old brocade in Wardour Street and had to bargain for hours for it.\r
+Nowadays people know the price of everything and the value of nothing."\r
+\r
+"I am afraid I must be going," exclaimed Lady Henry, breaking an\r
+awkward silence with her silly sudden laugh.  "I have promised to drive\r
+with the duchess.  Good-bye, Mr. Gray.  Good-bye, Harry.  You are\r
+dining out, I suppose?  So am I. Perhaps I shall see you at Lady\r
+Thornbury's."\r
+\r
+"I dare say, my dear," said Lord Henry, shutting the door behind her\r
+as, looking like a bird of paradise that had been out all night in the\r
+rain, she flitted out of the room, leaving a faint odour of\r
+frangipanni.  Then he lit a cigarette and flung himself down on the\r
+sofa.\r
+\r
+"Never marry a woman with straw-coloured hair, Dorian," he said after a\r
+few puffs.\r
+\r
+"Why, Harry?"\r
+\r
+"Because they are so sentimental."\r
+\r
+"But I like sentimental people."\r
+\r
+"Never marry at all, Dorian.  Men marry because they are tired; women,\r
+because they are curious:  both are disappointed."\r
+\r
+"I don't think I am likely to marry, Harry.  I am too much in love.\r
+That is one of your aphorisms.  I am putting it into practice, as I do\r
+everything that you say."\r
+\r
+"Who are you in love with?" asked Lord Henry after a pause.\r
+\r
+"With an actress," said Dorian Gray, blushing.\r
+\r
+Lord Henry shrugged his shoulders.  "That is a rather commonplace\r
+_debut_."\r
+\r
+"You would not say so if you saw her, Harry."\r
+\r
+"Who is she?"\r
+\r
+"Her name is Sibyl Vane."\r
+\r
+"Never heard of her."\r
+\r
+"No one has.  People will some day, however.  She is a genius."\r
+\r
+"My dear boy, no woman is a genius.  Women are a decorative sex.  They\r
+never have anything to say, but they say it charmingly.  Women\r
+represent the triumph of matter over mind, just as men represent the\r
+triumph of mind over morals."\r
+\r
+"Harry, how can you?"\r
+\r
+"My dear Dorian, it is quite true.  I am analysing women at present, so\r
+I ought to know.  The subject is not so abstruse as I thought it was.\r
+I find that, ultimately, there are only two kinds of women, the plain\r
+and the coloured.  The plain women are very useful.  If you want to\r
+gain a reputation for respectability, you have merely to take them down\r
+to supper.  The other women are very charming.  They commit one\r
+mistake, however.  They paint in order to try and look young.  Our\r
+grandmothers painted in order to try and talk brilliantly.  _Rouge_ and\r
+_esprit_ used to go together.  That is all over now.  As long as a woman\r
+can look ten years younger than her own daughter, she is perfectly\r
+satisfied.  As for conversation, there are only five women in London\r
+worth talking to, and two of these can't be admitted into decent\r
+society.  However, tell me about your genius.  How long have you known\r
+her?"\r
+\r
+"Ah!  Harry, your views terrify me."\r
+\r
+"Never mind that.  How long have you known her?"\r
+\r
+"About three weeks."\r
+\r
+"And where did you come across her?"\r
+\r
+"I will tell you, Harry, but you mustn't be unsympathetic about it.\r
+After all, it never would have happened if I had not met you.  You\r
+filled me with a wild desire to know everything about life.  For days\r
+after I met you, something seemed to throb in my veins.  As I lounged\r
+in the park, or strolled down Piccadilly, I used to look at every one\r
+who passed me and wonder, with a mad curiosity, what sort of lives they\r
+led.  Some of them fascinated me.  Others filled me with terror.  There\r
+was an exquisite poison in the air.  I had a passion for sensations....\r
+Well, one evening about seven o'clock, I determined to go out in search\r
+of some adventure.  I felt that this grey monstrous London of ours,\r
+with its myriads of people, its sordid sinners, and its splendid sins,\r
+as you once phrased it, must have something in store for me.  I fancied\r
+a thousand things.  The mere danger gave me a sense of delight.  I\r
+remembered what you had said to me on that wonderful evening when we\r
+first dined together, about the search for beauty being the real secret\r
+of life.  I don't know what I expected, but I went out and wandered\r
+eastward, soon losing my way in a labyrinth of grimy streets and black\r
+grassless squares.  About half-past eight I passed by an absurd little\r
+theatre, with great flaring gas-jets and gaudy play-bills.  A hideous\r
+Jew, in the most amazing waistcoat I ever beheld in my life, was\r
+standing at the entrance, smoking a vile cigar.  He had greasy\r
+ringlets, and an enormous diamond blazed in the centre of a soiled\r
+shirt. 'Have a box, my Lord?' he said, when he saw me, and he took off\r
+his hat with an air of gorgeous servility.  There was something about\r
+him, Harry, that amused me.  He was such a monster.  You will laugh at\r
+me, I know, but I really went in and paid a whole guinea for the\r
+stage-box. To the present day I can't make out why I did so; and yet if\r
+I hadn't--my dear Harry, if I hadn't--I should have missed the greatest\r
+romance of my life.  I see you are laughing.  It is horrid of you!"\r
+\r
+"I am not laughing, Dorian; at least I am not laughing at you.  But you\r
+should not say the greatest romance of your life.  You should say the\r
+first romance of your life.  You will always be loved, and you will\r
+always be in love with love.  A _grande passion_ is the privilege of\r
+people who have nothing to do.  That is the one use of the idle classes\r
+of a country.  Don't be afraid.  There are exquisite things in store\r
+for you.  This is merely the beginning."\r
+\r
+"Do you think my nature so shallow?" cried Dorian Gray angrily.\r
+\r
+"No; I think your nature so deep."\r
+\r
+"How do you mean?"\r
+\r
+"My dear boy, the people who love only once in their lives are really\r
+the shallow people.  What they call their loyalty, and their fidelity,\r
+I call either the lethargy of custom or their lack of imagination.\r
+Faithfulness is to the emotional life what consistency is to the life\r
+of the intellect--simply a confession of failure.  Faithfulness!  I\r
+must analyse it some day.  The passion for property is in it.  There\r
+are many things that we would throw away if we were not afraid that\r
+others might pick them up.  But I don't want to interrupt you.  Go on\r
+with your story."\r
+\r
+"Well, I found myself seated in a horrid little private box, with a\r
+vulgar drop-scene staring me in the face.  I looked out from behind the\r
+curtain and surveyed the house.  It was a tawdry affair, all Cupids and\r
+cornucopias, like a third-rate wedding-cake. The gallery and pit were\r
+fairly full, but the two rows of dingy stalls were quite empty, and\r
+there was hardly a person in what I suppose they called the\r
+dress-circle.  Women went about with oranges and ginger-beer, and there\r
+was a terrible consumption of nuts going on."\r
+\r
+"It must have been just like the palmy days of the British drama."\r
+\r
+"Just like, I should fancy, and very depressing.  I began to wonder\r
+what on earth I should do when I caught sight of the play-bill.  What\r
+do you think the play was, Harry?"\r
+\r
+"I should think 'The Idiot Boy', or 'Dumb but Innocent'.  Our fathers\r
+used to like that sort of piece, I believe.  The longer I live, Dorian,\r
+the more keenly I feel that whatever was good enough for our fathers is\r
+not good enough for us.  In art, as in politics, _les grandperes ont\r
+toujours tort_."\r
+\r
+"This play was good enough for us, Harry.  It was Romeo and Juliet.  I\r
+must admit that I was rather annoyed at the idea of seeing Shakespeare\r
+done in such a wretched hole of a place.  Still, I felt interested, in\r
+a sort of way.  At any rate, I determined to wait for the first act.\r
+There was a dreadful orchestra, presided over by a young Hebrew who sat\r
+at a cracked piano, that nearly drove me away, but at last the\r
+drop-scene was drawn up and the play began.  Romeo was a stout elderly\r
+gentleman, with corked eyebrows, a husky tragedy voice, and a figure\r
+like a beer-barrel. Mercutio was almost as bad.  He was played by the\r
+low-comedian, who had introduced gags of his own and was on most\r
+friendly terms with the pit.  They were both as grotesque as the\r
+scenery, and that looked as if it had come out of a country-booth. But\r
+Juliet!  Harry, imagine a girl, hardly seventeen years of age, with a\r
+little, flowerlike face, a small Greek head with plaited coils of\r
+dark-brown hair, eyes that were violet wells of passion, lips that were\r
+like the petals of a rose.  She was the loveliest thing I had ever seen\r
+in my life.  You said to me once that pathos left you unmoved, but that\r
+beauty, mere beauty, could fill your eyes with tears.  I tell you,\r
+Harry, I could hardly see this girl for the mist of tears that came\r
+across me.  And her voice--I never heard such a voice.  It was very low\r
+at first, with deep mellow notes that seemed to fall singly upon one's\r
+ear.  Then it became a little louder, and sounded like a flute or a\r
+distant hautboy.  In the garden-scene it had all the tremulous ecstasy\r
+that one hears just before dawn when nightingales are singing.  There\r
+were moments, later on, when it had the wild passion of violins.  You\r
+know how a voice can stir one.  Your voice and the voice of Sibyl Vane\r
+are two things that I shall never forget.  When I close my eyes, I hear\r
+them, and each of them says something different.  I don't know which to\r
+follow.  Why should I not love her?  Harry, I do love her.  She is\r
+everything to me in life.  Night after night I go to see her play.  One\r
+evening she is Rosalind, and the next evening she is Imogen.  I have\r
+seen her die in the gloom of an Italian tomb, sucking the poison from\r
+her lover's lips.  I have watched her wandering through the forest of\r
+Arden, disguised as a pretty boy in hose and doublet and dainty cap.\r
+She has been mad, and has come into the presence of a guilty king, and\r
+given him rue to wear and bitter herbs to taste of.  She has been\r
+innocent, and the black hands of jealousy have crushed her reedlike\r
+throat.  I have seen her in every age and in every costume.  Ordinary\r
+women never appeal to one's imagination.  They are limited to their\r
+century.  No glamour ever transfigures them.  One knows their minds as\r
+easily as one knows their bonnets.  One can always find them.  There is\r
+no mystery in any of them.  They ride in the park in the morning and\r
+chatter at tea-parties in the afternoon.  They have their stereotyped\r
+smile and their fashionable manner.  They are quite obvious.  But an\r
+actress!  How different an actress is!  Harry! why didn't you tell me\r
+that the only thing worth loving is an actress?"\r
+\r
+"Because I have loved so many of them, Dorian."\r
+\r
+"Oh, yes, horrid people with dyed hair and painted faces."\r
+\r
+"Don't run down dyed hair and painted faces.  There is an extraordinary\r
+charm in them, sometimes," said Lord Henry.\r
+\r
+"I wish now I had not told you about Sibyl Vane."\r
+\r
+"You could not have helped telling me, Dorian.  All through your life\r
+you will tell me everything you do."\r
+\r
+"Yes, Harry, I believe that is true.  I cannot help telling you things.\r
+You have a curious influence over me.  If I ever did a crime, I would\r
+come and confess it to you.  You would understand me."\r
+\r
+"People like you--the wilful sunbeams of life--don't commit crimes,\r
+Dorian.  But I am much obliged for the compliment, all the same.  And\r
+now tell me--reach me the matches, like a good boy--thanks--what are\r
+your actual relations with Sibyl Vane?"\r
+\r
+Dorian Gray leaped to his feet, with flushed cheeks and burning eyes.\r
+"Harry!  Sibyl Vane is sacred!"\r
+\r
+"It is only the sacred things that are worth touching, Dorian," said\r
+Lord Henry, with a strange touch of pathos in his voice.  "But why\r
+should you be annoyed?  I suppose she will belong to you some day.\r
+When one is in love, one always begins by deceiving one's self, and one\r
+always ends by deceiving others.  That is what the world calls a\r
+romance.  You know her, at any rate, I suppose?"\r
+\r
+"Of course I know her.  On the first night I was at the theatre, the\r
+horrid old Jew came round to the box after the performance was over and\r
+offered to take me behind the scenes and introduce me to her.  I was\r
+furious with him, and told him that Juliet had been dead for hundreds\r
+of years and that her body was lying in a marble tomb in Verona.  I\r
+think, from his blank look of amazement, that he was under the\r
+impression that I had taken too much champagne, or something."\r
+\r
+"I am not surprised."\r
+\r
+"Then he asked me if I wrote for any of the newspapers.  I told him I\r
+never even read them.  He seemed terribly disappointed at that, and\r
+confided to me that all the dramatic critics were in a conspiracy\r
+against him, and that they were every one of them to be bought."\r
+\r
+"I should not wonder if he was quite right there.  But, on the other\r
+hand, judging from their appearance, most of them cannot be at all\r
+expensive."\r
+\r
+"Well, he seemed to think they were beyond his means," laughed Dorian.\r
+"By this time, however, the lights were being put out in the theatre,\r
+and I had to go.  He wanted me to try some cigars that he strongly\r
+recommended.  I declined.  The next night, of course, I arrived at the\r
+place again.  When he saw me, he made me a low bow and assured me that\r
+I was a munificent patron of art.  He was a most offensive brute,\r
+though he had an extraordinary passion for Shakespeare.  He told me\r
+once, with an air of pride, that his five bankruptcies were entirely\r
+due to 'The Bard,' as he insisted on calling him.  He seemed to think\r
+it a distinction."\r
+\r
+"It was a distinction, my dear Dorian--a great distinction.  Most\r
+people become bankrupt through having invested too heavily in the prose\r
+of life.  To have ruined one's self over poetry is an honour.  But when\r
+did you first speak to Miss Sibyl Vane?"\r
+\r
+"The third night.  She had been playing Rosalind.  I could not help\r
+going round.  I had thrown her some flowers, and she had looked at\r
+me--at least I fancied that she had.  The old Jew was persistent.  He\r
+seemed determined to take me behind, so I consented.  It was curious my\r
+not wanting to know her, wasn't it?"\r
+\r
+"No; I don't think so."\r
+\r
+"My dear Harry, why?"\r
+\r
+"I will tell you some other time.  Now I want to know about the girl."\r
+\r
+"Sibyl?  Oh, she was so shy and so gentle.  There is something of a\r
+child about her.  Her eyes opened wide in exquisite wonder when I told\r
+her what I thought of her performance, and she seemed quite unconscious\r
+of her power.  I think we were both rather nervous.  The old Jew stood\r
+grinning at the doorway of the dusty greenroom, making elaborate\r
+speeches about us both, while we stood looking at each other like\r
+children.  He would insist on calling me 'My Lord,' so I had to assure\r
+Sibyl that I was not anything of the kind.  She said quite simply to\r
+me, 'You look more like a prince.  I must call you Prince Charming.'"\r
+\r
+"Upon my word, Dorian, Miss Sibyl knows how to pay compliments."\r
+\r
+"You don't understand her, Harry.  She regarded me merely as a person\r
+in a play.  She knows nothing of life.  She lives with her mother, a\r
+faded tired woman who played Lady Capulet in a sort of magenta\r
+dressing-wrapper on the first night, and looks as if she had seen\r
+better days."\r
+\r
+"I know that look.  It depresses me," murmured Lord Henry, examining\r
+his rings.\r
+\r
+"The Jew wanted to tell me her history, but I said it did not interest\r
+me."\r
+\r
+"You were quite right.  There is always something infinitely mean about\r
+other people's tragedies."\r
+\r
+"Sibyl is the only thing I care about.  What is it to me where she came\r
+from?  From her little head to her little feet, she is absolutely and\r
+entirely divine.  Every night of my life I go to see her act, and every\r
+night she is more marvellous."\r
+\r
+"That is the reason, I suppose, that you never dine with me now.  I\r
+thought you must have some curious romance on hand.  You have; but it\r
+is not quite what I expected."\r
+\r
+"My dear Harry, we either lunch or sup together every day, and I have\r
+been to the opera with you several times," said Dorian, opening his\r
+blue eyes in wonder.\r
+\r
+"You always come dreadfully late."\r
+\r
+"Well, I can't help going to see Sibyl play," he cried, "even if it is\r
+only for a single act.  I get hungry for her presence; and when I think\r
+of the wonderful soul that is hidden away in that little ivory body, I\r
+am filled with awe."\r
+\r
+"You can dine with me to-night, Dorian, can't you?"\r
+\r
+He shook his head.  "To-night she is Imogen," he answered, "and\r
+to-morrow night she will be Juliet."\r
+\r
+"When is she Sibyl Vane?"\r
+\r
+"Never."\r
+\r
+"I congratulate you."\r
+\r
+"How horrid you are!  She is all the great heroines of the world in\r
+one.  She is more than an individual.  You laugh, but I tell you she\r
+has genius.  I love her, and I must make her love me.  You, who know\r
+all the secrets of life, tell me how to charm Sibyl Vane to love me!  I\r
+want to make Romeo jealous.  I want the dead lovers of the world to\r
+hear our laughter and grow sad.  I want a breath of our passion to stir\r
+their dust into consciousness, to wake their ashes into pain.  My God,\r
+Harry, how I worship her!"  He was walking up and down the room as he\r
+spoke.  Hectic spots of red burned on his cheeks.  He was terribly\r
+excited.\r
+\r
+Lord Henry watched him with a subtle sense of pleasure.  How different\r
+he was now from the shy frightened boy he had met in Basil Hallward's\r
+studio!  His nature had developed like a flower, had borne blossoms of\r
+scarlet flame.  Out of its secret hiding-place had crept his soul, and\r
+desire had come to meet it on the way.\r
+\r
+"And what do you propose to do?" said Lord Henry at last.\r
+\r
+"I want you and Basil to come with me some night and see her act.  I\r
+have not the slightest fear of the result.  You are certain to\r
+acknowledge her genius.  Then we must get her out of the Jew's hands.\r
+She is bound to him for three years--at least for two years and eight\r
+months--from the present time.  I shall have to pay him something, of\r
+course.  When all that is settled, I shall take a West End theatre and\r
+bring her out properly.  She will make the world as mad as she has made\r
+me."\r
+\r
+"That would be impossible, my dear boy."\r
+\r
+"Yes, she will.  She has not merely art, consummate art-instinct, in\r
+her, but she has personality also; and you have often told me that it\r
+is personalities, not principles, that move the age."\r
+\r
+"Well, what night shall we go?"\r
+\r
+"Let me see.  To-day is Tuesday.  Let us fix to-morrow. She plays\r
+Juliet to-morrow."\r
+\r
+"All right.  The Bristol at eight o'clock; and I will get Basil."\r
+\r
+"Not eight, Harry, please.  Half-past six.  We must be there before the\r
+curtain rises.  You must see her in the first act, where she meets\r
+Romeo."\r
+\r
+"Half-past six!  What an hour!  It will be like having a meat-tea, or\r
+reading an English novel.  It must be seven.  No gentleman dines before\r
+seven.  Shall you see Basil between this and then?  Or shall I write to\r
+him?"\r
+\r
+"Dear Basil!  I have not laid eyes on him for a week.  It is rather\r
+horrid of me, as he has sent me my portrait in the most wonderful\r
+frame, specially designed by himself, and, though I am a little jealous\r
+of the picture for being a whole month younger than I am, I must admit\r
+that I delight in it.  Perhaps you had better write to him.  I don't\r
+want to see him alone.  He says things that annoy me.  He gives me good\r
+advice."\r
+\r
+Lord Henry smiled.  "People are very fond of giving away what they need\r
+most themselves.  It is what I call the depth of generosity."\r
+\r
+"Oh, Basil is the best of fellows, but he seems to me to be just a bit\r
+of a Philistine.  Since I have known you, Harry, I have discovered\r
+that."\r
+\r
+"Basil, my dear boy, puts everything that is charming in him into his\r
+work.  The consequence is that he has nothing left for life but his\r
+prejudices, his principles, and his common sense.  The only artists I\r
+have ever known who are personally delightful are bad artists.  Good\r
+artists exist simply in what they make, and consequently are perfectly\r
+uninteresting in what they are.  A great poet, a really great poet, is\r
+the most unpoetical of all creatures.  But inferior poets are\r
+absolutely fascinating.  The worse their rhymes are, the more\r
+picturesque they look.  The mere fact of having published a book of\r
+second-rate sonnets makes a man quite irresistible.  He lives the\r
+poetry that he cannot write.  The others write the poetry that they\r
+dare not realize."\r
+\r
+"I wonder is that really so, Harry?" said Dorian Gray, putting some\r
+perfume on his handkerchief out of a large, gold-topped bottle that\r
+stood on the table.  "It must be, if you say it.  And now I am off.\r
+Imogen is waiting for me.  Don't forget about to-morrow. Good-bye."\r
+\r
+As he left the room, Lord Henry's heavy eyelids drooped, and he began\r
+to think.  Certainly few people had ever interested him so much as\r
+Dorian Gray, and yet the lad's mad adoration of some one else caused\r
+him not the slightest pang of annoyance or jealousy.  He was pleased by\r
+it.  It made him a more interesting study.  He had been always\r
+enthralled by the methods of natural science, but the ordinary\r
+subject-matter of that science had seemed to him trivial and of no\r
+import.  And so he had begun by vivisecting himself, as he had ended by\r
+vivisecting others.  Human life--that appeared to him the one thing\r
+worth investigating.  Compared to it there was nothing else of any\r
+value.  It was true that as one watched life in its curious crucible of\r
+pain and pleasure, one could not wear over one's face a mask of glass,\r
+nor keep the sulphurous fumes from troubling the brain and making the\r
+imagination turbid with monstrous fancies and misshapen dreams.  There\r
+were poisons so subtle that to know their properties one had to sicken\r
+of them.  There were maladies so strange that one had to pass through\r
+them if one sought to understand their nature.  And, yet, what a great\r
+reward one received!  How wonderful the whole world became to one!  To\r
+note the curious hard logic of passion, and the emotional coloured life\r
+of the intellect--to observe where they met, and where they separated,\r
+at what point they were in unison, and at what point they were at\r
+discord--there was a delight in that!  What matter what the cost was?\r
+One could never pay too high a price for any sensation.\r
+\r
+He was conscious--and the thought brought a gleam of pleasure into his\r
+brown agate eyes--that it was through certain words of his, musical\r
+words said with musical utterance, that Dorian Gray's soul had turned\r
+to this white girl and bowed in worship before her.  To a large extent\r
+the lad was his own creation.  He had made him premature.  That was\r
+something.  Ordinary people waited till life disclosed to them its\r
+secrets, but to the few, to the elect, the mysteries of life were\r
+revealed before the veil was drawn away.  Sometimes this was the effect\r
+of art, and chiefly of the art of literature, which dealt immediately\r
+with the passions and the intellect.  But now and then a complex\r
+personality took the place and assumed the office of art, was indeed,\r
+in its way, a real work of art, life having its elaborate masterpieces,\r
+just as poetry has, or sculpture, or painting.\r
+\r
+Yes, the lad was premature.  He was gathering his harvest while it was\r
+yet spring.  The pulse and passion of youth were in him, but he was\r
+becoming self-conscious. It was delightful to watch him.  With his\r
+beautiful face, and his beautiful soul, he was a thing to wonder at.\r
+It was no matter how it all ended, or was destined to end.  He was like\r
+one of those gracious figures in a pageant or a play, whose joys seem\r
+to be remote from one, but whose sorrows stir one's sense of beauty,\r
+and whose wounds are like red roses.\r
+\r
+Soul and body, body and soul--how mysterious they were!  There was\r
+animalism in the soul, and the body had its moments of spirituality.\r
+The senses could refine, and the intellect could degrade.  Who could\r
+say where the fleshly impulse ceased, or the psychical impulse began?\r
+How shallow were the arbitrary definitions of ordinary psychologists!\r
+And yet how difficult to decide between the claims of the various\r
+schools!  Was the soul a shadow seated in the house of sin?  Or was the\r
+body really in the soul, as Giordano Bruno thought?  The separation of\r
+spirit from matter was a mystery, and the union of spirit with matter\r
+was a mystery also.\r
+\r
+He began to wonder whether we could ever make psychology so absolute a\r
+science that each little spring of life would be revealed to us.  As it\r
+was, we always misunderstood ourselves and rarely understood others.\r
+Experience was of no ethical value.  It was merely the name men gave to\r
+their mistakes.  Moralists had, as a rule, regarded it as a mode of\r
+warning, had claimed for it a certain ethical efficacy in the formation\r
+of character, had praised it as something that taught us what to follow\r
+and showed us what to avoid.  But there was no motive power in\r
+experience.  It was as little of an active cause as conscience itself.\r
+All that it really demonstrated was that our future would be the same\r
+as our past, and that the sin we had done once, and with loathing, we\r
+would do many times, and with joy.\r
+\r
+It was clear to him that the experimental method was the only method by\r
+which one could arrive at any scientific analysis of the passions; and\r
+certainly Dorian Gray was a subject made to his hand, and seemed to\r
+promise rich and fruitful results.  His sudden mad love for Sibyl Vane\r
+was a psychological phenomenon of no small interest.  There was no\r
+doubt that curiosity had much to do with it, curiosity and the desire\r
+for new experiences, yet it was not a simple, but rather a very complex\r
+passion.  What there was in it of the purely sensuous instinct of\r
+boyhood had been transformed by the workings of the imagination,\r
+changed into something that seemed to the lad himself to be remote from\r
+sense, and was for that very reason all the more dangerous.  It was the\r
+passions about whose origin we deceived ourselves that tyrannized most\r
+strongly over us.  Our weakest motives were those of whose nature we\r
+were conscious.  It often happened that when we thought we were\r
+experimenting on others we were really experimenting on ourselves.\r
+\r
+While Lord Henry sat dreaming on these things, a knock came to the\r
+door, and his valet entered and reminded him it was time to dress for\r
+dinner.  He got up and looked out into the street.  The sunset had\r
+smitten into scarlet gold the upper windows of the houses opposite.\r
+The panes glowed like plates of heated metal.  The sky above was like a\r
+faded rose.  He thought of his friend's young fiery-coloured life and\r
+wondered how it was all going to end.\r
+\r
+When he arrived home, about half-past twelve o'clock, he saw a telegram\r
+lying on the hall table.  He opened it and found it was from Dorian\r
+Gray.  It was to tell him that he was engaged to be married to Sibyl\r
+Vane.\r
+\r
+\r
+\r
+CHAPTER 5\r
+\r
+"Mother, Mother, I am so happy!" whispered the girl, burying her face\r
+in the lap of the faded, tired-looking woman who, with back turned to\r
+the shrill intrusive light, was sitting in the one arm-chair that their\r
+dingy sitting-room contained.  "I am so happy!" she repeated, "and you\r
+must be happy, too!"\r
+\r
+Mrs. Vane winced and put her thin, bismuth-whitened hands on her\r
+daughter's head.  "Happy!" she echoed, "I am only happy, Sibyl, when I\r
+see you act.  You must not think of anything but your acting.  Mr.\r
+Isaacs has been very good to us, and we owe him money."\r
+\r
+The girl looked up and pouted.  "Money, Mother?" she cried, "what does\r
+money matter?  Love is more than money."\r
+\r
+"Mr. Isaacs has advanced us fifty pounds to pay off our debts and to\r
+get a proper outfit for James.  You must not forget that, Sibyl.  Fifty\r
+pounds is a very large sum.  Mr. Isaacs has been most considerate."\r
+\r
+"He is not a gentleman, Mother, and I hate the way he talks to me,"\r
+said the girl, rising to her feet and going over to the window.\r
+\r
+"I don't know how we could manage without him," answered the elder\r
+woman querulously.\r
+\r
+Sibyl Vane tossed her head and laughed.  "We don't want him any more,\r
+Mother.  Prince Charming rules life for us now." Then she paused.  A\r
+rose shook in her blood and shadowed her cheeks.  Quick breath parted\r
+the petals of her lips.  They trembled.  Some southern wind of passion\r
+swept over her and stirred the dainty folds of her dress.  "I love\r
+him," she said simply.\r
+\r
+"Foolish child! foolish child!" was the parrot-phrase flung in answer.\r
+The waving of crooked, false-jewelled fingers gave grotesqueness to the\r
+words.\r
+\r
+The girl laughed again.  The joy of a caged bird was in her voice.  Her\r
+eyes caught the melody and echoed it in radiance, then closed for a\r
+moment, as though to hide their secret.  When they opened, the mist of\r
+a dream had passed across them.\r
+\r
+Thin-lipped wisdom spoke at her from the worn chair, hinted at\r
+prudence, quoted from that book of cowardice whose author apes the name\r
+of common sense.  She did not listen.  She was free in her prison of\r
+passion.  Her prince, Prince Charming, was with her.  She had called on\r
+memory to remake him.  She had sent her soul to search for him, and it\r
+had brought him back.  His kiss burned again upon her mouth.  Her\r
+eyelids were warm with his breath.\r
+\r
+Then wisdom altered its method and spoke of espial and discovery.  This\r
+young man might be rich.  If so, marriage should be thought of.\r
+Against the shell of her ear broke the waves of worldly cunning.  The\r
+arrows of craft shot by her.  She saw the thin lips moving, and smiled.\r
+\r
+Suddenly she felt the need to speak.  The wordy silence troubled her.\r
+"Mother, Mother," she cried, "why does he love me so much?  I know why\r
+I love him.  I love him because he is like what love himself should be.\r
+But what does he see in me?  I am not worthy of him.  And yet--why, I\r
+cannot tell--though I feel so much beneath him, I don't feel humble.  I\r
+feel proud, terribly proud.  Mother, did you love my father as I love\r
+Prince Charming?"\r
+\r
+The elder woman grew pale beneath the coarse powder that daubed her\r
+cheeks, and her dry lips twitched with a spasm of pain.  Sybil rushed\r
+to her, flung her arms round her neck, and kissed her.  "Forgive me,\r
+Mother.  I know it pains you to talk about our father.  But it only\r
+pains you because you loved him so much.  Don't look so sad.  I am as\r
+happy to-day as you were twenty years ago.  Ah! let me be happy for\r
+ever!"\r
+\r
+"My child, you are far too young to think of falling in love.  Besides,\r
+what do you know of this young man?  You don't even know his name.  The\r
+whole thing is most inconvenient, and really, when James is going away\r
+to Australia, and I have so much to think of, I must say that you\r
+should have shown more consideration.  However, as I said before, if he\r
+is rich ..."\r
+\r
+"Ah!  Mother, Mother, let me be happy!"\r
+\r
+Mrs. Vane glanced at her, and with one of those false theatrical\r
+gestures that so often become a mode of second nature to a\r
+stage-player, clasped her in her arms.  At this moment, the door opened\r
+and a young lad with rough brown hair came into the room.  He was\r
+thick-set of figure, and his hands and feet were large and somewhat\r
+clumsy in movement.  He was not so finely bred as his sister.  One\r
+would hardly have guessed the close relationship that existed between\r
+them.  Mrs. Vane fixed her eyes on him and intensified her smile.  She\r
+mentally elevated her son to the dignity of an audience.  She felt sure\r
+that the _tableau_ was interesting.\r
+\r
+"You might keep some of your kisses for me, Sibyl, I think," said the\r
+lad with a good-natured grumble.\r
+\r
+"Ah! but you don't like being kissed, Jim," she cried.  "You are a\r
+dreadful old bear."  And she ran across the room and hugged him.\r
+\r
+James Vane looked into his sister's face with tenderness.  "I want you\r
+to come out with me for a walk, Sibyl.  I don't suppose I shall ever\r
+see this horrid London again.  I am sure I don't want to."\r
+\r
+"My son, don't say such dreadful things," murmured Mrs. Vane, taking up\r
+a tawdry theatrical dress, with a sigh, and beginning to patch it.  She\r
+felt a little disappointed that he had not joined the group.  It would\r
+have increased the theatrical picturesqueness of the situation.\r
+\r
+"Why not, Mother?  I mean it."\r
+\r
+"You pain me, my son.  I trust you will return from Australia in a\r
+position of affluence.  I believe there is no society of any kind in\r
+the Colonies--nothing that I would call society--so when you have made\r
+your fortune, you must come back and assert yourself in London."\r
+\r
+"Society!" muttered the lad.  "I don't want to know anything about\r
+that.  I should like to make some money to take you and Sibyl off the\r
+stage.  I hate it."\r
+\r
+"Oh, Jim!" said Sibyl, laughing, "how unkind of you!  But are you\r
+really going for a walk with me?  That will be nice!  I was afraid you\r
+were going to say good-bye to some of your friends--to Tom Hardy, who\r
+gave you that hideous pipe, or Ned Langton, who makes fun of you for\r
+smoking it.  It is very sweet of you to let me have your last\r
+afternoon.  Where shall we go?  Let us go to the park."\r
+\r
+"I am too shabby," he answered, frowning.  "Only swell people go to the\r
+park."\r
+\r
+"Nonsense, Jim," she whispered, stroking the sleeve of his coat.\r
+\r
+He hesitated for a moment.  "Very well," he said at last, "but don't be\r
+too long dressing."  She danced out of the door.  One could hear her\r
+singing as she ran upstairs.  Her little feet pattered overhead.\r
+\r
+He walked up and down the room two or three times.  Then he turned to\r
+the still figure in the chair.  "Mother, are my things ready?" he asked.\r
+\r
+"Quite ready, James," she answered, keeping her eyes on her work.  For\r
+some months past she had felt ill at ease when she was alone with this\r
+rough stern son of hers.  Her shallow secret nature was troubled when\r
+their eyes met.  She used to wonder if he suspected anything.  The\r
+silence, for he made no other observation, became intolerable to her.\r
+She began to complain.  Women defend themselves by attacking, just as\r
+they attack by sudden and strange surrenders.  "I hope you will be\r
+contented, James, with your sea-faring life," she said.  "You must\r
+remember that it is your own choice.  You might have entered a\r
+solicitor's office.  Solicitors are a very respectable class, and in\r
+the country often dine with the best families."\r
+\r
+"I hate offices, and I hate clerks," he replied.  "But you are quite\r
+right.  I have chosen my own life.  All I say is, watch over Sibyl.\r
+Don't let her come to any harm.  Mother, you must watch over her."\r
+\r
+"James, you really talk very strangely.  Of course I watch over Sibyl."\r
+\r
+"I hear a gentleman comes every night to the theatre and goes behind to\r
+talk to her.  Is that right?  What about that?"\r
+\r
+"You are speaking about things you don't understand, James.  In the\r
+profession we are accustomed to receive a great deal of most gratifying\r
+attention.  I myself used to receive many bouquets at one time.  That\r
+was when acting was really understood.  As for Sibyl, I do not know at\r
+present whether her attachment is serious or not.  But there is no\r
+doubt that the young man in question is a perfect gentleman.  He is\r
+always most polite to me.  Besides, he has the appearance of being\r
+rich, and the flowers he sends are lovely."\r
+\r
+"You don't know his name, though," said the lad harshly.\r
+\r
+"No," answered his mother with a placid expression in her face.  "He\r
+has not yet revealed his real name.  I think it is quite romantic of\r
+him.  He is probably a member of the aristocracy."\r
+\r
+James Vane bit his lip.  "Watch over Sibyl, Mother," he cried, "watch\r
+over her."\r
+\r
+"My son, you distress me very much.  Sibyl is always under my special\r
+care.  Of course, if this gentleman is wealthy, there is no reason why\r
+she should not contract an alliance with him.  I trust he is one of the\r
+aristocracy.  He has all the appearance of it, I must say.  It might be\r
+a most brilliant marriage for Sibyl.  They would make a charming\r
+couple.  His good looks are really quite remarkable; everybody notices\r
+them."\r
+\r
+The lad muttered something to himself and drummed on the window-pane\r
+with his coarse fingers.  He had just turned round to say something\r
+when the door opened and Sibyl ran in.\r
+\r
+"How serious you both are!" she cried.  "What is the matter?"\r
+\r
+"Nothing," he answered.  "I suppose one must be serious sometimes.\r
+Good-bye, Mother; I will have my dinner at five o'clock. Everything is\r
+packed, except my shirts, so you need not trouble."\r
+\r
+"Good-bye, my son," she answered with a bow of strained stateliness.\r
+\r
+She was extremely annoyed at the tone he had adopted with her, and\r
+there was something in his look that had made her feel afraid.\r
+\r
+"Kiss me, Mother," said the girl.  Her flowerlike lips touched the\r
+withered cheek and warmed its frost.\r
+\r
+"My child! my child!" cried Mrs. Vane, looking up to the ceiling in\r
+search of an imaginary gallery.\r
+\r
+"Come, Sibyl," said her brother impatiently.  He hated his mother's\r
+affectations.\r
+\r
+They went out into the flickering, wind-blown sunlight and strolled\r
+down the dreary Euston Road.  The passersby glanced in wonder at the\r
+sullen heavy youth who, in coarse, ill-fitting clothes, was in the\r
+company of such a graceful, refined-looking girl.  He was like a common\r
+gardener walking with a rose.\r
+\r
+Jim frowned from time to time when he caught the inquisitive glance of\r
+some stranger.  He had that dislike of being stared at, which comes on\r
+geniuses late in life and never leaves the commonplace.  Sibyl,\r
+however, was quite unconscious of the effect she was producing.  Her\r
+love was trembling in laughter on her lips.  She was thinking of Prince\r
+Charming, and, that she might think of him all the more, she did not\r
+talk of him, but prattled on about the ship in which Jim was going to\r
+sail, about the gold he was certain to find, about the wonderful\r
+heiress whose life he was to save from the wicked, red-shirted\r
+bushrangers.  For he was not to remain a sailor, or a supercargo, or\r
+whatever he was going to be.  Oh, no!  A sailor's existence was\r
+dreadful.  Fancy being cooped up in a horrid ship, with the hoarse,\r
+hump-backed waves trying to get in, and a black wind blowing the masts\r
+down and tearing the sails into long screaming ribands!  He was to\r
+leave the vessel at Melbourne, bid a polite good-bye to the captain,\r
+and go off at once to the gold-fields. Before a week was over he was to\r
+come across a large nugget of pure gold, the largest nugget that had\r
+ever been discovered, and bring it down to the coast in a waggon\r
+guarded by six mounted policemen.  The bushrangers were to attack them\r
+three times, and be defeated with immense slaughter.  Or, no.  He was\r
+not to go to the gold-fields at all.  They were horrid places, where\r
+men got intoxicated, and shot each other in bar-rooms, and used bad\r
+language.  He was to be a nice sheep-farmer, and one evening, as he was\r
+riding home, he was to see the beautiful heiress being carried off by a\r
+robber on a black horse, and give chase, and rescue her.  Of course,\r
+she would fall in love with him, and he with her, and they would get\r
+married, and come home, and live in an immense house in London.  Yes,\r
+there were delightful things in store for him.  But he must be very\r
+good, and not lose his temper, or spend his money foolishly.  She was\r
+only a year older than he was, but she knew so much more of life.  He\r
+must be sure, also, to write to her by every mail, and to say his\r
+prayers each night before he went to sleep.  God was very good, and\r
+would watch over him.  She would pray for him, too, and in a few years\r
+he would come back quite rich and happy.\r
+\r
+The lad listened sulkily to her and made no answer.  He was heart-sick\r
+at leaving home.\r
+\r
+Yet it was not this alone that made him gloomy and morose.\r
+Inexperienced though he was, he had still a strong sense of the danger\r
+of Sibyl's position.  This young dandy who was making love to her could\r
+mean her no good.  He was a gentleman, and he hated him for that, hated\r
+him through some curious race-instinct for which he could not account,\r
+and which for that reason was all the more dominant within him.  He was\r
+conscious also of the shallowness and vanity of his mother's nature,\r
+and in that saw infinite peril for Sibyl and Sibyl's happiness.\r
+Children begin by loving their parents; as they grow older they judge\r
+them; sometimes they forgive them.\r
+\r
+His mother!  He had something on his mind to ask of her, something that\r
+he had brooded on for many months of silence.  A chance phrase that he\r
+had heard at the theatre, a whispered sneer that had reached his ears\r
+one night as he waited at the stage-door, had set loose a train of\r
+horrible thoughts.  He remembered it as if it had been the lash of a\r
+hunting-crop across his face.  His brows knit together into a wedge-like\r
+furrow, and with a twitch of pain he bit his underlip.\r
+\r
+"You are not listening to a word I am saying, Jim," cried Sibyl, "and I\r
+am making the most delightful plans for your future.  Do say something."\r
+\r
+"What do you want me to say?"\r
+\r
+"Oh! that you will be a good boy and not forget us," she answered,\r
+smiling at him.\r
+\r
+He shrugged his shoulders.  "You are more likely to forget me than I am\r
+to forget you, Sibyl."\r
+\r
+She flushed.  "What do you mean, Jim?" she asked.\r
+\r
+"You have a new friend, I hear.  Who is he?  Why have you not told me\r
+about him?  He means you no good."\r
+\r
+"Stop, Jim!" she exclaimed.  "You must not say anything against him.  I\r
+love him."\r
+\r
+"Why, you don't even know his name," answered the lad.  "Who is he?  I\r
+have a right to know."\r
+\r
+"He is called Prince Charming.  Don't you like the name.  Oh! you silly\r
+boy! you should never forget it.  If you only saw him, you would think\r
+him the most wonderful person in the world.  Some day you will meet\r
+him--when you come back from Australia.  You will like him so much.\r
+Everybody likes him, and I ... love him.  I wish you could come to the\r
+theatre to-night. He is going to be there, and I am to play Juliet.\r
+Oh! how I shall play it!  Fancy, Jim, to be in love and play Juliet!\r
+To have him sitting there!  To play for his delight!  I am afraid I may\r
+frighten the company, frighten or enthrall them.  To be in love is to\r
+surpass one's self.  Poor dreadful Mr. Isaacs will be shouting 'genius'\r
+to his loafers at the bar.  He has preached me as a dogma; to-night he\r
+will announce me as a revelation.  I feel it.  And it is all his, his\r
+only, Prince Charming, my wonderful lover, my god of graces.  But I am\r
+poor beside him.  Poor?  What does that matter?  When poverty creeps in\r
+at the door, love flies in through the window.  Our proverbs want\r
+rewriting.  They were made in winter, and it is summer now; spring-time\r
+for me, I think, a very dance of blossoms in blue skies."\r
+\r
+"He is a gentleman," said the lad sullenly.\r
+\r
+"A prince!" she cried musically.  "What more do you want?"\r
+\r
+"He wants to enslave you."\r
+\r
+"I shudder at the thought of being free."\r
+\r
+"I want you to beware of him."\r
+\r
+"To see him is to worship him; to know him is to trust him."\r
+\r
+"Sibyl, you are mad about him."\r
+\r
+She laughed and took his arm.  "You dear old Jim, you talk as if you\r
+were a hundred.  Some day you will be in love yourself.  Then you will\r
+know what it is.  Don't look so sulky.  Surely you should be glad to\r
+think that, though you are going away, you leave me happier than I have\r
+ever been before.  Life has been hard for us both, terribly hard and\r
+difficult.  But it will be different now.  You are going to a new\r
+world, and I have found one.  Here are two chairs; let us sit down and\r
+see the smart people go by."\r
+\r
+They took their seats amidst a crowd of watchers.  The tulip-beds\r
+across the road flamed like throbbing rings of fire.  A white\r
+dust--tremulous cloud of orris-root it seemed--hung in the panting air.\r
+The brightly coloured parasols danced and dipped like monstrous\r
+butterflies.\r
+\r
+She made her brother talk of himself, his hopes, his prospects.  He\r
+spoke slowly and with effort.  They passed words to each other as\r
+players at a game pass counters.  Sibyl felt oppressed.  She could not\r
+communicate her joy.  A faint smile curving that sullen mouth was all\r
+the echo she could win.  After some time she became silent.  Suddenly\r
+she caught a glimpse of golden hair and laughing lips, and in an open\r
+carriage with two ladies Dorian Gray drove past.\r
+\r
+She started to her feet.  "There he is!" she cried.\r
+\r
+"Who?" said Jim Vane.\r
+\r
+"Prince Charming," she answered, looking after the victoria.\r
+\r
+He jumped up and seized her roughly by the arm.  "Show him to me.\r
+Which is he?  Point him out.  I must see him!" he exclaimed; but at\r
+that moment the Duke of Berwick's four-in-hand came between, and when\r
+it had left the space clear, the carriage had swept out of the park.\r
+\r
+"He is gone," murmured Sibyl sadly.  "I wish you had seen him."\r
+\r
+"I wish I had, for as sure as there is a God in heaven, if he ever does\r
+you any wrong, I shall kill him."\r
+\r
+She looked at him in horror.  He repeated his words.  They cut the air\r
+like a dagger.  The people round began to gape.  A lady standing close\r
+to her tittered.\r
+\r
+"Come away, Jim; come away," she whispered.  He followed her doggedly\r
+as she passed through the crowd.  He felt glad at what he had said.\r
+\r
+When they reached the Achilles Statue, she turned round.  There was\r
+pity in her eyes that became laughter on her lips.  She shook her head\r
+at him.  "You are foolish, Jim, utterly foolish; a bad-tempered boy,\r
+that is all.  How can you say such horrible things?  You don't know\r
+what you are talking about.  You are simply jealous and unkind.  Ah!  I\r
+wish you would fall in love.  Love makes people good, and what you said\r
+was wicked."\r
+\r
+"I am sixteen," he answered, "and I know what I am about.  Mother is no\r
+help to you.  She doesn't understand how to look after you.  I wish now\r
+that I was not going to Australia at all.  I have a great mind to chuck\r
+the whole thing up.  I would, if my articles hadn't been signed."\r
+\r
+"Oh, don't be so serious, Jim.  You are like one of the heroes of those\r
+silly melodramas Mother used to be so fond of acting in.  I am not\r
+going to quarrel with you.  I have seen him, and oh! to see him is\r
+perfect happiness.  We won't quarrel.  I know you would never harm any\r
+one I love, would you?"\r
+\r
+"Not as long as you love him, I suppose," was the sullen answer.\r
+\r
+"I shall love him for ever!" she cried.\r
+\r
+"And he?"\r
+\r
+"For ever, too!"\r
+\r
+"He had better."\r
+\r
+She shrank from him.  Then she laughed and put her hand on his arm.  He\r
+was merely a boy.\r
+\r
+At the Marble Arch they hailed an omnibus, which left them close to\r
+their shabby home in the Euston Road.  It was after five o'clock, and\r
+Sibyl had to lie down for a couple of hours before acting.  Jim\r
+insisted that she should do so.  He said that he would sooner part with\r
+her when their mother was not present.  She would be sure to make a\r
+scene, and he detested scenes of every kind.\r
+\r
+In Sybil's own room they parted.  There was jealousy in the lad's\r
+heart, and a fierce murderous hatred of the stranger who, as it seemed\r
+to him, had come between them.  Yet, when her arms were flung round his\r
+neck, and her fingers strayed through his hair, he softened and kissed\r
+her with real affection.  There were tears in his eyes as he went\r
+downstairs.\r
+\r
+His mother was waiting for him below.  She grumbled at his\r
+unpunctuality, as he entered.  He made no answer, but sat down to his\r
+meagre meal.  The flies buzzed round the table and crawled over the\r
+stained cloth.  Through the rumble of omnibuses, and the clatter of\r
+street-cabs, he could hear the droning voice devouring each minute that\r
+was left to him.\r
+\r
+After some time, he thrust away his plate and put his head in his\r
+hands.  He felt that he had a right to know.  It should have been told\r
+to him before, if it was as he suspected.  Leaden with fear, his mother\r
+watched him.  Words dropped mechanically from her lips.  A tattered\r
+lace handkerchief twitched in her fingers.  When the clock struck six,\r
+he got up and went to the door.  Then he turned back and looked at her.\r
+Their eyes met.  In hers he saw a wild appeal for mercy.  It enraged\r
+him.\r
+\r
+"Mother, I have something to ask you," he said.  Her eyes wandered\r
+vaguely about the room.  She made no answer.  "Tell me the truth.  I\r
+have a right to know.  Were you married to my father?"\r
+\r
+She heaved a deep sigh.  It was a sigh of relief.  The terrible moment,\r
+the moment that night and day, for weeks and months, she had dreaded,\r
+had come at last, and yet she felt no terror.  Indeed, in some measure\r
+it was a disappointment to her.  The vulgar directness of the question\r
+called for a direct answer.  The situation had not been gradually led\r
+up to.  It was crude.  It reminded her of a bad rehearsal.\r
+\r
+"No," she answered, wondering at the harsh simplicity of life.\r
+\r
+"My father was a scoundrel then!" cried the lad, clenching his fists.\r
+\r
+She shook her head.  "I knew he was not free.  We loved each other very\r
+much.  If he had lived, he would have made provision for us.  Don't\r
+speak against him, my son.  He was your father, and a gentleman.\r
+Indeed, he was highly connected."\r
+\r
+An oath broke from his lips.  "I don't care for myself," he exclaimed,\r
+"but don't let Sibyl.... It is a gentleman, isn't it, who is in love\r
+with her, or says he is?  Highly connected, too, I suppose."\r
+\r
+For a moment a hideous sense of humiliation came over the woman.  Her\r
+head drooped.  She wiped her eyes with shaking hands.  "Sibyl has a\r
+mother," she murmured; "I had none."\r
+\r
+The lad was touched.  He went towards her, and stooping down, he kissed\r
+her.  "I am sorry if I have pained you by asking about my father," he\r
+said, "but I could not help it.  I must go now.  Good-bye. Don't forget\r
+that you will have only one child now to look after, and believe me\r
+that if this man wrongs my sister, I will find out who he is, track him\r
+down, and kill him like a dog.  I swear it."\r
+\r
+The exaggerated folly of the threat, the passionate gesture that\r
+accompanied it, the mad melodramatic words, made life seem more vivid\r
+to her.  She was familiar with the atmosphere.  She breathed more\r
+freely, and for the first time for many months she really admired her\r
+son.  She would have liked to have continued the scene on the same\r
+emotional scale, but he cut her short.  Trunks had to be carried down\r
+and mufflers looked for.  The lodging-house drudge bustled in and out.\r
+There was the bargaining with the cabman.  The moment was lost in\r
+vulgar details.  It was with a renewed feeling of disappointment that\r
+she waved the tattered lace handkerchief from the window, as her son\r
+drove away.  She was conscious that a great opportunity had been\r
+wasted.  She consoled herself by telling Sibyl how desolate she felt\r
+her life would be, now that she had only one child to look after.  She\r
+remembered the phrase.  It had pleased her.  Of the threat she said\r
+nothing.  It was vividly and dramatically expressed.  She felt that\r
+they would all laugh at it some day.\r
+\r
+\r
+\r
+CHAPTER 6\r
+\r
+"I suppose you have heard the news, Basil?" said Lord Henry that\r
+evening as Hallward was shown into a little private room at the Bristol\r
+where dinner had been laid for three.\r
+\r
+"No, Harry," answered the artist, giving his hat and coat to the bowing\r
+waiter.  "What is it?  Nothing about politics, I hope!  They don't\r
+interest me.  There is hardly a single person in the House of Commons\r
+worth painting, though many of them would be the better for a little\r
+whitewashing."\r
+\r
+"Dorian Gray is engaged to be married," said Lord Henry, watching him\r
+as he spoke.\r
+\r
+Hallward started and then frowned.  "Dorian engaged to be married!" he\r
+cried.  "Impossible!"\r
+\r
+"It is perfectly true."\r
+\r
+"To whom?"\r
+\r
+"To some little actress or other."\r
+\r
+"I can't believe it.  Dorian is far too sensible."\r
+\r
+"Dorian is far too wise not to do foolish things now and then, my dear\r
+Basil."\r
+\r
+"Marriage is hardly a thing that one can do now and then, Harry."\r
+\r
+"Except in America," rejoined Lord Henry languidly.  "But I didn't say\r
+he was married.  I said he was engaged to be married.  There is a great\r
+difference.  I have a distinct remembrance of being married, but I have\r
+no recollection at all of being engaged.  I am inclined to think that I\r
+never was engaged."\r
+\r
+"But think of Dorian's birth, and position, and wealth.  It would be\r
+absurd for him to marry so much beneath him."\r
+\r
+"If you want to make him marry this girl, tell him that, Basil.  He is\r
+sure to do it, then.  Whenever a man does a thoroughly stupid thing, it\r
+is always from the noblest motives."\r
+\r
+"I hope the girl is good, Harry.  I don't want to see Dorian tied to\r
+some vile creature, who might degrade his nature and ruin his\r
+intellect."\r
+\r
+"Oh, she is better than good--she is beautiful," murmured Lord Henry,\r
+sipping a glass of vermouth and orange-bitters. "Dorian says she is\r
+beautiful, and he is not often wrong about things of that kind.  Your\r
+portrait of him has quickened his appreciation of the personal\r
+appearance of other people.  It has had that excellent effect, amongst\r
+others.  We are to see her to-night, if that boy doesn't forget his\r
+appointment."\r
+\r
+"Are you serious?"\r
+\r
+"Quite serious, Basil.  I should be miserable if I thought I should\r
+ever be more serious than I am at the present moment."\r
+\r
+"But do you approve of it, Harry?" asked the painter, walking up and\r
+down the room and biting his lip.  "You can't approve of it, possibly.\r
+It is some silly infatuation."\r
+\r
+"I never approve, or disapprove, of anything now.  It is an absurd\r
+attitude to take towards life.  We are not sent into the world to air\r
+our moral prejudices.  I never take any notice of what common people\r
+say, and I never interfere with what charming people do.  If a\r
+personality fascinates me, whatever mode of expression that personality\r
+selects is absolutely delightful to me.  Dorian Gray falls in love with\r
+a beautiful girl who acts Juliet, and proposes to marry her.  Why not?\r
+If he wedded Messalina, he would be none the less interesting.  You\r
+know I am not a champion of marriage.  The real drawback to marriage is\r
+that it makes one unselfish.  And unselfish people are colourless.\r
+They lack individuality.  Still, there are certain temperaments that\r
+marriage makes more complex.  They retain their egotism, and add to it\r
+many other egos.  They are forced to have more than one life.  They\r
+become more highly organized, and to be highly organized is, I should\r
+fancy, the object of man's existence.  Besides, every experience is of\r
+value, and whatever one may say against marriage, it is certainly an\r
+experience.  I hope that Dorian Gray will make this girl his wife,\r
+passionately adore her for six months, and then suddenly become\r
+fascinated by some one else.  He would be a wonderful study."\r
+\r
+"You don't mean a single word of all that, Harry; you know you don't.\r
+If Dorian Gray's life were spoiled, no one would be sorrier than\r
+yourself.  You are much better than you pretend to be."\r
+\r
+Lord Henry laughed.  "The reason we all like to think so well of others\r
+is that we are all afraid for ourselves.  The basis of optimism is\r
+sheer terror.  We think that we are generous because we credit our\r
+neighbour with the possession of those virtues that are likely to be a\r
+benefit to us.  We praise the banker that we may overdraw our account,\r
+and find good qualities in the highwayman in the hope that he may spare\r
+our pockets.  I mean everything that I have said.  I have the greatest\r
+contempt for optimism.  As for a spoiled life, no life is spoiled but\r
+one whose growth is arrested.  If you want to mar a nature, you have\r
+merely to reform it.  As for marriage, of course that would be silly,\r
+but there are other and more interesting bonds between men and women.\r
+I will certainly encourage them.  They have the charm of being\r
+fashionable.  But here is Dorian himself.  He will tell you more than I\r
+can."\r
+\r
+"My dear Harry, my dear Basil, you must both congratulate me!" said the\r
+lad, throwing off his evening cape with its satin-lined wings and\r
+shaking each of his friends by the hand in turn.  "I have never been so\r
+happy.  Of course, it is sudden--all really delightful things are.  And\r
+yet it seems to me to be the one thing I have been looking for all my\r
+life." He was flushed with excitement and pleasure, and looked\r
+extraordinarily handsome.\r
+\r
+"I hope you will always be very happy, Dorian," said Hallward, "but I\r
+don't quite forgive you for not having let me know of your engagement.\r
+You let Harry know."\r
+\r
+"And I don't forgive you for being late for dinner," broke in Lord\r
+Henry, putting his hand on the lad's shoulder and smiling as he spoke.\r
+"Come, let us sit down and try what the new _chef_ here is like, and then\r
+you will tell us how it all came about."\r
+\r
+"There is really not much to tell," cried Dorian as they took their\r
+seats at the small round table.  "What happened was simply this.  After\r
+I left you yesterday evening, Harry, I dressed, had some dinner at that\r
+little Italian restaurant in Rupert Street you introduced me to, and\r
+went down at eight o'clock to the theatre.  Sibyl was playing Rosalind.\r
+Of course, the scenery was dreadful and the Orlando absurd.  But Sibyl!\r
+You should have seen her!  When she came on in her boy's clothes, she\r
+was perfectly wonderful.  She wore a moss-coloured velvet jerkin with\r
+cinnamon sleeves, slim, brown, cross-gartered hose, a dainty little\r
+green cap with a hawk's feather caught in a jewel, and a hooded cloak\r
+lined with dull red.  She had never seemed to me more exquisite.  She\r
+had all the delicate grace of that Tanagra figurine that you have in\r
+your studio, Basil.  Her hair clustered round her face like dark leaves\r
+round a pale rose.  As for her acting--well, you shall see her\r
+to-night. She is simply a born artist.  I sat in the dingy box\r
+absolutely enthralled.  I forgot that I was in London and in the\r
+nineteenth century.  I was away with my love in a forest that no man\r
+had ever seen.  After the performance was over, I went behind and spoke\r
+to her.  As we were sitting together, suddenly there came into her eyes\r
+a look that I had never seen there before.  My lips moved towards hers.\r
+We kissed each other.  I can't describe to you what I felt at that\r
+moment.  It seemed to me that all my life had been narrowed to one\r
+perfect point of rose-coloured joy.  She trembled all over and shook\r
+like a white narcissus.  Then she flung herself on her knees and kissed\r
+my hands.  I feel that I should not tell you all this, but I can't help\r
+it.  Of course, our engagement is a dead secret.  She has not even told\r
+her own mother.  I don't know what my guardians will say.  Lord Radley\r
+is sure to be furious.  I don't care.  I shall be of age in less than a\r
+year, and then I can do what I like.  I have been right, Basil, haven't\r
+I, to take my love out of poetry and to find my wife in Shakespeare's\r
+plays?  Lips that Shakespeare taught to speak have whispered their\r
+secret in my ear.  I have had the arms of Rosalind around me, and\r
+kissed Juliet on the mouth."\r
+\r
+"Yes, Dorian, I suppose you were right," said Hallward slowly.\r
+\r
+"Have you seen her to-day?" asked Lord Henry.\r
+\r
+Dorian Gray shook his head.  "I left her in the forest of Arden; I\r
+shall find her in an orchard in Verona."\r
+\r
+Lord Henry sipped his champagne in a meditative manner.  "At what\r
+particular point did you mention the word marriage, Dorian?  And what\r
+did she say in answer?  Perhaps you forgot all about it."\r
+\r
+"My dear Harry, I did not treat it as a business transaction, and I did\r
+not make any formal proposal.  I told her that I loved her, and she\r
+said she was not worthy to be my wife.  Not worthy!  Why, the whole\r
+world is nothing to me compared with her."\r
+\r
+"Women are wonderfully practical," murmured Lord Henry, "much more\r
+practical than we are.  In situations of that kind we often forget to\r
+say anything about marriage, and they always remind us."\r
+\r
+Hallward laid his hand upon his arm.  "Don't, Harry.  You have annoyed\r
+Dorian.  He is not like other men.  He would never bring misery upon\r
+any one.  His nature is too fine for that."\r
+\r
+Lord Henry looked across the table.  "Dorian is never annoyed with me,"\r
+he answered.  "I asked the question for the best reason possible, for\r
+the only reason, indeed, that excuses one for asking any\r
+question--simple curiosity.  I have a theory that it is always the\r
+women who propose to us, and not we who propose to the women.  Except,\r
+of course, in middle-class life.  But then the middle classes are not\r
+modern."\r
+\r
+Dorian Gray laughed, and tossed his head.  "You are quite incorrigible,\r
+Harry; but I don't mind.  It is impossible to be angry with you.  When\r
+you see Sibyl Vane, you will feel that the man who could wrong her\r
+would be a beast, a beast without a heart.  I cannot understand how any\r
+one can wish to shame the thing he loves.  I love Sibyl Vane.  I want\r
+to place her on a pedestal of gold and to see the world worship the\r
+woman who is mine.  What is marriage?  An irrevocable vow.  You mock at\r
+it for that.  Ah! don't mock.  It is an irrevocable vow that I want to\r
+take.  Her trust makes me faithful, her belief makes me good.  When I\r
+am with her, I regret all that you have taught me.  I become different\r
+from what you have known me to be.  I am changed, and the mere touch of\r
+Sibyl Vane's hand makes me forget you and all your wrong, fascinating,\r
+poisonous, delightful theories."\r
+\r
+"And those are ...?" asked Lord Henry, helping himself to some salad.\r
+\r
+"Oh, your theories about life, your theories about love, your theories\r
+about pleasure.  All your theories, in fact, Harry."\r
+\r
+"Pleasure is the only thing worth having a theory about," he answered\r
+in his slow melodious voice.  "But I am afraid I cannot claim my theory\r
+as my own.  It belongs to Nature, not to me.  Pleasure is Nature's\r
+test, her sign of approval.  When we are happy, we are always good, but\r
+when we are good, we are not always happy."\r
+\r
+"Ah! but what do you mean by good?" cried Basil Hallward.\r
+\r
+"Yes," echoed Dorian, leaning back in his chair and looking at Lord\r
+Henry over the heavy clusters of purple-lipped irises that stood in the\r
+centre of the table, "what do you mean by good, Harry?"\r
+\r
+"To be good is to be in harmony with one's self," he replied, touching\r
+the thin stem of his glass with his pale, fine-pointed fingers.\r
+"Discord is to be forced to be in harmony with others.  One's own\r
+life--that is the important thing.  As for the lives of one's\r
+neighbours, if one wishes to be a prig or a Puritan, one can flaunt\r
+one's moral views about them, but they are not one's concern.  Besides,\r
+individualism has really the higher aim.  Modern morality consists in\r
+accepting the standard of one's age.  I consider that for any man of\r
+culture to accept the standard of his age is a form of the grossest\r
+immorality."\r
+\r
+"But, surely, if one lives merely for one's self, Harry, one pays a\r
+terrible price for doing so?" suggested the painter.\r
+\r
+"Yes, we are overcharged for everything nowadays.  I should fancy that\r
+the real tragedy of the poor is that they can afford nothing but\r
+self-denial. Beautiful sins, like beautiful things, are the privilege\r
+of the rich."\r
+\r
+"One has to pay in other ways but money."\r
+\r
+"What sort of ways, Basil?"\r
+\r
+"Oh!  I should fancy in remorse, in suffering, in ... well, in the\r
+consciousness of degradation."\r
+\r
+Lord Henry shrugged his shoulders.  "My dear fellow, mediaeval art is\r
+charming, but mediaeval emotions are out of date.  One can use them in\r
+fiction, of course.  But then the only things that one can use in\r
+fiction are the things that one has ceased to use in fact.  Believe me,\r
+no civilized man ever regrets a pleasure, and no uncivilized man ever\r
+knows what a pleasure is."\r
+\r
+"I know what pleasure is," cried Dorian Gray.  "It is to adore some\r
+one."\r
+\r
+"That is certainly better than being adored," he answered, toying with\r
+some fruits.  "Being adored is a nuisance.  Women treat us just as\r
+humanity treats its gods.  They worship us, and are always bothering us\r
+to do something for them."\r
+\r
+"I should have said that whatever they ask for they had first given to\r
+us," murmured the lad gravely.  "They create love in our natures.  They\r
+have a right to demand it back."\r
+\r
+"That is quite true, Dorian," cried Hallward.\r
+\r
+"Nothing is ever quite true," said Lord Henry.\r
+\r
+"This is," interrupted Dorian.  "You must admit, Harry, that women give\r
+to men the very gold of their lives."\r
+\r
+"Possibly," he sighed, "but they invariably want it back in such very\r
+small change.  That is the worry.  Women, as some witty Frenchman once\r
+put it, inspire us with the desire to do masterpieces and always\r
+prevent us from carrying them out."\r
+\r
+"Harry, you are dreadful!  I don't know why I like you so much."\r
+\r
+"You will always like me, Dorian," he replied.  "Will you have some\r
+coffee, you fellows?  Waiter, bring coffee, and _fine-champagne_, and\r
+some cigarettes.  No, don't mind the cigarettes--I have some.  Basil, I\r
+can't allow you to smoke cigars.  You must have a cigarette.  A\r
+cigarette is the perfect type of a perfect pleasure.  It is exquisite,\r
+and it leaves one unsatisfied.  What more can one want?  Yes, Dorian,\r
+you will always be fond of me.  I represent to you all the sins you\r
+have never had the courage to commit."\r
+\r
+"What nonsense you talk, Harry!" cried the lad, taking a light from a\r
+fire-breathing silver dragon that the waiter had placed on the table.\r
+"Let us go down to the theatre.  When Sibyl comes on the stage you will\r
+have a new ideal of life.  She will represent something to you that you\r
+have never known."\r
+\r
+"I have known everything," said Lord Henry, with a tired look in his\r
+eyes, "but I am always ready for a new emotion.  I am afraid, however,\r
+that, for me at any rate, there is no such thing.  Still, your\r
+wonderful girl may thrill me.  I love acting.  It is so much more real\r
+than life.  Let us go.  Dorian, you will come with me.  I am so sorry,\r
+Basil, but there is only room for two in the brougham.  You must follow\r
+us in a hansom."\r
+\r
+They got up and put on their coats, sipping their coffee standing.  The\r
+painter was silent and preoccupied.  There was a gloom over him.  He\r
+could not bear this marriage, and yet it seemed to him to be better\r
+than many other things that might have happened.  After a few minutes,\r
+they all passed downstairs.  He drove off by himself, as had been\r
+arranged, and watched the flashing lights of the little brougham in\r
+front of him.  A strange sense of loss came over him.  He felt that\r
+Dorian Gray would never again be to him all that he had been in the\r
+past.  Life had come between them.... His eyes darkened, and the\r
+crowded flaring streets became blurred to his eyes.  When the cab drew\r
+up at the theatre, it seemed to him that he had grown years older.\r
+\r
+\r
+\r
+CHAPTER 7\r
+\r
+For some reason or other, the house was crowded that night, and the fat\r
+Jew manager who met them at the door was beaming from ear to ear with\r
+an oily tremulous smile.  He escorted them to their box with a sort of\r
+pompous humility, waving his fat jewelled hands and talking at the top\r
+of his voice.  Dorian Gray loathed him more than ever.  He felt as if\r
+he had come to look for Miranda and had been met by Caliban.  Lord\r
+Henry, upon the other hand, rather liked him.  At least he declared he\r
+did, and insisted on shaking him by the hand and assuring him that he\r
+was proud to meet a man who had discovered a real genius and gone\r
+bankrupt over a poet.  Hallward amused himself with watching the faces\r
+in the pit.  The heat was terribly oppressive, and the huge sunlight\r
+flamed like a monstrous dahlia with petals of yellow fire.  The youths\r
+in the gallery had taken off their coats and waistcoats and hung them\r
+over the side.  They talked to each other across the theatre and shared\r
+their oranges with the tawdry girls who sat beside them.  Some women\r
+were laughing in the pit.  Their voices were horribly shrill and\r
+discordant.  The sound of the popping of corks came from the bar.\r
+\r
+"What a place to find one's divinity in!" said Lord Henry.\r
+\r
+"Yes!" answered Dorian Gray.  "It was here I found her, and she is\r
+divine beyond all living things.  When she acts, you will forget\r
+everything.  These common rough people, with their coarse faces and\r
+brutal gestures, become quite different when she is on the stage.  They\r
+sit silently and watch her.  They weep and laugh as she wills them to\r
+do.  She makes them as responsive as a violin.  She spiritualizes them,\r
+and one feels that they are of the same flesh and blood as one's self."\r
+\r
+"The same flesh and blood as one's self!  Oh, I hope not!" exclaimed\r
+Lord Henry, who was scanning the occupants of the gallery through his\r
+opera-glass.\r
+\r
+"Don't pay any attention to him, Dorian," said the painter.  "I\r
+understand what you mean, and I believe in this girl.  Any one you love\r
+must be marvellous, and any girl who has the effect you describe must\r
+be fine and noble.  To spiritualize one's age--that is something worth\r
+doing.  If this girl can give a soul to those who have lived without\r
+one, if she can create the sense of beauty in people whose lives have\r
+been sordid and ugly, if she can strip them of their selfishness and\r
+lend them tears for sorrows that are not their own, she is worthy of\r
+all your adoration, worthy of the adoration of the world.  This\r
+marriage is quite right.  I did not think so at first, but I admit it\r
+now.  The gods made Sibyl Vane for you.  Without her you would have\r
+been incomplete."\r
+\r
+"Thanks, Basil," answered Dorian Gray, pressing his hand.  "I knew that\r
+you would understand me.  Harry is so cynical, he terrifies me.  But\r
+here is the orchestra.  It is quite dreadful, but it only lasts for\r
+about five minutes.  Then the curtain rises, and you will see the girl\r
+to whom I am going to give all my life, to whom I have given everything\r
+that is good in me."\r
+\r
+A quarter of an hour afterwards, amidst an extraordinary turmoil of\r
+applause, Sibyl Vane stepped on to the stage.  Yes, she was certainly\r
+lovely to look at--one of the loveliest creatures, Lord Henry thought,\r
+that he had ever seen.  There was something of the fawn in her shy\r
+grace and startled eyes.  A faint blush, like the shadow of a rose in a\r
+mirror of silver, came to her cheeks as she glanced at the crowded\r
+enthusiastic house.  She stepped back a few paces and her lips seemed\r
+to tremble.  Basil Hallward leaped to his feet and began to applaud.\r
+Motionless, and as one in a dream, sat Dorian Gray, gazing at her.\r
+Lord Henry peered through his glasses, murmuring, "Charming! charming!"\r
+\r
+The scene was the hall of Capulet's house, and Romeo in his pilgrim's\r
+dress had entered with Mercutio and his other friends.  The band, such\r
+as it was, struck up a few bars of music, and the dance began.  Through\r
+the crowd of ungainly, shabbily dressed actors, Sibyl Vane moved like a\r
+creature from a finer world.  Her body swayed, while she danced, as a\r
+plant sways in the water.  The curves of her throat were the curves of\r
+a white lily.  Her hands seemed to be made of cool ivory.\r
+\r
+Yet she was curiously listless.  She showed no sign of joy when her\r
+eyes rested on Romeo.  The few words she had to speak--\r
+\r
+    Good pilgrim, you do wrong your hand too much,\r
+        Which mannerly devotion shows in this;\r
+    For saints have hands that pilgrims' hands do touch,\r
+        And palm to palm is holy palmers' kiss--\r
+\r
+with the brief dialogue that follows, were spoken in a thoroughly\r
+artificial manner.  The voice was exquisite, but from the point of view\r
+of tone it was absolutely false.  It was wrong in colour.  It took away\r
+all the life from the verse.  It made the passion unreal.\r
+\r
+Dorian Gray grew pale as he watched her.  He was puzzled and anxious.\r
+Neither of his friends dared to say anything to him.  She seemed to\r
+them to be absolutely incompetent.  They were horribly disappointed.\r
+\r
+Yet they felt that the true test of any Juliet is the balcony scene of\r
+the second act.  They waited for that.  If she failed there, there was\r
+nothing in her.\r
+\r
+She looked charming as she came out in the moonlight.  That could not\r
+be denied.  But the staginess of her acting was unbearable, and grew\r
+worse as she went on.  Her gestures became absurdly artificial.  She\r
+overemphasized everything that she had to say.  The beautiful passage--\r
+\r
+    Thou knowest the mask of night is on my face,\r
+    Else would a maiden blush bepaint my cheek\r
+    For that which thou hast heard me speak to-night--\r
+\r
+was declaimed with the painful precision of a schoolgirl who has been\r
+taught to recite by some second-rate professor of elocution. When she\r
+leaned over the balcony and came to those wonderful lines--\r
+\r
+        Although I joy in thee,\r
+    I have no joy of this contract to-night:\r
+    It is too rash, too unadvised, too sudden;\r
+    Too like the lightning, which doth cease to be\r
+    Ere one can say, "It lightens."  Sweet, good-night!\r
+    This bud of love by summer's ripening breath\r
+    May prove a beauteous flower when next we meet--\r
+\r
+she spoke the words as though they conveyed no meaning to her. It was\r
+not nervousness. Indeed, so far from being nervous, she was absolutely\r
+self-contained. It was simply bad art. She was a complete failure.\r
+\r
+Even the common uneducated audience of the pit and gallery lost their\r
+interest in the play. They got restless, and began to talk loudly and\r
+to whistle. The Jew manager, who was standing at the back of the\r
+dress-circle, stamped and swore with rage. The only person unmoved was\r
+the girl herself.\r
+\r
+When the second act was over, there came a storm of hisses, and Lord\r
+Henry got up from his chair and put on his coat.  "She is quite\r
+beautiful, Dorian," he said, "but she can't act.  Let us go."\r
+\r
+"I am going to see the play through," answered the lad, in a hard\r
+bitter voice.  "I am awfully sorry that I have made you waste an\r
+evening, Harry.  I apologize to you both."\r
+\r
+"My dear Dorian, I should think Miss Vane was ill," interrupted\r
+Hallward.  "We will come some other night."\r
+\r
+"I wish she were ill," he rejoined.  "But she seems to me to be simply\r
+callous and cold.  She has entirely altered.  Last night she was a\r
+great artist.  This evening she is merely a commonplace mediocre\r
+actress."\r
+\r
+"Don't talk like that about any one you love, Dorian.  Love is a more\r
+wonderful thing than art."\r
+\r
+"They are both simply forms of imitation," remarked Lord Henry.  "But\r
+do let us go.  Dorian, you must not stay here any longer.  It is not\r
+good for one's morals to see bad acting.  Besides, I don't suppose you\r
+will want your wife to act, so what does it matter if she plays Juliet\r
+like a wooden doll?  She is very lovely, and if she knows as little\r
+about life as she does about acting, she will be a delightful\r
+experience.  There are only two kinds of people who are really\r
+fascinating--people who know absolutely everything, and people who know\r
+absolutely nothing.  Good heavens, my dear boy, don't look so tragic!\r
+The secret of remaining young is never to have an emotion that is\r
+unbecoming.  Come to the club with Basil and myself.  We will smoke\r
+cigarettes and drink to the beauty of Sibyl Vane.  She is beautiful.\r
+What more can you want?"\r
+\r
+"Go away, Harry," cried the lad.  "I want to be alone.  Basil, you must\r
+go.  Ah! can't you see that my heart is breaking?"  The hot tears came\r
+to his eyes.  His lips trembled, and rushing to the back of the box, he\r
+leaned up against the wall, hiding his face in his hands.\r
+\r
+"Let us go, Basil," said Lord Henry with a strange tenderness in his\r
+voice, and the two young men passed out together.\r
+\r
+A few moments afterwards the footlights flared up and the curtain rose\r
+on the third act.  Dorian Gray went back to his seat.  He looked pale,\r
+and proud, and indifferent.  The play dragged on, and seemed\r
+interminable.  Half of the audience went out, tramping in heavy boots\r
+and laughing.  The whole thing was a _fiasco_.  The last act was played\r
+to almost empty benches.  The curtain went down on a titter and some\r
+groans.\r
+\r
+As soon as it was over, Dorian Gray rushed behind the scenes into the\r
+greenroom.  The girl was standing there alone, with a look of triumph\r
+on her face.  Her eyes were lit with an exquisite fire.  There was a\r
+radiance about her.  Her parted lips were smiling over some secret of\r
+their own.\r
+\r
+When he entered, she looked at him, and an expression of infinite joy\r
+came over her.  "How badly I acted to-night, Dorian!" she cried.\r
+\r
+"Horribly!" he answered, gazing at her in amazement.  "Horribly!  It\r
+was dreadful.  Are you ill?  You have no idea what it was.  You have no\r
+idea what I suffered."\r
+\r
+The girl smiled.  "Dorian," she answered, lingering over his name with\r
+long-drawn music in her voice, as though it were sweeter than honey to\r
+the red petals of her mouth.  "Dorian, you should have understood.  But\r
+you understand now, don't you?"\r
+\r
+"Understand what?" he asked, angrily.\r
+\r
+"Why I was so bad to-night. Why I shall always be bad.  Why I shall\r
+never act well again."\r
+\r
+He shrugged his shoulders.  "You are ill, I suppose.  When you are ill\r
+you shouldn't act.  You make yourself ridiculous.  My friends were\r
+bored.  I was bored."\r
+\r
+She seemed not to listen to him.  She was transfigured with joy.  An\r
+ecstasy of happiness dominated her.\r
+\r
+"Dorian, Dorian," she cried, "before I knew you, acting was the one\r
+reality of my life.  It was only in the theatre that I lived.  I\r
+thought that it was all true.  I was Rosalind one night and Portia the\r
+other.  The joy of Beatrice was my joy, and the sorrows of Cordelia\r
+were mine also.  I believed in everything.  The common people who acted\r
+with me seemed to me to be godlike.  The painted scenes were my world.\r
+I knew nothing but shadows, and I thought them real.  You came--oh, my\r
+beautiful love!--and you freed my soul from prison.  You taught me what\r
+reality really is.  To-night, for the first time in my life, I saw\r
+through the hollowness, the sham, the silliness of the empty pageant in\r
+which I had always played.  To-night, for the first time, I became\r
+conscious that the Romeo was hideous, and old, and painted, that the\r
+moonlight in the orchard was false, that the scenery was vulgar, and\r
+that the words I had to speak were unreal, were not my words, were not\r
+what I wanted to say.  You had brought me something higher, something\r
+of which all art is but a reflection.  You had made me understand what\r
+love really is.  My love!  My love!  Prince Charming!  Prince of life!\r
+I have grown sick of shadows.  You are more to me than all art can ever\r
+be.  What have I to do with the puppets of a play?  When I came on\r
+to-night, I could not understand how it was that everything had gone\r
+from me.  I thought that I was going to be wonderful.  I found that I\r
+could do nothing.  Suddenly it dawned on my soul what it all meant.\r
+The knowledge was exquisite to me.  I heard them hissing, and I smiled.\r
+What could they know of love such as ours?  Take me away, Dorian--take\r
+me away with you, where we can be quite alone.  I hate the stage.  I\r
+might mimic a passion that I do not feel, but I cannot mimic one that\r
+burns me like fire.  Oh, Dorian, Dorian, you understand now what it\r
+signifies?  Even if I could do it, it would be profanation for me to\r
+play at being in love.  You have made me see that."\r
+\r
+He flung himself down on the sofa and turned away his face.  "You have\r
+killed my love," he muttered.\r
+\r
+She looked at him in wonder and laughed.  He made no answer.  She came\r
+across to him, and with her little fingers stroked his hair.  She knelt\r
+down and pressed his hands to her lips.  He drew them away, and a\r
+shudder ran through him.\r
+\r
+Then he leaped up and went to the door.  "Yes," he cried, "you have\r
+killed my love.  You used to stir my imagination.  Now you don't even\r
+stir my curiosity.  You simply produce no effect.  I loved you because\r
+you were marvellous, because you had genius and intellect, because you\r
+realized the dreams of great poets and gave shape and substance to the\r
+shadows of art.  You have thrown it all away.  You are shallow and\r
+stupid.  My God! how mad I was to love you!  What a fool I have been!\r
+You are nothing to me now.  I will never see you again.  I will never\r
+think of you.  I will never mention your name.  You don't know what you\r
+were to me, once.  Why, once ... Oh, I can't bear to think of it!  I\r
+wish I had never laid eyes upon you!  You have spoiled the romance of\r
+my life.  How little you can know of love, if you say it mars your art!\r
+Without your art, you are nothing.  I would have made you famous,\r
+splendid, magnificent.  The world would have worshipped you, and you\r
+would have borne my name.  What are you now?  A third-rate actress with\r
+a pretty face."\r
+\r
+The girl grew white, and trembled.  She clenched her hands together,\r
+and her voice seemed to catch in her throat.  "You are not serious,\r
+Dorian?" she murmured.  "You are acting."\r
+\r
+"Acting!  I leave that to you.  You do it so well," he answered\r
+bitterly.\r
+\r
+She rose from her knees and, with a piteous expression of pain in her\r
+face, came across the room to him.  She put her hand upon his arm and\r
+looked into his eyes.  He thrust her back.  "Don't touch me!" he cried.\r
+\r
+A low moan broke from her, and she flung herself at his feet and lay\r
+there like a trampled flower.  "Dorian, Dorian, don't leave me!" she\r
+whispered.  "I am so sorry I didn't act well.  I was thinking of you\r
+all the time.  But I will try--indeed, I will try.  It came so suddenly\r
+across me, my love for you.  I think I should never have known it if\r
+you had not kissed me--if we had not kissed each other.  Kiss me again,\r
+my love.  Don't go away from me.  I couldn't bear it.  Oh! don't go\r
+away from me.  My brother ... No; never mind.  He didn't mean it.  He\r
+was in jest.... But you, oh! can't you forgive me for to-night? I will\r
+work so hard and try to improve.  Don't be cruel to me, because I love\r
+you better than anything in the world.  After all, it is only once that\r
+I have not pleased you.  But you are quite right, Dorian.  I should\r
+have shown myself more of an artist.  It was foolish of me, and yet I\r
+couldn't help it.  Oh, don't leave me, don't leave me." A fit of\r
+passionate sobbing choked her.  She crouched on the floor like a\r
+wounded thing, and Dorian Gray, with his beautiful eyes, looked down at\r
+her, and his chiselled lips curled in exquisite disdain.  There is\r
+always something ridiculous about the emotions of people whom one has\r
+ceased to love.  Sibyl Vane seemed to him to be absurdly melodramatic.\r
+Her tears and sobs annoyed him.\r
+\r
+"I am going," he said at last in his calm clear voice.  "I don't wish\r
+to be unkind, but I can't see you again.  You have disappointed me."\r
+\r
+She wept silently, and made no answer, but crept nearer.  Her little\r
+hands stretched blindly out, and appeared to be seeking for him.  He\r
+turned on his heel and left the room.  In a few moments he was out of\r
+the theatre.\r
+\r
+Where he went to he hardly knew.  He remembered wandering through dimly\r
+lit streets, past gaunt, black-shadowed archways and evil-looking\r
+houses.  Women with hoarse voices and harsh laughter had called after\r
+him.  Drunkards had reeled by, cursing and chattering to themselves\r
+like monstrous apes.  He had seen grotesque children huddled upon\r
+door-steps, and heard shrieks and oaths from gloomy courts.\r
+\r
+As the dawn was just breaking, he found himself close to Covent Garden.\r
+The darkness lifted, and, flushed with faint fires, the sky hollowed\r
+itself into a perfect pearl.  Huge carts filled with nodding lilies\r
+rumbled slowly down the polished empty street.  The air was heavy with\r
+the perfume of the flowers, and their beauty seemed to bring him an\r
+anodyne for his pain.  He followed into the market and watched the men\r
+unloading their waggons.  A white-smocked carter offered him some\r
+cherries.  He thanked him, wondered why he refused to accept any money\r
+for them, and began to eat them listlessly.  They had been plucked at\r
+midnight, and the coldness of the moon had entered into them.  A long\r
+line of boys carrying crates of striped tulips, and of yellow and red\r
+roses, defiled in front of him, threading their way through the huge,\r
+jade-green piles of vegetables.  Under the portico, with its grey,\r
+sun-bleached pillars, loitered a troop of draggled bareheaded girls,\r
+waiting for the auction to be over.  Others crowded round the swinging\r
+doors of the coffee-house in the piazza.  The heavy cart-horses slipped\r
+and stamped upon the rough stones, shaking their bells and trappings.\r
+Some of the drivers were lying asleep on a pile of sacks.  Iris-necked\r
+and pink-footed, the pigeons ran about picking up seeds.\r
+\r
+After a little while, he hailed a hansom and drove home.  For a few\r
+moments he loitered upon the doorstep, looking round at the silent\r
+square, with its blank, close-shuttered windows and its staring blinds.\r
+The sky was pure opal now, and the roofs of the houses glistened like\r
+silver against it.  From some chimney opposite a thin wreath of smoke\r
+was rising.  It curled, a violet riband, through the nacre-coloured air.\r
+\r
+In the huge gilt Venetian lantern, spoil of some Doge's barge, that\r
+hung from the ceiling of the great, oak-panelled hall of entrance,\r
+lights were still burning from three flickering jets: thin blue petals\r
+of flame they seemed, rimmed with white fire.  He turned them out and,\r
+having thrown his hat and cape on the table, passed through the library\r
+towards the door of his bedroom, a large octagonal chamber on the\r
+ground floor that, in his new-born feeling for luxury, he had just had\r
+decorated for himself and hung with some curious Renaissance tapestries\r
+that had been discovered stored in a disused attic at Selby Royal.  As\r
+he was turning the handle of the door, his eye fell upon the portrait\r
+Basil Hallward had painted of him.  He started back as if in surprise.\r
+Then he went on into his own room, looking somewhat puzzled.  After he\r
+had taken the button-hole out of his coat, he seemed to hesitate.\r
+Finally, he came back, went over to the picture, and examined it.  In\r
+the dim arrested light that struggled through the cream-coloured silk\r
+blinds, the face appeared to him to be a little changed.  The\r
+expression looked different.  One would have said that there was a\r
+touch of cruelty in the mouth.  It was certainly strange.\r
+\r
+He turned round and, walking to the window, drew up the blind.  The\r
+bright dawn flooded the room and swept the fantastic shadows into dusky\r
+corners, where they lay shuddering.  But the strange expression that he\r
+had noticed in the face of the portrait seemed to linger there, to be\r
+more intensified even.  The quivering ardent sunlight showed him the\r
+lines of cruelty round the mouth as clearly as if he had been looking\r
+into a mirror after he had done some dreadful thing.\r
+\r
+He winced and, taking up from the table an oval glass framed in ivory\r
+Cupids, one of Lord Henry's many presents to him, glanced hurriedly\r
+into its polished depths.  No line like that warped his red lips.  What\r
+did it mean?\r
+\r
+He rubbed his eyes, and came close to the picture, and examined it\r
+again.  There were no signs of any change when he looked into the\r
+actual painting, and yet there was no doubt that the whole expression\r
+had altered.  It was not a mere fancy of his own.  The thing was\r
+horribly apparent.\r
+\r
+He threw himself into a chair and began to think.  Suddenly there\r
+flashed across his mind what he had said in Basil Hallward's studio the\r
+day the picture had been finished.  Yes, he remembered it perfectly.\r
+He had uttered a mad wish that he himself might remain young, and the\r
+portrait grow old; that his own beauty might be untarnished, and the\r
+face on the canvas bear the burden of his passions and his sins; that\r
+the painted image might be seared with the lines of suffering and\r
+thought, and that he might keep all the delicate bloom and loveliness\r
+of his then just conscious boyhood.  Surely his wish had not been\r
+fulfilled?  Such things were impossible.  It seemed monstrous even to\r
+think of them.  And, yet, there was the picture before him, with the\r
+touch of cruelty in the mouth.\r
+\r
+Cruelty!  Had he been cruel?  It was the girl's fault, not his.  He had\r
+dreamed of her as a great artist, had given his love to her because he\r
+had thought her great.  Then she had disappointed him.  She had been\r
+shallow and unworthy.  And, yet, a feeling of infinite regret came over\r
+him, as he thought of her lying at his feet sobbing like a little\r
+child.  He remembered with what callousness he had watched her.  Why\r
+had he been made like that?  Why had such a soul been given to him?\r
+But he had suffered also.  During the three terrible hours that the\r
+play had lasted, he had lived centuries of pain, aeon upon aeon of\r
+torture.  His life was well worth hers.  She had marred him for a\r
+moment, if he had wounded her for an age.  Besides, women were better\r
+suited to bear sorrow than men.  They lived on their emotions.  They\r
+only thought of their emotions.  When they took lovers, it was merely\r
+to have some one with whom they could have scenes.  Lord Henry had told\r
+him that, and Lord Henry knew what women were.  Why should he trouble\r
+about Sibyl Vane?  She was nothing to him now.\r
+\r
+But the picture?  What was he to say of that?  It held the secret of\r
+his life, and told his story.  It had taught him to love his own\r
+beauty.  Would it teach him to loathe his own soul?  Would he ever look\r
+at it again?\r
+\r
+No; it was merely an illusion wrought on the troubled senses.  The\r
+horrible night that he had passed had left phantoms behind it.\r
+Suddenly there had fallen upon his brain that tiny scarlet speck that\r
+makes men mad.  The picture had not changed.  It was folly to think so.\r
+\r
+Yet it was watching him, with its beautiful marred face and its cruel\r
+smile.  Its bright hair gleamed in the early sunlight.  Its blue eyes\r
+met his own.  A sense of infinite pity, not for himself, but for the\r
+painted image of himself, came over him.  It had altered already, and\r
+would alter more.  Its gold would wither into grey.  Its red and white\r
+roses would die.  For every sin that he committed, a stain would fleck\r
+and wreck its fairness.  But he would not sin.  The picture, changed or\r
+unchanged, would be to him the visible emblem of conscience.  He would\r
+resist temptation.  He would not see Lord Henry any more--would not, at\r
+any rate, listen to those subtle poisonous theories that in Basil\r
+Hallward's garden had first stirred within him the passion for\r
+impossible things.  He would go back to Sibyl Vane, make her amends,\r
+marry her, try to love her again.  Yes, it was his duty to do so.  She\r
+must have suffered more than he had.  Poor child!  He had been selfish\r
+and cruel to her.  The fascination that she had exercised over him\r
+would return.  They would be happy together.  His life with her would\r
+be beautiful and pure.\r
+\r
+He got up from his chair and drew a large screen right in front of the\r
+portrait, shuddering as he glanced at it.  "How horrible!" he murmured\r
+to himself, and he walked across to the window and opened it.  When he\r
+stepped out on to the grass, he drew a deep breath.  The fresh morning\r
+air seemed to drive away all his sombre passions.  He thought only of\r
+Sibyl.  A faint echo of his love came back to him.  He repeated her\r
+name over and over again.  The birds that were singing in the\r
+dew-drenched garden seemed to be telling the flowers about her.\r
+\r
+\r
+\r
+CHAPTER 8\r
+\r
+It was long past noon when he awoke.  His valet had crept several times\r
+on tiptoe into the room to see if he was stirring, and had wondered\r
+what made his young master sleep so late.  Finally his bell sounded,\r
+and Victor came in softly with a cup of tea, and a pile of letters, on\r
+a small tray of old Sevres china, and drew back the olive-satin\r
+curtains, with their shimmering blue lining, that hung in front of the\r
+three tall windows.\r
+\r
+"Monsieur has well slept this morning," he said, smiling.\r
+\r
+"What o'clock is it, Victor?" asked Dorian Gray drowsily.\r
+\r
+"One hour and a quarter, Monsieur."\r
+\r
+How late it was!  He sat up, and having sipped some tea, turned over\r
+his letters.  One of them was from Lord Henry, and had been brought by\r
+hand that morning.  He hesitated for a moment, and then put it aside.\r
+The others he opened listlessly.  They contained the usual collection\r
+of cards, invitations to dinner, tickets for private views, programmes\r
+of charity concerts, and the like that are showered on fashionable\r
+young men every morning during the season.  There was a rather heavy\r
+bill for a chased silver Louis-Quinze toilet-set that he had not yet\r
+had the courage to send on to his guardians, who were extremely\r
+old-fashioned people and did not realize that we live in an age when\r
+unnecessary things are our only necessities; and there were several\r
+very courteously worded communications from Jermyn Street money-lenders\r
+offering to advance any sum of money at a moment's notice and at the\r
+most reasonable rates of interest.\r
+\r
+After about ten minutes he got up, and throwing on an elaborate\r
+dressing-gown of silk-embroidered cashmere wool, passed into the\r
+onyx-paved bathroom.  The cool water refreshed him after his long\r
+sleep.  He seemed to have forgotten all that he had gone through.  A\r
+dim sense of having taken part in some strange tragedy came to him once\r
+or twice, but there was the unreality of a dream about it.\r
+\r
+As soon as he was dressed, he went into the library and sat down to a\r
+light French breakfast that had been laid out for him on a small round\r
+table close to the open window.  It was an exquisite day.  The warm air\r
+seemed laden with spices.  A bee flew in and buzzed round the\r
+blue-dragon bowl that, filled with sulphur-yellow roses, stood before\r
+him.  He felt perfectly happy.\r
+\r
+Suddenly his eye fell on the screen that he had placed in front of the\r
+portrait, and he started.\r
+\r
+"Too cold for Monsieur?" asked his valet, putting an omelette on the\r
+table.  "I shut the window?"\r
+\r
+Dorian shook his head.  "I am not cold," he murmured.\r
+\r
+Was it all true?  Had the portrait really changed?  Or had it been\r
+simply his own imagination that had made him see a look of evil where\r
+there had been a look of joy?  Surely a painted canvas could not alter?\r
+The thing was absurd.  It would serve as a tale to tell Basil some day.\r
+It would make him smile.\r
+\r
+And, yet, how vivid was his recollection of the whole thing!  First in\r
+the dim twilight, and then in the bright dawn, he had seen the touch of\r
+cruelty round the warped lips.  He almost dreaded his valet leaving the\r
+room.  He knew that when he was alone he would have to examine the\r
+portrait.  He was afraid of certainty.  When the coffee and cigarettes\r
+had been brought and the man turned to go, he felt a wild desire to\r
+tell him to remain.  As the door was closing behind him, he called him\r
+back.  The man stood waiting for his orders.  Dorian looked at him for\r
+a moment.  "I am not at home to any one, Victor," he said with a sigh.\r
+The man bowed and retired.\r
+\r
+Then he rose from the table, lit a cigarette, and flung himself down on\r
+a luxuriously cushioned couch that stood facing the screen.  The screen\r
+was an old one, of gilt Spanish leather, stamped and wrought with a\r
+rather florid Louis-Quatorze pattern.  He scanned it curiously,\r
+wondering if ever before it had concealed the secret of a man's life.\r
+\r
+Should he move it aside, after all?  Why not let it stay there?  What\r
+was the use of knowing? If the thing was true, it was terrible.  If it\r
+was not true, why trouble about it?  But what if, by some fate or\r
+deadlier chance, eyes other than his spied behind and saw the horrible\r
+change?  What should he do if Basil Hallward came and asked to look at\r
+his own picture?  Basil would be sure to do that.  No; the thing had to\r
+be examined, and at once.  Anything would be better than this dreadful\r
+state of doubt.\r
+\r
+He got up and locked both doors.  At least he would be alone when he\r
+looked upon the mask of his shame.  Then he drew the screen aside and\r
+saw himself face to face.  It was perfectly true.  The portrait had\r
+altered.\r
+\r
+As he often remembered afterwards, and always with no small wonder, he\r
+found himself at first gazing at the portrait with a feeling of almost\r
+scientific interest.  That such a change should have taken place was\r
+incredible to him.  And yet it was a fact.  Was there some subtle\r
+affinity between the chemical atoms that shaped themselves into form\r
+and colour on the canvas and the soul that was within him?  Could it be\r
+that what that soul thought, they realized?--that what it dreamed, they\r
+made true?  Or was there some other, more terrible reason?  He\r
+shuddered, and felt afraid, and, going back to the couch, lay there,\r
+gazing at the picture in sickened horror.\r
+\r
+One thing, however, he felt that it had done for him.  It had made him\r
+conscious how unjust, how cruel, he had been to Sibyl Vane.  It was not\r
+too late to make reparation for that.  She could still be his wife.\r
+His unreal and selfish love would yield to some higher influence, would\r
+be transformed into some nobler passion, and the portrait that Basil\r
+Hallward had painted of him would be a guide to him through life, would\r
+be to him what holiness is to some, and conscience to others, and the\r
+fear of God to us all.  There were opiates for remorse, drugs that\r
+could lull the moral sense to sleep.  But here was a visible symbol of\r
+the degradation of sin.  Here was an ever-present sign of the ruin men\r
+brought upon their souls.\r
+\r
+Three o'clock struck, and four, and the half-hour rang its double\r
+chime, but Dorian Gray did not stir.  He was trying to gather up the\r
+scarlet threads of life and to weave them into a pattern; to find his\r
+way through the sanguine labyrinth of passion through which he was\r
+wandering.  He did not know what to do, or what to think.  Finally, he\r
+went over to the table and wrote a passionate letter to the girl he had\r
+loved, imploring her forgiveness and accusing himself of madness.  He\r
+covered page after page with wild words of sorrow and wilder words of\r
+pain.  There is a luxury in self-reproach. When we blame ourselves, we\r
+feel that no one else has a right to blame us.  It is the confession,\r
+not the priest, that gives us absolution.  When Dorian had finished the\r
+letter, he felt that he had been forgiven.\r
+\r
+Suddenly there came a knock to the door, and he heard Lord Henry's\r
+voice outside.  "My dear boy, I must see you.  Let me in at once.  I\r
+can't bear your shutting yourself up like this."\r
+\r
+He made no answer at first, but remained quite still.  The knocking\r
+still continued and grew louder.  Yes, it was better to let Lord Henry\r
+in, and to explain to him the new life he was going to lead, to quarrel\r
+with him if it became necessary to quarrel, to part if parting was\r
+inevitable.  He jumped up, drew the screen hastily across the picture,\r
+and unlocked the door.\r
+\r
+"I am so sorry for it all, Dorian," said Lord Henry as he entered.\r
+"But you must not think too much about it."\r
+\r
+"Do you mean about Sibyl Vane?" asked the lad.\r
+\r
+"Yes, of course," answered Lord Henry, sinking into a chair and slowly\r
+pulling off his yellow gloves.  "It is dreadful, from one point of\r
+view, but it was not your fault.  Tell me, did you go behind and see\r
+her, after the play was over?"\r
+\r
+"Yes."\r
+\r
+"I felt sure you had.  Did you make a scene with her?"\r
+\r
+"I was brutal, Harry--perfectly brutal.  But it is all right now.  I am\r
+not sorry for anything that has happened.  It has taught me to know\r
+myself better."\r
+\r
+"Ah, Dorian, I am so glad you take it in that way!  I was afraid I\r
+would find you plunged in remorse and tearing that nice curly hair of\r
+yours."\r
+\r
+"I have got through all that," said Dorian, shaking his head and\r
+smiling.  "I am perfectly happy now.  I know what conscience is, to\r
+begin with.  It is not what you told me it was.  It is the divinest\r
+thing in us.  Don't sneer at it, Harry, any more--at least not before\r
+me.  I want to be good.  I can't bear the idea of my soul being\r
+hideous."\r
+\r
+"A very charming artistic basis for ethics, Dorian!  I congratulate you\r
+on it.  But how are you going to begin?"\r
+\r
+"By marrying Sibyl Vane."\r
+\r
+"Marrying Sibyl Vane!" cried Lord Henry, standing up and looking at him\r
+in perplexed amazement.  "But, my dear Dorian--"\r
+\r
+"Yes, Harry, I know what you are going to say.  Something dreadful\r
+about marriage.  Don't say it.  Don't ever say things of that kind to\r
+me again.  Two days ago I asked Sibyl to marry me.  I am not going to\r
+break my word to her.  She is to be my wife."\r
+\r
+"Your wife!  Dorian! ... Didn't you get my letter?  I wrote to you this\r
+morning, and sent the note down by my own man."\r
+\r
+"Your letter?  Oh, yes, I remember.  I have not read it yet, Harry.  I\r
+was afraid there might be something in it that I wouldn't like.  You\r
+cut life to pieces with your epigrams."\r
+\r
+"You know nothing then?"\r
+\r
+"What do you mean?"\r
+\r
+Lord Henry walked across the room, and sitting down by Dorian Gray,\r
+took both his hands in his own and held them tightly.  "Dorian," he\r
+said, "my letter--don't be frightened--was to tell you that Sibyl Vane\r
+is dead."\r
+\r
+A cry of pain broke from the lad's lips, and he leaped to his feet,\r
+tearing his hands away from Lord Henry's grasp.  "Dead!  Sibyl dead!\r
+It is not true!  It is a horrible lie!  How dare you say it?"\r
+\r
+"It is quite true, Dorian," said Lord Henry, gravely.  "It is in all\r
+the morning papers.  I wrote down to you to ask you not to see any one\r
+till I came.  There will have to be an inquest, of course, and you must\r
+not be mixed up in it.  Things like that make a man fashionable in\r
+Paris.  But in London people are so prejudiced.  Here, one should never\r
+make one's _debut_ with a scandal.  One should reserve that to give an\r
+interest to one's old age.  I suppose they don't know your name at the\r
+theatre?  If they don't, it is all right.  Did any one see you going\r
+round to her room?  That is an important point."\r
+\r
+Dorian did not answer for a few moments.  He was dazed with horror.\r
+Finally he stammered, in a stifled voice, "Harry, did you say an\r
+inquest?  What did you mean by that?  Did Sibyl--? Oh, Harry, I can't\r
+bear it!  But be quick.  Tell me everything at once."\r
+\r
+"I have no doubt it was not an accident, Dorian, though it must be put\r
+in that way to the public.  It seems that as she was leaving the\r
+theatre with her mother, about half-past twelve or so, she said she had\r
+forgotten something upstairs.  They waited some time for her, but she\r
+did not come down again.  They ultimately found her lying dead on the\r
+floor of her dressing-room. She had swallowed something by mistake,\r
+some dreadful thing they use at theatres.  I don't know what it was,\r
+but it had either prussic acid or white lead in it.  I should fancy it\r
+was prussic acid, as she seems to have died instantaneously."\r
+\r
+"Harry, Harry, it is terrible!" cried the lad.\r
+\r
+"Yes; it is very tragic, of course, but you must not get yourself mixed\r
+up in it.  I see by _The Standard_ that she was seventeen.  I should have\r
+thought she was almost younger than that.  She looked such a child, and\r
+seemed to know so little about acting.  Dorian, you mustn't let this\r
+thing get on your nerves.  You must come and dine with me, and\r
+afterwards we will look in at the opera.  It is a Patti night, and\r
+everybody will be there.  You can come to my sister's box.  She has got\r
+some smart women with her."\r
+\r
+"So I have murdered Sibyl Vane," said Dorian Gray, half to himself,\r
+"murdered her as surely as if I had cut her little throat with a knife.\r
+Yet the roses are not less lovely for all that.  The birds sing just as\r
+happily in my garden.  And to-night I am to dine with you, and then go\r
+on to the opera, and sup somewhere, I suppose, afterwards.  How\r
+extraordinarily dramatic life is!  If I had read all this in a book,\r
+Harry, I think I would have wept over it.  Somehow, now that it has\r
+happened actually, and to me, it seems far too wonderful for tears.\r
+Here is the first passionate love-letter I have ever written in my\r
+life.  Strange, that my first passionate love-letter should have been\r
+addressed to a dead girl.  Can they feel, I wonder, those white silent\r
+people we call the dead?  Sibyl!  Can she feel, or know, or listen?\r
+Oh, Harry, how I loved her once!  It seems years ago to me now.  She\r
+was everything to me.  Then came that dreadful night--was it really\r
+only last night?--when she played so badly, and my heart almost broke.\r
+She explained it all to me.  It was terribly pathetic.  But I was not\r
+moved a bit.  I thought her shallow.  Suddenly something happened that\r
+made me afraid.  I can't tell you what it was, but it was terrible.  I\r
+said I would go back to her.  I felt I had done wrong.  And now she is\r
+dead.  My God!  My God!  Harry, what shall I do?  You don't know the\r
+danger I am in, and there is nothing to keep me straight.  She would\r
+have done that for me.  She had no right to kill herself.  It was\r
+selfish of her."\r
+\r
+"My dear Dorian," answered Lord Henry, taking a cigarette from his case\r
+and producing a gold-latten matchbox, "the only way a woman can ever\r
+reform a man is by boring him so completely that he loses all possible\r
+interest in life.  If you had married this girl, you would have been\r
+wretched.  Of course, you would have treated her kindly.  One can\r
+always be kind to people about whom one cares nothing.  But she would\r
+have soon found out that you were absolutely indifferent to her.  And\r
+when a woman finds that out about her husband, she either becomes\r
+dreadfully dowdy, or wears very smart bonnets that some other woman's\r
+husband has to pay for.  I say nothing about the social mistake, which\r
+would have been abject--which, of course, I would not have allowed--but\r
+I assure you that in any case the whole thing would have been an\r
+absolute failure."\r
+\r
+"I suppose it would," muttered the lad, walking up and down the room\r
+and looking horribly pale.  "But I thought it was my duty.  It is not\r
+my fault that this terrible tragedy has prevented my doing what was\r
+right.  I remember your saying once that there is a fatality about good\r
+resolutions--that they are always made too late.  Mine certainly were."\r
+\r
+"Good resolutions are useless attempts to interfere with scientific\r
+laws.  Their origin is pure vanity.  Their result is absolutely _nil_.\r
+They give us, now and then, some of those luxurious sterile emotions\r
+that have a certain charm for the weak.  That is all that can be said\r
+for them.  They are simply cheques that men draw on a bank where they\r
+have no account."\r
+\r
+"Harry," cried Dorian Gray, coming over and sitting down beside him,\r
+"why is it that I cannot feel this tragedy as much as I want to?  I\r
+don't think I am heartless.  Do you?"\r
+\r
+"You have done too many foolish things during the last fortnight to be\r
+entitled to give yourself that name, Dorian," answered Lord Henry with\r
+his sweet melancholy smile.\r
+\r
+The lad frowned.  "I don't like that explanation, Harry," he rejoined,\r
+"but I am glad you don't think I am heartless.  I am nothing of the\r
+kind.  I know I am not.  And yet I must admit that this thing that has\r
+happened does not affect me as it should.  It seems to me to be simply\r
+like a wonderful ending to a wonderful play.  It has all the terrible\r
+beauty of a Greek tragedy, a tragedy in which I took a great part, but\r
+by which I have not been wounded."\r
+\r
+"It is an interesting question," said Lord Henry, who found an\r
+exquisite pleasure in playing on the lad's unconscious egotism, "an\r
+extremely interesting question.  I fancy that the true explanation is\r
+this:  It often happens that the real tragedies of life occur in such\r
+an inartistic manner that they hurt us by their crude violence, their\r
+absolute incoherence, their absurd want of meaning, their entire lack\r
+of style.  They affect us just as vulgarity affects us.  They give us\r
+an impression of sheer brute force, and we revolt against that.\r
+Sometimes, however, a tragedy that possesses artistic elements of\r
+beauty crosses our lives.  If these elements of beauty are real, the\r
+whole thing simply appeals to our sense of dramatic effect.  Suddenly\r
+we find that we are no longer the actors, but the spectators of the\r
+play.  Or rather we are both.  We watch ourselves, and the mere wonder\r
+of the spectacle enthralls us.  In the present case, what is it that\r
+has really happened?  Some one has killed herself for love of you.  I\r
+wish that I had ever had such an experience.  It would have made me in\r
+love with love for the rest of my life.  The people who have adored\r
+me--there have not been very many, but there have been some--have\r
+always insisted on living on, long after I had ceased to care for them,\r
+or they to care for me.  They have become stout and tedious, and when I\r
+meet them, they go in at once for reminiscences.  That awful memory of\r
+woman!  What a fearful thing it is!  And what an utter intellectual\r
+stagnation it reveals!  One should absorb the colour of life, but one\r
+should never remember its details.  Details are always vulgar."\r
+\r
+"I must sow poppies in my garden," sighed Dorian.\r
+\r
+"There is no necessity," rejoined his companion.  "Life has always\r
+poppies in her hands.  Of course, now and then things linger.  I once\r
+wore nothing but violets all through one season, as a form of artistic\r
+mourning for a romance that would not die.  Ultimately, however, it did\r
+die.  I forget what killed it.  I think it was her proposing to\r
+sacrifice the whole world for me.  That is always a dreadful moment.\r
+It fills one with the terror of eternity.  Well--would you believe\r
+it?--a week ago, at Lady Hampshire's, I found myself seated at dinner\r
+next the lady in question, and she insisted on going over the whole\r
+thing again, and digging up the past, and raking up the future.  I had\r
+buried my romance in a bed of asphodel.  She dragged it out again and\r
+assured me that I had spoiled her life.  I am bound to state that she\r
+ate an enormous dinner, so I did not feel any anxiety.  But what a lack\r
+of taste she showed!  The one charm of the past is that it is the past.\r
+But women never know when the curtain has fallen.  They always want a\r
+sixth act, and as soon as the interest of the play is entirely over,\r
+they propose to continue it.  If they were allowed their own way, every\r
+comedy would have a tragic ending, and every tragedy would culminate in\r
+a farce.  They are charmingly artificial, but they have no sense of\r
+art.  You are more fortunate than I am.  I assure you, Dorian, that not\r
+one of the women I have known would have done for me what Sibyl Vane\r
+did for you.  Ordinary women always console themselves.  Some of them\r
+do it by going in for sentimental colours.  Never trust a woman who\r
+wears mauve, whatever her age may be, or a woman over thirty-five who\r
+is fond of pink ribbons.  It always means that they have a history.\r
+Others find a great consolation in suddenly discovering the good\r
+qualities of their husbands.  They flaunt their conjugal felicity in\r
+one's face, as if it were the most fascinating of sins.  Religion\r
+consoles some.  Its mysteries have all the charm of a flirtation, a\r
+woman once told me, and I can quite understand it.  Besides, nothing\r
+makes one so vain as being told that one is a sinner.  Conscience makes\r
+egotists of us all.  Yes; there is really no end to the consolations\r
+that women find in modern life.  Indeed, I have not mentioned the most\r
+important one."\r
+\r
+"What is that, Harry?" said the lad listlessly.\r
+\r
+"Oh, the obvious consolation.  Taking some one else's admirer when one\r
+loses one's own.  In good society that always whitewashes a woman.  But\r
+really, Dorian, how different Sibyl Vane must have been from all the\r
+women one meets!  There is something to me quite beautiful about her\r
+death.  I am glad I am living in a century when such wonders happen.\r
+They make one believe in the reality of the things we all play with,\r
+such as romance, passion, and love."\r
+\r
+"I was terribly cruel to her.  You forget that."\r
+\r
+"I am afraid that women appreciate cruelty, downright cruelty, more\r
+than anything else.  They have wonderfully primitive instincts.  We\r
+have emancipated them, but they remain slaves looking for their\r
+masters, all the same.  They love being dominated.  I am sure you were\r
+splendid.  I have never seen you really and absolutely angry, but I can\r
+fancy how delightful you looked.  And, after all, you said something to\r
+me the day before yesterday that seemed to me at the time to be merely\r
+fanciful, but that I see now was absolutely true, and it holds the key\r
+to everything."\r
+\r
+"What was that, Harry?"\r
+\r
+"You said to me that Sibyl Vane represented to you all the heroines of\r
+romance--that she was Desdemona one night, and Ophelia the other; that\r
+if she died as Juliet, she came to life as Imogen."\r
+\r
+"She will never come to life again now," muttered the lad, burying his\r
+face in his hands.\r
+\r
+"No, she will never come to life.  She has played her last part.  But\r
+you must think of that lonely death in the tawdry dressing-room simply\r
+as a strange lurid fragment from some Jacobean tragedy, as a wonderful\r
+scene from Webster, or Ford, or Cyril Tourneur.  The girl never really\r
+lived, and so she has never really died.  To you at least she was\r
+always a dream, a phantom that flitted through Shakespeare's plays and\r
+left them lovelier for its presence, a reed through which Shakespeare's\r
+music sounded richer and more full of joy.  The moment she touched\r
+actual life, she marred it, and it marred her, and so she passed away.\r
+Mourn for Ophelia, if you like.  Put ashes on your head because\r
+Cordelia was strangled.  Cry out against Heaven because the daughter of\r
+Brabantio died.  But don't waste your tears over Sibyl Vane.  She was\r
+less real than they are."\r
+\r
+There was a silence.  The evening darkened in the room.  Noiselessly,\r
+and with silver feet, the shadows crept in from the garden.  The\r
+colours faded wearily out of things.\r
+\r
+After some time Dorian Gray looked up.  "You have explained me to\r
+myself, Harry," he murmured with something of a sigh of relief.  "I\r
+felt all that you have said, but somehow I was afraid of it, and I\r
+could not express it to myself.  How well you know me!  But we will not\r
+talk again of what has happened.  It has been a marvellous experience.\r
+That is all.  I wonder if life has still in store for me anything as\r
+marvellous."\r
+\r
+"Life has everything in store for you, Dorian.  There is nothing that\r
+you, with your extraordinary good looks, will not be able to do."\r
+\r
+"But suppose, Harry, I became haggard, and old, and wrinkled?  What\r
+then?"\r
+\r
+"Ah, then," said Lord Henry, rising to go, "then, my dear Dorian, you\r
+would have to fight for your victories.  As it is, they are brought to\r
+you.  No, you must keep your good looks.  We live in an age that reads\r
+too much to be wise, and that thinks too much to be beautiful.  We\r
+cannot spare you.  And now you had better dress and drive down to the\r
+club.  We are rather late, as it is."\r
+\r
+"I think I shall join you at the opera, Harry.  I feel too tired to eat\r
+anything.  What is the number of your sister's box?"\r
+\r
+"Twenty-seven, I believe.  It is on the grand tier.  You will see her\r
+name on the door.  But I am sorry you won't come and dine."\r
+\r
+"I don't feel up to it," said Dorian listlessly.  "But I am awfully\r
+obliged to you for all that you have said to me.  You are certainly my\r
+best friend.  No one has ever understood me as you have."\r
+\r
+"We are only at the beginning of our friendship, Dorian," answered Lord\r
+Henry, shaking him by the hand.  "Good-bye. I shall see you before\r
+nine-thirty, I hope.  Remember, Patti is singing."\r
+\r
+As he closed the door behind him, Dorian Gray touched the bell, and in\r
+a few minutes Victor appeared with the lamps and drew the blinds down.\r
+He waited impatiently for him to go.  The man seemed to take an\r
+interminable time over everything.\r
+\r
+As soon as he had left, he rushed to the screen and drew it back.  No;\r
+there was no further change in the picture.  It had received the news\r
+of Sibyl Vane's death before he had known of it himself.  It was\r
+conscious of the events of life as they occurred.  The vicious cruelty\r
+that marred the fine lines of the mouth had, no doubt, appeared at the\r
+very moment that the girl had drunk the poison, whatever it was.  Or\r
+was it indifferent to results?  Did it merely take cognizance of what\r
+passed within the soul?  He wondered, and hoped that some day he would\r
+see the change taking place before his very eyes, shuddering as he\r
+hoped it.\r
+\r
+Poor Sibyl!  What a romance it had all been!  She had often mimicked\r
+death on the stage.  Then Death himself had touched her and taken her\r
+with him.  How had she played that dreadful last scene?  Had she cursed\r
+him, as she died?  No; she had died for love of him, and love would\r
+always be a sacrament to him now.  She had atoned for everything by the\r
+sacrifice she had made of her life.  He would not think any more of\r
+what she had made him go through, on that horrible night at the\r
+theatre.  When he thought of her, it would be as a wonderful tragic\r
+figure sent on to the world's stage to show the supreme reality of\r
+love.  A wonderful tragic figure?  Tears came to his eyes as he\r
+remembered her childlike look, and winsome fanciful ways, and shy\r
+tremulous grace.  He brushed them away hastily and looked again at the\r
+picture.\r
+\r
+He felt that the time had really come for making his choice.  Or had\r
+his choice already been made?  Yes, life had decided that for\r
+him--life, and his own infinite curiosity about life.  Eternal youth,\r
+infinite passion, pleasures subtle and secret, wild joys and wilder\r
+sins--he was to have all these things.  The portrait was to bear the\r
+burden of his shame: that was all.\r
+\r
+A feeling of pain crept over him as he thought of the desecration that\r
+was in store for the fair face on the canvas.  Once, in boyish mockery\r
+of Narcissus, he had kissed, or feigned to kiss, those painted lips\r
+that now smiled so cruelly at him.  Morning after morning he had sat\r
+before the portrait wondering at its beauty, almost enamoured of it, as\r
+it seemed to him at times.  Was it to alter now with every mood to\r
+which he yielded?  Was it to become a monstrous and loathsome thing, to\r
+be hidden away in a locked room, to be shut out from the sunlight that\r
+had so often touched to brighter gold the waving wonder of its hair?\r
+The pity of it! the pity of it!\r
+\r
+For a moment, he thought of praying that the horrible sympathy that\r
+existed between him and the picture might cease.  It had changed in\r
+answer to a prayer; perhaps in answer to a prayer it might remain\r
+unchanged.  And yet, who, that knew anything about life, would\r
+surrender the chance of remaining always young, however fantastic that\r
+chance might be, or with what fateful consequences it might be fraught?\r
+Besides, was it really under his control?  Had it indeed been prayer\r
+that had produced the substitution?  Might there not be some curious\r
+scientific reason for it all?  If thought could exercise its influence\r
+upon a living organism, might not thought exercise an influence upon\r
+dead and inorganic things?  Nay, without thought or conscious desire,\r
+might not things external to ourselves vibrate in unison with our moods\r
+and passions, atom calling to atom in secret love or strange affinity?\r
+But the reason was of no importance.  He would never again tempt by a\r
+prayer any terrible power.  If the picture was to alter, it was to\r
+alter.  That was all.  Why inquire too closely into it?\r
+\r
+For there would be a real pleasure in watching it.  He would be able to\r
+follow his mind into its secret places.  This portrait would be to him\r
+the most magical of mirrors.  As it had revealed to him his own body,\r
+so it would reveal to him his own soul.  And when winter came upon it,\r
+he would still be standing where spring trembles on the verge of\r
+summer.  When the blood crept from its face, and left behind a pallid\r
+mask of chalk with leaden eyes, he would keep the glamour of boyhood.\r
+Not one blossom of his loveliness would ever fade.  Not one pulse of\r
+his life would ever weaken.  Like the gods of the Greeks, he would be\r
+strong, and fleet, and joyous.  What did it matter what happened to the\r
+coloured image on the canvas?  He would be safe.  That was everything.\r
+\r
+He drew the screen back into its former place in front of the picture,\r
+smiling as he did so, and passed into his bedroom, where his valet was\r
+already waiting for him.  An hour later he was at the opera, and Lord\r
+Henry was leaning over his chair.\r
+\r
+\r
+\r
+CHAPTER 9\r
+\r
+As he was sitting at breakfast next morning, Basil Hallward was shown\r
+into the room.\r
+\r
+"I am so glad I have found you, Dorian," he said gravely.  "I called\r
+last night, and they told me you were at the opera.  Of course, I knew\r
+that was impossible.  But I wish you had left word where you had really\r
+gone to.  I passed a dreadful evening, half afraid that one tragedy\r
+might be followed by another.  I think you might have telegraphed for\r
+me when you heard of it first.  I read of it quite by chance in a late\r
+edition of _The Globe_ that I picked up at the club.  I came here at once\r
+and was miserable at not finding you.  I can't tell you how\r
+heart-broken I am about the whole thing.  I know what you must suffer.\r
+But where were you?  Did you go down and see the girl's mother?  For a\r
+moment I thought of following you there.  They gave the address in the\r
+paper.  Somewhere in the Euston Road, isn't it?  But I was afraid of\r
+intruding upon a sorrow that I could not lighten.  Poor woman!  What a\r
+state she must be in!  And her only child, too!  What did she say about\r
+it all?"\r
+\r
+"My dear Basil, how do I know?" murmured Dorian Gray, sipping some\r
+pale-yellow wine from a delicate, gold-beaded bubble of Venetian glass\r
+and looking dreadfully bored.  "I was at the opera.  You should have\r
+come on there.  I met Lady Gwendolen, Harry's sister, for the first\r
+time.  We were in her box.  She is perfectly charming; and Patti sang\r
+divinely.  Don't talk about horrid subjects.  If one doesn't talk about\r
+a thing, it has never happened.  It is simply expression, as Harry\r
+says, that gives reality to things.  I may mention that she was not the\r
+woman's only child.  There is a son, a charming fellow, I believe.  But\r
+he is not on the stage.  He is a sailor, or something.  And now, tell\r
+me about yourself and what you are painting."\r
+\r
+"You went to the opera?" said Hallward, speaking very slowly and with a\r
+strained touch of pain in his voice.  "You went to the opera while\r
+Sibyl Vane was lying dead in some sordid lodging?  You can talk to me\r
+of other women being charming, and of Patti singing divinely, before\r
+the girl you loved has even the quiet of a grave to sleep in?  Why,\r
+man, there are horrors in store for that little white body of hers!"\r
+\r
+"Stop, Basil!  I won't hear it!" cried Dorian, leaping to his feet.\r
+"You must not tell me about things.  What is done is done.  What is\r
+past is past."\r
+\r
+"You call yesterday the past?"\r
+\r
+"What has the actual lapse of time got to do with it?  It is only\r
+shallow people who require years to get rid of an emotion.  A man who\r
+is master of himself can end a sorrow as easily as he can invent a\r
+pleasure.  I don't want to be at the mercy of my emotions.  I want to\r
+use them, to enjoy them, and to dominate them."\r
+\r
+"Dorian, this is horrible!  Something has changed you completely.  You\r
+look exactly the same wonderful boy who, day after day, used to come\r
+down to my studio to sit for his picture.  But you were simple,\r
+natural, and affectionate then.  You were the most unspoiled creature\r
+in the whole world.  Now, I don't know what has come over you.  You\r
+talk as if you had no heart, no pity in you.  It is all Harry's\r
+influence.  I see that."\r
+\r
+The lad flushed up and, going to the window, looked out for a few\r
+moments on the green, flickering, sun-lashed garden.  "I owe a great\r
+deal to Harry, Basil," he said at last, "more than I owe to you.  You\r
+only taught me to be vain."\r
+\r
+"Well, I am punished for that, Dorian--or shall be some day."\r
+\r
+"I don't know what you mean, Basil," he exclaimed, turning round.  "I\r
+don't know what you want.  What do you want?"\r
+\r
+"I want the Dorian Gray I used to paint," said the artist sadly.\r
+\r
+"Basil," said the lad, going over to him and putting his hand on his\r
+shoulder, "you have come too late.  Yesterday, when I heard that Sibyl\r
+Vane had killed herself--"\r
+\r
+"Killed herself!  Good heavens! is there no doubt about that?" cried\r
+Hallward, looking up at him with an expression of horror.\r
+\r
+"My dear Basil!  Surely you don't think it was a vulgar accident?  Of\r
+course she killed herself."\r
+\r
+The elder man buried his face in his hands.  "How fearful," he\r
+muttered, and a shudder ran through him.\r
+\r
+"No," said Dorian Gray, "there is nothing fearful about it.  It is one\r
+of the great romantic tragedies of the age.  As a rule, people who act\r
+lead the most commonplace lives.  They are good husbands, or faithful\r
+wives, or something tedious.  You know what I mean--middle-class virtue\r
+and all that kind of thing.  How different Sibyl was!  She lived her\r
+finest tragedy.  She was always a heroine.  The last night she\r
+played--the night you saw her--she acted badly because she had known\r
+the reality of love.  When she knew its unreality, she died, as Juliet\r
+might have died.  She passed again into the sphere of art.  There is\r
+something of the martyr about her.  Her death has all the pathetic\r
+uselessness of martyrdom, all its wasted beauty.  But, as I was saying,\r
+you must not think I have not suffered.  If you had come in yesterday\r
+at a particular moment--about half-past five, perhaps, or a quarter to\r
+six--you would have found me in tears.  Even Harry, who was here, who\r
+brought me the news, in fact, had no idea what I was going through.  I\r
+suffered immensely.  Then it passed away.  I cannot repeat an emotion.\r
+No one can, except sentimentalists.  And you are awfully unjust, Basil.\r
+You come down here to console me.  That is charming of you.  You find\r
+me consoled, and you are furious.  How like a sympathetic person!  You\r
+remind me of a story Harry told me about a certain philanthropist who\r
+spent twenty years of his life in trying to get some grievance\r
+redressed, or some unjust law altered--I forget exactly what it was.\r
+Finally he succeeded, and nothing could exceed his disappointment.  He\r
+had absolutely nothing to do, almost died of _ennui_, and became a\r
+confirmed misanthrope.  And besides, my dear old Basil, if you really\r
+want to console me, teach me rather to forget what has happened, or to\r
+see it from a proper artistic point of view.  Was it not Gautier who\r
+used to write about _la consolation des arts_?  I remember picking up a\r
+little vellum-covered book in your studio one day and chancing on that\r
+delightful phrase.  Well, I am not like that young man you told me of\r
+when we were down at Marlow together, the young man who used to say\r
+that yellow satin could console one for all the miseries of life.  I\r
+love beautiful things that one can touch and handle.  Old brocades,\r
+green bronzes, lacquer-work, carved ivories, exquisite surroundings,\r
+luxury, pomp--there is much to be got from all these.  But the artistic\r
+temperament that they create, or at any rate reveal, is still more to\r
+me.  To become the spectator of one's own life, as Harry says, is to\r
+escape the suffering of life.  I know you are surprised at my talking\r
+to you like this.  You have not realized how I have developed.  I was a\r
+schoolboy when you knew me.  I am a man now.  I have new passions, new\r
+thoughts, new ideas.  I am different, but you must not like me less.  I\r
+am changed, but you must always be my friend.  Of course, I am very\r
+fond of Harry.  But I know that you are better than he is.  You are not\r
+stronger--you are too much afraid of life--but you are better.  And how\r
+happy we used to be together!  Don't leave me, Basil, and don't quarrel\r
+with me.  I am what I am.  There is nothing more to be said."\r
+\r
+The painter felt strangely moved.  The lad was infinitely dear to him,\r
+and his personality had been the great turning point in his art.  He\r
+could not bear the idea of reproaching him any more.  After all, his\r
+indifference was probably merely a mood that would pass away.  There\r
+was so much in him that was good, so much in him that was noble.\r
+\r
+"Well, Dorian," he said at length, with a sad smile, "I won't speak to\r
+you again about this horrible thing, after to-day.  I only trust your\r
+name won't be mentioned in connection with it.  The inquest is to take\r
+place this afternoon.  Have they summoned you?"\r
+\r
+Dorian shook his head, and a look of annoyance passed over his face at\r
+the mention of the word "inquest."  There was something so crude and\r
+vulgar about everything of the kind.  "They don't know my name," he\r
+answered.\r
+\r
+"But surely she did?"\r
+\r
+"Only my Christian name, and that I am quite sure she never mentioned\r
+to any one.  She told me once that they were all rather curious to\r
+learn who I was, and that she invariably told them my name was Prince\r
+Charming.  It was pretty of her.  You must do me a drawing of Sibyl,\r
+Basil.  I should like to have something more of her than the memory of\r
+a few kisses and some broken pathetic words."\r
+\r
+"I will try and do something, Dorian, if it would please you.  But you\r
+must come and sit to me yourself again.  I can't get on without you."\r
+\r
+"I can never sit to you again, Basil.  It is impossible!" he exclaimed,\r
+starting back.\r
+\r
+The painter stared at him.  "My dear boy, what nonsense!" he cried.\r
+"Do you mean to say you don't like what I did of you?  Where is it?\r
+Why have you pulled the screen in front of it?  Let me look at it.  It\r
+is the best thing I have ever done.  Do take the screen away, Dorian.\r
+It is simply disgraceful of your servant hiding my work like that.  I\r
+felt the room looked different as I came in."\r
+\r
+"My servant has nothing to do with it, Basil.  You don't imagine I let\r
+him arrange my room for me?  He settles my flowers for me\r
+sometimes--that is all.  No; I did it myself.  The light was too strong\r
+on the portrait."\r
+\r
+"Too strong!  Surely not, my dear fellow?  It is an admirable place for\r
+it.  Let me see it."  And Hallward walked towards the corner of the\r
+room.\r
+\r
+A cry of terror broke from Dorian Gray's lips, and he rushed between\r
+the painter and the screen.  "Basil," he said, looking very pale, "you\r
+must not look at it.  I don't wish you to."\r
+\r
+"Not look at my own work!  You are not serious.  Why shouldn't I look\r
+at it?" exclaimed Hallward, laughing.\r
+\r
+"If you try to look at it, Basil, on my word of honour I will never\r
+speak to you again as long as I live.  I am quite serious.  I don't\r
+offer any explanation, and you are not to ask for any.  But, remember,\r
+if you touch this screen, everything is over between us."\r
+\r
+Hallward was thunderstruck.  He looked at Dorian Gray in absolute\r
+amazement.  He had never seen him like this before.  The lad was\r
+actually pallid with rage.  His hands were clenched, and the pupils of\r
+his eyes were like disks of blue fire.  He was trembling all over.\r
+\r
+"Dorian!"\r
+\r
+"Don't speak!"\r
+\r
+"But what is the matter?  Of course I won't look at it if you don't\r
+want me to," he said, rather coldly, turning on his heel and going over\r
+towards the window.  "But, really, it seems rather absurd that I\r
+shouldn't see my own work, especially as I am going to exhibit it in\r
+Paris in the autumn.  I shall probably have to give it another coat of\r
+varnish before that, so I must see it some day, and why not to-day?"\r
+\r
+"To exhibit it!  You want to exhibit it?" exclaimed Dorian Gray, a\r
+strange sense of terror creeping over him.  Was the world going to be\r
+shown his secret?  Were people to gape at the mystery of his life?\r
+That was impossible.  Something--he did not know what--had to be done\r
+at once.\r
+\r
+"Yes; I don't suppose you will object to that.  Georges Petit is going\r
+to collect all my best pictures for a special exhibition in the Rue de\r
+Seze, which will open the first week in October.  The portrait will\r
+only be away a month.  I should think you could easily spare it for\r
+that time.  In fact, you are sure to be out of town.  And if you keep\r
+it always behind a screen, you can't care much about it."\r
+\r
+Dorian Gray passed his hand over his forehead.  There were beads of\r
+perspiration there.  He felt that he was on the brink of a horrible\r
+danger.  "You told me a month ago that you would never exhibit it," he\r
+cried.  "Why have you changed your mind?  You people who go in for\r
+being consistent have just as many moods as others have.  The only\r
+difference is that your moods are rather meaningless.  You can't have\r
+forgotten that you assured me most solemnly that nothing in the world\r
+would induce you to send it to any exhibition.  You told Harry exactly\r
+the same thing." He stopped suddenly, and a gleam of light came into\r
+his eyes.  He remembered that Lord Henry had said to him once, half\r
+seriously and half in jest, "If you want to have a strange quarter of\r
+an hour, get Basil to tell you why he won't exhibit your picture.  He\r
+told me why he wouldn't, and it was a revelation to me."  Yes, perhaps\r
+Basil, too, had his secret.  He would ask him and try.\r
+\r
+"Basil," he said, coming over quite close and looking him straight in\r
+the face, "we have each of us a secret.  Let me know yours, and I shall\r
+tell you mine.  What was your reason for refusing to exhibit my\r
+picture?"\r
+\r
+The painter shuddered in spite of himself.  "Dorian, if I told you, you\r
+might like me less than you do, and you would certainly laugh at me.  I\r
+could not bear your doing either of those two things.  If you wish me\r
+never to look at your picture again, I am content.  I have always you\r
+to look at.  If you wish the best work I have ever done to be hidden\r
+from the world, I am satisfied.  Your friendship is dearer to me than\r
+any fame or reputation."\r
+\r
+"No, Basil, you must tell me," insisted Dorian Gray.  "I think I have a\r
+right to know."  His feeling of terror had passed away, and curiosity\r
+had taken its place.  He was determined to find out Basil Hallward's\r
+mystery.\r
+\r
+"Let us sit down, Dorian," said the painter, looking troubled.  "Let us\r
+sit down.  And just answer me one question.  Have you noticed in the\r
+picture something curious?--something that probably at first did not\r
+strike you, but that revealed itself to you suddenly?"\r
+\r
+"Basil!" cried the lad, clutching the arms of his chair with trembling\r
+hands and gazing at him with wild startled eyes.\r
+\r
+"I see you did.  Don't speak.  Wait till you hear what I have to say.\r
+Dorian, from the moment I met you, your personality had the most\r
+extraordinary influence over me.  I was dominated, soul, brain, and\r
+power, by you.  You became to me the visible incarnation of that unseen\r
+ideal whose memory haunts us artists like an exquisite dream.  I\r
+worshipped you.  I grew jealous of every one to whom you spoke.  I\r
+wanted to have you all to myself.  I was only happy when I was with\r
+you.  When you were away from me, you were still present in my art....\r
+Of course, I never let you know anything about this.  It would have\r
+been impossible.  You would not have understood it.  I hardly\r
+understood it myself.  I only knew that I had seen perfection face to\r
+face, and that the world had become wonderful to my eyes--too\r
+wonderful, perhaps, for in such mad worships there is peril, the peril\r
+of losing them, no less than the peril of keeping them....  Weeks and\r
+weeks went on, and I grew more and more absorbed in you.  Then came a\r
+new development.  I had drawn you as Paris in dainty armour, and as\r
+Adonis with huntsman's cloak and polished boar-spear. Crowned with\r
+heavy lotus-blossoms you had sat on the prow of Adrian's barge, gazing\r
+across the green turbid Nile.  You had leaned over the still pool of\r
+some Greek woodland and seen in the water's silent silver the marvel of\r
+your own face.  And it had all been what art should be--unconscious,\r
+ideal, and remote.  One day, a fatal day I sometimes think, I\r
+determined to paint a wonderful portrait of you as you actually are,\r
+not in the costume of dead ages, but in your own dress and in your own\r
+time.  Whether it was the realism of the method, or the mere wonder of\r
+your own personality, thus directly presented to me without mist or\r
+veil, I cannot tell.  But I know that as I worked at it, every flake\r
+and film of colour seemed to me to reveal my secret.  I grew afraid\r
+that others would know of my idolatry.  I felt, Dorian, that I had told\r
+too much, that I had put too much of myself into it.  Then it was that\r
+I resolved never to allow the picture to be exhibited.  You were a\r
+little annoyed; but then you did not realize all that it meant to me.\r
+Harry, to whom I talked about it, laughed at me.  But I did not mind\r
+that.  When the picture was finished, and I sat alone with it, I felt\r
+that I was right.... Well, after a few days the thing left my studio,\r
+and as soon as I had got rid of the intolerable fascination of its\r
+presence, it seemed to me that I had been foolish in imagining that I\r
+had seen anything in it, more than that you were extremely good-looking\r
+and that I could paint.  Even now I cannot help feeling that it is a\r
+mistake to think that the passion one feels in creation is ever really\r
+shown in the work one creates.  Art is always more abstract than we\r
+fancy.  Form and colour tell us of form and colour--that is all.  It\r
+often seems to me that art conceals the artist far more completely than\r
+it ever reveals him.  And so when I got this offer from Paris, I\r
+determined to make your portrait the principal thing in my exhibition.\r
+It never occurred to me that you would refuse.  I see now that you were\r
+right.  The picture cannot be shown.  You must not be angry with me,\r
+Dorian, for what I have told you.  As I said to Harry, once, you are\r
+made to be worshipped."\r
+\r
+Dorian Gray drew a long breath.  The colour came back to his cheeks,\r
+and a smile played about his lips.  The peril was over.  He was safe\r
+for the time.  Yet he could not help feeling infinite pity for the\r
+painter who had just made this strange confession to him, and wondered\r
+if he himself would ever be so dominated by the personality of a\r
+friend.  Lord Henry had the charm of being very dangerous.  But that\r
+was all.  He was too clever and too cynical to be really fond of.\r
+Would there ever be some one who would fill him with a strange\r
+idolatry?  Was that one of the things that life had in store?\r
+\r
+"It is extraordinary to me, Dorian," said Hallward, "that you should\r
+have seen this in the portrait.  Did you really see it?"\r
+\r
+"I saw something in it," he answered, "something that seemed to me very\r
+curious."\r
+\r
+"Well, you don't mind my looking at the thing now?"\r
+\r
+Dorian shook his head.  "You must not ask me that, Basil.  I could not\r
+possibly let you stand in front of that picture."\r
+\r
+"You will some day, surely?"\r
+\r
+"Never."\r
+\r
+"Well, perhaps you are right.  And now good-bye, Dorian.  You have been\r
+the one person in my life who has really influenced my art.  Whatever I\r
+have done that is good, I owe to you.  Ah! you don't know what it cost\r
+me to tell you all that I have told you."\r
+\r
+"My dear Basil," said Dorian, "what have you told me?  Simply that you\r
+felt that you admired me too much.  That is not even a compliment."\r
+\r
+"It was not intended as a compliment.  It was a confession.  Now that I\r
+have made it, something seems to have gone out of me.  Perhaps one\r
+should never put one's worship into words."\r
+\r
+"It was a very disappointing confession."\r
+\r
+"Why, what did you expect, Dorian?  You didn't see anything else in the\r
+picture, did you?  There was nothing else to see?"\r
+\r
+"No; there was nothing else to see.  Why do you ask?  But you mustn't\r
+talk about worship.  It is foolish.  You and I are friends, Basil, and\r
+we must always remain so."\r
+\r
+"You have got Harry," said the painter sadly.\r
+\r
+"Oh, Harry!" cried the lad, with a ripple of laughter.  "Harry spends\r
+his days in saying what is incredible and his evenings in doing what is\r
+improbable.  Just the sort of life I would like to lead.  But still I\r
+don't think I would go to Harry if I were in trouble.  I would sooner\r
+go to you, Basil."\r
+\r
+"You will sit to me again?"\r
+\r
+"Impossible!"\r
+\r
+"You spoil my life as an artist by refusing, Dorian.  No man comes\r
+across two ideal things.  Few come across one."\r
+\r
+"I can't explain it to you, Basil, but I must never sit to you again.\r
+There is something fatal about a portrait.  It has a life of its own.\r
+I will come and have tea with you.  That will be just as pleasant."\r
+\r
+"Pleasanter for you, I am afraid," murmured Hallward regretfully.  "And\r
+now good-bye. I am sorry you won't let me look at the picture once\r
+again.  But that can't be helped.  I quite understand what you feel\r
+about it."\r
+\r
+As he left the room, Dorian Gray smiled to himself.  Poor Basil!  How\r
+little he knew of the true reason!  And how strange it was that,\r
+instead of having been forced to reveal his own secret, he had\r
+succeeded, almost by chance, in wresting a secret from his friend!  How\r
+much that strange confession explained to him!  The painter's absurd\r
+fits of jealousy, his wild devotion, his extravagant panegyrics, his\r
+curious reticences--he understood them all now, and he felt sorry.\r
+There seemed to him to be something tragic in a friendship so coloured\r
+by romance.\r
+\r
+He sighed and touched the bell.  The portrait must be hidden away at\r
+all costs.  He could not run such a risk of discovery again.  It had\r
+been mad of him to have allowed the thing to remain, even for an hour,\r
+in a room to which any of his friends had access.\r
+\r
+\r
+\r
+CHAPTER 10\r
+\r
+When his servant entered, he looked at him steadfastly and wondered if\r
+he had thought of peering behind the screen.  The man was quite\r
+impassive and waited for his orders.  Dorian lit a cigarette and walked\r
+over to the glass and glanced into it.  He could see the reflection of\r
+Victor's face perfectly.  It was like a placid mask of servility.\r
+There was nothing to be afraid of, there.  Yet he thought it best to be\r
+on his guard.\r
+\r
+Speaking very slowly, he told him to tell the house-keeper that he\r
+wanted to see her, and then to go to the frame-maker and ask him to\r
+send two of his men round at once.  It seemed to him that as the man\r
+left the room his eyes wandered in the direction of the screen.  Or was\r
+that merely his own fancy?\r
+\r
+After a few moments, in her black silk dress, with old-fashioned thread\r
+mittens on her wrinkled hands, Mrs. Leaf bustled into the library.  He\r
+asked her for the key of the schoolroom.\r
+\r
+"The old schoolroom, Mr. Dorian?" she exclaimed.  "Why, it is full of\r
+dust.  I must get it arranged and put straight before you go into it.\r
+It is not fit for you to see, sir.  It is not, indeed."\r
+\r
+"I don't want it put straight, Leaf.  I only want the key."\r
+\r
+"Well, sir, you'll be covered with cobwebs if you go into it.  Why, it\r
+hasn't been opened for nearly five years--not since his lordship died."\r
+\r
+He winced at the mention of his grandfather.  He had hateful memories\r
+of him.  "That does not matter," he answered.  "I simply want to see\r
+the place--that is all.  Give me the key."\r
+\r
+"And here is the key, sir," said the old lady, going over the contents\r
+of her bunch with tremulously uncertain hands.  "Here is the key.  I'll\r
+have it off the bunch in a moment.  But you don't think of living up\r
+there, sir, and you so comfortable here?"\r
+\r
+"No, no," he cried petulantly.  "Thank you, Leaf.  That will do."\r
+\r
+She lingered for a few moments, and was garrulous over some detail of\r
+the household.  He sighed and told her to manage things as she thought\r
+best.  She left the room, wreathed in smiles.\r
+\r
+As the door closed, Dorian put the key in his pocket and looked round\r
+the room.  His eye fell on a large, purple satin coverlet heavily\r
+embroidered with gold, a splendid piece of late seventeenth-century\r
+Venetian work that his grandfather had found in a convent near Bologna.\r
+Yes, that would serve to wrap the dreadful thing in.  It had perhaps\r
+served often as a pall for the dead.  Now it was to hide something that\r
+had a corruption of its own, worse than the corruption of death\r
+itself--something that would breed horrors and yet would never die.\r
+What the worm was to the corpse, his sins would be to the painted image\r
+on the canvas.  They would mar its beauty and eat away its grace.  They\r
+would defile it and make it shameful.  And yet the thing would still\r
+live on.  It would be always alive.\r
+\r
+He shuddered, and for a moment he regretted that he had not told Basil\r
+the true reason why he had wished to hide the picture away.  Basil\r
+would have helped him to resist Lord Henry's influence, and the still\r
+more poisonous influences that came from his own temperament.  The love\r
+that he bore him--for it was really love--had nothing in it that was\r
+not noble and intellectual.  It was not that mere physical admiration\r
+of beauty that is born of the senses and that dies when the senses\r
+tire.  It was such love as Michelangelo had known, and Montaigne, and\r
+Winckelmann, and Shakespeare himself.  Yes, Basil could have saved him.\r
+But it was too late now.  The past could always be annihilated.\r
+Regret, denial, or forgetfulness could do that.  But the future was\r
+inevitable.  There were passions in him that would find their terrible\r
+outlet, dreams that would make the shadow of their evil real.\r
+\r
+He took up from the couch the great purple-and-gold texture that\r
+covered it, and, holding it in his hands, passed behind the screen.\r
+Was the face on the canvas viler than before?  It seemed to him that it\r
+was unchanged, and yet his loathing of it was intensified.  Gold hair,\r
+blue eyes, and rose-red lips--they all were there.  It was simply the\r
+expression that had altered.  That was horrible in its cruelty.\r
+Compared to what he saw in it of censure or rebuke, how shallow Basil's\r
+reproaches about Sibyl Vane had been!--how shallow, and of what little\r
+account!  His own soul was looking out at him from the canvas and\r
+calling him to judgement.  A look of pain came across him, and he flung\r
+the rich pall over the picture.  As he did so, a knock came to the\r
+door.  He passed out as his servant entered.\r
+\r
+"The persons are here, Monsieur."\r
+\r
+He felt that the man must be got rid of at once.  He must not be\r
+allowed to know where the picture was being taken to.  There was\r
+something sly about him, and he had thoughtful, treacherous eyes.\r
+Sitting down at the writing-table he scribbled a note to Lord Henry,\r
+asking him to send him round something to read and reminding him that\r
+they were to meet at eight-fifteen that evening.\r
+\r
+"Wait for an answer," he said, handing it to him, "and show the men in\r
+here."\r
+\r
+In two or three minutes there was another knock, and Mr. Hubbard\r
+himself, the celebrated frame-maker of South Audley Street, came in\r
+with a somewhat rough-looking young assistant.  Mr. Hubbard was a\r
+florid, red-whiskered little man, whose admiration for art was\r
+considerably tempered by the inveterate impecuniosity of most of the\r
+artists who dealt with him.  As a rule, he never left his shop.  He\r
+waited for people to come to him.  But he always made an exception in\r
+favour of Dorian Gray.  There was something about Dorian that charmed\r
+everybody.  It was a pleasure even to see him.\r
+\r
+"What can I do for you, Mr. Gray?" he said, rubbing his fat freckled\r
+hands.  "I thought I would do myself the honour of coming round in\r
+person.  I have just got a beauty of a frame, sir.  Picked it up at a\r
+sale.  Old Florentine.  Came from Fonthill, I believe.  Admirably\r
+suited for a religious subject, Mr. Gray."\r
+\r
+"I am so sorry you have given yourself the trouble of coming round, Mr.\r
+Hubbard.  I shall certainly drop in and look at the frame--though I\r
+don't go in much at present for religious art--but to-day I only want a\r
+picture carried to the top of the house for me.  It is rather heavy, so\r
+I thought I would ask you to lend me a couple of your men."\r
+\r
+"No trouble at all, Mr. Gray.  I am delighted to be of any service to\r
+you.  Which is the work of art, sir?"\r
+\r
+"This," replied Dorian, moving the screen back.  "Can you move it,\r
+covering and all, just as it is?  I don't want it to get scratched\r
+going upstairs."\r
+\r
+"There will be no difficulty, sir," said the genial frame-maker,\r
+beginning, with the aid of his assistant, to unhook the picture from\r
+the long brass chains by which it was suspended.  "And, now, where\r
+shall we carry it to, Mr. Gray?"\r
+\r
+"I will show you the way, Mr. Hubbard, if you will kindly follow me.\r
+Or perhaps you had better go in front.  I am afraid it is right at the\r
+top of the house.  We will go up by the front staircase, as it is\r
+wider."\r
+\r
+He held the door open for them, and they passed out into the hall and\r
+began the ascent.  The elaborate character of the frame had made the\r
+picture extremely bulky, and now and then, in spite of the obsequious\r
+protests of Mr. Hubbard, who had the true tradesman's spirited dislike\r
+of seeing a gentleman doing anything useful, Dorian put his hand to it\r
+so as to help them.\r
+\r
+"Something of a load to carry, sir," gasped the little man when they\r
+reached the top landing.  And he wiped his shiny forehead.\r
+\r
+"I am afraid it is rather heavy," murmured Dorian as he unlocked the\r
+door that opened into the room that was to keep for him the curious\r
+secret of his life and hide his soul from the eyes of men.\r
+\r
+He had not entered the place for more than four years--not, indeed,\r
+since he had used it first as a play-room when he was a child, and then\r
+as a study when he grew somewhat older.  It was a large,\r
+well-proportioned room, which had been specially built by the last Lord\r
+Kelso for the use of the little grandson whom, for his strange likeness\r
+to his mother, and also for other reasons, he had always hated and\r
+desired to keep at a distance.  It appeared to Dorian to have but\r
+little changed.  There was the huge Italian _cassone_, with its\r
+fantastically painted panels and its tarnished gilt mouldings, in which\r
+he had so often hidden himself as a boy.  There the satinwood book-case\r
+filled with his dog-eared schoolbooks.  On the wall behind it was\r
+hanging the same ragged Flemish tapestry where a faded king and queen\r
+were playing chess in a garden, while a company of hawkers rode by,\r
+carrying hooded birds on their gauntleted wrists.  How well he\r
+remembered it all!  Every moment of his lonely childhood came back to\r
+him as he looked round.  He recalled the stainless purity of his boyish\r
+life, and it seemed horrible to him that it was here the fatal portrait\r
+was to be hidden away.  How little he had thought, in those dead days,\r
+of all that was in store for him!\r
+\r
+But there was no other place in the house so secure from prying eyes as\r
+this.  He had the key, and no one else could enter it.  Beneath its\r
+purple pall, the face painted on the canvas could grow bestial, sodden,\r
+and unclean.  What did it matter?  No one could see it.  He himself\r
+would not see it.  Why should he watch the hideous corruption of his\r
+soul?  He kept his youth--that was enough.  And, besides, might not\r
+his nature grow finer, after all?  There was no reason that the future\r
+should be so full of shame.  Some love might come across his life, and\r
+purify him, and shield him from those sins that seemed to be already\r
+stirring in spirit and in flesh--those curious unpictured sins whose\r
+very mystery lent them their subtlety and their charm.  Perhaps, some\r
+day, the cruel look would have passed away from the scarlet sensitive\r
+mouth, and he might show to the world Basil Hallward's masterpiece.\r
+\r
+No; that was impossible.  Hour by hour, and week by week, the thing\r
+upon the canvas was growing old.  It might escape the hideousness of\r
+sin, but the hideousness of age was in store for it.  The cheeks would\r
+become hollow or flaccid.  Yellow crow's feet would creep round the\r
+fading eyes and make them horrible.  The hair would lose its\r
+brightness, the mouth would gape or droop, would be foolish or gross,\r
+as the mouths of old men are.  There would be the wrinkled throat, the\r
+cold, blue-veined hands, the twisted body, that he remembered in the\r
+grandfather who had been so stern to him in his boyhood.  The picture\r
+had to be concealed.  There was no help for it.\r
+\r
+"Bring it in, Mr. Hubbard, please," he said, wearily, turning round.\r
+"I am sorry I kept you so long.  I was thinking of something else."\r
+\r
+"Always glad to have a rest, Mr. Gray," answered the frame-maker, who\r
+was still gasping for breath.  "Where shall we put it, sir?"\r
+\r
+"Oh, anywhere.  Here:  this will do.  I don't want to have it hung up.\r
+Just lean it against the wall.  Thanks."\r
+\r
+"Might one look at the work of art, sir?"\r
+\r
+Dorian started.  "It would not interest you, Mr. Hubbard," he said,\r
+keeping his eye on the man.  He felt ready to leap upon him and fling\r
+him to the ground if he dared to lift the gorgeous hanging that\r
+concealed the secret of his life.  "I shan't trouble you any more now.\r
+I am much obliged for your kindness in coming round."\r
+\r
+"Not at all, not at all, Mr. Gray.  Ever ready to do anything for you,\r
+sir." And Mr. Hubbard tramped downstairs, followed by the assistant,\r
+who glanced back at Dorian with a look of shy wonder in his rough\r
+uncomely face.  He had never seen any one so marvellous.\r
+\r
+When the sound of their footsteps had died away, Dorian locked the door\r
+and put the key in his pocket.  He felt safe now.  No one would ever\r
+look upon the horrible thing.  No eye but his would ever see his shame.\r
+\r
+On reaching the library, he found that it was just after five o'clock\r
+and that the tea had been already brought up.  On a little table of\r
+dark perfumed wood thickly incrusted with nacre, a present from Lady\r
+Radley, his guardian's wife, a pretty professional invalid who had\r
+spent the preceding winter in Cairo, was lying a note from Lord Henry,\r
+and beside it was a book bound in yellow paper, the cover slightly torn\r
+and the edges soiled.  A copy of the third edition of _The St. James's\r
+Gazette_ had been placed on the tea-tray. It was evident that Victor had\r
+returned.  He wondered if he had met the men in the hall as they were\r
+leaving the house and had wormed out of them what they had been doing.\r
+He would be sure to miss the picture--had no doubt missed it already,\r
+while he had been laying the tea-things. The screen had not been set\r
+back, and a blank space was visible on the wall.  Perhaps some night he\r
+might find him creeping upstairs and trying to force the door of the\r
+room.  It was a horrible thing to have a spy in one's house.  He had\r
+heard of rich men who had been blackmailed all their lives by some\r
+servant who had read a letter, or overheard a conversation, or picked\r
+up a card with an address, or found beneath a pillow a withered flower\r
+or a shred of crumpled lace.\r
+\r
+He sighed, and having poured himself out some tea, opened Lord Henry's\r
+note.  It was simply to say that he sent him round the evening paper,\r
+and a book that might interest him, and that he would be at the club at\r
+eight-fifteen. He opened _The St. James's_ languidly, and looked through\r
+it.  A red pencil-mark on the fifth page caught his eye.  It drew\r
+attention to the following paragraph:\r
+\r
+\r
+INQUEST ON AN ACTRESS.--An inquest was held this morning at the Bell\r
+Tavern, Hoxton Road, by Mr. Danby, the District Coroner, on the body of\r
+Sibyl Vane, a young actress recently engaged at the Royal Theatre,\r
+Holborn.  A verdict of death by misadventure was returned.\r
+Considerable sympathy was expressed for the mother of the deceased, who\r
+was greatly affected during the giving of her own evidence, and that of\r
+Dr. Birrell, who had made the post-mortem examination of the deceased.\r
+\r
+\r
+He frowned, and tearing the paper in two, went across the room and\r
+flung the pieces away.  How ugly it all was!  And how horribly real\r
+ugliness made things!  He felt a little annoyed with Lord Henry for\r
+having sent him the report.  And it was certainly stupid of him to have\r
+marked it with red pencil.  Victor might have read it.  The man knew\r
+more than enough English for that.\r
+\r
+Perhaps he had read it and had begun to suspect something.  And, yet,\r
+what did it matter?  What had Dorian Gray to do with Sibyl Vane's\r
+death?  There was nothing to fear.  Dorian Gray had not killed her.\r
+\r
+His eye fell on the yellow book that Lord Henry had sent him.  What was\r
+it, he wondered.  He went towards the little, pearl-coloured octagonal\r
+stand that had always looked to him like the work of some strange\r
+Egyptian bees that wrought in silver, and taking up the volume, flung\r
+himself into an arm-chair and began to turn over the leaves.  After a\r
+few minutes he became absorbed.  It was the strangest book that he had\r
+ever read.  It seemed to him that in exquisite raiment, and to the\r
+delicate sound of flutes, the sins of the world were passing in dumb\r
+show before him.  Things that he had dimly dreamed of were suddenly\r
+made real to him.  Things of which he had never dreamed were gradually\r
+revealed.\r
+\r
+It was a novel without a plot and with only one character, being,\r
+indeed, simply a psychological study of a certain young Parisian who\r
+spent his life trying to realize in the nineteenth century all the\r
+passions and modes of thought that belonged to every century except his\r
+own, and to sum up, as it were, in himself the various moods through\r
+which the world-spirit had ever passed, loving for their mere\r
+artificiality those renunciations that men have unwisely called virtue,\r
+as much as those natural rebellions that wise men still call sin.  The\r
+style in which it was written was that curious jewelled style, vivid\r
+and obscure at once, full of _argot_ and of archaisms, of technical\r
+expressions and of elaborate paraphrases, that characterizes the work\r
+of some of the finest artists of the French school of _Symbolistes_.\r
+There were in it metaphors as monstrous as orchids and as subtle in\r
+colour.  The life of the senses was described in the terms of mystical\r
+philosophy.  One hardly knew at times whether one was reading the\r
+spiritual ecstasies of some mediaeval saint or the morbid confessions\r
+of a modern sinner.  It was a poisonous book.  The heavy odour of\r
+incense seemed to cling about its pages and to trouble the brain.  The\r
+mere cadence of the sentences, the subtle monotony of their music, so\r
+full as it was of complex refrains and movements elaborately repeated,\r
+produced in the mind of the lad, as he passed from chapter to chapter,\r
+a form of reverie, a malady of dreaming, that made him unconscious of\r
+the falling day and creeping shadows.\r
+\r
+Cloudless, and pierced by one solitary star, a copper-green sky gleamed\r
+through the windows.  He read on by its wan light till he could read no\r
+more.  Then, after his valet had reminded him several times of the\r
+lateness of the hour, he got up, and going into the next room, placed\r
+the book on the little Florentine table that always stood at his\r
+bedside and began to dress for dinner.\r
+\r
+It was almost nine o'clock before he reached the club, where he found\r
+Lord Henry sitting alone, in the morning-room, looking very much bored.\r
+\r
+"I am so sorry, Harry," he cried, "but really it is entirely your\r
+fault.  That book you sent me so fascinated me that I forgot how the\r
+time was going."\r
+\r
+"Yes, I thought you would like it," replied his host, rising from his\r
+chair.\r
+\r
+"I didn't say I liked it, Harry.  I said it fascinated me.  There is a\r
+great difference."\r
+\r
+"Ah, you have discovered that?" murmured Lord Henry.  And they passed\r
+into the dining-room.\r
+\r
+\r
+\r
+CHAPTER 11\r
+\r
+For years, Dorian Gray could not free himself from the influence of\r
+this book.  Or perhaps it would be more accurate to say that he never\r
+sought to free himself from it.  He procured from Paris no less than\r
+nine large-paper copies of the first edition, and had them bound in\r
+different colours, so that they might suit his various moods and the\r
+changing fancies of a nature over which he seemed, at times, to have\r
+almost entirely lost control.  The hero, the wonderful young Parisian\r
+in whom the romantic and the scientific temperaments were so strangely\r
+blended, became to him a kind of prefiguring type of himself.  And,\r
+indeed, the whole book seemed to him to contain the story of his own\r
+life, written before he had lived it.\r
+\r
+In one point he was more fortunate than the novel's fantastic hero.  He\r
+never knew--never, indeed, had any cause to know--that somewhat\r
+grotesque dread of mirrors, and polished metal surfaces, and still\r
+water which came upon the young Parisian so early in his life, and was\r
+occasioned by the sudden decay of a beau that had once, apparently,\r
+been so remarkable.  It was with an almost cruel joy--and perhaps in\r
+nearly every joy, as certainly in every pleasure, cruelty has its\r
+place--that he used to read the latter part of the book, with its\r
+really tragic, if somewhat overemphasized, account of the sorrow and\r
+despair of one who had himself lost what in others, and the world, he\r
+had most dearly valued.\r
+\r
+For the wonderful beauty that had so fascinated Basil Hallward, and\r
+many others besides him, seemed never to leave him.  Even those who had\r
+heard the most evil things against him--and from time to time strange\r
+rumours about his mode of life crept through London and became the\r
+chatter of the clubs--could not believe anything to his dishonour when\r
+they saw him.  He had always the look of one who had kept himself\r
+unspotted from the world.  Men who talked grossly became silent when\r
+Dorian Gray entered the room.  There was something in the purity of his\r
+face that rebuked them.  His mere presence seemed to recall to them the\r
+memory of the innocence that they had tarnished.  They wondered how one\r
+so charming and graceful as he was could have escaped the stain of an\r
+age that was at once sordid and sensual.\r
+\r
+Often, on returning home from one of those mysterious and prolonged\r
+absences that gave rise to such strange conjecture among those who were\r
+his friends, or thought that they were so, he himself would creep\r
+upstairs to the locked room, open the door with the key that never left\r
+him now, and stand, with a mirror, in front of the portrait that Basil\r
+Hallward had painted of him, looking now at the evil and aging face on\r
+the canvas, and now at the fair young face that laughed back at him\r
+from the polished glass.  The very sharpness of the contrast used to\r
+quicken his sense of pleasure.  He grew more and more enamoured of his\r
+own beauty, more and more interested in the corruption of his own soul.\r
+He would examine with minute care, and sometimes with a monstrous and\r
+terrible delight, the hideous lines that seared the wrinkling forehead\r
+or crawled around the heavy sensual mouth, wondering sometimes which\r
+were the more horrible, the signs of sin or the signs of age.  He would\r
+place his white hands beside the coarse bloated hands of the picture,\r
+and smile.  He mocked the misshapen body and the failing limbs.\r
+\r
+There were moments, indeed, at night, when, lying sleepless in his own\r
+delicately scented chamber, or in the sordid room of the little\r
+ill-famed tavern near the docks which, under an assumed name and in\r
+disguise, it was his habit to frequent, he would think of the ruin he\r
+had brought upon his soul with a pity that was all the more poignant\r
+because it was purely selfish.  But moments such as these were rare.\r
+That curiosity about life which Lord Henry had first stirred in him, as\r
+they sat together in the garden of their friend, seemed to increase\r
+with gratification.  The more he knew, the more he desired to know.  He\r
+had mad hungers that grew more ravenous as he fed them.\r
+\r
+Yet he was not really reckless, at any rate in his relations to\r
+society.  Once or twice every month during the winter, and on each\r
+Wednesday evening while the season lasted, he would throw open to the\r
+world his beautiful house and have the most celebrated musicians of the\r
+day to charm his guests with the wonders of their art.  His little\r
+dinners, in the settling of which Lord Henry always assisted him, were\r
+noted as much for the careful selection and placing of those invited,\r
+as for the exquisite taste shown in the decoration of the table, with\r
+its subtle symphonic arrangements of exotic flowers, and embroidered\r
+cloths, and antique plate of gold and silver.  Indeed, there were many,\r
+especially among the very young men, who saw, or fancied that they saw,\r
+in Dorian Gray the true realization of a type of which they had often\r
+dreamed in Eton or Oxford days, a type that was to combine something of\r
+the real culture of the scholar with all the grace and distinction and\r
+perfect manner of a citizen of the world.  To them he seemed to be of\r
+the company of those whom Dante describes as having sought to "make\r
+themselves perfect by the worship of beauty."  Like Gautier, he was one\r
+for whom "the visible world existed."\r
+\r
+And, certainly, to him life itself was the first, the greatest, of the\r
+arts, and for it all the other arts seemed to be but a preparation.\r
+Fashion, by which what is really fantastic becomes for a moment\r
+universal, and dandyism, which, in its own way, is an attempt to assert\r
+the absolute modernity of beauty, had, of course, their fascination for\r
+him.  His mode of dressing, and the particular styles that from time to\r
+time he affected, had their marked influence on the young exquisites of\r
+the Mayfair balls and Pall Mall club windows, who copied him in\r
+everything that he did, and tried to reproduce the accidental charm of\r
+his graceful, though to him only half-serious, fopperies.\r
+\r
+For, while he was but too ready to accept the position that was almost\r
+immediately offered to him on his coming of age, and found, indeed, a\r
+subtle pleasure in the thought that he might really become to the\r
+London of his own day what to imperial Neronian Rome the author of the\r
+Satyricon once had been, yet in his inmost heart he desired to be\r
+something more than a mere _arbiter elegantiarum_, to be consulted on the\r
+wearing of a jewel, or the knotting of a necktie, or the conduct of a\r
+cane.  He sought to elaborate some new scheme of life that would have\r
+its reasoned philosophy and its ordered principles, and find in the\r
+spiritualizing of the senses its highest realization.\r
+\r
+The worship of the senses has often, and with much justice, been\r
+decried, men feeling a natural instinct of terror about passions and\r
+sensations that seem stronger than themselves, and that they are\r
+conscious of sharing with the less highly organized forms of existence.\r
+But it appeared to Dorian Gray that the true nature of the senses had\r
+never been understood, and that they had remained savage and animal\r
+merely because the world had sought to starve them into submission or\r
+to kill them by pain, instead of aiming at making them elements of a\r
+new spirituality, of which a fine instinct for beauty was to be the\r
+dominant characteristic.  As he looked back upon man moving through\r
+history, he was haunted by a feeling of loss.  So much had been\r
+surrendered! and to such little purpose!  There had been mad wilful\r
+rejections, monstrous forms of self-torture and self-denial, whose\r
+origin was fear and whose result was a degradation infinitely more\r
+terrible than that fancied degradation from which, in their ignorance,\r
+they had sought to escape; Nature, in her wonderful irony, driving out\r
+the anchorite to feed with the wild animals of the desert and giving to\r
+the hermit the beasts of the field as his companions.\r
+\r
+Yes:  there was to be, as Lord Henry had prophesied, a new Hedonism\r
+that was to recreate life and to save it from that harsh uncomely\r
+puritanism that is having, in our own day, its curious revival.  It was\r
+to have its service of the intellect, certainly, yet it was never to\r
+accept any theory or system that would involve the sacrifice of any\r
+mode of passionate experience.  Its aim, indeed, was to be experience\r
+itself, and not the fruits of experience, sweet or bitter as they might\r
+be.  Of the asceticism that deadens the senses, as of the vulgar\r
+profligacy that dulls them, it was to know nothing.  But it was to\r
+teach man to concentrate himself upon the moments of a life that is\r
+itself but a moment.\r
+\r
+There are few of us who have not sometimes wakened before dawn, either\r
+after one of those dreamless nights that make us almost enamoured of\r
+death, or one of those nights of horror and misshapen joy, when through\r
+the chambers of the brain sweep phantoms more terrible than reality\r
+itself, and instinct with that vivid life that lurks in all grotesques,\r
+and that lends to Gothic art its enduring vitality, this art being, one\r
+might fancy, especially the art of those whose minds have been troubled\r
+with the malady of reverie.  Gradually white fingers creep through the\r
+curtains, and they appear to tremble.  In black fantastic shapes, dumb\r
+shadows crawl into the corners of the room and crouch there.  Outside,\r
+there is the stirring of birds among the leaves, or the sound of men\r
+going forth to their work, or the sigh and sob of the wind coming down\r
+from the hills and wandering round the silent house, as though it\r
+feared to wake the sleepers and yet must needs call forth sleep from\r
+her purple cave.  Veil after veil of thin dusky gauze is lifted, and by\r
+degrees the forms and colours of things are restored to them, and we\r
+watch the dawn remaking the world in its antique pattern.  The wan\r
+mirrors get back their mimic life.  The flameless tapers stand where we\r
+had left them, and beside them lies the half-cut book that we had been\r
+studying, or the wired flower that we had worn at the ball, or the\r
+letter that we had been afraid to read, or that we had read too often.\r
+Nothing seems to us changed.  Out of the unreal shadows of the night\r
+comes back the real life that we had known.  We have to resume it where\r
+we had left off, and there steals over us a terrible sense of the\r
+necessity for the continuance of energy in the same wearisome round of\r
+stereotyped habits, or a wild longing, it may be, that our eyelids\r
+might open some morning upon a world that had been refashioned anew in\r
+the darkness for our pleasure, a world in which things would have fresh\r
+shapes and colours, and be changed, or have other secrets, a world in\r
+which the past would have little or no place, or survive, at any rate,\r
+in no conscious form of obligation or regret, the remembrance even of\r
+joy having its bitterness and the memories of pleasure their pain.\r
+\r
+It was the creation of such worlds as these that seemed to Dorian Gray\r
+to be the true object, or amongst the true objects, of life; and in his\r
+search for sensations that would be at once new and delightful, and\r
+possess that element of strangeness that is so essential to romance, he\r
+would often adopt certain modes of thought that he knew to be really\r
+alien to his nature, abandon himself to their subtle influences, and\r
+then, having, as it were, caught their colour and satisfied his\r
+intellectual curiosity, leave them with that curious indifference that\r
+is not incompatible with a real ardour of temperament, and that,\r
+indeed, according to certain modern psychologists, is often a condition\r
+of it.\r
+\r
+It was rumoured of him once that he was about to join the Roman\r
+Catholic communion, and certainly the Roman ritual had always a great\r
+attraction for him.  The daily sacrifice, more awful really than all\r
+the sacrifices of the antique world, stirred him as much by its superb\r
+rejection of the evidence of the senses as by the primitive simplicity\r
+of its elements and the eternal pathos of the human tragedy that it\r
+sought to symbolize.  He loved to kneel down on the cold marble\r
+pavement and watch the priest, in his stiff flowered dalmatic, slowly\r
+and with white hands moving aside the veil of the tabernacle, or\r
+raising aloft the jewelled, lantern-shaped monstrance with that pallid\r
+wafer that at times, one would fain think, is indeed the "_panis\r
+caelestis_," the bread of angels, or, robed in the garments of the\r
+Passion of Christ, breaking the Host into the chalice and smiting his\r
+breast for his sins.  The fuming censers that the grave boys, in their\r
+lace and scarlet, tossed into the air like great gilt flowers had their\r
+subtle fascination for him.  As he passed out, he used to look with\r
+wonder at the black confessionals and long to sit in the dim shadow of\r
+one of them and listen to men and women whispering through the worn\r
+grating the true story of their lives.\r
+\r
+But he never fell into the error of arresting his intellectual\r
+development by any formal acceptance of creed or system, or of\r
+mistaking, for a house in which to live, an inn that is but suitable\r
+for the sojourn of a night, or for a few hours of a night in which\r
+there are no stars and the moon is in travail.  Mysticism, with its\r
+marvellous power of making common things strange to us, and the subtle\r
+antinomianism that always seems to accompany it, moved him for a\r
+season; and for a season he inclined to the materialistic doctrines of\r
+the _Darwinismus_ movement in Germany, and found a curious pleasure in\r
+tracing the thoughts and passions of men to some pearly cell in the\r
+brain, or some white nerve in the body, delighting in the conception of\r
+the absolute dependence of the spirit on certain physical conditions,\r
+morbid or healthy, normal or diseased.  Yet, as has been said of him\r
+before, no theory of life seemed to him to be of any importance\r
+compared with life itself.  He felt keenly conscious of how barren all\r
+intellectual speculation is when separated from action and experiment.\r
+He knew that the senses, no less than the soul, have their spiritual\r
+mysteries to reveal.\r
+\r
+And so he would now study perfumes and the secrets of their\r
+manufacture, distilling heavily scented oils and burning odorous gums\r
+from the East.  He saw that there was no mood of the mind that had not\r
+its counterpart in the sensuous life, and set himself to discover their\r
+true relations, wondering what there was in frankincense that made one\r
+mystical, and in ambergris that stirred one's passions, and in violets\r
+that woke the memory of dead romances, and in musk that troubled the\r
+brain, and in champak that stained the imagination; and seeking often\r
+to elaborate a real psychology of perfumes, and to estimate the several\r
+influences of sweet-smelling roots and scented, pollen-laden flowers;\r
+of aromatic balms and of dark and fragrant woods; of spikenard, that\r
+sickens; of hovenia, that makes men mad; and of aloes, that are said to\r
+be able to expel melancholy from the soul.\r
+\r
+At another time he devoted himself entirely to music, and in a long\r
+latticed room, with a vermilion-and-gold ceiling and walls of\r
+olive-green lacquer, he used to give curious concerts in which mad\r
+gipsies tore wild music from little zithers, or grave, yellow-shawled\r
+Tunisians plucked at the strained strings of monstrous lutes, while\r
+grinning Negroes beat monotonously upon copper drums and, crouching\r
+upon scarlet mats, slim turbaned Indians blew through long pipes of\r
+reed or brass and charmed--or feigned to charm--great hooded snakes and\r
+horrible horned adders.  The harsh intervals and shrill discords of\r
+barbaric music stirred him at times when Schubert's grace, and Chopin's\r
+beautiful sorrows, and the mighty harmonies of Beethoven himself, fell\r
+unheeded on his ear.  He collected together from all parts of the world\r
+the strangest instruments that could be found, either in the tombs of\r
+dead nations or among the few savage tribes that have survived contact\r
+with Western civilizations, and loved to touch and try them.  He had\r
+the mysterious _juruparis_ of the Rio Negro Indians, that women are not\r
+allowed to look at and that even youths may not see till they have been\r
+subjected to fasting and scourging, and the earthen jars of the\r
+Peruvians that have the shrill cries of birds, and flutes of human\r
+bones such as Alfonso de Ovalle heard in Chile, and the sonorous green\r
+jaspers that are found near Cuzco and give forth a note of singular\r
+sweetness.  He had painted gourds filled with pebbles that rattled when\r
+they were shaken; the long _clarin_ of the Mexicans, into which the\r
+performer does not blow, but through which he inhales the air; the\r
+harsh _ture_ of the Amazon tribes, that is sounded by the sentinels who\r
+sit all day long in high trees, and can be heard, it is said, at a\r
+distance of three leagues; the _teponaztli_, that has two vibrating\r
+tongues of wood and is beaten with sticks that are smeared with an\r
+elastic gum obtained from the milky juice of plants; the _yotl_-bells of\r
+the Aztecs, that are hung in clusters like grapes; and a huge\r
+cylindrical drum, covered with the skins of great serpents, like the\r
+one that Bernal Diaz saw when he went with Cortes into the Mexican\r
+temple, and of whose doleful sound he has left us so vivid a\r
+description.  The fantastic character of these instruments fascinated\r
+him, and he felt a curious delight in the thought that art, like\r
+Nature, has her monsters, things of bestial shape and with hideous\r
+voices.  Yet, after some time, he wearied of them, and would sit in his\r
+box at the opera, either alone or with Lord Henry, listening in rapt\r
+pleasure to "Tannhauser" and seeing in the prelude to that great work\r
+of art a presentation of the tragedy of his own soul.\r
+\r
+On one occasion he took up the study of jewels, and appeared at a\r
+costume ball as Anne de Joyeuse, Admiral of France, in a dress covered\r
+with five hundred and sixty pearls.  This taste enthralled him for\r
+years, and, indeed, may be said never to have left him.  He would often\r
+spend a whole day settling and resettling in their cases the various\r
+stones that he had collected, such as the olive-green chrysoberyl that\r
+turns red by lamplight, the cymophane with its wirelike line of silver,\r
+the pistachio-coloured peridot, rose-pink and wine-yellow topazes,\r
+carbuncles of fiery scarlet with tremulous, four-rayed stars, flame-red\r
+cinnamon-stones, orange and violet spinels, and amethysts with their\r
+alternate layers of ruby and sapphire.  He loved the red gold of the\r
+sunstone, and the moonstone's pearly whiteness, and the broken rainbow\r
+of the milky opal.  He procured from Amsterdam three emeralds of\r
+extraordinary size and richness of colour, and had a turquoise _de la\r
+vieille roche_ that was the envy of all the connoisseurs.\r
+\r
+He discovered wonderful stories, also, about jewels.  In Alphonso's\r
+Clericalis Disciplina a serpent was mentioned with eyes of real\r
+jacinth, and in the romantic history of Alexander, the Conqueror of\r
+Emathia was said to have found in the vale of Jordan snakes "with\r
+collars of real emeralds growing on their backs." There was a gem in\r
+the brain of the dragon, Philostratus told us, and "by the exhibition\r
+of golden letters and a scarlet robe" the monster could be thrown into\r
+a magical sleep and slain.  According to the great alchemist, Pierre de\r
+Boniface, the diamond rendered a man invisible, and the agate of India\r
+made him eloquent.  The cornelian appeased anger, and the hyacinth\r
+provoked sleep, and the amethyst drove away the fumes of wine.  The\r
+garnet cast out demons, and the hydropicus deprived the moon of her\r
+colour.  The selenite waxed and waned with the moon, and the meloceus,\r
+that discovers thieves, could be affected only by the blood of kids.\r
+Leonardus Camillus had seen a white stone taken from the brain of a\r
+newly killed toad, that was a certain antidote against poison.  The\r
+bezoar, that was found in the heart of the Arabian deer, was a charm\r
+that could cure the plague.  In the nests of Arabian birds was the\r
+aspilates, that, according to Democritus, kept the wearer from any\r
+danger by fire.\r
+\r
+The King of Ceilan rode through his city with a large ruby in his hand,\r
+as the ceremony of his coronation.  The gates of the palace of John the\r
+Priest were "made of sardius, with the horn of the horned snake\r
+inwrought, so that no man might bring poison within." Over the gable\r
+were "two golden apples, in which were two carbuncles," so that the\r
+gold might shine by day and the carbuncles by night.  In Lodge's\r
+strange romance 'A Margarite of America', it was stated that in the\r
+chamber of the queen one could behold "all the chaste ladies of the\r
+world, inchased out of silver, looking through fair mirrours of\r
+chrysolites, carbuncles, sapphires, and greene emeraults." Marco Polo\r
+had seen the inhabitants of Zipangu place rose-coloured pearls in the\r
+mouths of the dead.  A sea-monster had been enamoured of the pearl that\r
+the diver brought to King Perozes, and had slain the thief, and mourned\r
+for seven moons over its loss.  When the Huns lured the king into the\r
+great pit, he flung it away--Procopius tells the story--nor was it ever\r
+found again, though the Emperor Anastasius offered five hundred-weight\r
+of gold pieces for it.  The King of Malabar had shown to a certain\r
+Venetian a rosary of three hundred and four pearls, one for every god\r
+that he worshipped.\r
+\r
+When the Duke de Valentinois, son of Alexander VI, visited Louis XII of\r
+France, his horse was loaded with gold leaves, according to Brantome,\r
+and his cap had double rows of rubies that threw out a great light.\r
+Charles of England had ridden in stirrups hung with four hundred and\r
+twenty-one diamonds.  Richard II had a coat, valued at thirty thousand\r
+marks, which was covered with balas rubies.  Hall described Henry VIII,\r
+on his way to the Tower previous to his coronation, as wearing "a\r
+jacket of raised gold, the placard embroidered with diamonds and other\r
+rich stones, and a great bauderike about his neck of large balasses."\r
+The favourites of James I wore ear-rings of emeralds set in gold\r
+filigrane.  Edward II gave to Piers Gaveston a suit of red-gold armour\r
+studded with jacinths, a collar of gold roses set with\r
+turquoise-stones, and a skull-cap _parseme_ with pearls.  Henry II wore\r
+jewelled gloves reaching to the elbow, and had a hawk-glove sewn with\r
+twelve rubies and fifty-two great orients.  The ducal hat of Charles\r
+the Rash, the last Duke of Burgundy of his race, was hung with\r
+pear-shaped pearls and studded with sapphires.\r
+\r
+How exquisite life had once been!  How gorgeous in its pomp and\r
+decoration!  Even to read of the luxury of the dead was wonderful.\r
+\r
+Then he turned his attention to embroideries and to the tapestries that\r
+performed the office of frescoes in the chill rooms of the northern\r
+nations of Europe.  As he investigated the subject--and he always had\r
+an extraordinary faculty of becoming absolutely absorbed for the moment\r
+in whatever he took up--he was almost saddened by the reflection of the\r
+ruin that time brought on beautiful and wonderful things.  He, at any\r
+rate, had escaped that.  Summer followed summer, and the yellow\r
+jonquils bloomed and died many times, and nights of horror repeated the\r
+story of their shame, but he was unchanged.  No winter marred his face\r
+or stained his flowerlike bloom.  How different it was with material\r
+things!  Where had they passed to?  Where was the great crocus-coloured\r
+robe, on which the gods fought against the giants, that had been worked\r
+by brown girls for the pleasure of Athena?  Where the huge velarium\r
+that Nero had stretched across the Colosseum at Rome, that Titan sail\r
+of purple on which was represented the starry sky, and Apollo driving a\r
+chariot drawn by white, gilt-reined steeds?  He longed to see the\r
+curious table-napkins wrought for the Priest of the Sun, on which were\r
+displayed all the dainties and viands that could be wanted for a feast;\r
+the mortuary cloth of King Chilperic, with its three hundred golden\r
+bees; the fantastic robes that excited the indignation of the Bishop of\r
+Pontus and were figured with "lions, panthers, bears, dogs, forests,\r
+rocks, hunters--all, in fact, that a painter can copy from nature"; and\r
+the coat that Charles of Orleans once wore, on the sleeves of which\r
+were embroidered the verses of a song beginning "_Madame, je suis tout\r
+joyeux_," the musical accompaniment of the words being wrought in gold\r
+thread, and each note, of square shape in those days, formed with four\r
+pearls.  He read of the room that was prepared at the palace at Rheims\r
+for the use of Queen Joan of Burgundy and was decorated with "thirteen\r
+hundred and twenty-one parrots, made in broidery, and blazoned with the\r
+king's arms, and five hundred and sixty-one butterflies, whose wings\r
+were similarly ornamented with the arms of the queen, the whole worked\r
+in gold."  Catherine de Medicis had a mourning-bed made for her of\r
+black velvet powdered with crescents and suns.  Its curtains were of\r
+damask, with leafy wreaths and garlands, figured upon a gold and silver\r
+ground, and fringed along the edges with broideries of pearls, and it\r
+stood in a room hung with rows of the queen's devices in cut black\r
+velvet upon cloth of silver.  Louis XIV had gold embroidered caryatides\r
+fifteen feet high in his apartment.  The state bed of Sobieski, King of\r
+Poland, was made of Smyrna gold brocade embroidered in turquoises with\r
+verses from the Koran.  Its supports were of silver gilt, beautifully\r
+chased, and profusely set with enamelled and jewelled medallions.  It\r
+had been taken from the Turkish camp before Vienna, and the standard of\r
+Mohammed had stood beneath the tremulous gilt of its canopy.\r
+\r
+And so, for a whole year, he sought to accumulate the most exquisite\r
+specimens that he could find of textile and embroidered work, getting\r
+the dainty Delhi muslins, finely wrought with gold-thread palmates and\r
+stitched over with iridescent beetles' wings; the Dacca gauzes, that\r
+from their transparency are known in the East as "woven air," and\r
+"running water," and "evening dew"; strange figured cloths from Java;\r
+elaborate yellow Chinese hangings; books bound in tawny satins or fair\r
+blue silks and wrought with _fleurs-de-lis_, birds and images; veils of\r
+_lacis_ worked in Hungary point; Sicilian brocades and stiff Spanish\r
+velvets; Georgian work, with its gilt coins, and Japanese _Foukousas_,\r
+with their green-toned golds and their marvellously plumaged birds.\r
+\r
+He had a special passion, also, for ecclesiastical vestments, as indeed\r
+he had for everything connected with the service of the Church.  In the\r
+long cedar chests that lined the west gallery of his house, he had\r
+stored away many rare and beautiful specimens of what is really the\r
+raiment of the Bride of Christ, who must wear purple and jewels and\r
+fine linen that she may hide the pallid macerated body that is worn by\r
+the suffering that she seeks for and wounded by self-inflicted pain.\r
+He possessed a gorgeous cope of crimson silk and gold-thread damask,\r
+figured with a repeating pattern of golden pomegranates set in\r
+six-petalled formal blossoms, beyond which on either side was the\r
+pine-apple device wrought in seed-pearls. The orphreys were divided\r
+into panels representing scenes from the life of the Virgin, and the\r
+coronation of the Virgin was figured in coloured silks upon the hood.\r
+This was Italian work of the fifteenth century.  Another cope was of\r
+green velvet, embroidered with heart-shaped groups of acanthus-leaves,\r
+from which spread long-stemmed white blossoms, the details of which\r
+were picked out with silver thread and coloured crystals.  The morse\r
+bore a seraph's head in gold-thread raised work.  The orphreys were\r
+woven in a diaper of red and gold silk, and were starred with\r
+medallions of many saints and martyrs, among whom was St. Sebastian.\r
+He had chasubles, also, of amber-coloured silk, and blue silk and gold\r
+brocade, and yellow silk damask and cloth of gold, figured with\r
+representations of the Passion and Crucifixion of Christ, and\r
+embroidered with lions and peacocks and other emblems; dalmatics of\r
+white satin and pink silk damask, decorated with tulips and dolphins\r
+and _fleurs-de-lis_; altar frontals of crimson velvet and blue linen; and\r
+many corporals, chalice-veils, and sudaria.  In the mystic offices to\r
+which such things were put, there was something that quickened his\r
+imagination.\r
+\r
+For these treasures, and everything that he collected in his lovely\r
+house, were to be to him means of forgetfulness, modes by which he\r
+could escape, for a season, from the fear that seemed to him at times\r
+to be almost too great to be borne.  Upon the walls of the lonely\r
+locked room where he had spent so much of his boyhood, he had hung with\r
+his own hands the terrible portrait whose changing features showed him\r
+the real degradation of his life, and in front of it had draped the\r
+purple-and-gold pall as a curtain.  For weeks he would not go there,\r
+would forget the hideous painted thing, and get back his light heart,\r
+his wonderful joyousness, his passionate absorption in mere existence.\r
+Then, suddenly, some night he would creep out of the house, go down to\r
+dreadful places near Blue Gate Fields, and stay there, day after day,\r
+until he was driven away.  On his return he would sit in front of the\r
+picture, sometimes loathing it and himself, but filled, at other\r
+times, with that pride of individualism that is half the\r
+fascination of sin, and smiling with secret pleasure at the misshapen\r
+shadow that had to bear the burden that should have been his own.\r
+\r
+After a few years he could not endure to be long out of England, and\r
+gave up the villa that he had shared at Trouville with Lord Henry, as\r
+well as the little white walled-in house at Algiers where they had more\r
+than once spent the winter.  He hated to be separated from the picture\r
+that was such a part of his life, and was also afraid that during his\r
+absence some one might gain access to the room, in spite of the\r
+elaborate bars that he had caused to be placed upon the door.\r
+\r
+He was quite conscious that this would tell them nothing.  It was true\r
+that the portrait still preserved, under all the foulness and ugliness\r
+of the face, its marked likeness to himself; but what could they learn\r
+from that?  He would laugh at any one who tried to taunt him.  He had\r
+not painted it.  What was it to him how vile and full of shame it\r
+looked?  Even if he told them, would they believe it?\r
+\r
+Yet he was afraid.  Sometimes when he was down at his great house in\r
+Nottinghamshire, entertaining the fashionable young men of his own rank\r
+who were his chief companions, and astounding the county by the wanton\r
+luxury and gorgeous splendour of his mode of life, he would suddenly\r
+leave his guests and rush back to town to see that the door had not\r
+been tampered with and that the picture was still there.  What if it\r
+should be stolen?  The mere thought made him cold with horror.  Surely\r
+the world would know his secret then.  Perhaps the world already\r
+suspected it.\r
+\r
+For, while he fascinated many, there were not a few who distrusted him.\r
+He was very nearly blackballed at a West End club of which his birth\r
+and social position fully entitled him to become a member, and it was\r
+said that on one occasion, when he was brought by a friend into the\r
+smoking-room of the Churchill, the Duke of Berwick and another\r
+gentleman got up in a marked manner and went out.  Curious stories\r
+became current about him after he had passed his twenty-fifth year.  It\r
+was rumoured that he had been seen brawling with foreign sailors in a\r
+low den in the distant parts of Whitechapel, and that he consorted with\r
+thieves and coiners and knew the mysteries of their trade.  His\r
+extraordinary absences became notorious, and, when he used to reappear\r
+again in society, men would whisper to each other in corners, or pass\r
+him with a sneer, or look at him with cold searching eyes, as though\r
+they were determined to discover his secret.\r
+\r
+Of such insolences and attempted slights he, of course, took no notice,\r
+and in the opinion of most people his frank debonair manner, his\r
+charming boyish smile, and the infinite grace of that wonderful youth\r
+that seemed never to leave him, were in themselves a sufficient answer\r
+to the calumnies, for so they termed them, that were circulated about\r
+him.  It was remarked, however, that some of those who had been most\r
+intimate with him appeared, after a time, to shun him.  Women who had\r
+wildly adored him, and for his sake had braved all social censure and\r
+set convention at defiance, were seen to grow pallid with shame or\r
+horror if Dorian Gray entered the room.\r
+\r
+Yet these whispered scandals only increased in the eyes of many his\r
+strange and dangerous charm.  His great wealth was a certain element of\r
+security.  Society--civilized society, at least--is never very ready to\r
+believe anything to the detriment of those who are both rich and\r
+fascinating.  It feels instinctively that manners are of more\r
+importance than morals, and, in its opinion, the highest respectability\r
+is of much less value than the possession of a good _chef_.  And, after\r
+all, it is a very poor consolation to be told that the man who has\r
+given one a bad dinner, or poor wine, is irreproachable in his private\r
+life.  Even the cardinal virtues cannot atone for half-cold _entrees_, as\r
+Lord Henry remarked once, in a discussion on the subject, and there is\r
+possibly a good deal to be said for his view.  For the canons of good\r
+society are, or should be, the same as the canons of art.  Form is\r
+absolutely essential to it.  It should have the dignity of a ceremony,\r
+as well as its unreality, and should combine the insincere character of\r
+a romantic play with the wit and beauty that make such plays delightful\r
+to us.  Is insincerity such a terrible thing?  I think not.  It is\r
+merely a method by which we can multiply our personalities.\r
+\r
+Such, at any rate, was Dorian Gray's opinion.  He used to wonder at the\r
+shallow psychology of those who conceive the ego in man as a thing\r
+simple, permanent, reliable, and of one essence.  To him, man was a\r
+being with myriad lives and myriad sensations, a complex multiform\r
+creature that bore within itself strange legacies of thought and\r
+passion, and whose very flesh was tainted with the monstrous maladies\r
+of the dead.  He loved to stroll through the gaunt cold picture-gallery\r
+of his country house and look at the various portraits of those whose\r
+blood flowed in his veins.  Here was Philip Herbert, described by\r
+Francis Osborne, in his Memoires on the Reigns of Queen Elizabeth and\r
+King James, as one who was "caressed by the Court for his handsome\r
+face, which kept him not long company."  Was it young Herbert's life\r
+that he sometimes led?  Had some strange poisonous germ crept from body\r
+to body till it had reached his own?  Was it some dim sense of that\r
+ruined grace that had made him so suddenly, and almost without cause,\r
+give utterance, in Basil Hallward's studio, to the mad prayer that had\r
+so changed his life?  Here, in gold-embroidered red doublet, jewelled\r
+surcoat, and gilt-edged ruff and wristbands, stood Sir Anthony Sherard,\r
+with his silver-and-black armour piled at his feet.  What had this\r
+man's legacy been?  Had the lover of Giovanna of Naples bequeathed him\r
+some inheritance of sin and shame?  Were his own actions merely the\r
+dreams that the dead man had not dared to realize?  Here, from the\r
+fading canvas, smiled Lady Elizabeth Devereux, in her gauze hood, pearl\r
+stomacher, and pink slashed sleeves.  A flower was in her right hand,\r
+and her left clasped an enamelled collar of white and damask roses.  On\r
+a table by her side lay a mandolin and an apple.  There were large\r
+green rosettes upon her little pointed shoes.  He knew her life, and\r
+the strange stories that were told about her lovers.  Had he something\r
+of her temperament in him?  These oval, heavy-lidded eyes seemed to\r
+look curiously at him.  What of George Willoughby, with his powdered\r
+hair and fantastic patches?  How evil he looked!  The face was\r
+saturnine and swarthy, and the sensual lips seemed to be twisted with\r
+disdain.  Delicate lace ruffles fell over the lean yellow hands that\r
+were so overladen with rings.  He had been a macaroni of the eighteenth\r
+century, and the friend, in his youth, of Lord Ferrars.  What of the\r
+second Lord Beckenham, the companion of the Prince Regent in his\r
+wildest days, and one of the witnesses at the secret marriage with Mrs.\r
+Fitzherbert?  How proud and handsome he was, with his chestnut curls\r
+and insolent pose!  What passions had he bequeathed?  The world had\r
+looked upon him as infamous.  He had led the orgies at Carlton House.\r
+The star of the Garter glittered upon his breast.  Beside him hung the\r
+portrait of his wife, a pallid, thin-lipped woman in black.  Her blood,\r
+also, stirred within him.  How curious it all seemed!  And his mother\r
+with her Lady Hamilton face and her moist, wine-dashed lips--he knew\r
+what he had got from her.  He had got from her his beauty, and his\r
+passion for the beauty of others.  She laughed at him in her loose\r
+Bacchante dress.  There were vine leaves in her hair.  The purple\r
+spilled from the cup she was holding.  The carnations of the painting\r
+had withered, but the eyes were still wonderful in their depth and\r
+brilliancy of colour.  They seemed to follow him wherever he went.\r
+\r
+Yet one had ancestors in literature as well as in one's own race,\r
+nearer perhaps in type and temperament, many of them, and certainly\r
+with an influence of which one was more absolutely conscious.  There\r
+were times when it appeared to Dorian Gray that the whole of history\r
+was merely the record of his own life, not as he had lived it in act\r
+and circumstance, but as his imagination had created it for him, as it\r
+had been in his brain and in his passions.  He felt that he had known\r
+them all, those strange terrible figures that had passed across the\r
+stage of the world and made sin so marvellous and evil so full of\r
+subtlety.  It seemed to him that in some mysterious way their lives had\r
+been his own.\r
+\r
+The hero of the wonderful novel that had so influenced his life had\r
+himself known this curious fancy.  In the seventh chapter he tells how,\r
+crowned with laurel, lest lightning might strike him, he had sat, as\r
+Tiberius, in a garden at Capri, reading the shameful books of\r
+Elephantis, while dwarfs and peacocks strutted round him and the\r
+flute-player mocked the swinger of the censer; and, as Caligula, had\r
+caroused with the green-shirted jockeys in their stables and supped in\r
+an ivory manger with a jewel-frontleted horse; and, as Domitian, had\r
+wandered through a corridor lined with marble mirrors, looking round\r
+with haggard eyes for the reflection of the dagger that was to end his\r
+days, and sick with that ennui, that terrible _taedium vitae_, that comes\r
+on those to whom life denies nothing; and had peered through a clear\r
+emerald at the red shambles of the circus and then, in a litter of\r
+pearl and purple drawn by silver-shod mules, been carried through the\r
+Street of Pomegranates to a House of Gold and heard men cry on Nero\r
+Caesar as he passed by; and, as Elagabalus, had painted his face with\r
+colours, and plied the distaff among the women, and brought the Moon\r
+from Carthage and given her in mystic marriage to the Sun.\r
+\r
+Over and over again Dorian used to read this fantastic chapter, and the\r
+two chapters immediately following, in which, as in some curious\r
+tapestries or cunningly wrought enamels, were pictured the awful and\r
+beautiful forms of those whom vice and blood and weariness had made\r
+monstrous or mad:  Filippo, Duke of Milan, who slew his wife and\r
+painted her lips with a scarlet poison that her lover might suck death\r
+from the dead thing he fondled; Pietro Barbi, the Venetian, known as\r
+Paul the Second, who sought in his vanity to assume the title of\r
+Formosus, and whose tiara, valued at two hundred thousand florins, was\r
+bought at the price of a terrible sin; Gian Maria Visconti, who used\r
+hounds to chase living men and whose murdered body was covered with\r
+roses by a harlot who had loved him; the Borgia on his white horse,\r
+with Fratricide riding beside him and his mantle stained with the blood\r
+of Perotto; Pietro Riario, the young Cardinal Archbishop of Florence,\r
+child and minion of Sixtus IV, whose beauty was equalled only by his\r
+debauchery, and who received Leonora of Aragon in a pavilion of white\r
+and crimson silk, filled with nymphs and centaurs, and gilded a boy\r
+that he might serve at the feast as Ganymede or Hylas; Ezzelin, whose\r
+melancholy could be cured only by the spectacle of death, and who had a\r
+passion for red blood, as other men have for red wine--the son of the\r
+Fiend, as was reported, and one who had cheated his father at dice when\r
+gambling with him for his own soul; Giambattista Cibo, who in mockery\r
+took the name of Innocent and into whose torpid veins the blood of\r
+three lads was infused by a Jewish doctor; Sigismondo Malatesta, the\r
+lover of Isotta and the lord of Rimini, whose effigy was burned at Rome\r
+as the enemy of God and man, who strangled Polyssena with a napkin, and\r
+gave poison to Ginevra d'Este in a cup of emerald, and in honour of a\r
+shameful passion built a pagan church for Christian worship; Charles\r
+VI, who had so wildly adored his brother's wife that a leper had warned\r
+him of the insanity that was coming on him, and who, when his brain had\r
+sickened and grown strange, could only be soothed by Saracen cards\r
+painted with the images of love and death and madness; and, in his\r
+trimmed jerkin and jewelled cap and acanthuslike curls, Grifonetto\r
+Baglioni, who slew Astorre with his bride, and Simonetto with his page,\r
+and whose comeliness was such that, as he lay dying in the yellow\r
+piazza of Perugia, those who had hated him could not choose but weep,\r
+and Atalanta, who had cursed him, blessed him.\r
+\r
+There was a horrible fascination in them all.  He saw them at night,\r
+and they troubled his imagination in the day.  The Renaissance knew of\r
+strange manners of poisoning--poisoning by a helmet and a lighted\r
+torch, by an embroidered glove and a jewelled fan, by a gilded pomander\r
+and by an amber chain.  Dorian Gray had been poisoned by a book.  There\r
+were moments when he looked on evil simply as a mode through which he\r
+could realize his conception of the beautiful.\r
+\r
+\r
+\r
+CHAPTER 12\r
+\r
+It was on the ninth of November, the eve of his own thirty-eighth\r
+birthday, as he often remembered afterwards.\r
+\r
+He was walking home about eleven o'clock from Lord Henry's, where he\r
+had been dining, and was wrapped in heavy furs, as the night was cold\r
+and foggy.  At the corner of Grosvenor Square and South Audley Street,\r
+a man passed him in the mist, walking very fast and with the collar of\r
+his grey ulster turned up.  He had a bag in his hand.  Dorian\r
+recognized him.  It was Basil Hallward.  A strange sense of fear, for\r
+which he could not account, came over him.  He made no sign of\r
+recognition and went on quickly in the direction of his own house.\r
+\r
+But Hallward had seen him.  Dorian heard him first stopping on the\r
+pavement and then hurrying after him.  In a few moments, his hand was\r
+on his arm.\r
+\r
+"Dorian!  What an extraordinary piece of luck!  I have been waiting for\r
+you in your library ever since nine o'clock. Finally I took pity on\r
+your tired servant and told him to go to bed, as he let me out.  I am\r
+off to Paris by the midnight train, and I particularly wanted to see\r
+you before I left.  I thought it was you, or rather your fur coat, as\r
+you passed me.  But I wasn't quite sure.  Didn't you recognize me?"\r
+\r
+"In this fog, my dear Basil?  Why, I can't even recognize Grosvenor\r
+Square.  I believe my house is somewhere about here, but I don't feel\r
+at all certain about it.  I am sorry you are going away, as I have not\r
+seen you for ages.  But I suppose you will be back soon?"\r
+\r
+"No:  I am going to be out of England for six months.  I intend to take\r
+a studio in Paris and shut myself up till I have finished a great\r
+picture I have in my head.  However, it wasn't about myself I wanted to\r
+talk.  Here we are at your door.  Let me come in for a moment.  I have\r
+something to say to you."\r
+\r
+"I shall be charmed.  But won't you miss your train?" said Dorian Gray\r
+languidly as he passed up the steps and opened the door with his\r
+latch-key.\r
+\r
+The lamplight struggled out through the fog, and Hallward looked at his\r
+watch.  "I have heaps of time," he answered.  "The train doesn't go\r
+till twelve-fifteen, and it is only just eleven.  In fact, I was on my\r
+way to the club to look for you, when I met you.  You see, I shan't\r
+have any delay about luggage, as I have sent on my heavy things.  All I\r
+have with me is in this bag, and I can easily get to Victoria in twenty\r
+minutes."\r
+\r
+Dorian looked at him and smiled.  "What a way for a fashionable painter\r
+to travel!  A Gladstone bag and an ulster!  Come in, or the fog will\r
+get into the house.  And mind you don't talk about anything serious.\r
+Nothing is serious nowadays.  At least nothing should be."\r
+\r
+Hallward shook his head, as he entered, and followed Dorian into the\r
+library.  There was a bright wood fire blazing in the large open\r
+hearth.  The lamps were lit, and an open Dutch silver spirit-case\r
+stood, with some siphons of soda-water and large cut-glass tumblers, on\r
+a little marqueterie table.\r
+\r
+"You see your servant made me quite at home, Dorian.  He gave me\r
+everything I wanted, including your best gold-tipped cigarettes.  He is\r
+a most hospitable creature.  I like him much better than the Frenchman\r
+you used to have.  What has become of the Frenchman, by the bye?"\r
+\r
+Dorian shrugged his shoulders.  "I believe he married Lady Radley's\r
+maid, and has established her in Paris as an English dressmaker.\r
+Anglomania is very fashionable over there now, I hear.  It seems silly\r
+of the French, doesn't it?  But--do you know?--he was not at all a bad\r
+servant.  I never liked him, but I had nothing to complain about.  One\r
+often imagines things that are quite absurd.  He was really very\r
+devoted to me and seemed quite sorry when he went away.  Have another\r
+brandy-and-soda? Or would you like hock-and-seltzer? I always take\r
+hock-and-seltzer myself.  There is sure to be some in the next room."\r
+\r
+"Thanks, I won't have anything more," said the painter, taking his cap\r
+and coat off and throwing them on the bag that he had placed in the\r
+corner.  "And now, my dear fellow, I want to speak to you seriously.\r
+Don't frown like that.  You make it so much more difficult for me."\r
+\r
+"What is it all about?" cried Dorian in his petulant way, flinging\r
+himself down on the sofa.  "I hope it is not about myself.  I am tired\r
+of myself to-night. I should like to be somebody else."\r
+\r
+"It is about yourself," answered Hallward in his grave deep voice, "and\r
+I must say it to you.  I shall only keep you half an hour."\r
+\r
+Dorian sighed and lit a cigarette.  "Half an hour!" he murmured.\r
+\r
+"It is not much to ask of you, Dorian, and it is entirely for your own\r
+sake that I am speaking.  I think it right that you should know that\r
+the most dreadful things are being said against you in London."\r
+\r
+"I don't wish to know anything about them.  I love scandals about other\r
+people, but scandals about myself don't interest me.  They have not got\r
+the charm of novelty."\r
+\r
+"They must interest you, Dorian.  Every gentleman is interested in his\r
+good name.  You don't want people to talk of you as something vile and\r
+degraded.  Of course, you have your position, and your wealth, and all\r
+that kind of thing.  But position and wealth are not everything.  Mind\r
+you, I don't believe these rumours at all.  At least, I can't believe\r
+them when I see you.  Sin is a thing that writes itself across a man's\r
+face.  It cannot be concealed.  People talk sometimes of secret vices.\r
+There are no such things.  If a wretched man has a vice, it shows\r
+itself in the lines of his mouth, the droop of his eyelids, the\r
+moulding of his hands even.  Somebody--I won't mention his name, but\r
+you know him--came to me last year to have his portrait done.  I had\r
+never seen him before, and had never heard anything about him at the\r
+time, though I have heard a good deal since.  He offered an extravagant\r
+price.  I refused him.  There was something in the shape of his fingers\r
+that I hated.  I know now that I was quite right in what I fancied\r
+about him.  His life is dreadful.  But you, Dorian, with your pure,\r
+bright, innocent face, and your marvellous untroubled youth--I can't\r
+believe anything against you.  And yet I see you very seldom, and you\r
+never come down to the studio now, and when I am away from you, and I\r
+hear all these hideous things that people are whispering about you, I\r
+don't know what to say.  Why is it, Dorian, that a man like the Duke of\r
+Berwick leaves the room of a club when you enter it?  Why is it that so\r
+many gentlemen in London will neither go to your house or invite you to\r
+theirs?  You used to be a friend of Lord Staveley.  I met him at dinner\r
+last week.  Your name happened to come up in conversation, in\r
+connection with the miniatures you have lent to the exhibition at the\r
+Dudley.  Staveley curled his lip and said that you might have the most\r
+artistic tastes, but that you were a man whom no pure-minded girl\r
+should be allowed to know, and whom no chaste woman should sit in the\r
+same room with.  I reminded him that I was a friend of yours, and asked\r
+him what he meant.  He told me.  He told me right out before everybody.\r
+It was horrible!  Why is your friendship so fatal to young men?  There\r
+was that wretched boy in the Guards who committed suicide.  You were\r
+his great friend.  There was Sir Henry Ashton, who had to leave England\r
+with a tarnished name.  You and he were inseparable.  What about Adrian\r
+Singleton and his dreadful end?  What about Lord Kent's only son and\r
+his career?  I met his father yesterday in St. James's Street.  He\r
+seemed broken with shame and sorrow.  What about the young Duke of\r
+Perth?  What sort of life has he got now?  What gentleman would\r
+associate with him?"\r
+\r
+"Stop, Basil.  You are talking about things of which you know nothing,"\r
+said Dorian Gray, biting his lip, and with a note of infinite contempt\r
+in his voice.  "You ask me why Berwick leaves a room when I enter it.\r
+It is because I know everything about his life, not because he knows\r
+anything about mine.  With such blood as he has in his veins, how could\r
+his record be clean?  You ask me about Henry Ashton and young Perth.\r
+Did I teach the one his vices, and the other his debauchery?  If Kent's\r
+silly son takes his wife from the streets, what is that to me?  If\r
+Adrian Singleton writes his friend's name across a bill, am I his\r
+keeper?  I know how people chatter in England.  The middle classes air\r
+their moral prejudices over their gross dinner-tables, and whisper\r
+about what they call the profligacies of their betters in order to try\r
+and pretend that they are in smart society and on intimate terms with\r
+the people they slander.  In this country, it is enough for a man to\r
+have distinction and brains for every common tongue to wag against him.\r
+And what sort of lives do these people, who pose as being moral, lead\r
+themselves?  My dear fellow, you forget that we are in the native land\r
+of the hypocrite."\r
+\r
+"Dorian," cried Hallward, "that is not the question.  England is bad\r
+enough I know, and English society is all wrong.  That is the reason\r
+why I want you to be fine.  You have not been fine.  One has a right to\r
+judge of a man by the effect he has over his friends.  Yours seem to\r
+lose all sense of honour, of goodness, of purity.  You have filled them\r
+with a madness for pleasure.  They have gone down into the depths.  You\r
+led them there.  Yes:  you led them there, and yet you can smile, as\r
+you are smiling now.  And there is worse behind.  I know you and Harry\r
+are inseparable.  Surely for that reason, if for none other, you should\r
+not have made his sister's name a by-word."\r
+\r
+"Take care, Basil.  You go too far."\r
+\r
+"I must speak, and you must listen.  You shall listen.  When you met\r
+Lady Gwendolen, not a breath of scandal had ever touched her.  Is there\r
+a single decent woman in London now who would drive with her in the\r
+park?  Why, even her children are not allowed to live with her.  Then\r
+there are other stories--stories that you have been seen creeping at\r
+dawn out of dreadful houses and slinking in disguise into the foulest\r
+dens in London.  Are they true?  Can they be true?  When I first heard\r
+them, I laughed.  I hear them now, and they make me shudder.  What\r
+about your country-house and the life that is led there?  Dorian, you\r
+don't know what is said about you.  I won't tell you that I don't want\r
+to preach to you.  I remember Harry saying once that every man who\r
+turned himself into an amateur curate for the moment always began by\r
+saying that, and then proceeded to break his word.  I do want to preach\r
+to you.  I want you to lead such a life as will make the world respect\r
+you.  I want you to have a clean name and a fair record.  I want you to\r
+get rid of the dreadful people you associate with.  Don't shrug your\r
+shoulders like that.  Don't be so indifferent.  You have a wonderful\r
+influence.  Let it be for good, not for evil.  They say that you\r
+corrupt every one with whom you become intimate, and that it is quite\r
+sufficient for you to enter a house for shame of some kind to follow\r
+after.  I don't know whether it is so or not.  How should I know?  But\r
+it is said of you.  I am told things that it seems impossible to doubt.\r
+Lord Gloucester was one of my greatest friends at Oxford.  He showed me\r
+a letter that his wife had written to him when she was dying alone in\r
+her villa at Mentone.  Your name was implicated in the most terrible\r
+confession I ever read.  I told him that it was absurd--that I knew you\r
+thoroughly and that you were incapable of anything of the kind.  Know\r
+you?  I wonder do I know you?  Before I could answer that, I should\r
+have to see your soul."\r
+\r
+"To see my soul!" muttered Dorian Gray, starting up from the sofa and\r
+turning almost white from fear.\r
+\r
+"Yes," answered Hallward gravely, and with deep-toned sorrow in his\r
+voice, "to see your soul.  But only God can do that."\r
+\r
+A bitter laugh of mockery broke from the lips of the younger man.  "You\r
+shall see it yourself, to-night!" he cried, seizing a lamp from the\r
+table.  "Come:  it is your own handiwork.  Why shouldn't you look at\r
+it?  You can tell the world all about it afterwards, if you choose.\r
+Nobody would believe you.  If they did believe you, they would like me\r
+all the better for it.  I know the age better than you do, though you\r
+will prate about it so tediously.  Come, I tell you.  You have\r
+chattered enough about corruption.  Now you shall look on it face to\r
+face."\r
+\r
+There was the madness of pride in every word he uttered.  He stamped\r
+his foot upon the ground in his boyish insolent manner.  He felt a\r
+terrible joy at the thought that some one else was to share his secret,\r
+and that the man who had painted the portrait that was the origin of\r
+all his shame was to be burdened for the rest of his life with the\r
+hideous memory of what he had done.\r
+\r
+"Yes," he continued, coming closer to him and looking steadfastly into\r
+his stern eyes, "I shall show you my soul.  You shall see the thing\r
+that you fancy only God can see."\r
+\r
+Hallward started back.  "This is blasphemy, Dorian!" he cried.  "You\r
+must not say things like that.  They are horrible, and they don't mean\r
+anything."\r
+\r
+"You think so?"  He laughed again.\r
+\r
+"I know so.  As for what I said to you to-night, I said it for your\r
+good.  You know I have been always a stanch friend to you."\r
+\r
+"Don't touch me.  Finish what you have to say."\r
+\r
+A twisted flash of pain shot across the painter's face.  He paused for\r
+a moment, and a wild feeling of pity came over him.  After all, what\r
+right had he to pry into the life of Dorian Gray?  If he had done a\r
+tithe of what was rumoured about him, how much he must have suffered!\r
+Then he straightened himself up, and walked over to the fire-place, and\r
+stood there, looking at the burning logs with their frostlike ashes and\r
+their throbbing cores of flame.\r
+\r
+"I am waiting, Basil," said the young man in a hard clear voice.\r
+\r
+He turned round.  "What I have to say is this," he cried.  "You must\r
+give me some answer to these horrible charges that are made against\r
+you.  If you tell me that they are absolutely untrue from beginning to\r
+end, I shall believe you.  Deny them, Dorian, deny them!  Can't you see\r
+what I am going through?  My God! don't tell me that you are bad, and\r
+corrupt, and shameful."\r
+\r
+Dorian Gray smiled.  There was a curl of contempt in his lips.  "Come\r
+upstairs, Basil," he said quietly.  "I keep a diary of my life from day\r
+to day, and it never leaves the room in which it is written.  I shall\r
+show it to you if you come with me."\r
+\r
+"I shall come with you, Dorian, if you wish it.  I see I have missed my\r
+train.  That makes no matter.  I can go to-morrow. But don't ask me to\r
+read anything to-night. All I want is a plain answer to my question."\r
+\r
+"That shall be given to you upstairs.  I could not give it here.  You\r
+will not have to read long."\r
+\r
+\r
+\r
+CHAPTER 13\r
+\r
+He passed out of the room and began the ascent, Basil Hallward\r
+following close behind.  They walked softly, as men do instinctively at\r
+night.  The lamp cast fantastic shadows on the wall and staircase.  A\r
+rising wind made some of the windows rattle.\r
+\r
+When they reached the top landing, Dorian set the lamp down on the\r
+floor, and taking out the key, turned it in the lock.  "You insist on\r
+knowing, Basil?" he asked in a low voice.\r
+\r
+"Yes."\r
+\r
+"I am delighted," he answered, smiling.  Then he added, somewhat\r
+harshly, "You are the one man in the world who is entitled to know\r
+everything about me.  You have had more to do with my life than you\r
+think"; and, taking up the lamp, he opened the door and went in.  A\r
+cold current of air passed them, and the light shot up for a moment in\r
+a flame of murky orange.  He shuddered.  "Shut the door behind you," he\r
+whispered, as he placed the lamp on the table.\r
+\r
+Hallward glanced round him with a puzzled expression.  The room looked\r
+as if it had not been lived in for years.  A faded Flemish tapestry, a\r
+curtained picture, an old Italian _cassone_, and an almost empty\r
+book-case--that was all that it seemed to contain, besides a chair and\r
+a table.  As Dorian Gray was lighting a half-burned candle that was\r
+standing on the mantelshelf, he saw that the whole place was covered\r
+with dust and that the carpet was in holes.  A mouse ran scuffling\r
+behind the wainscoting.  There was a damp odour of mildew.\r
+\r
+"So you think that it is only God who sees the soul, Basil?  Draw that\r
+curtain back, and you will see mine."\r
+\r
+The voice that spoke was cold and cruel.  "You are mad, Dorian, or\r
+playing a part," muttered Hallward, frowning.\r
+\r
+"You won't? Then I must do it myself," said the young man, and he tore\r
+the curtain from its rod and flung it on the ground.\r
+\r
+An exclamation of horror broke from the painter's lips as he saw in the\r
+dim light the hideous face on the canvas grinning at him.  There was\r
+something in its expression that filled him with disgust and loathing.\r
+Good heavens! it was Dorian Gray's own face that he was looking at!\r
+The horror, whatever it was, had not yet entirely spoiled that\r
+marvellous beauty.  There was still some gold in the thinning hair and\r
+some scarlet on the sensual mouth.  The sodden eyes had kept something\r
+of the loveliness of their blue, the noble curves had not yet\r
+completely passed away from chiselled nostrils and from plastic throat.\r
+Yes, it was Dorian himself.  But who had done it?  He seemed to\r
+recognize his own brushwork, and the frame was his own design.  The\r
+idea was monstrous, yet he felt afraid.  He seized the lighted candle,\r
+and held it to the picture.  In the left-hand corner was his own name,\r
+traced in long letters of bright vermilion.\r
+\r
+It was some foul parody, some infamous ignoble satire.  He had never\r
+done that.  Still, it was his own picture.  He knew it, and he felt as\r
+if his blood had changed in a moment from fire to sluggish ice.  His\r
+own picture!  What did it mean?  Why had it altered?  He turned and\r
+looked at Dorian Gray with the eyes of a sick man.  His mouth twitched,\r
+and his parched tongue seemed unable to articulate.  He passed his hand\r
+across his forehead.  It was dank with clammy sweat.\r
+\r
+The young man was leaning against the mantelshelf, watching him with\r
+that strange expression that one sees on the faces of those who are\r
+absorbed in a play when some great artist is acting.  There was neither\r
+real sorrow in it nor real joy.  There was simply the passion of the\r
+spectator, with perhaps a flicker of triumph in his eyes.  He had taken\r
+the flower out of his coat, and was smelling it, or pretending to do so.\r
+\r
+"What does this mean?" cried Hallward, at last.  His own voice sounded\r
+shrill and curious in his ears.\r
+\r
+"Years ago, when I was a boy," said Dorian Gray, crushing the flower in\r
+his hand, "you met me, flattered me, and taught me to be vain of my\r
+good looks.  One day you introduced me to a friend of yours, who\r
+explained to me the wonder of youth, and you finished a portrait of me\r
+that revealed to me the wonder of beauty.  In a mad moment that, even\r
+now, I don't know whether I regret or not, I made a wish, perhaps you\r
+would call it a prayer...."\r
+\r
+"I remember it!  Oh, how well I remember it!  No! the thing is\r
+impossible.  The room is damp.  Mildew has got into the canvas.  The\r
+paints I used had some wretched mineral poison in them.  I tell you the\r
+thing is impossible."\r
+\r
+"Ah, what is impossible?" murmured the young man, going over to the\r
+window and leaning his forehead against the cold, mist-stained glass.\r
+\r
+"You told me you had destroyed it."\r
+\r
+"I was wrong.  It has destroyed me."\r
+\r
+"I don't believe it is my picture."\r
+\r
+"Can't you see your ideal in it?" said Dorian bitterly.\r
+\r
+"My ideal, as you call it..."\r
+\r
+"As you called it."\r
+\r
+"There was nothing evil in it, nothing shameful.  You were to me such\r
+an ideal as I shall never meet again.  This is the face of a satyr."\r
+\r
+"It is the face of my soul."\r
+\r
+"Christ! what a thing I must have worshipped!  It has the eyes of a\r
+devil."\r
+\r
+"Each of us has heaven and hell in him, Basil," cried Dorian with a\r
+wild gesture of despair.\r
+\r
+Hallward turned again to the portrait and gazed at it.  "My God!  If it\r
+is true," he exclaimed, "and this is what you have done with your life,\r
+why, you must be worse even than those who talk against you fancy you\r
+to be!" He held the light up again to the canvas and examined it.  The\r
+surface seemed to be quite undisturbed and as he had left it.  It was\r
+from within, apparently, that the foulness and horror had come.\r
+Through some strange quickening of inner life the leprosies of sin were\r
+slowly eating the thing away.  The rotting of a corpse in a watery\r
+grave was not so fearful.\r
+\r
+His hand shook, and the candle fell from its socket on the floor and\r
+lay there sputtering.  He placed his foot on it and put it out.  Then\r
+he flung himself into the rickety chair that was standing by the table\r
+and buried his face in his hands.\r
+\r
+"Good God, Dorian, what a lesson!  What an awful lesson!" There was no\r
+answer, but he could hear the young man sobbing at the window.  "Pray,\r
+Dorian, pray," he murmured.  "What is it that one was taught to say in\r
+one's boyhood?  'Lead us not into temptation.  Forgive us our sins.\r
+Wash away our iniquities.'  Let us say that together.  The prayer of\r
+your pride has been answered.  The prayer of your repentance will be\r
+answered also.  I worshipped you too much.  I am punished for it.  You\r
+worshipped yourself too much.  We are both punished."\r
+\r
+Dorian Gray turned slowly around and looked at him with tear-dimmed\r
+eyes.  "It is too late, Basil," he faltered.\r
+\r
+"It is never too late, Dorian.  Let us kneel down and try if we cannot\r
+remember a prayer.  Isn't there a verse somewhere, 'Though your sins be\r
+as scarlet, yet I will make them as white as snow'?"\r
+\r
+"Those words mean nothing to me now."\r
+\r
+"Hush!  Don't say that.  You have done enough evil in your life.  My\r
+God!  Don't you see that accursed thing leering at us?"\r
+\r
+Dorian Gray glanced at the picture, and suddenly an uncontrollable\r
+feeling of hatred for Basil Hallward came over him, as though it had\r
+been suggested to him by the image on the canvas, whispered into his\r
+ear by those grinning lips.  The mad passions of a hunted animal\r
+stirred within him, and he loathed the man who was seated at the table,\r
+more than in his whole life he had ever loathed anything.  He glanced\r
+wildly around.  Something glimmered on the top of the painted chest\r
+that faced him.  His eye fell on it.  He knew what it was.  It was a\r
+knife that he had brought up, some days before, to cut a piece of cord,\r
+and had forgotten to take away with him.  He moved slowly towards it,\r
+passing Hallward as he did so.  As soon as he got behind him, he seized\r
+it and turned round.  Hallward stirred in his chair as if he was going\r
+to rise.  He rushed at him and dug the knife into the great vein that\r
+is behind the ear, crushing the man's head down on the table and\r
+stabbing again and again.\r
+\r
+There was a stifled groan and the horrible sound of some one choking\r
+with blood.  Three times the outstretched arms shot up convulsively,\r
+waving grotesque, stiff-fingered hands in the air.  He stabbed him\r
+twice more, but the man did not move.  Something began to trickle on\r
+the floor.  He waited for a moment, still pressing the head down.  Then\r
+he threw the knife on the table, and listened.\r
+\r
+He could hear nothing, but the drip, drip on the threadbare carpet.  He\r
+opened the door and went out on the landing.  The house was absolutely\r
+quiet.  No one was about.  For a few seconds he stood bending over the\r
+balustrade and peering down into the black seething well of darkness.\r
+Then he took out the key and returned to the room, locking himself in\r
+as he did so.\r
+\r
+The thing was still seated in the chair, straining over the table with\r
+bowed head, and humped back, and long fantastic arms.  Had it not been\r
+for the red jagged tear in the neck and the clotted black pool that was\r
+slowly widening on the table, one would have said that the man was\r
+simply asleep.\r
+\r
+How quickly it had all been done!  He felt strangely calm, and walking\r
+over to the window, opened it and stepped out on the balcony.  The wind\r
+had blown the fog away, and the sky was like a monstrous peacock's\r
+tail, starred with myriads of golden eyes.  He looked down and saw the\r
+policeman going his rounds and flashing the long beam of his lantern on\r
+the doors of the silent houses.  The crimson spot of a prowling hansom\r
+gleamed at the corner and then vanished.  A woman in a fluttering shawl\r
+was creeping slowly by the railings, staggering as she went.  Now and\r
+then she stopped and peered back.  Once, she began to sing in a hoarse\r
+voice.  The policeman strolled over and said something to her.  She\r
+stumbled away, laughing.  A bitter blast swept across the square.  The\r
+gas-lamps flickered and became blue, and the leafless trees shook their\r
+black iron branches to and fro.  He shivered and went back, closing the\r
+window behind him.\r
+\r
+Having reached the door, he turned the key and opened it.  He did not\r
+even glance at the murdered man.  He felt that the secret of the whole\r
+thing was not to realize the situation.  The friend who had painted the\r
+fatal portrait to which all his misery had been due had gone out of his\r
+life.  That was enough.\r
+\r
+Then he remembered the lamp.  It was a rather curious one of Moorish\r
+workmanship, made of dull silver inlaid with arabesques of burnished\r
+steel, and studded with coarse turquoises.  Perhaps it might be missed\r
+by his servant, and questions would be asked.  He hesitated for a\r
+moment, then he turned back and took it from the table.  He could not\r
+help seeing the dead thing.  How still it was!  How horribly white the\r
+long hands looked!  It was like a dreadful wax image.\r
+\r
+Having locked the door behind him, he crept quietly downstairs.  The\r
+woodwork creaked and seemed to cry out as if in pain.  He stopped\r
+several times and waited.  No:  everything was still.  It was merely\r
+the sound of his own footsteps.\r
+\r
+When he reached the library, he saw the bag and coat in the corner.\r
+They must be hidden away somewhere.  He unlocked a secret press that\r
+was in the wainscoting, a press in which he kept his own curious\r
+disguises, and put them into it.  He could easily burn them afterwards.\r
+Then he pulled out his watch.  It was twenty minutes to two.\r
+\r
+He sat down and began to think.  Every year--every month, almost--men\r
+were strangled in England for what he had done.  There had been a\r
+madness of murder in the air.  Some red star had come too close to the\r
+earth.... And yet, what evidence was there against him?  Basil Hallward\r
+had left the house at eleven.  No one had seen him come in again.  Most\r
+of the servants were at Selby Royal.  His valet had gone to bed....\r
+Paris!  Yes.  It was to Paris that Basil had gone, and by the midnight\r
+train, as he had intended.  With his curious reserved habits, it would\r
+be months before any suspicions would be roused.  Months!  Everything\r
+could be destroyed long before then.\r
+\r
+A sudden thought struck him.  He put on his fur coat and hat and went\r
+out into the hall.  There he paused, hearing the slow heavy tread of\r
+the policeman on the pavement outside and seeing the flash of the\r
+bull's-eye reflected in the window.  He waited and held his breath.\r
+\r
+After a few moments he drew back the latch and slipped out, shutting\r
+the door very gently behind him.  Then he began ringing the bell.  In\r
+about five minutes his valet appeared, half-dressed and looking very\r
+drowsy.\r
+\r
+"I am sorry to have had to wake you up, Francis," he said, stepping in;\r
+"but I had forgotten my latch-key. What time is it?"\r
+\r
+"Ten minutes past two, sir," answered the man, looking at the clock and\r
+blinking.\r
+\r
+"Ten minutes past two?  How horribly late!  You must wake me at nine\r
+to-morrow. I have some work to do."\r
+\r
+"All right, sir."\r
+\r
+"Did any one call this evening?"\r
+\r
+"Mr. Hallward, sir.  He stayed here till eleven, and then he went away\r
+to catch his train."\r
+\r
+"Oh!  I am sorry I didn't see him.  Did he leave any message?"\r
+\r
+"No, sir, except that he would write to you from Paris, if he did not\r
+find you at the club."\r
+\r
+"That will do, Francis.  Don't forget to call me at nine to-morrow."\r
+\r
+"No, sir."\r
+\r
+The man shambled down the passage in his slippers.\r
+\r
+Dorian Gray threw his hat and coat upon the table and passed into the\r
+library.  For a quarter of an hour he walked up and down the room,\r
+biting his lip and thinking.  Then he took down the Blue Book from one\r
+of the shelves and began to turn over the leaves.  "Alan Campbell, 152,\r
+Hertford Street, Mayfair."  Yes; that was the man he wanted.\r
+\r
+\r
+\r
+CHAPTER 14\r
+\r
+At nine o'clock the next morning his servant came in with a cup of\r
+chocolate on a tray and opened the shutters.  Dorian was sleeping quite\r
+peacefully, lying on his right side, with one hand underneath his\r
+cheek.  He looked like a boy who had been tired out with play, or study.\r
+\r
+The man had to touch him twice on the shoulder before he woke, and as\r
+he opened his eyes a faint smile passed across his lips, as though he\r
+had been lost in some delightful dream.  Yet he had not dreamed at all.\r
+His night had been untroubled by any images of pleasure or of pain.\r
+But youth smiles without any reason.  It is one of its chiefest charms.\r
+\r
+He turned round, and leaning upon his elbow, began to sip his\r
+chocolate.  The mellow November sun came streaming into the room.  The\r
+sky was bright, and there was a genial warmth in the air.  It was\r
+almost like a morning in May.\r
+\r
+Gradually the events of the preceding night crept with silent,\r
+blood-stained feet into his brain and reconstructed themselves there\r
+with terrible distinctness.  He winced at the memory of all that he had\r
+suffered, and for a moment the same curious feeling of loathing for\r
+Basil Hallward that had made him kill him as he sat in the chair came\r
+back to him, and he grew cold with passion.  The dead man was still\r
+sitting there, too, and in the sunlight now.  How horrible that was!\r
+Such hideous things were for the darkness, not for the day.\r
+\r
+He felt that if he brooded on what he had gone through he would sicken\r
+or grow mad.  There were sins whose fascination was more in the memory\r
+than in the doing of them, strange triumphs that gratified the pride\r
+more than the passions, and gave to the intellect a quickened sense of\r
+joy, greater than any joy they brought, or could ever bring, to the\r
+senses.  But this was not one of them.  It was a thing to be driven out\r
+of the mind, to be drugged with poppies, to be strangled lest it might\r
+strangle one itself.\r
+\r
+When the half-hour struck, he passed his hand across his forehead, and\r
+then got up hastily and dressed himself with even more than his usual\r
+care, giving a good deal of attention to the choice of his necktie and\r
+scarf-pin and changing his rings more than once.  He spent a long time\r
+also over breakfast, tasting the various dishes, talking to his valet\r
+about some new liveries that he was thinking of getting made for the\r
+servants at Selby, and going through his correspondence.  At some of\r
+the letters, he smiled.  Three of them bored him.  One he read several\r
+times over and then tore up with a slight look of annoyance in his\r
+face.  "That awful thing, a woman's memory!" as Lord Henry had once\r
+said.\r
+\r
+After he had drunk his cup of black coffee, he wiped his lips slowly\r
+with a napkin, motioned to his servant to wait, and going over to the\r
+table, sat down and wrote two letters.  One he put in his pocket, the\r
+other he handed to the valet.\r
+\r
+"Take this round to 152, Hertford Street, Francis, and if Mr. Campbell\r
+is out of town, get his address."\r
+\r
+As soon as he was alone, he lit a cigarette and began sketching upon a\r
+piece of paper, drawing first flowers and bits of architecture, and\r
+then human faces.  Suddenly he remarked that every face that he drew\r
+seemed to have a fantastic likeness to Basil Hallward.  He frowned, and\r
+getting up, went over to the book-case and took out a volume at hazard.\r
+He was determined that he would not think about what had happened until\r
+it became absolutely necessary that he should do so.\r
+\r
+When he had stretched himself on the sofa, he looked at the title-page\r
+of the book.  It was Gautier's Emaux et Camees, Charpentier's\r
+Japanese-paper edition, with the Jacquemart etching.  The binding was\r
+of citron-green leather, with a design of gilt trellis-work and dotted\r
+pomegranates.  It had been given to him by Adrian Singleton.  As he\r
+turned over the pages, his eye fell on the poem about the hand of\r
+Lacenaire, the cold yellow hand "_du supplice encore mal lavee_," with\r
+its downy red hairs and its "_doigts de faune_."  He glanced at his own\r
+white taper fingers, shuddering slightly in spite of himself, and\r
+passed on, till he came to those lovely stanzas upon Venice:\r
+\r
+     Sur une gamme chromatique,\r
+       Le sein de perles ruisselant,\r
+     La Venus de l'Adriatique\r
+       Sort de l'eau son corps rose et blanc.\r
+\r
+     Les domes, sur l'azur des ondes\r
+       Suivant la phrase au pur contour,\r
+     S'enflent comme des gorges rondes\r
+       Que souleve un soupir d'amour.\r
+\r
+     L'esquif aborde et me depose,\r
+       Jetant son amarre au pilier,\r
+     Devant une facade rose,\r
+       Sur le marbre d'un escalier.\r
+\r
+\r
+How exquisite they were!  As one read them, one seemed to be floating\r
+down the green water-ways of the pink and pearl city, seated in a black\r
+gondola with silver prow and trailing curtains.  The mere lines looked\r
+to him like those straight lines of turquoise-blue that follow one as\r
+one pushes out to the Lido.  The sudden flashes of colour reminded him\r
+of the gleam of the opal-and-iris-throated birds that flutter round the\r
+tall honeycombed Campanile, or stalk, with such stately grace, through\r
+the dim, dust-stained arcades.  Leaning back with half-closed eyes, he\r
+kept saying over and over to himself:\r
+\r
+     "Devant une facade rose,\r
+        Sur le marbre d'un escalier."\r
+\r
+The whole of Venice was in those two lines.  He remembered the autumn\r
+that he had passed there, and a wonderful love that had stirred him to\r
+mad delightful follies.  There was romance in every place.  But Venice,\r
+like Oxford, had kept the background for romance, and, to the true\r
+romantic, background was everything, or almost everything.  Basil had\r
+been with him part of the time, and had gone wild over Tintoret.  Poor\r
+Basil!  What a horrible way for a man to die!\r
+\r
+He sighed, and took up the volume again, and tried to forget.  He read\r
+of the swallows that fly in and out of the little _cafe_ at Smyrna where\r
+the Hadjis sit counting their amber beads and the turbaned merchants\r
+smoke their long tasselled pipes and talk gravely to each other; he\r
+read of the Obelisk in the Place de la Concorde that weeps tears of\r
+granite in its lonely sunless exile and longs to be back by the hot,\r
+lotus-covered Nile, where there are Sphinxes, and rose-red ibises, and\r
+white vultures with gilded claws, and crocodiles with small beryl eyes\r
+that crawl over the green steaming mud; he began to brood over those\r
+verses which, drawing music from kiss-stained marble, tell of that\r
+curious statue that Gautier compares to a contralto voice, the "_monstre\r
+charmant_" that couches in the porphyry-room of the Louvre.  But after a\r
+time the book fell from his hand.  He grew nervous, and a horrible fit\r
+of terror came over him.  What if Alan Campbell should be out of\r
+England?  Days would elapse before he could come back.  Perhaps he\r
+might refuse to come.  What could he do then?  Every moment was of\r
+vital importance.\r
+\r
+They had been great friends once, five years before--almost\r
+inseparable, indeed.  Then the intimacy had come suddenly to an end.\r
+When they met in society now, it was only Dorian Gray who smiled:  Alan\r
+Campbell never did.\r
+\r
+He was an extremely clever young man, though he had no real\r
+appreciation of the visible arts, and whatever little sense of the\r
+beauty of poetry he possessed he had gained entirely from Dorian.  His\r
+dominant intellectual passion was for science.  At Cambridge he had\r
+spent a great deal of his time working in the laboratory, and had taken\r
+a good class in the Natural Science Tripos of his year.  Indeed, he was\r
+still devoted to the study of chemistry, and had a laboratory of his\r
+own in which he used to shut himself up all day long, greatly to the\r
+annoyance of his mother, who had set her heart on his standing for\r
+Parliament and had a vague idea that a chemist was a person who made up\r
+prescriptions.  He was an excellent musician, however, as well, and\r
+played both the violin and the piano better than most amateurs.  In\r
+fact, it was music that had first brought him and Dorian Gray\r
+together--music and that indefinable attraction that Dorian seemed to\r
+be able to exercise whenever he wished--and, indeed, exercised often\r
+without being conscious of it.  They had met at Lady Berkshire's the\r
+night that Rubinstein played there, and after that used to be always\r
+seen together at the opera and wherever good music was going on.  For\r
+eighteen months their intimacy lasted.  Campbell was always either at\r
+Selby Royal or in Grosvenor Square.  To him, as to many others, Dorian\r
+Gray was the type of everything that is wonderful and fascinating in\r
+life.  Whether or not a quarrel had taken place between them no one\r
+ever knew.  But suddenly people remarked that they scarcely spoke when\r
+they met and that Campbell seemed always to go away early from any\r
+party at which Dorian Gray was present.  He had changed, too--was\r
+strangely melancholy at times, appeared almost to dislike hearing\r
+music, and would never himself play, giving as his excuse, when he was\r
+called upon, that he was so absorbed in science that he had no time\r
+left in which to practise.  And this was certainly true.  Every day he\r
+seemed to become more interested in biology, and his name appeared once\r
+or twice in some of the scientific reviews in connection with certain\r
+curious experiments.\r
+\r
+This was the man Dorian Gray was waiting for.  Every second he kept\r
+glancing at the clock.  As the minutes went by he became horribly\r
+agitated.  At last he got up and began to pace up and down the room,\r
+looking like a beautiful caged thing.  He took long stealthy strides.\r
+His hands were curiously cold.\r
+\r
+The suspense became unbearable.  Time seemed to him to be crawling with\r
+feet of lead, while he by monstrous winds was being swept towards the\r
+jagged edge of some black cleft of precipice.  He knew what was waiting\r
+for him there; saw it, indeed, and, shuddering, crushed with dank hands\r
+his burning lids as though he would have robbed the very brain of sight\r
+and driven the eyeballs back into their cave.  It was useless.  The\r
+brain had its own food on which it battened, and the imagination, made\r
+grotesque by terror, twisted and distorted as a living thing by pain,\r
+danced like some foul puppet on a stand and grinned through moving\r
+masks.  Then, suddenly, time stopped for him.  Yes:  that blind,\r
+slow-breathing thing crawled no more, and horrible thoughts, time being\r
+dead, raced nimbly on in front, and dragged a hideous future from its\r
+grave, and showed it to him.  He stared at it.  Its very horror made\r
+him stone.\r
+\r
+At last the door opened and his servant entered.  He turned glazed eyes\r
+upon him.\r
+\r
+"Mr. Campbell, sir," said the man.\r
+\r
+A sigh of relief broke from his parched lips, and the colour came back\r
+to his cheeks.\r
+\r
+"Ask him to come in at once, Francis."  He felt that he was himself\r
+again.  His mood of cowardice had passed away.\r
+\r
+The man bowed and retired.  In a few moments, Alan Campbell walked in,\r
+looking very stern and rather pale, his pallor being intensified by his\r
+coal-black hair and dark eyebrows.\r
+\r
+"Alan!  This is kind of you.  I thank you for coming."\r
+\r
+"I had intended never to enter your house again, Gray.  But you said it\r
+was a matter of life and death."  His voice was hard and cold.  He\r
+spoke with slow deliberation.  There was a look of contempt in the\r
+steady searching gaze that he turned on Dorian.  He kept his hands in\r
+the pockets of his Astrakhan coat, and seemed not to have noticed the\r
+gesture with which he had been greeted.\r
+\r
+"Yes:  it is a matter of life and death, Alan, and to more than one\r
+person.  Sit down."\r
+\r
+Campbell took a chair by the table, and Dorian sat opposite to him.\r
+The two men's eyes met.  In Dorian's there was infinite pity.  He knew\r
+that what he was going to do was dreadful.\r
+\r
+After a strained moment of silence, he leaned across and said, very\r
+quietly, but watching the effect of each word upon the face of him he\r
+had sent for, "Alan, in a locked room at the top of this house, a room\r
+to which nobody but myself has access, a dead man is seated at a table.\r
+He has been dead ten hours now.  Don't stir, and don't look at me like\r
+that.  Who the man is, why he died, how he died, are matters that do\r
+not concern you.  What you have to do is this--"\r
+\r
+"Stop, Gray.  I don't want to know anything further.  Whether what you\r
+have told me is true or not true doesn't concern me.  I entirely\r
+decline to be mixed up in your life.  Keep your horrible secrets to\r
+yourself.  They don't interest me any more."\r
+\r
+"Alan, they will have to interest you.  This one will have to interest\r
+you.  I am awfully sorry for you, Alan.  But I can't help myself.  You\r
+are the one man who is able to save me.  I am forced to bring you into\r
+the matter.  I have no option.  Alan, you are scientific.  You know\r
+about chemistry and things of that kind.  You have made experiments.\r
+What you have got to do is to destroy the thing that is upstairs--to\r
+destroy it so that not a vestige of it will be left.  Nobody saw this\r
+person come into the house.  Indeed, at the present moment he is\r
+supposed to be in Paris.  He will not be missed for months.  When he is\r
+missed, there must be no trace of him found here.  You, Alan, you must\r
+change him, and everything that belongs to him, into a handful of ashes\r
+that I may scatter in the air."\r
+\r
+"You are mad, Dorian."\r
+\r
+"Ah!  I was waiting for you to call me Dorian."\r
+\r
+"You are mad, I tell you--mad to imagine that I would raise a finger to\r
+help you, mad to make this monstrous confession.  I will have nothing\r
+to do with this matter, whatever it is.  Do you think I am going to\r
+peril my reputation for you?  What is it to me what devil's work you\r
+are up to?"\r
+\r
+"It was suicide, Alan."\r
+\r
+"I am glad of that.  But who drove him to it?  You, I should fancy."\r
+\r
+"Do you still refuse to do this for me?"\r
+\r
+"Of course I refuse.  I will have absolutely nothing to do with it.  I\r
+don't care what shame comes on you.  You deserve it all.  I should not\r
+be sorry to see you disgraced, publicly disgraced.  How dare you ask\r
+me, of all men in the world, to mix myself up in this horror?  I should\r
+have thought you knew more about people's characters.  Your friend Lord\r
+Henry Wotton can't have taught you much about psychology, whatever else\r
+he has taught you.  Nothing will induce me to stir a step to help you.\r
+You have come to the wrong man.  Go to some of your friends.  Don't\r
+come to me."\r
+\r
+"Alan, it was murder.  I killed him.  You don't know what he had made\r
+me suffer.  Whatever my life is, he had more to do with the making or\r
+the marring of it than poor Harry has had.  He may not have intended\r
+it, the result was the same."\r
+\r
+"Murder!  Good God, Dorian, is that what you have come to?  I shall not\r
+inform upon you.  It is not my business.  Besides, without my stirring\r
+in the matter, you are certain to be arrested.  Nobody ever commits a\r
+crime without doing something stupid.  But I will have nothing to do\r
+with it."\r
+\r
+"You must have something to do with it.  Wait, wait a moment; listen to\r
+me.  Only listen, Alan.  All I ask of you is to perform a certain\r
+scientific experiment.  You go to hospitals and dead-houses, and the\r
+horrors that you do there don't affect you.  If in some hideous\r
+dissecting-room or fetid laboratory you found this man lying on a\r
+leaden table with red gutters scooped out in it for the blood to flow\r
+through, you would simply look upon him as an admirable subject.  You\r
+would not turn a hair.  You would not believe that you were doing\r
+anything wrong.  On the contrary, you would probably feel that you were\r
+benefiting the human race, or increasing the sum of knowledge in the\r
+world, or gratifying intellectual curiosity, or something of that kind.\r
+What I want you to do is merely what you have often done before.\r
+Indeed, to destroy a body must be far less horrible than what you are\r
+accustomed to work at.  And, remember, it is the only piece of evidence\r
+against me.  If it is discovered, I am lost; and it is sure to be\r
+discovered unless you help me."\r
+\r
+"I have no desire to help you.  You forget that.  I am simply\r
+indifferent to the whole thing.  It has nothing to do with me."\r
+\r
+"Alan, I entreat you.  Think of the position I am in.  Just before you\r
+came I almost fainted with terror.  You may know terror yourself some\r
+day.  No! don't think of that.  Look at the matter purely from the\r
+scientific point of view.  You don't inquire where the dead things on\r
+which you experiment come from.  Don't inquire now.  I have told you\r
+too much as it is.  But I beg of you to do this.  We were friends once,\r
+Alan."\r
+\r
+"Don't speak about those days, Dorian--they are dead."\r
+\r
+"The dead linger sometimes.  The man upstairs will not go away.  He is\r
+sitting at the table with bowed head and outstretched arms.  Alan!\r
+Alan!  If you don't come to my assistance, I am ruined.  Why, they will\r
+hang me, Alan!  Don't you understand?  They will hang me for what I\r
+have done."\r
+\r
+"There is no good in prolonging this scene.  I absolutely refuse to do\r
+anything in the matter.  It is insane of you to ask me."\r
+\r
+"You refuse?"\r
+\r
+"Yes."\r
+\r
+"I entreat you, Alan."\r
+\r
+"It is useless."\r
+\r
+The same look of pity came into Dorian Gray's eyes.  Then he stretched\r
+out his hand, took a piece of paper, and wrote something on it.  He\r
+read it over twice, folded it carefully, and pushed it across the\r
+table.  Having done this, he got up and went over to the window.\r
+\r
+Campbell looked at him in surprise, and then took up the paper, and\r
+opened it.  As he read it, his face became ghastly pale and he fell\r
+back in his chair.  A horrible sense of sickness came over him.  He\r
+felt as if his heart was beating itself to death in some empty hollow.\r
+\r
+After two or three minutes of terrible silence, Dorian turned round and\r
+came and stood behind him, putting his hand upon his shoulder.\r
+\r
+"I am so sorry for you, Alan," he murmured, "but you leave me no\r
+alternative.  I have a letter written already.  Here it is.  You see\r
+the address.  If you don't help me, I must send it.  If you don't help\r
+me, I will send it.  You know what the result will be.  But you are\r
+going to help me.  It is impossible for you to refuse now.  I tried to\r
+spare you.  You will do me the justice to admit that.  You were stern,\r
+harsh, offensive.  You treated me as no man has ever dared to treat\r
+me--no living man, at any rate.  I bore it all.  Now it is for me to\r
+dictate terms."\r
+\r
+Campbell buried his face in his hands, and a shudder passed through him.\r
+\r
+"Yes, it is my turn to dictate terms, Alan.  You know what they are.\r
+The thing is quite simple.  Come, don't work yourself into this fever.\r
+The thing has to be done.  Face it, and do it."\r
+\r
+A groan broke from Campbell's lips and he shivered all over.  The\r
+ticking of the clock on the mantelpiece seemed to him to be dividing\r
+time into separate atoms of agony, each of which was too terrible to be\r
+borne.  He felt as if an iron ring was being slowly tightened round his\r
+forehead, as if the disgrace with which he was threatened had already\r
+come upon him.  The hand upon his shoulder weighed like a hand of lead.\r
+It was intolerable.  It seemed to crush him.\r
+\r
+"Come, Alan, you must decide at once."\r
+\r
+"I cannot do it," he said, mechanically, as though words could alter\r
+things.\r
+\r
+"You must.  You have no choice.  Don't delay."\r
+\r
+He hesitated a moment.  "Is there a fire in the room upstairs?"\r
+\r
+"Yes, there is a gas-fire with asbestos."\r
+\r
+"I shall have to go home and get some things from the laboratory."\r
+\r
+"No, Alan, you must not leave the house.  Write out on a sheet of\r
+notepaper what you want and my servant will take a cab and bring the\r
+things back to you."\r
+\r
+Campbell scrawled a few lines, blotted them, and addressed an envelope\r
+to his assistant.  Dorian took the note up and read it carefully.  Then\r
+he rang the bell and gave it to his valet, with orders to return as\r
+soon as possible and to bring the things with him.\r
+\r
+As the hall door shut, Campbell started nervously, and having got up\r
+from the chair, went over to the chimney-piece. He was shivering with a\r
+kind of ague.  For nearly twenty minutes, neither of the men spoke.  A\r
+fly buzzed noisily about the room, and the ticking of the clock was\r
+like the beat of a hammer.\r
+\r
+As the chime struck one, Campbell turned round, and looking at Dorian\r
+Gray, saw that his eyes were filled with tears.  There was something in\r
+the purity and refinement of that sad face that seemed to enrage him.\r
+"You are infamous, absolutely infamous!" he muttered.\r
+\r
+"Hush, Alan.  You have saved my life," said Dorian.\r
+\r
+"Your life?  Good heavens! what a life that is!  You have gone from\r
+corruption to corruption, and now you have culminated in crime.  In\r
+doing what I am going to do--what you force me to do--it is not of your\r
+life that I am thinking."\r
+\r
+"Ah, Alan," murmured Dorian with a sigh, "I wish you had a thousandth\r
+part of the pity for me that I have for you." He turned away as he\r
+spoke and stood looking out at the garden.  Campbell made no answer.\r
+\r
+After about ten minutes a knock came to the door, and the servant\r
+entered, carrying a large mahogany chest of chemicals, with a long coil\r
+of steel and platinum wire and two rather curiously shaped iron clamps.\r
+\r
+"Shall I leave the things here, sir?" he asked Campbell.\r
+\r
+"Yes," said Dorian.  "And I am afraid, Francis, that I have another\r
+errand for you.  What is the name of the man at Richmond who supplies\r
+Selby with orchids?"\r
+\r
+"Harden, sir."\r
+\r
+"Yes--Harden.  You must go down to Richmond at once, see Harden\r
+personally, and tell him to send twice as many orchids as I ordered,\r
+and to have as few white ones as possible.  In fact, I don't want any\r
+white ones.  It is a lovely day, Francis, and Richmond is a very pretty\r
+place--otherwise I wouldn't bother you about it."\r
+\r
+"No trouble, sir.  At what time shall I be back?"\r
+\r
+Dorian looked at Campbell.  "How long will your experiment take, Alan?"\r
+he said in a calm indifferent voice.  The presence of a third person in\r
+the room seemed to give him extraordinary courage.\r
+\r
+Campbell frowned and bit his lip.  "It will take about five hours," he\r
+answered.\r
+\r
+"It will be time enough, then, if you are back at half-past seven,\r
+Francis.  Or stay:  just leave my things out for dressing.  You can\r
+have the evening to yourself.  I am not dining at home, so I shall not\r
+want you."\r
+\r
+"Thank you, sir," said the man, leaving the room.\r
+\r
+"Now, Alan, there is not a moment to be lost.  How heavy this chest is!\r
+I'll take it for you.  You bring the other things."  He spoke rapidly\r
+and in an authoritative manner.  Campbell felt dominated by him.  They\r
+left the room together.\r
+\r
+When they reached the top landing, Dorian took out the key and turned\r
+it in the lock.  Then he stopped, and a troubled look came into his\r
+eyes.  He shuddered.  "I don't think I can go in, Alan," he murmured.\r
+\r
+"It is nothing to me.  I don't require you," said Campbell coldly.\r
+\r
+Dorian half opened the door.  As he did so, he saw the face of his\r
+portrait leering in the sunlight.  On the floor in front of it the torn\r
+curtain was lying.  He remembered that the night before he had\r
+forgotten, for the first time in his life, to hide the fatal canvas,\r
+and was about to rush forward, when he drew back with a shudder.\r
+\r
+What was that loathsome red dew that gleamed, wet and glistening, on\r
+one of the hands, as though the canvas had sweated blood?  How horrible\r
+it was!--more horrible, it seemed to him for the moment, than the\r
+silent thing that he knew was stretched across the table, the thing\r
+whose grotesque misshapen shadow on the spotted carpet showed him that\r
+it had not stirred, but was still there, as he had left it.\r
+\r
+He heaved a deep breath, opened the door a little wider, and with\r
+half-closed eyes and averted head, walked quickly in, determined that\r
+he would not look even once upon the dead man.  Then, stooping down and\r
+taking up the gold-and-purple hanging, he flung it right over the\r
+picture.\r
+\r
+There he stopped, feeling afraid to turn round, and his eyes fixed\r
+themselves on the intricacies of the pattern before him.  He heard\r
+Campbell bringing in the heavy chest, and the irons, and the other\r
+things that he had required for his dreadful work.  He began to wonder\r
+if he and Basil Hallward had ever met, and, if so, what they had\r
+thought of each other.\r
+\r
+"Leave me now," said a stern voice behind him.\r
+\r
+He turned and hurried out, just conscious that the dead man had been\r
+thrust back into the chair and that Campbell was gazing into a\r
+glistening yellow face.  As he was going downstairs, he heard the key\r
+being turned in the lock.\r
+\r
+It was long after seven when Campbell came back into the library.  He\r
+was pale, but absolutely calm.  "I have done what you asked me to do,"\r
+he muttered. "And now, good-bye. Let us never see each other again."\r
+\r
+"You have saved me from ruin, Alan.  I cannot forget that," said Dorian\r
+simply.\r
+\r
+As soon as Campbell had left, he went upstairs.  There was a horrible\r
+smell of nitric acid in the room.  But the thing that had been sitting\r
+at the table was gone.\r
+\r
+\r
+\r
+CHAPTER 15\r
+\r
+That evening, at eight-thirty, exquisitely dressed and wearing a large\r
+button-hole of Parma violets, Dorian Gray was ushered into Lady\r
+Narborough's drawing-room by bowing servants.  His forehead was\r
+throbbing with maddened nerves, and he felt wildly excited, but his\r
+manner as he bent over his hostess's hand was as easy and graceful as\r
+ever.  Perhaps one never seems so much at one's ease as when one has to\r
+play a part.  Certainly no one looking at Dorian Gray that night could\r
+have believed that he had passed through a tragedy as horrible as any\r
+tragedy of our age.  Those finely shaped fingers could never have\r
+clutched a knife for sin, nor those smiling lips have cried out on God\r
+and goodness.  He himself could not help wondering at the calm of his\r
+demeanour, and for a moment felt keenly the terrible pleasure of a\r
+double life.\r
+\r
+It was a small party, got up rather in a hurry by Lady Narborough, who\r
+was a very clever woman with what Lord Henry used to describe as the\r
+remains of really remarkable ugliness.  She had proved an excellent\r
+wife to one of our most tedious ambassadors, and having buried her\r
+husband properly in a marble mausoleum, which she had herself designed,\r
+and married off her daughters to some rich, rather elderly men, she\r
+devoted herself now to the pleasures of French fiction, French cookery,\r
+and French _esprit_ when she could get it.\r
+\r
+Dorian was one of her especial favourites, and she always told him that\r
+she was extremely glad she had not met him in early life.  "I know, my\r
+dear, I should have fallen madly in love with you," she used to say,\r
+"and thrown my bonnet right over the mills for your sake.  It is most\r
+fortunate that you were not thought of at the time.  As it was, our\r
+bonnets were so unbecoming, and the mills were so occupied in trying to\r
+raise the wind, that I never had even a flirtation with anybody.\r
+However, that was all Narborough's fault.  He was dreadfully\r
+short-sighted, and there is no pleasure in taking in a husband who\r
+never sees anything."\r
+\r
+Her guests this evening were rather tedious.  The fact was, as she\r
+explained to Dorian, behind a very shabby fan, one of her married\r
+daughters had come up quite suddenly to stay with her, and, to make\r
+matters worse, had actually brought her husband with her.  "I think it\r
+is most unkind of her, my dear," she whispered.  "Of course I go and\r
+stay with them every summer after I come from Homburg, but then an old\r
+woman like me must have fresh air sometimes, and besides, I really wake\r
+them up.  You don't know what an existence they lead down there.  It is\r
+pure unadulterated country life.  They get up early, because they have\r
+so much to do, and go to bed early, because they have so little to\r
+think about.  There has not been a scandal in the neighbourhood since\r
+the time of Queen Elizabeth, and consequently they all fall asleep\r
+after dinner.  You shan't sit next either of them.  You shall sit by me\r
+and amuse me."\r
+\r
+Dorian murmured a graceful compliment and looked round the room.  Yes:\r
+it was certainly a tedious party.  Two of the people he had never seen\r
+before, and the others consisted of Ernest Harrowden, one of those\r
+middle-aged mediocrities so common in London clubs who have no enemies,\r
+but are thoroughly disliked by their friends; Lady Ruxton, an\r
+overdressed woman of forty-seven, with a hooked nose, who was always\r
+trying to get herself compromised, but was so peculiarly plain that to\r
+her great disappointment no one would ever believe anything against\r
+her; Mrs. Erlynne, a pushing nobody, with a delightful lisp and\r
+Venetian-red hair; Lady Alice Chapman, his hostess's daughter, a dowdy\r
+dull girl, with one of those characteristic British faces that, once\r
+seen, are never remembered; and her husband, a red-cheeked,\r
+white-whiskered creature who, like so many of his class, was under the\r
+impression that inordinate joviality can atone for an entire lack of\r
+ideas.\r
+\r
+He was rather sorry he had come, till Lady Narborough, looking at the\r
+great ormolu gilt clock that sprawled in gaudy curves on the\r
+mauve-draped mantelshelf, exclaimed:  "How horrid of Henry Wotton to be\r
+so late!  I sent round to him this morning on chance and he promised\r
+faithfully not to disappoint me."\r
+\r
+It was some consolation that Harry was to be there, and when the door\r
+opened and he heard his slow musical voice lending charm to some\r
+insincere apology, he ceased to feel bored.\r
+\r
+But at dinner he could not eat anything.  Plate after plate went away\r
+untasted.  Lady Narborough kept scolding him for what she called "an\r
+insult to poor Adolphe, who invented the _menu_ specially for you," and\r
+now and then Lord Henry looked across at him, wondering at his silence\r
+and abstracted manner.  From time to time the butler filled his glass\r
+with champagne.  He drank eagerly, and his thirst seemed to increase.\r
+\r
+"Dorian," said Lord Henry at last, as the _chaud-froid_ was being handed\r
+round, "what is the matter with you to-night? You are quite out of\r
+sorts."\r
+\r
+"I believe he is in love," cried Lady Narborough, "and that he is\r
+afraid to tell me for fear I should be jealous.  He is quite right.  I\r
+certainly should."\r
+\r
+"Dear Lady Narborough," murmured Dorian, smiling, "I have not been in\r
+love for a whole week--not, in fact, since Madame de Ferrol left town."\r
+\r
+"How you men can fall in love with that woman!" exclaimed the old lady.\r
+"I really cannot understand it."\r
+\r
+"It is simply because she remembers you when you were a little girl,\r
+Lady Narborough," said Lord Henry.  "She is the one link between us and\r
+your short frocks."\r
+\r
+"She does not remember my short frocks at all, Lord Henry.  But I\r
+remember her very well at Vienna thirty years ago, and how _decolletee_\r
+she was then."\r
+\r
+"She is still _decolletee_," he answered, taking an olive in his long\r
+fingers; "and when she is in a very smart gown she looks like an\r
+_edition de luxe_ of a bad French novel.  She is really wonderful, and\r
+full of surprises.  Her capacity for family affection is extraordinary.\r
+When her third husband died, her hair turned quite gold from grief."\r
+\r
+"How can you, Harry!" cried Dorian.\r
+\r
+"It is a most romantic explanation," laughed the hostess.  "But her\r
+third husband, Lord Henry!  You don't mean to say Ferrol is the fourth?"\r
+\r
+"Certainly, Lady Narborough."\r
+\r
+"I don't believe a word of it."\r
+\r
+"Well, ask Mr. Gray.  He is one of her most intimate friends."\r
+\r
+"Is it true, Mr. Gray?"\r
+\r
+"She assures me so, Lady Narborough," said Dorian.  "I asked her\r
+whether, like Marguerite de Navarre, she had their hearts embalmed and\r
+hung at her girdle.  She told me she didn't, because none of them had\r
+had any hearts at all."\r
+\r
+"Four husbands!  Upon my word that is _trop de zele_."\r
+\r
+"_Trop d'audace_, I tell her," said Dorian.\r
+\r
+"Oh! she is audacious enough for anything, my dear.  And what is Ferrol\r
+like?  I don't know him."\r
+\r
+"The husbands of very beautiful women belong to the criminal classes,"\r
+said Lord Henry, sipping his wine.\r
+\r
+Lady Narborough hit him with her fan.  "Lord Henry, I am not at all\r
+surprised that the world says that you are extremely wicked."\r
+\r
+"But what world says that?" asked Lord Henry, elevating his eyebrows.\r
+"It can only be the next world.  This world and I are on excellent\r
+terms."\r
+\r
+"Everybody I know says you are very wicked," cried the old lady,\r
+shaking her head.\r
+\r
+Lord Henry looked serious for some moments.  "It is perfectly\r
+monstrous," he said, at last, "the way people go about nowadays saying\r
+things against one behind one's back that are absolutely and entirely\r
+true."\r
+\r
+"Isn't he incorrigible?" cried Dorian, leaning forward in his chair.\r
+\r
+"I hope so," said his hostess, laughing.  "But really, if you all\r
+worship Madame de Ferrol in this ridiculous way, I shall have to marry\r
+again so as to be in the fashion."\r
+\r
+"You will never marry again, Lady Narborough," broke in Lord Henry.\r
+"You were far too happy.  When a woman marries again, it is because she\r
+detested her first husband.  When a man marries again, it is because he\r
+adored his first wife.  Women try their luck; men risk theirs."\r
+\r
+"Narborough wasn't perfect," cried the old lady.\r
+\r
+"If he had been, you would not have loved him, my dear lady," was the\r
+rejoinder.  "Women love us for our defects.  If we have enough of them,\r
+they will forgive us everything, even our intellects.  You will never\r
+ask me to dinner again after saying this, I am afraid, Lady Narborough,\r
+but it is quite true."\r
+\r
+"Of course it is true, Lord Henry.  If we women did not love you for\r
+your defects, where would you all be?  Not one of you would ever be\r
+married.  You would be a set of unfortunate bachelors.  Not, however,\r
+that that would alter you much.  Nowadays all the married men live like\r
+bachelors, and all the bachelors like married men."\r
+\r
+"_Fin de siecle_," murmured Lord Henry.\r
+\r
+"_Fin du globe_," answered his hostess.\r
+\r
+"I wish it were _fin du globe_," said Dorian with a sigh.  "Life is a\r
+great disappointment."\r
+\r
+"Ah, my dear," cried Lady Narborough, putting on her gloves, "don't\r
+tell me that you have exhausted life.  When a man says that one knows\r
+that life has exhausted him.  Lord Henry is very wicked, and I\r
+sometimes wish that I had been; but you are made to be good--you look\r
+so good.  I must find you a nice wife.  Lord Henry, don't you think\r
+that Mr. Gray should get married?"\r
+\r
+"I am always telling him so, Lady Narborough," said Lord Henry with a\r
+bow.\r
+\r
+"Well, we must look out for a suitable match for him.  I shall go\r
+through Debrett carefully to-night and draw out a list of all the\r
+eligible young ladies."\r
+\r
+"With their ages, Lady Narborough?" asked Dorian.\r
+\r
+"Of course, with their ages, slightly edited.  But nothing must be done\r
+in a hurry.  I want it to be what _The Morning Post_ calls a suitable\r
+alliance, and I want you both to be happy."\r
+\r
+"What nonsense people talk about happy marriages!" exclaimed Lord\r
+Henry.  "A man can be happy with any woman, as long as he does not love\r
+her."\r
+\r
+"Ah! what a cynic you are!" cried the old lady, pushing back her chair\r
+and nodding to Lady Ruxton.  "You must come and dine with me soon\r
+again.  You are really an admirable tonic, much better than what Sir\r
+Andrew prescribes for me.  You must tell me what people you would like\r
+to meet, though.  I want it to be a delightful gathering."\r
+\r
+"I like men who have a future and women who have a past," he answered.\r
+"Or do you think that would make it a petticoat party?"\r
+\r
+"I fear so," she said, laughing, as she stood up.  "A thousand pardons,\r
+my dear Lady Ruxton," she added, "I didn't see you hadn't finished your\r
+cigarette."\r
+\r
+"Never mind, Lady Narborough.  I smoke a great deal too much.  I am\r
+going to limit myself, for the future."\r
+\r
+"Pray don't, Lady Ruxton," said Lord Henry.  "Moderation is a fatal\r
+thing.  Enough is as bad as a meal.  More than enough is as good as a\r
+feast."\r
+\r
+Lady Ruxton glanced at him curiously.  "You must come and explain that\r
+to me some afternoon, Lord Henry.  It sounds a fascinating theory," she\r
+murmured, as she swept out of the room.\r
+\r
+"Now, mind you don't stay too long over your politics and scandal,"\r
+cried Lady Narborough from the door.  "If you do, we are sure to\r
+squabble upstairs."\r
+\r
+The men laughed, and Mr. Chapman got up solemnly from the foot of the\r
+table and came up to the top.  Dorian Gray changed his seat and went\r
+and sat by Lord Henry.  Mr. Chapman began to talk in a loud voice about\r
+the situation in the House of Commons.  He guffawed at his adversaries.\r
+The word _doctrinaire_--word full of terror to the British\r
+mind--reappeared from time to time between his explosions.  An\r
+alliterative prefix served as an ornament of oratory.  He hoisted the\r
+Union Jack on the pinnacles of thought.  The inherited stupidity of the\r
+race--sound English common sense he jovially termed it--was shown to be\r
+the proper bulwark for society.\r
+\r
+A smile curved Lord Henry's lips, and he turned round and looked at\r
+Dorian.\r
+\r
+"Are you better, my dear fellow?" he asked.  "You seemed rather out of\r
+sorts at dinner."\r
+\r
+"I am quite well, Harry.  I am tired.  That is all."\r
+\r
+"You were charming last night.  The little duchess is quite devoted to\r
+you.  She tells me she is going down to Selby."\r
+\r
+"She has promised to come on the twentieth."\r
+\r
+"Is Monmouth to be there, too?"\r
+\r
+"Oh, yes, Harry."\r
+\r
+"He bores me dreadfully, almost as much as he bores her.  She is very\r
+clever, too clever for a woman.  She lacks the indefinable charm of\r
+weakness.  It is the feet of clay that make the gold of the image\r
+precious.  Her feet are very pretty, but they are not feet of clay.\r
+White porcelain feet, if you like.  They have been through the fire,\r
+and what fire does not destroy, it hardens.  She has had experiences."\r
+\r
+"How long has she been married?" asked Dorian.\r
+\r
+"An eternity, she tells me.  I believe, according to the peerage, it is\r
+ten years, but ten years with Monmouth must have been like eternity,\r
+with time thrown in.  Who else is coming?"\r
+\r
+"Oh, the Willoughbys, Lord Rugby and his wife, our hostess, Geoffrey\r
+Clouston, the usual set.  I have asked Lord Grotrian."\r
+\r
+"I like him," said Lord Henry.  "A great many people don't, but I find\r
+him charming.  He atones for being occasionally somewhat overdressed by\r
+being always absolutely over-educated. He is a very modern type."\r
+\r
+"I don't know if he will be able to come, Harry.  He may have to go to\r
+Monte Carlo with his father."\r
+\r
+"Ah! what a nuisance people's people are!  Try and make him come.  By\r
+the way, Dorian, you ran off very early last night.  You left before\r
+eleven.  What did you do afterwards?  Did you go straight home?"\r
+\r
+Dorian glanced at him hurriedly and frowned.\r
+\r
+"No, Harry," he said at last, "I did not get home till nearly three."\r
+\r
+"Did you go to the club?"\r
+\r
+"Yes," he answered.  Then he bit his lip.  "No, I don't mean that.  I\r
+didn't go to the club.  I walked about.  I forget what I did.... How\r
+inquisitive you are, Harry!  You always want to know what one has been\r
+doing.  I always want to forget what I have been doing.  I came in at\r
+half-past two, if you wish to know the exact time.  I had left my\r
+latch-key at home, and my servant had to let me in.  If you want any\r
+corroborative evidence on the subject, you can ask him."\r
+\r
+Lord Henry shrugged his shoulders.  "My dear fellow, as if I cared!\r
+Let us go up to the drawing-room. No sherry, thank you, Mr. Chapman.\r
+Something has happened to you, Dorian.  Tell me what it is.  You are\r
+not yourself to-night."\r
+\r
+"Don't mind me, Harry.  I am irritable, and out of temper.  I shall\r
+come round and see you to-morrow, or next day.  Make my excuses to Lady\r
+Narborough.  I shan't go upstairs.  I shall go home.  I must go home."\r
+\r
+"All right, Dorian.  I dare say I shall see you to-morrow at tea-time.\r
+The duchess is coming."\r
+\r
+"I will try to be there, Harry," he said, leaving the room.  As he\r
+drove back to his own house, he was conscious that the sense of terror\r
+he thought he had strangled had come back to him.  Lord Henry's casual\r
+questioning had made him lose his nerve for the moment, and he wanted\r
+his nerve still.  Things that were dangerous had to be destroyed.  He\r
+winced.  He hated the idea of even touching them.\r
+\r
+Yet it had to be done.  He realized that, and when he had locked the\r
+door of his library, he opened the secret press into which he had\r
+thrust Basil Hallward's coat and bag.  A huge fire was blazing.  He\r
+piled another log on it.  The smell of the singeing clothes and burning\r
+leather was horrible.  It took him three-quarters of an hour to consume\r
+everything.  At the end he felt faint and sick, and having lit some\r
+Algerian pastilles in a pierced copper brazier, he bathed his hands and\r
+forehead with a cool musk-scented vinegar.\r
+\r
+Suddenly he started.  His eyes grew strangely bright, and he gnawed\r
+nervously at his underlip.  Between two of the windows stood a large\r
+Florentine cabinet, made out of ebony and inlaid with ivory and blue\r
+lapis.  He watched it as though it were a thing that could fascinate\r
+and make afraid, as though it held something that he longed for and yet\r
+almost loathed.  His breath quickened.  A mad craving came over him.\r
+He lit a cigarette and then threw it away.  His eyelids drooped till\r
+the long fringed lashes almost touched his cheek.  But he still watched\r
+the cabinet.  At last he got up from the sofa on which he had been\r
+lying, went over to it, and having unlocked it, touched some hidden\r
+spring.  A triangular drawer passed slowly out.  His fingers moved\r
+instinctively towards it, dipped in, and closed on something.  It was a\r
+small Chinese box of black and gold-dust lacquer, elaborately wrought,\r
+the sides patterned with curved waves, and the silken cords hung with\r
+round crystals and tasselled in plaited metal threads.  He opened it.\r
+Inside was a green paste, waxy in lustre, the odour curiously heavy and\r
+persistent.\r
+\r
+He hesitated for some moments, with a strangely immobile smile upon his\r
+face.  Then shivering, though the atmosphere of the room was terribly\r
+hot, he drew himself up and glanced at the clock.  It was twenty\r
+minutes to twelve.  He put the box back, shutting the cabinet doors as\r
+he did so, and went into his bedroom.\r
+\r
+As midnight was striking bronze blows upon the dusky air, Dorian Gray,\r
+dressed commonly, and with a muffler wrapped round his throat, crept\r
+quietly out of his house.  In Bond Street he found a hansom with a good\r
+horse.  He hailed it and in a low voice gave the driver an address.\r
+\r
+The man shook his head.  "It is too far for me," he muttered.\r
+\r
+"Here is a sovereign for you," said Dorian.  "You shall have another if\r
+you drive fast."\r
+\r
+"All right, sir," answered the man, "you will be there in an hour," and\r
+after his fare had got in he turned his horse round and drove rapidly\r
+towards the river.\r
+\r
+\r
+\r
+CHAPTER 16\r
+\r
+A cold rain began to fall, and the blurred street-lamps looked ghastly\r
+in the dripping mist.  The public-houses were just closing, and dim men\r
+and women were clustering in broken groups round their doors.  From\r
+some of the bars came the sound of horrible laughter.  In others,\r
+drunkards brawled and screamed.\r
+\r
+Lying back in the hansom, with his hat pulled over his forehead, Dorian\r
+Gray watched with listless eyes the sordid shame of the great city, and\r
+now and then he repeated to himself the words that Lord Henry had said\r
+to him on the first day they had met, "To cure the soul by means of the\r
+senses, and the senses by means of the soul."  Yes, that was the\r
+secret.  He had often tried it, and would try it again now.  There were\r
+opium dens where one could buy oblivion, dens of horror where the\r
+memory of old sins could be destroyed by the madness of sins that were\r
+new.\r
+\r
+The moon hung low in the sky like a yellow skull.  From time to time a\r
+huge misshapen cloud stretched a long arm across and hid it.  The\r
+gas-lamps grew fewer, and the streets more narrow and gloomy.  Once the\r
+man lost his way and had to drive back half a mile.  A steam rose from\r
+the horse as it splashed up the puddles.  The sidewindows of the hansom\r
+were clogged with a grey-flannel mist.\r
+\r
+"To cure the soul by means of the senses, and the senses by means of\r
+the soul!"  How the words rang in his ears!  His soul, certainly, was\r
+sick to death.  Was it true that the senses could cure it?  Innocent\r
+blood had been spilled.  What could atone for that?  Ah! for that there\r
+was no atonement; but though forgiveness was impossible, forgetfulness\r
+was possible still, and he was determined to forget, to stamp the thing\r
+out, to crush it as one would crush the adder that had stung one.\r
+Indeed, what right had Basil to have spoken to him as he had done?  Who\r
+had made him a judge over others?  He had said things that were\r
+dreadful, horrible, not to be endured.\r
+\r
+On and on plodded the hansom, going slower, it seemed to him, at each\r
+step.  He thrust up the trap and called to the man to drive faster.\r
+The hideous hunger for opium began to gnaw at him.  His throat burned\r
+and his delicate hands twitched nervously together.  He struck at the\r
+horse madly with his stick.  The driver laughed and whipped up.  He\r
+laughed in answer, and the man was silent.\r
+\r
+The way seemed interminable, and the streets like the black web of some\r
+sprawling spider.  The monotony became unbearable, and as the mist\r
+thickened, he felt afraid.\r
+\r
+Then they passed by lonely brickfields.  The fog was lighter here, and\r
+he could see the strange, bottle-shaped kilns with their orange,\r
+fanlike tongues of fire.  A dog barked as they went by, and far away in\r
+the darkness some wandering sea-gull screamed.  The horse stumbled in a\r
+rut, then swerved aside and broke into a gallop.\r
+\r
+After some time they left the clay road and rattled again over\r
+rough-paven streets.  Most of the windows were dark, but now and then\r
+fantastic shadows were silhouetted against some lamplit blind.  He\r
+watched them curiously.  They moved like monstrous marionettes and made\r
+gestures like live things.  He hated them.  A dull rage was in his\r
+heart.  As they turned a corner, a woman yelled something at them from\r
+an open door, and two men ran after the hansom for about a hundred\r
+yards.  The driver beat at them with his whip.\r
+\r
+It is said that passion makes one think in a circle.  Certainly with\r
+hideous iteration the bitten lips of Dorian Gray shaped and reshaped\r
+those subtle words that dealt with soul and sense, till he had found in\r
+them the full expression, as it were, of his mood, and justified, by\r
+intellectual approval, passions that without such justification would\r
+still have dominated his temper.  From cell to cell of his brain crept\r
+the one thought; and the wild desire to live, most terrible of all\r
+man's appetites, quickened into force each trembling nerve and fibre.\r
+Ugliness that had once been hateful to him because it made things real,\r
+became dear to him now for that very reason.  Ugliness was the one\r
+reality.  The coarse brawl, the loathsome den, the crude violence of\r
+disordered life, the very vileness of thief and outcast, were more\r
+vivid, in their intense actuality of impression, than all the gracious\r
+shapes of art, the dreamy shadows of song.  They were what he needed\r
+for forgetfulness.  In three days he would be free.\r
+\r
+Suddenly the man drew up with a jerk at the top of a dark lane.  Over\r
+the low roofs and jagged chimney-stacks of the houses rose the black\r
+masts of ships.  Wreaths of white mist clung like ghostly sails to the\r
+yards.\r
+\r
+"Somewhere about here, sir, ain't it?" he asked huskily through the\r
+trap.\r
+\r
+Dorian started and peered round.  "This will do," he answered, and\r
+having got out hastily and given the driver the extra fare he had\r
+promised him, he walked quickly in the direction of the quay.  Here and\r
+there a lantern gleamed at the stern of some huge merchantman.  The\r
+light shook and splintered in the puddles.  A red glare came from an\r
+outward-bound steamer that was coaling.  The slimy pavement looked like\r
+a wet mackintosh.\r
+\r
+He hurried on towards the left, glancing back now and then to see if he\r
+was being followed.  In about seven or eight minutes he reached a small\r
+shabby house that was wedged in between two gaunt factories.  In one of\r
+the top-windows stood a lamp.  He stopped and gave a peculiar knock.\r
+\r
+After a little time he heard steps in the passage and the chain being\r
+unhooked.  The door opened quietly, and he went in without saying a\r
+word to the squat misshapen figure that flattened itself into the\r
+shadow as he passed.  At the end of the hall hung a tattered green\r
+curtain that swayed and shook in the gusty wind which had followed him\r
+in from the street.  He dragged it aside and entered a long low room\r
+which looked as if it had once been a third-rate dancing-saloon. Shrill\r
+flaring gas-jets, dulled and distorted in the fly-blown mirrors that\r
+faced them, were ranged round the walls.  Greasy reflectors of ribbed\r
+tin backed them, making quivering disks of light.  The floor was\r
+covered with ochre-coloured sawdust, trampled here and there into mud,\r
+and stained with dark rings of spilled liquor.  Some Malays were\r
+crouching by a little charcoal stove, playing with bone counters and\r
+showing their white teeth as they chattered.  In one corner, with his\r
+head buried in his arms, a sailor sprawled over a table, and by the\r
+tawdrily painted bar that ran across one complete side stood two\r
+haggard women, mocking an old man who was brushing the sleeves of his\r
+coat with an expression of disgust.  "He thinks he's got red ants on\r
+him," laughed one of them, as Dorian passed by.  The man looked at her\r
+in terror and began to whimper.\r
+\r
+At the end of the room there was a little staircase, leading to a\r
+darkened chamber.  As Dorian hurried up its three rickety steps, the\r
+heavy odour of opium met him.  He heaved a deep breath, and his\r
+nostrils quivered with pleasure.  When he entered, a young man with\r
+smooth yellow hair, who was bending over a lamp lighting a long thin\r
+pipe, looked up at him and nodded in a hesitating manner.\r
+\r
+"You here, Adrian?" muttered Dorian.\r
+\r
+"Where else should I be?" he answered, listlessly.  "None of the chaps\r
+will speak to me now."\r
+\r
+"I thought you had left England."\r
+\r
+"Darlington is not going to do anything.  My brother paid the bill at\r
+last.  George doesn't speak to me either.... I don't care," he added\r
+with a sigh.  "As long as one has this stuff, one doesn't want friends.\r
+I think I have had too many friends."\r
+\r
+Dorian winced and looked round at the grotesque things that lay in such\r
+fantastic postures on the ragged mattresses.  The twisted limbs, the\r
+gaping mouths, the staring lustreless eyes, fascinated him.  He knew in\r
+what strange heavens they were suffering, and what dull hells were\r
+teaching them the secret of some new joy.  They were better off than he\r
+was.  He was prisoned in thought.  Memory, like a horrible malady, was\r
+eating his soul away.  From time to time he seemed to see the eyes of\r
+Basil Hallward looking at him.  Yet he felt he could not stay.  The\r
+presence of Adrian Singleton troubled him.  He wanted to be where no\r
+one would know who he was.  He wanted to escape from himself.\r
+\r
+"I am going on to the other place," he said after a pause.\r
+\r
+"On the wharf?"\r
+\r
+"Yes."\r
+\r
+"That mad-cat is sure to be there.  They won't have her in this place\r
+now."\r
+\r
+Dorian shrugged his shoulders.  "I am sick of women who love one.\r
+Women who hate one are much more interesting.  Besides, the stuff is\r
+better."\r
+\r
+"Much the same."\r
+\r
+"I like it better.  Come and have something to drink.  I must have\r
+something."\r
+\r
+"I don't want anything," murmured the young man.\r
+\r
+"Never mind."\r
+\r
+Adrian Singleton rose up wearily and followed Dorian to the bar.  A\r
+half-caste, in a ragged turban and a shabby ulster, grinned a hideous\r
+greeting as he thrust a bottle of brandy and two tumblers in front of\r
+them.  The women sidled up and began to chatter.  Dorian turned his\r
+back on them and said something in a low voice to Adrian Singleton.\r
+\r
+A crooked smile, like a Malay crease, writhed across the face of one of\r
+the women.  "We are very proud to-night," she sneered.\r
+\r
+"For God's sake don't talk to me," cried Dorian, stamping his foot on\r
+the ground.  "What do you want?  Money?  Here it is.  Don't ever talk\r
+to me again."\r
+\r
+Two red sparks flashed for a moment in the woman's sodden eyes, then\r
+flickered out and left them dull and glazed.  She tossed her head and\r
+raked the coins off the counter with greedy fingers.  Her companion\r
+watched her enviously.\r
+\r
+"It's no use," sighed Adrian Singleton.  "I don't care to go back.\r
+What does it matter?  I am quite happy here."\r
+\r
+"You will write to me if you want anything, won't you?" said Dorian,\r
+after a pause.\r
+\r
+"Perhaps."\r
+\r
+"Good night, then."\r
+\r
+"Good night," answered the young man, passing up the steps and wiping\r
+his parched mouth with a handkerchief.\r
+\r
+Dorian walked to the door with a look of pain in his face.  As he drew\r
+the curtain aside, a hideous laugh broke from the painted lips of the\r
+woman who had taken his money.  "There goes the devil's bargain!" she\r
+hiccoughed, in a hoarse voice.\r
+\r
+"Curse you!" he answered, "don't call me that."\r
+\r
+She snapped her fingers.  "Prince Charming is what you like to be\r
+called, ain't it?" she yelled after him.\r
+\r
+The drowsy sailor leaped to his feet as she spoke, and looked wildly\r
+round.  The sound of the shutting of the hall door fell on his ear.  He\r
+rushed out as if in pursuit.\r
+\r
+Dorian Gray hurried along the quay through the drizzling rain.  His\r
+meeting with Adrian Singleton had strangely moved him, and he wondered\r
+if the ruin of that young life was really to be laid at his door, as\r
+Basil Hallward had said to him with such infamy of insult.  He bit his\r
+lip, and for a few seconds his eyes grew sad.  Yet, after all, what did\r
+it matter to him?  One's days were too brief to take the burden of\r
+another's errors on one's shoulders.  Each man lived his own life and\r
+paid his own price for living it.  The only pity was one had to pay so\r
+often for a single fault.  One had to pay over and over again, indeed.\r
+In her dealings with man, destiny never closed her accounts.\r
+\r
+There are moments, psychologists tell us, when the passion for sin, or\r
+for what the world calls sin, so dominates a nature that every fibre of\r
+the body, as every cell of the brain, seems to be instinct with fearful\r
+impulses.  Men and women at such moments lose the freedom of their\r
+will.  They move to their terrible end as automatons move.  Choice is\r
+taken from them, and conscience is either killed, or, if it lives at\r
+all, lives but to give rebellion its fascination and disobedience its\r
+charm.  For all sins, as theologians weary not of reminding us, are\r
+sins of disobedience.  When that high spirit, that morning star of\r
+evil, fell from heaven, it was as a rebel that he fell.\r
+\r
+Callous, concentrated on evil, with stained mind, and soul hungry for\r
+rebellion, Dorian Gray hastened on, quickening his step as he went, but\r
+as he darted aside into a dim archway, that had served him often as a\r
+short cut to the ill-famed place where he was going, he felt himself\r
+suddenly seized from behind, and before he had time to defend himself,\r
+he was thrust back against the wall, with a brutal hand round his\r
+throat.\r
+\r
+He struggled madly for life, and by a terrible effort wrenched the\r
+tightening fingers away.  In a second he heard the click of a revolver,\r
+and saw the gleam of a polished barrel, pointing straight at his head,\r
+and the dusky form of a short, thick-set man facing him.\r
+\r
+"What do you want?" he gasped.\r
+\r
+"Keep quiet," said the man.  "If you stir, I shoot you."\r
+\r
+"You are mad.  What have I done to you?"\r
+\r
+"You wrecked the life of Sibyl Vane," was the answer, "and Sibyl Vane\r
+was my sister.  She killed herself.  I know it.  Her death is at your\r
+door.  I swore I would kill you in return.  For years I have sought\r
+you.  I had no clue, no trace.  The two people who could have described\r
+you were dead.  I knew nothing of you but the pet name she used to call\r
+you.  I heard it to-night by chance.  Make your peace with God, for\r
+to-night you are going to die."\r
+\r
+Dorian Gray grew sick with fear.  "I never knew her," he stammered.  "I\r
+never heard of her.  You are mad."\r
+\r
+"You had better confess your sin, for as sure as I am James Vane, you\r
+are going to die."  There was a horrible moment.  Dorian did not know\r
+what to say or do.  "Down on your knees!" growled the man.  "I give you\r
+one minute to make your peace--no more.  I go on board to-night for\r
+India, and I must do my job first.  One minute.  That's all."\r
+\r
+Dorian's arms fell to his side.  Paralysed with terror, he did not know\r
+what to do.  Suddenly a wild hope flashed across his brain.  "Stop," he\r
+cried.  "How long ago is it since your sister died?  Quick, tell me!"\r
+\r
+"Eighteen years," said the man.  "Why do you ask me?  What do years\r
+matter?"\r
+\r
+"Eighteen years," laughed Dorian Gray, with a touch of triumph in his\r
+voice.  "Eighteen years!  Set me under the lamp and look at my face!"\r
+\r
+James Vane hesitated for a moment, not understanding what was meant.\r
+Then he seized Dorian Gray and dragged him from the archway.\r
+\r
+Dim and wavering as was the wind-blown light, yet it served to show him\r
+the hideous error, as it seemed, into which he had fallen, for the face\r
+of the man he had sought to kill had all the bloom of boyhood, all the\r
+unstained purity of youth.  He seemed little more than a lad of twenty\r
+summers, hardly older, if older indeed at all, than his sister had been\r
+when they had parted so many years ago.  It was obvious that this was\r
+not the man who had destroyed her life.\r
+\r
+He loosened his hold and reeled back.  "My God! my God!" he cried, "and\r
+I would have murdered you!"\r
+\r
+Dorian Gray drew a long breath.  "You have been on the brink of\r
+committing a terrible crime, my man," he said, looking at him sternly.\r
+"Let this be a warning to you not to take vengeance into your own\r
+hands."\r
+\r
+"Forgive me, sir," muttered James Vane.  "I was deceived.  A chance\r
+word I heard in that damned den set me on the wrong track."\r
+\r
+"You had better go home and put that pistol away, or you may get into\r
+trouble," said Dorian, turning on his heel and going slowly down the\r
+street.\r
+\r
+James Vane stood on the pavement in horror.  He was trembling from head\r
+to foot.  After a little while, a black shadow that had been creeping\r
+along the dripping wall moved out into the light and came close to him\r
+with stealthy footsteps.  He felt a hand laid on his arm and looked\r
+round with a start.  It was one of the women who had been drinking at\r
+the bar.\r
+\r
+"Why didn't you kill him?" she hissed out, putting haggard face quite\r
+close to his.  "I knew you were following him when you rushed out from\r
+Daly's. You fool!  You should have killed him.  He has lots of money,\r
+and he's as bad as bad."\r
+\r
+"He is not the man I am looking for," he answered, "and I want no man's\r
+money.  I want a man's life.  The man whose life I want must be nearly\r
+forty now.  This one is little more than a boy.  Thank God, I have not\r
+got his blood upon my hands."\r
+\r
+The woman gave a bitter laugh.  "Little more than a boy!" she sneered.\r
+"Why, man, it's nigh on eighteen years since Prince Charming made me\r
+what I am."\r
+\r
+"You lie!" cried James Vane.\r
+\r
+She raised her hand up to heaven.  "Before God I am telling the truth,"\r
+she cried.\r
+\r
+"Before God?"\r
+\r
+"Strike me dumb if it ain't so.  He is the worst one that comes here.\r
+They say he has sold himself to the devil for a pretty face.  It's nigh\r
+on eighteen years since I met him.  He hasn't changed much since then.\r
+I have, though," she added, with a sickly leer.\r
+\r
+"You swear this?"\r
+\r
+"I swear it," came in hoarse echo from her flat mouth.  "But don't give\r
+me away to him," she whined; "I am afraid of him.  Let me have some\r
+money for my night's lodging."\r
+\r
+He broke from her with an oath and rushed to the corner of the street,\r
+but Dorian Gray had disappeared.  When he looked back, the woman had\r
+vanished also.\r
+\r
+\r
+\r
+CHAPTER 17\r
+\r
+A week later Dorian Gray was sitting in the conservatory at Selby\r
+Royal, talking to the pretty Duchess of Monmouth, who with her husband,\r
+a jaded-looking man of sixty, was amongst his guests.  It was tea-time,\r
+and the mellow light of the huge, lace-covered lamp that stood on the\r
+table lit up the delicate china and hammered silver of the service at\r
+which the duchess was presiding.  Her white hands were moving daintily\r
+among the cups, and her full red lips were smiling at something that\r
+Dorian had whispered to her.  Lord Henry was lying back in a\r
+silk-draped wicker chair, looking at them.  On a peach-coloured divan\r
+sat Lady Narborough, pretending to listen to the duke's description of\r
+the last Brazilian beetle that he had added to his collection.  Three\r
+young men in elaborate smoking-suits were handing tea-cakes to some of\r
+the women.  The house-party consisted of twelve people, and there were\r
+more expected to arrive on the next day.\r
+\r
+"What are you two talking about?" said Lord Henry, strolling over to\r
+the table and putting his cup down.  "I hope Dorian has told you about\r
+my plan for rechristening everything, Gladys.  It is a delightful idea."\r
+\r
+"But I don't want to be rechristened, Harry," rejoined the duchess,\r
+looking up at him with her wonderful eyes.  "I am quite satisfied with\r
+my own name, and I am sure Mr. Gray should be satisfied with his."\r
+\r
+"My dear Gladys, I would not alter either name for the world.  They are\r
+both perfect.  I was thinking chiefly of flowers.  Yesterday I cut an\r
+orchid, for my button-hole. It was a marvellous spotted thing, as\r
+effective as the seven deadly sins.  In a thoughtless moment I asked\r
+one of the gardeners what it was called.  He told me it was a fine\r
+specimen of _Robinsoniana_, or something dreadful of that kind.  It is a\r
+sad truth, but we have lost the faculty of giving lovely names to\r
+things.  Names are everything.  I never quarrel with actions.  My one\r
+quarrel is with words.  That is the reason I hate vulgar realism in\r
+literature.  The man who could call a spade a spade should be compelled\r
+to use one.  It is the only thing he is fit for."\r
+\r
+"Then what should we call you, Harry?" she asked.\r
+\r
+"His name is Prince Paradox," said Dorian.\r
+\r
+"I recognize him in a flash," exclaimed the duchess.\r
+\r
+"I won't hear of it," laughed Lord Henry, sinking into a chair.  "From\r
+a label there is no escape!  I refuse the title."\r
+\r
+"Royalties may not abdicate," fell as a warning from pretty lips.\r
+\r
+"You wish me to defend my throne, then?"\r
+\r
+"Yes."\r
+\r
+"I give the truths of to-morrow."\r
+\r
+"I prefer the mistakes of to-day," she answered.\r
+\r
+"You disarm me, Gladys," he cried, catching the wilfulness of her mood.\r
+\r
+"Of your shield, Harry, not of your spear."\r
+\r
+"I never tilt against beauty," he said, with a wave of his hand.\r
+\r
+"That is your error, Harry, believe me.  You value beauty far too much."\r
+\r
+"How can you say that?  I admit that I think that it is better to be\r
+beautiful than to be good.  But on the other hand, no one is more ready\r
+than I am to acknowledge that it is better to be good than to be ugly."\r
+\r
+"Ugliness is one of the seven deadly sins, then?" cried the duchess.\r
+"What becomes of your simile about the orchid?"\r
+\r
+"Ugliness is one of the seven deadly virtues, Gladys.  You, as a good\r
+Tory, must not underrate them.  Beer, the Bible, and the seven deadly\r
+virtues have made our England what she is."\r
+\r
+"You don't like your country, then?" she asked.\r
+\r
+"I live in it."\r
+\r
+"That you may censure it the better."\r
+\r
+"Would you have me take the verdict of Europe on it?" he inquired.\r
+\r
+"What do they say of us?"\r
+\r
+"That Tartuffe has emigrated to England and opened a shop."\r
+\r
+"Is that yours, Harry?"\r
+\r
+"I give it to you."\r
+\r
+"I could not use it.  It is too true."\r
+\r
+"You need not be afraid.  Our countrymen never recognize a description."\r
+\r
+"They are practical."\r
+\r
+"They are more cunning than practical.  When they make up their ledger,\r
+they balance stupidity by wealth, and vice by hypocrisy."\r
+\r
+"Still, we have done great things."\r
+\r
+"Great things have been thrust on us, Gladys."\r
+\r
+"We have carried their burden."\r
+\r
+"Only as far as the Stock Exchange."\r
+\r
+She shook her head.  "I believe in the race," she cried.\r
+\r
+"It represents the survival of the pushing."\r
+\r
+"It has development."\r
+\r
+"Decay fascinates me more."\r
+\r
+"What of art?" she asked.\r
+\r
+"It is a malady."\r
+\r
+"Love?"\r
+\r
+"An illusion."\r
+\r
+"Religion?"\r
+\r
+"The fashionable substitute for belief."\r
+\r
+"You are a sceptic."\r
+\r
+"Never!  Scepticism is the beginning of faith."\r
+\r
+"What are you?"\r
+\r
+"To define is to limit."\r
+\r
+"Give me a clue."\r
+\r
+"Threads snap.  You would lose your way in the labyrinth."\r
+\r
+"You bewilder me.  Let us talk of some one else."\r
+\r
+"Our host is a delightful topic.  Years ago he was christened Prince\r
+Charming."\r
+\r
+"Ah! don't remind me of that," cried Dorian Gray.\r
+\r
+"Our host is rather horrid this evening," answered the duchess,\r
+colouring.  "I believe he thinks that Monmouth married me on purely\r
+scientific principles as the best specimen he could find of a modern\r
+butterfly."\r
+\r
+"Well, I hope he won't stick pins into you, Duchess," laughed Dorian.\r
+\r
+"Oh! my maid does that already, Mr. Gray, when she is annoyed with me."\r
+\r
+"And what does she get annoyed with you about, Duchess?"\r
+\r
+"For the most trivial things, Mr. Gray, I assure you.  Usually because\r
+I come in at ten minutes to nine and tell her that I must be dressed by\r
+half-past eight."\r
+\r
+"How unreasonable of her!  You should give her warning."\r
+\r
+"I daren't, Mr. Gray.  Why, she invents hats for me.  You remember the\r
+one I wore at Lady Hilstone's garden-party?  You don't, but it is nice\r
+of you to pretend that you do.  Well, she made it out of nothing.  All\r
+good hats are made out of nothing."\r
+\r
+"Like all good reputations, Gladys," interrupted Lord Henry.  "Every\r
+effect that one produces gives one an enemy.  To be popular one must be\r
+a mediocrity."\r
+\r
+"Not with women," said the duchess, shaking her head; "and women rule\r
+the world.  I assure you we can't bear mediocrities.  We women, as some\r
+one says, love with our ears, just as you men love with your eyes, if\r
+you ever love at all."\r
+\r
+"It seems to me that we never do anything else," murmured Dorian.\r
+\r
+"Ah! then, you never really love, Mr. Gray," answered the duchess with\r
+mock sadness.\r
+\r
+"My dear Gladys!" cried Lord Henry.  "How can you say that?  Romance\r
+lives by repetition, and repetition converts an appetite into an art.\r
+Besides, each time that one loves is the only time one has ever loved.\r
+Difference of object does not alter singleness of passion.  It merely\r
+intensifies it.  We can have in life but one great experience at best,\r
+and the secret of life is to reproduce that experience as often as\r
+possible."\r
+\r
+"Even when one has been wounded by it, Harry?" asked the duchess after\r
+a pause.\r
+\r
+"Especially when one has been wounded by it," answered Lord Henry.\r
+\r
+The duchess turned and looked at Dorian Gray with a curious expression\r
+in her eyes.  "What do you say to that, Mr. Gray?" she inquired.\r
+\r
+Dorian hesitated for a moment.  Then he threw his head back and\r
+laughed.  "I always agree with Harry, Duchess."\r
+\r
+"Even when he is wrong?"\r
+\r
+"Harry is never wrong, Duchess."\r
+\r
+"And does his philosophy make you happy?"\r
+\r
+"I have never searched for happiness.  Who wants happiness?  I have\r
+searched for pleasure."\r
+\r
+"And found it, Mr. Gray?"\r
+\r
+"Often.  Too often."\r
+\r
+The duchess sighed.  "I am searching for peace," she said, "and if I\r
+don't go and dress, I shall have none this evening."\r
+\r
+"Let me get you some orchids, Duchess," cried Dorian, starting to his\r
+feet and walking down the conservatory.\r
+\r
+"You are flirting disgracefully with him," said Lord Henry to his\r
+cousin.  "You had better take care.  He is very fascinating."\r
+\r
+"If he were not, there would be no battle."\r
+\r
+"Greek meets Greek, then?"\r
+\r
+"I am on the side of the Trojans.  They fought for a woman."\r
+\r
+"They were defeated."\r
+\r
+"There are worse things than capture," she answered.\r
+\r
+"You gallop with a loose rein."\r
+\r
+"Pace gives life," was the _riposte_.\r
+\r
+"I shall write it in my diary to-night."\r
+\r
+"What?"\r
+\r
+"That a burnt child loves the fire."\r
+\r
+"I am not even singed.  My wings are untouched."\r
+\r
+"You use them for everything, except flight."\r
+\r
+"Courage has passed from men to women.  It is a new experience for us."\r
+\r
+"You have a rival."\r
+\r
+"Who?"\r
+\r
+He laughed.  "Lady Narborough," he whispered.  "She perfectly adores\r
+him."\r
+\r
+"You fill me with apprehension.  The appeal to antiquity is fatal to us\r
+who are romanticists."\r
+\r
+"Romanticists!  You have all the methods of science."\r
+\r
+"Men have educated us."\r
+\r
+"But not explained you."\r
+\r
+"Describe us as a sex," was her challenge.\r
+\r
+"Sphinxes without secrets."\r
+\r
+She looked at him, smiling.  "How long Mr. Gray is!" she said.  "Let us\r
+go and help him.  I have not yet told him the colour of my frock."\r
+\r
+"Ah! you must suit your frock to his flowers, Gladys."\r
+\r
+"That would be a premature surrender."\r
+\r
+"Romantic art begins with its climax."\r
+\r
+"I must keep an opportunity for retreat."\r
+\r
+"In the Parthian manner?"\r
+\r
+"They found safety in the desert.  I could not do that."\r
+\r
+"Women are not always allowed a choice," he answered, but hardly had he\r
+finished the sentence before from the far end of the conservatory came\r
+a stifled groan, followed by the dull sound of a heavy fall.  Everybody\r
+started up.  The duchess stood motionless in horror.  And with fear in\r
+his eyes, Lord Henry rushed through the flapping palms to find Dorian\r
+Gray lying face downwards on the tiled floor in a deathlike swoon.\r
+\r
+He was carried at once into the blue drawing-room and laid upon one of\r
+the sofas.  After a short time, he came to himself and looked round\r
+with a dazed expression.\r
+\r
+"What has happened?" he asked.  "Oh!  I remember.  Am I safe here,\r
+Harry?" He began to tremble.\r
+\r
+"My dear Dorian," answered Lord Henry, "you merely fainted.  That was\r
+all.  You must have overtired yourself.  You had better not come down\r
+to dinner.  I will take your place."\r
+\r
+"No, I will come down," he said, struggling to his feet.  "I would\r
+rather come down.  I must not be alone."\r
+\r
+He went to his room and dressed.  There was a wild recklessness of\r
+gaiety in his manner as he sat at table, but now and then a thrill of\r
+terror ran through him when he remembered that, pressed against the\r
+window of the conservatory, like a white handkerchief, he had seen the\r
+face of James Vane watching him.\r
+\r
+\r
+\r
+CHAPTER 18\r
+\r
+The next day he did not leave the house, and, indeed, spent most of the\r
+time in his own room, sick with a wild terror of dying, and yet\r
+indifferent to life itself.  The consciousness of being hunted, snared,\r
+tracked down, had begun to dominate him.  If the tapestry did but\r
+tremble in the wind, he shook.  The dead leaves that were blown against\r
+the leaded panes seemed to him like his own wasted resolutions and wild\r
+regrets.  When he closed his eyes, he saw again the sailor's face\r
+peering through the mist-stained glass, and horror seemed once more to\r
+lay its hand upon his heart.\r
+\r
+But perhaps it had been only his fancy that had called vengeance out of\r
+the night and set the hideous shapes of punishment before him.  Actual\r
+life was chaos, but there was something terribly logical in the\r
+imagination.  It was the imagination that set remorse to dog the feet\r
+of sin.  It was the imagination that made each crime bear its misshapen\r
+brood.  In the common world of fact the wicked were not punished, nor\r
+the good rewarded.  Success was given to the strong, failure thrust\r
+upon the weak.  That was all.  Besides, had any stranger been prowling\r
+round the house, he would have been seen by the servants or the\r
+keepers.  Had any foot-marks been found on the flower-beds, the\r
+gardeners would have reported it.  Yes, it had been merely fancy.\r
+Sibyl Vane's brother had not come back to kill him.  He had sailed away\r
+in his ship to founder in some winter sea.  From him, at any rate, he\r
+was safe.  Why, the man did not know who he was, could not know who he\r
+was.  The mask of youth had saved him.\r
+\r
+And yet if it had been merely an illusion, how terrible it was to think\r
+that conscience could raise such fearful phantoms, and give them\r
+visible form, and make them move before one!  What sort of life would\r
+his be if, day and night, shadows of his crime were to peer at him from\r
+silent corners, to mock him from secret places, to whisper in his ear\r
+as he sat at the feast, to wake him with icy fingers as he lay asleep!\r
+As the thought crept through his brain, he grew pale with terror, and\r
+the air seemed to him to have become suddenly colder.  Oh! in what a\r
+wild hour of madness he had killed his friend!  How ghastly the mere\r
+memory of the scene!  He saw it all again.  Each hideous detail came\r
+back to him with added horror.  Out of the black cave of time, terrible\r
+and swathed in scarlet, rose the image of his sin.  When Lord Henry\r
+came in at six o'clock, he found him crying as one whose heart will\r
+break.\r
+\r
+It was not till the third day that he ventured to go out.  There was\r
+something in the clear, pine-scented air of that winter morning that\r
+seemed to bring him back his joyousness and his ardour for life.  But\r
+it was not merely the physical conditions of environment that had\r
+caused the change.  His own nature had revolted against the excess of\r
+anguish that had sought to maim and mar the perfection of its calm.\r
+With subtle and finely wrought temperaments it is always so.  Their\r
+strong passions must either bruise or bend.  They either slay the man,\r
+or themselves die.  Shallow sorrows and shallow loves live on.  The\r
+loves and sorrows that are great are destroyed by their own plenitude.\r
+Besides, he had convinced himself that he had been the victim of a\r
+terror-stricken imagination, and looked back now on his fears with\r
+something of pity and not a little of contempt.\r
+\r
+After breakfast, he walked with the duchess for an hour in the garden\r
+and then drove across the park to join the shooting-party. The crisp\r
+frost lay like salt upon the grass.  The sky was an inverted cup of\r
+blue metal.  A thin film of ice bordered the flat, reed-grown lake.\r
+\r
+At the corner of the pine-wood he caught sight of Sir Geoffrey\r
+Clouston, the duchess's brother, jerking two spent cartridges out of\r
+his gun.  He jumped from the cart, and having told the groom to take\r
+the mare home, made his way towards his guest through the withered\r
+bracken and rough undergrowth.\r
+\r
+"Have you had good sport, Geoffrey?" he asked.\r
+\r
+"Not very good, Dorian.  I think most of the birds have gone to the\r
+open.  I dare say it will be better after lunch, when we get to new\r
+ground."\r
+\r
+Dorian strolled along by his side.  The keen aromatic air, the brown\r
+and red lights that glimmered in the wood, the hoarse cries of the\r
+beaters ringing out from time to time, and the sharp snaps of the guns\r
+that followed, fascinated him and filled him with a sense of delightful\r
+freedom.  He was dominated by the carelessness of happiness, by the\r
+high indifference of joy.\r
+\r
+Suddenly from a lumpy tussock of old grass some twenty yards in front\r
+of them, with black-tipped ears erect and long hinder limbs throwing it\r
+forward, started a hare.  It bolted for a thicket of alders.  Sir\r
+Geoffrey put his gun to his shoulder, but there was something in the\r
+animal's grace of movement that strangely charmed Dorian Gray, and he\r
+cried out at once, "Don't shoot it, Geoffrey.  Let it live."\r
+\r
+"What nonsense, Dorian!" laughed his companion, and as the hare bounded\r
+into the thicket, he fired.  There were two cries heard, the cry of a\r
+hare in pain, which is dreadful, the cry of a man in agony, which is\r
+worse.\r
+\r
+"Good heavens!  I have hit a beater!" exclaimed Sir Geoffrey.  "What an\r
+ass the man was to get in front of the guns!  Stop shooting there!" he\r
+called out at the top of his voice.  "A man is hurt."\r
+\r
+The head-keeper came running up with a stick in his hand.\r
+\r
+"Where, sir?  Where is he?" he shouted.  At the same time, the firing\r
+ceased along the line.\r
+\r
+"Here," answered Sir Geoffrey angrily, hurrying towards the thicket.\r
+"Why on earth don't you keep your men back?  Spoiled my shooting for\r
+the day."\r
+\r
+Dorian watched them as they plunged into the alder-clump, brushing the\r
+lithe swinging branches aside.  In a few moments they emerged, dragging\r
+a body after them into the sunlight.  He turned away in horror.  It\r
+seemed to him that misfortune followed wherever he went.  He heard Sir\r
+Geoffrey ask if the man was really dead, and the affirmative answer of\r
+the keeper.  The wood seemed to him to have become suddenly alive with\r
+faces.  There was the trampling of myriad feet and the low buzz of\r
+voices.  A great copper-breasted pheasant came beating through the\r
+boughs overhead.\r
+\r
+After a few moments--that were to him, in his perturbed state, like\r
+endless hours of pain--he felt a hand laid on his shoulder.  He started\r
+and looked round.\r
+\r
+"Dorian," said Lord Henry, "I had better tell them that the shooting is\r
+stopped for to-day. It would not look well to go on."\r
+\r
+"I wish it were stopped for ever, Harry," he answered bitterly.  "The\r
+whole thing is hideous and cruel.  Is the man ...?"\r
+\r
+He could not finish the sentence.\r
+\r
+"I am afraid so," rejoined Lord Henry.  "He got the whole charge of\r
+shot in his chest.  He must have died almost instantaneously.  Come;\r
+let us go home."\r
+\r
+They walked side by side in the direction of the avenue for nearly\r
+fifty yards without speaking.  Then Dorian looked at Lord Henry and\r
+said, with a heavy sigh, "It is a bad omen, Harry, a very bad omen."\r
+\r
+"What is?" asked Lord Henry.  "Oh! this accident, I suppose.  My dear\r
+fellow, it can't be helped.  It was the man's own fault.  Why did he\r
+get in front of the guns?  Besides, it is nothing to us.  It is rather\r
+awkward for Geoffrey, of course.  It does not do to pepper beaters.  It\r
+makes people think that one is a wild shot.  And Geoffrey is not; he\r
+shoots very straight.  But there is no use talking about the matter."\r
+\r
+Dorian shook his head.  "It is a bad omen, Harry.  I feel as if\r
+something horrible were going to happen to some of us.  To myself,\r
+perhaps," he added, passing his hand over his eyes, with a gesture of\r
+pain.\r
+\r
+The elder man laughed.  "The only horrible thing in the world is _ennui_,\r
+Dorian.  That is the one sin for which there is no forgiveness.  But we\r
+are not likely to suffer from it unless these fellows keep chattering\r
+about this thing at dinner.  I must tell them that the subject is to be\r
+tabooed.  As for omens, there is no such thing as an omen.  Destiny\r
+does not send us heralds.  She is too wise or too cruel for that.\r
+Besides, what on earth could happen to you, Dorian?  You have\r
+everything in the world that a man can want.  There is no one who would\r
+not be delighted to change places with you."\r
+\r
+"There is no one with whom I would not change places, Harry.  Don't\r
+laugh like that.  I am telling you the truth.  The wretched peasant who\r
+has just died is better off than I am.  I have no terror of death.  It\r
+is the coming of death that terrifies me.  Its monstrous wings seem to\r
+wheel in the leaden air around me.  Good heavens! don't you see a man\r
+moving behind the trees there, watching me, waiting for me?"\r
+\r
+Lord Henry looked in the direction in which the trembling gloved hand\r
+was pointing.  "Yes," he said, smiling, "I see the gardener waiting for\r
+you.  I suppose he wants to ask you what flowers you wish to have on\r
+the table to-night. How absurdly nervous you are, my dear fellow!  You\r
+must come and see my doctor, when we get back to town."\r
+\r
+Dorian heaved a sigh of relief as he saw the gardener approaching.  The\r
+man touched his hat, glanced for a moment at Lord Henry in a hesitating\r
+manner, and then produced a letter, which he handed to his master.\r
+"Her Grace told me to wait for an answer," he murmured.\r
+\r
+Dorian put the letter into his pocket.  "Tell her Grace that I am\r
+coming in," he said, coldly.  The man turned round and went rapidly in\r
+the direction of the house.\r
+\r
+"How fond women are of doing dangerous things!" laughed Lord Henry.\r
+"It is one of the qualities in them that I admire most.  A woman will\r
+flirt with anybody in the world as long as other people are looking on."\r
+\r
+"How fond you are of saying dangerous things, Harry!  In the present\r
+instance, you are quite astray.  I like the duchess very much, but I\r
+don't love her."\r
+\r
+"And the duchess loves you very much, but she likes you less, so you\r
+are excellently matched."\r
+\r
+"You are talking scandal, Harry, and there is never any basis for\r
+scandal."\r
+\r
+"The basis of every scandal is an immoral certainty," said Lord Henry,\r
+lighting a cigarette.\r
+\r
+"You would sacrifice anybody, Harry, for the sake of an epigram."\r
+\r
+"The world goes to the altar of its own accord," was the answer.\r
+\r
+"I wish I could love," cried Dorian Gray with a deep note of pathos in\r
+his voice.  "But I seem to have lost the passion and forgotten the\r
+desire.  I am too much concentrated on myself.  My own personality has\r
+become a burden to me.  I want to escape, to go away, to forget.  It\r
+was silly of me to come down here at all.  I think I shall send a wire\r
+to Harvey to have the yacht got ready.  On a yacht one is safe."\r
+\r
+"Safe from what, Dorian?  You are in some trouble.  Why not tell me\r
+what it is?  You know I would help you."\r
+\r
+"I can't tell you, Harry," he answered sadly.  "And I dare say it is\r
+only a fancy of mine.  This unfortunate accident has upset me.  I have\r
+a horrible presentiment that something of the kind may happen to me."\r
+\r
+"What nonsense!"\r
+\r
+"I hope it is, but I can't help feeling it.  Ah! here is the duchess,\r
+looking like Artemis in a tailor-made gown.  You see we have come back,\r
+Duchess."\r
+\r
+"I have heard all about it, Mr. Gray," she answered.  "Poor Geoffrey is\r
+terribly upset.  And it seems that you asked him not to shoot the hare.\r
+How curious!"\r
+\r
+"Yes, it was very curious.  I don't know what made me say it.  Some\r
+whim, I suppose.  It looked the loveliest of little live things.  But I\r
+am sorry they told you about the man.  It is a hideous subject."\r
+\r
+"It is an annoying subject," broke in Lord Henry.  "It has no\r
+psychological value at all.  Now if Geoffrey had done the thing on\r
+purpose, how interesting he would be!  I should like to know some one\r
+who had committed a real murder."\r
+\r
+"How horrid of you, Harry!" cried the duchess.  "Isn't it, Mr. Gray?\r
+Harry, Mr. Gray is ill again.  He is going to faint."\r
+\r
+Dorian drew himself up with an effort and smiled.  "It is nothing,\r
+Duchess," he murmured; "my nerves are dreadfully out of order.  That is\r
+all.  I am afraid I walked too far this morning.  I didn't hear what\r
+Harry said.  Was it very bad?  You must tell me some other time.  I\r
+think I must go and lie down.  You will excuse me, won't you?"\r
+\r
+They had reached the great flight of steps that led from the\r
+conservatory on to the terrace.  As the glass door closed behind\r
+Dorian, Lord Henry turned and looked at the duchess with his slumberous\r
+eyes.  "Are you very much in love with him?" he asked.\r
+\r
+She did not answer for some time, but stood gazing at the landscape.\r
+"I wish I knew," she said at last.\r
+\r
+He shook his head.  "Knowledge would be fatal.  It is the uncertainty\r
+that charms one.  A mist makes things wonderful."\r
+\r
+"One may lose one's way."\r
+\r
+"All ways end at the same point, my dear Gladys."\r
+\r
+"What is that?"\r
+\r
+"Disillusion."\r
+\r
+"It was my _debut_ in life," she sighed.\r
+\r
+"It came to you crowned."\r
+\r
+"I am tired of strawberry leaves."\r
+\r
+"They become you."\r
+\r
+"Only in public."\r
+\r
+"You would miss them," said Lord Henry.\r
+\r
+"I will not part with a petal."\r
+\r
+"Monmouth has ears."\r
+\r
+"Old age is dull of hearing."\r
+\r
+"Has he never been jealous?"\r
+\r
+"I wish he had been."\r
+\r
+He glanced about as if in search of something.  "What are you looking\r
+for?" she inquired.\r
+\r
+"The button from your foil," he answered.  "You have dropped it."\r
+\r
+She laughed.  "I have still the mask."\r
+\r
+"It makes your eyes lovelier," was his reply.\r
+\r
+She laughed again.  Her teeth showed like white seeds in a scarlet\r
+fruit.\r
+\r
+Upstairs, in his own room, Dorian Gray was lying on a sofa, with terror\r
+in every tingling fibre of his body.  Life had suddenly become too\r
+hideous a burden for him to bear.  The dreadful death of the unlucky\r
+beater, shot in the thicket like a wild animal, had seemed to him to\r
+pre-figure death for himself also.  He had nearly swooned at what Lord\r
+Henry had said in a chance mood of cynical jesting.\r
+\r
+At five o'clock he rang his bell for his servant and gave him orders to\r
+pack his things for the night-express to town, and to have the brougham\r
+at the door by eight-thirty. He was determined not to sleep another\r
+night at Selby Royal.  It was an ill-omened place.  Death walked there\r
+in the sunlight.  The grass of the forest had been spotted with blood.\r
+\r
+Then he wrote a note to Lord Henry, telling him that he was going up to\r
+town to consult his doctor and asking him to entertain his guests in\r
+his absence.  As he was putting it into the envelope, a knock came to\r
+the door, and his valet informed him that the head-keeper wished to see\r
+him.  He frowned and bit his lip.  "Send him in," he muttered, after\r
+some moments' hesitation.\r
+\r
+As soon as the man entered, Dorian pulled his chequebook out of a\r
+drawer and spread it out before him.\r
+\r
+"I suppose you have come about the unfortunate accident of this\r
+morning, Thornton?" he said, taking up a pen.\r
+\r
+"Yes, sir," answered the gamekeeper.\r
+\r
+"Was the poor fellow married?  Had he any people dependent on him?"\r
+asked Dorian, looking bored.  "If so, I should not like them to be left\r
+in want, and will send them any sum of money you may think necessary."\r
+\r
+"We don't know who he is, sir.  That is what I took the liberty of\r
+coming to you about."\r
+\r
+"Don't know who he is?" said Dorian, listlessly.  "What do you mean?\r
+Wasn't he one of your men?"\r
+\r
+"No, sir.  Never saw him before.  Seems like a sailor, sir."\r
+\r
+The pen dropped from Dorian Gray's hand, and he felt as if his heart\r
+had suddenly stopped beating.  "A sailor?" he cried out.  "Did you say\r
+a sailor?"\r
+\r
+"Yes, sir.  He looks as if he had been a sort of sailor; tattooed on\r
+both arms, and that kind of thing."\r
+\r
+"Was there anything found on him?" said Dorian, leaning forward and\r
+looking at the man with startled eyes.  "Anything that would tell his\r
+name?"\r
+\r
+"Some money, sir--not much, and a six-shooter. There was no name of any\r
+kind.  A decent-looking man, sir, but rough-like. A sort of sailor we\r
+think."\r
+\r
+Dorian started to his feet.  A terrible hope fluttered past him.  He\r
+clutched at it madly.  "Where is the body?" he exclaimed.  "Quick!  I\r
+must see it at once."\r
+\r
+"It is in an empty stable in the Home Farm, sir.  The folk don't like\r
+to have that sort of thing in their houses.  They say a corpse brings\r
+bad luck."\r
+\r
+"The Home Farm!  Go there at once and meet me.  Tell one of the grooms\r
+to bring my horse round.  No. Never mind.  I'll go to the stables\r
+myself.  It will save time."\r
+\r
+In less than a quarter of an hour, Dorian Gray was galloping down the\r
+long avenue as hard as he could go.  The trees seemed to sweep past him\r
+in spectral procession, and wild shadows to fling themselves across his\r
+path.  Once the mare swerved at a white gate-post and nearly threw him.\r
+He lashed her across the neck with his crop.  She cleft the dusky air\r
+like an arrow.  The stones flew from her hoofs.\r
+\r
+At last he reached the Home Farm.  Two men were loitering in the yard.\r
+He leaped from the saddle and threw the reins to one of them.  In the\r
+farthest stable a light was glimmering.  Something seemed to tell him\r
+that the body was there, and he hurried to the door and put his hand\r
+upon the latch.\r
+\r
+There he paused for a moment, feeling that he was on the brink of a\r
+discovery that would either make or mar his life.  Then he thrust the\r
+door open and entered.\r
+\r
+On a heap of sacking in the far corner was lying the dead body of a man\r
+dressed in a coarse shirt and a pair of blue trousers.  A spotted\r
+handkerchief had been placed over the face.  A coarse candle, stuck in\r
+a bottle, sputtered beside it.\r
+\r
+Dorian Gray shuddered.  He felt that his could not be the hand to take\r
+the handkerchief away, and called out to one of the farm-servants to\r
+come to him.\r
+\r
+"Take that thing off the face.  I wish to see it," he said, clutching\r
+at the door-post for support.\r
+\r
+When the farm-servant had done so, he stepped forward.  A cry of joy\r
+broke from his lips.  The man who had been shot in the thicket was\r
+James Vane.\r
+\r
+He stood there for some minutes looking at the dead body.  As he rode\r
+home, his eyes were full of tears, for he knew he was safe.\r
+\r
+\r
+\r
+CHAPTER 19\r
+\r
+"There is no use your telling me that you are going to be good," cried\r
+Lord Henry, dipping his white fingers into a red copper bowl filled\r
+with rose-water. "You are quite perfect.  Pray, don't change."\r
+\r
+Dorian Gray shook his head.  "No, Harry, I have done too many dreadful\r
+things in my life.  I am not going to do any more.  I began my good\r
+actions yesterday."\r
+\r
+"Where were you yesterday?"\r
+\r
+"In the country, Harry.  I was staying at a little inn by myself."\r
+\r
+"My dear boy," said Lord Henry, smiling, "anybody can be good in the\r
+country.  There are no temptations there.  That is the reason why\r
+people who live out of town are so absolutely uncivilized.\r
+Civilization is not by any means an easy thing to attain to.  There are\r
+only two ways by which man can reach it.  One is by being cultured, the\r
+other by being corrupt.  Country people have no opportunity of being\r
+either, so they stagnate."\r
+\r
+"Culture and corruption," echoed Dorian.  "I have known something of\r
+both.  It seems terrible to me now that they should ever be found\r
+together.  For I have a new ideal, Harry.  I am going to alter.  I\r
+think I have altered."\r
+\r
+"You have not yet told me what your good action was.  Or did you say\r
+you had done more than one?" asked his companion as he spilled into his\r
+plate a little crimson pyramid of seeded strawberries and, through a\r
+perforated, shell-shaped spoon, snowed white sugar upon them.\r
+\r
+"I can tell you, Harry.  It is not a story I could tell to any one\r
+else.  I spared somebody.  It sounds vain, but you understand what I\r
+mean.  She was quite beautiful and wonderfully like Sibyl Vane.  I\r
+think it was that which first attracted me to her.  You remember Sibyl,\r
+don't you?  How long ago that seems!  Well, Hetty was not one of our\r
+own class, of course.  She was simply a girl in a village.  But I\r
+really loved her.  I am quite sure that I loved her.  All during this\r
+wonderful May that we have been having, I used to run down and see her\r
+two or three times a week.  Yesterday she met me in a little orchard.\r
+The apple-blossoms kept tumbling down on her hair, and she was\r
+laughing.  We were to have gone away together this morning at dawn.\r
+Suddenly I determined to leave her as flowerlike as I had found her."\r
+\r
+"I should think the novelty of the emotion must have given you a thrill\r
+of real pleasure, Dorian," interrupted Lord Henry.  "But I can finish\r
+your idyll for you.  You gave her good advice and broke her heart.\r
+That was the beginning of your reformation."\r
+\r
+"Harry, you are horrible!  You mustn't say these dreadful things.\r
+Hetty's heart is not broken.  Of course, she cried and all that.  But\r
+there is no disgrace upon her.  She can live, like Perdita, in her\r
+garden of mint and marigold."\r
+\r
+"And weep over a faithless Florizel," said Lord Henry, laughing, as he\r
+leaned back in his chair.  "My dear Dorian, you have the most curiously\r
+boyish moods.  Do you think this girl will ever be really content now\r
+with any one of her own rank?  I suppose she will be married some day\r
+to a rough carter or a grinning ploughman.  Well, the fact of having\r
+met you, and loved you, will teach her to despise her husband, and she\r
+will be wretched.  From a moral point of view, I cannot say that I\r
+think much of your great renunciation.  Even as a beginning, it is\r
+poor.  Besides, how do you know that Hetty isn't floating at the\r
+present moment in some starlit mill-pond, with lovely water-lilies\r
+round her, like Ophelia?"\r
+\r
+"I can't bear this, Harry!  You mock at everything, and then suggest\r
+the most serious tragedies.  I am sorry I told you now.  I don't care\r
+what you say to me.  I know I was right in acting as I did.  Poor\r
+Hetty!  As I rode past the farm this morning, I saw her white face at\r
+the window, like a spray of jasmine.  Don't let us talk about it any\r
+more, and don't try to persuade me that the first good action I have\r
+done for years, the first little bit of self-sacrifice I have ever\r
+known, is really a sort of sin.  I want to be better.  I am going to be\r
+better.  Tell me something about yourself.  What is going on in town?\r
+I have not been to the club for days."\r
+\r
+"The people are still discussing poor Basil's disappearance."\r
+\r
+"I should have thought they had got tired of that by this time," said\r
+Dorian, pouring himself out some wine and frowning slightly.\r
+\r
+"My dear boy, they have only been talking about it for six weeks, and\r
+the British public are really not equal to the mental strain of having\r
+more than one topic every three months.  They have been very fortunate\r
+lately, however.  They have had my own divorce-case and Alan Campbell's\r
+suicide.  Now they have got the mysterious disappearance of an artist.\r
+Scotland Yard still insists that the man in the grey ulster who left\r
+for Paris by the midnight train on the ninth of November was poor\r
+Basil, and the French police declare that Basil never arrived in Paris\r
+at all.  I suppose in about a fortnight we shall be told that he has\r
+been seen in San Francisco.  It is an odd thing, but every one who\r
+disappears is said to be seen at San Francisco.  It must be a\r
+delightful city, and possess all the attractions of the next world."\r
+\r
+"What do you think has happened to Basil?" asked Dorian, holding up his\r
+Burgundy against the light and wondering how it was that he could\r
+discuss the matter so calmly.\r
+\r
+"I have not the slightest idea.  If Basil chooses to hide himself, it\r
+is no business of mine.  If he is dead, I don't want to think about\r
+him.  Death is the only thing that ever terrifies me.  I hate it."\r
+\r
+"Why?" said the younger man wearily.\r
+\r
+"Because," said Lord Henry, passing beneath his nostrils the gilt\r
+trellis of an open vinaigrette box, "one can survive everything\r
+nowadays except that.  Death and vulgarity are the only two facts in\r
+the nineteenth century that one cannot explain away.  Let us have our\r
+coffee in the music-room, Dorian.  You must play Chopin to me.  The man\r
+with whom my wife ran away played Chopin exquisitely.  Poor Victoria!\r
+I was very fond of her.  The house is rather lonely without her.  Of\r
+course, married life is merely a habit, a bad habit.  But then one\r
+regrets the loss even of one's worst habits.  Perhaps one regrets them\r
+the most.  They are such an essential part of one's personality."\r
+\r
+Dorian said nothing, but rose from the table, and passing into the next\r
+room, sat down to the piano and let his fingers stray across the white\r
+and black ivory of the keys.  After the coffee had been brought in, he\r
+stopped, and looking over at Lord Henry, said, "Harry, did it ever\r
+occur to you that Basil was murdered?"\r
+\r
+Lord Henry yawned.  "Basil was very popular, and always wore a\r
+Waterbury watch.  Why should he have been murdered?  He was not clever\r
+enough to have enemies.  Of course, he had a wonderful genius for\r
+painting.  But a man can paint like Velasquez and yet be as dull as\r
+possible.  Basil was really rather dull.  He only interested me once,\r
+and that was when he told me, years ago, that he had a wild adoration\r
+for you and that you were the dominant motive of his art."\r
+\r
+"I was very fond of Basil," said Dorian with a note of sadness in his\r
+voice.  "But don't people say that he was murdered?"\r
+\r
+"Oh, some of the papers do.  It does not seem to me to be at all\r
+probable.  I know there are dreadful places in Paris, but Basil was not\r
+the sort of man to have gone to them.  He had no curiosity.  It was his\r
+chief defect."\r
+\r
+"What would you say, Harry, if I told you that I had murdered Basil?"\r
+said the younger man.  He watched him intently after he had spoken.\r
+\r
+"I would say, my dear fellow, that you were posing for a character that\r
+doesn't suit you.  All crime is vulgar, just as all vulgarity is crime.\r
+It is not in you, Dorian, to commit a murder.  I am sorry if I hurt\r
+your vanity by saying so, but I assure you it is true.  Crime belongs\r
+exclusively to the lower orders.  I don't blame them in the smallest\r
+degree.  I should fancy that crime was to them what art is to us,\r
+simply a method of procuring extraordinary sensations."\r
+\r
+"A method of procuring sensations?  Do you think, then, that a man who\r
+has once committed a murder could possibly do the same crime again?\r
+Don't tell me that."\r
+\r
+"Oh! anything becomes a pleasure if one does it too often," cried Lord\r
+Henry, laughing.  "That is one of the most important secrets of life.\r
+I should fancy, however, that murder is always a mistake.  One should\r
+never do anything that one cannot talk about after dinner.  But let us\r
+pass from poor Basil.  I wish I could believe that he had come to such\r
+a really romantic end as you suggest, but I can't. I dare say he fell\r
+into the Seine off an omnibus and that the conductor hushed up the\r
+scandal.  Yes:  I should fancy that was his end.  I see him lying now\r
+on his back under those dull-green waters, with the heavy barges\r
+floating over him and long weeds catching in his hair.  Do you know, I\r
+don't think he would have done much more good work.  During the last\r
+ten years his painting had gone off very much."\r
+\r
+Dorian heaved a sigh, and Lord Henry strolled across the room and began\r
+to stroke the head of a curious Java parrot, a large, grey-plumaged\r
+bird with pink crest and tail, that was balancing itself upon a bamboo\r
+perch.  As his pointed fingers touched it, it dropped the white scurf\r
+of crinkled lids over black, glasslike eyes and began to sway backwards\r
+and forwards.\r
+\r
+"Yes," he continued, turning round and taking his handkerchief out of\r
+his pocket; "his painting had quite gone off.  It seemed to me to have\r
+lost something.  It had lost an ideal.  When you and he ceased to be\r
+great friends, he ceased to be a great artist.  What was it separated\r
+you?  I suppose he bored you.  If so, he never forgave you.  It's a\r
+habit bores have.  By the way, what has become of that wonderful\r
+portrait he did of you?  I don't think I have ever seen it since he\r
+finished it.  Oh!  I remember your telling me years ago that you had\r
+sent it down to Selby, and that it had got mislaid or stolen on the\r
+way.  You never got it back?  What a pity! it was really a\r
+masterpiece.  I remember I wanted to buy it.  I wish I had now.  It\r
+belonged to Basil's best period.  Since then, his work was that curious\r
+mixture of bad painting and good intentions that always entitles a man\r
+to be called a representative British artist.  Did you advertise for\r
+it?  You should."\r
+\r
+"I forget," said Dorian.  "I suppose I did.  But I never really liked\r
+it.  I am sorry I sat for it.  The memory of the thing is hateful to\r
+me.  Why do you talk of it?  It used to remind me of those curious\r
+lines in some play--Hamlet, I think--how do they run?--\r
+\r
+    "Like the painting of a sorrow,\r
+     A face without a heart."\r
+\r
+Yes:  that is what it was like."\r
+\r
+Lord Henry laughed.  "If a man treats life artistically, his brain is\r
+his heart," he answered, sinking into an arm-chair.\r
+\r
+Dorian Gray shook his head and struck some soft chords on the piano.\r
+"'Like the painting of a sorrow,'" he repeated, "'a face without a\r
+heart.'"\r
+\r
+The elder man lay back and looked at him with half-closed eyes.  "By\r
+the way, Dorian," he said after a pause, "'what does it profit a man if\r
+he gain the whole world and lose--how does the quotation run?--his own\r
+soul'?"\r
+\r
+The music jarred, and Dorian Gray started and stared at his friend.\r
+"Why do you ask me that, Harry?"\r
+\r
+"My dear fellow," said Lord Henry, elevating his eyebrows in surprise,\r
+"I asked you because I thought you might be able to give me an answer.\r
+That is all.  I was going through the park last Sunday, and close by\r
+the Marble Arch there stood a little crowd of shabby-looking people\r
+listening to some vulgar street-preacher. As I passed by, I heard the\r
+man yelling out that question to his audience.  It struck me as being\r
+rather dramatic.  London is very rich in curious effects of that kind.\r
+A wet Sunday, an uncouth Christian in a mackintosh, a ring of sickly\r
+white faces under a broken roof of dripping umbrellas, and a wonderful\r
+phrase flung into the air by shrill hysterical lips--it was really very\r
+good in its way, quite a suggestion.  I thought of telling the prophet\r
+that art had a soul, but that man had not.  I am afraid, however, he\r
+would not have understood me."\r
+\r
+"Don't, Harry.  The soul is a terrible reality.  It can be bought, and\r
+sold, and bartered away.  It can be poisoned, or made perfect.  There\r
+is a soul in each one of us.  I know it."\r
+\r
+"Do you feel quite sure of that, Dorian?"\r
+\r
+"Quite sure."\r
+\r
+"Ah! then it must be an illusion.  The things one feels absolutely\r
+certain about are never true.  That is the fatality of faith, and the\r
+lesson of romance.  How grave you are!  Don't be so serious.  What have\r
+you or I to do with the superstitions of our age?  No:  we have given\r
+up our belief in the soul.  Play me something.  Play me a nocturne,\r
+Dorian, and, as you play, tell me, in a low voice, how you have kept\r
+your youth.  You must have some secret.  I am only ten years older than\r
+you are, and I am wrinkled, and worn, and yellow.  You are really\r
+wonderful, Dorian.  You have never looked more charming than you do\r
+to-night. You remind me of the day I saw you first.  You were rather\r
+cheeky, very shy, and absolutely extraordinary.  You have changed, of\r
+course, but not in appearance.  I wish you would tell me your secret.\r
+To get back my youth I would do anything in the world, except take\r
+exercise, get up early, or be respectable.  Youth!  There is nothing\r
+like it.  It's absurd to talk of the ignorance of youth.  The only\r
+people to whose opinions I listen now with any respect are people much\r
+younger than myself.  They seem in front of me.  Life has revealed to\r
+them her latest wonder.  As for the aged, I always contradict the aged.\r
+I do it on principle.  If you ask them their opinion on something that\r
+happened yesterday, they solemnly give you the opinions current in\r
+1820, when people wore high stocks, believed in everything, and knew\r
+absolutely nothing.  How lovely that thing you are playing is!  I\r
+wonder, did Chopin write it at Majorca, with the sea weeping round the\r
+villa and the salt spray dashing against the panes?  It is marvellously\r
+romantic.  What a blessing it is that there is one art left to us that\r
+is not imitative!  Don't stop.  I want music to-night. It seems to me\r
+that you are the young Apollo and that I am Marsyas listening to you.\r
+I have sorrows, Dorian, of my own, that even you know nothing of.  The\r
+tragedy of old age is not that one is old, but that one is young.  I am\r
+amazed sometimes at my own sincerity.  Ah, Dorian, how happy you are!\r
+What an exquisite life you have had!  You have drunk deeply of\r
+everything.  You have crushed the grapes against your palate.  Nothing\r
+has been hidden from you.  And it has all been to you no more than the\r
+sound of music.  It has not marred you.  You are still the same."\r
+\r
+"I am not the same, Harry."\r
+\r
+"Yes, you are the same.  I wonder what the rest of your life will be.\r
+Don't spoil it by renunciations.  At present you are a perfect type.\r
+Don't make yourself incomplete.  You are quite flawless now.  You need\r
+not shake your head:  you know you are.  Besides, Dorian, don't deceive\r
+yourself.  Life is not governed by will or intention.  Life is a\r
+question of nerves, and fibres, and slowly built-up cells in which\r
+thought hides itself and passion has its dreams.  You may fancy\r
+yourself safe and think yourself strong.  But a chance tone of colour\r
+in a room or a morning sky, a particular perfume that you had once\r
+loved and that brings subtle memories with it, a line from a forgotten\r
+poem that you had come across again, a cadence from a piece of music\r
+that you had ceased to play--I tell you, Dorian, that it is on things\r
+like these that our lives depend.  Browning writes about that\r
+somewhere; but our own senses will imagine them for us.  There are\r
+moments when the odour of _lilas blanc_ passes suddenly across me, and I\r
+have to live the strangest month of my life over again.  I wish I could\r
+change places with you, Dorian.  The world has cried out against us\r
+both, but it has always worshipped you.  It always will worship you.\r
+You are the type of what the age is searching for, and what it is\r
+afraid it has found.  I am so glad that you have never done anything,\r
+never carved a statue, or painted a picture, or produced anything\r
+outside of yourself!  Life has been your art.  You have set yourself to\r
+music.  Your days are your sonnets."\r
+\r
+Dorian rose up from the piano and passed his hand through his hair.\r
+"Yes, life has been exquisite," he murmured, "but I am not going to\r
+have the same life, Harry.  And you must not say these extravagant\r
+things to me.  You don't know everything about me.  I think that if you\r
+did, even you would turn from me.  You laugh.  Don't laugh."\r
+\r
+"Why have you stopped playing, Dorian?  Go back and give me the\r
+nocturne over again.  Look at that great, honey-coloured moon that\r
+hangs in the dusky air.  She is waiting for you to charm her, and if\r
+you play she will come closer to the earth.  You won't?  Let us go to\r
+the club, then.  It has been a charming evening, and we must end it\r
+charmingly.  There is some one at White's who wants immensely to know\r
+you--young Lord Poole, Bournemouth's eldest son.  He has already copied\r
+your neckties, and has begged me to introduce him to you.  He is quite\r
+delightful and rather reminds me of you."\r
+\r
+"I hope not," said Dorian with a sad look in his eyes.  "But I am tired\r
+to-night, Harry.  I shan't go to the club.  It is nearly eleven, and I\r
+want to go to bed early."\r
+\r
+"Do stay.  You have never played so well as to-night. There was\r
+something in your touch that was wonderful.  It had more expression\r
+than I had ever heard from it before."\r
+\r
+"It is because I am going to be good," he answered, smiling.  "I am a\r
+little changed already."\r
+\r
+"You cannot change to me, Dorian," said Lord Henry.  "You and I will\r
+always be friends."\r
+\r
+"Yet you poisoned me with a book once.  I should not forgive that.\r
+Harry, promise me that you will never lend that book to any one.  It\r
+does harm."\r
+\r
+"My dear boy, you are really beginning to moralize.  You will soon be\r
+going about like the converted, and the revivalist, warning people\r
+against all the sins of which you have grown tired.  You are much too\r
+delightful to do that.  Besides, it is no use.  You and I are what we\r
+are, and will be what we will be.  As for being poisoned by a book,\r
+there is no such thing as that.  Art has no influence upon action.  It\r
+annihilates the desire to act.  It is superbly sterile.  The books that\r
+the world calls immoral are books that show the world its own shame.\r
+That is all.  But we won't discuss literature.  Come round to-morrow. I\r
+am going to ride at eleven.  We might go together, and I will take you\r
+to lunch afterwards with Lady Branksome.  She is a charming woman, and\r
+wants to consult you about some tapestries she is thinking of buying.\r
+Mind you come.  Or shall we lunch with our little duchess?  She says\r
+she never sees you now.  Perhaps you are tired of Gladys?  I thought\r
+you would be.  Her clever tongue gets on one's nerves.  Well, in any\r
+case, be here at eleven."\r
+\r
+"Must I really come, Harry?"\r
+\r
+"Certainly.  The park is quite lovely now.  I don't think there have\r
+been such lilacs since the year I met you."\r
+\r
+"Very well.  I shall be here at eleven," said Dorian.  "Good night,\r
+Harry."  As he reached the door, he hesitated for a moment, as if he\r
+had something more to say.  Then he sighed and went out.\r
+\r
+\r
+\r
+CHAPTER 20\r
+\r
+It was a lovely night, so warm that he threw his coat over his arm and\r
+did not even put his silk scarf round his throat.  As he strolled home,\r
+smoking his cigarette, two young men in evening dress passed him.  He\r
+heard one of them whisper to the other, "That is Dorian Gray." He\r
+remembered how pleased he used to be when he was pointed out, or stared\r
+at, or talked about.  He was tired of hearing his own name now.  Half\r
+the charm of the little village where he had been so often lately was\r
+that no one knew who he was.  He had often told the girl whom he had\r
+lured to love him that he was poor, and she had believed him.  He had\r
+told her once that he was wicked, and she had laughed at him and\r
+answered that wicked people were always very old and very ugly.  What a\r
+laugh she had!--just like a thrush singing.  And how pretty she had\r
+been in her cotton dresses and her large hats!  She knew nothing, but\r
+she had everything that he had lost.\r
+\r
+When he reached home, he found his servant waiting up for him.  He sent\r
+him to bed, and threw himself down on the sofa in the library, and\r
+began to think over some of the things that Lord Henry had said to him.\r
+\r
+Was it really true that one could never change?  He felt a wild longing\r
+for the unstained purity of his boyhood--his rose-white boyhood, as\r
+Lord Henry had once called it.  He knew that he had tarnished himself,\r
+filled his mind with corruption and given horror to his fancy; that he\r
+had been an evil influence to others, and had experienced a terrible\r
+joy in being so; and that of the lives that had crossed his own, it had\r
+been the fairest and the most full of promise that he had brought to\r
+shame.  But was it all irretrievable?  Was there no hope for him?\r
+\r
+Ah! in what a monstrous moment of pride and passion he had prayed that\r
+the portrait should bear the burden of his days, and he keep the\r
+unsullied splendour of eternal youth!  All his failure had been due to\r
+that.  Better for him that each sin of his life had brought its sure\r
+swift penalty along with it.  There was purification in punishment.\r
+Not "Forgive us our sins" but "Smite us for our iniquities" should be\r
+the prayer of man to a most just God.\r
+\r
+The curiously carved mirror that Lord Henry had given to him, so many\r
+years ago now, was standing on the table, and the white-limbed Cupids\r
+laughed round it as of old.  He took it up, as he had done on that\r
+night of horror when he had first noted the change in the fatal\r
+picture, and with wild, tear-dimmed eyes looked into its polished\r
+shield.  Once, some one who had terribly loved him had written to him a\r
+mad letter, ending with these idolatrous words: "The world is changed\r
+because you are made of ivory and gold.  The curves of your lips\r
+rewrite history."  The phrases came back to his memory, and he repeated\r
+them over and over to himself.  Then he loathed his own beauty, and\r
+flinging the mirror on the floor, crushed it into silver splinters\r
+beneath his heel.  It was his beauty that had ruined him, his beauty\r
+and the youth that he had prayed for.  But for those two things, his\r
+life might have been free from stain.  His beauty had been to him but a\r
+mask, his youth but a mockery.  What was youth at best?  A green, an\r
+unripe time, a time of shallow moods, and sickly thoughts.  Why had he\r
+worn its livery?  Youth had spoiled him.\r
+\r
+It was better not to think of the past.  Nothing could alter that.  It\r
+was of himself, and of his own future, that he had to think.  James\r
+Vane was hidden in a nameless grave in Selby churchyard.  Alan Campbell\r
+had shot himself one night in his laboratory, but had not revealed the\r
+secret that he had been forced to know.  The excitement, such as it\r
+was, over Basil Hallward's disappearance would soon pass away.  It was\r
+already waning.  He was perfectly safe there.  Nor, indeed, was it the\r
+death of Basil Hallward that weighed most upon his mind.  It was the\r
+living death of his own soul that troubled him.  Basil had painted the\r
+portrait that had marred his life.  He could not forgive him that.  It\r
+was the portrait that had done everything.  Basil had said things to\r
+him that were unbearable, and that he had yet borne with patience.  The\r
+murder had been simply the madness of a moment.  As for Alan Campbell,\r
+his suicide had been his own act.  He had chosen to do it.  It was\r
+nothing to him.\r
+\r
+A new life!  That was what he wanted.  That was what he was waiting\r
+for.  Surely he had begun it already.  He had spared one innocent\r
+thing, at any rate.  He would never again tempt innocence.  He would be\r
+good.\r
+\r
+As he thought of Hetty Merton, he began to wonder if the portrait in\r
+the locked room had changed.  Surely it was not still so horrible as it\r
+had been?  Perhaps if his life became pure, he would be able to expel\r
+every sign of evil passion from the face.  Perhaps the signs of evil\r
+had already gone away.  He would go and look.\r
+\r
+He took the lamp from the table and crept upstairs.  As he unbarred the\r
+door, a smile of joy flitted across his strangely young-looking face\r
+and lingered for a moment about his lips.  Yes, he would be good, and\r
+the hideous thing that he had hidden away would no longer be a terror\r
+to him.  He felt as if the load had been lifted from him already.\r
+\r
+He went in quietly, locking the door behind him, as was his custom, and\r
+dragged the purple hanging from the portrait.  A cry of pain and\r
+indignation broke from him.  He could see no change, save that in the\r
+eyes there was a look of cunning and in the mouth the curved wrinkle of\r
+the hypocrite.  The thing was still loathsome--more loathsome, if\r
+possible, than before--and the scarlet dew that spotted the hand seemed\r
+brighter, and more like blood newly spilled.  Then he trembled.  Had it\r
+been merely vanity that had made him do his one good deed?  Or the\r
+desire for a new sensation, as Lord Henry had hinted, with his mocking\r
+laugh?  Or that passion to act a part that sometimes makes us do things\r
+finer than we are ourselves?  Or, perhaps, all these?  And why was the\r
+red stain larger than it had been?  It seemed to have crept like a\r
+horrible disease over the wrinkled fingers.  There was blood on the\r
+painted feet, as though the thing had dripped--blood even on the hand\r
+that had not held the knife.  Confess?  Did it mean that he was to\r
+confess?  To give himself up and be put to death?  He laughed.  He felt\r
+that the idea was monstrous.  Besides, even if he did confess, who\r
+would believe him?  There was no trace of the murdered man anywhere.\r
+Everything belonging to him had been destroyed.  He himself had burned\r
+what had been below-stairs. The world would simply say that he was mad.\r
+They would shut him up if he persisted in his story.... Yet it was\r
+his duty to confess, to suffer public shame, and to make public\r
+atonement.  There was a God who called upon men to tell their sins to\r
+earth as well as to heaven.  Nothing that he could do would cleanse him\r
+till he had told his own sin.  His sin?  He shrugged his shoulders.\r
+The death of Basil Hallward seemed very little to him.  He was thinking\r
+of Hetty Merton.  For it was an unjust mirror, this mirror of his soul\r
+that he was looking at.  Vanity?  Curiosity?  Hypocrisy?  Had there\r
+been nothing more in his renunciation than that?  There had been\r
+something more.  At least he thought so.  But who could tell? ... No.\r
+There had been nothing more.  Through vanity he had spared her.  In\r
+hypocrisy he had worn the mask of goodness.  For curiosity's sake he\r
+had tried the denial of self.  He recognized that now.\r
+\r
+But this murder--was it to dog him all his life?  Was he always to be\r
+burdened by his past?  Was he really to confess?  Never.  There was\r
+only one bit of evidence left against him.  The picture itself--that\r
+was evidence.  He would destroy it.  Why had he kept it so long?  Once\r
+it had given him pleasure to watch it changing and growing old.  Of\r
+late he had felt no such pleasure.  It had kept him awake at night.\r
+When he had been away, he had been filled with terror lest other eyes\r
+should look upon it.  It had brought melancholy across his passions.\r
+Its mere memory had marred many moments of joy.  It had been like\r
+conscience to him.  Yes, it had been conscience.  He would destroy it.\r
+\r
+He looked round and saw the knife that had stabbed Basil Hallward.  He\r
+had cleaned it many times, till there was no stain left upon it.  It\r
+was bright, and glistened.  As it had killed the painter, so it would\r
+kill the painter's work, and all that that meant.  It would kill the\r
+past, and when that was dead, he would be free.  It would kill this\r
+monstrous soul-life, and without its hideous warnings, he would be at\r
+peace.  He seized the thing, and stabbed the picture with it.\r
+\r
+There was a cry heard, and a crash.  The cry was so horrible in its\r
+agony that the frightened servants woke and crept out of their rooms.\r
+Two gentlemen, who were passing in the square below, stopped and looked\r
+up at the great house.  They walked on till they met a policeman and\r
+brought him back.  The man rang the bell several times, but there was\r
+no answer.  Except for a light in one of the top windows, the house was\r
+all dark.  After a time, he went away and stood in an adjoining portico\r
+and watched.\r
+\r
+"Whose house is that, Constable?" asked the elder of the two gentlemen.\r
+\r
+"Mr. Dorian Gray's, sir," answered the policeman.\r
+\r
+They looked at each other, as they walked away, and sneered.  One of\r
+them was Sir Henry Ashton's uncle.\r
+\r
+Inside, in the servants' part of the house, the half-clad domestics\r
+were talking in low whispers to each other.  Old Mrs. Leaf was crying\r
+and wringing her hands.  Francis was as pale as death.\r
+\r
+After about a quarter of an hour, he got the coachman and one of the\r
+footmen and crept upstairs.  They knocked, but there was no reply.\r
+They called out.  Everything was still.  Finally, after vainly trying\r
+to force the door, they got on the roof and dropped down on to the\r
+balcony.  The windows yielded easily--their bolts were old.\r
+\r
+When they entered, they found hanging upon the wall a splendid portrait\r
+of their master as they had last seen him, in all the wonder of his\r
+exquisite youth and beauty.  Lying on the floor was a dead man, in\r
+evening dress, with a knife in his heart.  He was withered, wrinkled,\r
+and loathsome of visage.  It was not till they had examined the rings\r
+that they recognized who it was.\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+End of Project Gutenberg's The Picture of Dorian Gray, by Oscar Wilde\r
+\r
+*** END OF THIS PROJECT GUTENBERG EBOOK THE PICTURE OF DORIAN GRAY ***\r
+\r
+***** This file should be named 174.txt or 174.zip *****\r
+This and all associated files of various formats will be found in:\r
+        http://www.gutenberg.org/1/7/174/\r
+\r
+Produced by Judith Boss.  HTML version by Al Haines.\r
+\r
+Updated editions will replace the previous one--the old editions\r
+will be renamed.\r
+\r
+Creating the works from public domain print editions means that no\r
+one owns a United States copyright in these works, so the Foundation\r
+(and you!) can copy and distribute it in the United States without\r
+permission and without paying copyright royalties.  Special rules,\r
+set forth in the General Terms of Use part of this license, apply to\r
+copying and distributing Project Gutenberg-tm electronic works to\r
+protect the PROJECT GUTENBERG-tm concept and trademark.  Project\r
+Gutenberg is a registered trademark, and may not be used if you\r
+charge for the eBooks, unless you receive specific permission.  If you\r
+do not charge anything for copies of this eBook, complying with the\r
+rules is very easy.  You may use this eBook for nearly any purpose\r
+such as creation of derivative works, reports, performances and\r
+research.  They may be modified and printed and given away--you may do\r
+practically ANYTHING with public domain eBooks.  Redistribution is\r
+subject to the trademark license, especially commercial\r
+redistribution.\r
+\r
+\r
+\r
+*** START: FULL LICENSE ***\r
+\r
+THE FULL PROJECT GUTENBERG LICENSE\r
+PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\r
+\r
+To protect the Project Gutenberg-tm mission of promoting the free\r
+distribution of electronic works, by using or distributing this work\r
+(or any other work associated in any way with the phrase "Project\r
+Gutenberg"), you agree to comply with all the terms of the Full Project\r
+Gutenberg-tm License (available with this file or online at\r
+http://gutenberg.net/license).\r
+\r
+\r
+Section 1.  General Terms of Use and Redistributing Project Gutenberg-tm\r
+electronic works\r
+\r
+1.A.  By reading or using any part of this Project Gutenberg-tm\r
+electronic work, you indicate that you have read, understand, agree to\r
+and accept all the terms of this license and intellectual property\r
+(trademark/copyright) agreement.  If you do not agree to abide by all\r
+the terms of this agreement, you must cease using and return or destroy\r
+all copies of Project Gutenberg-tm electronic works in your possession.\r
+If you paid a fee for obtaining a copy of or access to a Project\r
+Gutenberg-tm electronic work and you do not agree to be bound by the\r
+terms of this agreement, you may obtain a refund from the person or\r
+entity to whom you paid the fee as set forth in paragraph 1.E.8.\r
+\r
+1.B.  "Project Gutenberg" is a registered trademark.  It may only be\r
+used on or associated in any way with an electronic work by people who\r
+agree to be bound by the terms of this agreement.  There are a few\r
+things that you can do with most Project Gutenberg-tm electronic works\r
+even without complying with the full terms of this agreement.  See\r
+paragraph 1.C below.  There are a lot of things you can do with Project\r
+Gutenberg-tm electronic works if you follow the terms of this agreement\r
+and help preserve free future access to Project Gutenberg-tm electronic\r
+works.  See paragraph 1.E below.\r
+\r
+1.C.  The Project Gutenberg Literary Archive Foundation ("the Foundation"\r
+or PGLAF), owns a compilation copyright in the collection of Project\r
+Gutenberg-tm electronic works.  Nearly all the individual works in the\r
+collection are in the public domain in the United States.  If an\r
+individual work is in the public domain in the United States and you are\r
+located in the United States, we do not claim a right to prevent you from\r
+copying, distributing, performing, displaying or creating derivative\r
+works based on the work as long as all references to Project Gutenberg\r
+are removed.  Of course, we hope that you will support the Project\r
+Gutenberg-tm mission of promoting free access to electronic works by\r
+freely sharing Project Gutenberg-tm works in compliance with the terms of\r
+this agreement for keeping the Project Gutenberg-tm name associated with\r
+the work.  You can easily comply with the terms of this agreement by\r
+keeping this work in the same format with its attached full Project\r
+Gutenberg-tm License when you share it without charge with others.\r
+\r
+1.D.  The copyright laws of the place where you are located also govern\r
+what you can do with this work.  Copyright laws in most countries are in\r
+a constant state of change.  If you are outside the United States, check\r
+the laws of your country in addition to the terms of this agreement\r
+before downloading, copying, displaying, performing, distributing or\r
+creating derivative works based on this work or any other Project\r
+Gutenberg-tm work.  The Foundation makes no representations concerning\r
+the copyright status of any work in any country outside the United\r
+States.\r
+\r
+1.E.  Unless you have removed all references to Project Gutenberg:\r
+\r
+1.E.1.  The following sentence, with active links to, or other immediate\r
+access to, the full Project Gutenberg-tm License must appear prominently\r
+whenever any copy of a Project Gutenberg-tm work (any work on which the\r
+phrase "Project Gutenberg" appears, or with which the phrase "Project\r
+Gutenberg" is associated) is accessed, displayed, performed, viewed,\r
+copied or distributed:\r
+\r
+This eBook is for the use of anyone anywhere at no cost and with\r
+almost no restrictions whatsoever.  You may copy it, give it away or\r
+re-use it under the terms of the Project Gutenberg License included\r
+with this eBook or online at www.gutenberg.net\r
+\r
+1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\r
+from the public domain (does not contain a notice indicating that it is\r
+posted with permission of the copyright holder), the work can be copied\r
+and distributed to anyone in the United States without paying any fees\r
+or charges.  If you are redistributing or providing access to a work\r
+with the phrase "Project Gutenberg" associated with or appearing on the\r
+work, you must comply either with the requirements of paragraphs 1.E.1\r
+through 1.E.7 or obtain permission for the use of the work and the\r
+Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\r
+1.E.9.\r
+\r
+1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\r
+with the permission of the copyright holder, your use and distribution\r
+must comply with both paragraphs 1.E.1 through 1.E.7 and any additional\r
+terms imposed by the copyright holder.  Additional terms will be linked\r
+to the Project Gutenberg-tm License for all works posted with the\r
+permission of the copyright holder found at the beginning of this work.\r
+\r
+1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\r
+License terms from this work, or any files containing a part of this\r
+work or any other work associated with Project Gutenberg-tm.\r
+\r
+1.E.5.  Do not copy, display, perform, distribute or redistribute this\r
+electronic work, or any part of this electronic work, without\r
+prominently displaying the sentence set forth in paragraph 1.E.1 with\r
+active links or immediate access to the full terms of the Project\r
+Gutenberg-tm License.\r
+\r
+1.E.6.  You may convert to and distribute this work in any binary,\r
+compressed, marked up, nonproprietary or proprietary form, including any\r
+word processing or hypertext form.  However, if you provide access to or\r
+distribute copies of a Project Gutenberg-tm work in a format other than\r
+"Plain Vanilla ASCII" or other format used in the official version\r
+posted on the official Project Gutenberg-tm web site (www.gutenberg.net),\r
+you must, at no additional cost, fee or expense to the user, provide a\r
+copy, a means of exporting a copy, or a means of obtaining a copy upon\r
+request, of the work in its original "Plain Vanilla ASCII" or other\r
+form.  Any alternate format must include the full Project Gutenberg-tm\r
+License as specified in paragraph 1.E.1.\r
+\r
+1.E.7.  Do not charge a fee for access to, viewing, displaying,\r
+performing, copying or distributing any Project Gutenberg-tm works\r
+unless you comply with paragraph 1.E.8 or 1.E.9.\r
+\r
+1.E.8.  You may charge a reasonable fee for copies of or providing\r
+access to or distributing Project Gutenberg-tm electronic works provided\r
+that\r
+\r
+- You pay a royalty fee of 20% of the gross profits you derive from\r
+     the use of Project Gutenberg-tm works calculated using the method\r
+     you already use to calculate your applicable taxes.  The fee is\r
+     owed to the owner of the Project Gutenberg-tm trademark, but he\r
+     has agreed to donate royalties under this paragraph to the\r
+     Project Gutenberg Literary Archive Foundation.  Royalty payments\r
+     must be paid within 60 days following each date on which you\r
+     prepare (or are legally required to prepare) your periodic tax\r
+     returns.  Royalty payments should be clearly marked as such and\r
+     sent to the Project Gutenberg Literary Archive Foundation at the\r
+     address specified in Section 4, "Information about donations to\r
+     the Project Gutenberg Literary Archive Foundation."\r
+\r
+- You provide a full refund of any money paid by a user who notifies\r
+     you in writing (or by e-mail) within 30 days of receipt that s/he\r
+     does not agree to the terms of the full Project Gutenberg-tm\r
+     License.  You must require such a user to return or\r
+     destroy all copies of the works possessed in a physical medium\r
+     and discontinue all use of and all access to other copies of\r
+     Project Gutenberg-tm works.\r
+\r
+- You provide, in accordance with paragraph 1.F.3, a full refund of any\r
+     money paid for a work or a replacement copy, if a defect in the\r
+     electronic work is discovered and reported to you within 90 days\r
+     of receipt of the work.\r
+\r
+- You comply with all other terms of this agreement for free\r
+     distribution of Project Gutenberg-tm works.\r
+\r
+1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\r
+electronic work or group of works on different terms than are set\r
+forth in this agreement, you must obtain permission in writing from\r
+both the Project Gutenberg Literary Archive Foundation and Michael\r
+Hart, the owner of the Project Gutenberg-tm trademark.  Contact the\r
+Foundation as set forth in Section 3 below.\r
+\r
+1.F.\r
+\r
+1.F.1.  Project Gutenberg volunteers and employees expend considerable\r
+effort to identify, do copyright research on, transcribe and proofread\r
+public domain works in creating the Project Gutenberg-tm\r
+collection.  Despite these efforts, Project Gutenberg-tm electronic\r
+works, and the medium on which they may be stored, may contain\r
+"Defects," such as, but not limited to, incomplete, inaccurate or\r
+corrupt data, transcription errors, a copyright or other intellectual\r
+property infringement, a defective or damaged disk or other medium, a\r
+computer virus, or computer codes that damage or cannot be read by\r
+your equipment.\r
+\r
+1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right\r
+of Replacement or Refund" described in paragraph 1.F.3, the Project\r
+Gutenberg Literary Archive Foundation, the owner of the Project\r
+Gutenberg-tm trademark, and any other party distributing a Project\r
+Gutenberg-tm electronic work under this agreement, disclaim all\r
+liability to you for damages, costs and expenses, including legal\r
+fees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\r
+LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\r
+PROVIDED IN PARAGRAPH F3.  YOU AGREE THAT THE FOUNDATION, THE\r
+TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\r
+LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\r
+INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\r
+DAMAGE.\r
+\r
+1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\r
+defect in this electronic work within 90 days of receiving it, you can\r
+receive a refund of the money (if any) you paid for it by sending a\r
+written explanation to the person you received the work from.  If you\r
+received the work on a physical medium, you must return the medium with\r
+your written explanation.  The person or entity that provided you with\r
+the defective work may elect to provide a replacement copy in lieu of a\r
+refund.  If you received the work electronically, the person or entity\r
+providing it to you may choose to give you a second opportunity to\r
+receive the work electronically in lieu of a refund.  If the second copy\r
+is also defective, you may demand a refund in writing without further\r
+opportunities to fix the problem.\r
+\r
+1.F.4.  Except for the limited right of replacement or refund set forth\r
+in paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER\r
+WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\r
+WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\r
+\r
+1.F.5.  Some states do not allow disclaimers of certain implied\r
+warranties or the exclusion or limitation of certain types of damages.\r
+If any disclaimer or limitation set forth in this agreement violates the\r
+law of the state applicable to this agreement, the agreement shall be\r
+interpreted to make the maximum disclaimer or limitation permitted by\r
+the applicable state law.  The invalidity or unenforceability of any\r
+provision of this agreement shall not void the remaining provisions.\r
+\r
+1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\r
+trademark owner, any agent or employee of the Foundation, anyone\r
+providing copies of Project Gutenberg-tm electronic works in accordance\r
+with this agreement, and any volunteers associated with the production,\r
+promotion and distribution of Project Gutenberg-tm electronic works,\r
+harmless from all liability, costs and expenses, including legal fees,\r
+that arise directly or indirectly from any of the following which you do\r
+or cause to occur: (a) distribution of this or any Project Gutenberg-tm\r
+work, (b) alteration, modification, or additions or deletions to any\r
+Project Gutenberg-tm work, and (c) any Defect you cause.\r
+\r
+\r
+Section  2.  Information about the Mission of Project Gutenberg-tm\r
+\r
+Project Gutenberg-tm is synonymous with the free distribution of\r
+electronic works in formats readable by the widest variety of computers\r
+including obsolete, old, middle-aged and new computers.  It exists\r
+because of the efforts of hundreds of volunteers and donations from\r
+people in all walks of life.\r
+\r
+Volunteers and financial support to provide volunteers with the\r
+assistance they need, is critical to reaching Project Gutenberg-tm's\r
+goals and ensuring that the Project Gutenberg-tm collection will\r
+remain freely available for generations to come.  In 2001, the Project\r
+Gutenberg Literary Archive Foundation was created to provide a secure\r
+and permanent future for Project Gutenberg-tm and future generations.\r
+To learn more about the Project Gutenberg Literary Archive Foundation\r
+and how your efforts and donations can help, see Sections 3 and 4\r
+and the Foundation web page at http://www.pglaf.org.\r
+\r
+\r
+Section 3.  Information about the Project Gutenberg Literary Archive\r
+Foundation\r
+\r
+The Project Gutenberg Literary Archive Foundation is a non profit\r
+501(c)(3) educational corporation organized under the laws of the\r
+state of Mississippi and granted tax exempt status by the Internal\r
+Revenue Service.  The Foundation's EIN or federal tax identification\r
+number is 64-6221541.  Its 501(c)(3) letter is posted at\r
+http://pglaf.org/fundraising.  Contributions to the Project Gutenberg\r
+Literary Archive Foundation are tax deductible to the full extent\r
+permitted by U.S. federal laws and your state's laws.\r
+\r
+The Foundation's principal office is located at 4557 Melan Dr. S.\r
+Fairbanks, AK, 99712., but its volunteers and employees are scattered\r
+throughout numerous locations.  Its business office is located at\r
+809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\r
+business@pglaf.org.  Email contact links and up to date contact\r
+information can be found at the Foundation's web site and official\r
+page at http://pglaf.org\r
+\r
+For additional contact information:\r
+     Dr. Gregory B. Newby\r
+     Chief Executive and Director\r
+     gbnewby@pglaf.org\r
+\r
+\r
+Section 4.  Information about Donations to the Project Gutenberg\r
+Literary Archive Foundation\r
+\r
+Project Gutenberg-tm depends upon and cannot survive without wide\r
+spread public support and donations to carry out its mission of\r
+increasing the number of public domain and licensed works that can be\r
+freely distributed in machine readable form accessible by the widest\r
+array of equipment including outdated equipment.  Many small donations\r
+($1 to $5,000) are particularly important to maintaining tax exempt\r
+status with the IRS.\r
+\r
+The Foundation is committed to complying with the laws regulating\r
+charities and charitable donations in all 50 states of the United\r
+States.  Compliance requirements are not uniform and it takes a\r
+considerable effort, much paperwork and many fees to meet and keep up\r
+with these requirements.  We do not solicit donations in locations\r
+where we have not received written confirmation of compliance.  To\r
+SEND DONATIONS or determine the status of compliance for any\r
+particular state visit http://pglaf.org\r
+\r
+While we cannot and do not solicit contributions from states where we\r
+have not met the solicitation requirements, we know of no prohibition\r
+against accepting unsolicited donations from donors in such states who\r
+approach us with offers to donate.\r
+\r
+International donations are gratefully accepted, but we cannot make\r
+any statements concerning tax treatment of donations received from\r
+outside the United States.  U.S. laws alone swamp our small staff.\r
+\r
+Please check the Project Gutenberg Web pages for current donation\r
+methods and addresses.  Donations are accepted in a number of other\r
+ways including including checks, online payments and credit card\r
+donations.  To donate, please visit: http://pglaf.org/donate\r
+\r
+\r
+Section 5.  General Information About Project Gutenberg-tm electronic\r
+works.\r
+\r
+Professor Michael S. Hart is the originator of the Project Gutenberg-tm\r
+concept of a library of electronic works that could be freely shared\r
+with anyone.  For thirty years, he produced and distributed Project\r
+Gutenberg-tm eBooks with only a loose network of volunteer support.\r
+\r
+\r
+Project Gutenberg-tm eBooks are often created from several printed\r
+editions, all of which are confirmed as Public Domain in the U.S.\r
+unless a copyright notice is included.  Thus, we do not necessarily\r
+keep eBooks in compliance with any particular paper edition.\r
+\r
+\r
+Most people start at our Web site which has the main PG search facility:\r
+\r
+     http://www.gutenberg.net\r
+\r
+This Web site includes information about Project Gutenberg-tm,\r
+including how to make donations to the Project Gutenberg Literary\r
+Archive Foundation, how to help produce our new eBooks, and how to\r
+subscribe to our email newsletter to hear about new eBooks.\r
diff --git a/minimal-examples/api-tests/api-test-gencrypto/CMakeLists.txt b/minimal-examples/api-tests/api-test-gencrypto/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4e97252
--- /dev/null
@@ -0,0 +1,80 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-gencrypto)
+set(SRCS main.c lws-genaes.c lws-genec.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_GENCRYPTO 1 requirements)
+require_lws_config(LWS_WITH_JOSE 1 requirements)
+
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
+
diff --git a/minimal-examples/api-tests/api-test-gencrypto/README.md b/minimal-examples/api-tests/api-test-gencrypto/README.md
new file mode 100644 (file)
index 0000000..ab8ff0b
--- /dev/null
@@ -0,0 +1,26 @@
+# lws api test gencrypto
+
+Demonstrates how to use and performs selftests for Generic Crypto,
+which works the same whether the tls backend is OpenSSL or mbedTLS
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+```
+ $ ./lws-api-test-gencrypto
+[2018/12/05 08:30:27:1342] USER: LWS gencrypto apis tests
+[2018/12/05 08:30:27:1343] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2018/12/05 08:30:27:1343] NOTICE: created client ssl context for default
+[2018/12/05 08:30:27:1344] NOTICE: test_genaes: selftest OK
+[2018/12/05 08:30:27:1344] USER: Completed: PASS
+```
+
diff --git a/minimal-examples/api-tests/api-test-gencrypto/lws-genaes.c b/minimal-examples/api-tests/api-test-gencrypto/lws-genaes.c
new file mode 100644 (file)
index 0000000..9063647
--- /dev/null
@@ -0,0 +1,801 @@
+/*
+ * lws-api-test-gencrypto - lws-genaes
+ *
+ * Written in 2010-2018 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+static const uint8_t
+       /*
+        * produced with (plaintext.txt contains "test plaintext\0\0")
+        *
+        * openssl enc -aes256 \
+        *   -K "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210" \
+        *   -iv "0123456789abcdeffedcba9876543210"
+        *   -in plaintext.txt -out out.enc
+        *
+        */
+       *cbc256 = (uint8_t *)"test plaintext\0\0",
+       cbc256_enc[] = {
+               0x2b, 0x5d, 0xb2, 0xa8, 0x5a, 0x5a, 0xf4, 0x2e,
+               0xf7, 0xf9, 0xc5, 0x3c, 0x73, 0xef, 0x40, 0x88,
+       }, cbc256_iv[] = {
+               0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+               0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+       }, cbc256_key[] = {
+               0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+               0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+               0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+               0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+       }
+;
+
+static int
+test_genaes_cbc(void)
+{
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32];
+
+       /*
+        * As part of a jwk, these are allocated.  But here we just use one as
+        * a wrapper on a static binary key.
+        */
+       e.buf = (uint8_t *)cbc256_key;
+       e.len = sizeof(cbc256_key);
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_CBC, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, cbc256, 16, res, (uint8_t *)cbc256_iv,
+                            NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy enc failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(cbc256_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_CBC, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, (uint8_t *)cbc256_iv,
+                            NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy dec failed\n", __func__);
+               lwsl_hexdump_notice(res1, 16);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(cbc256, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+static const uint8_t
+/*
+ * produced with (plaintext.txt contains "test plaintext\0\0")
+ *
+ * openssl enc -aes-128-cfb \
+ *   -K "0123456789abcdeffedcba9876543210" \
+ *   -iv "0123456789abcdeffedcba9876543210"
+ *   -in plaintext.txt -out out.enc
+ *
+ */
+*cfb128        = (uint8_t *)"test plaintext\0\0",
+cfb128_enc[] = {
+       0xd2, 0x11, 0x86, 0xd7, 0xa9, 0x55, 0x59, 0x04,
+       0x4f, 0x63, 0x7c, 0xb9, 0xc6, 0xa1, 0xc9, 0x71
+}, cfb128_iv[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+}, cfb128_key[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+};
+
+static int
+test_genaes_cfb128(void)
+{
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32];
+       size_t iv_off = 0;
+
+       e.buf = (uint8_t *)cfb128_key;
+       e.len = sizeof(cfb128_key);
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_CFB128, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, cfb128, 16, res, (uint8_t *)cfb128_iv,
+                            NULL, &iv_off, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(cfb128_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       iv_off = 0;
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_CFB128, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, (uint8_t *)cfb128_iv,
+                            NULL, &iv_off, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(cfb128, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res1, 16);
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+static const uint8_t
+/*
+ * produced with (plaintext.txt contains "test plaintext\0\0")
+ *
+ * openssl enc -aes-128-cfb8 \
+ *   -K "0123456789abcdeffedcba9876543210" \
+ *   -iv "0123456789abcdeffedcba9876543210"
+ *   -in plaintext.txt -out out.enc
+ *
+ */
+*cfb8  = (uint8_t *)"test plaintext\0\0",
+cfb8_enc[] = {
+       0xd2, 0x91, 0x06, 0x2d, 0x1b, 0x1e, 0x9b, 0x39,
+       0xa6, 0x65, 0x8e, 0xbe, 0x68, 0x32, 0x3d, 0xab
+}, cfb8_iv[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+}, cfb8_key[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+};
+
+static int
+test_genaes_cfb8(void)
+{
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32];
+
+       e.buf = (uint8_t *)cfb8_key;
+       e.len = sizeof(cfb8_key);
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_CFB8, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, cfb8, 16, res, (uint8_t *)cfb8_iv,
+                            NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(cfb8_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_CFB8, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, (uint8_t *)cfb8_iv,
+                            NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(cfb8, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res1, 16);
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+static const uint8_t
+/*
+ * produced with (plaintext.txt contains "test plaintext\0\0")
+ *
+ * openssl enc -aes-128-ctr \
+ *   -K "0123456789abcdeffedcba9876543210" \
+ *   -iv "0123456789abcdeffedcba9876543210"
+ *   -in plaintext.txt -out out.enc
+ *
+ */
+*ctr   = (uint8_t *)"test plaintext\0\0",
+ctr_enc[] = {
+       0xd2, 0x11, 0x86, 0xd7, 0xa9, 0x55, 0x59, 0x04,
+       0x4f, 0x63, 0x7c, 0xb9, 0xc6, 0xa1, 0xc9, 0x71
+}, ctr_iv[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+}, ctr_key[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+};
+
+static int
+test_genaes_ctr(void)
+{
+       uint8_t nonce_counter[16], sb[16];
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32];
+       size_t nc_off = 0;
+
+       e.buf = (uint8_t *)ctr_key;
+       e.len = sizeof(ctr_key);
+
+       memset(sb, 0, sizeof(nonce_counter));
+       memcpy(nonce_counter, ctr_iv, sizeof(ctr_iv));
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_CTR, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, ctr, 16, res, nonce_counter, sb, &nc_off, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(ctr_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       nc_off = 0;
+       memset(sb , 0, sizeof(nonce_counter));
+       memcpy(nonce_counter, ctr_iv, sizeof(ctr_iv));
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_CTR, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, nonce_counter, sb, &nc_off, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(ctr, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res1, 16);
+               return -1;
+       }
+
+       lws_explicit_bzero(sb, sizeof(sb));
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+static const uint8_t
+/*
+ * produced with (plaintext.txt contains "test plaintext\0\0")
+ *
+ * openssl enc -aes-128-ecb \
+ *   -K "0123456789abcdeffedcba9876543210" \
+ *   -in plaintext.txt -out out.enc
+ *
+ */
+*ecb   = (uint8_t *)"test plaintext\0\0",
+ecb_enc[] = {
+       0xf3, 0xe5, 0x6c, 0x80, 0x3a, 0xf1, 0xc4, 0xa0,
+       0x7e, 0xdf, 0x86, 0x0f, 0x6d, 0xca, 0x5d, 0x36,
+       0x17, 0x22, 0x37, 0x42, 0x47, 0x41, 0x67, 0x7d,
+       0x99, 0x25, 0x02, 0x6b, 0x6b, 0x8f, 0x9c, 0x7f
+}, ecb_key[] = {
+       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+       0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+};
+
+static int
+test_genaes_ecb(void)
+{
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32];
+
+       /*
+        * As part of a jwk, these are allocated.  But here we just use one as
+        * a wrapper on a static binary key.
+        */
+       e.buf = (uint8_t *)ecb_key;
+       e.len = sizeof(ecb_key);
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_ECB, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, ecb, 16, res, NULL, NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(ecb_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_ECB, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, NULL, NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(ecb, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+#if defined(MBEDTLS_CONFIG_H) && !defined(MBEDTLS_CIPHER_MODE_OFB)
+#else
+
+static const uint8_t
+       /*
+        * produced with (plaintext.txt contains "test plaintext\0\0")
+        *
+        * openssl enc -aes-128-ofb \
+        *   -K "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210" \
+        *   -iv "0123456789abcdeffedcba9876543210"
+        *   -in plaintext.txt -out out.enc
+        *
+        */
+       *ofb    = (uint8_t *)"test plaintext\0\0",
+       ofb_enc[] = {
+               /* !!! ugh... openssl app produces this... */
+               // 0xd2, 0x11, 0x86, 0xd7, 0xa9, 0x55, 0x59, 0x04,
+               // 0x4f, 0x63, 0x7c, 0xb9, 0xc6, 0xa1, 0xc9, 0x71,
+               /* but both OpenSSL and mbedTLS produce this */
+               0x11, 0x33, 0x6D, 0xFC, 0x88, 0x4C, 0x28, 0xBA,
+               0xD0, 0xF2, 0x6C, 0xBC, 0xDE, 0x4A, 0x56, 0x20
+       }, ofb_iv[] = {
+               0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+               0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+       }, ofb_key[] = {
+               0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+               0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+               0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+               0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
+       }
+;
+
+static int
+test_genaes_ofb(void)
+{
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32];
+       size_t iv_off = 0;
+
+       e.buf = (uint8_t *)ofb_key;
+       e.len = sizeof(ofb_key);
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_OFB, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, ofb, 16, res, (uint8_t *)ofb_iv, NULL,
+                            &iv_off, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(ofb_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       iv_off = 0;
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_OFB, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, (uint8_t *)ofb_iv, NULL,
+                            &iv_off, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(ofb, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+#endif
+
+#if defined(MBEDTLS_CONFIG_H) && !defined(MBEDTLS_CIPHER_MODE_XTS)
+#else
+
+static const uint8_t
+       /*
+        * Fedora openssl tool doesn't support xts... this data produced
+        * by testing on mbedtls + OpenSSL and getting the same result
+        *
+        * NOTICE that xts requires a double-length key... OpenSSL now checks
+        * the key for duplication so we use a random key
+        */
+       *xts    = (uint8_t *)"test plaintext\0\0",
+       xts_enc[] = {
+               0x87, 0x83, 0x20, 0x8B, 0x15, 0x89, 0xA1, 0x13,
+               0xDC, 0xEA, 0x82, 0xB6, 0xFF, 0x8D, 0x76, 0x3A
+       }, xts_key[] = {
+               0xa4, 0xd6, 0xa2, 0x1a, 0x3b, 0x34, 0x34, 0x43,
+               0x9a, 0xe2, 0x6a, 0x01, 0x1c, 0x73, 0x80, 0x3b,
+               0xdd, 0xf6, 0xd4, 0x37, 0x5e, 0x0e, 0x1c, 0x72,
+               0x8e, 0xe5, 0x18, 0x69, 0xfd, 0x08, 0x40, 0x2b,
+               0x98, 0xf9, 0x75, 0xa8, 0x36, 0xd5, 0x0f, 0xa2,
+               0x20, 0x04, 0x43, 0xa7, 0x3a, 0xa6, 0x4a, 0xdc,
+               0xe9, 0x54, 0x50, 0xfa, 0x38, 0xad, 0x6d, 0x96,
+               0x5f, 0x31, 0x9e, 0xcd, 0x33, 0x08, 0xa0, 0x44
+       }
+;
+
+static int
+test_genaes_xts(void)
+{
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       uint8_t res[32], res1[32], data_unit[16];
+
+       memset(data_unit, 0, sizeof(data_unit));
+
+       e.buf = (uint8_t *)xts_key;
+       e.len = sizeof(xts_key);
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_XTS, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       if (lws_genaes_crypt(&ctx, xts, 16, res, data_unit, NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(xts_enc, res, 16)) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_XTS, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genaes_crypt(&ctx, res, 16, res1, data_unit, NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_destroy failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(xts, res1, 16)) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+#endif
+
+static const uint8_t
+       /*
+        * https://csrc.nist.gov/CSRC/media/Projects/
+        * Cryptographic-Algorithm-Validation-Program/
+        * documents/mac/gcmtestvectors.zip
+        */
+
+       gcm_ct[] = {
+               0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c,
+               0xd5, 0x36, 0x86, 0x7e, 0xb9, 0xf2, 0x17, 0x36
+       }, gcm_iv[] = {
+               0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0,
+               0xee, 0xd0, 0x66, 0x84
+       }, gcm_key[] = {
+               0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92,
+               0x1c, 0x04, 0x65, 0x66, 0x5f, 0x8a, 0xe6, 0xd1,
+               0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69,
+               0xa3, 0x52, 0x02, 0x93, 0xa5, 0x72, 0x07, 0x8f
+       }, gcm_pt[] = {
+               0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e,
+               0xeb, 0x31, 0xb2, 0xea, 0xcc, 0x2b, 0xf2, 0xa5
+       }, gcm_aad[] = {
+               0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b,
+               0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, 0x78, 0xde
+       }, gcm_tag[] = {
+               0x67, 0xba, 0x05, 0x10, 0x26, 0x2a, 0xe4, 0x87,
+               0xd7, 0x37, 0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c
+       };
+
+static int
+test_genaes_gcm(void)
+{
+       uint8_t res[sizeof(gcm_ct)], tag[sizeof(gcm_tag)];
+       struct lws_genaes_ctx ctx;
+       struct lws_gencrypto_keyelem e;
+       size_t iv_off = 0;
+
+       e.buf = (uint8_t *)gcm_key;
+       e.len = sizeof(gcm_key);
+
+       /* Encrypt */
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_ENC, LWS_GAESM_GCM, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       /* first we set the iv and aad */
+
+       iv_off = sizeof(gcm_iv);
+       if (lws_genaes_crypt(&ctx, gcm_aad, sizeof(gcm_aad), NULL,
+                            (uint8_t *)gcm_iv, (uint8_t *)gcm_tag,
+                            &iv_off, sizeof(gcm_tag))) {
+               lwsl_err("%s: lws_genaes_crypt 1 failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_crypt(&ctx, gcm_pt, sizeof(gcm_pt), res,
+                            NULL, NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt 2 failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, tag, sizeof(tag))) {
+               lwsl_err("%s: lws_genaes_destroy enc failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(gcm_ct, res, sizeof(gcm_ct))) {
+               lwsl_err("%s: lws_genaes_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, sizeof(gcm_ct));
+               return -1;
+       }
+
+
+       /* Decrypt */
+
+       if (lws_genaes_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_GCM, &e, 0, NULL)) {
+               lwsl_err("%s: lws_genaes_create failed\n", __func__);
+               return 1;
+       }
+
+       iv_off = sizeof(gcm_iv); /* initial call sets iv + aad + tag */
+       if (lws_genaes_crypt(&ctx, gcm_aad, sizeof(gcm_aad), NULL,
+                            (uint8_t *)gcm_iv, (uint8_t *)gcm_tag,
+                            &iv_off, sizeof(gcm_tag))) {
+               lwsl_err("%s: lws_genaes_crypt 1 failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_crypt(&ctx, gcm_ct, sizeof(gcm_ct), res,
+                            NULL, NULL, NULL, 0)) {
+               lwsl_err("%s: lws_genaes_crypt 2 failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_genaes_destroy(&ctx, tag, sizeof(tag))) {
+               lwsl_err("%s: lws_genaes_destroy dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_timingsafe_bcmp(gcm_pt, res, sizeof(gcm_pt))) {
+               lwsl_err("%s: lws_genaes_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, sizeof(gcm_ct));
+               return -1;
+       }
+
+       return 0;
+
+bail:
+       lws_genaes_destroy(&ctx, NULL, 0);
+
+       return -1;
+}
+
+int
+test_genaes(struct lws_context *context)
+{
+
+       if (test_genaes_cbc())
+               goto bail;
+
+       if (test_genaes_cfb128())
+               goto bail;
+
+       if (test_genaes_cfb8())
+               goto bail;
+
+       if (test_genaes_ctr())
+               goto bail;
+
+       if (test_genaes_ecb())
+               goto bail;
+
+#if defined(MBEDTLS_CONFIG_H) && !defined(MBEDTLS_CIPHER_MODE_OFB)
+#else
+       if (test_genaes_ofb())
+               goto bail;
+#endif
+
+#if defined(MBEDTLS_CONFIG_H) && !defined(MBEDTLS_CIPHER_MODE_XTS)
+#else
+       if (test_genaes_xts())
+               goto bail;
+#endif
+
+       if (test_genaes_gcm())
+               goto bail;
+
+       /* end */
+
+       lwsl_notice("%s: selftest OK\n", __func__);
+
+       return 0;
+
+bail:
+       lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+
+       return 1;
+}
diff --git a/minimal-examples/api-tests/api-test-gencrypto/lws-genec.c b/minimal-examples/api-tests/api-test-gencrypto/lws-genec.c
new file mode 100644 (file)
index 0000000..57ab78b
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * lws-api-test-gencrypto - lws-genec
+ *
+ * Written in 2010-2018 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+static const uint8_t
+       *jwk_ec1 = (uint8_t *)
+               "{\"kty\":\"EC\","
+                 "\"crv\":\"P-256\","
+                 "\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","
+                 "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\","
+                 "\"d\":\"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE\","
+                 "\"use\":\"enc\","
+                 "\"kid\":\"rfc7517-A.2-example private key\"}"
+;
+
+static int
+test_genec1(struct lws_context *context)
+{
+       struct lws_genec_ctx ctx;
+       struct lws_jwk jwk;
+       struct lws_gencrypto_keyelem el[LWS_GENCRYPTO_EC_KEYEL_COUNT];
+       //uint8_t res[32], res1[32];
+       int n;
+
+       memset(el, 0, sizeof(el));
+
+       if (lws_genecdh_create(&ctx, context, NULL))
+               return 1;
+
+       /* let's create a new key */
+
+       if (lws_genecdh_new_keypair(&ctx, LDHS_OURS, "P-256", el)) {
+               lwsl_err("%s: lws_genec_new_keypair failed\n", __func__);
+               return 1;
+       }
+
+       lws_genec_dump(el);
+       lws_genec_destroy_elements(el);
+
+       lws_genec_destroy(&ctx);
+
+       if (lws_jwk_import(&jwk, NULL, NULL, (char *)jwk_ec1,
+                          strlen((char *)jwk_ec1)) < 0) {
+               lwsl_notice("Failed to decode JWK test key\n");
+               return 1;
+       }
+
+       lws_jwk_dump(&jwk);
+
+       if (jwk.kty != LWS_GENCRYPTO_KTY_EC) {
+               lws_jwk_destroy(&jwk);
+               lwsl_err("%s: jwk is not an EC key\n", __func__);
+               return 1;
+       }
+
+       if (lws_genecdh_create(&ctx, context, NULL))
+               return 1;
+
+       n = lws_genecdh_set_key(&ctx, jwk.e, LDHS_OURS);
+       if (n) {
+               lws_jwk_destroy(&jwk);
+               lwsl_err("%s: lws_genec_create failed: %d\n", __func__, n);
+               return 1;
+       }
+#if 0
+       if (lws_genec_crypt(&ctx, cbc256, 16, res, (uint8_t *)cbc256_iv,
+                            NULL, NULL)) {
+               lwsl_err("%s: lws_genec_crypt failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_timingsafe_bcmp(cbc256_enc, res, 16)) {
+               lwsl_err("%s: lws_genec_crypt encoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               goto bail;
+       }
+
+       lws_genec_destroy(&ctx);
+
+       if (lws_genec_create(&ctx, LWS_GAESO_DEC, LWS_GAESM_CBC, &e, NULL)) {
+               lwsl_err("%s: lws_genec_create dec failed\n", __func__);
+               return -1;
+       }
+
+       if (lws_genec_crypt(&ctx, res, 16, res1, (uint8_t *)cbc256_iv,
+                            NULL, NULL)) {
+               lwsl_err("%s: lws_genec_crypt dec failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_timingsafe_bcmp(cbc256, res1, 16)) {
+               lwsl_err("%s: lws_genec_crypt decoding mismatch\n", __func__);
+               lwsl_hexdump_notice(res, 16);
+               goto bail;
+       }
+#endif
+       lws_genec_destroy(&ctx);
+
+       lws_jwk_destroy(&jwk);
+
+       return 0;
+
+//bail:
+//     lws_genec_destroy(&ctx);
+
+//     return -1;
+}
+
+int
+test_genec(struct lws_context *context)
+{
+       if (test_genec1(context))
+               goto bail;
+
+       /* end */
+
+       lwsl_notice("%s: selftest OK\n", __func__);
+
+       return 0;
+
+bail:
+       lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+
+       return 1;
+}
diff --git a/minimal-examples/api-tests/api-test-gencrypto/main.c b/minimal-examples/api-tests/api-test-gencrypto/main.c
new file mode 100644 (file)
index 0000000..31137b0
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * lws-api-test-gencrypto
+ *
+ * Written in 2010-2018 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+int
+test_genaes(struct lws_context *context);
+int
+test_genec(struct lws_context *context);
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS gencrypto apis tests\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       result |= test_genaes(context);
+       result |= test_genec(context);
+
+       lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS");
+
+       lws_context_destroy(context);
+
+       return result;
+}
diff --git a/minimal-examples/api-tests/api-test-gencrypto/selftest.sh b/minimal-examples/api-tests/api-test-gencrypto/selftest.sh
new file mode 100755 (executable)
index 0000000..16d1e2e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 apiselftest
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-jose/CMakeLists.txt b/minimal-examples/api-tests/api-test-jose/CMakeLists.txt
new file mode 100644 (file)
index 0000000..64e8bde
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-jose)
+set(SRCS main.c jwk.c jws.c jwe.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_JOSE 1 requirements)
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/api-tests/api-test-jose/README.md b/minimal-examples/api-tests/api-test-jose/README.md
new file mode 100644 (file)
index 0000000..74034c7
--- /dev/null
@@ -0,0 +1,22 @@
+# lws api test lwsac
+
+Demonstrates how to use and performs selftests for lwsac
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+```
+ $ ./lws-api-test-lwsac
+[2018/10/09 09:14:17:4834] USER: LWS API selftest: lwsac
+[2018/10/09 09:14:17:4835] USER: Completed: PASS
+```
+
diff --git a/minimal-examples/api-tests/api-test-jose/jwe.c b/minimal-examples/api-tests/api-test-jose/jwe.c
new file mode 100644 (file)
index 0000000..2519ef4
--- /dev/null
@@ -0,0 +1,2231 @@
+/*
+ * lws-api-test-jose - RFC7516 jwe tests
+ *
+ * Written in 2010-2018 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+/*
+ * These are the inputs and outputs from the worked example in RFC7516
+ * Appendix A.1   {"alg":"RSA-OAEP","enc":"A256GCM"}
+ */
+
+
+static char
+
+*ex_a1_ptext =
+       "The true sign of intelligence is not knowledge but imagination.",
+
+*ex_a1_compact =
+       "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ."
+       "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe"
+       "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb"
+       "Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV"
+       "mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8"
+       "1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi"
+       "6UklfCpIMfIjf7iGdXKHzg."
+       "48V1_ALb6US04U3b."
+       "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji"
+       "SdiwkIr3ajwQzaBtQD_A."
+       "XFBoMYUZodetZdvTiFvSkQ",
+
+       *ex_a1_jwk_json =
+       "{\"kty\":\"RSA\","
+         "\"n\":\"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW"
+               "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S"
+               "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a"
+               "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS"
+               "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj"
+               "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw\","
+         "\"e\":\"AQAB\","
+         "\"d\":\"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N"
+               "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9"
+               "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk"
+               "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl"
+               "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd"
+               "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ\","
+         "\"p\":\"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-"
+               "SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf"
+               "fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0\","
+         "\"q\":\"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm"
+               "UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX"
+               "IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc\","
+         "\"dp\":\"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL"
+               "hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827"
+               "rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE\","
+         "\"dq\":\"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj"
+               "ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB"
+               "UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis\","
+         "\"qi\":\"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7"
+               "AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3"
+               "eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY\""
+       "}"
+;
+
+static int
+test_jwe_a1(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[2048], compact[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, ex_a1_jwk_json,
+                          strlen(ex_a1_jwk_json)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode(ex_a1_compact, strlen(ex_a1_compact),
+                                  &jwe.jws.map, &jwe.jws.map_b64, temp,
+                                  &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < strlen(ex_a1_ptext) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ex_a1_ptext,
+                               strlen(ex_a1_ptext))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ex_a1_ptext, strlen(ex_a1_ptext));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       /*
+        * Canned decrypt worked properly... let's also try encoding the
+        * plaintext ourselves and decoding that...
+        */
+       lws_jwe_destroy(&jwe);
+       temp_len = sizeof(temp);
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, ex_a1_jwk_json,
+                          strlen(ex_a1_jwk_json)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       if (lws_gencrypto_jwe_alg_to_definition("RSA-OAEP", &jwe.jose.alg)) {
+               lwsl_err("Unknown cipher alg \"RSA-OAEP\"\n");
+               goto bail;
+       }
+       if (lws_gencrypto_jwe_enc_to_definition("A256GCM", &jwe.jose.enc_alg)) {
+               lwsl_err("Unknown payload enc alg \"A256GCM\"\n");
+               goto bail;
+       }
+
+       /* we require a JOSE-formatted header to do the encryption */
+
+       jwe.jws.map.buf[LJWS_JOSE] = temp;
+       jwe.jws.map.len[LJWS_JOSE] = lws_snprintf(temp, temp_len,
+                       "{\"alg\":\"%s\",\"enc\":\"%s\"}", "RSA-OAEP", "A256GCM");
+       temp_len -= jwe.jws.map.len[LJWS_JOSE];
+
+       /*
+        * dup the plaintext into the ciphertext element, it will be
+        * encrypted in-place to a ciphertext of the same length
+        */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_CTXT,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               ex_a1_ptext, strlen(ex_a1_ptext), 0)) {
+               lwsl_notice("%s: Not enough temp space for ptext\n", __func__);
+               goto bail;
+       }
+
+       /* CEK size is determined by hash / hmac size */
+
+       n = lws_gencrypto_bits_to_bytes(jwe.jose.enc_alg->keybits_fixed);
+       if (lws_jws_randomize_element(context, &jwe.jws.map, LJWE_EKEY,
+                                     lws_concat_temp(temp, temp_len),
+                                     &temp_len, n,
+                                     LWS_JWE_LIMIT_KEY_ELEMENT_BYTES)) {
+               lwsl_err("Problem getting random\n");
+               goto bail;
+       }
+
+       n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len),
+                           &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+               goto bail;
+       }
+       n = lws_jwe_render_compact(&jwe, compact, sizeof(compact));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n",
+                        __func__, n);
+               goto bail;
+       }
+
+       // puts(compact);
+
+       /*
+        * Okay... what happens when we try to decode what we created?
+        */
+
+       lws_jwe_destroy(&jwe);
+       lws_jwe_init(&jwe, context);
+       temp_len = sizeof(temp);
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode(compact, strlen(compact), &jwe.jws.map,
+                                  &jwe.jws.map_b64, temp, &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, ex_a1_jwk_json,
+                          strlen(ex_a1_jwk_json)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: generated lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+/* A.2.  Example JWE using RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256
+ *
+ * This example encrypts the plaintext "Live long and prosper." to the
+ * recipient using RSAES-PKCS1-v1_5 for key encryption and
+ * AES_128_CBC_HMAC_SHA_256 for content encryption.
+ */
+
+/* "Live long and prosper." */
+static uint8_t
+
+ex_a2_ptext[] = {
+       76, 105, 118, 101, 32, 108, 111, 110,
+       103, 32, 97, 110, 100, 32,  112, 114,
+       111, 115, 112, 101, 114, 46
+}, *lws_jwe_ex_a2_jwk_json = (uint8_t *)
+       "{"
+        "\"kty\":\"RSA\","
+        "\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl"
+                "UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre"
+                "cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_"
+                "7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI"
+                "Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU"
+                "7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\","
+        "\"e\":\"AQAB\","
+        "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq"
+                "1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry"
+                "nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_"
+                "0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj"
+                "-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj"
+                "T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\","
+        "\"p\":\"9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68"
+                "ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP"
+                "krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM\","
+        "\"q\":\"uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y"
+                "BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN"
+                "-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0\","
+        "\"dp\":\"w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv"
+                "ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra"
+                "Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs\","
+        "\"dq\":\"o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff"
+                "7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_"
+                "odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU\","
+        "\"qi\":\"eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC"
+                "tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ"
+                "B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo\""
+       "}",
+
+*ex_a2_compact = (uint8_t *)
+       "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0"
+       "."
+       "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm"
+       "1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc"
+       "HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF"
+       "NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8"
+       "rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv"
+       "-B3oWh2TbqmScqXMR4gp_A"
+       "."
+       "AxY8DCtDaGlsbGljb3RoZQ"
+       "."
+       "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY"
+       "."
+       "9hH0vgRfYgPnAHOd8stkvw"
+;
+
+static int
+test_jwe_a2(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, (char *)lws_jwe_ex_a2_jwk_json,
+                          strlen((char *)lws_jwe_ex_a2_jwk_json)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode((const char *)ex_a2_compact,
+                                  strlen((char *)ex_a2_compact),
+                                  &jwe.jws.map, &jwe.jws.map_b64,
+                                  (char *)temp, &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ex_a2_ptext) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ex_a2_ptext,
+                               sizeof(ex_a2_ptext))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ex_a2_ptext, sizeof(ex_a2_ptext));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+/* JWE creation using RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256
+ *
+ * This example encrypts a different, larger plaintext using the jwk key from
+ * the test above, and AES_128_CBC_HMAC_SHA_256 for content encryption.
+ */
+
+static const char *rsa256a128_jose =
+               "{ \"alg\":\"RSA1_5\",\"enc\":\"A128CBC-HS256\"}";
+
+static uint8_t
+
+       /* plaintext is 1024 bytes from /dev/urandom */
+
+ra_ptext_1024[] = {
+               0xfe, 0xc6, 0x4f, 0x3e, 0x4a, 0x19, 0xe9, 0xd7,
+               0xc2, 0x13, 0xe7, 0xc5, 0x78, 0x6e, 0x71, 0xf6,
+               0x6e, 0xdd, 0x04, 0xaf, 0xaa, 0x4e, 0xa8, 0xad,
+               0xd8, 0xe0, 0xb3, 0x32, 0x97, 0x43, 0x7c, 0xd8,
+               0xd1, 0x5f, 0x56, 0xac, 0x70, 0xaf, 0x7d, 0x0b,
+               0x40, 0xa1, 0x96, 0x71, 0x7c, 0xc4, 0x4a, 0x37,
+               0x0b, 0xa6, 0x06, 0xb3, 0x8c, 0x87, 0xee, 0xb6,
+               0x15, 0xfe, 0xaa, 0x60, 0x7e, 0x7f, 0xdc, 0xb0,
+               0xff, 0x96, 0x4b, 0x30, 0x60, 0xcf, 0xc6, 0x5d,
+               0x09, 0x6a, 0x6f, 0x66, 0x0c, 0x5f, 0xb0, 0x6f,
+               0x61, 0xa6, 0x26, 0x02, 0xbd, 0x46, 0xda, 0xa3,
+               0x73, 0x19, 0x17, 0xff, 0xe0, 0x5f, 0x30, 0x72,
+               0x7d, 0x17, 0xd8, 0xb2, 0xbe, 0x84, 0x3e, 0x4d,
+               0x76, 0xbd, 0x62, 0x5d, 0x63, 0xfe, 0x11, 0x32,
+               0x11, 0x41, 0xdc, 0xed, 0x96, 0xfd, 0x31, 0x38,
+               0x6a, 0x84, 0x55, 0x7a, 0x33, 0x3f, 0x37, 0xc3,
+               0x37, 0x7b, 0xc1, 0xb7, 0x89, 0x00, 0x39, 0xa6,
+               0x94, 0x91, 0xb7, 0x19, 0x6b, 0x1d, 0x99, 0xeb,
+               0xf6, 0x10, 0xb9, 0xd2, 0xcd, 0x15, 0x0d, 0xbc,
+               0x24, 0x34, 0x9a, 0x52, 0x64, 0x21, 0x72, 0x1e,
+               0x9a, 0x00, 0xf2, 0xcf, 0xf1, 0x7d, 0x1a, 0x12,
+               0x8d, 0x39, 0xbc, 0xf9, 0x09, 0xfd, 0xd9, 0x22,
+               0x27, 0x28, 0xe1, 0x3a, 0x0b, 0x82, 0xba, 0x9a,
+               0xe5, 0x9d, 0xa8, 0x12, 0x6e, 0xf5, 0x4b, 0xc7,
+               0x2b, 0x9c, 0xdc, 0xfe, 0xf3, 0xe8, 0x74, 0x65,
+               0x3d, 0xe0, 0xaa, 0x64, 0xf3, 0x43, 0xa4, 0x88,
+               0xa8, 0xbe, 0x60, 0xdb, 0xfd, 0x2d, 0x3b, 0x84,
+               0x82, 0x8f, 0x4d, 0xbb, 0xe4, 0xa9, 0x59, 0xe3,
+               0x6c, 0x52, 0x45, 0xe4, 0x34, 0xdb, 0x28, 0x0e,
+               0x4a, 0x44, 0xb6, 0x9a, 0x25, 0x9b, 0x3b, 0xae,
+               0xe1, 0x12, 0x1d, 0x1c, 0x66, 0x7d, 0xb9, 0x5b,
+               0x5f, 0xc2, 0x4a, 0xaa, 0xd2, 0xe9, 0x65, 0xe2,
+               0x85, 0x6f, 0xf6, 0x67, 0x66, 0x8e, 0x0b, 0xd2,
+               0x60, 0xf8, 0x43, 0x60, 0x04, 0x9b, 0xa9, 0x3a,
+               0x6a, 0x3c, 0x02, 0x3c, 0x08, 0x9d, 0x60, 0x1c,
+               0xc4, 0x27, 0x3e, 0xff, 0xd0, 0x70, 0x94, 0x43,
+               0x3e, 0x9e, 0x69, 0x19, 0x22, 0xf0, 0xec, 0x26,
+               0x2d, 0xa5, 0x71, 0xf3, 0x92, 0x61, 0x95, 0xce,
+               0xc3, 0xc0, 0xa0, 0xc3, 0x98, 0x22, 0xdd, 0x32,
+               0x3c, 0x48, 0xcb, 0xd1, 0x61, 0xa0, 0xaa, 0x9a,
+               0x7e, 0x5a, 0xfa, 0x26, 0x46, 0x49, 0xfc, 0x9c,
+               0xaa, 0x21, 0x06, 0x45, 0xf1, 0xa0, 0xc9, 0xef,
+               0x6b, 0x89, 0xf2, 0x01, 0x20, 0x54, 0xfa, 0x0a,
+               0x23, 0xff, 0xbd, 0x64, 0x35, 0x94, 0xfd, 0x35,
+               0x70, 0x52, 0x94, 0x66, 0xc5, 0xd0, 0x27, 0xc1,
+               0x8f, 0x6d, 0xc4, 0xa3, 0x34, 0xc2, 0xea, 0xf0,
+               0xb3, 0x0d, 0x6c, 0x13, 0xb5, 0xc9, 0x6e, 0x5c,
+               0xeb, 0x8b, 0x7b, 0xf5, 0x21, 0x4c, 0xe3, 0xb7,
+               0x73, 0x6d, 0x07, 0xaa, 0x44, 0xc4, 0xba, 0xc5,
+               0xa5, 0x0e, 0x75, 0x28, 0xb7, 0x50, 0x22, 0x54,
+               0xa7, 0xe1, 0x2e, 0xfd, 0x20, 0xcd, 0xa4, 0x31,
+               0xa3, 0xb2, 0x73, 0x98, 0x7c, 0x3c, 0x8f, 0xa3,
+               0x40, 0x8a, 0xaf, 0x31, 0xfa, 0xf9, 0x70, 0x4d,
+               0x83, 0x10, 0xc4, 0xa0, 0x9c, 0xd6, 0xa3, 0xd5,
+               0x07, 0xaf, 0xaf, 0x35, 0x15, 0xd0, 0x84, 0x09,
+               0x20, 0x36, 0x88, 0xac, 0x6f, 0x16, 0x5e, 0x03,
+               0xa9, 0xfc, 0xb3, 0x2d, 0x01, 0x57, 0xb3, 0xed,
+               0x4b, 0x55, 0x2b, 0xbc, 0x92, 0x87, 0x3e, 0x27,
+               0xc4, 0x2c, 0x44, 0xac, 0x05, 0x5f, 0x26, 0xe7,
+               0xe9, 0xb0, 0x2d, 0x6b, 0x3c, 0x8c, 0xd2, 0xb4,
+               0x3c, 0xb4, 0x86, 0xfe, 0x68, 0x99, 0x2a, 0x42,
+               0xac, 0xa4, 0xb3, 0x89, 0x61, 0xb3, 0xd1, 0xdf,
+               0x9b, 0x58, 0xc7, 0x81, 0x62, 0x87, 0x26, 0x52,
+               0x51, 0xe7, 0x7d, 0x7c, 0x37, 0x14, 0xe5, 0x19,
+               0x28, 0x34, 0x3e, 0x95, 0x17, 0x36, 0x12, 0xf9,
+               0x5e, 0xc1, 0x3c, 0x9c, 0x28, 0x70, 0x06, 0xdf,
+               0xc4, 0x6d, 0x25, 0x04, 0x46, 0xe0, 0x95, 0xf0,
+               0xc8, 0x57, 0x48, 0x27, 0x26, 0xf3, 0xf7, 0x19,
+               0xbe, 0xea, 0xb4, 0xd4, 0x64, 0xaf, 0x67, 0x7c,
+               0xf5, 0xa9, 0xfb, 0x85, 0x4a, 0x43, 0x9c, 0x62,
+               0x06, 0x5e, 0x28, 0x2a, 0x7b, 0x1e, 0xb3, 0x07,
+               0xe7, 0x19, 0x32, 0xa4, 0x4e, 0xb4, 0xce, 0xe0,
+               0x92, 0x56, 0xf5, 0x10, 0xcb, 0x56, 0x34, 0x4b,
+               0x0d, 0xe1, 0xd3, 0x6d, 0xfe, 0xf0, 0x44, 0xf7,
+               0x22, 0x1d, 0x5e, 0x6b, 0xa7, 0xa5, 0x83, 0x2e,
+               0xeb, 0x14, 0xf2, 0xd7, 0x27, 0x5a, 0x2a, 0xd2,
+               0x55, 0x35, 0xe6, 0x7e, 0xd9, 0x3b, 0xac, 0x4e,
+               0x5a, 0x22, 0x46, 0xd5, 0x7b, 0x57, 0x9c, 0x58,
+               0xfe, 0xd0, 0xda, 0xbf, 0x7d, 0xe9, 0x8c, 0xb7,
+               0xba, 0x88, 0xf1, 0xc3, 0x82, 0x53, 0xc3, 0x66,
+               0x20, 0x51, 0x12, 0xd3, 0xf9, 0xaf, 0xe9, 0xcb,
+               0xc1, 0x7a, 0xe6, 0x22, 0x44, 0xa5, 0xdf, 0x18,
+               0xb3, 0x6e, 0x6c, 0xba, 0xf3, 0xc6, 0x24, 0x5a,
+               0x1c, 0x67, 0xa6, 0xa5, 0xb4, 0xb1, 0x35, 0xdf,
+               0x5a, 0x60, 0x5c, 0x0b, 0x66, 0xd3, 0x1f, 0x4e,
+               0x7c, 0xcb, 0x93, 0x7e, 0x2f, 0x6d, 0xbd, 0xce,
+               0x26, 0x52, 0x44, 0xee, 0xbb, 0xd8, 0x8f, 0xf2,
+               0x67, 0x38, 0x0d, 0x3b, 0xaa, 0x21, 0x73, 0xf8,
+               0x3b, 0x54, 0x9d, 0x4e, 0x5e, 0xf1, 0xa2, 0x18,
+               0x5a, 0xf1, 0x6c, 0x32, 0xbf, 0x0a, 0x73, 0x14,
+               0x48, 0x4f, 0x56, 0xc0, 0x87, 0x6d, 0x3b, 0x16,
+               0xcc, 0x3f, 0x44, 0x19, 0x85, 0x22, 0x43, 0x5f,
+               0x8c, 0x29, 0xbd, 0xa0, 0xce, 0x84, 0xd9, 0x4a,
+               0xcf, 0x00, 0x6b, 0x37, 0x35, 0xe0, 0xb3, 0xc9,
+               0xd1, 0x58, 0xd1, 0x1b, 0xc3, 0x6f, 0xe3, 0x50,
+               0xdb, 0xa6, 0x5e, 0x03, 0x18, 0xe5, 0xe2, 0xc1,
+               0x97, 0xd5, 0xf8, 0x42, 0x6f, 0xe6, 0x61, 0x80,
+               0xc9, 0x7c, 0xc6, 0x83, 0xf0, 0xad, 0x70, 0x13,
+               0x0e, 0x26, 0x75, 0xc0, 0x12, 0x23, 0x14, 0xef,
+               0x1f, 0xdf, 0xfd, 0x47, 0x99, 0x9f, 0x22, 0xf3,
+               0x57, 0x21, 0xdc, 0x38, 0xe4, 0x79, 0x87, 0x5b,
+               0x67, 0x66, 0xdd, 0x0b, 0xe0, 0xae, 0xb5, 0x97,
+               0xd8, 0xa6, 0x5d, 0x02, 0xcf, 0x6b, 0x84, 0x19,
+               0xc1, 0xbb, 0x25, 0xd2, 0x10, 0xb9, 0x63, 0xeb,
+               0x4b, 0x27, 0x8d, 0x05, 0x31, 0xce, 0x3b, 0x0c,
+               0x5f, 0xd4, 0x83, 0x47, 0xa4, 0x8b, 0xc4, 0x76,
+               0x33, 0x74, 0x1a, 0x07, 0xf8, 0x18, 0x82, 0x1c,
+               0x8e, 0x01, 0x75, 0x78, 0xea, 0xd9, 0x72, 0x61,
+               0x71, 0xa9, 0x09, 0x44, 0x7b, 0x0f, 0x12, 0xcf,
+               0x4c, 0x76, 0x7b, 0x69, 0xc8, 0x64, 0x98, 0x60,
+               0x45, 0xb6, 0xc7, 0x6b, 0xd8, 0x43, 0x99, 0x08,
+               0xc9, 0xd3, 0x6f, 0x01, 0x4f, 0x57, 0x6f, 0x49,
+               0x4f, 0x4f, 0x72, 0xa4, 0xa2, 0x45, 0xe1, 0x0e,
+               0xf2, 0x08, 0x3e, 0x67, 0xc3, 0x83, 0x5b, 0xb1,
+               0x24, 0xc0, 0xe0, 0x3a, 0xf5, 0x1f, 0xf2, 0x06,
+               0x4b, 0xa7, 0x6f, 0xd2, 0xb2, 0x81, 0x96, 0x91,
+               0x42, 0xb1, 0x53, 0x65, 0x3a, 0x12, 0xcd, 0x33,
+               0xb3, 0x7e, 0x79, 0xc0, 0x46, 0xf6, 0xd8, 0x4a,
+               0x22, 0x35, 0xb8, 0x3f, 0xe4, 0x08, 0x88, 0x49,
+               0x3c, 0x73, 0x9a, 0x44, 0xe3, 0x3b, 0xcc, 0xc4,
+               0xae, 0x7c, 0xbe, 0xfd, 0xa6, 0x4a, 0xd4, 0x26,
+               0x52, 0x58, 0x81, 0x30, 0x66, 0x44, 0x54, 0xc8,
+               0xe4, 0x7c, 0x5b, 0x63, 0x06, 0x60, 0x94, 0x62,
+               0xe5, 0x47, 0x45, 0xfb, 0x58, 0xf5, 0x6a, 0x7c,
+               0xb2, 0x35, 0x08, 0x03, 0x15, 0x68, 0xb3, 0x13,
+               0xa5, 0xbd, 0xf2, 0x1e, 0x2e, 0x1c, 0x8f, 0xc6,
+               0xc7, 0xd1, 0xa9, 0x64, 0x37, 0x2b, 0x23, 0xfa,
+               0x7e, 0x56, 0x22, 0xf0, 0x8a, 0xbd, 0xeb, 0x04
+},
+
+r256a128_cek[] = {
+               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+               0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+               0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+               0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
+}
+;
+
+static int
+test_jwe_ra_ptext_1024(struct lws_context *context, char *jwk_txt, int jwk_len)
+{
+       char temp[4096], compact[4096];
+       struct lws_jwe jwe;
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       /* reuse the rsa private key from the JWE Appendix 2 test above */
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, jwk_txt, jwk_len) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* dup the plaintext, it will be replaced in-situ by the ciphertext */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_CTXT,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               ra_ptext_1024, sizeof(ra_ptext_1024), 0)) {
+               lwsl_notice("%s: Not enough temp space for ptext\n", __func__);
+               goto bail;
+       }
+
+       /* dup the cek, since it will be replaced by the encrypted key */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_EKEY,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               r256a128_cek, sizeof(r256a128_cek),
+                               LWS_JWE_LIMIT_KEY_ELEMENT_BYTES)) {
+               lwsl_notice("%s: Not enough temp space for EKEY\n", __func__);
+               goto bail;
+       }
+
+       jwe.jws.map.buf[LJWE_JOSE] = rsa256a128_jose;
+       jwe.jws.map.len[LJWE_JOSE] = strlen(rsa256a128_jose);
+
+       n = lws_jwe_parse_jose(&jwe.jose, jwe.jws.map.buf[LJWE_JOSE],
+                              jwe.jws.map.len[LJWE_JOSE],
+                              lws_concat_temp(temp, temp_len), &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+
+               goto bail;
+       }
+
+       n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len),
+                           &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_render_compact(&jwe, compact, sizeof(compact));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n", __func__, n);
+               goto bail;
+       }
+
+       // puts(compact);
+
+       lws_jwe_destroy(&jwe);
+       lws_jwe_init(&jwe, context);
+       temp_len = sizeof(temp);
+
+       /* now we created the encrypted version, see if we can decrypt it */
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, jwk_txt, jwk_len) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       if (lws_jws_compact_decode(compact, n, &jwe.jws.map, &jwe.jws.map_b64,
+                                  temp, &temp_len) != 5) {
+               lwsl_err("%s: failed to parse generated compact\n", __func__);
+
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ra_ptext_1024) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ra_ptext_1024,
+                               sizeof(ra_ptext_1024))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ra_ptext_1024, sizeof(ra_ptext_1024));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+static const char *rsa256a192_jose =
+               "{ \"alg\":\"RSA1_5\",\"enc\":\"A192CBC-HS384\"}";
+
+static const uint8_t r256a192_cek[] = {
+               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+               0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+               0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+               0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+               0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+               0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
+}
+;
+
+static int
+test_jwe_r256a192_ptext(struct lws_context *context, char *jwk_txt, int jwk_len)
+{
+       struct lws_jwe jwe;
+       char temp[4096], compact[4096];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       /* reuse the rsa private key from the JWE Appendix 2 test above */
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, jwk_txt, jwk_len) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /*
+        * dup the plaintext into the ciphertext element, it will be
+        * encrypted in-place to a ciphertext of the same length
+        */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_CTXT,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               ra_ptext_1024, sizeof(ra_ptext_1024), 0)) {
+               lwsl_notice("%s: Not enough temp space for ptext\n", __func__);
+               goto bail;
+       }
+
+       /* copy the cek, since it will be replaced by the encrypted key */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_EKEY,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               r256a192_cek, sizeof(r256a192_cek),
+                               LWS_JWE_LIMIT_KEY_ELEMENT_BYTES)) {
+               lwsl_err("Problem getting random\n");
+               goto bail;
+       }
+
+       jwe.jws.map.buf[LJWE_JOSE] = rsa256a192_jose;
+       jwe.jws.map.len[LJWE_JOSE] = strlen(rsa256a192_jose);
+
+       n = lws_jwe_parse_jose(&jwe.jose, jwe.jws.map.buf[LJWE_JOSE],
+                              jwe.jws.map.len[LJWE_JOSE],
+                              lws_concat_temp(temp, temp_len), &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+
+               goto bail;
+       }
+
+       n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len),
+                           &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_render_compact(&jwe, compact, sizeof(compact));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n", __func__, n);
+               goto bail;
+       }
+
+       // puts(compact);
+
+       /* now we created the encrypted version, see if we can decrypt it */
+
+       lws_jwe_destroy(&jwe);
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, jwk_txt, jwk_len) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       if (lws_jws_compact_decode(compact, n, &jwe.jws.map, &jwe.jws.map_b64,
+                                  temp, &temp_len) != 5) {
+               lwsl_err("%s: failed to parse generated compact\n", __func__);
+
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ra_ptext_1024) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ra_ptext_1024,
+                               sizeof(ra_ptext_1024))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ra_ptext_1024, sizeof(ra_ptext_1024));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+static const char *rsa256a256_jose =
+               "{ \"alg\":\"RSA1_5\",\"enc\":\"A256CBC-HS512\"}";
+
+static const uint8_t r256a256_cek[] = {
+               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+               0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+               0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+               0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+               0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+               0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+               0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+               0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
+}
+;
+
+static int
+test_jwe_r256a256_ptext(struct lws_context *context, char *jwk_txt, int jwk_len)
+{
+       struct lws_jwe jwe;
+       char temp[4096], compact[4096];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       /* reuse the rsa private key from the JWE Appendix 2 test above */
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, jwk_txt, jwk_len) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /*
+        * dup the plaintext into the ciphertext element, it will be
+        * encrypted in-place to a ciphertext of the same length
+        */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_CTXT,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               ra_ptext_1024, sizeof(ra_ptext_1024), 0)) {
+               lwsl_notice("%s: Not enough temp space for ptext\n", __func__);
+               goto bail;
+       }
+
+       /* copy the cek, since it will be replaced by the encrypted key */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_EKEY,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               r256a256_cek, sizeof(r256a256_cek),
+                               LWS_JWE_LIMIT_KEY_ELEMENT_BYTES)) {
+               lwsl_err("Problem getting random\n");
+               goto bail;
+       }
+
+       jwe.jws.map.buf[LJWE_JOSE] = rsa256a256_jose;
+       jwe.jws.map.len[LJWE_JOSE] = strlen(rsa256a256_jose);
+
+       n = lws_jwe_parse_jose(&jwe.jose, rsa256a256_jose, strlen(rsa256a256_jose),
+                              lws_concat_temp(temp, temp_len), &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+
+               goto bail;
+       }
+
+       n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len),
+                           &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_render_compact(&jwe, compact, sizeof(compact));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n", __func__, n);
+               goto bail;
+       }
+
+       // puts(compact);
+
+       /* now we created the encrypted version, see if we can decrypt it */
+
+       lws_jwe_destroy(&jwe);
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, jwk_txt, jwk_len) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       if (lws_jws_compact_decode(compact, n, &jwe.jws.map, &jwe.jws.map_b64,
+                                  temp, &temp_len) != 5) {
+               lwsl_err("%s: failed to parse generated compact\n", __func__);
+
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ra_ptext_1024) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ra_ptext_1024,
+                               sizeof(ra_ptext_1024))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ra_ptext_1024, sizeof(ra_ptext_1024));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+/* produced by running the minimal example `lws-crypto-jwk -t RSA -b 2048 -c` */
+
+static const char *rsa_key_2048 =
+       "{"
+               "\"e\":\"AQAB\","
+               "\"kty\":\"RSA\","
+               "\"n\":\"lBJdvUq-9_8hlcduIWuBjRb0tGzzAvS4foqoNCO7g-rOXMdeAcmq"
+                "aSzWTbkaGIc3L1I4-Q3TOZtxn2UhuDlShZRIhM6JCQuUVNVAF3TD7oXxHtZ"
+                "LJ7y_BqCUlrAmW31lu-nVmhY2G3xW26yXWUsDbCxz0hfLbVnXRSvVKLzYWm"
+                "_yyrFyEWfxB8peDocvKGh879z_aPCKE3PDOEl2AsgzYfpnWCLytkgnrTeL6"
+                "qY8HXxvvV-Jw-XMaRiwH0VldpIjs4DaoN35Kj1Ex7QOZznTkbYtMIqse8bR"
+                "LoR8Irkxbc5ncUAuX1KSV6lpPtelsA3RtEjJ4NHV-5eEABiYh8_CFQ\","
+               "\"d\":\"DDpguQ9RVQFMoJC5z2hlkvq91kvsXPv2Y9Dcki256xYlg55H7Pre"
+                "p__hahrABR2Jg6QVJhArt5ABjUnDQ_JL69HH6VvLD6RVVBTQ-FRBZ_3HYKY"
+                "Oynx5BA7tJm1BRatF5FkBCvq27i8nAc4vfjAb22o9CFvEW3FLaKAgOCncQ3"
+                "Tnbz9CddH89n7DXw4kBFI8q5ugF_aRIg5-i42W_hQinLaBhZ_zhAuE-nvlt"
+                "ZnhDal8cX3T60lNoUrDOlirqEOXKO3gXCHpm3csZ6nabHYD1UCyHOmi2RsR"
+                "pzjaiqjXdPbwPzQoh2DcYpavNrf1mtHiqTwLZDTJIRHWHufJzHf-sw\","
+               "\"p\":\"ySeC3FtvzduDEL-FX4JqbRN06PdBhUmosCkymmbBjriuLNpkGkG-"
+                "1ex7r-M8neUBZbctmDdih6cpLZ8hjZv3eEDZ4b5Z2LqZnja4QvVoWLUs4Fb"
+                "NN_PxJCR5H28uUfT6ThxqT0Nb2enb8Dyp0Qxvd7eJUeYz6jOt7pEK-ErTB4"
+                "M\","
+               "\"q\":\"vHG2Pd6QUH7vFZjJtXwmlVnrz5tdJvUPQvz7ggeM69cqhf4vLajz"
+                "sqP9GhJr7bEkp6vKVdZGmfEdiFRD8cssIZq651oAO5Wr7zZd2mR_hG9jZx7"
+                "8Davfuxr4SZNN-bmoxO6dbDi-X2c7fvMI2YeJwL4groNKyiosdUYILTrYRI"
+                "c\","
+               "\"dp\":\"h5Gqf2rcokgEQGBjuigCJDtNuskRjoxDNV6-rRL99nt_X9lcR9n"
+                "xjOnRvowOyXeTBoN7JjCFpllBxm6ORYtNMO28KomIsimo6NmGPBJ7XfXVJe"
+                "k6bDBrX-l4_HeJJ1FM9SHvgDYsjGQxh-rKpIqWAYBf-yOD758e5T85vndnX"
+                "JM\","
+               "\"dq\":\"K9LiB-dfdmjenw4mMp-JtYfw8Bn4gtvQzcpZjzbETgB-8iRXwm2"
+                "dJvk-HjcUhHWCyb-I0YeAacKKFK9MEconHDWIq87haPn4vyvMjcJ7aUgiPN"
+                "QW1_MVl8TA4xNvudi0Z__5-jYEB9nRG0fX0gbUQU-19_-uf-9o4WkE88fQj"
+                "bc\","
+               "\"qi\":\"LEkTRqmomn9UiASeRfAKw-Z5q7cye9CSL4luSexFvA3Du7Oin-s"
+                "L9a7F3nJN4CuYzhtNMxQ0hM7k6ExzhDhXDlNRHxnNEDt81-CFRV98v7GVWV"
+                "SH1KnaKf9wgegxSSm-x536ki2SI8EN4k4qkqRF0iLVHZK7CgnWMbtt6tnpp"
+                "3k\""
+       "}";
+/* produced by running the minimal example `lws-crypto-jwk -t RSA -b 4096 -c` */
+
+static const char *rsa_key_4096 =
+       "{"
+               "\"e\":\"AQAB\","
+               "\"kty\":\"RSA\","
+               "\"n\":\"uiLBz1SUgd4eQ0okg6tlPdk9QUhTsqXmiJXygWVFgzT45E5_Rfkq"
+                "vZ2fwAqQ8DvxkDTUWiKpeXMpPRNWG5GxuBuq9n7xdA1vn1eQi8LoekB28dg"
+                "3MwMfozVSKCzyxG1f81xPE5x3EMVhCcx6hshhlMEHkzNNhE07d-oRO87ZC0"
+                "z_5L3Vh03uJBXaDKVlsgHAazoHLhn6G4odqv-ro54T6Nx1eEtyTnMmFY5ND"
+                "V4rN0SjQvSefbZZtsrtby8Z0JmeyvynmDwOINj7FpmPmpFLoWGXntc2yxPP"
+                "8SHnqfT9ESh94fxCMxRhDNohgpegRHyiYwj3M5ZYY6reCZYfOQONSWmc8yp"
+                "NBMJqj4LuJ2bTMGAFS17ZP4ZZWm5RP9ax100Dgk0yxP1UrybG5dCfJRQvHC"
+                "ncxG_aL6cSQu2o4fXqlJsNHxk3FjHtV_CMZ3tqvGTvwrs4yxvKwKv6r3fRh"
+                "KL01bGOePzp9THkHW2-lzVj6kUwnxBdHGZE6fcAnczOdp8ZIEdV1w6ThimC"
+                "m3Bw_TIyl3tkuxRWXpc_d6Q4iiSVKGKCvUvfAlESpTA4tIhQkij-T9FEoj2"
+                "WE2H1D35AKmjcfLCh6yszu8cmDNedn862pwnawE2RvRFAyuI113fLQeCbCz"
+                "tQ1JHuD8cnQt0hpGzReTa5UJ8OEOGIlyXNdWZyTpk\","
+               "\"d\":\"G2ZW582AT-6xvz-IiP5fuJ9EMloygeuEeEo0aMJO3X3cfoUknJkN"
+                "ZtyvYa5cgBSe3la8hKkyD9_5K9WvGP9VLTAbdk4g_m-k5QyXiU9PeAGJ0Nd"
+                "-Zqq4y0Zj2eil8u7Tz0fhFxay-zvG6VGZnsIcBTD2C7_jUwyoaqJA17A_CH"
+                "gU-ifMqS56VgMGdlKZmf7Cg7ZGzM1DoS6vZ9bbfgoczaw4OZVHlg9Cxa0NI"
+                "CDi1S-sJcTLGN_RLISKN5H0J54ZfzF6fUEn5kNykLTZrAvj2XV7g4UUOogn"
+                "1cvjJYRcBVzTzQKcfxbqo2DvymDGFZbQM6pj80rYJ5HFPh2EapjggPN8hXp"
+                "NlTNDEvC84QFv0lo2E-0nVWQqcyHtXd431O1JH2h5X822zKjXxkaztQSCj9"
+                "YP7AdAeoxIaWOa3aO1vcwURH2WWaNV-_KXVkPJNzfo9-bGYwblMw_RIqIkN"
+                "BDayTb8rBuQHTCE_tSEHgoSnkityGpr8j_vgA-Fa-SqmdqUlbklVpwA_Mq_"
+                "UH7RCaqe91dWxRhS_7c85tFMRFCKOcaRXkwxEpP2LD1AYe8yvVQlr0Se8_d"
+                "RefuQcC-BECwMW-TCgR3VxAuL7ExNTYe4bhBD8WYXsHP7wDXWX2Q4v7IRzj"
+                "cfVIdpTNYuWEd69PvXBCuy75hmDniSmS3Xps3ItGU\","
+               "\"p\":\"961BtLSIZkHO7Vu1KfaA3urcwGpISKJiTSB5Nh6npxJr9mSjzv_f"
+                "e8VoxCX6CWGY0SEeQNUQ6ceTnAAxkSHtZJQGed598jBtxIexAWEE7oc9s9d"
+                "b0cWu4QWIVZYXrcOTEWmK1kWN4PXmnnQknrWQF49adn81BaOXqoL-tahe7f"
+                "faXzXe0RXuohK543ZKbuuHQ2TxqFG7CZpXiH_qn1Syao32u0V3iDFpmmCUV"
+                "h9O2JCzfo8sAosTrnQwC0pXz3Nvr_9Cnk6bMluJoMrwB1Ywg_DPQ1WvpYHO"
+                "URezEOqVC8Y3zrko199TMX2COKGNFgutVpnzxs2_h0PyINUmwrY4zQ\","
+               "\"q\":\"wGQRaxy_gBafbrVJy4f32O0a2FQHzmS--WgHhoteDoF6ZAajLcV0"
+                "GEvb-AVmFER1Wii62BFaFJOYQIegELvnBFFzD6oHJRX7bM4m36G8J_TC1o9"
+                "T1IFnxOpaoFDf4JWf2k7DCXClGg_zueyOD8fj8F6j2nqpOfytuLmikHcWMc"
+                "dGTHTCRtQmvOk3pm0uk2qR0cQb5L3Ocv45tCKr55tMc6Zx3DKkMt1kmUwd2"
+                "HFfk_0WM6R7q4LNGIjwl8dwiERppLKA8xao9i3jOOdFEfAD-Zqv8H-32cyH"
+                "Mg6Guo4tPNAYSzcsz8nbEYPtKVVm-PDuM2cx0iaKnS8BIK2XTbzc_Q\","
+               "\"dp\":\"ZXLWIwp_hEMYWyjhP9r0VlqlKTtfeEDrOuQ-Qei0iz6EclwurK8"
+                "p_yyRCSb1D7qmOaLzHWMollllINUDeIsJDdWEAY8cz4L-sy1RV1tCBeHnaC"
+                "6iMX5jb1Aw072y3T3qk4tDjxjWUHroh6bTCR8dckkJqNfaBAFKMlGNuyLIH"
+                "3kSPUV3ivUM1d4NvhnJyz02HmjOgz9W-Uv65rJei_zJR9P2aCbAG00CEHXW"
+                "zJ_uT86VdxV11WTaHu8Abt94sER8Tv6jbuyLrUjJSs9VGew32xNcEhya4ZQ"
+                "VyimG8zri6fu7CDXXgPS8wtzB5ihl_c2ypnJQ4_GKrgEqwEAOrFqvUQ\","
+               "\"dq\":\"uzlmngcm8R6S3qi7fL7_2fG7uyPjSN5P3uR21l8QFCu6kFbJO8S"
+                "4muBP20hds4F_dlLGqXgRYo7TjpCtmztQsKoWv_ql41hGCfeAawa41WViqm"
+                "xmlxmrgzzRHsw1YhgZrNgTAz_E290EQT3Mbd0HnCZtbDMMNisIYAj_A3lwd"
+                "tbHOaYyXb0dSZ_nkSUVO05tQ2aGAo8Xtl5ih0NqaQR_XNhwW2pI0lsTB__D"
+                "15tU-O5FSdJaq2ip8KNrBzmF8IYrDKTNykKWAKRdSEX_uFoLdD8t0mxn3SM"
+                "luffa8vdjXJfh3GiASmHUt3HcPOooQEAufoWBPVJWeGqCvWtRH8yYfQ\","
+               "\"qi\":\"h-e9es5J49OUF48gSXUI8cynZ8ydv5cThXc1deV3mil_7_7Hg8E"
+                "jV3gAErO4l-irHJplFmHFZvU1ud4zs1gtBt5TA-EeeepYOHMSssWDvDK3WI"
+                "zsM6C3vcNTSkT-ihaSFmPWHCVwJ1R3auWfeI2In3at0jd4t-OK-cCcGZXb7"
+                "90-EnyyDcdFTU9WfwVSOJffRGjoUYX8DexavClv7CBzPhpdUzGoeyarNaG4"
+                "z9MI8Q8txHyHgc_D70lZUum1cj0bZwgEj6yDzOPzSgUmICFJiLDDj93oPaI"
+                "v-5CQ_Ckju7icexc_kuuYTKBOLTj_vfaURnV3KCHul2UljUYOxkfeNQ\""
+       "}";
+
+static const char *rsa_key_4096_no_optional =
+       "{"
+               "\"e\":\"AQAB\","
+               "\"kty\":\"RSA\","
+               "\"n\":\"uiLBz1SUgd4eQ0okg6tlPdk9QUhTsqXmiJXygWVFgzT45E5_Rfkq"
+                "vZ2fwAqQ8DvxkDTUWiKpeXMpPRNWG5GxuBuq9n7xdA1vn1eQi8LoekB28dg"
+                "3MwMfozVSKCzyxG1f81xPE5x3EMVhCcx6hshhlMEHkzNNhE07d-oRO87ZC0"
+                "z_5L3Vh03uJBXaDKVlsgHAazoHLhn6G4odqv-ro54T6Nx1eEtyTnMmFY5ND"
+                "V4rN0SjQvSefbZZtsrtby8Z0JmeyvynmDwOINj7FpmPmpFLoWGXntc2yxPP"
+                "8SHnqfT9ESh94fxCMxRhDNohgpegRHyiYwj3M5ZYY6reCZYfOQONSWmc8yp"
+                "NBMJqj4LuJ2bTMGAFS17ZP4ZZWm5RP9ax100Dgk0yxP1UrybG5dCfJRQvHC"
+                "ncxG_aL6cSQu2o4fXqlJsNHxk3FjHtV_CMZ3tqvGTvwrs4yxvKwKv6r3fRh"
+                "KL01bGOePzp9THkHW2-lzVj6kUwnxBdHGZE6fcAnczOdp8ZIEdV1w6ThimC"
+                "m3Bw_TIyl3tkuxRWXpc_d6Q4iiSVKGKCvUvfAlESpTA4tIhQkij-T9FEoj2"
+                "WE2H1D35AKmjcfLCh6yszu8cmDNedn862pwnawE2RvRFAyuI113fLQeCbCz"
+                "tQ1JHuD8cnQt0hpGzReTa5UJ8OEOGIlyXNdWZyTpk\","
+               "\"d\":\"G2ZW582AT-6xvz-IiP5fuJ9EMloygeuEeEo0aMJO3X3cfoUknJkN"
+                "ZtyvYa5cgBSe3la8hKkyD9_5K9WvGP9VLTAbdk4g_m-k5QyXiU9PeAGJ0Nd"
+                "-Zqq4y0Zj2eil8u7Tz0fhFxay-zvG6VGZnsIcBTD2C7_jUwyoaqJA17A_CH"
+                "gU-ifMqS56VgMGdlKZmf7Cg7ZGzM1DoS6vZ9bbfgoczaw4OZVHlg9Cxa0NI"
+                "CDi1S-sJcTLGN_RLISKN5H0J54ZfzF6fUEn5kNykLTZrAvj2XV7g4UUOogn"
+                "1cvjJYRcBVzTzQKcfxbqo2DvymDGFZbQM6pj80rYJ5HFPh2EapjggPN8hXp"
+                "NlTNDEvC84QFv0lo2E-0nVWQqcyHtXd431O1JH2h5X822zKjXxkaztQSCj9"
+                "YP7AdAeoxIaWOa3aO1vcwURH2WWaNV-_KXVkPJNzfo9-bGYwblMw_RIqIkN"
+                "BDayTb8rBuQHTCE_tSEHgoSnkityGpr8j_vgA-Fa-SqmdqUlbklVpwA_Mq_"
+                "UH7RCaqe91dWxRhS_7c85tFMRFCKOcaRXkwxEpP2LD1AYe8yvVQlr0Se8_d"
+                "RefuQcC-BECwMW-TCgR3VxAuL7ExNTYe4bhBD8WYXsHP7wDXWX2Q4v7IRzj"
+                "cfVIdpTNYuWEd69PvXBCuy75hmDniSmS3Xps3ItGU\","
+               "\"p\":\"961BtLSIZkHO7Vu1KfaA3urcwGpISKJiTSB5Nh6npxJr9mSjzv_f"
+                "e8VoxCX6CWGY0SEeQNUQ6ceTnAAxkSHtZJQGed598jBtxIexAWEE7oc9s9d"
+                "b0cWu4QWIVZYXrcOTEWmK1kWN4PXmnnQknrWQF49adn81BaOXqoL-tahe7f"
+                "faXzXe0RXuohK543ZKbuuHQ2TxqFG7CZpXiH_qn1Syao32u0V3iDFpmmCUV"
+                "h9O2JCzfo8sAosTrnQwC0pXz3Nvr_9Cnk6bMluJoMrwB1Ywg_DPQ1WvpYHO"
+                "URezEOqVC8Y3zrko199TMX2COKGNFgutVpnzxs2_h0PyINUmwrY4zQ\","
+               "\"q\":\"wGQRaxy_gBafbrVJy4f32O0a2FQHzmS--WgHhoteDoF6ZAajLcV0"
+                "GEvb-AVmFER1Wii62BFaFJOYQIegELvnBFFzD6oHJRX7bM4m36G8J_TC1o9"
+                "T1IFnxOpaoFDf4JWf2k7DCXClGg_zueyOD8fj8F6j2nqpOfytuLmikHcWMc"
+                "dGTHTCRtQmvOk3pm0uk2qR0cQb5L3Ocv45tCKr55tMc6Zx3DKkMt1kmUwd2"
+                "HFfk_0WM6R7q4LNGIjwl8dwiERppLKA8xao9i3jOOdFEfAD-Zqv8H-32cyH"
+                "Mg6Guo4tPNAYSzcsz8nbEYPtKVVm-PDuM2cx0iaKnS8BIK2XTbzc_Q\""
+       "}";
+
+/* This is a compact JWE containing the plaintext ra_ptext_1024 for the key
+ * lws_jwe_ex_a2_jwk_json... produced by  test test above running on OpenSSL.
+ */
+
+static char *jwe_compact_rsa_cbc_openssl =
+       "eyAiYWxnIjoiUlNBMV81IiwiZW5jIjoiQTEyOENCQy1IUzI1NiJ9"
+       "."
+       "mWXwMv4hxwgKbUAyMFAuHxiKjg62Z5owkFYLgxho5FNT3Hm5ZGiF8plS5W3NwUTmv8t6C"
+       "I0kV5cOOJXE_PXPaOptsie2aoQR-_Bs6gAFixa7aZNsnsMF4lMAiIy7VkrvP2qh0s04y2"
+       "2poOLfmS93tB9AyWdlnQ6Z-U1wzrM9kncqO9GpPol9M4WnAss1ZtTE-9Tbc7dMHURHbZb"
+       "vHn2h625pBD8oD_s0osRav8YEw7jNeQjW_ch4pI6HRox-hf0dyLtk9yFCtBjxbCvysadW"
+       "SlZPJBj0HYv0BVqCK0fETi7URx4MCJ3zgCJnpAuQo2yq1yQzXwOYcFoLIvY0jIm44A"
+       "."
+       "WINMABhU_GQKJarmmTP_-g"
+       "."
+       "V9kHAh9ajE558EPj_zX6p_C903MevMPJLcMU4MWhfhwe1cFW_0io-LvZfcF_Xj7aNoIZd"
+       "vPXJ0On_jHPFsnwe4dus6kuh8RrSKFFV0sGIv-FFXrKB99FFRY_8BTPsYFrcqt_8EV2Af"
+       "p7toaVOO15WXOEH6Ym81a3aOWCVGdj_akMN46Qx_JrQaql-Xs_fL2HdpaEWHHTV2ac9aY"
+       "ah7o0Ojl9UnzkHyXieRgrjXymvCcT0te3D4OQJhrv7TzH_hfKu621O-Frmkr-NvQGSNcl"
+       "fVgRkte2ks34j5HPqEbJQWWKG3IDfkPRvWmDZzEXW_JTrK_1r1FM-aYtY79tLnir8Zw7I"
+       "WCczD-XmtlOJNYA2Ss5dbjoJDtevbqaZWVl-sDSwO1xdf-DUfiemep7S7IFoFAdl0vXLT"
+       "YtuNBxuFw-cP2Kwi8RyF__uENo4vD003cI4htqSYIYXeyAVqWIkmsP1BFpT7MGixfvhAu"
+       "VCj_ToJmowGY3bOHiMuzyT9M7wtCCiCySEBARVU-EdQBXj8X-quSj-0OnBtxXChUS4QXw"
+       "q2pNn3UKSMsxqvHR25HQq_6U2AbvNHxKhup3luzn0T27uy0l3XeWSz_48SwJZKRnbYPtC"
+       "n5Jd5mRdr5GxihpNwupaO4BWnHZo_fHUTI9-Z18lpj_4QB-c3dzDL15xFN4HEZ5lv2iO5"
+       "zMiRI_NlVVDdA9lqGpn4IyO44osHQieBraUjWF8X5cSXDoqktXDVymAdrxe0fYZQca6Bq"
+       "CsBqFTYae4CG01SpG46ysfwAXmsTEKPzj7uiOguFCRB4hClTd-Q8R2axj9JNT1jU_Vb7U"
+       "GKFBGeDJt5PDXJyvW5rHyiQDewykf0Lpvdp39yITT8qARmJl2SwCrDCPADZ4TwwobT42B"
+       "J_Cq5IKgEOeuS3S7NOdOfXxmAcNfN0yujKbmfiOxnXhwnepQ-TnpgTV0nv8snBRITN7mS"
+       "EgflqQlKAZus_0mDbHmBmw1nY-0q4qMWI03IEwMC57-p4JLshnWgIAupnFCGp9nyi4E_s"
+       "GVyQlGCxzC5VSH1Hba3rvbulQGxx_kGk0j56NGhGsQEzqvSuI4xgIsGMPo1Ii7xUh68dd"
+       "BzJRzaov9oDTgnWM5-hoEQQoazW7hDKAFPYccC6zqX0fnI7vBIIBZsjUsol6-5bdujpb4"
+       "l3LRGCjULXlSPbnNGzyk5R-mIwQC8aM9wcIiZZdcdHdr4meMNr3HmpG_B5xtBmENAJAvU"
+       "K3DO6pro2xhypuNKYtOAdH0Xyl8QBPIJ0EFVH6_1V-H_gHs2MLMIqGfUmFCuRev60APcw"
+       "Pbf-GZxLeXLutPq2DOl1HD0XLNtYL1dB1aw2j4L8OJREOC_N-KpIH3g"
+       "."
+       "n4QRlTzW2urRnNiJlwQkZw"
+;
+
+
+static int
+test_jwe_r256a128_jwe_openssl(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, (char *)lws_jwe_ex_a2_jwk_json,
+                          strlen((char *)lws_jwe_ex_a2_jwk_json)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode((const char *)jwe_compact_rsa_cbc_openssl,
+                                  strlen((char *)jwe_compact_rsa_cbc_openssl),
+                                  &jwe.jws.map, &jwe.jws.map_b64,
+                                  temp, &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ra_ptext_1024) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ra_ptext_1024,
+                               sizeof(ra_ptext_1024))) {
+               lwsl_err("%s: plaintext RSA/AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ra_ptext_1024, sizeof(ra_ptext_1024));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+/* This is a compact JWE containing the plaintext ra_ptext_1024 for the key
+ * lws_jwe_ex_a2_jwk_json... produced by  test test above running on mbedTLS.
+ */
+
+static char
+*jwe_compact_rsa_cbc_mbedtls =
+       "eyAiYWxnIjoiUlNBMV81IiwiZW5jIjoiQTEyOENCQy1IUzI1NiJ9.oBqKJ06UJs2oryPLWZKyI8743GC0geUt_xaKLMaPtApp__swG2w0IhNtmkIBKA9LeeGyiCWKpGGzOlQUR5YSxrT99PnincHXw_pkCprOvi4j3oxThJ2pFRx-CBc9ZgPJ3Kje1QifOueT3vQt_65iiyXmqyc5PDxzuV0L_KtrA_jEsm2m1JVBMOX--qzXjYyqx_dc87d43TXY_4kuTmAtqVpQe7ixKJlUViPVSzuASyeLEUTIaNlALuEWial1wP-ICF37OQzOcZRH3OVZObrcZi1aWkDOLxF4qO4I_GtpuAgZT732a7gnobR-T2oyBpimcqCVEk88Wa7cYyBXZvAOUA.fNLEFh1mjdlyc3WKw0I2Kg.e8X-11K9yXK0KkK-8ikplEWFViruqduaKPDOA7x6lKpBk8l3RFX1aqC4s0WVc1eN0qd-fB__EoO_AIG1xsfw1ie2IDWV0p18ZaRkQRN9Th5UU-W9C9XyPFQUxcl7ShKRE-yKJU-VdZDk6L2-07FH3s-voVKx0oqLIYqkkXp9a2jvnzrZ0Psujs4PSCHOZEgcS8PNdMmdsjDHLsb0NDMifOSlXk2Mp6V2SizXRIPJtOkVJGKwuBc7FbdO02GnzzVXldiLC7GI0zoRsnSJndF8yc3pMrMQhoVRktkBClAcIujD_OxJwHG-i3OJqUg1uVfci86RoQrnULoygvB7apX_WMxF7eXXJdXbG8sPLLCf0SW4sgvuSclOHL2UXzGi6Tp_l1XjxFQTzVEfUaj7i0gD2wM74Ru79RX8yO0m-5qOOwkySU1lEXqbLTuxjJXD9WLcTQQmF0Nm5myTUyNOl7xKpeDpnNt5A0L8o6SW6iJ3DwZEzhMxk3JWQOYtQP1J2sgwAKEDM6SkGzTy9QXpCEoraKp2UEzunux9S6-roYpzgEFT2RZrq3Hg_JyequTtrcNaoiEKd5szJvE6pUc25WEjDzgg79v_n40gQm688mO62kiVBThVmc88u2JVlNpzVQFUfKt-bu2Xxiqn5lRfEMK93EEPZRd8n12vBq5aJKvvEpPN1AC4HaMepf78Ob0GNTYGR-70zSS0ErecCeIgUJ1CttE2Nn0qEOfbQcO48SjeIltecl9DRzeLT3tPN3Z4BqbzSX8kKU5LStUX5YC-obM_0Ss7swXJM19I1O-QH8VbHZl-9TADR6BLzmrsJQ9_BL_uTB6uPdLhYfqWw6VUf0eMLaqvsY92vV5-JVQqyv7s70FNLT1-8P94k79ZGiLvNdDNZgGsmRQOwA2Vk6snHI0oUYGj7NeEK4O64ZfNRZJgPfWnxtQ-LIhSYCJvxFGL7ZMoA_ijKl9_v_bRqd03_7o8YQisw2luDYqLa87Dh9u9tacOoraGAzcEBIAh-BOcnIrQEt5KoSbly5xNAkfqj7QDvL0vPHArZ5E3Gb_k3VbKjsqCzvisNMEjm887Z-Dc6tW4Y2OceYf-rfUDvJ3EXZ66CWSQ7yKhPVcP1RRtNUFEqLoIAkA4aEAAS2ZPKVHIJQwyMzbbNFAuvY_7piNYprAI5lySFcA1cz_hKl6s9xmqbAkH2XGZZduw5Nv-aY_LMXujjhmblqE2Ocej91xTdgMe74Ftr1b3y9FvPPVSqNjpTSfujCi5L57LOpjT78do8eSrDz6coG0zeRUybjWeTszoiYbif_NlyAcMScO5OMZHNkre6L8u-AVeYSKTGsdpK7em_iLN8cGSEjZABNAr_A9Lfg.6Qb_Qf-ktX0DRHWUHAJxDQ"
+;
+
+static int
+test_jwe_r256a128_jwe_mbedtls(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, (char *)lws_jwe_ex_a2_jwk_json,
+                          strlen((char *)lws_jwe_ex_a2_jwk_json)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode((const char *)jwe_compact_rsa_cbc_mbedtls,
+                                  strlen((char *)jwe_compact_rsa_cbc_mbedtls),
+                                  &jwe.jws.map, &jwe.jws.map_b64,
+                                  temp, &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ra_ptext_1024) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ra_ptext_1024,
+                               sizeof(ra_ptext_1024))) {
+               lwsl_err("%s: plaintext RSA/AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ra_ptext_1024, sizeof(ra_ptext_1024));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+
+/* A.3.  Example JWE Using AES Key Wrap and AES_128_CBC_HMAC_SHA_256
+ *
+ * This example encrypts the plaintext "Live long and prosper." to the
+ * recipient using AES Key Wrap for key encryption and
+ * AES_128_CBC_HMAC_SHA_256 for content encryption.
+ */
+
+/* "Live long and prosper." */
+static uint8_t
+
+ex_a3_ptext[] = {
+       76, 105, 118, 101, 32, 108, 111, 110,
+       103, 32, 97, 110, 100, 32,  112, 114,
+       111, 115, 112, 101, 114, 46
+},
+
+*ex_a3_compact = (uint8_t *)
+       "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0"
+       "."
+       "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ"
+       "."
+       "AxY8DCtDaGlsbGljb3RoZQ"
+       "."
+       "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY"
+       "."
+       "U0m_YmjN04DJvceFICbCVQ",
+
+*ex_a3_key = (uint8_t *)
+       "{\"kty\":\"oct\","
+          "\"k\":\"GawgguFyGrWKav7AX4VKUg\""
+       "}"
+;
+
+static int
+test_jwe_a3(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, (char *)ex_a3_key,
+                          strlen((char *)ex_a3_key)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode((const char *)ex_a3_compact,
+                                  strlen((char *)ex_a3_compact),
+                                  &jwe.jws.map, &jwe.jws.map_b64, temp,
+                                  &temp_len)  != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(ex_a3_ptext) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], ex_a3_ptext,
+                               sizeof(ex_a3_ptext))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(ex_a3_ptext, sizeof(ex_a3_ptext));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+/* JWA B.2.  Test Cases for AES_192_CBC_HMAC_SHA_384
+ *
+ * Unfortunately JWA just gives this test case as hex literals, not
+ * inside a JWE.  So we have to prepare the inputs "by hand".
+ */
+
+static uint8_t
+
+jwa_b2_ptext[] = {
+       0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72,
+       0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
+       0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74,
+       0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
+       0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20,
+       0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65,
+       0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69,
+       0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62,
+       0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74,
+       0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69,
+       0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
+       0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66,
+       0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65,
+       0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f,
+       0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e,
+       0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65
+},
+
+jwa_b2_rawkey[] = {
+       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+       0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+       0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+       0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+},
+
+jwa_b2_iv[] = {
+       0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd,
+       0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04
+},
+
+jwa_b2_e[] = {
+       0xea, 0x65, 0xda, 0x6b, 0x59, 0xe6, 0x1e, 0xdb,
+       0x41, 0x9b, 0xe6, 0x2d, 0x19, 0x71, 0x2a, 0xe5,
+       0xd3, 0x03, 0xee, 0xb5, 0x00, 0x52, 0xd0, 0xdf,
+       0xd6, 0x69, 0x7f, 0x77, 0x22, 0x4c, 0x8e, 0xdb,
+       0x00, 0x0d, 0x27, 0x9b, 0xdc, 0x14, 0xc1, 0x07,
+       0x26, 0x54, 0xbd, 0x30, 0x94, 0x42, 0x30, 0xc6,
+       0x57, 0xbe, 0xd4, 0xca, 0x0c, 0x9f, 0x4a, 0x84,
+       0x66, 0xf2, 0x2b, 0x22, 0x6d, 0x17, 0x46, 0x21,
+       0x4b, 0xf8, 0xcf, 0xc2, 0x40, 0x0a, 0xdd, 0x9f,
+       0x51, 0x26, 0xe4, 0x79, 0x66, 0x3f, 0xc9, 0x0b,
+       0x3b, 0xed, 0x78, 0x7a, 0x2f, 0x0f, 0xfc, 0xbf,
+       0x39, 0x04, 0xbe, 0x2a, 0x64, 0x1d, 0x5c, 0x21,
+       0x05, 0xbf, 0xe5, 0x91, 0xba, 0xe2, 0x3b, 0x1d,
+       0x74, 0x49, 0xe5, 0x32, 0xee, 0xf6, 0x0a, 0x9a,
+       0xc8, 0xbb, 0x6c, 0x6b, 0x01, 0xd3, 0x5d, 0x49,
+       0x78, 0x7b, 0xcd, 0x57, 0xef, 0x48, 0x49, 0x27,
+       0xf2, 0x80, 0xad, 0xc9, 0x1a, 0xc0, 0xc4, 0xe7,
+       0x9c, 0x7b, 0x11, 0xef, 0xc6, 0x00, 0x54, 0xe3
+},
+
+jwa_b2_a[] = { /* "The second principle of Auguste Kerckhoffs" */
+       0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f,
+       0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63,
+       0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20,
+       0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20,
+       0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66,
+       0x66, 0x73
+},
+
+jwa_b2_tag[] = {
+       0x84, 0x90, 0xac, 0x0e, 0x58, 0x94, 0x9b, 0xfe,
+       0x51, 0x87, 0x5d, 0x73, 0x3f, 0x93, 0xac, 0x20,
+       0x75, 0x16, 0x80, 0x39, 0xcc, 0xc7, 0x33, 0xd7
+
+}
+;
+
+static int
+test_jwa_b2(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       int n, ret = -1;
+       char buf[2048];
+
+       lws_jwe_init(&jwe, context);
+
+       /*
+        * normally all this is interpreted from the JWE blob.  But we don't
+        * have JWE test vectors for AES_256_CBC_HMAC_SHA_512, just a standalone
+        * one.  So we have to create it all by hand.
+        *
+        * See test_jwe_a3 above for a more normal usage pattern.
+        */
+
+       lws_jwk_dup_oct(&jwe.jwk, jwa_b2_rawkey, sizeof(jwa_b2_rawkey));
+
+       memcpy(buf, jwa_b2_e, sizeof(jwa_b2_e));
+
+       jwe.jws.map.buf[LJWE_IV] = (char *)jwa_b2_iv;
+       jwe.jws.map.len[LJWE_IV] = sizeof(jwa_b2_iv);
+
+       jwe.jws.map.buf[LJWE_CTXT] = buf;
+       jwe.jws.map.len[LJWE_CTXT] = sizeof(jwa_b2_e);
+
+       jwe.jws.map.buf[LJWE_ATAG] = (char *)jwa_b2_tag;
+       jwe.jws.map.len[LJWE_ATAG] = sizeof(jwa_b2_tag);
+
+       /*
+        * Normally this comes from the JOSE header.  But this test vector
+        * doesn't have one... so...
+        */
+
+       if (lws_gencrypto_jwe_alg_to_definition("A128KW", &jwe.jose.alg))
+               goto bail;
+       if (lws_gencrypto_jwe_enc_to_definition("A192CBC-HS384",
+                                               &jwe.jose.enc_alg))
+               goto bail;
+
+       n = lws_jwe_auth_and_decrypt_cbc_hs(&jwe, jwa_b2_rawkey,
+                                                   jwa_b2_a, sizeof(jwa_b2_a));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_a_cbc_hs_decrypt failed\n", __func__);
+
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(jwa_b2_ptext) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT],jwa_b2_ptext,
+                               sizeof(jwa_b2_ptext))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(jwa_b2_ptext, sizeof(jwa_b2_ptext));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+
+/* JWA B.3.  Test Cases for AES_256_CBC_HMAC_SHA_512
+ *
+ * Unfortunately JWA just gives this test case as hex literals, not
+ * inside a JWE.  So we have to prepare the inputs "by hand".
+ */
+
+static uint8_t
+
+jwa_b3_ptext[] = {
+       0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72,
+       0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20,
+       0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74,
+       0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
+       0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20,
+       0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65,
+       0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69,
+       0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62,
+       0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74,
+       0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69,
+       0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
+       0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66,
+       0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65,
+       0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f,
+       0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e,
+       0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65
+},
+
+
+jwa_b3_rawkey[] = {
+       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+       0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+       0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+       0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+       0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+       0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f
+},
+
+jwa_b3_iv[] = {
+       0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd,
+       0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04
+},
+
+jwa_b3_e[] = {
+       0x4a, 0xff, 0xaa, 0xad, 0xb7, 0x8c, 0x31, 0xc5,
+       0xda, 0x4b, 0x1b, 0x59, 0x0d, 0x10, 0xff, 0xbd,
+       0x3d, 0xd8, 0xd5, 0xd3, 0x02, 0x42, 0x35, 0x26,
+       0x91, 0x2d, 0xa0, 0x37, 0xec, 0xbc, 0xc7, 0xbd,
+       0x82, 0x2c, 0x30, 0x1d, 0xd6, 0x7c, 0x37, 0x3b,
+       0xcc, 0xb5, 0x84, 0xad, 0x3e, 0x92, 0x79, 0xc2,
+       0xe6, 0xd1, 0x2a, 0x13, 0x74, 0xb7, 0x7f, 0x07,
+       0x75, 0x53, 0xdf, 0x82, 0x94, 0x10, 0x44, 0x6b,
+       0x36, 0xeb, 0xd9, 0x70, 0x66, 0x29, 0x6a, 0xe6,
+       0x42, 0x7e, 0xa7, 0x5c, 0x2e, 0x08, 0x46, 0xa1,
+       0x1a, 0x09, 0xcc, 0xf5, 0x37, 0x0d, 0xc8, 0x0b,
+       0xfe, 0xcb, 0xad, 0x28, 0xc7, 0x3f, 0x09, 0xb3,
+       0xa3, 0xb7, 0x5e, 0x66, 0x2a, 0x25, 0x94, 0x41,
+       0x0a, 0xe4, 0x96, 0xb2, 0xe2, 0xe6, 0x60, 0x9e,
+       0x31, 0xe6, 0xe0, 0x2c, 0xc8, 0x37, 0xf0, 0x53,
+       0xd2, 0x1f, 0x37, 0xff, 0x4f, 0x51, 0x95, 0x0b,
+       0xbe, 0x26, 0x38, 0xd0, 0x9d, 0xd7, 0xa4, 0x93,
+       0x09, 0x30, 0x80, 0x6d, 0x07, 0x03, 0xb1, 0xf6,
+},
+
+jwa_b3_a[] = { /* "The second principle of Auguste Kerckhoffs" */
+       0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f,
+       0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63,
+       0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20,
+       0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20,
+       0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66,
+       0x66, 0x73
+},
+
+jws_b3_tag[] = {
+       0x4d, 0xd3, 0xb4, 0xc0, 0x88, 0xa7, 0xf4, 0x5c,
+       0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf,
+       0x2e, 0x62, 0x69, 0xa8, 0xc5, 0x6a, 0x81, 0x6d,
+       0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5
+}
+;
+
+static int
+test_jwa_b3(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char buf[2048];
+       int n, ret = -1;
+
+       lws_jwe_init(&jwe, context);
+
+       /*
+        * normally all this is interpreted from the JWE blob.  But we don't
+        * have JWE test vectors for AES_256_CBC_HMAC_SHA_512, just a standalone
+        * one.  So we have to create it all by hand.
+        *
+        * See test_jwe_a3 above for a more normal usage pattern.
+        */
+
+       lws_jwk_dup_oct(&jwe.jwk, jwa_b3_rawkey, sizeof(jwa_b3_rawkey));
+
+       memcpy(buf, jwa_b3_e, sizeof(jwa_b3_e));
+
+       jwe.jws.map.buf[LJWE_IV] = (char *)jwa_b3_iv;
+       jwe.jws.map.len[LJWE_IV] = sizeof(jwa_b3_iv);
+
+       jwe.jws.map.buf[LJWE_CTXT] = buf;
+       jwe.jws.map.len[LJWE_CTXT] = sizeof(jwa_b3_e);
+
+       jwe.jws.map.buf[LJWE_ATAG] = (char *)jws_b3_tag;
+       jwe.jws.map.len[LJWE_ATAG] = sizeof(jws_b3_tag);
+
+       /*
+        * Normally this comes from the JOSE header.  But this test vector
+        * doesn't feature one...
+        */
+
+       if (lws_gencrypto_jwe_alg_to_definition("A128KW", &jwe.jose.alg))
+               goto bail;
+       if (lws_gencrypto_jwe_enc_to_definition("A256CBC-HS512",
+                                               &jwe.jose.enc_alg))
+               goto bail;
+
+       n = lws_jwe_auth_and_decrypt_cbc_hs(&jwe, jwa_b3_rawkey,
+                                                   jwa_b3_a, sizeof(jwa_b3_a));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_a_cbc_hs_decrypt failed\n", __func__);
+
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < sizeof(jwa_b3_ptext) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT],jwa_b3_ptext,
+                               sizeof(jwa_b3_ptext))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(jwa_b3_ptext, sizeof(jwa_b3_ptext));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+
+       if (ret)
+               lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+/* JWA C.  Example ECDH-ES Key Agreement Computation
+ *
+ * This example uses ECDH-ES Key Agreement and the Concat KDF to derive
+ * the CEK in the manner described in Section 4.6.  In this example, the
+ * ECDH-ES Direct Key Agreement mode ("alg" value "ECDH-ES") is used to
+ * produce an agreed-upon key for AES GCM with a 128-bit key ("enc"
+ * value "A128GCM").
+ *
+ * In this example, a producer Alice is encrypting content to a consumer
+ * Bob.  The producer (Alice) generates an ephemeral key for the key
+ * agreement computation.
+ *
+ * JWA Appendix C where this comes from ONLY goes as far as to confirm the
+ * direct derived key, it doesn't do any AES128-GCM.
+ */
+
+static const char
+
+*ex_jwa_c_jose =
+       "{\"alg\":\"ECDH-ES\","
+        "\"enc\":\"A128GCM\","
+        "\"apu\":\"QWxpY2U\"," /* b64u("Alice") */
+        "\"apv\":\"Qm9i\","    /* b64u("Bob") */
+        "\"epk\":" /* public part of A's ephemeral key */
+        "{\"kty\":\"EC\","
+         "\"crv\":\"P-256\","
+         "\"x\":\"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0\","
+         "\"y\":\"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps\""
+        "}"
+       "}"
+;
+
+static uint8_t
+ex_jwa_c_z[] = {
+       158,  86, 217,  29, 129, 113,  53, 211,
+       114, 131,  66, 131, 191, 132,  38, 156,
+       251,  49, 110, 163, 218, 128, 106,  72,
+       246, 218, 167, 121, 140, 254, 144, 196
+},
+ex_jwa_c_derived_key[] = {
+        86, 170, 141, 234, 248,  35, 109,  32,
+        92,  34,  40, 205, 113, 167,  16,  26
+};
+
+
+static int
+test_jwa_c(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[2048], *p;
+       int ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       /*
+        * again the JWA Appendix C test vectors are not in the form of a
+        * complete JWE, but just the JWE JOSE header, so we must fake up the
+        * pieces and perform just the (normally internal) key agreement step
+        * for this test.
+        *
+        * See test_jwe_a3 above for a more normal usage pattern.
+        */
+
+       if (lws_jwe_parse_jose(&jwe.jose, ex_jwa_c_jose, strlen(ex_jwa_c_jose),
+                              temp, &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+
+               goto bail;
+       }
+
+       /*
+        * The ephemeral key has been parsed into a jwk "jwe.jose.jwk_ephemeral"
+        *
+        *  In this example, the ECDH-ES Direct Key Agreement mode ("alg" value
+        *  "ECDH-ES") is used to produce an agreed-upon key for AES GCM with a
+        *  128-bit key ("enc" value "A128GCM").
+        */
+
+       p = lws_concat_temp(temp, temp_len);
+
+       if (lws_jwa_concat_kdf(&jwe, 1, (uint8_t *)p,
+                              ex_jwa_c_z, sizeof(ex_jwa_c_z))) {
+               lwsl_err("%s: lws_jwa_concat_kdf failed\n", __func__);
+
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (lws_timingsafe_bcmp(p, ex_jwa_c_derived_key,
+                               sizeof(ex_jwa_c_derived_key))) {
+               lwsl_err("%s: ECDH-ES direct derived key wrong\n", __func__);
+               lwsl_hexdump_notice(ex_jwa_c_derived_key,
+                                   sizeof(ex_jwa_c_derived_key));
+               lwsl_hexdump_notice(p, sizeof(ex_jwa_c_derived_key));
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+/*
+ * ECDH-ES Homebrew Encryption test
+ */
+
+static const char
+
+       /* peer key */
+
+*ecdhes_t1_peer_p256_public_key = /* as below but with d removed */
+       "{"
+        "\"crv\":\"P-256\","
+        "\"kty\":\"EC\","
+        "\"x\":\"ySlIGttmXG80WPjDO01QaXg7oAzW3NE-a-GF0NDGk_E\","
+        "\"y\":\"i08k5z4ppqgtnLK8lh5qw4qp2FhxPdGjovgilajluuw\""
+       "}",
+
+*ecdhes_t1_peer_p256_private_key = /* created by ./lws-crypto-jwk -t EC */
+       "{"
+        "\"crv\":\"P-256\","
+        "\"d\":\"ldszv0_cGFMkjxaPspGCP6X0NAaVCVeK48oH4RzT2T0\","
+        "\"kty\":\"EC\","
+        "\"x\":\"ySlIGttmXG80WPjDO01QaXg7oAzW3NE-a-GF0NDGk_E\","
+        "\"y\":\"i08k5z4ppqgtnLK8lh5qw4qp2FhxPdGjovgilajluuw\""
+       "}",
+
+*ecdhes_t1_peer_p384_public_key = /* as below but with d removed */
+       "{\"crv\":\"P-384\","
+        "\"kty\":\"EC\","
+        "\"x\":\"injKcygDoG1AuP044ct88r_2DNinHr1CGqy4q2Sy5yo034Y"
+                "7yQ5_NT-lEUXrzlIW\","
+        "\"y\":\"y52QaJLhVm-ts8xa1jL8GkmwGm_dX6xV1PSq4s3pbwx2Hu9"
+                "X29z5WYcTPFOCPtwJ\"}",
+
+*ecdhes_t1_peer_p384_private_key = /* created by ./lws-crypto-jwk -t EC -v "P-384" */
+       "{\"crv\":\"P-384\","
+        "\"d\":\"jYGze6ZwZxrflVx_I2lYWNf9GkfbeQNRwQCdtZhBlb85lk-"
+                "SAvaZuNiRUs_eWmPQ\","
+        "\"kty\":\"EC\","
+        "\"x\":\"injKcygDoG1AuP044ct88r_2DNinHr1CGqy4q2Sy5yo034Y"
+                "7yQ5_NT-lEUXrzlIW\","
+        "\"y\":\"y52QaJLhVm-ts8xa1jL8GkmwGm_dX6xV1PSq4s3pbwx2Hu9"
+                "X29z5WYcTPFOCPtwJ\"}",
+
+ *ecdhes_t1_peer_p521_public_key = /* as below but with d removed */
+       "{\"crv\":\"P-521\","
+        "\"kty\":\"EC\","
+        "\"x\":\"AYe0gAkPzzjeQW5Ek9tVrWdfi0u6k7LVUru-b2x7V9EM3d"
+                "L4SbQiS1p2j2gmZ2a6aDoKDRU_2E4u9EQrlswlty-g\","
+        "\"y\":\"AEAIIRkVL0WhtDlDSM7dciBtL1dOo5UPiW7ixIOv5K75Mo"
+                "uFNWO7cFmcxaCOn9459ex0giVyptmX_956C_DWabG6\"}",
+
+*ecdhes_t1_peer_p521_private_key = /* created by ./lws-crypto-jwk -t EC -v "P-521" */
+       "{\"crv\":\"P-521\","
+        "\"d\":\"AUer7_-qJtQtDWN6CMeGB20rzTa648kpsfidTOu3lnn6__"
+                "yOXkMj1yTYUBjVOnUjGHiTU1rCGsw4CyF-1nDRe7SM\","
+        "\"kty\":\"EC\","
+        "\"x\":\"AYe0gAkPzzjeQW5Ek9tVrWdfi0u6k7LVUru-b2x7V9EM3d"
+                "L4SbQiS1p2j2gmZ2a6aDoKDRU_2E4u9EQrlswlty-g\","
+        "\"y\":\"AEAIIRkVL0WhtDlDSM7dciBtL1dOo5UPiW7ixIOv5K75Mo"
+                "uFNWO7cFmcxaCOn9459ex0giVyptmX_956C_DWabG6\"}",
+
+*ecdhes_t1_jose_hdr_es_128 =
+       "{\"alg\":\"ECDH-ES\",\"enc\":\"A128CBC-HS256\"}",
+
+*ecdhes_t1_jose_hdr_es_192 =
+       "{\"alg\":\"ECDH-ES\",\"enc\":\"A192CBC-HS384\"}",
+
+*ecdhes_t1_jose_hdr_es_256 =
+       "{\"alg\":\"ECDH-ES\",\"enc\":\"A256CBC-HS512\"}",
+
+*ecdhes_t1_jose_hdr_esakw128_128 =
+       "{\"alg\":\"ECDH-ES+A128KW\",\"enc\":\"A128CBC-HS256\"}",
+
+*ecdhes_t1_jose_hdr_esakw192_192 =
+       "{\"alg\":\"ECDH-ES+A192KW\",\"enc\":\"A192CBC-HS384\"}",
+
+*ecdhes_t1_jose_hdr_esakw256_256 =
+       "{\"alg\":\"ECDH-ES+A256KW\",\"enc\":\"A256CBC-HS512\"}",
+
+*ecdhes_t1_plaintext =
+       "This test plaintext is exactly 64 bytes long when unencrypted..."
+;
+
+static int
+test_ecdhes_t1(struct lws_context *context, const char *jose_hdr,
+              const char *peer_pubkey, const char *peer_privkey)
+{
+       char temp[3072], compact[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+       struct lws_jwe jwe;
+
+       lws_jwe_init(&jwe, context);
+
+       /* read and interpret our canned JOSE header, setting the algorithm */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWS_JOSE,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               jose_hdr, strlen(jose_hdr), 0))
+               goto bail;
+
+       if (lws_jwe_parse_jose(&jwe.jose, jose_hdr, strlen(jose_hdr),
+                              temp, &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+
+               goto bail;
+       }
+
+       /* for ecdh-es encryption, we need the peer's pubkey */
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, (char *)peer_pubkey,
+                          strlen((char *)peer_pubkey)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /*
+        * dup the plaintext into the ciphertext element, it will be
+        * encrypted in-place to a ciphertext of the same length
+        */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_CTXT,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               ecdhes_t1_plaintext,
+                               strlen(ecdhes_t1_plaintext), 0)) {
+               lwsl_notice("%s: Not enough temp space for ptext\n", __func__);
+               goto bail;
+       }
+
+       /*
+        * perform the actual encryption
+        */
+
+       n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len), &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+               goto bail;
+       }
+
+       /*
+        * format for output
+        */
+
+       n = lws_jwe_render_flattened(&jwe, compact, sizeof(compact));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n",
+                        __func__, n);
+               goto bail;
+       }
+
+       // puts(compact);
+
+       n = lws_jwe_render_compact(&jwe, compact, sizeof(compact));
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n",
+                        __func__, n);
+               goto bail;
+       }
+
+       // puts(compact);
+
+       /* okay, let's try to decrypt the whole thing, as the recipient
+        * getting the compact.  jws->jwk needs to be our private key.  */
+
+       lws_jwe_destroy(&jwe);
+       temp_len = sizeof(temp);
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, (char *)peer_privkey,
+                          strlen((char *)peer_privkey)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode(compact, strlen(compact), &jwe.jws.map,
+                                  &jwe.jws.map_b64, temp, &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                    &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: %s selftest failed +++++++++++++++++++\n",
+                        __func__, jose_hdr);
+       else
+               lwsl_notice("%s: %s selftest OK\n", __func__, jose_hdr);
+
+       return ret;
+}
+
+/* AES Key Wrap and AES_XXX_CBC_HMAC_SHA_YYY variations
+ *
+ * These were created by, eg
+ *
+ *   echo -n "plaintext0123456" | \
+ *   ./lws-crypto-jwe -e "A192KW A256CBC-HS512" -k aes192.key
+ */
+
+/* "Live long and prosper." */
+static const char
+       *akw_ptext = "plaintext0123456",
+       *akw_ct_128_128 = "eyJhbGciOiJBMTI4S1ciLCAiZW5jIjoiQTEyOENCQy1IUzI1NiJ9.zbTfhhWePf1UrCRDxJD_-8eAQr2AoWAL51_nNOv0L4nV3P0e4_9ARA.qWehIhy4j4_gh_h5MF9ZEw.GD40YH6NeNOEkhhxC9ryZA.PEuU6V3rhYXeoxENrAzDgw",
+       *akw_ct_128_192 = "eyJhbGciOiJBMTI4S1ciLCAiZW5jIjoiQTE5MkNCQy1IUzM4NCJ9.zpkr45xH_kSJ5eTBv5dGo5PN_A6YdC4JoJSOw3_VTqcOeAYyCkCAXeGWugqIVLzMzBKgtXdabO8.O28MVhkgfketu5sxQK4Ffw.j25N7luxh251kQwpAoYURQ.Pm_NOj0KZzUq2fV9ARpHxT3Iach9feLK",
+       *akw_ct_128_256 = "eyJhbGciOiJBMTI4S1ciLCAiZW5jIjoiQTI1NkNCQy1IUzUxMiJ9.VvFmi121jliyh_UKzsBv7HR3TVY7-yALpcdlasHqdzmfISd8LFU5oc2fEhfn3_TKfCbgRycm5M3103NEMbVSiNULZWvJAPFe.7uLHGFO1g-PgD9YkjPbvoA.AlPwQPWSqGaB_em4qEEyjw.0LgTLld5pSffZnzGG6IRWEwXg7HhClmwP4m_p1yKnHw",
+       *akw_ct_192_128 = "eyJhbGciOiJBMTkyS1ciLCAiZW5jIjoiQTEyOENCQy1IUzI1NiJ9.kxlmi-xn0JN-ZlnSfkVDP-fXvricJ-L63WP2bWddWEiVK4m-os2trw.iarAWaeV873kh5s7HjoZ4Q.nFHEpnnIxvbCiYfFfsLj7Q.karz-h-R93dJgwN_YZyPmw",
+       *akw_ct_192_192 = "eyJhbGciOiJBMTkyS1ciLCAiZW5jIjoiQTE5MkNCQy1IUzM4NCJ9.D869MEk-JERZU_4MgFuL_6Pg24LUEbXlTvGj-t_JUnNFsJ0p8fk5L-iOATqPmx2g7AyVWgcUqU0.RrxzDsy6Bne1pzx99PBGsA.C-ZWmMwd1uswYkvhKX2_jg.bIFY0TmGuohI2APxDZyFUYpa6s1Mx2j1",
+       *akw_ct_192_256 = "eyJhbGciOiJBMTkyS1ciLCAiZW5jIjoiQTI1NkNCQy1IUzUxMiJ9.XNOBw0Dy1paAX2_XGkZYm2Zm455i8InAVMqM3aOrVDpXYBAADuZ_Ke_dlo3Fc8J5b9m_KNCUtVUU8f3KV0sY-yESsqyZTSXk.n3wEIV1-tL50JAp4H19Y1w.ODPd-oxmpCai9CzqaO0P3Q.b9z08hJTySSVSOw-4qp5lrTEcUur46L-RRB-SEcqPpk",
+       *akw_ct_256_128 = "eyJhbGciOiJBMjU2S1ciLCAiZW5jIjoiQTEyOENCQy1IUzI1NiJ9.THaIbHUOHkr7McMeiQqIO_gBcm61F0BKx79JXkzQVVSF7m0u7Z6uhA.RAU8Yx_a9rbWeqr_0YyLZA.zzfdv55bM-qblTxaR5pNzQ.cySMIOTOcEoFkcVn0D6RKQ",
+       *akw_ct_256_192 = "eyJhbGciOiJBMjU2S1ciLCAiZW5jIjoiQTE5MkNCQy1IUzM4NCJ9.gFcfX6fVrpmDJWN5jPqSWEvpOOoNuV4Yn2KO47p1wGsdw5qIw3r5AO5U8zOEtoGNVX68IC8vkpo.9w3tBsve4e-77lI-S9cFog.Vj3L009JDipPJlHY0tS4Iw.WYGgCedW4SmxleDF3P6Hx26BUXxnizxl",
+       *akw_ct_256_256 = "eyJhbGciOiJBMjU2S1ciLCAiZW5jIjoiQTI1NkNCQy1IUzUxMiJ9.ldhqlMf2LJrZ7EDl-oZvaqi0b_KPGy4cMRx2QDpKtTg92tTSWF7ALVHPPCyT4qccIybP4rygajKfdC_Q_UE16KFyUvXhBgaj.S9OCmKpY0zDkArLF5XsrJw.zvJ1X-zuHsrwLXGJJbglPA.WaRKb7Le2ZQ30pGQAV3sfp-YY1563KXxPURHQ8ntdPc",
+       *akw_key_128 = "{\"k\":\"JjVJVh8JsXvKf9qgHHWWBA\",\"kty\":\"oct\"}",
+       *akw_key_192 = "{\"k\":\"BYF6urCMDRMKFXXRxXrDSVtW71AUZghj\",\"kty\":\"oct\"}",
+       *akw_key_256 = "{\"k\":\"cSHyZXGEfnlgKud21cM6tAxRyXnK6xbWRTsyLUegTMk\",\"kty\":\"oct\"}"
+;
+
+static int
+test_akw_decrypt(struct lws_context *context, const char *test_name,
+                const char *ciphertext, const char *key)
+{
+       struct lws_jwe jwe;
+       char temp[2048];
+       int n, ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, key, strlen(key)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       /* converts a compact serialization to jws b64 + decoded maps */
+       if (lws_jws_compact_decode(ciphertext, strlen(ciphertext),
+                                  &jwe.jws.map, &jwe.jws.map_b64,
+                                  temp, &temp_len) != 5) {
+               lwsl_err("%s: lws_jws_compact_decode failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_auth_and_decrypt(&jwe, lws_concat_temp(temp, temp_len), &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                        __func__);
+               goto bail;
+       }
+
+       /* allowing for trailing padding, confirm the plaintext */
+       if (jwe.jws.map.len[LJWE_CTXT] < strlen(akw_ptext) ||
+           lws_timingsafe_bcmp(jwe.jws.map.buf[LJWE_CTXT], akw_ptext,
+                               strlen(akw_ptext))) {
+               lwsl_err("%s: plaintext AES decrypt wrong\n", __func__);
+               lwsl_hexdump_notice(akw_ptext, strlen(akw_ptext));
+               lwsl_hexdump_notice(jwe.jws.map.buf[LJWE_CTXT],
+                                   jwe.jws.map.len[LJWE_CTXT]);
+               goto bail;
+       }
+
+       ret = 0;
+
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest %s failed +++++++++++++++++++\n",
+                       __func__, test_name);
+       else
+               lwsl_notice("%s: selftest %s OK\n", __func__, test_name);
+
+       return ret;
+}
+
+static int
+test_akw_encrypt(struct lws_context *context, const char *test_name,
+                const char *alg, const char *enc, const char *ciphertext,
+                const char *key, char *compact, int compact_len)
+{
+       struct lws_jwe jwe;
+       char temp[4096];
+       int ret = -1, n, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwk_import(&jwe.jwk, NULL, NULL, key, strlen(key)) < 0) {
+               lwsl_notice("%s: Failed to decode JWK test key\n", __func__);
+               goto bail;
+       }
+
+       if (lws_gencrypto_jwe_alg_to_definition(alg, &jwe.jose.alg)) {
+               lwsl_err("Unknown cipher alg %s\n", alg);
+               goto bail;
+       }
+       if (lws_gencrypto_jwe_enc_to_definition(enc, &jwe.jose.enc_alg)) {
+               lwsl_err("Unknown payload enc alg %s\n", enc);
+               goto bail;
+       }
+
+       /* we require a JOSE-formatted header to do the encryption */
+
+       jwe.jws.map.buf[LJWS_JOSE] = temp;
+       jwe.jws.map.len[LJWS_JOSE] = lws_snprintf(temp, temp_len,
+                       "{\"alg\":\"%s\", \"enc\":\"%s\"}", alg, enc);
+       temp_len -= jwe.jws.map.len[LJWS_JOSE];
+
+       /*
+        * dup the plaintext into the ciphertext element, it will be
+        * encrypted in-place to a ciphertext of the same length
+        */
+
+       if (lws_jws_dup_element(&jwe.jws.map, LJWE_CTXT,
+                               lws_concat_temp(temp, temp_len), &temp_len,
+                               akw_ptext, strlen(akw_ptext), 0)) {
+               lwsl_notice("%s: Not enough temp space for ptext\n", __func__);
+               goto bail;
+       }
+
+       /* CEK size is determined by hash / hmac size */
+
+       n = lws_gencrypto_bits_to_bytes(jwe.jose.enc_alg->keybits_fixed);
+       if (lws_jws_randomize_element(context, &jwe.jws.map, LJWE_EKEY,
+                                     lws_concat_temp(temp, temp_len),
+                                     &temp_len, n,
+                                     LWS_JWE_LIMIT_KEY_ELEMENT_BYTES)) {
+               lwsl_err("Problem getting random\n");
+               goto bail;
+       }
+
+       n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len),
+                           &temp_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+               goto bail;
+       }
+
+       n = lws_jwe_render_compact(&jwe, compact, compact_len);
+       if (n < 0) {
+               lwsl_err("%s: lws_jwe_render_compact failed: %d\n",
+                        __func__, n);
+               goto bail;
+       }
+
+       ret = 0;
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest %s failed +++++++++++++++++++\n",
+                       __func__, test_name);
+       else
+               lwsl_notice("%s: selftest %s OK\n", __func__, test_name);
+
+       return ret;
+}
+
+/*
+ * Check we can handle multi-recipient JWE
+ */
+
+static char *complete =
+    "{"
+      "\"protected\":"
+       "\"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0\","
+      "\"unprotected\":"
+       "{\"jku\":\"https://server.example.com/keys.jwks\"},"
+      "\"recipients\":["
+
+       "{\"header\":"
+         "{\"alg\":\"RSA1_5\",\"kid\":\"2011-04-29\"},"
+        "\"encrypted_key\":"
+         "\"UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-"
+          "kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKx"
+          "GHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3"
+          "YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPh"
+          "cCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPg"
+          "wCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A\"},"
+
+       "{\"header\":"
+         "{\"alg\":\"A128KW\",\"kid\":\"7\"},"
+        "\"encrypted_key\":"
+         "\"6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ\"}],"
+
+      "\"iv\":"
+       "\"AxY8DCtDaGlsbGljb3RoZQ\","
+      "\"ciphertext\":"
+       "\"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY\","
+      "\"tag\":"
+       "\"Mz-VPPyU4RlcuYv1IwIvzw\""
+     "}\""
+;
+
+static int
+test_jwe_json_complete(struct lws_context *context)
+{
+       struct lws_jwe jwe;
+       char temp[4096];
+       int ret = -1, temp_len = sizeof(temp);
+
+       lws_jwe_init(&jwe, context);
+
+       if (lws_jwe_parse_jose(&jwe.jose, complete, strlen(complete),
+                              temp, &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+
+               goto bail;
+       }
+
+       if (jwe.jose.recipients != 2) {
+               lwsl_err("%s: wrong recipients count %d\n", __func__,
+                        jwe.jose.recipients);
+               goto bail;
+       }
+
+       ret = 0;
+bail:
+       lws_jwe_destroy(&jwe);
+       if (ret)
+               lwsl_err("%s: selftest failed +++++++++++++++++++\n",
+                       __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+int
+test_jwe(struct lws_context *context)
+{
+       char compact[4096];
+       int n = 0;
+
+       n |= test_jwe_json_complete(context);
+
+       n |= test_ecdhes_t1(context, ecdhes_t1_jose_hdr_es_128,
+                           ecdhes_t1_peer_p256_public_key,
+                           ecdhes_t1_peer_p256_private_key);
+       n |= test_ecdhes_t1(context, ecdhes_t1_jose_hdr_es_192,
+                           ecdhes_t1_peer_p384_public_key,
+                           ecdhes_t1_peer_p384_private_key);
+       n |= test_ecdhes_t1(context, ecdhes_t1_jose_hdr_es_256,
+                           ecdhes_t1_peer_p521_public_key,
+                           ecdhes_t1_peer_p521_private_key);
+
+       n |= test_ecdhes_t1(context, ecdhes_t1_jose_hdr_esakw128_128,
+                           ecdhes_t1_peer_p256_public_key,
+                           ecdhes_t1_peer_p256_private_key);
+       n |= test_ecdhes_t1(context, ecdhes_t1_jose_hdr_esakw192_192,
+                           ecdhes_t1_peer_p384_public_key,
+                           ecdhes_t1_peer_p384_private_key);
+       n |= test_ecdhes_t1(context, ecdhes_t1_jose_hdr_esakw256_256,
+                           ecdhes_t1_peer_p521_public_key,
+                           ecdhes_t1_peer_p521_private_key);
+
+       n |= test_jwe_a1(context);
+
+       n |= test_jwe_a2(context);
+
+       n |= test_jwe_ra_ptext_1024(context, (char *)lws_jwe_ex_a2_jwk_json,
+                                   strlen((char *)lws_jwe_ex_a2_jwk_json));
+       n |= test_jwe_r256a192_ptext(context, (char *)lws_jwe_ex_a2_jwk_json,
+                                    strlen((char *)lws_jwe_ex_a2_jwk_json));
+       n |= test_jwe_r256a256_ptext(context, (char *)lws_jwe_ex_a2_jwk_json,
+                                    strlen((char *)lws_jwe_ex_a2_jwk_json));
+       n |= test_jwe_ra_ptext_1024(context, (char *)rsa_key_2048,
+                                   strlen((char *)rsa_key_2048));
+       n |= test_jwe_r256a192_ptext(context, (char *)rsa_key_2048,
+                                    strlen((char *)rsa_key_2048));
+       n |= test_jwe_r256a256_ptext(context, (char *)rsa_key_2048,
+                                    strlen((char *)rsa_key_2048));
+       n |= test_jwe_ra_ptext_1024(context, (char *)rsa_key_4096,
+                                   strlen((char *)rsa_key_4096));
+       n |= test_jwe_r256a192_ptext(context, (char *)rsa_key_4096,
+                                    strlen((char *)rsa_key_4096));
+       n |= test_jwe_r256a256_ptext(context, (char *)rsa_key_4096,
+                                    strlen((char *)rsa_key_4096));
+       n |= test_jwe_ra_ptext_1024(context, (char *)rsa_key_4096_no_optional,
+                                   strlen((char *)rsa_key_4096_no_optional));
+       n |= test_jwe_r256a192_ptext(context, (char *)rsa_key_4096_no_optional,
+                                    strlen((char *)rsa_key_4096_no_optional));
+       n |= test_jwe_r256a256_ptext(context, (char *)rsa_key_4096_no_optional,
+                                    strlen((char *)rsa_key_4096_no_optional));
+
+       /* AESKW decrypt all variations */
+
+       n |= test_akw_decrypt(context, "d-a128kw_128", akw_ct_128_128, akw_key_128);
+       n |= test_akw_decrypt(context, "d-a128kw_192", akw_ct_128_192, akw_key_128);
+       n |= test_akw_decrypt(context, "d-a128kw_256", akw_ct_128_256, akw_key_128);
+       n |= test_akw_decrypt(context, "d-a192kw_128", akw_ct_192_128, akw_key_192);
+       n |= test_akw_decrypt(context, "d-a192kw_192", akw_ct_192_192, akw_key_192);
+       n |= test_akw_decrypt(context, "d-a192kw_256", akw_ct_192_256, akw_key_192);
+       n |= test_akw_decrypt(context, "d-a256kw_128", akw_ct_256_128, akw_key_256);
+       n |= test_akw_decrypt(context, "d-a256kw_192", akw_ct_256_192, akw_key_256);
+       n |= test_akw_decrypt(context, "d-a256kw_256", akw_ct_256_256, akw_key_256);
+
+       /* AESKW encrypt then confirm decrypt */
+
+       if (!test_akw_encrypt(context, "ed-128kw_128", "A128KW", "A128CBC-HS256",
+                       akw_ptext, akw_key_128, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-128kw_128", compact, akw_key_128);
+       else
+               n = -1;
+       if (!test_akw_encrypt(context, "ed-128kw_192", "A128KW", "A192CBC-HS384",
+                       akw_ptext, akw_key_128, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-128kw_192", compact, akw_key_128);
+       else
+               n = -1;
+       if (!test_akw_encrypt(context, "ed-128kw_256", "A128KW", "A256CBC-HS512",
+                       akw_ptext, akw_key_128, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-128kw_256", compact, akw_key_128);
+       else
+               n = -1;
+
+       if (!test_akw_encrypt(context, "ed-192kw_128", "A192KW", "A128CBC-HS256",
+                       akw_ptext, akw_key_192, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-192kw_128", compact, akw_key_192);
+       else
+               n = -1;
+       if (!test_akw_encrypt(context, "ed-192kw_192", "A192KW", "A192CBC-HS384",
+                       akw_ptext, akw_key_192, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-192kw_192", compact, akw_key_192);
+       else
+               n = -1;
+       if (!test_akw_encrypt(context, "ed-192kw_256", "A192KW", "A256CBC-HS512",
+                       akw_ptext, akw_key_192, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-192kw_256", compact, akw_key_192);
+       else
+               n = -1;
+
+       if (!test_akw_encrypt(context, "ed-256kw_128", "A256KW", "A128CBC-HS256",
+                       akw_ptext, akw_key_256, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-256kw_128", compact, akw_key_256);
+       else
+               n = -1;
+       if (!test_akw_encrypt(context, "ed-256kw_192", "A256KW", "A192CBC-HS384",
+                       akw_ptext, akw_key_256, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-256kw_192", compact, akw_key_256);
+       else
+               n = -1;
+       if (!test_akw_encrypt(context, "ed-256kw_256", "A256KW", "A256CBC-HS512",
+                       akw_ptext, akw_key_256, compact, sizeof(compact)))
+               n |= test_akw_decrypt(context, "ed-256kw_256", compact, akw_key_256);
+       else
+               n = -1;
+
+       n |= test_jwe_r256a128_jwe_openssl(context);
+       n |= test_jwe_r256a128_jwe_mbedtls(context);
+       n |= test_jwe_a3(context);
+       n |= test_jwa_b2(context);
+       n |= test_jwa_b3(context);
+       n |= test_jwa_c(context);
+
+       return n;
+}
diff --git a/minimal-examples/api-tests/api-test-jose/jwk.c b/minimal-examples/api-tests/api-test-jose/jwk.c
new file mode 100644 (file)
index 0000000..2f88ff5
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+ * lws-api-test-jose - RFC7517 jwk tests
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+static
+uint8_t *lws_jwe_ex_a1_jwk_json = (uint8_t *) /* EC + RSA public keys */
+       "{\"keys\":"
+         "["
+           "{\"kty\":\"EC\","
+            "\"crv\":\"P-256\","
+            "\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","
+            "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\","
+            "\"use\":\"enc\","
+            "\"kid\":\"1\"},"
+
+           "{\"kty\":\"RSA\","
+            "\"n\": \"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx"
+       "4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs"
+       "tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2"
+       "QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI"
+       "SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb"
+       "w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\","
+            "\"e\":\"AQAB\","
+            "\"alg\":\"RS256\","
+            "\"kid\":\"2011-04-29\"}"
+         "]"
+       "}",
+
+*lws_jwe_ex_a2_jwk_json = (uint8_t *) /* EC + RSA private keys */
+       "{\"keys\":"
+            "["
+               "{\"kty\":\"EC\","
+               "\"crv\":\"P-256\","
+               "\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","
+               "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\","
+               "\"d\":\"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE\","
+               "\"use\":\"enc\","
+               "\"kid\":\"1\"},"
+
+               "{\"kty\":\"RSA\","
+                "\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4"
+            "cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst"
+            "n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q"
+            "vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS"
+            "D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw"
+            "0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\","
+                "\"e\":\"AQAB\","
+                "\"d\":\"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9"
+            "M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij"
+            "wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d"
+            "_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz"
+            "nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz"
+            "me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q\","
+               "\"p\":\"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV"
+            "nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV"
+            "WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs\","
+               "\"q\":\"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum"
+            "qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx"
+            "kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk\","
+               "\"dp\":\"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim"
+            "YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu"
+            "YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0\","
+               "\"dq\":\"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU"
+            "vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9"
+            "GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk\","
+               "\"qi\":\"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg"
+            "UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx"
+            "yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU\","
+               "\"alg\":\"RS256\","
+               "\"kid\":\"2011-04-29\"}"
+          "]"
+        "}",
+*lws_jwe_ex_a3_jwk_json = (uint8_t *) /* oct symmetric keys */
+         "{\"keys\":"
+           "["
+             "{\"kty\":\"oct\","
+              "\"alg\":\"A128KW\","
+              "\"k\":\"GawgguFyGrWKav7AX4VKUg\"},"
+
+             "{\"kty\":\"oct\","
+              "\"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75"
+                       "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\","
+                 "\"kid\":\"HMAC key used in JWS spec Appendix A.1 example\"}"
+              "]"
+            "}",
+
+*lws_jwe_ex_b_jwk_json = (uint8_t *) /* x5c example (no parent JSON) */
+            "{\"kty\":\"RSA\","
+             "\"use\":\"sig\","
+             "\"kid\":\"1b94c\","
+             "\"n\":\"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08"
+                "PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Q"
+                "u2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4a"
+                "YWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwH"
+                "MTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv"
+                "VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ\","
+                "\"e\":\"AQAB\","
+             "\"x5c\":"
+                "[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJB"
+                "gNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYD"
+                "VQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1"
+                "wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBg"
+                "NVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDV"
+                "QQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1w"
+                "YmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnH"
+                "YMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66"
+                "s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6"
+                "SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpn"
+                "fajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPq"
+                "PvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVk"
+                "aZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BA"
+                "QUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL"
+                "+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1"
+                "zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL"
+                "2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo"
+                "4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq"
+              "gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"]"
+            "}",
+*lws_jwe_ex_c1_jwk_json = (uint8_t *) /* RSA enc private key (no parent JSON) */
+        "{"
+         "\"kty\":\"RSA\","
+         "\"kid\":\"juliet@capulet.lit\","
+         "\"use\":\"enc\","
+         "\"n\":\"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRy"
+                 "O125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP"
+                 "8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0"
+                 "Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0X"
+                 "OC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1"
+                 "_I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q\","
+         "\"e\":\"AQAB\","
+         "\"d\":\"GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfS"
+                 "NkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9U"
+                 "vqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnu"
+                 "ToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsu"
+                 "rY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2a"
+                 "hecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ\","
+         "\"p\":\"2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHf"
+                 "QP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8"
+                 "UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws\","
+         "\"q\":\"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6I"
+                 "edis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYK"
+                 "rYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s\","
+         "\"dp\":\"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3"
+                 "tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1w"
+                 "Y52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c\","
+         "\"dq\":\"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9"
+                 "GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBy"
+                 "mXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots\","
+         "\"qi\":\"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqq"
+                 "abu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0o"
+                 "Yu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8\""
+        "}" /*,
+lws_jwe_ex_c1_plaintext[] = {
+       123, 34, 107, 116, 121, 34, 58, 34, 82, 83, 65, 34, 44, 34, 107,
+       105, 100, 34, 58, 34, 106, 117, 108, 105, 101, 116, 64, 99, 97, 112,
+       117, 108, 101, 116, 46, 108, 105, 116, 34, 44, 34, 117, 115, 101, 34,
+       58, 34, 101, 110, 99, 34, 44, 34, 110, 34, 58, 34, 116, 54, 81, 56,
+       80, 87, 83, 105, 49, 100, 107, 74, 106, 57, 104, 84, 80, 56, 104, 78,
+       89, 70, 108, 118, 97, 100, 77, 55, 68, 102, 108, 87, 57, 109, 87,
+       101, 112, 79, 74, 104, 74, 54, 54, 119, 55, 110, 121, 111, 75, 49,
+       103, 80, 78, 113, 70, 77, 83, 81, 82, 121, 79, 49, 50, 53, 71, 112,
+       45, 84, 69, 107, 111, 100, 104, 87, 114, 48, 105, 117, 106, 106, 72,
+       86, 120, 55, 66, 99, 86, 48, 108, 108, 83, 52, 119, 53, 65, 67, 71,
+       103, 80, 114, 99, 65, 100, 54, 90, 99, 83, 82, 48, 45, 73, 113, 111,
+       109, 45, 81, 70, 99, 78, 80, 56, 83, 106, 103, 48, 56, 54, 77, 119,
+       111, 113, 81, 85, 95, 76, 89, 121, 119, 108, 65, 71, 90, 50, 49, 87,
+       83, 100, 83, 95, 80, 69, 82, 121, 71, 70, 105, 78, 110, 106, 51, 81,
+       81, 108, 79, 56, 89, 110, 115, 53, 106, 67, 116, 76, 67, 82, 119, 76,
+       72, 76, 48, 80, 98, 49, 102, 69, 118, 52, 53, 65, 117, 82, 73, 117,
+       85, 102, 86, 99, 80, 121, 83, 66, 87, 89, 110, 68, 121, 71, 120, 118,
+       106, 89, 71, 68, 83, 77, 45, 65, 113, 87, 83, 57, 122, 73, 81, 50,
+       90, 105, 108, 103, 84, 45, 71, 113, 85, 109, 105, 112, 103, 48, 88,
+       79, 67, 48, 67, 99, 50, 48, 114, 103, 76, 101, 50, 121, 109, 76, 72,
+       106, 112, 72, 99, 105, 67, 75, 86, 65, 98, 89, 53, 45, 76, 51, 50,
+       45, 108, 83, 101, 90, 79, 45, 79, 115, 54, 85, 49, 53, 95, 97, 88,
+       114, 107, 57, 71, 119, 56, 99, 80, 85, 97, 88, 49, 95, 73, 56, 115,
+       76, 71, 117, 83, 105, 86, 100, 116, 51, 67, 95, 70, 110, 50, 80, 90,
+       51, 90, 56, 105, 55, 52, 52, 70, 80, 70, 71, 71, 99, 71, 49, 113,
+       115, 50, 87, 122, 45, 81, 34, 44, 34, 101, 34, 58, 34, 65, 81, 65,
+       66, 34, 44, 34, 100, 34, 58, 34, 71, 82, 116, 98, 73, 81, 109, 104,
+       79, 90, 116, 121, 115, 122, 102, 103, 75, 100, 103, 52, 117, 95, 78,
+       45, 82, 95, 109, 90, 71, 85, 95, 57, 107, 55, 74, 81, 95, 106, 110,
+       49, 68, 110, 102, 84, 117, 77, 100, 83, 78, 112, 114, 84, 101, 97,
+       83, 84, 121, 87, 102, 83, 78, 107, 117, 97, 65, 119, 110, 79, 69, 98,
+       73, 81, 86, 121, 49, 73, 81, 98, 87, 86, 86, 50, 53, 78, 89, 51, 121,
+       98, 99, 95, 73, 104, 85, 74, 116, 102, 114, 105, 55, 98, 65, 88, 89,
+       69, 82, 101, 87, 97, 67, 108, 51, 104, 100, 108, 80, 75, 88, 121, 57,
+       85, 118, 113, 80, 89, 71, 82, 48, 107, 73, 88, 84, 81, 82, 113, 110,
+       115, 45, 100, 86, 74, 55, 106, 97, 104, 108, 73, 55, 76, 121, 99,
+       107, 114, 112, 84, 109, 114, 77, 56, 100, 87, 66, 111, 52, 95, 80,
+       77, 97, 101, 110, 78, 110, 80, 105, 81, 103, 79, 48, 120, 110, 117,
+       84, 111, 120, 117, 116, 82, 90, 74, 102, 74, 118, 71, 52, 79, 120,
+       52, 107, 97, 51, 71, 79, 82, 81, 100, 57, 67, 115, 67, 90, 50, 118,
+       115, 85, 68, 109, 115, 88, 79, 102, 85, 69, 78, 79, 121, 77, 113, 65,
+       68, 67, 54, 112, 49, 77, 51, 104, 51, 51, 116, 115, 117, 114, 89, 49,
+       53, 107, 57, 113, 77, 83, 112, 71, 57, 79, 88, 95, 73, 74, 65, 88,
+       109, 120, 122, 65, 104, 95, 116, 87, 105, 90, 79, 119, 107, 50, 75,
+       52, 121, 120, 72, 57, 116, 83, 51, 76, 113, 49, 121, 88, 56, 67, 49,
+       69, 87, 109, 101, 82, 68, 107, 75, 50, 97, 104, 101, 99, 71, 56, 53,
+       45, 111, 76, 75, 81, 116, 53, 86, 69, 112, 87, 72, 75, 109, 106, 79,
+       105, 95, 103, 74, 83, 100, 83, 103, 113, 99, 78, 57, 54, 88, 53, 50,
+       101, 115, 65, 81, 34, 44, 34, 112, 34, 58, 34, 50, 114, 110, 83, 79,
+       86, 52, 104, 75, 83, 78, 56, 115, 83, 52, 67, 103, 99, 81, 72, 70,
+       98, 115, 48, 56, 88, 98, 111, 70, 68, 113, 75, 117, 109, 51, 115, 99,
+       52, 104, 51, 71, 82, 120, 114, 84, 109, 81, 100, 108, 49, 90, 75, 57,
+       117, 119, 45, 80, 73, 72, 102, 81, 80, 48, 70, 107, 120, 88, 86, 114,
+       120, 45, 87, 69, 45, 90, 69, 98, 114, 113, 105, 118, 72, 95, 50, 105,
+       67, 76, 85, 83, 55, 119, 65, 108, 54, 88, 118, 65, 82, 116, 49, 75,
+       107, 73, 97, 85, 120, 80, 80, 83, 89, 66, 57, 121, 107, 51, 49, 115,
+       48, 81, 56, 85, 75, 57, 54, 69, 51, 95, 79, 114, 65, 68, 65, 89, 116,
+       65, 74, 115, 45, 77, 51, 74, 120, 67, 76, 102, 78, 103, 113, 104, 53,
+       54, 72, 68, 110, 69, 84, 84, 81, 104, 72, 51, 114, 67, 84, 53, 84,
+       51, 121, 74, 119, 115, 34, 44, 34, 113, 34, 58, 34, 49, 117, 95, 82,
+       105, 70, 68, 80, 55, 76, 66, 89, 104, 51, 78, 52, 71, 88, 76, 84, 57,
+       79, 112, 83, 75, 89, 80, 48, 117, 81, 90, 121, 105, 97, 90, 119, 66,
+       116, 79, 67, 66, 78, 74, 103, 81, 120, 97, 106, 49, 48, 82, 87, 106,
+       115, 90, 117, 48, 99, 54, 73, 101, 100, 105, 115, 52, 83, 55, 66, 95,
+       99, 111, 83, 75, 66, 48, 75, 106, 57, 80, 97, 80, 97, 66, 122, 103,
+       45, 73, 121, 83, 82, 118, 118, 99, 81, 117, 80, 97, 109, 81, 117, 54,
+       54, 114, 105, 77, 104, 106, 86, 116, 71, 54, 84, 108, 86, 56, 67, 76,
+       67, 89, 75, 114, 89, 108, 53, 50, 122, 105, 113, 75, 48, 69, 95, 121,
+       109, 50, 81, 110, 107, 119, 115, 85, 88, 55, 101, 89, 84, 66, 55, 76,
+       98, 65, 72, 82, 75, 57, 71, 113, 111, 99, 68, 69, 53, 66, 48, 102,
+       56, 48, 56, 73, 52, 115, 34, 44, 34, 100, 112, 34, 58, 34, 75, 107,
+       77, 84, 87, 113, 66, 85, 101, 102, 86, 119, 90, 50, 95, 68, 98, 106,
+       49, 112, 80, 81, 113, 121, 72, 83, 72, 106, 106, 57, 48, 76, 53, 120,
+       95, 77, 79, 122, 113, 89, 65, 74, 77, 99, 76, 77, 90, 116, 98, 85,
+       116, 119, 75, 113, 118, 86, 68, 113, 51, 116, 98, 69, 111, 51, 90,
+       73, 99, 111, 104, 98, 68, 116, 116, 54, 83, 98, 102, 109, 87, 122,
+       103, 103, 97, 98, 112, 81, 120, 78, 120, 117, 66, 112, 111, 79, 79,
+       102, 95, 97, 95, 72, 103, 77, 88, 75, 95, 108, 104, 113, 105, 103,
+       73, 52, 121, 95, 107, 113, 83, 49, 119, 89, 53, 50, 73, 119, 106, 85,
+       110, 53, 114, 103, 82, 114, 74, 45, 121, 89, 111, 49, 104, 52, 49,
+       75, 82, 45, 118, 122, 50, 112, 89, 104, 69, 65, 101, 89, 114, 104,
+       116, 116, 87, 116, 120, 86, 113, 76, 67, 82, 86, 105, 68, 54, 99, 34,
+       44, 34, 100, 113, 34, 58, 34, 65, 118, 102, 83, 48, 45, 103, 82, 120,
+       118, 110, 48, 98, 119, 74, 111, 77, 83, 110, 70, 120, 89, 99, 75, 49,
+       87, 110, 117, 69, 106, 81, 70, 108, 117, 77, 71, 102, 119, 71, 105,
+       116, 81, 66, 87, 116, 102, 90, 49, 69, 114, 55, 116, 49, 120, 68,
+       107, 98, 78, 57, 71, 81, 84, 66, 57, 121, 113, 112, 68, 111, 89, 97,
+       78, 48, 54, 72, 55, 67, 70, 116, 114, 107, 120, 104, 74, 73, 66, 81,
+       97, 106, 54, 110, 107, 70, 53, 75, 75, 83, 51, 84, 81, 116, 81, 53,
+       113, 67, 122, 107, 79, 107, 109, 120, 73, 101, 51, 75, 82, 98, 66,
+       121, 109, 88, 120, 107, 98, 53, 113, 119, 85, 112, 88, 53, 69, 76,
+       68, 53, 120, 70, 99, 54, 70, 101, 105, 97, 102, 87, 89, 89, 54, 51,
+       84, 109, 109, 69, 65, 117, 95, 108, 82, 70, 67, 79, 74, 51, 120, 68,
+       101, 97, 45, 111, 116, 115, 34, 44, 34, 113, 105, 34, 58, 34, 108,
+       83, 81, 105, 45, 119, 57, 67, 112, 121, 85, 82, 101, 77, 69, 114, 80,
+       49, 82, 115, 66, 76, 107, 55, 119, 78, 116, 79, 118, 115, 53, 69, 81,
+       112, 80, 113, 109, 117, 77, 118, 113, 87, 53, 55, 78, 66, 85, 99,
+       122, 83, 99, 69, 111, 80, 119, 109, 85, 113, 113, 97, 98, 117, 57,
+       86, 48, 45, 80, 121, 52, 100, 81, 53, 55, 95, 98, 97, 112, 111, 75,
+       82, 117, 49, 82, 57, 48, 98, 118, 117, 70, 110, 85, 54, 51, 83, 72,
+       87, 69, 70, 103, 108, 90, 81, 118, 74, 68, 77, 101, 65, 118, 109,
+       106, 52, 115, 109, 45, 70, 112, 48, 111, 89, 117, 95, 110, 101, 111,
+       116, 103, 81, 48, 104, 122, 98, 73, 53, 103, 114, 121, 55, 97, 106,
+       100, 89, 121, 57, 45, 50, 108, 78, 120, 95, 55, 54, 97, 66, 90, 111,
+       79, 85, 117, 57, 72, 67, 74, 45, 85, 115, 102, 83, 79, 73, 56, 34,
+       125 } */
+;
+
+static int
+key_import_callback(struct lws_jwk *s, void *user)
+{
+       lwsl_notice("%s: key type %d\n", __func__, s->kty);
+
+       return 0;
+}
+
+
+int
+test_jwk(struct lws_context *context)
+{
+       struct lws_jwk jwk;
+
+       /* Test 1: A.1: Example public keys */
+
+       if (lws_jwk_import(&jwk, key_import_callback, NULL,
+                          (char *)lws_jwe_ex_a1_jwk_json,
+                          strlen((char *)lws_jwe_ex_a1_jwk_json)) < 0) {
+               lwsl_notice("Failed to decode JWK test key\n");
+               goto bail1;
+       }
+
+       lws_jwk_destroy(&jwk);
+
+       /* Test 1: A.2: Example private keys */
+
+       if (lws_jwk_import(&jwk, key_import_callback, NULL,
+                          (char *)lws_jwe_ex_a2_jwk_json,
+                          strlen((char *)lws_jwe_ex_a2_jwk_json)) < 0) {
+               lwsl_notice("Failed at A.2\n");
+               goto bail1;
+       }
+
+       lws_jwk_destroy(&jwk);
+
+       /* Test 1: A.3: Example symmetric keys */
+
+       if (lws_jwk_import(&jwk, key_import_callback, NULL,
+                          (char *)lws_jwe_ex_a3_jwk_json,
+                          strlen((char *)lws_jwe_ex_a3_jwk_json)) < 0) {
+               lwsl_notice("Failed at A.3\n");
+               goto bail1;
+       }
+
+       lws_jwk_destroy(&jwk);
+
+       /* Test 1: B: Example x509 cert chain (no parent JSON) */
+
+       if (lws_jwk_import(&jwk, NULL, NULL, (char *)lws_jwe_ex_b_jwk_json,
+                          strlen((char *)lws_jwe_ex_b_jwk_json)) < 0) {
+               lwsl_notice("Failed at B\n");
+               goto bail1;
+       }
+
+       lws_jwk_destroy(&jwk);
+
+       /* Test 1: C.1: Example private key (no parent JSON) */
+
+       if (lws_jwk_import(&jwk, NULL, NULL,
+                          (char *)lws_jwe_ex_c1_jwk_json,
+                          strlen((char *)lws_jwe_ex_c1_jwk_json)) < 0) {
+               lwsl_notice("Failed at B\n");
+               goto bail1;
+       }
+
+       lws_jwk_destroy(&jwk);
+
+       /* end */
+
+       lwsl_notice("%s: selftest OK\n", __func__);
+
+       return 0;
+
+//bail:
+//     lws_jwk_destroy(&jwk);
+bail1:
+       lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+
+       return 1;
+
+}
diff --git a/minimal-examples/api-tests/api-test-jose/jws.c b/minimal-examples/api-tests/api-test-jose/jws.c
new file mode 100644 (file)
index 0000000..2236d92
--- /dev/null
@@ -0,0 +1,712 @@
+/*
+ * lws-api-test-jose - RFC7515 jws tests
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+/*
+ * JSON Web Signature is defined in RFC7515
+ *
+ * https://tools.ietf.org/html/rfc7515
+ *
+ * It's basically a way to wrap some JSON with a JSON "header" describing the
+ * crypto, and a signature, all in a BASE64 wrapper with elided terminating '='.
+ *
+ * The signature stays with the content, it serves a different purpose than eg
+ * a TLS tunnel to transfer it.
+ *
+ */
+
+/* for none, the compact serialization format is b64u(jose hdr).b64u(payload) */
+
+static const char *none_cser =
+         "eyJhbGciOiJub25lIn0"
+         "."
+         "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt"
+         "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
+         *none_jose = "{\"alg\":\"none\"}",
+         *none_payload = "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n"
+                         " \"http://example.com/is_root\":true}";
+
+int
+test_jws_none(struct lws_context *context)
+{
+       struct lws_jws_map map;
+       struct lws_jose jose;
+       char temp[2048];
+       int n, temp_len = sizeof(temp), ret = -1;
+
+       lws_jose_init(&jose);
+
+       /* A.5 Unsecured JSON "none" RFC7515 worked example */
+
+       /* decode the b64.b64[.b64] compact serialization blocks */
+       n = lws_jws_compact_decode(none_cser, strlen(none_cser), &map, NULL,
+                                  temp, &temp_len);
+       if (n != 2) {
+               lwsl_err("%s: concat_map failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm the decoded JOSE header is exactly what we expect */
+               if (strncmp(none_jose, map.buf[LJWS_JOSE], map.len[LJWS_JOSE])) {
+                       lwsl_err("%s: jose b64 decode wrong\n", __func__);
+                       goto bail;
+               }
+
+       /* parse the JOSE header */
+       if (lws_jws_parse_jose(&jose, map.buf[LJWS_JOSE],
+                              map.len[LJWS_JOSE],
+                              (char *)lws_concat_temp(temp, temp_len),
+                              &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm we used the "none" alg as expected from JOSE hdr */
+               if (strcmp(jose.alg->alg, "none")) {
+                       lwsl_err("%s: JOSE header has wrong alg\n", __func__);
+                       goto bail;
+               }
+
+               /* confirm the payload is literally what we expect */
+               if (strncmp(none_payload, map.buf[LJWS_PYLD],
+                                         map.len[LJWS_PYLD])) {
+                       lwsl_err("%s: payload b64 decode wrong\n", __func__);
+                       goto bail;
+               }
+
+       /* end */
+
+       ret = 0;
+
+bail:
+       lws_jose_destroy(&jose);
+
+       if (ret)
+               lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+       else
+               lwsl_notice("%s: selftest OK\n", __func__);
+
+       return ret;
+}
+
+
+
+static const char
+          *test1       = "{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}",
+          *test1_enc   = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9",
+          *test2       = "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n"
+                         " \"http://example.com/is_root\":true}",
+          *test2_enc   = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQ"
+                         "ogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
+          *key_jwk     = "{\"kty\":\"oct\",\r\n"
+                         " \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQ"
+                         "Lr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}",
+          *hash_enc    = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
+;
+
+int
+test_jws_HS256(struct lws_context *context)
+{
+       char buf[2048], temp[256], *p = buf, *end = buf + sizeof(buf) - 1, *enc_ptr;
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_jws_map map;
+       int temp_len = sizeof(temp);
+       struct lws_genhmac_ctx ctx;
+       struct lws_jose jose;
+       struct lws_jwk jwk;
+       struct lws_jws jws;
+       int n;
+
+       lws_jose_init(&jose);
+       lws_jws_init(&jws, &jwk, context);
+
+       /* Test 1: SHA256 on RFC7515 worked example */
+
+       /* parse the JOSE header */
+
+       if (lws_jws_parse_jose(&jose, test1, strlen(test1), temp, &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm we used the "none" alg as expected from JOSE hdr */
+               if (strcmp(jose.alg->alg, "HS256")) {
+                       lwsl_err("%s: JOSE header has wrong alg\n", __func__);
+                       goto bail;
+               }
+
+       /* 1.1: import the JWK oct key */
+
+       if (lws_jwk_import(&jwk, NULL, NULL, key_jwk, strlen(key_jwk)) < 0) {
+               lwsl_notice("Failed to decode JWK test key\n");
+               return -1;
+       }
+               if (jwk.kty != LWS_GENCRYPTO_KTY_OCT) {
+                       lwsl_err("%s: unexpected kty %d\n", __func__, jwk.kty);
+
+                       return -1;
+               }
+
+       /* 1.2: create JWS known hdr + known payload */
+
+       n = lws_jws_encode_section(test1, strlen(test1), 1, &p, end);
+       if (n < 0) {
+               goto bail;
+       }
+
+               if (strcmp(buf, test1_enc))
+                       goto bail;
+
+       enc_ptr = p + 1; /* + 1 skips the . */
+       n = lws_jws_encode_section(test2, strlen(test2), 0, &p, end);
+       if (n < 0) {
+               goto bail;
+       }
+
+               if (strcmp(enc_ptr, test2_enc))
+                       goto bail;
+
+       /* 1.3: use HMAC SHA-256 with known key on the hdr . payload */
+
+       if (lws_genhmac_init(&ctx, jose.alg->hmac_type,
+                            jwk.e[LWS_GENCRYPTO_OCT_KEYEL_K].buf,
+                            jwk.e[LWS_GENCRYPTO_OCT_KEYEL_K].len))
+               goto bail;
+       if (lws_genhmac_update(&ctx, (uint8_t *)buf, p - buf))
+               goto bail_destroy_hmac;
+       lws_genhmac_destroy(&ctx, digest);
+
+       /* 1.4: append a base64 encode of the computed HMAC digest */
+
+       enc_ptr = p + 1; /* + 1 skips the . */
+       n = lws_jws_encode_section((const char *)digest, 32, 0, &p, end);
+       if (n < 0)
+               goto bail;
+       if (strcmp(enc_ptr, hash_enc)) { /* check against known B64URL hash */
+               lwsl_err("%s: b64 enc of computed HMAC mismatches '%s' '%s'\n",
+                        __func__, enc_ptr, hash_enc);
+               goto bail;
+       }
+
+       /* 1.5: Check we can agree the signature matches the payload */
+
+       if (lws_jws_sig_confirm_compact_b64(buf, p - buf, &map, &jwk, context,
+                       lws_concat_temp(temp, temp_len), &temp_len) < 0) {
+               lwsl_notice("%s: confirm sig failed\n", __func__);
+               goto bail;
+       }
+
+       lws_jws_destroy(&jws);
+       lws_jwk_destroy(&jwk);
+       lws_jose_destroy(&jose);
+
+       /* end */
+
+       lwsl_notice("%s: selftest OK\n", __func__);
+
+       return 0;
+
+bail_destroy_hmac:
+       lws_genhmac_destroy(&ctx, NULL);
+
+bail:
+       lws_jws_destroy(&jws);
+       lws_jwk_destroy(&jwk);
+       lws_jose_destroy(&jose);
+       lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+
+       return 1;
+}
+
+
+static const char
+       /* the key from worked example in RFC7515 A-2, as a JWK */
+       *rfc7515_rsa_key =
+       "{\"kty\":\"RSA\","
+       " \"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx"
+                "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs"
+                "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH"
+                "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV"
+                "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8"
+                "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\","
+       "\"e\":\"AQAB\","
+       "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I"
+               "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0"
+               "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn"
+               "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT"
+               "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh"
+               "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\","
+       "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi"
+               "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG"
+               "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\","
+       "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa"
+               "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA"
+               "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\","
+       "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q"
+               "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb"
+               "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\","
+       "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa"
+               "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky"
+               "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\","
+       "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o"
+               "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU"
+               "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\""
+       "}",
+       *rfc7515_rsa_a1 = /* the signed worked example in RFC7515 A-1 */
+       "eyJhbGciOiJSUzI1NiJ9"
+       ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt"
+       "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
+       ".cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7"
+       "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4"
+       "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K"
+       "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv"
+       "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB"
+       "p0igcN_IoypGlUPQGe77Rw"
+;
+
+int
+test_jws_RS256(struct lws_context *context)
+{
+       struct lws_jws_map map;
+       struct lws_jose jose;
+       struct lws_jwk jwk;
+       struct lws_jws jws;
+       char temp[2048], *in;
+       int n, l, temp_len = sizeof(temp);
+
+       lws_jose_init(&jose);
+       lws_jws_init(&jws, &jwk, context);
+
+       /* Test 2: RS256 on RFC7515 worked example */
+
+       if (lws_gencrypto_jws_alg_to_definition("RS256", &jose.alg)) {
+               lwsl_err("%s: RS256 not supported\n", __func__);
+               goto bail;
+       }
+
+       /* 2.1: import the jwk */
+
+       if (lws_jwk_import(&jwk, NULL, NULL,
+                          rfc7515_rsa_key, strlen(rfc7515_rsa_key))) {
+               lwsl_notice("%s: 2.2: Failed to read JWK key\n", __func__);
+               goto bail2;
+       }
+
+       if (jwk.kty != LWS_GENCRYPTO_KTY_RSA) {
+               lwsl_err("%s: 2.2: kty: %d instead of RSA\n", __func__, jwk.kty);
+               goto bail;
+       }
+
+       /* 2.2: check the signature on the test packet from RFC7515 A-1 */
+
+       if (lws_jws_sig_confirm_compact_b64(rfc7515_rsa_a1,
+                                           strlen(rfc7515_rsa_a1), &map,
+                                           &jwk, context, temp, &temp_len) < 0) {
+               lwsl_notice("%s: 2.2: confirm rsa sig failed\n", __func__);
+               goto bail;
+       }
+
+       if (lws_jws_b64_compact_map(rfc7515_rsa_a1, strlen(rfc7515_rsa_a1),
+                                  &jws.map_b64) != 3) {
+               lwsl_notice("%s: lws_jws_b64_compact_map failed\n", __func__);
+               goto bail;
+       }
+
+       /* 2.3: generate our own signature for a copy of the test packet */
+
+       in = lws_concat_temp(temp, temp_len);
+       l = strlen(rfc7515_rsa_a1);
+       if (temp_len < l + 1)
+               goto bail;
+       memcpy(in, rfc7515_rsa_a1, l + 1);
+       temp_len -= l + 1;
+
+       if (lws_jws_b64_compact_map(in, l, &jws.map_b64) != 3) {
+               lwsl_notice("%s: lws_jws_b64_compact_map failed\n", __func__);
+               goto bail;
+       }
+
+       /* overwrite the copy of the known b64 sig (it's all placed inside temp) */
+       n = lws_jws_sign_from_b64(&jose, &jws,
+                                 (char *)jws.map_b64.buf[LJWS_SIG],
+                                 jws.map_b64.len[LJWS_SIG] + 8);
+       if (n < 0) {
+               lwsl_err("%s: failed signing test packet\n", __func__);
+               goto bail;
+       }
+       jws.map_b64.len[LJWS_SIG] = n;
+
+       /* 2.4: confirm our signature can be verified */
+
+       in[l] = '\0';
+       if (lws_jws_sig_confirm_compact_b64(in, l, &map, &jwk, context, lws_concat_temp(temp, temp_len), &temp_len) < 0) {
+               lwsl_notice("%s: 2.2: confirm rsa sig failed\n", __func__);
+               goto bail;
+       }
+
+       lws_jwk_destroy(&jwk);
+
+       /* end */
+
+       lwsl_notice("%s: selftest OK\n", __func__);
+
+       return 0;
+
+bail:
+       lws_jwk_destroy(&jwk);
+bail2:
+       lws_jws_destroy(&jws);
+       lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__);
+
+       return 1;
+}
+
+static const char
+       *es256_jose = "{\"alg\":\"ES256\"}",
+       *es256_payload  = "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n"
+                         " \"http://example.com/is_root\":true}",
+       *es256_cser =
+           "eyJhbGciOiJFUzI1NiJ9"
+           "."
+           "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt"
+           "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
+           "."
+           "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSA"
+           "pmWQxfKTUJqPP3-Kg6NU1Q",
+       *es256_jwk =
+       "{"
+               "\"kty\":\"EC\","
+               "\"crv\":\"P-256\","
+               "\"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU\","
+               "\"y\":\"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\","
+               "\"d\":\"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI\""
+       "}"
+#if 0
+                       ,
+       rfc7515_ec_a3_R[] = {
+                14, 209,  33,  83, 121,  99, 108,  72,  60,  47, 127,  21,  88,
+                 7, 212,   2, 163, 178,  40,   3,  58, 249, 124, 126,  23, 129,
+               154, 195,  22, 158, 166, 101
+       },
+       rfc7515_ec_a3_S[] = {
+               197,  10,   7, 211, 140,  60, 112, 229, 216, 241,  45, 175,
+                 8,  74,  84, 128, 166, 101, 144, 197, 242, 147,  80, 154,
+               143,  63, 127, 138, 131, 163,  84, 213
+       }
+#endif
+;
+
+int
+test_jws_ES256(struct lws_context *context)
+{
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_genhash_ctx hash_ctx;
+       struct lws_jws_map map;
+       struct lws_jose jose;
+       struct lws_jwk jwk;
+       struct lws_jws jws;
+       char temp[2048], *p;
+       int ret = -1, l, n, temp_len = sizeof(temp);
+
+       /* A.3 "ES256" RFC7515 worked example - verify */
+
+       lws_jose_init(&jose);
+
+       /* decode the b64.b64[.b64] compact serialization blocks */
+       if (lws_jws_compact_decode(es256_cser, strlen(es256_cser),
+                                  &jws.map, &jws.map_b64,
+                                  temp, &temp_len) != 3) {
+               lwsl_err("%s: concat_map failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm the decoded JOSE header is exactly what we expect */
+               if (jws.map.len[LJWS_JOSE] != strlen(es256_jose) ||
+                   strncmp(es256_jose, jws.map.buf[LJWS_JOSE],
+                                   jws.map.len[LJWS_JOSE])) {
+                       lwsl_err("%s: jose b64 decode wrong\n", __func__);
+                       goto bail;
+               }
+
+               /* confirm the decoded payload is exactly what we expect */
+               if (jws.map.len[LJWS_PYLD] != strlen(es256_payload) ||
+                   strncmp(es256_payload, jws.map.buf[LJWS_PYLD],
+                                           jws.map.len[LJWS_PYLD])) {
+                       lwsl_err("%s: payload b64 decode wrong\n", __func__);
+                       goto bail;
+               }
+
+       /* parse the JOSE header */
+       if (lws_jws_parse_jose(&jose, jws.map.buf[LJWS_JOSE],
+                              jws.map.len[LJWS_JOSE],
+                              (char *)lws_concat_temp(temp, temp_len), &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm we used "ES256" alg we expect from the JOSE hdr */
+               if (strcmp(jose.alg->alg, "ES256")) {
+                       lwsl_err("%s: JOSE header has wrong alg\n", __func__);
+                       goto bail;
+               }
+
+       jws.jwk = &jwk;
+       jws.context = context;
+
+       /* import the ES256 jwk */
+       if (lws_jwk_import(&jwk, NULL, NULL, es256_jwk, strlen(es256_jwk))) {
+               lwsl_notice("%s: Failed to read JWK key\n", __func__);
+               goto bail;
+       }
+
+               /* sanity */
+               if (jwk.kty != LWS_GENCRYPTO_KTY_EC) {
+                       lwsl_err("%s: kty: %d instead of EC\n",
+                                       __func__, jwk.kty);
+                       goto bail1;
+               }
+
+       if (lws_jws_sig_confirm(&jws.map_b64, &jws.map, &jwk, context) < 0) {
+               lwsl_notice("%s: confirm EC sig failed\n", __func__);
+               goto bail1;
+       }
+
+       /* A.3 "ES256" RFC7515 worked example - sign */
+
+       l = strlen(es256_cser);
+       if (temp_len < l + 1)
+               goto bail1;
+       p = lws_concat_temp(temp, temp_len);
+       memcpy(p, es256_cser, l + 1);
+       temp_len -= l + 1;
+
+       /* scan the b64 compact serialization string to map the blocks */
+       if (lws_jws_b64_compact_map(p, l, &jws.map_b64) != 3)
+               goto bail1;
+
+       /* create the hash of the protected b64 part */
+       if (lws_genhash_init(&hash_ctx, jose.alg->hash_type) ||
+           lws_genhash_update(&hash_ctx, jws.map_b64.buf[LJWS_JOSE],
+                           jws.map_b64.len[LJWS_JOSE]) ||
+           lws_genhash_update(&hash_ctx, ".", 1) ||
+           lws_genhash_update(&hash_ctx, jws.map_b64.buf[LJWS_PYLD],
+                           jws.map_b64.len[LJWS_PYLD]) ||
+           lws_genhash_destroy(&hash_ctx, digest)) {
+               lws_genhash_destroy(&hash_ctx, NULL);
+
+               goto bail1;
+       }
+
+       lwsl_hexdump(jws.map_b64.buf[LJWS_SIG], jws.map_b64.len[LJWS_SIG]);
+
+       /* overwrite the copy of the known b64 sig (it's placed inside buf) */
+       n = lws_jws_sign_from_b64(&jose, &jws,
+                                 (char *)jws.map_b64.buf[LJWS_SIG],
+                                 jws.map_b64.len[LJWS_SIG] + 8);
+       if (n < 0) {
+               lwsl_err("%s: failed signing test packet\n", __func__);
+               goto bail1;
+       }
+       jws.map_b64.len[LJWS_SIG] = n;
+
+       lwsl_hexdump(jws.map_b64.buf[LJWS_SIG], jws.map_b64.len[LJWS_SIG]);
+
+       /* 2.4: confirm our generated signature can be verified */
+
+//     lwsl_err("p %p, l %d\n", p, (int)l);
+       p[l] = '\0';
+       if (lws_jws_sig_confirm_compact_b64(p, l, &map, &jwk, context, lws_concat_temp(temp, temp_len), &temp_len) < 0) {
+               lwsl_notice("%s: confirm our EC sig failed\n", __func__);
+               goto bail1;
+       }
+
+       /* end */
+       ret =  0;
+
+bail1:
+       lws_jwk_destroy(&jwk);
+       lws_jose_destroy(&jose);
+
+bail:
+       lwsl_notice("%s: selftest %s\n", __func__, ret ? "FAIL" : "OK");
+
+       return ret;
+}
+
+static const char
+       *es512_jose = "{\"alg\":\"ES512\"}",
+       *es512_payload  = "Payload",
+       *es512_cser =
+            "eyJhbGciOiJFUzUxMiJ9"
+            "."
+            "UGF5bG9hZA"
+            "."
+            "AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZq"
+            "wqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8Kp"
+            "EHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn",
+       *es512_jwk =
+          "{"
+             "\"kty\":\"EC\","
+             "\"crv\":\"P-521\","
+             "\"x\":\"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_"
+                  "NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk\","
+             "\"y\":\"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDl"
+                  "y79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2\","
+             "\"d\":\"AY5pb7A0UFiB3RELSD64fTLOSV_jazdF7fLYyuTw8lOfRhWg6Y6rUrPA"
+                  "xerEzgdRhajnu0ferB0d53vM9mE15j2C\""
+          "}"
+;
+
+int
+test_jws_ES512(struct lws_context *context)
+{
+       uint8_t digest[LWS_GENHASH_LARGEST];
+       struct lws_genhash_ctx hash_ctx;
+       struct lws_jws_map map;
+       struct lws_jose jose;
+       struct lws_jwk jwk;
+       struct lws_jws jws;
+       char temp[2048], *p;
+       int ret = -1, l, n, temp_len = sizeof(temp);
+
+       /* A.4 "ES512" RFC7515 worked example - verify */
+
+       lws_jose_init(&jose);
+
+       /* decode the b64.b64[.b64] compact serialization blocks */
+       if (lws_jws_compact_decode(es512_cser, strlen(es512_cser),
+                                  &jws.map, &jws.map_b64, temp,
+                                  &temp_len) != 3) {
+               lwsl_err("%s: concat_map failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm the decoded JOSE header is exactly what we expect */
+               if (jws.map.len[LJWS_JOSE] != strlen(es512_jose) ||
+                   strncmp(es512_jose, jws.map.buf[LJWS_JOSE],
+                                       jws.map.len[LJWS_JOSE])) {
+                       lwsl_err("%s: jose b64 decode wrong\n", __func__);
+                       goto bail;
+               }
+
+               /* confirm the decoded payload is exactly what we expect */
+               if (jws.map.len[LJWS_PYLD] != strlen(es512_payload) ||
+                   strncmp(es512_payload, jws.map.buf[LJWS_PYLD],
+                                          jws.map.len[LJWS_PYLD])) {
+                       lwsl_err("%s: payload b64 decode wrong\n", __func__);
+                       goto bail;
+               }
+
+       /* parse the JOSE header */
+       if (lws_jws_parse_jose(&jose, jws.map.buf[LJWS_JOSE],
+                             jws.map.len[LJWS_JOSE],
+                             lws_concat_temp(temp, temp_len), &temp_len) < 0) {
+               lwsl_err("%s: JOSE parse failed\n", __func__);
+               goto bail;
+       }
+
+               /* confirm we used "es512" alg we expect from the JOSE hdr */
+               if (strcmp(jose.alg->alg, "ES512")) {
+                       lwsl_err("%s: JOSE header has wrong alg\n", __func__);
+                       goto bail;
+               }
+
+       jws.jwk = &jwk;
+       jws.context = context;
+
+       /* import the es512 jwk */
+       if (lws_jwk_import(&jwk, NULL, NULL, es512_jwk, strlen(es512_jwk))) {
+               lwsl_notice("%s: Failed to read JWK key\n", __func__);
+               goto bail;
+       }
+
+               /* sanity */
+               if (jwk.kty != LWS_GENCRYPTO_KTY_EC) {
+                       lwsl_err("%s: kty: %d instead of EC\n",
+                                       __func__, jwk.kty);
+                       goto bail1;
+               }
+
+       if (lws_jws_sig_confirm(&jws.map_b64, &jws.map, &jwk, context) < 0) {
+               lwsl_notice("%s: confirm EC sig failed\n", __func__);
+               goto bail1;
+       }
+
+       /* A.3 "es512" RFC7515 worked example - sign */
+
+       l = strlen(es512_cser);
+       if (temp_len < l)
+               goto bail1;
+       p = lws_concat_temp(temp, temp_len);
+       memcpy(p, es512_cser, l + 1);
+       temp_len -= (l + 1);
+
+       /* scan the b64 compact serialization string to map the blocks */
+       if (lws_jws_b64_compact_map(p, l, &jws.map_b64) != 3)
+               goto bail1;
+
+       /* create the hash of the protected b64 part */
+       if (lws_genhash_init(&hash_ctx, jose.alg->hash_type) ||
+           lws_genhash_update(&hash_ctx, jws.map_b64.buf[LJWS_JOSE],
+                              jws.map_b64.len[LJWS_JOSE]) ||
+           lws_genhash_update(&hash_ctx, ".", 1) ||
+           lws_genhash_update(&hash_ctx, jws.map_b64.buf[LJWS_PYLD],
+                              jws.map_b64.len[LJWS_PYLD]) ||
+           lws_genhash_destroy(&hash_ctx, digest)) {
+               lws_genhash_destroy(&hash_ctx, NULL);
+
+               goto bail1;
+       }
+
+       /* overwrite the copy of the known b64 sig (it's placed inside buf) */
+       n = lws_jws_sign_from_b64(&jose, &jws,
+                                 (char *)jws.map_b64.buf[LJWS_SIG], 1024);
+       if (n < 0) {
+               lwsl_err("%s: failed signing test packet\n", __func__);
+               goto bail1;
+       }
+       jws.map_b64.len[LJWS_SIG] = n;
+
+       /* 2.4: confirm our generated signature can be verified */
+
+       p[l] = '\0';
+
+       if (lws_jws_sig_confirm_compact_b64(p, l, &map, &jwk, context,
+                       lws_concat_temp(temp, temp_len), &temp_len) < 0) {
+               lwsl_notice("%s: confirm our ECDSA sig failed\n", __func__);
+               goto bail1;
+       }
+
+       /* end */
+       ret =  0;
+
+bail1:
+       lws_jwk_destroy(&jwk);
+       lws_jose_destroy(&jose);
+
+bail:
+       lwsl_notice("%s: selftest %s\n", __func__, ret ? "FAIL" : "OK");
+
+       return ret;
+}
+
+int
+test_jws(struct lws_context *context)
+{
+       int n = 0;
+
+       n |= test_jws_none(context);
+       n |= test_jws_HS256(context);
+       n |= test_jws_RS256(context);
+       n |= test_jws_ES256(context);
+       n |= test_jws_ES512(context);
+
+       return n;
+}
diff --git a/minimal-examples/api-tests/api-test-jose/main.c b/minimal-examples/api-tests/api-test-jose/main.c
new file mode 100644 (file)
index 0000000..c2f2c72
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * lws-api-test-jose
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+int
+test_jwk(struct lws_context *context);
+int
+test_jws(struct lws_context *context);
+int
+test_jwe(struct lws_context *context);
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS JOSE api tests\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = 0;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       result |= test_jwk(context);
+       lwsl_notice("%d\n", result);
+       result |= test_jws(context);
+       lwsl_notice("%d\n", result);
+       result |= test_jwe(context);
+       lwsl_notice("%d\n", result);
+
+       lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS");
+
+       lws_context_destroy(context);
+
+       return result;
+}
diff --git a/minimal-examples/api-tests/api-test-jose/selftest.sh b/minimal-examples/api-tests/api-test-jose/selftest.sh
new file mode 100755 (executable)
index 0000000..16d1e2e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 apiselftest
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-lws_dsh/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_dsh/CMakeLists.txt
new file mode 100644 (file)
index 0000000..936d610
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-lws_dsh)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_NETWORK 1 requirements)
+require_lws_config(LWS_WITH_LWS_DSH 1 requirements)
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/api-tests/api-test-lws_dsh/README.md b/minimal-examples/api-tests/api-test-lws_dsh/README.md
new file mode 100644 (file)
index 0000000..f62a45a
--- /dev/null
@@ -0,0 +1,22 @@
+# lws api test lws_dsh
+
+Demonstrates how to use and performs selftests for lws_dsh
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+```
+ $ ./lws-api-test-lws_dsh
+[2018/10/09 09:14:17:4834] USER: LWS API selftest: lws_dsh
+[2018/10/09 09:14:17:4835] USER: Completed: PASS
+```
+
diff --git a/minimal-examples/api-tests/api-test-lws_dsh/main.c b/minimal-examples/api-tests/api-test-lws_dsh/main.c
new file mode 100644 (file)
index 0000000..8f92fd9
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * lws-api-test-lws_dsh
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+int
+test1(void)
+{
+       lws_dsh_t *dsh;
+       size_t size;
+       void *a1;
+
+       /*
+        * test 1: single dsh, alloc 2 kinds and free everything back to a
+        *         single free obj
+        */
+
+       dsh = lws_dsh_create(NULL, 16384, 2);
+       if (!dsh) {
+               lwsl_err("%s: Failed to create dsh\n", __func__);
+
+               return 1;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 0, "hello", 5, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 1\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 1, "some other string", 17, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 2\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 0, "hello again", 11, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 3\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_get_head(dsh, 1, &a1, &size)) {
+               lwsl_err("%s: no head 1\n", __func__);
+
+               goto bail;
+       }
+       if (size != 17 || memcmp(a1, "some other string", 17)) {
+               lwsl_err("%s: test 1 mismatch\n", __func__);
+
+               goto bail;
+       }
+       lws_dsh_free(&a1);
+
+       if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
+               lwsl_err("%s: no head 2\n", __func__);
+
+               goto bail;
+       }
+       if (size != 5 || memcmp(a1, "hello", 5)) {
+               lwsl_err("%s: test 2 mismatch\n", __func__);
+
+               goto bail;
+       }
+       lws_dsh_free(&a1);
+
+       if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
+               lwsl_err("%s: no head 3\n", __func__);
+
+               goto bail;
+       }
+       if (size != 11 || memcmp(a1, "hello again", 11)) {
+               lwsl_err("%s: test 3 mismatch\n", __func__);
+
+               goto bail;
+       }
+       lws_dsh_free(&a1);
+
+       lws_dsh_destroy(&dsh);
+
+       return 0;
+bail:
+       lws_dsh_destroy(&dsh);
+
+       return 1;
+}
+
+int
+test2(void)
+{
+       lws_dsh_t *dsh, *dsh2;
+       lws_dll2_owner_t owner;
+       uint8_t blob[4096];
+
+       memset(blob, 0, sizeof(blob));
+
+       /*
+        * test 2: multiple dsh, overflow allocation and dynamic destroy
+        */
+
+       lws_dll2_owner_clear(&owner);
+
+       dsh = lws_dsh_create(&owner, 4096, 2);
+       if (!dsh) {
+               lwsl_err("%s: Failed to create dsh1\n", __func__);
+
+               return 1;
+       }
+
+       dsh2 = lws_dsh_create(&owner, 4096, 2);
+       if (!dsh) {
+               lwsl_err("%s: Failed to create dsh2\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 0, blob, 4000, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 1\n", __func__);
+
+               goto bail2;
+       }
+
+       if (lws_dsh_alloc_tail(dsh2, 0, "hello", 5, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 2\n", __func__);
+
+               goto bail2;
+       }
+
+       /*
+        * We create this logically on dsh.  But there's no room for the body.
+        * It should figure out it can use space on dsh2.
+        */
+
+       if (lws_dsh_alloc_tail(dsh, 0, blob, 2000, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 3\n", __func__);
+
+               goto bail2;
+       }
+
+       if (lws_dsh_alloc_tail(dsh2, 0, "hello again", 11, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 4\n", __func__);
+
+               goto bail2;
+       }
+
+       /*
+        * When we destroy dsh2 it will try to migrate out the 2000 allocation
+        * from there but find there is no space in dsh1.  It should handle it
+        * by logicalling dropping the object.
+        */
+
+       lws_dsh_destroy(&dsh2);
+       lws_dsh_destroy(&dsh);
+
+       return 0;
+
+bail2:
+       lws_dsh_destroy(&dsh2);
+
+bail:
+       lws_dsh_destroy(&dsh);
+
+       return 1;
+
+}
+
+int
+test3(void)
+{
+       lws_dsh_t *dsh, *dsh2;
+       lws_dll2_owner_t owner;
+       uint8_t blob[4096];
+
+       memset(blob, 0, sizeof(blob));
+
+       /*
+        * test 3: multiple dsh, umeetable allocation request
+        */
+
+       lws_dll2_owner_clear(&owner);
+
+       dsh = lws_dsh_create(&owner, 4096, 2);
+       if (!dsh) {
+               lwsl_err("%s: Failed to create dsh1\n", __func__);
+
+               return 1;
+       }
+
+       dsh2 = lws_dsh_create(&owner, 4096, 2);
+       if (!dsh) {
+               lwsl_err("%s: Failed to create dsh2\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 0, blob, 4000, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 1\n", __func__);
+
+               goto bail2;
+       }
+
+       if (lws_dsh_alloc_tail(dsh2, 0, "hello", 5, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 2\n", __func__);
+
+               goto bail2;
+       }
+
+       /*
+        * There's just no room for this, we expect it to fail
+        */
+
+       if (!lws_dsh_alloc_tail(dsh, 0, blob, 5000, NULL, 0)) {
+               lwsl_err("%s: Didn't fail to alloc as expected\n", __func__);
+
+               goto bail2;
+       }
+
+       if (lws_dsh_alloc_tail(dsh2, 0, "hello again", 11, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 4\n", __func__);
+
+               goto bail2;
+       }
+
+       lws_dsh_destroy(&dsh2);
+       lws_dsh_destroy(&dsh);
+
+       return 0;
+
+bail2:
+       lws_dsh_destroy(&dsh2);
+
+bail:
+       lws_dsh_destroy(&dsh);
+
+       return 1;
+}
+
+int
+test4(void)
+{
+       uint8_t blob[4096];
+       lws_dsh_t *dsh;
+       size_t size;
+       void *a1;
+
+       memset(blob, 0, sizeof(blob));
+
+       /*
+        * test 1: use up whole free list, then recover and alloc something
+        *         else
+        */
+
+       dsh = lws_dsh_create(NULL, 4096, 2);
+       if (!dsh) {
+               lwsl_err("%s: Failed to create dsh\n", __func__);
+
+               return 1;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 0, blob, 4000, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 1\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
+               lwsl_err("%s: no head 1\n", __func__);
+
+               goto bail;
+       }
+       if (size != 4000) {
+               lwsl_err("%s: test 1 mismatch\n", __func__);
+
+               goto bail;
+       }
+       lws_dsh_free(&a1);
+
+       if (lws_dsh_alloc_tail(dsh, 0, "some other string", 17, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 2\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_alloc_tail(dsh, 0, "hello again", 11, NULL, 0)) {
+               lwsl_err("%s: Failed to alloc 3\n", __func__);
+
+               goto bail;
+       }
+
+       if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
+               lwsl_err("%s: no head 1\n", __func__);
+
+               goto bail;
+       }
+       if (size != 17 || memcmp(a1, "some other string", 17)) {
+               lwsl_err("%s: test 1 mismatch\n", __func__);
+
+               goto bail;
+       }
+       lws_dsh_free(&a1);
+
+       if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
+               lwsl_err("%s: no head 2\n", __func__);
+
+               goto bail;
+       }
+       if (size != 11 || memcmp(a1, "hello again", 11)) {
+               lwsl_err("%s: test 2 mismatch (%zu)\n", __func__, size);
+
+               goto bail;
+       }
+
+       lws_dsh_free(&a1);
+
+       lws_dsh_destroy(&dsh);
+
+       return 0;
+bail:
+       lws_dsh_destroy(&dsh);
+
+       return 1;
+}
+
+int main(int argc, const char **argv)
+{
+       int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       int ret = 0, n;
+       const char *p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: lws_dsh\n");
+
+       n = test1();
+       lwsl_user("%s: test1: %d\n", __func__, n);
+       ret |= n;
+
+       n = test2();
+       lwsl_user("%s: test2: %d\n", __func__, n);
+       ret |= n;
+
+       n = test3();
+       lwsl_user("%s: test3: %d\n", __func__, n);
+       ret |= n;
+
+       n = test4();
+       lwsl_user("%s: test4: %d\n", __func__, n);
+       ret |= n;
+
+       lwsl_user("Completed: %s\n", ret ? "FAIL" : "PASS");
+
+       return ret;
+}
diff --git a/minimal-examples/api-tests/api-test-lws_dsh/selftest.sh b/minimal-examples/api-tests/api-test-lws_dsh/selftest.sh
new file mode 100755 (executable)
index 0000000..16d1e2e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 apiselftest
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_sequencer/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1d24090
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-lws_sequencer)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+require_lws_config(LWS_WITH_SEQUENCER 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/libwebsockets.org.cer b/minimal-examples/api-tests/api-test-lws_sequencer/libwebsockets.org.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/main.c b/minimal-examples/api-tests/api-test-lws_sequencer/main.c
new file mode 100644 (file)
index 0000000..a84aa7e
--- /dev/null
@@ -0,0 +1,399 @@
+/*
+ * lws-api-test-lws_sequencer
+ *
+ * Written in 2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This api test uses the lws_sequencer api to make five http client requests
+ * to libwebsockets.org in sequence, from inside the event loop.  The fourth
+ * fourth http client request is directed to port 22 where it stalls
+ * triggering the lws_sequencer timeout flow.  The fifth is given a nonexistant
+ * dns name and is expected to fail.
+ */
+
+#include <libwebsockets.h>
+
+#include <signal.h>
+
+static int interrupted, test_good = 0;
+
+enum {
+       SEQ1,
+       SEQ2,
+       SEQ3_404,
+       SEQ4_TIMEOUT,           /* we expect to timeout */
+       SEQ5_BAD_ADDRESS        /* we expect the connection to fail */
+};
+
+/*
+ * This is the user defined struct whose space is allocated along with the
+ * sequencer when that is created.
+ *
+ * You'd put everything your sequencer needs to do its job in here.
+ */
+
+struct myseq {
+       struct lws_vhost        *vhost;
+       struct lws              *cwsi;  /* client wsi for current step if any */
+
+       int                     state;  /* which test we're on */
+       int                     http_resp;
+};
+
+/* sequencer messages specific to this sequencer */
+
+enum {
+       SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
+       SEQ_MSG_CLIENT_DONE,
+};
+
+/* this is the sequence of GETs we will do */
+
+static const char *url_paths[] = {
+       "https://libwebsockets.org/index.html",
+       "https://libwebsockets.org/lws.css",
+       "https://libwebsockets.org/404.html",
+       "https://libwebsockets.org:22",         /* this causes us to time out */
+       "https://doesntexist.invalid/"          /* fail early in connect */
+};
+
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+/*
+ * This is the sequencer-aware http protocol handler.  It monitors the client
+ * http action and queues messages for the sequencer when something definitive
+ * happens.
+ */
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+             void *in, size_t len)
+{
+       struct myseq *s = (struct myseq *)user;
+       int seq_msg = SEQ_MSG_CLIENT_FAILED;
+
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_notice("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               goto notify;
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               if (!s)
+                       return 1;
+               s->http_resp = lws_http_client_http_response(wsi);
+               lwsl_info("Connected with server response: %d\n", s->http_resp);
+               break;
+
+       /* chunks of chunked content, with header removed */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_info("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
+#if 0  /* enable to dump the html */
+               {
+                       const char *p = in;
+
+                       while (len--)
+                               if (*p < 0x7f)
+                                       putchar(*p++);
+                               else
+                                       putchar('.');
+               }
+#endif
+               return 0; /* don't passthru */
+
+       /* uninterpreted http content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char buffer[1024 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP: wsi %p\n",
+                           wsi);
+               if (!s)
+                       return 1;
+               /*
+                * We got a definitive transaction completion
+                */
+               seq_msg = SEQ_MSG_CLIENT_DONE;
+               goto notify;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               lwsl_info("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
+               if (!s)
+                       return 1;
+
+               lwsl_user("%s: wsi %p: seq failed at CLOSED_CLIENT_HTTP\n",
+                         __func__, wsi);
+               goto notify;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+
+notify:
+       /*
+        * We only inform the sequencer of a definitive outcome for our step.
+        *
+        * So once we have informed it, we detach ourselves from the sequencer
+        * and the sequencer from ourselves.  Wsi may want to live on but after
+        * we got our result and moved on to the next test or completed, the
+        * sequencer doesn't want to hear from it again.
+        */
+       if (!s)
+               return 1;
+
+       lws_set_wsi_user(wsi, NULL);
+       s->cwsi = NULL;
+       lws_seq_queue_event(lws_seq_from_user(s), seq_msg,
+                                 NULL, NULL);
+
+       return 0;
+}
+
+static const struct lws_protocols protocols[] = {
+       { "seq-test-http", callback_http, 0, 0, },
+       { NULL, NULL, 0, 0 }
+};
+
+
+static int
+sequencer_start_client(struct myseq *s)
+{
+       struct lws_client_connect_info i;
+       const char *prot, *path1;
+       char uri[128], path[128];
+       int n;
+
+       lws_strncpy(uri, url_paths[s->state], sizeof(uri));
+
+       memset(&i, 0, sizeof i);
+       i.context = lws_seq_get_context(lws_seq_from_user(s));
+
+       if (lws_parse_uri(uri, &prot, &i.address, &i.port, &path1)) {
+               lwsl_err("%s: uri error %s\n", __func__, uri);
+       }
+
+       if (!strcmp(prot, "https"))
+               i.ssl_connection = LCCSCF_USE_SSL;
+
+       path[0] = '/';
+       n = 1;
+       if (path1[0] == '/')
+               n = 0;
+       lws_strncpy(&path[n], path1, sizeof(path) - 1);
+
+       i.path = path;
+       i.host = i.address;
+       i.origin = i.address;
+       i.method = "GET";
+       i.vhost = s->vhost;
+       i.userdata = s;
+
+       i.protocol = protocols[0].name;
+       i.local_protocol_name = protocols[0].name;
+       i.pwsi = &s->cwsi;
+
+       if (!lws_client_connect_via_info(&i)) {
+               lwsl_notice("%s: connecting to %s://%s:%d%s failed\n",
+                           __func__, prot, i.address, i.port, path);
+
+               /* we couldn't even get started with the client connection */
+
+               lws_seq_queue_event(lws_seq_from_user(s),
+                                   SEQ_MSG_CLIENT_FAILED, NULL, NULL);
+
+               return 1;
+       }
+
+       lws_seq_timeout_us(lws_seq_from_user(s), 3 * LWS_US_PER_SEC);
+
+       lwsl_notice("%s: wsi %p: connecting to %s://%s:%d%s\n", __func__,
+                   s->cwsi, prot, i.address, i.port, path);
+
+       return 0;
+}
+
+/*
+ * The sequencer callback handles queued sequencer messages in the order they
+ * were queued.  The messages are presented from the event loop thread context
+ * even if they were queued from a different thread.
+ */
+
+static lws_seq_cb_return_t
+sequencer_cb(struct lws_sequencer *seq, void *user, int event,
+            void *data, void *aux)
+{
+       struct myseq *s = (struct myseq *)user;
+
+       switch ((int)event) {
+       case LWSSEQ_CREATED: /* our sequencer just got started */
+               s->state = SEQ1;  /* first thing we'll do is the first url */
+               goto step;
+
+       case LWSSEQ_DESTROYED:
+               /*
+                * This sequencer is about to be destroyed.  If we have any
+                * other assets in play, detach them from us.
+                */
+               if (s->cwsi)
+                       lws_set_wsi_user(s->cwsi, NULL);
+
+               interrupted = 1;
+               break;
+
+       case LWSSEQ_TIMED_OUT: /* current step timed out */
+               if (s->state == SEQ4_TIMEOUT) {
+                       lwsl_user("%s: test %d got expected timeout\n",
+                                 __func__, s->state);
+                       goto done;
+               }
+               lwsl_user("%s: seq timed out at step %d\n", __func__, s->state);
+               return LWSSEQ_RET_DESTROY;
+
+       case SEQ_MSG_CLIENT_FAILED:
+               if (s->state == SEQ5_BAD_ADDRESS) {
+                       /*
+                        * in this specific case, we expect to fail
+                        */
+                       lwsl_user("%s: test %d failed as expected\n",
+                                 __func__, s->state);
+                       goto done;
+               }
+
+               lwsl_user("%s: seq failed at step %d\n", __func__, s->state);
+
+               return LWSSEQ_RET_DESTROY;
+
+       case SEQ_MSG_CLIENT_DONE:
+               if (s->state >= SEQ4_TIMEOUT) {
+                       /*
+                        * In these specific cases, done would be a failure,
+                        * we expected to timeout or fail
+                        */
+                       lwsl_user("%s: seq failed at step %d\n", __func__,
+                                 s->state);
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+               lwsl_user("%s: seq done step %d (resp %d)\n", __func__,
+                         s->state, s->http_resp);
+
+done:
+               lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE);
+               s->state++;
+               if (s->state == LWS_ARRAY_SIZE(url_paths)) {
+                       /* the sequence has completed */
+                       lwsl_user("%s: sequence completed OK\n", __func__);
+
+                       test_good = 1;
+
+                       return LWSSEQ_RET_DESTROY;
+               }
+
+step:
+               sequencer_start_client(s);
+               break;
+       default:
+               break;
+       }
+
+       return LWSSEQ_RET_CONTINUE;
+}
+
+int
+main(int argc, const char **argv)
+{
+       int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       lws_seq_t *seq;
+       struct lws_vhost *vh;
+       lws_seq_info_t i;
+       struct myseq *s;
+       const char *p;
+
+       /* the normal lws init */
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: lws_sequencer\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+       info.protocols = protocols;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       vh = lws_create_vhost(context, &info);
+       if (!vh) {
+               lwsl_err("Failed to create first vhost\n");
+               goto bail1;
+       }
+
+       /*
+        * Create the sequencer... when the event loop starts, it will
+        * receive the LWSSEQ_CREATED callback
+        */
+
+       memset(&i, 0, sizeof(i));
+       i.context = context;
+       i.user_size = sizeof(struct myseq);
+       i.puser = (void **)&s;
+       i.cb = sequencer_cb;
+       i.name = "seq";
+
+       seq = lws_seq_create(&i);
+       if (!seq) {
+               lwsl_err("%s: unable to create sequencer\n", __func__);
+               goto bail1;
+       }
+       s->vhost = vh;
+
+       /* the usual lws event loop */
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail1:
+       lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
+
+       lws_context_destroy(context);
+
+       return !test_good;
+}
diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_struct-json/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a0900f8
--- /dev/null
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-lws_struct-json)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/README.md b/minimal-examples/api-tests/api-test-lws_struct-json/README.md
new file mode 100644 (file)
index 0000000..ebe930d
--- /dev/null
@@ -0,0 +1,56 @@
+# lws api test lws_struct JSON
+
+Demonstrates how to use and performs selftests for lws_struct
+JSON serialization and deserialization
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+```
+ $ ./lws-api-test-lws_struct-json
+[2019/03/30 22:09:09:2529] USER: LWS API selftest: lws_struct JSON
+[2019/03/30 22:09:09:2625] NOTICE: main: ++++++++++++++++ test 1
+[2019/03/30 22:09:09:2812] NOTICE: builder.hostname = 'learn', timeout = 1800, targets (2)
+[2019/03/30 22:09:09:2822] NOTICE:     target.name 'target1' (target 0x543a830)
+[2019/03/30 22:09:09:2824] NOTICE:     target.name 'target2' (target 0x543a860)
+[2019/03/30 22:09:09:2826] NOTICE: main:    .... strarting serialization of test 1
+[2019/03/30 22:09:09:2899] NOTICE: ser says 1
+{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":1800,"targets":[{"name":"target1"},{"name":"target2"}]}
+[2019/03/30 22:09:09:2929] NOTICE: main: ++++++++++++++++ test 2
+[2019/03/30 22:09:09:2932] NOTICE: builder.hostname = 'learn', timeout = 0, targets (3)
+[2019/03/30 22:09:09:2932] NOTICE:     target.name 'target1' (target 0x543b060)
+[2019/03/30 22:09:09:2933] NOTICE:     target.name 'target2' (target 0x543b090)
+[2019/03/30 22:09:09:2933] NOTICE:     target.name 'target3' (target 0x543b0c0)
+[2019/03/30 22:09:09:2934] NOTICE: main:    .... strarting serialization of test 2
+[2019/03/30 22:09:09:2935] NOTICE: ser says 1
+{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":0,"targets":[{"name":"target1"},{"name":"target2"},{"name":"target3"}]}
+[2019/03/30 22:09:09:2940] NOTICE: main: ++++++++++++++++ test 3
+[2019/03/30 22:09:09:2959] NOTICE: builder.hostname = 'learn', timeout = 1800, targets (2)
+[2019/03/30 22:09:09:2960] NOTICE:     target.name 'target1' (target 0x543b450)
+[2019/03/30 22:09:09:2961] NOTICE:       child 0x543b480, target.child.somename 'abc'
+[2019/03/30 22:09:09:2961] NOTICE:     target.name 'target2' (target 0x543b490)
+[2019/03/30 22:09:09:2962] NOTICE: main:    .... strarting serialization of test 3
+[2019/03/30 22:09:09:2969] NOTICE: ser says 1
+{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":1800,"targets":[{"name":"target1","child":{"somename":"abc"}},{"name":"target2"}]}
+[2019/03/30 22:09:09:2970] NOTICE: main: ++++++++++++++++ test 4
+[2019/03/30 22:09:09:2971] NOTICE: builder.hostname = 'learn', timeout = 1800, targets (0)
+[2019/03/30 22:09:09:2971] NOTICE: main:    .... strarting serialization of test 4
+[2019/03/30 22:09:09:2973] NOTICE: ser says 1
+{"schema":"com-warmcat-sai-builder","hostname":"learn","nspawn_timeout":1800}
+[2019/03/30 22:09:09:2974] NOTICE: main: ++++++++++++++++ test 5
+[2019/03/30 22:09:09:2978] NOTICE: builder.hostname = '', timeout = 0, targets (0)
+[2019/03/30 22:09:09:2979] NOTICE: main:    .... strarting serialization of test 5
+[2019/03/30 22:09:09:2980] NOTICE: ser says 1
+{"schema":"com-warmcat-sai-builder","hostname":"","nspawn_timeout":0}
+[2019/03/30 22:09:09:2982] USER: Completed: PASS
+```
+
diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/main.c b/minimal-examples/api-tests/api-test-lws_struct-json/main.c
new file mode 100644 (file)
index 0000000..5b84718
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * lws-api-test-lws_struct-json
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * lws_struct apis are used to serialize and deserialize your C structs and
+ * linked-lists in a standardized way that's very modest on memory but
+ * convenient and easy to maintain.
+ *
+ * The API test shows how to serialize and deserialize a struct with a linked-
+ * list of child structs in JSON using lws_struct APIs.
+ */
+
+#include <libwebsockets.h>
+
+/*
+ * in this example, the JSON is for one "builder" object, which may specify
+ * a child list "targets" of zero or more "target" objects.
+ */
+
+static const char * const json_tests[] = {
+       "{" /* test 1 */
+               "\"schema\":\"com-warmcat-sai-builder\","
+
+               "\"hostname\":\"learn\","
+               "\"nspawn_timeout\":1800,"
+               "\"targets\":["
+                       "{"
+                               "\"name\":\"target1\","
+                               "\"someflag\":true"
+                       "},"
+                       "{"
+                               "\"name\":\"target2\","
+                               "\"someflag\":false"
+                       "}"
+               "]"
+       "}",
+       "{" /* test 2 */
+               "\"schema\":\"com-warmcat-sai-builder\","
+
+               "\"hostname\":\"learn\","
+               "\"targets\":["
+                       "{"
+                               "\"name\":\"target1\""
+                       "},"
+                       "{"
+                               "\"name\":\"target2\""
+                       "},"
+                       "{"
+                               "\"name\":\"target3\""
+                       "}"
+               "]"
+       "}", "{" /* test 3 */
+               "\"schema\":\"com-warmcat-sai-builder\","
+
+               "\"hostname\":\"learn\","
+               "\"nspawn_timeout\":1800,"
+               "\"targets\":["
+                       "{"
+                               "\"name\":\"target1\","
+                               "\"unrecognized\":\"xyz\","
+                               "\"child\": {"
+                                       "\"somename\": \"abc\","
+                                       "\"junk\": { \"x\": \"y\" }"
+                               "}"
+                       "},"
+                       "{"
+                               "\"name\":\"target2\""
+                       "}"
+               "]"
+       "}",
+       "{" /* test 4 */
+               "\"schema\":\"com-warmcat-sai-builder\","
+
+               "\"hostname\":\"learn\","
+               "\"nspawn_timeout\":1800"
+       "}",
+       "{" /* test 5 */
+               "\"schema\":\"com-warmcat-sai-builder\""
+       "}",
+       "{" /* test 6 ... check huge strings into smaller fixed char array */
+               "\"schema\":\"com-warmcat-sai-builder\","
+               "\"hostname\":\""
+               "PYvtan6kqppjnS0KpYTCaiOLsJkc7XecAr1kcE0aCIciewYB+JcLG82mO1Vb1mJtjDwUjBxy2I6A"
+               "zefzoWUWmqZbsv4MXR55j9bKlyz1liiSX63iO0x6JAwACMtE2MkgcLwR86TSWAD9D1QKIWqg5RJ/"
+               "CRuVsW0DKAUMD52ql4JmPFuJpJgTq28z6PhYNzN3yI3bmQt6bzhA+A/xAsFzSBnb3MHYWzGMprr5"
+               "3FAP1ISo5Ec9i+2ehV40sG6Q470sH3PGQZ0YRPO7Sh/SyrSQ/scONmxRc3AcXl7X/CSs417ii+CV"
+               "8sq3ZgcxKNB7tNfN7idNx3upZ00G2BZy9jSy03cLKKLNaNUt0TQsxXbH55uDHzSEeZWvxJgT6zB1"
+               "NoMhdC02w+oXim94M6z6COCnqT3rgkGk8PHMry9Bkh4yVpRmzIRfMmln/lEhdZgxky2+g5hhlSIG"
+               "JYDCrdynD9kCfvfy6KGOpNIi1X+mhbbWn4lnL9ZKihL/RrfOV+oV4R26IDq+KqUiJBENeo8/GXkG"
+               "LUH/87iPyzXKEMavr6fkrK0vTGto8yEYxmOyaVz8phG5rwf4jJgmYNoMbGo8gWvhqO7UAGy2g7MW"
+               "v+B/t1eZZ+1euLsNrWAsFJiFbQKgdFfQT3RjB14iU8knlQ8usoy+pXssY2ddGJGVcGC21oZvstK9"
+               "eu1eRZftda/wP+N5unT1Hw7kCoVzqxHieiYt47EGIOaaQ7XjZDK6qPN6O/grHnvJZm2vBkxuXgsY"
+               "VkRQ7AuTWIecphqFsq7Wbc1YNbMW47SVU5zMD0WaCqbaaI0t4uIzRvPlD8cpiiTzFTrEHlIBTf8/"
+               "uZjjEGGLhJR1jPqA9D1Ej3ChV+ye6F9JTUMlozRMsGuF8U4btDzH5xdnmvRS4Ar6LKEtAXGkj2yu"
+               "yJln+v4RIWj2xOGPJovOqiXwi0FyM61f8U8gj0OiNA2/QlvrqQVDF7sMXgjvaE7iQt5vMETteZlx"
+               "+z3f+jTFM/aon511W4+ZkRD+6AHwucvM9BEC\""
+       "}",
+       "{" /* test 7 ... check huge strings into char * */
+               "\"schema\":\"com-warmcat-sai-builder\","
+               "\"targets\":["
+                       "{"
+                               "\"name\":\""
+               "PYvtan6kqppjnS0KpYTCaiOLsJkc7XecAr1kcE0aCIciewYB+JcLG82mO1Vb1mJtjDwUjBxy2I6A"
+               "zefzoWUWmqZbsv4MXR55j9bKlyz1liiSX63iO0x6JAwACMtE2MkgcLwR86TSWAD9D1QKIWqg5RJ/"
+               "CRuVsW0DKAUMD52ql4JmPFuJpJgTq28z6PhYNzN3yI3bmQt6bzhA+A/xAsFzSBnb3MHYWzGMprr5"
+               "3FAP1ISo5Ec9i+2ehV40sG6Q470sH3PGQZ0YRPO7Sh/SyrSQ/scONmxRc3AcXl7X/CSs417ii+CV"
+               "8sq3ZgcxKNB7tNfN7idNx3upZ00G2BZy9jSy03cLKKLNaNUt0TQsxXbH55uDHzSEeZWvxJgT6zB1"
+               "NoMhdC02w+oXim94M6z6COCnqT3rgkGk8PHMry9Bkh4yVpRmzIRfMmln/lEhdZgxky2+g5hhlSIG"
+               "JYDCrdynD9kCfvfy6KGOpNIi1X+mhbbWn4lnL9ZKihL/RrfOV+oV4R26IDq+KqUiJBENeo8/GXkG"
+               "LUH/87iPyzXKEMavr6fkrK0vTGto8yEYxmOyaVz8phG5rwf4jJgmYNoMbGo8gWvhqO7UAGy2g7MW"
+               "v+B/t1eZZ+1euLsNrWAsFJiFbQKgdFfQT3RjB14iU8knlQ8usoy+pXssY2ddGJGVcGC21oZvstK9"
+               "eu1eRZftda/wP+N5unT1Hw7kCoVzqxHieiYt47EGIOaaQ7XjZDK6qPN6O/grHnvJZm2vBkxuXgsY"
+               "VkRQ7AuTWIecphqFsq7Wbc1YNbMW47SVU5zMD0WaCqbaaI0t4uIzRvPlD8cpiiTzFTrEHlIBTf8/"
+               "uZjjEGGLhJR1jPqA9D1Ej3ChV+ye6F9JTUMlozRMsGuF8U4btDzH5xdnmvRS4Ar6LKEtAXGkj2yu"
+               "yJln+v4RIWj2xOGPJovOqiXwi0FyM61f8U8gj0OiNA2/QlvrqQVDF7sMXgjvaE7iQt5vMETteZlx"
+               "+z3f+jTFM/aon511W4+ZkRD+6AHwucvM9BEC\"}]}"
+       "}",
+};
+
+/*
+ * These are the expected outputs for each test, without pretty formatting.
+ *
+ * There are some differences to do with missing elements being rendered with
+ * default values.
+ */
+
+static const char * const json_expected[] = {
+       "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"learn\","
+         "\"nspawn_timeout\":1800,\"targets\":[{\"name\":\"target1\",\"someflag\":true},"
+         "{\"name\":\"target2\",\"someflag\":false}]}",
+
+       "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"learn\","
+        "\"nspawn_timeout\":0,\"targets\":[{\"name\":\"target1\",\"someflag\":false},"
+         "{\"name\":\"target2\",\"someflag\":false},{\"name\":\"target3\",\"someflag\":false}]}",
+
+       "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"learn\","
+       "\"nspawn_timeout\":1800,\"targets\":[{\"name\":\"target1\",\"someflag\":false,"
+         "\"child\":{\"somename\":\"abc\"}},{\"name\":\"target2\",\"someflag\":false}]}",
+
+       "{\"schema\":\"com-warmcat-sai-builder\","
+         "\"hostname\":\"learn\",\"nspawn_timeout\":1800}",
+
+       "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"\","
+       "\"nspawn_timeout\":0}",
+
+       "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":"
+               "\"PYvtan6kqppjnS0KpYTCaiOLsJkc7Xe\","
+       "\"nspawn_timeout\":0}",
+
+       "{\"schema\":\"com-warmcat-sai-builder\",\"hostname\":\"\","
+         "\"nspawn_timeout\":0,\"targets\":[{\"name\":\"PYvtan6kqppjnS0KpYTC"
+               "aiOLsJkc7XecAr1kcE0aCIciewYB+JcLG82mO1Vb1mJtjDwUjBxy2I6Azefz"
+               "oWUWmqZbsv4MXR55j9bKlyz1liiSX63iO0x6JAwACMtE2MkgcLwR86TSWAD9"
+               "D1QKIWqg5RJ/CRuVsW0DKAUMD52ql4JmPFuJpJgTq28z6PhYNzN3yI3bmQt6"
+               "bzhA+A/xAsFzSBnb3MHYWzGMprr53FAP1ISo5Ec9i+2ehV40sG6Q470sH3PG"
+               "QZ0YRPO7Sh/SyrSQ/scONmxRc3AcXl7X/CSs417ii+CV8sq3ZgcxKNB7tNfN"
+               "7idNx3upZ00G2BZy9jSy03cLKKLNaNUt0TQsxXbH55uDHzSEeZWvxJgT6zB1"
+               "NoMhdC02w+oXim94M6z6COCnqT3rgkGk8PHMry9Bkh4yVpRmzIRfMmln/lEh"
+               "dZgxky2+g5hhlSIGJYDCrdynD9kCfvfy6KGOpNIi1X+mhbbWn4lnL9ZKihL/"
+               "RrfOV+oV4R26IDq+KqUiJBENeo8/GXkGLUH/87iPyzXKEMavr6fkrK0vTGto"
+               "8yEYxmOyaVz8phG5rwf4jJgmYNoMbGo8gWvhqO7UAGy2g7MWv+B/t1eZZ+1e"
+               "uLsNrWAsFJiFbQKgdFfQT3RjB14iU8knlQ8usoy+pXssY2ddGJGVcGC21oZv"
+               "stK9eu1eRZftda/wP+N5unT1Hw7kCoVzqxHieiYt47EGIOaaQ7XjZDK6qPN6"
+               "O/grHnvJZm2vBkxuXgsYVkRQ7AuTWIecphqFsq7Wbc1YNbMW47SVU5zMD0Wa"
+               "CqbaaI0t4uIzRvPlD8cpiiTzFTrEHlIBTf8/uZjjEGGLhJR1jPqA9D1Ej3Ch"
+               "V+ye6F9JTUMlozRMsGuF8U4btDzH5xdnmvRS4Ar6LKEtAXGkj2yuyJln+v4R"
+               "IWj2xOGPJovOqiXwi0FyM61f8U8gj0OiNA2/QlvrqQVDF7sMXgjvaE7iQt5v"
+               "METteZlx+z3f+jTFM/aon511W4+ZkRD+6AHwucvM9BEC\""
+                       ",\"someflag\":false}]}"
+};
+
+/*
+ * These annotate the members in the struct that will be serialized and
+ * deserialized with type and size information, as well as the name to use
+ * in the serialization format.
+ *
+ * Struct members that aren't annotated like this won't be serialized and
+ * when the struct is created during deserialiation, the will be set to 0
+ * or NULL.
+ */
+
+/* child object */
+
+typedef struct sai_child {
+       const char *    somename;
+} sai_child_t;
+
+lws_struct_map_t lsm_child[] = { /* describes serializable members */
+       LSM_STRING_PTR  (sai_child_t, somename,                 "somename"),
+};
+
+/* target object */
+
+typedef struct sai_target {
+       struct lws_dll2 target_list;
+       sai_child_t *           child;
+
+       const char *            name;
+       char                    someflag;
+} sai_target_t;
+
+static const lws_struct_map_t lsm_target[] = {
+       LSM_STRING_PTR  (sai_target_t, name,                    "name"),
+       LSM_BOOLEAN     (sai_target_t, someflag,                "someflag"),
+       LSM_CHILD_PTR   (sai_target_t, child, sai_child_t,
+                        NULL, lsm_child,                       "child"),
+};
+
+/* builder object */
+
+typedef struct sai_builder {
+       struct lws_dll2_owner   targets;
+
+       char                    hostname[32];
+       unsigned int            nspawn_timeout;
+} sai_builder_t;
+
+static const lws_struct_map_t lsm_builder[] = {
+       LSM_CARRAY      (sai_builder_t, hostname,               "hostname"),
+       LSM_UNSIGNED    (sai_builder_t, nspawn_timeout,         "nspawn_timeout"),
+       LSM_LIST        (sai_builder_t, targets,
+                        sai_target_t, target_list,
+                        NULL, lsm_target,                      "targets"),
+};
+
+/* Schema table
+ *
+ * Before we can understand the serialization top level format, we must read
+ * the schema, use the table below to create the right toplevel object for the
+ * schema name, and select the correct map tables to interpret the rest of the
+ * serialization.
+ *
+ * Therefore the schema tables below are the starting point for the
+ * JSON deserialization.
+ */
+
+static const lws_struct_map_t lsm_schema_map[] = {
+       LSM_SCHEMA      (sai_builder_t, NULL,
+                        lsm_builder,           "com-warmcat-sai-builder"),
+};
+
+static int
+show_target(struct lws_dll2 *d, void *user)
+{
+       sai_target_t *t = lws_container_of(d, sai_target_t, target_list);
+
+       lwsl_notice("    target.name '%s' (target %p)\n", t->name, t);
+
+       if (t->child)
+               lwsl_notice("      child %p, target.child.somename '%s'\n",
+                         t->child, t->child->somename);
+
+       return 0;
+}
+
+
+int main(int argc, const char **argv)
+{
+       int n, m, e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+#if 1
+       lws_struct_serialize_t *ser;
+       uint8_t buf[4096];
+       size_t written;
+#endif
+       struct lejp_ctx ctx;
+       lws_struct_args_t a;
+       sai_builder_t *b;
+       const char *p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: lws_struct JSON\n");
+
+       for (m = 0; m < (int)LWS_ARRAY_SIZE(json_tests); m++) {
+
+               /* 1. deserialize the canned JSON into structs */
+
+               lwsl_notice("%s: ++++++++++++++++ test %d\n", __func__, m + 1);
+
+               memset(&a, 0, sizeof(a));
+               a.map_st[0] = lsm_schema_map;
+               a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_map);
+               a.ac_block_size = 512;
+
+               lws_struct_json_init_parse(&ctx, NULL, &a);
+               n = (int)(signed char)lejp_parse(&ctx, (uint8_t *)json_tests[m],
+                                                strlen(json_tests[m]));
+               if (n < 0) {
+                       lwsl_err("%s: notification JSON decode failed '%s'\n",
+                                       __func__, lejp_error_to_string(n));
+                       e++;
+                       goto done;
+               }
+               lwsac_info(a.ac);
+
+               b = a.dest;
+               if (!b) {
+                       lwsl_err("%s: didn't produce any output\n", __func__);
+                       e++;
+                       goto done;
+               }
+
+               lwsl_notice("builder.hostname = '%s', timeout = %d, targets (%d)\n",
+                           b->hostname, b->nspawn_timeout,
+                           b->targets.count);
+
+               lws_dll2_foreach_safe(&b->targets, NULL, show_target);
+
+               /* 2. serialize the structs into JSON and confirm */
+
+               lwsl_notice("%s:    .... strarting serialization of test %d\n",
+                               __func__, m + 1);
+               ser = lws_struct_json_serialize_create(lsm_schema_map,
+                                               LWS_ARRAY_SIZE(lsm_schema_map),
+                                                      0//LSSERJ_FLAG_PRETTY
+                                                      , b);
+               if (!ser) {
+                       lwsl_err("%s: unable to init serialization\n", __func__);
+                       goto bail;
+               }
+
+               do {
+                       n = lws_struct_json_serialize(ser, buf, sizeof(buf),
+                                                     &written);
+                       lwsl_notice("ser says %d\n", n);
+                       switch (n) {
+                       case LSJS_RESULT_CONTINUE:
+                       case LSJS_RESULT_FINISH:
+                               puts((const char *)buf);
+                               break;
+                       case LSJS_RESULT_ERROR:
+                               goto bail;
+                       }
+               } while(n == LSJS_RESULT_CONTINUE);
+
+               if (strcmp(json_expected[m], (char *)buf)) {
+                       lwsl_err("%s: test %d: expected %s\n", __func__, m + 1,
+                                       json_expected[m]);
+                       e++;
+               }
+
+               lws_struct_json_serialize_destroy(&ser);
+
+done:
+               lwsac_free(&a.ac);
+       }
+
+       if (e)
+               goto bail;
+
+       lwsl_user("Completed: PASS\n");
+
+       return 0;
+
+bail:
+       lwsl_user("Completed: FAIL\n");
+
+       return 1;
+}
diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/selftest.sh b/minimal-examples/api-tests/api-test-lws_struct-json/selftest.sh
new file mode 100755 (executable)
index 0000000..16d1e2e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 apiselftest
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-lws_tokenize/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_tokenize/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7bfc6f6
--- /dev/null
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-lws_tokenize)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
diff --git a/minimal-examples/api-tests/api-test-lws_tokenize/README.md b/minimal-examples/api-tests/api-test-lws_tokenize/README.md
new file mode 100644 (file)
index 0000000..a6b75ec
--- /dev/null
@@ -0,0 +1,37 @@
+# lws api test lws_tokenize
+
+Performs selftests for lws_tokenize
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-s "input string"|String to tokenize
+-f 15|LWS_TOKENIZE_F_ flag values to apply to processing of -s 
+
+```
+ $ ./lws-api-test-lws_tokenize
+[2018/10/09 09:14:17:4834] USER: LWS API selftest: lws_tokenize
+[2018/10/09 09:14:17:4835] USER: Completed: PASS: 6, FAIL: 0
+```
+
+If the `-s string` option is given, the string is tokenized on stdout in
+the format used to produce the tests in the sources
+
+```
+ $ ./lws-api-test-lws_tokenize -s "hello: 1234,256"
+[2018/10/09 09:14:17:4834] USER: LWS API selftest: lws_tokenize
+{ LWS_TOKZE_TOKEN_NAME_COLON, "hello", 5 }
+{ LWS_TOKZE_INTEGER, "1234", 4 }
+{ LWS_TOKZE_DELIMITER, ",", 1 }
+{ LWS_TOKZE_INTEGER, "256", 3 }
+{ LWS_TOKZE_ENDED, "", 0 }
+```
+
diff --git a/minimal-examples/api-tests/api-test-lws_tokenize/main.c b/minimal-examples/api-tests/api-test-lws_tokenize/main.c
new file mode 100644 (file)
index 0000000..3257c4f
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * lws-api-test-lws_tokenize
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <stdio.h>
+
+struct expected {
+       lws_tokenize_elem e;
+       const char *value;
+       int len;
+};
+
+struct tests {
+       const char *string;
+       struct expected *exp;
+       int count;
+       int flags;
+};
+
+struct expected expected1[] = {
+                       { LWS_TOKZE_TOKEN,              "protocol-1", 10 },
+               { LWS_TOKZE_DELIMITER, ",", 1},
+                       { LWS_TOKZE_TOKEN,              "protocol_2", 10 },
+               { LWS_TOKZE_DELIMITER, ",", 1},
+                       { LWS_TOKZE_TOKEN,              "protocol3", 9 },
+               { LWS_TOKZE_ENDED, NULL, 0 },
+       },
+       expected2[] = {
+               { LWS_TOKZE_TOKEN_NAME_COLON,           "Accept-Language", 15 },
+                       { LWS_TOKZE_TOKEN,              "fr-CH", 5 },
+               { LWS_TOKZE_DELIMITER,                  ",", 1 },
+                       { LWS_TOKZE_TOKEN,              "fr", 2 },
+                       { LWS_TOKZE_DELIMITER,          ";", 1},
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS,  "q", 1 },
+                       { LWS_TOKZE_FLOAT,              "0.9", 3 },
+               { LWS_TOKZE_DELIMITER,                  ",", 1 },
+                       { LWS_TOKZE_TOKEN,              "en", 2 },
+                       { LWS_TOKZE_DELIMITER,          ";", 1},
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS,  "q", 1 },
+                       { LWS_TOKZE_FLOAT,              "0.8", 3 },
+               { LWS_TOKZE_DELIMITER,                  ",", 1 },
+                       { LWS_TOKZE_TOKEN,              "de", 2 },
+                       { LWS_TOKZE_DELIMITER,          ";", 1},
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS,  "q", 1 },
+                       { LWS_TOKZE_FLOAT,              "0.7", 3 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+                       { LWS_TOKZE_DELIMITER,          "*", 1 },
+                       { LWS_TOKZE_DELIMITER,          ";", 1 },
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS,  "q", 1 },
+                       { LWS_TOKZE_FLOAT,              "0.5", 3 },
+               { LWS_TOKZE_ENDED, NULL, 0 },
+       },
+       expected3[] = {
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS,  "quoted", 6 },
+                       { LWS_TOKZE_QUOTED_STRING,      "things:", 7 },
+               { LWS_TOKZE_DELIMITER,                  ",", 1 },
+                       { LWS_TOKZE_INTEGER,            "1234", 4 },
+               { LWS_TOKZE_ENDED, NULL, 0 },
+       },
+       expected4[] = {
+               { LWS_TOKZE_ERR_COMMA_LIST,             ",", 1 },
+       },
+       expected5[] = {
+                       { LWS_TOKZE_TOKEN,              "brokenlist2", 11 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+               { LWS_TOKZE_ERR_COMMA_LIST,             ",", 1 },
+       },
+       expected6[] = {
+                       { LWS_TOKZE_TOKEN,              "brokenlist3", 11 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+               { LWS_TOKZE_ERR_COMMA_LIST,             ",", 1 },
+
+       },
+       expected7[] = {
+                       { LWS_TOKZE_TOKEN, "fr", 2 },
+                       { LWS_TOKZE_DELIMITER, "-", 1 },
+                       { LWS_TOKZE_TOKEN, "CH", 2 },
+                       { LWS_TOKZE_DELIMITER, ",", 1 },
+                       { LWS_TOKZE_TOKEN, "fr", 2 },
+                       { LWS_TOKZE_DELIMITER, ";", 1 },
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
+                       { LWS_TOKZE_FLOAT, "0.9", 3 },
+                       { LWS_TOKZE_DELIMITER, ",", 1 },
+                       { LWS_TOKZE_TOKEN, "en", 2 },
+                       { LWS_TOKZE_DELIMITER, ";", 1 },
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
+                       { LWS_TOKZE_FLOAT, "0.8", 3 },
+                       { LWS_TOKZE_DELIMITER, ",", 1 },
+                       { LWS_TOKZE_TOKEN, "de", 2 },
+                       { LWS_TOKZE_DELIMITER, ";", 1 },
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
+                       { LWS_TOKZE_FLOAT, "0.7", 3 },
+                       { LWS_TOKZE_DELIMITER, ",", 1 },
+                       { LWS_TOKZE_TOKEN, "*", 1 },
+                       { LWS_TOKZE_DELIMITER, ";", 1 },
+                       { LWS_TOKZE_TOKEN_NAME_EQUALS, "q", 1 },
+                       { LWS_TOKZE_FLOAT, "0.5", 3 },
+                       { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected8[] = {
+               { LWS_TOKZE_TOKEN, "Οὐχὶ", 10 },
+               { LWS_TOKZE_TOKEN, "ταὐτὰ", 12 },
+               { LWS_TOKZE_TOKEN, "παρίσταταί", 22 },
+               { LWS_TOKZE_TOKEN, "μοι", 6 },
+               { LWS_TOKZE_TOKEN, "γιγνώσκειν", 21 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+               { LWS_TOKZE_TOKEN, "ὦ", 3 },
+               { LWS_TOKZE_TOKEN, "ἄνδρες", 13 },
+               { LWS_TOKZE_TOKEN, "᾿Αθηναῖοι", 20 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+               { LWS_TOKZE_TOKEN, "greek", 5 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected9[] = {
+               /*
+                *  because the tokenizer scans ahead for = aggregation,
+                * it finds the broken utf8 before reporting the token
+                */
+               { LWS_TOKZE_ERR_BROKEN_UTF8, "", 0 },
+       },
+       expected10[] = {
+               { LWS_TOKZE_TOKEN, "badutf8-2", 9 },
+               { LWS_TOKZE_TOKEN, "퟿", 3 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+               { LWS_TOKZE_ERR_BROKEN_UTF8, "", 0 },
+       },
+       expected11[] = {
+               { LWS_TOKZE_TOKEN, "1.myserver", 10 },
+               { LWS_TOKZE_DELIMITER, ".", 1 },
+               { LWS_TOKZE_TOKEN, "com", 3 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected12[] = {
+               { LWS_TOKZE_TOKEN, "1.myserver.com", 14 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected13[] = {
+               { LWS_TOKZE_TOKEN, "1.myserver.com", 14 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected14[] = {
+               { LWS_TOKZE_INTEGER, "1", 1 },
+               { LWS_TOKZE_DELIMITER, ".", 1 },
+               { LWS_TOKZE_TOKEN, "myserver", 8 },
+               { LWS_TOKZE_DELIMITER, ".", 1 },
+               { LWS_TOKZE_TOKEN, "com", 3 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected15[] = {
+               { LWS_TOKZE_TOKEN, "close", 5 },
+               { LWS_TOKZE_DELIMITER, ",", 1 },
+               { LWS_TOKZE_TOKEN, "Upgrade", 7 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       },
+       expected16[] = {
+               { LWS_TOKZE_TOKEN_NAME_EQUALS, "a", 1 },
+               { LWS_TOKZE_TOKEN, "5", 1 },
+               { LWS_TOKZE_ENDED, "", 0 },
+       }
+
+;
+
+struct tests tests[] = {
+       {
+               " protocol-1, protocol_2\t,\tprotocol3\n",
+               expected1, LWS_ARRAY_SIZE(expected1),
+               LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_AGG_COLON
+       }, {
+               "Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
+               expected2, LWS_ARRAY_SIZE(expected2),
+               LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_AGG_COLON
+       }, {
+               "quoted = \"things:\", 1234",
+               expected3, LWS_ARRAY_SIZE(expected3),
+               LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_AGG_COLON
+       }, {
+               ", brokenlist1",
+               expected4, LWS_ARRAY_SIZE(expected4),
+               LWS_TOKENIZE_F_COMMA_SEP_LIST
+       }, {
+               "brokenlist2,,",
+               expected5, LWS_ARRAY_SIZE(expected5),
+               LWS_TOKENIZE_F_COMMA_SEP_LIST
+       }, {
+               "brokenlist3,",
+               expected6, LWS_ARRAY_SIZE(expected6),
+               LWS_TOKENIZE_F_COMMA_SEP_LIST
+       }, {
+               "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5",
+               expected7, LWS_ARRAY_SIZE(expected7),
+               LWS_TOKENIZE_F_RFC7230_DELIMS
+       },
+       {
+               " Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι, greek",
+               expected8, LWS_ARRAY_SIZE(expected8),
+               LWS_TOKENIZE_F_RFC7230_DELIMS
+       },
+       {
+               "badutf8-1 \x80...",
+               expected9, LWS_ARRAY_SIZE(expected9),
+               LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_RFC7230_DELIMS
+       },
+       {
+               "badutf8-2 \xed\x9f\xbf,\x80...",
+               expected10, LWS_ARRAY_SIZE(expected10),
+               LWS_TOKENIZE_F_MINUS_NONTERM | LWS_TOKENIZE_F_RFC7230_DELIMS
+       },
+       {
+               "1.myserver.com",
+               expected11, LWS_ARRAY_SIZE(expected11),
+               0
+       },
+       {
+               "1.myserver.com",
+               expected12, LWS_ARRAY_SIZE(expected12),
+               LWS_TOKENIZE_F_DOT_NONTERM
+       },
+       {
+               "1.myserver.com",
+               expected13, LWS_ARRAY_SIZE(expected13),
+               LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS
+       },
+       {
+               "1.myserver.com",
+               expected14, LWS_ARRAY_SIZE(expected14),
+               LWS_TOKENIZE_F_NO_FLOATS
+       },
+       {
+               "close,  Upgrade",
+               expected15, LWS_ARRAY_SIZE(expected15),
+               LWS_TOKENIZE_F_COMMA_SEP_LIST
+       },
+       {
+               "a=5", expected16, LWS_ARRAY_SIZE(expected16),
+               LWS_TOKENIZE_F_NO_INTEGERS
+       },
+};
+
+/*
+ * add LWS_TOKZE_ERRS to the element index (which may be negative by that
+ * amount) to index this array
+ */
+
+static const char *element_names[] = {
+       "LWS_TOKZE_ERR_BROKEN_UTF8",
+       "LWS_TOKZE_ERR_UNTERM_STRING",
+       "LWS_TOKZE_ERR_MALFORMED_FLOAT",
+       "LWS_TOKZE_ERR_NUM_ON_LHS",
+       "LWS_TOKZE_ERR_COMMA_LIST",
+       "LWS_TOKZE_ENDED",
+       "LWS_TOKZE_DELIMITER",
+       "LWS_TOKZE_TOKEN",
+       "LWS_TOKZE_INTEGER",
+       "LWS_TOKZE_FLOAT",
+       "LWS_TOKZE_TOKEN_NAME_EQUALS",
+       "LWS_TOKZE_TOKEN_NAME_COLON",
+       "LWS_TOKZE_QUOTED_STRING",
+};
+
+int main(int argc, const char **argv)
+{
+       struct lws_tokenize ts;
+       lws_tokenize_elem e;
+       const char *p;
+       int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+       int fail = 0, ok = 0, flags = 0;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: lws_tokenize\n");
+
+       if ((p = lws_cmdline_option(argc, argv, "-f")))
+               flags = atoi(p);
+
+       p = lws_cmdline_option(argc, argv, "-s");
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(tests); n++) {
+               int m = 0, in_fail = fail;
+               struct expected *exp = tests[n].exp;
+
+               ts.start = tests[n].string;
+               ts.len = strlen(ts.start);
+               ts.flags = tests[n].flags;
+
+               do {
+                       e = lws_tokenize(&ts);
+
+                       lwsl_info("{ %s, \"%.*s\", %d }\n",
+                                 element_names[e + LWS_TOKZE_ERRS],
+                                 (int)ts.token_len, ts.token,
+                                 (int)ts.token_len);
+
+                       if (m == (int)tests[n].count) {
+                               lwsl_notice("fail: expected end earlier\n");
+                               fail++;
+                               break;
+                       }
+
+                       if (e != exp->e) {
+                               lwsl_notice("fail... tok %s vs expected %s\n",
+                                       element_names[e + LWS_TOKZE_ERRS],
+                                       element_names[exp->e + LWS_TOKZE_ERRS]);
+                               fail++;
+                               break;
+                       }
+
+                       if (e > 0 &&
+                           (ts.token_len != exp->len ||
+                            memcmp(exp->value, ts.token, exp->len))) {
+                               lwsl_notice("fail token mismatch %d %d %.*s\n",
+                                               ts.token_len, exp->len, ts.token_len, ts.token);
+                               fail++;
+                               break;
+                       }
+
+                       m++;
+                       exp++;
+
+               } while (e > 0);
+
+               if (fail == in_fail)
+                       ok++;
+       }
+
+       if (p) {
+               ts.start = p;
+               ts.len = strlen(p);
+               ts.flags = flags;
+
+               printf("\t{\n\t\t\"%s\",\n"
+                      "\t\texpected%d, LWS_ARRAY_SIZE(expected%d),\n\t\t",
+                      p, (int)LWS_ARRAY_SIZE(tests) + 1,
+                      (int)LWS_ARRAY_SIZE(tests) + 1);
+
+               if (!flags)
+                       printf("0\n\t},\n");
+               else {
+                       if (flags & LWS_TOKENIZE_F_MINUS_NONTERM)
+                               printf("LWS_TOKENIZE_F_MINUS_NONTERM");
+                       if (flags & LWS_TOKENIZE_F_AGG_COLON) {
+                               if (flags & 1)
+                                       printf(" | ");
+                               printf("LWS_TOKENIZE_F_AGG_COLON");
+                       }
+                       if (flags & LWS_TOKENIZE_F_COMMA_SEP_LIST) {
+                               if (flags & 3)
+                                       printf(" | ");
+                               printf("LWS_TOKENIZE_F_COMMA_SEP_LIST");
+                       }
+                       if (flags & LWS_TOKENIZE_F_RFC7230_DELIMS) {
+                               if (flags & 7)
+                                       printf(" | ");
+                               printf("LWS_TOKENIZE_F_RFC7230_DELIMS");
+                       }
+                       if (flags & LWS_TOKENIZE_F_DOT_NONTERM) {
+                               if (flags & 15)
+                                       printf(" | ");
+                               printf("LWS_TOKENIZE_F_DOT_NONTERM");
+                       }
+                       if (flags & LWS_TOKENIZE_F_NO_FLOATS) {
+                               if (flags & 31)
+                                       printf(" | ");
+                               printf("LWS_TOKENIZE_F_NO_FLOATS");
+                       }
+                       printf("\n\t},\n");
+               }
+
+               printf("\texpected%d[] = {\n", (int)LWS_ARRAY_SIZE(tests) + 1);
+
+               do {
+                       e = lws_tokenize(&ts);
+
+                       printf("\t\t{ %s, \"%.*s\", %d },\n",
+                                 element_names[e + LWS_TOKZE_ERRS],
+                                 (int)ts.token_len,
+                                 ts.token, (int)ts.token_len);
+
+               } while (e > 0);
+
+               printf("\t}\n");
+       }
+
+
+       lwsl_user("Completed: PASS: %d, FAIL: %d\n", ok, fail);
+
+       return !(ok && !fail);
+}
diff --git a/minimal-examples/api-tests/api-test-lws_tokenize/selftest.sh b/minimal-examples/api-tests/api-test-lws_tokenize/selftest.sh
new file mode 100755 (executable)
index 0000000..16d1e2e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 apiselftest
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt b/minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a73c680
--- /dev/null
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-lwsac)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
diff --git a/minimal-examples/api-tests/api-test-lwsac/README.md b/minimal-examples/api-tests/api-test-lwsac/README.md
new file mode 100644 (file)
index 0000000..74034c7
--- /dev/null
@@ -0,0 +1,22 @@
+# lws api test lwsac
+
+Demonstrates how to use and performs selftests for lwsac
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+```
+ $ ./lws-api-test-lwsac
+[2018/10/09 09:14:17:4834] USER: LWS API selftest: lwsac
+[2018/10/09 09:14:17:4835] USER: Completed: PASS
+```
+
diff --git a/minimal-examples/api-tests/api-test-lwsac/main.c b/minimal-examples/api-tests/api-test-lwsac/main.c
new file mode 100644 (file)
index 0000000..0ea0aa4
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * lws-api-test-lwsac
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+
+struct mytest {
+       int payload;
+       /* notice doesn't have to be at start of struct */
+       lws_list_ptr list_next;
+       /* a struct can appear on multiple lists too... */
+};
+
+/* converts a ptr to struct mytest .list_next to a ptr to struct mytest */
+#define list_to_mytest(p) lws_list_ptr_container(p, struct mytest, list_next)
+
+int main(int argc, const char **argv)
+{
+       int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, acc;
+       lws_list_ptr list_head = NULL, iter;
+       struct lwsac *lwsac = NULL;
+       struct mytest *m;
+       const char *p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: lwsac\n");
+
+       /*
+        * 1) allocate and create 1000 struct mytest in a linked-list
+        */
+
+       for (n = 0; n < 1000; n++) {
+               m = lwsac_use(&lwsac, sizeof(*m), 0);
+               if (!m)
+                       return -1;
+               m->payload = n;
+
+               lws_list_ptr_insert(&list_head, &m->list_next, NULL);
+       }
+
+       /*
+        * 2) report some debug info about the lwsac state... those 1000
+        * allocations actually only required 4 mallocs
+        */
+
+       lwsac_info(lwsac);
+
+       /* 3) iterate the list, accumulating the payloads */
+
+       acc = 0;
+       iter = list_head;
+       while (iter) {
+               m = list_to_mytest(iter);
+               acc += m->payload;
+
+               lws_list_ptr_advance(iter);
+       }
+
+       if (acc != 499500) {
+               lwsl_err("%s: FAIL acc %d\n", __func__, acc);
+
+               return 1;
+       }
+
+       /*
+        * 4) deallocate everything (lwsac is also set to NULL).  It just
+        *    deallocates the 4 mallocs, everything in there is gone accordingly
+        */
+
+       lwsac_free(&lwsac);
+
+       lwsl_user("Completed: PASS\n");
+
+       return 0;
+}
diff --git a/minimal-examples/api-tests/api-test-lwsac/selftest.sh b/minimal-examples/api-tests/api-test-lwsac/selftest.sh
new file mode 100755 (executable)
index 0000000..16d1e2e
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 apiselftest
+exit $FAILS
diff --git a/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt b/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4c8e671
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-unit-tests-smtp-client)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_SMTP 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/api-tests/api-test-smtp_client/README.md b/minimal-examples/api-tests/api-test-smtp_client/README.md
new file mode 100644 (file)
index 0000000..4c2052d
--- /dev/null
@@ -0,0 +1,41 @@
+# lws api test smtp client
+
+Performs unit tests on the lws SMTP client abstract protocol
+implementation.
+
+The first test "sends mail to a server" (actually is prompted by
+test vectors that look like a server) and the second test
+confirm it can handle rejection by the "server" cleanly.
+
+## build
+
+Requires lws was built with `-DLWS_WITH_SMTP=1` at cmake.
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-r <recipient@whatever.com>|Send the test email to this email address
+
+
+```
+ $ ./lws-api-test-smtp_client
+[2019/06/28 21:56:41:0711] USER: LWS API selftest: SMTP client unit tests
+[2019/06/28 21:56:41:1114] NOTICE: test_sequencer_cb: test-seq: created
+[2019/06/28 21:56:41:1259] NOTICE: unit_test_sequencer_cb: unit-test-seq: created
+[2019/06/28 21:56:41:1272] NOTICE: lws_atcut_client_conn: smtp: test 'sending': start
+[2019/06/28 21:56:41:1441] NOTICE: unit_test_sequencer_cb: unit-test-seq: created
+[2019/06/28 21:56:41:1442] NOTICE: lws_atcut_client_conn: smtp: test 'rejected': start
+[2019/06/28 21:56:41:1453] NOTICE: lws_smtp_client_abs_rx: bad response from server: 500 (state 4) 500 Service Unavailable
+[2019/06/28 21:56:41:1467] USER: test_sequencer_cb: sequence completed OK
+[2019/06/28 21:56:41:1474] USER: main: 2 tests 0 fail
+[2019/06/28 21:56:41:1476] USER:   test 0: PASS
+[2019/06/28 21:56:41:1478] USER:   test 1: PASS
+[2019/06/28 21:56:41:1480] USER: Completed: PASS
+```
+
diff --git a/minimal-examples/api-tests/api-test-smtp_client/main.c b/minimal-examples/api-tests/api-test-smtp_client/main.c
new file mode 100644 (file)
index 0000000..df7adac
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * lws-unit-tests-smtp-client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This performs unit tests for the SMTP client abstract protocol
+ */
+
+#include <libwebsockets.h>
+
+#include <signal.h>
+
+static int interrupted, results[10], count_tests, count_passes;
+
+static int
+email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len)
+{
+       free(email);
+
+       return 0;
+}
+
+/*
+ * The test helper calls this on the instance it created to prepare it for
+ * the test.  In our case, we need to queue up a test email to send on the
+ * smtp client abstract protocol.
+ */
+
+static int
+smtp_test_instance_init(lws_abs_t *instance)
+{
+       lws_smtp_email_t *email = (lws_smtp_email_t *)
+                                       malloc(sizeof(*email) + 2048);
+
+       if (!email)
+               return 1;
+
+       /* attach an email to it */
+
+       memset(email, 0, sizeof(*email));
+       email->data = NULL /* email specific user data */;
+       email->email_from = "noreply@warmcat.com";
+       email->email_to = "andy@warmcat.com";
+       email->payload = (void *)&email[1];
+
+       lws_snprintf((char *)email->payload, 2048,
+                       "From: noreply@example.com\n"
+                       "To: %s\n"
+                       "Subject: Test email for lws smtp-client\n"
+                       "\n"
+                       "Hello this was an api test for lws smtp-client\n"
+                       "\r\n.\r\n", "andy@warmcat.com");
+       email->done = email_sent_or_failed;
+
+       if (lws_smtp_client_add_email(instance, email)) {
+               lwsl_err("%s: failed to add email\n", __func__);
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * from https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol
+ *
+ *             test vector sent to protocol
+ *                             test vector received from protocol
+ */
+
+static lws_unit_test_packet_t test_send1[] = {
+       {
+               "220 smtp.example.com ESMTP Postfix",
+               smtp_test_instance_init, 34, LWS_AUT_EXPECT_RX
+       }, {
+                               "HELO lws-test-client\x0a",
+               NULL, 21, LWS_AUT_EXPECT_TX
+       }, {
+               "250 smtp.example.com, I am glad to meet you",
+               NULL, 43, LWS_AUT_EXPECT_RX
+       }, {
+                               "MAIL FROM: <noreply@warmcat.com>\x0a",
+               NULL, 33, LWS_AUT_EXPECT_TX
+       }, {
+               "250 Ok",
+               NULL, 6, LWS_AUT_EXPECT_RX
+       }, {
+                               "RCPT TO: <andy@warmcat.com>\x0a",
+               NULL, 28, LWS_AUT_EXPECT_TX
+       }, {
+               "250 Ok",
+               NULL, 6, LWS_AUT_EXPECT_RX
+       }, {
+                               "DATA\x0a",
+               NULL, 5, LWS_AUT_EXPECT_TX
+       }, {
+               "354 End data with <CR><LF>.<CR><LF>\x0a",
+               NULL, 35, LWS_AUT_EXPECT_RX
+       }, {
+                               "From: noreply@example.com\n"
+                               "To: andy@warmcat.com\n"
+                               "Subject: Test email for lws smtp-client\n"
+                               "\n"
+                               "Hello this was an api test for lws smtp-client\n"
+                               "\r\n.\r\n",
+               NULL, 27 + 21 + 39 + 1 + 47 + 5, LWS_AUT_EXPECT_TX
+       }, {
+               "250 Ok: queued as 12345\x0a",
+               NULL, 23, LWS_AUT_EXPECT_RX
+       }, {
+                               "quit\x0a",
+               NULL, 5, LWS_AUT_EXPECT_TX
+       }, {
+               "221 Bye\x0a",
+               NULL, 7, LWS_AUT_EXPECT_RX |
+                  LWS_AUT_EXPECT_LOCAL_CLOSE |
+                  LWS_AUT_EXPECT_DO_REMOTE_CLOSE |
+                  LWS_AUT_EXPECT_TEST_END
+       }, {    /* sentinel */
+
+       }
+};
+
+
+static lws_unit_test_packet_t test_send2[] = {
+       {
+               "220 smtp.example.com ESMTP Postfix",
+               smtp_test_instance_init, 34, LWS_AUT_EXPECT_RX
+       }, {
+                               "HELO lws-test-client\x0a",
+               NULL, 21, LWS_AUT_EXPECT_TX
+       }, {
+               "250 smtp.example.com, I am glad to meet you",
+               NULL, 43, LWS_AUT_EXPECT_RX
+       }, {
+                               "MAIL FROM: <noreply@warmcat.com>\x0a",
+               NULL, 33, LWS_AUT_EXPECT_TX
+       }, {
+               "500 Service Unavailable",
+               NULL, 23, LWS_AUT_EXPECT_RX |
+                  LWS_AUT_EXPECT_DO_REMOTE_CLOSE |
+                  LWS_AUT_EXPECT_TEST_END
+       }, {    /* sentinel */
+
+       }
+};
+
+static lws_unit_test_t tests[] = {
+       { "sending", test_send1, 3 },
+       { "rejected", test_send2, 3 },
+       { }     /* sentinel */
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+/*
+ * set the HELO our SMTP client will use
+ */
+
+static const lws_token_map_t smtp_protocol_tokens[] = {
+ {
+       .u = { .value = "lws-test-client" },
+       .name_index = LTMI_PSMTP_V_HELO,
+ }, {  /* sentinel */
+ }
+};
+
+void
+tests_completion_cb(const void *cb_user)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_context_creation_info info;
+       lws_test_sequencer_args_t args;
+       struct lws_context *context;
+       struct lws_vhost *vh;
+       lws_abs_t abs, *instance;
+       const char *p;
+
+       /* the normal lws init */
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS API selftest: SMTP client unit tests\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       vh = lws_create_vhost(context, &info);
+       if (!vh) {
+               lwsl_err("Failed to create first vhost\n");
+               goto bail1;
+       }
+
+       /* create the smtp client */
+
+       memset(&abs, 0, sizeof(abs));
+       abs.vh = vh;
+
+       /* select the protocol and bind its tokens */
+
+       abs.ap = lws_abs_protocol_get_by_name("smtp");
+       if (!abs.ap)
+               goto bail1;
+
+       abs.ap_tokens = smtp_protocol_tokens;
+
+       /* select the transport and bind its tokens */
+
+       abs.at = lws_abs_transport_get_by_name("unit_test");
+       if (!abs.at)
+               goto bail1;
+
+       instance = lws_abs_bind_and_create_instance(&abs);
+       if (!instance)
+               goto bail1;
+
+       /* configure the test sequencer */
+
+       args.abs = &abs;
+       args.tests = tests;
+       args.results = results;
+       args.results_max = LWS_ARRAY_SIZE(results);
+       args.count_tests = &count_tests;
+       args.count_passes = &count_passes;
+       args.cb = tests_completion_cb;
+       args.cb_user = NULL;
+
+       if (lws_abs_unit_test_sequencer(&args)) {
+               lwsl_err("%s: failed to create test sequencer\n", __func__);
+               goto bail1;
+       }
+
+       /* the usual lws event loop */
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       /* describe the overall test results */
+
+       lwsl_user("%s: %d tests %d fail\n", __func__, count_tests,
+                       count_tests - count_passes);
+       for (n = 0; n < count_tests; n++)
+               lwsl_user("  test %d: %s\n", n,
+                         lws_unit_test_result_name(results[n]));
+
+bail1:
+       lwsl_user("Completed: %s\n",
+                 !count_tests || count_passes != count_tests ? "FAIL" : "PASS");
+
+       lws_context_destroy(context);
+
+       return !count_tests || count_passes != count_tests;
+}
diff --git a/minimal-examples/client-server/README.md b/minimal-examples/client-server/README.md
new file mode 100644 (file)
index 0000000..7339c61
--- /dev/null
@@ -0,0 +1,3 @@
+|name|demonstrates|
+---|---
+minimal-ws-proxy|Serves an index.html over http that connects back to the ws server, and maintains a ws client connection of its own at the same time to https://libwebsockets.org dumb-increment-protocol to feed a ringbuffer that is sent to all connected browsers.
diff --git a/minimal-examples/client-server/minimal-ws-proxy/CMakeLists.txt b/minimal-examples/client-server/minimal-ws-proxy/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a265496
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-proxy)
+set(SRCS minimal-ws-proxy.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/client-server/minimal-ws-proxy/README.md b/minimal-examples/client-server/minimal-ws-proxy/README.md
new file mode 100644 (file)
index 0000000..5c65500
--- /dev/null
@@ -0,0 +1,38 @@
+# lws minimal ws proxy
+
+## Build
+
+```
+ $ cmake . && make
+```
+
+## Description
+
+This is the same as minimal-ws-server-ring, but with the
+inclusion of a ws client connection to https://libwebsockets.org
+using the dumb-increment protocol feeding the ringbuffer.
+
+Each client that connect to this server receives the content that
+had arrived on the client connection feeding the ringbuffer proxied
+to their browser window over a ws connection.
+
+## Usage
+
+```
+ $ ./lws-minimal-ws-proxy 
+[2018/03/14 17:50:10:6938] USER: LWS minimal ws proxy | visit http://localhost:7681
+[2018/03/14 17:50:10:6955] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+[2018/03/14 17:50:10:6955] NOTICE:  Using non-SSL mode
+[2018/03/14 17:50:10:7035] NOTICE: created client ssl context for default
+[2018/03/14 17:50:11:7047] NOTICE: binding to lws-minimal-proxy
+[2018/03/14 17:50:11:7047] NOTICE: lws_client_connect_2: 0x872e60: address libwebsockets.org
+[2018/03/14 17:50:12:3282] NOTICE: lws_client_connect_2: 0x872e60: address libwebsockets.org
+[2018/03/14 17:50:13:8195] USER: callback_minimal: established
+```
+
+Visit http://localhost:7681 on multiple browser windows
+
+Data received on the remote wss connection is copied to all open browser windows.
+
+A ringbuffer holds up to 8 lines of text in the server, and the browser shows
+the last 20 lines of received text.
diff --git a/minimal-examples/client-server/minimal-ws-proxy/minimal-ws-proxy.c b/minimal-examples/client-server/minimal-ws-proxy/minimal-ws-proxy.c
new file mode 100644 (file)
index 0000000..4bb5a3e
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * lws-minimal-ws-proxy
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with an added websocket proxy distributing what is received on a
+ * dumb-increment wss connection to https://libwebsockets.org to all
+ * browsers connected to this server.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws proxy | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/client-server/minimal-ws-proxy/mount-origin/example.js b/minimal-examples/client-server/minimal-ws-proxy/mount-origin/example.js
new file mode 100644 (file)
index 0000000..a25a3cd
--- /dev/null
@@ -0,0 +1,70 @@
+var head = 0, tail = 0, ring = new Array();
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal-proxy");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("r").disabled = 0;
+               };
+       
+               ws.onmessage =function got_packet(msg) {
+                       var n, s = "";
+       
+                       ring[head] = msg.data + "\n";
+                       head = (head + 1) % 20;
+                       if (tail === head)
+                               tail = (tail + 1) % 20;
+       
+                       n = tail;
+                       do {
+                               s = s + ring[n];
+                               n = (n + 1) % 20;
+                       } while (n !== head);
+       
+                       document.getElementById("r").value = s; 
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("r").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+}, false);
diff --git a/minimal-examples/client-server/minimal-ws-proxy/mount-origin/index.html b/minimal-examples/client-server/minimal-ws-proxy/mount-origin/index.html
new file mode 100644 (file)
index 0000000..9df7cf8
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+       
+               <b>Minimal ws server proxy example</b>.<br>
+               The server makes a dumb-increment-protocol wss connection<br>
+               to libwebsockets.org.  It proxies what it was sent to<br>
+               all browsers open on this page.<br>
+               The textarea show the last 20 lines received.
+               <br>
+               <br>
+               <textarea id=r readonly cols=40 rows=20></textarea><br>
+       </body>
+</html>
diff --git a/minimal-examples/client-server/minimal-ws-proxy/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/client-server/minimal-ws-proxy/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/client-server/minimal-ws-proxy/mount-origin/strict-csp.svg b/minimal-examples/client-server/minimal-ws-proxy/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/client-server/minimal-ws-proxy/protocol_lws_minimal.c b/minimal-examples/client-server/minimal-ws-proxy/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..c51c304
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * ws protocol handler plugin for "lws-minimal"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This version uses an lws_ring ringbuffer to cache up to 8 messages at a time,
+ * so it's not so easy to lose messages.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       uint32_t tail;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+
+       struct lws_ring *ring; /* ringbuffer holding unsent messages */
+       struct lws_client_connect_info i;
+       struct lws *client_wsi;
+};
+
+/* destroys the message when everyone has had a copy of it */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static int
+connect_client(struct per_vhost_data__minimal *vhd)
+{
+       vhd->i.context = vhd->context;
+       vhd->i.port = 443;
+       vhd->i.address = "libwebsockets.org";
+       vhd->i.path = "/";
+       vhd->i.host = vhd->i.address;
+       vhd->i.origin = vhd->i.address;
+       vhd->i.ssl_connection = 1;
+
+       vhd->i.protocol = "dumb-increment-protocol";
+       vhd->i.local_protocol_name = "lws-minimal-proxy";
+       vhd->i.pwsi = &vhd->client_wsi;
+
+       return !lws_client_connect_via_info(&vhd->i);
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct msg *pmsg;
+       struct msg amsg;
+       int m;
+
+       switch (reason) {
+
+       /* --- protocol lifecycle callbacks --- */
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring)
+                       return 1;
+
+               if (connect_client(vhd))
+                       lws_timed_callback_vh_protocol(vhd->vhost,
+                                               vhd->protocol,
+                                               LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               lws_ring_destroy(vhd->ring);
+               break;
+
+       /* --- serving callbacks --- */
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* add ourselves to the list of live pss held in the vhd */
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               pss->tail = lws_ring_get_oldest_tail(vhd->ring);
+               pss->wsi = wsi;
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               /* remove our closing pss from the list of live pss */
+               lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
+                                 pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
+               if (!pmsg)
+                       break;
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
+                             pmsg->len, LWS_WRITE_TEXT);
+               if (m < (int)pmsg->len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lws_ring_consume_and_update_oldest_tail(
+                       vhd->ring,      /* lws_ring object */
+                       struct per_session_data__minimal, /* type of objects with tails */
+                       &pss->tail,     /* tail of guy doing the consuming */
+                       1,              /* number of payload objects being consumed */
+                       vhd->pss_list,  /* head of list of objects with tails */
+                       tail,           /* member name of tail in objects with tails */
+                       pss_list        /* member name of next object in objects with tails */
+               );
+
+               /* more to do? */
+               if (lws_ring_get_element(vhd->ring, &pss->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(pss->wsi);
+               break;
+
+       /* --- client callbacks --- */
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               vhd->client_wsi = NULL;
+               lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("%s: established\n", __func__);
+               break;
+
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+               /* if no clients, just drop incoming */
+               if (!vhd->pss_list)
+                       break;
+
+               if (!lws_ring_get_count_free_elements(vhd->ring)) {
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+
+               amsg.len = len;
+               /* notice we over-allocate by LWS_PRE */
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               memcpy((char *)amsg.payload + LWS_PRE, in, len);
+               if (!lws_ring_insert(vhd->ring, &amsg, 1)) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+
+               /*
+                * let everybody know we want to write something on them
+                * as soon as they are ready
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               vhd->client_wsi = NULL;
+               lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+       /* rate-limited client connect retries */
+
+       case LWS_CALLBACK_USER:
+               lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
+               if (connect_client(vhd))
+                       lws_timed_callback_vh_protocol(vhd->vhost,
+                                               vhd->protocol,
+                                               LWS_CALLBACK_USER, 1);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       {  \
+               "lws-minimal-proxy", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               128, \
+               0, NULL, 0 \
+       }
+
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/crypto/README.md b/minimal-examples/crypto/README.md
new file mode 100644 (file)
index 0000000..1c95c13
--- /dev/null
@@ -0,0 +1,7 @@
+|name|tests|
+---|---
+minimal-crypto-jwe|Examples for lws RFC7516 JWE apis
+minimal-crypto-jwk|Examples for lws RFC7517 JWK apis
+minimal-crypto-jws|Examples for lws RFC7515 JWS apis
+minimal-crypto-x509|Examples for lws X.509 apis
+
diff --git a/minimal-examples/crypto/minimal-crypto-jwe/CMakeLists.txt b/minimal-examples/crypto/minimal-crypto-jwe/CMakeLists.txt
new file mode 100644 (file)
index 0000000..05f7376
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-crypto-jwe)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_JOSE 1 requirements)
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/crypto/minimal-crypto-jwe/README.md b/minimal-examples/crypto/minimal-crypto-jwe/README.md
new file mode 100644 (file)
index 0000000..f7ddc7a
--- /dev/null
@@ -0,0 +1,70 @@
+# lws minimal example for JWE
+
+Demonstrates how to encrypt and decrypt using JWE and JWK, providing a
+commandline tool for creating encrypted JWE and decoding them.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Stdin is either the plaintext (if encrypting) or JWE (if decrypting).
+
+Stdout is either the JWE (if encrypting) or plaintext (if decrypting).
+
+You must pass a private or public key JWK file in the -k option if encrypting,
+and must pass a private key JWK file in the -k option if decrypting.  To be
+clear, for asymmetric keys the public part of the key is required to encrypt,
+and the private part required to decrypt.
+
+For convenience, a pair of public and private keys are provided,
+`key-rsa-4096.private` and `key-rsa-4096.pub`, these were produced with just
+
+```
+ $ lws-crypto-jwk -t RSA -b 4096 --public key-rsa-4096.pub >key-rsa-4096.private
+```
+
+Similar keys for EC modes may be produced with
+
+```
+ $ lws-crypto-jwk -t EC -v P-256 --public key-ecdh-p-256.pub >key-ecdh-p-256.private
+```
+
+and for AES ("octet") symmetric keys
+
+```
+ $ lws-crypto-jwk -t OCT -b 128 >key-aes-128.private
+```
+
+JWEs produced with openssl and mbedtls backends are completely interchangeable.
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-e "<cek cipher alg> <payload enc alg>"|Encrypt (default is decrypt), eg, -e "RSA1_5 A128CBC-HS256".  For decrypt, the cipher information comes from the input JWE.
+-k <jwk file>|JWK file to encrypt or decrypt with
+-c|Format the JWE as a linebroken C string
+-f|Output flattened representation (instead of compact by default)
+
+```
+ $ echo -n "plaintext0123456" | ./lws-crypto-jwe -k key-rsa-4096.private -e "RSA1_5 A128CBC-HS256"
+[2018/12/19 16:20:25:6519] USER: LWS JWE example tool
+[2018/12/19 16:20:25:6749] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+eyJhbGciOiJSU0ExXzUiLCAiZW5jIjoiQTEyOENCQy1IUzI1NiJ9.ivFr7qzx-pQ4V_edbjpdvR9OwWL9KmojPE2rXQM52oLtW0BtnxZu2_ezqhsAelyIcaworgfobs3u4bslXHMFbeJJjPb5xD0fBDe64OYXZH1NpUGTMJh9Ka4CrJ2B3xhxe7EByGAuGqmluqE0Yezj7rhSw7vlr5JAwuOJ8FaGa8aZ8ldki5G5h_S2Furlbjdcw3Rrxk7mCoMHcLoqzfZtggMPwGAMFogCqcwUo7oSLbBeGaa6hpMbfSysugseWdr8TzObQKPM52k6iVAlGwRaOg_qdLMgZiYRhHA6nFKTQd7XBbNY6qAS8sPuj7Zz344tF3RSfJ0zX_telG71sOtVv5fMpeDU-eCdpOWlCBfu6J6FQfAFu6SJryM4ajGOif09CwFI5qUQ33SOfQfS_M3nqSyd6Vu5M4lsDrb5wK7_XX5gqUwvI9wicf_8WWR-CQomRF-JvEASnA2SIf8QqYfa8R2rP9q6Md4vwO4EZrtxIsMDPsH-4ZEFu7vDjyy09QfIWWsnEb8-UgpVXensgt2m_2bZ76r1VB8-0nZLMwMyEhaH2wra9vX2FWao5UkmNJ7ht300f4_V6QzMFoePpwCvsufWBW6jcQLB-frCWe6uitWaZHEB4LxmNPKzQSz4QwwTKhpF1jNn8Xh1-w1m-2h0gj-oe-S8QBwPveqhPI1p2fI.snuhUTXHNu5mJ6dEPQqg6g.yl36qC4o0GE4nrquQ2YyCg.Vf0MoT7_kUrZdCNWXhq1DQ
+```
+
+Notice the logging is on stderr, and the output alone on stdout.
+
+You can also pipe the output of the encrypt action directly into the decrypt
+action, eg
+
+```
+ $ echo -n "plaintext0123456" | \
+   ./lws-crypto-jwe -k key-rsa-4096.pub -e "RSA1_5 A128CBC-HS256" | \
+   ./lws-crypto-jwe -k key-rsa-4096.private
+```
+
+prints the plaintext on stdout.
diff --git a/minimal-examples/crypto/minimal-crypto-jwe/key-rsa-4096.private b/minimal-examples/crypto/minimal-crypto-jwe/key-rsa-4096.private
new file mode 100644 (file)
index 0000000..1084143
--- /dev/null
@@ -0,0 +1 @@
+{"d":"XcSl3ulvs4OGomu9thRPVQGOstim0PY7CibP_bnCmzjvmGmzb8J4q5AUmJCnZT5TesOzXuXhyG95CxQWsakd9GWHSAinV1QQSLsahaezPULRG1qmo37JqKb9noKkvXguh5XU5np8HjeoeeEkF_XqtCdEo0wHijEjTL9RZar98jmyAmlizoHIY9NnECavs4DZB27onU61B61vGpw-y4xhC9jlZSIwRqIMDzeTcSv8fRKcVYR80ozm2_KwWMpue27rS2EfTQUtsMXuYmnvMAf_DHqA0tNWyD1gpUWYHvlyBh5xnYrWPuXxQBRNesImQdRQl5VMMsuvdtY-uZfIVUdN5CcsB0acronx4UsmVg-Qz-jd1NVW4koZQM9uA4oWiMZg4FEUTQ-UWelHCldg-PYLAazsItmaHPF9LcAPkLkI8jaVS33v-DhSeXHW3Pg3sibtnPhouiSvD84zMtzu1gjFT7vtapMynBeZouqeWYT-BFeu2wzppJcW1YxTQ_Ai80VJSFY__Huw-9r1MOHmDRcEW7x9W97UezWDjrh5Shhh4C6SMYbaf7ouACzFu1i_r8Q06JqKA7aY8i5izKlKA0We9tQKlTF8Fgsneu9gpxFglvZsd1ersiA-MkuP9qTBQpyAf3kJ6HS9GrQUju6r3DExdWDjdvM5Grt8QD7Zkv-qXeE","dp":"M-LFs3T2GI1JxD5LJt2GgV4cMDKbiPKBddLukfG0duUxNp0-6x2LZ0ptxrlHrhxBMMmvCg4GEaujrZdaYWCar6xCnlnkVlOELz4yZ3JBSpS86thJw03xuE7lyeR7usFY4CpSqUQGI_YveITuFeoh4YjwdKDuqPhOpDI-34ptgU93dlBRS9nnQFTiVoUdP4bhGTKOpULTiLgPXHQxQR5rfiGVD9AIwqHvMdBQ0hxQBKEt37PbRWK_eTzMslHZGWNfbg8ipwJxisvHyUn0c1X3Uelw8BRyvNVCNovNDeCj-R7kFkMvriMd_sqGVy1Go46WZ2wMkUJHkvmYk0gDlhnTGQ","dq":"qO89nQEJfdkaDtGGyD-sQE2Mm8p_PIPSpCmgMfpl8zgSOb4P9iqXBgpHyS7w10uY_UHt8KW6pY7ozy0y4Gu_f4Wk_rcXiYYdbuIhlFl0_nLI2mfFPGxr1xC64zfjjEaBr4zIJr_YzhvTpjZFtIdSAH5VG5Tv-2yUtCC2DnKnU2kzEkgUeSI6LHOEVhXqup7C0Kjiv9FJsLR0hiqwH4oLziqH7EVqVDvJI3yL1lhqoLKjAu1ogTDgH7hzSrqVhttnpwL8rDcgbtY6Q8C2csdN3Jt1ucgtGy-Yzgqf_QIULP3CRlqzDTvHrMe2A9cNAQ4dNsCbNAjW_MxxGKKWuWXAMQ","e":"AQAB","kid":"my kid","kty":"RSA","n":"2_YjG_D1sOWJxs6cohikupHf5WJfWSFfSCrnNZ7WR7AyTLnKZAF4VKyimMeJTLYYwCAXMDD5XmkF8VluI4O-hASUIJ7F9eDg3vO7nPwtkWa9lkqt-QyQZ_PjiOGpwetBLzrsaXsC9PvdVzrKXnjeNPsmmbC_Fx2cUn4H_9H_WfXi01VR75XFTBtxTrDY7hmpZHuFCFUOMCW9siTZRk9339Y6ORBznBs4jFbkGI1Pmc3op0o5f8S1gus9L81W5uyUrxfd-CkmJ6eWE8I36cfzI6irN2bhVhR_NXERUtS0QOEeJYlRJXqfYkxTMVlsXPl6zbYt__ZYLC6ZiUTCc6K2KmfGh7fihWbao4dyQW3Mq4kClhpIT0O01Y0r7sR1j4jTnFNqbmtPSl9lEMrfiUHfOLqRJo3qizQ-b6HLCDty1otFz8Q8gg0rD3copQ_zFrcTGwJGAv2Absraj7kp9EJXBqneCJ3dlRO8rzx7KB9Dsj-ygh3kZaubkPCeT1v4l_VUY2iGnK4vzIGKM7j56DQ97ZAi1Bb0y6GYSbrWB2_z0DKJu0fiU-NscbKplR68vgppUM6_iogrk48JEZg_kkTymniqbT3g7J_WeoZSx1Uu8ZHI3ysIFfUtFscOa2SJGlj1ds-lfk6Oqac_I8ahRqQeyVAEisZPmYIGSJajbJopJ4s","p":"_V4CwEjRd8Hv9-ncqGdB_vtReTIuHSWQzSx4al15J3VxvPFI2kxicNeQKyq3OAVT2kmCmUP3ETgCdwuKIgw_QbEc8qNxtS_KpM_KsuTe9a5jrQKpt8ctYhzELZfr_sy9UzUGJzr8glLjJ1IDX4x6_JAqYB_NhttP6bzgu5Dt-DKtRPNO1qZtfhrLIgmltpC2M6-AlAv-dyHSHck2VJIL84Hwk4FulozEYxop0dKuZdfM5Z1dZM8-ICo62O0zUKzoWxKmQcB9_gDZsxYaO6xZ9BLmaW6-WcPSEI6YDnPk8ptnk_Kbyc4kPW4Z3ASczxjaewBmfl2_lwkqkndFVptAeQ","q":"3j9DR6ZpKC3WrshSrxXFYAuT19Rlf6qQ_9uD_Fq7dIpTjCZdl01695Qx7UmujKoetutL3RMCpeRdZR-gCLVh8aMxpMuIc5fHC6HbhsdF-I7GoqO0DEJ6coS3n5Ey4EXL5uoLh4C3l67wBKfLmPW28bxxG2QAP59jncWXkrBQm_qbS5Qon8r7wj0tejG_tGdsPjhsFc9KdnkkBucT6MiEVpzpdwDlsn7bHpMsyPlNyc0fj5qYmRB-DN7rv5varaisBaVT0mLQdwKjBDVqNVnU2m5azPhY-2txvihHaI5_cLIsLLaqKMbB17UxGumuT_o8S03_h8-1syO3Ay87y9pPIw","qi":"JY2uUek6wPrp4fPcInX_5WdNlhyghcGVEvlqxs9iOEUeCtUc6d42n9tgiImMu605dQaigvNaH5y1pwDpLlmxUk0nOUVxqo9mv0Uw8WNXB88FyDb0fPbewLpn4Fskb8Umv6_OymJ1W814DRG-jq3sI5DsB7AjtqJQ22nP2Vs1bIrx5fUxuScwrMsWSrrjAx4Kr8-5eeSDqE-_c7DPZ_zSPYDoHaMeR2pZfNAq3mEbxp8jMukzh77rYZ3ffQEA6AyxFSCSCrxVozhP4ypQ0jAkXVWOlj4nuV6briIqlL3ZboydwsIolRwaPSgH6-bw03XS6Hb9DA0KHJKLun94N9n5kw","use":"enc"}
diff --git a/minimal-examples/crypto/minimal-crypto-jwe/key-rsa-4096.pub b/minimal-examples/crypto/minimal-crypto-jwe/key-rsa-4096.pub
new file mode 100644 (file)
index 0000000..e2bd85c
--- /dev/null
@@ -0,0 +1 @@
+{"e":"AQAB","kid":"my kid","kty":"RSA","n":"2_YjG_D1sOWJxs6cohikupHf5WJfWSFfSCrnNZ7WR7AyTLnKZAF4VKyimMeJTLYYwCAXMDD5XmkF8VluI4O-hASUIJ7F9eDg3vO7nPwtkWa9lkqt-QyQZ_PjiOGpwetBLzrsaXsC9PvdVzrKXnjeNPsmmbC_Fx2cUn4H_9H_WfXi01VR75XFTBtxTrDY7hmpZHuFCFUOMCW9siTZRk9339Y6ORBznBs4jFbkGI1Pmc3op0o5f8S1gus9L81W5uyUrxfd-CkmJ6eWE8I36cfzI6irN2bhVhR_NXERUtS0QOEeJYlRJXqfYkxTMVlsXPl6zbYt__ZYLC6ZiUTCc6K2KmfGh7fihWbao4dyQW3Mq4kClhpIT0O01Y0r7sR1j4jTnFNqbmtPSl9lEMrfiUHfOLqRJo3qizQ-b6HLCDty1otFz8Q8gg0rD3copQ_zFrcTGwJGAv2Absraj7kp9EJXBqneCJ3dlRO8rzx7KB9Dsj-ygh3kZaubkPCeT1v4l_VUY2iGnK4vzIGKM7j56DQ97ZAi1Bb0y6GYSbrWB2_z0DKJu0fiU-NscbKplR68vgppUM6_iogrk48JEZg_kkTymniqbT3g7J_WeoZSx1Uu8ZHI3ysIFfUtFscOa2SJGlj1ds-lfk6Oqac_I8ahRqQeyVAEisZPmYIGSJajbJopJ4s"}
diff --git a/minimal-examples/crypto/minimal-crypto-jwe/main.c b/minimal-examples/crypto/minimal-crypto-jwe/main.c
new file mode 100644 (file)
index 0000000..9bd2676
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * lws-crypto-jwe
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+/*
+ * handles escapes and line wrapping suitable for use
+ * defining a C char array ( -c option )
+ */
+
+static void
+format_c(const char *key)
+{
+       const char *k = key;
+       int seq = 0;
+
+       while (*k) {
+               if (*k == '{') {
+                       putchar('\"');
+                       putchar('{');
+                       putchar('\"');
+                       putchar('\n');
+                       putchar('\t');
+                       putchar('\"');
+                       k++;
+                       seq = 0;
+                       continue;
+               }
+               if (*k == '}') {
+                       putchar('\"');
+                       putchar('\n');
+                       putchar('\"');
+                       putchar('}');
+                       putchar('\"');
+                       putchar('\n');
+                       k++;
+                       seq = 0;
+                       continue;
+               }
+               if (*k == '\"') {
+                       putchar('\\');
+                       putchar('\"');
+                       seq += 2;
+                       k++;
+                       continue;
+               }
+               if (*k == ',') {
+                       putchar(',');
+                       putchar('\"');
+                       putchar('\n');
+                       putchar('\t');
+                       putchar('\"');
+                       k++;
+                       seq = 0;
+                       continue;
+               }
+               putchar(*k);
+               seq++;
+               if (seq >= 60) {
+                       putchar('\"');
+                       putchar('\n');
+                       putchar('\t');
+                       putchar(' ');
+                       putchar('\"');
+                       seq = 1;
+               }
+               k++;
+       }
+}
+
+#define MAX_SIZE (4 * 1024 * 1024)
+       char temp[MAX_SIZE], compact[MAX_SIZE];
+
+int main(int argc, const char **argv)
+{
+       int n, enc = 0, result = 0,
+           logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       char *in;
+       struct lws_context_creation_info info;
+       int temp_len = sizeof(temp);
+       struct lws_context *context;
+       struct lws_jwe jwe;
+       const char *p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS JWE example tool\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = 0;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       lws_jwe_init(&jwe, context);
+
+       /* if encrypting, set the ciphers */
+
+       if ((p = lws_cmdline_option(argc, argv, "-e"))) {
+               char *sp = strchr(p, ' ');
+
+               if (!sp) {
+                       lwsl_err("format: -e \"<cek cipher alg> "
+                                "<payload enc alg>\", eg, "
+                                "-e \"RSA1_5 A128CBC-HS256\"\n");
+
+                       return 1;
+               }
+               *sp = '\0';
+               if (lws_gencrypto_jwe_alg_to_definition(p, &jwe.jose.alg)) {
+                       lwsl_err("Unknown cipher alg %s\n", p);
+                       return 1;
+               }
+               if (lws_gencrypto_jwe_enc_to_definition(sp + 1, &jwe.jose.enc_alg)) {
+                       lwsl_err("Unknown payload enc alg %s\n", sp + 1);
+                       return 1;
+               }
+
+               /* create JOSE header, also needed for output */
+
+               if (lws_jws_alloc_element(&jwe.jws.map, LJWS_JOSE,
+                                         lws_concat_temp(temp, temp_len),
+                                         &temp_len, strlen(p) +
+                                         strlen(sp + 1) + 32, 0)) {
+                       lwsl_err("%s: temp space too small\n", __func__);
+                       return 1;
+               }
+
+               jwe.jws.map.len[LJWS_JOSE] = lws_snprintf(
+                               (char *)jwe.jws.map.buf[LJWS_JOSE], temp_len,
+                               "{\"alg\":\"%s\",\"enc\":\"%s\"}", p, sp + 1);
+
+               enc = 1;
+       }
+
+       in = lws_concat_temp(temp, temp_len);
+       n = read(0, in, temp_len);
+       if (n < 0) {
+               lwsl_err("Problem reading from stdin\n");
+               return 1;
+       }
+       temp_len -= n;
+
+       /* grab the key */
+
+       if ((p = lws_cmdline_option(argc, argv, "-k"))) {
+               if (lws_jwk_load(&jwe.jwk, p, NULL, NULL)) {
+                       lwsl_err("%s: problem loading JWK %s\n", __func__, p);
+
+                       return 1;
+               }
+       } else {
+               lwsl_err("-k <jwk file> is required\n");
+
+               return 1;
+       }
+
+       if (enc) {
+
+               /* point CTXT to the plaintext we read from stdin */
+
+               jwe.jws.map.buf[LJWE_CTXT] = in;
+               jwe.jws.map.len[LJWE_CTXT] = n;
+
+               /*
+                * Create a random CEK and set EKEY to it
+                * CEK size is determined by hash / hmac size
+                */
+
+               n = lws_gencrypto_bits_to_bytes(jwe.jose.enc_alg->keybits_fixed);
+               if (lws_jws_randomize_element(context, &jwe.jws.map, LJWE_EKEY,
+                                             lws_concat_temp(temp, temp_len),
+                                             &temp_len, n,
+                                             LWS_JWE_LIMIT_KEY_ELEMENT_BYTES)) {
+                       lwsl_err("Problem getting random\n");
+                       goto bail1;
+               }
+
+               /* perform the encryption of the CEK and the plaintext */
+
+               n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len),
+                                   &temp_len);
+               if (n < 0) {
+                       lwsl_err("%s: lws_jwe_encrypt failed\n", __func__);
+                       goto bail1;
+               }
+               if (lws_cmdline_option(argc, argv, "-f"))
+                       /* output the JWE in flattened form */
+                       n = lws_jwe_render_flattened(&jwe, compact,
+                                                    sizeof(compact));
+               else
+                       /* output the JWE in compact form */
+                       n = lws_jwe_render_compact(&jwe, compact,
+                                                  sizeof(compact));
+
+               if (n < 0) {
+                       lwsl_err("%s: lws_jwe_render failed: %d\n",
+                                __func__, n);
+                       goto bail1;
+               }
+
+               if (lws_cmdline_option(argc, argv, "-c"))
+                       format_c(compact);
+               else
+                       if (write(1, compact, strlen(compact)) < 0) {
+                               lwsl_err("Write stdout failed\n");
+                               goto bail1;
+                       }
+       } else {
+               if (lws_cmdline_option(argc, argv, "-f")) {
+                       if (lws_jwe_json_parse(&jwe, (uint8_t *)in, n,
+                                              lws_concat_temp(temp, temp_len),
+                                              &temp_len)) {
+                               lwsl_err("%s: lws_jwe_json_parse failed\n",
+                                                                __func__);
+                               goto bail1;
+                       }
+               } else
+                       /*
+                        * converts a compact serialization to b64 + decoded maps
+                        * held in jws
+                        */
+                       if (lws_jws_compact_decode(in, n, &jwe.jws.map,
+                                                  &jwe.jws.map_b64,
+                                                  lws_concat_temp(temp, temp_len),
+                                                  &temp_len) != 5) {
+                               lwsl_err("%s: lws_jws_compact_decode failed\n",
+                                        __func__);
+                               goto bail1;
+                       }
+
+               /*
+                * Do the crypto according to what we parsed into the jose
+                * (information on the ciphers) and the jws (plaintext and
+                * signature info)
+                */
+
+               n = lws_jwe_auth_and_decrypt(&jwe,
+                                            lws_concat_temp(temp, temp_len),
+                                            &temp_len);
+               if (n < 0) {
+                       lwsl_err("%s: lws_jwe_auth_and_decrypt failed\n",
+                                __func__);
+                       goto bail1;
+               }
+
+               /* if it's valid, dump the plaintext and return 0 */
+
+               if (write(1, jwe.jws.map.buf[LJWE_CTXT],
+                            jwe.jws.map.len[LJWE_CTXT]) < 0) {
+                       lwsl_err("Write stdout failed\n");
+                       goto bail1;
+               }
+       }
+
+       result = 0;
+
+bail1:
+
+       lws_jwe_destroy(&jwe);
+
+       lws_context_destroy(context);
+
+       return result;
+}
diff --git a/minimal-examples/crypto/minimal-crypto-jwk/CMakeLists.txt b/minimal-examples/crypto/minimal-crypto-jwk/CMakeLists.txt
new file mode 100644 (file)
index 0000000..fb8c3e3
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-crypto-jwk)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_JOSE 1 requirements)
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/crypto/minimal-crypto-jwk/README.md b/minimal-examples/crypto/minimal-crypto-jwk/README.md
new file mode 100644 (file)
index 0000000..eea687d
--- /dev/null
@@ -0,0 +1,52 @@
+# lws minimal example for JWK
+
+Demonstrates how to generate and format any kind of supported new random JWK keys.
+
+The full private key is output to stdout, a version of the key with the private
+part removed and some metadata adapted can be saved to a file at the same time
+using `--public <file>`.  In the public form, `key_ops` and `use` elements are
+adjusted to remove activities that require a private key.
+
+Key elements are output in strict RFC7638 lexicographic order as required by
+some applications.
+
+Keys produced with openssl and mbedtls backends are completely interchangeable.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-t <type>|RSA, OCT or EC
+-b <bits>|For RSA and OCT, key size in bits
+-v <curve>|For EC keys, the curve, eg, "P-384"... this implies the key bits
+--kid "ID string"|Key identity string
+--use "use[ use]"|Key use restriction (mutually exclusive with --key-ops): sig, enc
+--alg <alg>|Specify the algorithm the key is designed for, eg "RSA1_5"
+--key-ops "op[ op]"|Key valid operations (mutually exclusive with --use): sign, verify, encrypt, decrypt, wrapKey, unwrapKey, deriveKey, deriveBits
+-c|Format the jwk as a linebroken C string
+--public <filepath>|Only output the full, private key, not the public version first
+
+For legibility the example uses -c, however this
+
+```
+ $ ./lws-crypto-jwk -t EC -v P-256 --key-ops "sign verify" --public mykey.pub
+[2018/12/18 20:19:29:6972] USER: LWS JWK example
+[2018/12/18 20:19:29:7200] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2018/12/18 20:19:29:7251] NOTICE: lws_jwk_generate: generating ECDSA key on curve P-256
+{"crv":"P-256","d":"eMKM_S4BTL2aiebZLqvxglufV2YX4b3_32DesgEUOaM","key_ops":["sign","verify"],"kty":"EC","x":"OWauiGGtJ60ZegtqlwETQlmO1exTZdWbT2VbUs4a1hg","y":"g_eNOlqPecbguVQArL6Fd4T5xZthBgipNCBypXubPos"}
+```
+
+The output in `mykey.pub` is:
+
+```
+{"crv":"P-256","key_ops":["verify"],"kty":"EC","x":"OWauiGGtJ60ZegtqlwETQlmO1exTZdWbT2VbUs4a1hg","y":"g_eNOlqPecbguVQArL6Fd4T5xZthBgipNCBypXubPos"}
+```
+
+Notice the logging goes out on stderr, the key data goes on stdout.
diff --git a/minimal-examples/crypto/minimal-crypto-jwk/main.c b/minimal-examples/crypto/minimal-crypto-jwk/main.c
new file mode 100644 (file)
index 0000000..f962136
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * lws-crypto-jwk
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+/*
+ * handles escapes and line wrapping suitable for use
+ * defining a C char array ( -c option )
+ */
+
+static int
+format_c(int fd, const char *key)
+{
+       const char *k = key;
+       int seq = 0;
+
+       while (*k) {
+               if (*k == '{') {
+                       if (write(fd, "\"{\"\n\t\"", 6) < 6)
+                               return -1;
+                       k++;
+                       seq = 0;
+                       continue;
+               }
+               if (*k == '}') {
+                       if (write(fd, "\"\n\"}\"\n", 6) < 6)
+                               return -1;
+                       k++;
+                       seq = 0;
+                       continue;
+               }
+               if (*k == '\"') {
+                       if (write(fd, "\\\"", 2) < 2)
+                               return -1;
+                       seq += 2;
+                       k++;
+                       continue;
+               }
+               if (*k == ',') {
+                       if (write(fd, ",\"\n\t\"", 5) < 5)
+                               return -1;
+                       k++;
+                       seq = 0;
+                       continue;
+               }
+               if (write(fd, k, 1) < 1)
+                       return -1;
+               seq++;
+               if (seq >= 60) {
+                       if (write(fd, "\"\n\t \"", 5) < 5)
+                               return -1;
+                       seq = 1;
+               }
+               k++;
+       }
+
+       return 0;
+}
+
+int main(int argc, const char **argv)
+{
+       int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       enum lws_gencrypto_kty kty = LWS_GENCRYPTO_KTY_RSA;
+       struct lws_context_creation_info info;
+       const char *curve = "P-256", *p;
+       struct lws_context *context;
+       struct lws_jwk jwk;
+       int bits = 4096;
+       char key[32768];
+       int vl = sizeof(key);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS JWK example\n");
+
+       if ((p = lws_cmdline_option(argc, argv, "-b")))
+               bits = atoi(p);
+
+       if ((p = lws_cmdline_option(argc, argv, "-t"))) {
+               if (!strcmp(p, "RSA"))
+                       kty = LWS_GENCRYPTO_KTY_RSA;
+               else
+                       if (!strcmp(p, "OCT"))
+                               kty = LWS_GENCRYPTO_KTY_OCT;
+                       else
+                               if (!strcmp(p, "EC"))
+                                       kty = LWS_GENCRYPTO_KTY_EC;
+                               else {
+                                       lwsl_err("Unknown key type (must be "
+                                                "OCT, RSA or EC)\n");
+
+                                       return 1;
+                               }
+       }
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = 0;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       if ((p = lws_cmdline_option(argc, argv, "-v")))
+               curve = p;
+
+       if (lws_jwk_generate(context, &jwk, kty, bits, curve)) {
+               lwsl_err("lws_jwk_generate failed\n");
+
+               return 1;
+       }
+
+       if ((p = lws_cmdline_option(argc, argv, "--kid")))
+               lws_jwk_strdup_meta(&jwk, JWK_META_KID, p, strlen(p));
+
+       if ((p = lws_cmdline_option(argc, argv, "--use")))
+               lws_jwk_strdup_meta(&jwk, JWK_META_USE, p, strlen(p));
+
+       if ((p = lws_cmdline_option(argc, argv, "--alg")))
+               lws_jwk_strdup_meta(&jwk, JWK_META_ALG, p, strlen(p));
+
+       if ((p = lws_cmdline_option(argc, argv, "--key-ops")))
+               lws_jwk_strdup_meta(&jwk, JWK_META_KEY_OPS, p, strlen(p));
+
+       if ((p = lws_cmdline_option(argc, argv, "--public")) &&
+           kty != LWS_GENCRYPTO_KTY_OCT) {
+
+               int fd;
+
+               /* public version */
+
+               if (lws_jwk_export(&jwk, 0, key, &vl) < 0) {
+                       lwsl_err("lws_jwk_export failed\n");
+
+                       return 1;
+               }
+
+               fd = open(p, LWS_O_CREAT | LWS_O_TRUNC | LWS_O_WRONLY, 0600);
+               if (fd < 0) {
+                       lwsl_err("Can't open public key file %s\n", p);
+                       return 1;
+               }
+
+               if (lws_cmdline_option(argc, argv, "-c"))
+                       format_c(fd, key);
+               else {
+                       if (write(fd, key, strlen(key)) < 0) {
+                               lwsl_err("Write public failed\n");
+                               return 1;
+                       }
+               }
+
+               close(fd);
+       }
+
+       /* private version */
+
+       if (lws_jwk_export(&jwk, 1, key, &vl) < 0) {
+               lwsl_err("lws_jwk_export failed\n");
+
+               return 1;
+       }
+
+       if (lws_cmdline_option(argc, argv, "-c")) {
+               if (format_c(1, key) < 0)
+                       return 1;
+       } else
+               if (write(1, key, strlen(key)) < 0) {
+                       lwsl_err("Write stdout failed\n");
+                       return 1;
+               }
+
+       lws_jwk_destroy(&jwk);
+
+       lws_context_destroy(context);
+
+       return result;
+}
diff --git a/minimal-examples/crypto/minimal-crypto-jws/CMakeLists.txt b/minimal-examples/crypto/minimal-crypto-jws/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ddf9579
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-crypto-jws)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_JOSE 1 requirements)
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/crypto/minimal-crypto-jws/README.md b/minimal-examples/crypto/minimal-crypto-jws/README.md
new file mode 100644 (file)
index 0000000..97cbf00
--- /dev/null
@@ -0,0 +1,60 @@
+# lws minimal example for JWS
+
+Demonstrates how to sign and verify using compact JWS and JWK, providing a
+commandline tool for signing and verifying stdin.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Stdin is either the plaintext (if signing) or compact JWS (if verifying).
+
+Stdout is either the JWE (if encrypting) or plaintext (if decrypting).
+
+You must pass a private or public key JWK file in the -k option if encrypting,
+and must pass a private key JWK file in the -k option if decrypting.  To be
+clear, for asymmetric keys the public part of the key is required to encrypt,
+and the private part required to decrypt.
+
+For convenience, a pair of public and private keys are provided,
+`key-rsa-4096.private` and `key-rsa-4096.pub`, these were produced with just
+
+```
+ $ lws-crypto-jwk -t RSA -b 4096 --public key-rsa-4096.pub >key-rsa-4096.private
+```
+
+Similar keys for EC modes may be produced with
+
+```
+ $ lws-crypto-jwk -t EC -v P-256 --public key-ecdh-p-256.pub >key-ecdh-p-256.private
+```
+
+JWSs produced with openssl and mbedtls backends are completely interchangeable.
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-s "<signature alg>"|Sign (default is verify), eg, -e "ES256".  For verify, the cipher information comes from the input JWS.
+-k <jwk file>|JWK file to sign or verify with... sign requires the key has its private part
+-c|Format the JWE as a linebroken C string
+-f|Output flattened representation (instead of compact by default)
+
+```
+ $ echo -n "plaintext0123456" | ./lws-crypto-jws -s "ES256" -k ec-p256.private
+[2018/12/19 16:20:25:6519] USER: LWS JWE example tool
+[2018/12/19 16:20:25:6749] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+eyJhbGciOiJSU0ExXzUiLCAiZW5jIjoiQTEyOENCQy1IUzI1NiJ9.ivFr7qzx-pQ4V_edbjpdvR9OwWL9KmojPE2rXQM52oLtW0BtnxZu2_ezqhsAelyIcaworgfobs3u4bslXHMFbeJJjPb5xD0fBDe64OYXZH1NpUGTMJh9Ka4CrJ2B3xhxe7EByGAuGqmluqE0Yezj7rhSw7vlr5JAwuOJ8FaGa8aZ8ldki5G5h_S2Furlbjdcw3Rrxk7mCoMHcLoqzfZtggMPwGAMFogCqcwUo7oSLbBeGaa6hpMbfSysugseWdr8TzObQKPM52k6iVAlGwRaOg_qdLMgZiYRhHA6nFKTQd7XBbNY6qAS8sPuj7Zz344tF3RSfJ0zX_telG71sOtVv5fMpeDU-eCdpOWlCBfu6J6FQfAFu6SJryM4ajGOif09CwFI5qUQ33SOfQfS_M3nqSyd6Vu5M4lsDrb5wK7_XX5gqUwvI9wicf_8WWR-CQomRF-JvEASnA2SIf8QqYfa8R2rP9q6Md4vwO4EZrtxIsMDPsH-4ZEFu7vDjyy09QfIWWsnEb8-UgpVXensgt2m_2bZ76r1VB8-0nZLMwMyEhaH2wra9vX2FWao5UkmNJ7ht300f4_V6QzMFoePpwCvsufWBW6jcQLB-frCWe6uitWaZHEB4LxmNPKzQSz4QwwTKhpF1jNn8Xh1-w1m-2h0gj-oe-S8QBwPveqhPI1p2fI.snuhUTXHNu5mJ6dEPQqg6g.yl36qC4o0GE4nrquQ2YyCg.Vf0MoT7_kUrZdCNWXhq1DQ
+```
+
+Notice the logging is on stderr, and the output alone on stdout.
+
+When signing, the compact representation of the JWS is output on stdout.
+
+When verifying, if the signature is valid the plaintext is output on stdout
+and the tool exits with a 0 exit code.  Otherwise nothing is output on stdout
+and it exits with a nonzero exit code.
+
diff --git a/minimal-examples/crypto/minimal-crypto-jws/main.c b/minimal-examples/crypto/minimal-crypto-jws/main.c
new file mode 100644 (file)
index 0000000..5879fa9
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * lws-crypto-jws
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#define MAX_SIZE (4 * 1024 * 1024)
+char temp[MAX_SIZE], compact[MAX_SIZE];
+
+int main(int argc, const char **argv)
+{
+       int n, sign = 0, result = 0,
+           logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       char *in;
+       struct lws_context_creation_info info;
+       struct lws_jws_map map;
+       int temp_len = sizeof(temp);
+       struct lws_context *context;
+       struct lws_jose jose;
+       struct lws_jwk jwk;
+       struct lws_jws jws;
+       const char *p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS JWS example tool\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = 0;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       lws_jose_init(&jose);
+       lws_jws_init(&jws, &jwk, context);
+
+       /* if signing, set the ciphers */
+
+       if ((p = lws_cmdline_option(argc, argv, "-s"))) {
+
+               if (lws_gencrypto_jws_alg_to_definition(p, &jose.alg)) {
+                       lwsl_err("format: -s \"<jws cipher alg>\", eg, "
+                                "-e \"RS256\"\n");
+
+                       return 1;
+               }
+
+               /* create JOSE header, also needed for output */
+
+               if (lws_jws_alloc_element(&jws.map, LJWS_JOSE,
+                                     lws_concat_temp(temp, temp_len),
+                                     &temp_len, strlen(p) + 10, 0)) {
+                       lwsl_err("%s: temp space too small\n", __func__);
+                       return 1;
+               }
+
+               jws.map.len[LJWS_JOSE] =
+                               lws_snprintf((char *)jws.map.buf[LJWS_JOSE],
+                                            temp_len, "{\"alg\":\"%s\"}", p);
+               sign = 1;
+       }
+
+       in = lws_concat_temp(temp, temp_len);
+       n = read(0, in, temp_len);
+       if (n < 0) {
+               lwsl_err("Problem reading from stdin\n");
+               return 1;
+       }
+       temp_len -= n;
+
+       /* grab the key */
+
+       if ((p = lws_cmdline_option(argc, argv, "-k"))) {
+               if (lws_jwk_load(&jwk, p, NULL, NULL)) {
+                       lwsl_err("%s: problem loading JWK %s\n", __func__, p);
+
+                       return 1;
+               }
+       } else {
+               lwsl_err("-k <jwk file> is required\n");
+
+               return 1;
+       }
+       if (sign) {
+
+               /* add the plaintext from stdin to the map and a b64 version */
+
+               jws.map.buf[LJWS_PYLD] = in;
+               jws.map.len[LJWS_PYLD] = n;
+
+               if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_PYLD,
+                                              lws_concat_temp(temp, temp_len),
+                                              &temp_len, jws.map.buf[LJWS_PYLD],
+                                              jws.map.len[LJWS_PYLD]))
+                       goto bail1;
+
+               /* add the b64 JOSE header to the b64 map */
+
+               if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE,
+                                              lws_concat_temp(temp, temp_len),
+                                              &temp_len, jws.map.buf[LJWS_JOSE],
+                                              jws.map.len[LJWS_JOSE]))
+                       goto bail1;
+
+               /* prepare the space for the b64 signature in the map */
+
+               if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG,
+                                     lws_concat_temp(temp, temp_len),
+                                     &temp_len, lws_base64_size(
+                                        LWS_JWE_LIMIT_KEY_ELEMENT_BYTES), 0)) {
+                       lwsl_err("%s: temp space too small\n", __func__);
+                       goto bail1;
+               }
+
+       
+
+               /* sign the plaintext */
+
+               n = lws_jws_sign_from_b64(&jose, &jws,
+                                         (char *)jws.map_b64.buf[LJWS_SIG],
+                                         jws.map_b64.len[LJWS_SIG]);
+               if (n < 0) {
+                       lwsl_err("%s: failed signing test packet\n", __func__);
+                       goto bail1;
+               }
+               /* set the actual b64 signature size */
+               jws.map_b64.len[LJWS_SIG] = n;
+
+               if (lws_cmdline_option(argc, argv, "-f"))
+                       /* create the flattened representation */
+                       n = lws_jws_write_flattened_json(&jws, compact, sizeof(compact));
+               else
+                       /* create the compact JWS representation */
+                       n = lws_jws_write_compact(&jws, compact, sizeof(compact));
+               if (n < 0) {
+                       lwsl_notice("%s: write_compact failed\n", __func__);
+                       goto bail1;
+               }
+
+               /* dump the compact JWS representation on stdout */
+
+               if (write(1, compact, strlen(compact))  < 0) {
+                       lwsl_err("Write stdout failed\n");
+                       goto bail1;
+               }
+
+       } else {
+               /* perform the verify directly on the compact representation */
+
+               if (lws_cmdline_option(argc, argv, "-f")) {
+                       if (lws_jws_sig_confirm_json(in, n, &jws, &jwk, context,
+                                       lws_concat_temp(temp, temp_len),
+                                       &temp_len) < 0) {
+                               lwsl_notice("%s: confirm rsa sig failed\n",
+                                           __func__);
+                               lwsl_hexdump_notice(jws.map.buf[LJWS_JOSE], jws.map.len[LJWS_JOSE]);
+                               lwsl_hexdump_notice(jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD]);
+                               lwsl_hexdump_notice(jws.map.buf[LJWS_SIG], jws.map.len[LJWS_SIG]);
+
+                               lwsl_hexdump_notice(jws.map_b64.buf[LJWS_JOSE], jws.map_b64.len[LJWS_JOSE]);
+                               lwsl_hexdump_notice(jws.map_b64.buf[LJWS_PYLD], jws.map_b64.len[LJWS_PYLD]);
+                               lwsl_hexdump_notice(jws.map_b64.buf[LJWS_SIG], jws.map_b64.len[LJWS_SIG]);
+                               goto bail1;
+                       }
+               } else {
+                       if (lws_jws_sig_confirm_compact_b64(in,
+                                       lws_concat_used(temp, temp_len),
+                                       &map, &jwk, context,
+                                       lws_concat_temp(temp, temp_len),
+                                       &temp_len) < 0) {
+                               lwsl_notice("%s: confirm rsa sig failed\n",
+                                           __func__);
+                               goto bail1;
+                       }
+               }
+
+               lwsl_notice("VALID\n");
+
+               /* dump the verifed plaintext and return 0 */
+
+               if (write(1, jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD]) < 0) {
+                       lwsl_err("Write stdout failed\n");
+                       goto bail1;
+               }
+       }
+
+       result = 0;
+
+bail1:
+       lws_jws_destroy(&jws);
+       lws_jwk_destroy(&jwk);
+
+       lws_context_destroy(context);
+
+       return result;
+}
diff --git a/minimal-examples/crypto/minimal-crypto-x509/CMakeLists.txt b/minimal-examples/crypto/minimal-crypto-x509/CMakeLists.txt
new file mode 100644 (file)
index 0000000..327cdcd
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-crypto-x509)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITH_JOSE 1 requirements)
+
+if (requirements)
+
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/crypto/minimal-crypto-x509/README.md b/minimal-examples/crypto/minimal-crypto-x509/README.md
new file mode 100644 (file)
index 0000000..b0d641e
--- /dev/null
@@ -0,0 +1,59 @@
+# lws minimal example for X509
+
+The example shows how to:
+
+ - confirm one PEM cert or chain (-c) was signed by a trusted PEM cert (-t)
+ - convert a certificate public key to JWK
+ - convert a certificate public key and its private key PEM to a private JWK
+
+The examples work for EC and RSA certs and on mbedtls and OpenSSL the same.
+
+Notice the logging is on stderr, and only the JWK is output on stdout.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-c <PEM certificate path>|Required PEM Certificate(s) to operate on... may be multiple concatednated PEM
+-t <PEM certificate path>|Single PEM trusted certificate
+-p <PEM private key path>|Optional private key matching certificate given in -c.  If given, only the private JWK is printed to stdout
+
+Example for confirming trust relationship.  Notice the PEM in -c must contain not only
+the final certificate but also the certificates for any intermediate CAs.
+
+```
+ $ ./lws-crypto-x509 -c ec-cert.pem -t ca-cert.pem
+[2019/01/02 20:31:13:2031] USER: LWS X509 api example
+[2019/01/02 20:31:13:2032] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2019/01/02 20:31:13:2043] NOTICE: main: certs loaded OK
+[2019/01/02 20:31:13:2043] NOTICE: main: verified OK  <<<<======
+[2019/01/02 20:31:13:2045] NOTICE: Cert Public JWK
+{"crv":"P-521","kty":"EC","x":"_uRNBbIbm0zhk8v6ujvQX9924264ZkqJhit0qamAoCegzuJbLf434kN7_aFEt6u-QWUu6-N1R8t6OlvrLo2jrNY","y":"AU-29XpNyB7e5e3s5t0ylzGEnF601A8A7Tx8m8xxngARZX_bn22itGJ3Y57BTcclPMoG80KjWAMnRVtrKqrD_aGD"}
+
+[2019/01/02 20:31:13:2045] NOTICE: main: OK
+```
+
+Example creating JWKs for public and public + private cert + PEM keys:
+
+```
+ $ ./lws-crypto-x509 -c ec-cert.pem -p ec-key.pem
+[2019/01/02 20:14:43:4966] USER: LWS X509 api example
+[2019/01/02 20:14:43:5225] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2019/01/02 20:14:43:5707] NOTICE: lws_x509_public_to_jwk: EC key
+[2019/01/02 20:24:59:9514] USER: LWS X509 api example
+[2019/01/02 20:24:59:9741] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2019/01/02 20:25:00:1261] NOTICE: lws_x509_public_to_jwk: key type 408 "id-ecPublicKey"
+[2019/01/02 20:25:00:1269] NOTICE: lws_x509_public_to_jwk: EC key
+[2019/01/02 20:25:00:2097] NOTICE: Cert + Key Private JWK
+{"crv":"P-521","d":"AU3iQSKfPskMTW4ZncrYLhipUYzLYty2XhemTQ_nSuUB1vB76jHmOYUTRXFBLkVCW8cQYyMa5dMa3Bvv-cdvH0IB","kty":"EC","x":"_uRNBbIbm0zhk8v6ujvQX9924264ZkqJhit0qamAoCegzuJbLf434kN7_aFEt6u-QWUu6-N1R8t6OlvrLo2jrNY","y":"AU-29XpNyB7e5e3s5t0ylzGEnF601A8A7Tx8m8xxngARZX_bn22itGJ3Y57BTcclPMoG80KjWAMnRVtrKqrD_aGD"}
+
+[2019/01/02 20:25:00:2207] NOTICE: main: OK
+```
+
diff --git a/minimal-examples/crypto/minimal-crypto-x509/main.c b/minimal-examples/crypto/minimal-crypto-x509/main.c
new file mode 100644 (file)
index 0000000..64875fb
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * lws-crypto-x509
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#include <libwebsockets.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static int
+read_pem(const char *filename, char *pembuf, int pembuf_len)
+{
+       int n, fd = open(filename, LWS_O_RDONLY);
+       if (fd == -1)
+               return -1;
+
+       n = read(fd, pembuf, pembuf_len - 1);
+       close(fd);
+
+       pembuf[n++] = '\0';
+
+       return n;
+}
+
+static int
+read_pem_c509_cert(struct lws_x509_cert **x509, const char *filename,
+                  char *pembuf, int pembuf_len)
+{
+       int n;
+
+       n = read_pem(filename, pembuf, pembuf_len);
+       if (n < 0)
+               return -1;
+
+       if (lws_x509_create(x509)) {
+               lwsl_err("%s: failed to create x509\n", __func__);
+
+               return -1;
+       }
+
+       if (lws_x509_parse_from_pem(*x509, pembuf, n) < 0) {
+               lwsl_err("%s: unable to parse PEM %s\n", __func__, filename);
+               lws_x509_destroy(x509);
+
+               return -1;
+       }
+
+       return 0;
+}
+
+int main(int argc, const char **argv)
+{
+       int n, result = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_x509_cert *x509 = NULL, *x509_trusted = NULL;
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       struct lws_jwk jwk;
+       char pembuf[6144];
+       const char *p;
+
+       memset(&jwk, 0, sizeof(jwk));
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS X509 api example\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.options = 0;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+
+       p = lws_cmdline_option(argc, argv, "-c");
+       if (!p) {
+               lwsl_err("%s: missing -c <cert pem file>\n", __func__);
+               goto bail;
+       }
+       if (read_pem_c509_cert(&x509, p, pembuf, sizeof(pembuf))) {
+               lwsl_err("%s: unable to read \"%s\": errno %d\n",
+                        __func__, p, errno);
+               goto bail;
+       }
+
+       p = lws_cmdline_option(argc, argv, "-t");
+       if (p) {
+
+               if (read_pem_c509_cert(&x509_trusted, p, pembuf,
+                                      sizeof(pembuf))) {
+                       lwsl_err("%s: unable to read \"%s\": errno %d\n",
+                                __func__, p, errno);
+                       goto bail1;
+               }
+
+               lwsl_notice("%s: certs loaded OK\n", __func__);
+
+               if (lws_x509_verify(x509, x509_trusted, NULL)) {
+                       lwsl_err("%s: verify failed\n", __func__);
+                       goto bail2;
+               }
+
+               lwsl_notice("%s: verified OK\n", __func__);
+       }
+
+       if (x509_trusted) {
+
+               /* show the trusted cert public key as a JWK */
+
+               if (lws_x509_public_to_jwk(&jwk, x509_trusted,
+                                          "P-256,P-384,P-521", 4096)) {
+                       lwsl_err("%s: unable to get trusted cert pubkey as JWK\n",
+                                __func__);
+
+                       goto bail2;
+               }
+
+               if ((p = lws_cmdline_option(argc, argv, "--alg")))
+                       lws_jwk_strdup_meta(&jwk, JWK_META_ALG, p, strlen(p));
+
+               lwsl_info("JWK version of trusted cert:\n");
+               lws_jwk_dump(&jwk);
+               lws_jwk_destroy(&jwk);
+       }
+
+       /* get the cert public key as a JWK */
+
+       if (lws_x509_public_to_jwk(&jwk, x509, "P-256,P-384,P-521", 4096)) {
+               lwsl_err("%s: unable to get cert pubkey as JWK\n", __func__);
+
+               goto bail3;
+       }
+       lwsl_info("JWK version of cert:\n");
+
+       if ((p = lws_cmdline_option(argc, argv, "--alg")))
+               lws_jwk_strdup_meta(&jwk, JWK_META_ALG, p, strlen(p));
+
+       lws_jwk_dump(&jwk);
+       /* only print public if he doesn't provide private */
+       if (!lws_cmdline_option(argc, argv, "-p")) {
+               lwsl_notice("Issuing Cert Public JWK on stdout\n");
+               n = sizeof(pembuf);
+               if (lws_jwk_export(&jwk, 0, pembuf, &n))
+                       puts(pembuf);
+       }
+
+       /* if we know where the cert private key is, add that to the cert JWK */
+
+       p = lws_cmdline_option(argc, argv, "-p");
+       if (p) {
+               n = read_pem(p, pembuf, sizeof(pembuf));
+               if (n < 0) {
+                       lwsl_err("%s: unable read privkey %s\n", __func__, p);
+
+                       goto bail3;
+               }
+               if (lws_x509_jwk_privkey_pem(&jwk, pembuf, n, NULL)) {
+                       lwsl_err("%s: unable to parse privkey %s\n",
+                                       __func__, p);
+
+                       goto bail3;
+               }
+
+               if ((p = lws_cmdline_option(argc, argv, "--alg")))
+                       lws_jwk_strdup_meta(&jwk, JWK_META_ALG, p, strlen(p));
+
+               lwsl_info("JWK version of cert + privkey:\n");
+               lws_jwk_dump(&jwk);
+               lwsl_notice("Issuing Cert + Private JWK on stdout\n");
+               n = sizeof(pembuf);
+               if (lws_jwk_export(&jwk, 1, pembuf, &n))
+                       puts(pembuf);
+       }
+
+       result = 0;
+
+bail3:
+       lws_jwk_destroy(&jwk);
+bail2:
+       lws_x509_destroy(&x509_trusted);
+bail1:
+       lws_x509_destroy(&x509);
+bail:
+       lws_context_destroy(context);
+
+       if (result)
+               lwsl_err("%s: failed\n", __func__);
+       else
+               lwsl_notice("%s: OK\n", __func__);
+
+       return result;
+}
diff --git a/minimal-examples/dbus-client/README.md b/minimal-examples/dbus-client/README.md
new file mode 100644 (file)
index 0000000..ecde9d1
--- /dev/null
@@ -0,0 +1,4 @@
+|Example|Demonstrates|
+---|---
+minimal-dbus-client|Shows how to connect to a DBusServer dbus server like minimal-dbus-server
+minimal-dbus-ws-proxy-testclient|A test client for use with minimal-dbus-ws-proxy
diff --git a/minimal-examples/dbus-client/minimal-dbus-client/CMakeLists.txt b/minimal-examples/dbus-client/minimal-dbus-client/CMakeLists.txt
new file mode 100644 (file)
index 0000000..674bb09
--- /dev/null
@@ -0,0 +1,120 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+include(CheckLibraryExists)
+
+set(SAMP lws-minimal-dbus-client)
+set(SRCS minimal-dbus-client.c)
+
+if (NOT LWS_WITH_MINIMAL_EXAMPLES)
+       CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
+       if (NOT LWS_HAVE_LIBDBUS)
+               message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
+       endif()
+
+       if (NOT LWS_DBUS_LIB)
+               set(LWS_DBUS_LIB "dbus-1")
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE1)
+               # look in fedora and debian / ubuntu place
+               if (EXISTS "/usr/include/dbus-1.0")
+                       set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
+               endif()
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE2)
+               # look in fedora... debian / ubuntu has the ARCH in the path...
+               if (EXISTS "/usr/lib64/dbus-1.0/include")
+                       set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
+               endif()
+       endif()
+
+       set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
+
+       if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2)
+               message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md")
+       endif()
+
+endif()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_DBUS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+       
+       include_directories("${LWS_DBUS_INCLUDE1}")
+       include_directories("${LWS_DBUS_INCLUDE2}")
+       list(APPEND LIB_LIST dbus-1)
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB})
+       else()
+               target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB})
+       endif()
+endif()
diff --git a/minimal-examples/dbus-client/minimal-dbus-client/README.md b/minimal-examples/dbus-client/minimal-dbus-client/README.md
new file mode 100644 (file)
index 0000000..42563c6
--- /dev/null
@@ -0,0 +1,49 @@
+# lws minimal dbus client
+
+This demonstrates nonblocking, asynchronous dbus method calls as the client.
+
+## build
+
+Using libdbus requires additional non-default include paths setting, same as
+is necessary for lws build described in ./lib/roles/dbus/README.md
+
+CMake can guess one path and the library name usually, see the README above
+for details of how to override for custom libdbus and cross build.
+
+Fedora example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
+$ make
+```
+
+Ubuntu example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
+$ make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+The minimal client connects to the minimal dbus server example, which is
+expected to be listening on its default abstract unix domain socket path.
+
+It call the server Echo method with "Hello!" and returns to the event loop.
+When the reply comes, it prints the returned message.
+
+Afterwards it just sits there receiving unsolicited messages from the server
+example, until closed by the user.
+
+```
+ $ ./lws-minimal-dbus-client
+ctx
+[2018/10/05 06:08:31:4901] NOTICE: pending_call_notify
+[2018/10/05 06:08:31:4929] USER: pending_call_notify: received 'Hello!'
+^C[2018/10/05 06:09:22:4409] NOTICE: destroy_dbus_client_conn
+[2018/10/05 06:09:22:4691] NOTICE: Exiting cleanly
+...
+```
+
diff --git a/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c b/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c
new file mode 100644 (file)
index 0000000..43b48a1
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * lws-minimal-dbus-client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal session dbus server that uses the lws event loop,
+ * making it possible to integrate it with other lws features.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <libwebsockets.h>
+#include <libwebsockets/lws-dbus.h>
+
+static struct lws_dbus_ctx *dbus_ctx;
+static struct lws_context *context;
+static int interrupted;
+
+#define THIS_INTERFACE  "org.libwebsockets.test"
+#define THIS_OBJECT     "/org/libwebsockets/test"
+#define THIS_BUSNAME    "org.libwebsockets.test"
+
+#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test"
+
+
+static DBusHandlerResult
+client_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
+{
+       const char *str;
+
+       lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
+                 dbus_message_get_interface(message),
+                 dbus_message_get_member(message),
+                 dbus_message_get_path(message));
+
+       if (!dbus_message_get_args(message, NULL,
+                                  DBUS_TYPE_STRING, &str,
+                                  DBUS_TYPE_INVALID))
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       lwsl_notice("%s: '%s'\n", __func__, str);
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void
+destroy_dbus_client_conn(struct lws_dbus_ctx *ctx)
+{
+       if (!ctx || !ctx->conn)
+               return;
+
+       lwsl_notice("%s\n", __func__);
+
+       dbus_connection_remove_filter(ctx->conn, client_message_handler, ctx);
+       dbus_connection_close(ctx->conn);
+       dbus_connection_unref(ctx->conn);
+
+       free(ctx);
+}
+
+/*
+ * This callback is coming when lws has noticed the fd took a POLLHUP.  The
+ * ctx has effectively gone out of scope before this, and the connection can
+ * be cleaned up and the ctx freed.
+ */
+
+static void
+cb_closing(struct lws_dbus_ctx *ctx)
+{
+       lwsl_err("%s: closing\n", __func__);
+
+       if (ctx == dbus_ctx)
+               dbus_ctx = NULL;
+
+       destroy_dbus_client_conn(ctx);
+}
+
+static struct lws_dbus_ctx *
+create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads)
+{
+       struct lws_dbus_ctx *ctx;
+       DBusError err;
+
+       ctx = malloc(sizeof(*ctx));
+       if (!ctx)
+               return NULL;
+
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->vh = vh;
+       ctx->tsi = tsi;
+
+        dbus_error_init(&err);
+
+       /* connect to the daemon bus */
+       ctx->conn = dbus_connection_open_private(ads, &err);
+       if (!ctx->conn) {
+               lwsl_err("%s: Failed to connect: %s\n",
+                        __func__, err.message);
+               goto fail;
+       }
+
+       dbus_connection_set_exit_on_disconnect(ctx->conn, 0);
+
+       if (!dbus_connection_add_filter(ctx->conn, client_message_handler,
+                                       ctx, NULL)) {
+               lwsl_err("%s: Failed to add filter\n", __func__);
+               goto fail;
+       }
+
+       /*
+        * This is the part that binds the connection to lws watcher and
+        * timeout handling provided by lws
+        */
+
+       if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) {
+               lwsl_err("%s: connection bind to lws failed\n", __func__);
+               goto fail;
+       }
+
+       lwsl_notice("%s: created OK\n", __func__);
+
+       return ctx;
+
+fail:
+       dbus_error_free(&err);
+
+       free(ctx);
+
+       return NULL;
+}
+
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+/*
+ * This gets called if we timed out waiting for the server reply, or the
+ * reply arrived.
+ */
+
+static void
+pending_call_notify(DBusPendingCall *pending, void *data)
+{
+       // struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data;
+       const char *payload;
+       DBusMessage *msg;
+
+       if (!dbus_pending_call_get_completed(pending)) {
+               lwsl_err("%s: timed out waiting for reply\n", __func__);
+
+               goto bail;
+       }
+
+       msg = dbus_pending_call_steal_reply(pending);
+       if (!msg)
+               goto bail;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload,
+                                  DBUS_TYPE_INVALID)) {
+               goto bail1;
+       }
+
+       lwsl_user("%s: received '%s'\n", __func__, payload);
+
+bail1:
+       dbus_message_unref(msg);
+bail:
+       dbus_pending_call_unref(pending);
+}
+
+static int
+remote_method_call(struct lws_dbus_ctx *ctx)
+{
+       DBusMessage *msg;
+       const char *payload = "Hello!";
+       int ret = 1;
+
+       msg = dbus_message_new_method_call(
+                       /* dest */        THIS_BUSNAME,
+                       /* object-path */ THIS_OBJECT,
+                       /* interface */   THIS_INTERFACE,
+                       /* method */      "Echo");
+       if (!msg)
+               return 1;
+
+       if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload,
+                                     DBUS_TYPE_INVALID))
+               goto bail;
+
+       if (!dbus_connection_send_with_reply(ctx->conn, msg,
+                                            &ctx->pc,
+                                            DBUS_TIMEOUT_USE_DEFAULT)) {
+               lwsl_err("%s: unable to send\n", __func__);
+
+               goto bail;
+       }
+
+       dbus_pending_call_set_notify(ctx->pc, pending_call_notify, ctx, NULL);
+
+       ret = 0;
+
+bail:
+       dbus_message_unref(msg);
+
+       return ret;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_vhost *vh;
+       struct lws_context_creation_info info;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */ /* | LLL_THREAD */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal DBUS client\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       vh = lws_create_vhost(context, &info);
+       if (!vh)
+               goto bail;
+
+       dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH);
+       if (!dbus_ctx)
+               goto bail1;
+
+       if (remote_method_call(dbus_ctx))
+               goto bail2;
+
+       /* lws event loop (default poll one) */
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail2:
+       destroy_dbus_client_conn(dbus_ctx);
+
+bail1:
+       /* this is required for valgrind-cleanliness */
+       dbus_shutdown();
+       lws_context_destroy(context);
+
+       lwsl_notice("Exiting cleanly\n");
+
+       return 0;
+
+bail:
+       lwsl_err("%s: failed to start\n", __func__);
+       lws_context_destroy(context);
+
+       return 1;
+}
diff --git a/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/CMakeLists.txt b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/CMakeLists.txt
new file mode 100644 (file)
index 0000000..dda46bf
--- /dev/null
@@ -0,0 +1,120 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+include(CheckLibraryExists)
+
+set(SAMP lws-minimal-dbus-ws-proxy-testclient)
+set(SRCS minimal-dbus-ws-proxy-testclient.c)
+
+if (NOT LWS_WITH_MINIMAL_EXAMPLES)
+       CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
+       if (NOT LWS_HAVE_LIBDBUS)
+               message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
+       endif()
+
+       if (NOT LWS_DBUS_LIB)
+               set(LWS_DBUS_LIB "dbus-1")
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE1)
+               # look in fedora and debian / ubuntu place
+               if (EXISTS "/usr/include/dbus-1.0")
+                       set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
+               endif()
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE2)
+               # look in fedora... debian / ubuntu has the ARCH in the path...
+               if (EXISTS "/usr/lib64/dbus-1.0/include")
+                       set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
+               endif()
+       endif()
+
+       set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
+
+       if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2)
+               message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md")
+       endif()
+
+endif()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_DBUS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+       
+       include_directories("${LWS_DBUS_INCLUDE1}")
+       include_directories("${LWS_DBUS_INCLUDE2}")
+       list(APPEND LIB_LIST dbus-1)
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB})
+       else()
+               target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB})
+       endif()
+endif()
diff --git a/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/README.md b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/README.md
new file mode 100644 (file)
index 0000000..d583462
--- /dev/null
@@ -0,0 +1,52 @@
+# lws minimal dbus ws proxy testclient
+
+This is a test client used to test `./minimal-examples/dbus-server/minimal-dbus-ws-proxy`
+
+It asks the minimal dbus ws proxy application to connect to libwebsockets.org
+over the mirror protocol.  And it proxies back the ASCII packets used to
+communicate the mirror sample drawing vectors over dbus to this test client
+if you draw on the [mirror example app](https://libwebsockets.org/testserver/)
+in a browser.
+
+## build
+
+Using libdbus requires additional non-default include paths setting, same as
+is necessary for lws build described in ./lib/roles/dbus/README.md
+
+CMake can guess one path and the library name usually, see the README above
+for details of how to override for custom libdbus and cross build.
+
+Fedora example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
+$ make
+```
+
+Ubuntu example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
+$ make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+
+This connects to the minimal-dbus-ws-proxy example running in another terminal.
+
+```
+ $ ./lws-minimal-dbus-ws-proxy-testclient
+[2018/10/05 14:17:16:6286] USER: LWS minimal DBUS ws proxy testclient
+[2018/10/05 14:17:16:6538] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off
+[2018/10/05 14:17:16:6617] USER: create_dbus_client_conn: connecting to 'unix:abstract=org.libwebsockets.wsclientproxy'
+[2018/10/05 14:17:16:7189] NOTICE: create_dbus_client_conn: created OK
+[2018/10/05 14:17:16:7429] USER: remote_method_call: requesting proxy connection wss://libwebsockets.org/ lws-mirror-protocol
+[2018/10/05 14:17:17:0387] USER: pending_call_notify: received 'Connecting'
+[2018/10/05 14:17:18:7475] NOTICE: client_message_handler: (type 7) 'ws client connection established'
+[2018/10/05 14:17:21:2028] NOTICE: client_message_handler: (type 6) 'd #000000 323 63 323 67;'
+[2018/10/05 14:17:21:2197] NOTICE: client_message_handler: (type 6) 'd #000000 323 67 327 73;'
+...
+```
+
diff --git a/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c
new file mode 100644 (file)
index 0000000..d2818fa
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+ * lws-minimal-dbus-ws-proxy-testclient
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This acts as a test client over DBUS, opening a session with
+ * minimal-dbus-ws-proxy and sending and receiving data on the libwebsockets
+ * mirror demo page.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <libwebsockets.h>
+#include <libwebsockets/lws-dbus.h>
+
+/*
+ * These are the various states our connection can be in, both with regards
+ * to the direct connection to the proxy, and the state of the onward ws
+ * connection the proxy opens at our request.
+ */
+
+enum lws_dbus_client_state {
+       LDCS_NOTHING,             /* no connection yet */
+       LDCS_CONN,                /* conn to proxy */
+       LDCS_CONN_WAITING_ONWARD, /* conn to proxy, awaiting proxied conn */
+       LDCS_CONN_ONWARD,         /* conn to proxy and onward conn OK */
+       LDCS_CONN_CLOSED,         /* conn to proxy but onward conn closed */
+       LDCS_CLOSED,              /* connection to proxy is closed */
+};
+
+/*
+ * our expanded dbus context
+ */
+
+struct lws_dbus_ctx_wsproxy_client {
+       struct lws_dbus_ctx ctx;
+
+       enum lws_dbus_client_state state;
+};
+
+static struct lws_dbus_ctx_wsproxy_client *dbus_ctx;
+static struct lws_context *context;
+static int interrupted, autoexit_budget = -1, count_rx, count_tx;
+
+#define THIS_INTERFACE  "org.libwebsockets.wsclientproxy"
+#define THIS_OBJECT     "/org/libwebsockets/wsclientproxy"
+#define THIS_BUSNAME    "org.libwebsockets.wsclientproxy"
+
+#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.wsclientproxy"
+
+static void
+state_transition(struct lws_dbus_ctx_wsproxy_client *dcwc,
+                enum lws_dbus_client_state state)
+{
+       lwsl_notice("%s: %p: from state %d -> %d\n", __func__,
+                   dcwc,dcwc->state, state);
+       dcwc->state = state;
+}
+
+static DBusHandlerResult
+filter(DBusConnection *conn, DBusMessage *message, void *data)
+{
+       struct lws_dbus_ctx_wsproxy_client *dcwc =
+                       (struct lws_dbus_ctx_wsproxy_client *)data;
+       const char *str;
+
+       if (!dbus_message_get_args(message, NULL,
+                                  DBUS_TYPE_STRING, &str,
+                                  DBUS_TYPE_INVALID))
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       /* received ws data */
+
+       if (dbus_message_is_signal(message, THIS_INTERFACE, "Receive")) {
+               lwsl_user("%s: Received '%s'\n", __func__, str);
+               count_rx++;
+       }
+
+       /* proxy ws connection failed */
+
+       if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") &&
+           !strcmp(str, "ws client connection error"))
+               state_transition(dcwc, LDCS_CONN_CLOSED);
+
+       /* proxy ws connection succeeded */
+
+       if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") &&
+           !strcmp(str, "ws client connection established"))
+               state_transition(dcwc, LDCS_CONN_ONWARD);
+
+       /* proxy ws connection has closed */
+
+       if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") &&
+           !strcmp(str, "ws client connection closed"))
+               state_transition(dcwc, LDCS_CONN_CLOSED);
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void
+destroy_dbus_client_conn(struct lws_dbus_ctx_wsproxy_client **pdcwc)
+{
+       struct lws_dbus_ctx_wsproxy_client *dcwc = *pdcwc;
+
+       if (!dcwc || !dcwc->ctx.conn)
+               return;
+
+       lwsl_notice("%s\n", __func__);
+
+       dbus_connection_remove_filter(dcwc->ctx.conn, filter, &dcwc->ctx);
+       dbus_connection_close(dcwc->ctx.conn);
+       dbus_connection_unref(dcwc->ctx.conn);
+
+       free(dcwc);
+
+       *pdcwc = NULL;
+}
+
+/*
+ * This callback is coming when lws has noticed the fd took a POLLHUP.  The
+ * ctx has effectively gone out of scope before this, and the connection can
+ * be cleaned up and the ctx freed.
+ */
+
+static void
+cb_closing(struct lws_dbus_ctx *ctx)
+{
+       struct lws_dbus_ctx_wsproxy_client *dcwc =
+                       (struct lws_dbus_ctx_wsproxy_client *)ctx;
+
+       lwsl_err("%s: closing\n", __func__);
+
+       if (dcwc == dbus_ctx)
+               dbus_ctx = NULL;
+
+       destroy_dbus_client_conn(&dcwc);
+
+       interrupted = 1;
+}
+
+static struct lws_dbus_ctx_wsproxy_client *
+create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads)
+{
+       struct lws_dbus_ctx_wsproxy_client *dcwc;
+       DBusError e;
+
+       dcwc = malloc(sizeof(*dcwc));
+       if (!dcwc)
+               return NULL;
+
+       memset(dcwc, 0, sizeof(*dcwc));
+
+       dcwc->state = LDCS_NOTHING;
+       dcwc->ctx.vh = vh;
+       dcwc->ctx.tsi = tsi;
+
+        dbus_error_init(&e);
+
+        lwsl_user("%s: connecting to '%s'\n", __func__, ads);
+#if 1
+       /* connect to our daemon bus */
+
+        dcwc->ctx.conn = dbus_connection_open_private(ads, &e);
+       if (!dcwc->ctx.conn) {
+               lwsl_err("%s: Failed to connect: %s\n",
+                        __func__, e.message);
+               goto fail;
+       }
+#else
+       /* connect to the SYSTEM bus */
+
+       dcwc->ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e);
+       if (!dcwc->ctx.conn) {
+               lwsl_err("%s: Failed to get a session DBus connection: %s\n",
+                        __func__, e.message);
+               goto fail;
+       }
+#endif
+       dbus_connection_set_exit_on_disconnect(dcwc->ctx.conn, 0);
+
+       if (!dbus_connection_add_filter(dcwc->ctx.conn, filter,
+                                       &dcwc->ctx, NULL)) {
+               lwsl_err("%s: Failed to add filter\n", __func__);
+               goto fail;
+       }
+
+       /*
+        * This is the part that binds the connection to lws watcher and
+        * timeout handling provided by lws
+        */
+
+       if (lws_dbus_connection_setup(&dcwc->ctx, dcwc->ctx.conn, cb_closing)) {
+               lwsl_err("%s: connection bind to lws failed\n", __func__);
+               goto fail;
+       }
+
+       state_transition(dcwc, LDCS_CONN);
+
+       lwsl_notice("%s: created OK\n", __func__);
+
+       return dcwc;
+
+fail:
+       dbus_error_free(&e);
+
+       free(dcwc);
+
+       return NULL;
+}
+
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+/*
+ * This gets called if we timed out waiting for the dbus server reply, or the
+ * reply arrived.
+ */
+
+static void
+pending_call_notify(DBusPendingCall *pending, void *data)
+{
+       const char *payload;
+       DBusMessage *msg;
+
+       if (!dbus_pending_call_get_completed(pending)) {
+               lwsl_err("%s: timed out waiting for reply\n", __func__);
+
+               goto bail;
+       }
+
+       msg = dbus_pending_call_steal_reply(pending);
+       if (!msg)
+               goto bail;
+
+       if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload,
+                                             DBUS_TYPE_INVALID)) {
+               goto bail1;
+       }
+
+       lwsl_user("%s: received '%s'\n", __func__, payload);
+
+bail1:
+       dbus_message_unref(msg);
+bail:
+       dbus_pending_call_unref(pending);
+}
+
+static int
+remote_method_call(struct lws_dbus_ctx_wsproxy_client *dcwc)
+{
+       char _uri[96];
+       const char *subprotocol = "lws-mirror-protocol", *uri = _uri;
+       DBusMessage *msg;
+       int ret = 1;
+
+       /*
+        * make our own private mirror session... because others may run this
+        * at the same time against libwebsockets.org... as happened 2019-03-14
+        * and broke travis tests :-)
+        */
+
+       lws_snprintf(_uri, sizeof(_uri), "wss://libwebsockets.org/?mirror=dbt-%d",
+                       (int)getpid());
+
+       msg = dbus_message_new_method_call(
+                       /* dest */        THIS_BUSNAME,
+                       /* object-path */ THIS_OBJECT,
+                       /* interface */   THIS_INTERFACE,
+                       /* method */      "Connect");
+       if (!msg)
+               return 1;
+
+       if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &uri,
+                                          DBUS_TYPE_STRING, &subprotocol,
+                                          DBUS_TYPE_INVALID))
+               goto bail;
+
+       lwsl_user("%s: requesting proxy connection %s %s\n", __func__,
+                       uri, subprotocol);
+
+       if (!dbus_connection_send_with_reply(dcwc->ctx.conn, msg, &dcwc->ctx.pc,
+                                            DBUS_TIMEOUT_USE_DEFAULT)) {
+               lwsl_err("%s: unable to send\n", __func__);
+
+               goto bail;
+       }
+
+       dbus_pending_call_set_notify(dcwc->ctx.pc, pending_call_notify,
+                                    &dcwc->ctx, NULL);
+
+       state_transition(dcwc, LDCS_CONN_WAITING_ONWARD);
+
+       ret = 0;
+
+bail:
+       dbus_message_unref(msg);
+
+       return ret;
+}
+
+/*
+ * Stub lws protocol, just so we can get synchronous timers conveniently.
+ *
+ * Set up a 1Hz timer and if our connection state is suitable, use that
+ * to write mirror protocol drawing packets to the proxied ws connection
+ */
+
+static int
+callback_just_timer(struct lws *wsi, enum lws_callback_reasons reason,
+                   void *user, void *in, size_t len)
+{
+       char payload[64];
+       const char *ws_pkt = payload;
+       DBusMessage *msg;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+       case LWS_CALLBACK_USER:
+               lwsl_info("%s: LWS_CALLBACK_USER\n", __func__);
+
+               if (!dbus_ctx || dbus_ctx->state != LDCS_CONN_ONWARD)
+                       goto again;
+
+               if (autoexit_budget > 0) {
+                       if (!--autoexit_budget) {
+                               lwsl_notice("reached autoexit budget\n");
+                               interrupted = 1;
+                               break;
+                       }
+               }
+
+               msg = dbus_message_new_method_call(THIS_BUSNAME, THIS_OBJECT,
+                                                  THIS_INTERFACE, "Send");
+               if (!msg)
+                       break;
+
+               lws_snprintf(payload, sizeof(payload), "d #%06X %d %d %d %d;",
+                            rand() & 0xffffff, rand() % 480, rand() % 300,
+                            rand() % 480, rand() % 300);
+
+               if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &ws_pkt,
+                                                  DBUS_TYPE_INVALID)) {
+                       dbus_message_unref(msg);
+                       break;
+               }
+
+               if (!dbus_connection_send_with_reply(dbus_ctx->ctx.conn, msg,
+                                                    &dbus_ctx->ctx.pc,
+                                                   DBUS_TIMEOUT_USE_DEFAULT)) {
+                       lwsl_err("%s: unable to send\n", __func__);
+                       dbus_message_unref(msg);
+                       break;
+               }
+
+               dbus_message_unref(msg);
+               dbus_pending_call_set_notify(dbus_ctx->ctx.pc,
+                                            pending_call_notify,
+                                            &dbus_ctx->ctx, NULL);
+               count_tx++;
+
+again:
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                              lws_get_protocol(wsi),
+                                              LWS_CALLBACK_USER, 2);
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static struct lws_protocols protocols[] = {
+               { "_just_timer", callback_just_timer, 0, 10, 0, NULL, 0 },
+               { }
+};
+
+
+int main(int argc, const char **argv)
+{
+       struct lws_vhost *vh;
+       struct lws_context_creation_info info;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */ /* | LLL_THREAD */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       if ((p = lws_cmdline_option(argc, argv, "-x")))
+               autoexit_budget = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal DBUS ws proxy testclient\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+       info.protocols = protocols;
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.options |=
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       vh = lws_create_vhost(context, &info);
+       if (!vh)
+               goto bail;
+
+       dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH);
+       if (!dbus_ctx)
+               goto bail1;
+
+       if (remote_method_call(dbus_ctx))
+               goto bail2;
+
+       /* lws event loop (default poll one) */
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail2:
+       destroy_dbus_client_conn(&dbus_ctx);
+
+bail1:
+       /* this is required for valgrind-cleanliness */
+       dbus_shutdown();
+       lws_context_destroy(context);
+
+       lwsl_notice("Exiting cleanly, rx: %d, tx: %d\n", count_rx, count_tx);
+
+       return 0;
+
+bail:
+       lwsl_err("%s: failed to start\n", __func__);
+       lws_context_destroy(context);
+
+       return 1;
+}
diff --git a/minimal-examples/dbus-server/README.md b/minimal-examples/dbus-server/README.md
new file mode 100644 (file)
index 0000000..fc59bfb
--- /dev/null
@@ -0,0 +1,4 @@
+|Example|Demonstrates|
+---|---
+minimal-dbus-server|Shows how to run a DBUS session server using lws event loop
+minimal-dbus-ws-proxy|Control ws client connections via DBUS
diff --git a/minimal-examples/dbus-server/minimal-dbus-server/CMakeLists.txt b/minimal-examples/dbus-server/minimal-dbus-server/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7260d5a
--- /dev/null
@@ -0,0 +1,120 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+include(CheckLibraryExists)
+
+set(SAMP lws-minimal-dbus-server)
+set(SRCS main.c)
+
+if (NOT LWS_WITH_MINIMAL_EXAMPLES)
+       CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
+       if (NOT LWS_HAVE_LIBDBUS)
+               message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
+       endif()
+
+       if (NOT LWS_DBUS_LIB)
+               set(LWS_DBUS_LIB "dbus-1")
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE1)
+               # look in fedora and debian / ubuntu place
+               if (EXISTS "/usr/include/dbus-1.0")
+                       set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
+               endif()
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE2)
+               # look in fedora... debian / ubuntu has the ARCH in the path...
+               if (EXISTS "/usr/lib64/dbus-1.0/include")
+                       set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
+               endif()
+       endif()
+
+       set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
+
+       if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2)
+               message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md")
+       endif()
+
+endif()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_DBUS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+       
+       include_directories("${LWS_DBUS_INCLUDE1}")
+       include_directories("${LWS_DBUS_INCLUDE2}")
+       list(APPEND LIB_LIST dbus-1)
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB})
+       else()
+               target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB})
+       endif()
+endif()
diff --git a/minimal-examples/dbus-server/minimal-dbus-server/README.md b/minimal-examples/dbus-server/minimal-dbus-server/README.md
new file mode 100644 (file)
index 0000000..7b61eb1
--- /dev/null
@@ -0,0 +1,96 @@
+# lws minimal dbus server
+
+## build
+
+Using libdbus requires additional non-default include paths setting, same as
+is necessary for lws build described in ./lib/roles/dbus/README.md
+
+CMake can guess one path and the library name usually, see the README above
+for details of how to override for custom libdbus and cross build.
+
+Fedora example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
+$ make
+```
+
+Ubuntu example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
+$ make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+--session | Bind to session bus instead of creating private abstract unix socket
+
+By default the minimal server listens using its own abstract unix socket
+at `unix:abstract=org.libwebsockets.test`.
+
+You can also run it instead as a participant on the session bus, without its own
+unix socket, by giving `--session`.
+
+### Examples using the default private abstract unix socket
+
+```
+ $ ./lws-minimal-dbus-server
+[2018/10/03 07:08:02:6448] USER: LWS minimal dbus server
+[2018/10/03 07:08:02:6693] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off
+...
+```
+
+You can communicate with the dbus server over its private abstract socket using, eg
+
+```
+$ gdbus introspect --address unix:abstract=org.libwebsockets.test --dest org.libwebsockets.test --object-path /org/libwebsockets/test
+node /org/example/TestObject {
+  interface org.freedesktop.DBus.Introspectable {
+    methods:
+      Introspect(out s data);
+    signals:
+    properties:
+  };
+  interface org.freedesktop.DBus.Properties {
+    methods:
+      Get(in  s interface,
+...
+```
+
+```
+$ gdbus call --address unix:abstract=org.libwebsockets.test --dest org.libwebsockets.test --object-path /org/libwebsockets/test --method org.libwebsockets.test.Echo HELLO
+('HELLO',)
+```
+
+### Examples using the DBUS session bus
+
+```
+ $ ./lws-minimal-dbus-server --session
+[2018/10/03 07:08:02:6448] USER: LWS minimal dbus server
+[2018/10/03 07:08:02:6693] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off
+...
+```
+
+You can communicate with the dbus server over the session bus using, eg
+
+```
+$ gdbus introspect --session --dest org.libwebsockets.test --object-path /org/libwebsockets/test
+node /org/example/TestObject {
+  interface org.freedesktop.DBus.Introspectable {
+    methods:
+      Introspect(out s data);
+    signals:
+    properties:
+  };
+  interface org.freedesktop.DBus.Properties {
+    methods:
+      Get(in  s interface,
+...
+```
+
+```
+$ gdbus call --session --dest org.libwebsockets.test --object-path /org/libwebsockets/test --method org.libwebsockets.test.Echo HELLO
+('HELLO',)
+```
diff --git a/minimal-examples/dbus-server/minimal-dbus-server/main.c b/minimal-examples/dbus-server/minimal-dbus-server/main.c
new file mode 100644 (file)
index 0000000..0d74b9b
--- /dev/null
@@ -0,0 +1,535 @@
+/*
+ * lws-minimal-dbus-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal session dbus server that uses the lws event loop,
+ * making it possible to integrate it with other lws features.
+ *
+ * The dbus server parts are based on "Sample code illustrating basic use of
+ * D-BUS" (presumed Public Domain) here:
+ *
+ * https://github.com/fbuihuu/samples-dbus/blob/master/dbus-server.c
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <libwebsockets.h>
+#include <libwebsockets/lws-dbus.h>
+
+static struct lws_context *context;
+static const char *version = "0.1";
+static int interrupted;
+static struct lws_dbus_ctx dbus_ctx, ctx_listener;
+static char session;
+
+#define THIS_INTERFACE  "org.libwebsockets.test"
+#define THIS_OBJECT     "/org/libwebsockets/test"
+#define THIS_BUSNAME    "org.libwebsockets.test"
+
+#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test"
+
+static const char *
+server_introspection_xml =
+       DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+       "<node>\n"
+       "  <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
+       "    <method name='Introspect'>\n"
+       "      <arg name='data' type='s' direction='out' />\n"
+       "    </method>\n"
+       "  </interface>\n"
+
+       "  <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
+       "    <method name='Get'>\n"
+       "      <arg name='interface' type='s' direction='in' />\n"
+       "      <arg name='property'  type='s' direction='in' />\n"
+       "      <arg name='value'     type='s' direction='out' />\n"
+       "    </method>\n"
+       "    <method name='GetAll'>\n"
+       "      <arg name='interface'  type='s'     direction='in'/>\n"
+       "      <arg name='properties' type='a{sv}' direction='out'/>\n"
+       "    </method>\n"
+       "  </interface>\n"
+
+       "  <interface name='"THIS_INTERFACE"'>\n"
+       "    <property name='Version' type='s' access='read' />\n"
+       "    <method name='Ping' >\n"
+       "      <arg type='s' direction='out' />\n"
+       "    </method>\n"
+       "    <method name='Echo'>\n"
+       "      <arg name='string' direction='in' type='s'/>\n"
+       "      <arg type='s' direction='out' />\n"
+       "    </method>\n"
+       "    <method name='EmitSignal'>\n"
+       "    </method>\n"
+       "    <method name='Quit'>\n"
+       "    </method>\n"
+       "    <signal name='OnEmitSignal'>\n"
+       "    </signal>"
+       "  </interface>\n"
+
+       "</node>\n";
+
+static DBusHandlerResult
+dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       dbus_message_append_args(*reply, DBUS_TYPE_STRING,
+                                &server_introspection_xml, DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       const char *interface, *property;
+       DBusError err;
+
+       dbus_error_init(&err);
+
+       if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface,
+                                           DBUS_TYPE_STRING, &property,
+                                           DBUS_TYPE_INVALID)) {
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, err.name, err.message);
+               dbus_error_free(&err);
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       if (strcmp(property, "Version")) /* Unknown property */
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version,
+                                DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       DBusMessageIter arr, di, iter, va;
+       const char *property = "Version";
+
+       dbus_message_iter_init_append(*reply, &iter);
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr);
+
+       /* Append all properties name/value pairs */
+       dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di);
+       dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property);
+       dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va);
+       dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version);
+       dbus_message_iter_close_container(&di, &va);
+       dbus_message_iter_close_container(&arr, &di);
+
+       dbus_message_iter_close_container(&iter, &arr);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_ping(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       const char *pong = "Pong";
+
+       dbus_message_append_args(*reply, DBUS_TYPE_STRING, &pong,
+                                        DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_echo(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       const char *msg;
+       DBusError err;
+
+       dbus_error_init(&err);
+
+       if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING,
+                                  &msg, DBUS_TYPE_INVALID)) {
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, err.name, err.message);
+               dbus_error_free(&err);
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       dbus_message_append_args(*reply, DBUS_TYPE_STRING, &msg,
+                                        DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_emit_signal(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       DBusMessage *r = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE,
+                                                "OnEmitSignal");
+
+       if (!r)
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       if (!dbus_connection_send(c, r, NULL))
+               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+       /* and send the original empty reply after */
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_emit_quit(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       interrupted = 1;
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+struct lws_dbus_methods {
+       const char *inter;
+       const char *call;
+       lws_dbus_message_handler handler;
+} meths[] = {
+       { DBUS_INTERFACE_INTROSPECTABLE, "Introspect",  dmh_introspect  },
+       { DBUS_INTERFACE_PROPERTIES,     "Get",         dmh_get         },
+       { DBUS_INTERFACE_PROPERTIES,     "GetAll",      dmh_getall      },
+       { THIS_INTERFACE,                "Ping",        dmh_ping        },
+       { THIS_INTERFACE,                "Echo",        dmh_echo        },
+       { THIS_INTERFACE,                "EmitSignal",  dmh_emit_signal },
+       { THIS_INTERFACE,                "Quit",        dmh_emit_quit   },
+};
+
+static DBusHandlerResult
+server_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
+{
+       struct lws_dbus_methods *mp = meths;
+       DBusHandlerResult result;
+        DBusMessage *reply = NULL;
+       size_t n;
+
+       lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
+                 dbus_message_get_interface(message),
+                 dbus_message_get_member(message),
+                 dbus_message_get_path(message));
+
+       for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) {
+               if (dbus_message_is_method_call(message, mp->inter, mp->call)) {
+                       reply = dbus_message_new_method_return(message);
+                       if (!reply)
+                               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+                       result = mp->handler(conn, message, &reply, data);
+
+                       if (result == DBUS_HANDLER_RESULT_HANDLED &&
+                           !dbus_connection_send(conn, reply, NULL))
+                               result = DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+                       dbus_message_unref(reply);
+
+                       return result;
+               }
+
+               mp++;
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static const DBusObjectPathVTable server_vtable = {
+       .message_function = server_message_handler
+};
+
+static void
+destroy_dbus_server_conn(struct lws_dbus_ctx *ctx)
+{
+       if (!ctx->conn)
+               return;
+
+       lwsl_notice("%s\n", __func__);
+
+       dbus_connection_unregister_object_path(ctx->conn, THIS_OBJECT);
+       lws_dll2_remove(&ctx->next);
+       dbus_connection_unref(ctx->conn);
+}
+
+static void
+cb_closing(struct lws_dbus_ctx *ctx)
+{
+       lwsl_err("%s: closing\n", __func__);
+       destroy_dbus_server_conn(ctx);
+
+       free(ctx);
+}
+
+
+static void
+new_conn(DBusServer *server, DBusConnection *conn, void *data)
+{
+       struct lws_dbus_ctx *conn_ctx, *ctx = (struct lws_dbus_ctx *)data;
+
+       lwsl_notice("%s: vh %s\n", __func__, lws_get_vhost_name(ctx->vh));
+
+       conn_ctx = malloc(sizeof(*conn_ctx));
+       if (!conn_ctx)
+               return;
+
+       memset(conn_ctx, 0, sizeof(*conn_ctx));
+
+       conn_ctx->tsi = ctx->tsi;
+       conn_ctx->vh = ctx->vh;
+       conn_ctx->conn = conn;
+
+       if (lws_dbus_connection_setup(conn_ctx, conn, cb_closing)) {
+               lwsl_err("%s: connection bind to lws failed\n", __func__);
+               goto bail;
+       }
+
+       if (!dbus_connection_register_object_path(conn, THIS_OBJECT,
+                                                 &server_vtable, conn_ctx)) {
+               lwsl_err("%s: Failed to register object path\n", __func__);
+               goto bail;
+       }
+
+       lws_dll2_add_head(&conn_ctx->next, &ctx->owner);
+
+       /* we take on responsibility for explicit close / unref with this... */
+       dbus_connection_ref(conn);
+
+       return;
+
+bail:
+       free(conn_ctx);
+}
+
+static int
+create_dbus_listener(const char *ads)
+{
+       DBusError e;
+
+        dbus_error_init(&e);
+
+       if (!lws_dbus_server_listen(&ctx_listener, ads, &e, new_conn)) {
+               lwsl_err("%s: failed\n", __func__);
+               dbus_error_free(&e);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+static int
+create_dbus_server_conn(struct lws_dbus_ctx *ctx, DBusBusType type)
+{
+       DBusError err;
+       int rv;
+
+        dbus_error_init(&err);
+
+       /* connect to the daemon bus */
+       ctx->conn = dbus_bus_get(type, &err);
+       if (!ctx->conn) {
+               lwsl_err("%s: Failed to get a session DBus connection: %s\n",
+                        __func__, err.message);
+               goto fail;
+       }
+
+       /*
+        * by default dbus will call exit() when this connection closes...
+        * we have to shut down other things cleanly, so disable that
+        */
+       dbus_connection_set_exit_on_disconnect(ctx->conn, 0);
+
+       rv = dbus_bus_request_name(ctx->conn, THIS_BUSNAME,
+                                  DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
+       if (rv != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+               lwsl_err("%s: Failed to request name on bus: %s\n",
+                        __func__, err.message);
+               goto fail;
+       }
+
+       if (!dbus_connection_register_object_path(ctx->conn, THIS_OBJECT,
+                                                 &server_vtable, NULL)) {
+               lwsl_err("%s: Failed to register object path for TestObject\n",
+                        __func__);
+               dbus_bus_release_name(ctx->conn, THIS_BUSNAME, &err);
+               goto fail;
+       }
+
+       /*
+        * This is the part that binds the connection to lws watcher and
+        * timeout handling provided by lws
+        */
+
+       if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) {
+               lwsl_err("%s: connection bind to lws failed\n", __func__);
+               goto fail;
+       }
+
+       lwsl_notice("%s: created OK\n", __func__);
+
+       return 0;
+
+fail:
+       dbus_error_free(&err);
+
+       return 1;
+}
+
+/*
+ * Cleanly release the connection
+ */
+
+static void
+destroy_dbus_server_listener(struct lws_dbus_ctx *ctx)
+{
+       dbus_server_disconnect(ctx->dbs);
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
+                                  ctx->owner.head) {
+               struct lws_dbus_ctx *r =
+                       lws_container_of(rdt, struct lws_dbus_ctx, next);
+
+               dbus_connection_close(r->conn);
+               dbus_connection_unref(r->conn);
+               free(r);
+       } lws_end_foreach_dll_safe(rdt, nx);
+
+       dbus_server_unref(ctx->dbs);
+}
+
+/*
+ * DBUS can send messages outside the usual client-initiated RPC concept.
+ *
+ * You can receive them using a message filter.
+ */
+
+static void
+spam_connected_clients(struct lws_dbus_ctx *ctx)
+{
+
+       /* send connected clients an unsolicited message */
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
+                                  ctx->owner.head) {
+               struct lws_dbus_ctx *r =
+                       lws_container_of(rdt, struct lws_dbus_ctx, next);
+
+
+               DBusMessage *msg;
+               const char *payload = "Unsolicited message";
+
+               msg = dbus_message_new(DBUS_NUM_MESSAGE_TYPES + 1);
+               if (!msg) {
+                       lwsl_err("%s: new message failed\n", __func__);
+               }
+
+               dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload,
+                                                DBUS_TYPE_INVALID);
+               if (!dbus_connection_send(r->conn, msg, NULL)) {
+                       lwsl_err("%s: unable to send\n", __func__);
+               }
+
+               lwsl_notice("%s\n", __func__);
+
+               dbus_message_unref(msg);
+
+       } lws_end_foreach_dll_safe(rdt, nx);
+
+}
+
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */ /* | LLL_THREAD */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal DBUS server\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.options |=
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       dbus_ctx.tsi = 0;
+       ctx_listener.tsi = 0;
+       ctx_listener.vh = dbus_ctx.vh = lws_create_vhost(context, &info);
+       if (!dbus_ctx.vh)
+               goto bail;
+
+       session = !!lws_cmdline_option(argc, argv, "--session");
+
+       if (session) {
+               /* create the dbus connection, loosely bound to our lws vhost */
+
+               if (create_dbus_server_conn(&dbus_ctx, DBUS_BUS_SESSION))
+                       goto bail;
+       } else {
+               if (create_dbus_listener(THIS_LISTEN_PATH)) {
+                       lwsl_err("%s: create_dbus_listener failed\n", __func__);
+                       goto bail;
+               }
+       }
+
+       /* lws event loop (default poll one) */
+
+       while (n >= 0 && !interrupted) {
+               if (!session)
+                       spam_connected_clients(&ctx_listener);
+               n = lws_service(context, 0);
+       }
+
+       if (session)
+               destroy_dbus_server_conn(&dbus_ctx);
+       else
+               destroy_dbus_server_listener(&ctx_listener);
+
+       /* this is required for valgrind-cleanliness */
+       dbus_shutdown();
+       lws_context_destroy(context);
+
+       lwsl_notice("Exiting cleanly\n");
+
+       return 0;
+
+bail:
+       lwsl_err("%s: failed to start\n", __func__);
+
+       lws_context_destroy(context);
+
+       return 1;
+}
diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/CMakeLists.txt b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bad9ec3
--- /dev/null
@@ -0,0 +1,122 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+include(CheckLibraryExists)
+
+set(SAMP lws-minimal-dbus-ws-proxy)
+set(SRCS main.c)
+
+if (NOT LWS_WITH_MINIMAL_EXAMPLES)
+       CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
+       if (NOT LWS_HAVE_LIBDBUS)
+               message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
+       endif()
+
+       if (NOT LWS_DBUS_LIB)
+               set(LWS_DBUS_LIB "dbus-1")
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE1)
+               # look in fedora and debian / ubuntu place
+               if (EXISTS "/usr/include/dbus-1.0")
+                       set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
+               endif()
+       endif()
+
+       if (NOT LWS_DBUS_INCLUDE2)
+               # look in fedora... debian / ubuntu has the ARCH in the path...
+               if (EXISTS "/usr/lib64/dbus-1.0/include")
+                       set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
+               else()
+                       message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
+               endif()
+       endif()
+
+       set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
+
+       if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2)
+               message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md")
+       endif()
+
+endif()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_DBUS 1 requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       include_directories("${LWS_DBUS_INCLUDE1}")
+       include_directories("${LWS_DBUS_INCLUDE2}")
+       list(APPEND LIB_LIST dbus-1)
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB})
+       else()
+               target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB})
+       endif()
+endif()
diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/README.md b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/README.md
new file mode 100644 (file)
index 0000000..7192854
--- /dev/null
@@ -0,0 +1,115 @@
+# lws minimal dbus ws proxy
+
+This is an application which presents a DBUS server on one side, and a
+websocket client proxy on the other.
+
+You connect to it over DBUS, send a Connect method on its interface giving
+a URI and a ws subprotocol name.
+
+It replies with a string "Connecting" if all is well.
+
+Connection progress (including close) is then provided using type 7 messages
+sent back to the dbus client.
+
+Payload from the ws connection is provided using type 6 messages sent back to
+the dbus client.
+
+## build
+
+Using libdbus requires additional non-default include paths setting, same as
+is necessary for lws build described in ./lib/roles/dbus/README.md
+
+CMake can guess one path and the library name usually, see the README above
+for details of how to override for custom libdbus and cross build.
+
+Fedora example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
+$ make
+```
+
+Ubuntu example:
+```
+$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
+$ make
+```
+
+## Configuration
+
+The dbus-ws-proxy server tries to register its actual bus name with the SYSTEM
+bus in DBUS.  If it fails, eg because of insufficient permissions on the user,
+then it continues without that and starts its own daemon normally.
+
+The main dbus daemon must be told how to accept these registrations if that's
+what you want.  A config file is provided that tells dbus to allow the
+well-known busname for this daemon to be registered, but only by root.
+
+``` 
+$ sudo cp org.libwebsockets.wsclientproxy.conf /etc/dbus-1/system.d
+$ sudo systemctl restart dbus
+```
+
+## usage
+
+Run the dbus-ws-proxy server, then start lws-minimal-dbus-ws-proxy-testclient in
+another terminal.
+
+This test app sends a random line drawing message to the mirror example on
+https://libwebsockets.org/testserver every couple of seconds, and displays
+any received messages (such as its own sends mirrored back, or anything
+drawn in the canvas in a browser).
+
+```
+ $ sudo ./lws-minimal-dbus-ws-proxy-testclient
+[2018/10/07 10:05:29:2084] USER: LWS minimal DBUS ws proxy testclient
+[2018/10/07 10:05:29:2345] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off
+[2018/10/07 10:05:29:2424] USER: create_dbus_client_conn: connecting to 'unix:abstract=org.libwebsockets.wsclientproxy'
+[2018/10/07 10:05:29:2997] NOTICE: state_transition: 0x5679720: from state 0 -> 1
+[2018/10/07 10:05:29:2999] NOTICE: create_dbus_client_conn: created OK
+[2018/10/07 10:05:29:3232] USER: remote_method_call: requesting proxy connection wss://libwebsockets.org/ lws-mirror-protocol
+[2018/10/07 10:05:29:3450] NOTICE: state_transition: 0x5679720: from state 1 -> 2
+[2018/10/07 10:05:29:5972] USER: pending_call_notify: received 'Connecting'
+[2018/10/07 10:05:31:3387] NOTICE: state_transition: 0x5679720: from state 2 -> 3
+[2018/10/07 10:05:33:6672] USER: filter: Received 'd #B0DC51 115 177 166 283;'
+[2018/10/07 10:05:35:9723] USER: filter: Received 'd #E87CCD 9 192 106 235;'
+[2018/10/07 10:05:38:2784] USER: filter: Received 'd #E2A9E3 379 290 427 62;'
+[2018/10/07 10:05:39:5833] USER: filter: Received 'd #B127F8 52 126 60 226;'
+[2018/10/07 10:05:41:8908] USER: filter: Received 'd #0E0F76 429 267 8 11;'
+...
+```
+
+## ws proxy DBUS details
+
+### Fixed details
+
+Item|Value
+---|---
+Address|unix:abstract=org.libwebsockets.wsclientproxy
+Interface|org.libwebsockets.wsclientproxy
+Bus Name|org.libwebsockets.wsclientproxy
+Object path|/org/libwebsockets/wsclientproxy
+
+### Interface Methods
+
+Method|Arguments|Returns
+---|---|---
+Connect|s: ws URI, s: ws subprotocol name|"Bad Uri", "Connecting" or "Failed"
+Send|s: payload|Empty message if no problem, or error message
+
+When Connecting, the actual connection happens asynchronously if the initial
+connection attempt doesn't fail immediately.  If it's continuing in the
+background, the reply will have been "Connecting".
+
+### Signals
+
+Signal Name|Argument|Meaning
+---|---|---
+Receive|s: payload|Received data from the ws link
+Status|s: status|See table below
+
+Status String|Meaning
+---|---
+"ws client connection error"|The ws connection attempt ended with a fatal error
+"ws client connection established"|The ws connection attempt succeeded
+"ws client connection closed"|The ws connection has closed
+
diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/main.c b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/main.c
new file mode 100644 (file)
index 0000000..f926c80
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * lws-minimal-dbus-ws-proxy
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal session dbus server that uses the lws event loop,
+ * and allows proxying ws client connections via DBUS.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <libwebsockets.h>
+#include <libwebsockets/lws-dbus.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_dbus_ws_proxy.c"
+
+static int interrupted;
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/*
+ * we pass the dbus address to connect to proxy with from outside the
+ * protocol plugin... eg if built as a plugin for lwsws, you would instead
+ * set this pvo in the lwsws JSON config.
+ */
+
+static const struct lws_protocol_vhost_options pvo_ads = {
+       NULL,
+       NULL,
+       "ads",                          /* pvo name */
+       (void *)"unix:abstract=org.libwebsockets.wsclientproxy" /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,                           /* "next" pvo linked-list */
+       &pvo_ads,                       /* "child" pvo linked-list */
+       "lws-minimal-dbus-wsproxy",     /* protocol name we belong to on this vhost */
+       ""                              /* ignored */
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       static struct lws_context *context;
+       struct lws_context_creation_info info;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */ /* | LLL_THREAD */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS DBUS ws client proxy\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.ws_ping_pong_interval = 30;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       /* lws event loop (default poll one) */
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       lwsl_notice("Exiting cleanly\n");
+
+       return 0;
+}
diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/org.libwebsockets.wsclientproxy.conf b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/org.libwebsockets.wsclientproxy.conf
new file mode 100644 (file)
index 0000000..49e430b
--- /dev/null
@@ -0,0 +1,14 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+       <policy user="root">
+               <allow own="org.libwebsockets.wsclientproxy"/>
+               <allow send_destination="org.libwebsockets.wsclientproxy"/>
+       </policy>
+       <policy context="default">
+                <deny own="org.libwebsockets.wsclientproxy"/>
+                <deny send_destination="org.libwebsockets.wsclientproxy"/>
+        </policy>
+</busconfig>
+
diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/protocol_lws_minimal_dbus_ws_proxy.c b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/protocol_lws_minimal_dbus_ws_proxy.c
new file mode 100644 (file)
index 0000000..693acc7
--- /dev/null
@@ -0,0 +1,828 @@
+/*
+ * ws protocol handler plugin for dbus ws proxy
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This proxies outgoing ws client connections on DBUS.  So a DBUS client can
+ * reach out and get remote WS payloads in both directions.
+ *
+ * DEVELOPER NOTE
+ *
+ * Two worlds, dbus and ws, collide in this file.
+ *
+ * There main thing keeping it sane is both worlds are running in the same
+ * thread and on the same event loop.  Although things may happen completely
+ * asynchronously in both worlds, the logical reaction to those events are
+ * serialized in a single event loop doing one thing at a time.
+ *
+ * So while you are servicing an event in the ws world, you can be certain the
+ * logical state of any related dbus thing cannot change underneath you, until
+ * you return back to the event loop, and vice versa.  So other-world objects
+ * can't be freed, other-world handles can't close etc while you are servicing
+ * in your world.
+ *
+ * Since all bets are off what happens next, and in which world, after you
+ * return back to the event loop though, an additional rule is needed: worlds
+ * must not allocate in objects owned by the other world.  They must generate
+ * their own objects in their world and use those for allocations and state.
+ *
+ * For example in the dbus-world there is a struct lws_dbus_ctx_wsproxy with
+ * various state, but he is subject to deletion by events in dbus-world.  If
+ * the ws-world stored things there, they are subject to going out of scope
+ * at the whim of the dbus connection without the ws world hearing about it and
+ * cleanly deallocaing them.  So the ws world must keep his own pss that remains
+ * in scope until the ws link closes for allocations from ws-world.
+ *
+ * In this application there's a point of contact between the worlds, a ring
+ * buffer allocated in ws world when the ws connection is established, and
+ * deallocated when the ws connection is closed.  The DBUS world needs to put
+ * things in this ringbuffer.  But the way lws_ring works, when the message
+ * allocated in DBUS world is queued on the ringbuffer, the ringbuffer itself
+ * takes responsibility for deallocation.  So there is no problem.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#include <libwebsockets/lws-dbus.h>
+#endif
+
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+
+/*
+ * dbus accepted connections create these larger context structs that start
+ * with the lws dbus context
+ */
+
+struct vhd_dbus_proxy;
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+       char binary;
+       char first;
+       char final;
+};
+
+struct pss_dbus_proxy {
+       struct lws_ring *ring_out;
+       uint32_t ring_out_tail;
+};
+
+struct lws_dbus_ctx_wsproxy {
+       struct lws_dbus_ctx ctx;
+
+       struct lws *cwsi;
+       struct vhd_dbus_proxy *vhd;
+       struct pss_dbus_proxy *pss;
+};
+
+struct vhd_dbus_proxy {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+
+       /*
+        * Because the listener ctx is composed in the vhd, we can always get a
+        * pointer to the outer vhd from a pointer to ctx_listener inside.
+        */
+       struct lws_dbus_ctx ctx_listener;
+       struct lws_dbus_ctx_wsproxy dctx;
+
+       const char *dbus_listen_ads;
+};
+
+#define THIS_INTERFACE "org.libwebsockets.wsclientproxy"
+#define THIS_OBJECT    "/org/libwebsockets/wsclientproxy"
+#define THIS_BUSNAME   "org.libwebsockets.wsclientproxy"
+static const char *version = "0.1";
+
+static const char *server_introspection_xml =
+       DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+       "<node>\n"
+       "  <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
+       "    <method name='Introspect'>\n"
+       "      <arg name='data' type='s' direction='out' />\n"
+       "    </method>\n"
+       "  </interface>\n"
+
+       "  <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
+       "    <method name='Get'>\n"
+       "      <arg name='interface' type='s' direction='in' />\n"
+       "      <arg name='property'  type='s' direction='in' />\n"
+       "      <arg name='value'     type='s' direction='out' />\n"
+       "    </method>\n"
+       "    <method name='GetAll'>\n"
+       "      <arg name='interface'  type='s'     direction='in'/>\n"
+       "      <arg name='properties' type='a{sv}' direction='out'/>\n"
+       "    </method>\n"
+       "  </interface>\n"
+
+       "  <interface name='"THIS_INTERFACE"'>\n"
+       "    <property name='Version' type='s' access='read' />\n"
+       "    <method name='Connect' >\n"
+       "      <arg name='url' type='s' direction='in' />\n"
+       "      <arg name='subprotocol' type='s' direction='in' />\n"
+       "    </method>\n"
+       "    <method name='Send'>\n"
+       "      <arg name='payload' type='s' direction='in' />\n"
+       "    </method>\n"
+       "    <signal name='Receive'>\n"
+       "    </signal>"
+       "    <signal name='Status'>\n"
+       "    </signal>"
+       "  </interface>\n"
+
+       "</node>\n";
+
+static void
+destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+/*
+ * DBUS WORLD
+ */
+
+static DBusHandlerResult
+dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       dbus_message_append_args(*reply,
+                                DBUS_TYPE_STRING, &server_introspection_xml,
+                                DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       const char *interface, *property;
+       DBusError err;
+
+       dbus_error_init(&err);
+
+       if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface,
+                                           DBUS_TYPE_STRING, &property,
+                                           DBUS_TYPE_INVALID)) {
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, err.name, err.message);
+               dbus_error_free(&err);
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       if (strcmp(property, "Version")) /* Unknown property */
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+       dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version,
+                                        DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       DBusMessageIter arr, di, iter, va;
+       const char *property = "Version";
+
+       dbus_message_iter_init_append(*reply, &iter);
+       dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr);
+
+       /* Append all properties name/value pairs */
+       dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di);
+       dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property);
+       dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va);
+       dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version);
+       dbus_message_iter_close_container(&di, &va);
+       dbus_message_iter_close_container(&arr, &di);
+
+       dbus_message_iter_close_container(&iter, &arr);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+dmh_connect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
+       const char *prot = "", *ads = "", *path = "", *baduri = "Bad Uri",
+                  *connecting = "Connecting", *failed = "Failed", **pp;
+       struct lws_client_connect_info i;
+       char host[128], uri_copy[512];
+       const char *uri, *subprotocol;
+       DBusError err;
+       int port = 0;
+
+       dbus_error_init(&err);
+
+       if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &uri,
+                                           DBUS_TYPE_STRING, &subprotocol,
+                                           DBUS_TYPE_INVALID)) {
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, err.name, err.message);
+               dbus_error_free(&err);
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       strncpy(uri_copy, uri, sizeof(uri_copy) - 1);
+       uri_copy[sizeof(uri_copy) - 1] = '\0';
+
+       if (lws_parse_uri(uri_copy, &prot, &ads, &port, &path)) {
+               pp = &baduri;
+               goto send_reply;
+       }
+
+       lws_snprintf(host, sizeof(host), "%s:%u", ads, port);
+
+       memset(&i, 0, sizeof(i));
+
+       assert(wspctx);
+       assert(wspctx->vhd);
+
+       i.context = wspctx->vhd->context;
+       i.port = port;
+       i.address = ads;
+       i.path = path;
+       i.host = host;
+       i.origin = host;
+       i.ssl_connection = !strcmp(prot, "https") || !strcmp(prot, "wss");
+       i.vhost = wspctx->ctx.vh;
+       i.protocol = subprotocol;
+       i.local_protocol_name = "lws-minimal-dbus-wsproxy";
+       i.pwsi = &wspctx->cwsi;
+
+       lwsl_user("%s: connecting to %s://%s:%d%s\n", __func__, prot,
+                       i.address, i.port, i.path);
+
+       if (!lws_client_connect_via_info(&i)) {
+               lwsl_notice("%s: client connect failed\n", __func__);
+               pp = &failed;
+               goto send_reply;
+       }
+
+       lws_set_opaque_parent_data(wspctx->cwsi, wspctx);
+       lwsl_notice("%s: client connecting...\n", __func__);
+       pp = &connecting;
+
+send_reply:
+       dbus_message_append_args(*reply, DBUS_TYPE_STRING, pp,
+                                        DBUS_TYPE_INVALID);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static int
+issue_dbus_signal(struct lws *wsi, const char *signame, const char *string)
+{
+       struct lws_dbus_ctx_wsproxy *wspctx =
+                       lws_get_opaque_parent_data(wsi);
+       DBusMessage *m;
+
+       if (!wspctx)
+               return 1;
+
+       m = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE, signame);
+       if (!m) {
+               lwsl_err("%s: new signal failed\n", __func__);
+               return 1;
+       }
+
+       dbus_message_append_args(m, DBUS_TYPE_STRING, &string,
+                                   DBUS_TYPE_INVALID);
+
+       if (!dbus_connection_send(wspctx->ctx.conn, m, NULL))
+               lwsl_err("%s: unable to send\n", __func__);
+
+       dbus_message_unref(m);
+
+       return 0;
+}
+
+static DBusHandlerResult
+dmh_send(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
+{
+       struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
+       const char *payload;
+       struct msg amsg;
+       DBusError err;
+
+       dbus_error_init(&err);
+
+       if (!wspctx->cwsi || !wspctx->pss) {
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, "Send Fail", "No ws conn");
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &payload,
+                                           DBUS_TYPE_INVALID)) {
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, err.name, err.message);
+               dbus_error_free(&err);
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+
+       /*
+        * we allocate on the ringbuffer in ws world, but responsibility for
+        * freeing it is understood by lws_ring.
+        */
+
+       amsg.len = strlen(payload);
+       /* notice we over-allocate by LWS_PRE */
+       amsg.payload = malloc(LWS_PRE + amsg.len);
+       if (!amsg.payload) {
+               lwsl_user("OOM: dropping\n");
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, "Send Fail", "OOM");
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+       amsg.binary = 0;
+       amsg.first = 1;
+       amsg.final = 1;
+
+       memcpy((char *)amsg.payload + LWS_PRE, payload, amsg.len);
+       if (!lws_ring_insert(wspctx->pss->ring_out, &amsg, 1)) {
+               destroy_message(&amsg);
+               lwsl_user("Ring Full!\n");
+               dbus_message_unref(*reply);
+               *reply = dbus_message_new_error(m, "Send Fail", "Ring full");
+
+               return DBUS_HANDLER_RESULT_HANDLED;
+       }
+       if (wspctx->cwsi)
+               lws_callback_on_writable(wspctx->cwsi);
+
+       return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+struct lws_dbus_methods {
+       const char *inter;
+       const char *call;
+       lws_dbus_message_handler handler;
+} meths[] = {
+       { DBUS_INTERFACE_INTROSPECTABLE, "Introspect",  dmh_introspect  },
+       { DBUS_INTERFACE_PROPERTIES,     "Get",         dmh_get         },
+       { DBUS_INTERFACE_PROPERTIES,     "GetAll",      dmh_getall      },
+       { THIS_INTERFACE,                "Connect",     dmh_connect     },
+       { THIS_INTERFACE,                "Send",        dmh_send        },
+};
+
+static DBusHandlerResult
+server_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
+{
+       struct lws_dbus_methods *mp = meths;
+        DBusMessage *reply = NULL;
+       DBusHandlerResult result;
+       size_t n;
+
+       assert(data);
+
+       lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
+               dbus_message_get_interface(message),
+               dbus_message_get_member(message),
+               dbus_message_get_path(message));
+
+       for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) {
+               if (dbus_message_is_method_call(message, mp->inter, mp->call)) {
+                       reply = dbus_message_new_method_return(message);
+                       if (!reply)
+                               return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+                       result = mp->handler(conn, message, &reply, data);
+
+                       if (result == DBUS_HANDLER_RESULT_HANDLED &&
+                           !dbus_connection_send(conn, reply, NULL))
+                               result = DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+                       dbus_message_unref(reply);
+
+                       return result;
+               }
+
+               mp++;
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static const DBusObjectPathVTable vtable = {
+       .message_function = server_message_handler
+};
+
+static void
+destroy_dbus_server_conn(struct lws_dbus_ctx_wsproxy *wsctx)
+{
+       if (!wsctx->ctx.conn)
+               return;
+
+       lwsl_notice("%s\n", __func__);
+
+       dbus_connection_unregister_object_path(wsctx->ctx.conn, THIS_OBJECT);
+       lws_dll2_remove(&wsctx->ctx.next);
+       dbus_connection_unref(wsctx->ctx.conn);
+}
+
+/*
+ * This is the client dbus side going away.  We need to stop the associated
+ * client ws part and make sure it can't dereference us now we are gone.
+ */
+
+static void
+cb_closing(struct lws_dbus_ctx *ctx)
+{
+       struct lws_dbus_ctx_wsproxy *wspctx =
+                       (struct lws_dbus_ctx_wsproxy *)ctx;
+       lwsl_err("%s: closing\n", __func__);
+
+       /*
+        * We have to take care that the associated proxy wsi knows our
+        * dbus ctx is going out of scope after we return from here.
+        *
+        * We do it by setting its pointer to our dbus ctx to NULL.
+        */
+
+       if (wspctx->cwsi) {
+               lws_set_opaque_parent_data(wspctx->cwsi, NULL);
+               lws_set_timeout(wspctx->cwsi,
+                               PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE,
+                               LWS_TO_KILL_ASYNC);
+       }
+
+       destroy_dbus_server_conn(wspctx);
+
+       free(wspctx);
+}
+
+static void
+new_conn(DBusServer *server, DBusConnection *conn, void *d)
+{
+       struct lws_dbus_ctx_wsproxy *conn_wspctx, /* the new conn context */
+                                   /* the listener context */
+                                   *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
+       struct vhd_dbus_proxy *vhd = lws_container_of(d,
+                                       struct vhd_dbus_proxy, ctx_listener);
+
+       assert(vhd->vhost == wspctx->ctx.vh);
+
+       lwsl_notice("%s\n", __func__);
+
+       conn_wspctx = malloc(sizeof(*conn_wspctx));
+       if (!conn_wspctx)
+               return;
+
+       memset(conn_wspctx, 0, sizeof(*conn_wspctx));
+
+       conn_wspctx->ctx.tsi = wspctx->ctx.tsi;
+       conn_wspctx->ctx.vh = wspctx->ctx.vh;
+       conn_wspctx->ctx.conn = conn;
+       conn_wspctx->vhd = vhd; /* let accepted connections also know the vhd */
+
+       assert(conn_wspctx->vhd);
+
+       if (lws_dbus_connection_setup(&conn_wspctx->ctx, conn, cb_closing)) {
+               lwsl_err("%s: connection bind to lws failed\n", __func__);
+               goto bail;
+       }
+
+       if (!dbus_connection_register_object_path(conn, THIS_OBJECT, &vtable,
+                                                 conn_wspctx)) {
+               lwsl_err("%s: Failed to register object path\n", __func__);
+               goto bail;
+       }
+
+       lws_dll2_add_head(&conn_wspctx->ctx.next, &wspctx->ctx.owner);
+
+       /* we take on responsibility for explicit close / unref with this... */
+       dbus_connection_ref(conn);
+
+       return;
+
+bail:
+       free(conn_wspctx);
+}
+
+static int
+create_dbus_listener(struct vhd_dbus_proxy *vhd, int tsi)
+{
+       DBusError e;
+
+        dbus_error_init(&e);
+#if 0
+        vhd->dctx.ctx.tsi = tsi;
+        vhd->dctx.ctx.vh = vhd->vhost;
+        vhd->dctx.ctx.next.prev = NULL;
+        vhd->dctx.ctx.next.next = NULL;
+        vhd->dctx.vhd = vhd;
+        vhd->dctx.cwsi = NULL;
+
+       /* connect to the SYSTEM bus */
+
+       vhd->dctx.ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e);
+       if (!vhd->dctx.ctx.conn) {
+               lwsl_notice("%s: Failed to get a session DBus connection: '%s'"
+                           ", continuing with daemon listener only\n",
+                        __func__, e.message);
+               dbus_error_free(&e);
+               dbus_error_init(&e);
+               goto daemon;
+       }
+
+       /*
+        * by default dbus will call exit() when this connection closes...
+        * we have to shut down other things cleanly, so disable that
+        */
+       dbus_connection_set_exit_on_disconnect(vhd->dctx.ctx.conn, 0);
+
+       if (dbus_bus_request_name(vhd->dctx.ctx.conn, THIS_BUSNAME,
+                                 DBUS_NAME_FLAG_REPLACE_EXISTING, &e) !=
+                                       DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+               lwsl_notice("%s: Failed to request name on bus: '%s',"
+                        " continuing with daemon listener only\n",
+                        __func__, e.message);
+               dbus_connection_unref(vhd->dctx.ctx.conn);
+               vhd->dctx.ctx.conn = NULL;
+               dbus_error_free(&e);
+               dbus_error_init(&e);
+               goto daemon;
+       }
+
+       if (!dbus_connection_register_object_path(vhd->dctx.ctx.conn,
+                                                 THIS_OBJECT, &vtable,
+                                                 &vhd->dctx)) {
+               lwsl_err("%s: Failed to register object path\n", __func__);
+               goto fail;
+       }
+
+       /*
+        * This is the part that binds the connection to lws watcher and
+        * timeout handling provided by lws
+        */
+
+       if (lws_dbus_connection_setup(&vhd->dctx.ctx, vhd->dctx.ctx.conn,
+                                     cb_closing)) {
+               lwsl_err("%s: connection bind to lws failed\n", __func__);
+               goto fail;
+       }
+
+daemon:
+#endif
+        vhd->ctx_listener.vh = vhd->vhost;
+        vhd->ctx_listener.tsi = tsi;
+
+       if (!lws_dbus_server_listen(&vhd->ctx_listener, vhd->dbus_listen_ads,
+                                   &e, new_conn)) {
+               lwsl_err("%s: failed\n", __func__);
+               dbus_error_free(&e);
+
+               return 1;
+       }
+
+       lwsl_notice("%s: created DBUS listener on %s\n", __func__,
+                       vhd->dbus_listen_ads);
+
+       return 0;
+#if 0
+fail:
+       dbus_error_free(&e);
+
+       return 1;
+#endif
+}
+
+static void
+destroy_dbus_server_listener(struct vhd_dbus_proxy *vhd)
+{
+       dbus_server_disconnect(vhd->ctx_listener.dbs);
+
+       lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
+                       vhd->ctx_listener.owner.head) {
+               struct lws_dbus_ctx *r = lws_container_of(rdt,
+                                               struct lws_dbus_ctx, next);
+
+               dbus_connection_close(r->conn);
+               dbus_connection_unref(r->conn);
+               free(r);
+       } lws_end_foreach_dll_safe(rdt, nx);
+
+       if (vhd->dctx.ctx.conn)
+               dbus_connection_unref(vhd->dctx.ctx.conn);
+       dbus_server_unref(vhd->ctx_listener.dbs);
+}
+
+/*
+ * WS WORLD
+ */
+
+static int
+callback_minimal_dbus_wsproxy(struct lws *wsi, enum lws_callback_reasons reason,
+                             void *user, void *in, size_t len)
+{
+       struct pss_dbus_proxy *pss = (struct pss_dbus_proxy *)user;
+       struct vhd_dbus_proxy *vhd = (struct vhd_dbus_proxy *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                lws_get_protocol(wsi));
+       struct lws_dbus_ctx_wsproxy *wspctx;
+       const struct msg *pmsg;
+       int flags, m;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi), sizeof(*vhd));
+               if (!vhd)
+                       return -1;
+
+               vhd->context = lws_get_context(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               if (lws_pvo_get_str(in, "ads", &vhd->dbus_listen_ads)) {
+                       lwsl_err("%s: pvo 'ads' must be set\n", __func__);
+                       return -1;
+               }
+
+               if (create_dbus_listener(vhd, 0)) {
+                       lwsl_err("%s: create_dbus_listener failed\n", __func__);
+                       return -1;
+               }
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               destroy_dbus_server_listener(vhd);
+               /* this is required for valgrind-cleanliness */
+               dbus_shutdown();
+               break;
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED\n");
+
+               /*
+                * create the send ringbuffer now the ws connection is
+                * established.
+                */
+
+               wspctx = lws_get_opaque_parent_data(wsi);
+               if (!wspctx)
+                       break;
+
+               wspctx->pss = pss;
+               pss->ring_out_tail = 0;
+               pss->ring_out = lws_ring_create(sizeof(struct msg), 8,
+                                                  destroy_message);
+               if (!pss->ring_out) {
+                       lwsl_err("OOM\n");
+                       return -1;
+               }
+
+               issue_dbus_signal(wsi, "Status",
+                                 "ws client connection established");
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+               lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE:\n");
+
+               pmsg = lws_ring_get_element(pss->ring_out, &pss->ring_out_tail);
+               if (!pmsg) {
+                       lwsl_user(" (nothing in ring)\n");
+                       break;
+               }
+
+               flags = lws_write_ws_flags(
+                           pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+                           pmsg->first, pmsg->final);
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
+                             pmsg->len, flags);
+               if (m < (int)pmsg->len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
+                               m, flags, pmsg->first, pmsg->final);
+
+               lws_ring_consume_single_tail(pss->ring_out,
+                                            &pss->ring_out_tail, 1);
+
+               /* more to do for us? */
+               if (lws_ring_get_element(pss->ring_out, &pss->ring_out_tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(wsi);
+
+               break;
+
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+
+               lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d "
+                         "(rpp %5d, first %d, last %d, bin %d)\n",
+                         (int)len, (int)lws_remaining_packet_payload(wsi),
+                         lws_is_first_fragment(wsi),
+                         lws_is_final_fragment(wsi),
+                         lws_frame_is_binary(wsi));
+
+               {
+                       char strbuf[256];
+                       int l = len;
+
+                       if (l > (int)sizeof(strbuf) - 1)
+                               l = sizeof(strbuf) - 1;
+
+                       memcpy(strbuf, in, l);
+                       strbuf[l] = '\0';
+
+                       issue_dbus_signal(wsi, "Receive", strbuf);
+               }
+               break;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               issue_dbus_signal(wsi, "Status", "ws client connection error");
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               lwsl_err("LWS_CALLBACK_CLIENT_CLOSED ()\n");
+               issue_dbus_signal(wsi, "Status", "ws client connection closed");
+
+               /* destroy any ringbuffer and pending messages */
+
+               lws_ring_destroy(pss->ring_out);
+
+               wspctx = lws_get_opaque_parent_data(wsi);
+               if (!wspctx)
+                       break;
+
+               /*
+                * the wspctx cannot refer to its child wsi any longer, it is
+                * about to go out of scope.
+                */
+
+               wspctx->cwsi = NULL;
+               wspctx->pss = NULL;
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY \
+       { \
+               "lws-minimal-dbus-wsproxy", \
+               callback_minimal_dbus_wsproxy, \
+               sizeof(struct pss_dbus_proxy), \
+               1024, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_dbus_wsproxy(struct lws_context *context,
+                              struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_dbus_wsproxy(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/http-client/README.md b/minimal-examples/http-client/README.md
new file mode 100644 (file)
index 0000000..fc56f6c
--- /dev/null
@@ -0,0 +1,8 @@
+|name|demonstrates|
+---|---
+minimal-http-client-certinfo|Shows how to gain detailed information on the peer certificate
+minimal-http-client-custom-headers|Shows how to send and receive custom headers (h1 only)
+minimal-http-client-hugeurl|Sends a > 2.5KB URL to warmcat.com
+minimal-http-client-multi|Connects to and reads https://warmcat.com, 8 times concurrently
+minimal-http-client-post|POSTs a form containing an uploaded file and a form variable, and captures the response
+minimal-http-client|Connects to and reads https://warmcat.com
diff --git a/minimal-examples/http-client/minimal-http-client-certinfo/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-certinfo/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1bc44df
--- /dev/null
@@ -0,0 +1,80 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-client-certinfo)
+set(SRCS minimal-http-client-certinfo.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-client/minimal-http-client-certinfo/README.md b/minimal-examples/http-client/minimal-http-client-certinfo/README.md
new file mode 100644 (file)
index 0000000..ff6ada4
--- /dev/null
@@ -0,0 +1,77 @@
+# lws minimal http client certinfo
+
+This demonstrates how to dump information from the peer
+certificate largely independent of the tls backend.
+
+The application goes to https://warmcat.com and receives the page data.
+
+Before receiving the page it dumps information on the server's cert.
+
+This works independently of the tls backend being OpenSSL or mbedTLS.
+
+However the public keys cannot be compared between the two tls
+backends, since they produce different representations.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-l| Connect to https://localhost:7681 and accept selfsigned cert
+--h1|Specify http/1.1 only using ALPN, rejects h2 even if server supports it
+
+```
+ $ ./lws-minimal-http-client-certinfo
+[2018/04/05 21:39:26:5882] USER: LWS minimal http client
+[2018/04/05 21:39:26:5897] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 on
+[2018/04/05 21:39:26:5955] NOTICE: created client ssl context for default
+[2018/04/05 21:39:28:0824] NOTICE: lws_http_client_http_response 200
+[2018/04/05 21:39:28:0824] NOTICE:  Peer Cert CN        : warmcat.com
+[2018/04/05 21:39:28:0824] NOTICE:  Peer Cert issuer    : /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited
+[2018/04/05 21:39:28:0825] NOTICE:  Peer Cert Valid from: Mon Nov  3 00:00:00 2014
+[2018/04/05 21:39:28:0825] NOTICE:  Peer Cert Valid to  : Sat Nov  2 23:59:59 2019
+[2018/04/05 21:39:28:0825] NOTICE:  Peer Cert usage bits: 0xa0
+[2018/04/05 21:39:28:0825] NOTICE:  Peer Cert public key:
+[2018/04/05 21:39:28:0825] NOTICE: 
+[2018/04/05 21:39:28:0825] NOTICE: 0000: 30 82 01 22 30 0D 06 09 2A 86 48 86 F7 0D 01 01    0.."0...*.H.....
+[2018/04/05 21:39:28:0825] NOTICE: 0010: 01 05 00 03 82 01 0F 00 30 82 01 0A 02 82 01 01    ........0.......
+[2018/04/05 21:39:28:0825] NOTICE: 0020: 00 EC 39 C1 98 25 A8 99 AC 01 9B D2 16 C0 CA A3    ..9..%..........
+[2018/04/05 21:39:28:0825] NOTICE: 0030: 0E 19 57 E5 3D 23 F3 79 7E 63 BF CD B8 88 D1 16    ..W.=#.y~c......
+[2018/04/05 21:39:28:0825] NOTICE: 0040: C6 F0 A6 ED 66 CB F3 C3 D6 7E A7 A3 AB 00 0A 3E    ....f....~.....>
+[2018/04/05 21:39:28:0825] NOTICE: 0050: AD EF 20 44 85 5A 61 F0 71 20 BD E3 D1 4B B6 53    .. D.Za.q ...K.S
+[2018/04/05 21:39:28:0825] NOTICE: 0060: 57 AA 81 E6 ED 74 36 40 E7 FC 62 24 AD E8 82 1D    W....t6@..b$....
+[2018/04/05 21:39:28:0826] NOTICE: 0070: 89 C4 3D 64 6C A8 34 4B DB FB DD 7D D2 2D FB 86    ..=dl.4K...}.-..
+[2018/04/05 21:39:28:0826] NOTICE: 0080: 97 EA 6B E2 C9 39 D6 19 DE A8 90 E7 86 8F CF 0A    ..k..9..........
+[2018/04/05 21:39:28:0826] NOTICE: 0090: CD 09 3C AF FB 0A FF 85 E8 93 D1 4B A0 C5 21 AD    ..<........K..!.
+[2018/04/05 21:39:28:0826] NOTICE: 00A0: 58 52 30 0E 4B FE 4F C8 01 B9 BD 0F D4 E4 64 7B    XR0.K.O.......d{
+[2018/04/05 21:39:28:0826] NOTICE: 00B0: 04 B4 D2 68 69 8F F1 D5 FD B0 1A CE 55 43 08 B7    ...hi.......UC..
+[2018/04/05 21:39:28:0826] NOTICE: 00C0: 9F 57 0D 4E E1 CA E8 5C B4 2A 6B AB 05 B5 57 67    .W.N...\.*k...Wg
+[2018/04/05 21:39:28:0826] NOTICE: 00D0: B8 FD 20 F4 4F 6B 0E 47 7C AD EB B4 99 2C 9B 53    .. .Ok.G|....,.S
+[2018/04/05 21:39:28:0826] NOTICE: 00E0: DF EA 67 8D 8A 9D A7 17 01 F9 4E BD 56 43 50 53    ..g.......N.VCPS
+[2018/04/05 21:39:28:0826] NOTICE: 00F0: 08 4E FE 6A 85 4A 4D 45 03 DA 01 00 96 7A C0 A9    .N.j.JME.....z..
+[2018/04/05 21:39:28:0826] NOTICE: 0100: C2 32 5E 1A 9F 6F 7B E2 02 5E 70 12 D3 8E 76 6A    .2^..o{..^p...vj
+[2018/04/05 21:39:28:0826] NOTICE: 0110: 0B 59 A4 D7 31 9D C6 86 08 53 2E 02 8A 1E B1 FB    .Y..1....S......
+[2018/04/05 21:39:28:0826] NOTICE: 0120: 7B 02 03 01 00 01                                  {.....          
+[2018/04/05 21:39:28:0826] NOTICE: 
+[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 503
+[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+[2018/04/05 21:39:28:0829] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+...
+[2018/04/05 21:39:28:3777] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+[2018/04/05 21:39:28:3777] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 503
+[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 512
+[2018/04/05 21:39:28:3778] USER: RECEIVE_CLIENT_HTTP_READ: read 471
+[2018/04/05 21:39:28:3778] USER: LWS_CALLBACK_COMPLETED_CLIENT_HTTP
+[2018/04/05 21:39:28:3787] USER: Completed
+```
+
+
diff --git a/minimal-examples/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c b/minimal-examples/http-client/minimal-http-client-certinfo/minimal-http-client-certinfo.c
new file mode 100644 (file)
index 0000000..99454f1
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * lws-minimal-http-client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal http client using lws.
+ *
+ * It visits https://warmcat.com/ and receives the html page there.  You
+ * can dump the page data by changing the #if 0 below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted, bad = 1, status;
+static struct lws *client_wsi;
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       uint8_t buf[1280];
+       union lws_tls_cert_info_results *ci =
+               (union lws_tls_cert_info_results *)buf;
+
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi = NULL;
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               status = lws_http_client_http_response(wsi);
+               lwsl_notice("lws_http_client_http_response %d\n", status);
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME,
+                                           ci, sizeof(buf) - sizeof(*ci)))
+                       lwsl_notice(" Peer Cert CN        : %s\n", ci->ns.name);
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_ISSUER_NAME,
+                                           ci, sizeof(ci->ns.name)))
+                       lwsl_notice(" Peer Cert issuer    : %s\n", ci->ns.name);
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_FROM,
+                                           ci, 0))
+                       lwsl_notice(" Peer Cert Valid from: %s", ctime(&ci->time));
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_TO,
+                                           ci, 0))
+                       lwsl_notice(" Peer Cert Valid to  : %s", ctime(&ci->time));
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_USAGE,
+                                           ci, 0))
+                       lwsl_notice(" Peer Cert usage bits: 0x%x\n", ci->usage);
+               if (!lws_tls_peer_cert_info(wsi,
+                                           LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY,
+                                           ci, sizeof(buf) - sizeof(*ci))) {
+                       lwsl_notice(" Peer Cert public key:\n");
+                       lwsl_hexdump_notice(ci->ns.name, ci->ns.len);
+               }
+               break;
+
+       /* chunks of chunked content, with header removed */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
+#if 0  /* enable to dump the html */
+               {
+                       const char *p = in;
+
+                       while (len--)
+                               if (*p < 0x7f)
+                                       putchar(*p++);
+                               else
+                                       putchar('.');
+               }
+#endif
+               return 0; /* don't passthru */
+
+       /* uninterpreted http content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char buffer[1024 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "http",
+               callback_http,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_client_connect_info i;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                  /*
+                   * For LLL_ verbosity above NOTICE to be built into lws,
+                   * lws must have been configured and built with
+                   * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
+                   *
+                   * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
+                   *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
+                   */ ;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http client [<-d <verbosity>] [-l] [--h1]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./warmcat.com.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+       i.context = context;
+       i.ssl_connection = LCCSCF_USE_SSL;
+
+       if (lws_cmdline_option(argc, argv, "-l")) {
+               i.port = 7681;
+               i.address = "localhost";
+               i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       } else {
+               i.port = 443;
+               i.address = "warmcat.com";
+       }
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+
+       /* force h1 even if h2 available */
+       if (lws_cmdline_option(argc, argv, "--h1"))
+               i.alpn = "http/1.1";
+
+       i.method = "GET";
+
+       i.protocol = protocols[0].name;
+       i.pwsi = &client_wsi;
+       lws_client_connect_via_info(&i);
+
+       while (n >= 0 && client_wsi && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
+
+       return bad;
+}
diff --git a/minimal-examples/http-client/minimal-http-client-certinfo/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client-certinfo/warmcat.com.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-client/minimal-http-client-custom-headers/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-custom-headers/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a81d45d
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-client-custom-headers)
+set(SRCS minimal-http-client-custom-headers.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-client/minimal-http-client-custom-headers/README.md b/minimal-examples/http-client/minimal-http-client-custom-headers/README.md
new file mode 100644 (file)
index 0000000..ac49a87
--- /dev/null
@@ -0,0 +1,45 @@
+# lws minimal http client custom headers
+
+This http client application shows how to send and receive custom headers.
+
+This currently only works on http 1, so the app forces that even if h2 enables.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-l| Connect to https://localhost:7681 and accept selfsigned cert
+-n|no TLS
+
+The app looks for a custom header "test-custom-header" sent by warmcat.com.
+
+```
+ $ ./lws-minimal-http-client-custom-headers
+[2019/03/11 05:46:45:7582] USER: LWS minimal http client Custom Headers [-d<verbosity>] [-l] [--h1]
+[2019/03/11 05:46:45:7671] NOTICE: created client ssl context for default
+[2019/03/11 05:46:46:7812] USER: Connected with server response: 200
+[2019/03/11 05:46:46:7812] NOTICE: callback_http: custom header: 'hello'
+[2019/03/11 05:46:46:7814] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+...
+```
+You can use the -n and -l to make this test app connect to localhost:7681 over http,
+and confirm the "dnt:1" header was sent either by tcpdump or by running the test
+server on :7681 with -d1151
+
+```
+[2019/03/11 05:48:53:6806] PARSER: WSI_TOKEN_NAME_PART 'd' 0x64 (role=0x20000000) wsi->lextable_pos=0
+[2019/03/11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART 'n' 0x6E (role=0x20000000) wsi->lextable_pos=567
+[2019/03/11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART 't' 0x74 (role=0x20000000) wsi->lextable_pos=-1
+[2019/03/11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART ' ' 0x20 (role=0x20000000) wsi->lextable_pos=-1
+[2019/03/11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART '1' 0x31 (role=0x20000000) wsi->lextable_pos=-1
+' 0x0D (role=0x20000000) wsi->lextable_pos=-1NAME_PART '
+[2019/03/11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART '
+```
+
diff --git a/minimal-examples/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c b/minimal-examples/http-client/minimal-http-client-custom-headers/minimal-http-client-custom-headers.c
new file mode 100644 (file)
index 0000000..377d970
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * lws-minimal-http-client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal http client using lws.
+ *
+ * It visits https://warmcat.com/ and receives the html page there.  You
+ * can dump the page data by changing the #if 0 below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted, bad = 1, status;
+static struct lws *client_wsi;
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       char val[32];
+       int n;
+
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi = NULL;
+               break;
+
+       case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+       {
+                unsigned char **p = (unsigned char **)in, *end = (*p) + len;
+
+                /*
+                 * How to send a custom header in the request to the server
+                 */
+
+                if (lws_add_http_header_by_name(wsi,
+                                (const unsigned char *)"dnt",
+                                (const unsigned char *)"1", 1, p, end))
+                        return -1;
+               break;
+       }
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               status = lws_http_client_http_response(wsi);
+               lwsl_user("Connected with server response: %d\n", status);
+
+               /*
+                * How to query custom headers (http 1.x only at the momemnt)
+                *
+                * warmcat.com sends a custom header "test-custom-header" for
+                * testing, it has the fixed value "hello".
+                */
+
+               n = lws_hdr_custom_length(wsi, "test-custom-header:", 19);
+               if (n < 0)
+                       lwsl_notice("%s: Can't find test-custom-header\n",
+                                   __func__);
+               else {
+                       if (lws_hdr_custom_copy(wsi, val, sizeof(val),
+                                               "test-custom-header:", 19) < 0)
+                               lwsl_notice("%s: custom header too long\n",
+                                           __func__);
+                       else
+                               lwsl_notice("%s: custom header: '%s'\n",
+                                               __func__, val);
+               }
+               break;
+
+       /* chunks of chunked content, with header removed */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
+#if 0  /* enable to dump the html */
+               {
+                       const char *p = in;
+
+                       while (len--)
+                               if (*p < 0x7f)
+                                       putchar(*p++);
+                               else
+                                       putchar('.');
+               }
+#endif
+               return 0; /* don't passthru */
+
+       /* uninterpreted http content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char buffer[1024 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "http",
+               callback_http,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_client_connect_info i;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                  /*
+                   * For LLL_ verbosity above NOTICE to be built into lws,
+                   * lws must have been configured and built with
+                   * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
+                   *
+                   * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
+                   *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
+                   */ ;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http client Custom Headers [-d<verbosity>] [-l] [--h1]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./warmcat.com.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+       i.context = context;
+
+       if (!lws_cmdline_option(argc, argv, "-n"))
+               i.ssl_connection = LCCSCF_USE_SSL;
+
+       if (lws_cmdline_option(argc, argv, "-l")) {
+               i.port = 7681;
+               i.address = "localhost";
+               i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       } else {
+               i.port = 443;
+               i.address = "warmcat.com";
+       }
+
+       /* currently custom headers receive only works with h1 */
+       i.alpn = "http/1.1";
+
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+       i.method = "GET";
+
+       i.protocol = protocols[0].name;
+       i.pwsi = &client_wsi;
+       lws_client_connect_via_info(&i);
+
+       while (n >= 0 && client_wsi && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
+
+       return bad;
+}
diff --git a/minimal-examples/http-client/minimal-http-client-custom-headers/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client-custom-headers/warmcat.com.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-client/minimal-http-client-hugeurl/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-hugeurl/CMakeLists.txt
new file mode 100644 (file)
index 0000000..22a3011
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-client-hugeurl)
+set(SRCS minimal-http-client-hugeurl.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-client/minimal-http-client-hugeurl/README.md b/minimal-examples/http-client/minimal-http-client-hugeurl/README.md
new file mode 100644 (file)
index 0000000..6a7d06d
--- /dev/null
@@ -0,0 +1,52 @@
+# lws minimal http client hugeurl
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+The application goes to https://warmcat.com/?fakeparam=<2KB> and receives the page data.
+
+```
+ $ ./lws-minimal-http-client
+[2018/03/04 14:43:20:8562] USER: LWS minimal http client hugeurl
+[2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on
+[2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default
+[2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com
+[2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com
+[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520
+[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 974
+[2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1
+[2018/03/04 14:43:23:3042] USER: Completed
+```
+
+
diff --git a/minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c b/minimal-examples/http-client/minimal-http-client-hugeurl/minimal-http-client-hugeurl.c
new file mode 100644 (file)
index 0000000..174ddb9
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * lws-minimal-http-client hugeurl
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal http client using lws.
+ *
+ * It visits https://warmcat.com/?fakeparam=<2KB> and receives the html
+ * page there.  You can dump the page data by changing the #if 0 below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted, bad = 1, status;
+static struct lws *client_wsi;
+
+static const char * const uri =
+       "/?fakeparam="
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000"
+       "00000000000000000000000000000000000000000000000000" /* 500 */
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111"
+       "11111111111111111111111111111111111111111111111111" /* 1000 */
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222"
+       "22222222222222222222222222222222222222222222222222" /* 1500 */
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333"
+       "33333333333333333333333333333333333333333333333333" /* 2000 */
+;
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi = NULL;
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               status = lws_http_client_http_response(wsi);
+               lwsl_user("Connected with server response: %d\n", status);
+               break;
+
+       /* chunks of chunked content, with header removed */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
+#if 0  /* enable to dump the html */
+               {
+                       const char *p = in;
+
+                       while (len--)
+                               if (*p < 0x7f)
+                                       putchar(*p++);
+                               else
+                                       putchar('.');
+               }
+#endif
+               return 0; /* don't passthru */
+
+       /* uninterpreted http content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char buffer[1024 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "http",
+               callback_http,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_client_connect_info i;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       signal(SIGINT, sigint_handler);
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http client hugeurl [-d <verbosity>] [-l] [--h1]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./warmcat.com.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+       i.context = context;
+       i.ssl_connection = LCCSCF_USE_SSL;
+
+       if (lws_cmdline_option(argc, argv, "-l")) {
+               i.port = 7681;
+               i.address = "localhost";
+               i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       } else {
+               i.port = 443;
+               i.address = "warmcat.com";
+       }
+
+       if (lws_cmdline_option(argc, argv, "--h1"))
+               i.alpn = "http/1.1";
+
+       i.path = uri;
+       i.host = i.address;
+       i.origin = i.address;
+       i.method = "GET";
+       i.protocol = protocols[0].name;
+       i.pwsi = &client_wsi;
+
+       lws_client_connect_via_info(&i);
+
+       while (n >= 0 && client_wsi && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed: %s\n", bad? "failed": "OK");
+
+       return bad;
+}
diff --git a/minimal-examples/http-client/minimal-http-client-hugeurl/selftest.sh b/minimal-examples/http-client/minimal-http-client-hugeurl/selftest.sh
new file mode 100755 (executable)
index 0000000..2da54b6
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=6
+
+dotest $1 $2 warmcat
+dotest $1 $2 warmcat-h1 --h1
+
+spawn "" $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost -l
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-h1 -l --h1
+kill $SPID 2>/dev/null
+wait $SPID 2>/dev/null
+
+
+if [ -z "$TRAVIS_OS_NAME" ] ; then
+       SPID=""
+       spawn "" $5/http-server/minimal-http-server-eventlib $1/lws-minimal-http-server-eventlib --uv -s
+       dotest $1 $2 localhost-suv -l
+       spawn $SPID $5/http-server/minimal-http-server-eventlib $1/lws-minimal-http-server-eventlib --uv -s
+       dotest $1 $2 localhost-suv-h1 -l --h1
+
+       kill $SPID 2>/dev/null
+       wait $SPID 2>/dev/null
+fi
+
+exit $FAILS
+
+
diff --git a/minimal-examples/http-client/minimal-http-client-hugeurl/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client-hugeurl/warmcat.com.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-client/minimal-http-client-multi/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-multi/CMakeLists.txt
new file mode 100644 (file)
index 0000000..be0314e
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-client-multi)
+set(SRCS minimal-http-client-multi.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-client/minimal-http-client-multi/README.md b/minimal-examples/http-client/minimal-http-client-multi/README.md
new file mode 100644 (file)
index 0000000..7eaac9d
--- /dev/null
@@ -0,0 +1,25 @@
+# lws minimal http client multi
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+The application goes to https://warmcat.com and receives the page data
+same as minimal http client.
+
+However it does it for 8 client connections concurrently.
+
+## Commandline Options
+
+Option|Meaning
+---|---
+-s|Stagger the connections by 100ms, the last by 1s
+-p|Use http/1.1 pipelining or h2 simultaneous streams
+--h1|Force http/1 only
+-l|Connect to server on https://localhost:7681 instead of https://warmcat.com:443
+-n|Read numbered files like /1.png, /2.png etc.  Default is just read /
+
diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c
new file mode 100644 (file)
index 0000000..de348f6
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * lws-minimal-http-client-multi
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal http client using lws, which makes
+ * 8 downloads simultaneously from warmcat.com.
+ *
+ * Currently that takes the form of 8 individual simultaneous tcp and
+ * tls connections, which happen concurrently.  Notice that the ordering
+ * of the returned payload may be intermingled for the various connections.
+ *
+ * By default the connections happen all together at the beginning and operate
+ * concurrently, which is fast.  However this is resource-intenstive, there are
+ * 8 tcp connections, 8 tls tunnels on both the client and server.  You can
+ * instead opt to have the connections happen one after the other inside a
+ * single tcp connection and tls tunnel, using HTTP/1.1 pipelining.  To be
+ * eligible to be pipelined on another existing connection to the same server,
+ * the client connection must have the LCCSCF_PIPELINE flag on its
+ * info.ssl_connection member (this is independent of whether the connection
+ * is in ssl mode or not).
+ *
+ * HTTP/1.0: Pipelining only possible if Keep-Alive: yes sent by server
+ * HTTP/1.1: always possible... serializes requests
+ * HTTP/2:   always possible... all requests sent as individual streams in parallel
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <assert.h>
+#include <time.h>
+
+#define COUNT 8
+
+struct user {
+       int index;
+};
+
+static int interrupted, completed, failed, numbered, stagger_idx;
+static struct lws *client_wsi[COUNT];
+static struct user user[COUNT];
+static lws_sorted_usec_list_t sul_stagger;
+static struct lws_client_connect_info i;
+struct lws_context *context;
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       struct user *u = (struct user *)user;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: resp %u\n",
+                               lws_http_client_http_response(wsi));
+               break;
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi[u->index] = NULL;
+               failed++;
+               if (++completed == COUNT) {
+                       lwsl_err("Done: failed: %d\n", failed);
+                       interrupted = 1;
+               }
+               break;
+
+       /* chunks of chunked content, with header removed */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n",
+                       u->index, (int)len);
+#if 0  /* enable to dump the html */
+               {
+                       const char *p = in;
+
+                       while (len--)
+                               if (*p < 0x7f)
+                                       putchar(*p++);
+                               else
+                                       putchar('.');
+               }
+#endif
+               return 0; /* don't passthru */
+
+       /* uninterpreted http content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char buffer[1024 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %p: idx %d\n",
+                         wsi, u->index);
+               client_wsi[u->index] = NULL;
+               if (++completed == COUNT) {
+                       if (!failed)
+                               lwsl_user("Done: all OK\n");
+                       else
+                               lwsl_err("Done: failed: %d\n", failed);
+                       interrupted = 1;
+                       /* so we exit immediately */
+                       lws_cancel_service(lws_get_context(wsi));
+               }
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               if (u && client_wsi[u->index]) {
+                       /*
+                        * If it completed normally, it will have been set to
+                        * NULL then already.  So we are dealing with an
+                        * abnormal, failing, close
+                        */
+                       client_wsi[u->index] = NULL;
+                       failed++;
+                       if (++completed == COUNT) {
+                               lwsl_err("Done: failed: %d\n", failed);
+                               interrupted = 1;
+                       }
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       { "http", callback_http, 0, 0, },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+#if defined(WIN32)
+int gettimeofday(struct timeval * tp, struct timezone * tzp)
+{
+    // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's
+    // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
+    // until 00:00:00 January 1, 1970 
+    static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);
+
+    SYSTEMTIME  system_time;
+    FILETIME    file_time;
+    uint64_t    time;
+
+    GetSystemTime( &system_time );
+    SystemTimeToFileTime( &system_time, &file_time );
+    time =  ((uint64_t)file_time.dwLowDateTime )      ;
+    time += ((uint64_t)file_time.dwHighDateTime) << 32;
+
+    tp->tv_sec  = (long) ((time - EPOCH) / 10000000L);
+    tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
+    return 0;
+}
+#endif
+
+unsigned long long us(void)
+{
+       struct timeval t;
+
+       gettimeofday(&t, NULL);
+
+       return (t.tv_sec * 1000000ull) + t.tv_usec;
+}
+
+static void
+lws_try_client_connection(struct lws_client_connect_info *i, int m)
+{
+       char path[128];
+
+       if (numbered) {
+               lws_snprintf(path, sizeof(path), "/%d.png", m + 1);
+               i->path = path;
+       } else
+               i->path = "/";
+
+       i->pwsi = &client_wsi[m];
+       user[m].index = m;
+       i->userdata = &user[m];
+
+       if (!lws_client_connect_via_info(i)) {
+               failed++;
+               if (++completed == COUNT) {
+                       lwsl_user("Done: failed: %d\n", failed);
+                       interrupted = 1;
+               }
+       } else
+               lwsl_user("started connection %p: idx %d (%s)\n",
+                         client_wsi[m], m, i->path);
+}
+
+static void
+stagger_cb(lws_sorted_usec_list_t *sul)
+{
+       lws_usec_t next;
+
+       /*
+        * open the connections at 100ms intervals, with the
+        * last one being after 1s, testing both queuing, and
+        * direct H2 stream addition stability
+        */
+       lws_try_client_connection(&i, stagger_idx++);
+
+       if (stagger_idx == (int)LWS_ARRAY_SIZE(client_wsi))
+               return;
+
+       next = 300 * LWS_US_PER_MS;
+       if (stagger_idx == (int)LWS_ARRAY_SIZE(client_wsi) - 1)
+               next += 700 * LWS_US_PER_MS;
+
+       lws_sul_schedule(context, 0, &sul_stagger, stagger_cb, next);
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       unsigned long long start;
+       const char *p;
+       int n = 0, m, staggered = 0, logs =
+               LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+               /* for LLL_ verbosity above NOTICE to be built into lws,
+                * lws must have been configured and built with
+                * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+               /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+               /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+               /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+
+       staggered = !!lws_cmdline_option(argc, argv, "-s");
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http client [-s (staggered)] [-p (pipeline)]\n");
+       lwsl_user("   [--h1 (http/1 only)] [-l (localhost)] [-d <logs>]\n");
+       lwsl_user("   [-n (numbered)]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * COUNT client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and COUNT + 1 (allowing for h2
+        * network wsi) that we will use.
+        */
+       info.fd_limit_per_thread = 1 + COUNT + 1;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./warmcat.com.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       i.context = context;
+       i.ssl_connection = LCCSCF_USE_SSL;
+
+       /* enables h1 or h2 connection sharing */
+       if (lws_cmdline_option(argc, argv, "-p"))
+               i.ssl_connection |= LCCSCF_PIPELINE;
+
+       /* force h1 even if h2 available */
+       if (lws_cmdline_option(argc, argv, "--h1"))
+               i.alpn = "http/1.1";
+
+       if (lws_cmdline_option(argc, argv, "-l")) {
+               i.port = 7681;
+               i.address = "localhost";
+               i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       } else {
+               i.port = 443;
+               i.address = "warmcat.com";
+       }
+
+       if (lws_cmdline_option(argc, argv, "-n"))
+               numbered = 1;
+
+       if ((p = lws_cmdline_option(argc, argv, "--port")))
+               i.port = atoi(p);
+
+       i.host = i.address;
+       i.origin = i.address;
+       i.method = "GET";
+       i.protocol = protocols[0].name;
+
+       if (!staggered)
+               /*
+                * just pile on all the connections at once, testing the
+                * pipeline queuing before the first is connected
+                */
+               for (m = 0; m < (int)LWS_ARRAY_SIZE(client_wsi); m++)
+                       lws_try_client_connection(&i, m);
+       else
+               /*
+                * delay the connections slightly
+                */
+               lws_sul_schedule(context, 0, &sul_stagger, stagger_cb,
+                                100 * LWS_US_PER_MS);
+
+       start = us();
+       m = 0;
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lwsl_user("Duration: %lldms\n", (us() - start) / 1000);
+       lws_context_destroy(context);
+
+       lwsl_user("Exiting with %d\n", failed || completed != COUNT);
+
+       return failed || completed != COUNT;
+}
diff --git a/minimal-examples/http-client/minimal-http-client-multi/selftest.sh b/minimal-examples/http-client/minimal-http-client-multi/selftest.sh
new file mode 100755 (executable)
index 0000000..49d61a1
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=16
+
+dotest $1 $2 warmcat
+dotest $1 $2 warmcat-pipe -p
+dotest $1 $2 warmcat-h1 --h1
+dotest $1 $2 warmcat-h1-pipe --h1 -p
+dotest $1 $2 warmcat-stag -s
+dotest $1 $2 warmcat-pipe-stag -p -s
+dotest $1 $2 warmcat-h1-stag --h1 -s
+dotest $1 $2 warmcat-h1-pipe-stag --h1 -p -s
+
+spawn "" $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost -l
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-pipe -l -p
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-h1 -l --h1
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-h1-pipe -l --h1 -p
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-stag -l -s
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-pipe-stag -l -p -s
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-h1-stag -l --h1 -s
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-h1-pipe-stag -l --h1 -p -s
+
+kill $SPID 2>/dev/null
+wait $SPID 2>/dev/null
+exit $FAILS
+
diff --git a/minimal-examples/http-client/minimal-http-client-multi/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client-multi/warmcat.com.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-client/minimal-http-client-post/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-post/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9fe8d51
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-client-post)
+set(SRCS minimal-http-client-post.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-client/minimal-http-client-post/README.md b/minimal-examples/http-client/minimal-http-client-post/README.md
new file mode 100644 (file)
index 0000000..9a7ff28
--- /dev/null
@@ -0,0 +1,74 @@
+# lws minimal http client POST
+
+This example demonstrates a multipart POST to
+
+https://libwebsockets.org/testserver/formtest
+
+setting both a form variable and uploading a
+short file.
+
+The result of the POST form processing is captured
+and displayed in a hexdump.
+
+This is programmatically POSTing to the same
+form you can access at
+
+https://libwebsockets.org/testserver
+
+in the "POST" tab with file upload.
+
+By default the client action occurs using http/2 if
+your lws was built with `-DLWS_WITH_HTTP2=1`.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-client-post
+[2018/04/03 13:13:10:7891] USER: LWS minimal http client - POST
+[2018/04/03 13:13:10:7905] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 on
+[2018/04/03 13:13:10:7984] NOTICE: created client ssl context for default
+[2018/04/03 13:13:12:8444] USER: LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER
+[2018/04/03 13:13:12:8444] USER: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE
+[2018/04/03 13:13:12:8445] USER: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE
+[2018/04/03 13:13:12:8445] USER: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE
+[2018/04/03 13:13:13:1437] USER: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE
+[2018/04/03 13:13:13:1440] USER: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE
+[2018/04/03 13:13:13:1440] USER: RECEIVE_CLIENT_HTTP_READ: read 402
+[2018/04/03 13:13:13:1441] NOTICE: 
+[2018/04/03 13:13:13:1441] NOTICE: 0000: 3C 68 74 6D 6C 3E 3C 62 6F 64 79 3E 3C 68 31 3E    <html><body><h1>
+[2018/04/03 13:13:13:1441] NOTICE: 0010: 46 6F 72 6D 20 72 65 73 75 6C 74 73 20 28 61 66    Form results (af
+[2018/04/03 13:13:13:1441] NOTICE: 0020: 74 65 72 20 75 72 6C 64 65 63 6F 64 69 6E 67 29    ter urldecoding)
+[2018/04/03 13:13:13:1441] NOTICE: 0030: 3C 2F 68 31 3E 3C 74 61 62 6C 65 3E 3C 74 72 3E    </h1><table><tr>
+[2018/04/03 13:13:13:1441] NOTICE: 0040: 3C 74 64 3E 4E 61 6D 65 3C 2F 74 64 3E 3C 74 64    <td>Name</td><td
+[2018/04/03 13:13:13:1441] NOTICE: 0050: 3E 4C 65 6E 67 74 68 3C 2F 74 64 3E 3C 74 64 3E    >Length</td><td>
+[2018/04/03 13:13:13:1441] NOTICE: 0060: 56 61 6C 75 65 3C 2F 74 64 3E 3C 2F 74 72 3E 3C    Value</td></tr><
+[2018/04/03 13:13:13:1441] NOTICE: 0070: 74 72 3E 3C 74 64 3E 3C 62 3E 74 65 78 74 3C 2F    tr><td><b>text</
+[2018/04/03 13:13:13:1441] NOTICE: 0080: 62 3E 3C 2F 74 64 3E 3C 74 64 3E 31 33 3C 2F 74    b></td><td>13</t
+[2018/04/03 13:13:13:1441] NOTICE: 0090: 64 3E 3C 74 64 3E 6D 79 20 74 65 78 74 20 66 69    d><td>my text fi
+[2018/04/03 13:13:13:1441] NOTICE: 00A0: 65 6C 64 3C 2F 74 64 3E 3C 2F 74 72 3E 3C 74 72    eld</td></tr><tr
+[2018/04/03 13:13:13:1441] NOTICE: 00B0: 3E 3C 74 64 3E 3C 62 3E 73 65 6E 64 3C 2F 62 3E    ><td><b>send</b>
+[2018/04/03 13:13:13:1441] NOTICE: 00C0: 3C 2F 74 64 3E 3C 74 64 3E 30 3C 2F 74 64 3E 3C    </td><td>0</td><
+[2018/04/03 13:13:13:1442] NOTICE: 00D0: 74 64 3E 4E 55 4C 4C 3C 2F 74 64 3E 3C 2F 74 72    td>NULL</td></tr
+[2018/04/03 13:13:13:1442] NOTICE: 00E0: 3E 3C 74 72 3E 3C 74 64 3E 3C 62 3E 66 69 6C 65    ><tr><td><b>file
+[2018/04/03 13:13:13:1442] NOTICE: 00F0: 3C 2F 62 3E 3C 2F 74 64 3E 3C 74 64 3E 30 3C 2F    </b></td><td>0</
+[2018/04/03 13:13:13:1442] NOTICE: 0100: 74 64 3E 3C 74 64 3E 4E 55 4C 4C 3C 2F 74 64 3E    td><td>NULL</td>
+[2018/04/03 13:13:13:1442] NOTICE: 0110: 3C 2F 74 72 3E 3C 74 72 3E 3C 74 64 3E 3C 62 3E    </tr><tr><td><b>
+[2018/04/03 13:13:13:1442] NOTICE: 0120: 75 70 6C 6F 61 64 3C 2F 62 3E 3C 2F 74 64 3E 3C    upload</b></td><
+[2018/04/03 13:13:13:1442] NOTICE: 0130: 74 64 3E 30 3C 2F 74 64 3E 3C 74 64 3E 4E 55 4C    td>0</td><td>NUL
+[2018/04/03 13:13:13:1442] NOTICE: 0140: 4C 3C 2F 74 64 3E 3C 2F 74 72 3E 3C 2F 74 61 62    L</td></tr></tab
+[2018/04/03 13:13:13:1442] NOTICE: 0150: 6C 65 3E 3C 62 72 3E 3C 62 3E 66 69 6C 65 6E 61    le><br><b>filena
+[2018/04/03 13:13:13:1442] NOTICE: 0160: 6D 65 3A 3C 2F 62 3E 20 6D 79 66 69 6C 65 2E 74    me:</b> myfile.t
+[2018/04/03 13:13:13:1442] NOTICE: 0170: 78 74 2C 20 3C 62 3E 6C 65 6E 67 74 68 3C 2F 62    xt, <b>length</b
+[2018/04/03 13:13:13:1442] NOTICE: 0180: 3E 20 34 34 3C 2F 62 6F 64 79 3E 3C 2F 68 74 6D    > 44</body></htm
+[2018/04/03 13:13:13:1442] NOTICE: 0190: 6C 3E                                              l>              
+[2018/04/03 13:13:13:1442] NOTICE: 
+[2018/04/03 13:13:13:1442] USER: LWS_CALLBACK_COMPLETED_CLIENT_HTTP
+[2018/04/03 13:13:13:1455] USER: Completed
+```
+
diff --git a/minimal-examples/http-client/minimal-http-client-post/libwebsockets.org.cer b/minimal-examples/http-client/minimal-http-client-post/libwebsockets.org.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c b/minimal-examples/http-client/minimal-http-client-post/minimal-http-client-post.c
new file mode 100644 (file)
index 0000000..d6b080a
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * lws-minimal-http-client-post
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal http client using lws and POST.
+ *
+ * It POSTs both form data and a file to the form at
+ * https://libwebsockets.org/testserver/formtest and dumps
+ * the html page received generated by the POST handler.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted, bad = 0, status, count_clients = 1, completed;
+static struct lws *client_wsi[4];
+
+struct pss {
+       char boundary[32];
+       char body_part;
+};
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - 1];
+       uint8_t **up, *uend;
+       uint32_t r;
+       int n;
+
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               bad = 1;
+               if (++completed == count_clients)
+                       lws_cancel_service(lws_get_context(wsi));
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               for (n = 0; n < count_clients; n++)
+                       if (client_wsi[n] == wsi) {
+                               client_wsi[n] = NULL;
+                               bad |= status != 200;
+                               if (++completed == count_clients)
+                                       /* abort poll wait */
+                                       lws_cancel_service(lws_get_context(wsi));
+                       }
+               break;
+
+       /* ...callbacks related to receiving the result... */
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               status = lws_http_client_http_response(wsi);
+               lwsl_user("Connected with server response: %d\n", status);
+               break;
+
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
+               lwsl_hexdump_notice(in, len);
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               n = sizeof(buf) - LWS_PRE;
+               if (lws_http_client_read(wsi, &p, &n) < 0)
+                       return -1;
+
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               bad |= status != 200;
+               /*
+                * Do this to mark us as having processed the completion
+                * so close doesn't duplicate (with pipelining, completion !=
+                * connection close
+                */
+               for (n = 0; n < count_clients; n++)
+                       if (client_wsi[n] == wsi)
+                               client_wsi[n] = NULL;
+               if (++completed == count_clients)
+                       /* abort poll wait */
+                       lws_cancel_service(lws_get_context(wsi));
+               break;
+
+       /* ...callbacks related to generating the POST... */
+
+       case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+               lwsl_user("LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER\n");
+               up = (uint8_t **)in;
+               uend = *up + len - 1;
+
+               /* generate a random boundary string */
+
+               lws_get_random(lws_get_context(wsi), &r, sizeof(r));
+               lws_snprintf(pss->boundary, sizeof(pss->boundary) - 1,
+                               "---boundary-%08x", r);
+
+               n = lws_snprintf(buf, sizeof(buf) - 1,
+                       "multipart/form-data; boundary=%s", pss->boundary);
+               if (lws_add_http_header_by_token(wsi,
+                               WSI_TOKEN_HTTP_CONTENT_TYPE,
+                               (uint8_t *)buf, n, up, uend))
+                       return 1;
+               /*
+                * Notice because we are sending multipart/form-data we can
+                * usually rely on the server to understand where the form
+                * payload ends without having to give it an overall
+                * content-length (which can be troublesome to compute ahead
+                * of generating the data to send).
+                *
+                * Tell lws we are going to send the body next...
+                */
+               lws_client_http_body_pending(wsi, 1);
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
+               lwsl_user("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n");
+               n = LWS_WRITE_HTTP;
+
+               /*
+                * For a small body like this, we could prepare it in memory and
+                * send it all at once.  But to show how to handle, eg,
+                * arbitrary-sized file payloads, or huge form-data fields, the
+                * sending is done in multiple passes through the event loop.
+                */
+
+               switch (pss->body_part++) {
+               case 0:
+                       /* notice every usage of the boundary starts with -- */
+                       p += lws_snprintf(p, end - p, "--%s\xd\xa"
+                               "content-disposition: "
+                                       "form-data; name=\"text\"\xd\xa"
+                               "\xd\xa"
+                               "my text field"
+                               "\xd\xa", pss->boundary);
+                       break;
+               case 1:
+                       p += lws_snprintf(p, end - p,
+                               "--%s\xd\xa"
+                               "content-disposition: form-data; name=\"file\";"
+                               "filename=\"myfile.txt\"\xd\xa"
+                               "content-type: text/plain\xd\xa"
+                               "\xd\xa"
+                                       "This is the contents of the "
+                                       "uploaded file.\xd\xa"
+                               "\xd\xa", pss->boundary);
+                       break;
+               case 2:
+                       p += lws_snprintf(p, end - p, "--%s--\xd\xa",
+                                         pss->boundary);
+                       lws_client_http_body_pending(wsi, 0);
+                        /* necessary to support H2, it means we will write no
+                         * more on this stream */
+                       n = LWS_WRITE_HTTP_FINAL;
+                       break;
+
+               default:
+                       /*
+                        * We can get extra callbacks here, if nothing to do,
+                        * then do nothing.
+                        */
+                       return 0;
+               }
+
+               if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n)
+                               != lws_ptr_diff(p, start))
+                       return 1;
+
+               if (n != LWS_WRITE_HTTP_FINAL)
+                       lws_callback_on_writable(wsi);
+
+               return 0;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "http",
+               callback_http,
+               sizeof(struct pss),
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_client_connect_info i;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                  /*
+                   * For LLL_ verbosity above NOTICE to be built into lws,
+                   * lws must have been configured and built with
+                   * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
+                   *
+                   * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
+                   *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
+                   */ ;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http client - POST [-d<verbosity>] [-l] [--h1]\n");
+
+       if (lws_cmdline_option(argc, argv, "-m"))
+               count_clients = LWS_ARRAY_SIZE(client_wsi);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + count_clients + 1;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       if (!lws_cmdline_option(argc, argv, "-l"))
+               info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+       i.context = context;
+       i.ssl_connection = LCCSCF_USE_SSL;
+
+       if (lws_cmdline_option(argc, argv, "-l")) {
+               i.port = 7681;
+               i.address = "localhost";
+               i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+               i.path = "/formtest";
+       } else {
+               i.port = 443;
+               i.address = "libwebsockets.org";
+               i.path = "/testserver/formtest";
+       }
+
+       i.host = i.address;
+       i.origin = i.address;
+       i.method = "POST";
+
+       /* force h1 even if h2 available */
+       if (lws_cmdline_option(argc, argv, "--h1"))
+               i.alpn = "http/1.1";
+
+       i.protocol = protocols[0].name;
+
+       for (n = 0; n < count_clients; n++) {
+               i.pwsi = &client_wsi[n];
+               if (!lws_client_connect_via_info(&i))
+                       completed++;
+       }
+
+       while (n >= 0 && completed != count_clients && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
+
+       return bad;
+}
diff --git a/minimal-examples/http-client/minimal-http-client-post/selftest.sh b/minimal-examples/http-client/minimal-http-client-post/selftest.sh
new file mode 100755 (executable)
index 0000000..2f887f2
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=8
+
+dotest $1 $2 warmcat
+dotest $1 $2 warmcat-h1 --h1
+dotest $1 $2 warmcat-m -m
+dotest $1 $2 warmcat-m-h1 -m --h1
+
+spawn "" $5 $1/libwebsockets-test-server -s
+dotest $1 $2 localhost -l
+spawn $SPID $5 $1/libwebsockets-test-server -s
+dotest $1 $2 localhost-h1 -l --h1
+spawn $SPID $5 $1/libwebsockets-test-server -s
+dotest $1 $2 localhost-m -l -m
+spawn $SPID $5 $1/libwebsockets-test-server -s
+dotest $1 $2 localhost-m-h1 -l -m --h1
+
+kill $SPID 2>/dev/null
+wait $SPID 2>/dev/null
+exit $FAILS
diff --git a/minimal-examples/http-client/minimal-http-client/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6181371
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-client)
+set(SRCS minimal-http-client.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/http-client/minimal-http-client/README.md b/minimal-examples/http-client/minimal-http-client/README.md
new file mode 100644 (file)
index 0000000..a3ac8d6
--- /dev/null
@@ -0,0 +1,59 @@
+# lws minimal http client
+
+The application goes to either https://warmcat.com or
+https://localhost:7681 (with `-l` option) and receives the page data.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-l| Connect to https://localhost:7681 and accept selfsigned cert
+--h1|Specify http/1.1 only using ALPN, rejects h2 even if server supports it
+
+```
+ $ ./lws-minimal-http-client
+[2018/03/04 14:43:20:8562] USER: LWS minimal http client
+[2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on
+[2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default
+[2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com
+[2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com
+[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520
+[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
+[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
+[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 974
+[2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1
+[2018/03/04 14:43:23:3042] USER: Completed
+```
+
+
diff --git a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c
new file mode 100644 (file)
index 0000000..1dc1be7
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * lws-minimal-http-client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal http client using lws.
+ *
+ * It visits https://warmcat.com/ and receives the html page there.  You
+ * can dump the page data by changing the #if 0 below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted, bad = 1, status;
+static struct lws *client_wsi;
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi = NULL;
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               status = lws_http_client_http_response(wsi);
+               lwsl_user("Connected with server response: %d\n", status);
+               break;
+
+       /* chunks of chunked content, with header removed */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
+#if 0  /* enable to dump the html */
+               {
+                       const char *p = in;
+
+                       while (len--)
+                               if (*p < 0x7f)
+                                       putchar(*p++);
+                               else
+                                       putchar('.');
+               }
+#endif
+               return 0; /* don't passthru */
+
+       /* uninterpreted http content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               {
+                       char buffer[1024 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               return 0; /* don't passthru */
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               client_wsi = NULL;
+               bad = status != 200;
+               lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "http",
+               callback_http,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_client_connect_info i;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                  /*
+                   * For LLL_ verbosity above NOTICE to be built into lws,
+                   * lws must have been configured and built with
+                   * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
+                   *
+                   * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
+                   *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
+                   */ ;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http client [-d<verbosity>] [-l] [--h1]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./warmcat.com.cer";
+#endif
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+       i.context = context;
+       if (!lws_cmdline_option(argc, argv, "-n"))
+               i.ssl_connection = LCCSCF_USE_SSL;
+
+       if (lws_cmdline_option(argc, argv, "-l")) {
+               i.port = 7681;
+               i.address = "localhost";
+               i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       } else {
+               i.port = 443;
+               i.address = "warmcat.com";
+       }
+
+       if (lws_cmdline_option(argc, argv, "--h1"))
+               i.alpn = "http/1.1";
+
+       if ((p = lws_cmdline_option(argc, argv, "-p")))
+               i.port = atoi(p);
+
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+       i.method = "GET";
+
+       i.protocol = protocols[0].name;
+       i.pwsi = &client_wsi;
+       lws_client_connect_via_info(&i);
+
+       while (n >= 0 && client_wsi && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
+
+       return bad;
+}
diff --git a/minimal-examples/http-client/minimal-http-client/selftest.sh b/minimal-examples/http-client/minimal-http-client/selftest.sh
new file mode 100755 (executable)
index 0000000..c065b44
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=4
+
+dotest $1 $2 warmcat
+dotest $1 $2 warmcat-h1 --h1
+
+spawn "" $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost -l
+spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
+dotest $1 $2 localhost-h1 -l --h1
+
+kill $SPID 2>/dev/null
+wait $SPID 2>/dev/null
+exit $FAILS
diff --git a/minimal-examples/http-client/minimal-http-client/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client/warmcat.com.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/README.md b/minimal-examples/http-server/README.md
new file mode 100644 (file)
index 0000000..a6d0b8e
--- /dev/null
@@ -0,0 +1,24 @@
+|Example|Demonstrates|
+---|---
+minimal-http-server-basicauth|Shows how to protect a mount using a password file and basic auth
+minimal-http-server-custom-headers|Shows how to query custom headers that lws doesn't already know
+minimal-http-server-deaddrop|Shows how to use the deaddrop drag and drop file upload + sharing plugin
+minimal-http-server-dynamic|Serves both static and dynamically generated http content
+minimal-http-server-eventlib-foreign|Demonstrates integrating lws with a foreign event library
+minimal-http-server-eventlib-demos|Using the demo plugins with event libraries
+minimal-http-server-eventlib|Same as minimal-http-server but works with a supported event library
+minimal-http-server-form-get|Process a GET form
+minimal-http-server-form-post-file|Process a multipart POST form with file transfer
+minimal-http-server-form-post|Process a POST form (no file transfer)
+minimal-http-server-fulltext-search|Demonstrates using lws Fulltext Search
+minimal-http-server-mimetypes|Shows how to add support for additional mimetypes at runtime
+minimal-http-server-multivhost|Same as minimal-http-server but three different vhosts
+minimal-http-server-proxy|Reverse Proxy
+minimal-http-server-smp|Multiple service threads
+minimal-http-server-sse-ring|Server Side Events with ringbuffer and threaded event sources
+minimal-http-server-sse|Simple Server Side Events
+minimal-http-server-tls-80|Serves a directory over http/1 or http/2 with TLS (SSL), custom 404 handler, redirect to https on port 80
+minimal-http-server-tls-mem|Serves using TLS with the cert and key provided as memory buffers instead of files
+minimal-http-server-tls|Serves a directory over http/1 or http/2 with TLS (SSL), custom 404 handler
+minimal-http-server|Serves a directory over http/1, custom 404 handler
+
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-basicauth/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c1bb2ce
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-basicauth)
+set(SRCS minimal-http-server-basicauth.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/README.md b/minimal-examples/http-server/minimal-http-server-basicauth/README.md
new file mode 100644 (file)
index 0000000..7cf7751
--- /dev/null
@@ -0,0 +1,34 @@
+# lws minimal http server basic auth
+
+This demonstrates how to protect a mount using a password
+file outside of the mount itself.
+
+The demo has two mounts, a normal one at / and one protected
+by basic auth at /secret.
+
+The file at ./ba-passwords contains valid user:password
+combinations.
+
+## Discovering the authenticated user
+
+After a successful authentication, the `WSI_TOKEN_HTTP_AUTHORIZATION` token
+contains the authenticated username.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-basic-auth
+[2018/04/19 08:40:05:1333] USER: LWS minimal http server basic auth | visit http://localhost:7681
+[2018/04/19 08:40:05:1333] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+```
+
+Visit http://localhost:7681, and follow the link there to the secret area.
+
+Give your browser "user" and "password" as the credentials.
+
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/ba-passwords b/minimal-examples/http-server/minimal-http-server-basicauth/ba-passwords
new file mode 100644 (file)
index 0000000..28b9bb2
--- /dev/null
@@ -0,0 +1 @@
+user:password
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c b/minimal-examples/http-server/minimal-http-server-basicauth/minimal-http-server-basicauth.c
new file mode 100644 (file)
index 0000000..1336d28
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * lws-minimal-http-server-basicauth
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server with a second mount that
+ * is protected using a password file and basic auth.
+ *
+ * To keep it simple, it serves the static stuff from the subdirectory
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+
+static int interrupted;
+
+/* override the default mount for /secret in the URL space */
+
+static const struct lws_http_mount mount_secret = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/secret",      /* mountpoint URL */
+       /* .origin */                   "./mount-secret-origin",
+       /* .def */                      "index.html",
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE, /* dynamic */
+       /* .mountpoint_len */           7,              /* char count */
+       /* .basic_auth_login_file */    "./ba-passwords",
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_secret,  /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server basic auth | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/index.html
new file mode 100644 (file)
index 0000000..0b35368
--- /dev/null
@@ -0,0 +1,25 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server basic auth example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               Stuff down /secret in the URL space is protected by Basic Auth.<br>
+               Your browser will ask for a username / password combination, and<br>
+               lws will check it against ./ba-passwords, which contains a list of<br>
+               "username:password" one per line.<br>
+               <br>
+               The example content for ba-passwords is literally "user:password".<br>
+               Click on the link into the protected area of the URL space below<br>
+               and give your browser the credentials "user" and "password".
+               <p>
+               <a href="/secret/" target=_blank>/secret</a>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-basicauth/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-secret-origin/index.html b/minimal-examples/http-server/minimal-http-server-basicauth/mount-secret-origin/index.html
new file mode 100644 (file)
index 0000000..a0bb441
--- /dev/null
@@ -0,0 +1,11 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+
+               This is the big secret protected by <b>basic auth</b>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-basicauth/mount-secret-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-basicauth/mount-secret-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-custom-headers/CMakeLists.txt
new file mode 100644 (file)
index 0000000..92d0d18
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-custom-headers)
+set(SRCS minimal-http-server-custom-headers.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITH_CUSTOM_HEADERS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/README.md b/minimal-examples/http-server/minimal-http-server-custom-headers/README.md
new file mode 100644 (file)
index 0000000..9666d02
--- /dev/null
@@ -0,0 +1,20 @@
+# lws minimal http server dynamic content
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-dynamic
+[2018/03/20 10:24:24:7099] USER: LWS minimal http server dynamic | visit http://localhost:7681
+[2018/03/20 10:24:24:7099] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+```
+
+Visit http://localhost:7681, which is all static content.
+
+Click on the link to /dyn/anything, this opens a new tab with dynamicly-produced content.
+
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-custom-headers/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c b/minimal-examples/http-server/minimal-http-server-custom-headers/minimal-http-server-custom-headers.c
new file mode 100644 (file)
index 0000000..dcdb0b8
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * lws-minimal-http-server-custom-headers
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that can produce dynamic http
+ * content as well as static content.
+ *
+ * To keep it simple, it serves the static stuff from the subdirectory
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+
+static int interrupted;
+
+struct pss {
+       char result[128 + LWS_PRE];
+       int len;
+};
+
+/*
+ * This just lets us override LWS_CALLBACK_HTTP handling before passing it
+ * and everything else to the default handler.
+ */
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - LWS_PRE - 1];
+       struct pss *pss = (struct pss *)user;
+       char value[32], *pr = &pss->result[LWS_PRE];
+       int n, e = sizeof(pss->result) - LWS_PRE;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+               /*
+                * LWS doesn't have the "DNT" header built-in.  But we can
+                * query it using the "custom" versions of the header apis.
+                *
+                * You can set your modern browser to issue DNT, look in the
+                * privacy settings of your browser.
+                */
+
+               pss->len = 0;
+               n = lws_hdr_custom_length(wsi, "dnt:", 4);
+               if (n < 0)
+                       pss->len = lws_snprintf(pr, e,
+                                       "%s: DNT header not found\n", __func__);
+               else {
+
+                       pss->len = lws_snprintf(pr, e,
+                                       "%s: DNT length %d<br>", __func__, n);
+                       n = lws_hdr_custom_copy(wsi, value, sizeof(value), "dnt:", 4);
+                       if (n < 0)
+                               pss->len += lws_snprintf(pr + pss->len, e - pss->len,
+                                       "%s: unable to get DNT value\n", __func__);
+                       else
+
+                               pss->len += lws_snprintf(pr + pss->len , e - pss->len,
+                                       "%s: DNT value '%s'\n", __func__, value);
+               }
+
+               lwsl_user("%s\n", pr);
+
+               if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
+                               "text/html", pss->len, &p, end))
+                       return 1;
+
+               if (lws_finalize_write_http_header(wsi, start, &p, end))
+                       return 1;
+
+
+               /* write the body separately */
+               lws_callback_on_writable(wsi);
+               return 0;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+
+               strcpy((char *)start, "hello");
+
+               if (lws_write(wsi, (uint8_t *)pr, pss->len, LWS_WRITE_HTTP_FINAL) != pss->len)
+                       return 1;
+
+               if (lws_http_transaction_completed(wsi))
+                       return -1;
+               return 0;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", callback_http, sizeof(struct pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static const struct lws_http_mount mount_dyn = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/dyn",         /* mountpoint URL */
+       /* .origin */                   NULL,   /* protocol */
+       /* .def */                      NULL,
+       /* .protocol */                 "http",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_CALLBACK, /* dynamic */
+       /* .mountpoint_len */           4,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_dyn,             /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */           "./mount-origin",       /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server custom headers | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       /* for testing ah queue, not useful in real world */
+       if (lws_cmdline_option(argc, argv, "--ah1"))
+               info.max_http_header_pool = 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       /* http on 7681 */
+
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.vhost_name = "http";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create tls vhost\n");
+               goto bail;
+       }
+
+       /* https on 7682 */
+
+       info.port = 7682;
+       info.error_document_404 = "/404.html";
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+       info.vhost_name = "https";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create tls vhost\n");
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6f85f25
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8">
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/index.html
new file mode 100644 (file)
index 0000000..a5fbbd7
--- /dev/null
@@ -0,0 +1,20 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server custom headers example</b>.
+               <p>
+               The idea is it will tell you what your browser sent for DNT, a header lws doesn't already know.
+               <p>
+               At the moment the custom header api only works on h1.
+               <p>
+
+               <a href="http://127.0.0.1:7681/dyn/xxx" >Show DNT header using h1 over http</a><br>
+               <a href="https://127.0.0.1:7682/dyn/xxx" >Show DNT header using h1 (h2 if enabled) over https</a><br>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-custom-headers/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-deaddrop/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d5f5188
--- /dev/null
@@ -0,0 +1,86 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-deaddrop)
+set(SRCS minimal-http-server-deaddrop.c)
+
+# NOTE... if you are building this standalone, you must point LWS_PLUGINS_DIR
+# to the lws plugins dir so it can pick up the plugin source.  Eg,
+# cmake . -DLWS_PLUGINS_DIR=~/libwebsockets/plugins
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (LWS_PLUGINS_DIR)
+               include_directories(${LWS_PLUGINS_DIR})
+       endif()
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/README.md b/minimal-examples/http-server/minimal-http-server-deaddrop/README.md
new file mode 100644 (file)
index 0000000..4a07ad4
--- /dev/null
@@ -0,0 +1,49 @@
+# lws minimal http server deaddrop
+
+This demonstrates how you can leverage the lws deaddrop plugin to make a
+secure, modern html5 file upload and sharing application.
+
+The demo is protected by basic auth credentials defined in the file at
+./ba-passwords - by default the credentials are user: user1, password: password;
+and user: user2, password: password again.
+
+You can upload files and have them appear on a shared, downloadable list that
+is dynamically updated to all clients open on the page.  Only the authenticated
+uploader is able to delete the files he uploaded.
+
+Multiple simultaneous ongoing file uploads are supported.
+
+## build
+
+To build this standalone, you must tell cmake where the lws source tree
+./plugins directory can be found, since it relies on including the source
+of the raw-proxy plugin.
+
+```
+ $ cmake . -DLWS_PLUGINS_DIR=~/libwebsockets/plugins && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-deaddrop
+[2018/12/01 10:31:09:7108] USER: LWS minimal http server deaddrop | visit https://localhost:7681
+[2018/12/01 10:31:09:8511] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/12/01 10:31:09:8522] NOTICE:  Using SSL mode
+[2018/12/01 10:31:10:0755] NOTICE:  SSL ECDH curve 'prime256v1'
+[2018/12/01 10:31:10:2562] NOTICE: lws_tls_client_create_vhost_context: doing cert filepath localhost-100y.cert
+[2018/12/01 10:31:10:2581] NOTICE: Loaded client cert localhost-100y.cert
+[2018/12/01 10:31:10:2583] NOTICE: lws_tls_client_create_vhost_context: doing private key filepath
+[2018/12/01 10:31:10:2593] NOTICE: Loaded client cert private key localhost-100y.key
+[2018/12/01 10:31:10:2596] NOTICE: created client ssl context for default
+[2018/12/01 10:31:10:5290] NOTICE:   deaddrop: vh default, upload dir ./uploads, max size 10000000
+[2018/12/01 10:31:10:5376] NOTICE:    vhost default: cert expiry: 730203d
+...
+```
+
+Visit https://localhost:7681, and follow the link there to the secret area.
+
+Give your browser "user1" and "password" as the credentials.  For testing to
+confirm what a different user sees, you can also log in as "user2" and
+"password".
+
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/ba-passwords b/minimal-examples/http-server/minimal-http-server-deaddrop/ba-passwords
new file mode 100644 (file)
index 0000000..cd9feb0
--- /dev/null
@@ -0,0 +1,2 @@
+user1:password
+user2:password
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-deaddrop/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-deaddrop/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c b/minimal-examples/http-server/minimal-http-server-deaddrop/minimal-http-server-deaddrop.c
new file mode 100644 (file)
index 0000000..fe06412
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * lws-minimal-http-server-deaddrop
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates how you can leverage the lws deaddrop plugin to make a
+ * secure, modern html5 file upload and sharing application.
+ *
+ * Because the guts are in a plugin, you can avoid all this setup by using the
+ * plugin from lwsws and do the config in JSON.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+
+#define LWS_PLUGIN_STATIC
+#include "../plugins/deaddrop/protocol_lws_deaddrop.c"
+
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_DEADDROP,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+
+static int interrupted;
+
+/*
+ * teach the /get mount how to present various filetypes to the client...
+ * lws won't serve files it doesn't know the mimetype for as a security
+ * measure.
+ */
+
+static struct lws_protocol_vhost_options em3 = {
+        NULL, NULL, ".zip", "application/zip"
+}, em2 = {
+       &em3, NULL, ".pdf", "application/pdf"
+}, extra_mimetypes = {
+       &em2, NULL, ".tar.gz", "application/x-gzip"
+};
+
+/* wire up /upload URLs to the plugin (protected by basic auth) */
+
+static const struct lws_http_mount mount_upload = {
+       /* .mount_next */               NULL,
+       /* .mountpoint */               "/upload",      /* mountpoint URL */
+       /* .origin */                   "lws-deaddrop",
+       /* .def */                      "",
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_CALLBACK,
+       /* .mountpoint_len */           7,              /* char count */
+       /* .basic_auth_login_file */    "./ba-passwords",
+};
+
+/* wire up /get URLs to the upload directory (protected by basic auth) */
+
+static const struct lws_http_mount mount_get = {
+       /* .mount_next */               &mount_upload,  /* linked-list "next" */
+       /* .mountpoint */               "/get", /* mountpoint URL */
+       /* .origin */                   "./uploads",
+       /* .def */                      "",
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          &extra_mimetypes,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE, /* dynamic */
+       /* .mountpoint_len */           4,              /* char count */
+       /* .basic_auth_login_file */    "./ba-passwords",
+};
+
+/* wire up / to serve from ./mount-origin (protected by basic auth) */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_get,     /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    "./ba-passwords",
+};
+
+/* pass config options to the deaddrop plugin using pvos */
+
+static struct lws_protocol_vhost_options pvo3 = {
+       /* make the wss also require to pass basic auth */
+       NULL, NULL, "basic-auth", "./ba-passwords"
+}, pvo2 = {
+       &pvo3, NULL, "max-size", "10000000"
+}, pvo1 = {
+        &pvo2, NULL, "upload-dir", "./uploads" /* would be an absolute path */
+}, pvo = {
+        NULL,                  /* "next" pvo linked-list */
+        &pvo1,                 /* "child" pvo linked-list */
+        "lws-deaddrop",        /* protocol name we belong to on this vhost */
+        ""                     /* ignored */
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server deaddrop | visit https://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.pvo = &pvo;
+       info.protocols = protocols;
+       info.error_document_404 = "/404.html";
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/deaddrop.css b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/deaddrop.css
new file mode 100644 (file)
index 0000000..549e362
--- /dev/null
@@ -0,0 +1,70 @@
+.td { padding: 8px }
+.h1 { }
+.dd-fileinfo { font-size: 8pt; }
+table td { 
+  display: table-cell;
+  vertical-align: top;
+  background-color: rgba(247, 247, 232, 0.6);
+  text-align: center
+}
+table {
+       border: 2px solid #ccc;
+       padding: 4px;
+       border-radius: 12px;
+       transition: background-color 0.5s ease;
+}
+table.nb { border: 0px; border-radius: 0px; transition: opacity 0.5s; }
+table.noconn { background-color: #ddd;  }
+
+div { transition: opacity 0.5s; }
+div.da { padding-left: 20px; padding-right:20px; }
+div.trot {
+       animation: scale 0.5s linear infinite;
+}
+div.uplbox { padding-bottom: 8px; }
+div.disa { opacity: 0.2; }
+
+td.ogn { text-align:left; font-size: 8pt; padding-left: 4px; padding-right: 4px;}
+td.dow { text-align:left; font-size: 9pt; padding-left: 4px; padding-right: 4px;}
+td.r { text-align: right; }
+td.err { color: red; font-weight: bold; }
+td.vm { display: table-cell; vertical-align: middle; padding-top: 12px; padding-bottom: 12px; }
+
+h3 { font-size: 12pt; margin-bottom: 6px; }
+span { font-size: 9pt; }
+a { font-size: 9pt; }
+
+input.ubtn { font-size: 16pt; margin-top: 4px; text-align: center }
+
+img.working {
+       display: inline-block;
+       float:left;
+       background: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKICAgICB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzMHB4IiB2aWV3Qm94PSIwIDAgMjQgMzAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUwIDUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICA8cmVjdCB4PSIwIiB5PSIxMCIgd2lkdGg9IjQiIGhlaWdodD0iMTAiIGZpbGw9IiMzMzMiIG9wYWNpdHk9IjAuMiI+CiAgICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9Im9wYWNpdHkiIGF0dHJpYnV0ZVR5cGU9IlhNTCIgdmFsdWVzPSIwLjI7IDE7IC4yIiBiZWdpbj0iMHMiIGR1cj0iMC42cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImhlaWdodCIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjEwOyAyMDsgMTAiIGJlZ2luPSIwcyIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ieSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjEwOyA1OyAxMCIgYmVnaW49IjBzIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0iOCIgeT0iMTAiIHdpZHRoPSI0IiBoZWlnaHQ9IjEwIiBmaWxsPSIjMzMzIiAgb3BhY2l0eT0iMC4yIj4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ib3BhY2l0eSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjAuMjsgMTsgLjIiIGJlZ2luPSIwLjE1cyIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iaGVpZ2h0IiBhdHRyaWJ1dGVUeXBlPSJYTUwiIHZhbHVlcz0iMTA7IDIwOyAxMCIgYmVnaW49IjAuMTVzIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJ5IiBhdHRyaWJ1dGVUeXBlPSJYTUwiIHZhbHVlcz0iMTA7IDU7IDEwIiBiZWdpbj0iMC4xNXMiIGR1cj0iMC42cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICA8L3JlY3Q+CiAgICA8cmVjdCB4PSIxNiIgeT0iMTAiIHdpZHRoPSI0IiBoZWlnaHQ9IjEwIiBmaWxsPSIjMzMzIiAgb3BhY2l0eT0iMC4yIj4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ib3BhY2l0eSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjAuMjsgMTsgLjIiIGJlZ2luPSIwLjNzIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJoZWlnaHQiIGF0dHJpYnV0ZVR5cGU9IlhNTCIgdmFsdWVzPSIxMDsgMjA7IDEwIiBiZWdpbj0iMC4zcyIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ieSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjEwOyA1OyAxMCIgYmVnaW49IjAuM3MiIGR1cj0iMC42cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICA8L3JlY3Q+Cjwvc3ZnPg==");
+       width:0px;
+       height:0px;
+       cursor:pointer;
+       padding:0.6em 1em;
+       background-repeat: no-repeat;
+       vertical-align:middle;
+       color: rgba(0, 0, 0, 0);
+}
+
+img.delbtn {
+       display: inline-block;
+       float:left;
+       background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNC4yMTcxMzMgMzQuMTQ0MjQ5IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KCTxkZWZzPgoJCTxyYWRpYWxHcmFkaWVudCBpZD0iYSIgY3g9IjEzNTguNSIgY3k9Ijk3Ny43MiIgcj0iNC4xMTYiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMS41MDM1IDAgMCAyLjQ5NDQgLTI1NzcuNCAtMTgyMy44KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjZmZmIiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIiBvZmZzZXQ9IjEiLz4KCQk8L3JhZGlhbEdyYWRpZW50PgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjEzNjYuMyIgeDI9IjEzNDQuOCIgeTE9Ijk3OC4xOSIgeTI9Ijk1OC43MyIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMjE3NCAtMTA3Ni4xKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjOWM5NzlkIiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iI2Y0ZmVmMyIgc3RvcC1vcGFjaXR5PSIuNzQwMTYiIG9mZnNldD0iMSIvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4MzQuMjkgMTIzLjc1KSI+CgkJPGcgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4KCQkJPGNpcmNsZSBjeD0iLTgxNy4yMiIgY3k9Ii0xMDYuNjgiIHI9IjE2LjA5IiBmaWxsPSJ1cmwoI2IpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMS45NjUiLz4KCQkJPHBhdGggZD0ibS04MjUuNjctMTE0LjcxYzE3LjMzIDE3LjMzIDE3IDE2Ljk5OSAxNyAxNi45OTkiIGZpbGw9IiNmNTUiIHN0cm9rZT0iI2EwMCIgc3Ryb2tlLXdpZHRoPSIxLjY2NSIvPgoJCQk8cGF0aCBkPSJtLTgwOC45Ni0xMTQuNjFjLTE3LjMzIDE3LjMzLTE3IDE2Ljk5OS0xNyAxNi45OTkiIGZpbGw9IiNmNTUiIHN0cm9rZT0iI2EwMCIgc3Ryb2tlLXdpZHRoPSIxLjY2NSIvPgoJCTwvZz4KCQk8ZWxsaXBzZSB0cmFuc2Zvcm09InJvdGF0ZSg1NS44ODUpIiBjeD0iLTUzNC45NiIgY3k9IjYxNS4wNyIgcng9IjYuMTg4MyIgcnk9IjEwLjI2NyIgZmlsbD0idXJsKCNhKSIvPgoJPC9nPgo8L3N2Zz4=");
+       width:0px;
+       height:0px;
+       cursor:pointer;
+       padding:0.45em;
+       background-repeat: no-repeat;
+       vertical-align:middle;
+       color: rgba(0, 0, 0, 0);
+}
+
+@keyframes scale {
+  50% {
+    opacity: 0.5;
+    transform:scale(1.1) rotate(2deg);
+  }
+}
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/deaddrop.js b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/deaddrop.js
new file mode 100644 (file)
index 0000000..ebb6e12
--- /dev/null
@@ -0,0 +1,300 @@
+(function() {
+
+       var server_max_size = 0, ws;
+
+       function san(s)
+       {
+               if (!s)
+                       return "";
+
+               return s.replace(/&/g, "&amp;").
+               replace(/\</g, "&lt;").
+               replace(/\>/g, "&gt;").
+               replace(/\"/g, "&quot;").
+               replace(/%/g, "&#37;");
+       }
+
+       function lws_urlencode(s)
+       {
+               return encodeURI(s).replace(/@/g, "%40");
+       }
+
+       function trim(num)
+       {
+               var s = num.toString();
+
+               if (!s.indexOf("."))
+                       return s;
+
+               while (s.length && s[s.length - 1] === "0")
+                       s = s.substring(0, s.length - 1);
+
+               if (s[s.length - 1] === ".")
+                       s = s.substring(0, s.length - 1);
+
+               return s;
+       }
+
+       function humanize(n)
+       {
+               if (n < 1024)
+                       return n + "B";
+
+               if (n < 1024 * 1024)
+                       return trim((n / 1024).toFixed(2)) + "KiB";
+
+               if (n < 1024 * 1024 * 1024)
+                       return trim((n / (1024 * 1024)).toFixed(2)) + "MiB";
+
+               return trim((n / (1024 * 1024 * 1024)).toFixed(2)) + "GiB";
+       }
+
+       function da_enter(e)
+       {
+               var da = document.getElementById("da");
+
+               e.preventDefault();
+               da.classList.add("trot");
+       }
+
+       function da_leave(e)
+       {
+               var da = document.getElementById("da");
+
+               e.preventDefault();     
+               da.classList.remove("trot");
+       }
+
+       function da_over(e)
+       {
+               var da = document.getElementById("da");
+
+               e.preventDefault();             
+               da.classList.add("trot");
+       }
+
+       function clear_errors() {
+               var t = document.getElementById("ongoing");
+
+               for (n = 0; n < t.rows.length; n++)
+                       if (t.rows[n].cells[0].classList.contains("err"))
+                               t.deleteRow(n);
+       }
+
+       function do_upload(file) {
+               var formData = new FormData();
+               var t = document.getElementById("ongoing");
+
+               formData.append("file", file);
+
+               var row = t.insertRow(0), c1 = row.insertCell(0),
+               c2 = row.insertCell(1), c3 = row.insertCell(2);
+
+               c1.classList.add("ogn");
+               c1.classList.add("r");
+
+               if (file.size > server_max_size) {
+                       c1.innerHTML = "Too Large";
+                       c1.classList.add("err");
+               } else
+                       c1.innerHTML = "<img class=\"working\">";
+
+               c2.classList.add("ogn");
+               c2.classList.add("r");
+               c2.innerHTML = humanize(file.size);
+
+               c3.classList.add("ogn");
+               c3.innerHTML = file.name;
+
+               if (file.size > server_max_size)
+                       return;
+
+               fetch("upload/" + lws_urlencode(file.name), {
+                       method: "POST",
+                       body: formData
+               })
+               .then((e) => { /* this just means we got a response code */                       
+                       var us = e.url.split("/"), ul = us[us.length - 1], n;
+
+                       for (n = 0; n < t.rows.length; n++)
+                               if (ul === lws_urlencode(
+                                             t.rows[n].cells[2].textContent)) {
+                                       if (e.ok === true) {
+                                               t.deleteRow(n);
+                                       } else {
+                                               t.rows[n].cells[0].textContent =
+                                       "Failed " + san(e.status.toString());
+                                               t.rows[n].cells[0].
+                                                       classList.add("err");
+                                       }
+                                       break;
+                               }
+               })
+               .catch((e) => {
+                       var us = e.url.split("/"), ul = us[us.length - 1], n;
+
+                       for (n = 0; n < t.rows.length; n++)
+                               if (ul === lws_urlencode(
+                                         t.rows[n].cells[2].textContent)) {
+                                       t.rows[n].cells[0] = "FAIL";
+                                       break;
+                               }
+               });
+       }
+
+       function da_drop(e) {
+               var da = document.getElementById("da");
+
+               e.preventDefault();             
+               da.classList.remove("trot");
+
+               clear_errors();
+
+               ([...e.dataTransfer.files]).forEach(do_upload);
+       }
+
+       function upl_button(e) {
+               var fi = document.getElementById("file"),
+               da = document.getElementById("da");
+
+               clear_errors();
+               e.preventDefault();
+
+               ([...fi.files]).forEach(do_upload);
+       }
+
+       function body_drop(e) {
+               e.preventDefault();
+       }
+
+       function inp() {
+               var fi = document.getElementById("file"),
+               upl = document.getElementById("upl");
+               console.log("inp");
+               upl.disabled = !fi.files.length;
+       }
+
+       function delfile(e)
+       {
+               e.stopPropagation();
+               e.preventDefault();
+
+               ws.send("{\"del\":\"" + decodeURI(e.target.getAttribute("file")) +
+               "\"}");
+       }
+
+       function get_appropriate_ws_url(extra_url)
+       {
+               var pcol;
+               var u = document.URL;
+
+               /*
+                * We open the websocket encrypted if this page came on an
+                * https:// url itself, otherwise unencrypted
+                */
+
+               if (u.substring(0, 5) === "https") {
+                       pcol = "wss://";
+                       u = u.substr(8);
+               } else {
+                       pcol = "ws://";
+                       if (u.substring(0, 4) === "http")
+                               u = u.substr(7);
+               }
+
+               u = u.split("/");
+
+               /* + "/xxx" bit is for IE10 workaround */
+
+               return pcol + u[0] + "/" + extra_url;
+       }
+
+       function new_ws(urlpath, protocol)
+       {
+               if (typeof MozWebSocket != "undefined")
+                       return new MozWebSocket(urlpath, protocol);
+
+               return new WebSocket(urlpath, protocol);
+       }
+
+       document.addEventListener("DOMContentLoaded", function() {
+               var da = document.getElementById("da"),
+               fi = document.getElementById("file"),
+               upl = document.getElementById("upl");
+
+               da.addEventListener("dragenter", da_enter, false);
+               da.addEventListener("dragleave", da_leave, false);
+               da.addEventListener("dragover", da_over, false);
+               da.addEventListener("drop", da_drop, false);
+
+               upl.addEventListener("click", upl_button, false);               
+               fi.addEventListener("change", inp, false);
+
+               window.addEventListener("dragover", body_drop, false);
+               window.addEventListener("drop", body_drop, false);
+
+               ws = new_ws(get_appropriate_ws_url(""), "lws-deaddrop");
+               try {
+                       ws.onopen = function() {
+                               var dd = document.getElementById("ddrop"),
+                               da = document.getElementById("da");
+
+                               dd.classList.remove("noconn");
+                               da.classList.remove("disa");
+                       };
+
+                       ws.onmessage = function got_packet(msg) {
+                               var j = JSON.parse(msg.data), s = "", n,
+                               t = document.getElementById("dd-list");
+
+                               server_max_size = j.max_size;
+                               document.getElementById("size").innerHTML =
+                                       "Server maximum file size " +
+                                       humanize(j.max_size);
+
+                               s += "<table class=\"nb\">";
+                               for (n = 0; n < j.files.length; n++) {
+                                       var date = new Date(j.files[n].mtime * 1000);
+                                       s += "<tr><td class=\"dow r\">" +
+                                       humanize(j.files[n].size) +
+                                       "</td><td class=\"dow\">" +
+                                       date.toDateString() + " " +
+                                       date.toLocaleTimeString() +
+                                       "</td><td>";
+                                       if (j.files[n].yours === 1)
+                                               s += "<img id=\"d" + n +
+                                         "\" class=\"delbtn\" file=\"" +
+                                               lws_urlencode(san(j.files[n].name)) + "\">";
+                                       else
+                                               s += " ";
+
+                                       s += "</td><td class=\"ogn\"><a href=\"get/" +
+                                       lws_urlencode(san(j.files[n].name)) +
+                                         "\" download>" +
+                                       san(j.files[n].name) + "</a></td></tr>";
+                               }
+                               s += "</table>";
+
+                               t.innerHTML = s;
+
+                               for (n = 0; n < j.files.length; n++) {
+                                       var d = document.getElementById("d" + n);
+                                       if (d)
+                                               d.addEventListener("click",
+                                                               delfile, false);
+                               }
+                       };
+
+                       ws.onclose = function() {
+                               var dd = document.getElementById("ddrop"),
+                               da = document.getElementById("da");
+
+                               dd.classList.add("noconn");
+                               da.classList.add("disa");
+                       };
+               } catch(exception) {
+                       alert("<p>Error " + exception);
+               }
+
+       });
+}());
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/drop.svg b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/drop.svg
new file mode 100644 (file)
index 0000000..f413cf0
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="49.443mm" height="49.443mm" version="1.1" viewBox="0 0 49.442779 49.442779" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+       <defs>
+               <filter id="d" x="-.088487" y="-.064772" width="1.177" height="1.1295" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.15643472"/>
+               </filter>
+               <filter id="e" x="-.088487" y="-.064772" width="1.177" height="1.1295" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.15643472"/>
+               </filter>
+               <linearGradient id="c" x1="208.34" x2="352.29" y1="32.317" y2="159.6" gradientTransform="matrix(.26458 0 0 .26458 -586.44 -188.57)" gradientUnits="userSpaceOnUse">
+                       <stop stop-opacity=".49606" offset="0"/>
+                       <stop stop-color="#fff6d5" stop-opacity="0" offset="1"/>
+               </linearGradient>
+               <linearGradient id="b" x1="365.17" x2="216.93" y1="163.64" y2="36.61" gradientTransform="matrix(.26458 0 0 .26458 -586.5 -188.43)" gradientUnits="userSpaceOnUse">
+                       <stop stop-color="#fff" offset="0"/>
+                       <stop stop-color="#fff" stop-opacity=".007874" offset="1"/>
+               </linearGradient>
+               <linearGradient id="a" x1="-529.78" x2="-494.03" y1="-175.41" y2="-147.94" gradientUnits="userSpaceOnUse">
+                       <stop stop-color="#c1dc5f" stop-opacity=".61417" offset="0"/>
+                       <stop stop-color="#a9aa52" stop-opacity=".8937" offset="1"/>
+               </linearGradient>
+       </defs>
+       <g transform="translate(536.12 186.96)">
+               <circle cx="-511.4" cy="-162.24" r="24.457" fill="url(#a)"/>
+               <g transform="matrix(.78726 0 0 .78726 -108.47 -33.661)">
+                       <g stroke="#000">
+                               <path transform="matrix(2.9413 0 0 2.9413 -4498.4 -3189.2)" d="m1354 1027.2v5.7964h4.2429v-4.5102l-1.211-1.211-0.075-0.075z" filter="url(#e)" stroke-width=".26458px"/>
+                               <path transform="matrix(2.9413 0 0 2.9413 -4498.4 -3189.2)" d="m1353.2 1025.7v5.7964h4.2429v-4.5101l-1.2111-1.2111-0.075-0.075z" filter="url(#d)" stroke-width=".26458px"/>
+                               <path d="m-516.24-168.48v17.049h12.479v-13.266l-3.5622-3.5619-0.22056-0.22058z" fill="#ececec" stroke-width=".77821px"/>
+                       </g>
+                       <g fill="none" stroke="#4d4d4d" stroke-width=".77821px">
+                               <path d="m-513.59-164.94h7.2225"/>
+                               <path d="m-513.56-162.83h7.2222"/>
+                               <path d="m-513.57-160.59h7.2222"/>
+                               <path d="m-513.55-158.48h7.2225"/>
+                               <path d="m-513.57-156.27h7.2222"/>
+                               <path d="m-513.55-154.16h7.2225"/>
+                       </g>
+                       <path d="m-518.5-172.78v17.048h12.479v-13.265l-3.5622-3.5622-0.22057-0.22059z" fill="#fff" stroke="#000" stroke-width=".77821px"/>
+                       <g fill="none" stroke="#000" stroke-width=".77821px">
+                               <path d="m-515.99-169.38h7.2225"/>
+                               <path d="m-515.82-167.13h7.2222"/>
+                               <path d="m-515.83-164.89h7.2223"/>
+                               <path d="m-515.81-162.78h7.2225"/>
+                               <path d="m-515.83-160.57h7.2223"/>
+                               <path d="m-515.81-158.46h7.2225"/>
+                       </g>
+               </g>
+               <g>
+                       <g>
+                               <g dominant-baseline="auto" fill="#540" stroke-width=".023836" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="to upload">
+                                       <path d="m-518.24-173.3q0.0983 0 0.1743-0.0268v0.17877q-0.0983 0.0447-0.25251 0.0447-0.37541 0-0.37541-0.56088v-1.7206h-0.21899v-0.12514l0.21452-0.0626 0.0693-0.57653h0.14749v0.57653h0.38435v0.18771h-0.38435v1.6603q0 0.24581 0.0536 0.33519 0.0536 0.0894 0.1877 0.0894z"/>
+                                       <path d="m-516.32-174.37q0 0.61228-0.19441 0.93854-0.19218 0.32401-0.55195 0.32401-0.3553 0-0.54525-0.32401-0.1877-0.32626-0.1877-0.93854 0-1.2536 0.74189-1.2536 0.3486 0 0.54301 0.32848 0.19441 0.32849 0.19441 0.92513zm-1.2581 0q0 0.52513 0.12514 0.79329 0.12514 0.26815 0.39106 0.26815 0.52066 0 0.52066-1.0614 0-1.0525-0.52066-1.0525-0.27262 0-0.39553 0.26368-0.12067 0.26369-0.12067 0.78882z"/>
+                                       <path d="m-514.72-175.57v1.5821q0 0.35978 0.0849 0.52514 0.0872 0.16536 0.26592 0.16536 0.27486 0 0.40223-0.22123 0.12961-0.22346 0.12961-0.71954v-1.3318h0.21005v2.4246h-0.17877l-0.0268-0.33966h-0.0179q-0.0782 0.18547-0.21676 0.28603-0.13854 0.0983-0.30167 0.0983-0.29273 0-0.42904-0.20781-0.13408-0.20782-0.13408-0.67933v-1.5821z"/>
+                                       <path d="m-512.19-173.11q-0.18323 0-0.33519-0.10055-0.14972-0.10279-0.22793-0.27933h-0.0201l0.0156 0.26592v1.1687h-0.21228v-3.524h0.1743l0.0223 0.33296h0.0201q0.0894-0.18324 0.22793-0.28156 0.14078-0.0983 0.30614-0.0983 0.71061 0 0.71061 1.2536 0 0.60558-0.1743 0.93407-0.1743 0.32848-0.50726 0.32848zm-0.0425-2.315q-0.26592 0-0.39553 0.23463-0.12961 0.23464-0.12961 0.73519v0.0693q0 0.55866 0.13185 0.82234 0.13184 0.26145 0.40223 0.26145 0.25027 0 0.37094-0.25922 0.1229-0.26145 0.1229-0.81116 0-0.52513-0.11396-0.78882-0.11397-0.26368-0.38882-0.26368z"/>
+                                       <path d="m-510.77-173.15h-0.21228v-3.477h0.21228z"/>
+                                       <path d="m-508.76-174.37q0 0.61228-0.19441 0.93854-0.19217 0.32401-0.55195 0.32401-0.3553 0-0.54524-0.32401-0.18771-0.32626-0.18771-0.93854 0-1.2536 0.74189-1.2536 0.3486 0 0.54301 0.32848 0.19441 0.32849 0.19441 0.92513zm-1.2581 0q0 0.52513 0.12514 0.79329 0.12513 0.26815 0.39105 0.26815 0.52067 0 0.52067-1.0614 0-1.0525-0.52067-1.0525-0.27262 0-0.39552 0.26368-0.12067 0.26369-0.12067 0.78882z"/>
+                                       <path d="m-507.28-173.15-0.0268-0.33966h-9e-3q-0.1743 0.38435-0.52737 0.38435-0.23687 0-0.38212-0.1877-0.14302-0.18994-0.14302-0.50726 0-0.34636 0.20782-0.54748 0.20782-0.20335 0.58324-0.22122l0.26145-0.0134v-0.20112q0-0.33966-0.0849-0.49385-0.0849-0.15642-0.28156-0.15642-0.20782 0-0.42458 0.13631l-0.0916-0.16759q0.25252-0.15642 0.52961-0.15642 0.29943 0 0.43128 0.18994 0.13184 0.1877 0.13184 0.63016v1.6514zm-0.5162-0.13631q0.22793 0 0.35307-0.22346 0.12738-0.2257 0.12738-0.63686v-0.25251l-0.25252 0.0134q-0.29273 0.0156-0.43574 0.16313-0.14078 0.14525-0.14078 0.42681 0 0.26368 0.0938 0.38659 0.0939 0.1229 0.25474 0.1229z"/>
+                                       <path d="m-505.87-173.11q-0.70614 0-0.70614-1.2536 0-0.61675 0.17654-0.93854 0.17653-0.32401 0.52066-0.32401 0.16313 0 0.30838 0.0939 0.14748 0.0938 0.23463 0.25921h0.0179l-9e-3 -0.27038v-1.0883h0.21229v3.477h-0.1743l-0.0179-0.33966h-0.0201q-0.0872 0.18547-0.2257 0.28603-0.13854 0.0983-0.31731 0.0983zm0.0134-0.18547q0.25474 0 0.39105-0.23463 0.13855-0.23687 0.13855-0.69497v-0.13854q0-0.54971-0.13408-0.80446-0.13184-0.25698-0.40446-0.25698-0.26145 0-0.37542 0.27486-0.11396 0.27262-0.11396 0.79105 0 0.5229 0.11843 0.79328 0.11844 0.27039 0.37989 0.27039z"/>
+                               </g>
+                               <g dominant-baseline="auto" fill="#540" stroke-width=".023836" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Drop files here">
+                                       <path d="m-520.62-179.04q0 1.6648-1.0391 1.6648h-0.581v-3.267h0.59441q0.50502 0 0.76424 0.40894 0.26144 0.40669 0.26144 1.1933zm-0.23016 0.0134q0-0.69049-0.20335-1.0525-0.20335-0.362-0.60111-0.362h-0.36648v2.8648h0.3486q0.42458 0 0.62346-0.37094 0.19888-0.37095 0.19888-1.0793z"/>
+                                       <path d="m-519.37-179.85q0.11397 0 0.21229 0.0313l-0.0514 0.21228q-0.0804-0.0335-0.16536-0.0335-0.1229 0-0.23016 0.12737-0.10503 0.12514-0.16536 0.35083-0.0603 0.2257-0.0603 0.49832v1.2849h-0.21229v-2.4246h0.1743l0.0224 0.42457h0.0156q0.16983-0.4715 0.46033-0.4715z"/>
+                                       <path d="m-517.43-178.6q0 0.61228-0.19441 0.93853-0.19218 0.32402-0.55195 0.32402-0.3553 0-0.54524-0.32402-0.18771-0.32625-0.18771-0.93853 0-1.2536 0.74189-1.2536 0.3486 0 0.54301 0.32849t0.19441 0.92513zm-1.2581 0q0 0.52513 0.12513 0.79328 0.12514 0.26816 0.39106 0.26816 0.52066 0 0.52066-1.0614 0-1.0525-0.52066-1.0525-0.27262 0-0.39552 0.26368-0.12067 0.26368-0.12067 0.78882z"/>
+                                       <path d="m-516.11-177.33q-0.18324 0-0.33519-0.10056-0.14972-0.10279-0.22793-0.27932h-0.0201l0.0156 0.26592v1.1687h-0.21229v-3.524h0.1743l0.0224 0.33296h0.0201q0.0894-0.18324 0.22793-0.28156 0.14078-0.0983 0.30614-0.0983 0.71061 0 0.71061 1.2536 0 0.60558-0.1743 0.93406-0.1743 0.32849-0.50726 0.32849zm-0.0425-2.315q-0.26592 0-0.39552 0.23463-0.12961 0.23464-0.12961 0.73519v0.0693q0 0.55865 0.13184 0.82234 0.13184 0.26145 0.40223 0.26145 0.25028 0 0.37095-0.25922 0.1229-0.26145 0.1229-0.81116 0-0.52514-0.11397-0.78882-0.11396-0.26368-0.38882-0.26368z"/>
+                                       <path d="m-513.42-179.61h-0.37988v2.2368h-0.21006v-2.2368h-0.30837v-0.12067l0.30837-0.0916v-0.18324q0-0.46256 0.1095-0.66591 0.10949-0.20335 0.37988-0.20335 0.15642 0 0.2838 0.0559l-0.0737 0.19665q-0.11843-0.0559-0.21452-0.0559-0.10949 0-0.16536 0.0648-0.0559 0.0648-0.0827 0.21005t-0.0268 0.40223v0.20335h0.37988zm0.60558 2.2368h-0.21229v-2.4246h0.21229zm-0.24357-3.0972q0-0.10056 0.0402-0.15642 0.0425-0.0559 0.10726-0.0559 0.0603 0 0.0961 0.0559t0.0358 0.15642q0 0.0961-0.0358 0.15419-0.0358 0.0559-0.0961 0.0559-0.0648 0-0.10726-0.0559-0.0402-0.0581-0.0402-0.15419z"/>
+                                       <path d="m-511.95-177.38h-0.21229v-3.477h0.21229z"/>
+                                       <path d="m-510.61-177.33q-0.38435 0-0.59217-0.32625-0.20558-0.32849-0.20558-0.91396 0-0.62122 0.18323-0.94747 0.18548-0.32849 0.53184-0.32849 0.30167 0 0.47597 0.28827 0.1743 0.28603 0.1743 0.77317v0.19665h-1.1486q4e-3 0.5296 0.14972 0.79328 0.14525 0.26369 0.44022 0.26369 0.22793 0 0.48044-0.14972v0.20558q-0.2324 0.14525-0.48938 0.14525zm-0.0961-2.324q-0.43798 0-0.48044 0.87373h0.93184q0-0.39999-0.12291-0.63686-0.12067-0.23687-0.32849-0.23687z"/>
+                                       <path d="m-508.55-177.99q0 0.30614-0.16089 0.48044-0.16089 0.17206-0.4648 0.17206-0.16536 0-0.2905-0.0425t-0.19441-0.0939v-0.24804q0.0827 0.0827 0.21899 0.13408 0.13631 0.0492 0.27933 0.0492 0.18771 0 0.29497-0.12291 0.10726-0.1229 0.10726-0.32848 0-0.1609-0.0782-0.27039-0.076-0.11173-0.29274-0.25251-0.24804-0.15642-0.33966-0.25028-0.0894-0.0938-0.14078-0.21005-0.0492-0.1162-0.0492-0.27709 0-0.26145 0.17653-0.43128 0.17654-0.17207 0.44916-0.17207 0.29273 0 0.48938 0.13855l-0.1095 0.18547q-0.18323-0.1229-0.38882-0.1229-0.1877 0-0.29943 0.11173-0.11174 0.10949-0.11174 0.2905 0 0.16089 0.076 0.27038 0.076 0.10727 0.32179 0.26145 0.24133 0.15866 0.33072 0.25475 0.0894 0.0939 0.13184 0.21005 0.0447 0.11397 0.0447 0.26369z"/>
+                                       <path d="m-506.11-177.38v-1.6715q0-0.32849-0.0872-0.4648-0.0849-0.13854-0.27932-0.13854-0.25698 0-0.38659 0.22793t-0.12961 0.71954v1.3274h-0.21228v-3.477h0.21228v1.1285q0 0.15418-9e-3 0.25698h0.0179q0.0715-0.18101 0.21452-0.27933 0.14525-0.10056 0.30168-0.10056 0.30837 0 0.43798 0.20112 0.12961 0.20111 0.12961 0.59887v1.6715z"/>
+                                       <path d="m-504.57-177.33q-0.38435 0-0.59217-0.32625-0.20558-0.32849-0.20558-0.91396 0-0.62122 0.18324-0.94747 0.18547-0.32849 0.53183-0.32849 0.30168 0 0.47597 0.28827 0.1743 0.28603 0.1743 0.77317v0.19665h-1.1486q4e-3 0.5296 0.14971 0.79328 0.14525 0.26369 0.44022 0.26369 0.22793 0 0.48044-0.14972v0.20558q-0.2324 0.14525-0.48938 0.14525zm-0.0961-2.324q-0.43799 0-0.48045 0.87373h0.93184q0-0.39999-0.12291-0.63686-0.12067-0.23687-0.32848-0.23687z"/>
+                                       <path d="m-502.79-179.85q0.11396 0 0.21228 0.0313l-0.0514 0.21228q-0.0805-0.0335-0.16536-0.0335-0.12291 0-0.23017 0.12737-0.10502 0.12514-0.16536 0.35083-0.0603 0.2257-0.0603 0.49832v1.2849h-0.21229v-2.4246h0.1743l0.0223 0.42457h0.0156q0.16983-0.4715 0.46033-0.4715z"/>
+                                       <path d="m-501.52-177.33q-0.38435 0-0.59217-0.32625-0.20559-0.32849-0.20559-0.91396 0-0.62122 0.18324-0.94747 0.18547-0.32849 0.53184-0.32849 0.30167 0 0.47597 0.28827 0.1743 0.28603 0.1743 0.77317v0.19665h-1.1486q4e-3 0.5296 0.14972 0.79328 0.14525 0.26369 0.44022 0.26369 0.22793 0 0.48044-0.14972v0.20558q-0.2324 0.14525-0.48938 0.14525zm-0.0961-2.324q-0.43798 0-0.48044 0.87373h0.93183q0-0.39999-0.1229-0.63686-0.12067-0.23687-0.32849-0.23687z"/>
+                               </g>
+                               <g dominant-baseline="auto" fill="#786721" stroke-width=".21029" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="multiple selection OK">
+                                       <path d="m-521.09-144.73v-1.2141q0-0.44852-0.26286-0.44852-0.18073 0-0.26123 0.15772-0.0789 0.15608-0.0789 0.46167v1.0432h-0.15443v-1.2141q0-0.22672-0.0641-0.3368-0.0641-0.11172-0.19879-0.11172-0.17744 0-0.25794 0.16101t-0.0805 0.52245v0.97918h-0.15608v-1.7826h0.13143l0.0131 0.24808h0.0148q0.10679-0.28258 0.36309-0.28258 0.14786 0 0.23329 0.0789 0.0854 0.0789 0.12322 0.23001 0.0641-0.16429 0.15772-0.23658 0.0937-0.0723 0.24316-0.0723 0.20208 0 0.29572 0.15608 0.0936 0.15443 0.0936 0.49616v1.1648z"/>
+                                       <path d="m-520.31-146.51v1.1632q0 0.26451 0.0624 0.38609 0.0641 0.12157 0.19551 0.12157 0.20208 0 0.29572-0.16265 0.0953-0.16429 0.0953-0.52902v-0.97918h0.15444v1.7826h-0.13144l-0.0197-0.24972h-0.0132q-0.0575 0.13636-0.15936 0.21029-0.10186 0.0723-0.22179 0.0723-0.21523 0-0.31545-0.15279-0.0986-0.15279-0.0986-0.49945v-1.1632z"/>
+                                       <path d="m-518.88-144.73h-0.15608v-2.5564h0.15608z"/>
+                                       <path d="m-518.07-144.84q0.0723 0 0.12815-0.0197v0.13143q-0.0723 0.0329-0.18565 0.0329-0.27601 0-0.27601-0.41237v-1.265h-0.16101v-0.092l0.15772-0.046 0.0509-0.42387h0.10843v0.42387h0.28258v0.13801h-0.28258v1.2207q0 0.18072 0.0394 0.24644 0.0394 0.0657 0.138 0.0657z"/>
+                                       <path d="m-517.51-144.73h-0.15608v-1.7826h0.15608zm-0.17908-2.2771q0-0.0739 0.0296-0.115 0.0312-0.0411 0.0789-0.0411 0.0444 0 0.0707 0.0411t0.0263 0.115q0 0.0707-0.0263 0.11336-0.0263 0.0411-0.0707 0.0411-0.0476 0-0.0789-0.0411-0.0296-0.0427-0.0296-0.11336z"/>
+                                       <path d="m-516.46-144.69q-0.13471 0-0.24643-0.0739-0.11008-0.0756-0.16758-0.20537h-0.0148l0.0115 0.19551v0.85925h-0.15608v-2.5909h0.12815l0.0164 0.2448h0.0148q0.0657-0.13472 0.16758-0.20701 0.1035-0.0723 0.22508-0.0723 0.52245 0 0.52245 0.92168 0 0.44523-0.12815 0.68674t-0.37295 0.24151zm-0.0312-1.7021q-0.19551 0-0.2908 0.17251-0.0953 0.17251-0.0953 0.54052v0.0509q0 0.41073 0.0969 0.6046 0.0969 0.19222 0.29573 0.19222 0.18401 0 0.27273-0.19058 0.0904-0.19222 0.0904-0.59638 0-0.38609-0.0838-0.57995-0.0838-0.19387-0.28587-0.19387z"/>
+                                       <path d="m-515.41-144.73h-0.15608v-2.5564h0.15608z"/>
+                                       <path d="m-514.43-144.69q-0.28259 0-0.43538-0.23987-0.15115-0.24151-0.15115-0.67195 0-0.45673 0.13472-0.6966 0.13636-0.24151 0.39102-0.24151 0.22179 0 0.34994 0.21194 0.12815 0.21029 0.12815 0.56845v0.14458h-0.84446q3e-3 0.38937 0.11007 0.58323 0.10679 0.19387 0.32366 0.19387 0.16758 0 0.35323-0.11008v0.15115q-0.17087 0.10679-0.3598 0.10679zm-0.0707-1.7086q-0.32201 0-0.35323 0.64238h0.6851q0-0.29408-0.0904-0.46823-0.0887-0.17415-0.24151-0.17415z"/>
+                                       <path d="m-512.29-145.17q0 0.22508-0.11829 0.35323-0.11829 0.1265-0.34173 0.1265-0.12157 0-0.21358-0.0312-0.092-0.0312-0.14293-0.069v-0.18236q0.0608 0.0608 0.16101 0.0986 0.10021 0.0362 0.20536 0.0362 0.13801 0 0.21687-0.0904 0.0789-0.0904 0.0789-0.24151 0-0.11829-0.0575-0.1988-0.0559-0.0821-0.21523-0.18565-0.18236-0.115-0.24972-0.184-0.0657-0.069-0.10351-0.15444-0.0361-0.0854-0.0361-0.20372 0-0.19222 0.12979-0.31709 0.12979-0.1265 0.33023-0.1265 0.21522 0 0.3598 0.10186l-0.0805 0.13636q-0.13472-0.0904-0.28587-0.0904-0.13801 0-0.22015 0.0822-0.0821 0.0805-0.0821 0.21358 0 0.11829 0.0559 0.19879 0.0559 0.0789 0.23658 0.19222 0.17744 0.11665 0.24315 0.1873 0.0657 0.069 0.0969 0.15443 0.0329 0.0838 0.0329 0.19387z"/>
+                                       <path d="m-511.42-144.69q-0.28258 0-0.43537-0.23987-0.15115-0.24151-0.15115-0.67195 0-0.45673 0.13472-0.6966 0.13636-0.24151 0.39101-0.24151 0.2218 0 0.34995 0.21194 0.12814 0.21029 0.12814 0.56845v0.14458h-0.84446q3e-3 0.38937 0.11008 0.58323 0.10679 0.19387 0.32365 0.19387 0.16758 0 0.35323-0.11008v0.15115q-0.17086 0.10679-0.3598 0.10679zm-0.0706-1.7086q-0.32202 0-0.35323 0.64238h0.6851q0-0.29408-0.0904-0.46823-0.0887-0.17415-0.24151-0.17415z"/>
+                                       <path d="m-510.45-144.73h-0.15608v-2.5564h0.15608z"/>
+                                       <path d="m-509.47-144.69q-0.28258 0-0.43537-0.23987-0.15115-0.24151-0.15115-0.67195 0-0.45673 0.13472-0.6966 0.13636-0.24151 0.39102-0.24151 0.22179 0 0.34994 0.21194 0.12815 0.21029 0.12815 0.56845v0.14458h-0.84447q3e-3 0.38937 0.11008 0.58323 0.10679 0.19387 0.32366 0.19387 0.16757 0 0.35322-0.11008v0.15115q-0.17086 0.10679-0.3598 0.10679zm-0.0706-1.7086q-0.32201 0-0.35323 0.64238h0.6851q0-0.29408-0.0904-0.46823-0.0887-0.17415-0.24151-0.17415z"/>
+                                       <path d="m-508.2-144.69q-0.2678 0-0.40416-0.23165-0.13472-0.2333-0.13472-0.68346 0-0.45673 0.13636-0.69495 0.13801-0.23987 0.40581-0.23987 0.16593 0 0.27601 0.0608l-0.0608 0.138q-0.11008-0.0509-0.20208-0.0509-0.39266 0-0.39266 0.78368 0 0.7771 0.39266 0.7771 0.11665 0 0.25301-0.0526v0.13143q-0.0542 0.0296-0.13308 0.046-0.0789 0.0164-0.13636 0.0164z"/>
+                                       <path d="m-507.28-144.84q0.0723 0 0.12815-0.0197v0.13143q-0.0723 0.0329-0.18565 0.0329-0.27601 0-0.27601-0.41237v-1.265h-0.161v-0.092l0.15772-0.046 0.0509-0.42387h0.10843v0.42387h0.28258v0.13801h-0.28258v1.2207q0 0.18072 0.0394 0.24644 0.0394 0.0657 0.138 0.0657z"/>
+                                       <path d="m-506.72-144.73h-0.15608v-1.7826h0.15608zm-0.17908-2.2771q0-0.0739 0.0296-0.115 0.0312-0.0411 0.0789-0.0411 0.0444 0 0.0707 0.0411t0.0263 0.115q0 0.0707-0.0263 0.11336-0.0263 0.0411-0.0707 0.0411-0.0476 0-0.0789-0.0411-0.0296-0.0427-0.0296-0.11336z"/>
+                                       <path d="m-505.24-145.62q0 0.45016-0.14293 0.69003-0.14129 0.23822-0.4058 0.23822-0.26123 0-0.40088-0.23822-0.138-0.23987-0.138-0.69003 0-0.92168 0.54545-0.92168 0.25629 0 0.39923 0.24151 0.14293 0.24151 0.14293 0.68017zm-0.92496 0q0 0.38609 0.092 0.58324t0.28751 0.19715q0.3828 0 0.3828-0.78039 0-0.77382-0.3828-0.77382-0.20043 0-0.2908 0.19387-0.0887 0.19386-0.0887 0.57995z"/>
+                                       <path d="m-504.04-144.73v-1.2289q0-0.44359-0.26287-0.44359-0.20044 0-0.29408 0.16593-0.092 0.16594-0.092 0.52738v0.97918h-0.15607v-1.7826h0.13143l0.0131 0.24808h0.0148q0.0559-0.13472 0.16101-0.20865 0.10514-0.0739 0.22508-0.0739 0.20701 0 0.31051 0.13965 0.1035 0.138 0.1035 0.44523v1.2322z"/>
+                                       <path d="m-501.45-145.93q0 0.59638-0.17908 0.91675-0.17743 0.32037-0.51423 0.32037-0.34009 0-0.51588-0.32201-0.17579-0.32366-0.17579-0.9184 0-0.62102 0.17415-0.92496t0.52245-0.30394q0.33515 0 0.51095 0.32037 0.17743 0.31872 0.17743 0.91182zm-1.2174 0q0 0.53559 0.13307 0.80996 0.13472 0.27273 0.39102 0.27273 0.25794 0 0.39101-0.27109 0.13472-0.27108 0.13472-0.8116 0-0.53395-0.13143-0.80503-0.13143-0.27273-0.38937-0.27273-0.26451 0-0.39759 0.27601-0.13143 0.27437-0.13143 0.80175z"/>
+                                       <path d="m-499.91-144.73h-0.18072l-0.56517-1.1977-0.18565 0.2448v0.95289h-0.16101v-2.402h0.16101v1.2716q0.0838-0.15772 0.70974-1.2716h0.1758l-0.60296 1.0564z"/>
+                               </g>
+                       </g>
+                       <path d="m-511.33-186.63a24.457 24.457 0 0 0 -24.457 24.457 24.457 24.457 0 0 0 24.457 24.457 24.457 24.457 0 0 0 24.457 -24.457 24.457 24.457 0 0 0 -24.457 -24.457zm0.0667 1.7198a22.804 22.804 0 0 1 22.804 22.804 22.804 22.804 0 0 1 -22.804 22.804 22.804 22.804 0 0 1 -22.804 -22.804 22.804 22.804 0 0 1 22.804 -22.804z" fill="url(#c)"/>
+                       <path d="m-511.4-186.5a24.457 24.457 0 0 0 -24.457 24.457 24.457 24.457 0 0 0 24.457 24.457 24.457 24.457 0 0 0 24.457 -24.457 24.457 24.457 0 0 0 -24.457 -24.457zm0.0667 1.7198a22.804 22.804 0 0 1 22.804 22.804 22.804 22.804 0 0 1 -22.804 22.804 22.804 22.804 0 0 1 -22.804 -22.804 22.804 22.804 0 0 1 22.804 -22.804z" fill="url(#b)"/>
+               </g>
+               <circle cx="-511.47" cy="-162.18" r="24.457" fill="none" stroke="#d4aa00" stroke-dasharray="1.58700002, 1.58700002" stroke-linecap="round" stroke-linejoin="round" stroke-width=".529"/>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/index.html
new file mode 100644 (file)
index 0000000..6788572
--- /dev/null
@@ -0,0 +1,35 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <link rel="stylesheet" type="text/css" href="deaddrop.css"/>
+  <script src="deaddrop.js"></script>
+ </head>
+ <body>
+       <img src="libwebsockets.org-logo.svg"><img src="strict-csp.svg">
+       &nbsp;<br>
+       <div class="uplbox">
+        <table id="ddrop" class="noconn">
+         <tr><td class="vm">
+               <div id="da" class="da"><img class="disa" src="drop.svg"></div>
+         </td><td>
+          <h3>...or select files to upload:</h3>
+          <span id="size"></span><br>
+         <form name=multipart action="upload" method="post"
+                enctype="multipart/form-data">
+           <input multiple type="file" name="file" id="file" size="20">
+               <span id="file_info" class="dd-fileinfo"></span>
+           <br>
+           <input id="upl" type="submit" name="Upload" value="Upload"
+                   class="ubtn" disabled>
+         </form>
+          <table id="ongoing" class="nb"></table>
+        </td></tr>
+        <tr><td colspan="2">
+               <div id="dd-list" class="dd-list">
+               <!-- deaddrop-list -->
+               </div>
+        </td></tr>
+        </table>
+       </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-deaddrop/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-deaddrop/uploads/user1/placeholder.txt b/minimal-examples/http-server/minimal-http-server-deaddrop/uploads/user1/placeholder.txt
new file mode 100644 (file)
index 0000000..2d1be9e
--- /dev/null
@@ -0,0 +1 @@
+git doesn't support empty dirs...
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-dynamic/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5b5794c
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-dynamic)
+set(SRCS minimal-http-server-dynamic.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/README.md b/minimal-examples/http-server/minimal-http-server-dynamic/README.md
new file mode 100644 (file)
index 0000000..9666d02
--- /dev/null
@@ -0,0 +1,20 @@
+# lws minimal http server dynamic content
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-dynamic
+[2018/03/20 10:24:24:7099] USER: LWS minimal http server dynamic | visit http://localhost:7681
+[2018/03/20 10:24:24:7099] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+```
+
+Visit http://localhost:7681, which is all static content.
+
+Click on the link to /dyn/anything, this opens a new tab with dynamicly-produced content.
+
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-dynamic/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-dynamic/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c b/minimal-examples/http-server/minimal-http-server-dynamic/minimal-http-server-dynamic.c
new file mode 100644 (file)
index 0000000..bb537eb
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * lws-minimal-http-server-dynamic
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that can produce dynamic http
+ * content as well as static content.
+ *
+ * To keep it simple, it serves the static stuff from the subdirectory
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+
+/*
+ * Unlike ws, http is a stateless protocol.  This pss only exists for the
+ * duration of a single http transaction.  With http/1.1 keep-alive and http/2,
+ * that is unrelated to (shorter than) the lifetime of the network connection.
+ */
+struct pss {
+       char path[128];
+
+       int times;
+       int budget;
+
+       int content_lines;
+};
+
+static int interrupted;
+
+static int
+callback_dynamic_http(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - LWS_PRE - 1];
+       time_t t;
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+
+               /* in contains the url part after our mountpoint /dyn, if any */
+               lws_snprintf(pss->path, sizeof(pss->path), "%s", (const char *)in);
+
+               /*
+                * prepare and write http headers... with regards to content-
+                * length, there are three approaches:
+                *
+                *  - http/1.0 or connection:close: no need, but no pipelining
+                *  - http/1.1 or connected:keep-alive
+                *     (keep-alive is default for 1.1): content-length required
+                *  - http/2: no need, LWS_WRITE_HTTP_FINAL closes the stream
+                *
+                * giving the api below LWS_ILLEGAL_HTTP_CONTENT_LEN instead of
+                * a content length forces the connection response headers to
+                * send back "connection: close", disabling keep-alive.
+                *
+                * If you know the final content-length, it's always OK to give
+                * it and keep-alive can work then if otherwise possible.  But
+                * often you don't know it and avoiding having to compute it
+                * at header-time makes life easier at the server.
+                */
+               if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
+                               "text/html",
+                               LWS_ILLEGAL_HTTP_CONTENT_LEN, /* no content len */
+                               &p, end))
+                       return 1;
+               if (lws_finalize_write_http_header(wsi, start, &p, end))
+                       return 1;
+
+               pss->times = 0;
+               pss->budget = atoi((char *)in + 1);
+               pss->content_lines = 0;
+               if (!pss->budget)
+                       pss->budget = 10;
+
+               /* write the body separately */
+               lws_callback_on_writable(wsi);
+
+               return 0;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+
+               if (!pss || pss->times > pss->budget)
+                       break;
+
+               /*
+                * We send a large reply in pieces of around 2KB each.
+                *
+                * For http/1, it's possible to send a large buffer at once,
+                * but lws will malloc() up a temp buffer to hold any data
+                * that the kernel didn't accept in one go.  This is expensive
+                * in memory and cpu, so it's better to stage the creation of
+                * the data to be sent each time.
+                *
+                * For http/2, large data frames would block the whole
+                * connection, not just the stream and are not allowed.  Lws
+                * will call back on writable when the stream both has transmit
+                * credit and the round-robin fair access for sibling streams
+                * allows it.
+                *
+                * For http/2, we must send the last part with
+                * LWS_WRITE_HTTP_FINAL to close the stream representing
+                * this transaction.
+                */
+               n = LWS_WRITE_HTTP;
+               if (pss->times == pss->budget)
+                       n = LWS_WRITE_HTTP_FINAL;
+
+               if (!pss->times) {
+                       /*
+                        * the first time, we print some html title
+                        */
+                       t = time(NULL);
+                       /*
+                        * to work with http/2, we must take care about LWS_PRE
+                        * valid behind the buffer we will send.
+                        */
+                       p += lws_snprintf((char *)p, end - p, "<html>"
+                               "<head><meta charset=utf-8 "
+                               "http-equiv=\"Content-Language\" "
+                               "content=\"en\"/></head><body>"
+                               "<img src=\"/libwebsockets.org-logo.svg\">"
+                               "<br>Dynamic content for '%s' from mountpoint."
+                               "<br>Time: %s<br><br>"
+                               "</body></html>", pss->path, ctime(&t));
+               } else {
+                       /*
+                        * after the first time, we create bulk content.
+                        *
+                        * Again we take care about LWS_PRE valid behind the
+                        * buffer we will send.
+                        */
+
+                       while (lws_ptr_diff(end, p) > 80)
+                               p += lws_snprintf((char *)p, end - p,
+                                       "%d.%d: this is some content... ",
+                                       pss->times, pss->content_lines++);
+
+                       p += lws_snprintf((char *)p, end - p, "<br><br>");
+               }
+
+               pss->times++;
+               if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n) !=
+                               lws_ptr_diff(p, start))
+                       return 1;
+
+               /*
+                * HTTP/1.0 no keepalive: close network connection
+                * HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction
+                * HTTP/2: stream ended, parent connection remains up
+                */
+               if (n == LWS_WRITE_HTTP_FINAL) {
+                   if (lws_http_transaction_completed(wsi))
+                       return -1;
+               } else
+                       lws_callback_on_writable(wsi);
+
+               return 0;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocol =
+       { "http", callback_dynamic_http, sizeof(struct pss), 0 };
+
+static const struct lws_protocols *pprotocols[] = { &protocol, NULL };
+
+/* override the default mount for /dyn in the URL space */
+
+static const struct lws_http_mount mount_dyn = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/dyn",         /* mountpoint URL */
+       /* .origin */                   NULL,   /* protocol */
+       /* .def */                      NULL,
+       /* .protocol */                 "http",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_CALLBACK, /* dynamic */
+       /* .mountpoint_len */           4,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */       &mount_dyn,             /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */           "./mount-origin",       /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server dynamic | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       /* for testing ah queue, not useful in real world */
+       if (lws_cmdline_option(argc, argv, "--ah1"))
+               info.max_http_header_pool = 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       /* http on 7681 */
+
+       info.port = 7681;
+       info.pprotocols = pprotocols;
+       info.mounts = &mount;
+       info.vhost_name = "http";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create tls vhost\n");
+               goto bail;
+       }
+
+       /* https on 7682 */
+
+       info.port = 7682;
+       info.error_document_404 = "/404.html";
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+       info.vhost_name = "localhost";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create tls vhost\n");
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/index.html
new file mode 100644 (file)
index 0000000..8fe93d7
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server dynamic content example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               Stuff down /dyn in the URL space is generated dynamically<br>
+               by the callback.  For example, click on
+               <a href="/dyn/anything" target=_blank>/dyn/anything</a> to
+               see dynamic content.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-dynamic/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-eventlib-demos/CMakeLists.txt
new file mode 100644 (file)
index 0000000..593d687
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-eventlib-demos)
+set(SRCS minimal-http-server-eventlib-demos.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/README.md b/minimal-examples/http-server/minimal-http-server-eventlib-demos/README.md
new file mode 100644 (file)
index 0000000..90720e4
--- /dev/null
@@ -0,0 +1,30 @@
+# lws minimal http server eventlib demos
+
+This demonstrates a slightly more complex demo that can use
+any of the event loops (it defaults to poll)
+
+It uses statically included plugins to provide the lws test server functions
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+--uv|Use the libuv event library (lws must have been configured with `-DLWS_WITH_LIBUV=1`)
+--event|Use the libevent library (lws must have been configured with `-DLWS_WITH_LIBEVENT=1`)
+--ev|Use the libev event library (lws must have been configured with `-DLWS_WITH_LIBEV=1`)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-eventlib-demos
+[2018/03/04 09:30:02:7986] USER: LWS minimal http server-eventlib-demos | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-eventlib-demos/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c b/minimal-examples/http-server/minimal-http-server-eventlib-demos/minimal-http-server-eventlib-demos.c
new file mode 100644 (file)
index 0000000..eaad580
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * lws-minimal-http-server-eventlib
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http[s] server that can work with any of the
+ * supported event loop backends, or the default poll() one.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "../../../plugins/protocol_lws_mirror.c"
+#include "../../../plugins/protocol_lws_status.c"
+#include "../../../plugins/protocol_dumb_increment.c"
+#include "../../../plugins/protocol_post_demo.c"
+
+static struct lws_context *context;
+
+static struct lws_protocols protocols[] = {
+       /* first protocol must always be HTTP handler */
+
+       { "http-only", lws_callback_http_dummy, 0, 0, },
+       LWS_PLUGIN_PROTOCOL_DUMB_INCREMENT,
+       LWS_PLUGIN_PROTOCOL_MIRROR,
+       LWS_PLUGIN_PROTOCOL_LWS_STATUS,
+       LWS_PLUGIN_PROTOCOL_POST_DEMO,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/*
+ * mount handlers for sections of the URL space
+ */
+
+static const struct lws_http_mount mount_ziptest = {
+       NULL,                   /* linked-list pointer to next*/
+       "/ziptest",             /* mountpoint in URL namespace on this vhost */
+       "candide.zip",  /* handler */
+       NULL,   /* default filename if none given */
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_FILE,   /* origin points to a callback */
+       8,                      /* strlen("/ziptest"), ie length of the mountpoint */
+       NULL,
+
+       { NULL, NULL } // sentinel
+};
+
+static const struct lws_http_mount mount_post = {
+       (struct lws_http_mount *)&mount_ziptest, /* linked-list pointer to next*/
+       "/formtest",            /* mountpoint in URL namespace on this vhost */
+       "protocol-post-demo",   /* handler */
+       NULL,   /* default filename if none given */
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_CALLBACK,       /* origin points to a callback */
+       9,                      /* strlen("/formtest"), ie length of the mountpoint */
+       NULL,
+
+       { NULL, NULL } // sentinel
+};
+
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_post,    /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "test.html",    /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void signal_cb(void *handle, int signum)
+{
+       lwsl_err("%s: signal %d\n", __func__, signum);
+
+       switch (signum) {
+       case SIGTERM:
+       case SIGINT:
+               break;
+       default:
+
+               break;
+       }
+       lws_context_destroy(context);
+}
+
+void sigint_handler(int sig)
+{
+       signal_cb(NULL, sig);
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p;
+       int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server eventlib | visit http://localhost:7681\n");
+       lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.pcontext = &context;
+       info.protocols = protocols;
+       info.signal_cb = signal_cb;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       if (lws_cmdline_option(argc, argv, "--uv"))
+               info.options |= LWS_SERVER_OPTION_LIBUV;
+       else
+               if (lws_cmdline_option(argc, argv, "--event"))
+                       info.options |= LWS_SERVER_OPTION_LIBEVENT;
+               else
+                       if (lws_cmdline_option(argc, argv, "--ev"))
+                               info.options |= LWS_SERVER_OPTION_LIBEV;
+                       else
+                               signal(SIGINT, sigint_handler);
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (!lws_service(context, 0))
+               ;
+
+       lwsl_info("calling external context destroy\n");
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png
new file mode 100644 (file)
index 0000000..b4129e7
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/http2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/lws-common.js b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/lws-common.js
new file mode 100644 (file)
index 0000000..5d56ca2
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * This section around grayOut came from here:
+ * http://www.codingforums.com/archive/index.php/t-151720.html
+ * Assumed public domain
+ *
+ * Init like this in your main html script, this also reapplies the gray
+ *
+ *    lws_gray_out(true,{'zindex':'499'});
+ *
+ * To remove the gray
+ *
+ *    lws_gray_out(false);
+ *
+ */
+
+function gsize(ptype)
+{
+       var h = document.compatMode === "CSS1Compat" &&
+               !window.opera ?
+                       document.documentElement.clientHeight :
+                                               document.body.clientHeight;
+       var w = document.compatMode === "CSS1Compat" &&
+               !window.opera ? 
+                       document.documentElement.clientWidth :
+                                               document.body.clientWidth;
+       var pageWidth, pageHeight, t;
+
+       if (document.body && 
+                   (document.body.scrollWidth || document.body.scrollHeight)) {
+               t = document.body.scrollWidth;
+               pageWidth = (w > t) ? ("" + w + "px") : ("" + (t) + "px");
+               t = document.body.scrollHeight;
+               pageHeight = (h > t) ? ("" + h + "px") : ("" + (t) + "px");
+       } else if (document.body.offsetWidth) {
+               t = document.body.offsetWidth;
+               pageWidth = (w > t) ? ("" + w + "px") : ("" + (t) + "px");
+               t = document.body.offsetHeight;
+               pageHeight =(h > t) ? ("" + h + "px") : ("" + (t) + "px");
+       } else {
+               pageWidth = "100%";
+               pageHeight = "100%";
+       }
+       return (ptype === 1) ? pageWidth : pageHeight;
+}
+
+function addEvent( obj, type, fn ) {
+       if ( obj.attachEvent ) {
+               obj["e" + type + fn] = fn;
+               obj[type+fn] = function() { obj["e" + type + fn]( window.event );};
+               obj.attachEvent("on" + type, obj[type + fn]);
+       } else
+               obj.addEventListener(type, fn, false);
+}
+
+function removeEvent( obj, type, fn ) {
+       if ( obj.detachEvent ) {
+               obj.detachEvent("on" + type, obj[type + fn]);
+               obj[type + fn] = null;
+       } else
+               obj.removeEventListener(type, fn, false);
+}
+
+function lws_gray_out(vis, _options) {
+
+       var options = _options || {};
+       var zindex = options.zindex || 50;
+       var opacity = options.opacity || 70;
+       var opaque = (opacity / 100);
+       var bgcolor = options.bgcolor || "#000000";
+       var dark = document.getElementById("darkenScreenObject");
+
+       if (!dark) {
+               var tbody = document.getElementsByTagName("body")[0];
+               var tnode = document.createElement("div");
+               tnode.style.position = "absolute";
+               tnode.style.top = "0px";
+               tnode.style.left = "0px";
+               tnode.style.overflow = "hidden";
+               tnode.style.display ="none";
+               tnode.id = "darkenScreenObject";
+               tbody.appendChild(tnode);
+               dark = document.getElementById("darkenScreenObject");
+       }
+       if (vis) {
+               dark.style.opacity = opaque;
+               dark.style.MozOpacity = opaque;
+               // dark.style.filter ='alpha(opacity='+opacity+')';
+               dark.style.zIndex = zindex;
+               dark.style.backgroundColor = bgcolor;
+               dark.style.width = gsize(1);
+               dark.style.height = gsize(0);
+               dark.style.display = "block";
+               addEvent(window, "resize",
+                       function() {
+                               dark.style.height = gsize(0);
+                               dark.style.width = gsize(1);
+                       }
+               );
+       } else {
+               dark.style.display = "none";
+               removeEvent(window, "resize",
+                       function() {
+                               dark.style.height = gsize(0);
+                               dark.style.width = gsize(1);
+                       }
+               );
+       }
+}
+
+/*
+ * end of grayOut related stuff
+ */
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+function lws_san(s)
+{
+       if (s.search("<") !== -1)
+               return "invalid string";
+       
+       return s;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.css b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.css
new file mode 100644 (file)
index 0000000..6cd32e7
--- /dev/null
@@ -0,0 +1,190 @@
+
+span.title {
+       font-size:18pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#000000;
+}
+span.mount {
+       font-size:10pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#000000;
+}
+span.mountname {
+       font-size:14pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#404010;
+}
+span.n {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#808020;
+}
+span.v {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#202020;
+}
+span.m1 {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#202020;
+}
+span.m2 {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#202020;
+}
+
+.browser { font-size:12pt; font-family: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; border-radius:10px;}
+.group2 { vertical-align:middle;
+       text-align:center;
+       background:#f0f0e0; 
+       padding:12px; 
+       -webkit-border-radius:10px; 
+       border-radius:10px; }
+.explain { vertical-align:middle;
+       text-align:center;
+       background:#f0f0c0; padding:12px;
+       -webkit-border-radius:10px;
+       border-radius:10px;
+       color:#404000;
+       padding:3px;
+}
+td.wsstatus { vertical-align:middle; width:200px; height:50px;
+       text-align:center;
+       background:#f0f0c0; padding:6px;
+       -webkit-border-radius:8px;
+       border-radius:8px;
+       color:#404000; }
+.tdform { vertical-align:middle; width:200px; height:50px;
+       text-align:center;
+       background:#f0f0d0; padding:6px;
+       -webkit-border-radius:8px;
+       margin:10px;
+       border-radius:8px;
+       border: 1px solid black;
+       border-collapse: collapse;font-size:18pt; font-family: Arial; font-weight:normal; text-align:center; color:#000000; 
+       color:#404000; }
+       
+td.l { vertical-align:middle;
+       text-align:center;
+       background:#d0d0b0; 
+       padding:3px; 
+       -webkit-border-radius:3px;
+       border-radius:3px; }
+       
+td.bigger { font-size:120%; }
+
+div.bgw {  background:white }
+div.conninfo {
+       border: solid 2px #e0d040;
+       padding: 4px;
+       width: 500px;
+       height:350px;
+       overflow: auto;
+}
+span.f12 { font-size:12pt }
+       
+.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; border-radius:10px; }
+.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; border-radius:10px; }
+.tabs {
+  position: relative;   
+  min-height: 750px; /* This part sucks */
+  clear: both;
+  margin: 25px 0;
+}
+.tab {
+  float: left;
+}
+.tab label {
+  background: #eee; 
+  padding: 10px; 
+  border: 1px solid #ccc; 
+  margin-left: -1px; 
+  position: relative;
+  left: 1px; 
+}
+.tab [type=radio] {
+  display: none;   
+}
+.content {
+  position: absolute;
+  top: 28px;
+  left: 0;
+  background: white;
+  right: 0;
+  bottom: 0;
+  padding: 20px;
+  border: 1px solid #ccc; 
+}
+[type=radio]:checked ~ label {
+  background: white;
+  border-bottom: 1px solid white;
+  z-index: 2;
+}
+[type=radio]:checked ~ label ~ .content {
+  z-index: 1;
+}
+
+       td.wsstatus { vertical-align:middle; width:200px; height:50px;
+               text-align:center;
+               background:#f0f0c0; padding:6px;
+               -webkit-border-radius:8px;
+               border-radius:8px;
+               color:#404000; }
+       td.l { vertical-align:middle;
+               text-align:center;
+               background:#d0d0b0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.dl { vertical-align:middle;
+               text-align:center;
+               background:#c0c0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c { vertical-align:middle;
+               text-align:center;
+               background:#c0c0a0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c0 { vertical-align:middle;
+               text-align:center;
+               background:#b0b090; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.dc0 { vertical-align:middle;
+               text-align:center;
+               background:#a0a0a0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c1 { vertical-align:middle;
+               text-align:center;
+               background:#c0c0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.t { vertical-align:middle;
+               text-align:center;
+               background:#e0e0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.html b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/test.html
new file mode 100644 (file)
index 0000000..047bcc8
--- /dev/null
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ <link rel="stylesheet" type="text/css" href="test.css"/>
+ <script type='text/javascript' src="/lws-common.js"></script>
+ <script type='text/javascript' src='test.js'></script>
+ <title>Minimal Websocket test app</title>
+</head>
+
+<body>
+<header></header>
+<article>
+
+<table><tr><td>
+
+<table width=800px>
+ <tr>
+  <td valign=middle align=center>
+   <a href="https://libwebsockets.org">
+          <img src="libwebsockets.org-logo.svg"></a>
+  </td>
+  <td><img src="strict-csp.svg"></td>
+  <td>
+       <section class="browser">
+       <div id=brow>...</div></section>
+  </td>
+  <td width="64" height="64" id="wstransport"></td>
+  <td width="64" height="64" id="transport"></td>
+ </tr>
+
+</table>
+</td></tr>
+<tr><td colspan=2 align=center>
+Click <a href="leaf.jpg" target="_blank">Here</a> to
+have the test server send a big picture by http.
+</td></tr>
+<tr><td colspan=2>
+<div class="tabs">
+
+   <div class="tab">
+       <input type="radio" id="tab-1" name="tab-group-1" checked>
+       <label for="tab-1">Dumb Increment Demo</label>
+       
+       <div class="content">
+        <div id="dumb" class="group2">
+         <table>
+          <tr>
+          <td id=wsdi_statustd align=center class="wsstatus">
+            <span id=wsdi_status>Websocket connection not initialized
+               </span></td>
+           <td><span class="title">dumb increment-protocol</span></td>
+          </tr>
+          <tr>
+          <td class="explain" colspan=2>
+The incrementing number is coming from the server at 20Hz and is individual for
+each connection to the server... try opening a second browser window.
+<br/><br/>
+The button sends a message over the websocket link to ask the server
+to zero just this connection's number.
+          </td>
+         </tr>
+          <tr>
+           <td align=center><div id=number class="bigger"> </div></td>
+           <td align=center>
+            <input type=button id=offset value="Reset counter">
+            <input type=button id=junk value="Send junk">
+           </td>
+           </tr>
+        </table>
+       </div>
+       </div> 
+   </div>
+
+   <div class="tab">
+    <input type="radio" id="tab-2" name="tab-group-1">
+    <label for="tab-2">Mirror Demo</label>
+       
+    <div class="content">
+     <div id="mirror" class="group2">
+      <table>
+       <tr>
+        <td colspan=1 id=wslm_statustd align=center class="wsstatus">
+        <span id=wslm_status>Websocket connection not initialized</span>
+       </td>
+        <td>
+         <span class="title">lws-mirror-protocol</span>
+        </td>
+       </tr>
+       <tr>
+       <td colspan=2>
+         <div class="explain">
+Use the mouse to draw on the canvas below -- all other browser windows open
+on this page see your drawing in realtime and you can see any of theirs as
+well.
+<br/><br/>
+The lws-mirror protocol doesn't interpret what is being sent to it, it just
+re-sends it to every other websocket it has a connection with using that
+protocol, including the guy who sent the packet.
+<br/><br/>
+<b>libwebsockets-test-client</b> joins in by spamming circles on to this
+shared canvas when run.
+         </div>
+        </td>
+       </tr>
+       <tr>
+       <td colspan=2>Drawing color:
+         <select id="color">
+               <option value=#000000>Black</option>
+               <option value=#0000ff>Blue</option>
+               <option value=#20ff20>Green</option>
+               <option value=#802020>Dark Red</option>
+         </select>
+       </tr>
+       <tr>
+        <td colspan=2 width=500 height=320>
+               <div id="wslm_drawing" class="bgw"></div>
+       </td>
+       </tr>
+      </table>
+     </div>
+    </div> 
+   </div>
+    
+    <div class="tab">
+       <input type="radio" id="tab-3" name="tab-group-1">
+       <label for="tab-3">Close Testing</label>
+     
+       <div class="content">
+<div id="ot" class="group2">
+      <table>
+       <tr>
+        <td>
+
+               </td></tr>
+               <tr><td id=ot_statustd align=center class="wsstatus">
+                <span id=ot_status>Websocket connection not initialized</span>
+               </td>
+               <td colspan=2><span class="title">Open and close testing
+                               </span></td>
+               </tr>
+               <tr>    
+<td class="explain" colspan=3 >
+To help with open and close testing, you can open and close a connection by
+hand using the buttons.<br>
+ "<b>Close</b>" closes the connection from the browser with code 3000
+  and reason 'Bye!".<br>
+ "<b>Request Server Close</b>" sends a message asking the server to
+initiate the close, which it does with code 1001 and reason "Seeya".
+</td></tr>
+       <tr>
+       <td align=center>
+         <input type="button" id="ot_open_btn" value="Open"></td>
+       <td align=center>
+         <input type="button" id="ot_close_btn" disabled value="Close" ></td>
+       <td align=center>
+         <input type="button" id="ot_req_close_btn" disabled
+                value="Request Server Close" ></td>
+       </tr>
+
+</table>
+
+</div>
+       </div> 
+   </div>
+   
+    <div class="tab">
+       <input type="radio" id="tab-4" name="tab-group-1">
+       <label for="tab-4">Server info</label>
+
+       <div class="content">
+<div id="ot" class="group2">
+      <table>
+       <tr>
+       <td id=s_statustd align=center class="wsstatus">
+        <div id=s_status>Websocket connection not initialized</div>
+       </td>
+               <td colspan=1>
+       <span class="title">Server Info</span>
+               <input type=button id=pmd value="Test pmd">
+
+       </td>
+       </tr><tr>
+<td class="explain" colspan=2>
+This information is sent by the server over a ws[s] link and updated live
+whenever the information changes server-side.
+</td></tr>
+       <tr>
+       <td align=center colspan=2><div id=servinfo></div></td>
+       </tr>
+       <tr>
+       <td align=center colspan=2><div id=conninfo class="conninfo"></div></td>
+       </tr>
+</table>
+</div>
+       </div> 
+   </div>
+
+    <div class="tab">
+       <input type="radio" id="tab-5" name="tab-group-1">
+       <label for="tab-5">POST</label>
+
+       <div class="content">
+<div id="ot" class="group2">
+      <table width=100%>
+       <tr>
+               <td colspan=1>
+<span class="title">POST Form testing</span>
+       </td>
+       </tr><tr>
+<td class="explain" colspan=2>
+This tests POST handling in lws.
+</td></tr>
+       <tr>
+       <td align=center colspan=2 class=tdform><div id=postinfo>
+       FORM 1: send with urlencoded POST body args<br>
+       <form action="formtest" method="post">
+ <span class="f12">Some text: </span>
+  <input type="text" name="text" value="Give me some text"><br>
+  <input type="submit" name="send" value="Send the form">
+       </form>
+       </div></td>
+       </tr>
+
+
+       <tr>
+       <td align=center colspan=2 class=tdform><div id=postinfo >
+       FORM 2: send with multipart/form-data<br>
+       (can handle file upload, test limited to 100KB)<br>
+       <form name=multipart action="formtest" method="post"
+             enctype="multipart/form-data">
+  <span class="f12">Some text: </span>
+  <input type="text" name="text" value="Give me some text">
+<br>
+  <input type="file" name="file" id="file" size="20">&nbsp;
+   <span id=file_info class="f12"></span><br>
+    <input type="submit" id="upload" name="upload" disabled=1 value="Upload">
+       </form>
+       </div></td>
+       </tr>
+       
+</table>
+</div>
+       </div> 
+   </div>
+
+</div>
+</td></tr></table>
+
+Looking for support?
+<a href="https://libwebsockets.org">https://libwebsockets.org</a>,
+<a href="https://github.com/warmcat/libwebsockets">
+       https://github.com/warmcat/libwebsockets</a></a><br/>
+Join the mailing list:
+<a href="https://libwebsockets.org/mailman/listinfo/libwebsockets">
+       https://libwebsockets.org/mailman/listinfo/libwebsockets</a>
+
+</article>
+
+</body>
+</html>
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
- <script src="/lws-common.js"></script>
- <title>Minimal Websocket test app</title>
-<style type="text/css">
-       span.title { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; }
-       .browser { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px;}
-       .group2 { vertical-align:middle;
-               text-align:center;
-               background:#f0f0e0; 
-               padding:12px; 
-               -webkit-border-radius:10px; 
-               -moz-border-radius:10px;
-               border-radius:10px; }
-       .explain { vertical-align:middle;
-               text-align:center;
-               background:#f0f0c0; padding:12px;
-               -webkit-border-radius:10px;
-               -moz-border-radius:10px;
-               border-radius:10px;
-               color:#404000; }
-       td.wsstatus { vertical-align:middle; width:200px; height:50px;
-               text-align:center;
-               background:#f0f0c0; padding:6px;
-               -webkit-border-radius:8px;
-               -moz-border-radius:8px;
-               border-radius:8px;
-               color:#404000; }
-       .tdform { vertical-align:middle; width:200px; height:50px;
-               text-align:center;
-               background:#f0f0d0; padding:6px;
-               -webkit-border-radius:8px;
-               -moz-border-radius:8px; margin:10px;
-               border-radius:8px;
-               border: 1px solid black;
-               border-collapse: collapse;font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; 
-               color:#404000; }
-               
-       td.l { vertical-align:middle;
-               text-align:center;
-               background:#d0d0b0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       .content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
-       .canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
-.tabs {
-  position: relative;   
-  min-height: 750px; /* This part sucks */
-  clear: both;
-  margin: 25px 0;
-}
-.tab {
-  float: left;
-}
-.tab label {
-  background: #eee; 
-  padding: 10px; 
-  border: 1px solid #ccc; 
-  margin-left: -1px; 
-  position: relative;
-  left: 1px; 
-}
-.tab [type=radio] {
-  display: none;   
-}
-.content {
-  position: absolute;
-  top: 28px;
-  left: 0;
-  background: white;
-  right: 0;
-  bottom: 0;
-  padding: 20px;
-  border: 1px solid #ccc; 
-}
-[type=radio]:checked ~ label {
-  background: white;
-  border-bottom: 1px solid white;
-  z-index: 2;
-}
-[type=radio]:checked ~ label ~ .content {
-  z-index: 1;
-}
-</style>
-</head>
-
-<body>
-<header></header>
-<article>
-
-<table><tr><td>
-
-<table width=600px>
- <tr>
-  <td valign=middle align=center>
-   <a href="https://libwebsockets.org">
-    <img src="libwebsockets.org-logo.png"></a></td><td>
-       <section class="browser">Detected Browser: 
-       <div id=brow>...</div></section>
-  </td>
- </tr>
-
-</table>
-</td></tr>
-<tr><td colspan=2 align=center>
-Click <a href="leaf.jpg" target="_blank">Here</a> to
-have the test server send a big picture by http.
-</td></tr>
-<tr><td colspan=2>
-<div class="tabs">
-
-   <div class="tab">
-       <input type="radio" id="tab-1" name="tab-group-1" checked>
-       <label for="tab-1">Dumb Increment Demo</label>
-       
-       <div class="content">
-        <div id="dumb" class="group2">
-         <table>
-          <tr>
-          <td id=wsdi_statustd align=center class="wsstatus">
-            <span id=wsdi_status>Websocket connection not initialized</span></td>
-           <td><span class="title">dumb increment-protocol</span></td>
-          </tr>
-          <tr>
-          <td class="explain" colspan=2>
-The incrementing number is coming from the server at 20Hz and is individual for
-each connection to the server... try opening a second browser window.
-<br/><br/>
-The button sends a message over the websocket link to ask the server
-to zero just this connection's number.
-          </td>
-         </tr>
-          <tr>
-           <td align=center><div id=number style="font-size:120%;"> </div></td>
-           <td align=center>
-            <input type=button id=offset value="Reset counter">
-            <input type=button id=junk value="Send junk">
-           </td>
-           </tr>
-        </table>
-       </div>
-       </div> 
-   </div>
-
-   <div class="tab">
-    <input type="radio" id="tab-2" name="tab-group-1">
-    <label for="tab-2">Mirror Demo</label>
-       
-    <div class="content">
-     <div id="mirror" class="group2">
-      <table>
-       <tr>
-        <td colspan=1 id=wslm_statustd align=center class="wsstatus">
-        <span id=wslm_status>Websocket connection not initialized</span>
-       </td>
-        <td>
-         <span class="title">lws-mirror-protocol</span>
-        </td>
-       </tr>
-       <tr>
-       <td colspan=2>
-         <div class="explain">
-Use the mouse to draw on the canvas below -- all other browser windows open
-on this page see your drawing in realtime and you can see any of theirs as
-well.
-<br/><br/>
-The lws-mirror protocol doesn't interpret what is being sent to it, it just
-re-sends it to every other websocket it has a connection with using that
-protocol, including the guy who sent the packet.
-<br/><br/>
-<b>libwebsockets-test-client</b> joins in by spamming circles on to this shared canvas when
-run.
-         </div>
-        </td>
-       </tr>
-       <tr>
-       <td colspan=2>Drawing color:
-         <select id="color">
-               <option value=#000000>Black</option>
-               <option value=#0000ff>Blue</option>
-               <option value=#20ff20>Green</option>
-               <option value=#802020>Dark Red</option>
-         </select>
-       </tr>
-       <tr>
-        <td colspan=2 width=500 height=320>
-               <div id="wslm_drawing" style="background:white"></div>
-       </td>
-       </tr>
-      </table>
-     </div>
-    </div> 
-   </div>
-    
-    <div class="tab">
-       <input type="radio" id="tab-3" name="tab-group-1">
-       <label for="tab-3">Close Testing</label>
-     
-       <div class="content">
-<div id="ot" class="group2">
-      <table>
-       <tr>
-        <td>
-
-               </td></tr>
-               <tr><td id=ot_statustd align=center class="wsstatus">
-                <span id=ot_status>Websocket connection not initialized</span>
-               </td>
-               <td colspan=2><span class="title">Open and close testing</span></td>
-               </tr>
-               <tr>    
-<td class="explain" colspan=3 style="padding:3">
-To help with open and close testing, you can open and close a connection by hand using
- the buttons.<br>
- "<b>Close</b>" closes the connection from the browser with code 3000
-  and reason 'Bye!".<br>
- "<b>Request Server Close</b>" sends a message asking the server to
-initiate the close, which it does with code 1001 and reason "Seeya".
-</td></tr>
-               <tr>
-               <td align=center><input type=button id=ot_open_btn value="Open"></td>
-               <td align=center><input type=button id=ot_close_btn disabled value="Close" ></td>
-               <td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" ></td>
-               </tr>
-
-</table>
-
-</div>
-       </div> 
-   </div>
-   
-    <div class="tab">
-       <input type="radio" id="tab-4" name="tab-group-1">
-       <label for="tab-4">Server info</label>
-
-       <div class="content">
-<div id="ot" class="group2">
-      <table>
-       <tr>
-       <td id=s_statustd align=center class="wsstatus">
-        <div id=s_status>Websocket connection not initialized</div>
-       </td>
-               <td colspan=1>
-<span class="title">Server Info</span>      <input type=button id=pmd value="Test pmd">
-
-       </td>
-       </tr><tr>
-<td class="explain" colspan=2>
-This information is sent by the server over a ws[s] link and updated live
-whenever the information changes server-side.
-</td></tr>
-       <tr>
-       <td align=center colspan=2><div id=servinfo></div></td>
-       </tr>
-       <tr>
-       <td align=center colspan=2><div id=conninfo style="border : solid 2px #e0d040; padding : 4px; width : 500px; height : 350px; overflow : auto; "></</div></td>
-       </tr>
-</table>
-</div>
-       </div> 
-   </div>
-
-    <div class="tab">
-       <input type="radio" id="tab-5" name="tab-group-1">
-       <label for="tab-5">POST</label>
-
-       <div class="content">
-<div id="ot" class="group2">
-      <table width=100%>
-       <tr>
-               <td colspan=1>
-<span class="title">POST Form testing</span>
-       </td>
-       </tr><tr>
-<td class="explain" colspan=2>
-This tests POST handling in lws.
-</td></tr>
-       <tr>
-       <td align=center colspan=2 class=tdform><div id=postinfo style=form>
-       FORM 1: send with urlencoded POST body args<br>
-       <form action="formtest" method="post">
- <span style="font-size:12pt;">Some text: </span>
-  <input type="text" name="text" value="Give me some text"><br>
-  <input type="submit" name="send" value="Send the form">
-       </form>
-       </div></td>
-       </tr>
-
-<script nonce="lwscaro">
+(function () {
 function check_file()
 {
-       var f = document.getElementById('file').files[0];
+       var f = document.getElementById("file").files[0];
        var max_len = 100000;
        var dis = 0;
        
        if (f) {
                if (f.size >= max_len) {
                        dis = 1;
-                       document.getElementById('file_info').innerHTML =
-                               "<span style=\"color:red;font-weight:bold\">File larger than "+max_len+"</span>";
+                       document.getElementById("file_info").innerHTML =
+                               "<span style=\"color:red;font-weight:bold\">File larger than " +
+                                                       max_len+"</span>";
                } else
-                       document.getElementById('file_info').innerHTML =
+                       document.getElementById("file_info").innerHTML =
                                "File length "+f.size;
        } else
                dis = 1;
        
-       document.getElementById('upload').disabled = dis;
+       document.getElementById("upload").disabled = dis;
 }
-</script>
-
-       <tr>
-       <td align=center colspan=2 class=tdform><div id=postinfo style=form>
-       FORM 2: send with multipart/form-data<br>
-       (can handle file upload, test limited to 100KB)<br>
-       <form name=multipart action="formtest" method="post" enctype="multipart/form-data">
-  <span style="font-size:12pt;">Some text: </span>
-  <input type="text" name="text" value="Give me some text">
-<br>
-  <input type="file" name="file" id="file" size="20">&nbsp;<span id=file_info style="font-size:12pt;"></span><br>
-    <input type="submit" id="upload" name="upload" disabled=1 value="Upload">
-       </form>
-       </div></td>
-       </tr>
-       
-</table>
-</div>
-       </div> 
-   </div>
-
-</div>
-</td></tr></table>
-
-Looking for support? <a href="https://libwebsockets.org">https://libwebsockets.org</a>, <a href="https://github.com/warmcat/libwebsockets">https://github.com/warmcat/libwebsockets</a></a><br/>
-Join the mailing list: <a href="https://libwebsockets.org/mailman/listinfo/libwebsockets">https://libwebsockets.org/mailman/listinfo/libwebsockets</a>
-
-</article>
-
-<script nonce="lwscaro">
-
-document.getElementById('file').onchange = check_file;
-document.getElementById('offset').onclick = reset;
-document.getElementById('junk').onclick = junk;
-document.getElementById('color').onclick = update_color;
-document.getElementById('ot_open_btn').onclick = ot_open;
-document.getElementById('ot_close_btn').onclick = ot_close;
-document.getElementById('ot_req_close_btn').onclick = ot_req_close;
-document.getElementById('pmd').onclick = on_pmd;
 
 /*
  * We display untrusted stuff in html context... reject anything
@@ -357,19 +27,18 @@ document.getElementById('pmd').onclick = on_pmd;
 
 function san(s)
 {
-       if (s.search("<") != -1)
+       if (s.search("<") !== -1)
                return "invalid string";
        
        return s;
 }
 
-lws_gray_out(true,{'zindex':'499'});
-
 /* BrowserDetect came from http://www.quirksmode.org/js/detect.html */
 
 var BrowserDetect = {
        init: function () {
-               this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
+               this.browser = this.searchString(this.dataBrowser) ||
+                                               "An unknown browser";
                this.version = this.searchVersion(navigator.userAgent)
                        || this.searchVersion(navigator.appVersion)
                        || "an unknown version";
@@ -381,7 +50,7 @@ var BrowserDetect = {
                        var dataProp = data[i].prop;
                        this.versionSearchString = data[i].versionSearch || data[i].identity;
                        if (dataString) {
-                               if (dataString.indexOf(data[i].subString) != -1)
+                               if (dataString.indexOf(data[i].subString) !== -1)
                                        return data[i].identity;
                        }
                        else if (dataProp)
@@ -390,8 +59,9 @@ var BrowserDetect = {
        },
        searchVersion: function (dataString) {
                var index = dataString.indexOf(this.versionSearchString);
-               if (index == -1) return;
-               return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
+               if (index === -1) return 0;
+               return parseFloat(dataString.substring(index +
+                                                                               this.versionSearchString.length + 1));
        },
        dataBrowser: [
                {
@@ -483,12 +153,8 @@ var BrowserDetect = {
        ]
 
 };
-BrowserDetect.init();
-
-document.getElementById("brow").textContent = " " + BrowserDetect.browser + " "
-       + BrowserDetect.version +" " + BrowserDetect.OS +" ";
 
-       var pos = 0;
+var pos = 0;
 
 function get_appropriate_ws_url(extra_url)
 {
@@ -500,16 +166,16 @@ function get_appropriate_ws_url(extra_url)
         * https:// url itself, otherwise unencrypted
         */
 
-       if (u.substring(0, 5) == "https") {
+       if (u.substring(0, 5) === "https") {
                pcol = "wss://";
                u = u.substr(8);
        } else {
                pcol = "ws://";
-               if (u.substring(0, 4) == "http")
+               if (u.substring(0, 4) === "http")
                        u = u.substr(7);
        }
 
-       u = u.split('/');
+       u = u.split("/");
 
        /* + "/xxx" bit is for IE10 workaround */
 
@@ -519,78 +185,66 @@ function get_appropriate_ws_url(extra_url)
 var params = {};
 
 if (location.search) {
-    var parts = location.search.substring(1).split('&');
+    var parts = location.search.substring(1).split("&");
 
     for (var i = 0; i < parts.length; i++) {
-        var nv = parts[i].split('=');
+        var nv = parts[i].split("=");
         if (!nv[0]) continue;
         params[nv[0]] = nv[1] || true;
     }
 }
 
+var socket_di;
+
 var mirror_name = "";
 if (params.mirror)
        mirror_name = params.mirror;
 
        console.log(mirror_name);
-       
-
-/*
- * if using lws-meta to carry the other ws connections, declare the
- * parent connection object and start its connection to the server.
- *
- * These helpers are defined in lws-common.js
- */
-
-
-var lws_meta = new lws_meta_ws();
-lws_meta.new_parent(get_appropriate_ws_url("?mirror=" + mirror_name));
-
 
-document.getElementById("number").textContent = get_appropriate_ws_url(mirror_name);
-
-/* dumb increment protocol */
-
-       /*
-        * to connect via an lws-meta connection, start the connection using
-        * lws_meta.new_ws().  To connect by independent connection, start
-        * the connection using just new_ws()
-        */
-       
-       var socket_di = lws_meta.new_ws("", "dumb-increment-protocol");
+function ws_open_dumb_increment()
+{
+       socket_di = new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol");
 
        try {
                socket_di.onopen = function() {
-                       document.getElementById("wsdi_statustd").style.backgroundColor = "#40ff40";
+                       document.getElementById("wsdi_statustd").style.backgroundColor =
+                                                                                                                               "#40ff40";
                        document.getElementById("wsdi_status").innerHTML =
                                " <b>websocket connection opened</b><br>" +
                                san(socket_di.extensions);
-               } 
+               };
 
                socket_di.onmessage =function got_packet(msg) {
                        document.getElementById("number").textContent = msg.data + "\n";
-               } 
+               };
 
                socket_di.onclose = function(){
-                       document.getElementById("wsdi_statustd").style.backgroundColor = "#ff4040";
-                       document.getElementById("wsdi_status").textContent = " websocket connection CLOSED ";
-               }
+                       document.getElementById("wsdi_statustd").style.backgroundColor =
+                                                                                                                               "#ff4040";
+                       document.getElementById("wsdi_status").textContent =
+                                                                       " websocket connection CLOSED ";
+               };
        } catch(exception) {
-               alert('<p>Error' + exception);  
+               alert("<p>Error" + exception);  
        }
+}
        
        var socket_status, jso, s;
-
-
-       socket_status = lws_meta.new_ws(get_appropriate_ws_url(""), "lws-status");
+       
+function ws_open_status()
+{      
+       
+       socket_status = new_ws(get_appropriate_ws_url(""), "lws-status");
 
        try {
                socket_status.onopen = function() {
-                       document.getElementById("s_statustd").style.backgroundColor = "#40ff40";
+                       document.getElementById("s_statustd").style.backgroundColor =
+                                                                                                                               "#40ff40";
                        document.getElementById("s_status").innerHTML =
                                " <b>websocket connection opened</b><br>" +
                                san(socket_status.extensions);
-               } 
+               };
 
                socket_status.onmessage =function got_packet(msg) {
                        var s;
@@ -599,6 +253,10 @@ document.getElementById("number").textContent = get_appropriate_ws_url(mirror_na
                        
                        jso = JSON.parse(msg.data);
                        
+                       if (jso.wss_over_h2 === "1")
+                               document.getElementById("wstransport").innerHTML =
+                                                                               "<img src=\"./wss-over-h2.png\">";
+                       
                        document.getElementById("servinfo").innerHTML = 
                                "<table><tr><td class=l>Build info</td><td>"+
                                        san(jso.version) + "</td></tr>" +
@@ -608,7 +266,7 @@ document.getElementById("number").textContent = get_appropriate_ws_url(mirror_na
                        s="<table>";
                        var n;
                        for (n = 0; n < jso.conns.length; n++) {
-                               var d = new Date(parseInt(jso.conns[n].time) * 1000);
+                               var d = new Date(parseInt(jso.conns[n].time, 10) * 1000);
                                
                                s = s + "<tr><td class=l>client " + (n + 1) +
                                "</td><td><b>" + san(jso.conns[n].peer) +
@@ -619,15 +277,18 @@ document.getElementById("number").textContent = get_appropriate_ws_url(mirror_na
                        s = s + "</table>";
                        
                        document.getElementById("conninfo").innerHTML = s;
-               } 
+               };
 
                socket_status.onclose = function(){
-                       document.getElementById("s_statustd").style.backgroundColor = "#ff4040";
-                       document.getElementById("s_status").textContent = " websocket connection CLOSED ";
-               }
+                       document.getElementById("s_statustd").style.backgroundColor =
+                                                                                                                                       "#ff4040";
+                       document.getElementById("s_status").textContent =
+                                                               " websocket connection CLOSED ";
+               };
        } catch(exception) {
-               alert('<p>Error' + exception);  
+               alert("<p>Error" + exception);  
        }
+}
 
 function reset() {
        socket_di.send("reset\n");
@@ -635,44 +296,48 @@ function reset() {
 
 
 function junk() {
-       for(var word = ''; word.length < 9000; word += 'a'){}
+       for(var word = ""; word.length < 9000; word += "a"){}
        socket_di.send(word);
 }
 
 function on_pmd() {
        socket_status.send("{ \"RequestType\":\"DDoS\", \"blob\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAJbElEQVR4Xu2af4xUVxXHv+e9O0OhIEibgrRlF8rOG6CWKiumrUlTo6byj6mmNFta2DdQsRJJrP5j+0cnjSVp1BoSU1yzO28LEeL6h7GJNhpJNa2CBQ1gd9k3QPlRBU2LLV0W2p173zFvZvbtzOybmbd7J9DEd/+befece97nfe+95/4gxEWLAGlZx8aIAWqKIAYYA9QkoGkeKzAGqElA0zxWYAxQk4CmeazAGKAmAU3zWIExQE0CmuaxAmOAmgQ0zWMFxgA1CWiaxwqMAWoS0DRvrQJ3ujeb17Hrx6SuzLwV32x7VzO+1pj3HJprJue8BcBQCSONRzr+2RrHaO2OtHDc1wDcUwqOj0s7nWpVoDp+hOMeA5Au+zggbesuHX+Vti1VoOh3j4BxRxEf8xmVSbe3KlAdP2bOPUmEpaXviiGZsVbq+IsBxgCnpp9YgVPjNan2RwagmXMfUpnUAEAc9k6Rx8AHB0zzy3euU5nUXk02RXMzl+9Ssw8PYN06FeYvOkAmM5dfpzLWL6LGFXkSMR13jIAEmKXMpBM6AIXjeih+BVLqTEcSWfJ/T71ks4bZ9vAYgU1/epC2ZegAFLnhAogEM0tV5x1r/UcGKBw3UJ1sTyVwH8laZ5EU2DO4WCTFmXFbOTbnemxZdHnq9AAMvDVTjF4ObKVS7di8IvA97jOSArNsiLZ8oGBpW5HYRKrkB1IFcGwkiS2dhWkB7B1qE6Z5uiUAew7NEsk5o4EveEtgLw98TwngwIApRlcFoogBVnzdSArUAWg6+T4QS3U69Xi98eiqK7A4vnX1+YsldWbPJmSzk8fJa6FAP67FXTv9sVLZqU1k9rvHibGsmKQzBlTGeih0gqgcA69CFxaO+zKA+4uxEF6W3dbaSXFdA4Bmzt1DhK5SLHycTMd9n4A5pT9oSNqp0GXO1VagcFx/cphZhnZF2tasjwJAkRs+CqJPFvEBIzHA8a8ScQyMAdbbTGgJwAY7FS3LA3edXCyUbJoHitzwKIhK3Zb5ssykr5/Uha9BHigc9whQ3nHyu7DIDe8A0TY/OI/xtJexngnP5ocLRCRAkLLbas1KhOGps6lE2MxvOO6TBvBsMS7Pe8rbtHz7pLhavRLpdwtgCDCkzIS/o9Gff8pg/n55EtlRSqT7h+8B0xhs62AYvHJlf524QZ3duzs0pfCT7aj7gQNsmqPHH1V2qr9+ewBePHYXIICNHfsb1TNzw7Y6e343svdNWh35dpHyQL9iKUV5VGVSu+qt94tx7DrZCa8wA93pP0deiTR80fLDyACjOGthncgAp9FmqwG+CsbnynHkpW1Z04ip5Sai3x0CY3kpU+P9sjt9d6saaSlA9JybJWaMXARDyRFjLrZ1fNiqQLX8DAwmxai4CCAhzblzsWFhsH7W8lvM8eOiRSAGqIUvVqAmvhhgDFCbgKaDqY+B2ayBBV2fQJJnwzQlSL6HjcsvhMaRHUwG/2dXjjWMtXdwPjBjPryCARKXIcQ7sJd8EOn9fFuTbkCBCDOMUZw69596SXWVv4HB2bicXAClBARdwqm95+stEurFER1gz6GESM45DGBFmLNJW+C5E7cKUmfH68pkoh3rl046r8CL7s3CQ+hdFQY2KNvaXS94o8/\" }");
-       socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"283463389\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAGqAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArlviX43Pw78H3fipdHl1RreSGJLSKQRtK0kqxgBiDjlvSuprhvjNpmoav4INnpljPdznU9Ok8qCMu2xbuJmbA5wFBJPYA0AL4r+K2j+G/hxH8Rra0k1G3ureOezto3CPOXXcFBwcHbk9O1bmg+L9H12wW7juEimSygvrmAtlreOVCy7jjngN+VeOal4L8Vz3Wv8Ag99DvG0Hw5b317pEixMUupLpS0UUf94w5ZMD1FWdLfW/CFxqcF34S126l1vwxpttZfZdPllU3EUMitFIyqREcuOXIHXninYD0y8+KPgCwNkt34ntU/tCGO4tyA7BopPuOSBhQc8bsVYXxtpEc+tHULm2tbPRVheW5afIKyAkEjHy9OOTnNeEHQNX0bw/pTQaH4l0/W38NabbNCNFkvbS/kjgUeTMqrmF1bKncVx1rZ1Lwz4tuV1bVL3wpdSJDf8Ah+/urCGBmFzFAxaeOIf8tNv90ZzgDvRYD2fw34w8NeLoppvDurRXqwNslChlZD1GVYAj8q0rq9tLIRtd3CRCVxGhc4BY9Bn3rlfBl/Z61reqazp/g660q3kjhiW8u7SS0muyoOR5UiqwVc4DEc89q2fFQSTRprVtFk1VrjESWyjhmPQs38AHXdkYx60gNivPb74uwWXhPW/EX/CPzyXmjal/ZQ05Zh5k85ZFjCtjjcZFxxXXeGdO1LSdDtNP1bUPtt1CmJJvXngZ74HGcDOM4FeYXfhPXm+Nq28elXDeG7yaHXbi68s+SLmGORRGW6bi7RNjr8hoA6XU/i3p1pYeHr+w043a+INPk1NMziMQwIiNuY7T/fA/OtS8+J3gnSntLbWtftbO6u4IpxESzhA4BG5gMKPc4ryPSvBHipIvGdheaDei20HT7jRtB/ct/pMLzSSK0Yx8w8t4k47oaj8XaLqVglzJpmjeIbfV7rRLSAWw0WS/sdUdIiBFJsX9yQSVJZhgHPanYD1B/jD4PsfEWseHNdvo9Nm0q7itUeRiwn8yGOQN8q/IP3m3k9R1ra8ceLoPBfgzVfGTWpvYdMs3vPKSQL5qqucBsHGfXFebDwpq15YfE681LwvIuoaotuLdRbl/NK2EAKxHHzgSBxx3Bre+IukavqHwA1TRbPTbq41GXw75C2scTNM0vkAbAgGS2eMYzmgDt9c1xNF0CfXXiRlhjWQo8mwckDlsHHX0rMf4leCYNUg0O78QW0OoT+WogO4gO+Nql8bQTkYBIrjfFvjJfFvgjUfDeleE/Fq3s1qgQXHh68gQlXTI3PGBnr3rjdc0rWtM1K8Ph3QdeXVbq6tJW0250iS50/UGUIPN89V2wEYOSzfKVziiwHsPhL4k+GfGer6zoujXLvcaLcfZ5gyMA3yg7gSOmTj8K6qvPfh9aT6V418b2d3ot5bG81NL22uDZusE0Jt4l+WXGwkMrZUHI9K9CpAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVma34m8OeGvsf/CQ67YaZ/aNyllafa7hIvtFw/3Yk3EbnPZRyaANOiisbQfGfhHxTc39l4a8T6XqtxpUohvorO7jme1kOcLIFJKHg8HHQ0AbNFZ2u+ItA8MWa6j4j1qx0u1eVIFmvLhYUMjnCoGYgbieg6mi08R+H7/VrnQbLW7G41KzjSa4s47hWmhjb7rOgOVB7EjmgDRoorO1/wAQ6D4V0uXXPE2tWOladAVEt3ezrDChZgq5diAMkgDnqRQBo0UgORkUtABRVbUtT07RrCfVNWvreys7VDJPcTyCOONB1ZmPAHuaNO1LT9XsYNT0q9gvLO5QSQzwSB45EPRlYcEe4oAs0VT1fWNJ8P6Zcazrup2un2FonmT3V1KsUUS+rMxAA9zU9tc297bRXlnPHPBOiyxSxsGV0YZDAjggggg0AS0UUmRnGeaAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqrqGpWumQ+dcsQCcKBySaaTk7ITairstUVz3/AAmdh/z63H/jv+NH/CZ2H/Prcf8Ajv8AjW31ar/KZfWKfc6Giue/4TOw/wCfW4/8d/xo/wCEzsP+fW4/8d/xo+rVf5Q+sU+50NFc9/wmdh/z63H/AI7/AI0f8JnYf8+tx/47/jR9Wq/yh9Yp9zoaK57/AITOw/59bj/x3/GtDTNcstVLJBuWRRkow5xUyoVIK8kVGtCTsmaNFFFZGgUUUUAFFFFABXin7YfhS78TfAbxBeaUCup+HVj16ykUZaOS1cSkr7lVYfjXtdVtRsLXVLC50y+hWW3u4nhljboyMMEH8DQBxNz8UNOb4IH4tQyrHaz6AurREH7peEMg/wC+mAr52/Zu8OXnwh+KfhZtT3xn4seHLi9u1JO3+0Y5DcA/UxMwA9qxNG1PUX+COl/s0vcNJrFt46m8ITg9Xs7aYzE/TyvK9sGvaf2ptPTwl4J8LfEvS4WB+HGuWGolYx8xsg4imT6eU7E/SgBn7Qif8Jp8Ufhb8KU/eQTanJ4h1KP0gtgBG3/fbH8q6zw7rXw8j+M/jO3stIltPENlpdrNq2oSSnypbfLbAATgbcEk4rmPh40fj39pfx146jdZ7DwtYWnhrT5lOV8woJ7gA/70gH4Vm6DDpFx+0b8XLfX7mO30yXwzZpeTSSCNY4T5gdix4UAZ57UAbEH7S2r67YyeJfAvwY8TeIfDCFzHq0UkcX2iNTgyRRN8zrwcHIzXK/tQ+P8Aw78Sf2QNf8X+Grh3sriWzVllXZJDIt5EHjcdmB6irvg3wv8AtE/BnwxZ6B4BPhj4ieEbGEDSkkujZXv2Xqi7yDG+FxghuRiuE+Mvivwf4x/Y78djw34Tl8MXVtq8EOs6TKuJLe+a7hMhPJB3cEEcGgD2LU/2htUjt5tY8J/CHxL4j8O2u7fq9u0cccqL96SFG+aROCQeMjpXo/gHx34f+JPhSx8Y+GLhpbC/Qsu9drowOGRh2YHgitXTNNsdM0q10mxtkitLaBIIoVGFWNVwFA9McV4l+xt8vw28QQrwkPjDV0Reyr5q8D86AO2/aF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBqP8AaF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBoAn+Nt74T074UeJb7xzpcupaDDZFr+0ico8se5flBBBHOO9Y3iH4w+Gfh/4c8Jabo3h/UtV1HxDZxLoWh2QDTyRLCG5ZjhVVcZY1D+1X/wAm8eOv+wWf/Q1rktd+Hep+Lrb4aeLPh5440vRvHnhbQIns7W+Hmw3NrNbxrKskaneFPHzAH9aAOn8P/H+ZvF2neC/iJ8OtY8GX2tFk0uW6lSe3unUZKCRcbWx2IryTxZ8WPF1p+1VodxD8JvFM/wBj0TULaOxjeLddruT9+g3Y2jHfnmuq174jeNPDus+HbL9o/wCEumHSm1SKHT/EWk3v2m3t71uI2eMhZI89Bwa1vEP/ACeL4Q/7FbUv/Qo6APcrOeS6tIbmW3e3eWNXaKT70ZIztOO46VNRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXKeNSfNtRnjax/UV1dcp41/wBda/7rfzFdOD/jI58V/CZzVeear8Q9V0r4tWHg6e3g/sa8tlDT4PmR3D58vJ6YYgL9WFeh15/4t+HOo+IdY1fVrW/t4JLnTYobB23boLuKQSRyHA+6GVTxzxX0eF9lzNVdrf0/keFiPacqdPe5Qb4sXMPxG1fRbiGIeH9Js2Zp1UmWSdCBJjnGFZtv1U11Ws/ELw1oKeZqc8sajS5NX3bMj7OhQHv97Mi8Vx8fwf1MWcEMmq2rTto89reTYbMt7LIZXlAx90uSfXmn/wDCAePdSujfaxceHo3h8PzaNDAvmTxu7PEdz7lX5SI8EDkZ4zXVKGFk009Fo/Pz+ZzxniIp3Wr/AK/A2n+JNneQwG3t77S5jfQWzx39n8zrIpK7drYwcdcnGOlWrP4m+H77V49LtrXUmimuTaRX/wBm/wBFeYdUD5znIxnGM965DTvhN4lW4M00+n6fafbbW5XT4Lya4iTyg+91aRQQW3gbeny1veGPCXjXw+bTQFvdK/sKyvJLlZlLm6ljZy4jKFdo+ZuWDHipqU8Mk+V/j/V/QcJ4htcyO/rV8LkjWoAD1D5/75NZVavhf/kNwfR//QTXk1v4cvRnp0f4kfU7uiiivBPZCiiigAooooAKKKKAPEbP9mu3tf2kLv47HX1ayngLx6P5JxFetFHG9wGzjJEYzxnnrXqHjzwlZePfBWueC9RIW31uwnsJGIztEiFdw9xnNb1FAHl/7O/wZl+B/wAPh4Tv9eGt6ncXk99f6j5Rj+0SyOSCQSTwu1eT2p0HwVtp/iH428Wa3qEV7pfjPSItIn0/yirLGoYPl88hgxHAFenUUAeC+G/hh+0R8OdFh8D+CPiV4fvvD9kgttNm1nS3lvLK3UYSPcjqsm0YA3L0Apus/suyan8E/EXw0HjBpNc8VX8Wp6prdzb7vNuFmRziNSMLhNoGeM175RQAijaoX0GK8/8Agp8L7j4TeG9T0G51iPUm1DW73VhIkJjCLOwITBJyRjr3r0GigDm/iP4Tl8d+BNc8HQ3q2b6vZvarOybxGW7lQRn868h8L/CT9qDwj4e0/wAM6R8dPC62WmQLbwB/C+5gijjJMnNfQVFAHhmsfCL46eNfBviXwX8QPi1oWo2euaebSE2ugfZzBIWB3kh8sMDGOK0PGHwM128bwh4o8B+MF0Pxh4Q09NNivHtvNtr238tUkimiJ5VtoI5yp6GvY6KAPDL/AOEXxa+Juo6Snxj8ZaIdB0i9i1AaVolg8Qu54jmNpZJHZtoPO1cVr/FL4ReMvEPxB8PfFD4c+MLLRNa0O1uLGSO+sftMFzBNjIIDKQRtGCDXrlFAENmt0lpCl9KklwsaiV0XarPjkgdhntU1FFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFZ+r6RDq0IR2KOnKOO1aFFVGTg+aO4pRUlZnKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFb/XK3cw+q0uxyn/AAhU3/QQT/v2f8aP+EKm/wCggn/fs/411dFH1yt3D6rS7HKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFH1yt3D6rS7HKf8ACFTf9BBP+/Z/xrT0bw7FpUhuHl82XGAcYArYoqZ4mrNcrZUcPTg7pBRRRWBsFFFFABRRRQAVFcPcRqDbwLK2eQX24H5GpaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNOSfUWdRJp8aqSMsJ84HrjFW6KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACioZry0t/8AX3Mcf+8wFUZvEekxcfaN5/2VJosBqUVz03jC3GRBau3+8QKozeLb9/8AVRRxj86fKwOvorg5tc1Sb7124HoOKjtdUvba4WcXDtgjILZBFPlA9AoooqQCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACori5gtYzLcSqijualrkPFsztfpAWO1EBA9zTSuBqzeK9NjyIxJIfYYFUpvGMh/497NR/vtn+Vc3RV8qA1pvE+rS/dlWMf7Kj+tUZtRv7j/XXcrD0LnFV6KdkAUUUUAFFFFABQOtFA60AelDpS0g6UtZAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcb4r/5Cv/bJf612Vcb4r/5Cv/bJf61UdwMaiiirAKKKVVZztVSSewFACUVch0fU5/8AV2UuD3K4H61eh8KanJ/rDFF/vNn+VF0Bi0V08Pg5BzPeE+yrV2HwxpUX3o3kP+01LmQHF0DrXb3ui6b9ilCWqIVQkEdQRXEDrQncD0odKWkHSlrMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArjfFf8AyFf+2S/1rsq43xX/AMhX/tkv9aqO4FfSNGl1UuVkEaJwWIzzW7D4RsU5lmlkPpnApvg//jzn/wCuv9BW/Q27gZ8Og6TD92zRv9/5v51djhihG2KJUHooxT6KkAooooAKKKKAIbz/AI9Jv+ubfyrzoda9FvP+PSb/AK5t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv8A5Cv/AGyX+tdlXG+K/wDkK/8AbJf61UdwNPwf/wAec3/XX+grfrA8H/8AHnN/11/oK36T3AKKKKQBRRRQAUUUUAQ3n/HpN/1zb+VedDrXot5/x6Tf9c2/lXnQ61cQPSh0paQdKWoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK43xX/yFf+2S/wBa7KuN8V/8hX/tkv8AWqjuBp+D/wDjzm/66/0Fb9YHg/8A485v+uv9BW/Se4BRRRSAKKKKACiiigCG8/49Jv8Arm38q86HWvRbz/j0m/65t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv/AJCv/bJf612Vcb4r/wCQr/2yX+tVHcDT8H/8ec3/AF1/oK36wPB//HnN/wBdf6Ct+k9wCiiikAUUVDc3dtZp5lzMsY7ZPX6UATUViy+K9MQ4QSye4XH86j/4S+w/59p/yH+NOzA2Lw4tJs/882/lXnQ610GqeKBdW7W9pCyBxhmbGce1c+OtXFWA9KHSlpB0pazAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiioLy9t7GEz3MgVR09SfQUAT0Vy9z4vkJItLYAdi55/Kqh8Vaqe8Q+iVXKwOzri/FDq+qttOdqKp+vNNfxNqrqV81Fz3C81mO7yOXdizMcknvTirAdV4P/AOPOb/rr/QVv1geD/wDjzm/66/0Fb9S9wCiiikBXv7tLG1kuX/gHA9TXBXd3PeztPO5Zj09h6Cuq8WFhpqgdDIM/rXH1cUAUUUVQBQOtFS2sTT3MUKjJdwv5mgD0UdKWkpayAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACuH8QX73l+67j5cR2qP5mu4rzzUI2ivZ43HIc/zqo7gV6KKKsAooooA6zweP8AQpj/ANNf6Ct+snwzbNb6WjMMGUl/w7fpWtWb3AKKKKQFXU7IX9lJbHgsMqfQ1wU8EttK0MyFWU4INej1UvtLstQXFzECezDgiqTsB5/RXUyeDoScxXjKPQrmmr4OT+K+P4J/9eq5kBzFdH4Y0h/MGo3CFQv+rB7n1rSs/DWnWrB2VpWHQv0/KtUAAYAwBUuXYBaKKKkAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK5/xFocl03220XMgHzr/e966CimnYDzZ0eNtsiFSOxGKbXok9laXP8Ar7dH9yOar/2FpH/PjH+tVzAcGAScAZNbOjeH57yRZrpCkAOcHgt7V1EOm2Fucw2kan6Zq1ScgEVQoCqAABgAUtFFSAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUV4145vPGfw28Sv8A8InG2oW3jeZbK0jnmythqTD5ZOTnyioJIHdR60Aey0V4pot5490ix1zSvB+pWctl4LPk3UupK8s+qXIjE07bs/uwS5A68+grpPCfxLv/ABPLr08dvFFb2Oj2WoWyEfMJJUlLqx7gGMYp2A9HorzjVPEWp678BR4qlYR3+oaBDev5JKASvGrHb3AyaxtB8X/ELw7NpFh4ludIvYNT0CW9tgmYPIlhiVgskrHDKRnLHGDSA9gorwvw/wDFbxvqGrSaLDrOj61PdaLcajbS2dlIkEU0WCYlkJ2zAhsblP4Vrat8a7qy0hPENnZxT2lj4dl1fUI8c/aMhI4Qf4cvv59ENOwHr1FeA/8AC8fFNlb38S3mn6zP/Z5u4JYbCa3jt5lkRTE+8fMCH4bg8Hiuz8P+IfHL+K7fwp4tu9Omi1zRZdRtZLOFo2tmVo1ZGyfm4k4PHTpRYD0uivJNOtPG2hfFTw/4WTxTHcaRZ+H4xLHLAS8/l/Izls/fLDOfTitz4oeLdY8OzaXZaVruk6QL1pDJcXiGeVto4SKBSGkJ7kdAKQHf0V4x4c+KXjbxtpnhvStE+wWWs6oL57u7uLd/KjjtZjESsJw25yM7SRt71l6B4v8AH2malq3h1prKTxFrPij+zop3LNbQItuZXkCHnGxGwvHJ/GnYD3uivAL3x54w+HviHxjdeKLy0vbyKPTbW0liV1t2MgbEhi5K4AOQM5IqxafG7xKTceH7aSy1fU7uezttNv1s5bW3Ek8yxESK4z8m4NwTkA9KLAe70V5R4afxxa/GWXTPF2qWl6q6BHJDJaI0UbgzSZLRkkBgQRnJyAPpTvin4+8SeHNXaw0bX9H08Q2LXSwy27Xl1dOOiiGM7lX/AG8HrSA9Vorxaw+I/wAR/GCyT+Gm0nToYfDljrjG5haUl5oi5iGCOOOvb0Naum/Fy9tlj1TxNBBBpt14ck1iFkHPmwf69M98g7gPRTTsB6pRXC6v4n8W6D8JD4q1C2tR4gisIZ5odpESzuVDJjrgFiPwrmbfxD8Y7vxPH4STVPD8ct5pX9rrdm1ci3UMqmLZn5yTIvzZHAPFKwHsFFeY/Dn4ma14vv8AQre/treFdR8PR6lOsYJxP5jo20/3fl4FcrffGDxsdO0y/mu7HQ7C5F75uqz6dLcWwlimKRxPs4iBUElmOOKdgPeKK8XvPibfaZfT+IpYbG6n/wCER0+8X7LcmSCS4muJUARgdpTcQQwGSK7fwkfiZDqYj8Xy6Ve2Fxa+cs9mhia3myP3RUklxgn5h6dOaQHY0V5D4m8S+L/DfxV1e9l1iCXQ9P8ADceoiwEJ3N+9lUDdn7xZfvemB2q5pPjD4gaXqXhe68XSaXcab4skEEcVpEySWUjxNJGCxP7wELtJ4wTTsB6lRXI+J/Fl74Z8WaNb3nlDRNThuI5JSvzx3KAMoz6Mu7j/AGTXIaV48+IXi2+0/QdFfTdNury1uNVkubmAyLFarKI4kCAjczbgxORgUgPXaK8s8JfE7xBrHiPRvDWqWtrFdedq1lqZiyVaa0dEDxk9FbcTg+uO1ZsnxM+IGq67beHPD0GmLcXusahp6zXCNshighVw5xyTyeO5wOOtFgPZaK5Pxz4i1jwnpWl6rH5EsS39vBqRKHHkvlSy+nzlfzrhrD4weJNTn1TToLS1S6m1mytNGJUkSWssxjkkbnkoEkY49BRYD2WivBrn4i+KtFspTYPDpmnyeINcgutVlspryK3MV46xq6qcoGAPzEhRjirEGv8AjrxV408MTeHNf0dLi98OXslxcxMbmzIW4hAkRAQGJ4HJGNx9KdgPcaK4zwL4n1TxX8Pm1jV44Y9Qj+2WlwYMhDLBI8TMueQCUJH1rzPRfFvibS9J0a40XTpNX1NPCmrXkELO7PNLHdIFXGfm6+hPGBRYD3+ivALz4i+Mte8F+JU0zxto095YW8E4kjs3trq3LPh45LdzvXthz15GK9p8J/2ofDOmNrV3Hc3rWsbTTRpsV2IzkDtxilYDWooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACsPxJ4TsvE11ot3eXM8TaJqCajCI8Yd1UgK2QePm7YNblFAHCa58K4tU1PVb7TPFWraNBrwX+1bWz8ry7ohAhbLoWRigCkoVJAqG/+D2nyXckuieI9U0W1u9Oi0u9tbPy9lxbxghRllLI2GYblIPJ5r0Gii4HNxeBtOh8AwfD1bu5Nlb6dFpqzEr5pjRAoY8Y3YX0xWf4l+FeheKrS1sdSu7wQ2ulXGkBY2Ub4povLYk4zux0x3rtKKAOD0b4XXGneItL8T3/AI21fUrvSraSzjWaOBI2gfb8hVI1GcqDuHzH1xTtK+D3hTTNL8R6M/2i6tPE25blJnH7uMhv3cZABVQXcjuCa7qigDgH+FDahY3Nl4h8ba5qwmtltImnMKmGMOGyAiAMx2jLMCfeuh/4RCxHiTTPE32mf7Rpeny6dHHxsZHZCWbjOf3Y6HHJreooA57UvB0V94v07xjDq13aXNhA1q8MQjMdzESTtfcpI5OcqQfeqninwE+v6/YeJtN8SX+jajYW8toJbaOGTfDIVLKVlRgOVUggZ4rrKKAPNrD4KWekWNlDo/i3WLS90y7u7iyvx5TzRpcPvlibehEiljn5wT70sHwT0u3spwPE2sNqkuqLrMeqM0Znhugu3co27SCuQVIIwSMYr0iincDzmP4L6dM2s3eteJtX1PUNb+zvLeSmNHhkh/1bxBFCoR0wBj25NWbj4Tw6rp93B4h8Waxqd7cNBJBfu0cUlo8L743iWNVRWDAEnbzjnIrvaKVwOM8OfDh9G8UzeMtU8WaprWqT2a2LPdLEiCNWLAKkaKq8seg569aj174YprHiG+16z8U6ppn9rWkdlqEFsIitxEm7ADMheM4ZhlCp5rt6KAOH8IfCrTfCNlNaRazf3hm0mDRy84jBEMKFEI2qBu2nGfauf8UfDf8AtF/Bvgmz028l07RLtbq41GRlCmBQ2+FsY3eYG2kYxgk9q9YoouBleJ/D1r4q0G78P3k0sMF2qqzxY3ABg3GQR2qtb+D7G28SxeJ0uZzcQ6YdKEZxsMZdG3dM7sxjvjk1vUUAedW/wZtNLg0ZNA8Wavpk+kWTad9oiELNcW5YttcMhAOScMoBGetGn/B86BYWdn4Y8c65pr2scsLyfuZ/PSR953pKjLuB6MAD716LRQB55ZfBDwjZae2lJLePato0OjGNnH3I5XlWXIGQ+9yfTgcVr+GPAb6DqY1jU/E+p65dxWpsrd7wRqIISQSFWNVBJ2rljknHWusooA5TXvh7p+v+JD4huNRuoxNpp0q7tFCGK5t9zsA25SwIMjcqQaz9D+FUWl6npV7qXirVtYt9B3f2VaXfleXakqUDZRAzkKxUFyxANd3RQBwHxh0C/wDF+jWXhWw0q4ma8u45GvEYKlmqn5mY5zkqWUAepq/rXw5t7y70zVNA12+0HUdKtDYRXNmsbl7chcxukisjDKqRkZBHFdhRQB55/wAKc0+0tNK/sTxJqunanpc9zc/2mhjknuJLhg07SiRWVtzDPTjtirHhj4R6P4Y1Cy1ODWNSu57K7u70NcOrGSS4jCPuwo44yMfy4ru6KLgZfifw/Z+KtAvvD2oPIlvfRGJ2jIDLzkMM9wQCPpXM6V8IfDuk614c1uC7vGm8N2ktrAjsu2YuCDJIMcuNzYIx9413VFAHBn4VfY2a48PeMNY0m6a+vr1pYhFIr/apmldGjkRkIDMdpIyB3qfwl8K9E8IajZ6rY3t3LPa2dzaMZSuJTPKkjyMABhtyDgYGCeK7WigDC8N+EbHwzoM3h+0uZ5YZp7qcvJjcDPK8jDgAYBcgewFc6vwc0NbW1tU1fVI/sel3WlRSRSiOQJPKspkDKAQ6soxjj1Brv6KAPPf+FQwX76jdeJvFWp6xe39gNNW5ljgiaGAMWG0RIqk7jnLA10Wg+F73RZ7SWfxVqmoJa2IsvJn8tY5CGBEpVFA3gDbxgYzxmugooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Z\"}")
-       socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"788346414\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAHgAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAxst6mky3940UmRQA7J9TRk+ppKKAAFh/EaCWP8RoooAXJ9TRk+ppKKADL/AN40Zf8AvGkwKMCgA3N/eNG5v7xoI70Ad6AF3Mf4iPxpMsP4j+dHSlxmgBCWH8R/OlBb+8fzpCM96OgoAVWb+8fzoLN6n86OBTSc0AOy3940Zb+8aTg0YFACkv8A3jRlv7xpNvvSYx3oAXLn+I0Ev/eNAwOc0YBNABubHU/nSbm/vH86VaMc5oATc394/nTt3+0aTb70baAAl8/eP50uW/vGk20baADc3940B27k0babQA/Lf3jRlv7xpMAdTRgHpQA4lj/EaTLf3jTSMUvFAC5b+8aMt/eNJjHNGAelAC5b+8aMt/eNJxQSD3oAXLf3jRlv7xpNvvRjHegBSW/vGgFv7xpuB60YHrQA7Lf3jRlv7xpAQO9IMDvQA7Lf3jRlv7xpuM96XbQAuW/vGjLf3jSfLR8tAC5b+8aAW/vGkAFJgetADst/eNGW/vGk2+9IAPWgB5LH+I0mW/vGmkYpeKAFy3940Zb+8aTGOaMA9KAFy3940Zb+8aTigkHvQAuW/vGjLf3jSbfejGO9ACkt/eNALf3jTcD1owPWgB2Wz940mWzjcaP4aMDFAC5b+8aPm/vGmjA70pGaAF+b+8aPm/vGm4HrRgetADst/eNALf3jTcD1pQPegBQX/vGjLf3jSAClOO9AAS2fvGgFs/eNJgYzRgYzQApLf3jQS3940hA9aQjHegB2W/vGjLf3jTcD1owPWgB1FFFABRRRQAUUUUAGc0UnzUtACZFGRRk+lGT6UALSZx2paMigApMr1paTJ9KAA57UDPelpPmoAMCjAoyKMigA+Wj5aMn0oyfSgBcYowKKKAEwKCAO1Lz6UhJoAMg0ZApfwo/CgBMCjApAT0FOoATAowKWigBMCjgUZPpRk+lAAeDmlpDycUtACfNSgGkzntRz9KADn1oOR3oyBQSD3oAXoKRTnqaXp1ozmgAooooAKKT5qPmoAXPGaQEmlBzScCgAwfWjB9aMijIoADntQM96BnvQc9qAFpP4qXPOKOM0AITilHNHtRQAnzUoBpM57Uc/SgA59aDkd6MgUEg96AF6CkU56ml6daM5oAKKKKACik+aj5qADqOKFpQc0ZFACEZpQMUgPrS0AIc9qBnvQc9qBnvQAtJto3UtACE54FKOKAc0ZxQAUUm72o3e1AAc9qTBFKCaDntQAtFITigHNACg5oJxQBigjNAATQPeikyKAFoozijOaACg57UUUAJgUv4Ugz3paAEwKMCl59KOfSgAooooATgUGl59KKADgUmRS0mT6UALRwKKTJ9KADg0vAoOR2pMn0oACfalyKTJ9KUnFABRjFFGRQAcdBzQeODwPSuN+IXxa8EfDLTW1DxZq8NrGASFJyx/Ac189aj/AMFFvhRaXTQ2K/aVU4D/ADCgD635AyRj3oPTI596+evh3+218HvHl8mlx6wlrdyHARwcZ+pr362vrW8tFvbW4SSJ13KynIIoAm45/hPal6ngY968W179q34XeHfGjeBtQ1YDUldY/L2Hqa9lgnS6hSdPuSorg+xGRQA89KWkPSloAQDFLRRQAc+lI1LnFITxQANSjgUUUAIc8sB16ilHp+leZ/Fz4++Bfgy1jH4xvxbPqG7yvlJzt+ldN8PfH+hfErw1beKfDtx59ldZ8t8Yzg4oA6XHt06UuOpHJpMkHPf0rB8ceM9H8A+HLnxNrkvlWVqAZG69aAN4nC5xk96Mc4HXtXlvwq/aJ8AfF2+ubHwjqQmltSBINpHX616iB6jPvQA7A60UUhGaAA57UtIc9qWgABzSYpQMUZoAQ9KWkPSloAQDFLRRQAc+lI1LnFITxQANSjiiigAooooAMYoPNIc9qWgAHSiijPOKACiiigBPmo+ag57Upz2oATrQDmjpQBigBaKKKAEBz2paQYzSk4oAQnFKeKQjNKeaAEY0ueM0YzQelAAcnvR+NJxnOaOtAC4zRSEZpaACiiigAooooAKKBzRQAUUUUAH40UHHeigAopMCjAoAX8aPxpDjvScetADufWjn1pDjvQMdqADB9aMH1oyKMigA59aXn1pDjvSkjvQAgyc4rmfiR40sfh94O1LxPqMgWO0iJBJ744/Wum6jBr5Z/wCChOuXen/Bi6s7aRkW5xvIPo1AHxBdah8R/wBrj4vnTo7u4aGe4ZQoY7Y0B/LpX2j4Q/4J4/CnTdESDX4ft96yAyS5K849K8x/4Ji+FbSX/hIvEVzErzr5XkuRyvXNfoGAoHAz70Afl5+1T+x3cfBy2HjPwJPM2nodzxxAgxc8c9TXuP7Anxn1/wAV+Hb3wT4j8+STTY8RSSggnIPrX2B4h8N6N4p02TStbtEubab78bDg1z/hv4V+BfBBnu/DWix2csincy9+KAPzF+M8m39rm4ZjhRdxdT06V+j+rfHf4X+BrCytPEfii3t5fs8YKr838I9K/MH9qI6j/wANIawulyFblp40jYDueK+h/CP7Auv+OvDCeJfG3jO4i1O6hDrEyltvHHf0oA+1vBnxW8B+P4xJ4X1+C6B7AgH8utdaCBkk5B6e1fjday+NP2avjpHoEGq3BksrqOMqWO11cgdM46Gv0i/aI+Nn/Cqvg5/wk0L41G6tIzAueSWUZP60Ad74w+MHw+8CKW8SeIbe229VDBiPwFZ3hX4+/CzxpdCz8PeKLeeU8YY7f51+dfwU+A/j39qnVr3xb4p8Tz2tk8m4SsxIcEnjGe1bHx6/ZA8SfAnSR448G+LJruCyIeXYSu054780Afp7G6yrvQh1PRgeKz9a8RaN4dtGvdX1CG0iQZLO4Gfzr5i/Yq/aIu/iN4GvNK8S3e/UdDiy0jHlxg/0FfLPx9+LPjv9oP4vt4A8MXs6WInMMMUTEdPvHIoA+/JP2pfgjHfGwk8YwifO3btOM/WvQ/D/AIr0DxVbC70LU4LqMjIKOCfyr4bsv+CbUkvhcXdx41mGpvF5m3YchsZxnNeS/BzWfiz+zv8AGhPC98uoXGmpOIrhSrMrg8Kc84oA9N/4KiFheeEDuwczYHr0r6E/YZOf2fNAYntJwe3zV86f8FN5zcDwRc7cNMkjn2yAa2vhh+0h4f8Agn+y3owa5SbWJ45RBbhuc7utAH2jrXxE8HeHbr7Hq+u20Ew6ozjNeTftU69pev8A7P2v32lXAmhZF+ZenWvjP4OfCf4lftUeO38ceKdQu4tIabe77yFK54AANfX/AO0f4N0rwN+zTq+g6OhjhtokVcsWJOeetAHy/wD8E6NWsNI8R+Ib7VLyO2hjZSWdgB39a+1Zf2nfgvFqR0mTxhCtyG2425GfrX5UfAnwN45+JnimTwV4TvJrW3vpMXUkf8IBr6s8Xf8ABOO7sfC8moaL4xmn1OCIyFdpBY4yec0Afd+i6/pHiG0F9o2oRXMDAEFGBq+8ixxtJKQEHcnFflj+yj8a/GPwg+K8fw78V3s7afLMYZklYnZjgdfU19O/tw/tEX/w08I2eieGLny9Q1qM7ZF6oMD+hoA9y8VfHn4XeDJWt9f8UW8MgOMKd38qd4T+Ovwy8bTeToHii3mc8BWIX+dfnz8AP2Q/F3x7sH8b+L/EN1Z2t0S0buWbfzzxnioPj/8AsneMv2ereLxr4T8Q3V5ZW7b5HQlAnPHGeaAP1Gmu7eC3N08gMaruLA8Yrmbb4p+BLu8TT4fEVs1ySVEe8A5/Ovn/APYt+Ok/xm8DXXhjxROZb+yjEUjE8sDx/KvmD9rj4NeKfgj8QI/G/hm5vP7Knm85cSMRHg5PfuaAP1Ha4hihNzJIqx7dxYngCuWh+KngWe7+wQ+ILd7gttCKwJJ/CvhLxj+3edT+BltoemztH4luofJlYdUxx+oq5+wr+z7rXiPUh8VfGU939n3mSzSSRsSEk7uM0AfoYjh1DL0IBFOPtTY0WNAi9FAApSaAFOT3opOBRkGgBaKKKAAmijIoJxQAmD60YPrRkUZFAC/jQOKQAUvAoATPGaM8ZoPSg9KAFooooAAc0E4oooAKKM5ooATgUvBpODS8CgAopMijIoAWiiigBM84paT+KjHOaAAUZ5xS0e9ABRRRQAUUUUAJ81HzUmB60YHrQAvzUfNSYHrRgetAClvSgZ70tJkUALRSA5oJxQAtI1GRQT2oAGoPShqD0oAAcmj+KgHn60fxUAB7YoPSgnmgnigAB4znrXzR+3v4TvPEHwS1C7so2lktApCqOeWr6XyNpBXkdKy/Evh+x8T6Jd6HqcKvFdxlCp5HSgD8+f8Agml4/sNH1vXPB+ozrHPfmNbdWOOVzmv0ZJ6gYr8mPjT8BviX+zf8Qz4u8JJcPYrMZra5hUngnJGB+VereEv+Ckuv6XoyWPiXwXNdXUKBfOZypcgemKAPsT9oX4sR/B/4dX3ilNjXUQHkxFgCxzivF/2Vf2pfFvx31nVLLUtNMNvZr97dkcg18ffFn41fFX9qjxFbaLY6Rdw2Uj7YrRVJVQfVsV94/slfs/j4LeAWfU4h/a9/FuuOORxxQB8MfGlFk/a4uFcAj7ZD1/Cv1f0dVTRbJEGB9mj/APQBX5UfGWyvH/a2uHWznK/a4fmEZI7d6/VjSfl0ezXv9mj/APQRQB+U/wC2QAP2oJ8Lg/bLbt7ivb/+Ch73q/DXwWsDN5JtB5uOn3VxXi/7Y1jezftNzSRWk7j7ZbfMsZI6r3r7e/aK+DUvxf8AgfDpmnW+7Uraxia3Hf7oJ/lQB8Ufs/eG/wBrDUvBFvP8JrnGjHOxVKZHPfPNdx4q+FX7c3ifRbjR/EtwJrCcASI7RqPzrhPgN+0J45/Zf1a78I+JvDt1PYb9vlEECPB7HHeu5+Nf7dPib4laGfCXgHw7dWcl5hWmj3E5zx2oA2P2afgB8T/hND4l1XxBbRwQ3FuxPlzq+fkPYGvKP2NTbP8AtMRPqpUuLqfbv79fWvrb9jb4Z+OrHwXfav8AEW5nkl1iLEcMpJ2jBH9a+Tfjp8LfHv7OvxjPjnw7p85sTOZraWJSQRnLdKAP1aBVUBBBGPyFeb618Qvg1ZeJX0jVbywGrKyqytEpbcenNfJw/wCCj8p8IG1bwfMdX8nys7jndjGeleU/s7/Df4h/H74zr478QxXkVgJ/NuJX3KMdVGKAPRf+CoLo7eDnh6MsxXHcYGK+VdI+C/xL8TfD6bx3a2E8mjaeAyHdkcnnAr6u/wCCmunXIbwXb20EswgWRPkQnoAO1e7/ALHPh201j9mrS9I1awGy5ilR1dMHknrQB5F/wT7+PdlNp/8Awq7W3igurc7bbIAL85NfQX7YRB+A2vt1BROfxr8+/jp8MvFP7N/xmXXvDkVwtq9x5ttJEhIIzlhxX1r45+KsPxf/AGTdR1eCGUXawRrPEUO4EHFAHj//AATVOlDxTr3nbBdl18vPXvnFfovPsMTBz8m07s9xivxa+Cni/wCIPwo8Uv448PaReTW9lJm4QIwyCcelfVnij/go7cXPhOW10nwjPDqs8Xl5ycqcYJxigDwj9oQRp+1tfroG3aLy32lOnUZrr/29/tv/AAmvhtrzd5XlQ7M9Purmsz9lb4Q+MfjV8YY/Hviayn/s+GYzXM0qkbs8r1r6l/bh/Z51H4leDrPXPDFoZNQ0OM7Y1HMg4/kBQB67+zCbD/hTegiwKeWYjkrjk1T/AGsf7OPwR13+0dhTyxgN65r4c/Z7/a68W/AnTj4H8WeHrq7sbViibwy+Wc89uai/aF/ay8X/AB/ij8FeEvD11a2M7BHCBm83J47cUAdD/wAE2zc/8J9riwqRbiQbj2xzivq79sXxF4E0j4TalB4yjhla4TFvG33ic9u9cN+x78G5PgT8Or3xj4xgMF5eRCaRCMlcAkfnXyf8cPHnjX9qD4vw+G9Ftbs6atx5dvFsYKFzhjQB876W9tZ67bajfWbyaeLoOFIIBTf6/Sv2n+Avinwh4q+G+kah4N8lbIQhfKTqpAAOfxr5/wDHv7E3h6b4EW+haPZr/bunW5lS4A+ZifmIr54/ZA+NXiT4JfECTwL4qhu00u4n8ly0bERkHA7dzQB+poPpSDOeaitLqG+tormE/LKgcfQjNS8YwaADvR3o70d6AAnFHQUEZpaAAc80HnikJxS4xQAU3JpQc0EZoAU8UgNKeaQCgA3UZFHy0fLQAZFGRR8tHy0AGQaMgUny0fLQAuRRn3pDtxSjaaADvR3o70d6ADn1oOR3oIHrRgHpQAEkDrQARzRjPNAAoACcUtITiloAQDvS0UUAJ3o70d6O9AC0UUGgBAeaCOc0mBig4xQDVg5zkUvPrQMdKBjPFJK/UNWHA4ozQwxQRTt5hdCE0ueKRqXHFHzC6BaAecYoFIetO6DQUkjvQcgdaQ896OtK6DQXIHSjIoA4pB1o1DUM0ZoHWlajUNRaMUh6YoWlqBS1PRdM1u2az1OyhuYmGCsiA/zrzLUv2V/gjrF013f+DIWlY5JDkD9K9ZODgmg4DYLZFPUZyHhL4SfD7wOAPDvh62tj2YoGI/E115A/1eARjFKeRjHSj5cZzk+lOzE7HJXnwr8DX+sHXbzQoJL1iG84gZyK6tFVVWNQAEGAPQU8YIwBSdyCdxPalqLc5TWvhd4I17UjrOraFBPeFg3mMBnI6V1EUccUaQxgeWi7QuO1PHXnj0o3EfL2FFhnGeLPg98PPG0vmeI/DltPJ/eChT+grO8O/AL4U+FrsX2jeFreKVTkFhu/nXooOBn0oJzyOlFgGRxRQxpHDGqIvACjAFZ+u+GtE8SWr2OuWEN1E4xh0BxWkOufXtRz0zg0rtB7zPK3/Zf+Cz3f2tvBkPmk7t27jP0r0LQvDei+GrNNP0TT4bWKMYARAP1rSIz06+tKD2LZI600nILM57xR4C8LeMjC3iPS4rtrfOwuM4zWlouh6X4c0+PStHtVt7SP7iKOBV7nlg2c0vG0ZGCKAOf8TeBPCvjARf8ACQaPDeeVnbvUZGarWXw08GaZpU+i2mjRR2VxjzIh0P4V1HGN4GCetB9hgetAHJad8KvAWmW1xaWfh21SO5H71SgOf0rmpv2Zvgxc3IvJfCEAk3bg2e/0r1IEgbs5IoXptxwaAMzQfDWheGLJdP0OwhtYIhgBEAzWk6LIrCVAynjBGQaXg8E5pM8H+8egoA4PxN8C/hj4vuTda74Xt5ZW5LKoX+VL4X+B/wAMfB9yLnQfC9vFKvIZlDY/MV3mcDBPTtS8AdeT2p2sw0XQpalpOn6rZNpl9CJLZxhkAwMVzuifCjwJ4d1AappPh+3guUJKOFBPPWuu7YB4WjqcjqaSTb0YIR1V1MTKCMYxXGXvwe+Heo6k2qXXh23a5dg28KAcjvXaZ6ZOcUDjhjgetF0O5Fb20NpAlvAMKgCgegFSnOQc0ZA+ajjv3ouguLRSAYpScUm3HYQhOKWjIFFHqAhGaWk3Uuad0AUUgOaDzTugFHFNPWnZpo60XQCnpQOlLRRdAIM96Dk0ZFGRS0QW1uGD60YPrRkUZFK6YO973A9KAMUtJupgAOaCcUZA4oPSgBc0mD60ZAoyKADJ9KMn0oyKMigANABpcZpuB60ALg+tGD60ZFGRQAvPrRRkUUAJg+tGD60YFGBQAHPagjNGBQCACc9KG1b3gbSXvBjvnHvRjuR+NeX/ABB/aE8BfD+8bTr7UUe7X70YycfiK4cftneAedqcDqea46mMoU92cVTMMNSdnI+iM/7X6UZ/2v0r54/4bO8Bdk/nR/w2d4B/ufzqf7QofzELNMK/tH0OcHvQMDvXzv8A8NneAv7n86P+GzvAX9z+dH9oYf8AmD+1ML/MfRHPrRz6187/APDZ3gP+5/Oj/hs7wH/c/nR/aFD+YP7Uwv8AMfRB3HvSYP8AEa+eD+2d4C7J/Omy/tpeAIo8uvT61rTxtGbsmH9qYX+Y+icn1oHHevmj/huj4c/3P50f8Nz/AA6/ufzr2FleKkrqI/7Uw38x9L4PpQQT2r5nP7c/w4/55/zo/wCG6Phx3jP60/7KxX8of2phv5j6ZNA4/wD118zf8N0fDf8A55n9aP8Ahuj4b/8APM/rR/ZWK/lF/aWH/mPpgcUdTXzP/wAN0fDf/nmf1o/4bn+HHaM/rT/srFfyh/aeG/mPpnnOB0pAMn6V8zn9uf4c8/uzx9asWf7bnw6vrlLaKP5n+tH9lYr+Ul5thIK8pH0jn/apPoa8NH7WHgkgEJ/Oj/hq/wAFdk/nT/snFP7Bxf6yZbF2lUPc8H1owfWvDP8Ahq/wX/d/nR/w1f4L/u/zo/sjF/yh/rLlv/Pw9zz/ALX6UZ9/0rwz/hrDwV/c/nR/w1f4K/ufzpf2Ti/5Q/1ly3/n4e5k+9A69a8M/wCGsPBX9z+dH/DV/gofwfzprKMU/sjXEuXJfGe5kkKTnk0AgMQK8MP7V/gv72z+dXNL/ae8Iatex2FvH+8f61FTLMVBX5S6Gf4CtKymez5HrRn/AGv0rhB8VtNIBFu2D9aX/haunf8APu361wNW0Z7SaaujusH1owfWuE/4Wrp3/Pu360f8LV07/n3b9aBnd5/2v0oz/tfpXCf8LV07/n3b9aP+Fq6d/wA+7frQB3fXvSH1zXC/8LV07/n3b9aP+Fq6d/z7t+tAHdHjk85pQDjrxXC/8LV0wHi3bH41R1T41aJpVv8AabqEhR9axxFanhoe2qOyNKVGVeXLBHpBDY4NAz3NePf8NI+Ev7v86P8AhpHwlj7v868KXFWWN6VLHpvIsdNXjA9hyfSjn0rx7/hpHwn/AHf50n/DSXhP+7/Oj/WrK/8An4P+wcd/IexfhRz6V49/w0n4U/u/zo/4aT8J+n86P9asr/5+B/YWO/kPYefSgZHavHv+GkvCfp/Oj/hpLwp6fzprivLLXdQP7Ax/8h7CB37mgnHJPSvHz+0l4U/u9PrWN4j/AGtvBHhu3FxdD5W+taUeJMtxMuWNQqPD+Pe0D3nPtRn2/WvmA/t2/Dj+7/Ok/wCG7fhx/cP616Sx9D+Y2/1WzP8A59M+oM+360Z9v1r5f/4bu+HH9w/rR/w3d8OP7h/Wj6/Q/mD/AFWzT/n0z6gz7frRn2/Wvl//AIbu+HB/gP60f8N3fDj+4f1p/XqH8wf6rZp/z6Z9QZpMj0r5g/4bt+HH9w/rSj9u34b/ANw/rS+v0P5h/wCq2af8+mfTxzwc8GlIGeelfMcP7dfw2aRUYbVY8nnivZfhx8X/AAZ8TrP7V4Z1RLgoBuToR+dXTxdGo7JnLisix+DhzVoWR3GBRnFIDmlxmult9DyNluIRmgDFAIoJxQAfNR81Hy0fLQApz2oGe9J8tHy0AAOe1BOO1G6jdQAZ5xR/FS55xSfxUALRz6UnzUc+tAC0UUUAFFFFACcHLetY3i6+k07w7f3kJ+ZIWI/I1skk4OOBXPeP9p8I6mB3gb+RqKvwMyry9x+h+ZHiPUr7Xtcu9Sv5WkkkmcfMc9CapfZMZIA5q3sBvLknn9/Jj/vo1YWMAZ71+e1qj52flderLnkjM+yn+7R9kI6CtTyh6UeUPSsXOSdkY+0ktDK+yn+7S/ZT6VqeUPSjyhjOKfPJD9pK9rmX9k9qPsp9K1PKHpR5Q9KFO60H7WWzMo2xGeKo6pBttJDjtXRGNcEkVl62irYyYHQV6GVyviYJ9wjVu7HExQbh071L9m5HFS2Y+Q8d6thAa/pXDQjKlGy6IcqjUtWUPsgoFoK0PKo8ut/ZQWpPtJt6Gf8AZBR9kHpWh5dHl0eyXQftGt2Z5tKPsnPStDy6PLpuiJVuhnG268Ve0CDbq0P1pzR8ZqxoqY1WH60vZR7GGJqP2TPSoLYsi8dqn+xHd0q1Yx5iX6VdEIHOKtU0fn9WvJS3Mj7H7Uv2L2rW8kelHkj0o9mk0kiPrGm5k/YvQUfYj6VriIelHlewodKKshfWJRd4syPsXtR9i9q1vJHpR5I9KPZK6G8RJK9zHNlz0rc8CwbPFFnx/FUTQgdK0vByAeKLPA/jrz8ypWw0rHs5BXlLHU031Pqe00xXhjIQcqO3tVj+x1I/1Y/KtbS4Fa2iyP4F/lWkLZcdK/LJ/Ez+jKXwL0OY/sdP+eQ/Kj+x0/55D8q6j7MPQUfZl9BUlnL/ANjp/wA8h+VH9jp/zyH5V1H2ZfQUfZh6CgDl/wCx0/55D8qP7HT/AJ5D8q6j7MPQUfZh6CgDljpA/wCeYx9K4H4tWSw+HnOwD8PevZZLdQM4ry741RKvhtyPUfzr5/idtZbUPWyTXGwR4BHaZHAqUWRx0rQtIlKjirawJ6V/M9SvKMnqftlOlHkWhifYj6UGxz2rb8lPSgQIe1R9Zk1uV7GOljE+xcfdo+w/7Nbfkp6UeSnpU/Wp7Jh7GLaujE+xe1H2I+lbnkL6UeQvpVfWJ8urGqMXJJGEbJvSvNfjNBs0mH8a9kkhXB4ryj42pjSocep/nXs5DXlLGQVzowlH98rnhywbuop32fBq1EgqXy81+tqbaPs44eLitCh9nGelH2cDtWh5Q9KPL9qfPbqX9VjfYz/s6+lH2celaHlD+7R5Y9KTqX0QfVoJNWM/7MB2pfs6+lX/ACx/dpPLX0oc7ISw0exnmADOT16V7T+yX4n1TQ/i3pem21w/kXjMJV3ccDjivJWjHOF6da9F/ZsG340+Hzjje+fyrrwVR+1jqeDxHhYf2fUuuh+qcZLRo3qoNPOccVFBnyY/9wfyqT5q+6Xwn8vTilKXqLQBimg4pQc1RAfxUNRjnNH8VABkUuRRz6Uc+lACZFGRRk+lHPpQAHpQOlLRQAUUUUAFFFFABRRRQAh6Vzvj/wD5FLUv+uDfyNdEOc1zvxA/5FLUv+uDfyNZVvgZlX/hy9D8yf8Al5uP+u8n/oRqyOlVx/x9XH/XeT/0I1aAr86qfGz8nr/HL1A9RluvQ0AHgbcepz1o7EYz6V6P8Lvgn4i+JQlns42htYlyZcZyadKlOtLlp7ioYedafLT1Z5zgnIxmun+H/wAP9b+IGsw6VpVs5RmHmNjhRW7pfwR8YXvjM+E202RXjkAd8cBfX8q+0/AHw/8ACvwd8K+dKYklSPdNO3UnFelg8ulXqc1TRI9fL8oliJ81TRLc+Zvi5+zTN4H8PRa3pdyJljXNxkYwa8AKkEgr0OGr3r9oH493XjO7m0DRJCumxkqWH8deDHnG7qefrXPmHsvaWo7HLmaoKrbDidjWRrufsMn0rXI4NZGu/wDHjJ9KrKrPFwXmcCepylkBtH1NXF6VTs/uD8auL0r+n8M7YeK8kKau7sXoQSeOwpOnbHtS4J4ByB0969Z+Fn7PHiz4oabcarZwNDbwrujcj79KtXpYWHNVZpRoVMTK1M8mABbIruvhP8J/EHxR12HTdMt38ncPOl28KK2vB37PvjXxH43bwpNp0sIgkAmkI4Uf/qr738G+DPBfwI8Fb5DDB5EW6aZgMsa8TM86jh48lDWTPZy7KJV3zV9Io+Pfj/8Asv3Pwy0iLxDpFwJrJFHng8bD/XmvnVjghvzFe9ftG/tD6h8TtSl0XSnMejwsVAB4krwckkliceld+VLEOhzYndnDmUqCq8uH2Qh6VNo//ITi+tQnpU+jf8hOH616LR5GJ/gs9Z08fukq+DVHT/8AUp9KvDvVxR+d1viF6YK9uvtR1BP3g3X2o5+WvRPBnwW8ReMdGn1eCMxxqu6IY+/61jiMRTw0eao7GuDwNbHVHToRuzzvAHAGAK7T4cfDXVfH2pi3ghZLYEeZLjgCr3gr4P8AiDxJ4jOk3No8Mdu+JnI7V9X6bp/hn4VeGOTFBHAmST1Jrw81zqNL93h9ZM+u4f4XliZe3xq5acT5l+LnwYn8CRrqFk4ltSBu7bT/AFrycAZGOlel/F74sX3j3UmtYnKWEbEKg715qMngngdK9PK/b/V08R8R4GffVPrklgvgQ1hxitHweP8Aip7PP9+s9q0fB/Piiz/36WaL/ZpJmnDzisfTXW59jaT/AMe0X+4v8q1FrL0n/j2i/wBxf5VqDpX5RP4mf0lS+Begc8DGcdqD97nr29qGIXndhQCSfSvCfH37W3gHwJ42tPB9xcpIXcpPID/qj2qSz3bv0+prxn9oL9ozw58F9Ckle4S41KUfuIA3JP8ASs745ftQ+Efh14OXUtNv4ry+vYibaNG7kf8A16/O1U+IX7SHxCABnu7i8l99qLn8hxQB+hP7MX7TVl8arB7G/iaDU7cncuCQwJ45+lfQGSTnHTrXjf7O37PehfBfw6kcSh9WuEU3E2OSa9lOeMNn8KAGS/dryz43f8i5J+H869Tl+7Xlnxt/5FyT6j+dfP8AE7Tyyoevkf8AvsDxW05UfSrvQVStBhV+lXB0r+X63xP1P3Ck7U0GB/hS8HqKFUllK9+MV3WmfCTxBqXh+TWkjYEDdHFjlh3roweW4jMZNYdXsYYnG0MIlKu7XOF2jcAOQf1rufh18M7/AMY3PmyQtHZp1cjrVj4d/C3VPE2pbr+FobWFvm3DrX0BqGpeH/h1oG3EcSxJgKOpNfa8OcJqSeLzBWprv1Pl864hcGsNgdZv8D5x+I3w9n8GXow++3f7prjM/wAIHPb2rqPHnji/8Y6m9zKSLdSfLT0Fct83ds4r5LOvqzxklgvgPosqWIeGj9Z+LqNkHymvJfjeP+JVDj3/AJ1605yDXk3xw50mH6n+dbcO6Y6ET2cIl7eJ4zF0qeoYugqev2B7H29NqMUg4+8TgUnQYIx71JDDLcTLDCpZ2IUL6k17Rbfss+ObnwL/AMJgsDl9m8W+3kitaeHlVWhw47M8Nl0l9Zmo32PFApY/J1PQV7v+z9+zVrHxRn/tPVIXt9LQH5mX73HH61e/Z5/Zm1jx7rSal4jspLfTLaTL7xjfg/8A1q+zvHPjnwT8B/BXlRiCHyItsUS4yxxXr4HLo8nta2x8FxPxhUVRYDKveqy6rofn18dPgxqHwi8QiwkkElpOT5MnHzAdeO1eY46DORXc/Fn4qa38U/EUmrapKRAHPkx/3BXC8keory8T7P2j9nsfdZOsX9Sh9dd6ltRH6V6H+zf/AMlq0D/ff+VeeP0r0P8AZv8A+S16B/vP/KrwS/fROTiPXL6nofqfbHMMf+4KkJxUdt/qI/8AdH8qlr71fCfyrW+J+oUgHNLRVGYhGe9KRmiigApPmpaT5qAFPtSDPelPtSDPegBaMiijAoATrR0paKACiiigAooooAMVzvxAA/4RLUuf+WDfyNdDj3rnviAMeEtSP/TBv5Gsq2kGY19KTPzJH/H1cf8AXeT/ANCNWR1FV1/4+rn/AK7yf+hGrA7V+d1fjZ+UV9arFP6Gvoz9mr462/hMxeEteVFs3O2ObAG3/GvnPHG3OPUUqM8Tb0Yqy8hga0wteeHmpwNcJip4OoqkT9RZ9S8M2FhJ4ldrdUZN5m4yRj1r4z+Pvx+v/Gd5LoegzNHpsRKsVb79eb3XxX8Y3vhmPwrPqsn2KIYxnkj61xuSSWLEnv716eLzV4iHJT0PYx+ePE0/Z0lbuKxLEsxJJ9aKQUA5rxNz53fcQ96ydd/48ZPpWse9ZOu/8eMn0r0Mq/3uHqVHc5Sz+4KuLVSy+4PqatrX9PYbWhG/ZClJ82gobDAgYAORX1v+yl+0ZZ6EsPgfxN5cUBIWGUgDFfJBA4+bOf0p0c8tvIkkLFGQ5BBwc1jj8DTzCl7OR04HFTwVX2iP1y1bW/CfhnTJvFVw9rFGyeY0wAy3HFfAn7Rf7ReqfEnVZdH0e4eLSIWKqqnHmV57rfxi8ca94atvCmoavLJZW4KhemR9a4jcxADHNeNluQrCy9rX1fQ9bMM6liV7Ojog5PJPWgZ70c0gz3r6bW10fP2V7MG61Po//ITh+tQN1qfR/wDkJw/WkzDE/wAFnrdgcxJ9BV/PSqFgMRJ9BV/0qon53W+MVSVYSDqpyPevpH4BfGWyihh8K63sjI+WKQ8Bq+bQOPX29KfBPJbyLNBKUKHII4INcOY4Cnj6bpz3PTyXN6mT4lVobdT9BNa1vw74Z06bW53hhTbuLgD5vSvkL4s/FvUfHWoyW9vM0dhGxEag9a5zXPiJ4m1/TLfS9Qv3eCEFQOma5gf7TV5eVZEsHP2lfWXQ+gz/AIulmUVRw/ux6hnNFFFfSHw71dxDWh4OP/FT2n+/Wea0PB3/ACM9p/v15uaf7tI93h7/AJGEPU+x9J/49ov9xf5VqLWXpP8Ax7Rf7i/yrUWvyifxM/pKl8C9Bs8azRPC4yJFKt9CK/PX9sD9ljV9H1G5+IPhKOW4tpG8yaMEkx/jX6Gjhsk9aq6lplpq1nLY6hAs8MylWVhkEVJZ+MngnwV43+KviK28MWSXVy6vsO8kiMd+tfp9+z1+zz4d+Dfh6FRbRy6pMgM9wV5zXVeBfgt4E+Ht/d6j4d0aK3mum3O/U59s13g65z06UAAAPNAwe1LSAY70ANk/1Zryv42f8i3J+H869Uk/1Zryv42f8i3J+H86+f4n/wCRZUPXyP8A32B4ta/dH0q6eoqla/dH0q6eor+Xq/xM/caX8NDomMUqP3Qgivo74RfFCx1i0j0PVCkdyg2qT0YV83H72c/Sp7O9ubC4jubSUxOhySK9zh7PquR1lUirxe6PJznKaebUeSWjWzPsbxJ4k0Pwfpcl6/lJxkBcDJr5b8deOdQ8X6k8s07CIE+XGOgFUfEPi7WfEpRdSu2kSMYVegFYoO3kHr39K9niXi6ebr2ND3YHnZJw5HLf3tb3ph0FA5FIOuKOp4r4Y+pW42Toa8m+N/8AyCYfqa9ak6V5L8cP+QVF9TXucPf79A6MJ/GR41D92phyKhh+7Uw4r9etdWZ9vT1SZa02/m028hv7fG+FwwyM5wa/Q39mv9obQfH+jw+GtcaGHUIkCbWxh/6V+dJycnOCa0dB1/VPDmpRappFy8E8JyCprvwWM+pvyPm+JuHKfENDlbtNbM/Ur4n/ABP8H/CDw3Les0ETlSYokABY/hX5vfFf4seIfijr8+p6lcP5Ic+VDu4UVn+OviX4q+IN0s/iHUZJ/KUCNScDp6VynGPQ1vjsyeI92HwnmcK8H08kXtsR71TuLRRRXk+h92rX0GSdK9D/AGbv+S16B/vP/KvPJfu16H+zd/yWrQP99/5V2YP+NE8LiPTL6iXY/VG34gj/ANwfyqSo4P8AUx/7g/lUlfexfuo/lOprNt92FAGKCcUVRmJjnNB6UtFACHpS0h6UtABRRRQAUUUUAFFFFABRRRQAUUUUAGMsR61zvxAwPCGp5/54N/I10OTuA9Ky/E2ntquh3lggy0sLAfXBqKq5oMyrq9Nn5eJzdXBzx50n/oRqyvI54Bq/4q8N6l4U8RXuk6payRPHMxUlTggknrWasqbfvZPpX53WpyjN6H5Xiaco1XoS0UzzV9aTzU9RWXJNdDD2c+iJKTB9ab5qetIZU7mhwl0QOnN7okoqMyoO4pfNX1/Wjkl2Dkl2HHpWVrv/AB5S/StEypjiszXHU2UmPSvRyqLWLhddSowl2OUs87B9aueuTVKzYBOfWrXmJzzX9M4acPZRbfRBKEnokSYPrRg+tM80daXzV9a256b1TJ5Ki0aHUUzzEHejzEPWhVV3B02ug/B9aMH1pvmrSeatNVIN2uHsprWw5j2qxo3Opw896qmRc1Y0ZgdVi+tJzj3McTCXsXoeuaef3S89hV8YwOazrB1ES5PYVd81OOaqM49z88rUanN8LJKKZ5y+opfMT+8Kp1ILVsyWHqb8rHUUzzk9R+dHmp6inzR6MPYztqmPopnnJ6j86POT1H50c8e4OjU/lY5ulaPg8Z8T2f8Av1lGZDxkVp+D2U+J7PB/jrzcznF4eVme5w/TmsfBtM+yNJ/49ov9xf5VqD2NYulX1oLeMGZPuD+IelaI1Cz7zx/99CvyqfxM/o+l8CLLUo+tVf7Qs/8AnvH/AN9Cg6hZ9p4/++hUllnB9aMH1qr/AGhZ/wDPeP8A76o/tCz/AOe8f/fVAFoYoOKrf2hZf890/wC+hR/aFl/z3T/voUATy/dxmvK/jdn/AIRyTnuP516W+oWewkTx/wDfQrzD403EEvh6QJIrHjgHPevA4mTeW1Ej1skaWNg2eN2h4H0q4elUbWRNo57Va82PH3hX8w1qNTmfus/b6dSHs1qSUH61H5sfrR5sfZh+dZOjWbvys09pB63Hk89aUnio/MT+8KPNTGMj86FRqr7LEqkU7XHnpQtM8yP1A/GgSx+o/On7Gp/KxKpC+4rk7SDXk3xw/wCQVF9T/OvV3ljKk5FeTfG8g6XDj1P869zh6lNY6LaZ0YWpD2y1PHIcham5x1qCJuKl3DGK/XeSUldI+2hXpctnJfePopu8elIXHalyN6NF+2pS2kvvHUtM3il3ijkklZIft6ezkvvHUU3eKTeKfI3okL29GWikr+oPggdh6V6J+zfx8a9Ay3LO/wCHFedFwScjPpXt/wCyR4B1rX/ihY67HZyCz09iZJGXA5HFdmChL2sdD5zijF06WXT95bH6S25P2eL/AHR/KpabEu2NU/ugCnGvu0vdR/LtWXNNpAelIBigHNLTICiiigAooooAKRaWigAopcH0NGD6GgBo6UtAVsdDS4PoaAEowKXB9DRg+hoASilwfQ0bT6GgBoPFID+tP2N/dP5Umw56Gl7z0YWvuch4u+Ffgfxud3iLRI7lu7A7T+YrlG/Zb+C+Qf8AhFj/AN/mr1raR2NG3sQR+FYywtOW6OWeEoTeqPJf+GW/gvjJ8Lf+R2o/4Zb+C2M/8It/5HavWtr47/lRtOO/5VH1Sl/KT9Rw/Y8lH7LnwWH/ADK//kZqP+GW/gt/0K//AJGavW9rf3T+VG1v7p/Kj6pS/lD6jh/5TyQ/sufBU/8AMrn/AL/NR/wy78F/+hW/8jtXrWxvQ/lRhvf8qHhKP8ofUcP/ACnkv/DLnwXzz4WP/f8Aakk/ZX+Ckow3hXI/67NXrZR+pB/KlKsRwD+VXDD0ou6QfUcP/KeM/wDDI3wJ/wChR/8AI7Uf8Mi/Ar/oUv8AyO1eyhWx0P5UYbP3TXorG10rKTH9Rw/8p4z/AMMi/Av/AKFL/wAjtS/8Mi/Ar/oUv/I7V7LsPoaNh9DT+u4j+Zh9Rw/8p41/wyL8Cv8AoUv/ACO1H/DI3wK/6FL/AMjtXsuG9D+VGG9DR9exH8zD6jh/5Txr/hkb4Ff9Cj/5Hag/si/Ar/oUf/I7V7LsPoaNh9DR9dxH8zD6jh/5Txo/sjfAwAf8UlwP+m7VLbfsn/A+1mE9t4Sww/6btXsAQg5IODQVPoQPpR9er/zMmWX0JKzieZ/8M6fCYdPD2P8Atq1H/DO3wn/6F8/9/Wr0zBHY0YJ7H8qP7Qr/AMzOZ5LgnvBHmZ/Z1+FA/wCZfP8A39agfs6/Cg/8y+f+/rV6YVbPQ/lQA3ofyp/X6/8AMw/sTBfyI8z/AOGdvhP/ANC8f+/rUf8ADO3wn/6F4/8Af1q9M5/umjn+6aX16v8AzMP7EwX/AD7R5of2dPhT/wBC8f8Av81N/wCGdPhV/wBC8f8Av61emkE/wGk2n+4aax9f+Zg8jwT+wjzT/hnP4UFiP+EePP8A01arFj8Avhhp1yt3Z6BslTofNNeiEEjaSQfpS7Tx8pI7molja8tHJmlPKMHSd1BXOVHwz8I9rBx/21NH/CtPCX/PjJ/39aup2n+6aNp/umuZu56KVlY5b/hWnhH/AJ8ZP+/rUf8ACtPCP/PjJ/39aup2n+6aNh7KaBnLf8K08Jf8+En/AH9aj/hWnhL/AJ8JP+/rV1JDd0NADdkNAHLH4a+Ev+fCQ/8AbVqT/hWnhP8A58JP+/zV1O0/3DRtP9w0Acv/AMK08In5Rp8mP+uzVXvfhP4Hvo/Ju9LZl95WNdkEI4OfypMMOCpIrKrRjiI8s1oVCq6UrxPPj8Cvhz/DouP+2ho/4UV8Of8AoDH/AL+GvQQpH8JoCE/wGvLWQZfJ3nTR3vM8Sl7s39558fgV8Ou2jH/v6aT/AIUV8O/+gN/5FNehFTn7po2kdFNP+wMv/wCfaJ/tTF/zv7zz7/hRfw7/AOgMf+/rUn/Civh3/wBAb/yK1eh4/wBk0Y/2TR/YGX/8+0P+1MX/ADv7zz0/Ar4ddtGP/f00n/Ci/h0P+YL/AORGr0LaT2NJtP8AdNC4fwFv4aB5pi/53955/wD8KL+HWMHRj/38NZ2sfs2/CfXIfJ1PQPNT08xq9SKnGMGja4GApAqqWTYKjK6poazXFr7b+88U/wCGP/gX28J/+Rmo/wCGP/gZ/wBCn/5Hava9r/5FG1/Su76rSX2TX+2sb/z8f3s8UP7IHwK7+Ff/ACM1A/ZA+BX/AEKn/kZq9rCnupoKHspo+q0v5Q/trHf8/X97PFP+GPvgX/0Kf/kZqP8Ahj/4Gf8AQp/+R2r2sq3YGja/p+lH1Wl/KH9tY7/n6/vZ4mf2QPgX28K/+RmpR+x/8CyP+RU/8jNXtW1v7hpdpHRTR9Vpdg/trHf8/H97PF4v2QvgdG4b/hE+VOR++avSvCvgnw54LshYeH9MjtYlGPlHP51vBTkkgk+lCq3J2EVUcPGHQwrZlisQuWrNteoCil2n+6fyo2n0NbX6HBs7oTpRS7T6GjafQ0AJSYFO2n0NGD6GgBM4oo2k9jS7T6GgBKM5pdp9DSbSOxoA2tq/3R+VG1f7o/KlooATav8AdH5UbV/uj8qWigBNq/3R+VG1f7o/KlooATav90flRtX+6PypaKAGlR2UflSbB6CnfjSHPehXFZMNqjsPypML/dFKtLkdKA2EwvoKMJ6D8qNvvRt96Bi/L6CjC+gpNvvRt96A0FwPQUbV/uj8qTHvRt96QC4X0FJhfQUbfejb70wDC/3R+VGF/uj8qXHSjHSgBML/AHR+VGF/uj8qXHSjHSkGgmF/uj8qML/dH5UuOlGOlAaCYX+6Pyowv90flS46UY96YaBtX+6PypNo/uj8qdSZFAtRNq/3RRgDHyilyKTbS1GLtX+6Pyo2r/dH5UYo20C0Dav90flRtX+6Pyo20baA0Dav90flRtX+6PypNvvS7aY9g2r/AHR+VN2+wp9FK7Fa4m1f7o/Kjav90flS0UxibV/uj8qNq/3R+VLRQAm1f7o/Kjav90flS0UAJtX+6Pyo2r/dH5UtFACbV7gflSYT0H5U6kyKV2hbjSF7AUYHoKfRRa4xuxfQUbF9BTqKYDdi+go2L6D8qdRQAzavt+VKFX0FKDmlpagN2p3Ao2r/AHRTqTFMWobV/uj8qNq/3R+VGKNtINA2r/dH5UbV/uj8qNtG2gNA2r/dH5UbV/uj8qNtG2gNA2r/AHR+VG1f7o/KjbRtoDQQKOuBS7V9BR+NLRqwGkKP4RS7V/uj8qWm7fejYYu1f7o/Kjav90flS0UwE2r/AHR+VG1f7o/KlooATav90flRtX+6PypaKAE2r/dH5UbV/uj8qWigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9k=\"}")
+       socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"283463389\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAGqAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArlviX43Pw78H3fipdHl1RreSGJLSKQRtK0kqxgBiDjlvSuprhvjNpmoav4INnpljPdznU9Ok8qCMu2xbuJmbA5wFBJPYA0AL4r+K2j+G/hxH8Rra0k1G3ureOezto3CPOXXcFBwcHbk9O1bmg+L9H12wW7juEimSygvrmAtlreOVCy7jjngN+VeOal4L8Vz3Wv8Ag99DvG0Hw5b317pEixMUupLpS0UUf94w5ZMD1FWdLfW/CFxqcF34S126l1vwxpttZfZdPllU3EUMitFIyqREcuOXIHXninYD0y8+KPgCwNkt34ntU/tCGO4tyA7BopPuOSBhQc8bsVYXxtpEc+tHULm2tbPRVheW5afIKyAkEjHy9OOTnNeEHQNX0bw/pTQaH4l0/W38NabbNCNFkvbS/kjgUeTMqrmF1bKncVx1rZ1Lwz4tuV1bVL3wpdSJDf8Ah+/urCGBmFzFAxaeOIf8tNv90ZzgDvRYD2fw34w8NeLoppvDurRXqwNslChlZD1GVYAj8q0rq9tLIRtd3CRCVxGhc4BY9Bn3rlfBl/Z61reqazp/g660q3kjhiW8u7SS0muyoOR5UiqwVc4DEc89q2fFQSTRprVtFk1VrjESWyjhmPQs38AHXdkYx60gNivPb74uwWXhPW/EX/CPzyXmjal/ZQ05Zh5k85ZFjCtjjcZFxxXXeGdO1LSdDtNP1bUPtt1CmJJvXngZ74HGcDOM4FeYXfhPXm+Nq28elXDeG7yaHXbi68s+SLmGORRGW6bi7RNjr8hoA6XU/i3p1pYeHr+w043a+INPk1NMziMQwIiNuY7T/fA/OtS8+J3gnSntLbWtftbO6u4IpxESzhA4BG5gMKPc4ryPSvBHipIvGdheaDei20HT7jRtB/ct/pMLzSSK0Yx8w8t4k47oaj8XaLqVglzJpmjeIbfV7rRLSAWw0WS/sdUdIiBFJsX9yQSVJZhgHPanYD1B/jD4PsfEWseHNdvo9Nm0q7itUeRiwn8yGOQN8q/IP3m3k9R1ra8ceLoPBfgzVfGTWpvYdMs3vPKSQL5qqucBsHGfXFebDwpq15YfE681LwvIuoaotuLdRbl/NK2EAKxHHzgSBxx3Bre+IukavqHwA1TRbPTbq41GXw75C2scTNM0vkAbAgGS2eMYzmgDt9c1xNF0CfXXiRlhjWQo8mwckDlsHHX0rMf4leCYNUg0O78QW0OoT+WogO4gO+Nql8bQTkYBIrjfFvjJfFvgjUfDeleE/Fq3s1qgQXHh68gQlXTI3PGBnr3rjdc0rWtM1K8Ph3QdeXVbq6tJW0250iS50/UGUIPN89V2wEYOSzfKVziiwHsPhL4k+GfGer6zoujXLvcaLcfZ5gyMA3yg7gSOmTj8K6qvPfh9aT6V418b2d3ot5bG81NL22uDZusE0Jt4l+WXGwkMrZUHI9K9CpAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVma34m8OeGvsf/CQ67YaZ/aNyllafa7hIvtFw/3Yk3EbnPZRyaANOiisbQfGfhHxTc39l4a8T6XqtxpUohvorO7jme1kOcLIFJKHg8HHQ0AbNFZ2u+ItA8MWa6j4j1qx0u1eVIFmvLhYUMjnCoGYgbieg6mi08R+H7/VrnQbLW7G41KzjSa4s47hWmhjb7rOgOVB7EjmgDRoorO1/wAQ6D4V0uXXPE2tWOladAVEt3ezrDChZgq5diAMkgDnqRQBo0UgORkUtABRVbUtT07RrCfVNWvreys7VDJPcTyCOONB1ZmPAHuaNO1LT9XsYNT0q9gvLO5QSQzwSB45EPRlYcEe4oAs0VT1fWNJ8P6Zcazrup2un2FonmT3V1KsUUS+rMxAA9zU9tc297bRXlnPHPBOiyxSxsGV0YZDAjggggg0AS0UUmRnGeaAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqrqGpWumQ+dcsQCcKBySaaTk7ITairstUVz3/AAmdh/z63H/jv+NH/CZ2H/Prcf8Ajv8AjW31ar/KZfWKfc6Giue/4TOw/wCfW4/8d/xo/wCEzsP+fW4/8d/xo+rVf5Q+sU+50NFc9/wmdh/z63H/AI7/AI0f8JnYf8+tx/47/jR9Wq/yh9Yp9zoaK57/AITOw/59bj/x3/GtDTNcstVLJBuWRRkow5xUyoVIK8kVGtCTsmaNFFFZGgUUUUAFFFFABXin7YfhS78TfAbxBeaUCup+HVj16ykUZaOS1cSkr7lVYfjXtdVtRsLXVLC50y+hWW3u4nhljboyMMEH8DQBxNz8UNOb4IH4tQyrHaz6AurREH7peEMg/wC+mAr52/Zu8OXnwh+KfhZtT3xn4seHLi9u1JO3+0Y5DcA/UxMwA9qxNG1PUX+COl/s0vcNJrFt46m8ITg9Xs7aYzE/TyvK9sGvaf2ptPTwl4J8LfEvS4WB+HGuWGolYx8xsg4imT6eU7E/SgBn7Qif8Jp8Ufhb8KU/eQTanJ4h1KP0gtgBG3/fbH8q6zw7rXw8j+M/jO3stIltPENlpdrNq2oSSnypbfLbAATgbcEk4rmPh40fj39pfx146jdZ7DwtYWnhrT5lOV8woJ7gA/70gH4Vm6DDpFx+0b8XLfX7mO30yXwzZpeTSSCNY4T5gdix4UAZ57UAbEH7S2r67YyeJfAvwY8TeIfDCFzHq0UkcX2iNTgyRRN8zrwcHIzXK/tQ+P8Aw78Sf2QNf8X+Grh3sriWzVllXZJDIt5EHjcdmB6irvg3wv8AtE/BnwxZ6B4BPhj4ieEbGEDSkkujZXv2Xqi7yDG+FxghuRiuE+Mvivwf4x/Y78djw34Tl8MXVtq8EOs6TKuJLe+a7hMhPJB3cEEcGgD2LU/2htUjt5tY8J/CHxL4j8O2u7fq9u0cccqL96SFG+aROCQeMjpXo/gHx34f+JPhSx8Y+GLhpbC/Qsu9drowOGRh2YHgitXTNNsdM0q10mxtkitLaBIIoVGFWNVwFA9McV4l+xt8vw28QQrwkPjDV0Reyr5q8D86AO2/aF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBqP8AaF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBoAn+Nt74T074UeJb7xzpcupaDDZFr+0ico8se5flBBBHOO9Y3iH4w+Gfh/4c8Jabo3h/UtV1HxDZxLoWh2QDTyRLCG5ZjhVVcZY1D+1X/wAm8eOv+wWf/Q1rktd+Hep+Lrb4aeLPh5440vRvHnhbQIns7W+Hmw3NrNbxrKskaneFPHzAH9aAOn8P/H+ZvF2neC/iJ8OtY8GX2tFk0uW6lSe3unUZKCRcbWx2IryTxZ8WPF1p+1VodxD8JvFM/wBj0TULaOxjeLddruT9+g3Y2jHfnmuq174jeNPDus+HbL9o/wCEumHSm1SKHT/EWk3v2m3t71uI2eMhZI89Bwa1vEP/ACeL4Q/7FbUv/Qo6APcrOeS6tIbmW3e3eWNXaKT70ZIztOO46VNRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXKeNSfNtRnjax/UV1dcp41/wBda/7rfzFdOD/jI58V/CZzVeear8Q9V0r4tWHg6e3g/sa8tlDT4PmR3D58vJ6YYgL9WFeh15/4t+HOo+IdY1fVrW/t4JLnTYobB23boLuKQSRyHA+6GVTxzxX0eF9lzNVdrf0/keFiPacqdPe5Qb4sXMPxG1fRbiGIeH9Js2Zp1UmWSdCBJjnGFZtv1U11Ws/ELw1oKeZqc8sajS5NX3bMj7OhQHv97Mi8Vx8fwf1MWcEMmq2rTto89reTYbMt7LIZXlAx90uSfXmn/wDCAePdSujfaxceHo3h8PzaNDAvmTxu7PEdz7lX5SI8EDkZ4zXVKGFk009Fo/Pz+ZzxniIp3Wr/AK/A2n+JNneQwG3t77S5jfQWzx39n8zrIpK7drYwcdcnGOlWrP4m+H77V49LtrXUmimuTaRX/wBm/wBFeYdUD5znIxnGM965DTvhN4lW4M00+n6fafbbW5XT4Lya4iTyg+91aRQQW3gbeny1veGPCXjXw+bTQFvdK/sKyvJLlZlLm6ljZy4jKFdo+ZuWDHipqU8Mk+V/j/V/QcJ4htcyO/rV8LkjWoAD1D5/75NZVavhf/kNwfR//QTXk1v4cvRnp0f4kfU7uiiivBPZCiiigAooooAKKKKAPEbP9mu3tf2kLv47HX1ayngLx6P5JxFetFHG9wGzjJEYzxnnrXqHjzwlZePfBWueC9RIW31uwnsJGIztEiFdw9xnNb1FAHl/7O/wZl+B/wAPh4Tv9eGt6ncXk99f6j5Rj+0SyOSCQSTwu1eT2p0HwVtp/iH428Wa3qEV7pfjPSItIn0/yirLGoYPl88hgxHAFenUUAeC+G/hh+0R8OdFh8D+CPiV4fvvD9kgttNm1nS3lvLK3UYSPcjqsm0YA3L0Apus/suyan8E/EXw0HjBpNc8VX8Wp6prdzb7vNuFmRziNSMLhNoGeM175RQAijaoX0GK8/8Agp8L7j4TeG9T0G51iPUm1DW73VhIkJjCLOwITBJyRjr3r0GigDm/iP4Tl8d+BNc8HQ3q2b6vZvarOybxGW7lQRn868h8L/CT9qDwj4e0/wAM6R8dPC62WmQLbwB/C+5gijjJMnNfQVFAHhmsfCL46eNfBviXwX8QPi1oWo2euaebSE2ugfZzBIWB3kh8sMDGOK0PGHwM128bwh4o8B+MF0Pxh4Q09NNivHtvNtr238tUkimiJ5VtoI5yp6GvY6KAPDL/AOEXxa+Juo6Snxj8ZaIdB0i9i1AaVolg8Qu54jmNpZJHZtoPO1cVr/FL4ReMvEPxB8PfFD4c+MLLRNa0O1uLGSO+sftMFzBNjIIDKQRtGCDXrlFAENmt0lpCl9KklwsaiV0XarPjkgdhntU1FFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFZ+r6RDq0IR2KOnKOO1aFFVGTg+aO4pRUlZnKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFb/XK3cw+q0uxyn/AAhU3/QQT/v2f8aP+EKm/wCggn/fs/411dFH1yt3D6rS7HKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFH1yt3D6rS7HKf8ACFTf9BBP+/Z/xrT0bw7FpUhuHl82XGAcYArYoqZ4mrNcrZUcPTg7pBRRRWBsFFFFABRRRQAVFcPcRqDbwLK2eQX24H5GpaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNOSfUWdRJp8aqSMsJ84HrjFW6KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACioZry0t/8AX3Mcf+8wFUZvEekxcfaN5/2VJosBqUVz03jC3GRBau3+8QKozeLb9/8AVRRxj86fKwOvorg5tc1Sb7124HoOKjtdUvba4WcXDtgjILZBFPlA9AoooqQCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACori5gtYzLcSqijualrkPFsztfpAWO1EBA9zTSuBqzeK9NjyIxJIfYYFUpvGMh/497NR/vtn+Vc3RV8qA1pvE+rS/dlWMf7Kj+tUZtRv7j/XXcrD0LnFV6KdkAUUUUAFFFFABQOtFA60AelDpS0g6UtZAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcb4r/5Cv/bJf612Vcb4r/5Cv/bJf61UdwMaiiirAKKKVVZztVSSewFACUVch0fU5/8AV2UuD3K4H61eh8KanJ/rDFF/vNn+VF0Bi0V08Pg5BzPeE+yrV2HwxpUX3o3kP+01LmQHF0DrXb3ui6b9ilCWqIVQkEdQRXEDrQncD0odKWkHSlrMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArjfFf8AyFf+2S/1rsq43xX/AMhX/tkv9aqO4FfSNGl1UuVkEaJwWIzzW7D4RsU5lmlkPpnApvg//jzn/wCuv9BW/Q27gZ8Og6TD92zRv9/5v51djhihG2KJUHooxT6KkAooooAKKKKAIbz/AI9Jv+ubfyrzoda9FvP+PSb/AK5t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv8A5Cv/AGyX+tdlXG+K/wDkK/8AbJf61UdwNPwf/wAec3/XX+grfrA8H/8AHnN/11/oK36T3AKKKKQBRRRQAUUUUAQ3n/HpN/1zb+VedDrXot5/x6Tf9c2/lXnQ61cQPSh0paQdKWoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK43xX/yFf+2S/wBa7KuN8V/8hX/tkv8AWqjuBp+D/wDjzm/66/0Fb9YHg/8A485v+uv9BW/Se4BRRRSAKKKKACiiigCG8/49Jv8Arm38q86HWvRbz/j0m/65t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv/AJCv/bJf612Vcb4r/wCQr/2yX+tVHcDT8H/8ec3/AF1/oK36wPB//HnN/wBdf6Ct+k9wCiiikAUUVDc3dtZp5lzMsY7ZPX6UATUViy+K9MQ4QSye4XH86j/4S+w/59p/yH+NOzA2Lw4tJs/882/lXnQ610GqeKBdW7W9pCyBxhmbGce1c+OtXFWA9KHSlpB0pazAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiioLy9t7GEz3MgVR09SfQUAT0Vy9z4vkJItLYAdi55/Kqh8Vaqe8Q+iVXKwOzri/FDq+qttOdqKp+vNNfxNqrqV81Fz3C81mO7yOXdizMcknvTirAdV4P/AOPOb/rr/QVv1geD/wDjzm/66/0Fb9S9wCiiikBXv7tLG1kuX/gHA9TXBXd3PeztPO5Zj09h6Cuq8WFhpqgdDIM/rXH1cUAUUUVQBQOtFS2sTT3MUKjJdwv5mgD0UdKWkpayAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACuH8QX73l+67j5cR2qP5mu4rzzUI2ivZ43HIc/zqo7gV6KKKsAooooA6zweP8AQpj/ANNf6Ct+snwzbNb6WjMMGUl/w7fpWtWb3AKKKKQFXU7IX9lJbHgsMqfQ1wU8EttK0MyFWU4INej1UvtLstQXFzECezDgiqTsB5/RXUyeDoScxXjKPQrmmr4OT+K+P4J/9eq5kBzFdH4Y0h/MGo3CFQv+rB7n1rSs/DWnWrB2VpWHQv0/KtUAAYAwBUuXYBaKKKkAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK5/xFocl03220XMgHzr/e966CimnYDzZ0eNtsiFSOxGKbXok9laXP8Ar7dH9yOar/2FpH/PjH+tVzAcGAScAZNbOjeH57yRZrpCkAOcHgt7V1EOm2Fucw2kan6Zq1ScgEVQoCqAABgAUtFFSAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUV4145vPGfw28Sv8A8InG2oW3jeZbK0jnmythqTD5ZOTnyioJIHdR60Aey0V4pot5490ix1zSvB+pWctl4LPk3UupK8s+qXIjE07bs/uwS5A68+grpPCfxLv/ABPLr08dvFFb2Oj2WoWyEfMJJUlLqx7gGMYp2A9HorzjVPEWp678BR4qlYR3+oaBDev5JKASvGrHb3AyaxtB8X/ELw7NpFh4ludIvYNT0CW9tgmYPIlhiVgskrHDKRnLHGDSA9gorwvw/wDFbxvqGrSaLDrOj61PdaLcajbS2dlIkEU0WCYlkJ2zAhsblP4Vrat8a7qy0hPENnZxT2lj4dl1fUI8c/aMhI4Qf4cvv59ENOwHr1FeA/8AC8fFNlb38S3mn6zP/Z5u4JYbCa3jt5lkRTE+8fMCH4bg8Hiuz8P+IfHL+K7fwp4tu9Omi1zRZdRtZLOFo2tmVo1ZGyfm4k4PHTpRYD0uivJNOtPG2hfFTw/4WTxTHcaRZ+H4xLHLAS8/l/Izls/fLDOfTitz4oeLdY8OzaXZaVruk6QL1pDJcXiGeVto4SKBSGkJ7kdAKQHf0V4x4c+KXjbxtpnhvStE+wWWs6oL57u7uLd/KjjtZjESsJw25yM7SRt71l6B4v8AH2malq3h1prKTxFrPij+zop3LNbQItuZXkCHnGxGwvHJ/GnYD3uivAL3x54w+HviHxjdeKLy0vbyKPTbW0liV1t2MgbEhi5K4AOQM5IqxafG7xKTceH7aSy1fU7uezttNv1s5bW3Ek8yxESK4z8m4NwTkA9KLAe70V5R4afxxa/GWXTPF2qWl6q6BHJDJaI0UbgzSZLRkkBgQRnJyAPpTvin4+8SeHNXaw0bX9H08Q2LXSwy27Xl1dOOiiGM7lX/AG8HrSA9Vorxaw+I/wAR/GCyT+Gm0nToYfDljrjG5haUl5oi5iGCOOOvb0Naum/Fy9tlj1TxNBBBpt14ck1iFkHPmwf69M98g7gPRTTsB6pRXC6v4n8W6D8JD4q1C2tR4gisIZ5odpESzuVDJjrgFiPwrmbfxD8Y7vxPH4STVPD8ct5pX9rrdm1ci3UMqmLZn5yTIvzZHAPFKwHsFFeY/Dn4ma14vv8AQre/treFdR8PR6lOsYJxP5jo20/3fl4FcrffGDxsdO0y/mu7HQ7C5F75uqz6dLcWwlimKRxPs4iBUElmOOKdgPeKK8XvPibfaZfT+IpYbG6n/wCER0+8X7LcmSCS4muJUARgdpTcQQwGSK7fwkfiZDqYj8Xy6Ve2Fxa+cs9mhia3myP3RUklxgn5h6dOaQHY0V5D4m8S+L/DfxV1e9l1iCXQ9P8ADceoiwEJ3N+9lUDdn7xZfvemB2q5pPjD4gaXqXhe68XSaXcab4skEEcVpEySWUjxNJGCxP7wELtJ4wTTsB6lRXI+J/Fl74Z8WaNb3nlDRNThuI5JSvzx3KAMoz6Mu7j/AGTXIaV48+IXi2+0/QdFfTdNury1uNVkubmAyLFarKI4kCAjczbgxORgUgPXaK8s8JfE7xBrHiPRvDWqWtrFdedq1lqZiyVaa0dEDxk9FbcTg+uO1ZsnxM+IGq67beHPD0GmLcXusahp6zXCNshighVw5xyTyeO5wOOtFgPZaK5Pxz4i1jwnpWl6rH5EsS39vBqRKHHkvlSy+nzlfzrhrD4weJNTn1TToLS1S6m1mytNGJUkSWssxjkkbnkoEkY49BRYD2WivBrn4i+KtFspTYPDpmnyeINcgutVlspryK3MV46xq6qcoGAPzEhRjirEGv8AjrxV408MTeHNf0dLi98OXslxcxMbmzIW4hAkRAQGJ4HJGNx9KdgPcaK4zwL4n1TxX8Pm1jV44Y9Qj+2WlwYMhDLBI8TMueQCUJH1rzPRfFvibS9J0a40XTpNX1NPCmrXkELO7PNLHdIFXGfm6+hPGBRYD3+ivALz4i+Mte8F+JU0zxto095YW8E4kjs3trq3LPh45LdzvXthz15GK9p8J/2ofDOmNrV3Hc3rWsbTTRpsV2IzkDtxilYDWooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACsPxJ4TsvE11ot3eXM8TaJqCajCI8Yd1UgK2QePm7YNblFAHCa58K4tU1PVb7TPFWraNBrwX+1bWz8ry7ohAhbLoWRigCkoVJAqG/+D2nyXckuieI9U0W1u9Oi0u9tbPy9lxbxghRllLI2GYblIPJ5r0Gii4HNxeBtOh8AwfD1bu5Nlb6dFpqzEr5pjRAoY8Y3YX0xWf4l+FeheKrS1sdSu7wQ2ulXGkBY2Ub4povLYk4zux0x3rtKKAOD0b4XXGneItL8T3/AI21fUrvSraSzjWaOBI2gfb8hVI1GcqDuHzH1xTtK+D3hTTNL8R6M/2i6tPE25blJnH7uMhv3cZABVQXcjuCa7qigDgH+FDahY3Nl4h8ba5qwmtltImnMKmGMOGyAiAMx2jLMCfeuh/4RCxHiTTPE32mf7Rpeny6dHHxsZHZCWbjOf3Y6HHJreooA57UvB0V94v07xjDq13aXNhA1q8MQjMdzESTtfcpI5OcqQfeqninwE+v6/YeJtN8SX+jajYW8toJbaOGTfDIVLKVlRgOVUggZ4rrKKAPNrD4KWekWNlDo/i3WLS90y7u7iyvx5TzRpcPvlibehEiljn5wT70sHwT0u3spwPE2sNqkuqLrMeqM0Znhugu3co27SCuQVIIwSMYr0iincDzmP4L6dM2s3eteJtX1PUNb+zvLeSmNHhkh/1bxBFCoR0wBj25NWbj4Tw6rp93B4h8Waxqd7cNBJBfu0cUlo8L743iWNVRWDAEnbzjnIrvaKVwOM8OfDh9G8UzeMtU8WaprWqT2a2LPdLEiCNWLAKkaKq8seg569aj174YprHiG+16z8U6ppn9rWkdlqEFsIitxEm7ADMheM4ZhlCp5rt6KAOH8IfCrTfCNlNaRazf3hm0mDRy84jBEMKFEI2qBu2nGfauf8UfDf8AtF/Bvgmz028l07RLtbq41GRlCmBQ2+FsY3eYG2kYxgk9q9YoouBleJ/D1r4q0G78P3k0sMF2qqzxY3ABg3GQR2qtb+D7G28SxeJ0uZzcQ6YdKEZxsMZdG3dM7sxjvjk1vUUAedW/wZtNLg0ZNA8Wavpk+kWTad9oiELNcW5YttcMhAOScMoBGetGn/B86BYWdn4Y8c65pr2scsLyfuZ/PSR953pKjLuB6MAD716LRQB55ZfBDwjZae2lJLePato0OjGNnH3I5XlWXIGQ+9yfTgcVr+GPAb6DqY1jU/E+p65dxWpsrd7wRqIISQSFWNVBJ2rljknHWusooA5TXvh7p+v+JD4huNRuoxNpp0q7tFCGK5t9zsA25SwIMjcqQaz9D+FUWl6npV7qXirVtYt9B3f2VaXfleXakqUDZRAzkKxUFyxANd3RQBwHxh0C/wDF+jWXhWw0q4ma8u45GvEYKlmqn5mY5zkqWUAepq/rXw5t7y70zVNA12+0HUdKtDYRXNmsbl7chcxukisjDKqRkZBHFdhRQB55/wAKc0+0tNK/sTxJqunanpc9zc/2mhjknuJLhg07SiRWVtzDPTjtirHhj4R6P4Y1Cy1ODWNSu57K7u70NcOrGSS4jCPuwo44yMfy4ru6KLgZfifw/Z+KtAvvD2oPIlvfRGJ2jIDLzkMM9wQCPpXM6V8IfDuk614c1uC7vGm8N2ktrAjsu2YuCDJIMcuNzYIx9413VFAHBn4VfY2a48PeMNY0m6a+vr1pYhFIr/apmldGjkRkIDMdpIyB3qfwl8K9E8IajZ6rY3t3LPa2dzaMZSuJTPKkjyMABhtyDgYGCeK7WigDC8N+EbHwzoM3h+0uZ5YZp7qcvJjcDPK8jDgAYBcgewFc6vwc0NbW1tU1fVI/sel3WlRSRSiOQJPKspkDKAQ6soxjj1Brv6KAPPf+FQwX76jdeJvFWp6xe39gNNW5ljgiaGAMWG0RIqk7jnLA10Wg+F73RZ7SWfxVqmoJa2IsvJn8tY5CGBEpVFA3gDbxgYzxmugooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Z\"}");
+       socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"788346414\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAHgAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAxst6mky3940UmRQA7J9TRk+ppKKAAFh/EaCWP8RoooAXJ9TRk+ppKKADL/AN40Zf8AvGkwKMCgA3N/eNG5v7xoI70Ad6AF3Mf4iPxpMsP4j+dHSlxmgBCWH8R/OlBb+8fzpCM96OgoAVWb+8fzoLN6n86OBTSc0AOy3940Zb+8aTg0YFACkv8A3jRlv7xpNvvSYx3oAXLn+I0Ev/eNAwOc0YBNABubHU/nSbm/vH86VaMc5oATc394/nTt3+0aTb70baAAl8/eP50uW/vGk20baADc3940B27k0babQA/Lf3jRlv7xpMAdTRgHpQA4lj/EaTLf3jTSMUvFAC5b+8aMt/eNJjHNGAelAC5b+8aMt/eNJxQSD3oAXLf3jRlv7xpNvvRjHegBSW/vGgFv7xpuB60YHrQA7Lf3jRlv7xpAQO9IMDvQA7Lf3jRlv7xpuM96XbQAuW/vGjLf3jSfLR8tAC5b+8aAW/vGkAFJgetADst/eNGW/vGk2+9IAPWgB5LH+I0mW/vGmkYpeKAFy3940Zb+8aTGOaMA9KAFy3940Zb+8aTigkHvQAuW/vGjLf3jSbfejGO9ACkt/eNALf3jTcD1owPWgB2Wz940mWzjcaP4aMDFAC5b+8aPm/vGmjA70pGaAF+b+8aPm/vGm4HrRgetADst/eNALf3jTcD1pQPegBQX/vGjLf3jSAClOO9AAS2fvGgFs/eNJgYzRgYzQApLf3jQS3940hA9aQjHegB2W/vGjLf3jTcD1owPWgB1FFFABRRRQAUUUUAGc0UnzUtACZFGRRk+lGT6UALSZx2paMigApMr1paTJ9KAA57UDPelpPmoAMCjAoyKMigA+Wj5aMn0oyfSgBcYowKKKAEwKCAO1Lz6UhJoAMg0ZApfwo/CgBMCjApAT0FOoATAowKWigBMCjgUZPpRk+lAAeDmlpDycUtACfNSgGkzntRz9KADn1oOR3oyBQSD3oAXoKRTnqaXp1ozmgAooooAKKT5qPmoAXPGaQEmlBzScCgAwfWjB9aMijIoADntQM96BnvQc9qAFpP4qXPOKOM0AITilHNHtRQAnzUoBpM57Uc/SgA59aDkd6MgUEg96AF6CkU56ml6daM5oAKKKKACik+aj5qADqOKFpQc0ZFACEZpQMUgPrS0AIc9qBnvQc9qBnvQAtJto3UtACE54FKOKAc0ZxQAUUm72o3e1AAc9qTBFKCaDntQAtFITigHNACg5oJxQBigjNAATQPeikyKAFoozijOaACg57UUUAJgUv4Ugz3paAEwKMCl59KOfSgAooooATgUGl59KKADgUmRS0mT6UALRwKKTJ9KADg0vAoOR2pMn0oACfalyKTJ9KUnFABRjFFGRQAcdBzQeODwPSuN+IXxa8EfDLTW1DxZq8NrGASFJyx/Ac189aj/AMFFvhRaXTQ2K/aVU4D/ADCgD635AyRj3oPTI596+evh3+218HvHl8mlx6wlrdyHARwcZ+pr362vrW8tFvbW4SSJ13KynIIoAm45/hPal6ngY968W179q34XeHfGjeBtQ1YDUldY/L2Hqa9lgnS6hSdPuSorg+xGRQA89KWkPSloAQDFLRRQAc+lI1LnFITxQANSjgUUUAIc8sB16ilHp+leZ/Fz4++Bfgy1jH4xvxbPqG7yvlJzt+ldN8PfH+hfErw1beKfDtx59ldZ8t8Yzg4oA6XHt06UuOpHJpMkHPf0rB8ceM9H8A+HLnxNrkvlWVqAZG69aAN4nC5xk96Mc4HXtXlvwq/aJ8AfF2+ubHwjqQmltSBINpHX616iB6jPvQA7A60UUhGaAA57UtIc9qWgABzSYpQMUZoAQ9KWkPSloAQDFLRRQAc+lI1LnFITxQANSjiiigAooooAMYoPNIc9qWgAHSiijPOKACiiigBPmo+ag57Upz2oATrQDmjpQBigBaKKKAEBz2paQYzSk4oAQnFKeKQjNKeaAEY0ueM0YzQelAAcnvR+NJxnOaOtAC4zRSEZpaACiiigAooooAKKBzRQAUUUUAH40UHHeigAopMCjAoAX8aPxpDjvScetADufWjn1pDjvQMdqADB9aMH1oyKMigA59aXn1pDjvSkjvQAgyc4rmfiR40sfh94O1LxPqMgWO0iJBJ744/Wum6jBr5Z/wCChOuXen/Bi6s7aRkW5xvIPo1AHxBdah8R/wBrj4vnTo7u4aGe4ZQoY7Y0B/LpX2j4Q/4J4/CnTdESDX4ft96yAyS5K849K8x/4Ji+FbSX/hIvEVzErzr5XkuRyvXNfoGAoHAz70Afl5+1T+x3cfBy2HjPwJPM2nodzxxAgxc8c9TXuP7Anxn1/wAV+Hb3wT4j8+STTY8RSSggnIPrX2B4h8N6N4p02TStbtEubab78bDg1z/hv4V+BfBBnu/DWix2csincy9+KAPzF+M8m39rm4ZjhRdxdT06V+j+rfHf4X+BrCytPEfii3t5fs8YKr838I9K/MH9qI6j/wANIawulyFblp40jYDueK+h/CP7Auv+OvDCeJfG3jO4i1O6hDrEyltvHHf0oA+1vBnxW8B+P4xJ4X1+C6B7AgH8utdaCBkk5B6e1fjday+NP2avjpHoEGq3BksrqOMqWO11cgdM46Gv0i/aI+Nn/Cqvg5/wk0L41G6tIzAueSWUZP60Ad74w+MHw+8CKW8SeIbe229VDBiPwFZ3hX4+/CzxpdCz8PeKLeeU8YY7f51+dfwU+A/j39qnVr3xb4p8Tz2tk8m4SsxIcEnjGe1bHx6/ZA8SfAnSR448G+LJruCyIeXYSu054780Afp7G6yrvQh1PRgeKz9a8RaN4dtGvdX1CG0iQZLO4Gfzr5i/Yq/aIu/iN4GvNK8S3e/UdDiy0jHlxg/0FfLPx9+LPjv9oP4vt4A8MXs6WInMMMUTEdPvHIoA+/JP2pfgjHfGwk8YwifO3btOM/WvQ/D/AIr0DxVbC70LU4LqMjIKOCfyr4bsv+CbUkvhcXdx41mGpvF5m3YchsZxnNeS/BzWfiz+zv8AGhPC98uoXGmpOIrhSrMrg8Kc84oA9N/4KiFheeEDuwczYHr0r6E/YZOf2fNAYntJwe3zV86f8FN5zcDwRc7cNMkjn2yAa2vhh+0h4f8Agn+y3owa5SbWJ45RBbhuc7utAH2jrXxE8HeHbr7Hq+u20Ew6ozjNeTftU69pev8A7P2v32lXAmhZF+ZenWvjP4OfCf4lftUeO38ceKdQu4tIabe77yFK54AANfX/AO0f4N0rwN+zTq+g6OhjhtokVcsWJOeetAHy/wD8E6NWsNI8R+Ib7VLyO2hjZSWdgB39a+1Zf2nfgvFqR0mTxhCtyG2425GfrX5UfAnwN45+JnimTwV4TvJrW3vpMXUkf8IBr6s8Xf8ABOO7sfC8moaL4xmn1OCIyFdpBY4yec0Afd+i6/pHiG0F9o2oRXMDAEFGBq+8ixxtJKQEHcnFflj+yj8a/GPwg+K8fw78V3s7afLMYZklYnZjgdfU19O/tw/tEX/w08I2eieGLny9Q1qM7ZF6oMD+hoA9y8VfHn4XeDJWt9f8UW8MgOMKd38qd4T+Ovwy8bTeToHii3mc8BWIX+dfnz8AP2Q/F3x7sH8b+L/EN1Z2t0S0buWbfzzxnioPj/8AsneMv2ereLxr4T8Q3V5ZW7b5HQlAnPHGeaAP1Gmu7eC3N08gMaruLA8Yrmbb4p+BLu8TT4fEVs1ySVEe8A5/Ovn/APYt+Ok/xm8DXXhjxROZb+yjEUjE8sDx/KvmD9rj4NeKfgj8QI/G/hm5vP7Knm85cSMRHg5PfuaAP1Ha4hihNzJIqx7dxYngCuWh+KngWe7+wQ+ILd7gttCKwJJ/CvhLxj+3edT+BltoemztH4luofJlYdUxx+oq5+wr+z7rXiPUh8VfGU939n3mSzSSRsSEk7uM0AfoYjh1DL0IBFOPtTY0WNAi9FAApSaAFOT3opOBRkGgBaKKKAAmijIoJxQAmD60YPrRkUZFAC/jQOKQAUvAoATPGaM8ZoPSg9KAFooooAAc0E4oooAKKM5ooATgUvBpODS8CgAopMijIoAWiiigBM84paT+KjHOaAAUZ5xS0e9ABRRRQAUUUUAJ81HzUmB60YHrQAvzUfNSYHrRgetAClvSgZ70tJkUALRSA5oJxQAtI1GRQT2oAGoPShqD0oAAcmj+KgHn60fxUAB7YoPSgnmgnigAB4znrXzR+3v4TvPEHwS1C7so2lktApCqOeWr6XyNpBXkdKy/Evh+x8T6Jd6HqcKvFdxlCp5HSgD8+f8Agml4/sNH1vXPB+ozrHPfmNbdWOOVzmv0ZJ6gYr8mPjT8BviX+zf8Qz4u8JJcPYrMZra5hUngnJGB+VereEv+Ckuv6XoyWPiXwXNdXUKBfOZypcgemKAPsT9oX4sR/B/4dX3ilNjXUQHkxFgCxzivF/2Vf2pfFvx31nVLLUtNMNvZr97dkcg18ffFn41fFX9qjxFbaLY6Rdw2Uj7YrRVJVQfVsV94/slfs/j4LeAWfU4h/a9/FuuOORxxQB8MfGlFk/a4uFcAj7ZD1/Cv1f0dVTRbJEGB9mj/APQBX5UfGWyvH/a2uHWznK/a4fmEZI7d6/VjSfl0ezXv9mj/APQRQB+U/wC2QAP2oJ8Lg/bLbt7ivb/+Ch73q/DXwWsDN5JtB5uOn3VxXi/7Y1jezftNzSRWk7j7ZbfMsZI6r3r7e/aK+DUvxf8AgfDpmnW+7Uraxia3Hf7oJ/lQB8Ufs/eG/wBrDUvBFvP8JrnGjHOxVKZHPfPNdx4q+FX7c3ifRbjR/EtwJrCcASI7RqPzrhPgN+0J45/Zf1a78I+JvDt1PYb9vlEECPB7HHeu5+Nf7dPib4laGfCXgHw7dWcl5hWmj3E5zx2oA2P2afgB8T/hND4l1XxBbRwQ3FuxPlzq+fkPYGvKP2NTbP8AtMRPqpUuLqfbv79fWvrb9jb4Z+OrHwXfav8AEW5nkl1iLEcMpJ2jBH9a+Tfjp8LfHv7OvxjPjnw7p85sTOZraWJSQRnLdKAP1aBVUBBBGPyFeb618Qvg1ZeJX0jVbywGrKyqytEpbcenNfJw/wCCj8p8IG1bwfMdX8nys7jndjGeleU/s7/Df4h/H74zr478QxXkVgJ/NuJX3KMdVGKAPRf+CoLo7eDnh6MsxXHcYGK+VdI+C/xL8TfD6bx3a2E8mjaeAyHdkcnnAr6u/wCCmunXIbwXb20EswgWRPkQnoAO1e7/ALHPh201j9mrS9I1awGy5ilR1dMHknrQB5F/wT7+PdlNp/8Awq7W3igurc7bbIAL85NfQX7YRB+A2vt1BROfxr8+/jp8MvFP7N/xmXXvDkVwtq9x5ttJEhIIzlhxX1r45+KsPxf/AGTdR1eCGUXawRrPEUO4EHFAHj//AATVOlDxTr3nbBdl18vPXvnFfovPsMTBz8m07s9xivxa+Cni/wCIPwo8Uv448PaReTW9lJm4QIwyCcelfVnij/go7cXPhOW10nwjPDqs8Xl5ycqcYJxigDwj9oQRp+1tfroG3aLy32lOnUZrr/29/tv/AAmvhtrzd5XlQ7M9Purmsz9lb4Q+MfjV8YY/Hviayn/s+GYzXM0qkbs8r1r6l/bh/Z51H4leDrPXPDFoZNQ0OM7Y1HMg4/kBQB67+zCbD/hTegiwKeWYjkrjk1T/AGsf7OPwR13+0dhTyxgN65r4c/Z7/a68W/AnTj4H8WeHrq7sbViibwy+Wc89uai/aF/ay8X/AB/ij8FeEvD11a2M7BHCBm83J47cUAdD/wAE2zc/8J9riwqRbiQbj2xzivq79sXxF4E0j4TalB4yjhla4TFvG33ic9u9cN+x78G5PgT8Or3xj4xgMF5eRCaRCMlcAkfnXyf8cPHnjX9qD4vw+G9Ftbs6atx5dvFsYKFzhjQB876W9tZ67bajfWbyaeLoOFIIBTf6/Sv2n+Avinwh4q+G+kah4N8lbIQhfKTqpAAOfxr5/wDHv7E3h6b4EW+haPZr/bunW5lS4A+ZifmIr54/ZA+NXiT4JfECTwL4qhu00u4n8ly0bERkHA7dzQB+poPpSDOeaitLqG+tormE/LKgcfQjNS8YwaADvR3o70d6AAnFHQUEZpaAAc80HnikJxS4xQAU3JpQc0EZoAU8UgNKeaQCgA3UZFHy0fLQAZFGRR8tHy0AGQaMgUny0fLQAuRRn3pDtxSjaaADvR3o70d6ADn1oOR3oIHrRgHpQAEkDrQARzRjPNAAoACcUtITiloAQDvS0UUAJ3o70d6O9AC0UUGgBAeaCOc0mBig4xQDVg5zkUvPrQMdKBjPFJK/UNWHA4ozQwxQRTt5hdCE0ueKRqXHFHzC6BaAecYoFIetO6DQUkjvQcgdaQ896OtK6DQXIHSjIoA4pB1o1DUM0ZoHWlajUNRaMUh6YoWlqBS1PRdM1u2az1OyhuYmGCsiA/zrzLUv2V/gjrF013f+DIWlY5JDkD9K9ZODgmg4DYLZFPUZyHhL4SfD7wOAPDvh62tj2YoGI/E115A/1eARjFKeRjHSj5cZzk+lOzE7HJXnwr8DX+sHXbzQoJL1iG84gZyK6tFVVWNQAEGAPQU8YIwBSdyCdxPalqLc5TWvhd4I17UjrOraFBPeFg3mMBnI6V1EUccUaQxgeWi7QuO1PHXnj0o3EfL2FFhnGeLPg98PPG0vmeI/DltPJ/eChT+grO8O/AL4U+FrsX2jeFreKVTkFhu/nXooOBn0oJzyOlFgGRxRQxpHDGqIvACjAFZ+u+GtE8SWr2OuWEN1E4xh0BxWkOufXtRz0zg0rtB7zPK3/Zf+Cz3f2tvBkPmk7t27jP0r0LQvDei+GrNNP0TT4bWKMYARAP1rSIz06+tKD2LZI600nILM57xR4C8LeMjC3iPS4rtrfOwuM4zWlouh6X4c0+PStHtVt7SP7iKOBV7nlg2c0vG0ZGCKAOf8TeBPCvjARf8ACQaPDeeVnbvUZGarWXw08GaZpU+i2mjRR2VxjzIh0P4V1HGN4GCetB9hgetAHJad8KvAWmW1xaWfh21SO5H71SgOf0rmpv2Zvgxc3IvJfCEAk3bg2e/0r1IEgbs5IoXptxwaAMzQfDWheGLJdP0OwhtYIhgBEAzWk6LIrCVAynjBGQaXg8E5pM8H+8egoA4PxN8C/hj4vuTda74Xt5ZW5LKoX+VL4X+B/wAMfB9yLnQfC9vFKvIZlDY/MV3mcDBPTtS8AdeT2p2sw0XQpalpOn6rZNpl9CJLZxhkAwMVzuifCjwJ4d1AappPh+3guUJKOFBPPWuu7YB4WjqcjqaSTb0YIR1V1MTKCMYxXGXvwe+Heo6k2qXXh23a5dg28KAcjvXaZ6ZOcUDjhjgetF0O5Fb20NpAlvAMKgCgegFSnOQc0ZA+ajjv3ouguLRSAYpScUm3HYQhOKWjIFFHqAhGaWk3Uuad0AUUgOaDzTugFHFNPWnZpo60XQCnpQOlLRRdAIM96Dk0ZFGRS0QW1uGD60YPrRkUZFK6YO973A9KAMUtJupgAOaCcUZA4oPSgBc0mD60ZAoyKADJ9KMn0oyKMigANABpcZpuB60ALg+tGD60ZFGRQAvPrRRkUUAJg+tGD60YFGBQAHPagjNGBQCACc9KG1b3gbSXvBjvnHvRjuR+NeX/ABB/aE8BfD+8bTr7UUe7X70YycfiK4cftneAedqcDqea46mMoU92cVTMMNSdnI+iM/7X6UZ/2v0r54/4bO8Bdk/nR/w2d4B/ufzqf7QofzELNMK/tH0OcHvQMDvXzv8A8NneAv7n86P+GzvAX9z+dH9oYf8AmD+1ML/MfRHPrRz6187/APDZ3gP+5/Oj/hs7wH/c/nR/aFD+YP7Uwv8AMfRB3HvSYP8AEa+eD+2d4C7J/Omy/tpeAIo8uvT61rTxtGbsmH9qYX+Y+icn1oHHevmj/huj4c/3P50f8Nz/AA6/ufzr2FleKkrqI/7Uw38x9L4PpQQT2r5nP7c/w4/55/zo/wCG6Phx3jP60/7KxX8of2phv5j6ZNA4/wD118zf8N0fDf8A55n9aP8Ahuj4b/8APM/rR/ZWK/lF/aWH/mPpgcUdTXzP/wAN0fDf/nmf1o/4bn+HHaM/rT/srFfyh/aeG/mPpnnOB0pAMn6V8zn9uf4c8/uzx9asWf7bnw6vrlLaKP5n+tH9lYr+Ul5thIK8pH0jn/apPoa8NH7WHgkgEJ/Oj/hq/wAFdk/nT/snFP7Bxf6yZbF2lUPc8H1owfWvDP8Ahq/wX/d/nR/w1f4L/u/zo/sjF/yh/rLlv/Pw9zz/ALX6UZ9/0rwz/hrDwV/c/nR/w1f4K/ufzpf2Ti/5Q/1ly3/n4e5k+9A69a8M/wCGsPBX9z+dH/DV/gofwfzprKMU/sjXEuXJfGe5kkKTnk0AgMQK8MP7V/gv72z+dXNL/ae8Iatex2FvH+8f61FTLMVBX5S6Gf4CtKymez5HrRn/AGv0rhB8VtNIBFu2D9aX/haunf8APu361wNW0Z7SaaujusH1owfWuE/4Wrp3/Pu360f8LV07/n3b9aBnd5/2v0oz/tfpXCf8LV07/n3b9aP+Fq6d/wA+7frQB3fXvSH1zXC/8LV07/n3b9aP+Fq6d/z7t+tAHdHjk85pQDjrxXC/8LV0wHi3bH41R1T41aJpVv8AabqEhR9axxFanhoe2qOyNKVGVeXLBHpBDY4NAz3NePf8NI+Ev7v86P8AhpHwlj7v868KXFWWN6VLHpvIsdNXjA9hyfSjn0rx7/hpHwn/AHf50n/DSXhP+7/Oj/WrK/8An4P+wcd/IexfhRz6V49/w0n4U/u/zo/4aT8J+n86P9asr/5+B/YWO/kPYefSgZHavHv+GkvCfp/Oj/hpLwp6fzprivLLXdQP7Ax/8h7CB37mgnHJPSvHz+0l4U/u9PrWN4j/AGtvBHhu3FxdD5W+taUeJMtxMuWNQqPD+Pe0D3nPtRn2/WvmA/t2/Dj+7/Ok/wCG7fhx/cP616Sx9D+Y2/1WzP8A59M+oM+360Z9v1r5f/4bu+HH9w/rR/w3d8OP7h/Wj6/Q/mD/AFWzT/n0z6gz7frRn2/Wvl//AIbu+HB/gP60f8N3fDj+4f1p/XqH8wf6rZp/z6Z9QZpMj0r5g/4bt+HH9w/rSj9u34b/ANw/rS+v0P5h/wCq2af8+mfTxzwc8GlIGeelfMcP7dfw2aRUYbVY8nnivZfhx8X/AAZ8TrP7V4Z1RLgoBuToR+dXTxdGo7JnLisix+DhzVoWR3GBRnFIDmlxmult9DyNluIRmgDFAIoJxQAfNR81Hy0fLQApz2oGe9J8tHy0AAOe1BOO1G6jdQAZ5xR/FS55xSfxUALRz6UnzUc+tAC0UUUAFFFFACcHLetY3i6+k07w7f3kJ+ZIWI/I1skk4OOBXPeP9p8I6mB3gb+RqKvwMyry9x+h+ZHiPUr7Xtcu9Sv5WkkkmcfMc9CapfZMZIA5q3sBvLknn9/Jj/vo1YWMAZ71+e1qj52flderLnkjM+yn+7R9kI6CtTyh6UeUPSsXOSdkY+0ktDK+yn+7S/ZT6VqeUPSjyhjOKfPJD9pK9rmX9k9qPsp9K1PKHpR5Q9KFO60H7WWzMo2xGeKo6pBttJDjtXRGNcEkVl62irYyYHQV6GVyviYJ9wjVu7HExQbh071L9m5HFS2Y+Q8d6thAa/pXDQjKlGy6IcqjUtWUPsgoFoK0PKo8ut/ZQWpPtJt6Gf8AZBR9kHpWh5dHl0eyXQftGt2Z5tKPsnPStDy6PLpuiJVuhnG268Ve0CDbq0P1pzR8ZqxoqY1WH60vZR7GGJqP2TPSoLYsi8dqn+xHd0q1Yx5iX6VdEIHOKtU0fn9WvJS3Mj7H7Uv2L2rW8kelHkj0o9mk0kiPrGm5k/YvQUfYj6VriIelHlewodKKshfWJRd4syPsXtR9i9q1vJHpR5I9KPZK6G8RJK9zHNlz0rc8CwbPFFnx/FUTQgdK0vByAeKLPA/jrz8ypWw0rHs5BXlLHU031Pqe00xXhjIQcqO3tVj+x1I/1Y/KtbS4Fa2iyP4F/lWkLZcdK/LJ/Ez+jKXwL0OY/sdP+eQ/Kj+x0/55D8q6j7MPQUfZl9BUlnL/ANjp/wA8h+VH9jp/zyH5V1H2ZfQUfZh6CgDl/wCx0/55D8qP7HT/AJ5D8q6j7MPQUfZh6CgDljpA/wCeYx9K4H4tWSw+HnOwD8PevZZLdQM4ry741RKvhtyPUfzr5/idtZbUPWyTXGwR4BHaZHAqUWRx0rQtIlKjirawJ6V/M9SvKMnqftlOlHkWhifYj6UGxz2rb8lPSgQIe1R9Zk1uV7GOljE+xcfdo+w/7Nbfkp6UeSnpU/Wp7Jh7GLaujE+xe1H2I+lbnkL6UeQvpVfWJ8urGqMXJJGEbJvSvNfjNBs0mH8a9kkhXB4ryj42pjSocep/nXs5DXlLGQVzowlH98rnhywbuop32fBq1EgqXy81+tqbaPs44eLitCh9nGelH2cDtWh5Q9KPL9qfPbqX9VjfYz/s6+lH2celaHlD+7R5Y9KTqX0QfVoJNWM/7MB2pfs6+lX/ACx/dpPLX0oc7ISw0exnmADOT16V7T+yX4n1TQ/i3pem21w/kXjMJV3ccDjivJWjHOF6da9F/ZsG340+Hzjje+fyrrwVR+1jqeDxHhYf2fUuuh+qcZLRo3qoNPOccVFBnyY/9wfyqT5q+6Xwn8vTilKXqLQBimg4pQc1RAfxUNRjnNH8VABkUuRRz6Uc+lACZFGRRk+lHPpQAHpQOlLRQAUUUUAFFFFABRRRQAh6Vzvj/wD5FLUv+uDfyNdEOc1zvxA/5FLUv+uDfyNZVvgZlX/hy9D8yf8Al5uP+u8n/oRqyOlVx/x9XH/XeT/0I1aAr86qfGz8nr/HL1A9RluvQ0AHgbcepz1o7EYz6V6P8Lvgn4i+JQlns42htYlyZcZyadKlOtLlp7ioYedafLT1Z5zgnIxmun+H/wAP9b+IGsw6VpVs5RmHmNjhRW7pfwR8YXvjM+E202RXjkAd8cBfX8q+0/AHw/8ACvwd8K+dKYklSPdNO3UnFelg8ulXqc1TRI9fL8oliJ81TRLc+Zvi5+zTN4H8PRa3pdyJljXNxkYwa8AKkEgr0OGr3r9oH493XjO7m0DRJCumxkqWH8deDHnG7qefrXPmHsvaWo7HLmaoKrbDidjWRrufsMn0rXI4NZGu/wDHjJ9KrKrPFwXmcCepylkBtH1NXF6VTs/uD8auL0r+n8M7YeK8kKau7sXoQSeOwpOnbHtS4J4ByB0969Z+Fn7PHiz4oabcarZwNDbwrujcj79KtXpYWHNVZpRoVMTK1M8mABbIruvhP8J/EHxR12HTdMt38ncPOl28KK2vB37PvjXxH43bwpNp0sIgkAmkI4Uf/qr738G+DPBfwI8Fb5DDB5EW6aZgMsa8TM86jh48lDWTPZy7KJV3zV9Io+Pfj/8Asv3Pwy0iLxDpFwJrJFHng8bD/XmvnVjghvzFe9ftG/tD6h8TtSl0XSnMejwsVAB4krwckkliceld+VLEOhzYndnDmUqCq8uH2Qh6VNo//ITi+tQnpU+jf8hOH616LR5GJ/gs9Z08fukq+DVHT/8AUp9KvDvVxR+d1viF6YK9uvtR1BP3g3X2o5+WvRPBnwW8ReMdGn1eCMxxqu6IY+/61jiMRTw0eao7GuDwNbHVHToRuzzvAHAGAK7T4cfDXVfH2pi3ghZLYEeZLjgCr3gr4P8AiDxJ4jOk3No8Mdu+JnI7V9X6bp/hn4VeGOTFBHAmST1Jrw81zqNL93h9ZM+u4f4XliZe3xq5acT5l+LnwYn8CRrqFk4ltSBu7bT/AFrycAZGOlel/F74sX3j3UmtYnKWEbEKg715qMngngdK9PK/b/V08R8R4GffVPrklgvgQ1hxitHweP8Aip7PP9+s9q0fB/Piiz/36WaL/ZpJmnDzisfTXW59jaT/AMe0X+4v8q1FrL0n/j2i/wBxf5VqDpX5RP4mf0lS+Begc8DGcdqD97nr29qGIXndhQCSfSvCfH37W3gHwJ42tPB9xcpIXcpPID/qj2qSz3bv0+prxn9oL9ozw58F9Ckle4S41KUfuIA3JP8ASs745ftQ+Efh14OXUtNv4ry+vYibaNG7kf8A16/O1U+IX7SHxCABnu7i8l99qLn8hxQB+hP7MX7TVl8arB7G/iaDU7cncuCQwJ45+lfQGSTnHTrXjf7O37PehfBfw6kcSh9WuEU3E2OSa9lOeMNn8KAGS/dryz43f8i5J+H869Tl+7Xlnxt/5FyT6j+dfP8AE7Tyyoevkf8AvsDxW05UfSrvQVStBhV+lXB0r+X63xP1P3Ck7U0GB/hS8HqKFUllK9+MV3WmfCTxBqXh+TWkjYEDdHFjlh3roweW4jMZNYdXsYYnG0MIlKu7XOF2jcAOQf1rufh18M7/AMY3PmyQtHZp1cjrVj4d/C3VPE2pbr+FobWFvm3DrX0BqGpeH/h1oG3EcSxJgKOpNfa8OcJqSeLzBWprv1Pl864hcGsNgdZv8D5x+I3w9n8GXow++3f7prjM/wAIHPb2rqPHnji/8Y6m9zKSLdSfLT0Fct83ds4r5LOvqzxklgvgPosqWIeGj9Z+LqNkHymvJfjeP+JVDj3/AJ1605yDXk3xw50mH6n+dbcO6Y6ET2cIl7eJ4zF0qeoYugqev2B7H29NqMUg4+8TgUnQYIx71JDDLcTLDCpZ2IUL6k17Rbfss+ObnwL/AMJgsDl9m8W+3kitaeHlVWhw47M8Nl0l9Zmo32PFApY/J1PQV7v+z9+zVrHxRn/tPVIXt9LQH5mX73HH61e/Z5/Zm1jx7rSal4jspLfTLaTL7xjfg/8A1q+zvHPjnwT8B/BXlRiCHyItsUS4yxxXr4HLo8nta2x8FxPxhUVRYDKveqy6rofn18dPgxqHwi8QiwkkElpOT5MnHzAdeO1eY46DORXc/Fn4qa38U/EUmrapKRAHPkx/3BXC8keory8T7P2j9nsfdZOsX9Sh9dd6ltRH6V6H+zf/AMlq0D/ff+VeeP0r0P8AZv8A+S16B/vP/KrwS/fROTiPXL6nofqfbHMMf+4KkJxUdt/qI/8AdH8qlr71fCfyrW+J+oUgHNLRVGYhGe9KRmiigApPmpaT5qAFPtSDPelPtSDPegBaMiijAoATrR0paKACiiigAooooAMVzvxAA/4RLUuf+WDfyNdDj3rnviAMeEtSP/TBv5Gsq2kGY19KTPzJH/H1cf8AXeT/ANCNWR1FV1/4+rn/AK7yf+hGrA7V+d1fjZ+UV9arFP6Gvoz9mr462/hMxeEteVFs3O2ObAG3/GvnPHG3OPUUqM8Tb0Yqy8hga0wteeHmpwNcJip4OoqkT9RZ9S8M2FhJ4ldrdUZN5m4yRj1r4z+Pvx+v/Gd5LoegzNHpsRKsVb79eb3XxX8Y3vhmPwrPqsn2KIYxnkj61xuSSWLEnv716eLzV4iHJT0PYx+ePE0/Z0lbuKxLEsxJJ9aKQUA5rxNz53fcQ96ydd/48ZPpWse9ZOu/8eMn0r0Mq/3uHqVHc5Sz+4KuLVSy+4PqatrX9PYbWhG/ZClJ82gobDAgYAORX1v+yl+0ZZ6EsPgfxN5cUBIWGUgDFfJBA4+bOf0p0c8tvIkkLFGQ5BBwc1jj8DTzCl7OR04HFTwVX2iP1y1bW/CfhnTJvFVw9rFGyeY0wAy3HFfAn7Rf7ReqfEnVZdH0e4eLSIWKqqnHmV57rfxi8ca94atvCmoavLJZW4KhemR9a4jcxADHNeNluQrCy9rX1fQ9bMM6liV7Ojog5PJPWgZ70c0gz3r6bW10fP2V7MG61Po//ITh+tQN1qfR/wDkJw/WkzDE/wAFnrdgcxJ9BV/PSqFgMRJ9BV/0qon53W+MVSVYSDqpyPevpH4BfGWyihh8K63sjI+WKQ8Bq+bQOPX29KfBPJbyLNBKUKHII4INcOY4Cnj6bpz3PTyXN6mT4lVobdT9BNa1vw74Z06bW53hhTbuLgD5vSvkL4s/FvUfHWoyW9vM0dhGxEag9a5zXPiJ4m1/TLfS9Qv3eCEFQOma5gf7TV5eVZEsHP2lfWXQ+gz/AIulmUVRw/ux6hnNFFFfSHw71dxDWh4OP/FT2n+/Wea0PB3/ACM9p/v15uaf7tI93h7/AJGEPU+x9J/49ov9xf5VqLWXpP8Ax7Rf7i/yrUWvyifxM/pKl8C9Bs8azRPC4yJFKt9CK/PX9sD9ljV9H1G5+IPhKOW4tpG8yaMEkx/jX6Gjhsk9aq6lplpq1nLY6hAs8MylWVhkEVJZ+MngnwV43+KviK28MWSXVy6vsO8kiMd+tfp9+z1+zz4d+Dfh6FRbRy6pMgM9wV5zXVeBfgt4E+Ht/d6j4d0aK3mum3O/U59s13g65z06UAAAPNAwe1LSAY70ANk/1Zryv42f8i3J+H869Uk/1Zryv42f8i3J+H86+f4n/wCRZUPXyP8A32B4ta/dH0q6eoqla/dH0q6eor+Xq/xM/caX8NDomMUqP3Qgivo74RfFCx1i0j0PVCkdyg2qT0YV83H72c/Sp7O9ubC4jubSUxOhySK9zh7PquR1lUirxe6PJznKaebUeSWjWzPsbxJ4k0Pwfpcl6/lJxkBcDJr5b8deOdQ8X6k8s07CIE+XGOgFUfEPi7WfEpRdSu2kSMYVegFYoO3kHr39K9niXi6ebr2ND3YHnZJw5HLf3tb3ph0FA5FIOuKOp4r4Y+pW42Toa8m+N/8AyCYfqa9ak6V5L8cP+QVF9TXucPf79A6MJ/GR41D92phyKhh+7Uw4r9etdWZ9vT1SZa02/m028hv7fG+FwwyM5wa/Q39mv9obQfH+jw+GtcaGHUIkCbWxh/6V+dJycnOCa0dB1/VPDmpRappFy8E8JyCprvwWM+pvyPm+JuHKfENDlbtNbM/Ur4n/ABP8H/CDw3Les0ETlSYokABY/hX5vfFf4seIfijr8+p6lcP5Ic+VDu4UVn+OviX4q+IN0s/iHUZJ/KUCNScDp6VynGPQ1vjsyeI92HwnmcK8H08kXtsR71TuLRRRXk+h92rX0GSdK9D/AGbv+S16B/vP/KvPJfu16H+zd/yWrQP99/5V2YP+NE8LiPTL6iXY/VG34gj/ANwfyqSo4P8AUx/7g/lUlfexfuo/lOprNt92FAGKCcUVRmJjnNB6UtFACHpS0h6UtABRRRQAUUUUAFFFFABRRRQAUUUUAGMsR61zvxAwPCGp5/54N/I10OTuA9Ky/E2ntquh3lggy0sLAfXBqKq5oMyrq9Nn5eJzdXBzx50n/oRqyvI54Bq/4q8N6l4U8RXuk6payRPHMxUlTggknrWasqbfvZPpX53WpyjN6H5Xiaco1XoS0UzzV9aTzU9RWXJNdDD2c+iJKTB9ab5qetIZU7mhwl0QOnN7okoqMyoO4pfNX1/Wjkl2Dkl2HHpWVrv/AB5S/StEypjiszXHU2UmPSvRyqLWLhddSowl2OUs87B9aueuTVKzYBOfWrXmJzzX9M4acPZRbfRBKEnokSYPrRg+tM80daXzV9a256b1TJ5Ki0aHUUzzEHejzEPWhVV3B02ug/B9aMH1pvmrSeatNVIN2uHsprWw5j2qxo3Opw896qmRc1Y0ZgdVi+tJzj3McTCXsXoeuaef3S89hV8YwOazrB1ES5PYVd81OOaqM49z88rUanN8LJKKZ5y+opfMT+8Kp1ILVsyWHqb8rHUUzzk9R+dHmp6inzR6MPYztqmPopnnJ6j86POT1H50c8e4OjU/lY5ulaPg8Z8T2f8Av1lGZDxkVp+D2U+J7PB/jrzcznF4eVme5w/TmsfBtM+yNJ/49ov9xf5VqD2NYulX1oLeMGZPuD+IelaI1Cz7zx/99CvyqfxM/o+l8CLLUo+tVf7Qs/8AnvH/AN9Cg6hZ9p4/++hUllnB9aMH1qr/AGhZ/wDPeP8A76o/tCz/AOe8f/fVAFoYoOKrf2hZf890/wC+hR/aFl/z3T/voUATy/dxmvK/jdn/AIRyTnuP516W+oWewkTx/wDfQrzD403EEvh6QJIrHjgHPevA4mTeW1Ej1skaWNg2eN2h4H0q4elUbWRNo57Va82PH3hX8w1qNTmfus/b6dSHs1qSUH61H5sfrR5sfZh+dZOjWbvys09pB63Hk89aUnio/MT+8KPNTGMj86FRqr7LEqkU7XHnpQtM8yP1A/GgSx+o/On7Gp/KxKpC+4rk7SDXk3xw/wCQVF9T/OvV3ljKk5FeTfG8g6XDj1P869zh6lNY6LaZ0YWpD2y1PHIcham5x1qCJuKl3DGK/XeSUldI+2hXpctnJfePopu8elIXHalyN6NF+2pS2kvvHUtM3il3ijkklZIft6ezkvvHUU3eKTeKfI3okL29GWikr+oPggdh6V6J+zfx8a9Ay3LO/wCHFedFwScjPpXt/wCyR4B1rX/ihY67HZyCz09iZJGXA5HFdmChL2sdD5zijF06WXT95bH6S25P2eL/AHR/KpabEu2NU/ugCnGvu0vdR/LtWXNNpAelIBigHNLTICiiigAooooAKRaWigAopcH0NGD6GgBo6UtAVsdDS4PoaAEowKXB9DRg+hoASilwfQ0bT6GgBoPFID+tP2N/dP5Umw56Gl7z0YWvuch4u+Ffgfxud3iLRI7lu7A7T+YrlG/Zb+C+Qf8AhFj/AN/mr1raR2NG3sQR+FYywtOW6OWeEoTeqPJf+GW/gvjJ8Lf+R2o/4Zb+C2M/8It/5HavWtr47/lRtOO/5VH1Sl/KT9Rw/Y8lH7LnwWH/ADK//kZqP+GW/gt/0K//AJGavW9rf3T+VG1v7p/Kj6pS/lD6jh/5TyQ/sufBU/8AMrn/AL/NR/wy78F/+hW/8jtXrWxvQ/lRhvf8qHhKP8ofUcP/ACnkv/DLnwXzz4WP/f8Aakk/ZX+Ckow3hXI/67NXrZR+pB/KlKsRwD+VXDD0ou6QfUcP/KeM/wDDI3wJ/wChR/8AI7Uf8Mi/Ar/oUv8AyO1eyhWx0P5UYbP3TXorG10rKTH9Rw/8p4z/AMMi/Av/AKFL/wAjtS/8Mi/Ar/oUv/I7V7LsPoaNh9DT+u4j+Zh9Rw/8p41/wyL8Cv8AoUv/ACO1H/DI3wK/6FL/AMjtXsuG9D+VGG9DR9exH8zD6jh/5Txr/hkb4Ff9Cj/5Hag/si/Ar/oUf/I7V7LsPoaNh9DR9dxH8zD6jh/5Txo/sjfAwAf8UlwP+m7VLbfsn/A+1mE9t4Sww/6btXsAQg5IODQVPoQPpR9er/zMmWX0JKzieZ/8M6fCYdPD2P8Atq1H/DO3wn/6F8/9/Wr0zBHY0YJ7H8qP7Qr/AMzOZ5LgnvBHmZ/Z1+FA/wCZfP8A39agfs6/Cg/8y+f+/rV6YVbPQ/lQA3ofyp/X6/8AMw/sTBfyI8z/AOGdvhP/ANC8f+/rUf8ADO3wn/6F4/8Af1q9M5/umjn+6aX16v8AzMP7EwX/AD7R5of2dPhT/wBC8f8Av81N/wCGdPhV/wBC8f8Av61emkE/wGk2n+4aax9f+Zg8jwT+wjzT/hnP4UFiP+EePP8A01arFj8Avhhp1yt3Z6BslTofNNeiEEjaSQfpS7Tx8pI7molja8tHJmlPKMHSd1BXOVHwz8I9rBx/21NH/CtPCX/PjJ/39aup2n+6aNp/umuZu56KVlY5b/hWnhH/AJ8ZP+/rUf8ACtPCP/PjJ/39aup2n+6aNh7KaBnLf8K08Jf8+En/AH9aj/hWnhL/AJ8JP+/rV1JDd0NADdkNAHLH4a+Ev+fCQ/8AbVqT/hWnhP8A58JP+/zV1O0/3DRtP9w0Acv/AMK08In5Rp8mP+uzVXvfhP4Hvo/Ju9LZl95WNdkEI4OfypMMOCpIrKrRjiI8s1oVCq6UrxPPj8Cvhz/DouP+2ho/4UV8Of8AoDH/AL+GvQQpH8JoCE/wGvLWQZfJ3nTR3vM8Sl7s39558fgV8Ou2jH/v6aT/AIUV8O/+gN/5FNehFTn7po2kdFNP+wMv/wCfaJ/tTF/zv7zz7/hRfw7/AOgMf+/rUn/Civh3/wBAb/yK1eh4/wBk0Y/2TR/YGX/8+0P+1MX/ADv7zz0/Ar4ddtGP/f00n/Ci/h0P+YL/AORGr0LaT2NJtP8AdNC4fwFv4aB5pi/53955/wD8KL+HWMHRj/38NZ2sfs2/CfXIfJ1PQPNT08xq9SKnGMGja4GApAqqWTYKjK6poazXFr7b+88U/wCGP/gX28J/+Rmo/wCGP/gZ/wBCn/5Hava9r/5FG1/Su76rSX2TX+2sb/z8f3s8UP7IHwK7+Ff/ACM1A/ZA+BX/AEKn/kZq9rCnupoKHspo+q0v5Q/trHf8/X97PFP+GPvgX/0Kf/kZqP8Ahj/4Gf8AQp/+R2r2sq3YGja/p+lH1Wl/KH9tY7/n6/vZ4mf2QPgX28K/+RmpR+x/8CyP+RU/8jNXtW1v7hpdpHRTR9Vpdg/trHf8/H97PF4v2QvgdG4b/hE+VOR++avSvCvgnw54LshYeH9MjtYlGPlHP51vBTkkgk+lCq3J2EVUcPGHQwrZlisQuWrNteoCil2n+6fyo2n0NbX6HBs7oTpRS7T6GjafQ0AJSYFO2n0NGD6GgBM4oo2k9jS7T6GgBKM5pdp9DSbSOxoA2tq/3R+VG1f7o/KlooATav8AdH5UbV/uj8qWigBNq/3R+VG1f7o/KlooATav90flRtX+6PypaKAGlR2UflSbB6CnfjSHPehXFZMNqjsPypML/dFKtLkdKA2EwvoKMJ6D8qNvvRt96Bi/L6CjC+gpNvvRt96A0FwPQUbV/uj8qTHvRt96QC4X0FJhfQUbfejb70wDC/3R+VGF/uj8qXHSjHSgBML/AHR+VGF/uj8qXHSjHSkGgmF/uj8qML/dH5UuOlGOlAaCYX+6Pyowv90flS46UY96YaBtX+6PypNo/uj8qdSZFAtRNq/3RRgDHyilyKTbS1GLtX+6Pyo2r/dH5UYo20C0Dav90flRtX+6Pyo20baA0Dav90flRtX+6PypNvvS7aY9g2r/AHR+VN2+wp9FK7Fa4m1f7o/Kjav90flS0UxibV/uj8qNq/3R+VLRQAm1f7o/Kjav90flS0UAJtX+6Pyo2r/dH5UtFACbV7gflSYT0H5U6kyKV2hbjSF7AUYHoKfRRa4xuxfQUbF9BTqKYDdi+go2L6D8qdRQAzavt+VKFX0FKDmlpagN2p3Ao2r/AHRTqTFMWobV/uj8qNq/3R+VGKNtINA2r/dH5UbV/uj8qNtG2gNA2r/dH5UbV/uj8qNtG2gNA2r/AHR+VG1f7o/KjbRtoDQQKOuBS7V9BR+NLRqwGkKP4RS7V/uj8qWm7fejYYu1f7o/Kjav90flS0UwE2r/AHR+VG1f7o/KlooATav90flRtX+6PypaKAE2r/dH5UbV/uj8qWigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9k=\"}");
 }
 
 var socket_ot;
 
 function ot_open() {
-
-       socket_ot = lws_meta.new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol");
+       socket_ot = new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol");
 
        console.log("ot_open");
 
        try {
                socket_ot.onopen = function() {
-                       document.getElementById("ot_statustd").style.backgroundColor = "#40ff40";
-                       document.getElementById("ot_status").innerHTML = " <b>websocket connection opened</b><br>" + san(socket_di.extensions);
+                       document.getElementById("ot_statustd").style.backgroundColor =
+                                                                                                                               "#40ff40";
+                       document.getElementById("ot_status").innerHTML =
+                                       " <b>websocket connection opened</b><br>" +
+                                       san(socket_di.extensions);
                        document.getElementById("ot_open_btn").disabled = true;
                        document.getElementById("ot_close_btn").disabled = false;
                        document.getElementById("ot_req_close_btn").disabled = false;
                        console.log("ot_open.onopen");
-               } 
+               };
 
                socket_ot.onclose = function(e){
-                       document.getElementById("ot_statustd").style.backgroundColor = "#ff4040";
-                       document.getElementById("ot_status").textContent = " websocket connection CLOSED, code: " + e.code +
-                       ", reason: " + e.reason;
+                       document.getElementById("ot_statustd").style.backgroundColor =
+                                                                                                                               "#ff4040";
+                       document.getElementById("ot_status").textContent =
+                                               " websocket connection CLOSED, code: " + e.code +
+                                               ", reason: " + e.reason;
                        document.getElementById("ot_open_btn").disabled = false;
                        document.getElementById("ot_close_btn").disabled = true;
                        document.getElementById("ot_req_close_btn").disabled = true;
-               }
+               };
        } catch(exception) {
-               alert('<p>Error' + exception);  
+               alert("<p>Error" + exception);  
        }
 }
 
@@ -686,40 +351,94 @@ function ot_req_close() {
        socket_ot.send("closeme\n");
 }
 
+var socket_lm;
+var pending = "";
+
+function lm_timer_handler(ev) {
+       socket_lm.send(pending);
+       pending="";
+}
+
 /* lws-mirror protocol */
 
-       var down = 0;
-       var no_last = 1;
-       var last_x = 0, last_y = 0;
-       var ctx;
-       var socket_lm;
-       var color = "#000000";
+var down = 0;
+var no_last = 1;
+var last_x = 0, last_y = 0;
+var ctx;
+var color = "#000000";
+var lm_timer;
 
-       socket_lm = lws_meta.new_ws(get_appropriate_ws_url("?mirror=" + mirror_name),
-                       "lws-mirror-protocol");
+function ev_mousemove (ev) {
+       var x, y;
+
+       if (ev.offsetX) {
+               x = ev.offsetX;
+               y = ev.offsetY;
+       } else {
+               x = ev.layerX - offsetX;
+               y = ev.layerY - offsetY;
+
+       }
+
+       if (!down)
+               return;
+       if (no_last) {
+               no_last = 0;
+               last_x = x;
+               last_y = y;
+               return;
+       }
+       pending = pending + "d " + color + " " + last_x + " " + last_y +
+                       " " + x + " " + y + ";";
+                       
+       if (pending.length > 400) {
+               socket_lm.send(pending);
+               clearTimeout(lm_timer);
+               pending = "";
+       } else
+               lm_timer = setTimeout(lm_timer_handler, 1);
+
+       last_x = x;
+       last_y = y;
+}
+
+function ev_mousedown (ev) {
+       down = 1;
+}
+
+function ev_mouseup(ev) {
+       down = 0;
+       no_last = 1;
+}
 
+
+function ws_open_mirror()
+{      
+       socket_lm = new_ws(get_appropriate_ws_url("?mirror=" + mirror_name),
+                       "lws-mirror-protocol");
        try {
                socket_lm.onopen = function() {
-                       document.getElementById("wslm_statustd").style.backgroundColor = "#40ff40";
+                       document.getElementById("wslm_statustd").style.backgroundColor =
+                                                                                                                                       "#40ff40";
                        document.getElementById("wslm_status").innerHTML =
-                               " <b>websocket connection opened</b><br>" +
-                               san(socket_lm.extensions);
+                                                               " <b>websocket connection opened</b><br>" +
+                                                               san(socket_lm.extensions);
                        lws_gray_out(false);
-               } 
+               };
 
                socket_lm.onmessage =function got_packet(msg) {
-                       j = msg.data.split(';');
-                       f = 0;
+                       j = msg.data.split(";");
+                       var f = 0;
                        while (f < j.length - 1) {
-                               i = j[f].split(' ');
-                               if (i[0] == 'd') {
+                               i = j[f].split(" ");
+                               if (i[0] === "d") {
                                        ctx.strokeStyle = i[1];
                                        ctx.beginPath();
                                        ctx.moveTo(+(i[2]), +(i[3]));
                                        ctx.lineTo(+(i[4]), +(i[5]));
                                        ctx.stroke();
                                }
-                               if (i[0] == 'c') {
+                               if (i[0] === "c") {
                                        ctx.strokeStyle = i[1];
                                        ctx.beginPath();
                                        ctx.arc(+(i[2]), +(i[3]), +(i[4]), 0, Math.PI*2, true); 
@@ -728,27 +447,29 @@ function ot_req_close() {
 
                                f++;
                        }
-               }
+               };
 
                socket_lm.onclose = function(){
-                       document.getElementById("wslm_statustd").style.backgroundColor = "#ff4040";
-                       document.getElementById("wslm_status").textContent = " websocket connection CLOSED ";
-                       lws_gray_out(true,{'zindex':'499'});
-               }
+                       document.getElementById("wslm_statustd").style.backgroundColor =
+                                                                                                                                       "#ff4040";
+                       document.getElementById("wslm_status").textContent =
+                                                                                       " websocket connection CLOSED ";
+                       lws_gray_out(true,{"zindex":"499"});
+               };
        } catch(exception) {
-               alert('<p>Error' + exception);  
+               alert("<p>Error" + exception);  
        }
 
-       var canvas = document.createElement('canvas');
+       var canvas = document.createElement("canvas");
        canvas.height = 300;
        canvas.width = 480;
        ctx = canvas.getContext("2d");
 
-       document.getElementById('wslm_drawing').appendChild(canvas);
+       document.getElementById("wslm_drawing").appendChild(canvas);
 
-       canvas.addEventListener('mousemove', ev_mousemove, false);
-       canvas.addEventListener('mousedown', ev_mousedown, false);
-       canvas.addEventListener('mouseup', ev_mouseup, false);
+       canvas.addEventListener("mousemove", ev_mousemove, false);
+       canvas.addEventListener("mousedown", ev_mousedown, false);
+       canvas.addEventListener("mouseup", ev_mouseup, false);
 
        offsetX = offsetY = 0;
        element = canvas;
@@ -756,50 +477,67 @@ function ot_req_close() {
         do {
           offsetX += element.offsetLeft;
           offsetY += element.offsetTop;
-        } while ((element = element.offsetParent));
+          element = element.offsetParent;
+        } while (element);
       }
-function update_color() {
-       color = document.getElementById("color").value;
-}
-
-function ev_mousedown (ev) {
-       down = 1;
 }
 
-function ev_mouseup(ev) {
-       down = 0;
-       no_last = 1;
+function update_color() {
+       color = document.getElementById("color").value;
 }
 
-function ev_mousemove (ev) {
-       var x, y;
+/* stuff that has to be delayed until all the page assets are loaded */
 
-       if (ev.offsetX) {
-               x = ev.offsetX;
-               y = ev.offsetY;
+window.addEventListener("load", function() {
+       
+       lws_gray_out(true,{"zindex":"499"});
+
+       document.getElementById("file").onchange = check_file;
+       document.getElementById("offset").onclick = reset;
+       document.getElementById("junk").onclick = junk;
+       document.getElementById("color").onclick = update_color;
+       document.getElementById("ot_open_btn").onclick = ot_open;
+       document.getElementById("ot_close_btn").onclick = ot_close;
+       document.getElementById("ot_req_close_btn").onclick = ot_req_close;
+       document.getElementById("pmd").onclick = on_pmd;
+
+       var transport_protocol = "";
+
+       if ( performance && performance.timing.nextHopProtocol ) {
+           transport_protocol = performance.timing.nextHopProtocol;
+       } else if ( window.chrome && window.chrome.loadTimes ) {
+           transport_protocol = window.chrome.loadTimes().connectionInfo;
        } else {
-               x = ev.layerX - offsetX;
-               y = ev.layerY - offsetY;
-
-       }
-
-       if (!down)
-               return;
-       if (no_last) {
-               no_last = 0;
-               last_x = x;
-               last_y = y;
-               return;
-       }
-       socket_lm.send("d " + color + " " + last_x + " " + last_y + " " + x + ' ' + y + ';');
-
-       last_x = x;
-       last_y = y;
-}
-
-
-</script>
 
-</body>
-</html>
+         var p = performance.getEntriesByType("resource");
+         for (var i=0; i < p.length; i++) {
+       var value = "nextHopProtocol" in p[i];
+         if (value)
+           transport_protocol = p[i].nextHopProtocol;
+           }
+          }
+          
+          console.log("transport protocol " + transport_protocol);
+          
+          if (transport_protocol === "h2")
+                  document.getElementById("transport").innerHTML =
+                                                                                       "<img src=\"./http2.png\">";
+
+          BrowserDetect.init();
+
+          document.getElementById("brow").textContent = " " +
+                       BrowserDetect.browser + " " + BrowserDetect.version + " " +
+                       BrowserDetect.OS +" ";
+
+          document.getElementById("number").textContent =
+                  get_appropriate_ws_url(mirror_name);
+          
+          /* create the ws connections back to the server */
+          
+          ws_open_dumb_increment();
+          ws_open_status();
+          ws_open_mirror();
+
+}, false);
+
+}());
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png
new file mode 100644 (file)
index 0000000..1a62d83
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-demos/mount-origin/wss-over-h2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2d804a7
--- /dev/null
@@ -0,0 +1,97 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-eventlib-foreign)
+set(SRCS minimal-http-server-eventlib-foreign.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(LWS_WITH_LIBUV)\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" LWS_WITH_LIBUV)
+CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(LWS_WITH_LIBEVENT)\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" LWS_WITH_LIBEVENT)
+CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(LWS_WITH_LIBEV)\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" LWS_WITH_LIBEV)
+
+if (LWS_WITH_LIBUV)
+       set(extralibs ${extralibs} uv)
+endif()
+if (LWS_WITH_LIBEVENT)
+       set(extralibs ${extralibs} event)
+endif()
+if (LWS_WITH_LIBEV)
+       set(extralibs ${extralibs} ev)
+endif()
+
+message("Extra libs: ${extralibs}")
+
+if (NOT LWS_WITH_LIBUV AND NOT LWS_WITH_LIBEVENT AND NOT LWS_WITH_LIBEV)
+       set(requirements 0)
+endif()
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared ${extralibs})
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets ${extralibs})
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/README.md
new file mode 100644 (file)
index 0000000..4c21fa1
--- /dev/null
@@ -0,0 +1,58 @@
+# lws minimal http server eventlib foreign
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+--uv|Use the libuv event library (lws must have been configured with `-DLWS_WITH_LIBUV=1`)
+--event|Use the libevent library (lws must have been configured with `-DLWS_WITH_LIBEVENT=1`)
+--ev|Use the libev event library (lws must have been configured with `-DLWS_WITH_LIBEV=1`)
+
+Notice libevent and libev cannot coexist in the one library.  But all the other combinations are OK.
+
+x|libuv|libevent|libev
+---|---|---|---
+libuv|-|OK|OK
+libevent|OK|-|no
+libev|OK|no|-
+
+This demonstrates having lws take part in a libuv loop owned by
+something else, with its own objects running in the loop.
+
+Lws can join the loop, and clean up perfectly after itself without
+leaving anything behind or making trouble in the larger loop, which
+does not need to stop during lws creation or destruction.
+
+First the foreign loop is created with a 1s timer, and runs alone for 5s.
+
+Then the lws context is created inside the timer callback and runs for 10s...
+during this period you can visit http://localhost:7681 for normal lws
+service using the foreign loop.
+
+After the 10s are up, the lws context is destroyed inside the foreign loop
+timer.  The foreign loop runs alone again for a further 5s and then
+exits itself.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-eventlib-foreign
+[2018/03/29 12:19:31:3480] USER: LWS minimal http server eventlib + foreign loop | visit http://localhost:7681
+[2018/03/29 12:19:31:3724] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/29 12:19:31:3804] NOTICE:  Using foreign event loop...
+[2018/03/29 12:19:31:3938] USER: Foreign 1Hz timer
+[2018/03/29 12:19:32:4011] USER: Foreign 1Hz timer
+[2018/03/29 12:19:33:4024] USER: Foreign 1Hz timer
+^C[2018/03/29 12:19:33:8868] NOTICE: Signal 2 caught, exiting...
+[2018/03/29 12:19:33:8963] USER: main: starting exit cleanup...
+[2018/03/29 12:19:33:9064] USER: main: lws context destroyed: cleaning the foreign loop
+[2018/03/29 12:19:33:9108] USER: main: exiting...
+```
+
+Visit http://localhost:7681
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c
new file mode 100644 (file)
index 0000000..053f1bf
--- /dev/null
@@ -0,0 +1,425 @@
+/*
+ * lws-minimal-http-server-eventlib-foreign
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws that
+ * uses a libuv event loop created outside lws.  It shows how lws can
+ * participate in someone else's event loop and clean up after itself.
+ *
+ * You choose the event loop to work with at runtime, by giving the
+ * --uv, --event or --ev switch.  Lws has to have been configured to build the
+ * selected event lib support.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+struct lws_context_creation_info info;
+static struct lws_context *context;
+static int lifetime = 5, reported;
+
+static void foreign_timer_service(void *foreign_loop);
+
+enum {
+       TEST_STATE_CREATE_LWS_CONTEXT,
+       TEST_STATE_DESTROY_LWS_CONTEXT,
+       TEST_STATE_EXIT
+};
+
+static int sequence = TEST_STATE_CREATE_LWS_CONTEXT;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static void
+signal_cb(int signum)
+{
+       lwsl_notice("Signal %d caught, exiting...\n", signum);
+
+       switch (signum) {
+       case SIGTERM:
+       case SIGINT:
+               break;
+       default:
+               break;
+       }
+
+       lws_context_destroy(context);
+}
+
+/*
+ * The event-loop specific foreign loop code, one set for each event loop lib
+ *
+ * Only the code in this section is specific to the event library used.
+ */
+
+#if defined(LWS_WITH_LIBUV)
+
+static uv_loop_t loop_uv;
+static uv_timer_t timer_outer_uv;
+static uv_signal_t sighandler_uv;
+
+static void
+timer_cb_uv(uv_timer_t *t)
+{
+       foreign_timer_service(&loop_uv);
+}
+
+static void
+signal_cb_uv(uv_signal_t *watcher, int signum)
+{
+       signal_cb(signum);
+}
+
+static void
+foreign_event_loop_init_and_run_libuv(void)
+{
+       /* we create and start our "foreign loop" */
+
+#if (UV_VERSION_MAJOR > 0) // Travis...
+       uv_loop_init(&loop_uv);
+#endif
+       uv_signal_init(&loop_uv, &sighandler_uv);
+       uv_signal_start(&sighandler_uv, signal_cb_uv, SIGINT);
+
+       uv_timer_init(&loop_uv, &timer_outer_uv);
+#if (UV_VERSION_MAJOR > 0) // Travis...
+       uv_timer_start(&timer_outer_uv, timer_cb_uv, 0, 1000);
+#else
+       (void)timer_cb_uv;
+#endif
+
+       uv_run(&loop_uv, UV_RUN_DEFAULT);
+}
+
+static void
+foreign_event_loop_stop_libuv(void)
+{
+       uv_stop(&loop_uv);
+}
+
+static void
+foreign_event_loop_cleanup_libuv(void)
+{
+       /* cleanup the foreign loop assets */
+
+       uv_timer_stop(&timer_outer_uv);
+       uv_close((uv_handle_t*)&timer_outer_uv, NULL);
+       uv_signal_stop(&sighandler_uv);
+       uv_close((uv_handle_t *)&sighandler_uv, NULL);
+
+       uv_run(&loop_uv, UV_RUN_DEFAULT);
+#if (UV_VERSION_MAJOR > 0) // Travis...
+       uv_loop_close(&loop_uv);
+#endif
+}
+
+#endif
+
+#if defined(LWS_WITH_LIBEVENT)
+
+static struct event_base *loop_event;
+static struct event *timer_outer_event;
+static struct event *sighandler_event;
+
+static void
+timer_cb_event(int fd, short event, void *arg)
+{
+       foreign_timer_service(loop_event);
+}
+
+static void
+signal_cb_event(int fd, short event, void *arg)
+{
+       signal_cb((int)(lws_intptr_t)arg);
+}
+
+static void
+foreign_event_loop_init_and_run_libevent(void)
+{
+       struct timeval tv;
+
+       /* we create and start our "foreign loop" */
+
+       tv.tv_sec = 1;
+       tv.tv_usec = 0;
+
+       loop_event = event_base_new();
+
+       sighandler_event = evsignal_new(loop_event, SIGINT, signal_cb_event,
+                                       (void*)SIGINT);
+
+       timer_outer_event = event_new(loop_event, -1, EV_PERSIST,
+                                     timer_cb_event, NULL);
+       //evtimer_new(loop_event, timer_cb_event, NULL);
+       evtimer_add(timer_outer_event, &tv);
+
+       event_base_loop(loop_event, 0);
+}
+
+static void
+foreign_event_loop_stop_libevent(void)
+{
+       event_base_loopexit(loop_event, NULL);
+}
+
+static void
+foreign_event_loop_cleanup_libevent(void)
+{
+       /* cleanup the foreign loop assets */
+
+       evtimer_del(timer_outer_event);
+       event_free(timer_outer_event);
+       evsignal_del(sighandler_event);
+       event_free(sighandler_event);
+
+       event_base_loop(loop_event, 0);
+       event_base_free(loop_event);
+}
+
+#endif
+
+#if defined(LWS_WITH_LIBEV)
+
+static struct ev_loop *loop_ev;
+static struct ev_timer timer_outer_ev;
+static struct ev_signal sighandler_ev;
+
+static void
+timer_cb_ev(struct ev_loop *loop, struct ev_timer *watcher, int revents)
+{
+       foreign_timer_service(loop_ev);
+}
+
+static void
+signal_cb_ev(struct ev_loop *loop, struct ev_signal *watcher, int revents)
+{
+       signal_cb(watcher->signum);
+}
+
+static void
+foreign_event_loop_init_and_run_libev(void)
+{
+       /* we create and start our "foreign loop" */
+
+       loop_ev = ev_loop_new(0);
+
+       ev_signal_init(&sighandler_ev, signal_cb_ev, SIGINT);
+       ev_signal_start(loop_ev, &sighandler_ev);
+
+       ev_timer_init(&timer_outer_ev, timer_cb_ev, 0, 1);
+       ev_timer_start(loop_ev, &timer_outer_ev);
+
+       ev_run(loop_ev, 0);
+}
+
+static void
+foreign_event_loop_stop_libev(void)
+{
+       ev_break(loop_ev, EVBREAK_ALL);
+}
+
+static void
+foreign_event_loop_cleanup_libev(void)
+{
+       /* cleanup the foreign loop assets */
+
+       ev_timer_stop(loop_ev, &timer_outer_ev);
+       ev_signal_stop(loop_ev, &sighandler_ev);
+
+       ev_run(loop_ev, UV_RUN_DEFAULT);
+       ev_loop_destroy(loop_ev);
+}
+
+#endif
+
+/* this is called at 1Hz using a foreign loop timer */
+
+static void
+foreign_timer_service(void *foreign_loop)
+{
+       void *foreign_loops[1];
+
+       lwsl_user("Foreign 1Hz timer\n");
+
+       if (sequence == TEST_STATE_EXIT && !context && !reported) {
+               /*
+                * at this point the lws_context_destroy() we did earlier
+                * has completed and the entire context is wholly destroyed
+                */
+               lwsl_user("lws_destroy_context() done, continuing for 5s\n");
+               reported = 1;
+       }
+
+       if (--lifetime)
+               return;
+
+       switch (sequence++) {
+       case TEST_STATE_CREATE_LWS_CONTEXT:
+               /* this only has to exist for the duration of create context */
+               foreign_loops[0] = foreign_loop;
+               info.foreign_loops = foreign_loops;
+
+               context = lws_create_context(&info);
+               if (!context) {
+                       lwsl_err("lws init failed\n");
+                       return;
+               }
+               lwsl_user("LWS Context created and will be active for 10s\n");
+               lifetime = 11;
+               break;
+
+       case TEST_STATE_DESTROY_LWS_CONTEXT:
+               /* cleanup the lws part */
+               lwsl_user("Destroying lws context and continuing loop for 5s\n");
+               lws_context_destroy(context);
+               lifetime = 6;
+               break;
+
+       case TEST_STATE_EXIT:
+               lwsl_user("Deciding to exit foreign loop too\n");
+#if defined(LWS_WITH_LIBUV)
+               if (info.options & LWS_SERVER_OPTION_LIBUV)
+                       foreign_event_loop_stop_libuv();
+#endif
+#if defined(LWS_WITH_LIBEVENT)
+               if (info.options & LWS_SERVER_OPTION_LIBEVENT)
+                       foreign_event_loop_stop_libevent();
+#endif
+#if defined(LWS_WITH_LIBEV)
+               if (info.options & LWS_SERVER_OPTION_LIBEV)
+                       foreign_event_loop_stop_libev();
+#endif
+               break;
+       default:
+               break;
+       }
+}
+
+int main(int argc, const char **argv)
+{
+       const char *p;
+       int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server eventlib + foreign loop |"
+                 " visit http://localhost:7681\n");
+
+       /*
+        * We prepare the info here, but don't use it until later in the
+        * timer callback, to demonstrate the independence of the foreign loop
+        * and lws.
+        */
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.pcontext = &context;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       if (lws_cmdline_option(argc, argv, "--uv"))
+               info.options |= LWS_SERVER_OPTION_LIBUV;
+       else
+               if (lws_cmdline_option(argc, argv, "--event"))
+                       info.options |= LWS_SERVER_OPTION_LIBEVENT;
+               else
+                       if (lws_cmdline_option(argc, argv, "--ev"))
+                               info.options |= LWS_SERVER_OPTION_LIBEV;
+                       else {
+                               lwsl_err("This app only makes sense when used\n");
+                               lwsl_err(" with a foreign loop, --uv, --event, or --ev\n");
+
+                               return 1;
+                       }
+
+       lwsl_user("  This app creates a foreign event loop with a timer +\n");
+       lwsl_user("  signalhandler, and performs a test in three phases:\n");
+       lwsl_user("\n");
+       lwsl_user("  1) 5s: Runs the loop with just the timer\n");
+       lwsl_user("  2) 10s: create an lws context serving on localhost:7681\n");
+       lwsl_user("     using the same foreign loop.  Destroy it after 10s.\n");
+       lwsl_user("  3) 5s: Run the loop again with just the timer\n");
+       lwsl_user("\n");
+       lwsl_user("  Finally close only the timer and signalhandler and\n");
+       lwsl_user("   exit the loop cleanly\n");
+       lwsl_user("\n");
+
+       /* foreign loop specific startup and run */
+
+#if defined(LWS_WITH_LIBUV)
+       if (info.options & LWS_SERVER_OPTION_LIBUV)
+               foreign_event_loop_init_and_run_libuv();
+#endif
+#if defined(LWS_WITH_LIBEVENT)
+       if (info.options & LWS_SERVER_OPTION_LIBEVENT)
+               foreign_event_loop_init_and_run_libevent();
+#endif
+#if defined(LWS_WITH_LIBEV)
+       if (info.options & LWS_SERVER_OPTION_LIBEV)
+               foreign_event_loop_init_and_run_libev();
+#endif
+
+       lws_context_destroy(context);
+
+       /* foreign loop specific cleanup and exit */
+
+#if defined(LWS_WITH_LIBUV)
+       if (info.options & LWS_SERVER_OPTION_LIBUV)
+               foreign_event_loop_cleanup_libuv();
+#endif
+#if defined(LWS_WITH_LIBEVENT)
+       if (info.options & LWS_SERVER_OPTION_LIBEVENT)
+               foreign_event_loop_cleanup_libevent();
+#endif
+#if defined(LWS_WITH_LIBEV)
+       if (info.options & LWS_SERVER_OPTION_LIBEV)
+               foreign_event_loop_cleanup_libev();
+#endif
+
+       lwsl_user("%s: exiting...\n", __func__);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/index.html
new file mode 100644 (file)
index 0000000..bee267a
--- /dev/null
@@ -0,0 +1,16 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server eventlib foreign loop example</b>.
+               <br>
+               The timer messages in the console are coming from<br>
+               a timer on the event library lib loop set up before the lws context<br>
+               started using it.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-eventlib-smp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a60b3d6
--- /dev/null
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-eventlib-smp)
+set(SRCS minimal-http-server-eventlib-smp.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared pthread)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/README.md b/minimal-examples/http-server/minimal-http-server-eventlib-smp/README.md
new file mode 100644 (file)
index 0000000..56dfcc4
--- /dev/null
@@ -0,0 +1,33 @@
+# lws minimal http server eventlib
+
+WARNING: this is under development, it's not stable.
+
+This demonstrates a minimal http server that can use any of the event libraries
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-t <threads>|Number of threads to use.
+--uv|Use the libuv event library (lws must have been configured with `-DLWS_WITH_LIBUV=1`)
+--event|Use the libevent library (lws must have been configured with `-DLWS_WITH_LIBEVENT=1`)
+--ev|Use the libev event library (lws must have been configured with `-DLWS_WITH_LIBEV=1`)
+
+## build
+
+lilbwebsockets must have been built with `LWS_MAX_SMP` greater than 1 to use
+multiple threads.
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-eventlib-smp
+[2018/03/04 09:30:02:7986] USER: LWS minimal http server-eventlib | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-eventlib-smp/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-eventlib-smp/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c b/minimal-examples/http-server/minimal-http-server-eventlib-smp/minimal-http-server-eventlib-smp.c
new file mode 100644 (file)
index 0000000..7e166e6
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * lws-minimal-http-server-eventlib-smp
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http[s] server that can work with any of the
+ * supported event loop backends, or the default poll() one.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#include <pthread.h>
+
+#define COUNT_THREADS 8
+
+static struct lws_context *context;
+static volatile int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void *thread_service(void *threadid)
+{
+       while (lws_service_tsi(context, 10000,
+                              (int)(lws_intptr_t)threadid) >= 0 &&
+              !interrupted)
+               ;
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+void signal_cb(void *handle, int signum)
+{
+       interrupted = 1;
+
+       switch (signum) {
+       case SIGTERM:
+       case SIGINT:
+               break;
+       default:
+               lwsl_err("%s: signal %d\n", __func__, signum);
+               break;
+       }
+       lws_context_destroy(context);
+}
+
+void sigint_handler(int sig)
+{
+       signal_cb(NULL, sig);
+}
+
+int main(int argc, const char **argv)
+{
+       pthread_t pthread_service[COUNT_THREADS];
+       struct lws_context_creation_info info;
+       const char *p;
+       void *retval;
+       int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server eventlib SMP | visit http://localhost:7681\n");
+       lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n");
+       lwsl_user("WARNING: Not stable, under development!\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.pcontext = &context;
+       info.signal_cb = signal_cb;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if ((p = lws_cmdline_option(argc, argv, "-t"))) {
+               info.count_threads = atoi(p);
+               if (info.count_threads < 1 || info.count_threads > LWS_MAX_SMP)
+                       return 1;
+       } else
+               info.count_threads = COUNT_THREADS;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       if (lws_cmdline_option(argc, argv, "--uv"))
+               info.options |= LWS_SERVER_OPTION_LIBUV;
+       else
+               if (lws_cmdline_option(argc, argv, "--event"))
+                       info.options |= LWS_SERVER_OPTION_LIBEVENT;
+               else
+                       if (lws_cmdline_option(argc, argv, "--ev"))
+                               info.options |= LWS_SERVER_OPTION_LIBEV;
+                       else
+                               signal(SIGINT, sigint_handler);
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       lwsl_notice("  Service threads: %d\n", lws_get_count_threads(context));
+
+       /* start all the service threads */
+
+       for (n = 0; n < lws_get_count_threads(context); n++)
+               if (pthread_create(&pthread_service[n], NULL, thread_service,
+                                  (void *)(lws_intptr_t)n))
+                       lwsl_err("Failed to start service thread\n");
+
+       /* wait for all the service threads to exit */
+
+       while ((--n) >= 0)
+               pthread_join(pthread_service[n], &retval);
+
+       lwsl_notice("%s: calling external context destroy\n", __func__);
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/index.html
new file mode 100644 (file)
index 0000000..8da5b66
--- /dev/null
@@ -0,0 +1,15 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server event loop example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-eventlib-smp/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-eventlib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..66a4452
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-eventlib)
+set(SRCS minimal-http-server-eventlib.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/README.md b/minimal-examples/http-server/minimal-http-server-eventlib/README.md
new file mode 100644 (file)
index 0000000..ecfb733
--- /dev/null
@@ -0,0 +1,27 @@
+# lws minimal http server eventlib
+
+This demonstrates a minimal http server that can use any of the event libraries
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+--uv|Use the libuv event library (lws must have been configured with `-DLWS_WITH_LIBUV=1`)
+--event|Use the libevent library (lws must have been configured with `-DLWS_WITH_LIBEVENT=1`)
+--ev|Use the libev event library (lws must have been configured with `-DLWS_WITH_LIBEV=1`)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-eventlib
+[2018/03/04 09:30:02:7986] USER: LWS minimal http server-eventlib | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-eventlib/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-eventlib/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c b/minimal-examples/http-server/minimal-http-server-eventlib/minimal-http-server-eventlib.c
new file mode 100644 (file)
index 0000000..9c2d49c
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * lws-minimal-http-server-eventlib
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http[s] server that can work with any of the
+ * supported event loop backends, or the default poll() one.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static struct lws_context *context;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void signal_cb(void *handle, int signum)
+{
+       switch (signum) {
+       case SIGTERM:
+       case SIGINT:
+               break;
+       default:
+               lwsl_err("%s: signal %d\n", __func__, signum);
+               break;
+       }
+       lws_context_destroy(context);
+}
+
+void sigint_handler(int sig)
+{
+       signal_cb(NULL, sig);
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p;
+       int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server eventlib | visit http://localhost:7681\n");
+       lwsl_user(" [-s (ssl)] [--uv (libuv)] [--ev (libev)] [--event (libevent)]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.pcontext = &context;
+       info.signal_cb = signal_cb;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       if (lws_cmdline_option(argc, argv, "--uv"))
+               info.options |= LWS_SERVER_OPTION_LIBUV;
+       else
+               if (lws_cmdline_option(argc, argv, "--event"))
+                       info.options |= LWS_SERVER_OPTION_LIBEVENT;
+               else
+                       if (lws_cmdline_option(argc, argv, "--ev"))
+                               info.options |= LWS_SERVER_OPTION_LIBEV;
+                       else
+                               signal(SIGINT, sigint_handler);
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (!lws_service(context, 0))
+               ;
+
+       lwsl_info("calling external context destroy\n");
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/index.html
new file mode 100644 (file)
index 0000000..8da5b66
--- /dev/null
@@ -0,0 +1,15 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server event loop example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-eventlib/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-form-get/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1b43056
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-form-get)
+set(SRCS minimal-http-server-form-get.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/README.md b/minimal-examples/http-server/minimal-http-server-form-get/README.md
new file mode 100644 (file)
index 0000000..a22d8c2
--- /dev/null
@@ -0,0 +1,21 @@
+# lws minimal http server form GET
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-form-get
+[2018/03/29 08:29:41:7044] USER: LWS minimal http server form GET | visit http://localhost:7681
+[2018/03/29 08:29:41:7044] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/29 08:29:49:8601] USER: text1: (len 4) 'xxxx'
+[2018/03/29 08:29:49:8601] USER: send: (len 6) 'Submit'
+```
+
+Visit http://localhost:7681, submit the form.
+
+The form parameters are dumped to the log and you are redirected to a different page.
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c b/minimal-examples/http-server/minimal-http-server-form-get/minimal-http-server-form-get.c
new file mode 100644 (file)
index 0000000..de149ac
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * lws-minimal-http-server-form-get
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that performs a form GET with a couple
+ * of parameters.  It dumps the parameters to the console log and redirects
+ * to another page.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const char * param_names[] = {
+       "text1",
+       "send"
+};
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+             void *in, size_t len)
+{
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
+               *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - 1];
+       const char *val;
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+
+               if (!lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI))
+                       /* not a GET */
+                       break;
+               lwsl_err("%s: %s\n", __func__, (const char *)in);
+               if (strcmp((const char *)in, "/form1"))
+                       /* not our form URL */
+                       break;
+
+               /* we just dump the decoded things to the log */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
+                       val = lws_get_urlarg_by_name(wsi, param_names[n],
+                                       (char *)buf, sizeof(buf));
+                       if (!val)
+                               lwsl_user("%s: undefined\n", param_names[n]);
+                       else
+                               lwsl_user("%s: (len %d) '%s'\n", param_names[n],
+                                         (int)strlen((const char *)buf),buf);
+               }
+
+               /*
+                * Our response is to redirect to a static page.  We could
+                * have generated a dynamic html page here instead.
+                */
+
+               if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
+                                     (unsigned char *)"after-form1.html",
+                                     16, &p, end) < 0)
+                       return -1;
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", callback_http, 0, 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */              NULL,            /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */           "./mount-origin",       /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server GET | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6f85f25
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8">
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/after-form1.html b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/after-form1.html
new file mode 100644 (file)
index 0000000..938ccb7
--- /dev/null
@@ -0,0 +1,12 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Thanks for posting the form.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/index.html
new file mode 100644 (file)
index 0000000..147ce5f
--- /dev/null
@@ -0,0 +1,23 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http form GET example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               When you submit the form below, you will see the values of the<br>
+               form parameters reported on the console log.
+               <p>
+               <form action="/form1" method="get">
+                       Type some text:<br>
+                       <input type="text" name="text1"><br>
+                       <input type="submit" name="send" value="Submit">
+               </form>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-form-get/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-form-post-file/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1cffb98
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-form-post-file)
+set(SRCS minimal-http-server-form-post-file.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/README.md b/minimal-examples/http-server/minimal-http-server-form-post-file/README.md
new file mode 100644 (file)
index 0000000..d0fce2c
--- /dev/null
@@ -0,0 +1,23 @@
+# lws minimal http server form POST file
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-form-post-file
+[2018/03/29 09:58:30:8800] USER: LWS minimal http server POST file | visit http://localhost:7681
+[2018/03/29 09:58:30:8800] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/29 09:58:45:3284] USER: file_upload_cb: upload done, written 2729 to wss-over-h2.png
+[2018/03/29 09:58:45:3284] USER: text1: (len 3) 'xxx'
+[2018/03/29 09:58:45:3284] USER: send: (len 6) 'Submit'
+```
+
+Visit http://localhost:7681, select a file to upload and submit the form.
+
+The file is uploaded and saved in the cwd, the form parameters are dumped to the log and
+you are redirected to a different page.
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c b/minimal-examples/http-server/minimal-http-server-form-post-file/minimal-http-server-form-post-file.c
new file mode 100644 (file)
index 0000000..14d78cd
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * lws-minimal-http-server-form-post-file
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that performs POST with a couple
+ * of parameters and a file upload, all in multipart (mime) form mode.
+ * It saves the uploaded file in the current directory, dumps the parameters to
+ * the console log and redirects to another page.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+
+/*
+ * Unlike ws, http is a stateless protocol.  This pss only exists for the
+ * duration of a single http transaction.  With http/1.1 keep-alive and http/2,
+ * that is unrelated to (shorter than) the lifetime of the network connection.
+ */
+struct pss {
+       struct lws_spa *spa;            /* lws helper decodes multipart form */
+       char filename[128];             /* the filename of the uploaded file */
+       unsigned long long file_length; /* the amount of bytes uploaded */
+       int fd;                         /* fd on file being saved */
+};
+
+static int interrupted;
+
+static const char * const param_names[] = {
+       "text1",
+       "send",
+};
+
+enum enum_param_names {
+       EPN_TEXT1,
+       EPN_SEND,
+};
+
+static int
+file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct pss *pss = (struct pss *)data;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               /* take a copy of the provided filename */
+               lws_strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               /* remove any scary things like .. */
+               lws_filename_purify_inplace(pss->filename);
+               /* open a file of that name for write in the cwd */
+               pss->fd = lws_open(pss->filename, O_CREAT | O_TRUNC | O_RDWR, 0600);
+               if (pss->fd == -1) {
+                       lwsl_notice("Failed to open output file %s\n",
+                                   pss->filename);
+                       return 1;
+               }
+               break;
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (len) {
+                       int n;
+
+                       pss->file_length += len;
+
+                       n = write(pss->fd, buf, len);
+                       if (n < len) {
+                               lwsl_notice("Problem writing file %d\n", errno);
+                       }
+               }
+               if (state == LWS_UFS_CONTENT)
+                       /* wasn't the last part of the file */
+                       break;
+
+               /* the file upload is completed */
+
+               lwsl_user("%s: upload done, written %lld to %s\n", __func__,
+                         pss->file_length, pss->filename);
+
+               close(pss->fd);
+               pss->fd = -1;
+               break;
+       case LWS_UFS_CLOSE:
+               break;
+       }
+
+       return 0;
+}
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+             void *in, size_t len)
+{
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
+               *p = start, *end = &buf[sizeof(buf) - 1];
+       struct pss *pss = (struct pss *)user;
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+
+               /*
+                * Manually report that our form target URL exists
+                *
+                * you can also do this by adding a mount for the form URL
+                * to the protocol with type LWSMPRO_CALLBACK, then no need
+                * to trap LWS_CALLBACK_HTTP.
+                */
+
+               if (!strcmp((const char *)in, "/form1"))
+                       /* assertively allow it to exist in the URL space */
+                       return 0;
+
+               /* default to 404-ing the URL if not mounted */
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY:
+
+               /* create the POST argument parser if not already existing */
+
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                       LWS_ARRAY_SIZE(param_names), 1024,
+                                       file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
+               }
+
+               /* let it parse the POST data */
+
+               if (lws_spa_process(pss->spa, in, (int)len))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+
+               /* inform the spa no more payload data coming */
+
+               lws_spa_finalize(pss->spa);
+
+               /* we just dump the decoded things to the log */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
+                       if (!lws_spa_get_string(pss->spa, n))
+                               lwsl_user("%s: undefined\n", param_names[n]);
+                       else
+                               lwsl_user("%s: (len %d) '%s'\n",
+                                   param_names[n],
+                                   lws_spa_get_length(pss->spa, n),
+                                   lws_spa_get_string(pss->spa, n));
+               }
+
+               /*
+                * Our response is to redirect to a static page.  We could
+                * have generated a dynamic html page here instead.
+                */
+
+               if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
+                                     (unsigned char *)"after-form1.html",
+                                     16, &p, end) < 0)
+                       return -1;
+
+               break;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", callback_http, sizeof(struct pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */              NULL,            /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */           "./mount-origin",       /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server POST file | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6f85f25
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8">
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/after-form1.html b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/after-form1.html
new file mode 100644 (file)
index 0000000..b01062e
--- /dev/null
@@ -0,0 +1,14 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Thanks for posting the form.<br>
+               <br>
+               The file you uploaded should have been saved in the current directory.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/index.html
new file mode 100644 (file)
index 0000000..06ffd24
--- /dev/null
@@ -0,0 +1,29 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http form POST file example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               When you POST the form below, you will see the values of the<br>
+               form parameters reported on the console log, and the file will
+               be uploaded and saved in the current working directory.
+               <p>
+               <form name=multipart action="/form1" method="post" enctype="multipart/form-data">
+                       Type some text:<br>
+                       <input type="text" name="text1"><br>
+                       <br>
+                       Select a file to upload: 
+                       <input type="file" name="file" id="file" size="20">&nbsp;
+                               <span id=file_info style="font-size:12pt;"></span><br>
+                       <br>
+                       <input type="submit" name="send" value="Submit">
+               </form>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-form-post-file/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-lwsac/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-form-post-lwsac/CMakeLists.txt
new file mode 100644 (file)
index 0000000..eec5b06
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-form-post-lwsac)
+set(SRCS minimal-http-server-form-post.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-lwsac/README.md b/minimal-examples/http-server/minimal-http-server-form-post-lwsac/README.md
new file mode 100644 (file)
index 0000000..910b4ce
--- /dev/null
@@ -0,0 +1,23 @@
+# lws minimal http server form POST lwsac
+
+Shows how to parse the form using an lwsac to hold the form data
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-form-post-lwsac
+[2018/03/29 08:29:41:7044] USER: LWS minimal http server form POST | visit http://localhost:7681
+[2018/03/29 08:29:41:7044] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/29 08:29:49:8601] USER: text1: (len 4) 'xxxx'
+[2018/03/29 08:29:49:8601] USER: send: (len 6) 'Submit'
+```
+
+Visit http://localhost:7681, submit the form.
+
+The form parameters are dumped to the log and you are redirected to a different page.
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-lwsac/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-form-post-lwsac/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-lwsac/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-form-post-lwsac/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c b/minimal-examples/http-server/minimal-http-server-form-post-lwsac/minimal-http-server-form-post.c
new file mode 100644 (file)
index 0000000..5acc255
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * lws-minimal-http-server-form-post-lwsac
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that performs POST with a couple
+ * of parameters.  It dumps the parameters to the console log and redirects
+ * to another page.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+/*
+ * Unlike ws, http is a stateless protocol.  This pss only exists for the
+ * duration of a single http transaction.  With http/1.1 keep-alive and http/2,
+ * that is unrelated to (shorter than) the lifetime of the network connection.
+ */
+struct pss {
+       struct lws_spa *spa;
+       struct lwsac *ac;
+};
+
+static int interrupted;
+
+static const char * const param_names[] = {
+       "text1",
+       "send",
+};
+
+enum enum_param_names {
+       EPN_TEXT1,
+       EPN_SEND,
+};
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+             void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
+               *p = start, *end = &buf[sizeof(buf) - 1];
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+
+               /*
+                * Manually report that our form target URL exists
+                *
+                * you can also do this by adding a mount for the form URL
+                * to the protocol with type LWSMPRO_CALLBACK, then no need
+                * to trap LWS_CALLBACK_HTTP.
+                */
+
+               if (!strcmp((const char *)in, "/form1"))
+                       /* assertively allow it to exist in the URL space */
+                       return 0;
+
+               /* default to 404-ing the URL if not mounted */
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY:
+
+               /* create the POST argument parser if not already existing */
+
+               if (!pss->spa) {
+                       lws_spa_create_info_t i;
+
+                       memset(&i, 0, sizeof(i));
+                       i.param_names = param_names;
+                       i.count_params = LWS_ARRAY_SIZE(param_names);
+                       i.ac = &pss->ac;
+                       i.ac_chunk_size = 512;
+
+                       pss->spa = lws_spa_create_via_info(wsi, &i); /* no file upload */
+                       if (!pss->spa)
+                               return -1;
+               }
+
+               /* let it parse the POST data */
+
+               if (lws_spa_process(pss->spa, in, (int)len))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+
+               /* inform the spa no more payload data coming */
+
+               lwsl_user("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+               lws_spa_finalize(pss->spa);
+
+               /* we just dump the decoded things to the log */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
+                       if (!lws_spa_get_string(pss->spa, n))
+                               lwsl_user("%s: undefined\n", param_names[n]);
+                       else
+                               lwsl_user("%s: (len %d) '%s'\n",
+                                   param_names[n],
+                                   lws_spa_get_length(pss->spa, n),
+                                   lws_spa_get_string(pss->spa, n));
+               }
+
+               lwsac_free(&pss->ac);
+
+               /*
+                * Our response is to redirect to a static page.  We could
+                * have generated a dynamic html page here instead.
+                */
+
+               if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
+                                     (unsigned char *)"after-form1.html",
+                                     16, &p, end) < 0)
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               lwsac_free(&pss->ac);
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", callback_http, sizeof(struct pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */              NULL,            /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */           "./mount-origin",       /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server POST | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-form-post/CMakeLists.txt
new file mode 100644 (file)
index 0000000..32a9f76
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-form-post)
+set(SRCS minimal-http-server-form-post.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/README.md b/minimal-examples/http-server/minimal-http-server-form-post/README.md
new file mode 100644 (file)
index 0000000..b89353c
--- /dev/null
@@ -0,0 +1,21 @@
+# lws minimal http server form POST
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-form-post
+[2018/03/29 08:29:41:7044] USER: LWS minimal http server form POST | visit http://localhost:7681
+[2018/03/29 08:29:41:7044] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/29 08:29:49:8601] USER: text1: (len 4) 'xxxx'
+[2018/03/29 08:29:49:8601] USER: send: (len 6) 'Submit'
+```
+
+Visit http://localhost:7681, submit the form.
+
+The form parameters are dumped to the log and you are redirected to a different page.
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-form-post/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-form-post/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c b/minimal-examples/http-server/minimal-http-server-form-post/minimal-http-server-form-post.c
new file mode 100644 (file)
index 0000000..c7d0943
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * lws-minimal-http-server-form-post
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that performs POST with a couple
+ * of parameters.  It dumps the parameters to the console log and redirects
+ * to another page.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+/*
+ * Unlike ws, http is a stateless protocol.  This pss only exists for the
+ * duration of a single http transaction.  With http/1.1 keep-alive and http/2,
+ * that is unrelated to (shorter than) the lifetime of the network connection.
+ */
+struct pss {
+       struct lws_spa *spa;
+};
+
+static int interrupted;
+
+static const char * const param_names[] = {
+       "text1",
+       "send",
+};
+
+enum enum_param_names {
+       EPN_TEXT1,
+       EPN_SEND,
+};
+
+static int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+             void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
+               *p = start, *end = &buf[sizeof(buf) - 1];
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+
+               /*
+                * Manually report that our form target URL exists
+                *
+                * you can also do this by adding a mount for the form URL
+                * to the protocol with type LWSMPRO_CALLBACK, then no need
+                * to trap LWS_CALLBACK_HTTP.
+                */
+
+               if (!strcmp((const char *)in, "/form1"))
+                       /* assertively allow it to exist in the URL space */
+                       return 0;
+
+               /* default to 404-ing the URL if not mounted */
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY:
+
+               /* create the POST argument parser if not already existing */
+
+               if (!pss->spa) {
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                       LWS_ARRAY_SIZE(param_names), 1024,
+                                       NULL, NULL); /* no file upload */
+                       if (!pss->spa)
+                               return -1;
+               }
+
+               /* let it parse the POST data */
+
+               if (lws_spa_process(pss->spa, in, (int)len))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+
+               /* inform the spa no more payload data coming */
+
+               lwsl_user("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+               lws_spa_finalize(pss->spa);
+
+               /* we just dump the decoded things to the log */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
+                       if (!lws_spa_get_string(pss->spa, n))
+                               lwsl_user("%s: undefined\n", param_names[n]);
+                       else
+                               lwsl_user("%s: (len %d) '%s'\n",
+                                   param_names[n],
+                                   lws_spa_get_length(pss->spa, n),
+                                   lws_spa_get_string(pss->spa, n));
+               }
+
+               /*
+                * Our response is to redirect to a static page.  We could
+                * have generated a dynamic html page here instead.
+                */
+
+               if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
+                                     (unsigned char *)"after-form1.html",
+                                     16, &p, end) < 0)
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", callback_http, sizeof(struct pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */              NULL,            /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */           "./mount-origin",       /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server POST | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6f85f25
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8">
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/after-form1.html b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/after-form1.html
new file mode 100644 (file)
index 0000000..938ccb7
--- /dev/null
@@ -0,0 +1,12 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Thanks for posting the form.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/index.html
new file mode 100644 (file)
index 0000000..12ab4f4
--- /dev/null
@@ -0,0 +1,23 @@
+<html>
+ <head>
+  <meta charset="utf-8" http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http POST example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               When you POST the form below, you will see the values of the<br>
+               form parameters reported on the console log.
+               <p>
+               <form action="/form1" method="post">
+                       Type some text:<br>
+                       <input type="text" name="text1"><br>
+                       <input type="submit" name="send" value="Submit">
+               </form>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-form-post/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-fulltext-search/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6032845
--- /dev/null
@@ -0,0 +1,82 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-fulltext-search)
+set(SRCS minimal-http-server.c)
+
+include_directories(../../../plugins)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITH_FTS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/README.md b/minimal-examples/http-server/minimal-http-server-fulltext-search/README.md
new file mode 100644 (file)
index 0000000..cc8794b
--- /dev/null
@@ -0,0 +1,18 @@
+# lws minimal http server
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server
+[2018/03/04 09:30:02:7986] USER: LWS minimal http server | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681
+
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/lws-fts.index b/minimal-examples/http-server/minimal-http-server-fulltext-search/lws-fts.index
new file mode 100644 (file)
index 0000000..b38484b
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-fulltext-search/lws-fts.index differ
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/minimal-http-server.c b/minimal-examples/http-server/minimal-http-server-fulltext-search/minimal-http-server.c
new file mode 100644 (file)
index 0000000..29b3ec3
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * lws-minimal-http-server-fts
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates how to use lws full-text search
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include <protocol_fulltext_demo.c>
+
+const char *index_filepath = "./lws-fts.index";
+static int interrupted;
+
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_FULLTEXT_DEMO,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static struct lws_protocol_vhost_options pvo_idx = {
+       NULL,
+       NULL,
+       "indexpath",            /* pvo name */
+       NULL    /* filled in at runtime */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,           /* "next" pvo linked-list */
+       &pvo_idx,       /* "child" pvo linked-list */
+       "lws-test-fts", /* protocol name we belong to on this vhost */
+       ""              /* ignored */
+};
+
+/* override the default mount for /fts in the URL space */
+
+static const struct lws_http_mount mount_fts = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/fts",         /* mountpoint URL */
+       /* .origin */                   NULL,   /* protocol */
+       /* .def */                      NULL,
+       /* .protocol */                 "lws-test-fts",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_CALLBACK, /* dynamic */
+       /* .mountpoint_len */           4,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_fts,     /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server fulltext search | "
+                 "visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info);
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       pvo_idx.value = index_filepath;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/dorian-gray-wikipedia.jpg b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/dorian-gray-wikipedia.jpg
new file mode 100644 (file)
index 0000000..00e54da
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/dorian-gray-wikipedia.jpg differ
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/index.html
new file mode 100644 (file)
index 0000000..d1f7d25
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ <script src="lws-fts.js"></script>
+ <link rel="stylesheet" type="text/css" href="lws-fts.css"/>
+</head>
+
+<body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+<div class="searchbg">
+
+<span class="title">The Picture of Dorian Gray</span><br>
+
+       <table class="searchtable">
+               <tr>
+                <td rowspan='2'><img class='eyeglass'></td>
+                   <td class='searchboxtitle'>Fulltext search<br>
+                       <input type='text' id='lws_fts' class='nonviable'
+                               name='lws_fts' maxlength='80'>
+               <div class='acomplete' id='acomplete'></div>
+               </td></tr></table>
+
+       <div class='searchresults' id='searchresults'></div>
+</div>
+</body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/lws-fts.css b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/lws-fts.css
new file mode 100644 (file)
index 0000000..c1bfdb3
--- /dev/null
@@ -0,0 +1,154 @@
+span.title {
+       font-size: 24pt;
+       text-align: center;
+}
+
+img.eyeglass {
+       display: inline-block;
+        background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyMjQuMDUyMjIgMjU2LjQ4MjE1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9IjkwOS4xMSIgeDI9Ijk5Ni42OSIgeTE9Ii03MS4wMzUiIHkyPSItMzEuNjEzIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC05MTYuMDYgMTA1LjU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjNGQ0ZDRkIiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iIzgwODA4MCIgc3RvcC1vcGFjaXR5PSIuMDgyNjQ1IiBvZmZzZXQ9IjEiLz4KCQk8L2xpbmVhckdyYWRpZW50PgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjEwMjAuMyIgeDI9IjEwMTcuNiIgeTE9Ii0zNy44NjMiIHkyPSItMzUuMzgzIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC05MTYuMDYgMTA1LjU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjOTk5IiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iIzk5OSIgc3RvcC1vcGFjaXR5PSIwIiBvZmZzZXQ9IjEiLz4KCQk8L2xpbmVhckdyYWRpZW50PgoJCTxmaWx0ZXIgaWQ9ImMiIHg9Ii0uMDg1NTgiIHk9Ii0uMTIyNiIgd2lkdGg9IjEuMTcxMiIgaGVpZ2h0PSIxLjI0NTIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNDc2NDQ5MzkiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJkIiB4PSItLjIxNjYxIiB5PSItLjI5NDQ1IiB3aWR0aD0iMS40MzMyIiBoZWlnaHQ9IjEuNTg4OSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KCQkJPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMS40NjkwMTk0Ii8+CgkJPC9maWx0ZXI+CgkJPGZpbHRlciBpZD0iZSIgeD0iLS4wNTkyMjgiIHk9Ii0uMDc3NjUyIiB3aWR0aD0iMS4xMTg1IiBoZWlnaHQ9IjEuMTU1MyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KCQkJPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMy40NzIzODc4Ii8+CgkJPC9maWx0ZXI+CgkJPGZpbHRlciBpZD0iZiIgeD0iLS4wNzUwNTMiIHk9Ii0uMDY3MDAzIiB3aWR0aD0iMS4xNTAxIiBoZWlnaHQ9IjEuMTM0IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgoJCQk8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyLjMwNjQ1MzgiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJqIiB4PSItLjcwMzc4IiB5PSItLjY5MDc0IiB3aWR0aD0iMi40MDc2IiBoZWlnaHQ9IjIuMzgxNSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KCQkJPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNC4xMTIwODgiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJpIiB4PSItLjA2MSIgeT0iLS4wOTUzNDUiIHdpZHRoPSIxLjEyMiIgaGVpZ2h0PSIxLjE5MDciIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNzU4ODkwNDIiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJoIiB4PSItLjA3OTU4NyIgeT0iLS4xNjc5NyIgd2lkdGg9IjEuMTU5MiIgaGVpZ2h0PSIxLjMzNTkiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNTgwMDg2MTUiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJnIiB4PSItLjAzMjI5NSIgeT0iLS4wMjgwMDkiIHdpZHRoPSIxLjA2NDYiIGhlaWdodD0iMS4wNTYiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjEuMjM0MzQ2OCIvPgoJCTwvZmlsdGVyPgoJCTxmaWx0ZXIgaWQ9ImsiIHg9Ii0uMTY2NDgiIHk9Ii0uMzQ5ODEiIHdpZHRoPSIxLjMzMyIgaGVpZ2h0PSIxLjY5OTYiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjMuODg4NzYzNiIvPgoJCTwvZmlsdGVyPgoJCTxmaWx0ZXIgaWQ9Im0iIHg9Ii0uMDM1OTIyIiB5PSItLjA0NzIxMSIgd2lkdGg9IjEuMDcxOCIgaGVpZ2h0PSIxLjA5NDQiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMDUzNDk2NiIvPgoJCTwvZmlsdGVyPgoJCTxmaWx0ZXIgaWQ9ImwiIHg9Ii0uMDM1OTIyIiB5PSItLjA0NzIxMSIgd2lkdGg9IjEuMDcxOCIgaGVpZ2h0PSIxLjA5NDQiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMTg1OTg3NSIvPgoJCTwvZmlsdGVyPgoJPC9kZWZzPgoJPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTkuMDQ0IDUzLjQ5MSkiPgoJCTxwYXRoIGQ9Im0tOC44MDY4IDI2LjA4MmMtMy4wMzI4LTM1LjM1MyAyOC43MTItNjIuMTY3IDY0LjY1My02NC4wMTcgMjIuOTg5LTIuNTkzMiA2Ni44MTkgOS4wNDY5IDgyLjk1NSA0Ni4yOTMtMS4yODAxLTIxLjkyMy0yNS45NjctNjguMjkxLTEwOC43OC00OS4yOTMtMjUuMzc2IDguMjg5Mi01MS4zMzYgMzUuMzQ3LTQxLjQxMSA2My41NTZ6IiBmaWxsPSIjOTk5Ii8+CgkJPGVsbGlwc2UgY3g9IjYzLjgzMiIgY3k9IjEzLjY5NyIgcng9Ijc1LjgwNyIgcnk9IjU3LjY3OSIgZmlsbD0iI2IzYjNiMyIgZmlsbC1vcGFjaXR5PSIuNDQyMTUiLz4KCQk8cGF0aCBkPSJtNjIuNzU1LTQ3LjQ3MmE3Ny43ODQgNTkuMTgzIDAgMCAwIC03Ny43ODMgNTkuMTgzIDc3Ljc4NCA1OS4xODMgMCAwIDAgNzcuNzgzIDU5LjE4MyA3Ny43ODQgNTkuMTgzIDAgMCAwIDc3Ljc4NCAtNTkuMTgzIDc3Ljc4NCA1OS4xODMgMCAwIDAgLTc3Ljc4NCAtNTkuMTgzem0wLjEyNjEgMi4xMDI3YTc1LjgxNyA1Ny42ODcgMCAwIDEgNzUuODE3IDU3LjY4NiA3NS44MTcgNTcuNjg3IDAgMCAxIC03NS44MTcgNTcuNjg3IDc1LjgxNyA1Ny42ODcgMCAwIDEgLTc1LjgxNyAtNTcuNjg3IDc1LjgxNyA1Ny42ODcgMCAwIDEgNzUuODE3IC01Ny42ODZ6IiBmaWxsPSIjODA4MDgwIi8+CgkJPGVsbGlwc2UgY3g9IjYzLjYxMSIgY3k9IjE3LjE4OCIgcng9IjczLjAyNSIgcnk9IjU1LjU2MiIgZmlsbD0iI2IzYjNiMyIgZmlsbC1vcGFjaXR5PSIuNTUzNzIiIGZpbHRlcj0idXJsKCNsKSIvPgoJCTxwYXRoIGQ9Im0xMzkuNDMgMTguMjI0Yy0xLjEyMjUgMzYuMDg2LTMzLjMxMyA2My4xMjUtNzUuMzMxIDYzLjEyNS00Mi4wMTcgMC03Ny45NS0zNC43MS03Ny45NS02Ni42NzkgNy4yOTY1IDIxLjUzOCAyNS4xMTEgNTIuMTA4IDc4LjY5OCA1Mi4zMDkgNTMuOTYyLTIuNjA1NCA2OS45MDUtMzQuODg3IDc0LjU4Mi00OC43NTR6IiBmaWxsPSIjODA4MDgwIi8+CgkJPGVsbGlwc2UgY3g9IjYwLjQ5NiIgY3k9IjEyLjEyOSIgcng9IjY4LjU5OSIgcnk9IjUyLjE5NSIgZmlsbD0iI2VjZWNlYyIgZmlsbC1vcGFjaXR5PSIuNTQ1NDUiIGZpbHRlcj0idXJsKCNtKSIvPgoJCTxlbGxpcHNlIHRyYW5zZm9ybT0ibWF0cml4KC45MTQ2MSAtLjUzNjUyIC41MzY1MiAuOTE0NjEgLTg5NC4zNCA0NDguMjkpIiBjeD0iOTY5Ljk1IiBjeT0iNjMuNzI0IiByeD0iMjguNDQzIiByeT0iMTIuNDM1IiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii44MDE2NSIgZmlsdGVyPSJ1cmwoI2spIi8+CgkJPHBhdGggZD0ibTg2LjU0NSA3NS4wODYgMy42NjU0IDUuMjU0OCAyLjAwNDggMS42ODM3IDIuMDkxMSAwLjc2Mzg1IDAuOTQ2NiAwLjQ0MTc0IDkuMjMyMyAxMy43MzQgMS4xMDUyIDUuNDI2NSA2Mi43MTIgODkuOTA3YzguMTA1IDkuNjI1MiAyOC45OTUgMTIuMDIgMzIuMzM3LTMuMDkwNiAzLjE1MzItMTMuMTg1LTkuNDY5Mi0yMS42NTItMTYuNDY3LTMwLjc0OC0xOS41NDgtMjIuMzU4LTQ3LjU3LTU1LjE3OS01OC4yODgtNjcuNDQ1LTMuNTc0My0yLjIxMjQtMy4wOTY5LTAuOTY3NTUtNS45NDg3LTMuMjQ2Ni0yLjg1MTktMi4yNzkxLTcuNDI5Mi04LjAxODgtMTAuMjAyLTExLjEyOC0wLjgzNjMtMC45Mzc4Ny0wLjE5ODQtNC4xMDIyLTAuOTg4NS00Ljk4NTktMi41OTg4LTIuOTA2Ny00LjkwMy01LjQ3MTEtNi42MTkzLTcuMzY4NS02Ljc2NjctNC4xOTItMTguMTk3IDIuNDc2MS0xNS41ODIgMTAuODAyeiIgZmlsbD0iIzY2NiIvPgoJCTxnIGZpbGw9IiM0ZDRkNGQiPgoJCQk8cGF0aCBkPSJtMTEzLjQ5IDk0LjI2MmMzLjUwNzEgOS4xNTc4IDYuMDYwNiAxOC44NjUgMTAuODM0IDI3LjQzMiA2Ljg4NTggMTAuMTY2IDEyLjYyNCAyMS4yMzMgMjEuNDE2IDI5Ljk0NyA1LjUyNDggNS44Mzk1IDEwLjIwOSAxMi42NjggMTcuMzU4IDE2LjY2NCA1Ljk5MyA2LjI1NCAxNC45MjUgNS41MDU2IDIxLjk5MSA5LjQ3MTIgNy4xMjEzIDIuNDAzOCAxNi4yMTUgMTIuMjIgMTAuNDQgMTkuMTk3LTguMjk5MiA1Ljk1NjItMTkuOTU3IDIuNDY3Mi0yNi44NDMtNC4xNDItNi41OTU5LTguMjQ1Mi0xMi4xODUtMTcuNDA5LTE4LjM5LTI2LjAxLTE0Ljg4OC0yMS41MDUtMjkuNzEtNDMuMDc2LTQ0LjU5OC02NC41OCAxLjMyMjQtMy44ODcgMy40MjM2LTYuNjg0NiA3Ljc5MTYtNy45Nzg3eiIgZmlsdGVyPSJ1cmwoI2cpIi8+CgkJCTxwYXRoIGQ9Im0xMDguNDMgNzIuMTg2Yy0yLjA0NzctMC40OTI2NS00LjEyNDItMS4yMTE1LTYuMjQyNS0wLjY0OTA3LTIuODIxNiAwLjI1ODIzLTUuMjYxOSAxLjkyMjMtNy4xMjgyIDMuOTY2Ni0xLjA5NDggMC45MjI5MyAxLjU3NS0wLjM3OTc2IDIuMTg4NC0wLjQxOCAyLjA4ODItMC41MTk4NSA0LjI5MDItMC40OTI3MiA2LjQyNDctMC4zMTQ5MyAzLjAyNTUgMC4yMTIyMSA1LjMzMDUgMi4zMTc3IDcuNTYzNiA0LjEzNTIgMC42MTI0IDAuNDkyMzYgMS42OTg4IDEuMjIzMiAwLjU1MiAwLjE5MjkyLTAuODQ4NS0xLjE4MjYtMi41OTM1LTEuOTIwNC0yLjg3NjctMy4zODM0LTAuMTYwNC0xLjE3NjQtMC4zMjA5LTIuMzUyOC0wLjQ4MTMtMy41MjkyeiIgZmlsdGVyPSJ1cmwoI2gpIi8+CgkJCTxwYXRoIGQ9Im0xMTguMzUgODYuMjE3Yy0yLjQzODggMC4wMjQtNC45NzU3LTAuMjc1MjYtNy4yNTQ3IDAuODEzNzYtMi43MDYzIDAuODYzNzEtNC44OTc1IDIuMDc2MS04LjI3MzYgMS45OTI2LTEuMDk4NS0wLjE4MjktMi4zNTk0LTEuMTY2Mi0zLjgzMjUtMi4zNTM0LTEuNTU4My0xLjI1NTgtMy4yMjE1LTMuMjE1My0zLjY5NS0zLjMyMjIgMi45MTQ1IDQuNDE3NSA2LjEwOTYgOS4wMjIyIDkuMDI0MiAxMy40NCAwLjM1NSAxLjU2MTUgMC43MTQ4IDQuOTY2OCAxLjMwMjEgNS42NjI4IDIuNTQzOS0zLjI1NDcgNS4wMTgzLTYuNzQ4IDguNzIzMy04Ljc3OTggMi42ODQ4LTEuNDkyNiA1LjUwOTgtMy40MDAyIDguNzQ1MS0yLjk1NDQgMC45MDg5IDAuMTMyOTUgMy4zOTA3IDAuMjE4NDEgMS4xNTQ5LTAuNTU4MS0yLjEwODctMS4wNjU2LTQuMzA2NS0yLjEzNTUtNS44OTM4LTMuOTQxeiIgZmlsdGVyPSJ1cmwoI2kpIi8+CgkJPC9nPgoJCTxlbGxpcHNlIGN4PSIxODguMTMiIGN5PSIxODUuMTciIHJ4PSI3LjAxMTUiIHJ5PSI3LjE0MzciIGZpbGw9IiM5OTkiIGZpbHRlcj0idXJsKCNqKSIvPgoJCTxnIGZpbGw9IiNiM2IzYjMiPgoJCQk8cGF0aCBkPSJtMTI1LjIgOTMuNTUxYy0xLjkxLTAuMjc5MjItMy43NTc3LTAuMDIwNi01LjU1NjMgMC42NjE0Ni0wLjg2MTcgMS4zMzE2LTAuODQ5MyAyLjUzNzkgMC40NDI5IDMuNTYwOSAxOS45NjEgMjUuNDQ4IDM5LjkyMSA1MC44OTYgNTkuODgyIDc2LjM0MyAyLjgwNzIgMC42MTM3IDUuNjEwMiAxLjQ4ODIgOC40MiAxLjkzOSAxLjU3MDQtMC4zNDM4MyAzLjQwMi0wLjQ4OTAzIDQuMzg1MS0xLjY3NzkgMC4zMDg3LTIuMTczOS0xLjkzMzctMy4zNDY5LTIuOTkxNS00Ljk3NDEtMjEuNTI4LTI1LjI4NC00My4wNTUtNTAuNTY4LTY0LjU4Mi03NS44NTN6IiBmaWx0ZXI9InVybCgjZikiLz4KCQkJPHBhdGggZD0ibTEwMy45MS0zMi4zOTFjNy4yMzI3IDEzLjM0OCAxMS44MiAzMC4wNjkgNi4wMTA0IDQ0LjY1LTguMjg4MiAxNi4yNTktMjQuNDQ0IDI3LjQ4NC00Mi42MjEgMjkuNTc1LTIwLjU0MSA1LjMzMTktNDMuNjc4IDUuNjI3OS02Mi4zMzUtNS41NDYzLTE3LjgyOC01LjEyNTQgNC40MzQ4IDE0LjgzNiAxMC4xNTkgMTcuNjQ0IDMxLjMyOSAxOS4yMjUgNzUuMDUyIDE2LjY0NyAxMDQuMDUtNS45NDA5IDEyLjE3Ni0xMC44MjggMjIuOTY0LTI2LjMyNSAxOC43OTQtNDMuNDQ4LTMuOTA5NS0yMi4yNTYtMjMuNzAzLTM4LjEwNC00NC4xMzQtNDUuMDYgMy4xMTc0IDMuMDA1NCA2Ljg3NzEgNS4yMTggMTAuMDc2IDguMTI3MXoiIGZpbGwtb3BhY2l0eT0iLjQwOTA5IiBmaWx0ZXI9InVybCgjZSkiLz4KCQkJPHBhdGggZD0ibTEwMi4wNyA3NC45OTJoNC40OTAybDIuOTkzNCAxLjY4MzggOC43OTMxIDkuOTE1Ny00LjExNTktMC45MzU0NC0yLjYxOTIgMS4zMDk2eiIgZmlsbC1vcGFjaXR5PSIuNDA5MDkiIGZpbHRlcj0idXJsKCNkKSIvPgoJCTwvZz4KCQk8Zz4KCQkJPHBhdGggZD0ibTk1Ljc3IDYyLjk5MiA2LjQxNjEgOC40MDA1IDIuOTEwNS0wLjA2NjEgMi4wNTA1IDAuMzMwNzMgMS45ODQ0IDAuNjYxNDYtMC4zOTY5LTAuNzkzNzUtNi40MTYyLTcuMjA5OS0xLjM4OS0wLjcyNzYxLTIuMjQ5LTAuNTI5MTYtMS4wNTgzLTAuMDY2MnoiIGZpbGw9InVybCgjYikiIGZpbHRlcj0idXJsKCNjKSIvPgoJCQk8cGF0aCBkPSJtLTE0LjM2MyAxNC4xNzYgMC43OTM3NSAxMC4wNTQgNS4wMjcxIDExLjY0MiA1LjAyNzEgMTAuMDU0IDkuNzg5NiAxMC44NDggMTIuNDM1IDEwLjg0OCAxNC44MTcgNy42NzI5IDEyLjk2NSAzLjcwNDIgMTEuOTA2IDIuMTE2N2g4LjczMTJsMjAuNjM4LTIuNjQ1OCAxLjA1ODMtMS4wNTgzLTIuMzgxMy0zLjQzOTYgMC41MjkyLTUuODIwOCAzLjQzOTYtMi4zODEyIDMuNzA0Mi0yLjExNjcgNy45Mzc1LTQuNDk3OS0xNS4wODEgNC43NjI1LTE3LjcyNyAzLjE3NS0yMC42MzgtMS4wNTgzLTE2LjY2OS0zLjcwNDItMTYuOTMzLTguMjAyMS0xMS4xMTItOC40NjY3LTExLjkwNi0xNi40MDR6IiBmaWxsPSJ1cmwoI2EpIi8+CgkJCTx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDUyLjY0MikiIHg9IjIyMS4yMzgzNCIgeT0iLTM0LjUzODExMyIgZG9taW5hbnQtYmFzZWxpbmU9ImF1dG8iIGZpbGw9IiM2NjY2NjYiIGZvbnQtZmFtaWx5PSInT3BlbiBTYW5zJyIgZm9udC1zaXplPSI4LjkxMDNweCIgbGV0dGVyLXNwYWNpbmc9IjBweCIgc3Ryb2tlLXdpZHRoPSIuMDQ2NDA4IiB0ZXh0LWFsaWduPSJjZW50ZXIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0iZm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDtmb250LXZhcmlhbnQtYWx0ZXJuYXRlczpub3JtYWw7Zm9udC12YXJpYW50LWNhcHM6bm9ybWFsO2ZvbnQtdmFyaWFudC1saWdhdHVyZXM6bm9ybWFsO2ZvbnQtdmFyaWFudC1udW1lcmljOm5vcm1hbDtmb250LXZhcmlhbnQtcG9zaXRpb246bm9ybWFsO2xpbmUtaGVpZ2h0OjEuMjU7c2hhcGUtcGFkZGluZzowO3RleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwMDAwO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC1vcmllbnRhdGlvbjptaXhlZDt0ZXh0LXRyYW5zZm9ybTpub25lO3doaXRlLXNwYWNlOm5vcm1hbCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjIyMS4yMzgzNCIgeT0iLTM0LjUzODExMyIgZmlsbD0iIzY2NjY2NiIgc3Ryb2tlLXdpZHRoPSIuMDQ2NDA4Ij5naW90b2hhc2hpPC90c3Bhbj48L3RleHQ+CgkJPC9nPgoJPC9nPgo8L3N2Zz4=");
+        width:0px;
+        height:0px;
+        padding:2em 1.6em;
+        vertical-align:middle;
+       margin-right: 0.1em;
+        background-repeat: no-repeat;
+        color: rgba(0, 0, 0, 0);
+}
+
+img.spinner {
+       display: inline-block;
+       background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiCiAgaWQ9InN2Zy1zcGlubmVyIiAKICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiCiAgeD0iMHB4IiAKICB5PSIwcHgiIAogIHZpZXdCb3g9Ii0xMTIgLTEyOCA0NDggNTEyIgogIHhtbDpzcGFjZT0icHJlc2VydmUiPgoKCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9IjkwOS4xMSIgeDI9Ijk5Ni42OSIgeTE9Ii03MS4wMzUiIHkyPSItMzEuNjEzIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC05MTYuMDYgMTA1LjU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjNGQ0ZDRkIiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iIzgwODA4MCIgc3RvcC1vcGFjaXR5PSIuMDgyNjQ1IiBvZmZzZXQ9IjEiLz4KCQk8L2xpbmVhckdyYWRpZW50PgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjEwMjAuMyIgeDI9IjEwMTcuNiIgeTE9Ii0zNy44NjMiIHkyPSItMzUuMzgzIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC05MTYuMDYgMTA1LjU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjOTk5IiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iIzk5OSIgc3RvcC1vcGFjaXR5PSIwIiBvZmZzZXQ9IjEiLz4KCQk8L2xpbmVhckdyYWRpZW50PgoJCTxmaWx0ZXIgaWQ9ImMiIHg9Ii0uMDg1NTgiIHk9Ii0uMTIyNiIgd2lkdGg9IjEuMTcxMiIgaGVpZ2h0PSIxLjI0NTIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNDc2NDQ5MzkiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJkIiB4PSItLjIxNjYxIiB5PSItLjI5NDQ1IiB3aWR0aD0iMS40MzMyIiBoZWlnaHQ9IjEuNTg4OSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KCQkJPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMS40NjkwMTk0Ii8+CgkJPC9maWx0ZXI+CgkJPGZpbHRlciBpZD0iZSIgeD0iLS4wNTkyMjgiIHk9Ii0uMDc3NjUyIiB3aWR0aD0iMS4xMTg1IiBoZWlnaHQ9IjEuMTU1MyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KCQkJPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMy40NzIzODc4Ii8+CgkJPC9maWx0ZXI+CgkJPGZpbHRlciBpZD0iZiIgeD0iLS4wNzUwNTMiIHk9Ii0uMDY3MDAzIiB3aWR0aD0iMS4xNTAxIiBoZWlnaHQ9IjEuMTM0IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgoJCQk8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyLjMwNjQ1MzgiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJqIiB4PSItLjcwMzc4IiB5PSItLjY5MDc0IiB3aWR0aD0iMi40MDc2IiBoZWlnaHQ9IjIuMzgxNSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KCQkJPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNC4xMTIwODgiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJpIiB4PSItLjA2MSIgeT0iLS4wOTUzNDUiIHdpZHRoPSIxLjEyMiIgaGVpZ2h0PSIxLjE5MDciIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNzU4ODkwNDIiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJoIiB4PSItLjA3OTU4NyIgeT0iLS4xNjc5NyIgd2lkdGg9IjEuMTU5MiIgaGVpZ2h0PSIxLjMzNTkiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjAuNTgwMDg2MTUiLz4KCQk8L2ZpbHRlcj4KCQk8ZmlsdGVyIGlkPSJnIiB4PSItLjAzMjI5NSIgeT0iLS4wMjgwMDkiIHdpZHRoPSIxLjA2NDYiIGhlaWdodD0iMS4wNTYiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjEuMjM0MzQ2OCIvPgoJCTwvZmlsdGVyPgoJCTxmaWx0ZXIgaWQ9ImsiIHg9Ii0uMTY2NDgiIHk9Ii0uMzQ5ODEiIHdpZHRoPSIxLjMzMyIgaGVpZ2h0PSIxLjY5OTYiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjMuODg4NzYzNiIvPgoJCTwvZmlsdGVyPgoJCTxmaWx0ZXIgaWQ9Im0iIHg9Ii0uMDM1OTIyIiB5PSItLjA0NzIxMSIgd2lkdGg9IjEuMDcxOCIgaGVpZ2h0PSIxLjA5NDQiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMDUzNDk2NiIvPgoJCTwvZmlsdGVyPgoJCTxmaWx0ZXIgaWQ9ImwiIHg9Ii0uMDM1OTIyIiB5PSItLjA0NzIxMSIgd2lkdGg9IjEuMDcxOCIgaGVpZ2h0PSIxLjA5NDQiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CgkJCTxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuMTg1OTg3NSIvPgoJCTwvZmlsdGVyPgoJPC9kZWZzPgoJPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTkuMDQ0IDUzLjQ5MSkiPgoJCTxwYXRoIGQ9Im0tOC44MDY4IDI2LjA4MmMtMy4wMzI4LTM1LjM1MyAyOC43MTItNjIuMTY3IDY0LjY1My02NC4wMTcgMjIuOTg5LTIuNTkzMiA2Ni44MTkgOS4wNDY5IDgyLjk1NSA0Ni4yOTMtMS4yODAxLTIxLjkyMy0yNS45NjctNjguMjkxLTEwOC43OC00OS4yOTMtMjUuMzc2IDguMjg5Mi01MS4zMzYgMzUuMzQ3LTQxLjQxMSA2My41NTZ6IiBmaWxsPSIjOTk5Ii8+CgkJPGVsbGlwc2UgY3g9IjYzLjgzMiIgY3k9IjEzLjY5NyIgcng9Ijc1LjgwNyIgcnk9IjU3LjY3OSIgZmlsbD0iI2IzYjNiMyIgZmlsbC1vcGFjaXR5PSIuNDQyMTUiLz4KCQk8cGF0aCBkPSJtNjIuNzU1LTQ3LjQ3MmE3Ny43ODQgNTkuMTgzIDAgMCAwIC03Ny43ODMgNTkuMTgzIDc3Ljc4NCA1OS4xODMgMCAwIDAgNzcuNzgzIDU5LjE4MyA3Ny43ODQgNTkuMTgzIDAgMCAwIDc3Ljc4NCAtNTkuMTgzIDc3Ljc4NCA1OS4xODMgMCAwIDAgLTc3Ljc4NCAtNTkuMTgzem0wLjEyNjEgMi4xMDI3YTc1LjgxNyA1Ny42ODcgMCAwIDEgNzUuODE3IDU3LjY4NiA3NS44MTcgNTcuNjg3IDAgMCAxIC03NS44MTcgNTcuNjg3IDc1LjgxNyA1Ny42ODcgMCAwIDEgLTc1LjgxNyAtNTcuNjg3IDc1LjgxNyA1Ny42ODcgMCAwIDEgNzUuODE3IC01Ny42ODZ6IiBmaWxsPSIjODA4MDgwIi8+CgkJPGVsbGlwc2UgY3g9IjYzLjYxMSIgY3k9IjE3LjE4OCIgcng9IjczLjAyNSIgcnk9IjU1LjU2MiIgZmlsbD0iI2IzYjNiMyIgZmlsbC1vcGFjaXR5PSIuNTUzNzIiIGZpbHRlcj0idXJsKCNsKSIvPgoJCTxwYXRoIGQ9Im0xMzkuNDMgMTguMjI0Yy0xLjEyMjUgMzYuMDg2LTMzLjMxMyA2My4xMjUtNzUuMzMxIDYzLjEyNS00Mi4wMTcgMC03Ny45NS0zNC43MS03Ny45NS02Ni42NzkgNy4yOTY1IDIxLjUzOCAyNS4xMTEgNTIuMTA4IDc4LjY5OCA1Mi4zMDkgNTMuOTYyLTIuNjA1NCA2OS45MDUtMzQuODg3IDc0LjU4Mi00OC43NTR6IiBmaWxsPSIjODA4MDgwIi8+CgkJPGVsbGlwc2UgY3g9IjYwLjQ5NiIgY3k9IjEyLjEyOSIgcng9IjY4LjU5OSIgcnk9IjUyLjE5NSIgZmlsbD0iI2VjZWNlYyIgZmlsbC1vcGFjaXR5PSIuNTQ1NDUiIGZpbHRlcj0idXJsKCNtKSIvPgoJCTxlbGxpcHNlIHRyYW5zZm9ybT0ibWF0cml4KC45MTQ2MSAtLjUzNjUyIC41MzY1MiAuOTE0NjEgLTg5NC4zNCA0NDguMjkpIiBjeD0iOTY5Ljk1IiBjeT0iNjMuNzI0IiByeD0iMjguNDQzIiByeT0iMTIuNDM1IiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii44MDE2NSIgZmlsdGVyPSJ1cmwoI2spIi8+CgkJPHBhdGggZD0ibTg2LjU0NSA3NS4wODYgMy42NjU0IDUuMjU0OCAyLjAwNDggMS42ODM3IDIuMDkxMSAwLjc2Mzg1IDAuOTQ2NiAwLjQ0MTc0IDkuMjMyMyAxMy43MzQgMS4xMDUyIDUuNDI2NSA2Mi43MTIgODkuOTA3YzguMTA1IDkuNjI1MiAyOC45OTUgMTIuMDIgMzIuMzM3LTMuMDkwNiAzLjE1MzItMTMuMTg1LTkuNDY5Mi0yMS42NTItMTYuNDY3LTMwLjc0OC0xOS41NDgtMjIuMzU4LTQ3LjU3LTU1LjE3OS01OC4yODgtNjcuNDQ1LTMuNTc0My0yLjIxMjQtMy4wOTY5LTAuOTY3NTUtNS45NDg3LTMuMjQ2Ni0yLjg1MTktMi4yNzkxLTcuNDI5Mi04LjAxODgtMTAuMjAyLTExLjEyOC0wLjgzNjMtMC45Mzc4Ny0wLjE5ODQtNC4xMDIyLTAuOTg4NS00Ljk4NTktMi41OTg4LTIuOTA2Ny00LjkwMy01LjQ3MTEtNi42MTkzLTcuMzY4NS02Ljc2NjctNC4xOTItMTguMTk3IDIuNDc2MS0xNS41ODIgMTAuODAyeiIgZmlsbD0iIzY2NiIvPgoJCTxnIGZpbGw9IiM0ZDRkNGQiPgoJCQk8cGF0aCBkPSJtMTEzLjQ5IDk0LjI2MmMzLjUwNzEgOS4xNTc4IDYuMDYwNiAxOC44NjUgMTAuODM0IDI3LjQzMiA2Ljg4NTggMTAuMTY2IDEyLjYyNCAyMS4yMzMgMjEuNDE2IDI5Ljk0NyA1LjUyNDggNS44Mzk1IDEwLjIwOSAxMi42NjggMTcuMzU4IDE2LjY2NCA1Ljk5MyA2LjI1NCAxNC45MjUgNS41MDU2IDIxLjk5MSA5LjQ3MTIgNy4xMjEzIDIuNDAzOCAxNi4yMTUgMTIuMjIgMTAuNDQgMTkuMTk3LTguMjk5MiA1Ljk1NjItMTkuOTU3IDIuNDY3Mi0yNi44NDMtNC4xNDItNi41OTU5LTguMjQ1Mi0xMi4xODUtMTcuNDA5LTE4LjM5LTI2LjAxLTE0Ljg4OC0yMS41MDUtMjkuNzEtNDMuMDc2LTQ0LjU5OC02NC41OCAxLjMyMjQtMy44ODcgMy40MjM2LTYuNjg0NiA3Ljc5MTYtNy45Nzg3eiIgZmlsdGVyPSJ1cmwoI2cpIi8+CgkJCTxwYXRoIGQ9Im0xMDguNDMgNzIuMTg2Yy0yLjA0NzctMC40OTI2NS00LjEyNDItMS4yMTE1LTYuMjQyNS0wLjY0OTA3LTIuODIxNiAwLjI1ODIzLTUuMjYxOSAxLjkyMjMtNy4xMjgyIDMuOTY2Ni0xLjA5NDggMC45MjI5MyAxLjU3NS0wLjM3OTc2IDIuMTg4NC0wLjQxOCAyLjA4ODItMC41MTk4NSA0LjI5MDItMC40OTI3MiA2LjQyNDctMC4zMTQ5MyAzLjAyNTUgMC4yMTIyMSA1LjMzMDUgMi4zMTc3IDcuNTYzNiA0LjEzNTIgMC42MTI0IDAuNDkyMzYgMS42OTg4IDEuMjIzMiAwLjU1MiAwLjE5MjkyLTAuODQ4NS0xLjE4MjYtMi41OTM1LTEuOTIwNC0yLjg3NjctMy4zODM0LTAuMTYwNC0xLjE3NjQtMC4zMjA5LTIuMzUyOC0wLjQ4MTMtMy41MjkyeiIgZmlsdGVyPSJ1cmwoI2gpIi8+CgkJCTxwYXRoIGQ9Im0xMTguMzUgODYuMjE3Yy0yLjQzODggMC4wMjQtNC45NzU3LTAuMjc1MjYtNy4yNTQ3IDAuODEzNzYtMi43MDYzIDAuODYzNzEtNC44OTc1IDIuMDc2MS04LjI3MzYgMS45OTI2LTEuMDk4NS0wLjE4MjktMi4zNTk0LTEuMTY2Mi0zLjgzMjUtMi4zNTM0LTEuNTU4My0xLjI1NTgtMy4yMjE1LTMuMjE1My0zLjY5NS0zLjMyMjIgMi45MTQ1IDQuNDE3NSA2LjEwOTYgOS4wMjIyIDkuMDI0MiAxMy40NCAwLjM1NSAxLjU2MTUgMC43MTQ4IDQuOTY2OCAxLjMwMjEgNS42NjI4IDIuNTQzOS0zLjI1NDcgNS4wMTgzLTYuNzQ4IDguNzIzMy04Ljc3OTggMi42ODQ4LTEuNDkyNiA1LjUwOTgtMy40MDAyIDguNzQ1MS0yLjk1NDQgMC45MDg5IDAuMTMyOTUgMy4zOTA3IDAuMjE4NDEgMS4xNTQ5LTAuNTU4MS0yLjEwODctMS4wNjU2LTQuMzA2NS0yLjEzNTUtNS44OTM4LTMuOTQxeiIgZmlsdGVyPSJ1cmwoI2kpIi8+CgkJPC9nPgoJCTxlbGxpcHNlIGN4PSIxODguMTMiIGN5PSIxODUuMTciIHJ4PSI3LjAxMTUiIHJ5PSI3LjE0MzciIGZpbGw9IiM5OTkiIGZpbHRlcj0idXJsKCNqKSIvPgoJCTxnIGZpbGw9IiNiM2IzYjMiPgoJCQk8cGF0aCBkPSJtMTI1LjIgOTMuNTUxYy0xLjkxLTAuMjc5MjItMy43NTc3LTAuMDIwNi01LjU1NjMgMC42NjE0Ni0wLjg2MTcgMS4zMzE2LTAuODQ5MyAyLjUzNzkgMC40NDI5IDMuNTYwOSAxOS45NjEgMjUuNDQ4IDM5LjkyMSA1MC44OTYgNTkuODgyIDc2LjM0MyAyLjgwNzIgMC42MTM3IDUuNjEwMiAxLjQ4ODIgOC40MiAxLjkzOSAxLjU3MDQtMC4zNDM4MyAzLjQwMi0wLjQ4OTAzIDQuMzg1MS0xLjY3NzkgMC4zMDg3LTIuMTczOS0xLjkzMzctMy4zNDY5LTIuOTkxNS00Ljk3NDEtMjEuNTI4LTI1LjI4NC00My4wNTUtNTAuNTY4LTY0LjU4Mi03NS44NTN6IiBmaWx0ZXI9InVybCgjZikiLz4KCQkJPHBhdGggZD0ibTEwMy45MS0zMi4zOTFjNy4yMzI3IDEzLjM0OCAxMS44MiAzMC4wNjkgNi4wMTA0IDQ0LjY1LTguMjg4MiAxNi4yNTktMjQuNDQ0IDI3LjQ4NC00Mi42MjEgMjkuNTc1LTIwLjU0MSA1LjMzMTktNDMuNjc4IDUuNjI3OS02Mi4zMzUtNS41NDYzLTE3LjgyOC01LjEyNTQgNC40MzQ4IDE0LjgzNiAxMC4xNTkgMTcuNjQ0IDMxLjMyOSAxOS4yMjUgNzUuMDUyIDE2LjY0NyAxMDQuMDUtNS45NDA5IDEyLjE3Ni0xMC44MjggMjIuOTY0LTI2LjMyNSAxOC43OTQtNDMuNDQ4LTMuOTA5NS0yMi4yNTYtMjMuNzAzLTM4LjEwNC00NC4xMzQtNDUuMDYgMy4xMTc0IDMuMDA1NCA2Ljg3NzEgNS4yMTggMTAuMDc2IDguMTI3MXoiIGZpbGwtb3BhY2l0eT0iLjQwOTA5IiBmaWx0ZXI9InVybCgjZSkiLz4KCQkJPHBhdGggZD0ibTEwMi4wNyA3NC45OTJoNC40OTAybDIuOTkzNCAxLjY4MzggOC43OTMxIDkuOTE1Ny00LjExNTktMC45MzU0NC0yLjYxOTIgMS4zMDk2eiIgZmlsbC1vcGFjaXR5PSIuNDA5MDkiIGZpbHRlcj0idXJsKCNkKSIvPgoJCTwvZz4KCQk8Zz4KCQkJPHBhdGggZD0ibTk1Ljc3IDYyLjk5MiA2LjQxNjEgOC40MDA1IDIuOTEwNS0wLjA2NjEgMi4wNTA1IDAuMzMwNzMgMS45ODQ0IDAuNjYxNDYtMC4zOTY5LTAuNzkzNzUtNi40MTYyLTcuMjA5OS0xLjM4OS0wLjcyNzYxLTIuMjQ5LTAuNTI5MTYtMS4wNTgzLTAuMDY2MnoiIGZpbGw9InVybCgjYikiIGZpbHRlcj0idXJsKCNjKSIvPgoJCQk8cGF0aCBkPSJtLTE0LjM2MyAxNC4xNzYgMC43OTM3NSAxMC4wNTQgNS4wMjcxIDExLjY0MiA1LjAyNzEgMTAuMDU0IDkuNzg5NiAxMC44NDggMTIuNDM1IDEwLjg0OCAxNC44MTcgNy42NzI5IDEyLjk2NSAzLjcwNDIgMTEuOTA2IDIuMTE2N2g4LjczMTJsMjAuNjM4LTIuNjQ1OCAxLjA1ODMtMS4wNTgzLTIuMzgxMy0zLjQzOTYgMC41MjkyLTUuODIwOCAzLjQzOTYtMi4zODEyIDMuNzA0Mi0yLjExNjcgNy45Mzc1LTQuNDk3OS0xNS4wODEgNC43NjI1LTE3LjcyNyAzLjE3NS0yMC42MzgtMS4wNTgzLTE2LjY2OS0zLjcwNDItMTYuOTMzLTguMjAyMS0xMS4xMTItOC40NjY3LTExLjkwNi0xNi40MDR6IiBmaWxsPSJ1cmwoI2EpIi8+CgkJCTx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKDUyLjY0MikiIHg9IjIyMS4yMzgzNCIgeT0iLTM0LjUzODExMyIgZG9taW5hbnQtYmFzZWxpbmU9ImF1dG8iIGZpbGw9IiM2NjY2NjYiIGZvbnQtZmFtaWx5PSInT3BlbiBTYW5zJyIgZm9udC1zaXplPSI4LjkxMDNweCIgbGV0dGVyLXNwYWNpbmc9IjBweCIgc3Ryb2tlLXdpZHRoPSIuMDQ2NDA4IiB0ZXh0LWFsaWduPSJjZW50ZXIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0iZm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDtmb250LXZhcmlhbnQtYWx0ZXJuYXRlczpub3JtYWw7Zm9udC12YXJpYW50LWNhcHM6bm9ybWFsO2ZvbnQtdmFyaWFudC1saWdhdHVyZXM6bm9ybWFsO2ZvbnQtdmFyaWFudC1udW1lcmljOm5vcm1hbDtmb250LXZhcmlhbnQtcG9zaXRpb246bm9ybWFsO2xpbmUtaGVpZ2h0OjEuMjU7c2hhcGUtcGFkZGluZzowO3RleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwMDAwO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC1vcmllbnRhdGlvbjptaXhlZDt0ZXh0LXRyYW5zZm9ybTpub25lO3doaXRlLXNwYWNlOm5vcm1hbCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjIyMS4yMzgzNCIgeT0iLTM0LjUzODExMyIgZmlsbD0iIzY2NjY2NiIgc3Ryb2tlLXdpZHRoPSIuMDQ2NDA4Ij5naW90b2hhc2hpPC90c3Bhbj48L3RleHQ+CgkJPC9nPgoJPC9nPgogIAogIDxhbmltYXRlVHJhbnNmb3JtIAogICAgYXR0cmlidXRlVHlwZT0ieG1sIgogICAgYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIgogICAgdHlwZT0icm90YXRlIiAKICAgIGZyb209IjAgMCAwIgogICAgdG89IjM2MCAwIDAiIAogICAgZHVyPSIyMi44cyIKICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIgogIC8+IAo8L3N2Zz4=");
+       width:0px;
+       height:0px;
+       padding:2.25em 2em 2.25em 2em;
+       margin: 0 0.8em;
+       background-repeat: no-repeat;
+       line-height:100%;
+       vertical-align:middle;
+       color: rgba(0, 0, 0, 0);
+}
+
+img.noentry {
+       display: inline-block;
+       background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMy4wNTQyMDcgMzMuMDQ0NzI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgoJPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY5LjY1MSAtNjguODExKSI+CgkJPGNpcmNsZSBjeD0iODYuMzk1IiBjeT0iODUuMzQiIHI9IjE1Ljk0OSIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjc0MiIvPgoJCTxwYXRoIGQ9Im04My4zMDYgMTAxLjY4Yy0yLjI5MDItMC40MDg2NC00Ljg2NTItMS40OTg5LTYuNzktMi44NzQ5LTMuNTAwOC0yLjUwMjYtNS45NDE1LTYuNDIyNS02LjY5NzItMTAuNzU2LTAuMjIzMDEtMS4yNzg5LTAuMjIzMDEtNC4xNDUzIDAtNS40MjQyIDAuNDIzNTYtMi40Mjg5IDEuNDc1NC00Ljk1OTIgMi44ODE2LTYuOTMxOSAyLjQ5MTMtMy40OTQ5IDYuNDQ3LTUuOTYyNCAxMC43NjYtNi43MTU1IDEuMjc4OS0wLjIyMzAyIDQuMTQ1NC0wLjIyMzAyIDUuNDI0MiAwIDIuNDM2MiAwLjQyNDg0IDQuOTU3NyAxLjQ3NDggNi45NTA0IDIuODk0MiAzLjQ2ODQgMi40NzA2IDUuOTYxOSA2LjQ1NzQgNi42ODk5IDEwLjY5NiAwLjIzMDA0IDEuMzM5NiAwLjIzMzc4IDQuMTgxOSA4ZS0zIDUuNDgxMi0wLjM4NzA1IDIuMjE5NS0xLjM2ODcgNC43MjA2LTIuNTMxMiA2LjQ0OTItMi41ODEyIDMuODM4Mi02LjUzNyA2LjM5OTctMTEuMTE2IDcuMTk4My0xLjI1MjIgMC4yMTgzNi00LjMxODQgMC4yMDkxNC01LjU4NDYtMC4wMTczem0tMy45Mjc2LTUuODQ5NmMwLTAuNDY0NzEgMC4wNjQ1LTAuNDQxODMtMS4yODk3LTAuNDU3NDctMC41NDgxMi02ZS0zIC0xLjA2MjUtMC4wNTQzLTEuMTQzMS0wLjEwNjQxLTAuMTg3OTktMC4xMjE2LTAuMTkyNTYtMS4xNjA1LTZlLTMgLTEuMzQ3MiAwLjA3NzMtMC4wNzczIDAuNDQ2NjktMC4xNDA2NSAwLjgyMDY5LTAuMTQwNjVoMC42ODAwMXYtMC44MjA3aC0wLjY4OTA5Yy0wLjgyNjc2IDAtMC45ODA2OC0wLjEzOTMtMC45MzAxNi0wLjg0MTM0bDAuMDM2Ni0wLjUwNjk0IDAuOTM3OTUtMC4wMjk0YzEuMzg3NS0wLjA0MzcgMS40ODctMC4wODAyIDEuNDQzNC0wLjUzMTRsLTAuMDM2NC0wLjM3NzAyaC0zLjE2NTZsLTAuMDMxNCAyLjc4NDUtMC4wMzE0IDIuNzg0NWgzLjQwNDN6bTEuNzU4Ni0xLjI5OTRjMC0xLjI2NTMgMC4wMzYyLTEuNjk3OCAwLjEzODkyLTEuNjYzNSAwLjA3NjQgMC4wMjU0IDAuNTYxNTMgMC43OTQ4NiAxLjA3ODEgMS43MDk4IDAuOTEzMzMgMS42MTc1IDAuOTUwMDQgMS42NjM1IDEuMzI4OCAxLjY2MzVoMC4zODk0OWwtMC4wMzE0LTIuNzg0NS0wLjAzMTQtMi43ODQ1aC0wLjcwMzQ2bC0wLjA1ODcgMS41NTQxYy0wLjAzMjMgMC44NTQ3NS0wLjEwODUxIDEuNTU5Ni0wLjE2OTMyIDEuNTY2NC0wLjA2MSA4ZS0zIC0wLjUxMDY5LTAuNjkyNTctMC45OTk1LTEuNTU0MS0wLjg3MzQtMS41Mzk0LTAuODk1NzgtMS41NjY0LTEuMjk2Mi0xLjU2NjRoLTAuNDA3NGwtMC4wMzE0IDIuNzg0NS0wLjAzMTQgMi43ODQ1aDAuODI0OTN6bTUuOTc5NC0wLjY5MzY5IDAuMDU4Ny0yLjM0NDkgMC41ODYyMy0wLjA1ODdjMC41NjAxNi0wLjA1NiAwLjU4NjIxLTAuMDc0MyAwLjU4NjIxLTAuNDEwMzV2LTAuMzUxNzJsLTEuNjYxOC0wLjAzMjVjLTEuNTkzMS0wLjAzMTItMS42NjUtMC4wMjI1LTEuNzM5IDAuMjEwOC0wLjExODMzIDAuMzcyOTkgMC4xNDEwNCAwLjU4Mzc1IDAuNzE4NTkgMC41ODM3NWgwLjUxMzIzdjIuMzI1M2MwIDEuMjc4OSAwLjAzNzcgMi4zNjMgMC4wODM3IDIuNDA5IDAuMDQ2IDAuMDQ2IDAuMjQzODkgMC4wNjgxIDAuNDM5NjcgMC4wNDg5bDAuMzU1OTQtMC4wMzQ4em0zLjA0ODMgMS4xNzI0IDAuMDU4Ny0xLjE3MjQgMC40MjAzNy0wLjAzNTJjMC4yNDM0LTAuMDIwNCAwLjQ3NDAyIDAuMDI5NCAwLjU0Nzc1IDAuMTE4MzQgMC4wNyAwLjA4NDUgMC4zMDQxOCAwLjYyNzg0IDAuNTIwMjcgMS4yMDc2IDAuMzkxNzMgMS4wNTExIDAuMzk0MTkgMS4wNTQzIDAuODI5ODQgMS4wOTA2IDAuNTM2MzIgMC4wNDQ2IDAuNTQwNzEtNmUtMyAwLjEwMTAxLTEuMTUyOS0wLjUyODg5LTEuMzc5OC0wLjUyNDA4LTEuMzE3OS0wLjEzNTQ1LTEuNzUzIDAuNTUwMjUtMC42MTYzNCAwLjYwNTQzLTEuMzg2NiAwLjE0OTExLTIuMDgyOS0wLjMwNDEtMC40NjQwOS0wLjgyOTM1LTAuNjE2NjMtMi4xMjM0LTAuNjE2NjMtMC45NTI5NSAwLTEuMTc2NSAwLjAzMzctMS4yMzQ4IDAuMTg1NDgtMC4xMDA2MyAwLjI2MjI0LTAuMDg4OSA1LjI2ODEgMC4wMTM1IDUuMzY5NiAwLjA0NiAwLjA0NiAwLjI0Mzg4IDAuMDY3OSAwLjQzOTY2IDAuMDQ4OWwwLjM1NTkzLTAuMDM0OHptMC4wNDc1LTIuMjE0MmMtMC4wMzQzLTAuMDg5My0wLjA0NTgtMC4zOTI3MS0wLjAyNTYtMC42NzQxNGwwLjAzNjctMC41MTE2OSAwLjcxOTE0LTAuMDM0NmMwLjU0MDI1LTAuMDI2IDAuNzY2MjkgMC4wMTM1IDAuOTA4NjMgMC4xNTQ4OSAwLjI3ODA1IDAuMjc4MDcgMC4yNDA3MiAwLjgyNzAxLTAuMDcxIDEuMDQ1NC0wLjMzNjk0IDAuMjM2MDEtMS40NzkzIDAuMjUwNjItMS41Njc4IDAuMDJ6bTYuMzc1NiAyLjE4MTEgMC4wMzM1LTEuMjA1NiAwLjg3NTE3LTEuNDM4MmMwLjQ4MTM1LTAuNzkxMDMgMC44NzUxOS0xLjUwNDcgMC44NzUxOS0xLjU4NiAwLTAuMDk5MS0wLjE0NDY5LTAuMTM1ODQtMC40Mzg5NC0wLjExMTQtMC4zOTgxOSAwLjAzMzEtMC40ODQzNyAwLjEwNzE3LTAuOTI4MjcgMC43OTg0Ni0wLjI2OTE1IDAuNDE5MTYtMC41NzI4MiAwLjg2NzYtMC42NzQ4OCAwLjk5NjU4bC0wLjE4NTQ4IDAuMjM0NDYtMC4xODU0OC0wLjIzNDQ2Yy0wLjEwMTk4LTAuMTI4OTItMC40MDU3My0wLjU3NzQyLTAuNjc0ODctMC45OTY1OC0wLjQ0MzkxLTAuNjkxMzMtMC41MzAwOS0wLjc2NTQ0LTAuOTI4MjgtMC43OTg0Ni0wLjMxMzI2LTAuMDI2LTAuNDM4OTQgMC4wMS0wLjQzODk0IDAuMTI2MDMgMCAwLjA4OTMgMC4zOTU3MSAwLjgwMDIgMC44NzkzMyAxLjU3OThsMC44NzkzMiAxLjQxNzV2MS4xNjI5YzAgMC42Mzk2MSAwLjAzNzcgMS4yMDA2IDAuMDgzNyAxLjI0NjYgMC4wNDYgMC4wNDYgMC4yNDM4OCAwLjA2ODEgMC40Mzk2NSAwLjA0ODlsMC4zNTU5Ni0wLjAzNDh6bTMuNjM2Mi02LjczMThjMC4xNDg3NC0wLjE0ODczIDAuMjExMzgtNC45MzE4IDAuMDY5NS01LjMwMTUtMC4wNjQzLTAuMTY3NTktMS40MTctMC4xODU0OC0xNC4xMTUtMC4xODU0OC0xMi42OTggMC0xNC4wNTEgMC4wMTczLTE0LjExNSAwLjE4NTQ4LTAuMTI0MyAwLjMyMzktMC4wNzk1IDQuOTczIDAuMDUwMiA1LjIxNTQgMC4xMTg5MSAwLjIyMjI1IDAuMzk5MTIgMC4yMjY3OSAxNC4wNDYgMC4yMjY3OSAxMC42NzQgMCAxMy45NTctMC4wMzI5IDE0LjA2NS0wLjE0MDY1em0tMTAuNDQtOC40OTYyYzAuNzAwNzctMC41MjMyIDEuMDYyNC0xLjkyOTYgMC44NDUzNy0zLjI4OC0wLjIxNjIxLTEuMzUzMy0wLjg0MTIxLTIuMDI1Ny0xLjg4MzktMi4wMjY1LTEuMDcxLTllLTQgLTEuODAyMyAwLjg4Njc1LTEuOTM5MyAyLjM1MzYtMC4xMzQzIDEuNDM4OSAwLjMxMTM4IDIuNjQ4NyAxLjEzMjkgMy4wNzQ5IDAuNTExMjUgMC4yNjUyNCAxLjQxMTUgMC4yMDk1OSAxLjg0NDktMC4xMTM5em0tMS42NDUxLTAuODMwNjVjLTAuMzExNDktMC4zNDQxOC0wLjQzNjY3LTAuODI2MzctMC40MzY2Ny0xLjY4MTkgMC0wLjk0OTY1IDAuMjA1MzUtMS41Mjg3IDAuNjQ0MTItMS44MTYyIDAuNjY0MjctMC40MzUyNCAxLjMyODIgMC4xODA0OCAxLjQzMjMgMS4zMjgzIDAuMTM5MTEgMS41MzMyLTAuMjM1NDEgMi4zNzUyLTEuMDU2MyAyLjM3NTItMC4yMjk4MyAwLTAuNDc1OS0wLjA4NjYtMC41ODMzOC0wLjIwNTR6bS01LjY2NjUgMC44NDA2NGMwLjAzOTEtMC4xMDE5OCAwLjA3MTItMC44NDA2MiAwLjA3MTItMS42NDE0IDAtMC44MDA3NyAwLjAzOTYtMS40NTU5IDAuMDg3OS0xLjQ1NTcgMC4wNDgzIDllLTUgMC40OTEzOSAwLjcyNTUzIDAuOTg0NSAxLjYxMjEgMC44MjcwOSAxLjQ4NyAwLjkyNDc1IDEuNjE0OCAxLjI2MDQgMS42NDg2IDAuMjAwMzggMC4wMjAyIDAuMzY1NDgtMC4wMTkyIDAuMzY3NTQtMC4wODc5IDJlLTMgLTAuMDY4NSA0ZS0zIC0xLjIzMjYgNGUtMyAtMi41ODY4IDAtMS4zNTQyLTJlLTMgLTIuNTE4Mi00ZS0zIC0yLjU4NjgtMmUtMyAtMC4wNjk1LTAuMTcyMjEtMC4xMDg1Mi0wLjM4NDc4LTAuMDg3OWwtMC4zODEwMiAwLjAzNjctMC4wMzI3IDEuNDk0OGMtMC4wMTczIDAuODIyMTYtMC4wNzM5IDEuNDk0OC0wLjEyMzkyIDEuNDk0OC0wLjA1MDIgMC0wLjQ2NTEyLTAuNjcyNjgtMC45MjIxOS0xLjQ5NDgtMC43NzY1OC0xLjM5Ny0wLjg1NTk3LTEuNDk3NC0xLjIxMi0xLjUzMjktMC4yMjU0Ni0wLjAyMjUtMC40MjExNiAwLjAyNTQtMC40Nzk0MiAwLjExNzE3LTAuMTExNzkgMC4xNzY0NC0wLjEzOTg4IDQuNzg1Ny0wLjAzMDggNS4wNjk4IDAuMDQxNCAwLjEwNzc1IDAuMjA4MTEgMC4xODU0OCAwLjM5NzgxIDAuMTg1NDggMC4xODk3MSAwIDAuMzU2NDktMC4wNzc3IDAuMzk3ODItMC4xODU0OHoiIGZpbGw9IiNlYzIyMjkiIHN0cm9rZS13aWR0aD0iLjExNzI0Ii8+Cgk8L2c+Cjwvc3ZnPgo=");
+       width:0px;
+       height:0px;
+       padding:2.25em 2em 2.25em 2em;
+       margin: 0 0.8em;
+       background-repeat: no-repeat;
+       line-height:100%;
+       vertical-align:middle;
+       font-size: 20pt;
+       color: rgba(0, 0, 0, 0);
+}
+
+div.searchbg {
+       background-repeat: no-repeat;
+       background-image: url("dorian-gray-wikipedia.jpg");
+       background-position: left top;
+       width: 561px;
+       height: 844px;
+       padding: 10px;
+       padding-top: 20px;
+       text-align:center;
+}
+
+table.searchtable {
+       position:relative;
+       display:inline-table;
+       padding-top: 6px;
+}
+
+div.acomplete {
+       position:absolute;
+       display:block;
+       float:right;
+       text-align:left;
+       background-color: #aaa;
+       font-size: 12pt;
+       max-height: 50vh;
+       right:0px;
+       margin: 0px;
+       padding: 0px 1px;
+       overflow:auto;
+       opacity: 0;
+       z-index: 4;
+       border: 1px solid gray;
+       border-radius: 3px;
+       background-color: white;
+       white-space: nowrap;
+       box-shadow: 0px 5px 15px gray;
+       transition: opacity 0.3s;
+       font-weight:normal
+}
+
+div.acomplete ul {
+       list-style-type: none;
+       padding: 0px 2px;
+       cursor: pointer;
+}
+
+div.acomplete ul li {
+       margin: 2px;
+       padding: 1px;
+       font-size: 14px;
+       left: 0px;
+}
+
+div.acomplete ul li:hover {
+       background-color: lightblue;
+}
+
+div.acomplete ul li:active {
+       background-color: blue;
+       color: white;
+}
+
+div.searchresults {
+       position:absolute;
+       display:block;
+       float:right;
+       text-align:left;
+       font-size: 9pt;
+       width: 100%;
+       max-height: 600px;
+       left:0px;
+       margin: 4px 20px;
+       margin-top: 24px;
+       padding: 0px 20px;
+       overflow: scroll;
+       opacity: 0;
+       z-index: 3;
+       border: 1px solid gray;
+       border-radius: 3px;
+       background-color: rgba(255,255,255,0.7);
+       white-space: nowrap;
+       box-shadow: 0px 5px 15px gray;
+       transition: opacity 0.3s;
+       font-weight:normal;
+}
+
+div.filepath {
+       font-size: 14pt;
+       padding: 12px 0px;
+}
+
+input.viable {
+       color: #000
+}
+
+input.nonviable {
+       color: #aaa
+}
+
+td.searchboxtitle {
+       text-align:right;
+       font-size: 15pt;
+}
+
+td.r {
+       text-align:right;
+       color: #aaa;
+       width:99%;
+}
+
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/lws-fts.js b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/lws-fts.js
new file mode 100644 (file)
index 0000000..8da940b
--- /dev/null
@@ -0,0 +1,211 @@
+/* lws-fts.js - JS supporting lws fulltext search
+ *
+ * Copyright (C) 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ */
+
+(function() {
+       
+       var last_ac = "";
+       
+       function san(s)
+       {
+               s.replace("<", "!");
+               s.replace("%", "!");
+               
+               return s;
+       }
+       
+       function lws_fts_choose()
+       {
+               var xhr = new XMLHttpRequest();
+               var sr = document.getElementById("searchresults");
+               var ac = document.getElementById("acomplete");
+               var inp = document.getElementById("lws_fts");
+
+               xhr.onopen = function(e) {
+                       xhr.setRequestHeader("cache-control", "max-age=0");
+               };
+
+               xhr.onload = function(e) {      
+                       var jj, n, m, s = "", x, lic = 0, hl, re;
+                       var sr = document.getElementById("searchresults");
+                       var ac = document.getElementById("acomplete");
+                       var inp = document.getElementById("lws_fts");
+                       sr.style.width = (parseInt(sr.parentNode.offsetWidth, 10) - 88) + "px";
+                       sr.style.opacity = "1";
+                       inp.blur();
+                       
+                       hl = document.getElementById("lws_fts").value;
+                       re = new RegExp(hl, "gi");
+                       
+                       // console.log(xhr.responseText);
+                       jj = JSON.parse(xhr.responseText);
+                       
+                       if (jj.fp) {
+                               lic = jj.fp.length;                                             
+                               for (n = 0; n < lic; n++) {
+                                       var q;
+                                       
+                                       s += "<div class='filepath'>" + jj.fp[n].path + "</div>";
+                                       
+                                       s += "<table>";
+                                       for (m = 0; m < jj.fp[n].hits.length; m++)
+                                               s += "<tr><td class='r'>" + jj.fp[n].hits[m].l +
+                                                        "</td><td>" + jj.fp[n].hits[m].s +
+                                                        "</td></tr>";
+                                       
+                                       s += "</table>";
+       
+                               }
+                       }
+                       
+                       sr.innerHTML = s;
+               };
+               
+               inp.blur();
+               ac.style.opacity = "0";
+               sr.style.innerHTML = "";
+               xhr.open("GET", "../fts/r/" + document.getElementById("lws_fts").value);
+               xhr.send();
+       }
+       
+       function lws_fts_ac_select(e)
+       {
+               var t = e.target;
+
+               while (t) {
+                       if (t.getAttribute && t.getAttribute("string")) {
+                               document.getElementById("lws_fts").value =
+                                               t.getAttribute("string");
+
+                               lws_fts_choose();
+                       }
+
+                       t = t.parentNode;
+               }
+       }
+       
+       function lws_fts_search_input()
+       {
+               var ac = document.getElementById("acomplete"),
+                   sb = document.getElementById("lws_fts");
+               
+               if (last_ac === sb.value)
+                       return;
+               
+               last_ac = sb.value;
+               
+               ac.style.width = (parseInt(sb.offsetWidth, 10) - 2) + "px";
+               ac.style.opacity = "1";
+               
+               /* detect loss of focus for popup menu */
+               sb.addEventListener("focusout", function(e) {
+                               ac.style.opacity = "0";
+               });
+               
+               
+               var xhr = new XMLHttpRequest();
+
+               xhr.onopen = function(e) {
+                       xhr.setRequestHeader("cache-control", "max-age=0");
+               };
+               xhr.onload = function(e) {
+                       var jj, n, s = "", x, lic = 0;
+                       var inp = document.getElementById("lws_fts");
+                       var ac = document.getElementById("acomplete");
+                       
+                       // console.log(xhr.responseText);
+                       jj = JSON.parse(xhr.responseText);
+                       
+                       switch(parseInt(jj.indexed, 10)) {
+                       case 0: /* there is no index */
+                               break;
+
+                       case 1: /* yay there is an index */
+                       
+                                       if (jj.ac) {
+                                               lic = jj.ac.length;
+                                               s += "<ul id='menu-ul'>";
+                                               for (n = 0; n < lic; n++) {
+
+                                                       if (jj.ac[n] && parseInt(jj.ac[n].matches, 10))
+                                                               s += "<li id='mi_ac" + n + "' string='" +
+                                                                       san(jj.ac[n].ac) + 
+                                                                       "'><table><tr><td>" + san(jj.ac[n].ac) +
+                                                                       "</td><td class='r'>" +
+                                                                       parseInt(jj.ac[n].matches, 10) +
+                                                                       "</td></tr></table></li>";
+                                               }
+                                               
+                                               s += "</ul>";
+
+                                        if (!lic) {
+                                               //s = "<img class='noentry'>";
+                                               inp.className = "nonviable";
+                                               ac.style.opacity = "0";
+                                        } else {
+                                                inp.className = "viable";
+                                                ac.style.opacity = "1";
+                                        }
+                                       }
+
+                               break;
+                               
+                       default:
+                               
+                               /* an index is being built... */
+                               
+                               s = "<table><tr><td><img class='spinner'></td><td>" +
+                                       "<table><tr><td>Indexing</td></tr><tr><td>" +
+                                       "<div id='bar1' class='bar1'>" +
+                                       "<div id='bar2' class='bar2'>" +
+                                       jj.index_done + "&nbsp;/&nbsp;" + jj.index_files +
+                                       "</div></div></td></tr></table>" +
+                                       "</td></tr></table>";
+                       
+                               setTimeout(lws_fts_search_input, 300);
+                       
+                               break;
+                       }
+                       
+                       ac.innerHTML = s;
+                       
+                       for (n = 0; n < lic; n++)
+                               if (document.getElementById("mi_ac" + n))
+                                       document.getElementById("mi_ac" + n).
+                                               addEventListener("click", lws_fts_ac_select);
+                       if (jj.index_files) {
+                               document.getElementById("bar2").style.width =
+                                       ((150 * jj.index_done) / (jj.index_files + 1)) + "px";
+                       }
+               };
+               
+               xhr.open("GET", "../fts/a/" + document.getElementById("lws_fts").value);
+               xhr.send();
+       }
+
+       document.addEventListener("DOMContentLoaded", function() {
+               var inp = document.getElementById("lws_fts");
+
+               inp.addEventListener("input", lws_fts_search_input, false);
+
+               inp.addEventListener("keydown",
+                               function(e) {
+                       var inp = document.getElementById("lws_fts");
+                       var sr = document.getElementById("searchresults");
+                       var ac = document.getElementById("acomplete");
+                       if (e.key === "Enter" && inp.className === "viable") {
+                               lws_fts_choose();
+                               sr.focus();
+                               ac.style.opacity = "0";
+                       }
+               }, false);
+
+       }, false);
+       
+}());
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-fulltext-search/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-fulltext-search/the-picture-of-dorian-gray.txt b/minimal-examples/http-server/minimal-http-server-fulltext-search/the-picture-of-dorian-gray.txt
new file mode 100644 (file)
index 0000000..f4ffc49
--- /dev/null
@@ -0,0 +1,8904 @@
+The Project Gutenberg EBook of The Picture of Dorian Gray, by Oscar Wilde\r
+\r
+This eBook is for the use of anyone anywhere at no cost and with\r
+almost no restrictions whatsoever.  You may copy it, give it away or\r
+re-use it under the terms of the Project Gutenberg License included\r
+with this eBook or online at www.gutenberg.net\r
+\r
+\r
+Title: The Picture of Dorian Gray\r
+\r
+Author: Oscar Wilde\r
+\r
+Release Date: June 9, 2008 [EBook #174]\r
+[This file last updated on July 2, 2011]\r
+[This file last updated on July 23, 2014]\r
+\r
+\r
+Language: English\r
+\r
+\r
+*** START OF THIS PROJECT GUTENBERG EBOOK THE PICTURE OF DORIAN GRAY ***\r
+\r
+\r
+\r
+\r
+Produced by Judith Boss.  HTML version by Al Haines.\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+The Picture of Dorian Gray\r
+\r
+by\r
+\r
+Oscar Wilde\r
+\r
+\r
+\r
+\r
+THE PREFACE\r
+\r
+The artist is the creator of beautiful things.  To reveal art and\r
+conceal the artist is art's aim.  The critic is he who can translate\r
+into another manner or a new material his impression of beautiful\r
+things.\r
+\r
+The highest as the lowest form of criticism is a mode of autobiography.\r
+Those who find ugly meanings in beautiful things are corrupt without\r
+being charming.  This is a fault.\r
+\r
+Those who find beautiful meanings in beautiful things are the\r
+cultivated.  For these there is hope.  They are the elect to whom\r
+beautiful things mean only beauty.\r
+\r
+There is no such thing as a moral or an immoral book.  Books are well\r
+written, or badly written.  That is all.\r
+\r
+The nineteenth century dislike of realism is the rage of Caliban seeing\r
+his own face in a glass.\r
+\r
+The nineteenth century dislike of romanticism is the rage of Caliban\r
+not seeing his own face in a glass.  The moral life of man forms part\r
+of the subject-matter of the artist, but the morality of art consists\r
+in the perfect use of an imperfect medium.  No artist desires to prove\r
+anything.  Even things that are true can be proved.  No artist has\r
+ethical sympathies.  An ethical sympathy in an artist is an\r
+unpardonable mannerism of style.  No artist is ever morbid.  The artist\r
+can express everything.  Thought and language are to the artist\r
+instruments of an art.  Vice and virtue are to the artist materials for\r
+an art.  From the point of view of form, the type of all the arts is\r
+the art of the musician.  From the point of view of feeling, the\r
+actor's craft is the type.  All art is at once surface and symbol.\r
+Those who go beneath the surface do so at their peril.  Those who read\r
+the symbol do so at their peril.  It is the spectator, and not life,\r
+that art really mirrors.  Diversity of opinion about a work of art\r
+shows that the work is new, complex, and vital.  When critics disagree,\r
+the artist is in accord with himself.  We can forgive a man for making\r
+a useful thing as long as he does not admire it.  The only excuse for\r
+making a useless thing is that one admires it intensely.\r
+\r
+               All art is quite useless.\r
+\r
+                            OSCAR WILDE\r
+\r
+\r
+\r
+\r
+CHAPTER 1\r
+\r
+The studio was filled with the rich odour of roses, and when the light\r
+summer wind stirred amidst the trees of the garden, there came through\r
+the open door the heavy scent of the lilac, or the more delicate\r
+perfume of the pink-flowering thorn.\r
+\r
+From the corner of the divan of Persian saddle-bags on which he was\r
+lying, smoking, as was his custom, innumerable cigarettes, Lord Henry\r
+Wotton could just catch the gleam of the honey-sweet and honey-coloured\r
+blossoms of a laburnum, whose tremulous branches seemed hardly able to\r
+bear the burden of a beauty so flamelike as theirs; and now and then\r
+the fantastic shadows of birds in flight flitted across the long\r
+tussore-silk curtains that were stretched in front of the huge window,\r
+producing a kind of momentary Japanese effect, and making him think of\r
+those pallid, jade-faced painters of Tokyo who, through the medium of\r
+an art that is necessarily immobile, seek to convey the sense of\r
+swiftness and motion.  The sullen murmur of the bees shouldering their\r
+way through the long unmown grass, or circling with monotonous\r
+insistence round the dusty gilt horns of the straggling woodbine,\r
+seemed to make the stillness more oppressive.  The dim roar of London\r
+was like the bourdon note of a distant organ.\r
+\r
+In the centre of the room, clamped to an upright easel, stood the\r
+full-length portrait of a young man of extraordinary personal beauty,\r
+and in front of it, some little distance away, was sitting the artist\r
+himself, Basil Hallward, whose sudden disappearance some years ago\r
+caused, at the time, such public excitement and gave rise to so many\r
+strange conjectures.\r
+\r
+As the painter looked at the gracious and comely form he had so\r
+skilfully mirrored in his art, a smile of pleasure passed across his\r
+face, and seemed about to linger there.  But he suddenly started up,\r
+and closing his eyes, placed his fingers upon the lids, as though he\r
+sought to imprison within his brain some curious dream from which he\r
+feared he might awake.\r
+\r
+"It is your best work, Basil, the best thing you have ever done," said\r
+Lord Henry languidly.  "You must certainly send it next year to the\r
+Grosvenor.  The Academy is too large and too vulgar.  Whenever I have\r
+gone there, there have been either so many people that I have not been\r
+able to see the pictures, which was dreadful, or so many pictures that\r
+I have not been able to see the people, which was worse.  The Grosvenor\r
+is really the only place."\r
+\r
+"I don't think I shall send it anywhere," he answered, tossing his head\r
+back in that odd way that used to make his friends laugh at him at\r
+Oxford.  "No, I won't send it anywhere."\r
+\r
+Lord Henry elevated his eyebrows and looked at him in amazement through\r
+the thin blue wreaths of smoke that curled up in such fanciful whorls\r
+from his heavy, opium-tainted cigarette.  "Not send it anywhere?  My\r
+dear fellow, why?  Have you any reason?  What odd chaps you painters\r
+are!  You do anything in the world to gain a reputation.  As soon as\r
+you have one, you seem to want to throw it away.  It is silly of you,\r
+for there is only one thing in the world worse than being talked about,\r
+and that is not being talked about.  A portrait like this would set you\r
+far above all the young men in England, and make the old men quite\r
+jealous, if old men are ever capable of any emotion."\r
+\r
+"I know you will laugh at me," he replied, "but I really can't exhibit\r
+it.  I have put too much of myself into it."\r
+\r
+Lord Henry stretched himself out on the divan and laughed.\r
+\r
+"Yes, I knew you would; but it is quite true, all the same."\r
+\r
+"Too much of yourself in it! Upon my word, Basil, I didn't know you\r
+were so vain; and I really can't see any resemblance between you, with\r
+your rugged strong face and your coal-black hair, and this young\r
+Adonis, who looks as if he was made out of ivory and rose-leaves. Why,\r
+my dear Basil, he is a Narcissus, and you--well, of course you have an\r
+intellectual expression and all that.  But beauty, real beauty, ends\r
+where an intellectual expression begins.  Intellect is in itself a mode\r
+of exaggeration, and destroys the harmony of any face.  The moment one\r
+sits down to think, one becomes all nose, or all forehead, or something\r
+horrid.  Look at the successful men in any of the learned professions.\r
+How perfectly hideous they are!  Except, of course, in the Church.  But\r
+then in the Church they don't think.  A bishop keeps on saying at the\r
+age of eighty what he was told to say when he was a boy of eighteen,\r
+and as a natural consequence he always looks absolutely delightful.\r
+Your mysterious young friend, whose name you have never told me, but\r
+whose picture really fascinates me, never thinks.  I feel quite sure of\r
+that.  He is some brainless beautiful creature who should be always\r
+here in winter when we have no flowers to look at, and always here in\r
+summer when we want something to chill our intelligence.  Don't flatter\r
+yourself, Basil:  you are not in the least like him."\r
+\r
+"You don't understand me, Harry," answered the artist.  "Of course I am\r
+not like him.  I know that perfectly well.  Indeed, I should be sorry\r
+to look like him.  You shrug your shoulders?  I am telling you the\r
+truth.  There is a fatality about all physical and intellectual\r
+distinction, the sort of fatality that seems to dog through history the\r
+faltering steps of kings.  It is better not to be different from one's\r
+fellows.  The ugly and the stupid have the best of it in this world.\r
+They can sit at their ease and gape at the play.  If they know nothing\r
+of victory, they are at least spared the knowledge of defeat.  They\r
+live as we all should live--undisturbed, indifferent, and without\r
+disquiet.  They neither bring ruin upon others, nor ever receive it\r
+from alien hands.  Your rank and wealth, Harry; my brains, such as they\r
+are--my art, whatever it may be worth; Dorian Gray's good looks--we\r
+shall all suffer for what the gods have given us, suffer terribly."\r
+\r
+"Dorian Gray?  Is that his name?" asked Lord Henry, walking across the\r
+studio towards Basil Hallward.\r
+\r
+"Yes, that is his name.  I didn't intend to tell it to you."\r
+\r
+"But why not?"\r
+\r
+"Oh, I can't explain.  When I like people immensely, I never tell their\r
+names to any one.  It is like surrendering a part of them.  I have\r
+grown to love secrecy.  It seems to be the one thing that can make\r
+modern life mysterious or marvellous to us.  The commonest thing is\r
+delightful if one only hides it.  When I leave town now I never tell my\r
+people where I am going.  If I did, I would lose all my pleasure.  It\r
+is a silly habit, I dare say, but somehow it seems to bring a great\r
+deal of romance into one's life.  I suppose you think me awfully\r
+foolish about it?"\r
+\r
+"Not at all," answered Lord Henry, "not at all, my dear Basil.  You\r
+seem to forget that I am married, and the one charm of marriage is that\r
+it makes a life of deception absolutely necessary for both parties.  I\r
+never know where my wife is, and my wife never knows what I am doing.\r
+When we meet--we do meet occasionally, when we dine out together, or go\r
+down to the Duke's--we tell each other the most absurd stories with the\r
+most serious faces.  My wife is very good at it--much better, in fact,\r
+than I am.  She never gets confused over her dates, and I always do.\r
+But when she does find me out, she makes no row at all.  I sometimes\r
+wish she would; but she merely laughs at me."\r
+\r
+"I hate the way you talk about your married life, Harry," said Basil\r
+Hallward, strolling towards the door that led into the garden.  "I\r
+believe that you are really a very good husband, but that you are\r
+thoroughly ashamed of your own virtues.  You are an extraordinary\r
+fellow.  You never say a moral thing, and you never do a wrong thing.\r
+Your cynicism is simply a pose."\r
+\r
+"Being natural is simply a pose, and the most irritating pose I know,"\r
+cried Lord Henry, laughing; and the two young men went out into the\r
+garden together and ensconced themselves on a long bamboo seat that\r
+stood in the shade of a tall laurel bush.  The sunlight slipped over\r
+the polished leaves.  In the grass, white daisies were tremulous.\r
+\r
+After a pause, Lord Henry pulled out his watch.  "I am afraid I must be\r
+going, Basil," he murmured, "and before I go, I insist on your\r
+answering a question I put to you some time ago."\r
+\r
+"What is that?" said the painter, keeping his eyes fixed on the ground.\r
+\r
+"You know quite well."\r
+\r
+"I do not, Harry."\r
+\r
+"Well, I will tell you what it is.  I want you to explain to me why you\r
+won't exhibit Dorian Gray's picture.  I want the real reason."\r
+\r
+"I told you the real reason."\r
+\r
+"No, you did not.  You said it was because there was too much of\r
+yourself in it.  Now, that is childish."\r
+\r
+"Harry," said Basil Hallward, looking him straight in the face, "every\r
+portrait that is painted with feeling is a portrait of the artist, not\r
+of the sitter.  The sitter is merely the accident, the occasion.  It is\r
+not he who is revealed by the painter; it is rather the painter who, on\r
+the coloured canvas, reveals himself.  The reason I will not exhibit\r
+this picture is that I am afraid that I have shown in it the secret of\r
+my own soul."\r
+\r
+Lord Henry laughed.  "And what is that?" he asked.\r
+\r
+"I will tell you," said Hallward; but an expression of perplexity came\r
+over his face.\r
+\r
+"I am all expectation, Basil," continued his companion, glancing at him.\r
+\r
+"Oh, there is really very little to tell, Harry," answered the painter;\r
+"and I am afraid you will hardly understand it.  Perhaps you will\r
+hardly believe it."\r
+\r
+Lord Henry smiled, and leaning down, plucked a pink-petalled daisy from\r
+the grass and examined it.  "I am quite sure I shall understand it," he\r
+replied, gazing intently at the little golden, white-feathered disk,\r
+"and as for believing things, I can believe anything, provided that it\r
+is quite incredible."\r
+\r
+The wind shook some blossoms from the trees, and the heavy\r
+lilac-blooms, with their clustering stars, moved to and fro in the\r
+languid air.  A grasshopper began to chirrup by the wall, and like a\r
+blue thread a long thin dragon-fly floated past on its brown gauze\r
+wings.  Lord Henry felt as if he could hear Basil Hallward's heart\r
+beating, and wondered what was coming.\r
+\r
+"The story is simply this," said the painter after some time.  "Two\r
+months ago I went to a crush at Lady Brandon's. You know we poor\r
+artists have to show ourselves in society from time to time, just to\r
+remind the public that we are not savages.  With an evening coat and a\r
+white tie, as you told me once, anybody, even a stock-broker, can gain\r
+a reputation for being civilized.  Well, after I had been in the room\r
+about ten minutes, talking to huge overdressed dowagers and tedious\r
+academicians, I suddenly became conscious that some one was looking at\r
+me.  I turned half-way round and saw Dorian Gray for the first time.\r
+When our eyes met, I felt that I was growing pale.  A curious sensation\r
+of terror came over me.  I knew that I had come face to face with some\r
+one whose mere personality was so fascinating that, if I allowed it to\r
+do so, it would absorb my whole nature, my whole soul, my very art\r
+itself.  I did not want any external influence in my life.  You know\r
+yourself, Harry, how independent I am by nature.  I have always been my\r
+own master; had at least always been so, till I met Dorian Gray.\r
+Then--but I don't know how to explain it to you.  Something seemed to\r
+tell me that I was on the verge of a terrible crisis in my life.  I had\r
+a strange feeling that fate had in store for me exquisite joys and\r
+exquisite sorrows.  I grew afraid and turned to quit the room.  It was\r
+not conscience that made me do so:  it was a sort of cowardice.  I take\r
+no credit to myself for trying to escape."\r
+\r
+"Conscience and cowardice are really the same things, Basil.\r
+Conscience is the trade-name of the firm.  That is all."\r
+\r
+"I don't believe that, Harry, and I don't believe you do either.\r
+However, whatever was my motive--and it may have been pride, for I used\r
+to be very proud--I certainly struggled to the door.  There, of course,\r
+I stumbled against Lady Brandon.  'You are not going to run away so\r
+soon, Mr. Hallward?' she screamed out.  You know her curiously shrill\r
+voice?"\r
+\r
+"Yes; she is a peacock in everything but beauty," said Lord Henry,\r
+pulling the daisy to bits with his long nervous fingers.\r
+\r
+"I could not get rid of her.  She brought me up to royalties, and\r
+people with stars and garters, and elderly ladies with gigantic tiaras\r
+and parrot noses.  She spoke of me as her dearest friend.  I had only\r
+met her once before, but she took it into her head to lionize me.  I\r
+believe some picture of mine had made a great success at the time, at\r
+least had been chattered about in the penny newspapers, which is the\r
+nineteenth-century standard of immortality.  Suddenly I found myself\r
+face to face with the young man whose personality had so strangely\r
+stirred me.  We were quite close, almost touching.  Our eyes met again.\r
+It was reckless of me, but I asked Lady Brandon to introduce me to him.\r
+Perhaps it was not so reckless, after all.  It was simply inevitable.\r
+We would have spoken to each other without any introduction.  I am sure\r
+of that.  Dorian told me so afterwards.  He, too, felt that we were\r
+destined to know each other."\r
+\r
+"And how did Lady Brandon describe this wonderful young man?" asked his\r
+companion.  "I know she goes in for giving a rapid _precis_ of all her\r
+guests.  I remember her bringing me up to a truculent and red-faced old\r
+gentleman covered all over with orders and ribbons, and hissing into my\r
+ear, in a tragic whisper which must have been perfectly audible to\r
+everybody in the room, the most astounding details.  I simply fled.  I\r
+like to find out people for myself.  But Lady Brandon treats her guests\r
+exactly as an auctioneer treats his goods.  She either explains them\r
+entirely away, or tells one everything about them except what one wants\r
+to know."\r
+\r
+"Poor Lady Brandon!  You are hard on her, Harry!" said Hallward\r
+listlessly.\r
+\r
+"My dear fellow, she tried to found a _salon_, and only succeeded in\r
+opening a restaurant.  How could I admire her?  But tell me, what did\r
+she say about Mr. Dorian Gray?"\r
+\r
+"Oh, something like, 'Charming boy--poor dear mother and I absolutely\r
+inseparable.  Quite forget what he does--afraid he--doesn't do\r
+anything--oh, yes, plays the piano--or is it the violin, dear Mr.\r
+Gray?'  Neither of us could help laughing, and we became friends at\r
+once."\r
+\r
+"Laughter is not at all a bad beginning for a friendship, and it is far\r
+the best ending for one," said the young lord, plucking another daisy.\r
+\r
+Hallward shook his head.  "You don't understand what friendship is,\r
+Harry," he murmured--"or what enmity is, for that matter.  You like\r
+every one; that is to say, you are indifferent to every one."\r
+\r
+"How horribly unjust of you!" cried Lord Henry, tilting his hat back\r
+and looking up at the little clouds that, like ravelled skeins of\r
+glossy white silk, were drifting across the hollowed turquoise of the\r
+summer sky.  "Yes; horribly unjust of you.  I make a great difference\r
+between people.  I choose my friends for their good looks, my\r
+acquaintances for their good characters, and my enemies for their good\r
+intellects.  A man cannot be too careful in the choice of his enemies.\r
+I have not got one who is a fool.  They are all men of some\r
+intellectual power, and consequently they all appreciate me.  Is that\r
+very vain of me?  I think it is rather vain."\r
+\r
+"I should think it was, Harry.  But according to your category I must\r
+be merely an acquaintance."\r
+\r
+"My dear old Basil, you are much more than an acquaintance."\r
+\r
+"And much less than a friend.  A sort of brother, I suppose?"\r
+\r
+"Oh, brothers!  I don't care for brothers.  My elder brother won't die,\r
+and my younger brothers seem never to do anything else."\r
+\r
+"Harry!" exclaimed Hallward, frowning.\r
+\r
+"My dear fellow, I am not quite serious.  But I can't help detesting my\r
+relations.  I suppose it comes from the fact that none of us can stand\r
+other people having the same faults as ourselves.  I quite sympathize\r
+with the rage of the English democracy against what they call the vices\r
+of the upper orders.  The masses feel that drunkenness, stupidity, and\r
+immorality should be their own special property, and that if any one of\r
+us makes an ass of himself, he is poaching on their preserves.  When\r
+poor Southwark got into the divorce court, their indignation was quite\r
+magnificent.  And yet I don't suppose that ten per cent of the\r
+proletariat live correctly."\r
+\r
+"I don't agree with a single word that you have said, and, what is\r
+more, Harry, I feel sure you don't either."\r
+\r
+Lord Henry stroked his pointed brown beard and tapped the toe of his\r
+patent-leather boot with a tasselled ebony cane.  "How English you are\r
+Basil!  That is the second time you have made that observation.  If one\r
+puts forward an idea to a true Englishman--always a rash thing to\r
+do--he never dreams of considering whether the idea is right or wrong.\r
+The only thing he considers of any importance is whether one believes\r
+it oneself.  Now, the value of an idea has nothing whatsoever to do\r
+with the sincerity of the man who expresses it.  Indeed, the\r
+probabilities are that the more insincere the man is, the more purely\r
+intellectual will the idea be, as in that case it will not be coloured\r
+by either his wants, his desires, or his prejudices.  However, I don't\r
+propose to discuss politics, sociology, or metaphysics with you.  I\r
+like persons better than principles, and I like persons with no\r
+principles better than anything else in the world.  Tell me more about\r
+Mr. Dorian Gray.  How often do you see him?"\r
+\r
+"Every day.  I couldn't be happy if I didn't see him every day.  He is\r
+absolutely necessary to me."\r
+\r
+"How extraordinary!  I thought you would never care for anything but\r
+your art."\r
+\r
+"He is all my art to me now," said the painter gravely.  "I sometimes\r
+think, Harry, that there are only two eras of any importance in the\r
+world's history.  The first is the appearance of a new medium for art,\r
+and the second is the appearance of a new personality for art also.\r
+What the invention of oil-painting was to the Venetians, the face of\r
+Antinous was to late Greek sculpture, and the face of Dorian Gray will\r
+some day be to me.  It is not merely that I paint from him, draw from\r
+him, sketch from him.  Of course, I have done all that.  But he is much\r
+more to me than a model or a sitter.  I won't tell you that I am\r
+dissatisfied with what I have done of him, or that his beauty is such\r
+that art cannot express it.  There is nothing that art cannot express,\r
+and I know that the work I have done, since I met Dorian Gray, is good\r
+work, is the best work of my life.  But in some curious way--I wonder\r
+will you understand me?--his personality has suggested to me an\r
+entirely new manner in art, an entirely new mode of style.  I see\r
+things differently, I think of them differently.  I can now recreate\r
+life in a way that was hidden from me before.  'A dream of form in days\r
+of thought'--who is it who says that?  I forget; but it is what Dorian\r
+Gray has been to me.  The merely visible presence of this lad--for he\r
+seems to me little more than a lad, though he is really over\r
+twenty--his merely visible presence--ah!  I wonder can you realize all\r
+that that means?  Unconsciously he defines for me the lines of a fresh\r
+school, a school that is to have in it all the passion of the romantic\r
+spirit, all the perfection of the spirit that is Greek.  The harmony of\r
+soul and body--how much that is!  We in our madness have separated the\r
+two, and have invented a realism that is vulgar, an ideality that is\r
+void.  Harry! if you only knew what Dorian Gray is to me!  You remember\r
+that landscape of mine, for which Agnew offered me such a huge price\r
+but which I would not part with?  It is one of the best things I have\r
+ever done.  And why is it so?  Because, while I was painting it, Dorian\r
+Gray sat beside me.  Some subtle influence passed from him to me, and\r
+for the first time in my life I saw in the plain woodland the wonder I\r
+had always looked for and always missed."\r
+\r
+"Basil, this is extraordinary!  I must see Dorian Gray."\r
+\r
+Hallward got up from the seat and walked up and down the garden.  After\r
+some time he came back.  "Harry," he said, "Dorian Gray is to me simply\r
+a motive in art.  You might see nothing in him.  I see everything in\r
+him.  He is never more present in my work than when no image of him is\r
+there.  He is a suggestion, as I have said, of a new manner.  I find\r
+him in the curves of certain lines, in the loveliness and subtleties of\r
+certain colours.  That is all."\r
+\r
+"Then why won't you exhibit his portrait?" asked Lord Henry.\r
+\r
+"Because, without intending it, I have put into it some expression of\r
+all this curious artistic idolatry, of which, of course, I have never\r
+cared to speak to him.  He knows nothing about it.  He shall never know\r
+anything about it.  But the world might guess it, and I will not bare\r
+my soul to their shallow prying eyes.  My heart shall never be put\r
+under their microscope.  There is too much of myself in the thing,\r
+Harry--too much of myself!"\r
+\r
+"Poets are not so scrupulous as you are.  They know how useful passion\r
+is for publication.  Nowadays a broken heart will run to many editions."\r
+\r
+"I hate them for it," cried Hallward.  "An artist should create\r
+beautiful things, but should put nothing of his own life into them.  We\r
+live in an age when men treat art as if it were meant to be a form of\r
+autobiography.  We have lost the abstract sense of beauty.  Some day I\r
+will show the world what it is; and for that reason the world shall\r
+never see my portrait of Dorian Gray."\r
+\r
+"I think you are wrong, Basil, but I won't argue with you.  It is only\r
+the intellectually lost who ever argue.  Tell me, is Dorian Gray very\r
+fond of you?"\r
+\r
+The painter considered for a few moments.  "He likes me," he answered\r
+after a pause; "I know he likes me.  Of course I flatter him\r
+dreadfully.  I find a strange pleasure in saying things to him that I\r
+know I shall be sorry for having said.  As a rule, he is charming to\r
+me, and we sit in the studio and talk of a thousand things.  Now and\r
+then, however, he is horribly thoughtless, and seems to take a real\r
+delight in giving me pain.  Then I feel, Harry, that I have given away\r
+my whole soul to some one who treats it as if it were a flower to put\r
+in his coat, a bit of decoration to charm his vanity, an ornament for a\r
+summer's day."\r
+\r
+"Days in summer, Basil, are apt to linger," murmured Lord Henry.\r
+"Perhaps you will tire sooner than he will.  It is a sad thing to think\r
+of, but there is no doubt that genius lasts longer than beauty.  That\r
+accounts for the fact that we all take such pains to over-educate\r
+ourselves.  In the wild struggle for existence, we want to have\r
+something that endures, and so we fill our minds with rubbish and\r
+facts, in the silly hope of keeping our place.  The thoroughly\r
+well-informed man--that is the modern ideal.  And the mind of the\r
+thoroughly well-informed man is a dreadful thing.  It is like a\r
+_bric-a-brac_ shop, all monsters and dust, with everything priced above\r
+its proper value.  I think you will tire first, all the same.  Some day\r
+you will look at your friend, and he will seem to you to be a little\r
+out of drawing, or you won't like his tone of colour, or something.\r
+You will bitterly reproach him in your own heart, and seriously think\r
+that he has behaved very badly to you.  The next time he calls, you\r
+will be perfectly cold and indifferent.  It will be a great pity, for\r
+it will alter you.  What you have told me is quite a romance, a romance\r
+of art one might call it, and the worst of having a romance of any kind\r
+is that it leaves one so unromantic."\r
+\r
+"Harry, don't talk like that.  As long as I live, the personality of\r
+Dorian Gray will dominate me.  You can't feel what I feel.  You change\r
+too often."\r
+\r
+"Ah, my dear Basil, that is exactly why I can feel it.  Those who are\r
+faithful know only the trivial side of love: it is the faithless who\r
+know love's tragedies."  And Lord Henry struck a light on a dainty\r
+silver case and began to smoke a cigarette with a self-conscious and\r
+satisfied air, as if he had summed up the world in a phrase.  There was\r
+a rustle of chirruping sparrows in the green lacquer leaves of the ivy,\r
+and the blue cloud-shadows chased themselves across the grass like\r
+swallows.  How pleasant it was in the garden!  And how delightful other\r
+people's emotions were!--much more delightful than their ideas, it\r
+seemed to him.  One's own soul, and the passions of one's\r
+friends--those were the fascinating things in life.  He pictured to\r
+himself with silent amusement the tedious luncheon that he had missed\r
+by staying so long with Basil Hallward.  Had he gone to his aunt's, he\r
+would have been sure to have met Lord Goodbody there, and the whole\r
+conversation would have been about the feeding of the poor and the\r
+necessity for model lodging-houses. Each class would have preached the\r
+importance of those virtues, for whose exercise there was no necessity\r
+in their own lives.  The rich would have spoken on the value of thrift,\r
+and the idle grown eloquent over the dignity of labour.  It was\r
+charming to have escaped all that!  As he thought of his aunt, an idea\r
+seemed to strike him.  He turned to Hallward and said, "My dear fellow,\r
+I have just remembered."\r
+\r
+"Remembered what, Harry?"\r
+\r
+"Where I heard the name of Dorian Gray."\r
+\r
+"Where was it?" asked Hallward, with a slight frown.\r
+\r
+"Don't look so angry, Basil.  It was at my aunt, Lady Agatha's.  She\r
+told me she had discovered a wonderful young man who was going to help\r
+her in the East End, and that his name was Dorian Gray.  I am bound to\r
+state that she never told me he was good-looking. Women have no\r
+appreciation of good looks; at least, good women have not.  She said\r
+that he was very earnest and had a beautiful nature.  I at once\r
+pictured to myself a creature with spectacles and lank hair, horribly\r
+freckled, and tramping about on huge feet.  I wish I had known it was\r
+your friend."\r
+\r
+"I am very glad you didn't, Harry."\r
+\r
+"Why?"\r
+\r
+"I don't want you to meet him."\r
+\r
+"You don't want me to meet him?"\r
+\r
+"No."\r
+\r
+"Mr. Dorian Gray is in the studio, sir," said the butler, coming into\r
+the garden.\r
+\r
+"You must introduce me now," cried Lord Henry, laughing.\r
+\r
+The painter turned to his servant, who stood blinking in the sunlight.\r
+"Ask Mr. Gray to wait, Parker:  I shall be in in a few moments." The\r
+man bowed and went up the walk.\r
+\r
+Then he looked at Lord Henry.  "Dorian Gray is my dearest friend," he\r
+said.  "He has a simple and a beautiful nature.  Your aunt was quite\r
+right in what she said of him.  Don't spoil him.  Don't try to\r
+influence him.  Your influence would be bad.  The world is wide, and\r
+has many marvellous people in it.  Don't take away from me the one\r
+person who gives to my art whatever charm it possesses:  my life as an\r
+artist depends on him.  Mind, Harry, I trust you."  He spoke very\r
+slowly, and the words seemed wrung out of him almost against his will.\r
+\r
+"What nonsense you talk!" said Lord Henry, smiling, and taking Hallward\r
+by the arm, he almost led him into the house.\r
+\r
+\r
+\r
+CHAPTER 2\r
+\r
+As they entered they saw Dorian Gray.  He was seated at the piano, with\r
+his back to them, turning over the pages of a volume of Schumann's\r
+"Forest Scenes."  "You must lend me these, Basil," he cried.  "I want\r
+to learn them.  They are perfectly charming."\r
+\r
+"That entirely depends on how you sit to-day, Dorian."\r
+\r
+"Oh, I am tired of sitting, and I don't want a life-sized portrait of\r
+myself," answered the lad, swinging round on the music-stool in a\r
+wilful, petulant manner.  When he caught sight of Lord Henry, a faint\r
+blush coloured his cheeks for a moment, and he started up.  "I beg your\r
+pardon, Basil, but I didn't know you had any one with you."\r
+\r
+"This is Lord Henry Wotton, Dorian, an old Oxford friend of mine.  I\r
+have just been telling him what a capital sitter you were, and now you\r
+have spoiled everything."\r
+\r
+"You have not spoiled my pleasure in meeting you, Mr. Gray," said Lord\r
+Henry, stepping forward and extending his hand.  "My aunt has often\r
+spoken to me about you.  You are one of her favourites, and, I am\r
+afraid, one of her victims also."\r
+\r
+"I am in Lady Agatha's black books at present," answered Dorian with a\r
+funny look of penitence.  "I promised to go to a club in Whitechapel\r
+with her last Tuesday, and I really forgot all about it.  We were to\r
+have played a duet together--three duets, I believe.  I don't know what\r
+she will say to me.  I am far too frightened to call."\r
+\r
+"Oh, I will make your peace with my aunt.  She is quite devoted to you.\r
+And I don't think it really matters about your not being there.  The\r
+audience probably thought it was a duet.  When Aunt Agatha sits down to\r
+the piano, she makes quite enough noise for two people."\r
+\r
+"That is very horrid to her, and not very nice to me," answered Dorian,\r
+laughing.\r
+\r
+Lord Henry looked at him.  Yes, he was certainly wonderfully handsome,\r
+with his finely curved scarlet lips, his frank blue eyes, his crisp\r
+gold hair.  There was something in his face that made one trust him at\r
+once.  All the candour of youth was there, as well as all youth's\r
+passionate purity.  One felt that he had kept himself unspotted from\r
+the world.  No wonder Basil Hallward worshipped him.\r
+\r
+"You are too charming to go in for philanthropy, Mr. Gray--far too\r
+charming." And Lord Henry flung himself down on the divan and opened\r
+his cigarette-case.\r
+\r
+The painter had been busy mixing his colours and getting his brushes\r
+ready.  He was looking worried, and when he heard Lord Henry's last\r
+remark, he glanced at him, hesitated for a moment, and then said,\r
+"Harry, I want to finish this picture to-day. Would you think it\r
+awfully rude of me if I asked you to go away?"\r
+\r
+Lord Henry smiled and looked at Dorian Gray.  "Am I to go, Mr. Gray?"\r
+he asked.\r
+\r
+"Oh, please don't, Lord Henry.  I see that Basil is in one of his sulky\r
+moods, and I can't bear him when he sulks.  Besides, I want you to tell\r
+me why I should not go in for philanthropy."\r
+\r
+"I don't know that I shall tell you that, Mr. Gray.  It is so tedious a\r
+subject that one would have to talk seriously about it.  But I\r
+certainly shall not run away, now that you have asked me to stop.  You\r
+don't really mind, Basil, do you?  You have often told me that you\r
+liked your sitters to have some one to chat to."\r
+\r
+Hallward bit his lip.  "If Dorian wishes it, of course you must stay.\r
+Dorian's whims are laws to everybody, except himself."\r
+\r
+Lord Henry took up his hat and gloves.  "You are very pressing, Basil,\r
+but I am afraid I must go.  I have promised to meet a man at the\r
+Orleans.  Good-bye, Mr. Gray.  Come and see me some afternoon in Curzon\r
+Street.  I am nearly always at home at five o'clock. Write to me when\r
+you are coming.  I should be sorry to miss you."\r
+\r
+"Basil," cried Dorian Gray, "if Lord Henry Wotton goes, I shall go,\r
+too.  You never open your lips while you are painting, and it is\r
+horribly dull standing on a platform and trying to look pleasant.  Ask\r
+him to stay.  I insist upon it."\r
+\r
+"Stay, Harry, to oblige Dorian, and to oblige me," said Hallward,\r
+gazing intently at his picture.  "It is quite true, I never talk when I\r
+am working, and never listen either, and it must be dreadfully tedious\r
+for my unfortunate sitters.  I beg you to stay."\r
+\r
+"But what about my man at the Orleans?"\r
+\r
+The painter laughed.  "I don't think there will be any difficulty about\r
+that.  Sit down again, Harry.  And now, Dorian, get up on the platform,\r
+and don't move about too much, or pay any attention to what Lord Henry\r
+says.  He has a very bad influence over all his friends, with the\r
+single exception of myself."\r
+\r
+Dorian Gray stepped up on the dais with the air of a young Greek\r
+martyr, and made a little _moue_ of discontent to Lord Henry, to whom he\r
+had rather taken a fancy.  He was so unlike Basil.  They made a\r
+delightful contrast.  And he had such a beautiful voice.  After a few\r
+moments he said to him, "Have you really a very bad influence, Lord\r
+Henry?  As bad as Basil says?"\r
+\r
+"There is no such thing as a good influence, Mr. Gray.  All influence\r
+is immoral--immoral from the scientific point of view."\r
+\r
+"Why?"\r
+\r
+"Because to influence a person is to give him one's own soul.  He does\r
+not think his natural thoughts, or burn with his natural passions.  His\r
+virtues are not real to him.  His sins, if there are such things as\r
+sins, are borrowed.  He becomes an echo of some one else's music, an\r
+actor of a part that has not been written for him.  The aim of life is\r
+self-development. To realize one's nature perfectly--that is what each\r
+of us is here for.  People are afraid of themselves, nowadays.  They\r
+have forgotten the highest of all duties, the duty that one owes to\r
+one's self.  Of course, they are charitable.  They feed the hungry and\r
+clothe the beggar.  But their own souls starve, and are naked.  Courage\r
+has gone out of our race.  Perhaps we never really had it.  The terror\r
+of society, which is the basis of morals, the terror of God, which is\r
+the secret of religion--these are the two things that govern us.  And\r
+yet--"\r
+\r
+"Just turn your head a little more to the right, Dorian, like a good\r
+boy," said the painter, deep in his work and conscious only that a look\r
+had come into the lad's face that he had never seen there before.\r
+\r
+"And yet," continued Lord Henry, in his low, musical voice, and with\r
+that graceful wave of the hand that was always so characteristic of\r
+him, and that he had even in his Eton days, "I believe that if one man\r
+were to live out his life fully and completely, were to give form to\r
+every feeling, expression to every thought, reality to every dream--I\r
+believe that the world would gain such a fresh impulse of joy that we\r
+would forget all the maladies of mediaevalism, and return to the\r
+Hellenic ideal--to something finer, richer than the Hellenic ideal, it\r
+may be.  But the bravest man amongst us is afraid of himself.  The\r
+mutilation of the savage has its tragic survival in the self-denial\r
+that mars our lives.  We are punished for our refusals.  Every impulse\r
+that we strive to strangle broods in the mind and poisons us.  The body\r
+sins once, and has done with its sin, for action is a mode of\r
+purification.  Nothing remains then but the recollection of a pleasure,\r
+or the luxury of a regret.  The only way to get rid of a temptation is\r
+to yield to it.  Resist it, and your soul grows sick with longing for\r
+the things it has forbidden to itself, with desire for what its\r
+monstrous laws have made monstrous and unlawful.  It has been said that\r
+the great events of the world take place in the brain.  It is in the\r
+brain, and the brain only, that the great sins of the world take place\r
+also.  You, Mr. Gray, you yourself, with your rose-red youth and your\r
+rose-white boyhood, you have had passions that have made you afraid,\r
+thoughts that have filled you with terror, day-dreams and sleeping\r
+dreams whose mere memory might stain your cheek with shame--"\r
+\r
+"Stop!" faltered Dorian Gray, "stop! you bewilder me.  I don't know\r
+what to say.  There is some answer to you, but I cannot find it.  Don't\r
+speak.  Let me think.  Or, rather, let me try not to think."\r
+\r
+For nearly ten minutes he stood there, motionless, with parted lips and\r
+eyes strangely bright.  He was dimly conscious that entirely fresh\r
+influences were at work within him.  Yet they seemed to him to have\r
+come really from himself.  The few words that Basil's friend had said\r
+to him--words spoken by chance, no doubt, and with wilful paradox in\r
+them--had touched some secret chord that had never been touched before,\r
+but that he felt was now vibrating and throbbing to curious pulses.\r
+\r
+Music had stirred him like that.  Music had troubled him many times.\r
+But music was not articulate.  It was not a new world, but rather\r
+another chaos, that it created in us.  Words!  Mere words!  How\r
+terrible they were!  How clear, and vivid, and cruel!  One could not\r
+escape from them.  And yet what a subtle magic there was in them!  They\r
+seemed to be able to give a plastic form to formless things, and to\r
+have a music of their own as sweet as that of viol or of lute.  Mere\r
+words!  Was there anything so real as words?\r
+\r
+Yes; there had been things in his boyhood that he had not understood.\r
+He understood them now.  Life suddenly became fiery-coloured to him.\r
+It seemed to him that he had been walking in fire.  Why had he not\r
+known it?\r
+\r
+With his subtle smile, Lord Henry watched him.  He knew the precise\r
+psychological moment when to say nothing.  He felt intensely\r
+interested.  He was amazed at the sudden impression that his words had\r
+produced, and, remembering a book that he had read when he was sixteen,\r
+a book which had revealed to him much that he had not known before, he\r
+wondered whether Dorian Gray was passing through a similar experience.\r
+He had merely shot an arrow into the air.  Had it hit the mark?  How\r
+fascinating the lad was!\r
+\r
+Hallward painted away with that marvellous bold touch of his, that had\r
+the true refinement and perfect delicacy that in art, at any rate comes\r
+only from strength.  He was unconscious of the silence.\r
+\r
+"Basil, I am tired of standing," cried Dorian Gray suddenly.  "I must\r
+go out and sit in the garden.  The air is stifling here."\r
+\r
+"My dear fellow, I am so sorry.  When I am painting, I can't think of\r
+anything else.  But you never sat better.  You were perfectly still.\r
+And I have caught the effect I wanted--the half-parted lips and the\r
+bright look in the eyes.  I don't know what Harry has been saying to\r
+you, but he has certainly made you have the most wonderful expression.\r
+I suppose he has been paying you compliments.  You mustn't believe a\r
+word that he says."\r
+\r
+"He has certainly not been paying me compliments.  Perhaps that is the\r
+reason that I don't believe anything he has told me."\r
+\r
+"You know you believe it all," said Lord Henry, looking at him with his\r
+dreamy languorous eyes.  "I will go out to the garden with you.  It is\r
+horribly hot in the studio.  Basil, let us have something iced to\r
+drink, something with strawberries in it."\r
+\r
+"Certainly, Harry.  Just touch the bell, and when Parker comes I will\r
+tell him what you want.  I have got to work up this background, so I\r
+will join you later on.  Don't keep Dorian too long.  I have never been\r
+in better form for painting than I am to-day. This is going to be my\r
+masterpiece.  It is my masterpiece as it stands."\r
+\r
+Lord Henry went out to the garden and found Dorian Gray burying his\r
+face in the great cool lilac-blossoms, feverishly drinking in their\r
+perfume as if it had been wine.  He came close to him and put his hand\r
+upon his shoulder.  "You are quite right to do that," he murmured.\r
+"Nothing can cure the soul but the senses, just as nothing can cure the\r
+senses but the soul."\r
+\r
+The lad started and drew back.  He was bareheaded, and the leaves had\r
+tossed his rebellious curls and tangled all their gilded threads.\r
+There was a look of fear in his eyes, such as people have when they are\r
+suddenly awakened.  His finely chiselled nostrils quivered, and some\r
+hidden nerve shook the scarlet of his lips and left them trembling.\r
+\r
+"Yes," continued Lord Henry, "that is one of the great secrets of\r
+life--to cure the soul by means of the senses, and the senses by means\r
+of the soul.  You are a wonderful creation.  You know more than you\r
+think you know, just as you know less than you want to know."\r
+\r
+Dorian Gray frowned and turned his head away.  He could not help liking\r
+the tall, graceful young man who was standing by him.  His romantic,\r
+olive-coloured face and worn expression interested him.  There was\r
+something in his low languid voice that was absolutely fascinating.\r
+His cool, white, flowerlike hands, even, had a curious charm.  They\r
+moved, as he spoke, like music, and seemed to have a language of their\r
+own.  But he felt afraid of him, and ashamed of being afraid.  Why had\r
+it been left for a stranger to reveal him to himself?  He had known\r
+Basil Hallward for months, but the friendship between them had never\r
+altered him.  Suddenly there had come some one across his life who\r
+seemed to have disclosed to him life's mystery.  And, yet, what was\r
+there to be afraid of?  He was not a schoolboy or a girl.  It was\r
+absurd to be frightened.\r
+\r
+"Let us go and sit in the shade," said Lord Henry.  "Parker has brought\r
+out the drinks, and if you stay any longer in this glare, you will be\r
+quite spoiled, and Basil will never paint you again.  You really must\r
+not allow yourself to become sunburnt.  It would be unbecoming."\r
+\r
+"What can it matter?" cried Dorian Gray, laughing, as he sat down on\r
+the seat at the end of the garden.\r
+\r
+"It should matter everything to you, Mr. Gray."\r
+\r
+"Why?"\r
+\r
+"Because you have the most marvellous youth, and youth is the one thing\r
+worth having."\r
+\r
+"I don't feel that, Lord Henry."\r
+\r
+"No, you don't feel it now.  Some day, when you are old and wrinkled\r
+and ugly, when thought has seared your forehead with its lines, and\r
+passion branded your lips with its hideous fires, you will feel it, you\r
+will feel it terribly.  Now, wherever you go, you charm the world.\r
+Will it always be so? ... You have a wonderfully beautiful face, Mr.\r
+Gray.  Don't frown.  You have.  And beauty is a form of genius--is\r
+higher, indeed, than genius, as it needs no explanation.  It is of the\r
+great facts of the world, like sunlight, or spring-time, or the\r
+reflection in dark waters of that silver shell we call the moon.  It\r
+cannot be questioned.  It has its divine right of sovereignty.  It\r
+makes princes of those who have it.  You smile?  Ah! when you have lost\r
+it you won't smile.... People say sometimes that beauty is only\r
+superficial.  That may be so, but at least it is not so superficial as\r
+thought is.  To me, beauty is the wonder of wonders.  It is only\r
+shallow people who do not judge by appearances.  The true mystery of\r
+the world is the visible, not the invisible.... Yes, Mr. Gray, the\r
+gods have been good to you.  But what the gods give they quickly take\r
+away.  You have only a few years in which to live really, perfectly,\r
+and fully.  When your youth goes, your beauty will go with it, and then\r
+you will suddenly discover that there are no triumphs left for you, or\r
+have to content yourself with those mean triumphs that the memory of\r
+your past will make more bitter than defeats.  Every month as it wanes\r
+brings you nearer to something dreadful.  Time is jealous of you, and\r
+wars against your lilies and your roses.  You will become sallow, and\r
+hollow-cheeked, and dull-eyed.  You will suffer horribly.... Ah!\r
+realize your youth while you have it.  Don't squander the gold of your\r
+days, listening to the tedious, trying to improve the hopeless failure,\r
+or giving away your life to the ignorant, the common, and the vulgar.\r
+These are the sickly aims, the false ideals, of our age.  Live!  Live\r
+the wonderful life that is in you!  Let nothing be lost upon you.  Be\r
+always searching for new sensations.  Be afraid of nothing.... A new\r
+Hedonism--that is what our century wants.  You might be its visible\r
+symbol.  With your personality there is nothing you could not do.  The\r
+world belongs to you for a season.... The moment I met you I saw that\r
+you were quite unconscious of what you really are, of what you really\r
+might be.  There was so much in you that charmed me that I felt I must\r
+tell you something about yourself.  I thought how tragic it would be if\r
+you were wasted.  For there is such a little time that your youth will\r
+last--such a little time.  The common hill-flowers wither, but they\r
+blossom again.  The laburnum will be as yellow next June as it is now.\r
+In a month there will be purple stars on the clematis, and year after\r
+year the green night of its leaves will hold its purple stars.  But we\r
+never get back our youth.  The pulse of joy that beats in us at twenty\r
+becomes sluggish.  Our limbs fail, our senses rot.  We degenerate into\r
+hideous puppets, haunted by the memory of the passions of which we were\r
+too much afraid, and the exquisite temptations that we had not the\r
+courage to yield to.  Youth!  Youth!  There is absolutely nothing in\r
+the world but youth!"\r
+\r
+Dorian Gray listened, open-eyed and wondering.  The spray of lilac fell\r
+from his hand upon the gravel.  A furry bee came and buzzed round it\r
+for a moment.  Then it began to scramble all over the oval stellated\r
+globe of the tiny blossoms.  He watched it with that strange interest\r
+in trivial things that we try to develop when things of high import\r
+make us afraid, or when we are stirred by some new emotion for which we\r
+cannot find expression, or when some thought that terrifies us lays\r
+sudden siege to the brain and calls on us to yield.  After a time the\r
+bee flew away.  He saw it creeping into the stained trumpet of a Tyrian\r
+convolvulus.  The flower seemed to quiver, and then swayed gently to\r
+and fro.\r
+\r
+Suddenly the painter appeared at the door of the studio and made\r
+staccato signs for them to come in.  They turned to each other and\r
+smiled.\r
+\r
+"I am waiting," he cried.  "Do come in.  The light is quite perfect,\r
+and you can bring your drinks."\r
+\r
+They rose up and sauntered down the walk together.  Two green-and-white\r
+butterflies fluttered past them, and in the pear-tree at the corner of\r
+the garden a thrush began to sing.\r
+\r
+"You are glad you have met me, Mr. Gray," said Lord Henry, looking at\r
+him.\r
+\r
+"Yes, I am glad now.  I wonder shall I always be glad?"\r
+\r
+"Always!  That is a dreadful word.  It makes me shudder when I hear it.\r
+Women are so fond of using it.  They spoil every romance by trying to\r
+make it last for ever.  It is a meaningless word, too.  The only\r
+difference between a caprice and a lifelong passion is that the caprice\r
+lasts a little longer."\r
+\r
+As they entered the studio, Dorian Gray put his hand upon Lord Henry's\r
+arm.  "In that case, let our friendship be a caprice," he murmured,\r
+flushing at his own boldness, then stepped up on the platform and\r
+resumed his pose.\r
+\r
+Lord Henry flung himself into a large wicker arm-chair and watched him.\r
+The sweep and dash of the brush on the canvas made the only sound that\r
+broke the stillness, except when, now and then, Hallward stepped back\r
+to look at his work from a distance.  In the slanting beams that\r
+streamed through the open doorway the dust danced and was golden.  The\r
+heavy scent of the roses seemed to brood over everything.\r
+\r
+After about a quarter of an hour Hallward stopped painting, looked for\r
+a long time at Dorian Gray, and then for a long time at the picture,\r
+biting the end of one of his huge brushes and frowning.  "It is quite\r
+finished," he cried at last, and stooping down he wrote his name in\r
+long vermilion letters on the left-hand corner of the canvas.\r
+\r
+Lord Henry came over and examined the picture.  It was certainly a\r
+wonderful work of art, and a wonderful likeness as well.\r
+\r
+"My dear fellow, I congratulate you most warmly," he said.  "It is the\r
+finest portrait of modern times.  Mr. Gray, come over and look at\r
+yourself."\r
+\r
+The lad started, as if awakened from some dream.\r
+\r
+"Is it really finished?" he murmured, stepping down from the platform.\r
+\r
+"Quite finished," said the painter.  "And you have sat splendidly\r
+to-day. I am awfully obliged to you."\r
+\r
+"That is entirely due to me," broke in Lord Henry.  "Isn't it, Mr.\r
+Gray?"\r
+\r
+Dorian made no answer, but passed listlessly in front of his picture\r
+and turned towards it.  When he saw it he drew back, and his cheeks\r
+flushed for a moment with pleasure.  A look of joy came into his eyes,\r
+as if he had recognized himself for the first time.  He stood there\r
+motionless and in wonder, dimly conscious that Hallward was speaking to\r
+him, but not catching the meaning of his words.  The sense of his own\r
+beauty came on him like a revelation.  He had never felt it before.\r
+Basil Hallward's compliments had seemed to him to be merely the\r
+charming exaggeration of friendship.  He had listened to them, laughed\r
+at them, forgotten them.  They had not influenced his nature.  Then had\r
+come Lord Henry Wotton with his strange panegyric on youth, his\r
+terrible warning of its brevity.  That had stirred him at the time, and\r
+now, as he stood gazing at the shadow of his own loveliness, the full\r
+reality of the description flashed across him.  Yes, there would be a\r
+day when his face would be wrinkled and wizen, his eyes dim and\r
+colourless, the grace of his figure broken and deformed.  The scarlet\r
+would pass away from his lips and the gold steal from his hair.  The\r
+life that was to make his soul would mar his body.  He would become\r
+dreadful, hideous, and uncouth.\r
+\r
+As he thought of it, a sharp pang of pain struck through him like a\r
+knife and made each delicate fibre of his nature quiver.  His eyes\r
+deepened into amethyst, and across them came a mist of tears.  He felt\r
+as if a hand of ice had been laid upon his heart.\r
+\r
+"Don't you like it?" cried Hallward at last, stung a little by the\r
+lad's silence, not understanding what it meant.\r
+\r
+"Of course he likes it," said Lord Henry.  "Who wouldn't like it?  It\r
+is one of the greatest things in modern art.  I will give you anything\r
+you like to ask for it.  I must have it."\r
+\r
+"It is not my property, Harry."\r
+\r
+"Whose property is it?"\r
+\r
+"Dorian's, of course," answered the painter.\r
+\r
+"He is a very lucky fellow."\r
+\r
+"How sad it is!" murmured Dorian Gray with his eyes still fixed upon\r
+his own portrait.  "How sad it is!  I shall grow old, and horrible, and\r
+dreadful.  But this picture will remain always young.  It will never be\r
+older than this particular day of June.... If it were only the other\r
+way!  If it were I who was to be always young, and the picture that was\r
+to grow old!  For that--for that--I would give everything!  Yes, there\r
+is nothing in the whole world I would not give!  I would give my soul\r
+for that!"\r
+\r
+"You would hardly care for such an arrangement, Basil," cried Lord\r
+Henry, laughing.  "It would be rather hard lines on your work."\r
+\r
+"I should object very strongly, Harry," said Hallward.\r
+\r
+Dorian Gray turned and looked at him.  "I believe you would, Basil.\r
+You like your art better than your friends.  I am no more to you than a\r
+green bronze figure.  Hardly as much, I dare say."\r
+\r
+The painter stared in amazement.  It was so unlike Dorian to speak like\r
+that.  What had happened?  He seemed quite angry.  His face was flushed\r
+and his cheeks burning.\r
+\r
+"Yes," he continued, "I am less to you than your ivory Hermes or your\r
+silver Faun.  You will like them always.  How long will you like me?\r
+Till I have my first wrinkle, I suppose.  I know, now, that when one\r
+loses one's good looks, whatever they may be, one loses everything.\r
+Your picture has taught me that.  Lord Henry Wotton is perfectly right.\r
+Youth is the only thing worth having.  When I find that I am growing\r
+old, I shall kill myself."\r
+\r
+Hallward turned pale and caught his hand.  "Dorian!  Dorian!" he cried,\r
+"don't talk like that.  I have never had such a friend as you, and I\r
+shall never have such another.  You are not jealous of material things,\r
+are you?--you who are finer than any of them!"\r
+\r
+"I am jealous of everything whose beauty does not die.  I am jealous of\r
+the portrait you have painted of me.  Why should it keep what I must\r
+lose?  Every moment that passes takes something from me and gives\r
+something to it.  Oh, if it were only the other way!  If the picture\r
+could change, and I could be always what I am now!  Why did you paint\r
+it?  It will mock me some day--mock me horribly!"  The hot tears welled\r
+into his eyes; he tore his hand away and, flinging himself on the\r
+divan, he buried his face in the cushions, as though he was praying.\r
+\r
+"This is your doing, Harry," said the painter bitterly.\r
+\r
+Lord Henry shrugged his shoulders.  "It is the real Dorian Gray--that\r
+is all."\r
+\r
+"It is not."\r
+\r
+"If it is not, what have I to do with it?"\r
+\r
+"You should have gone away when I asked you," he muttered.\r
+\r
+"I stayed when you asked me," was Lord Henry's answer.\r
+\r
+"Harry, I can't quarrel with my two best friends at once, but between\r
+you both you have made me hate the finest piece of work I have ever\r
+done, and I will destroy it.  What is it but canvas and colour?  I will\r
+not let it come across our three lives and mar them."\r
+\r
+Dorian Gray lifted his golden head from the pillow, and with pallid\r
+face and tear-stained eyes, looked at him as he walked over to the deal\r
+painting-table that was set beneath the high curtained window.  What\r
+was he doing there?  His fingers were straying about among the litter\r
+of tin tubes and dry brushes, seeking for something.  Yes, it was for\r
+the long palette-knife, with its thin blade of lithe steel.  He had\r
+found it at last.  He was going to rip up the canvas.\r
+\r
+With a stifled sob the lad leaped from the couch, and, rushing over to\r
+Hallward, tore the knife out of his hand, and flung it to the end of\r
+the studio.  "Don't, Basil, don't!" he cried.  "It would be murder!"\r
+\r
+"I am glad you appreciate my work at last, Dorian," said the painter\r
+coldly when he had recovered from his surprise.  "I never thought you\r
+would."\r
+\r
+"Appreciate it?  I am in love with it, Basil.  It is part of myself.  I\r
+feel that."\r
+\r
+"Well, as soon as you are dry, you shall be varnished, and framed, and\r
+sent home.  Then you can do what you like with yourself." And he walked\r
+across the room and rang the bell for tea.  "You will have tea, of\r
+course, Dorian?  And so will you, Harry?  Or do you object to such\r
+simple pleasures?"\r
+\r
+"I adore simple pleasures," said Lord Henry.  "They are the last refuge\r
+of the complex.  But I don't like scenes, except on the stage.  What\r
+absurd fellows you are, both of you!  I wonder who it was defined man\r
+as a rational animal.  It was the most premature definition ever given.\r
+Man is many things, but he is not rational.  I am glad he is not, after\r
+all--though I wish you chaps would not squabble over the picture.  You\r
+had much better let me have it, Basil.  This silly boy doesn't really\r
+want it, and I really do."\r
+\r
+"If you let any one have it but me, Basil, I shall never forgive you!"\r
+cried Dorian Gray; "and I don't allow people to call me a silly boy."\r
+\r
+"You know the picture is yours, Dorian.  I gave it to you before it\r
+existed."\r
+\r
+"And you know you have been a little silly, Mr. Gray, and that you\r
+don't really object to being reminded that you are extremely young."\r
+\r
+"I should have objected very strongly this morning, Lord Henry."\r
+\r
+"Ah! this morning!  You have lived since then."\r
+\r
+There came a knock at the door, and the butler entered with a laden\r
+tea-tray and set it down upon a small Japanese table.  There was a\r
+rattle of cups and saucers and the hissing of a fluted Georgian urn.\r
+Two globe-shaped china dishes were brought in by a page.  Dorian Gray\r
+went over and poured out the tea.  The two men sauntered languidly to\r
+the table and examined what was under the covers.\r
+\r
+"Let us go to the theatre to-night," said Lord Henry.  "There is sure\r
+to be something on, somewhere.  I have promised to dine at White's, but\r
+it is only with an old friend, so I can send him a wire to say that I\r
+am ill, or that I am prevented from coming in consequence of a\r
+subsequent engagement.  I think that would be a rather nice excuse:  it\r
+would have all the surprise of candour."\r
+\r
+"It is such a bore putting on one's dress-clothes," muttered Hallward.\r
+"And, when one has them on, they are so horrid."\r
+\r
+"Yes," answered Lord Henry dreamily, "the costume of the nineteenth\r
+century is detestable.  It is so sombre, so depressing.  Sin is the\r
+only real colour-element left in modern life."\r
+\r
+"You really must not say things like that before Dorian, Harry."\r
+\r
+"Before which Dorian?  The one who is pouring out tea for us, or the\r
+one in the picture?"\r
+\r
+"Before either."\r
+\r
+"I should like to come to the theatre with you, Lord Henry," said the\r
+lad.\r
+\r
+"Then you shall come; and you will come, too, Basil, won't you?"\r
+\r
+"I can't, really.  I would sooner not.  I have a lot of work to do."\r
+\r
+"Well, then, you and I will go alone, Mr. Gray."\r
+\r
+"I should like that awfully."\r
+\r
+The painter bit his lip and walked over, cup in hand, to the picture.\r
+"I shall stay with the real Dorian," he said, sadly.\r
+\r
+"Is it the real Dorian?" cried the original of the portrait, strolling\r
+across to him.  "Am I really like that?"\r
+\r
+"Yes; you are just like that."\r
+\r
+"How wonderful, Basil!"\r
+\r
+"At least you are like it in appearance.  But it will never alter,"\r
+sighed Hallward.  "That is something."\r
+\r
+"What a fuss people make about fidelity!" exclaimed Lord Henry.  "Why,\r
+even in love it is purely a question for physiology.  It has nothing to\r
+do with our own will.  Young men want to be faithful, and are not; old\r
+men want to be faithless, and cannot: that is all one can say."\r
+\r
+"Don't go to the theatre to-night, Dorian," said Hallward.  "Stop and\r
+dine with me."\r
+\r
+"I can't, Basil."\r
+\r
+"Why?"\r
+\r
+"Because I have promised Lord Henry Wotton to go with him."\r
+\r
+"He won't like you the better for keeping your promises.  He always\r
+breaks his own.  I beg you not to go."\r
+\r
+Dorian Gray laughed and shook his head.\r
+\r
+"I entreat you."\r
+\r
+The lad hesitated, and looked over at Lord Henry, who was watching them\r
+from the tea-table with an amused smile.\r
+\r
+"I must go, Basil," he answered.\r
+\r
+"Very well," said Hallward, and he went over and laid down his cup on\r
+the tray.  "It is rather late, and, as you have to dress, you had\r
+better lose no time.  Good-bye, Harry.  Good-bye, Dorian.  Come and see\r
+me soon.  Come to-morrow."\r
+\r
+"Certainly."\r
+\r
+"You won't forget?"\r
+\r
+"No, of course not," cried Dorian.\r
+\r
+"And ... Harry!"\r
+\r
+"Yes, Basil?"\r
+\r
+"Remember what I asked you, when we were in the garden this morning."\r
+\r
+"I have forgotten it."\r
+\r
+"I trust you."\r
+\r
+"I wish I could trust myself," said Lord Henry, laughing.  "Come, Mr.\r
+Gray, my hansom is outside, and I can drop you at your own place.\r
+Good-bye, Basil.  It has been a most interesting afternoon."\r
+\r
+As the door closed behind them, the painter flung himself down on a\r
+sofa, and a look of pain came into his face.\r
+\r
+\r
+\r
+CHAPTER 3\r
+\r
+At half-past twelve next day Lord Henry Wotton strolled from Curzon\r
+Street over to the Albany to call on his uncle, Lord Fermor, a genial\r
+if somewhat rough-mannered old bachelor, whom the outside world called\r
+selfish because it derived no particular benefit from him, but who was\r
+considered generous by Society as he fed the people who amused him.\r
+His father had been our ambassador at Madrid when Isabella was young\r
+and Prim unthought of, but had retired from the diplomatic service in a\r
+capricious moment of annoyance on not being offered the Embassy at\r
+Paris, a post to which he considered that he was fully entitled by\r
+reason of his birth, his indolence, the good English of his dispatches,\r
+and his inordinate passion for pleasure.  The son, who had been his\r
+father's secretary, had resigned along with his chief, somewhat\r
+foolishly as was thought at the time, and on succeeding some months\r
+later to the title, had set himself to the serious study of the great\r
+aristocratic art of doing absolutely nothing.  He had two large town\r
+houses, but preferred to live in chambers as it was less trouble, and\r
+took most of his meals at his club.  He paid some attention to the\r
+management of his collieries in the Midland counties, excusing himself\r
+for this taint of industry on the ground that the one advantage of\r
+having coal was that it enabled a gentleman to afford the decency of\r
+burning wood on his own hearth.  In politics he was a Tory, except when\r
+the Tories were in office, during which period he roundly abused them\r
+for being a pack of Radicals.  He was a hero to his valet, who bullied\r
+him, and a terror to most of his relations, whom he bullied in turn.\r
+Only England could have produced him, and he always said that the\r
+country was going to the dogs.  His principles were out of date, but\r
+there was a good deal to be said for his prejudices.\r
+\r
+When Lord Henry entered the room, he found his uncle sitting in a rough\r
+shooting-coat, smoking a cheroot and grumbling over _The Times_.  "Well,\r
+Harry," said the old gentleman, "what brings you out so early?  I\r
+thought you dandies never got up till two, and were not visible till\r
+five."\r
+\r
+"Pure family affection, I assure you, Uncle George.  I want to get\r
+something out of you."\r
+\r
+"Money, I suppose," said Lord Fermor, making a wry face.  "Well, sit\r
+down and tell me all about it.  Young people, nowadays, imagine that\r
+money is everything."\r
+\r
+"Yes," murmured Lord Henry, settling his button-hole in his coat; "and\r
+when they grow older they know it.  But I don't want money.  It is only\r
+people who pay their bills who want that, Uncle George, and I never pay\r
+mine.  Credit is the capital of a younger son, and one lives charmingly\r
+upon it.  Besides, I always deal with Dartmoor's tradesmen, and\r
+consequently they never bother me.  What I want is information:  not\r
+useful information, of course; useless information."\r
+\r
+"Well, I can tell you anything that is in an English Blue Book, Harry,\r
+although those fellows nowadays write a lot of nonsense.  When I was in\r
+the Diplomatic, things were much better.  But I hear they let them in\r
+now by examination.  What can you expect?  Examinations, sir, are pure\r
+humbug from beginning to end.  If a man is a gentleman, he knows quite\r
+enough, and if he is not a gentleman, whatever he knows is bad for him."\r
+\r
+"Mr. Dorian Gray does not belong to Blue Books, Uncle George," said\r
+Lord Henry languidly.\r
+\r
+"Mr. Dorian Gray?  Who is he?" asked Lord Fermor, knitting his bushy\r
+white eyebrows.\r
+\r
+"That is what I have come to learn, Uncle George.  Or rather, I know\r
+who he is.  He is the last Lord Kelso's grandson.  His mother was a\r
+Devereux, Lady Margaret Devereux.  I want you to tell me about his\r
+mother.  What was she like?  Whom did she marry?  You have known nearly\r
+everybody in your time, so you might have known her.  I am very much\r
+interested in Mr. Gray at present.  I have only just met him."\r
+\r
+"Kelso's grandson!" echoed the old gentleman.  "Kelso's grandson! ...\r
+Of course.... I knew his mother intimately.  I believe I was at her\r
+christening.  She was an extraordinarily beautiful girl, Margaret\r
+Devereux, and made all the men frantic by running away with a penniless\r
+young fellow--a mere nobody, sir, a subaltern in a foot regiment, or\r
+something of that kind.  Certainly.  I remember the whole thing as if\r
+it happened yesterday.  The poor chap was killed in a duel at Spa a few\r
+months after the marriage.  There was an ugly story about it.  They\r
+said Kelso got some rascally adventurer, some Belgian brute, to insult\r
+his son-in-law in public--paid him, sir, to do it, paid him--and that\r
+the fellow spitted his man as if he had been a pigeon.  The thing was\r
+hushed up, but, egad, Kelso ate his chop alone at the club for some\r
+time afterwards.  He brought his daughter back with him, I was told,\r
+and she never spoke to him again.  Oh, yes; it was a bad business.  The\r
+girl died, too, died within a year.  So she left a son, did she?  I had\r
+forgotten that.  What sort of boy is he?  If he is like his mother, he\r
+must be a good-looking chap."\r
+\r
+"He is very good-looking," assented Lord Henry.\r
+\r
+"I hope he will fall into proper hands," continued the old man.  "He\r
+should have a pot of money waiting for him if Kelso did the right thing\r
+by him.  His mother had money, too.  All the Selby property came to\r
+her, through her grandfather.  Her grandfather hated Kelso, thought him\r
+a mean dog.  He was, too.  Came to Madrid once when I was there.  Egad,\r
+I was ashamed of him.  The Queen used to ask me about the English noble\r
+who was always quarrelling with the cabmen about their fares.  They\r
+made quite a story of it.  I didn't dare show my face at Court for a\r
+month.  I hope he treated his grandson better than he did the jarvies."\r
+\r
+"I don't know," answered Lord Henry.  "I fancy that the boy will be\r
+well off.  He is not of age yet.  He has Selby, I know.  He told me so.\r
+And ... his mother was very beautiful?"\r
+\r
+"Margaret Devereux was one of the loveliest creatures I ever saw,\r
+Harry.  What on earth induced her to behave as she did, I never could\r
+understand.  She could have married anybody she chose.  Carlington was\r
+mad after her.  She was romantic, though.  All the women of that family\r
+were.  The men were a poor lot, but, egad! the women were wonderful.\r
+Carlington went on his knees to her.  Told me so himself.  She laughed\r
+at him, and there wasn't a girl in London at the time who wasn't after\r
+him.  And by the way, Harry, talking about silly marriages, what is\r
+this humbug your father tells me about Dartmoor wanting to marry an\r
+American?  Ain't English girls good enough for him?"\r
+\r
+"It is rather fashionable to marry Americans just now, Uncle George."\r
+\r
+"I'll back English women against the world, Harry," said Lord Fermor,\r
+striking the table with his fist.\r
+\r
+"The betting is on the Americans."\r
+\r
+"They don't last, I am told," muttered his uncle.\r
+\r
+"A long engagement exhausts them, but they are capital at a\r
+steeplechase.  They take things flying.  I don't think Dartmoor has a\r
+chance."\r
+\r
+"Who are her people?" grumbled the old gentleman.  "Has she got any?"\r
+\r
+Lord Henry shook his head.  "American girls are as clever at concealing\r
+their parents, as English women are at concealing their past," he said,\r
+rising to go.\r
+\r
+"They are pork-packers, I suppose?"\r
+\r
+"I hope so, Uncle George, for Dartmoor's sake.  I am told that\r
+pork-packing is the most lucrative profession in America, after\r
+politics."\r
+\r
+"Is she pretty?"\r
+\r
+"She behaves as if she was beautiful.  Most American women do.  It is\r
+the secret of their charm."\r
+\r
+"Why can't these American women stay in their own country?  They are\r
+always telling us that it is the paradise for women."\r
+\r
+"It is.  That is the reason why, like Eve, they are so excessively\r
+anxious to get out of it," said Lord Henry.  "Good-bye, Uncle George.\r
+I shall be late for lunch, if I stop any longer.  Thanks for giving me\r
+the information I wanted.  I always like to know everything about my\r
+new friends, and nothing about my old ones."\r
+\r
+"Where are you lunching, Harry?"\r
+\r
+"At Aunt Agatha's. I have asked myself and Mr. Gray.  He is her latest\r
+_protege_."\r
+\r
+"Humph! tell your Aunt Agatha, Harry, not to bother me any more with\r
+her charity appeals.  I am sick of them.  Why, the good woman thinks\r
+that I have nothing to do but to write cheques for her silly fads."\r
+\r
+"All right, Uncle George, I'll tell her, but it won't have any effect.\r
+Philanthropic people lose all sense of humanity.  It is their\r
+distinguishing characteristic."\r
+\r
+The old gentleman growled approvingly and rang the bell for his\r
+servant.  Lord Henry passed up the low arcade into Burlington Street\r
+and turned his steps in the direction of Berkeley Square.\r
+\r
+So that was the story of Dorian Gray's parentage.  Crudely as it had\r
+been told to him, it had yet stirred him by its suggestion of a\r
+strange, almost modern romance.  A beautiful woman risking everything\r
+for a mad passion.  A few wild weeks of happiness cut short by a\r
+hideous, treacherous crime.  Months of voiceless agony, and then a\r
+child born in pain.  The mother snatched away by death, the boy left to\r
+solitude and the tyranny of an old and loveless man.  Yes; it was an\r
+interesting background.  It posed the lad, made him more perfect, as it\r
+were.  Behind every exquisite thing that existed, there was something\r
+tragic.  Worlds had to be in travail, that the meanest flower might\r
+blow.... And how charming he had been at dinner the night before, as\r
+with startled eyes and lips parted in frightened pleasure he had sat\r
+opposite to him at the club, the red candleshades staining to a richer\r
+rose the wakening wonder of his face.  Talking to him was like playing\r
+upon an exquisite violin.  He answered to every touch and thrill of the\r
+bow.... There was something terribly enthralling in the exercise of\r
+influence.  No other activity was like it.  To project one's soul into\r
+some gracious form, and let it tarry there for a moment; to hear one's\r
+own intellectual views echoed back to one with all the added music of\r
+passion and youth; to convey one's temperament into another as though\r
+it were a subtle fluid or a strange perfume: there was a real joy in\r
+that--perhaps the most satisfying joy left to us in an age so limited\r
+and vulgar as our own, an age grossly carnal in its pleasures, and\r
+grossly common in its aims.... He was a marvellous type, too, this lad,\r
+whom by so curious a chance he had met in Basil's studio, or could be\r
+fashioned into a marvellous type, at any rate.  Grace was his, and the\r
+white purity of boyhood, and beauty such as old Greek marbles kept for\r
+us.  There was nothing that one could not do with him.  He could be\r
+made a Titan or a toy.  What a pity it was that such beauty was\r
+destined to fade! ...  And Basil?  From a psychological point of view,\r
+how interesting he was!  The new manner in art, the fresh mode of\r
+looking at life, suggested so strangely by the merely visible presence\r
+of one who was unconscious of it all; the silent spirit that dwelt in\r
+dim woodland, and walked unseen in open field, suddenly showing\r
+herself, Dryadlike and not afraid, because in his soul who sought for\r
+her there had been wakened that wonderful vision to which alone are\r
+wonderful things revealed; the mere shapes and patterns of things\r
+becoming, as it were, refined, and gaining a kind of symbolical value,\r
+as though they were themselves patterns of some other and more perfect\r
+form whose shadow they made real:  how strange it all was!  He\r
+remembered something like it in history.  Was it not Plato, that artist\r
+in thought, who had first analyzed it?  Was it not Buonarotti who had\r
+carved it in the coloured marbles of a sonnet-sequence? But in our own\r
+century it was strange.... Yes; he would try to be to Dorian Gray\r
+what, without knowing it, the lad was to the painter who had fashioned\r
+the wonderful portrait.  He would seek to dominate him--had already,\r
+indeed, half done so.  He would make that wonderful spirit his own.\r
+There was something fascinating in this son of love and death.\r
+\r
+Suddenly he stopped and glanced up at the houses.  He found that he had\r
+passed his aunt's some distance, and, smiling to himself, turned back.\r
+When he entered the somewhat sombre hall, the butler told him that they\r
+had gone in to lunch.  He gave one of the footmen his hat and stick and\r
+passed into the dining-room.\r
+\r
+"Late as usual, Harry," cried his aunt, shaking her head at him.\r
+\r
+He invented a facile excuse, and having taken the vacant seat next to\r
+her, looked round to see who was there.  Dorian bowed to him shyly from\r
+the end of the table, a flush of pleasure stealing into his cheek.\r
+Opposite was the Duchess of Harley, a lady of admirable good-nature and\r
+good temper, much liked by every one who knew her, and of those ample\r
+architectural proportions that in women who are not duchesses are\r
+described by contemporary historians as stoutness.  Next to her sat, on\r
+her right, Sir Thomas Burdon, a Radical member of Parliament, who\r
+followed his leader in public life and in private life followed the\r
+best cooks, dining with the Tories and thinking with the Liberals, in\r
+accordance with a wise and well-known rule.  The post on her left was\r
+occupied by Mr. Erskine of Treadley, an old gentleman of considerable\r
+charm and culture, who had fallen, however, into bad habits of silence,\r
+having, as he explained once to Lady Agatha, said everything that he\r
+had to say before he was thirty.  His own neighbour was Mrs. Vandeleur,\r
+one of his aunt's oldest friends, a perfect saint amongst women, but so\r
+dreadfully dowdy that she reminded one of a badly bound hymn-book.\r
+Fortunately for him she had on the other side Lord Faudel, a most\r
+intelligent middle-aged mediocrity, as bald as a ministerial statement\r
+in the House of Commons, with whom she was conversing in that intensely\r
+earnest manner which is the one unpardonable error, as he remarked once\r
+himself, that all really good people fall into, and from which none of\r
+them ever quite escape.\r
+\r
+"We are talking about poor Dartmoor, Lord Henry," cried the duchess,\r
+nodding pleasantly to him across the table.  "Do you think he will\r
+really marry this fascinating young person?"\r
+\r
+"I believe she has made up her mind to propose to him, Duchess."\r
+\r
+"How dreadful!" exclaimed Lady Agatha.  "Really, some one should\r
+interfere."\r
+\r
+"I am told, on excellent authority, that her father keeps an American\r
+dry-goods store," said Sir Thomas Burdon, looking supercilious.\r
+\r
+"My uncle has already suggested pork-packing, Sir Thomas."\r
+\r
+"Dry-goods! What are American dry-goods?" asked the duchess, raising\r
+her large hands in wonder and accentuating the verb.\r
+\r
+"American novels," answered Lord Henry, helping himself to some quail.\r
+\r
+The duchess looked puzzled.\r
+\r
+"Don't mind him, my dear," whispered Lady Agatha.  "He never means\r
+anything that he says."\r
+\r
+"When America was discovered," said the Radical member--and he began to\r
+give some wearisome facts.  Like all people who try to exhaust a\r
+subject, he exhausted his listeners.  The duchess sighed and exercised\r
+her privilege of interruption.  "I wish to goodness it never had been\r
+discovered at all!" she exclaimed.  "Really, our girls have no chance\r
+nowadays.  It is most unfair."\r
+\r
+"Perhaps, after all, America never has been discovered," said Mr.\r
+Erskine; "I myself would say that it had merely been detected."\r
+\r
+"Oh! but I have seen specimens of the inhabitants," answered the\r
+duchess vaguely.  "I must confess that most of them are extremely\r
+pretty.  And they dress well, too.  They get all their dresses in\r
+Paris.  I wish I could afford to do the same."\r
+\r
+"They say that when good Americans die they go to Paris," chuckled Sir\r
+Thomas, who had a large wardrobe of Humour's cast-off clothes.\r
+\r
+"Really!  And where do bad Americans go to when they die?" inquired the\r
+duchess.\r
+\r
+"They go to America," murmured Lord Henry.\r
+\r
+Sir Thomas frowned.  "I am afraid that your nephew is prejudiced\r
+against that great country," he said to Lady Agatha.  "I have travelled\r
+all over it in cars provided by the directors, who, in such matters,\r
+are extremely civil.  I assure you that it is an education to visit it."\r
+\r
+"But must we really see Chicago in order to be educated?" asked Mr.\r
+Erskine plaintively.  "I don't feel up to the journey."\r
+\r
+Sir Thomas waved his hand.  "Mr. Erskine of Treadley has the world on\r
+his shelves. We practical men like to see things, not to read about\r
+them. The Americans are an extremely interesting people. They are\r
+absolutely reasonable. I think that is their distinguishing\r
+characteristic. Yes, Mr. Erskine, an absolutely reasonable people. I\r
+assure you there is no nonsense about the Americans."\r
+\r
+"How dreadful!" cried Lord Henry.  "I can stand brute force, but brute\r
+reason is quite unbearable.  There is something unfair about its use.\r
+It is hitting below the intellect."\r
+\r
+"I do not understand you," said Sir Thomas, growing rather red.\r
+\r
+"I do, Lord Henry," murmured Mr. Erskine, with a smile.\r
+\r
+"Paradoxes are all very well in their way...." rejoined the baronet.\r
+\r
+"Was that a paradox?" asked Mr. Erskine.  "I did not think so.  Perhaps\r
+it was.  Well, the way of paradoxes is the way of truth.  To test\r
+reality we must see it on the tight rope.  When the verities become\r
+acrobats, we can judge them."\r
+\r
+"Dear me!" said Lady Agatha, "how you men argue!  I am sure I never can\r
+make out what you are talking about.  Oh!  Harry, I am quite vexed with\r
+you.  Why do you try to persuade our nice Mr. Dorian Gray to give up\r
+the East End?  I assure you he would be quite invaluable.  They would\r
+love his playing."\r
+\r
+"I want him to play to me," cried Lord Henry, smiling, and he looked\r
+down the table and caught a bright answering glance.\r
+\r
+"But they are so unhappy in Whitechapel," continued Lady Agatha.\r
+\r
+"I can sympathize with everything except suffering," said Lord Henry,\r
+shrugging his shoulders.  "I cannot sympathize with that.  It is too\r
+ugly, too horrible, too distressing.  There is something terribly\r
+morbid in the modern sympathy with pain.  One should sympathize with\r
+the colour, the beauty, the joy of life.  The less said about life's\r
+sores, the better."\r
+\r
+"Still, the East End is a very important problem," remarked Sir Thomas\r
+with a grave shake of the head.\r
+\r
+"Quite so," answered the young lord.  "It is the problem of slavery,\r
+and we try to solve it by amusing the slaves."\r
+\r
+The politician looked at him keenly.  "What change do you propose,\r
+then?" he asked.\r
+\r
+Lord Henry laughed.  "I don't desire to change anything in England\r
+except the weather," he answered.  "I am quite content with philosophic\r
+contemplation.  But, as the nineteenth century has gone bankrupt\r
+through an over-expenditure of sympathy, I would suggest that we should\r
+appeal to science to put us straight.  The advantage of the emotions is\r
+that they lead us astray, and the advantage of science is that it is\r
+not emotional."\r
+\r
+"But we have such grave responsibilities," ventured Mrs. Vandeleur\r
+timidly.\r
+\r
+"Terribly grave," echoed Lady Agatha.\r
+\r
+Lord Henry looked over at Mr. Erskine.  "Humanity takes itself too\r
+seriously.  It is the world's original sin.  If the caveman had known\r
+how to laugh, history would have been different."\r
+\r
+"You are really very comforting," warbled the duchess.  "I have always\r
+felt rather guilty when I came to see your dear aunt, for I take no\r
+interest at all in the East End.  For the future I shall be able to\r
+look her in the face without a blush."\r
+\r
+"A blush is very becoming, Duchess," remarked Lord Henry.\r
+\r
+"Only when one is young," she answered.  "When an old woman like myself\r
+blushes, it is a very bad sign.  Ah!  Lord Henry, I wish you would tell\r
+me how to become young again."\r
+\r
+He thought for a moment.  "Can you remember any great error that you\r
+committed in your early days, Duchess?" he asked, looking at her across\r
+the table.\r
+\r
+"A great many, I fear," she cried.\r
+\r
+"Then commit them over again," he said gravely.  "To get back one's\r
+youth, one has merely to repeat one's follies."\r
+\r
+"A delightful theory!" she exclaimed.  "I must put it into practice."\r
+\r
+"A dangerous theory!" came from Sir Thomas's tight lips.  Lady Agatha\r
+shook her head, but could not help being amused.  Mr. Erskine listened.\r
+\r
+"Yes," he continued, "that is one of the great secrets of life.\r
+Nowadays most people die of a sort of creeping common sense, and\r
+discover when it is too late that the only things one never regrets are\r
+one's mistakes."\r
+\r
+A laugh ran round the table.\r
+\r
+He played with the idea and grew wilful; tossed it into the air and\r
+transformed it; let it escape and recaptured it; made it iridescent\r
+with fancy and winged it with paradox.  The praise of folly, as he went\r
+on, soared into a philosophy, and philosophy herself became young, and\r
+catching the mad music of pleasure, wearing, one might fancy, her\r
+wine-stained robe and wreath of ivy, danced like a Bacchante over the\r
+hills of life, and mocked the slow Silenus for being sober.  Facts fled\r
+before her like frightened forest things.  Her white feet trod the huge\r
+press at which wise Omar sits, till the seething grape-juice rose round\r
+her bare limbs in waves of purple bubbles, or crawled in red foam over\r
+the vat's black, dripping, sloping sides.  It was an extraordinary\r
+improvisation.  He felt that the eyes of Dorian Gray were fixed on him,\r
+and the consciousness that amongst his audience there was one whose\r
+temperament he wished to fascinate seemed to give his wit keenness and\r
+to lend colour to his imagination.  He was brilliant, fantastic,\r
+irresponsible.  He charmed his listeners out of themselves, and they\r
+followed his pipe, laughing.  Dorian Gray never took his gaze off him,\r
+but sat like one under a spell, smiles chasing each other over his lips\r
+and wonder growing grave in his darkening eyes.\r
+\r
+At last, liveried in the costume of the age, reality entered the room\r
+in the shape of a servant to tell the duchess that her carriage was\r
+waiting.  She wrung her hands in mock despair.  "How annoying!" she\r
+cried.  "I must go.  I have to call for my husband at the club, to take\r
+him to some absurd meeting at Willis's Rooms, where he is going to be\r
+in the chair.  If I am late he is sure to be furious, and I couldn't\r
+have a scene in this bonnet.  It is far too fragile.  A harsh word\r
+would ruin it.  No, I must go, dear Agatha.  Good-bye, Lord Henry, you\r
+are quite delightful and dreadfully demoralizing.  I am sure I don't\r
+know what to say about your views.  You must come and dine with us some\r
+night.  Tuesday?  Are you disengaged Tuesday?"\r
+\r
+"For you I would throw over anybody, Duchess," said Lord Henry with a\r
+bow.\r
+\r
+"Ah! that is very nice, and very wrong of you," she cried; "so mind you\r
+come"; and she swept out of the room, followed by Lady Agatha and the\r
+other ladies.\r
+\r
+When Lord Henry had sat down again, Mr. Erskine moved round, and taking\r
+a chair close to him, placed his hand upon his arm.\r
+\r
+"You talk books away," he said; "why don't you write one?"\r
+\r
+"I am too fond of reading books to care to write them, Mr. Erskine.  I\r
+should like to write a novel certainly, a novel that would be as lovely\r
+as a Persian carpet and as unreal.  But there is no literary public in\r
+England for anything except newspapers, primers, and encyclopaedias.\r
+Of all people in the world the English have the least sense of the\r
+beauty of literature."\r
+\r
+"I fear you are right," answered Mr. Erskine.  "I myself used to have\r
+literary ambitions, but I gave them up long ago.  And now, my dear\r
+young friend, if you will allow me to call you so, may I ask if you\r
+really meant all that you said to us at lunch?"\r
+\r
+"I quite forget what I said," smiled Lord Henry.  "Was it all very bad?"\r
+\r
+"Very bad indeed.  In fact I consider you extremely dangerous, and if\r
+anything happens to our good duchess, we shall all look on you as being\r
+primarily responsible.  But I should like to talk to you about life.\r
+The generation into which I was born was tedious.  Some day, when you\r
+are tired of London, come down to Treadley and expound to me your\r
+philosophy of pleasure over some admirable Burgundy I am fortunate\r
+enough to possess."\r
+\r
+"I shall be charmed.  A visit to Treadley would be a great privilege.\r
+It has a perfect host, and a perfect library."\r
+\r
+"You will complete it," answered the old gentleman with a courteous\r
+bow.  "And now I must bid good-bye to your excellent aunt.  I am due at\r
+the Athenaeum.  It is the hour when we sleep there."\r
+\r
+"All of you, Mr. Erskine?"\r
+\r
+"Forty of us, in forty arm-chairs. We are practising for an English\r
+Academy of Letters."\r
+\r
+Lord Henry laughed and rose.  "I am going to the park," he cried.\r
+\r
+As he was passing out of the door, Dorian Gray touched him on the arm.\r
+"Let me come with you," he murmured.\r
+\r
+"But I thought you had promised Basil Hallward to go and see him,"\r
+answered Lord Henry.\r
+\r
+"I would sooner come with you; yes, I feel I must come with you.  Do\r
+let me.  And you will promise to talk to me all the time?  No one talks\r
+so wonderfully as you do."\r
+\r
+"Ah!  I have talked quite enough for to-day," said Lord Henry, smiling.\r
+"All I want now is to look at life.  You may come and look at it with\r
+me, if you care to."\r
+\r
+\r
+\r
+CHAPTER 4\r
+\r
+One afternoon, a month later, Dorian Gray was reclining in a luxurious\r
+arm-chair, in the little library of Lord Henry's house in Mayfair.  It\r
+was, in its way, a very charming room, with its high panelled\r
+wainscoting of olive-stained oak, its cream-coloured frieze and ceiling\r
+of raised plasterwork, and its brickdust felt carpet strewn with silk,\r
+long-fringed Persian rugs.  On a tiny satinwood table stood a statuette\r
+by Clodion, and beside it lay a copy of Les Cent Nouvelles, bound for\r
+Margaret of Valois by Clovis Eve and powdered with the gilt daisies\r
+that Queen had selected for her device.  Some large blue china jars and\r
+parrot-tulips were ranged on the mantelshelf, and through the small\r
+leaded panes of the window streamed the apricot-coloured light of a\r
+summer day in London.\r
+\r
+Lord Henry had not yet come in.  He was always late on principle, his\r
+principle being that punctuality is the thief of time.  So the lad was\r
+looking rather sulky, as with listless fingers he turned over the pages\r
+of an elaborately illustrated edition of Manon Lescaut that he had\r
+found in one of the book-cases. The formal monotonous ticking of the\r
+Louis Quatorze clock annoyed him.  Once or twice he thought of going\r
+away.\r
+\r
+At last he heard a step outside, and the door opened.  "How late you\r
+are, Harry!" he murmured.\r
+\r
+"I am afraid it is not Harry, Mr. Gray," answered a shrill voice.\r
+\r
+He glanced quickly round and rose to his feet.  "I beg your pardon.  I\r
+thought--"\r
+\r
+"You thought it was my husband.  It is only his wife.  You must let me\r
+introduce myself.  I know you quite well by your photographs.  I think\r
+my husband has got seventeen of them."\r
+\r
+"Not seventeen, Lady Henry?"\r
+\r
+"Well, eighteen, then.  And I saw you with him the other night at the\r
+opera."  She laughed nervously as she spoke, and watched him with her\r
+vague forget-me-not eyes.  She was a curious woman, whose dresses\r
+always looked as if they had been designed in a rage and put on in a\r
+tempest.  She was usually in love with somebody, and, as her passion\r
+was never returned, she had kept all her illusions.  She tried to look\r
+picturesque, but only succeeded in being untidy.  Her name was\r
+Victoria, and she had a perfect mania for going to church.\r
+\r
+"That was at Lohengrin, Lady Henry, I think?"\r
+\r
+"Yes; it was at dear Lohengrin.  I like Wagner's music better than\r
+anybody's. It is so loud that one can talk the whole time without other\r
+people hearing what one says.  That is a great advantage, don't you\r
+think so, Mr. Gray?"\r
+\r
+The same nervous staccato laugh broke from her thin lips, and her\r
+fingers began to play with a long tortoise-shell paper-knife.\r
+\r
+Dorian smiled and shook his head:  "I am afraid I don't think so, Lady\r
+Henry.  I never talk during music--at least, during good music.  If one\r
+hears bad music, it is one's duty to drown it in conversation."\r
+\r
+"Ah! that is one of Harry's views, isn't it, Mr. Gray?  I always hear\r
+Harry's views from his friends.  It is the only way I get to know of\r
+them.  But you must not think I don't like good music.  I adore it, but\r
+I am afraid of it.  It makes me too romantic.  I have simply worshipped\r
+pianists--two at a time, sometimes, Harry tells me.  I don't know what\r
+it is about them.  Perhaps it is that they are foreigners.  They all\r
+are, ain't they?  Even those that are born in England become foreigners\r
+after a time, don't they?  It is so clever of them, and such a\r
+compliment to art.  Makes it quite cosmopolitan, doesn't it?  You have\r
+never been to any of my parties, have you, Mr. Gray?  You must come.  I\r
+can't afford orchids, but I spare no expense in foreigners.  They make\r
+one's rooms look so picturesque.  But here is Harry!  Harry, I came in\r
+to look for you, to ask you something--I forget what it was--and I\r
+found Mr. Gray here.  We have had such a pleasant chat about music.  We\r
+have quite the same ideas.  No; I think our ideas are quite different.\r
+But he has been most pleasant.  I am so glad I've seen him."\r
+\r
+"I am charmed, my love, quite charmed," said Lord Henry, elevating his\r
+dark, crescent-shaped eyebrows and looking at them both with an amused\r
+smile.  "So sorry I am late, Dorian.  I went to look after a piece of\r
+old brocade in Wardour Street and had to bargain for hours for it.\r
+Nowadays people know the price of everything and the value of nothing."\r
+\r
+"I am afraid I must be going," exclaimed Lady Henry, breaking an\r
+awkward silence with her silly sudden laugh.  "I have promised to drive\r
+with the duchess.  Good-bye, Mr. Gray.  Good-bye, Harry.  You are\r
+dining out, I suppose?  So am I. Perhaps I shall see you at Lady\r
+Thornbury's."\r
+\r
+"I dare say, my dear," said Lord Henry, shutting the door behind her\r
+as, looking like a bird of paradise that had been out all night in the\r
+rain, she flitted out of the room, leaving a faint odour of\r
+frangipanni.  Then he lit a cigarette and flung himself down on the\r
+sofa.\r
+\r
+"Never marry a woman with straw-coloured hair, Dorian," he said after a\r
+few puffs.\r
+\r
+"Why, Harry?"\r
+\r
+"Because they are so sentimental."\r
+\r
+"But I like sentimental people."\r
+\r
+"Never marry at all, Dorian.  Men marry because they are tired; women,\r
+because they are curious:  both are disappointed."\r
+\r
+"I don't think I am likely to marry, Harry.  I am too much in love.\r
+That is one of your aphorisms.  I am putting it into practice, as I do\r
+everything that you say."\r
+\r
+"Who are you in love with?" asked Lord Henry after a pause.\r
+\r
+"With an actress," said Dorian Gray, blushing.\r
+\r
+Lord Henry shrugged his shoulders.  "That is a rather commonplace\r
+_debut_."\r
+\r
+"You would not say so if you saw her, Harry."\r
+\r
+"Who is she?"\r
+\r
+"Her name is Sibyl Vane."\r
+\r
+"Never heard of her."\r
+\r
+"No one has.  People will some day, however.  She is a genius."\r
+\r
+"My dear boy, no woman is a genius.  Women are a decorative sex.  They\r
+never have anything to say, but they say it charmingly.  Women\r
+represent the triumph of matter over mind, just as men represent the\r
+triumph of mind over morals."\r
+\r
+"Harry, how can you?"\r
+\r
+"My dear Dorian, it is quite true.  I am analysing women at present, so\r
+I ought to know.  The subject is not so abstruse as I thought it was.\r
+I find that, ultimately, there are only two kinds of women, the plain\r
+and the coloured.  The plain women are very useful.  If you want to\r
+gain a reputation for respectability, you have merely to take them down\r
+to supper.  The other women are very charming.  They commit one\r
+mistake, however.  They paint in order to try and look young.  Our\r
+grandmothers painted in order to try and talk brilliantly.  _Rouge_ and\r
+_esprit_ used to go together.  That is all over now.  As long as a woman\r
+can look ten years younger than her own daughter, she is perfectly\r
+satisfied.  As for conversation, there are only five women in London\r
+worth talking to, and two of these can't be admitted into decent\r
+society.  However, tell me about your genius.  How long have you known\r
+her?"\r
+\r
+"Ah!  Harry, your views terrify me."\r
+\r
+"Never mind that.  How long have you known her?"\r
+\r
+"About three weeks."\r
+\r
+"And where did you come across her?"\r
+\r
+"I will tell you, Harry, but you mustn't be unsympathetic about it.\r
+After all, it never would have happened if I had not met you.  You\r
+filled me with a wild desire to know everything about life.  For days\r
+after I met you, something seemed to throb in my veins.  As I lounged\r
+in the park, or strolled down Piccadilly, I used to look at every one\r
+who passed me and wonder, with a mad curiosity, what sort of lives they\r
+led.  Some of them fascinated me.  Others filled me with terror.  There\r
+was an exquisite poison in the air.  I had a passion for sensations....\r
+Well, one evening about seven o'clock, I determined to go out in search\r
+of some adventure.  I felt that this grey monstrous London of ours,\r
+with its myriads of people, its sordid sinners, and its splendid sins,\r
+as you once phrased it, must have something in store for me.  I fancied\r
+a thousand things.  The mere danger gave me a sense of delight.  I\r
+remembered what you had said to me on that wonderful evening when we\r
+first dined together, about the search for beauty being the real secret\r
+of life.  I don't know what I expected, but I went out and wandered\r
+eastward, soon losing my way in a labyrinth of grimy streets and black\r
+grassless squares.  About half-past eight I passed by an absurd little\r
+theatre, with great flaring gas-jets and gaudy play-bills.  A hideous\r
+Jew, in the most amazing waistcoat I ever beheld in my life, was\r
+standing at the entrance, smoking a vile cigar.  He had greasy\r
+ringlets, and an enormous diamond blazed in the centre of a soiled\r
+shirt. 'Have a box, my Lord?' he said, when he saw me, and he took off\r
+his hat with an air of gorgeous servility.  There was something about\r
+him, Harry, that amused me.  He was such a monster.  You will laugh at\r
+me, I know, but I really went in and paid a whole guinea for the\r
+stage-box. To the present day I can't make out why I did so; and yet if\r
+I hadn't--my dear Harry, if I hadn't--I should have missed the greatest\r
+romance of my life.  I see you are laughing.  It is horrid of you!"\r
+\r
+"I am not laughing, Dorian; at least I am not laughing at you.  But you\r
+should not say the greatest romance of your life.  You should say the\r
+first romance of your life.  You will always be loved, and you will\r
+always be in love with love.  A _grande passion_ is the privilege of\r
+people who have nothing to do.  That is the one use of the idle classes\r
+of a country.  Don't be afraid.  There are exquisite things in store\r
+for you.  This is merely the beginning."\r
+\r
+"Do you think my nature so shallow?" cried Dorian Gray angrily.\r
+\r
+"No; I think your nature so deep."\r
+\r
+"How do you mean?"\r
+\r
+"My dear boy, the people who love only once in their lives are really\r
+the shallow people.  What they call their loyalty, and their fidelity,\r
+I call either the lethargy of custom or their lack of imagination.\r
+Faithfulness is to the emotional life what consistency is to the life\r
+of the intellect--simply a confession of failure.  Faithfulness!  I\r
+must analyse it some day.  The passion for property is in it.  There\r
+are many things that we would throw away if we were not afraid that\r
+others might pick them up.  But I don't want to interrupt you.  Go on\r
+with your story."\r
+\r
+"Well, I found myself seated in a horrid little private box, with a\r
+vulgar drop-scene staring me in the face.  I looked out from behind the\r
+curtain and surveyed the house.  It was a tawdry affair, all Cupids and\r
+cornucopias, like a third-rate wedding-cake. The gallery and pit were\r
+fairly full, but the two rows of dingy stalls were quite empty, and\r
+there was hardly a person in what I suppose they called the\r
+dress-circle.  Women went about with oranges and ginger-beer, and there\r
+was a terrible consumption of nuts going on."\r
+\r
+"It must have been just like the palmy days of the British drama."\r
+\r
+"Just like, I should fancy, and very depressing.  I began to wonder\r
+what on earth I should do when I caught sight of the play-bill.  What\r
+do you think the play was, Harry?"\r
+\r
+"I should think 'The Idiot Boy', or 'Dumb but Innocent'.  Our fathers\r
+used to like that sort of piece, I believe.  The longer I live, Dorian,\r
+the more keenly I feel that whatever was good enough for our fathers is\r
+not good enough for us.  In art, as in politics, _les grandperes ont\r
+toujours tort_."\r
+\r
+"This play was good enough for us, Harry.  It was Romeo and Juliet.  I\r
+must admit that I was rather annoyed at the idea of seeing Shakespeare\r
+done in such a wretched hole of a place.  Still, I felt interested, in\r
+a sort of way.  At any rate, I determined to wait for the first act.\r
+There was a dreadful orchestra, presided over by a young Hebrew who sat\r
+at a cracked piano, that nearly drove me away, but at last the\r
+drop-scene was drawn up and the play began.  Romeo was a stout elderly\r
+gentleman, with corked eyebrows, a husky tragedy voice, and a figure\r
+like a beer-barrel. Mercutio was almost as bad.  He was played by the\r
+low-comedian, who had introduced gags of his own and was on most\r
+friendly terms with the pit.  They were both as grotesque as the\r
+scenery, and that looked as if it had come out of a country-booth. But\r
+Juliet!  Harry, imagine a girl, hardly seventeen years of age, with a\r
+little, flowerlike face, a small Greek head with plaited coils of\r
+dark-brown hair, eyes that were violet wells of passion, lips that were\r
+like the petals of a rose.  She was the loveliest thing I had ever seen\r
+in my life.  You said to me once that pathos left you unmoved, but that\r
+beauty, mere beauty, could fill your eyes with tears.  I tell you,\r
+Harry, I could hardly see this girl for the mist of tears that came\r
+across me.  And her voice--I never heard such a voice.  It was very low\r
+at first, with deep mellow notes that seemed to fall singly upon one's\r
+ear.  Then it became a little louder, and sounded like a flute or a\r
+distant hautboy.  In the garden-scene it had all the tremulous ecstasy\r
+that one hears just before dawn when nightingales are singing.  There\r
+were moments, later on, when it had the wild passion of violins.  You\r
+know how a voice can stir one.  Your voice and the voice of Sibyl Vane\r
+are two things that I shall never forget.  When I close my eyes, I hear\r
+them, and each of them says something different.  I don't know which to\r
+follow.  Why should I not love her?  Harry, I do love her.  She is\r
+everything to me in life.  Night after night I go to see her play.  One\r
+evening she is Rosalind, and the next evening she is Imogen.  I have\r
+seen her die in the gloom of an Italian tomb, sucking the poison from\r
+her lover's lips.  I have watched her wandering through the forest of\r
+Arden, disguised as a pretty boy in hose and doublet and dainty cap.\r
+She has been mad, and has come into the presence of a guilty king, and\r
+given him rue to wear and bitter herbs to taste of.  She has been\r
+innocent, and the black hands of jealousy have crushed her reedlike\r
+throat.  I have seen her in every age and in every costume.  Ordinary\r
+women never appeal to one's imagination.  They are limited to their\r
+century.  No glamour ever transfigures them.  One knows their minds as\r
+easily as one knows their bonnets.  One can always find them.  There is\r
+no mystery in any of them.  They ride in the park in the morning and\r
+chatter at tea-parties in the afternoon.  They have their stereotyped\r
+smile and their fashionable manner.  They are quite obvious.  But an\r
+actress!  How different an actress is!  Harry! why didn't you tell me\r
+that the only thing worth loving is an actress?"\r
+\r
+"Because I have loved so many of them, Dorian."\r
+\r
+"Oh, yes, horrid people with dyed hair and painted faces."\r
+\r
+"Don't run down dyed hair and painted faces.  There is an extraordinary\r
+charm in them, sometimes," said Lord Henry.\r
+\r
+"I wish now I had not told you about Sibyl Vane."\r
+\r
+"You could not have helped telling me, Dorian.  All through your life\r
+you will tell me everything you do."\r
+\r
+"Yes, Harry, I believe that is true.  I cannot help telling you things.\r
+You have a curious influence over me.  If I ever did a crime, I would\r
+come and confess it to you.  You would understand me."\r
+\r
+"People like you--the wilful sunbeams of life--don't commit crimes,\r
+Dorian.  But I am much obliged for the compliment, all the same.  And\r
+now tell me--reach me the matches, like a good boy--thanks--what are\r
+your actual relations with Sibyl Vane?"\r
+\r
+Dorian Gray leaped to his feet, with flushed cheeks and burning eyes.\r
+"Harry!  Sibyl Vane is sacred!"\r
+\r
+"It is only the sacred things that are worth touching, Dorian," said\r
+Lord Henry, with a strange touch of pathos in his voice.  "But why\r
+should you be annoyed?  I suppose she will belong to you some day.\r
+When one is in love, one always begins by deceiving one's self, and one\r
+always ends by deceiving others.  That is what the world calls a\r
+romance.  You know her, at any rate, I suppose?"\r
+\r
+"Of course I know her.  On the first night I was at the theatre, the\r
+horrid old Jew came round to the box after the performance was over and\r
+offered to take me behind the scenes and introduce me to her.  I was\r
+furious with him, and told him that Juliet had been dead for hundreds\r
+of years and that her body was lying in a marble tomb in Verona.  I\r
+think, from his blank look of amazement, that he was under the\r
+impression that I had taken too much champagne, or something."\r
+\r
+"I am not surprised."\r
+\r
+"Then he asked me if I wrote for any of the newspapers.  I told him I\r
+never even read them.  He seemed terribly disappointed at that, and\r
+confided to me that all the dramatic critics were in a conspiracy\r
+against him, and that they were every one of them to be bought."\r
+\r
+"I should not wonder if he was quite right there.  But, on the other\r
+hand, judging from their appearance, most of them cannot be at all\r
+expensive."\r
+\r
+"Well, he seemed to think they were beyond his means," laughed Dorian.\r
+"By this time, however, the lights were being put out in the theatre,\r
+and I had to go.  He wanted me to try some cigars that he strongly\r
+recommended.  I declined.  The next night, of course, I arrived at the\r
+place again.  When he saw me, he made me a low bow and assured me that\r
+I was a munificent patron of art.  He was a most offensive brute,\r
+though he had an extraordinary passion for Shakespeare.  He told me\r
+once, with an air of pride, that his five bankruptcies were entirely\r
+due to 'The Bard,' as he insisted on calling him.  He seemed to think\r
+it a distinction."\r
+\r
+"It was a distinction, my dear Dorian--a great distinction.  Most\r
+people become bankrupt through having invested too heavily in the prose\r
+of life.  To have ruined one's self over poetry is an honour.  But when\r
+did you first speak to Miss Sibyl Vane?"\r
+\r
+"The third night.  She had been playing Rosalind.  I could not help\r
+going round.  I had thrown her some flowers, and she had looked at\r
+me--at least I fancied that she had.  The old Jew was persistent.  He\r
+seemed determined to take me behind, so I consented.  It was curious my\r
+not wanting to know her, wasn't it?"\r
+\r
+"No; I don't think so."\r
+\r
+"My dear Harry, why?"\r
+\r
+"I will tell you some other time.  Now I want to know about the girl."\r
+\r
+"Sibyl?  Oh, she was so shy and so gentle.  There is something of a\r
+child about her.  Her eyes opened wide in exquisite wonder when I told\r
+her what I thought of her performance, and she seemed quite unconscious\r
+of her power.  I think we were both rather nervous.  The old Jew stood\r
+grinning at the doorway of the dusty greenroom, making elaborate\r
+speeches about us both, while we stood looking at each other like\r
+children.  He would insist on calling me 'My Lord,' so I had to assure\r
+Sibyl that I was not anything of the kind.  She said quite simply to\r
+me, 'You look more like a prince.  I must call you Prince Charming.'"\r
+\r
+"Upon my word, Dorian, Miss Sibyl knows how to pay compliments."\r
+\r
+"You don't understand her, Harry.  She regarded me merely as a person\r
+in a play.  She knows nothing of life.  She lives with her mother, a\r
+faded tired woman who played Lady Capulet in a sort of magenta\r
+dressing-wrapper on the first night, and looks as if she had seen\r
+better days."\r
+\r
+"I know that look.  It depresses me," murmured Lord Henry, examining\r
+his rings.\r
+\r
+"The Jew wanted to tell me her history, but I said it did not interest\r
+me."\r
+\r
+"You were quite right.  There is always something infinitely mean about\r
+other people's tragedies."\r
+\r
+"Sibyl is the only thing I care about.  What is it to me where she came\r
+from?  From her little head to her little feet, she is absolutely and\r
+entirely divine.  Every night of my life I go to see her act, and every\r
+night she is more marvellous."\r
+\r
+"That is the reason, I suppose, that you never dine with me now.  I\r
+thought you must have some curious romance on hand.  You have; but it\r
+is not quite what I expected."\r
+\r
+"My dear Harry, we either lunch or sup together every day, and I have\r
+been to the opera with you several times," said Dorian, opening his\r
+blue eyes in wonder.\r
+\r
+"You always come dreadfully late."\r
+\r
+"Well, I can't help going to see Sibyl play," he cried, "even if it is\r
+only for a single act.  I get hungry for her presence; and when I think\r
+of the wonderful soul that is hidden away in that little ivory body, I\r
+am filled with awe."\r
+\r
+"You can dine with me to-night, Dorian, can't you?"\r
+\r
+He shook his head.  "To-night she is Imogen," he answered, "and\r
+to-morrow night she will be Juliet."\r
+\r
+"When is she Sibyl Vane?"\r
+\r
+"Never."\r
+\r
+"I congratulate you."\r
+\r
+"How horrid you are!  She is all the great heroines of the world in\r
+one.  She is more than an individual.  You laugh, but I tell you she\r
+has genius.  I love her, and I must make her love me.  You, who know\r
+all the secrets of life, tell me how to charm Sibyl Vane to love me!  I\r
+want to make Romeo jealous.  I want the dead lovers of the world to\r
+hear our laughter and grow sad.  I want a breath of our passion to stir\r
+their dust into consciousness, to wake their ashes into pain.  My God,\r
+Harry, how I worship her!"  He was walking up and down the room as he\r
+spoke.  Hectic spots of red burned on his cheeks.  He was terribly\r
+excited.\r
+\r
+Lord Henry watched him with a subtle sense of pleasure.  How different\r
+he was now from the shy frightened boy he had met in Basil Hallward's\r
+studio!  His nature had developed like a flower, had borne blossoms of\r
+scarlet flame.  Out of its secret hiding-place had crept his soul, and\r
+desire had come to meet it on the way.\r
+\r
+"And what do you propose to do?" said Lord Henry at last.\r
+\r
+"I want you and Basil to come with me some night and see her act.  I\r
+have not the slightest fear of the result.  You are certain to\r
+acknowledge her genius.  Then we must get her out of the Jew's hands.\r
+She is bound to him for three years--at least for two years and eight\r
+months--from the present time.  I shall have to pay him something, of\r
+course.  When all that is settled, I shall take a West End theatre and\r
+bring her out properly.  She will make the world as mad as she has made\r
+me."\r
+\r
+"That would be impossible, my dear boy."\r
+\r
+"Yes, she will.  She has not merely art, consummate art-instinct, in\r
+her, but she has personality also; and you have often told me that it\r
+is personalities, not principles, that move the age."\r
+\r
+"Well, what night shall we go?"\r
+\r
+"Let me see.  To-day is Tuesday.  Let us fix to-morrow. She plays\r
+Juliet to-morrow."\r
+\r
+"All right.  The Bristol at eight o'clock; and I will get Basil."\r
+\r
+"Not eight, Harry, please.  Half-past six.  We must be there before the\r
+curtain rises.  You must see her in the first act, where she meets\r
+Romeo."\r
+\r
+"Half-past six!  What an hour!  It will be like having a meat-tea, or\r
+reading an English novel.  It must be seven.  No gentleman dines before\r
+seven.  Shall you see Basil between this and then?  Or shall I write to\r
+him?"\r
+\r
+"Dear Basil!  I have not laid eyes on him for a week.  It is rather\r
+horrid of me, as he has sent me my portrait in the most wonderful\r
+frame, specially designed by himself, and, though I am a little jealous\r
+of the picture for being a whole month younger than I am, I must admit\r
+that I delight in it.  Perhaps you had better write to him.  I don't\r
+want to see him alone.  He says things that annoy me.  He gives me good\r
+advice."\r
+\r
+Lord Henry smiled.  "People are very fond of giving away what they need\r
+most themselves.  It is what I call the depth of generosity."\r
+\r
+"Oh, Basil is the best of fellows, but he seems to me to be just a bit\r
+of a Philistine.  Since I have known you, Harry, I have discovered\r
+that."\r
+\r
+"Basil, my dear boy, puts everything that is charming in him into his\r
+work.  The consequence is that he has nothing left for life but his\r
+prejudices, his principles, and his common sense.  The only artists I\r
+have ever known who are personally delightful are bad artists.  Good\r
+artists exist simply in what they make, and consequently are perfectly\r
+uninteresting in what they are.  A great poet, a really great poet, is\r
+the most unpoetical of all creatures.  But inferior poets are\r
+absolutely fascinating.  The worse their rhymes are, the more\r
+picturesque they look.  The mere fact of having published a book of\r
+second-rate sonnets makes a man quite irresistible.  He lives the\r
+poetry that he cannot write.  The others write the poetry that they\r
+dare not realize."\r
+\r
+"I wonder is that really so, Harry?" said Dorian Gray, putting some\r
+perfume on his handkerchief out of a large, gold-topped bottle that\r
+stood on the table.  "It must be, if you say it.  And now I am off.\r
+Imogen is waiting for me.  Don't forget about to-morrow. Good-bye."\r
+\r
+As he left the room, Lord Henry's heavy eyelids drooped, and he began\r
+to think.  Certainly few people had ever interested him so much as\r
+Dorian Gray, and yet the lad's mad adoration of some one else caused\r
+him not the slightest pang of annoyance or jealousy.  He was pleased by\r
+it.  It made him a more interesting study.  He had been always\r
+enthralled by the methods of natural science, but the ordinary\r
+subject-matter of that science had seemed to him trivial and of no\r
+import.  And so he had begun by vivisecting himself, as he had ended by\r
+vivisecting others.  Human life--that appeared to him the one thing\r
+worth investigating.  Compared to it there was nothing else of any\r
+value.  It was true that as one watched life in its curious crucible of\r
+pain and pleasure, one could not wear over one's face a mask of glass,\r
+nor keep the sulphurous fumes from troubling the brain and making the\r
+imagination turbid with monstrous fancies and misshapen dreams.  There\r
+were poisons so subtle that to know their properties one had to sicken\r
+of them.  There were maladies so strange that one had to pass through\r
+them if one sought to understand their nature.  And, yet, what a great\r
+reward one received!  How wonderful the whole world became to one!  To\r
+note the curious hard logic of passion, and the emotional coloured life\r
+of the intellect--to observe where they met, and where they separated,\r
+at what point they were in unison, and at what point they were at\r
+discord--there was a delight in that!  What matter what the cost was?\r
+One could never pay too high a price for any sensation.\r
+\r
+He was conscious--and the thought brought a gleam of pleasure into his\r
+brown agate eyes--that it was through certain words of his, musical\r
+words said with musical utterance, that Dorian Gray's soul had turned\r
+to this white girl and bowed in worship before her.  To a large extent\r
+the lad was his own creation.  He had made him premature.  That was\r
+something.  Ordinary people waited till life disclosed to them its\r
+secrets, but to the few, to the elect, the mysteries of life were\r
+revealed before the veil was drawn away.  Sometimes this was the effect\r
+of art, and chiefly of the art of literature, which dealt immediately\r
+with the passions and the intellect.  But now and then a complex\r
+personality took the place and assumed the office of art, was indeed,\r
+in its way, a real work of art, life having its elaborate masterpieces,\r
+just as poetry has, or sculpture, or painting.\r
+\r
+Yes, the lad was premature.  He was gathering his harvest while it was\r
+yet spring.  The pulse and passion of youth were in him, but he was\r
+becoming self-conscious. It was delightful to watch him.  With his\r
+beautiful face, and his beautiful soul, he was a thing to wonder at.\r
+It was no matter how it all ended, or was destined to end.  He was like\r
+one of those gracious figures in a pageant or a play, whose joys seem\r
+to be remote from one, but whose sorrows stir one's sense of beauty,\r
+and whose wounds are like red roses.\r
+\r
+Soul and body, body and soul--how mysterious they were!  There was\r
+animalism in the soul, and the body had its moments of spirituality.\r
+The senses could refine, and the intellect could degrade.  Who could\r
+say where the fleshly impulse ceased, or the psychical impulse began?\r
+How shallow were the arbitrary definitions of ordinary psychologists!\r
+And yet how difficult to decide between the claims of the various\r
+schools!  Was the soul a shadow seated in the house of sin?  Or was the\r
+body really in the soul, as Giordano Bruno thought?  The separation of\r
+spirit from matter was a mystery, and the union of spirit with matter\r
+was a mystery also.\r
+\r
+He began to wonder whether we could ever make psychology so absolute a\r
+science that each little spring of life would be revealed to us.  As it\r
+was, we always misunderstood ourselves and rarely understood others.\r
+Experience was of no ethical value.  It was merely the name men gave to\r
+their mistakes.  Moralists had, as a rule, regarded it as a mode of\r
+warning, had claimed for it a certain ethical efficacy in the formation\r
+of character, had praised it as something that taught us what to follow\r
+and showed us what to avoid.  But there was no motive power in\r
+experience.  It was as little of an active cause as conscience itself.\r
+All that it really demonstrated was that our future would be the same\r
+as our past, and that the sin we had done once, and with loathing, we\r
+would do many times, and with joy.\r
+\r
+It was clear to him that the experimental method was the only method by\r
+which one could arrive at any scientific analysis of the passions; and\r
+certainly Dorian Gray was a subject made to his hand, and seemed to\r
+promise rich and fruitful results.  His sudden mad love for Sibyl Vane\r
+was a psychological phenomenon of no small interest.  There was no\r
+doubt that curiosity had much to do with it, curiosity and the desire\r
+for new experiences, yet it was not a simple, but rather a very complex\r
+passion.  What there was in it of the purely sensuous instinct of\r
+boyhood had been transformed by the workings of the imagination,\r
+changed into something that seemed to the lad himself to be remote from\r
+sense, and was for that very reason all the more dangerous.  It was the\r
+passions about whose origin we deceived ourselves that tyrannized most\r
+strongly over us.  Our weakest motives were those of whose nature we\r
+were conscious.  It often happened that when we thought we were\r
+experimenting on others we were really experimenting on ourselves.\r
+\r
+While Lord Henry sat dreaming on these things, a knock came to the\r
+door, and his valet entered and reminded him it was time to dress for\r
+dinner.  He got up and looked out into the street.  The sunset had\r
+smitten into scarlet gold the upper windows of the houses opposite.\r
+The panes glowed like plates of heated metal.  The sky above was like a\r
+faded rose.  He thought of his friend's young fiery-coloured life and\r
+wondered how it was all going to end.\r
+\r
+When he arrived home, about half-past twelve o'clock, he saw a telegram\r
+lying on the hall table.  He opened it and found it was from Dorian\r
+Gray.  It was to tell him that he was engaged to be married to Sibyl\r
+Vane.\r
+\r
+\r
+\r
+CHAPTER 5\r
+\r
+"Mother, Mother, I am so happy!" whispered the girl, burying her face\r
+in the lap of the faded, tired-looking woman who, with back turned to\r
+the shrill intrusive light, was sitting in the one arm-chair that their\r
+dingy sitting-room contained.  "I am so happy!" she repeated, "and you\r
+must be happy, too!"\r
+\r
+Mrs. Vane winced and put her thin, bismuth-whitened hands on her\r
+daughter's head.  "Happy!" she echoed, "I am only happy, Sibyl, when I\r
+see you act.  You must not think of anything but your acting.  Mr.\r
+Isaacs has been very good to us, and we owe him money."\r
+\r
+The girl looked up and pouted.  "Money, Mother?" she cried, "what does\r
+money matter?  Love is more than money."\r
+\r
+"Mr. Isaacs has advanced us fifty pounds to pay off our debts and to\r
+get a proper outfit for James.  You must not forget that, Sibyl.  Fifty\r
+pounds is a very large sum.  Mr. Isaacs has been most considerate."\r
+\r
+"He is not a gentleman, Mother, and I hate the way he talks to me,"\r
+said the girl, rising to her feet and going over to the window.\r
+\r
+"I don't know how we could manage without him," answered the elder\r
+woman querulously.\r
+\r
+Sibyl Vane tossed her head and laughed.  "We don't want him any more,\r
+Mother.  Prince Charming rules life for us now." Then she paused.  A\r
+rose shook in her blood and shadowed her cheeks.  Quick breath parted\r
+the petals of her lips.  They trembled.  Some southern wind of passion\r
+swept over her and stirred the dainty folds of her dress.  "I love\r
+him," she said simply.\r
+\r
+"Foolish child! foolish child!" was the parrot-phrase flung in answer.\r
+The waving of crooked, false-jewelled fingers gave grotesqueness to the\r
+words.\r
+\r
+The girl laughed again.  The joy of a caged bird was in her voice.  Her\r
+eyes caught the melody and echoed it in radiance, then closed for a\r
+moment, as though to hide their secret.  When they opened, the mist of\r
+a dream had passed across them.\r
+\r
+Thin-lipped wisdom spoke at her from the worn chair, hinted at\r
+prudence, quoted from that book of cowardice whose author apes the name\r
+of common sense.  She did not listen.  She was free in her prison of\r
+passion.  Her prince, Prince Charming, was with her.  She had called on\r
+memory to remake him.  She had sent her soul to search for him, and it\r
+had brought him back.  His kiss burned again upon her mouth.  Her\r
+eyelids were warm with his breath.\r
+\r
+Then wisdom altered its method and spoke of espial and discovery.  This\r
+young man might be rich.  If so, marriage should be thought of.\r
+Against the shell of her ear broke the waves of worldly cunning.  The\r
+arrows of craft shot by her.  She saw the thin lips moving, and smiled.\r
+\r
+Suddenly she felt the need to speak.  The wordy silence troubled her.\r
+"Mother, Mother," she cried, "why does he love me so much?  I know why\r
+I love him.  I love him because he is like what love himself should be.\r
+But what does he see in me?  I am not worthy of him.  And yet--why, I\r
+cannot tell--though I feel so much beneath him, I don't feel humble.  I\r
+feel proud, terribly proud.  Mother, did you love my father as I love\r
+Prince Charming?"\r
+\r
+The elder woman grew pale beneath the coarse powder that daubed her\r
+cheeks, and her dry lips twitched with a spasm of pain.  Sybil rushed\r
+to her, flung her arms round her neck, and kissed her.  "Forgive me,\r
+Mother.  I know it pains you to talk about our father.  But it only\r
+pains you because you loved him so much.  Don't look so sad.  I am as\r
+happy to-day as you were twenty years ago.  Ah! let me be happy for\r
+ever!"\r
+\r
+"My child, you are far too young to think of falling in love.  Besides,\r
+what do you know of this young man?  You don't even know his name.  The\r
+whole thing is most inconvenient, and really, when James is going away\r
+to Australia, and I have so much to think of, I must say that you\r
+should have shown more consideration.  However, as I said before, if he\r
+is rich ..."\r
+\r
+"Ah!  Mother, Mother, let me be happy!"\r
+\r
+Mrs. Vane glanced at her, and with one of those false theatrical\r
+gestures that so often become a mode of second nature to a\r
+stage-player, clasped her in her arms.  At this moment, the door opened\r
+and a young lad with rough brown hair came into the room.  He was\r
+thick-set of figure, and his hands and feet were large and somewhat\r
+clumsy in movement.  He was not so finely bred as his sister.  One\r
+would hardly have guessed the close relationship that existed between\r
+them.  Mrs. Vane fixed her eyes on him and intensified her smile.  She\r
+mentally elevated her son to the dignity of an audience.  She felt sure\r
+that the _tableau_ was interesting.\r
+\r
+"You might keep some of your kisses for me, Sibyl, I think," said the\r
+lad with a good-natured grumble.\r
+\r
+"Ah! but you don't like being kissed, Jim," she cried.  "You are a\r
+dreadful old bear."  And she ran across the room and hugged him.\r
+\r
+James Vane looked into his sister's face with tenderness.  "I want you\r
+to come out with me for a walk, Sibyl.  I don't suppose I shall ever\r
+see this horrid London again.  I am sure I don't want to."\r
+\r
+"My son, don't say such dreadful things," murmured Mrs. Vane, taking up\r
+a tawdry theatrical dress, with a sigh, and beginning to patch it.  She\r
+felt a little disappointed that he had not joined the group.  It would\r
+have increased the theatrical picturesqueness of the situation.\r
+\r
+"Why not, Mother?  I mean it."\r
+\r
+"You pain me, my son.  I trust you will return from Australia in a\r
+position of affluence.  I believe there is no society of any kind in\r
+the Colonies--nothing that I would call society--so when you have made\r
+your fortune, you must come back and assert yourself in London."\r
+\r
+"Society!" muttered the lad.  "I don't want to know anything about\r
+that.  I should like to make some money to take you and Sibyl off the\r
+stage.  I hate it."\r
+\r
+"Oh, Jim!" said Sibyl, laughing, "how unkind of you!  But are you\r
+really going for a walk with me?  That will be nice!  I was afraid you\r
+were going to say good-bye to some of your friends--to Tom Hardy, who\r
+gave you that hideous pipe, or Ned Langton, who makes fun of you for\r
+smoking it.  It is very sweet of you to let me have your last\r
+afternoon.  Where shall we go?  Let us go to the park."\r
+\r
+"I am too shabby," he answered, frowning.  "Only swell people go to the\r
+park."\r
+\r
+"Nonsense, Jim," she whispered, stroking the sleeve of his coat.\r
+\r
+He hesitated for a moment.  "Very well," he said at last, "but don't be\r
+too long dressing."  She danced out of the door.  One could hear her\r
+singing as she ran upstairs.  Her little feet pattered overhead.\r
+\r
+He walked up and down the room two or three times.  Then he turned to\r
+the still figure in the chair.  "Mother, are my things ready?" he asked.\r
+\r
+"Quite ready, James," she answered, keeping her eyes on her work.  For\r
+some months past she had felt ill at ease when she was alone with this\r
+rough stern son of hers.  Her shallow secret nature was troubled when\r
+their eyes met.  She used to wonder if he suspected anything.  The\r
+silence, for he made no other observation, became intolerable to her.\r
+She began to complain.  Women defend themselves by attacking, just as\r
+they attack by sudden and strange surrenders.  "I hope you will be\r
+contented, James, with your sea-faring life," she said.  "You must\r
+remember that it is your own choice.  You might have entered a\r
+solicitor's office.  Solicitors are a very respectable class, and in\r
+the country often dine with the best families."\r
+\r
+"I hate offices, and I hate clerks," he replied.  "But you are quite\r
+right.  I have chosen my own life.  All I say is, watch over Sibyl.\r
+Don't let her come to any harm.  Mother, you must watch over her."\r
+\r
+"James, you really talk very strangely.  Of course I watch over Sibyl."\r
+\r
+"I hear a gentleman comes every night to the theatre and goes behind to\r
+talk to her.  Is that right?  What about that?"\r
+\r
+"You are speaking about things you don't understand, James.  In the\r
+profession we are accustomed to receive a great deal of most gratifying\r
+attention.  I myself used to receive many bouquets at one time.  That\r
+was when acting was really understood.  As for Sibyl, I do not know at\r
+present whether her attachment is serious or not.  But there is no\r
+doubt that the young man in question is a perfect gentleman.  He is\r
+always most polite to me.  Besides, he has the appearance of being\r
+rich, and the flowers he sends are lovely."\r
+\r
+"You don't know his name, though," said the lad harshly.\r
+\r
+"No," answered his mother with a placid expression in her face.  "He\r
+has not yet revealed his real name.  I think it is quite romantic of\r
+him.  He is probably a member of the aristocracy."\r
+\r
+James Vane bit his lip.  "Watch over Sibyl, Mother," he cried, "watch\r
+over her."\r
+\r
+"My son, you distress me very much.  Sibyl is always under my special\r
+care.  Of course, if this gentleman is wealthy, there is no reason why\r
+she should not contract an alliance with him.  I trust he is one of the\r
+aristocracy.  He has all the appearance of it, I must say.  It might be\r
+a most brilliant marriage for Sibyl.  They would make a charming\r
+couple.  His good looks are really quite remarkable; everybody notices\r
+them."\r
+\r
+The lad muttered something to himself and drummed on the window-pane\r
+with his coarse fingers.  He had just turned round to say something\r
+when the door opened and Sibyl ran in.\r
+\r
+"How serious you both are!" she cried.  "What is the matter?"\r
+\r
+"Nothing," he answered.  "I suppose one must be serious sometimes.\r
+Good-bye, Mother; I will have my dinner at five o'clock. Everything is\r
+packed, except my shirts, so you need not trouble."\r
+\r
+"Good-bye, my son," she answered with a bow of strained stateliness.\r
+\r
+She was extremely annoyed at the tone he had adopted with her, and\r
+there was something in his look that had made her feel afraid.\r
+\r
+"Kiss me, Mother," said the girl.  Her flowerlike lips touched the\r
+withered cheek and warmed its frost.\r
+\r
+"My child! my child!" cried Mrs. Vane, looking up to the ceiling in\r
+search of an imaginary gallery.\r
+\r
+"Come, Sibyl," said her brother impatiently.  He hated his mother's\r
+affectations.\r
+\r
+They went out into the flickering, wind-blown sunlight and strolled\r
+down the dreary Euston Road.  The passersby glanced in wonder at the\r
+sullen heavy youth who, in coarse, ill-fitting clothes, was in the\r
+company of such a graceful, refined-looking girl.  He was like a common\r
+gardener walking with a rose.\r
+\r
+Jim frowned from time to time when he caught the inquisitive glance of\r
+some stranger.  He had that dislike of being stared at, which comes on\r
+geniuses late in life and never leaves the commonplace.  Sibyl,\r
+however, was quite unconscious of the effect she was producing.  Her\r
+love was trembling in laughter on her lips.  She was thinking of Prince\r
+Charming, and, that she might think of him all the more, she did not\r
+talk of him, but prattled on about the ship in which Jim was going to\r
+sail, about the gold he was certain to find, about the wonderful\r
+heiress whose life he was to save from the wicked, red-shirted\r
+bushrangers.  For he was not to remain a sailor, or a supercargo, or\r
+whatever he was going to be.  Oh, no!  A sailor's existence was\r
+dreadful.  Fancy being cooped up in a horrid ship, with the hoarse,\r
+hump-backed waves trying to get in, and a black wind blowing the masts\r
+down and tearing the sails into long screaming ribands!  He was to\r
+leave the vessel at Melbourne, bid a polite good-bye to the captain,\r
+and go off at once to the gold-fields. Before a week was over he was to\r
+come across a large nugget of pure gold, the largest nugget that had\r
+ever been discovered, and bring it down to the coast in a waggon\r
+guarded by six mounted policemen.  The bushrangers were to attack them\r
+three times, and be defeated with immense slaughter.  Or, no.  He was\r
+not to go to the gold-fields at all.  They were horrid places, where\r
+men got intoxicated, and shot each other in bar-rooms, and used bad\r
+language.  He was to be a nice sheep-farmer, and one evening, as he was\r
+riding home, he was to see the beautiful heiress being carried off by a\r
+robber on a black horse, and give chase, and rescue her.  Of course,\r
+she would fall in love with him, and he with her, and they would get\r
+married, and come home, and live in an immense house in London.  Yes,\r
+there were delightful things in store for him.  But he must be very\r
+good, and not lose his temper, or spend his money foolishly.  She was\r
+only a year older than he was, but she knew so much more of life.  He\r
+must be sure, also, to write to her by every mail, and to say his\r
+prayers each night before he went to sleep.  God was very good, and\r
+would watch over him.  She would pray for him, too, and in a few years\r
+he would come back quite rich and happy.\r
+\r
+The lad listened sulkily to her and made no answer.  He was heart-sick\r
+at leaving home.\r
+\r
+Yet it was not this alone that made him gloomy and morose.\r
+Inexperienced though he was, he had still a strong sense of the danger\r
+of Sibyl's position.  This young dandy who was making love to her could\r
+mean her no good.  He was a gentleman, and he hated him for that, hated\r
+him through some curious race-instinct for which he could not account,\r
+and which for that reason was all the more dominant within him.  He was\r
+conscious also of the shallowness and vanity of his mother's nature,\r
+and in that saw infinite peril for Sibyl and Sibyl's happiness.\r
+Children begin by loving their parents; as they grow older they judge\r
+them; sometimes they forgive them.\r
+\r
+His mother!  He had something on his mind to ask of her, something that\r
+he had brooded on for many months of silence.  A chance phrase that he\r
+had heard at the theatre, a whispered sneer that had reached his ears\r
+one night as he waited at the stage-door, had set loose a train of\r
+horrible thoughts.  He remembered it as if it had been the lash of a\r
+hunting-crop across his face.  His brows knit together into a wedge-like\r
+furrow, and with a twitch of pain he bit his underlip.\r
+\r
+"You are not listening to a word I am saying, Jim," cried Sibyl, "and I\r
+am making the most delightful plans for your future.  Do say something."\r
+\r
+"What do you want me to say?"\r
+\r
+"Oh! that you will be a good boy and not forget us," she answered,\r
+smiling at him.\r
+\r
+He shrugged his shoulders.  "You are more likely to forget me than I am\r
+to forget you, Sibyl."\r
+\r
+She flushed.  "What do you mean, Jim?" she asked.\r
+\r
+"You have a new friend, I hear.  Who is he?  Why have you not told me\r
+about him?  He means you no good."\r
+\r
+"Stop, Jim!" she exclaimed.  "You must not say anything against him.  I\r
+love him."\r
+\r
+"Why, you don't even know his name," answered the lad.  "Who is he?  I\r
+have a right to know."\r
+\r
+"He is called Prince Charming.  Don't you like the name.  Oh! you silly\r
+boy! you should never forget it.  If you only saw him, you would think\r
+him the most wonderful person in the world.  Some day you will meet\r
+him--when you come back from Australia.  You will like him so much.\r
+Everybody likes him, and I ... love him.  I wish you could come to the\r
+theatre to-night. He is going to be there, and I am to play Juliet.\r
+Oh! how I shall play it!  Fancy, Jim, to be in love and play Juliet!\r
+To have him sitting there!  To play for his delight!  I am afraid I may\r
+frighten the company, frighten or enthrall them.  To be in love is to\r
+surpass one's self.  Poor dreadful Mr. Isaacs will be shouting 'genius'\r
+to his loafers at the bar.  He has preached me as a dogma; to-night he\r
+will announce me as a revelation.  I feel it.  And it is all his, his\r
+only, Prince Charming, my wonderful lover, my god of graces.  But I am\r
+poor beside him.  Poor?  What does that matter?  When poverty creeps in\r
+at the door, love flies in through the window.  Our proverbs want\r
+rewriting.  They were made in winter, and it is summer now; spring-time\r
+for me, I think, a very dance of blossoms in blue skies."\r
+\r
+"He is a gentleman," said the lad sullenly.\r
+\r
+"A prince!" she cried musically.  "What more do you want?"\r
+\r
+"He wants to enslave you."\r
+\r
+"I shudder at the thought of being free."\r
+\r
+"I want you to beware of him."\r
+\r
+"To see him is to worship him; to know him is to trust him."\r
+\r
+"Sibyl, you are mad about him."\r
+\r
+She laughed and took his arm.  "You dear old Jim, you talk as if you\r
+were a hundred.  Some day you will be in love yourself.  Then you will\r
+know what it is.  Don't look so sulky.  Surely you should be glad to\r
+think that, though you are going away, you leave me happier than I have\r
+ever been before.  Life has been hard for us both, terribly hard and\r
+difficult.  But it will be different now.  You are going to a new\r
+world, and I have found one.  Here are two chairs; let us sit down and\r
+see the smart people go by."\r
+\r
+They took their seats amidst a crowd of watchers.  The tulip-beds\r
+across the road flamed like throbbing rings of fire.  A white\r
+dust--tremulous cloud of orris-root it seemed--hung in the panting air.\r
+The brightly coloured parasols danced and dipped like monstrous\r
+butterflies.\r
+\r
+She made her brother talk of himself, his hopes, his prospects.  He\r
+spoke slowly and with effort.  They passed words to each other as\r
+players at a game pass counters.  Sibyl felt oppressed.  She could not\r
+communicate her joy.  A faint smile curving that sullen mouth was all\r
+the echo she could win.  After some time she became silent.  Suddenly\r
+she caught a glimpse of golden hair and laughing lips, and in an open\r
+carriage with two ladies Dorian Gray drove past.\r
+\r
+She started to her feet.  "There he is!" she cried.\r
+\r
+"Who?" said Jim Vane.\r
+\r
+"Prince Charming," she answered, looking after the victoria.\r
+\r
+He jumped up and seized her roughly by the arm.  "Show him to me.\r
+Which is he?  Point him out.  I must see him!" he exclaimed; but at\r
+that moment the Duke of Berwick's four-in-hand came between, and when\r
+it had left the space clear, the carriage had swept out of the park.\r
+\r
+"He is gone," murmured Sibyl sadly.  "I wish you had seen him."\r
+\r
+"I wish I had, for as sure as there is a God in heaven, if he ever does\r
+you any wrong, I shall kill him."\r
+\r
+She looked at him in horror.  He repeated his words.  They cut the air\r
+like a dagger.  The people round began to gape.  A lady standing close\r
+to her tittered.\r
+\r
+"Come away, Jim; come away," she whispered.  He followed her doggedly\r
+as she passed through the crowd.  He felt glad at what he had said.\r
+\r
+When they reached the Achilles Statue, she turned round.  There was\r
+pity in her eyes that became laughter on her lips.  She shook her head\r
+at him.  "You are foolish, Jim, utterly foolish; a bad-tempered boy,\r
+that is all.  How can you say such horrible things?  You don't know\r
+what you are talking about.  You are simply jealous and unkind.  Ah!  I\r
+wish you would fall in love.  Love makes people good, and what you said\r
+was wicked."\r
+\r
+"I am sixteen," he answered, "and I know what I am about.  Mother is no\r
+help to you.  She doesn't understand how to look after you.  I wish now\r
+that I was not going to Australia at all.  I have a great mind to chuck\r
+the whole thing up.  I would, if my articles hadn't been signed."\r
+\r
+"Oh, don't be so serious, Jim.  You are like one of the heroes of those\r
+silly melodramas Mother used to be so fond of acting in.  I am not\r
+going to quarrel with you.  I have seen him, and oh! to see him is\r
+perfect happiness.  We won't quarrel.  I know you would never harm any\r
+one I love, would you?"\r
+\r
+"Not as long as you love him, I suppose," was the sullen answer.\r
+\r
+"I shall love him for ever!" she cried.\r
+\r
+"And he?"\r
+\r
+"For ever, too!"\r
+\r
+"He had better."\r
+\r
+She shrank from him.  Then she laughed and put her hand on his arm.  He\r
+was merely a boy.\r
+\r
+At the Marble Arch they hailed an omnibus, which left them close to\r
+their shabby home in the Euston Road.  It was after five o'clock, and\r
+Sibyl had to lie down for a couple of hours before acting.  Jim\r
+insisted that she should do so.  He said that he would sooner part with\r
+her when their mother was not present.  She would be sure to make a\r
+scene, and he detested scenes of every kind.\r
+\r
+In Sybil's own room they parted.  There was jealousy in the lad's\r
+heart, and a fierce murderous hatred of the stranger who, as it seemed\r
+to him, had come between them.  Yet, when her arms were flung round his\r
+neck, and her fingers strayed through his hair, he softened and kissed\r
+her with real affection.  There were tears in his eyes as he went\r
+downstairs.\r
+\r
+His mother was waiting for him below.  She grumbled at his\r
+unpunctuality, as he entered.  He made no answer, but sat down to his\r
+meagre meal.  The flies buzzed round the table and crawled over the\r
+stained cloth.  Through the rumble of omnibuses, and the clatter of\r
+street-cabs, he could hear the droning voice devouring each minute that\r
+was left to him.\r
+\r
+After some time, he thrust away his plate and put his head in his\r
+hands.  He felt that he had a right to know.  It should have been told\r
+to him before, if it was as he suspected.  Leaden with fear, his mother\r
+watched him.  Words dropped mechanically from her lips.  A tattered\r
+lace handkerchief twitched in her fingers.  When the clock struck six,\r
+he got up and went to the door.  Then he turned back and looked at her.\r
+Their eyes met.  In hers he saw a wild appeal for mercy.  It enraged\r
+him.\r
+\r
+"Mother, I have something to ask you," he said.  Her eyes wandered\r
+vaguely about the room.  She made no answer.  "Tell me the truth.  I\r
+have a right to know.  Were you married to my father?"\r
+\r
+She heaved a deep sigh.  It was a sigh of relief.  The terrible moment,\r
+the moment that night and day, for weeks and months, she had dreaded,\r
+had come at last, and yet she felt no terror.  Indeed, in some measure\r
+it was a disappointment to her.  The vulgar directness of the question\r
+called for a direct answer.  The situation had not been gradually led\r
+up to.  It was crude.  It reminded her of a bad rehearsal.\r
+\r
+"No," she answered, wondering at the harsh simplicity of life.\r
+\r
+"My father was a scoundrel then!" cried the lad, clenching his fists.\r
+\r
+She shook her head.  "I knew he was not free.  We loved each other very\r
+much.  If he had lived, he would have made provision for us.  Don't\r
+speak against him, my son.  He was your father, and a gentleman.\r
+Indeed, he was highly connected."\r
+\r
+An oath broke from his lips.  "I don't care for myself," he exclaimed,\r
+"but don't let Sibyl.... It is a gentleman, isn't it, who is in love\r
+with her, or says he is?  Highly connected, too, I suppose."\r
+\r
+For a moment a hideous sense of humiliation came over the woman.  Her\r
+head drooped.  She wiped her eyes with shaking hands.  "Sibyl has a\r
+mother," she murmured; "I had none."\r
+\r
+The lad was touched.  He went towards her, and stooping down, he kissed\r
+her.  "I am sorry if I have pained you by asking about my father," he\r
+said, "but I could not help it.  I must go now.  Good-bye. Don't forget\r
+that you will have only one child now to look after, and believe me\r
+that if this man wrongs my sister, I will find out who he is, track him\r
+down, and kill him like a dog.  I swear it."\r
+\r
+The exaggerated folly of the threat, the passionate gesture that\r
+accompanied it, the mad melodramatic words, made life seem more vivid\r
+to her.  She was familiar with the atmosphere.  She breathed more\r
+freely, and for the first time for many months she really admired her\r
+son.  She would have liked to have continued the scene on the same\r
+emotional scale, but he cut her short.  Trunks had to be carried down\r
+and mufflers looked for.  The lodging-house drudge bustled in and out.\r
+There was the bargaining with the cabman.  The moment was lost in\r
+vulgar details.  It was with a renewed feeling of disappointment that\r
+she waved the tattered lace handkerchief from the window, as her son\r
+drove away.  She was conscious that a great opportunity had been\r
+wasted.  She consoled herself by telling Sibyl how desolate she felt\r
+her life would be, now that she had only one child to look after.  She\r
+remembered the phrase.  It had pleased her.  Of the threat she said\r
+nothing.  It was vividly and dramatically expressed.  She felt that\r
+they would all laugh at it some day.\r
+\r
+\r
+\r
+CHAPTER 6\r
+\r
+"I suppose you have heard the news, Basil?" said Lord Henry that\r
+evening as Hallward was shown into a little private room at the Bristol\r
+where dinner had been laid for three.\r
+\r
+"No, Harry," answered the artist, giving his hat and coat to the bowing\r
+waiter.  "What is it?  Nothing about politics, I hope!  They don't\r
+interest me.  There is hardly a single person in the House of Commons\r
+worth painting, though many of them would be the better for a little\r
+whitewashing."\r
+\r
+"Dorian Gray is engaged to be married," said Lord Henry, watching him\r
+as he spoke.\r
+\r
+Hallward started and then frowned.  "Dorian engaged to be married!" he\r
+cried.  "Impossible!"\r
+\r
+"It is perfectly true."\r
+\r
+"To whom?"\r
+\r
+"To some little actress or other."\r
+\r
+"I can't believe it.  Dorian is far too sensible."\r
+\r
+"Dorian is far too wise not to do foolish things now and then, my dear\r
+Basil."\r
+\r
+"Marriage is hardly a thing that one can do now and then, Harry."\r
+\r
+"Except in America," rejoined Lord Henry languidly.  "But I didn't say\r
+he was married.  I said he was engaged to be married.  There is a great\r
+difference.  I have a distinct remembrance of being married, but I have\r
+no recollection at all of being engaged.  I am inclined to think that I\r
+never was engaged."\r
+\r
+"But think of Dorian's birth, and position, and wealth.  It would be\r
+absurd for him to marry so much beneath him."\r
+\r
+"If you want to make him marry this girl, tell him that, Basil.  He is\r
+sure to do it, then.  Whenever a man does a thoroughly stupid thing, it\r
+is always from the noblest motives."\r
+\r
+"I hope the girl is good, Harry.  I don't want to see Dorian tied to\r
+some vile creature, who might degrade his nature and ruin his\r
+intellect."\r
+\r
+"Oh, she is better than good--she is beautiful," murmured Lord Henry,\r
+sipping a glass of vermouth and orange-bitters. "Dorian says she is\r
+beautiful, and he is not often wrong about things of that kind.  Your\r
+portrait of him has quickened his appreciation of the personal\r
+appearance of other people.  It has had that excellent effect, amongst\r
+others.  We are to see her to-night, if that boy doesn't forget his\r
+appointment."\r
+\r
+"Are you serious?"\r
+\r
+"Quite serious, Basil.  I should be miserable if I thought I should\r
+ever be more serious than I am at the present moment."\r
+\r
+"But do you approve of it, Harry?" asked the painter, walking up and\r
+down the room and biting his lip.  "You can't approve of it, possibly.\r
+It is some silly infatuation."\r
+\r
+"I never approve, or disapprove, of anything now.  It is an absurd\r
+attitude to take towards life.  We are not sent into the world to air\r
+our moral prejudices.  I never take any notice of what common people\r
+say, and I never interfere with what charming people do.  If a\r
+personality fascinates me, whatever mode of expression that personality\r
+selects is absolutely delightful to me.  Dorian Gray falls in love with\r
+a beautiful girl who acts Juliet, and proposes to marry her.  Why not?\r
+If he wedded Messalina, he would be none the less interesting.  You\r
+know I am not a champion of marriage.  The real drawback to marriage is\r
+that it makes one unselfish.  And unselfish people are colourless.\r
+They lack individuality.  Still, there are certain temperaments that\r
+marriage makes more complex.  They retain their egotism, and add to it\r
+many other egos.  They are forced to have more than one life.  They\r
+become more highly organized, and to be highly organized is, I should\r
+fancy, the object of man's existence.  Besides, every experience is of\r
+value, and whatever one may say against marriage, it is certainly an\r
+experience.  I hope that Dorian Gray will make this girl his wife,\r
+passionately adore her for six months, and then suddenly become\r
+fascinated by some one else.  He would be a wonderful study."\r
+\r
+"You don't mean a single word of all that, Harry; you know you don't.\r
+If Dorian Gray's life were spoiled, no one would be sorrier than\r
+yourself.  You are much better than you pretend to be."\r
+\r
+Lord Henry laughed.  "The reason we all like to think so well of others\r
+is that we are all afraid for ourselves.  The basis of optimism is\r
+sheer terror.  We think that we are generous because we credit our\r
+neighbour with the possession of those virtues that are likely to be a\r
+benefit to us.  We praise the banker that we may overdraw our account,\r
+and find good qualities in the highwayman in the hope that he may spare\r
+our pockets.  I mean everything that I have said.  I have the greatest\r
+contempt for optimism.  As for a spoiled life, no life is spoiled but\r
+one whose growth is arrested.  If you want to mar a nature, you have\r
+merely to reform it.  As for marriage, of course that would be silly,\r
+but there are other and more interesting bonds between men and women.\r
+I will certainly encourage them.  They have the charm of being\r
+fashionable.  But here is Dorian himself.  He will tell you more than I\r
+can."\r
+\r
+"My dear Harry, my dear Basil, you must both congratulate me!" said the\r
+lad, throwing off his evening cape with its satin-lined wings and\r
+shaking each of his friends by the hand in turn.  "I have never been so\r
+happy.  Of course, it is sudden--all really delightful things are.  And\r
+yet it seems to me to be the one thing I have been looking for all my\r
+life." He was flushed with excitement and pleasure, and looked\r
+extraordinarily handsome.\r
+\r
+"I hope you will always be very happy, Dorian," said Hallward, "but I\r
+don't quite forgive you for not having let me know of your engagement.\r
+You let Harry know."\r
+\r
+"And I don't forgive you for being late for dinner," broke in Lord\r
+Henry, putting his hand on the lad's shoulder and smiling as he spoke.\r
+"Come, let us sit down and try what the new _chef_ here is like, and then\r
+you will tell us how it all came about."\r
+\r
+"There is really not much to tell," cried Dorian as they took their\r
+seats at the small round table.  "What happened was simply this.  After\r
+I left you yesterday evening, Harry, I dressed, had some dinner at that\r
+little Italian restaurant in Rupert Street you introduced me to, and\r
+went down at eight o'clock to the theatre.  Sibyl was playing Rosalind.\r
+Of course, the scenery was dreadful and the Orlando absurd.  But Sibyl!\r
+You should have seen her!  When she came on in her boy's clothes, she\r
+was perfectly wonderful.  She wore a moss-coloured velvet jerkin with\r
+cinnamon sleeves, slim, brown, cross-gartered hose, a dainty little\r
+green cap with a hawk's feather caught in a jewel, and a hooded cloak\r
+lined with dull red.  She had never seemed to me more exquisite.  She\r
+had all the delicate grace of that Tanagra figurine that you have in\r
+your studio, Basil.  Her hair clustered round her face like dark leaves\r
+round a pale rose.  As for her acting--well, you shall see her\r
+to-night. She is simply a born artist.  I sat in the dingy box\r
+absolutely enthralled.  I forgot that I was in London and in the\r
+nineteenth century.  I was away with my love in a forest that no man\r
+had ever seen.  After the performance was over, I went behind and spoke\r
+to her.  As we were sitting together, suddenly there came into her eyes\r
+a look that I had never seen there before.  My lips moved towards hers.\r
+We kissed each other.  I can't describe to you what I felt at that\r
+moment.  It seemed to me that all my life had been narrowed to one\r
+perfect point of rose-coloured joy.  She trembled all over and shook\r
+like a white narcissus.  Then she flung herself on her knees and kissed\r
+my hands.  I feel that I should not tell you all this, but I can't help\r
+it.  Of course, our engagement is a dead secret.  She has not even told\r
+her own mother.  I don't know what my guardians will say.  Lord Radley\r
+is sure to be furious.  I don't care.  I shall be of age in less than a\r
+year, and then I can do what I like.  I have been right, Basil, haven't\r
+I, to take my love out of poetry and to find my wife in Shakespeare's\r
+plays?  Lips that Shakespeare taught to speak have whispered their\r
+secret in my ear.  I have had the arms of Rosalind around me, and\r
+kissed Juliet on the mouth."\r
+\r
+"Yes, Dorian, I suppose you were right," said Hallward slowly.\r
+\r
+"Have you seen her to-day?" asked Lord Henry.\r
+\r
+Dorian Gray shook his head.  "I left her in the forest of Arden; I\r
+shall find her in an orchard in Verona."\r
+\r
+Lord Henry sipped his champagne in a meditative manner.  "At what\r
+particular point did you mention the word marriage, Dorian?  And what\r
+did she say in answer?  Perhaps you forgot all about it."\r
+\r
+"My dear Harry, I did not treat it as a business transaction, and I did\r
+not make any formal proposal.  I told her that I loved her, and she\r
+said she was not worthy to be my wife.  Not worthy!  Why, the whole\r
+world is nothing to me compared with her."\r
+\r
+"Women are wonderfully practical," murmured Lord Henry, "much more\r
+practical than we are.  In situations of that kind we often forget to\r
+say anything about marriage, and they always remind us."\r
+\r
+Hallward laid his hand upon his arm.  "Don't, Harry.  You have annoyed\r
+Dorian.  He is not like other men.  He would never bring misery upon\r
+any one.  His nature is too fine for that."\r
+\r
+Lord Henry looked across the table.  "Dorian is never annoyed with me,"\r
+he answered.  "I asked the question for the best reason possible, for\r
+the only reason, indeed, that excuses one for asking any\r
+question--simple curiosity.  I have a theory that it is always the\r
+women who propose to us, and not we who propose to the women.  Except,\r
+of course, in middle-class life.  But then the middle classes are not\r
+modern."\r
+\r
+Dorian Gray laughed, and tossed his head.  "You are quite incorrigible,\r
+Harry; but I don't mind.  It is impossible to be angry with you.  When\r
+you see Sibyl Vane, you will feel that the man who could wrong her\r
+would be a beast, a beast without a heart.  I cannot understand how any\r
+one can wish to shame the thing he loves.  I love Sibyl Vane.  I want\r
+to place her on a pedestal of gold and to see the world worship the\r
+woman who is mine.  What is marriage?  An irrevocable vow.  You mock at\r
+it for that.  Ah! don't mock.  It is an irrevocable vow that I want to\r
+take.  Her trust makes me faithful, her belief makes me good.  When I\r
+am with her, I regret all that you have taught me.  I become different\r
+from what you have known me to be.  I am changed, and the mere touch of\r
+Sibyl Vane's hand makes me forget you and all your wrong, fascinating,\r
+poisonous, delightful theories."\r
+\r
+"And those are ...?" asked Lord Henry, helping himself to some salad.\r
+\r
+"Oh, your theories about life, your theories about love, your theories\r
+about pleasure.  All your theories, in fact, Harry."\r
+\r
+"Pleasure is the only thing worth having a theory about," he answered\r
+in his slow melodious voice.  "But I am afraid I cannot claim my theory\r
+as my own.  It belongs to Nature, not to me.  Pleasure is Nature's\r
+test, her sign of approval.  When we are happy, we are always good, but\r
+when we are good, we are not always happy."\r
+\r
+"Ah! but what do you mean by good?" cried Basil Hallward.\r
+\r
+"Yes," echoed Dorian, leaning back in his chair and looking at Lord\r
+Henry over the heavy clusters of purple-lipped irises that stood in the\r
+centre of the table, "what do you mean by good, Harry?"\r
+\r
+"To be good is to be in harmony with one's self," he replied, touching\r
+the thin stem of his glass with his pale, fine-pointed fingers.\r
+"Discord is to be forced to be in harmony with others.  One's own\r
+life--that is the important thing.  As for the lives of one's\r
+neighbours, if one wishes to be a prig or a Puritan, one can flaunt\r
+one's moral views about them, but they are not one's concern.  Besides,\r
+individualism has really the higher aim.  Modern morality consists in\r
+accepting the standard of one's age.  I consider that for any man of\r
+culture to accept the standard of his age is a form of the grossest\r
+immorality."\r
+\r
+"But, surely, if one lives merely for one's self, Harry, one pays a\r
+terrible price for doing so?" suggested the painter.\r
+\r
+"Yes, we are overcharged for everything nowadays.  I should fancy that\r
+the real tragedy of the poor is that they can afford nothing but\r
+self-denial. Beautiful sins, like beautiful things, are the privilege\r
+of the rich."\r
+\r
+"One has to pay in other ways but money."\r
+\r
+"What sort of ways, Basil?"\r
+\r
+"Oh!  I should fancy in remorse, in suffering, in ... well, in the\r
+consciousness of degradation."\r
+\r
+Lord Henry shrugged his shoulders.  "My dear fellow, mediaeval art is\r
+charming, but mediaeval emotions are out of date.  One can use them in\r
+fiction, of course.  But then the only things that one can use in\r
+fiction are the things that one has ceased to use in fact.  Believe me,\r
+no civilized man ever regrets a pleasure, and no uncivilized man ever\r
+knows what a pleasure is."\r
+\r
+"I know what pleasure is," cried Dorian Gray.  "It is to adore some\r
+one."\r
+\r
+"That is certainly better than being adored," he answered, toying with\r
+some fruits.  "Being adored is a nuisance.  Women treat us just as\r
+humanity treats its gods.  They worship us, and are always bothering us\r
+to do something for them."\r
+\r
+"I should have said that whatever they ask for they had first given to\r
+us," murmured the lad gravely.  "They create love in our natures.  They\r
+have a right to demand it back."\r
+\r
+"That is quite true, Dorian," cried Hallward.\r
+\r
+"Nothing is ever quite true," said Lord Henry.\r
+\r
+"This is," interrupted Dorian.  "You must admit, Harry, that women give\r
+to men the very gold of their lives."\r
+\r
+"Possibly," he sighed, "but they invariably want it back in such very\r
+small change.  That is the worry.  Women, as some witty Frenchman once\r
+put it, inspire us with the desire to do masterpieces and always\r
+prevent us from carrying them out."\r
+\r
+"Harry, you are dreadful!  I don't know why I like you so much."\r
+\r
+"You will always like me, Dorian," he replied.  "Will you have some\r
+coffee, you fellows?  Waiter, bring coffee, and _fine-champagne_, and\r
+some cigarettes.  No, don't mind the cigarettes--I have some.  Basil, I\r
+can't allow you to smoke cigars.  You must have a cigarette.  A\r
+cigarette is the perfect type of a perfect pleasure.  It is exquisite,\r
+and it leaves one unsatisfied.  What more can one want?  Yes, Dorian,\r
+you will always be fond of me.  I represent to you all the sins you\r
+have never had the courage to commit."\r
+\r
+"What nonsense you talk, Harry!" cried the lad, taking a light from a\r
+fire-breathing silver dragon that the waiter had placed on the table.\r
+"Let us go down to the theatre.  When Sibyl comes on the stage you will\r
+have a new ideal of life.  She will represent something to you that you\r
+have never known."\r
+\r
+"I have known everything," said Lord Henry, with a tired look in his\r
+eyes, "but I am always ready for a new emotion.  I am afraid, however,\r
+that, for me at any rate, there is no such thing.  Still, your\r
+wonderful girl may thrill me.  I love acting.  It is so much more real\r
+than life.  Let us go.  Dorian, you will come with me.  I am so sorry,\r
+Basil, but there is only room for two in the brougham.  You must follow\r
+us in a hansom."\r
+\r
+They got up and put on their coats, sipping their coffee standing.  The\r
+painter was silent and preoccupied.  There was a gloom over him.  He\r
+could not bear this marriage, and yet it seemed to him to be better\r
+than many other things that might have happened.  After a few minutes,\r
+they all passed downstairs.  He drove off by himself, as had been\r
+arranged, and watched the flashing lights of the little brougham in\r
+front of him.  A strange sense of loss came over him.  He felt that\r
+Dorian Gray would never again be to him all that he had been in the\r
+past.  Life had come between them.... His eyes darkened, and the\r
+crowded flaring streets became blurred to his eyes.  When the cab drew\r
+up at the theatre, it seemed to him that he had grown years older.\r
+\r
+\r
+\r
+CHAPTER 7\r
+\r
+For some reason or other, the house was crowded that night, and the fat\r
+Jew manager who met them at the door was beaming from ear to ear with\r
+an oily tremulous smile.  He escorted them to their box with a sort of\r
+pompous humility, waving his fat jewelled hands and talking at the top\r
+of his voice.  Dorian Gray loathed him more than ever.  He felt as if\r
+he had come to look for Miranda and had been met by Caliban.  Lord\r
+Henry, upon the other hand, rather liked him.  At least he declared he\r
+did, and insisted on shaking him by the hand and assuring him that he\r
+was proud to meet a man who had discovered a real genius and gone\r
+bankrupt over a poet.  Hallward amused himself with watching the faces\r
+in the pit.  The heat was terribly oppressive, and the huge sunlight\r
+flamed like a monstrous dahlia with petals of yellow fire.  The youths\r
+in the gallery had taken off their coats and waistcoats and hung them\r
+over the side.  They talked to each other across the theatre and shared\r
+their oranges with the tawdry girls who sat beside them.  Some women\r
+were laughing in the pit.  Their voices were horribly shrill and\r
+discordant.  The sound of the popping of corks came from the bar.\r
+\r
+"What a place to find one's divinity in!" said Lord Henry.\r
+\r
+"Yes!" answered Dorian Gray.  "It was here I found her, and she is\r
+divine beyond all living things.  When she acts, you will forget\r
+everything.  These common rough people, with their coarse faces and\r
+brutal gestures, become quite different when she is on the stage.  They\r
+sit silently and watch her.  They weep and laugh as she wills them to\r
+do.  She makes them as responsive as a violin.  She spiritualizes them,\r
+and one feels that they are of the same flesh and blood as one's self."\r
+\r
+"The same flesh and blood as one's self!  Oh, I hope not!" exclaimed\r
+Lord Henry, who was scanning the occupants of the gallery through his\r
+opera-glass.\r
+\r
+"Don't pay any attention to him, Dorian," said the painter.  "I\r
+understand what you mean, and I believe in this girl.  Any one you love\r
+must be marvellous, and any girl who has the effect you describe must\r
+be fine and noble.  To spiritualize one's age--that is something worth\r
+doing.  If this girl can give a soul to those who have lived without\r
+one, if she can create the sense of beauty in people whose lives have\r
+been sordid and ugly, if she can strip them of their selfishness and\r
+lend them tears for sorrows that are not their own, she is worthy of\r
+all your adoration, worthy of the adoration of the world.  This\r
+marriage is quite right.  I did not think so at first, but I admit it\r
+now.  The gods made Sibyl Vane for you.  Without her you would have\r
+been incomplete."\r
+\r
+"Thanks, Basil," answered Dorian Gray, pressing his hand.  "I knew that\r
+you would understand me.  Harry is so cynical, he terrifies me.  But\r
+here is the orchestra.  It is quite dreadful, but it only lasts for\r
+about five minutes.  Then the curtain rises, and you will see the girl\r
+to whom I am going to give all my life, to whom I have given everything\r
+that is good in me."\r
+\r
+A quarter of an hour afterwards, amidst an extraordinary turmoil of\r
+applause, Sibyl Vane stepped on to the stage.  Yes, she was certainly\r
+lovely to look at--one of the loveliest creatures, Lord Henry thought,\r
+that he had ever seen.  There was something of the fawn in her shy\r
+grace and startled eyes.  A faint blush, like the shadow of a rose in a\r
+mirror of silver, came to her cheeks as she glanced at the crowded\r
+enthusiastic house.  She stepped back a few paces and her lips seemed\r
+to tremble.  Basil Hallward leaped to his feet and began to applaud.\r
+Motionless, and as one in a dream, sat Dorian Gray, gazing at her.\r
+Lord Henry peered through his glasses, murmuring, "Charming! charming!"\r
+\r
+The scene was the hall of Capulet's house, and Romeo in his pilgrim's\r
+dress had entered with Mercutio and his other friends.  The band, such\r
+as it was, struck up a few bars of music, and the dance began.  Through\r
+the crowd of ungainly, shabbily dressed actors, Sibyl Vane moved like a\r
+creature from a finer world.  Her body swayed, while she danced, as a\r
+plant sways in the water.  The curves of her throat were the curves of\r
+a white lily.  Her hands seemed to be made of cool ivory.\r
+\r
+Yet she was curiously listless.  She showed no sign of joy when her\r
+eyes rested on Romeo.  The few words she had to speak--\r
+\r
+    Good pilgrim, you do wrong your hand too much,\r
+        Which mannerly devotion shows in this;\r
+    For saints have hands that pilgrims' hands do touch,\r
+        And palm to palm is holy palmers' kiss--\r
+\r
+with the brief dialogue that follows, were spoken in a thoroughly\r
+artificial manner.  The voice was exquisite, but from the point of view\r
+of tone it was absolutely false.  It was wrong in colour.  It took away\r
+all the life from the verse.  It made the passion unreal.\r
+\r
+Dorian Gray grew pale as he watched her.  He was puzzled and anxious.\r
+Neither of his friends dared to say anything to him.  She seemed to\r
+them to be absolutely incompetent.  They were horribly disappointed.\r
+\r
+Yet they felt that the true test of any Juliet is the balcony scene of\r
+the second act.  They waited for that.  If she failed there, there was\r
+nothing in her.\r
+\r
+She looked charming as she came out in the moonlight.  That could not\r
+be denied.  But the staginess of her acting was unbearable, and grew\r
+worse as she went on.  Her gestures became absurdly artificial.  She\r
+overemphasized everything that she had to say.  The beautiful passage--\r
+\r
+    Thou knowest the mask of night is on my face,\r
+    Else would a maiden blush bepaint my cheek\r
+    For that which thou hast heard me speak to-night--\r
+\r
+was declaimed with the painful precision of a schoolgirl who has been\r
+taught to recite by some second-rate professor of elocution. When she\r
+leaned over the balcony and came to those wonderful lines--\r
+\r
+        Although I joy in thee,\r
+    I have no joy of this contract to-night:\r
+    It is too rash, too unadvised, too sudden;\r
+    Too like the lightning, which doth cease to be\r
+    Ere one can say, "It lightens."  Sweet, good-night!\r
+    This bud of love by summer's ripening breath\r
+    May prove a beauteous flower when next we meet--\r
+\r
+she spoke the words as though they conveyed no meaning to her. It was\r
+not nervousness. Indeed, so far from being nervous, she was absolutely\r
+self-contained. It was simply bad art. She was a complete failure.\r
+\r
+Even the common uneducated audience of the pit and gallery lost their\r
+interest in the play. They got restless, and began to talk loudly and\r
+to whistle. The Jew manager, who was standing at the back of the\r
+dress-circle, stamped and swore with rage. The only person unmoved was\r
+the girl herself.\r
+\r
+When the second act was over, there came a storm of hisses, and Lord\r
+Henry got up from his chair and put on his coat.  "She is quite\r
+beautiful, Dorian," he said, "but she can't act.  Let us go."\r
+\r
+"I am going to see the play through," answered the lad, in a hard\r
+bitter voice.  "I am awfully sorry that I have made you waste an\r
+evening, Harry.  I apologize to you both."\r
+\r
+"My dear Dorian, I should think Miss Vane was ill," interrupted\r
+Hallward.  "We will come some other night."\r
+\r
+"I wish she were ill," he rejoined.  "But she seems to me to be simply\r
+callous and cold.  She has entirely altered.  Last night she was a\r
+great artist.  This evening she is merely a commonplace mediocre\r
+actress."\r
+\r
+"Don't talk like that about any one you love, Dorian.  Love is a more\r
+wonderful thing than art."\r
+\r
+"They are both simply forms of imitation," remarked Lord Henry.  "But\r
+do let us go.  Dorian, you must not stay here any longer.  It is not\r
+good for one's morals to see bad acting.  Besides, I don't suppose you\r
+will want your wife to act, so what does it matter if she plays Juliet\r
+like a wooden doll?  She is very lovely, and if she knows as little\r
+about life as she does about acting, she will be a delightful\r
+experience.  There are only two kinds of people who are really\r
+fascinating--people who know absolutely everything, and people who know\r
+absolutely nothing.  Good heavens, my dear boy, don't look so tragic!\r
+The secret of remaining young is never to have an emotion that is\r
+unbecoming.  Come to the club with Basil and myself.  We will smoke\r
+cigarettes and drink to the beauty of Sibyl Vane.  She is beautiful.\r
+What more can you want?"\r
+\r
+"Go away, Harry," cried the lad.  "I want to be alone.  Basil, you must\r
+go.  Ah! can't you see that my heart is breaking?"  The hot tears came\r
+to his eyes.  His lips trembled, and rushing to the back of the box, he\r
+leaned up against the wall, hiding his face in his hands.\r
+\r
+"Let us go, Basil," said Lord Henry with a strange tenderness in his\r
+voice, and the two young men passed out together.\r
+\r
+A few moments afterwards the footlights flared up and the curtain rose\r
+on the third act.  Dorian Gray went back to his seat.  He looked pale,\r
+and proud, and indifferent.  The play dragged on, and seemed\r
+interminable.  Half of the audience went out, tramping in heavy boots\r
+and laughing.  The whole thing was a _fiasco_.  The last act was played\r
+to almost empty benches.  The curtain went down on a titter and some\r
+groans.\r
+\r
+As soon as it was over, Dorian Gray rushed behind the scenes into the\r
+greenroom.  The girl was standing there alone, with a look of triumph\r
+on her face.  Her eyes were lit with an exquisite fire.  There was a\r
+radiance about her.  Her parted lips were smiling over some secret of\r
+their own.\r
+\r
+When he entered, she looked at him, and an expression of infinite joy\r
+came over her.  "How badly I acted to-night, Dorian!" she cried.\r
+\r
+"Horribly!" he answered, gazing at her in amazement.  "Horribly!  It\r
+was dreadful.  Are you ill?  You have no idea what it was.  You have no\r
+idea what I suffered."\r
+\r
+The girl smiled.  "Dorian," she answered, lingering over his name with\r
+long-drawn music in her voice, as though it were sweeter than honey to\r
+the red petals of her mouth.  "Dorian, you should have understood.  But\r
+you understand now, don't you?"\r
+\r
+"Understand what?" he asked, angrily.\r
+\r
+"Why I was so bad to-night. Why I shall always be bad.  Why I shall\r
+never act well again."\r
+\r
+He shrugged his shoulders.  "You are ill, I suppose.  When you are ill\r
+you shouldn't act.  You make yourself ridiculous.  My friends were\r
+bored.  I was bored."\r
+\r
+She seemed not to listen to him.  She was transfigured with joy.  An\r
+ecstasy of happiness dominated her.\r
+\r
+"Dorian, Dorian," she cried, "before I knew you, acting was the one\r
+reality of my life.  It was only in the theatre that I lived.  I\r
+thought that it was all true.  I was Rosalind one night and Portia the\r
+other.  The joy of Beatrice was my joy, and the sorrows of Cordelia\r
+were mine also.  I believed in everything.  The common people who acted\r
+with me seemed to me to be godlike.  The painted scenes were my world.\r
+I knew nothing but shadows, and I thought them real.  You came--oh, my\r
+beautiful love!--and you freed my soul from prison.  You taught me what\r
+reality really is.  To-night, for the first time in my life, I saw\r
+through the hollowness, the sham, the silliness of the empty pageant in\r
+which I had always played.  To-night, for the first time, I became\r
+conscious that the Romeo was hideous, and old, and painted, that the\r
+moonlight in the orchard was false, that the scenery was vulgar, and\r
+that the words I had to speak were unreal, were not my words, were not\r
+what I wanted to say.  You had brought me something higher, something\r
+of which all art is but a reflection.  You had made me understand what\r
+love really is.  My love!  My love!  Prince Charming!  Prince of life!\r
+I have grown sick of shadows.  You are more to me than all art can ever\r
+be.  What have I to do with the puppets of a play?  When I came on\r
+to-night, I could not understand how it was that everything had gone\r
+from me.  I thought that I was going to be wonderful.  I found that I\r
+could do nothing.  Suddenly it dawned on my soul what it all meant.\r
+The knowledge was exquisite to me.  I heard them hissing, and I smiled.\r
+What could they know of love such as ours?  Take me away, Dorian--take\r
+me away with you, where we can be quite alone.  I hate the stage.  I\r
+might mimic a passion that I do not feel, but I cannot mimic one that\r
+burns me like fire.  Oh, Dorian, Dorian, you understand now what it\r
+signifies?  Even if I could do it, it would be profanation for me to\r
+play at being in love.  You have made me see that."\r
+\r
+He flung himself down on the sofa and turned away his face.  "You have\r
+killed my love," he muttered.\r
+\r
+She looked at him in wonder and laughed.  He made no answer.  She came\r
+across to him, and with her little fingers stroked his hair.  She knelt\r
+down and pressed his hands to her lips.  He drew them away, and a\r
+shudder ran through him.\r
+\r
+Then he leaped up and went to the door.  "Yes," he cried, "you have\r
+killed my love.  You used to stir my imagination.  Now you don't even\r
+stir my curiosity.  You simply produce no effect.  I loved you because\r
+you were marvellous, because you had genius and intellect, because you\r
+realized the dreams of great poets and gave shape and substance to the\r
+shadows of art.  You have thrown it all away.  You are shallow and\r
+stupid.  My God! how mad I was to love you!  What a fool I have been!\r
+You are nothing to me now.  I will never see you again.  I will never\r
+think of you.  I will never mention your name.  You don't know what you\r
+were to me, once.  Why, once ... Oh, I can't bear to think of it!  I\r
+wish I had never laid eyes upon you!  You have spoiled the romance of\r
+my life.  How little you can know of love, if you say it mars your art!\r
+Without your art, you are nothing.  I would have made you famous,\r
+splendid, magnificent.  The world would have worshipped you, and you\r
+would have borne my name.  What are you now?  A third-rate actress with\r
+a pretty face."\r
+\r
+The girl grew white, and trembled.  She clenched her hands together,\r
+and her voice seemed to catch in her throat.  "You are not serious,\r
+Dorian?" she murmured.  "You are acting."\r
+\r
+"Acting!  I leave that to you.  You do it so well," he answered\r
+bitterly.\r
+\r
+She rose from her knees and, with a piteous expression of pain in her\r
+face, came across the room to him.  She put her hand upon his arm and\r
+looked into his eyes.  He thrust her back.  "Don't touch me!" he cried.\r
+\r
+A low moan broke from her, and she flung herself at his feet and lay\r
+there like a trampled flower.  "Dorian, Dorian, don't leave me!" she\r
+whispered.  "I am so sorry I didn't act well.  I was thinking of you\r
+all the time.  But I will try--indeed, I will try.  It came so suddenly\r
+across me, my love for you.  I think I should never have known it if\r
+you had not kissed me--if we had not kissed each other.  Kiss me again,\r
+my love.  Don't go away from me.  I couldn't bear it.  Oh! don't go\r
+away from me.  My brother ... No; never mind.  He didn't mean it.  He\r
+was in jest.... But you, oh! can't you forgive me for to-night? I will\r
+work so hard and try to improve.  Don't be cruel to me, because I love\r
+you better than anything in the world.  After all, it is only once that\r
+I have not pleased you.  But you are quite right, Dorian.  I should\r
+have shown myself more of an artist.  It was foolish of me, and yet I\r
+couldn't help it.  Oh, don't leave me, don't leave me." A fit of\r
+passionate sobbing choked her.  She crouched on the floor like a\r
+wounded thing, and Dorian Gray, with his beautiful eyes, looked down at\r
+her, and his chiselled lips curled in exquisite disdain.  There is\r
+always something ridiculous about the emotions of people whom one has\r
+ceased to love.  Sibyl Vane seemed to him to be absurdly melodramatic.\r
+Her tears and sobs annoyed him.\r
+\r
+"I am going," he said at last in his calm clear voice.  "I don't wish\r
+to be unkind, but I can't see you again.  You have disappointed me."\r
+\r
+She wept silently, and made no answer, but crept nearer.  Her little\r
+hands stretched blindly out, and appeared to be seeking for him.  He\r
+turned on his heel and left the room.  In a few moments he was out of\r
+the theatre.\r
+\r
+Where he went to he hardly knew.  He remembered wandering through dimly\r
+lit streets, past gaunt, black-shadowed archways and evil-looking\r
+houses.  Women with hoarse voices and harsh laughter had called after\r
+him.  Drunkards had reeled by, cursing and chattering to themselves\r
+like monstrous apes.  He had seen grotesque children huddled upon\r
+door-steps, and heard shrieks and oaths from gloomy courts.\r
+\r
+As the dawn was just breaking, he found himself close to Covent Garden.\r
+The darkness lifted, and, flushed with faint fires, the sky hollowed\r
+itself into a perfect pearl.  Huge carts filled with nodding lilies\r
+rumbled slowly down the polished empty street.  The air was heavy with\r
+the perfume of the flowers, and their beauty seemed to bring him an\r
+anodyne for his pain.  He followed into the market and watched the men\r
+unloading their waggons.  A white-smocked carter offered him some\r
+cherries.  He thanked him, wondered why he refused to accept any money\r
+for them, and began to eat them listlessly.  They had been plucked at\r
+midnight, and the coldness of the moon had entered into them.  A long\r
+line of boys carrying crates of striped tulips, and of yellow and red\r
+roses, defiled in front of him, threading their way through the huge,\r
+jade-green piles of vegetables.  Under the portico, with its grey,\r
+sun-bleached pillars, loitered a troop of draggled bareheaded girls,\r
+waiting for the auction to be over.  Others crowded round the swinging\r
+doors of the coffee-house in the piazza.  The heavy cart-horses slipped\r
+and stamped upon the rough stones, shaking their bells and trappings.\r
+Some of the drivers were lying asleep on a pile of sacks.  Iris-necked\r
+and pink-footed, the pigeons ran about picking up seeds.\r
+\r
+After a little while, he hailed a hansom and drove home.  For a few\r
+moments he loitered upon the doorstep, looking round at the silent\r
+square, with its blank, close-shuttered windows and its staring blinds.\r
+The sky was pure opal now, and the roofs of the houses glistened like\r
+silver against it.  From some chimney opposite a thin wreath of smoke\r
+was rising.  It curled, a violet riband, through the nacre-coloured air.\r
+\r
+In the huge gilt Venetian lantern, spoil of some Doge's barge, that\r
+hung from the ceiling of the great, oak-panelled hall of entrance,\r
+lights were still burning from three flickering jets: thin blue petals\r
+of flame they seemed, rimmed with white fire.  He turned them out and,\r
+having thrown his hat and cape on the table, passed through the library\r
+towards the door of his bedroom, a large octagonal chamber on the\r
+ground floor that, in his new-born feeling for luxury, he had just had\r
+decorated for himself and hung with some curious Renaissance tapestries\r
+that had been discovered stored in a disused attic at Selby Royal.  As\r
+he was turning the handle of the door, his eye fell upon the portrait\r
+Basil Hallward had painted of him.  He started back as if in surprise.\r
+Then he went on into his own room, looking somewhat puzzled.  After he\r
+had taken the button-hole out of his coat, he seemed to hesitate.\r
+Finally, he came back, went over to the picture, and examined it.  In\r
+the dim arrested light that struggled through the cream-coloured silk\r
+blinds, the face appeared to him to be a little changed.  The\r
+expression looked different.  One would have said that there was a\r
+touch of cruelty in the mouth.  It was certainly strange.\r
+\r
+He turned round and, walking to the window, drew up the blind.  The\r
+bright dawn flooded the room and swept the fantastic shadows into dusky\r
+corners, where they lay shuddering.  But the strange expression that he\r
+had noticed in the face of the portrait seemed to linger there, to be\r
+more intensified even.  The quivering ardent sunlight showed him the\r
+lines of cruelty round the mouth as clearly as if he had been looking\r
+into a mirror after he had done some dreadful thing.\r
+\r
+He winced and, taking up from the table an oval glass framed in ivory\r
+Cupids, one of Lord Henry's many presents to him, glanced hurriedly\r
+into its polished depths.  No line like that warped his red lips.  What\r
+did it mean?\r
+\r
+He rubbed his eyes, and came close to the picture, and examined it\r
+again.  There were no signs of any change when he looked into the\r
+actual painting, and yet there was no doubt that the whole expression\r
+had altered.  It was not a mere fancy of his own.  The thing was\r
+horribly apparent.\r
+\r
+He threw himself into a chair and began to think.  Suddenly there\r
+flashed across his mind what he had said in Basil Hallward's studio the\r
+day the picture had been finished.  Yes, he remembered it perfectly.\r
+He had uttered a mad wish that he himself might remain young, and the\r
+portrait grow old; that his own beauty might be untarnished, and the\r
+face on the canvas bear the burden of his passions and his sins; that\r
+the painted image might be seared with the lines of suffering and\r
+thought, and that he might keep all the delicate bloom and loveliness\r
+of his then just conscious boyhood.  Surely his wish had not been\r
+fulfilled?  Such things were impossible.  It seemed monstrous even to\r
+think of them.  And, yet, there was the picture before him, with the\r
+touch of cruelty in the mouth.\r
+\r
+Cruelty!  Had he been cruel?  It was the girl's fault, not his.  He had\r
+dreamed of her as a great artist, had given his love to her because he\r
+had thought her great.  Then she had disappointed him.  She had been\r
+shallow and unworthy.  And, yet, a feeling of infinite regret came over\r
+him, as he thought of her lying at his feet sobbing like a little\r
+child.  He remembered with what callousness he had watched her.  Why\r
+had he been made like that?  Why had such a soul been given to him?\r
+But he had suffered also.  During the three terrible hours that the\r
+play had lasted, he had lived centuries of pain, aeon upon aeon of\r
+torture.  His life was well worth hers.  She had marred him for a\r
+moment, if he had wounded her for an age.  Besides, women were better\r
+suited to bear sorrow than men.  They lived on their emotions.  They\r
+only thought of their emotions.  When they took lovers, it was merely\r
+to have some one with whom they could have scenes.  Lord Henry had told\r
+him that, and Lord Henry knew what women were.  Why should he trouble\r
+about Sibyl Vane?  She was nothing to him now.\r
+\r
+But the picture?  What was he to say of that?  It held the secret of\r
+his life, and told his story.  It had taught him to love his own\r
+beauty.  Would it teach him to loathe his own soul?  Would he ever look\r
+at it again?\r
+\r
+No; it was merely an illusion wrought on the troubled senses.  The\r
+horrible night that he had passed had left phantoms behind it.\r
+Suddenly there had fallen upon his brain that tiny scarlet speck that\r
+makes men mad.  The picture had not changed.  It was folly to think so.\r
+\r
+Yet it was watching him, with its beautiful marred face and its cruel\r
+smile.  Its bright hair gleamed in the early sunlight.  Its blue eyes\r
+met his own.  A sense of infinite pity, not for himself, but for the\r
+painted image of himself, came over him.  It had altered already, and\r
+would alter more.  Its gold would wither into grey.  Its red and white\r
+roses would die.  For every sin that he committed, a stain would fleck\r
+and wreck its fairness.  But he would not sin.  The picture, changed or\r
+unchanged, would be to him the visible emblem of conscience.  He would\r
+resist temptation.  He would not see Lord Henry any more--would not, at\r
+any rate, listen to those subtle poisonous theories that in Basil\r
+Hallward's garden had first stirred within him the passion for\r
+impossible things.  He would go back to Sibyl Vane, make her amends,\r
+marry her, try to love her again.  Yes, it was his duty to do so.  She\r
+must have suffered more than he had.  Poor child!  He had been selfish\r
+and cruel to her.  The fascination that she had exercised over him\r
+would return.  They would be happy together.  His life with her would\r
+be beautiful and pure.\r
+\r
+He got up from his chair and drew a large screen right in front of the\r
+portrait, shuddering as he glanced at it.  "How horrible!" he murmured\r
+to himself, and he walked across to the window and opened it.  When he\r
+stepped out on to the grass, he drew a deep breath.  The fresh morning\r
+air seemed to drive away all his sombre passions.  He thought only of\r
+Sibyl.  A faint echo of his love came back to him.  He repeated her\r
+name over and over again.  The birds that were singing in the\r
+dew-drenched garden seemed to be telling the flowers about her.\r
+\r
+\r
+\r
+CHAPTER 8\r
+\r
+It was long past noon when he awoke.  His valet had crept several times\r
+on tiptoe into the room to see if he was stirring, and had wondered\r
+what made his young master sleep so late.  Finally his bell sounded,\r
+and Victor came in softly with a cup of tea, and a pile of letters, on\r
+a small tray of old Sevres china, and drew back the olive-satin\r
+curtains, with their shimmering blue lining, that hung in front of the\r
+three tall windows.\r
+\r
+"Monsieur has well slept this morning," he said, smiling.\r
+\r
+"What o'clock is it, Victor?" asked Dorian Gray drowsily.\r
+\r
+"One hour and a quarter, Monsieur."\r
+\r
+How late it was!  He sat up, and having sipped some tea, turned over\r
+his letters.  One of them was from Lord Henry, and had been brought by\r
+hand that morning.  He hesitated for a moment, and then put it aside.\r
+The others he opened listlessly.  They contained the usual collection\r
+of cards, invitations to dinner, tickets for private views, programmes\r
+of charity concerts, and the like that are showered on fashionable\r
+young men every morning during the season.  There was a rather heavy\r
+bill for a chased silver Louis-Quinze toilet-set that he had not yet\r
+had the courage to send on to his guardians, who were extremely\r
+old-fashioned people and did not realize that we live in an age when\r
+unnecessary things are our only necessities; and there were several\r
+very courteously worded communications from Jermyn Street money-lenders\r
+offering to advance any sum of money at a moment's notice and at the\r
+most reasonable rates of interest.\r
+\r
+After about ten minutes he got up, and throwing on an elaborate\r
+dressing-gown of silk-embroidered cashmere wool, passed into the\r
+onyx-paved bathroom.  The cool water refreshed him after his long\r
+sleep.  He seemed to have forgotten all that he had gone through.  A\r
+dim sense of having taken part in some strange tragedy came to him once\r
+or twice, but there was the unreality of a dream about it.\r
+\r
+As soon as he was dressed, he went into the library and sat down to a\r
+light French breakfast that had been laid out for him on a small round\r
+table close to the open window.  It was an exquisite day.  The warm air\r
+seemed laden with spices.  A bee flew in and buzzed round the\r
+blue-dragon bowl that, filled with sulphur-yellow roses, stood before\r
+him.  He felt perfectly happy.\r
+\r
+Suddenly his eye fell on the screen that he had placed in front of the\r
+portrait, and he started.\r
+\r
+"Too cold for Monsieur?" asked his valet, putting an omelette on the\r
+table.  "I shut the window?"\r
+\r
+Dorian shook his head.  "I am not cold," he murmured.\r
+\r
+Was it all true?  Had the portrait really changed?  Or had it been\r
+simply his own imagination that had made him see a look of evil where\r
+there had been a look of joy?  Surely a painted canvas could not alter?\r
+The thing was absurd.  It would serve as a tale to tell Basil some day.\r
+It would make him smile.\r
+\r
+And, yet, how vivid was his recollection of the whole thing!  First in\r
+the dim twilight, and then in the bright dawn, he had seen the touch of\r
+cruelty round the warped lips.  He almost dreaded his valet leaving the\r
+room.  He knew that when he was alone he would have to examine the\r
+portrait.  He was afraid of certainty.  When the coffee and cigarettes\r
+had been brought and the man turned to go, he felt a wild desire to\r
+tell him to remain.  As the door was closing behind him, he called him\r
+back.  The man stood waiting for his orders.  Dorian looked at him for\r
+a moment.  "I am not at home to any one, Victor," he said with a sigh.\r
+The man bowed and retired.\r
+\r
+Then he rose from the table, lit a cigarette, and flung himself down on\r
+a luxuriously cushioned couch that stood facing the screen.  The screen\r
+was an old one, of gilt Spanish leather, stamped and wrought with a\r
+rather florid Louis-Quatorze pattern.  He scanned it curiously,\r
+wondering if ever before it had concealed the secret of a man's life.\r
+\r
+Should he move it aside, after all?  Why not let it stay there?  What\r
+was the use of knowing? If the thing was true, it was terrible.  If it\r
+was not true, why trouble about it?  But what if, by some fate or\r
+deadlier chance, eyes other than his spied behind and saw the horrible\r
+change?  What should he do if Basil Hallward came and asked to look at\r
+his own picture?  Basil would be sure to do that.  No; the thing had to\r
+be examined, and at once.  Anything would be better than this dreadful\r
+state of doubt.\r
+\r
+He got up and locked both doors.  At least he would be alone when he\r
+looked upon the mask of his shame.  Then he drew the screen aside and\r
+saw himself face to face.  It was perfectly true.  The portrait had\r
+altered.\r
+\r
+As he often remembered afterwards, and always with no small wonder, he\r
+found himself at first gazing at the portrait with a feeling of almost\r
+scientific interest.  That such a change should have taken place was\r
+incredible to him.  And yet it was a fact.  Was there some subtle\r
+affinity between the chemical atoms that shaped themselves into form\r
+and colour on the canvas and the soul that was within him?  Could it be\r
+that what that soul thought, they realized?--that what it dreamed, they\r
+made true?  Or was there some other, more terrible reason?  He\r
+shuddered, and felt afraid, and, going back to the couch, lay there,\r
+gazing at the picture in sickened horror.\r
+\r
+One thing, however, he felt that it had done for him.  It had made him\r
+conscious how unjust, how cruel, he had been to Sibyl Vane.  It was not\r
+too late to make reparation for that.  She could still be his wife.\r
+His unreal and selfish love would yield to some higher influence, would\r
+be transformed into some nobler passion, and the portrait that Basil\r
+Hallward had painted of him would be a guide to him through life, would\r
+be to him what holiness is to some, and conscience to others, and the\r
+fear of God to us all.  There were opiates for remorse, drugs that\r
+could lull the moral sense to sleep.  But here was a visible symbol of\r
+the degradation of sin.  Here was an ever-present sign of the ruin men\r
+brought upon their souls.\r
+\r
+Three o'clock struck, and four, and the half-hour rang its double\r
+chime, but Dorian Gray did not stir.  He was trying to gather up the\r
+scarlet threads of life and to weave them into a pattern; to find his\r
+way through the sanguine labyrinth of passion through which he was\r
+wandering.  He did not know what to do, or what to think.  Finally, he\r
+went over to the table and wrote a passionate letter to the girl he had\r
+loved, imploring her forgiveness and accusing himself of madness.  He\r
+covered page after page with wild words of sorrow and wilder words of\r
+pain.  There is a luxury in self-reproach. When we blame ourselves, we\r
+feel that no one else has a right to blame us.  It is the confession,\r
+not the priest, that gives us absolution.  When Dorian had finished the\r
+letter, he felt that he had been forgiven.\r
+\r
+Suddenly there came a knock to the door, and he heard Lord Henry's\r
+voice outside.  "My dear boy, I must see you.  Let me in at once.  I\r
+can't bear your shutting yourself up like this."\r
+\r
+He made no answer at first, but remained quite still.  The knocking\r
+still continued and grew louder.  Yes, it was better to let Lord Henry\r
+in, and to explain to him the new life he was going to lead, to quarrel\r
+with him if it became necessary to quarrel, to part if parting was\r
+inevitable.  He jumped up, drew the screen hastily across the picture,\r
+and unlocked the door.\r
+\r
+"I am so sorry for it all, Dorian," said Lord Henry as he entered.\r
+"But you must not think too much about it."\r
+\r
+"Do you mean about Sibyl Vane?" asked the lad.\r
+\r
+"Yes, of course," answered Lord Henry, sinking into a chair and slowly\r
+pulling off his yellow gloves.  "It is dreadful, from one point of\r
+view, but it was not your fault.  Tell me, did you go behind and see\r
+her, after the play was over?"\r
+\r
+"Yes."\r
+\r
+"I felt sure you had.  Did you make a scene with her?"\r
+\r
+"I was brutal, Harry--perfectly brutal.  But it is all right now.  I am\r
+not sorry for anything that has happened.  It has taught me to know\r
+myself better."\r
+\r
+"Ah, Dorian, I am so glad you take it in that way!  I was afraid I\r
+would find you plunged in remorse and tearing that nice curly hair of\r
+yours."\r
+\r
+"I have got through all that," said Dorian, shaking his head and\r
+smiling.  "I am perfectly happy now.  I know what conscience is, to\r
+begin with.  It is not what you told me it was.  It is the divinest\r
+thing in us.  Don't sneer at it, Harry, any more--at least not before\r
+me.  I want to be good.  I can't bear the idea of my soul being\r
+hideous."\r
+\r
+"A very charming artistic basis for ethics, Dorian!  I congratulate you\r
+on it.  But how are you going to begin?"\r
+\r
+"By marrying Sibyl Vane."\r
+\r
+"Marrying Sibyl Vane!" cried Lord Henry, standing up and looking at him\r
+in perplexed amazement.  "But, my dear Dorian--"\r
+\r
+"Yes, Harry, I know what you are going to say.  Something dreadful\r
+about marriage.  Don't say it.  Don't ever say things of that kind to\r
+me again.  Two days ago I asked Sibyl to marry me.  I am not going to\r
+break my word to her.  She is to be my wife."\r
+\r
+"Your wife!  Dorian! ... Didn't you get my letter?  I wrote to you this\r
+morning, and sent the note down by my own man."\r
+\r
+"Your letter?  Oh, yes, I remember.  I have not read it yet, Harry.  I\r
+was afraid there might be something in it that I wouldn't like.  You\r
+cut life to pieces with your epigrams."\r
+\r
+"You know nothing then?"\r
+\r
+"What do you mean?"\r
+\r
+Lord Henry walked across the room, and sitting down by Dorian Gray,\r
+took both his hands in his own and held them tightly.  "Dorian," he\r
+said, "my letter--don't be frightened--was to tell you that Sibyl Vane\r
+is dead."\r
+\r
+A cry of pain broke from the lad's lips, and he leaped to his feet,\r
+tearing his hands away from Lord Henry's grasp.  "Dead!  Sibyl dead!\r
+It is not true!  It is a horrible lie!  How dare you say it?"\r
+\r
+"It is quite true, Dorian," said Lord Henry, gravely.  "It is in all\r
+the morning papers.  I wrote down to you to ask you not to see any one\r
+till I came.  There will have to be an inquest, of course, and you must\r
+not be mixed up in it.  Things like that make a man fashionable in\r
+Paris.  But in London people are so prejudiced.  Here, one should never\r
+make one's _debut_ with a scandal.  One should reserve that to give an\r
+interest to one's old age.  I suppose they don't know your name at the\r
+theatre?  If they don't, it is all right.  Did any one see you going\r
+round to her room?  That is an important point."\r
+\r
+Dorian did not answer for a few moments.  He was dazed with horror.\r
+Finally he stammered, in a stifled voice, "Harry, did you say an\r
+inquest?  What did you mean by that?  Did Sibyl--? Oh, Harry, I can't\r
+bear it!  But be quick.  Tell me everything at once."\r
+\r
+"I have no doubt it was not an accident, Dorian, though it must be put\r
+in that way to the public.  It seems that as she was leaving the\r
+theatre with her mother, about half-past twelve or so, she said she had\r
+forgotten something upstairs.  They waited some time for her, but she\r
+did not come down again.  They ultimately found her lying dead on the\r
+floor of her dressing-room. She had swallowed something by mistake,\r
+some dreadful thing they use at theatres.  I don't know what it was,\r
+but it had either prussic acid or white lead in it.  I should fancy it\r
+was prussic acid, as she seems to have died instantaneously."\r
+\r
+"Harry, Harry, it is terrible!" cried the lad.\r
+\r
+"Yes; it is very tragic, of course, but you must not get yourself mixed\r
+up in it.  I see by _The Standard_ that she was seventeen.  I should have\r
+thought she was almost younger than that.  She looked such a child, and\r
+seemed to know so little about acting.  Dorian, you mustn't let this\r
+thing get on your nerves.  You must come and dine with me, and\r
+afterwards we will look in at the opera.  It is a Patti night, and\r
+everybody will be there.  You can come to my sister's box.  She has got\r
+some smart women with her."\r
+\r
+"So I have murdered Sibyl Vane," said Dorian Gray, half to himself,\r
+"murdered her as surely as if I had cut her little throat with a knife.\r
+Yet the roses are not less lovely for all that.  The birds sing just as\r
+happily in my garden.  And to-night I am to dine with you, and then go\r
+on to the opera, and sup somewhere, I suppose, afterwards.  How\r
+extraordinarily dramatic life is!  If I had read all this in a book,\r
+Harry, I think I would have wept over it.  Somehow, now that it has\r
+happened actually, and to me, it seems far too wonderful for tears.\r
+Here is the first passionate love-letter I have ever written in my\r
+life.  Strange, that my first passionate love-letter should have been\r
+addressed to a dead girl.  Can they feel, I wonder, those white silent\r
+people we call the dead?  Sibyl!  Can she feel, or know, or listen?\r
+Oh, Harry, how I loved her once!  It seems years ago to me now.  She\r
+was everything to me.  Then came that dreadful night--was it really\r
+only last night?--when she played so badly, and my heart almost broke.\r
+She explained it all to me.  It was terribly pathetic.  But I was not\r
+moved a bit.  I thought her shallow.  Suddenly something happened that\r
+made me afraid.  I can't tell you what it was, but it was terrible.  I\r
+said I would go back to her.  I felt I had done wrong.  And now she is\r
+dead.  My God!  My God!  Harry, what shall I do?  You don't know the\r
+danger I am in, and there is nothing to keep me straight.  She would\r
+have done that for me.  She had no right to kill herself.  It was\r
+selfish of her."\r
+\r
+"My dear Dorian," answered Lord Henry, taking a cigarette from his case\r
+and producing a gold-latten matchbox, "the only way a woman can ever\r
+reform a man is by boring him so completely that he loses all possible\r
+interest in life.  If you had married this girl, you would have been\r
+wretched.  Of course, you would have treated her kindly.  One can\r
+always be kind to people about whom one cares nothing.  But she would\r
+have soon found out that you were absolutely indifferent to her.  And\r
+when a woman finds that out about her husband, she either becomes\r
+dreadfully dowdy, or wears very smart bonnets that some other woman's\r
+husband has to pay for.  I say nothing about the social mistake, which\r
+would have been abject--which, of course, I would not have allowed--but\r
+I assure you that in any case the whole thing would have been an\r
+absolute failure."\r
+\r
+"I suppose it would," muttered the lad, walking up and down the room\r
+and looking horribly pale.  "But I thought it was my duty.  It is not\r
+my fault that this terrible tragedy has prevented my doing what was\r
+right.  I remember your saying once that there is a fatality about good\r
+resolutions--that they are always made too late.  Mine certainly were."\r
+\r
+"Good resolutions are useless attempts to interfere with scientific\r
+laws.  Their origin is pure vanity.  Their result is absolutely _nil_.\r
+They give us, now and then, some of those luxurious sterile emotions\r
+that have a certain charm for the weak.  That is all that can be said\r
+for them.  They are simply cheques that men draw on a bank where they\r
+have no account."\r
+\r
+"Harry," cried Dorian Gray, coming over and sitting down beside him,\r
+"why is it that I cannot feel this tragedy as much as I want to?  I\r
+don't think I am heartless.  Do you?"\r
+\r
+"You have done too many foolish things during the last fortnight to be\r
+entitled to give yourself that name, Dorian," answered Lord Henry with\r
+his sweet melancholy smile.\r
+\r
+The lad frowned.  "I don't like that explanation, Harry," he rejoined,\r
+"but I am glad you don't think I am heartless.  I am nothing of the\r
+kind.  I know I am not.  And yet I must admit that this thing that has\r
+happened does not affect me as it should.  It seems to me to be simply\r
+like a wonderful ending to a wonderful play.  It has all the terrible\r
+beauty of a Greek tragedy, a tragedy in which I took a great part, but\r
+by which I have not been wounded."\r
+\r
+"It is an interesting question," said Lord Henry, who found an\r
+exquisite pleasure in playing on the lad's unconscious egotism, "an\r
+extremely interesting question.  I fancy that the true explanation is\r
+this:  It often happens that the real tragedies of life occur in such\r
+an inartistic manner that they hurt us by their crude violence, their\r
+absolute incoherence, their absurd want of meaning, their entire lack\r
+of style.  They affect us just as vulgarity affects us.  They give us\r
+an impression of sheer brute force, and we revolt against that.\r
+Sometimes, however, a tragedy that possesses artistic elements of\r
+beauty crosses our lives.  If these elements of beauty are real, the\r
+whole thing simply appeals to our sense of dramatic effect.  Suddenly\r
+we find that we are no longer the actors, but the spectators of the\r
+play.  Or rather we are both.  We watch ourselves, and the mere wonder\r
+of the spectacle enthralls us.  In the present case, what is it that\r
+has really happened?  Some one has killed herself for love of you.  I\r
+wish that I had ever had such an experience.  It would have made me in\r
+love with love for the rest of my life.  The people who have adored\r
+me--there have not been very many, but there have been some--have\r
+always insisted on living on, long after I had ceased to care for them,\r
+or they to care for me.  They have become stout and tedious, and when I\r
+meet them, they go in at once for reminiscences.  That awful memory of\r
+woman!  What a fearful thing it is!  And what an utter intellectual\r
+stagnation it reveals!  One should absorb the colour of life, but one\r
+should never remember its details.  Details are always vulgar."\r
+\r
+"I must sow poppies in my garden," sighed Dorian.\r
+\r
+"There is no necessity," rejoined his companion.  "Life has always\r
+poppies in her hands.  Of course, now and then things linger.  I once\r
+wore nothing but violets all through one season, as a form of artistic\r
+mourning for a romance that would not die.  Ultimately, however, it did\r
+die.  I forget what killed it.  I think it was her proposing to\r
+sacrifice the whole world for me.  That is always a dreadful moment.\r
+It fills one with the terror of eternity.  Well--would you believe\r
+it?--a week ago, at Lady Hampshire's, I found myself seated at dinner\r
+next the lady in question, and she insisted on going over the whole\r
+thing again, and digging up the past, and raking up the future.  I had\r
+buried my romance in a bed of asphodel.  She dragged it out again and\r
+assured me that I had spoiled her life.  I am bound to state that she\r
+ate an enormous dinner, so I did not feel any anxiety.  But what a lack\r
+of taste she showed!  The one charm of the past is that it is the past.\r
+But women never know when the curtain has fallen.  They always want a\r
+sixth act, and as soon as the interest of the play is entirely over,\r
+they propose to continue it.  If they were allowed their own way, every\r
+comedy would have a tragic ending, and every tragedy would culminate in\r
+a farce.  They are charmingly artificial, but they have no sense of\r
+art.  You are more fortunate than I am.  I assure you, Dorian, that not\r
+one of the women I have known would have done for me what Sibyl Vane\r
+did for you.  Ordinary women always console themselves.  Some of them\r
+do it by going in for sentimental colours.  Never trust a woman who\r
+wears mauve, whatever her age may be, or a woman over thirty-five who\r
+is fond of pink ribbons.  It always means that they have a history.\r
+Others find a great consolation in suddenly discovering the good\r
+qualities of their husbands.  They flaunt their conjugal felicity in\r
+one's face, as if it were the most fascinating of sins.  Religion\r
+consoles some.  Its mysteries have all the charm of a flirtation, a\r
+woman once told me, and I can quite understand it.  Besides, nothing\r
+makes one so vain as being told that one is a sinner.  Conscience makes\r
+egotists of us all.  Yes; there is really no end to the consolations\r
+that women find in modern life.  Indeed, I have not mentioned the most\r
+important one."\r
+\r
+"What is that, Harry?" said the lad listlessly.\r
+\r
+"Oh, the obvious consolation.  Taking some one else's admirer when one\r
+loses one's own.  In good society that always whitewashes a woman.  But\r
+really, Dorian, how different Sibyl Vane must have been from all the\r
+women one meets!  There is something to me quite beautiful about her\r
+death.  I am glad I am living in a century when such wonders happen.\r
+They make one believe in the reality of the things we all play with,\r
+such as romance, passion, and love."\r
+\r
+"I was terribly cruel to her.  You forget that."\r
+\r
+"I am afraid that women appreciate cruelty, downright cruelty, more\r
+than anything else.  They have wonderfully primitive instincts.  We\r
+have emancipated them, but they remain slaves looking for their\r
+masters, all the same.  They love being dominated.  I am sure you were\r
+splendid.  I have never seen you really and absolutely angry, but I can\r
+fancy how delightful you looked.  And, after all, you said something to\r
+me the day before yesterday that seemed to me at the time to be merely\r
+fanciful, but that I see now was absolutely true, and it holds the key\r
+to everything."\r
+\r
+"What was that, Harry?"\r
+\r
+"You said to me that Sibyl Vane represented to you all the heroines of\r
+romance--that she was Desdemona one night, and Ophelia the other; that\r
+if she died as Juliet, she came to life as Imogen."\r
+\r
+"She will never come to life again now," muttered the lad, burying his\r
+face in his hands.\r
+\r
+"No, she will never come to life.  She has played her last part.  But\r
+you must think of that lonely death in the tawdry dressing-room simply\r
+as a strange lurid fragment from some Jacobean tragedy, as a wonderful\r
+scene from Webster, or Ford, or Cyril Tourneur.  The girl never really\r
+lived, and so she has never really died.  To you at least she was\r
+always a dream, a phantom that flitted through Shakespeare's plays and\r
+left them lovelier for its presence, a reed through which Shakespeare's\r
+music sounded richer and more full of joy.  The moment she touched\r
+actual life, she marred it, and it marred her, and so she passed away.\r
+Mourn for Ophelia, if you like.  Put ashes on your head because\r
+Cordelia was strangled.  Cry out against Heaven because the daughter of\r
+Brabantio died.  But don't waste your tears over Sibyl Vane.  She was\r
+less real than they are."\r
+\r
+There was a silence.  The evening darkened in the room.  Noiselessly,\r
+and with silver feet, the shadows crept in from the garden.  The\r
+colours faded wearily out of things.\r
+\r
+After some time Dorian Gray looked up.  "You have explained me to\r
+myself, Harry," he murmured with something of a sigh of relief.  "I\r
+felt all that you have said, but somehow I was afraid of it, and I\r
+could not express it to myself.  How well you know me!  But we will not\r
+talk again of what has happened.  It has been a marvellous experience.\r
+That is all.  I wonder if life has still in store for me anything as\r
+marvellous."\r
+\r
+"Life has everything in store for you, Dorian.  There is nothing that\r
+you, with your extraordinary good looks, will not be able to do."\r
+\r
+"But suppose, Harry, I became haggard, and old, and wrinkled?  What\r
+then?"\r
+\r
+"Ah, then," said Lord Henry, rising to go, "then, my dear Dorian, you\r
+would have to fight for your victories.  As it is, they are brought to\r
+you.  No, you must keep your good looks.  We live in an age that reads\r
+too much to be wise, and that thinks too much to be beautiful.  We\r
+cannot spare you.  And now you had better dress and drive down to the\r
+club.  We are rather late, as it is."\r
+\r
+"I think I shall join you at the opera, Harry.  I feel too tired to eat\r
+anything.  What is the number of your sister's box?"\r
+\r
+"Twenty-seven, I believe.  It is on the grand tier.  You will see her\r
+name on the door.  But I am sorry you won't come and dine."\r
+\r
+"I don't feel up to it," said Dorian listlessly.  "But I am awfully\r
+obliged to you for all that you have said to me.  You are certainly my\r
+best friend.  No one has ever understood me as you have."\r
+\r
+"We are only at the beginning of our friendship, Dorian," answered Lord\r
+Henry, shaking him by the hand.  "Good-bye. I shall see you before\r
+nine-thirty, I hope.  Remember, Patti is singing."\r
+\r
+As he closed the door behind him, Dorian Gray touched the bell, and in\r
+a few minutes Victor appeared with the lamps and drew the blinds down.\r
+He waited impatiently for him to go.  The man seemed to take an\r
+interminable time over everything.\r
+\r
+As soon as he had left, he rushed to the screen and drew it back.  No;\r
+there was no further change in the picture.  It had received the news\r
+of Sibyl Vane's death before he had known of it himself.  It was\r
+conscious of the events of life as they occurred.  The vicious cruelty\r
+that marred the fine lines of the mouth had, no doubt, appeared at the\r
+very moment that the girl had drunk the poison, whatever it was.  Or\r
+was it indifferent to results?  Did it merely take cognizance of what\r
+passed within the soul?  He wondered, and hoped that some day he would\r
+see the change taking place before his very eyes, shuddering as he\r
+hoped it.\r
+\r
+Poor Sibyl!  What a romance it had all been!  She had often mimicked\r
+death on the stage.  Then Death himself had touched her and taken her\r
+with him.  How had she played that dreadful last scene?  Had she cursed\r
+him, as she died?  No; she had died for love of him, and love would\r
+always be a sacrament to him now.  She had atoned for everything by the\r
+sacrifice she had made of her life.  He would not think any more of\r
+what she had made him go through, on that horrible night at the\r
+theatre.  When he thought of her, it would be as a wonderful tragic\r
+figure sent on to the world's stage to show the supreme reality of\r
+love.  A wonderful tragic figure?  Tears came to his eyes as he\r
+remembered her childlike look, and winsome fanciful ways, and shy\r
+tremulous grace.  He brushed them away hastily and looked again at the\r
+picture.\r
+\r
+He felt that the time had really come for making his choice.  Or had\r
+his choice already been made?  Yes, life had decided that for\r
+him--life, and his own infinite curiosity about life.  Eternal youth,\r
+infinite passion, pleasures subtle and secret, wild joys and wilder\r
+sins--he was to have all these things.  The portrait was to bear the\r
+burden of his shame: that was all.\r
+\r
+A feeling of pain crept over him as he thought of the desecration that\r
+was in store for the fair face on the canvas.  Once, in boyish mockery\r
+of Narcissus, he had kissed, or feigned to kiss, those painted lips\r
+that now smiled so cruelly at him.  Morning after morning he had sat\r
+before the portrait wondering at its beauty, almost enamoured of it, as\r
+it seemed to him at times.  Was it to alter now with every mood to\r
+which he yielded?  Was it to become a monstrous and loathsome thing, to\r
+be hidden away in a locked room, to be shut out from the sunlight that\r
+had so often touched to brighter gold the waving wonder of its hair?\r
+The pity of it! the pity of it!\r
+\r
+For a moment, he thought of praying that the horrible sympathy that\r
+existed between him and the picture might cease.  It had changed in\r
+answer to a prayer; perhaps in answer to a prayer it might remain\r
+unchanged.  And yet, who, that knew anything about life, would\r
+surrender the chance of remaining always young, however fantastic that\r
+chance might be, or with what fateful consequences it might be fraught?\r
+Besides, was it really under his control?  Had it indeed been prayer\r
+that had produced the substitution?  Might there not be some curious\r
+scientific reason for it all?  If thought could exercise its influence\r
+upon a living organism, might not thought exercise an influence upon\r
+dead and inorganic things?  Nay, without thought or conscious desire,\r
+might not things external to ourselves vibrate in unison with our moods\r
+and passions, atom calling to atom in secret love or strange affinity?\r
+But the reason was of no importance.  He would never again tempt by a\r
+prayer any terrible power.  If the picture was to alter, it was to\r
+alter.  That was all.  Why inquire too closely into it?\r
+\r
+For there would be a real pleasure in watching it.  He would be able to\r
+follow his mind into its secret places.  This portrait would be to him\r
+the most magical of mirrors.  As it had revealed to him his own body,\r
+so it would reveal to him his own soul.  And when winter came upon it,\r
+he would still be standing where spring trembles on the verge of\r
+summer.  When the blood crept from its face, and left behind a pallid\r
+mask of chalk with leaden eyes, he would keep the glamour of boyhood.\r
+Not one blossom of his loveliness would ever fade.  Not one pulse of\r
+his life would ever weaken.  Like the gods of the Greeks, he would be\r
+strong, and fleet, and joyous.  What did it matter what happened to the\r
+coloured image on the canvas?  He would be safe.  That was everything.\r
+\r
+He drew the screen back into its former place in front of the picture,\r
+smiling as he did so, and passed into his bedroom, where his valet was\r
+already waiting for him.  An hour later he was at the opera, and Lord\r
+Henry was leaning over his chair.\r
+\r
+\r
+\r
+CHAPTER 9\r
+\r
+As he was sitting at breakfast next morning, Basil Hallward was shown\r
+into the room.\r
+\r
+"I am so glad I have found you, Dorian," he said gravely.  "I called\r
+last night, and they told me you were at the opera.  Of course, I knew\r
+that was impossible.  But I wish you had left word where you had really\r
+gone to.  I passed a dreadful evening, half afraid that one tragedy\r
+might be followed by another.  I think you might have telegraphed for\r
+me when you heard of it first.  I read of it quite by chance in a late\r
+edition of _The Globe_ that I picked up at the club.  I came here at once\r
+and was miserable at not finding you.  I can't tell you how\r
+heart-broken I am about the whole thing.  I know what you must suffer.\r
+But where were you?  Did you go down and see the girl's mother?  For a\r
+moment I thought of following you there.  They gave the address in the\r
+paper.  Somewhere in the Euston Road, isn't it?  But I was afraid of\r
+intruding upon a sorrow that I could not lighten.  Poor woman!  What a\r
+state she must be in!  And her only child, too!  What did she say about\r
+it all?"\r
+\r
+"My dear Basil, how do I know?" murmured Dorian Gray, sipping some\r
+pale-yellow wine from a delicate, gold-beaded bubble of Venetian glass\r
+and looking dreadfully bored.  "I was at the opera.  You should have\r
+come on there.  I met Lady Gwendolen, Harry's sister, for the first\r
+time.  We were in her box.  She is perfectly charming; and Patti sang\r
+divinely.  Don't talk about horrid subjects.  If one doesn't talk about\r
+a thing, it has never happened.  It is simply expression, as Harry\r
+says, that gives reality to things.  I may mention that she was not the\r
+woman's only child.  There is a son, a charming fellow, I believe.  But\r
+he is not on the stage.  He is a sailor, or something.  And now, tell\r
+me about yourself and what you are painting."\r
+\r
+"You went to the opera?" said Hallward, speaking very slowly and with a\r
+strained touch of pain in his voice.  "You went to the opera while\r
+Sibyl Vane was lying dead in some sordid lodging?  You can talk to me\r
+of other women being charming, and of Patti singing divinely, before\r
+the girl you loved has even the quiet of a grave to sleep in?  Why,\r
+man, there are horrors in store for that little white body of hers!"\r
+\r
+"Stop, Basil!  I won't hear it!" cried Dorian, leaping to his feet.\r
+"You must not tell me about things.  What is done is done.  What is\r
+past is past."\r
+\r
+"You call yesterday the past?"\r
+\r
+"What has the actual lapse of time got to do with it?  It is only\r
+shallow people who require years to get rid of an emotion.  A man who\r
+is master of himself can end a sorrow as easily as he can invent a\r
+pleasure.  I don't want to be at the mercy of my emotions.  I want to\r
+use them, to enjoy them, and to dominate them."\r
+\r
+"Dorian, this is horrible!  Something has changed you completely.  You\r
+look exactly the same wonderful boy who, day after day, used to come\r
+down to my studio to sit for his picture.  But you were simple,\r
+natural, and affectionate then.  You were the most unspoiled creature\r
+in the whole world.  Now, I don't know what has come over you.  You\r
+talk as if you had no heart, no pity in you.  It is all Harry's\r
+influence.  I see that."\r
+\r
+The lad flushed up and, going to the window, looked out for a few\r
+moments on the green, flickering, sun-lashed garden.  "I owe a great\r
+deal to Harry, Basil," he said at last, "more than I owe to you.  You\r
+only taught me to be vain."\r
+\r
+"Well, I am punished for that, Dorian--or shall be some day."\r
+\r
+"I don't know what you mean, Basil," he exclaimed, turning round.  "I\r
+don't know what you want.  What do you want?"\r
+\r
+"I want the Dorian Gray I used to paint," said the artist sadly.\r
+\r
+"Basil," said the lad, going over to him and putting his hand on his\r
+shoulder, "you have come too late.  Yesterday, when I heard that Sibyl\r
+Vane had killed herself--"\r
+\r
+"Killed herself!  Good heavens! is there no doubt about that?" cried\r
+Hallward, looking up at him with an expression of horror.\r
+\r
+"My dear Basil!  Surely you don't think it was a vulgar accident?  Of\r
+course she killed herself."\r
+\r
+The elder man buried his face in his hands.  "How fearful," he\r
+muttered, and a shudder ran through him.\r
+\r
+"No," said Dorian Gray, "there is nothing fearful about it.  It is one\r
+of the great romantic tragedies of the age.  As a rule, people who act\r
+lead the most commonplace lives.  They are good husbands, or faithful\r
+wives, or something tedious.  You know what I mean--middle-class virtue\r
+and all that kind of thing.  How different Sibyl was!  She lived her\r
+finest tragedy.  She was always a heroine.  The last night she\r
+played--the night you saw her--she acted badly because she had known\r
+the reality of love.  When she knew its unreality, she died, as Juliet\r
+might have died.  She passed again into the sphere of art.  There is\r
+something of the martyr about her.  Her death has all the pathetic\r
+uselessness of martyrdom, all its wasted beauty.  But, as I was saying,\r
+you must not think I have not suffered.  If you had come in yesterday\r
+at a particular moment--about half-past five, perhaps, or a quarter to\r
+six--you would have found me in tears.  Even Harry, who was here, who\r
+brought me the news, in fact, had no idea what I was going through.  I\r
+suffered immensely.  Then it passed away.  I cannot repeat an emotion.\r
+No one can, except sentimentalists.  And you are awfully unjust, Basil.\r
+You come down here to console me.  That is charming of you.  You find\r
+me consoled, and you are furious.  How like a sympathetic person!  You\r
+remind me of a story Harry told me about a certain philanthropist who\r
+spent twenty years of his life in trying to get some grievance\r
+redressed, or some unjust law altered--I forget exactly what it was.\r
+Finally he succeeded, and nothing could exceed his disappointment.  He\r
+had absolutely nothing to do, almost died of _ennui_, and became a\r
+confirmed misanthrope.  And besides, my dear old Basil, if you really\r
+want to console me, teach me rather to forget what has happened, or to\r
+see it from a proper artistic point of view.  Was it not Gautier who\r
+used to write about _la consolation des arts_?  I remember picking up a\r
+little vellum-covered book in your studio one day and chancing on that\r
+delightful phrase.  Well, I am not like that young man you told me of\r
+when we were down at Marlow together, the young man who used to say\r
+that yellow satin could console one for all the miseries of life.  I\r
+love beautiful things that one can touch and handle.  Old brocades,\r
+green bronzes, lacquer-work, carved ivories, exquisite surroundings,\r
+luxury, pomp--there is much to be got from all these.  But the artistic\r
+temperament that they create, or at any rate reveal, is still more to\r
+me.  To become the spectator of one's own life, as Harry says, is to\r
+escape the suffering of life.  I know you are surprised at my talking\r
+to you like this.  You have not realized how I have developed.  I was a\r
+schoolboy when you knew me.  I am a man now.  I have new passions, new\r
+thoughts, new ideas.  I am different, but you must not like me less.  I\r
+am changed, but you must always be my friend.  Of course, I am very\r
+fond of Harry.  But I know that you are better than he is.  You are not\r
+stronger--you are too much afraid of life--but you are better.  And how\r
+happy we used to be together!  Don't leave me, Basil, and don't quarrel\r
+with me.  I am what I am.  There is nothing more to be said."\r
+\r
+The painter felt strangely moved.  The lad was infinitely dear to him,\r
+and his personality had been the great turning point in his art.  He\r
+could not bear the idea of reproaching him any more.  After all, his\r
+indifference was probably merely a mood that would pass away.  There\r
+was so much in him that was good, so much in him that was noble.\r
+\r
+"Well, Dorian," he said at length, with a sad smile, "I won't speak to\r
+you again about this horrible thing, after to-day.  I only trust your\r
+name won't be mentioned in connection with it.  The inquest is to take\r
+place this afternoon.  Have they summoned you?"\r
+\r
+Dorian shook his head, and a look of annoyance passed over his face at\r
+the mention of the word "inquest."  There was something so crude and\r
+vulgar about everything of the kind.  "They don't know my name," he\r
+answered.\r
+\r
+"But surely she did?"\r
+\r
+"Only my Christian name, and that I am quite sure she never mentioned\r
+to any one.  She told me once that they were all rather curious to\r
+learn who I was, and that she invariably told them my name was Prince\r
+Charming.  It was pretty of her.  You must do me a drawing of Sibyl,\r
+Basil.  I should like to have something more of her than the memory of\r
+a few kisses and some broken pathetic words."\r
+\r
+"I will try and do something, Dorian, if it would please you.  But you\r
+must come and sit to me yourself again.  I can't get on without you."\r
+\r
+"I can never sit to you again, Basil.  It is impossible!" he exclaimed,\r
+starting back.\r
+\r
+The painter stared at him.  "My dear boy, what nonsense!" he cried.\r
+"Do you mean to say you don't like what I did of you?  Where is it?\r
+Why have you pulled the screen in front of it?  Let me look at it.  It\r
+is the best thing I have ever done.  Do take the screen away, Dorian.\r
+It is simply disgraceful of your servant hiding my work like that.  I\r
+felt the room looked different as I came in."\r
+\r
+"My servant has nothing to do with it, Basil.  You don't imagine I let\r
+him arrange my room for me?  He settles my flowers for me\r
+sometimes--that is all.  No; I did it myself.  The light was too strong\r
+on the portrait."\r
+\r
+"Too strong!  Surely not, my dear fellow?  It is an admirable place for\r
+it.  Let me see it."  And Hallward walked towards the corner of the\r
+room.\r
+\r
+A cry of terror broke from Dorian Gray's lips, and he rushed between\r
+the painter and the screen.  "Basil," he said, looking very pale, "you\r
+must not look at it.  I don't wish you to."\r
+\r
+"Not look at my own work!  You are not serious.  Why shouldn't I look\r
+at it?" exclaimed Hallward, laughing.\r
+\r
+"If you try to look at it, Basil, on my word of honour I will never\r
+speak to you again as long as I live.  I am quite serious.  I don't\r
+offer any explanation, and you are not to ask for any.  But, remember,\r
+if you touch this screen, everything is over between us."\r
+\r
+Hallward was thunderstruck.  He looked at Dorian Gray in absolute\r
+amazement.  He had never seen him like this before.  The lad was\r
+actually pallid with rage.  His hands were clenched, and the pupils of\r
+his eyes were like disks of blue fire.  He was trembling all over.\r
+\r
+"Dorian!"\r
+\r
+"Don't speak!"\r
+\r
+"But what is the matter?  Of course I won't look at it if you don't\r
+want me to," he said, rather coldly, turning on his heel and going over\r
+towards the window.  "But, really, it seems rather absurd that I\r
+shouldn't see my own work, especially as I am going to exhibit it in\r
+Paris in the autumn.  I shall probably have to give it another coat of\r
+varnish before that, so I must see it some day, and why not to-day?"\r
+\r
+"To exhibit it!  You want to exhibit it?" exclaimed Dorian Gray, a\r
+strange sense of terror creeping over him.  Was the world going to be\r
+shown his secret?  Were people to gape at the mystery of his life?\r
+That was impossible.  Something--he did not know what--had to be done\r
+at once.\r
+\r
+"Yes; I don't suppose you will object to that.  Georges Petit is going\r
+to collect all my best pictures for a special exhibition in the Rue de\r
+Seze, which will open the first week in October.  The portrait will\r
+only be away a month.  I should think you could easily spare it for\r
+that time.  In fact, you are sure to be out of town.  And if you keep\r
+it always behind a screen, you can't care much about it."\r
+\r
+Dorian Gray passed his hand over his forehead.  There were beads of\r
+perspiration there.  He felt that he was on the brink of a horrible\r
+danger.  "You told me a month ago that you would never exhibit it," he\r
+cried.  "Why have you changed your mind?  You people who go in for\r
+being consistent have just as many moods as others have.  The only\r
+difference is that your moods are rather meaningless.  You can't have\r
+forgotten that you assured me most solemnly that nothing in the world\r
+would induce you to send it to any exhibition.  You told Harry exactly\r
+the same thing." He stopped suddenly, and a gleam of light came into\r
+his eyes.  He remembered that Lord Henry had said to him once, half\r
+seriously and half in jest, "If you want to have a strange quarter of\r
+an hour, get Basil to tell you why he won't exhibit your picture.  He\r
+told me why he wouldn't, and it was a revelation to me."  Yes, perhaps\r
+Basil, too, had his secret.  He would ask him and try.\r
+\r
+"Basil," he said, coming over quite close and looking him straight in\r
+the face, "we have each of us a secret.  Let me know yours, and I shall\r
+tell you mine.  What was your reason for refusing to exhibit my\r
+picture?"\r
+\r
+The painter shuddered in spite of himself.  "Dorian, if I told you, you\r
+might like me less than you do, and you would certainly laugh at me.  I\r
+could not bear your doing either of those two things.  If you wish me\r
+never to look at your picture again, I am content.  I have always you\r
+to look at.  If you wish the best work I have ever done to be hidden\r
+from the world, I am satisfied.  Your friendship is dearer to me than\r
+any fame or reputation."\r
+\r
+"No, Basil, you must tell me," insisted Dorian Gray.  "I think I have a\r
+right to know."  His feeling of terror had passed away, and curiosity\r
+had taken its place.  He was determined to find out Basil Hallward's\r
+mystery.\r
+\r
+"Let us sit down, Dorian," said the painter, looking troubled.  "Let us\r
+sit down.  And just answer me one question.  Have you noticed in the\r
+picture something curious?--something that probably at first did not\r
+strike you, but that revealed itself to you suddenly?"\r
+\r
+"Basil!" cried the lad, clutching the arms of his chair with trembling\r
+hands and gazing at him with wild startled eyes.\r
+\r
+"I see you did.  Don't speak.  Wait till you hear what I have to say.\r
+Dorian, from the moment I met you, your personality had the most\r
+extraordinary influence over me.  I was dominated, soul, brain, and\r
+power, by you.  You became to me the visible incarnation of that unseen\r
+ideal whose memory haunts us artists like an exquisite dream.  I\r
+worshipped you.  I grew jealous of every one to whom you spoke.  I\r
+wanted to have you all to myself.  I was only happy when I was with\r
+you.  When you were away from me, you were still present in my art....\r
+Of course, I never let you know anything about this.  It would have\r
+been impossible.  You would not have understood it.  I hardly\r
+understood it myself.  I only knew that I had seen perfection face to\r
+face, and that the world had become wonderful to my eyes--too\r
+wonderful, perhaps, for in such mad worships there is peril, the peril\r
+of losing them, no less than the peril of keeping them....  Weeks and\r
+weeks went on, and I grew more and more absorbed in you.  Then came a\r
+new development.  I had drawn you as Paris in dainty armour, and as\r
+Adonis with huntsman's cloak and polished boar-spear. Crowned with\r
+heavy lotus-blossoms you had sat on the prow of Adrian's barge, gazing\r
+across the green turbid Nile.  You had leaned over the still pool of\r
+some Greek woodland and seen in the water's silent silver the marvel of\r
+your own face.  And it had all been what art should be--unconscious,\r
+ideal, and remote.  One day, a fatal day I sometimes think, I\r
+determined to paint a wonderful portrait of you as you actually are,\r
+not in the costume of dead ages, but in your own dress and in your own\r
+time.  Whether it was the realism of the method, or the mere wonder of\r
+your own personality, thus directly presented to me without mist or\r
+veil, I cannot tell.  But I know that as I worked at it, every flake\r
+and film of colour seemed to me to reveal my secret.  I grew afraid\r
+that others would know of my idolatry.  I felt, Dorian, that I had told\r
+too much, that I had put too much of myself into it.  Then it was that\r
+I resolved never to allow the picture to be exhibited.  You were a\r
+little annoyed; but then you did not realize all that it meant to me.\r
+Harry, to whom I talked about it, laughed at me.  But I did not mind\r
+that.  When the picture was finished, and I sat alone with it, I felt\r
+that I was right.... Well, after a few days the thing left my studio,\r
+and as soon as I had got rid of the intolerable fascination of its\r
+presence, it seemed to me that I had been foolish in imagining that I\r
+had seen anything in it, more than that you were extremely good-looking\r
+and that I could paint.  Even now I cannot help feeling that it is a\r
+mistake to think that the passion one feels in creation is ever really\r
+shown in the work one creates.  Art is always more abstract than we\r
+fancy.  Form and colour tell us of form and colour--that is all.  It\r
+often seems to me that art conceals the artist far more completely than\r
+it ever reveals him.  And so when I got this offer from Paris, I\r
+determined to make your portrait the principal thing in my exhibition.\r
+It never occurred to me that you would refuse.  I see now that you were\r
+right.  The picture cannot be shown.  You must not be angry with me,\r
+Dorian, for what I have told you.  As I said to Harry, once, you are\r
+made to be worshipped."\r
+\r
+Dorian Gray drew a long breath.  The colour came back to his cheeks,\r
+and a smile played about his lips.  The peril was over.  He was safe\r
+for the time.  Yet he could not help feeling infinite pity for the\r
+painter who had just made this strange confession to him, and wondered\r
+if he himself would ever be so dominated by the personality of a\r
+friend.  Lord Henry had the charm of being very dangerous.  But that\r
+was all.  He was too clever and too cynical to be really fond of.\r
+Would there ever be some one who would fill him with a strange\r
+idolatry?  Was that one of the things that life had in store?\r
+\r
+"It is extraordinary to me, Dorian," said Hallward, "that you should\r
+have seen this in the portrait.  Did you really see it?"\r
+\r
+"I saw something in it," he answered, "something that seemed to me very\r
+curious."\r
+\r
+"Well, you don't mind my looking at the thing now?"\r
+\r
+Dorian shook his head.  "You must not ask me that, Basil.  I could not\r
+possibly let you stand in front of that picture."\r
+\r
+"You will some day, surely?"\r
+\r
+"Never."\r
+\r
+"Well, perhaps you are right.  And now good-bye, Dorian.  You have been\r
+the one person in my life who has really influenced my art.  Whatever I\r
+have done that is good, I owe to you.  Ah! you don't know what it cost\r
+me to tell you all that I have told you."\r
+\r
+"My dear Basil," said Dorian, "what have you told me?  Simply that you\r
+felt that you admired me too much.  That is not even a compliment."\r
+\r
+"It was not intended as a compliment.  It was a confession.  Now that I\r
+have made it, something seems to have gone out of me.  Perhaps one\r
+should never put one's worship into words."\r
+\r
+"It was a very disappointing confession."\r
+\r
+"Why, what did you expect, Dorian?  You didn't see anything else in the\r
+picture, did you?  There was nothing else to see?"\r
+\r
+"No; there was nothing else to see.  Why do you ask?  But you mustn't\r
+talk about worship.  It is foolish.  You and I are friends, Basil, and\r
+we must always remain so."\r
+\r
+"You have got Harry," said the painter sadly.\r
+\r
+"Oh, Harry!" cried the lad, with a ripple of laughter.  "Harry spends\r
+his days in saying what is incredible and his evenings in doing what is\r
+improbable.  Just the sort of life I would like to lead.  But still I\r
+don't think I would go to Harry if I were in trouble.  I would sooner\r
+go to you, Basil."\r
+\r
+"You will sit to me again?"\r
+\r
+"Impossible!"\r
+\r
+"You spoil my life as an artist by refusing, Dorian.  No man comes\r
+across two ideal things.  Few come across one."\r
+\r
+"I can't explain it to you, Basil, but I must never sit to you again.\r
+There is something fatal about a portrait.  It has a life of its own.\r
+I will come and have tea with you.  That will be just as pleasant."\r
+\r
+"Pleasanter for you, I am afraid," murmured Hallward regretfully.  "And\r
+now good-bye. I am sorry you won't let me look at the picture once\r
+again.  But that can't be helped.  I quite understand what you feel\r
+about it."\r
+\r
+As he left the room, Dorian Gray smiled to himself.  Poor Basil!  How\r
+little he knew of the true reason!  And how strange it was that,\r
+instead of having been forced to reveal his own secret, he had\r
+succeeded, almost by chance, in wresting a secret from his friend!  How\r
+much that strange confession explained to him!  The painter's absurd\r
+fits of jealousy, his wild devotion, his extravagant panegyrics, his\r
+curious reticences--he understood them all now, and he felt sorry.\r
+There seemed to him to be something tragic in a friendship so coloured\r
+by romance.\r
+\r
+He sighed and touched the bell.  The portrait must be hidden away at\r
+all costs.  He could not run such a risk of discovery again.  It had\r
+been mad of him to have allowed the thing to remain, even for an hour,\r
+in a room to which any of his friends had access.\r
+\r
+\r
+\r
+CHAPTER 10\r
+\r
+When his servant entered, he looked at him steadfastly and wondered if\r
+he had thought of peering behind the screen.  The man was quite\r
+impassive and waited for his orders.  Dorian lit a cigarette and walked\r
+over to the glass and glanced into it.  He could see the reflection of\r
+Victor's face perfectly.  It was like a placid mask of servility.\r
+There was nothing to be afraid of, there.  Yet he thought it best to be\r
+on his guard.\r
+\r
+Speaking very slowly, he told him to tell the house-keeper that he\r
+wanted to see her, and then to go to the frame-maker and ask him to\r
+send two of his men round at once.  It seemed to him that as the man\r
+left the room his eyes wandered in the direction of the screen.  Or was\r
+that merely his own fancy?\r
+\r
+After a few moments, in her black silk dress, with old-fashioned thread\r
+mittens on her wrinkled hands, Mrs. Leaf bustled into the library.  He\r
+asked her for the key of the schoolroom.\r
+\r
+"The old schoolroom, Mr. Dorian?" she exclaimed.  "Why, it is full of\r
+dust.  I must get it arranged and put straight before you go into it.\r
+It is not fit for you to see, sir.  It is not, indeed."\r
+\r
+"I don't want it put straight, Leaf.  I only want the key."\r
+\r
+"Well, sir, you'll be covered with cobwebs if you go into it.  Why, it\r
+hasn't been opened for nearly five years--not since his lordship died."\r
+\r
+He winced at the mention of his grandfather.  He had hateful memories\r
+of him.  "That does not matter," he answered.  "I simply want to see\r
+the place--that is all.  Give me the key."\r
+\r
+"And here is the key, sir," said the old lady, going over the contents\r
+of her bunch with tremulously uncertain hands.  "Here is the key.  I'll\r
+have it off the bunch in a moment.  But you don't think of living up\r
+there, sir, and you so comfortable here?"\r
+\r
+"No, no," he cried petulantly.  "Thank you, Leaf.  That will do."\r
+\r
+She lingered for a few moments, and was garrulous over some detail of\r
+the household.  He sighed and told her to manage things as she thought\r
+best.  She left the room, wreathed in smiles.\r
+\r
+As the door closed, Dorian put the key in his pocket and looked round\r
+the room.  His eye fell on a large, purple satin coverlet heavily\r
+embroidered with gold, a splendid piece of late seventeenth-century\r
+Venetian work that his grandfather had found in a convent near Bologna.\r
+Yes, that would serve to wrap the dreadful thing in.  It had perhaps\r
+served often as a pall for the dead.  Now it was to hide something that\r
+had a corruption of its own, worse than the corruption of death\r
+itself--something that would breed horrors and yet would never die.\r
+What the worm was to the corpse, his sins would be to the painted image\r
+on the canvas.  They would mar its beauty and eat away its grace.  They\r
+would defile it and make it shameful.  And yet the thing would still\r
+live on.  It would be always alive.\r
+\r
+He shuddered, and for a moment he regretted that he had not told Basil\r
+the true reason why he had wished to hide the picture away.  Basil\r
+would have helped him to resist Lord Henry's influence, and the still\r
+more poisonous influences that came from his own temperament.  The love\r
+that he bore him--for it was really love--had nothing in it that was\r
+not noble and intellectual.  It was not that mere physical admiration\r
+of beauty that is born of the senses and that dies when the senses\r
+tire.  It was such love as Michelangelo had known, and Montaigne, and\r
+Winckelmann, and Shakespeare himself.  Yes, Basil could have saved him.\r
+But it was too late now.  The past could always be annihilated.\r
+Regret, denial, or forgetfulness could do that.  But the future was\r
+inevitable.  There were passions in him that would find their terrible\r
+outlet, dreams that would make the shadow of their evil real.\r
+\r
+He took up from the couch the great purple-and-gold texture that\r
+covered it, and, holding it in his hands, passed behind the screen.\r
+Was the face on the canvas viler than before?  It seemed to him that it\r
+was unchanged, and yet his loathing of it was intensified.  Gold hair,\r
+blue eyes, and rose-red lips--they all were there.  It was simply the\r
+expression that had altered.  That was horrible in its cruelty.\r
+Compared to what he saw in it of censure or rebuke, how shallow Basil's\r
+reproaches about Sibyl Vane had been!--how shallow, and of what little\r
+account!  His own soul was looking out at him from the canvas and\r
+calling him to judgement.  A look of pain came across him, and he flung\r
+the rich pall over the picture.  As he did so, a knock came to the\r
+door.  He passed out as his servant entered.\r
+\r
+"The persons are here, Monsieur."\r
+\r
+He felt that the man must be got rid of at once.  He must not be\r
+allowed to know where the picture was being taken to.  There was\r
+something sly about him, and he had thoughtful, treacherous eyes.\r
+Sitting down at the writing-table he scribbled a note to Lord Henry,\r
+asking him to send him round something to read and reminding him that\r
+they were to meet at eight-fifteen that evening.\r
+\r
+"Wait for an answer," he said, handing it to him, "and show the men in\r
+here."\r
+\r
+In two or three minutes there was another knock, and Mr. Hubbard\r
+himself, the celebrated frame-maker of South Audley Street, came in\r
+with a somewhat rough-looking young assistant.  Mr. Hubbard was a\r
+florid, red-whiskered little man, whose admiration for art was\r
+considerably tempered by the inveterate impecuniosity of most of the\r
+artists who dealt with him.  As a rule, he never left his shop.  He\r
+waited for people to come to him.  But he always made an exception in\r
+favour of Dorian Gray.  There was something about Dorian that charmed\r
+everybody.  It was a pleasure even to see him.\r
+\r
+"What can I do for you, Mr. Gray?" he said, rubbing his fat freckled\r
+hands.  "I thought I would do myself the honour of coming round in\r
+person.  I have just got a beauty of a frame, sir.  Picked it up at a\r
+sale.  Old Florentine.  Came from Fonthill, I believe.  Admirably\r
+suited for a religious subject, Mr. Gray."\r
+\r
+"I am so sorry you have given yourself the trouble of coming round, Mr.\r
+Hubbard.  I shall certainly drop in and look at the frame--though I\r
+don't go in much at present for religious art--but to-day I only want a\r
+picture carried to the top of the house for me.  It is rather heavy, so\r
+I thought I would ask you to lend me a couple of your men."\r
+\r
+"No trouble at all, Mr. Gray.  I am delighted to be of any service to\r
+you.  Which is the work of art, sir?"\r
+\r
+"This," replied Dorian, moving the screen back.  "Can you move it,\r
+covering and all, just as it is?  I don't want it to get scratched\r
+going upstairs."\r
+\r
+"There will be no difficulty, sir," said the genial frame-maker,\r
+beginning, with the aid of his assistant, to unhook the picture from\r
+the long brass chains by which it was suspended.  "And, now, where\r
+shall we carry it to, Mr. Gray?"\r
+\r
+"I will show you the way, Mr. Hubbard, if you will kindly follow me.\r
+Or perhaps you had better go in front.  I am afraid it is right at the\r
+top of the house.  We will go up by the front staircase, as it is\r
+wider."\r
+\r
+He held the door open for them, and they passed out into the hall and\r
+began the ascent.  The elaborate character of the frame had made the\r
+picture extremely bulky, and now and then, in spite of the obsequious\r
+protests of Mr. Hubbard, who had the true tradesman's spirited dislike\r
+of seeing a gentleman doing anything useful, Dorian put his hand to it\r
+so as to help them.\r
+\r
+"Something of a load to carry, sir," gasped the little man when they\r
+reached the top landing.  And he wiped his shiny forehead.\r
+\r
+"I am afraid it is rather heavy," murmured Dorian as he unlocked the\r
+door that opened into the room that was to keep for him the curious\r
+secret of his life and hide his soul from the eyes of men.\r
+\r
+He had not entered the place for more than four years--not, indeed,\r
+since he had used it first as a play-room when he was a child, and then\r
+as a study when he grew somewhat older.  It was a large,\r
+well-proportioned room, which had been specially built by the last Lord\r
+Kelso for the use of the little grandson whom, for his strange likeness\r
+to his mother, and also for other reasons, he had always hated and\r
+desired to keep at a distance.  It appeared to Dorian to have but\r
+little changed.  There was the huge Italian _cassone_, with its\r
+fantastically painted panels and its tarnished gilt mouldings, in which\r
+he had so often hidden himself as a boy.  There the satinwood book-case\r
+filled with his dog-eared schoolbooks.  On the wall behind it was\r
+hanging the same ragged Flemish tapestry where a faded king and queen\r
+were playing chess in a garden, while a company of hawkers rode by,\r
+carrying hooded birds on their gauntleted wrists.  How well he\r
+remembered it all!  Every moment of his lonely childhood came back to\r
+him as he looked round.  He recalled the stainless purity of his boyish\r
+life, and it seemed horrible to him that it was here the fatal portrait\r
+was to be hidden away.  How little he had thought, in those dead days,\r
+of all that was in store for him!\r
+\r
+But there was no other place in the house so secure from prying eyes as\r
+this.  He had the key, and no one else could enter it.  Beneath its\r
+purple pall, the face painted on the canvas could grow bestial, sodden,\r
+and unclean.  What did it matter?  No one could see it.  He himself\r
+would not see it.  Why should he watch the hideous corruption of his\r
+soul?  He kept his youth--that was enough.  And, besides, might not\r
+his nature grow finer, after all?  There was no reason that the future\r
+should be so full of shame.  Some love might come across his life, and\r
+purify him, and shield him from those sins that seemed to be already\r
+stirring in spirit and in flesh--those curious unpictured sins whose\r
+very mystery lent them their subtlety and their charm.  Perhaps, some\r
+day, the cruel look would have passed away from the scarlet sensitive\r
+mouth, and he might show to the world Basil Hallward's masterpiece.\r
+\r
+No; that was impossible.  Hour by hour, and week by week, the thing\r
+upon the canvas was growing old.  It might escape the hideousness of\r
+sin, but the hideousness of age was in store for it.  The cheeks would\r
+become hollow or flaccid.  Yellow crow's feet would creep round the\r
+fading eyes and make them horrible.  The hair would lose its\r
+brightness, the mouth would gape or droop, would be foolish or gross,\r
+as the mouths of old men are.  There would be the wrinkled throat, the\r
+cold, blue-veined hands, the twisted body, that he remembered in the\r
+grandfather who had been so stern to him in his boyhood.  The picture\r
+had to be concealed.  There was no help for it.\r
+\r
+"Bring it in, Mr. Hubbard, please," he said, wearily, turning round.\r
+"I am sorry I kept you so long.  I was thinking of something else."\r
+\r
+"Always glad to have a rest, Mr. Gray," answered the frame-maker, who\r
+was still gasping for breath.  "Where shall we put it, sir?"\r
+\r
+"Oh, anywhere.  Here:  this will do.  I don't want to have it hung up.\r
+Just lean it against the wall.  Thanks."\r
+\r
+"Might one look at the work of art, sir?"\r
+\r
+Dorian started.  "It would not interest you, Mr. Hubbard," he said,\r
+keeping his eye on the man.  He felt ready to leap upon him and fling\r
+him to the ground if he dared to lift the gorgeous hanging that\r
+concealed the secret of his life.  "I shan't trouble you any more now.\r
+I am much obliged for your kindness in coming round."\r
+\r
+"Not at all, not at all, Mr. Gray.  Ever ready to do anything for you,\r
+sir." And Mr. Hubbard tramped downstairs, followed by the assistant,\r
+who glanced back at Dorian with a look of shy wonder in his rough\r
+uncomely face.  He had never seen any one so marvellous.\r
+\r
+When the sound of their footsteps had died away, Dorian locked the door\r
+and put the key in his pocket.  He felt safe now.  No one would ever\r
+look upon the horrible thing.  No eye but his would ever see his shame.\r
+\r
+On reaching the library, he found that it was just after five o'clock\r
+and that the tea had been already brought up.  On a little table of\r
+dark perfumed wood thickly incrusted with nacre, a present from Lady\r
+Radley, his guardian's wife, a pretty professional invalid who had\r
+spent the preceding winter in Cairo, was lying a note from Lord Henry,\r
+and beside it was a book bound in yellow paper, the cover slightly torn\r
+and the edges soiled.  A copy of the third edition of _The St. James's\r
+Gazette_ had been placed on the tea-tray. It was evident that Victor had\r
+returned.  He wondered if he had met the men in the hall as they were\r
+leaving the house and had wormed out of them what they had been doing.\r
+He would be sure to miss the picture--had no doubt missed it already,\r
+while he had been laying the tea-things. The screen had not been set\r
+back, and a blank space was visible on the wall.  Perhaps some night he\r
+might find him creeping upstairs and trying to force the door of the\r
+room.  It was a horrible thing to have a spy in one's house.  He had\r
+heard of rich men who had been blackmailed all their lives by some\r
+servant who had read a letter, or overheard a conversation, or picked\r
+up a card with an address, or found beneath a pillow a withered flower\r
+or a shred of crumpled lace.\r
+\r
+He sighed, and having poured himself out some tea, opened Lord Henry's\r
+note.  It was simply to say that he sent him round the evening paper,\r
+and a book that might interest him, and that he would be at the club at\r
+eight-fifteen. He opened _The St. James's_ languidly, and looked through\r
+it.  A red pencil-mark on the fifth page caught his eye.  It drew\r
+attention to the following paragraph:\r
+\r
+\r
+INQUEST ON AN ACTRESS.--An inquest was held this morning at the Bell\r
+Tavern, Hoxton Road, by Mr. Danby, the District Coroner, on the body of\r
+Sibyl Vane, a young actress recently engaged at the Royal Theatre,\r
+Holborn.  A verdict of death by misadventure was returned.\r
+Considerable sympathy was expressed for the mother of the deceased, who\r
+was greatly affected during the giving of her own evidence, and that of\r
+Dr. Birrell, who had made the post-mortem examination of the deceased.\r
+\r
+\r
+He frowned, and tearing the paper in two, went across the room and\r
+flung the pieces away.  How ugly it all was!  And how horribly real\r
+ugliness made things!  He felt a little annoyed with Lord Henry for\r
+having sent him the report.  And it was certainly stupid of him to have\r
+marked it with red pencil.  Victor might have read it.  The man knew\r
+more than enough English for that.\r
+\r
+Perhaps he had read it and had begun to suspect something.  And, yet,\r
+what did it matter?  What had Dorian Gray to do with Sibyl Vane's\r
+death?  There was nothing to fear.  Dorian Gray had not killed her.\r
+\r
+His eye fell on the yellow book that Lord Henry had sent him.  What was\r
+it, he wondered.  He went towards the little, pearl-coloured octagonal\r
+stand that had always looked to him like the work of some strange\r
+Egyptian bees that wrought in silver, and taking up the volume, flung\r
+himself into an arm-chair and began to turn over the leaves.  After a\r
+few minutes he became absorbed.  It was the strangest book that he had\r
+ever read.  It seemed to him that in exquisite raiment, and to the\r
+delicate sound of flutes, the sins of the world were passing in dumb\r
+show before him.  Things that he had dimly dreamed of were suddenly\r
+made real to him.  Things of which he had never dreamed were gradually\r
+revealed.\r
+\r
+It was a novel without a plot and with only one character, being,\r
+indeed, simply a psychological study of a certain young Parisian who\r
+spent his life trying to realize in the nineteenth century all the\r
+passions and modes of thought that belonged to every century except his\r
+own, and to sum up, as it were, in himself the various moods through\r
+which the world-spirit had ever passed, loving for their mere\r
+artificiality those renunciations that men have unwisely called virtue,\r
+as much as those natural rebellions that wise men still call sin.  The\r
+style in which it was written was that curious jewelled style, vivid\r
+and obscure at once, full of _argot_ and of archaisms, of technical\r
+expressions and of elaborate paraphrases, that characterizes the work\r
+of some of the finest artists of the French school of _Symbolistes_.\r
+There were in it metaphors as monstrous as orchids and as subtle in\r
+colour.  The life of the senses was described in the terms of mystical\r
+philosophy.  One hardly knew at times whether one was reading the\r
+spiritual ecstasies of some mediaeval saint or the morbid confessions\r
+of a modern sinner.  It was a poisonous book.  The heavy odour of\r
+incense seemed to cling about its pages and to trouble the brain.  The\r
+mere cadence of the sentences, the subtle monotony of their music, so\r
+full as it was of complex refrains and movements elaborately repeated,\r
+produced in the mind of the lad, as he passed from chapter to chapter,\r
+a form of reverie, a malady of dreaming, that made him unconscious of\r
+the falling day and creeping shadows.\r
+\r
+Cloudless, and pierced by one solitary star, a copper-green sky gleamed\r
+through the windows.  He read on by its wan light till he could read no\r
+more.  Then, after his valet had reminded him several times of the\r
+lateness of the hour, he got up, and going into the next room, placed\r
+the book on the little Florentine table that always stood at his\r
+bedside and began to dress for dinner.\r
+\r
+It was almost nine o'clock before he reached the club, where he found\r
+Lord Henry sitting alone, in the morning-room, looking very much bored.\r
+\r
+"I am so sorry, Harry," he cried, "but really it is entirely your\r
+fault.  That book you sent me so fascinated me that I forgot how the\r
+time was going."\r
+\r
+"Yes, I thought you would like it," replied his host, rising from his\r
+chair.\r
+\r
+"I didn't say I liked it, Harry.  I said it fascinated me.  There is a\r
+great difference."\r
+\r
+"Ah, you have discovered that?" murmured Lord Henry.  And they passed\r
+into the dining-room.\r
+\r
+\r
+\r
+CHAPTER 11\r
+\r
+For years, Dorian Gray could not free himself from the influence of\r
+this book.  Or perhaps it would be more accurate to say that he never\r
+sought to free himself from it.  He procured from Paris no less than\r
+nine large-paper copies of the first edition, and had them bound in\r
+different colours, so that they might suit his various moods and the\r
+changing fancies of a nature over which he seemed, at times, to have\r
+almost entirely lost control.  The hero, the wonderful young Parisian\r
+in whom the romantic and the scientific temperaments were so strangely\r
+blended, became to him a kind of prefiguring type of himself.  And,\r
+indeed, the whole book seemed to him to contain the story of his own\r
+life, written before he had lived it.\r
+\r
+In one point he was more fortunate than the novel's fantastic hero.  He\r
+never knew--never, indeed, had any cause to know--that somewhat\r
+grotesque dread of mirrors, and polished metal surfaces, and still\r
+water which came upon the young Parisian so early in his life, and was\r
+occasioned by the sudden decay of a beau that had once, apparently,\r
+been so remarkable.  It was with an almost cruel joy--and perhaps in\r
+nearly every joy, as certainly in every pleasure, cruelty has its\r
+place--that he used to read the latter part of the book, with its\r
+really tragic, if somewhat overemphasized, account of the sorrow and\r
+despair of one who had himself lost what in others, and the world, he\r
+had most dearly valued.\r
+\r
+For the wonderful beauty that had so fascinated Basil Hallward, and\r
+many others besides him, seemed never to leave him.  Even those who had\r
+heard the most evil things against him--and from time to time strange\r
+rumours about his mode of life crept through London and became the\r
+chatter of the clubs--could not believe anything to his dishonour when\r
+they saw him.  He had always the look of one who had kept himself\r
+unspotted from the world.  Men who talked grossly became silent when\r
+Dorian Gray entered the room.  There was something in the purity of his\r
+face that rebuked them.  His mere presence seemed to recall to them the\r
+memory of the innocence that they had tarnished.  They wondered how one\r
+so charming and graceful as he was could have escaped the stain of an\r
+age that was at once sordid and sensual.\r
+\r
+Often, on returning home from one of those mysterious and prolonged\r
+absences that gave rise to such strange conjecture among those who were\r
+his friends, or thought that they were so, he himself would creep\r
+upstairs to the locked room, open the door with the key that never left\r
+him now, and stand, with a mirror, in front of the portrait that Basil\r
+Hallward had painted of him, looking now at the evil and aging face on\r
+the canvas, and now at the fair young face that laughed back at him\r
+from the polished glass.  The very sharpness of the contrast used to\r
+quicken his sense of pleasure.  He grew more and more enamoured of his\r
+own beauty, more and more interested in the corruption of his own soul.\r
+He would examine with minute care, and sometimes with a monstrous and\r
+terrible delight, the hideous lines that seared the wrinkling forehead\r
+or crawled around the heavy sensual mouth, wondering sometimes which\r
+were the more horrible, the signs of sin or the signs of age.  He would\r
+place his white hands beside the coarse bloated hands of the picture,\r
+and smile.  He mocked the misshapen body and the failing limbs.\r
+\r
+There were moments, indeed, at night, when, lying sleepless in his own\r
+delicately scented chamber, or in the sordid room of the little\r
+ill-famed tavern near the docks which, under an assumed name and in\r
+disguise, it was his habit to frequent, he would think of the ruin he\r
+had brought upon his soul with a pity that was all the more poignant\r
+because it was purely selfish.  But moments such as these were rare.\r
+That curiosity about life which Lord Henry had first stirred in him, as\r
+they sat together in the garden of their friend, seemed to increase\r
+with gratification.  The more he knew, the more he desired to know.  He\r
+had mad hungers that grew more ravenous as he fed them.\r
+\r
+Yet he was not really reckless, at any rate in his relations to\r
+society.  Once or twice every month during the winter, and on each\r
+Wednesday evening while the season lasted, he would throw open to the\r
+world his beautiful house and have the most celebrated musicians of the\r
+day to charm his guests with the wonders of their art.  His little\r
+dinners, in the settling of which Lord Henry always assisted him, were\r
+noted as much for the careful selection and placing of those invited,\r
+as for the exquisite taste shown in the decoration of the table, with\r
+its subtle symphonic arrangements of exotic flowers, and embroidered\r
+cloths, and antique plate of gold and silver.  Indeed, there were many,\r
+especially among the very young men, who saw, or fancied that they saw,\r
+in Dorian Gray the true realization of a type of which they had often\r
+dreamed in Eton or Oxford days, a type that was to combine something of\r
+the real culture of the scholar with all the grace and distinction and\r
+perfect manner of a citizen of the world.  To them he seemed to be of\r
+the company of those whom Dante describes as having sought to "make\r
+themselves perfect by the worship of beauty."  Like Gautier, he was one\r
+for whom "the visible world existed."\r
+\r
+And, certainly, to him life itself was the first, the greatest, of the\r
+arts, and for it all the other arts seemed to be but a preparation.\r
+Fashion, by which what is really fantastic becomes for a moment\r
+universal, and dandyism, which, in its own way, is an attempt to assert\r
+the absolute modernity of beauty, had, of course, their fascination for\r
+him.  His mode of dressing, and the particular styles that from time to\r
+time he affected, had their marked influence on the young exquisites of\r
+the Mayfair balls and Pall Mall club windows, who copied him in\r
+everything that he did, and tried to reproduce the accidental charm of\r
+his graceful, though to him only half-serious, fopperies.\r
+\r
+For, while he was but too ready to accept the position that was almost\r
+immediately offered to him on his coming of age, and found, indeed, a\r
+subtle pleasure in the thought that he might really become to the\r
+London of his own day what to imperial Neronian Rome the author of the\r
+Satyricon once had been, yet in his inmost heart he desired to be\r
+something more than a mere _arbiter elegantiarum_, to be consulted on the\r
+wearing of a jewel, or the knotting of a necktie, or the conduct of a\r
+cane.  He sought to elaborate some new scheme of life that would have\r
+its reasoned philosophy and its ordered principles, and find in the\r
+spiritualizing of the senses its highest realization.\r
+\r
+The worship of the senses has often, and with much justice, been\r
+decried, men feeling a natural instinct of terror about passions and\r
+sensations that seem stronger than themselves, and that they are\r
+conscious of sharing with the less highly organized forms of existence.\r
+But it appeared to Dorian Gray that the true nature of the senses had\r
+never been understood, and that they had remained savage and animal\r
+merely because the world had sought to starve them into submission or\r
+to kill them by pain, instead of aiming at making them elements of a\r
+new spirituality, of which a fine instinct for beauty was to be the\r
+dominant characteristic.  As he looked back upon man moving through\r
+history, he was haunted by a feeling of loss.  So much had been\r
+surrendered! and to such little purpose!  There had been mad wilful\r
+rejections, monstrous forms of self-torture and self-denial, whose\r
+origin was fear and whose result was a degradation infinitely more\r
+terrible than that fancied degradation from which, in their ignorance,\r
+they had sought to escape; Nature, in her wonderful irony, driving out\r
+the anchorite to feed with the wild animals of the desert and giving to\r
+the hermit the beasts of the field as his companions.\r
+\r
+Yes:  there was to be, as Lord Henry had prophesied, a new Hedonism\r
+that was to recreate life and to save it from that harsh uncomely\r
+puritanism that is having, in our own day, its curious revival.  It was\r
+to have its service of the intellect, certainly, yet it was never to\r
+accept any theory or system that would involve the sacrifice of any\r
+mode of passionate experience.  Its aim, indeed, was to be experience\r
+itself, and not the fruits of experience, sweet or bitter as they might\r
+be.  Of the asceticism that deadens the senses, as of the vulgar\r
+profligacy that dulls them, it was to know nothing.  But it was to\r
+teach man to concentrate himself upon the moments of a life that is\r
+itself but a moment.\r
+\r
+There are few of us who have not sometimes wakened before dawn, either\r
+after one of those dreamless nights that make us almost enamoured of\r
+death, or one of those nights of horror and misshapen joy, when through\r
+the chambers of the brain sweep phantoms more terrible than reality\r
+itself, and instinct with that vivid life that lurks in all grotesques,\r
+and that lends to Gothic art its enduring vitality, this art being, one\r
+might fancy, especially the art of those whose minds have been troubled\r
+with the malady of reverie.  Gradually white fingers creep through the\r
+curtains, and they appear to tremble.  In black fantastic shapes, dumb\r
+shadows crawl into the corners of the room and crouch there.  Outside,\r
+there is the stirring of birds among the leaves, or the sound of men\r
+going forth to their work, or the sigh and sob of the wind coming down\r
+from the hills and wandering round the silent house, as though it\r
+feared to wake the sleepers and yet must needs call forth sleep from\r
+her purple cave.  Veil after veil of thin dusky gauze is lifted, and by\r
+degrees the forms and colours of things are restored to them, and we\r
+watch the dawn remaking the world in its antique pattern.  The wan\r
+mirrors get back their mimic life.  The flameless tapers stand where we\r
+had left them, and beside them lies the half-cut book that we had been\r
+studying, or the wired flower that we had worn at the ball, or the\r
+letter that we had been afraid to read, or that we had read too often.\r
+Nothing seems to us changed.  Out of the unreal shadows of the night\r
+comes back the real life that we had known.  We have to resume it where\r
+we had left off, and there steals over us a terrible sense of the\r
+necessity for the continuance of energy in the same wearisome round of\r
+stereotyped habits, or a wild longing, it may be, that our eyelids\r
+might open some morning upon a world that had been refashioned anew in\r
+the darkness for our pleasure, a world in which things would have fresh\r
+shapes and colours, and be changed, or have other secrets, a world in\r
+which the past would have little or no place, or survive, at any rate,\r
+in no conscious form of obligation or regret, the remembrance even of\r
+joy having its bitterness and the memories of pleasure their pain.\r
+\r
+It was the creation of such worlds as these that seemed to Dorian Gray\r
+to be the true object, or amongst the true objects, of life; and in his\r
+search for sensations that would be at once new and delightful, and\r
+possess that element of strangeness that is so essential to romance, he\r
+would often adopt certain modes of thought that he knew to be really\r
+alien to his nature, abandon himself to their subtle influences, and\r
+then, having, as it were, caught their colour and satisfied his\r
+intellectual curiosity, leave them with that curious indifference that\r
+is not incompatible with a real ardour of temperament, and that,\r
+indeed, according to certain modern psychologists, is often a condition\r
+of it.\r
+\r
+It was rumoured of him once that he was about to join the Roman\r
+Catholic communion, and certainly the Roman ritual had always a great\r
+attraction for him.  The daily sacrifice, more awful really than all\r
+the sacrifices of the antique world, stirred him as much by its superb\r
+rejection of the evidence of the senses as by the primitive simplicity\r
+of its elements and the eternal pathos of the human tragedy that it\r
+sought to symbolize.  He loved to kneel down on the cold marble\r
+pavement and watch the priest, in his stiff flowered dalmatic, slowly\r
+and with white hands moving aside the veil of the tabernacle, or\r
+raising aloft the jewelled, lantern-shaped monstrance with that pallid\r
+wafer that at times, one would fain think, is indeed the "_panis\r
+caelestis_," the bread of angels, or, robed in the garments of the\r
+Passion of Christ, breaking the Host into the chalice and smiting his\r
+breast for his sins.  The fuming censers that the grave boys, in their\r
+lace and scarlet, tossed into the air like great gilt flowers had their\r
+subtle fascination for him.  As he passed out, he used to look with\r
+wonder at the black confessionals and long to sit in the dim shadow of\r
+one of them and listen to men and women whispering through the worn\r
+grating the true story of their lives.\r
+\r
+But he never fell into the error of arresting his intellectual\r
+development by any formal acceptance of creed or system, or of\r
+mistaking, for a house in which to live, an inn that is but suitable\r
+for the sojourn of a night, or for a few hours of a night in which\r
+there are no stars and the moon is in travail.  Mysticism, with its\r
+marvellous power of making common things strange to us, and the subtle\r
+antinomianism that always seems to accompany it, moved him for a\r
+season; and for a season he inclined to the materialistic doctrines of\r
+the _Darwinismus_ movement in Germany, and found a curious pleasure in\r
+tracing the thoughts and passions of men to some pearly cell in the\r
+brain, or some white nerve in the body, delighting in the conception of\r
+the absolute dependence of the spirit on certain physical conditions,\r
+morbid or healthy, normal or diseased.  Yet, as has been said of him\r
+before, no theory of life seemed to him to be of any importance\r
+compared with life itself.  He felt keenly conscious of how barren all\r
+intellectual speculation is when separated from action and experiment.\r
+He knew that the senses, no less than the soul, have their spiritual\r
+mysteries to reveal.\r
+\r
+And so he would now study perfumes and the secrets of their\r
+manufacture, distilling heavily scented oils and burning odorous gums\r
+from the East.  He saw that there was no mood of the mind that had not\r
+its counterpart in the sensuous life, and set himself to discover their\r
+true relations, wondering what there was in frankincense that made one\r
+mystical, and in ambergris that stirred one's passions, and in violets\r
+that woke the memory of dead romances, and in musk that troubled the\r
+brain, and in champak that stained the imagination; and seeking often\r
+to elaborate a real psychology of perfumes, and to estimate the several\r
+influences of sweet-smelling roots and scented, pollen-laden flowers;\r
+of aromatic balms and of dark and fragrant woods; of spikenard, that\r
+sickens; of hovenia, that makes men mad; and of aloes, that are said to\r
+be able to expel melancholy from the soul.\r
+\r
+At another time he devoted himself entirely to music, and in a long\r
+latticed room, with a vermilion-and-gold ceiling and walls of\r
+olive-green lacquer, he used to give curious concerts in which mad\r
+gipsies tore wild music from little zithers, or grave, yellow-shawled\r
+Tunisians plucked at the strained strings of monstrous lutes, while\r
+grinning Negroes beat monotonously upon copper drums and, crouching\r
+upon scarlet mats, slim turbaned Indians blew through long pipes of\r
+reed or brass and charmed--or feigned to charm--great hooded snakes and\r
+horrible horned adders.  The harsh intervals and shrill discords of\r
+barbaric music stirred him at times when Schubert's grace, and Chopin's\r
+beautiful sorrows, and the mighty harmonies of Beethoven himself, fell\r
+unheeded on his ear.  He collected together from all parts of the world\r
+the strangest instruments that could be found, either in the tombs of\r
+dead nations or among the few savage tribes that have survived contact\r
+with Western civilizations, and loved to touch and try them.  He had\r
+the mysterious _juruparis_ of the Rio Negro Indians, that women are not\r
+allowed to look at and that even youths may not see till they have been\r
+subjected to fasting and scourging, and the earthen jars of the\r
+Peruvians that have the shrill cries of birds, and flutes of human\r
+bones such as Alfonso de Ovalle heard in Chile, and the sonorous green\r
+jaspers that are found near Cuzco and give forth a note of singular\r
+sweetness.  He had painted gourds filled with pebbles that rattled when\r
+they were shaken; the long _clarin_ of the Mexicans, into which the\r
+performer does not blow, but through which he inhales the air; the\r
+harsh _ture_ of the Amazon tribes, that is sounded by the sentinels who\r
+sit all day long in high trees, and can be heard, it is said, at a\r
+distance of three leagues; the _teponaztli_, that has two vibrating\r
+tongues of wood and is beaten with sticks that are smeared with an\r
+elastic gum obtained from the milky juice of plants; the _yotl_-bells of\r
+the Aztecs, that are hung in clusters like grapes; and a huge\r
+cylindrical drum, covered with the skins of great serpents, like the\r
+one that Bernal Diaz saw when he went with Cortes into the Mexican\r
+temple, and of whose doleful sound he has left us so vivid a\r
+description.  The fantastic character of these instruments fascinated\r
+him, and he felt a curious delight in the thought that art, like\r
+Nature, has her monsters, things of bestial shape and with hideous\r
+voices.  Yet, after some time, he wearied of them, and would sit in his\r
+box at the opera, either alone or with Lord Henry, listening in rapt\r
+pleasure to "Tannhauser" and seeing in the prelude to that great work\r
+of art a presentation of the tragedy of his own soul.\r
+\r
+On one occasion he took up the study of jewels, and appeared at a\r
+costume ball as Anne de Joyeuse, Admiral of France, in a dress covered\r
+with five hundred and sixty pearls.  This taste enthralled him for\r
+years, and, indeed, may be said never to have left him.  He would often\r
+spend a whole day settling and resettling in their cases the various\r
+stones that he had collected, such as the olive-green chrysoberyl that\r
+turns red by lamplight, the cymophane with its wirelike line of silver,\r
+the pistachio-coloured peridot, rose-pink and wine-yellow topazes,\r
+carbuncles of fiery scarlet with tremulous, four-rayed stars, flame-red\r
+cinnamon-stones, orange and violet spinels, and amethysts with their\r
+alternate layers of ruby and sapphire.  He loved the red gold of the\r
+sunstone, and the moonstone's pearly whiteness, and the broken rainbow\r
+of the milky opal.  He procured from Amsterdam three emeralds of\r
+extraordinary size and richness of colour, and had a turquoise _de la\r
+vieille roche_ that was the envy of all the connoisseurs.\r
+\r
+He discovered wonderful stories, also, about jewels.  In Alphonso's\r
+Clericalis Disciplina a serpent was mentioned with eyes of real\r
+jacinth, and in the romantic history of Alexander, the Conqueror of\r
+Emathia was said to have found in the vale of Jordan snakes "with\r
+collars of real emeralds growing on their backs." There was a gem in\r
+the brain of the dragon, Philostratus told us, and "by the exhibition\r
+of golden letters and a scarlet robe" the monster could be thrown into\r
+a magical sleep and slain.  According to the great alchemist, Pierre de\r
+Boniface, the diamond rendered a man invisible, and the agate of India\r
+made him eloquent.  The cornelian appeased anger, and the hyacinth\r
+provoked sleep, and the amethyst drove away the fumes of wine.  The\r
+garnet cast out demons, and the hydropicus deprived the moon of her\r
+colour.  The selenite waxed and waned with the moon, and the meloceus,\r
+that discovers thieves, could be affected only by the blood of kids.\r
+Leonardus Camillus had seen a white stone taken from the brain of a\r
+newly killed toad, that was a certain antidote against poison.  The\r
+bezoar, that was found in the heart of the Arabian deer, was a charm\r
+that could cure the plague.  In the nests of Arabian birds was the\r
+aspilates, that, according to Democritus, kept the wearer from any\r
+danger by fire.\r
+\r
+The King of Ceilan rode through his city with a large ruby in his hand,\r
+as the ceremony of his coronation.  The gates of the palace of John the\r
+Priest were "made of sardius, with the horn of the horned snake\r
+inwrought, so that no man might bring poison within." Over the gable\r
+were "two golden apples, in which were two carbuncles," so that the\r
+gold might shine by day and the carbuncles by night.  In Lodge's\r
+strange romance 'A Margarite of America', it was stated that in the\r
+chamber of the queen one could behold "all the chaste ladies of the\r
+world, inchased out of silver, looking through fair mirrours of\r
+chrysolites, carbuncles, sapphires, and greene emeraults." Marco Polo\r
+had seen the inhabitants of Zipangu place rose-coloured pearls in the\r
+mouths of the dead.  A sea-monster had been enamoured of the pearl that\r
+the diver brought to King Perozes, and had slain the thief, and mourned\r
+for seven moons over its loss.  When the Huns lured the king into the\r
+great pit, he flung it away--Procopius tells the story--nor was it ever\r
+found again, though the Emperor Anastasius offered five hundred-weight\r
+of gold pieces for it.  The King of Malabar had shown to a certain\r
+Venetian a rosary of three hundred and four pearls, one for every god\r
+that he worshipped.\r
+\r
+When the Duke de Valentinois, son of Alexander VI, visited Louis XII of\r
+France, his horse was loaded with gold leaves, according to Brantome,\r
+and his cap had double rows of rubies that threw out a great light.\r
+Charles of England had ridden in stirrups hung with four hundred and\r
+twenty-one diamonds.  Richard II had a coat, valued at thirty thousand\r
+marks, which was covered with balas rubies.  Hall described Henry VIII,\r
+on his way to the Tower previous to his coronation, as wearing "a\r
+jacket of raised gold, the placard embroidered with diamonds and other\r
+rich stones, and a great bauderike about his neck of large balasses."\r
+The favourites of James I wore ear-rings of emeralds set in gold\r
+filigrane.  Edward II gave to Piers Gaveston a suit of red-gold armour\r
+studded with jacinths, a collar of gold roses set with\r
+turquoise-stones, and a skull-cap _parseme_ with pearls.  Henry II wore\r
+jewelled gloves reaching to the elbow, and had a hawk-glove sewn with\r
+twelve rubies and fifty-two great orients.  The ducal hat of Charles\r
+the Rash, the last Duke of Burgundy of his race, was hung with\r
+pear-shaped pearls and studded with sapphires.\r
+\r
+How exquisite life had once been!  How gorgeous in its pomp and\r
+decoration!  Even to read of the luxury of the dead was wonderful.\r
+\r
+Then he turned his attention to embroideries and to the tapestries that\r
+performed the office of frescoes in the chill rooms of the northern\r
+nations of Europe.  As he investigated the subject--and he always had\r
+an extraordinary faculty of becoming absolutely absorbed for the moment\r
+in whatever he took up--he was almost saddened by the reflection of the\r
+ruin that time brought on beautiful and wonderful things.  He, at any\r
+rate, had escaped that.  Summer followed summer, and the yellow\r
+jonquils bloomed and died many times, and nights of horror repeated the\r
+story of their shame, but he was unchanged.  No winter marred his face\r
+or stained his flowerlike bloom.  How different it was with material\r
+things!  Where had they passed to?  Where was the great crocus-coloured\r
+robe, on which the gods fought against the giants, that had been worked\r
+by brown girls for the pleasure of Athena?  Where the huge velarium\r
+that Nero had stretched across the Colosseum at Rome, that Titan sail\r
+of purple on which was represented the starry sky, and Apollo driving a\r
+chariot drawn by white, gilt-reined steeds?  He longed to see the\r
+curious table-napkins wrought for the Priest of the Sun, on which were\r
+displayed all the dainties and viands that could be wanted for a feast;\r
+the mortuary cloth of King Chilperic, with its three hundred golden\r
+bees; the fantastic robes that excited the indignation of the Bishop of\r
+Pontus and were figured with "lions, panthers, bears, dogs, forests,\r
+rocks, hunters--all, in fact, that a painter can copy from nature"; and\r
+the coat that Charles of Orleans once wore, on the sleeves of which\r
+were embroidered the verses of a song beginning "_Madame, je suis tout\r
+joyeux_," the musical accompaniment of the words being wrought in gold\r
+thread, and each note, of square shape in those days, formed with four\r
+pearls.  He read of the room that was prepared at the palace at Rheims\r
+for the use of Queen Joan of Burgundy and was decorated with "thirteen\r
+hundred and twenty-one parrots, made in broidery, and blazoned with the\r
+king's arms, and five hundred and sixty-one butterflies, whose wings\r
+were similarly ornamented with the arms of the queen, the whole worked\r
+in gold."  Catherine de Medicis had a mourning-bed made for her of\r
+black velvet powdered with crescents and suns.  Its curtains were of\r
+damask, with leafy wreaths and garlands, figured upon a gold and silver\r
+ground, and fringed along the edges with broideries of pearls, and it\r
+stood in a room hung with rows of the queen's devices in cut black\r
+velvet upon cloth of silver.  Louis XIV had gold embroidered caryatides\r
+fifteen feet high in his apartment.  The state bed of Sobieski, King of\r
+Poland, was made of Smyrna gold brocade embroidered in turquoises with\r
+verses from the Koran.  Its supports were of silver gilt, beautifully\r
+chased, and profusely set with enamelled and jewelled medallions.  It\r
+had been taken from the Turkish camp before Vienna, and the standard of\r
+Mohammed had stood beneath the tremulous gilt of its canopy.\r
+\r
+And so, for a whole year, he sought to accumulate the most exquisite\r
+specimens that he could find of textile and embroidered work, getting\r
+the dainty Delhi muslins, finely wrought with gold-thread palmates and\r
+stitched over with iridescent beetles' wings; the Dacca gauzes, that\r
+from their transparency are known in the East as "woven air," and\r
+"running water," and "evening dew"; strange figured cloths from Java;\r
+elaborate yellow Chinese hangings; books bound in tawny satins or fair\r
+blue silks and wrought with _fleurs-de-lis_, birds and images; veils of\r
+_lacis_ worked in Hungary point; Sicilian brocades and stiff Spanish\r
+velvets; Georgian work, with its gilt coins, and Japanese _Foukousas_,\r
+with their green-toned golds and their marvellously plumaged birds.\r
+\r
+He had a special passion, also, for ecclesiastical vestments, as indeed\r
+he had for everything connected with the service of the Church.  In the\r
+long cedar chests that lined the west gallery of his house, he had\r
+stored away many rare and beautiful specimens of what is really the\r
+raiment of the Bride of Christ, who must wear purple and jewels and\r
+fine linen that she may hide the pallid macerated body that is worn by\r
+the suffering that she seeks for and wounded by self-inflicted pain.\r
+He possessed a gorgeous cope of crimson silk and gold-thread damask,\r
+figured with a repeating pattern of golden pomegranates set in\r
+six-petalled formal blossoms, beyond which on either side was the\r
+pine-apple device wrought in seed-pearls. The orphreys were divided\r
+into panels representing scenes from the life of the Virgin, and the\r
+coronation of the Virgin was figured in coloured silks upon the hood.\r
+This was Italian work of the fifteenth century.  Another cope was of\r
+green velvet, embroidered with heart-shaped groups of acanthus-leaves,\r
+from which spread long-stemmed white blossoms, the details of which\r
+were picked out with silver thread and coloured crystals.  The morse\r
+bore a seraph's head in gold-thread raised work.  The orphreys were\r
+woven in a diaper of red and gold silk, and were starred with\r
+medallions of many saints and martyrs, among whom was St. Sebastian.\r
+He had chasubles, also, of amber-coloured silk, and blue silk and gold\r
+brocade, and yellow silk damask and cloth of gold, figured with\r
+representations of the Passion and Crucifixion of Christ, and\r
+embroidered with lions and peacocks and other emblems; dalmatics of\r
+white satin and pink silk damask, decorated with tulips and dolphins\r
+and _fleurs-de-lis_; altar frontals of crimson velvet and blue linen; and\r
+many corporals, chalice-veils, and sudaria.  In the mystic offices to\r
+which such things were put, there was something that quickened his\r
+imagination.\r
+\r
+For these treasures, and everything that he collected in his lovely\r
+house, were to be to him means of forgetfulness, modes by which he\r
+could escape, for a season, from the fear that seemed to him at times\r
+to be almost too great to be borne.  Upon the walls of the lonely\r
+locked room where he had spent so much of his boyhood, he had hung with\r
+his own hands the terrible portrait whose changing features showed him\r
+the real degradation of his life, and in front of it had draped the\r
+purple-and-gold pall as a curtain.  For weeks he would not go there,\r
+would forget the hideous painted thing, and get back his light heart,\r
+his wonderful joyousness, his passionate absorption in mere existence.\r
+Then, suddenly, some night he would creep out of the house, go down to\r
+dreadful places near Blue Gate Fields, and stay there, day after day,\r
+until he was driven away.  On his return he would sit in front of the\r
+picture, sometimes loathing it and himself, but filled, at other\r
+times, with that pride of individualism that is half the\r
+fascination of sin, and smiling with secret pleasure at the misshapen\r
+shadow that had to bear the burden that should have been his own.\r
+\r
+After a few years he could not endure to be long out of England, and\r
+gave up the villa that he had shared at Trouville with Lord Henry, as\r
+well as the little white walled-in house at Algiers where they had more\r
+than once spent the winter.  He hated to be separated from the picture\r
+that was such a part of his life, and was also afraid that during his\r
+absence some one might gain access to the room, in spite of the\r
+elaborate bars that he had caused to be placed upon the door.\r
+\r
+He was quite conscious that this would tell them nothing.  It was true\r
+that the portrait still preserved, under all the foulness and ugliness\r
+of the face, its marked likeness to himself; but what could they learn\r
+from that?  He would laugh at any one who tried to taunt him.  He had\r
+not painted it.  What was it to him how vile and full of shame it\r
+looked?  Even if he told them, would they believe it?\r
+\r
+Yet he was afraid.  Sometimes when he was down at his great house in\r
+Nottinghamshire, entertaining the fashionable young men of his own rank\r
+who were his chief companions, and astounding the county by the wanton\r
+luxury and gorgeous splendour of his mode of life, he would suddenly\r
+leave his guests and rush back to town to see that the door had not\r
+been tampered with and that the picture was still there.  What if it\r
+should be stolen?  The mere thought made him cold with horror.  Surely\r
+the world would know his secret then.  Perhaps the world already\r
+suspected it.\r
+\r
+For, while he fascinated many, there were not a few who distrusted him.\r
+He was very nearly blackballed at a West End club of which his birth\r
+and social position fully entitled him to become a member, and it was\r
+said that on one occasion, when he was brought by a friend into the\r
+smoking-room of the Churchill, the Duke of Berwick and another\r
+gentleman got up in a marked manner and went out.  Curious stories\r
+became current about him after he had passed his twenty-fifth year.  It\r
+was rumoured that he had been seen brawling with foreign sailors in a\r
+low den in the distant parts of Whitechapel, and that he consorted with\r
+thieves and coiners and knew the mysteries of their trade.  His\r
+extraordinary absences became notorious, and, when he used to reappear\r
+again in society, men would whisper to each other in corners, or pass\r
+him with a sneer, or look at him with cold searching eyes, as though\r
+they were determined to discover his secret.\r
+\r
+Of such insolences and attempted slights he, of course, took no notice,\r
+and in the opinion of most people his frank debonair manner, his\r
+charming boyish smile, and the infinite grace of that wonderful youth\r
+that seemed never to leave him, were in themselves a sufficient answer\r
+to the calumnies, for so they termed them, that were circulated about\r
+him.  It was remarked, however, that some of those who had been most\r
+intimate with him appeared, after a time, to shun him.  Women who had\r
+wildly adored him, and for his sake had braved all social censure and\r
+set convention at defiance, were seen to grow pallid with shame or\r
+horror if Dorian Gray entered the room.\r
+\r
+Yet these whispered scandals only increased in the eyes of many his\r
+strange and dangerous charm.  His great wealth was a certain element of\r
+security.  Society--civilized society, at least--is never very ready to\r
+believe anything to the detriment of those who are both rich and\r
+fascinating.  It feels instinctively that manners are of more\r
+importance than morals, and, in its opinion, the highest respectability\r
+is of much less value than the possession of a good _chef_.  And, after\r
+all, it is a very poor consolation to be told that the man who has\r
+given one a bad dinner, or poor wine, is irreproachable in his private\r
+life.  Even the cardinal virtues cannot atone for half-cold _entrees_, as\r
+Lord Henry remarked once, in a discussion on the subject, and there is\r
+possibly a good deal to be said for his view.  For the canons of good\r
+society are, or should be, the same as the canons of art.  Form is\r
+absolutely essential to it.  It should have the dignity of a ceremony,\r
+as well as its unreality, and should combine the insincere character of\r
+a romantic play with the wit and beauty that make such plays delightful\r
+to us.  Is insincerity such a terrible thing?  I think not.  It is\r
+merely a method by which we can multiply our personalities.\r
+\r
+Such, at any rate, was Dorian Gray's opinion.  He used to wonder at the\r
+shallow psychology of those who conceive the ego in man as a thing\r
+simple, permanent, reliable, and of one essence.  To him, man was a\r
+being with myriad lives and myriad sensations, a complex multiform\r
+creature that bore within itself strange legacies of thought and\r
+passion, and whose very flesh was tainted with the monstrous maladies\r
+of the dead.  He loved to stroll through the gaunt cold picture-gallery\r
+of his country house and look at the various portraits of those whose\r
+blood flowed in his veins.  Here was Philip Herbert, described by\r
+Francis Osborne, in his Memoires on the Reigns of Queen Elizabeth and\r
+King James, as one who was "caressed by the Court for his handsome\r
+face, which kept him not long company."  Was it young Herbert's life\r
+that he sometimes led?  Had some strange poisonous germ crept from body\r
+to body till it had reached his own?  Was it some dim sense of that\r
+ruined grace that had made him so suddenly, and almost without cause,\r
+give utterance, in Basil Hallward's studio, to the mad prayer that had\r
+so changed his life?  Here, in gold-embroidered red doublet, jewelled\r
+surcoat, and gilt-edged ruff and wristbands, stood Sir Anthony Sherard,\r
+with his silver-and-black armour piled at his feet.  What had this\r
+man's legacy been?  Had the lover of Giovanna of Naples bequeathed him\r
+some inheritance of sin and shame?  Were his own actions merely the\r
+dreams that the dead man had not dared to realize?  Here, from the\r
+fading canvas, smiled Lady Elizabeth Devereux, in her gauze hood, pearl\r
+stomacher, and pink slashed sleeves.  A flower was in her right hand,\r
+and her left clasped an enamelled collar of white and damask roses.  On\r
+a table by her side lay a mandolin and an apple.  There were large\r
+green rosettes upon her little pointed shoes.  He knew her life, and\r
+the strange stories that were told about her lovers.  Had he something\r
+of her temperament in him?  These oval, heavy-lidded eyes seemed to\r
+look curiously at him.  What of George Willoughby, with his powdered\r
+hair and fantastic patches?  How evil he looked!  The face was\r
+saturnine and swarthy, and the sensual lips seemed to be twisted with\r
+disdain.  Delicate lace ruffles fell over the lean yellow hands that\r
+were so overladen with rings.  He had been a macaroni of the eighteenth\r
+century, and the friend, in his youth, of Lord Ferrars.  What of the\r
+second Lord Beckenham, the companion of the Prince Regent in his\r
+wildest days, and one of the witnesses at the secret marriage with Mrs.\r
+Fitzherbert?  How proud and handsome he was, with his chestnut curls\r
+and insolent pose!  What passions had he bequeathed?  The world had\r
+looked upon him as infamous.  He had led the orgies at Carlton House.\r
+The star of the Garter glittered upon his breast.  Beside him hung the\r
+portrait of his wife, a pallid, thin-lipped woman in black.  Her blood,\r
+also, stirred within him.  How curious it all seemed!  And his mother\r
+with her Lady Hamilton face and her moist, wine-dashed lips--he knew\r
+what he had got from her.  He had got from her his beauty, and his\r
+passion for the beauty of others.  She laughed at him in her loose\r
+Bacchante dress.  There were vine leaves in her hair.  The purple\r
+spilled from the cup she was holding.  The carnations of the painting\r
+had withered, but the eyes were still wonderful in their depth and\r
+brilliancy of colour.  They seemed to follow him wherever he went.\r
+\r
+Yet one had ancestors in literature as well as in one's own race,\r
+nearer perhaps in type and temperament, many of them, and certainly\r
+with an influence of which one was more absolutely conscious.  There\r
+were times when it appeared to Dorian Gray that the whole of history\r
+was merely the record of his own life, not as he had lived it in act\r
+and circumstance, but as his imagination had created it for him, as it\r
+had been in his brain and in his passions.  He felt that he had known\r
+them all, those strange terrible figures that had passed across the\r
+stage of the world and made sin so marvellous and evil so full of\r
+subtlety.  It seemed to him that in some mysterious way their lives had\r
+been his own.\r
+\r
+The hero of the wonderful novel that had so influenced his life had\r
+himself known this curious fancy.  In the seventh chapter he tells how,\r
+crowned with laurel, lest lightning might strike him, he had sat, as\r
+Tiberius, in a garden at Capri, reading the shameful books of\r
+Elephantis, while dwarfs and peacocks strutted round him and the\r
+flute-player mocked the swinger of the censer; and, as Caligula, had\r
+caroused with the green-shirted jockeys in their stables and supped in\r
+an ivory manger with a jewel-frontleted horse; and, as Domitian, had\r
+wandered through a corridor lined with marble mirrors, looking round\r
+with haggard eyes for the reflection of the dagger that was to end his\r
+days, and sick with that ennui, that terrible _taedium vitae_, that comes\r
+on those to whom life denies nothing; and had peered through a clear\r
+emerald at the red shambles of the circus and then, in a litter of\r
+pearl and purple drawn by silver-shod mules, been carried through the\r
+Street of Pomegranates to a House of Gold and heard men cry on Nero\r
+Caesar as he passed by; and, as Elagabalus, had painted his face with\r
+colours, and plied the distaff among the women, and brought the Moon\r
+from Carthage and given her in mystic marriage to the Sun.\r
+\r
+Over and over again Dorian used to read this fantastic chapter, and the\r
+two chapters immediately following, in which, as in some curious\r
+tapestries or cunningly wrought enamels, were pictured the awful and\r
+beautiful forms of those whom vice and blood and weariness had made\r
+monstrous or mad:  Filippo, Duke of Milan, who slew his wife and\r
+painted her lips with a scarlet poison that her lover might suck death\r
+from the dead thing he fondled; Pietro Barbi, the Venetian, known as\r
+Paul the Second, who sought in his vanity to assume the title of\r
+Formosus, and whose tiara, valued at two hundred thousand florins, was\r
+bought at the price of a terrible sin; Gian Maria Visconti, who used\r
+hounds to chase living men and whose murdered body was covered with\r
+roses by a harlot who had loved him; the Borgia on his white horse,\r
+with Fratricide riding beside him and his mantle stained with the blood\r
+of Perotto; Pietro Riario, the young Cardinal Archbishop of Florence,\r
+child and minion of Sixtus IV, whose beauty was equalled only by his\r
+debauchery, and who received Leonora of Aragon in a pavilion of white\r
+and crimson silk, filled with nymphs and centaurs, and gilded a boy\r
+that he might serve at the feast as Ganymede or Hylas; Ezzelin, whose\r
+melancholy could be cured only by the spectacle of death, and who had a\r
+passion for red blood, as other men have for red wine--the son of the\r
+Fiend, as was reported, and one who had cheated his father at dice when\r
+gambling with him for his own soul; Giambattista Cibo, who in mockery\r
+took the name of Innocent and into whose torpid veins the blood of\r
+three lads was infused by a Jewish doctor; Sigismondo Malatesta, the\r
+lover of Isotta and the lord of Rimini, whose effigy was burned at Rome\r
+as the enemy of God and man, who strangled Polyssena with a napkin, and\r
+gave poison to Ginevra d'Este in a cup of emerald, and in honour of a\r
+shameful passion built a pagan church for Christian worship; Charles\r
+VI, who had so wildly adored his brother's wife that a leper had warned\r
+him of the insanity that was coming on him, and who, when his brain had\r
+sickened and grown strange, could only be soothed by Saracen cards\r
+painted with the images of love and death and madness; and, in his\r
+trimmed jerkin and jewelled cap and acanthuslike curls, Grifonetto\r
+Baglioni, who slew Astorre with his bride, and Simonetto with his page,\r
+and whose comeliness was such that, as he lay dying in the yellow\r
+piazza of Perugia, those who had hated him could not choose but weep,\r
+and Atalanta, who had cursed him, blessed him.\r
+\r
+There was a horrible fascination in them all.  He saw them at night,\r
+and they troubled his imagination in the day.  The Renaissance knew of\r
+strange manners of poisoning--poisoning by a helmet and a lighted\r
+torch, by an embroidered glove and a jewelled fan, by a gilded pomander\r
+and by an amber chain.  Dorian Gray had been poisoned by a book.  There\r
+were moments when he looked on evil simply as a mode through which he\r
+could realize his conception of the beautiful.\r
+\r
+\r
+\r
+CHAPTER 12\r
+\r
+It was on the ninth of November, the eve of his own thirty-eighth\r
+birthday, as he often remembered afterwards.\r
+\r
+He was walking home about eleven o'clock from Lord Henry's, where he\r
+had been dining, and was wrapped in heavy furs, as the night was cold\r
+and foggy.  At the corner of Grosvenor Square and South Audley Street,\r
+a man passed him in the mist, walking very fast and with the collar of\r
+his grey ulster turned up.  He had a bag in his hand.  Dorian\r
+recognized him.  It was Basil Hallward.  A strange sense of fear, for\r
+which he could not account, came over him.  He made no sign of\r
+recognition and went on quickly in the direction of his own house.\r
+\r
+But Hallward had seen him.  Dorian heard him first stopping on the\r
+pavement and then hurrying after him.  In a few moments, his hand was\r
+on his arm.\r
+\r
+"Dorian!  What an extraordinary piece of luck!  I have been waiting for\r
+you in your library ever since nine o'clock. Finally I took pity on\r
+your tired servant and told him to go to bed, as he let me out.  I am\r
+off to Paris by the midnight train, and I particularly wanted to see\r
+you before I left.  I thought it was you, or rather your fur coat, as\r
+you passed me.  But I wasn't quite sure.  Didn't you recognize me?"\r
+\r
+"In this fog, my dear Basil?  Why, I can't even recognize Grosvenor\r
+Square.  I believe my house is somewhere about here, but I don't feel\r
+at all certain about it.  I am sorry you are going away, as I have not\r
+seen you for ages.  But I suppose you will be back soon?"\r
+\r
+"No:  I am going to be out of England for six months.  I intend to take\r
+a studio in Paris and shut myself up till I have finished a great\r
+picture I have in my head.  However, it wasn't about myself I wanted to\r
+talk.  Here we are at your door.  Let me come in for a moment.  I have\r
+something to say to you."\r
+\r
+"I shall be charmed.  But won't you miss your train?" said Dorian Gray\r
+languidly as he passed up the steps and opened the door with his\r
+latch-key.\r
+\r
+The lamplight struggled out through the fog, and Hallward looked at his\r
+watch.  "I have heaps of time," he answered.  "The train doesn't go\r
+till twelve-fifteen, and it is only just eleven.  In fact, I was on my\r
+way to the club to look for you, when I met you.  You see, I shan't\r
+have any delay about luggage, as I have sent on my heavy things.  All I\r
+have with me is in this bag, and I can easily get to Victoria in twenty\r
+minutes."\r
+\r
+Dorian looked at him and smiled.  "What a way for a fashionable painter\r
+to travel!  A Gladstone bag and an ulster!  Come in, or the fog will\r
+get into the house.  And mind you don't talk about anything serious.\r
+Nothing is serious nowadays.  At least nothing should be."\r
+\r
+Hallward shook his head, as he entered, and followed Dorian into the\r
+library.  There was a bright wood fire blazing in the large open\r
+hearth.  The lamps were lit, and an open Dutch silver spirit-case\r
+stood, with some siphons of soda-water and large cut-glass tumblers, on\r
+a little marqueterie table.\r
+\r
+"You see your servant made me quite at home, Dorian.  He gave me\r
+everything I wanted, including your best gold-tipped cigarettes.  He is\r
+a most hospitable creature.  I like him much better than the Frenchman\r
+you used to have.  What has become of the Frenchman, by the bye?"\r
+\r
+Dorian shrugged his shoulders.  "I believe he married Lady Radley's\r
+maid, and has established her in Paris as an English dressmaker.\r
+Anglomania is very fashionable over there now, I hear.  It seems silly\r
+of the French, doesn't it?  But--do you know?--he was not at all a bad\r
+servant.  I never liked him, but I had nothing to complain about.  One\r
+often imagines things that are quite absurd.  He was really very\r
+devoted to me and seemed quite sorry when he went away.  Have another\r
+brandy-and-soda? Or would you like hock-and-seltzer? I always take\r
+hock-and-seltzer myself.  There is sure to be some in the next room."\r
+\r
+"Thanks, I won't have anything more," said the painter, taking his cap\r
+and coat off and throwing them on the bag that he had placed in the\r
+corner.  "And now, my dear fellow, I want to speak to you seriously.\r
+Don't frown like that.  You make it so much more difficult for me."\r
+\r
+"What is it all about?" cried Dorian in his petulant way, flinging\r
+himself down on the sofa.  "I hope it is not about myself.  I am tired\r
+of myself to-night. I should like to be somebody else."\r
+\r
+"It is about yourself," answered Hallward in his grave deep voice, "and\r
+I must say it to you.  I shall only keep you half an hour."\r
+\r
+Dorian sighed and lit a cigarette.  "Half an hour!" he murmured.\r
+\r
+"It is not much to ask of you, Dorian, and it is entirely for your own\r
+sake that I am speaking.  I think it right that you should know that\r
+the most dreadful things are being said against you in London."\r
+\r
+"I don't wish to know anything about them.  I love scandals about other\r
+people, but scandals about myself don't interest me.  They have not got\r
+the charm of novelty."\r
+\r
+"They must interest you, Dorian.  Every gentleman is interested in his\r
+good name.  You don't want people to talk of you as something vile and\r
+degraded.  Of course, you have your position, and your wealth, and all\r
+that kind of thing.  But position and wealth are not everything.  Mind\r
+you, I don't believe these rumours at all.  At least, I can't believe\r
+them when I see you.  Sin is a thing that writes itself across a man's\r
+face.  It cannot be concealed.  People talk sometimes of secret vices.\r
+There are no such things.  If a wretched man has a vice, it shows\r
+itself in the lines of his mouth, the droop of his eyelids, the\r
+moulding of his hands even.  Somebody--I won't mention his name, but\r
+you know him--came to me last year to have his portrait done.  I had\r
+never seen him before, and had never heard anything about him at the\r
+time, though I have heard a good deal since.  He offered an extravagant\r
+price.  I refused him.  There was something in the shape of his fingers\r
+that I hated.  I know now that I was quite right in what I fancied\r
+about him.  His life is dreadful.  But you, Dorian, with your pure,\r
+bright, innocent face, and your marvellous untroubled youth--I can't\r
+believe anything against you.  And yet I see you very seldom, and you\r
+never come down to the studio now, and when I am away from you, and I\r
+hear all these hideous things that people are whispering about you, I\r
+don't know what to say.  Why is it, Dorian, that a man like the Duke of\r
+Berwick leaves the room of a club when you enter it?  Why is it that so\r
+many gentlemen in London will neither go to your house or invite you to\r
+theirs?  You used to be a friend of Lord Staveley.  I met him at dinner\r
+last week.  Your name happened to come up in conversation, in\r
+connection with the miniatures you have lent to the exhibition at the\r
+Dudley.  Staveley curled his lip and said that you might have the most\r
+artistic tastes, but that you were a man whom no pure-minded girl\r
+should be allowed to know, and whom no chaste woman should sit in the\r
+same room with.  I reminded him that I was a friend of yours, and asked\r
+him what he meant.  He told me.  He told me right out before everybody.\r
+It was horrible!  Why is your friendship so fatal to young men?  There\r
+was that wretched boy in the Guards who committed suicide.  You were\r
+his great friend.  There was Sir Henry Ashton, who had to leave England\r
+with a tarnished name.  You and he were inseparable.  What about Adrian\r
+Singleton and his dreadful end?  What about Lord Kent's only son and\r
+his career?  I met his father yesterday in St. James's Street.  He\r
+seemed broken with shame and sorrow.  What about the young Duke of\r
+Perth?  What sort of life has he got now?  What gentleman would\r
+associate with him?"\r
+\r
+"Stop, Basil.  You are talking about things of which you know nothing,"\r
+said Dorian Gray, biting his lip, and with a note of infinite contempt\r
+in his voice.  "You ask me why Berwick leaves a room when I enter it.\r
+It is because I know everything about his life, not because he knows\r
+anything about mine.  With such blood as he has in his veins, how could\r
+his record be clean?  You ask me about Henry Ashton and young Perth.\r
+Did I teach the one his vices, and the other his debauchery?  If Kent's\r
+silly son takes his wife from the streets, what is that to me?  If\r
+Adrian Singleton writes his friend's name across a bill, am I his\r
+keeper?  I know how people chatter in England.  The middle classes air\r
+their moral prejudices over their gross dinner-tables, and whisper\r
+about what they call the profligacies of their betters in order to try\r
+and pretend that they are in smart society and on intimate terms with\r
+the people they slander.  In this country, it is enough for a man to\r
+have distinction and brains for every common tongue to wag against him.\r
+And what sort of lives do these people, who pose as being moral, lead\r
+themselves?  My dear fellow, you forget that we are in the native land\r
+of the hypocrite."\r
+\r
+"Dorian," cried Hallward, "that is not the question.  England is bad\r
+enough I know, and English society is all wrong.  That is the reason\r
+why I want you to be fine.  You have not been fine.  One has a right to\r
+judge of a man by the effect he has over his friends.  Yours seem to\r
+lose all sense of honour, of goodness, of purity.  You have filled them\r
+with a madness for pleasure.  They have gone down into the depths.  You\r
+led them there.  Yes:  you led them there, and yet you can smile, as\r
+you are smiling now.  And there is worse behind.  I know you and Harry\r
+are inseparable.  Surely for that reason, if for none other, you should\r
+not have made his sister's name a by-word."\r
+\r
+"Take care, Basil.  You go too far."\r
+\r
+"I must speak, and you must listen.  You shall listen.  When you met\r
+Lady Gwendolen, not a breath of scandal had ever touched her.  Is there\r
+a single decent woman in London now who would drive with her in the\r
+park?  Why, even her children are not allowed to live with her.  Then\r
+there are other stories--stories that you have been seen creeping at\r
+dawn out of dreadful houses and slinking in disguise into the foulest\r
+dens in London.  Are they true?  Can they be true?  When I first heard\r
+them, I laughed.  I hear them now, and they make me shudder.  What\r
+about your country-house and the life that is led there?  Dorian, you\r
+don't know what is said about you.  I won't tell you that I don't want\r
+to preach to you.  I remember Harry saying once that every man who\r
+turned himself into an amateur curate for the moment always began by\r
+saying that, and then proceeded to break his word.  I do want to preach\r
+to you.  I want you to lead such a life as will make the world respect\r
+you.  I want you to have a clean name and a fair record.  I want you to\r
+get rid of the dreadful people you associate with.  Don't shrug your\r
+shoulders like that.  Don't be so indifferent.  You have a wonderful\r
+influence.  Let it be for good, not for evil.  They say that you\r
+corrupt every one with whom you become intimate, and that it is quite\r
+sufficient for you to enter a house for shame of some kind to follow\r
+after.  I don't know whether it is so or not.  How should I know?  But\r
+it is said of you.  I am told things that it seems impossible to doubt.\r
+Lord Gloucester was one of my greatest friends at Oxford.  He showed me\r
+a letter that his wife had written to him when she was dying alone in\r
+her villa at Mentone.  Your name was implicated in the most terrible\r
+confession I ever read.  I told him that it was absurd--that I knew you\r
+thoroughly and that you were incapable of anything of the kind.  Know\r
+you?  I wonder do I know you?  Before I could answer that, I should\r
+have to see your soul."\r
+\r
+"To see my soul!" muttered Dorian Gray, starting up from the sofa and\r
+turning almost white from fear.\r
+\r
+"Yes," answered Hallward gravely, and with deep-toned sorrow in his\r
+voice, "to see your soul.  But only God can do that."\r
+\r
+A bitter laugh of mockery broke from the lips of the younger man.  "You\r
+shall see it yourself, to-night!" he cried, seizing a lamp from the\r
+table.  "Come:  it is your own handiwork.  Why shouldn't you look at\r
+it?  You can tell the world all about it afterwards, if you choose.\r
+Nobody would believe you.  If they did believe you, they would like me\r
+all the better for it.  I know the age better than you do, though you\r
+will prate about it so tediously.  Come, I tell you.  You have\r
+chattered enough about corruption.  Now you shall look on it face to\r
+face."\r
+\r
+There was the madness of pride in every word he uttered.  He stamped\r
+his foot upon the ground in his boyish insolent manner.  He felt a\r
+terrible joy at the thought that some one else was to share his secret,\r
+and that the man who had painted the portrait that was the origin of\r
+all his shame was to be burdened for the rest of his life with the\r
+hideous memory of what he had done.\r
+\r
+"Yes," he continued, coming closer to him and looking steadfastly into\r
+his stern eyes, "I shall show you my soul.  You shall see the thing\r
+that you fancy only God can see."\r
+\r
+Hallward started back.  "This is blasphemy, Dorian!" he cried.  "You\r
+must not say things like that.  They are horrible, and they don't mean\r
+anything."\r
+\r
+"You think so?"  He laughed again.\r
+\r
+"I know so.  As for what I said to you to-night, I said it for your\r
+good.  You know I have been always a stanch friend to you."\r
+\r
+"Don't touch me.  Finish what you have to say."\r
+\r
+A twisted flash of pain shot across the painter's face.  He paused for\r
+a moment, and a wild feeling of pity came over him.  After all, what\r
+right had he to pry into the life of Dorian Gray?  If he had done a\r
+tithe of what was rumoured about him, how much he must have suffered!\r
+Then he straightened himself up, and walked over to the fire-place, and\r
+stood there, looking at the burning logs with their frostlike ashes and\r
+their throbbing cores of flame.\r
+\r
+"I am waiting, Basil," said the young man in a hard clear voice.\r
+\r
+He turned round.  "What I have to say is this," he cried.  "You must\r
+give me some answer to these horrible charges that are made against\r
+you.  If you tell me that they are absolutely untrue from beginning to\r
+end, I shall believe you.  Deny them, Dorian, deny them!  Can't you see\r
+what I am going through?  My God! don't tell me that you are bad, and\r
+corrupt, and shameful."\r
+\r
+Dorian Gray smiled.  There was a curl of contempt in his lips.  "Come\r
+upstairs, Basil," he said quietly.  "I keep a diary of my life from day\r
+to day, and it never leaves the room in which it is written.  I shall\r
+show it to you if you come with me."\r
+\r
+"I shall come with you, Dorian, if you wish it.  I see I have missed my\r
+train.  That makes no matter.  I can go to-morrow. But don't ask me to\r
+read anything to-night. All I want is a plain answer to my question."\r
+\r
+"That shall be given to you upstairs.  I could not give it here.  You\r
+will not have to read long."\r
+\r
+\r
+\r
+CHAPTER 13\r
+\r
+He passed out of the room and began the ascent, Basil Hallward\r
+following close behind.  They walked softly, as men do instinctively at\r
+night.  The lamp cast fantastic shadows on the wall and staircase.  A\r
+rising wind made some of the windows rattle.\r
+\r
+When they reached the top landing, Dorian set the lamp down on the\r
+floor, and taking out the key, turned it in the lock.  "You insist on\r
+knowing, Basil?" he asked in a low voice.\r
+\r
+"Yes."\r
+\r
+"I am delighted," he answered, smiling.  Then he added, somewhat\r
+harshly, "You are the one man in the world who is entitled to know\r
+everything about me.  You have had more to do with my life than you\r
+think"; and, taking up the lamp, he opened the door and went in.  A\r
+cold current of air passed them, and the light shot up for a moment in\r
+a flame of murky orange.  He shuddered.  "Shut the door behind you," he\r
+whispered, as he placed the lamp on the table.\r
+\r
+Hallward glanced round him with a puzzled expression.  The room looked\r
+as if it had not been lived in for years.  A faded Flemish tapestry, a\r
+curtained picture, an old Italian _cassone_, and an almost empty\r
+book-case--that was all that it seemed to contain, besides a chair and\r
+a table.  As Dorian Gray was lighting a half-burned candle that was\r
+standing on the mantelshelf, he saw that the whole place was covered\r
+with dust and that the carpet was in holes.  A mouse ran scuffling\r
+behind the wainscoting.  There was a damp odour of mildew.\r
+\r
+"So you think that it is only God who sees the soul, Basil?  Draw that\r
+curtain back, and you will see mine."\r
+\r
+The voice that spoke was cold and cruel.  "You are mad, Dorian, or\r
+playing a part," muttered Hallward, frowning.\r
+\r
+"You won't? Then I must do it myself," said the young man, and he tore\r
+the curtain from its rod and flung it on the ground.\r
+\r
+An exclamation of horror broke from the painter's lips as he saw in the\r
+dim light the hideous face on the canvas grinning at him.  There was\r
+something in its expression that filled him with disgust and loathing.\r
+Good heavens! it was Dorian Gray's own face that he was looking at!\r
+The horror, whatever it was, had not yet entirely spoiled that\r
+marvellous beauty.  There was still some gold in the thinning hair and\r
+some scarlet on the sensual mouth.  The sodden eyes had kept something\r
+of the loveliness of their blue, the noble curves had not yet\r
+completely passed away from chiselled nostrils and from plastic throat.\r
+Yes, it was Dorian himself.  But who had done it?  He seemed to\r
+recognize his own brushwork, and the frame was his own design.  The\r
+idea was monstrous, yet he felt afraid.  He seized the lighted candle,\r
+and held it to the picture.  In the left-hand corner was his own name,\r
+traced in long letters of bright vermilion.\r
+\r
+It was some foul parody, some infamous ignoble satire.  He had never\r
+done that.  Still, it was his own picture.  He knew it, and he felt as\r
+if his blood had changed in a moment from fire to sluggish ice.  His\r
+own picture!  What did it mean?  Why had it altered?  He turned and\r
+looked at Dorian Gray with the eyes of a sick man.  His mouth twitched,\r
+and his parched tongue seemed unable to articulate.  He passed his hand\r
+across his forehead.  It was dank with clammy sweat.\r
+\r
+The young man was leaning against the mantelshelf, watching him with\r
+that strange expression that one sees on the faces of those who are\r
+absorbed in a play when some great artist is acting.  There was neither\r
+real sorrow in it nor real joy.  There was simply the passion of the\r
+spectator, with perhaps a flicker of triumph in his eyes.  He had taken\r
+the flower out of his coat, and was smelling it, or pretending to do so.\r
+\r
+"What does this mean?" cried Hallward, at last.  His own voice sounded\r
+shrill and curious in his ears.\r
+\r
+"Years ago, when I was a boy," said Dorian Gray, crushing the flower in\r
+his hand, "you met me, flattered me, and taught me to be vain of my\r
+good looks.  One day you introduced me to a friend of yours, who\r
+explained to me the wonder of youth, and you finished a portrait of me\r
+that revealed to me the wonder of beauty.  In a mad moment that, even\r
+now, I don't know whether I regret or not, I made a wish, perhaps you\r
+would call it a prayer...."\r
+\r
+"I remember it!  Oh, how well I remember it!  No! the thing is\r
+impossible.  The room is damp.  Mildew has got into the canvas.  The\r
+paints I used had some wretched mineral poison in them.  I tell you the\r
+thing is impossible."\r
+\r
+"Ah, what is impossible?" murmured the young man, going over to the\r
+window and leaning his forehead against the cold, mist-stained glass.\r
+\r
+"You told me you had destroyed it."\r
+\r
+"I was wrong.  It has destroyed me."\r
+\r
+"I don't believe it is my picture."\r
+\r
+"Can't you see your ideal in it?" said Dorian bitterly.\r
+\r
+"My ideal, as you call it..."\r
+\r
+"As you called it."\r
+\r
+"There was nothing evil in it, nothing shameful.  You were to me such\r
+an ideal as I shall never meet again.  This is the face of a satyr."\r
+\r
+"It is the face of my soul."\r
+\r
+"Christ! what a thing I must have worshipped!  It has the eyes of a\r
+devil."\r
+\r
+"Each of us has heaven and hell in him, Basil," cried Dorian with a\r
+wild gesture of despair.\r
+\r
+Hallward turned again to the portrait and gazed at it.  "My God!  If it\r
+is true," he exclaimed, "and this is what you have done with your life,\r
+why, you must be worse even than those who talk against you fancy you\r
+to be!" He held the light up again to the canvas and examined it.  The\r
+surface seemed to be quite undisturbed and as he had left it.  It was\r
+from within, apparently, that the foulness and horror had come.\r
+Through some strange quickening of inner life the leprosies of sin were\r
+slowly eating the thing away.  The rotting of a corpse in a watery\r
+grave was not so fearful.\r
+\r
+His hand shook, and the candle fell from its socket on the floor and\r
+lay there sputtering.  He placed his foot on it and put it out.  Then\r
+he flung himself into the rickety chair that was standing by the table\r
+and buried his face in his hands.\r
+\r
+"Good God, Dorian, what a lesson!  What an awful lesson!" There was no\r
+answer, but he could hear the young man sobbing at the window.  "Pray,\r
+Dorian, pray," he murmured.  "What is it that one was taught to say in\r
+one's boyhood?  'Lead us not into temptation.  Forgive us our sins.\r
+Wash away our iniquities.'  Let us say that together.  The prayer of\r
+your pride has been answered.  The prayer of your repentance will be\r
+answered also.  I worshipped you too much.  I am punished for it.  You\r
+worshipped yourself too much.  We are both punished."\r
+\r
+Dorian Gray turned slowly around and looked at him with tear-dimmed\r
+eyes.  "It is too late, Basil," he faltered.\r
+\r
+"It is never too late, Dorian.  Let us kneel down and try if we cannot\r
+remember a prayer.  Isn't there a verse somewhere, 'Though your sins be\r
+as scarlet, yet I will make them as white as snow'?"\r
+\r
+"Those words mean nothing to me now."\r
+\r
+"Hush!  Don't say that.  You have done enough evil in your life.  My\r
+God!  Don't you see that accursed thing leering at us?"\r
+\r
+Dorian Gray glanced at the picture, and suddenly an uncontrollable\r
+feeling of hatred for Basil Hallward came over him, as though it had\r
+been suggested to him by the image on the canvas, whispered into his\r
+ear by those grinning lips.  The mad passions of a hunted animal\r
+stirred within him, and he loathed the man who was seated at the table,\r
+more than in his whole life he had ever loathed anything.  He glanced\r
+wildly around.  Something glimmered on the top of the painted chest\r
+that faced him.  His eye fell on it.  He knew what it was.  It was a\r
+knife that he had brought up, some days before, to cut a piece of cord,\r
+and had forgotten to take away with him.  He moved slowly towards it,\r
+passing Hallward as he did so.  As soon as he got behind him, he seized\r
+it and turned round.  Hallward stirred in his chair as if he was going\r
+to rise.  He rushed at him and dug the knife into the great vein that\r
+is behind the ear, crushing the man's head down on the table and\r
+stabbing again and again.\r
+\r
+There was a stifled groan and the horrible sound of some one choking\r
+with blood.  Three times the outstretched arms shot up convulsively,\r
+waving grotesque, stiff-fingered hands in the air.  He stabbed him\r
+twice more, but the man did not move.  Something began to trickle on\r
+the floor.  He waited for a moment, still pressing the head down.  Then\r
+he threw the knife on the table, and listened.\r
+\r
+He could hear nothing, but the drip, drip on the threadbare carpet.  He\r
+opened the door and went out on the landing.  The house was absolutely\r
+quiet.  No one was about.  For a few seconds he stood bending over the\r
+balustrade and peering down into the black seething well of darkness.\r
+Then he took out the key and returned to the room, locking himself in\r
+as he did so.\r
+\r
+The thing was still seated in the chair, straining over the table with\r
+bowed head, and humped back, and long fantastic arms.  Had it not been\r
+for the red jagged tear in the neck and the clotted black pool that was\r
+slowly widening on the table, one would have said that the man was\r
+simply asleep.\r
+\r
+How quickly it had all been done!  He felt strangely calm, and walking\r
+over to the window, opened it and stepped out on the balcony.  The wind\r
+had blown the fog away, and the sky was like a monstrous peacock's\r
+tail, starred with myriads of golden eyes.  He looked down and saw the\r
+policeman going his rounds and flashing the long beam of his lantern on\r
+the doors of the silent houses.  The crimson spot of a prowling hansom\r
+gleamed at the corner and then vanished.  A woman in a fluttering shawl\r
+was creeping slowly by the railings, staggering as she went.  Now and\r
+then she stopped and peered back.  Once, she began to sing in a hoarse\r
+voice.  The policeman strolled over and said something to her.  She\r
+stumbled away, laughing.  A bitter blast swept across the square.  The\r
+gas-lamps flickered and became blue, and the leafless trees shook their\r
+black iron branches to and fro.  He shivered and went back, closing the\r
+window behind him.\r
+\r
+Having reached the door, he turned the key and opened it.  He did not\r
+even glance at the murdered man.  He felt that the secret of the whole\r
+thing was not to realize the situation.  The friend who had painted the\r
+fatal portrait to which all his misery had been due had gone out of his\r
+life.  That was enough.\r
+\r
+Then he remembered the lamp.  It was a rather curious one of Moorish\r
+workmanship, made of dull silver inlaid with arabesques of burnished\r
+steel, and studded with coarse turquoises.  Perhaps it might be missed\r
+by his servant, and questions would be asked.  He hesitated for a\r
+moment, then he turned back and took it from the table.  He could not\r
+help seeing the dead thing.  How still it was!  How horribly white the\r
+long hands looked!  It was like a dreadful wax image.\r
+\r
+Having locked the door behind him, he crept quietly downstairs.  The\r
+woodwork creaked and seemed to cry out as if in pain.  He stopped\r
+several times and waited.  No:  everything was still.  It was merely\r
+the sound of his own footsteps.\r
+\r
+When he reached the library, he saw the bag and coat in the corner.\r
+They must be hidden away somewhere.  He unlocked a secret press that\r
+was in the wainscoting, a press in which he kept his own curious\r
+disguises, and put them into it.  He could easily burn them afterwards.\r
+Then he pulled out his watch.  It was twenty minutes to two.\r
+\r
+He sat down and began to think.  Every year--every month, almost--men\r
+were strangled in England for what he had done.  There had been a\r
+madness of murder in the air.  Some red star had come too close to the\r
+earth.... And yet, what evidence was there against him?  Basil Hallward\r
+had left the house at eleven.  No one had seen him come in again.  Most\r
+of the servants were at Selby Royal.  His valet had gone to bed....\r
+Paris!  Yes.  It was to Paris that Basil had gone, and by the midnight\r
+train, as he had intended.  With his curious reserved habits, it would\r
+be months before any suspicions would be roused.  Months!  Everything\r
+could be destroyed long before then.\r
+\r
+A sudden thought struck him.  He put on his fur coat and hat and went\r
+out into the hall.  There he paused, hearing the slow heavy tread of\r
+the policeman on the pavement outside and seeing the flash of the\r
+bull's-eye reflected in the window.  He waited and held his breath.\r
+\r
+After a few moments he drew back the latch and slipped out, shutting\r
+the door very gently behind him.  Then he began ringing the bell.  In\r
+about five minutes his valet appeared, half-dressed and looking very\r
+drowsy.\r
+\r
+"I am sorry to have had to wake you up, Francis," he said, stepping in;\r
+"but I had forgotten my latch-key. What time is it?"\r
+\r
+"Ten minutes past two, sir," answered the man, looking at the clock and\r
+blinking.\r
+\r
+"Ten minutes past two?  How horribly late!  You must wake me at nine\r
+to-morrow. I have some work to do."\r
+\r
+"All right, sir."\r
+\r
+"Did any one call this evening?"\r
+\r
+"Mr. Hallward, sir.  He stayed here till eleven, and then he went away\r
+to catch his train."\r
+\r
+"Oh!  I am sorry I didn't see him.  Did he leave any message?"\r
+\r
+"No, sir, except that he would write to you from Paris, if he did not\r
+find you at the club."\r
+\r
+"That will do, Francis.  Don't forget to call me at nine to-morrow."\r
+\r
+"No, sir."\r
+\r
+The man shambled down the passage in his slippers.\r
+\r
+Dorian Gray threw his hat and coat upon the table and passed into the\r
+library.  For a quarter of an hour he walked up and down the room,\r
+biting his lip and thinking.  Then he took down the Blue Book from one\r
+of the shelves and began to turn over the leaves.  "Alan Campbell, 152,\r
+Hertford Street, Mayfair."  Yes; that was the man he wanted.\r
+\r
+\r
+\r
+CHAPTER 14\r
+\r
+At nine o'clock the next morning his servant came in with a cup of\r
+chocolate on a tray and opened the shutters.  Dorian was sleeping quite\r
+peacefully, lying on his right side, with one hand underneath his\r
+cheek.  He looked like a boy who had been tired out with play, or study.\r
+\r
+The man had to touch him twice on the shoulder before he woke, and as\r
+he opened his eyes a faint smile passed across his lips, as though he\r
+had been lost in some delightful dream.  Yet he had not dreamed at all.\r
+His night had been untroubled by any images of pleasure or of pain.\r
+But youth smiles without any reason.  It is one of its chiefest charms.\r
+\r
+He turned round, and leaning upon his elbow, began to sip his\r
+chocolate.  The mellow November sun came streaming into the room.  The\r
+sky was bright, and there was a genial warmth in the air.  It was\r
+almost like a morning in May.\r
+\r
+Gradually the events of the preceding night crept with silent,\r
+blood-stained feet into his brain and reconstructed themselves there\r
+with terrible distinctness.  He winced at the memory of all that he had\r
+suffered, and for a moment the same curious feeling of loathing for\r
+Basil Hallward that had made him kill him as he sat in the chair came\r
+back to him, and he grew cold with passion.  The dead man was still\r
+sitting there, too, and in the sunlight now.  How horrible that was!\r
+Such hideous things were for the darkness, not for the day.\r
+\r
+He felt that if he brooded on what he had gone through he would sicken\r
+or grow mad.  There were sins whose fascination was more in the memory\r
+than in the doing of them, strange triumphs that gratified the pride\r
+more than the passions, and gave to the intellect a quickened sense of\r
+joy, greater than any joy they brought, or could ever bring, to the\r
+senses.  But this was not one of them.  It was a thing to be driven out\r
+of the mind, to be drugged with poppies, to be strangled lest it might\r
+strangle one itself.\r
+\r
+When the half-hour struck, he passed his hand across his forehead, and\r
+then got up hastily and dressed himself with even more than his usual\r
+care, giving a good deal of attention to the choice of his necktie and\r
+scarf-pin and changing his rings more than once.  He spent a long time\r
+also over breakfast, tasting the various dishes, talking to his valet\r
+about some new liveries that he was thinking of getting made for the\r
+servants at Selby, and going through his correspondence.  At some of\r
+the letters, he smiled.  Three of them bored him.  One he read several\r
+times over and then tore up with a slight look of annoyance in his\r
+face.  "That awful thing, a woman's memory!" as Lord Henry had once\r
+said.\r
+\r
+After he had drunk his cup of black coffee, he wiped his lips slowly\r
+with a napkin, motioned to his servant to wait, and going over to the\r
+table, sat down and wrote two letters.  One he put in his pocket, the\r
+other he handed to the valet.\r
+\r
+"Take this round to 152, Hertford Street, Francis, and if Mr. Campbell\r
+is out of town, get his address."\r
+\r
+As soon as he was alone, he lit a cigarette and began sketching upon a\r
+piece of paper, drawing first flowers and bits of architecture, and\r
+then human faces.  Suddenly he remarked that every face that he drew\r
+seemed to have a fantastic likeness to Basil Hallward.  He frowned, and\r
+getting up, went over to the book-case and took out a volume at hazard.\r
+He was determined that he would not think about what had happened until\r
+it became absolutely necessary that he should do so.\r
+\r
+When he had stretched himself on the sofa, he looked at the title-page\r
+of the book.  It was Gautier's Emaux et Camees, Charpentier's\r
+Japanese-paper edition, with the Jacquemart etching.  The binding was\r
+of citron-green leather, with a design of gilt trellis-work and dotted\r
+pomegranates.  It had been given to him by Adrian Singleton.  As he\r
+turned over the pages, his eye fell on the poem about the hand of\r
+Lacenaire, the cold yellow hand "_du supplice encore mal lavee_," with\r
+its downy red hairs and its "_doigts de faune_."  He glanced at his own\r
+white taper fingers, shuddering slightly in spite of himself, and\r
+passed on, till he came to those lovely stanzas upon Venice:\r
+\r
+     Sur une gamme chromatique,\r
+       Le sein de perles ruisselant,\r
+     La Venus de l'Adriatique\r
+       Sort de l'eau son corps rose et blanc.\r
+\r
+     Les domes, sur l'azur des ondes\r
+       Suivant la phrase au pur contour,\r
+     S'enflent comme des gorges rondes\r
+       Que souleve un soupir d'amour.\r
+\r
+     L'esquif aborde et me depose,\r
+       Jetant son amarre au pilier,\r
+     Devant une facade rose,\r
+       Sur le marbre d'un escalier.\r
+\r
+\r
+How exquisite they were!  As one read them, one seemed to be floating\r
+down the green water-ways of the pink and pearl city, seated in a black\r
+gondola with silver prow and trailing curtains.  The mere lines looked\r
+to him like those straight lines of turquoise-blue that follow one as\r
+one pushes out to the Lido.  The sudden flashes of colour reminded him\r
+of the gleam of the opal-and-iris-throated birds that flutter round the\r
+tall honeycombed Campanile, or stalk, with such stately grace, through\r
+the dim, dust-stained arcades.  Leaning back with half-closed eyes, he\r
+kept saying over and over to himself:\r
+\r
+     "Devant une facade rose,\r
+        Sur le marbre d'un escalier."\r
+\r
+The whole of Venice was in those two lines.  He remembered the autumn\r
+that he had passed there, and a wonderful love that had stirred him to\r
+mad delightful follies.  There was romance in every place.  But Venice,\r
+like Oxford, had kept the background for romance, and, to the true\r
+romantic, background was everything, or almost everything.  Basil had\r
+been with him part of the time, and had gone wild over Tintoret.  Poor\r
+Basil!  What a horrible way for a man to die!\r
+\r
+He sighed, and took up the volume again, and tried to forget.  He read\r
+of the swallows that fly in and out of the little _cafe_ at Smyrna where\r
+the Hadjis sit counting their amber beads and the turbaned merchants\r
+smoke their long tasselled pipes and talk gravely to each other; he\r
+read of the Obelisk in the Place de la Concorde that weeps tears of\r
+granite in its lonely sunless exile and longs to be back by the hot,\r
+lotus-covered Nile, where there are Sphinxes, and rose-red ibises, and\r
+white vultures with gilded claws, and crocodiles with small beryl eyes\r
+that crawl over the green steaming mud; he began to brood over those\r
+verses which, drawing music from kiss-stained marble, tell of that\r
+curious statue that Gautier compares to a contralto voice, the "_monstre\r
+charmant_" that couches in the porphyry-room of the Louvre.  But after a\r
+time the book fell from his hand.  He grew nervous, and a horrible fit\r
+of terror came over him.  What if Alan Campbell should be out of\r
+England?  Days would elapse before he could come back.  Perhaps he\r
+might refuse to come.  What could he do then?  Every moment was of\r
+vital importance.\r
+\r
+They had been great friends once, five years before--almost\r
+inseparable, indeed.  Then the intimacy had come suddenly to an end.\r
+When they met in society now, it was only Dorian Gray who smiled:  Alan\r
+Campbell never did.\r
+\r
+He was an extremely clever young man, though he had no real\r
+appreciation of the visible arts, and whatever little sense of the\r
+beauty of poetry he possessed he had gained entirely from Dorian.  His\r
+dominant intellectual passion was for science.  At Cambridge he had\r
+spent a great deal of his time working in the laboratory, and had taken\r
+a good class in the Natural Science Tripos of his year.  Indeed, he was\r
+still devoted to the study of chemistry, and had a laboratory of his\r
+own in which he used to shut himself up all day long, greatly to the\r
+annoyance of his mother, who had set her heart on his standing for\r
+Parliament and had a vague idea that a chemist was a person who made up\r
+prescriptions.  He was an excellent musician, however, as well, and\r
+played both the violin and the piano better than most amateurs.  In\r
+fact, it was music that had first brought him and Dorian Gray\r
+together--music and that indefinable attraction that Dorian seemed to\r
+be able to exercise whenever he wished--and, indeed, exercised often\r
+without being conscious of it.  They had met at Lady Berkshire's the\r
+night that Rubinstein played there, and after that used to be always\r
+seen together at the opera and wherever good music was going on.  For\r
+eighteen months their intimacy lasted.  Campbell was always either at\r
+Selby Royal or in Grosvenor Square.  To him, as to many others, Dorian\r
+Gray was the type of everything that is wonderful and fascinating in\r
+life.  Whether or not a quarrel had taken place between them no one\r
+ever knew.  But suddenly people remarked that they scarcely spoke when\r
+they met and that Campbell seemed always to go away early from any\r
+party at which Dorian Gray was present.  He had changed, too--was\r
+strangely melancholy at times, appeared almost to dislike hearing\r
+music, and would never himself play, giving as his excuse, when he was\r
+called upon, that he was so absorbed in science that he had no time\r
+left in which to practise.  And this was certainly true.  Every day he\r
+seemed to become more interested in biology, and his name appeared once\r
+or twice in some of the scientific reviews in connection with certain\r
+curious experiments.\r
+\r
+This was the man Dorian Gray was waiting for.  Every second he kept\r
+glancing at the clock.  As the minutes went by he became horribly\r
+agitated.  At last he got up and began to pace up and down the room,\r
+looking like a beautiful caged thing.  He took long stealthy strides.\r
+His hands were curiously cold.\r
+\r
+The suspense became unbearable.  Time seemed to him to be crawling with\r
+feet of lead, while he by monstrous winds was being swept towards the\r
+jagged edge of some black cleft of precipice.  He knew what was waiting\r
+for him there; saw it, indeed, and, shuddering, crushed with dank hands\r
+his burning lids as though he would have robbed the very brain of sight\r
+and driven the eyeballs back into their cave.  It was useless.  The\r
+brain had its own food on which it battened, and the imagination, made\r
+grotesque by terror, twisted and distorted as a living thing by pain,\r
+danced like some foul puppet on a stand and grinned through moving\r
+masks.  Then, suddenly, time stopped for him.  Yes:  that blind,\r
+slow-breathing thing crawled no more, and horrible thoughts, time being\r
+dead, raced nimbly on in front, and dragged a hideous future from its\r
+grave, and showed it to him.  He stared at it.  Its very horror made\r
+him stone.\r
+\r
+At last the door opened and his servant entered.  He turned glazed eyes\r
+upon him.\r
+\r
+"Mr. Campbell, sir," said the man.\r
+\r
+A sigh of relief broke from his parched lips, and the colour came back\r
+to his cheeks.\r
+\r
+"Ask him to come in at once, Francis."  He felt that he was himself\r
+again.  His mood of cowardice had passed away.\r
+\r
+The man bowed and retired.  In a few moments, Alan Campbell walked in,\r
+looking very stern and rather pale, his pallor being intensified by his\r
+coal-black hair and dark eyebrows.\r
+\r
+"Alan!  This is kind of you.  I thank you for coming."\r
+\r
+"I had intended never to enter your house again, Gray.  But you said it\r
+was a matter of life and death."  His voice was hard and cold.  He\r
+spoke with slow deliberation.  There was a look of contempt in the\r
+steady searching gaze that he turned on Dorian.  He kept his hands in\r
+the pockets of his Astrakhan coat, and seemed not to have noticed the\r
+gesture with which he had been greeted.\r
+\r
+"Yes:  it is a matter of life and death, Alan, and to more than one\r
+person.  Sit down."\r
+\r
+Campbell took a chair by the table, and Dorian sat opposite to him.\r
+The two men's eyes met.  In Dorian's there was infinite pity.  He knew\r
+that what he was going to do was dreadful.\r
+\r
+After a strained moment of silence, he leaned across and said, very\r
+quietly, but watching the effect of each word upon the face of him he\r
+had sent for, "Alan, in a locked room at the top of this house, a room\r
+to which nobody but myself has access, a dead man is seated at a table.\r
+He has been dead ten hours now.  Don't stir, and don't look at me like\r
+that.  Who the man is, why he died, how he died, are matters that do\r
+not concern you.  What you have to do is this--"\r
+\r
+"Stop, Gray.  I don't want to know anything further.  Whether what you\r
+have told me is true or not true doesn't concern me.  I entirely\r
+decline to be mixed up in your life.  Keep your horrible secrets to\r
+yourself.  They don't interest me any more."\r
+\r
+"Alan, they will have to interest you.  This one will have to interest\r
+you.  I am awfully sorry for you, Alan.  But I can't help myself.  You\r
+are the one man who is able to save me.  I am forced to bring you into\r
+the matter.  I have no option.  Alan, you are scientific.  You know\r
+about chemistry and things of that kind.  You have made experiments.\r
+What you have got to do is to destroy the thing that is upstairs--to\r
+destroy it so that not a vestige of it will be left.  Nobody saw this\r
+person come into the house.  Indeed, at the present moment he is\r
+supposed to be in Paris.  He will not be missed for months.  When he is\r
+missed, there must be no trace of him found here.  You, Alan, you must\r
+change him, and everything that belongs to him, into a handful of ashes\r
+that I may scatter in the air."\r
+\r
+"You are mad, Dorian."\r
+\r
+"Ah!  I was waiting for you to call me Dorian."\r
+\r
+"You are mad, I tell you--mad to imagine that I would raise a finger to\r
+help you, mad to make this monstrous confession.  I will have nothing\r
+to do with this matter, whatever it is.  Do you think I am going to\r
+peril my reputation for you?  What is it to me what devil's work you\r
+are up to?"\r
+\r
+"It was suicide, Alan."\r
+\r
+"I am glad of that.  But who drove him to it?  You, I should fancy."\r
+\r
+"Do you still refuse to do this for me?"\r
+\r
+"Of course I refuse.  I will have absolutely nothing to do with it.  I\r
+don't care what shame comes on you.  You deserve it all.  I should not\r
+be sorry to see you disgraced, publicly disgraced.  How dare you ask\r
+me, of all men in the world, to mix myself up in this horror?  I should\r
+have thought you knew more about people's characters.  Your friend Lord\r
+Henry Wotton can't have taught you much about psychology, whatever else\r
+he has taught you.  Nothing will induce me to stir a step to help you.\r
+You have come to the wrong man.  Go to some of your friends.  Don't\r
+come to me."\r
+\r
+"Alan, it was murder.  I killed him.  You don't know what he had made\r
+me suffer.  Whatever my life is, he had more to do with the making or\r
+the marring of it than poor Harry has had.  He may not have intended\r
+it, the result was the same."\r
+\r
+"Murder!  Good God, Dorian, is that what you have come to?  I shall not\r
+inform upon you.  It is not my business.  Besides, without my stirring\r
+in the matter, you are certain to be arrested.  Nobody ever commits a\r
+crime without doing something stupid.  But I will have nothing to do\r
+with it."\r
+\r
+"You must have something to do with it.  Wait, wait a moment; listen to\r
+me.  Only listen, Alan.  All I ask of you is to perform a certain\r
+scientific experiment.  You go to hospitals and dead-houses, and the\r
+horrors that you do there don't affect you.  If in some hideous\r
+dissecting-room or fetid laboratory you found this man lying on a\r
+leaden table with red gutters scooped out in it for the blood to flow\r
+through, you would simply look upon him as an admirable subject.  You\r
+would not turn a hair.  You would not believe that you were doing\r
+anything wrong.  On the contrary, you would probably feel that you were\r
+benefiting the human race, or increasing the sum of knowledge in the\r
+world, or gratifying intellectual curiosity, or something of that kind.\r
+What I want you to do is merely what you have often done before.\r
+Indeed, to destroy a body must be far less horrible than what you are\r
+accustomed to work at.  And, remember, it is the only piece of evidence\r
+against me.  If it is discovered, I am lost; and it is sure to be\r
+discovered unless you help me."\r
+\r
+"I have no desire to help you.  You forget that.  I am simply\r
+indifferent to the whole thing.  It has nothing to do with me."\r
+\r
+"Alan, I entreat you.  Think of the position I am in.  Just before you\r
+came I almost fainted with terror.  You may know terror yourself some\r
+day.  No! don't think of that.  Look at the matter purely from the\r
+scientific point of view.  You don't inquire where the dead things on\r
+which you experiment come from.  Don't inquire now.  I have told you\r
+too much as it is.  But I beg of you to do this.  We were friends once,\r
+Alan."\r
+\r
+"Don't speak about those days, Dorian--they are dead."\r
+\r
+"The dead linger sometimes.  The man upstairs will not go away.  He is\r
+sitting at the table with bowed head and outstretched arms.  Alan!\r
+Alan!  If you don't come to my assistance, I am ruined.  Why, they will\r
+hang me, Alan!  Don't you understand?  They will hang me for what I\r
+have done."\r
+\r
+"There is no good in prolonging this scene.  I absolutely refuse to do\r
+anything in the matter.  It is insane of you to ask me."\r
+\r
+"You refuse?"\r
+\r
+"Yes."\r
+\r
+"I entreat you, Alan."\r
+\r
+"It is useless."\r
+\r
+The same look of pity came into Dorian Gray's eyes.  Then he stretched\r
+out his hand, took a piece of paper, and wrote something on it.  He\r
+read it over twice, folded it carefully, and pushed it across the\r
+table.  Having done this, he got up and went over to the window.\r
+\r
+Campbell looked at him in surprise, and then took up the paper, and\r
+opened it.  As he read it, his face became ghastly pale and he fell\r
+back in his chair.  A horrible sense of sickness came over him.  He\r
+felt as if his heart was beating itself to death in some empty hollow.\r
+\r
+After two or three minutes of terrible silence, Dorian turned round and\r
+came and stood behind him, putting his hand upon his shoulder.\r
+\r
+"I am so sorry for you, Alan," he murmured, "but you leave me no\r
+alternative.  I have a letter written already.  Here it is.  You see\r
+the address.  If you don't help me, I must send it.  If you don't help\r
+me, I will send it.  You know what the result will be.  But you are\r
+going to help me.  It is impossible for you to refuse now.  I tried to\r
+spare you.  You will do me the justice to admit that.  You were stern,\r
+harsh, offensive.  You treated me as no man has ever dared to treat\r
+me--no living man, at any rate.  I bore it all.  Now it is for me to\r
+dictate terms."\r
+\r
+Campbell buried his face in his hands, and a shudder passed through him.\r
+\r
+"Yes, it is my turn to dictate terms, Alan.  You know what they are.\r
+The thing is quite simple.  Come, don't work yourself into this fever.\r
+The thing has to be done.  Face it, and do it."\r
+\r
+A groan broke from Campbell's lips and he shivered all over.  The\r
+ticking of the clock on the mantelpiece seemed to him to be dividing\r
+time into separate atoms of agony, each of which was too terrible to be\r
+borne.  He felt as if an iron ring was being slowly tightened round his\r
+forehead, as if the disgrace with which he was threatened had already\r
+come upon him.  The hand upon his shoulder weighed like a hand of lead.\r
+It was intolerable.  It seemed to crush him.\r
+\r
+"Come, Alan, you must decide at once."\r
+\r
+"I cannot do it," he said, mechanically, as though words could alter\r
+things.\r
+\r
+"You must.  You have no choice.  Don't delay."\r
+\r
+He hesitated a moment.  "Is there a fire in the room upstairs?"\r
+\r
+"Yes, there is a gas-fire with asbestos."\r
+\r
+"I shall have to go home and get some things from the laboratory."\r
+\r
+"No, Alan, you must not leave the house.  Write out on a sheet of\r
+notepaper what you want and my servant will take a cab and bring the\r
+things back to you."\r
+\r
+Campbell scrawled a few lines, blotted them, and addressed an envelope\r
+to his assistant.  Dorian took the note up and read it carefully.  Then\r
+he rang the bell and gave it to his valet, with orders to return as\r
+soon as possible and to bring the things with him.\r
+\r
+As the hall door shut, Campbell started nervously, and having got up\r
+from the chair, went over to the chimney-piece. He was shivering with a\r
+kind of ague.  For nearly twenty minutes, neither of the men spoke.  A\r
+fly buzzed noisily about the room, and the ticking of the clock was\r
+like the beat of a hammer.\r
+\r
+As the chime struck one, Campbell turned round, and looking at Dorian\r
+Gray, saw that his eyes were filled with tears.  There was something in\r
+the purity and refinement of that sad face that seemed to enrage him.\r
+"You are infamous, absolutely infamous!" he muttered.\r
+\r
+"Hush, Alan.  You have saved my life," said Dorian.\r
+\r
+"Your life?  Good heavens! what a life that is!  You have gone from\r
+corruption to corruption, and now you have culminated in crime.  In\r
+doing what I am going to do--what you force me to do--it is not of your\r
+life that I am thinking."\r
+\r
+"Ah, Alan," murmured Dorian with a sigh, "I wish you had a thousandth\r
+part of the pity for me that I have for you." He turned away as he\r
+spoke and stood looking out at the garden.  Campbell made no answer.\r
+\r
+After about ten minutes a knock came to the door, and the servant\r
+entered, carrying a large mahogany chest of chemicals, with a long coil\r
+of steel and platinum wire and two rather curiously shaped iron clamps.\r
+\r
+"Shall I leave the things here, sir?" he asked Campbell.\r
+\r
+"Yes," said Dorian.  "And I am afraid, Francis, that I have another\r
+errand for you.  What is the name of the man at Richmond who supplies\r
+Selby with orchids?"\r
+\r
+"Harden, sir."\r
+\r
+"Yes--Harden.  You must go down to Richmond at once, see Harden\r
+personally, and tell him to send twice as many orchids as I ordered,\r
+and to have as few white ones as possible.  In fact, I don't want any\r
+white ones.  It is a lovely day, Francis, and Richmond is a very pretty\r
+place--otherwise I wouldn't bother you about it."\r
+\r
+"No trouble, sir.  At what time shall I be back?"\r
+\r
+Dorian looked at Campbell.  "How long will your experiment take, Alan?"\r
+he said in a calm indifferent voice.  The presence of a third person in\r
+the room seemed to give him extraordinary courage.\r
+\r
+Campbell frowned and bit his lip.  "It will take about five hours," he\r
+answered.\r
+\r
+"It will be time enough, then, if you are back at half-past seven,\r
+Francis.  Or stay:  just leave my things out for dressing.  You can\r
+have the evening to yourself.  I am not dining at home, so I shall not\r
+want you."\r
+\r
+"Thank you, sir," said the man, leaving the room.\r
+\r
+"Now, Alan, there is not a moment to be lost.  How heavy this chest is!\r
+I'll take it for you.  You bring the other things."  He spoke rapidly\r
+and in an authoritative manner.  Campbell felt dominated by him.  They\r
+left the room together.\r
+\r
+When they reached the top landing, Dorian took out the key and turned\r
+it in the lock.  Then he stopped, and a troubled look came into his\r
+eyes.  He shuddered.  "I don't think I can go in, Alan," he murmured.\r
+\r
+"It is nothing to me.  I don't require you," said Campbell coldly.\r
+\r
+Dorian half opened the door.  As he did so, he saw the face of his\r
+portrait leering in the sunlight.  On the floor in front of it the torn\r
+curtain was lying.  He remembered that the night before he had\r
+forgotten, for the first time in his life, to hide the fatal canvas,\r
+and was about to rush forward, when he drew back with a shudder.\r
+\r
+What was that loathsome red dew that gleamed, wet and glistening, on\r
+one of the hands, as though the canvas had sweated blood?  How horrible\r
+it was!--more horrible, it seemed to him for the moment, than the\r
+silent thing that he knew was stretched across the table, the thing\r
+whose grotesque misshapen shadow on the spotted carpet showed him that\r
+it had not stirred, but was still there, as he had left it.\r
+\r
+He heaved a deep breath, opened the door a little wider, and with\r
+half-closed eyes and averted head, walked quickly in, determined that\r
+he would not look even once upon the dead man.  Then, stooping down and\r
+taking up the gold-and-purple hanging, he flung it right over the\r
+picture.\r
+\r
+There he stopped, feeling afraid to turn round, and his eyes fixed\r
+themselves on the intricacies of the pattern before him.  He heard\r
+Campbell bringing in the heavy chest, and the irons, and the other\r
+things that he had required for his dreadful work.  He began to wonder\r
+if he and Basil Hallward had ever met, and, if so, what they had\r
+thought of each other.\r
+\r
+"Leave me now," said a stern voice behind him.\r
+\r
+He turned and hurried out, just conscious that the dead man had been\r
+thrust back into the chair and that Campbell was gazing into a\r
+glistening yellow face.  As he was going downstairs, he heard the key\r
+being turned in the lock.\r
+\r
+It was long after seven when Campbell came back into the library.  He\r
+was pale, but absolutely calm.  "I have done what you asked me to do,"\r
+he muttered. "And now, good-bye. Let us never see each other again."\r
+\r
+"You have saved me from ruin, Alan.  I cannot forget that," said Dorian\r
+simply.\r
+\r
+As soon as Campbell had left, he went upstairs.  There was a horrible\r
+smell of nitric acid in the room.  But the thing that had been sitting\r
+at the table was gone.\r
+\r
+\r
+\r
+CHAPTER 15\r
+\r
+That evening, at eight-thirty, exquisitely dressed and wearing a large\r
+button-hole of Parma violets, Dorian Gray was ushered into Lady\r
+Narborough's drawing-room by bowing servants.  His forehead was\r
+throbbing with maddened nerves, and he felt wildly excited, but his\r
+manner as he bent over his hostess's hand was as easy and graceful as\r
+ever.  Perhaps one never seems so much at one's ease as when one has to\r
+play a part.  Certainly no one looking at Dorian Gray that night could\r
+have believed that he had passed through a tragedy as horrible as any\r
+tragedy of our age.  Those finely shaped fingers could never have\r
+clutched a knife for sin, nor those smiling lips have cried out on God\r
+and goodness.  He himself could not help wondering at the calm of his\r
+demeanour, and for a moment felt keenly the terrible pleasure of a\r
+double life.\r
+\r
+It was a small party, got up rather in a hurry by Lady Narborough, who\r
+was a very clever woman with what Lord Henry used to describe as the\r
+remains of really remarkable ugliness.  She had proved an excellent\r
+wife to one of our most tedious ambassadors, and having buried her\r
+husband properly in a marble mausoleum, which she had herself designed,\r
+and married off her daughters to some rich, rather elderly men, she\r
+devoted herself now to the pleasures of French fiction, French cookery,\r
+and French _esprit_ when she could get it.\r
+\r
+Dorian was one of her especial favourites, and she always told him that\r
+she was extremely glad she had not met him in early life.  "I know, my\r
+dear, I should have fallen madly in love with you," she used to say,\r
+"and thrown my bonnet right over the mills for your sake.  It is most\r
+fortunate that you were not thought of at the time.  As it was, our\r
+bonnets were so unbecoming, and the mills were so occupied in trying to\r
+raise the wind, that I never had even a flirtation with anybody.\r
+However, that was all Narborough's fault.  He was dreadfully\r
+short-sighted, and there is no pleasure in taking in a husband who\r
+never sees anything."\r
+\r
+Her guests this evening were rather tedious.  The fact was, as she\r
+explained to Dorian, behind a very shabby fan, one of her married\r
+daughters had come up quite suddenly to stay with her, and, to make\r
+matters worse, had actually brought her husband with her.  "I think it\r
+is most unkind of her, my dear," she whispered.  "Of course I go and\r
+stay with them every summer after I come from Homburg, but then an old\r
+woman like me must have fresh air sometimes, and besides, I really wake\r
+them up.  You don't know what an existence they lead down there.  It is\r
+pure unadulterated country life.  They get up early, because they have\r
+so much to do, and go to bed early, because they have so little to\r
+think about.  There has not been a scandal in the neighbourhood since\r
+the time of Queen Elizabeth, and consequently they all fall asleep\r
+after dinner.  You shan't sit next either of them.  You shall sit by me\r
+and amuse me."\r
+\r
+Dorian murmured a graceful compliment and looked round the room.  Yes:\r
+it was certainly a tedious party.  Two of the people he had never seen\r
+before, and the others consisted of Ernest Harrowden, one of those\r
+middle-aged mediocrities so common in London clubs who have no enemies,\r
+but are thoroughly disliked by their friends; Lady Ruxton, an\r
+overdressed woman of forty-seven, with a hooked nose, who was always\r
+trying to get herself compromised, but was so peculiarly plain that to\r
+her great disappointment no one would ever believe anything against\r
+her; Mrs. Erlynne, a pushing nobody, with a delightful lisp and\r
+Venetian-red hair; Lady Alice Chapman, his hostess's daughter, a dowdy\r
+dull girl, with one of those characteristic British faces that, once\r
+seen, are never remembered; and her husband, a red-cheeked,\r
+white-whiskered creature who, like so many of his class, was under the\r
+impression that inordinate joviality can atone for an entire lack of\r
+ideas.\r
+\r
+He was rather sorry he had come, till Lady Narborough, looking at the\r
+great ormolu gilt clock that sprawled in gaudy curves on the\r
+mauve-draped mantelshelf, exclaimed:  "How horrid of Henry Wotton to be\r
+so late!  I sent round to him this morning on chance and he promised\r
+faithfully not to disappoint me."\r
+\r
+It was some consolation that Harry was to be there, and when the door\r
+opened and he heard his slow musical voice lending charm to some\r
+insincere apology, he ceased to feel bored.\r
+\r
+But at dinner he could not eat anything.  Plate after plate went away\r
+untasted.  Lady Narborough kept scolding him for what she called "an\r
+insult to poor Adolphe, who invented the _menu_ specially for you," and\r
+now and then Lord Henry looked across at him, wondering at his silence\r
+and abstracted manner.  From time to time the butler filled his glass\r
+with champagne.  He drank eagerly, and his thirst seemed to increase.\r
+\r
+"Dorian," said Lord Henry at last, as the _chaud-froid_ was being handed\r
+round, "what is the matter with you to-night? You are quite out of\r
+sorts."\r
+\r
+"I believe he is in love," cried Lady Narborough, "and that he is\r
+afraid to tell me for fear I should be jealous.  He is quite right.  I\r
+certainly should."\r
+\r
+"Dear Lady Narborough," murmured Dorian, smiling, "I have not been in\r
+love for a whole week--not, in fact, since Madame de Ferrol left town."\r
+\r
+"How you men can fall in love with that woman!" exclaimed the old lady.\r
+"I really cannot understand it."\r
+\r
+"It is simply because she remembers you when you were a little girl,\r
+Lady Narborough," said Lord Henry.  "She is the one link between us and\r
+your short frocks."\r
+\r
+"She does not remember my short frocks at all, Lord Henry.  But I\r
+remember her very well at Vienna thirty years ago, and how _decolletee_\r
+she was then."\r
+\r
+"She is still _decolletee_," he answered, taking an olive in his long\r
+fingers; "and when she is in a very smart gown she looks like an\r
+_edition de luxe_ of a bad French novel.  She is really wonderful, and\r
+full of surprises.  Her capacity for family affection is extraordinary.\r
+When her third husband died, her hair turned quite gold from grief."\r
+\r
+"How can you, Harry!" cried Dorian.\r
+\r
+"It is a most romantic explanation," laughed the hostess.  "But her\r
+third husband, Lord Henry!  You don't mean to say Ferrol is the fourth?"\r
+\r
+"Certainly, Lady Narborough."\r
+\r
+"I don't believe a word of it."\r
+\r
+"Well, ask Mr. Gray.  He is one of her most intimate friends."\r
+\r
+"Is it true, Mr. Gray?"\r
+\r
+"She assures me so, Lady Narborough," said Dorian.  "I asked her\r
+whether, like Marguerite de Navarre, she had their hearts embalmed and\r
+hung at her girdle.  She told me she didn't, because none of them had\r
+had any hearts at all."\r
+\r
+"Four husbands!  Upon my word that is _trop de zele_."\r
+\r
+"_Trop d'audace_, I tell her," said Dorian.\r
+\r
+"Oh! she is audacious enough for anything, my dear.  And what is Ferrol\r
+like?  I don't know him."\r
+\r
+"The husbands of very beautiful women belong to the criminal classes,"\r
+said Lord Henry, sipping his wine.\r
+\r
+Lady Narborough hit him with her fan.  "Lord Henry, I am not at all\r
+surprised that the world says that you are extremely wicked."\r
+\r
+"But what world says that?" asked Lord Henry, elevating his eyebrows.\r
+"It can only be the next world.  This world and I are on excellent\r
+terms."\r
+\r
+"Everybody I know says you are very wicked," cried the old lady,\r
+shaking her head.\r
+\r
+Lord Henry looked serious for some moments.  "It is perfectly\r
+monstrous," he said, at last, "the way people go about nowadays saying\r
+things against one behind one's back that are absolutely and entirely\r
+true."\r
+\r
+"Isn't he incorrigible?" cried Dorian, leaning forward in his chair.\r
+\r
+"I hope so," said his hostess, laughing.  "But really, if you all\r
+worship Madame de Ferrol in this ridiculous way, I shall have to marry\r
+again so as to be in the fashion."\r
+\r
+"You will never marry again, Lady Narborough," broke in Lord Henry.\r
+"You were far too happy.  When a woman marries again, it is because she\r
+detested her first husband.  When a man marries again, it is because he\r
+adored his first wife.  Women try their luck; men risk theirs."\r
+\r
+"Narborough wasn't perfect," cried the old lady.\r
+\r
+"If he had been, you would not have loved him, my dear lady," was the\r
+rejoinder.  "Women love us for our defects.  If we have enough of them,\r
+they will forgive us everything, even our intellects.  You will never\r
+ask me to dinner again after saying this, I am afraid, Lady Narborough,\r
+but it is quite true."\r
+\r
+"Of course it is true, Lord Henry.  If we women did not love you for\r
+your defects, where would you all be?  Not one of you would ever be\r
+married.  You would be a set of unfortunate bachelors.  Not, however,\r
+that that would alter you much.  Nowadays all the married men live like\r
+bachelors, and all the bachelors like married men."\r
+\r
+"_Fin de siecle_," murmured Lord Henry.\r
+\r
+"_Fin du globe_," answered his hostess.\r
+\r
+"I wish it were _fin du globe_," said Dorian with a sigh.  "Life is a\r
+great disappointment."\r
+\r
+"Ah, my dear," cried Lady Narborough, putting on her gloves, "don't\r
+tell me that you have exhausted life.  When a man says that one knows\r
+that life has exhausted him.  Lord Henry is very wicked, and I\r
+sometimes wish that I had been; but you are made to be good--you look\r
+so good.  I must find you a nice wife.  Lord Henry, don't you think\r
+that Mr. Gray should get married?"\r
+\r
+"I am always telling him so, Lady Narborough," said Lord Henry with a\r
+bow.\r
+\r
+"Well, we must look out for a suitable match for him.  I shall go\r
+through Debrett carefully to-night and draw out a list of all the\r
+eligible young ladies."\r
+\r
+"With their ages, Lady Narborough?" asked Dorian.\r
+\r
+"Of course, with their ages, slightly edited.  But nothing must be done\r
+in a hurry.  I want it to be what _The Morning Post_ calls a suitable\r
+alliance, and I want you both to be happy."\r
+\r
+"What nonsense people talk about happy marriages!" exclaimed Lord\r
+Henry.  "A man can be happy with any woman, as long as he does not love\r
+her."\r
+\r
+"Ah! what a cynic you are!" cried the old lady, pushing back her chair\r
+and nodding to Lady Ruxton.  "You must come and dine with me soon\r
+again.  You are really an admirable tonic, much better than what Sir\r
+Andrew prescribes for me.  You must tell me what people you would like\r
+to meet, though.  I want it to be a delightful gathering."\r
+\r
+"I like men who have a future and women who have a past," he answered.\r
+"Or do you think that would make it a petticoat party?"\r
+\r
+"I fear so," she said, laughing, as she stood up.  "A thousand pardons,\r
+my dear Lady Ruxton," she added, "I didn't see you hadn't finished your\r
+cigarette."\r
+\r
+"Never mind, Lady Narborough.  I smoke a great deal too much.  I am\r
+going to limit myself, for the future."\r
+\r
+"Pray don't, Lady Ruxton," said Lord Henry.  "Moderation is a fatal\r
+thing.  Enough is as bad as a meal.  More than enough is as good as a\r
+feast."\r
+\r
+Lady Ruxton glanced at him curiously.  "You must come and explain that\r
+to me some afternoon, Lord Henry.  It sounds a fascinating theory," she\r
+murmured, as she swept out of the room.\r
+\r
+"Now, mind you don't stay too long over your politics and scandal,"\r
+cried Lady Narborough from the door.  "If you do, we are sure to\r
+squabble upstairs."\r
+\r
+The men laughed, and Mr. Chapman got up solemnly from the foot of the\r
+table and came up to the top.  Dorian Gray changed his seat and went\r
+and sat by Lord Henry.  Mr. Chapman began to talk in a loud voice about\r
+the situation in the House of Commons.  He guffawed at his adversaries.\r
+The word _doctrinaire_--word full of terror to the British\r
+mind--reappeared from time to time between his explosions.  An\r
+alliterative prefix served as an ornament of oratory.  He hoisted the\r
+Union Jack on the pinnacles of thought.  The inherited stupidity of the\r
+race--sound English common sense he jovially termed it--was shown to be\r
+the proper bulwark for society.\r
+\r
+A smile curved Lord Henry's lips, and he turned round and looked at\r
+Dorian.\r
+\r
+"Are you better, my dear fellow?" he asked.  "You seemed rather out of\r
+sorts at dinner."\r
+\r
+"I am quite well, Harry.  I am tired.  That is all."\r
+\r
+"You were charming last night.  The little duchess is quite devoted to\r
+you.  She tells me she is going down to Selby."\r
+\r
+"She has promised to come on the twentieth."\r
+\r
+"Is Monmouth to be there, too?"\r
+\r
+"Oh, yes, Harry."\r
+\r
+"He bores me dreadfully, almost as much as he bores her.  She is very\r
+clever, too clever for a woman.  She lacks the indefinable charm of\r
+weakness.  It is the feet of clay that make the gold of the image\r
+precious.  Her feet are very pretty, but they are not feet of clay.\r
+White porcelain feet, if you like.  They have been through the fire,\r
+and what fire does not destroy, it hardens.  She has had experiences."\r
+\r
+"How long has she been married?" asked Dorian.\r
+\r
+"An eternity, she tells me.  I believe, according to the peerage, it is\r
+ten years, but ten years with Monmouth must have been like eternity,\r
+with time thrown in.  Who else is coming?"\r
+\r
+"Oh, the Willoughbys, Lord Rugby and his wife, our hostess, Geoffrey\r
+Clouston, the usual set.  I have asked Lord Grotrian."\r
+\r
+"I like him," said Lord Henry.  "A great many people don't, but I find\r
+him charming.  He atones for being occasionally somewhat overdressed by\r
+being always absolutely over-educated. He is a very modern type."\r
+\r
+"I don't know if he will be able to come, Harry.  He may have to go to\r
+Monte Carlo with his father."\r
+\r
+"Ah! what a nuisance people's people are!  Try and make him come.  By\r
+the way, Dorian, you ran off very early last night.  You left before\r
+eleven.  What did you do afterwards?  Did you go straight home?"\r
+\r
+Dorian glanced at him hurriedly and frowned.\r
+\r
+"No, Harry," he said at last, "I did not get home till nearly three."\r
+\r
+"Did you go to the club?"\r
+\r
+"Yes," he answered.  Then he bit his lip.  "No, I don't mean that.  I\r
+didn't go to the club.  I walked about.  I forget what I did.... How\r
+inquisitive you are, Harry!  You always want to know what one has been\r
+doing.  I always want to forget what I have been doing.  I came in at\r
+half-past two, if you wish to know the exact time.  I had left my\r
+latch-key at home, and my servant had to let me in.  If you want any\r
+corroborative evidence on the subject, you can ask him."\r
+\r
+Lord Henry shrugged his shoulders.  "My dear fellow, as if I cared!\r
+Let us go up to the drawing-room. No sherry, thank you, Mr. Chapman.\r
+Something has happened to you, Dorian.  Tell me what it is.  You are\r
+not yourself to-night."\r
+\r
+"Don't mind me, Harry.  I am irritable, and out of temper.  I shall\r
+come round and see you to-morrow, or next day.  Make my excuses to Lady\r
+Narborough.  I shan't go upstairs.  I shall go home.  I must go home."\r
+\r
+"All right, Dorian.  I dare say I shall see you to-morrow at tea-time.\r
+The duchess is coming."\r
+\r
+"I will try to be there, Harry," he said, leaving the room.  As he\r
+drove back to his own house, he was conscious that the sense of terror\r
+he thought he had strangled had come back to him.  Lord Henry's casual\r
+questioning had made him lose his nerve for the moment, and he wanted\r
+his nerve still.  Things that were dangerous had to be destroyed.  He\r
+winced.  He hated the idea of even touching them.\r
+\r
+Yet it had to be done.  He realized that, and when he had locked the\r
+door of his library, he opened the secret press into which he had\r
+thrust Basil Hallward's coat and bag.  A huge fire was blazing.  He\r
+piled another log on it.  The smell of the singeing clothes and burning\r
+leather was horrible.  It took him three-quarters of an hour to consume\r
+everything.  At the end he felt faint and sick, and having lit some\r
+Algerian pastilles in a pierced copper brazier, he bathed his hands and\r
+forehead with a cool musk-scented vinegar.\r
+\r
+Suddenly he started.  His eyes grew strangely bright, and he gnawed\r
+nervously at his underlip.  Between two of the windows stood a large\r
+Florentine cabinet, made out of ebony and inlaid with ivory and blue\r
+lapis.  He watched it as though it were a thing that could fascinate\r
+and make afraid, as though it held something that he longed for and yet\r
+almost loathed.  His breath quickened.  A mad craving came over him.\r
+He lit a cigarette and then threw it away.  His eyelids drooped till\r
+the long fringed lashes almost touched his cheek.  But he still watched\r
+the cabinet.  At last he got up from the sofa on which he had been\r
+lying, went over to it, and having unlocked it, touched some hidden\r
+spring.  A triangular drawer passed slowly out.  His fingers moved\r
+instinctively towards it, dipped in, and closed on something.  It was a\r
+small Chinese box of black and gold-dust lacquer, elaborately wrought,\r
+the sides patterned with curved waves, and the silken cords hung with\r
+round crystals and tasselled in plaited metal threads.  He opened it.\r
+Inside was a green paste, waxy in lustre, the odour curiously heavy and\r
+persistent.\r
+\r
+He hesitated for some moments, with a strangely immobile smile upon his\r
+face.  Then shivering, though the atmosphere of the room was terribly\r
+hot, he drew himself up and glanced at the clock.  It was twenty\r
+minutes to twelve.  He put the box back, shutting the cabinet doors as\r
+he did so, and went into his bedroom.\r
+\r
+As midnight was striking bronze blows upon the dusky air, Dorian Gray,\r
+dressed commonly, and with a muffler wrapped round his throat, crept\r
+quietly out of his house.  In Bond Street he found a hansom with a good\r
+horse.  He hailed it and in a low voice gave the driver an address.\r
+\r
+The man shook his head.  "It is too far for me," he muttered.\r
+\r
+"Here is a sovereign for you," said Dorian.  "You shall have another if\r
+you drive fast."\r
+\r
+"All right, sir," answered the man, "you will be there in an hour," and\r
+after his fare had got in he turned his horse round and drove rapidly\r
+towards the river.\r
+\r
+\r
+\r
+CHAPTER 16\r
+\r
+A cold rain began to fall, and the blurred street-lamps looked ghastly\r
+in the dripping mist.  The public-houses were just closing, and dim men\r
+and women were clustering in broken groups round their doors.  From\r
+some of the bars came the sound of horrible laughter.  In others,\r
+drunkards brawled and screamed.\r
+\r
+Lying back in the hansom, with his hat pulled over his forehead, Dorian\r
+Gray watched with listless eyes the sordid shame of the great city, and\r
+now and then he repeated to himself the words that Lord Henry had said\r
+to him on the first day they had met, "To cure the soul by means of the\r
+senses, and the senses by means of the soul."  Yes, that was the\r
+secret.  He had often tried it, and would try it again now.  There were\r
+opium dens where one could buy oblivion, dens of horror where the\r
+memory of old sins could be destroyed by the madness of sins that were\r
+new.\r
+\r
+The moon hung low in the sky like a yellow skull.  From time to time a\r
+huge misshapen cloud stretched a long arm across and hid it.  The\r
+gas-lamps grew fewer, and the streets more narrow and gloomy.  Once the\r
+man lost his way and had to drive back half a mile.  A steam rose from\r
+the horse as it splashed up the puddles.  The sidewindows of the hansom\r
+were clogged with a grey-flannel mist.\r
+\r
+"To cure the soul by means of the senses, and the senses by means of\r
+the soul!"  How the words rang in his ears!  His soul, certainly, was\r
+sick to death.  Was it true that the senses could cure it?  Innocent\r
+blood had been spilled.  What could atone for that?  Ah! for that there\r
+was no atonement; but though forgiveness was impossible, forgetfulness\r
+was possible still, and he was determined to forget, to stamp the thing\r
+out, to crush it as one would crush the adder that had stung one.\r
+Indeed, what right had Basil to have spoken to him as he had done?  Who\r
+had made him a judge over others?  He had said things that were\r
+dreadful, horrible, not to be endured.\r
+\r
+On and on plodded the hansom, going slower, it seemed to him, at each\r
+step.  He thrust up the trap and called to the man to drive faster.\r
+The hideous hunger for opium began to gnaw at him.  His throat burned\r
+and his delicate hands twitched nervously together.  He struck at the\r
+horse madly with his stick.  The driver laughed and whipped up.  He\r
+laughed in answer, and the man was silent.\r
+\r
+The way seemed interminable, and the streets like the black web of some\r
+sprawling spider.  The monotony became unbearable, and as the mist\r
+thickened, he felt afraid.\r
+\r
+Then they passed by lonely brickfields.  The fog was lighter here, and\r
+he could see the strange, bottle-shaped kilns with their orange,\r
+fanlike tongues of fire.  A dog barked as they went by, and far away in\r
+the darkness some wandering sea-gull screamed.  The horse stumbled in a\r
+rut, then swerved aside and broke into a gallop.\r
+\r
+After some time they left the clay road and rattled again over\r
+rough-paven streets.  Most of the windows were dark, but now and then\r
+fantastic shadows were silhouetted against some lamplit blind.  He\r
+watched them curiously.  They moved like monstrous marionettes and made\r
+gestures like live things.  He hated them.  A dull rage was in his\r
+heart.  As they turned a corner, a woman yelled something at them from\r
+an open door, and two men ran after the hansom for about a hundred\r
+yards.  The driver beat at them with his whip.\r
+\r
+It is said that passion makes one think in a circle.  Certainly with\r
+hideous iteration the bitten lips of Dorian Gray shaped and reshaped\r
+those subtle words that dealt with soul and sense, till he had found in\r
+them the full expression, as it were, of his mood, and justified, by\r
+intellectual approval, passions that without such justification would\r
+still have dominated his temper.  From cell to cell of his brain crept\r
+the one thought; and the wild desire to live, most terrible of all\r
+man's appetites, quickened into force each trembling nerve and fibre.\r
+Ugliness that had once been hateful to him because it made things real,\r
+became dear to him now for that very reason.  Ugliness was the one\r
+reality.  The coarse brawl, the loathsome den, the crude violence of\r
+disordered life, the very vileness of thief and outcast, were more\r
+vivid, in their intense actuality of impression, than all the gracious\r
+shapes of art, the dreamy shadows of song.  They were what he needed\r
+for forgetfulness.  In three days he would be free.\r
+\r
+Suddenly the man drew up with a jerk at the top of a dark lane.  Over\r
+the low roofs and jagged chimney-stacks of the houses rose the black\r
+masts of ships.  Wreaths of white mist clung like ghostly sails to the\r
+yards.\r
+\r
+"Somewhere about here, sir, ain't it?" he asked huskily through the\r
+trap.\r
+\r
+Dorian started and peered round.  "This will do," he answered, and\r
+having got out hastily and given the driver the extra fare he had\r
+promised him, he walked quickly in the direction of the quay.  Here and\r
+there a lantern gleamed at the stern of some huge merchantman.  The\r
+light shook and splintered in the puddles.  A red glare came from an\r
+outward-bound steamer that was coaling.  The slimy pavement looked like\r
+a wet mackintosh.\r
+\r
+He hurried on towards the left, glancing back now and then to see if he\r
+was being followed.  In about seven or eight minutes he reached a small\r
+shabby house that was wedged in between two gaunt factories.  In one of\r
+the top-windows stood a lamp.  He stopped and gave a peculiar knock.\r
+\r
+After a little time he heard steps in the passage and the chain being\r
+unhooked.  The door opened quietly, and he went in without saying a\r
+word to the squat misshapen figure that flattened itself into the\r
+shadow as he passed.  At the end of the hall hung a tattered green\r
+curtain that swayed and shook in the gusty wind which had followed him\r
+in from the street.  He dragged it aside and entered a long low room\r
+which looked as if it had once been a third-rate dancing-saloon. Shrill\r
+flaring gas-jets, dulled and distorted in the fly-blown mirrors that\r
+faced them, were ranged round the walls.  Greasy reflectors of ribbed\r
+tin backed them, making quivering disks of light.  The floor was\r
+covered with ochre-coloured sawdust, trampled here and there into mud,\r
+and stained with dark rings of spilled liquor.  Some Malays were\r
+crouching by a little charcoal stove, playing with bone counters and\r
+showing their white teeth as they chattered.  In one corner, with his\r
+head buried in his arms, a sailor sprawled over a table, and by the\r
+tawdrily painted bar that ran across one complete side stood two\r
+haggard women, mocking an old man who was brushing the sleeves of his\r
+coat with an expression of disgust.  "He thinks he's got red ants on\r
+him," laughed one of them, as Dorian passed by.  The man looked at her\r
+in terror and began to whimper.\r
+\r
+At the end of the room there was a little staircase, leading to a\r
+darkened chamber.  As Dorian hurried up its three rickety steps, the\r
+heavy odour of opium met him.  He heaved a deep breath, and his\r
+nostrils quivered with pleasure.  When he entered, a young man with\r
+smooth yellow hair, who was bending over a lamp lighting a long thin\r
+pipe, looked up at him and nodded in a hesitating manner.\r
+\r
+"You here, Adrian?" muttered Dorian.\r
+\r
+"Where else should I be?" he answered, listlessly.  "None of the chaps\r
+will speak to me now."\r
+\r
+"I thought you had left England."\r
+\r
+"Darlington is not going to do anything.  My brother paid the bill at\r
+last.  George doesn't speak to me either.... I don't care," he added\r
+with a sigh.  "As long as one has this stuff, one doesn't want friends.\r
+I think I have had too many friends."\r
+\r
+Dorian winced and looked round at the grotesque things that lay in such\r
+fantastic postures on the ragged mattresses.  The twisted limbs, the\r
+gaping mouths, the staring lustreless eyes, fascinated him.  He knew in\r
+what strange heavens they were suffering, and what dull hells were\r
+teaching them the secret of some new joy.  They were better off than he\r
+was.  He was prisoned in thought.  Memory, like a horrible malady, was\r
+eating his soul away.  From time to time he seemed to see the eyes of\r
+Basil Hallward looking at him.  Yet he felt he could not stay.  The\r
+presence of Adrian Singleton troubled him.  He wanted to be where no\r
+one would know who he was.  He wanted to escape from himself.\r
+\r
+"I am going on to the other place," he said after a pause.\r
+\r
+"On the wharf?"\r
+\r
+"Yes."\r
+\r
+"That mad-cat is sure to be there.  They won't have her in this place\r
+now."\r
+\r
+Dorian shrugged his shoulders.  "I am sick of women who love one.\r
+Women who hate one are much more interesting.  Besides, the stuff is\r
+better."\r
+\r
+"Much the same."\r
+\r
+"I like it better.  Come and have something to drink.  I must have\r
+something."\r
+\r
+"I don't want anything," murmured the young man.\r
+\r
+"Never mind."\r
+\r
+Adrian Singleton rose up wearily and followed Dorian to the bar.  A\r
+half-caste, in a ragged turban and a shabby ulster, grinned a hideous\r
+greeting as he thrust a bottle of brandy and two tumblers in front of\r
+them.  The women sidled up and began to chatter.  Dorian turned his\r
+back on them and said something in a low voice to Adrian Singleton.\r
+\r
+A crooked smile, like a Malay crease, writhed across the face of one of\r
+the women.  "We are very proud to-night," she sneered.\r
+\r
+"For God's sake don't talk to me," cried Dorian, stamping his foot on\r
+the ground.  "What do you want?  Money?  Here it is.  Don't ever talk\r
+to me again."\r
+\r
+Two red sparks flashed for a moment in the woman's sodden eyes, then\r
+flickered out and left them dull and glazed.  She tossed her head and\r
+raked the coins off the counter with greedy fingers.  Her companion\r
+watched her enviously.\r
+\r
+"It's no use," sighed Adrian Singleton.  "I don't care to go back.\r
+What does it matter?  I am quite happy here."\r
+\r
+"You will write to me if you want anything, won't you?" said Dorian,\r
+after a pause.\r
+\r
+"Perhaps."\r
+\r
+"Good night, then."\r
+\r
+"Good night," answered the young man, passing up the steps and wiping\r
+his parched mouth with a handkerchief.\r
+\r
+Dorian walked to the door with a look of pain in his face.  As he drew\r
+the curtain aside, a hideous laugh broke from the painted lips of the\r
+woman who had taken his money.  "There goes the devil's bargain!" she\r
+hiccoughed, in a hoarse voice.\r
+\r
+"Curse you!" he answered, "don't call me that."\r
+\r
+She snapped her fingers.  "Prince Charming is what you like to be\r
+called, ain't it?" she yelled after him.\r
+\r
+The drowsy sailor leaped to his feet as she spoke, and looked wildly\r
+round.  The sound of the shutting of the hall door fell on his ear.  He\r
+rushed out as if in pursuit.\r
+\r
+Dorian Gray hurried along the quay through the drizzling rain.  His\r
+meeting with Adrian Singleton had strangely moved him, and he wondered\r
+if the ruin of that young life was really to be laid at his door, as\r
+Basil Hallward had said to him with such infamy of insult.  He bit his\r
+lip, and for a few seconds his eyes grew sad.  Yet, after all, what did\r
+it matter to him?  One's days were too brief to take the burden of\r
+another's errors on one's shoulders.  Each man lived his own life and\r
+paid his own price for living it.  The only pity was one had to pay so\r
+often for a single fault.  One had to pay over and over again, indeed.\r
+In her dealings with man, destiny never closed her accounts.\r
+\r
+There are moments, psychologists tell us, when the passion for sin, or\r
+for what the world calls sin, so dominates a nature that every fibre of\r
+the body, as every cell of the brain, seems to be instinct with fearful\r
+impulses.  Men and women at such moments lose the freedom of their\r
+will.  They move to their terrible end as automatons move.  Choice is\r
+taken from them, and conscience is either killed, or, if it lives at\r
+all, lives but to give rebellion its fascination and disobedience its\r
+charm.  For all sins, as theologians weary not of reminding us, are\r
+sins of disobedience.  When that high spirit, that morning star of\r
+evil, fell from heaven, it was as a rebel that he fell.\r
+\r
+Callous, concentrated on evil, with stained mind, and soul hungry for\r
+rebellion, Dorian Gray hastened on, quickening his step as he went, but\r
+as he darted aside into a dim archway, that had served him often as a\r
+short cut to the ill-famed place where he was going, he felt himself\r
+suddenly seized from behind, and before he had time to defend himself,\r
+he was thrust back against the wall, with a brutal hand round his\r
+throat.\r
+\r
+He struggled madly for life, and by a terrible effort wrenched the\r
+tightening fingers away.  In a second he heard the click of a revolver,\r
+and saw the gleam of a polished barrel, pointing straight at his head,\r
+and the dusky form of a short, thick-set man facing him.\r
+\r
+"What do you want?" he gasped.\r
+\r
+"Keep quiet," said the man.  "If you stir, I shoot you."\r
+\r
+"You are mad.  What have I done to you?"\r
+\r
+"You wrecked the life of Sibyl Vane," was the answer, "and Sibyl Vane\r
+was my sister.  She killed herself.  I know it.  Her death is at your\r
+door.  I swore I would kill you in return.  For years I have sought\r
+you.  I had no clue, no trace.  The two people who could have described\r
+you were dead.  I knew nothing of you but the pet name she used to call\r
+you.  I heard it to-night by chance.  Make your peace with God, for\r
+to-night you are going to die."\r
+\r
+Dorian Gray grew sick with fear.  "I never knew her," he stammered.  "I\r
+never heard of her.  You are mad."\r
+\r
+"You had better confess your sin, for as sure as I am James Vane, you\r
+are going to die."  There was a horrible moment.  Dorian did not know\r
+what to say or do.  "Down on your knees!" growled the man.  "I give you\r
+one minute to make your peace--no more.  I go on board to-night for\r
+India, and I must do my job first.  One minute.  That's all."\r
+\r
+Dorian's arms fell to his side.  Paralysed with terror, he did not know\r
+what to do.  Suddenly a wild hope flashed across his brain.  "Stop," he\r
+cried.  "How long ago is it since your sister died?  Quick, tell me!"\r
+\r
+"Eighteen years," said the man.  "Why do you ask me?  What do years\r
+matter?"\r
+\r
+"Eighteen years," laughed Dorian Gray, with a touch of triumph in his\r
+voice.  "Eighteen years!  Set me under the lamp and look at my face!"\r
+\r
+James Vane hesitated for a moment, not understanding what was meant.\r
+Then he seized Dorian Gray and dragged him from the archway.\r
+\r
+Dim and wavering as was the wind-blown light, yet it served to show him\r
+the hideous error, as it seemed, into which he had fallen, for the face\r
+of the man he had sought to kill had all the bloom of boyhood, all the\r
+unstained purity of youth.  He seemed little more than a lad of twenty\r
+summers, hardly older, if older indeed at all, than his sister had been\r
+when they had parted so many years ago.  It was obvious that this was\r
+not the man who had destroyed her life.\r
+\r
+He loosened his hold and reeled back.  "My God! my God!" he cried, "and\r
+I would have murdered you!"\r
+\r
+Dorian Gray drew a long breath.  "You have been on the brink of\r
+committing a terrible crime, my man," he said, looking at him sternly.\r
+"Let this be a warning to you not to take vengeance into your own\r
+hands."\r
+\r
+"Forgive me, sir," muttered James Vane.  "I was deceived.  A chance\r
+word I heard in that damned den set me on the wrong track."\r
+\r
+"You had better go home and put that pistol away, or you may get into\r
+trouble," said Dorian, turning on his heel and going slowly down the\r
+street.\r
+\r
+James Vane stood on the pavement in horror.  He was trembling from head\r
+to foot.  After a little while, a black shadow that had been creeping\r
+along the dripping wall moved out into the light and came close to him\r
+with stealthy footsteps.  He felt a hand laid on his arm and looked\r
+round with a start.  It was one of the women who had been drinking at\r
+the bar.\r
+\r
+"Why didn't you kill him?" she hissed out, putting haggard face quite\r
+close to his.  "I knew you were following him when you rushed out from\r
+Daly's. You fool!  You should have killed him.  He has lots of money,\r
+and he's as bad as bad."\r
+\r
+"He is not the man I am looking for," he answered, "and I want no man's\r
+money.  I want a man's life.  The man whose life I want must be nearly\r
+forty now.  This one is little more than a boy.  Thank God, I have not\r
+got his blood upon my hands."\r
+\r
+The woman gave a bitter laugh.  "Little more than a boy!" she sneered.\r
+"Why, man, it's nigh on eighteen years since Prince Charming made me\r
+what I am."\r
+\r
+"You lie!" cried James Vane.\r
+\r
+She raised her hand up to heaven.  "Before God I am telling the truth,"\r
+she cried.\r
+\r
+"Before God?"\r
+\r
+"Strike me dumb if it ain't so.  He is the worst one that comes here.\r
+They say he has sold himself to the devil for a pretty face.  It's nigh\r
+on eighteen years since I met him.  He hasn't changed much since then.\r
+I have, though," she added, with a sickly leer.\r
+\r
+"You swear this?"\r
+\r
+"I swear it," came in hoarse echo from her flat mouth.  "But don't give\r
+me away to him," she whined; "I am afraid of him.  Let me have some\r
+money for my night's lodging."\r
+\r
+He broke from her with an oath and rushed to the corner of the street,\r
+but Dorian Gray had disappeared.  When he looked back, the woman had\r
+vanished also.\r
+\r
+\r
+\r
+CHAPTER 17\r
+\r
+A week later Dorian Gray was sitting in the conservatory at Selby\r
+Royal, talking to the pretty Duchess of Monmouth, who with her husband,\r
+a jaded-looking man of sixty, was amongst his guests.  It was tea-time,\r
+and the mellow light of the huge, lace-covered lamp that stood on the\r
+table lit up the delicate china and hammered silver of the service at\r
+which the duchess was presiding.  Her white hands were moving daintily\r
+among the cups, and her full red lips were smiling at something that\r
+Dorian had whispered to her.  Lord Henry was lying back in a\r
+silk-draped wicker chair, looking at them.  On a peach-coloured divan\r
+sat Lady Narborough, pretending to listen to the duke's description of\r
+the last Brazilian beetle that he had added to his collection.  Three\r
+young men in elaborate smoking-suits were handing tea-cakes to some of\r
+the women.  The house-party consisted of twelve people, and there were\r
+more expected to arrive on the next day.\r
+\r
+"What are you two talking about?" said Lord Henry, strolling over to\r
+the table and putting his cup down.  "I hope Dorian has told you about\r
+my plan for rechristening everything, Gladys.  It is a delightful idea."\r
+\r
+"But I don't want to be rechristened, Harry," rejoined the duchess,\r
+looking up at him with her wonderful eyes.  "I am quite satisfied with\r
+my own name, and I am sure Mr. Gray should be satisfied with his."\r
+\r
+"My dear Gladys, I would not alter either name for the world.  They are\r
+both perfect.  I was thinking chiefly of flowers.  Yesterday I cut an\r
+orchid, for my button-hole. It was a marvellous spotted thing, as\r
+effective as the seven deadly sins.  In a thoughtless moment I asked\r
+one of the gardeners what it was called.  He told me it was a fine\r
+specimen of _Robinsoniana_, or something dreadful of that kind.  It is a\r
+sad truth, but we have lost the faculty of giving lovely names to\r
+things.  Names are everything.  I never quarrel with actions.  My one\r
+quarrel is with words.  That is the reason I hate vulgar realism in\r
+literature.  The man who could call a spade a spade should be compelled\r
+to use one.  It is the only thing he is fit for."\r
+\r
+"Then what should we call you, Harry?" she asked.\r
+\r
+"His name is Prince Paradox," said Dorian.\r
+\r
+"I recognize him in a flash," exclaimed the duchess.\r
+\r
+"I won't hear of it," laughed Lord Henry, sinking into a chair.  "From\r
+a label there is no escape!  I refuse the title."\r
+\r
+"Royalties may not abdicate," fell as a warning from pretty lips.\r
+\r
+"You wish me to defend my throne, then?"\r
+\r
+"Yes."\r
+\r
+"I give the truths of to-morrow."\r
+\r
+"I prefer the mistakes of to-day," she answered.\r
+\r
+"You disarm me, Gladys," he cried, catching the wilfulness of her mood.\r
+\r
+"Of your shield, Harry, not of your spear."\r
+\r
+"I never tilt against beauty," he said, with a wave of his hand.\r
+\r
+"That is your error, Harry, believe me.  You value beauty far too much."\r
+\r
+"How can you say that?  I admit that I think that it is better to be\r
+beautiful than to be good.  But on the other hand, no one is more ready\r
+than I am to acknowledge that it is better to be good than to be ugly."\r
+\r
+"Ugliness is one of the seven deadly sins, then?" cried the duchess.\r
+"What becomes of your simile about the orchid?"\r
+\r
+"Ugliness is one of the seven deadly virtues, Gladys.  You, as a good\r
+Tory, must not underrate them.  Beer, the Bible, and the seven deadly\r
+virtues have made our England what she is."\r
+\r
+"You don't like your country, then?" she asked.\r
+\r
+"I live in it."\r
+\r
+"That you may censure it the better."\r
+\r
+"Would you have me take the verdict of Europe on it?" he inquired.\r
+\r
+"What do they say of us?"\r
+\r
+"That Tartuffe has emigrated to England and opened a shop."\r
+\r
+"Is that yours, Harry?"\r
+\r
+"I give it to you."\r
+\r
+"I could not use it.  It is too true."\r
+\r
+"You need not be afraid.  Our countrymen never recognize a description."\r
+\r
+"They are practical."\r
+\r
+"They are more cunning than practical.  When they make up their ledger,\r
+they balance stupidity by wealth, and vice by hypocrisy."\r
+\r
+"Still, we have done great things."\r
+\r
+"Great things have been thrust on us, Gladys."\r
+\r
+"We have carried their burden."\r
+\r
+"Only as far as the Stock Exchange."\r
+\r
+She shook her head.  "I believe in the race," she cried.\r
+\r
+"It represents the survival of the pushing."\r
+\r
+"It has development."\r
+\r
+"Decay fascinates me more."\r
+\r
+"What of art?" she asked.\r
+\r
+"It is a malady."\r
+\r
+"Love?"\r
+\r
+"An illusion."\r
+\r
+"Religion?"\r
+\r
+"The fashionable substitute for belief."\r
+\r
+"You are a sceptic."\r
+\r
+"Never!  Scepticism is the beginning of faith."\r
+\r
+"What are you?"\r
+\r
+"To define is to limit."\r
+\r
+"Give me a clue."\r
+\r
+"Threads snap.  You would lose your way in the labyrinth."\r
+\r
+"You bewilder me.  Let us talk of some one else."\r
+\r
+"Our host is a delightful topic.  Years ago he was christened Prince\r
+Charming."\r
+\r
+"Ah! don't remind me of that," cried Dorian Gray.\r
+\r
+"Our host is rather horrid this evening," answered the duchess,\r
+colouring.  "I believe he thinks that Monmouth married me on purely\r
+scientific principles as the best specimen he could find of a modern\r
+butterfly."\r
+\r
+"Well, I hope he won't stick pins into you, Duchess," laughed Dorian.\r
+\r
+"Oh! my maid does that already, Mr. Gray, when she is annoyed with me."\r
+\r
+"And what does she get annoyed with you about, Duchess?"\r
+\r
+"For the most trivial things, Mr. Gray, I assure you.  Usually because\r
+I come in at ten minutes to nine and tell her that I must be dressed by\r
+half-past eight."\r
+\r
+"How unreasonable of her!  You should give her warning."\r
+\r
+"I daren't, Mr. Gray.  Why, she invents hats for me.  You remember the\r
+one I wore at Lady Hilstone's garden-party?  You don't, but it is nice\r
+of you to pretend that you do.  Well, she made it out of nothing.  All\r
+good hats are made out of nothing."\r
+\r
+"Like all good reputations, Gladys," interrupted Lord Henry.  "Every\r
+effect that one produces gives one an enemy.  To be popular one must be\r
+a mediocrity."\r
+\r
+"Not with women," said the duchess, shaking her head; "and women rule\r
+the world.  I assure you we can't bear mediocrities.  We women, as some\r
+one says, love with our ears, just as you men love with your eyes, if\r
+you ever love at all."\r
+\r
+"It seems to me that we never do anything else," murmured Dorian.\r
+\r
+"Ah! then, you never really love, Mr. Gray," answered the duchess with\r
+mock sadness.\r
+\r
+"My dear Gladys!" cried Lord Henry.  "How can you say that?  Romance\r
+lives by repetition, and repetition converts an appetite into an art.\r
+Besides, each time that one loves is the only time one has ever loved.\r
+Difference of object does not alter singleness of passion.  It merely\r
+intensifies it.  We can have in life but one great experience at best,\r
+and the secret of life is to reproduce that experience as often as\r
+possible."\r
+\r
+"Even when one has been wounded by it, Harry?" asked the duchess after\r
+a pause.\r
+\r
+"Especially when one has been wounded by it," answered Lord Henry.\r
+\r
+The duchess turned and looked at Dorian Gray with a curious expression\r
+in her eyes.  "What do you say to that, Mr. Gray?" she inquired.\r
+\r
+Dorian hesitated for a moment.  Then he threw his head back and\r
+laughed.  "I always agree with Harry, Duchess."\r
+\r
+"Even when he is wrong?"\r
+\r
+"Harry is never wrong, Duchess."\r
+\r
+"And does his philosophy make you happy?"\r
+\r
+"I have never searched for happiness.  Who wants happiness?  I have\r
+searched for pleasure."\r
+\r
+"And found it, Mr. Gray?"\r
+\r
+"Often.  Too often."\r
+\r
+The duchess sighed.  "I am searching for peace," she said, "and if I\r
+don't go and dress, I shall have none this evening."\r
+\r
+"Let me get you some orchids, Duchess," cried Dorian, starting to his\r
+feet and walking down the conservatory.\r
+\r
+"You are flirting disgracefully with him," said Lord Henry to his\r
+cousin.  "You had better take care.  He is very fascinating."\r
+\r
+"If he were not, there would be no battle."\r
+\r
+"Greek meets Greek, then?"\r
+\r
+"I am on the side of the Trojans.  They fought for a woman."\r
+\r
+"They were defeated."\r
+\r
+"There are worse things than capture," she answered.\r
+\r
+"You gallop with a loose rein."\r
+\r
+"Pace gives life," was the _riposte_.\r
+\r
+"I shall write it in my diary to-night."\r
+\r
+"What?"\r
+\r
+"That a burnt child loves the fire."\r
+\r
+"I am not even singed.  My wings are untouched."\r
+\r
+"You use them for everything, except flight."\r
+\r
+"Courage has passed from men to women.  It is a new experience for us."\r
+\r
+"You have a rival."\r
+\r
+"Who?"\r
+\r
+He laughed.  "Lady Narborough," he whispered.  "She perfectly adores\r
+him."\r
+\r
+"You fill me with apprehension.  The appeal to antiquity is fatal to us\r
+who are romanticists."\r
+\r
+"Romanticists!  You have all the methods of science."\r
+\r
+"Men have educated us."\r
+\r
+"But not explained you."\r
+\r
+"Describe us as a sex," was her challenge.\r
+\r
+"Sphinxes without secrets."\r
+\r
+She looked at him, smiling.  "How long Mr. Gray is!" she said.  "Let us\r
+go and help him.  I have not yet told him the colour of my frock."\r
+\r
+"Ah! you must suit your frock to his flowers, Gladys."\r
+\r
+"That would be a premature surrender."\r
+\r
+"Romantic art begins with its climax."\r
+\r
+"I must keep an opportunity for retreat."\r
+\r
+"In the Parthian manner?"\r
+\r
+"They found safety in the desert.  I could not do that."\r
+\r
+"Women are not always allowed a choice," he answered, but hardly had he\r
+finished the sentence before from the far end of the conservatory came\r
+a stifled groan, followed by the dull sound of a heavy fall.  Everybody\r
+started up.  The duchess stood motionless in horror.  And with fear in\r
+his eyes, Lord Henry rushed through the flapping palms to find Dorian\r
+Gray lying face downwards on the tiled floor in a deathlike swoon.\r
+\r
+He was carried at once into the blue drawing-room and laid upon one of\r
+the sofas.  After a short time, he came to himself and looked round\r
+with a dazed expression.\r
+\r
+"What has happened?" he asked.  "Oh!  I remember.  Am I safe here,\r
+Harry?" He began to tremble.\r
+\r
+"My dear Dorian," answered Lord Henry, "you merely fainted.  That was\r
+all.  You must have overtired yourself.  You had better not come down\r
+to dinner.  I will take your place."\r
+\r
+"No, I will come down," he said, struggling to his feet.  "I would\r
+rather come down.  I must not be alone."\r
+\r
+He went to his room and dressed.  There was a wild recklessness of\r
+gaiety in his manner as he sat at table, but now and then a thrill of\r
+terror ran through him when he remembered that, pressed against the\r
+window of the conservatory, like a white handkerchief, he had seen the\r
+face of James Vane watching him.\r
+\r
+\r
+\r
+CHAPTER 18\r
+\r
+The next day he did not leave the house, and, indeed, spent most of the\r
+time in his own room, sick with a wild terror of dying, and yet\r
+indifferent to life itself.  The consciousness of being hunted, snared,\r
+tracked down, had begun to dominate him.  If the tapestry did but\r
+tremble in the wind, he shook.  The dead leaves that were blown against\r
+the leaded panes seemed to him like his own wasted resolutions and wild\r
+regrets.  When he closed his eyes, he saw again the sailor's face\r
+peering through the mist-stained glass, and horror seemed once more to\r
+lay its hand upon his heart.\r
+\r
+But perhaps it had been only his fancy that had called vengeance out of\r
+the night and set the hideous shapes of punishment before him.  Actual\r
+life was chaos, but there was something terribly logical in the\r
+imagination.  It was the imagination that set remorse to dog the feet\r
+of sin.  It was the imagination that made each crime bear its misshapen\r
+brood.  In the common world of fact the wicked were not punished, nor\r
+the good rewarded.  Success was given to the strong, failure thrust\r
+upon the weak.  That was all.  Besides, had any stranger been prowling\r
+round the house, he would have been seen by the servants or the\r
+keepers.  Had any foot-marks been found on the flower-beds, the\r
+gardeners would have reported it.  Yes, it had been merely fancy.\r
+Sibyl Vane's brother had not come back to kill him.  He had sailed away\r
+in his ship to founder in some winter sea.  From him, at any rate, he\r
+was safe.  Why, the man did not know who he was, could not know who he\r
+was.  The mask of youth had saved him.\r
+\r
+And yet if it had been merely an illusion, how terrible it was to think\r
+that conscience could raise such fearful phantoms, and give them\r
+visible form, and make them move before one!  What sort of life would\r
+his be if, day and night, shadows of his crime were to peer at him from\r
+silent corners, to mock him from secret places, to whisper in his ear\r
+as he sat at the feast, to wake him with icy fingers as he lay asleep!\r
+As the thought crept through his brain, he grew pale with terror, and\r
+the air seemed to him to have become suddenly colder.  Oh! in what a\r
+wild hour of madness he had killed his friend!  How ghastly the mere\r
+memory of the scene!  He saw it all again.  Each hideous detail came\r
+back to him with added horror.  Out of the black cave of time, terrible\r
+and swathed in scarlet, rose the image of his sin.  When Lord Henry\r
+came in at six o'clock, he found him crying as one whose heart will\r
+break.\r
+\r
+It was not till the third day that he ventured to go out.  There was\r
+something in the clear, pine-scented air of that winter morning that\r
+seemed to bring him back his joyousness and his ardour for life.  But\r
+it was not merely the physical conditions of environment that had\r
+caused the change.  His own nature had revolted against the excess of\r
+anguish that had sought to maim and mar the perfection of its calm.\r
+With subtle and finely wrought temperaments it is always so.  Their\r
+strong passions must either bruise or bend.  They either slay the man,\r
+or themselves die.  Shallow sorrows and shallow loves live on.  The\r
+loves and sorrows that are great are destroyed by their own plenitude.\r
+Besides, he had convinced himself that he had been the victim of a\r
+terror-stricken imagination, and looked back now on his fears with\r
+something of pity and not a little of contempt.\r
+\r
+After breakfast, he walked with the duchess for an hour in the garden\r
+and then drove across the park to join the shooting-party. The crisp\r
+frost lay like salt upon the grass.  The sky was an inverted cup of\r
+blue metal.  A thin film of ice bordered the flat, reed-grown lake.\r
+\r
+At the corner of the pine-wood he caught sight of Sir Geoffrey\r
+Clouston, the duchess's brother, jerking two spent cartridges out of\r
+his gun.  He jumped from the cart, and having told the groom to take\r
+the mare home, made his way towards his guest through the withered\r
+bracken and rough undergrowth.\r
+\r
+"Have you had good sport, Geoffrey?" he asked.\r
+\r
+"Not very good, Dorian.  I think most of the birds have gone to the\r
+open.  I dare say it will be better after lunch, when we get to new\r
+ground."\r
+\r
+Dorian strolled along by his side.  The keen aromatic air, the brown\r
+and red lights that glimmered in the wood, the hoarse cries of the\r
+beaters ringing out from time to time, and the sharp snaps of the guns\r
+that followed, fascinated him and filled him with a sense of delightful\r
+freedom.  He was dominated by the carelessness of happiness, by the\r
+high indifference of joy.\r
+\r
+Suddenly from a lumpy tussock of old grass some twenty yards in front\r
+of them, with black-tipped ears erect and long hinder limbs throwing it\r
+forward, started a hare.  It bolted for a thicket of alders.  Sir\r
+Geoffrey put his gun to his shoulder, but there was something in the\r
+animal's grace of movement that strangely charmed Dorian Gray, and he\r
+cried out at once, "Don't shoot it, Geoffrey.  Let it live."\r
+\r
+"What nonsense, Dorian!" laughed his companion, and as the hare bounded\r
+into the thicket, he fired.  There were two cries heard, the cry of a\r
+hare in pain, which is dreadful, the cry of a man in agony, which is\r
+worse.\r
+\r
+"Good heavens!  I have hit a beater!" exclaimed Sir Geoffrey.  "What an\r
+ass the man was to get in front of the guns!  Stop shooting there!" he\r
+called out at the top of his voice.  "A man is hurt."\r
+\r
+The head-keeper came running up with a stick in his hand.\r
+\r
+"Where, sir?  Where is he?" he shouted.  At the same time, the firing\r
+ceased along the line.\r
+\r
+"Here," answered Sir Geoffrey angrily, hurrying towards the thicket.\r
+"Why on earth don't you keep your men back?  Spoiled my shooting for\r
+the day."\r
+\r
+Dorian watched them as they plunged into the alder-clump, brushing the\r
+lithe swinging branches aside.  In a few moments they emerged, dragging\r
+a body after them into the sunlight.  He turned away in horror.  It\r
+seemed to him that misfortune followed wherever he went.  He heard Sir\r
+Geoffrey ask if the man was really dead, and the affirmative answer of\r
+the keeper.  The wood seemed to him to have become suddenly alive with\r
+faces.  There was the trampling of myriad feet and the low buzz of\r
+voices.  A great copper-breasted pheasant came beating through the\r
+boughs overhead.\r
+\r
+After a few moments--that were to him, in his perturbed state, like\r
+endless hours of pain--he felt a hand laid on his shoulder.  He started\r
+and looked round.\r
+\r
+"Dorian," said Lord Henry, "I had better tell them that the shooting is\r
+stopped for to-day. It would not look well to go on."\r
+\r
+"I wish it were stopped for ever, Harry," he answered bitterly.  "The\r
+whole thing is hideous and cruel.  Is the man ...?"\r
+\r
+He could not finish the sentence.\r
+\r
+"I am afraid so," rejoined Lord Henry.  "He got the whole charge of\r
+shot in his chest.  He must have died almost instantaneously.  Come;\r
+let us go home."\r
+\r
+They walked side by side in the direction of the avenue for nearly\r
+fifty yards without speaking.  Then Dorian looked at Lord Henry and\r
+said, with a heavy sigh, "It is a bad omen, Harry, a very bad omen."\r
+\r
+"What is?" asked Lord Henry.  "Oh! this accident, I suppose.  My dear\r
+fellow, it can't be helped.  It was the man's own fault.  Why did he\r
+get in front of the guns?  Besides, it is nothing to us.  It is rather\r
+awkward for Geoffrey, of course.  It does not do to pepper beaters.  It\r
+makes people think that one is a wild shot.  And Geoffrey is not; he\r
+shoots very straight.  But there is no use talking about the matter."\r
+\r
+Dorian shook his head.  "It is a bad omen, Harry.  I feel as if\r
+something horrible were going to happen to some of us.  To myself,\r
+perhaps," he added, passing his hand over his eyes, with a gesture of\r
+pain.\r
+\r
+The elder man laughed.  "The only horrible thing in the world is _ennui_,\r
+Dorian.  That is the one sin for which there is no forgiveness.  But we\r
+are not likely to suffer from it unless these fellows keep chattering\r
+about this thing at dinner.  I must tell them that the subject is to be\r
+tabooed.  As for omens, there is no such thing as an omen.  Destiny\r
+does not send us heralds.  She is too wise or too cruel for that.\r
+Besides, what on earth could happen to you, Dorian?  You have\r
+everything in the world that a man can want.  There is no one who would\r
+not be delighted to change places with you."\r
+\r
+"There is no one with whom I would not change places, Harry.  Don't\r
+laugh like that.  I am telling you the truth.  The wretched peasant who\r
+has just died is better off than I am.  I have no terror of death.  It\r
+is the coming of death that terrifies me.  Its monstrous wings seem to\r
+wheel in the leaden air around me.  Good heavens! don't you see a man\r
+moving behind the trees there, watching me, waiting for me?"\r
+\r
+Lord Henry looked in the direction in which the trembling gloved hand\r
+was pointing.  "Yes," he said, smiling, "I see the gardener waiting for\r
+you.  I suppose he wants to ask you what flowers you wish to have on\r
+the table to-night. How absurdly nervous you are, my dear fellow!  You\r
+must come and see my doctor, when we get back to town."\r
+\r
+Dorian heaved a sigh of relief as he saw the gardener approaching.  The\r
+man touched his hat, glanced for a moment at Lord Henry in a hesitating\r
+manner, and then produced a letter, which he handed to his master.\r
+"Her Grace told me to wait for an answer," he murmured.\r
+\r
+Dorian put the letter into his pocket.  "Tell her Grace that I am\r
+coming in," he said, coldly.  The man turned round and went rapidly in\r
+the direction of the house.\r
+\r
+"How fond women are of doing dangerous things!" laughed Lord Henry.\r
+"It is one of the qualities in them that I admire most.  A woman will\r
+flirt with anybody in the world as long as other people are looking on."\r
+\r
+"How fond you are of saying dangerous things, Harry!  In the present\r
+instance, you are quite astray.  I like the duchess very much, but I\r
+don't love her."\r
+\r
+"And the duchess loves you very much, but she likes you less, so you\r
+are excellently matched."\r
+\r
+"You are talking scandal, Harry, and there is never any basis for\r
+scandal."\r
+\r
+"The basis of every scandal is an immoral certainty," said Lord Henry,\r
+lighting a cigarette.\r
+\r
+"You would sacrifice anybody, Harry, for the sake of an epigram."\r
+\r
+"The world goes to the altar of its own accord," was the answer.\r
+\r
+"I wish I could love," cried Dorian Gray with a deep note of pathos in\r
+his voice.  "But I seem to have lost the passion and forgotten the\r
+desire.  I am too much concentrated on myself.  My own personality has\r
+become a burden to me.  I want to escape, to go away, to forget.  It\r
+was silly of me to come down here at all.  I think I shall send a wire\r
+to Harvey to have the yacht got ready.  On a yacht one is safe."\r
+\r
+"Safe from what, Dorian?  You are in some trouble.  Why not tell me\r
+what it is?  You know I would help you."\r
+\r
+"I can't tell you, Harry," he answered sadly.  "And I dare say it is\r
+only a fancy of mine.  This unfortunate accident has upset me.  I have\r
+a horrible presentiment that something of the kind may happen to me."\r
+\r
+"What nonsense!"\r
+\r
+"I hope it is, but I can't help feeling it.  Ah! here is the duchess,\r
+looking like Artemis in a tailor-made gown.  You see we have come back,\r
+Duchess."\r
+\r
+"I have heard all about it, Mr. Gray," she answered.  "Poor Geoffrey is\r
+terribly upset.  And it seems that you asked him not to shoot the hare.\r
+How curious!"\r
+\r
+"Yes, it was very curious.  I don't know what made me say it.  Some\r
+whim, I suppose.  It looked the loveliest of little live things.  But I\r
+am sorry they told you about the man.  It is a hideous subject."\r
+\r
+"It is an annoying subject," broke in Lord Henry.  "It has no\r
+psychological value at all.  Now if Geoffrey had done the thing on\r
+purpose, how interesting he would be!  I should like to know some one\r
+who had committed a real murder."\r
+\r
+"How horrid of you, Harry!" cried the duchess.  "Isn't it, Mr. Gray?\r
+Harry, Mr. Gray is ill again.  He is going to faint."\r
+\r
+Dorian drew himself up with an effort and smiled.  "It is nothing,\r
+Duchess," he murmured; "my nerves are dreadfully out of order.  That is\r
+all.  I am afraid I walked too far this morning.  I didn't hear what\r
+Harry said.  Was it very bad?  You must tell me some other time.  I\r
+think I must go and lie down.  You will excuse me, won't you?"\r
+\r
+They had reached the great flight of steps that led from the\r
+conservatory on to the terrace.  As the glass door closed behind\r
+Dorian, Lord Henry turned and looked at the duchess with his slumberous\r
+eyes.  "Are you very much in love with him?" he asked.\r
+\r
+She did not answer for some time, but stood gazing at the landscape.\r
+"I wish I knew," she said at last.\r
+\r
+He shook his head.  "Knowledge would be fatal.  It is the uncertainty\r
+that charms one.  A mist makes things wonderful."\r
+\r
+"One may lose one's way."\r
+\r
+"All ways end at the same point, my dear Gladys."\r
+\r
+"What is that?"\r
+\r
+"Disillusion."\r
+\r
+"It was my _debut_ in life," she sighed.\r
+\r
+"It came to you crowned."\r
+\r
+"I am tired of strawberry leaves."\r
+\r
+"They become you."\r
+\r
+"Only in public."\r
+\r
+"You would miss them," said Lord Henry.\r
+\r
+"I will not part with a petal."\r
+\r
+"Monmouth has ears."\r
+\r
+"Old age is dull of hearing."\r
+\r
+"Has he never been jealous?"\r
+\r
+"I wish he had been."\r
+\r
+He glanced about as if in search of something.  "What are you looking\r
+for?" she inquired.\r
+\r
+"The button from your foil," he answered.  "You have dropped it."\r
+\r
+She laughed.  "I have still the mask."\r
+\r
+"It makes your eyes lovelier," was his reply.\r
+\r
+She laughed again.  Her teeth showed like white seeds in a scarlet\r
+fruit.\r
+\r
+Upstairs, in his own room, Dorian Gray was lying on a sofa, with terror\r
+in every tingling fibre of his body.  Life had suddenly become too\r
+hideous a burden for him to bear.  The dreadful death of the unlucky\r
+beater, shot in the thicket like a wild animal, had seemed to him to\r
+pre-figure death for himself also.  He had nearly swooned at what Lord\r
+Henry had said in a chance mood of cynical jesting.\r
+\r
+At five o'clock he rang his bell for his servant and gave him orders to\r
+pack his things for the night-express to town, and to have the brougham\r
+at the door by eight-thirty. He was determined not to sleep another\r
+night at Selby Royal.  It was an ill-omened place.  Death walked there\r
+in the sunlight.  The grass of the forest had been spotted with blood.\r
+\r
+Then he wrote a note to Lord Henry, telling him that he was going up to\r
+town to consult his doctor and asking him to entertain his guests in\r
+his absence.  As he was putting it into the envelope, a knock came to\r
+the door, and his valet informed him that the head-keeper wished to see\r
+him.  He frowned and bit his lip.  "Send him in," he muttered, after\r
+some moments' hesitation.\r
+\r
+As soon as the man entered, Dorian pulled his chequebook out of a\r
+drawer and spread it out before him.\r
+\r
+"I suppose you have come about the unfortunate accident of this\r
+morning, Thornton?" he said, taking up a pen.\r
+\r
+"Yes, sir," answered the gamekeeper.\r
+\r
+"Was the poor fellow married?  Had he any people dependent on him?"\r
+asked Dorian, looking bored.  "If so, I should not like them to be left\r
+in want, and will send them any sum of money you may think necessary."\r
+\r
+"We don't know who he is, sir.  That is what I took the liberty of\r
+coming to you about."\r
+\r
+"Don't know who he is?" said Dorian, listlessly.  "What do you mean?\r
+Wasn't he one of your men?"\r
+\r
+"No, sir.  Never saw him before.  Seems like a sailor, sir."\r
+\r
+The pen dropped from Dorian Gray's hand, and he felt as if his heart\r
+had suddenly stopped beating.  "A sailor?" he cried out.  "Did you say\r
+a sailor?"\r
+\r
+"Yes, sir.  He looks as if he had been a sort of sailor; tattooed on\r
+both arms, and that kind of thing."\r
+\r
+"Was there anything found on him?" said Dorian, leaning forward and\r
+looking at the man with startled eyes.  "Anything that would tell his\r
+name?"\r
+\r
+"Some money, sir--not much, and a six-shooter. There was no name of any\r
+kind.  A decent-looking man, sir, but rough-like. A sort of sailor we\r
+think."\r
+\r
+Dorian started to his feet.  A terrible hope fluttered past him.  He\r
+clutched at it madly.  "Where is the body?" he exclaimed.  "Quick!  I\r
+must see it at once."\r
+\r
+"It is in an empty stable in the Home Farm, sir.  The folk don't like\r
+to have that sort of thing in their houses.  They say a corpse brings\r
+bad luck."\r
+\r
+"The Home Farm!  Go there at once and meet me.  Tell one of the grooms\r
+to bring my horse round.  No. Never mind.  I'll go to the stables\r
+myself.  It will save time."\r
+\r
+In less than a quarter of an hour, Dorian Gray was galloping down the\r
+long avenue as hard as he could go.  The trees seemed to sweep past him\r
+in spectral procession, and wild shadows to fling themselves across his\r
+path.  Once the mare swerved at a white gate-post and nearly threw him.\r
+He lashed her across the neck with his crop.  She cleft the dusky air\r
+like an arrow.  The stones flew from her hoofs.\r
+\r
+At last he reached the Home Farm.  Two men were loitering in the yard.\r
+He leaped from the saddle and threw the reins to one of them.  In the\r
+farthest stable a light was glimmering.  Something seemed to tell him\r
+that the body was there, and he hurried to the door and put his hand\r
+upon the latch.\r
+\r
+There he paused for a moment, feeling that he was on the brink of a\r
+discovery that would either make or mar his life.  Then he thrust the\r
+door open and entered.\r
+\r
+On a heap of sacking in the far corner was lying the dead body of a man\r
+dressed in a coarse shirt and a pair of blue trousers.  A spotted\r
+handkerchief had been placed over the face.  A coarse candle, stuck in\r
+a bottle, sputtered beside it.\r
+\r
+Dorian Gray shuddered.  He felt that his could not be the hand to take\r
+the handkerchief away, and called out to one of the farm-servants to\r
+come to him.\r
+\r
+"Take that thing off the face.  I wish to see it," he said, clutching\r
+at the door-post for support.\r
+\r
+When the farm-servant had done so, he stepped forward.  A cry of joy\r
+broke from his lips.  The man who had been shot in the thicket was\r
+James Vane.\r
+\r
+He stood there for some minutes looking at the dead body.  As he rode\r
+home, his eyes were full of tears, for he knew he was safe.\r
+\r
+\r
+\r
+CHAPTER 19\r
+\r
+"There is no use your telling me that you are going to be good," cried\r
+Lord Henry, dipping his white fingers into a red copper bowl filled\r
+with rose-water. "You are quite perfect.  Pray, don't change."\r
+\r
+Dorian Gray shook his head.  "No, Harry, I have done too many dreadful\r
+things in my life.  I am not going to do any more.  I began my good\r
+actions yesterday."\r
+\r
+"Where were you yesterday?"\r
+\r
+"In the country, Harry.  I was staying at a little inn by myself."\r
+\r
+"My dear boy," said Lord Henry, smiling, "anybody can be good in the\r
+country.  There are no temptations there.  That is the reason why\r
+people who live out of town are so absolutely uncivilized.\r
+Civilization is not by any means an easy thing to attain to.  There are\r
+only two ways by which man can reach it.  One is by being cultured, the\r
+other by being corrupt.  Country people have no opportunity of being\r
+either, so they stagnate."\r
+\r
+"Culture and corruption," echoed Dorian.  "I have known something of\r
+both.  It seems terrible to me now that they should ever be found\r
+together.  For I have a new ideal, Harry.  I am going to alter.  I\r
+think I have altered."\r
+\r
+"You have not yet told me what your good action was.  Or did you say\r
+you had done more than one?" asked his companion as he spilled into his\r
+plate a little crimson pyramid of seeded strawberries and, through a\r
+perforated, shell-shaped spoon, snowed white sugar upon them.\r
+\r
+"I can tell you, Harry.  It is not a story I could tell to any one\r
+else.  I spared somebody.  It sounds vain, but you understand what I\r
+mean.  She was quite beautiful and wonderfully like Sibyl Vane.  I\r
+think it was that which first attracted me to her.  You remember Sibyl,\r
+don't you?  How long ago that seems!  Well, Hetty was not one of our\r
+own class, of course.  She was simply a girl in a village.  But I\r
+really loved her.  I am quite sure that I loved her.  All during this\r
+wonderful May that we have been having, I used to run down and see her\r
+two or three times a week.  Yesterday she met me in a little orchard.\r
+The apple-blossoms kept tumbling down on her hair, and she was\r
+laughing.  We were to have gone away together this morning at dawn.\r
+Suddenly I determined to leave her as flowerlike as I had found her."\r
+\r
+"I should think the novelty of the emotion must have given you a thrill\r
+of real pleasure, Dorian," interrupted Lord Henry.  "But I can finish\r
+your idyll for you.  You gave her good advice and broke her heart.\r
+That was the beginning of your reformation."\r
+\r
+"Harry, you are horrible!  You mustn't say these dreadful things.\r
+Hetty's heart is not broken.  Of course, she cried and all that.  But\r
+there is no disgrace upon her.  She can live, like Perdita, in her\r
+garden of mint and marigold."\r
+\r
+"And weep over a faithless Florizel," said Lord Henry, laughing, as he\r
+leaned back in his chair.  "My dear Dorian, you have the most curiously\r
+boyish moods.  Do you think this girl will ever be really content now\r
+with any one of her own rank?  I suppose she will be married some day\r
+to a rough carter or a grinning ploughman.  Well, the fact of having\r
+met you, and loved you, will teach her to despise her husband, and she\r
+will be wretched.  From a moral point of view, I cannot say that I\r
+think much of your great renunciation.  Even as a beginning, it is\r
+poor.  Besides, how do you know that Hetty isn't floating at the\r
+present moment in some starlit mill-pond, with lovely water-lilies\r
+round her, like Ophelia?"\r
+\r
+"I can't bear this, Harry!  You mock at everything, and then suggest\r
+the most serious tragedies.  I am sorry I told you now.  I don't care\r
+what you say to me.  I know I was right in acting as I did.  Poor\r
+Hetty!  As I rode past the farm this morning, I saw her white face at\r
+the window, like a spray of jasmine.  Don't let us talk about it any\r
+more, and don't try to persuade me that the first good action I have\r
+done for years, the first little bit of self-sacrifice I have ever\r
+known, is really a sort of sin.  I want to be better.  I am going to be\r
+better.  Tell me something about yourself.  What is going on in town?\r
+I have not been to the club for days."\r
+\r
+"The people are still discussing poor Basil's disappearance."\r
+\r
+"I should have thought they had got tired of that by this time," said\r
+Dorian, pouring himself out some wine and frowning slightly.\r
+\r
+"My dear boy, they have only been talking about it for six weeks, and\r
+the British public are really not equal to the mental strain of having\r
+more than one topic every three months.  They have been very fortunate\r
+lately, however.  They have had my own divorce-case and Alan Campbell's\r
+suicide.  Now they have got the mysterious disappearance of an artist.\r
+Scotland Yard still insists that the man in the grey ulster who left\r
+for Paris by the midnight train on the ninth of November was poor\r
+Basil, and the French police declare that Basil never arrived in Paris\r
+at all.  I suppose in about a fortnight we shall be told that he has\r
+been seen in San Francisco.  It is an odd thing, but every one who\r
+disappears is said to be seen at San Francisco.  It must be a\r
+delightful city, and possess all the attractions of the next world."\r
+\r
+"What do you think has happened to Basil?" asked Dorian, holding up his\r
+Burgundy against the light and wondering how it was that he could\r
+discuss the matter so calmly.\r
+\r
+"I have not the slightest idea.  If Basil chooses to hide himself, it\r
+is no business of mine.  If he is dead, I don't want to think about\r
+him.  Death is the only thing that ever terrifies me.  I hate it."\r
+\r
+"Why?" said the younger man wearily.\r
+\r
+"Because," said Lord Henry, passing beneath his nostrils the gilt\r
+trellis of an open vinaigrette box, "one can survive everything\r
+nowadays except that.  Death and vulgarity are the only two facts in\r
+the nineteenth century that one cannot explain away.  Let us have our\r
+coffee in the music-room, Dorian.  You must play Chopin to me.  The man\r
+with whom my wife ran away played Chopin exquisitely.  Poor Victoria!\r
+I was very fond of her.  The house is rather lonely without her.  Of\r
+course, married life is merely a habit, a bad habit.  But then one\r
+regrets the loss even of one's worst habits.  Perhaps one regrets them\r
+the most.  They are such an essential part of one's personality."\r
+\r
+Dorian said nothing, but rose from the table, and passing into the next\r
+room, sat down to the piano and let his fingers stray across the white\r
+and black ivory of the keys.  After the coffee had been brought in, he\r
+stopped, and looking over at Lord Henry, said, "Harry, did it ever\r
+occur to you that Basil was murdered?"\r
+\r
+Lord Henry yawned.  "Basil was very popular, and always wore a\r
+Waterbury watch.  Why should he have been murdered?  He was not clever\r
+enough to have enemies.  Of course, he had a wonderful genius for\r
+painting.  But a man can paint like Velasquez and yet be as dull as\r
+possible.  Basil was really rather dull.  He only interested me once,\r
+and that was when he told me, years ago, that he had a wild adoration\r
+for you and that you were the dominant motive of his art."\r
+\r
+"I was very fond of Basil," said Dorian with a note of sadness in his\r
+voice.  "But don't people say that he was murdered?"\r
+\r
+"Oh, some of the papers do.  It does not seem to me to be at all\r
+probable.  I know there are dreadful places in Paris, but Basil was not\r
+the sort of man to have gone to them.  He had no curiosity.  It was his\r
+chief defect."\r
+\r
+"What would you say, Harry, if I told you that I had murdered Basil?"\r
+said the younger man.  He watched him intently after he had spoken.\r
+\r
+"I would say, my dear fellow, that you were posing for a character that\r
+doesn't suit you.  All crime is vulgar, just as all vulgarity is crime.\r
+It is not in you, Dorian, to commit a murder.  I am sorry if I hurt\r
+your vanity by saying so, but I assure you it is true.  Crime belongs\r
+exclusively to the lower orders.  I don't blame them in the smallest\r
+degree.  I should fancy that crime was to them what art is to us,\r
+simply a method of procuring extraordinary sensations."\r
+\r
+"A method of procuring sensations?  Do you think, then, that a man who\r
+has once committed a murder could possibly do the same crime again?\r
+Don't tell me that."\r
+\r
+"Oh! anything becomes a pleasure if one does it too often," cried Lord\r
+Henry, laughing.  "That is one of the most important secrets of life.\r
+I should fancy, however, that murder is always a mistake.  One should\r
+never do anything that one cannot talk about after dinner.  But let us\r
+pass from poor Basil.  I wish I could believe that he had come to such\r
+a really romantic end as you suggest, but I can't. I dare say he fell\r
+into the Seine off an omnibus and that the conductor hushed up the\r
+scandal.  Yes:  I should fancy that was his end.  I see him lying now\r
+on his back under those dull-green waters, with the heavy barges\r
+floating over him and long weeds catching in his hair.  Do you know, I\r
+don't think he would have done much more good work.  During the last\r
+ten years his painting had gone off very much."\r
+\r
+Dorian heaved a sigh, and Lord Henry strolled across the room and began\r
+to stroke the head of a curious Java parrot, a large, grey-plumaged\r
+bird with pink crest and tail, that was balancing itself upon a bamboo\r
+perch.  As his pointed fingers touched it, it dropped the white scurf\r
+of crinkled lids over black, glasslike eyes and began to sway backwards\r
+and forwards.\r
+\r
+"Yes," he continued, turning round and taking his handkerchief out of\r
+his pocket; "his painting had quite gone off.  It seemed to me to have\r
+lost something.  It had lost an ideal.  When you and he ceased to be\r
+great friends, he ceased to be a great artist.  What was it separated\r
+you?  I suppose he bored you.  If so, he never forgave you.  It's a\r
+habit bores have.  By the way, what has become of that wonderful\r
+portrait he did of you?  I don't think I have ever seen it since he\r
+finished it.  Oh!  I remember your telling me years ago that you had\r
+sent it down to Selby, and that it had got mislaid or stolen on the\r
+way.  You never got it back?  What a pity! it was really a\r
+masterpiece.  I remember I wanted to buy it.  I wish I had now.  It\r
+belonged to Basil's best period.  Since then, his work was that curious\r
+mixture of bad painting and good intentions that always entitles a man\r
+to be called a representative British artist.  Did you advertise for\r
+it?  You should."\r
+\r
+"I forget," said Dorian.  "I suppose I did.  But I never really liked\r
+it.  I am sorry I sat for it.  The memory of the thing is hateful to\r
+me.  Why do you talk of it?  It used to remind me of those curious\r
+lines in some play--Hamlet, I think--how do they run?--\r
+\r
+    "Like the painting of a sorrow,\r
+     A face without a heart."\r
+\r
+Yes:  that is what it was like."\r
+\r
+Lord Henry laughed.  "If a man treats life artistically, his brain is\r
+his heart," he answered, sinking into an arm-chair.\r
+\r
+Dorian Gray shook his head and struck some soft chords on the piano.\r
+"'Like the painting of a sorrow,'" he repeated, "'a face without a\r
+heart.'"\r
+\r
+The elder man lay back and looked at him with half-closed eyes.  "By\r
+the way, Dorian," he said after a pause, "'what does it profit a man if\r
+he gain the whole world and lose--how does the quotation run?--his own\r
+soul'?"\r
+\r
+The music jarred, and Dorian Gray started and stared at his friend.\r
+"Why do you ask me that, Harry?"\r
+\r
+"My dear fellow," said Lord Henry, elevating his eyebrows in surprise,\r
+"I asked you because I thought you might be able to give me an answer.\r
+That is all.  I was going through the park last Sunday, and close by\r
+the Marble Arch there stood a little crowd of shabby-looking people\r
+listening to some vulgar street-preacher. As I passed by, I heard the\r
+man yelling out that question to his audience.  It struck me as being\r
+rather dramatic.  London is very rich in curious effects of that kind.\r
+A wet Sunday, an uncouth Christian in a mackintosh, a ring of sickly\r
+white faces under a broken roof of dripping umbrellas, and a wonderful\r
+phrase flung into the air by shrill hysterical lips--it was really very\r
+good in its way, quite a suggestion.  I thought of telling the prophet\r
+that art had a soul, but that man had not.  I am afraid, however, he\r
+would not have understood me."\r
+\r
+"Don't, Harry.  The soul is a terrible reality.  It can be bought, and\r
+sold, and bartered away.  It can be poisoned, or made perfect.  There\r
+is a soul in each one of us.  I know it."\r
+\r
+"Do you feel quite sure of that, Dorian?"\r
+\r
+"Quite sure."\r
+\r
+"Ah! then it must be an illusion.  The things one feels absolutely\r
+certain about are never true.  That is the fatality of faith, and the\r
+lesson of romance.  How grave you are!  Don't be so serious.  What have\r
+you or I to do with the superstitions of our age?  No:  we have given\r
+up our belief in the soul.  Play me something.  Play me a nocturne,\r
+Dorian, and, as you play, tell me, in a low voice, how you have kept\r
+your youth.  You must have some secret.  I am only ten years older than\r
+you are, and I am wrinkled, and worn, and yellow.  You are really\r
+wonderful, Dorian.  You have never looked more charming than you do\r
+to-night. You remind me of the day I saw you first.  You were rather\r
+cheeky, very shy, and absolutely extraordinary.  You have changed, of\r
+course, but not in appearance.  I wish you would tell me your secret.\r
+To get back my youth I would do anything in the world, except take\r
+exercise, get up early, or be respectable.  Youth!  There is nothing\r
+like it.  It's absurd to talk of the ignorance of youth.  The only\r
+people to whose opinions I listen now with any respect are people much\r
+younger than myself.  They seem in front of me.  Life has revealed to\r
+them her latest wonder.  As for the aged, I always contradict the aged.\r
+I do it on principle.  If you ask them their opinion on something that\r
+happened yesterday, they solemnly give you the opinions current in\r
+1820, when people wore high stocks, believed in everything, and knew\r
+absolutely nothing.  How lovely that thing you are playing is!  I\r
+wonder, did Chopin write it at Majorca, with the sea weeping round the\r
+villa and the salt spray dashing against the panes?  It is marvellously\r
+romantic.  What a blessing it is that there is one art left to us that\r
+is not imitative!  Don't stop.  I want music to-night. It seems to me\r
+that you are the young Apollo and that I am Marsyas listening to you.\r
+I have sorrows, Dorian, of my own, that even you know nothing of.  The\r
+tragedy of old age is not that one is old, but that one is young.  I am\r
+amazed sometimes at my own sincerity.  Ah, Dorian, how happy you are!\r
+What an exquisite life you have had!  You have drunk deeply of\r
+everything.  You have crushed the grapes against your palate.  Nothing\r
+has been hidden from you.  And it has all been to you no more than the\r
+sound of music.  It has not marred you.  You are still the same."\r
+\r
+"I am not the same, Harry."\r
+\r
+"Yes, you are the same.  I wonder what the rest of your life will be.\r
+Don't spoil it by renunciations.  At present you are a perfect type.\r
+Don't make yourself incomplete.  You are quite flawless now.  You need\r
+not shake your head:  you know you are.  Besides, Dorian, don't deceive\r
+yourself.  Life is not governed by will or intention.  Life is a\r
+question of nerves, and fibres, and slowly built-up cells in which\r
+thought hides itself and passion has its dreams.  You may fancy\r
+yourself safe and think yourself strong.  But a chance tone of colour\r
+in a room or a morning sky, a particular perfume that you had once\r
+loved and that brings subtle memories with it, a line from a forgotten\r
+poem that you had come across again, a cadence from a piece of music\r
+that you had ceased to play--I tell you, Dorian, that it is on things\r
+like these that our lives depend.  Browning writes about that\r
+somewhere; but our own senses will imagine them for us.  There are\r
+moments when the odour of _lilas blanc_ passes suddenly across me, and I\r
+have to live the strangest month of my life over again.  I wish I could\r
+change places with you, Dorian.  The world has cried out against us\r
+both, but it has always worshipped you.  It always will worship you.\r
+You are the type of what the age is searching for, and what it is\r
+afraid it has found.  I am so glad that you have never done anything,\r
+never carved a statue, or painted a picture, or produced anything\r
+outside of yourself!  Life has been your art.  You have set yourself to\r
+music.  Your days are your sonnets."\r
+\r
+Dorian rose up from the piano and passed his hand through his hair.\r
+"Yes, life has been exquisite," he murmured, "but I am not going to\r
+have the same life, Harry.  And you must not say these extravagant\r
+things to me.  You don't know everything about me.  I think that if you\r
+did, even you would turn from me.  You laugh.  Don't laugh."\r
+\r
+"Why have you stopped playing, Dorian?  Go back and give me the\r
+nocturne over again.  Look at that great, honey-coloured moon that\r
+hangs in the dusky air.  She is waiting for you to charm her, and if\r
+you play she will come closer to the earth.  You won't?  Let us go to\r
+the club, then.  It has been a charming evening, and we must end it\r
+charmingly.  There is some one at White's who wants immensely to know\r
+you--young Lord Poole, Bournemouth's eldest son.  He has already copied\r
+your neckties, and has begged me to introduce him to you.  He is quite\r
+delightful and rather reminds me of you."\r
+\r
+"I hope not," said Dorian with a sad look in his eyes.  "But I am tired\r
+to-night, Harry.  I shan't go to the club.  It is nearly eleven, and I\r
+want to go to bed early."\r
+\r
+"Do stay.  You have never played so well as to-night. There was\r
+something in your touch that was wonderful.  It had more expression\r
+than I had ever heard from it before."\r
+\r
+"It is because I am going to be good," he answered, smiling.  "I am a\r
+little changed already."\r
+\r
+"You cannot change to me, Dorian," said Lord Henry.  "You and I will\r
+always be friends."\r
+\r
+"Yet you poisoned me with a book once.  I should not forgive that.\r
+Harry, promise me that you will never lend that book to any one.  It\r
+does harm."\r
+\r
+"My dear boy, you are really beginning to moralize.  You will soon be\r
+going about like the converted, and the revivalist, warning people\r
+against all the sins of which you have grown tired.  You are much too\r
+delightful to do that.  Besides, it is no use.  You and I are what we\r
+are, and will be what we will be.  As for being poisoned by a book,\r
+there is no such thing as that.  Art has no influence upon action.  It\r
+annihilates the desire to act.  It is superbly sterile.  The books that\r
+the world calls immoral are books that show the world its own shame.\r
+That is all.  But we won't discuss literature.  Come round to-morrow. I\r
+am going to ride at eleven.  We might go together, and I will take you\r
+to lunch afterwards with Lady Branksome.  She is a charming woman, and\r
+wants to consult you about some tapestries she is thinking of buying.\r
+Mind you come.  Or shall we lunch with our little duchess?  She says\r
+she never sees you now.  Perhaps you are tired of Gladys?  I thought\r
+you would be.  Her clever tongue gets on one's nerves.  Well, in any\r
+case, be here at eleven."\r
+\r
+"Must I really come, Harry?"\r
+\r
+"Certainly.  The park is quite lovely now.  I don't think there have\r
+been such lilacs since the year I met you."\r
+\r
+"Very well.  I shall be here at eleven," said Dorian.  "Good night,\r
+Harry."  As he reached the door, he hesitated for a moment, as if he\r
+had something more to say.  Then he sighed and went out.\r
+\r
+\r
+\r
+CHAPTER 20\r
+\r
+It was a lovely night, so warm that he threw his coat over his arm and\r
+did not even put his silk scarf round his throat.  As he strolled home,\r
+smoking his cigarette, two young men in evening dress passed him.  He\r
+heard one of them whisper to the other, "That is Dorian Gray." He\r
+remembered how pleased he used to be when he was pointed out, or stared\r
+at, or talked about.  He was tired of hearing his own name now.  Half\r
+the charm of the little village where he had been so often lately was\r
+that no one knew who he was.  He had often told the girl whom he had\r
+lured to love him that he was poor, and she had believed him.  He had\r
+told her once that he was wicked, and she had laughed at him and\r
+answered that wicked people were always very old and very ugly.  What a\r
+laugh she had!--just like a thrush singing.  And how pretty she had\r
+been in her cotton dresses and her large hats!  She knew nothing, but\r
+she had everything that he had lost.\r
+\r
+When he reached home, he found his servant waiting up for him.  He sent\r
+him to bed, and threw himself down on the sofa in the library, and\r
+began to think over some of the things that Lord Henry had said to him.\r
+\r
+Was it really true that one could never change?  He felt a wild longing\r
+for the unstained purity of his boyhood--his rose-white boyhood, as\r
+Lord Henry had once called it.  He knew that he had tarnished himself,\r
+filled his mind with corruption and given horror to his fancy; that he\r
+had been an evil influence to others, and had experienced a terrible\r
+joy in being so; and that of the lives that had crossed his own, it had\r
+been the fairest and the most full of promise that he had brought to\r
+shame.  But was it all irretrievable?  Was there no hope for him?\r
+\r
+Ah! in what a monstrous moment of pride and passion he had prayed that\r
+the portrait should bear the burden of his days, and he keep the\r
+unsullied splendour of eternal youth!  All his failure had been due to\r
+that.  Better for him that each sin of his life had brought its sure\r
+swift penalty along with it.  There was purification in punishment.\r
+Not "Forgive us our sins" but "Smite us for our iniquities" should be\r
+the prayer of man to a most just God.\r
+\r
+The curiously carved mirror that Lord Henry had given to him, so many\r
+years ago now, was standing on the table, and the white-limbed Cupids\r
+laughed round it as of old.  He took it up, as he had done on that\r
+night of horror when he had first noted the change in the fatal\r
+picture, and with wild, tear-dimmed eyes looked into its polished\r
+shield.  Once, some one who had terribly loved him had written to him a\r
+mad letter, ending with these idolatrous words: "The world is changed\r
+because you are made of ivory and gold.  The curves of your lips\r
+rewrite history."  The phrases came back to his memory, and he repeated\r
+them over and over to himself.  Then he loathed his own beauty, and\r
+flinging the mirror on the floor, crushed it into silver splinters\r
+beneath his heel.  It was his beauty that had ruined him, his beauty\r
+and the youth that he had prayed for.  But for those two things, his\r
+life might have been free from stain.  His beauty had been to him but a\r
+mask, his youth but a mockery.  What was youth at best?  A green, an\r
+unripe time, a time of shallow moods, and sickly thoughts.  Why had he\r
+worn its livery?  Youth had spoiled him.\r
+\r
+It was better not to think of the past.  Nothing could alter that.  It\r
+was of himself, and of his own future, that he had to think.  James\r
+Vane was hidden in a nameless grave in Selby churchyard.  Alan Campbell\r
+had shot himself one night in his laboratory, but had not revealed the\r
+secret that he had been forced to know.  The excitement, such as it\r
+was, over Basil Hallward's disappearance would soon pass away.  It was\r
+already waning.  He was perfectly safe there.  Nor, indeed, was it the\r
+death of Basil Hallward that weighed most upon his mind.  It was the\r
+living death of his own soul that troubled him.  Basil had painted the\r
+portrait that had marred his life.  He could not forgive him that.  It\r
+was the portrait that had done everything.  Basil had said things to\r
+him that were unbearable, and that he had yet borne with patience.  The\r
+murder had been simply the madness of a moment.  As for Alan Campbell,\r
+his suicide had been his own act.  He had chosen to do it.  It was\r
+nothing to him.\r
+\r
+A new life!  That was what he wanted.  That was what he was waiting\r
+for.  Surely he had begun it already.  He had spared one innocent\r
+thing, at any rate.  He would never again tempt innocence.  He would be\r
+good.\r
+\r
+As he thought of Hetty Merton, he began to wonder if the portrait in\r
+the locked room had changed.  Surely it was not still so horrible as it\r
+had been?  Perhaps if his life became pure, he would be able to expel\r
+every sign of evil passion from the face.  Perhaps the signs of evil\r
+had already gone away.  He would go and look.\r
+\r
+He took the lamp from the table and crept upstairs.  As he unbarred the\r
+door, a smile of joy flitted across his strangely young-looking face\r
+and lingered for a moment about his lips.  Yes, he would be good, and\r
+the hideous thing that he had hidden away would no longer be a terror\r
+to him.  He felt as if the load had been lifted from him already.\r
+\r
+He went in quietly, locking the door behind him, as was his custom, and\r
+dragged the purple hanging from the portrait.  A cry of pain and\r
+indignation broke from him.  He could see no change, save that in the\r
+eyes there was a look of cunning and in the mouth the curved wrinkle of\r
+the hypocrite.  The thing was still loathsome--more loathsome, if\r
+possible, than before--and the scarlet dew that spotted the hand seemed\r
+brighter, and more like blood newly spilled.  Then he trembled.  Had it\r
+been merely vanity that had made him do his one good deed?  Or the\r
+desire for a new sensation, as Lord Henry had hinted, with his mocking\r
+laugh?  Or that passion to act a part that sometimes makes us do things\r
+finer than we are ourselves?  Or, perhaps, all these?  And why was the\r
+red stain larger than it had been?  It seemed to have crept like a\r
+horrible disease over the wrinkled fingers.  There was blood on the\r
+painted feet, as though the thing had dripped--blood even on the hand\r
+that had not held the knife.  Confess?  Did it mean that he was to\r
+confess?  To give himself up and be put to death?  He laughed.  He felt\r
+that the idea was monstrous.  Besides, even if he did confess, who\r
+would believe him?  There was no trace of the murdered man anywhere.\r
+Everything belonging to him had been destroyed.  He himself had burned\r
+what had been below-stairs. The world would simply say that he was mad.\r
+They would shut him up if he persisted in his story.... Yet it was\r
+his duty to confess, to suffer public shame, and to make public\r
+atonement.  There was a God who called upon men to tell their sins to\r
+earth as well as to heaven.  Nothing that he could do would cleanse him\r
+till he had told his own sin.  His sin?  He shrugged his shoulders.\r
+The death of Basil Hallward seemed very little to him.  He was thinking\r
+of Hetty Merton.  For it was an unjust mirror, this mirror of his soul\r
+that he was looking at.  Vanity?  Curiosity?  Hypocrisy?  Had there\r
+been nothing more in his renunciation than that?  There had been\r
+something more.  At least he thought so.  But who could tell? ... No.\r
+There had been nothing more.  Through vanity he had spared her.  In\r
+hypocrisy he had worn the mask of goodness.  For curiosity's sake he\r
+had tried the denial of self.  He recognized that now.\r
+\r
+But this murder--was it to dog him all his life?  Was he always to be\r
+burdened by his past?  Was he really to confess?  Never.  There was\r
+only one bit of evidence left against him.  The picture itself--that\r
+was evidence.  He would destroy it.  Why had he kept it so long?  Once\r
+it had given him pleasure to watch it changing and growing old.  Of\r
+late he had felt no such pleasure.  It had kept him awake at night.\r
+When he had been away, he had been filled with terror lest other eyes\r
+should look upon it.  It had brought melancholy across his passions.\r
+Its mere memory had marred many moments of joy.  It had been like\r
+conscience to him.  Yes, it had been conscience.  He would destroy it.\r
+\r
+He looked round and saw the knife that had stabbed Basil Hallward.  He\r
+had cleaned it many times, till there was no stain left upon it.  It\r
+was bright, and glistened.  As it had killed the painter, so it would\r
+kill the painter's work, and all that that meant.  It would kill the\r
+past, and when that was dead, he would be free.  It would kill this\r
+monstrous soul-life, and without its hideous warnings, he would be at\r
+peace.  He seized the thing, and stabbed the picture with it.\r
+\r
+There was a cry heard, and a crash.  The cry was so horrible in its\r
+agony that the frightened servants woke and crept out of their rooms.\r
+Two gentlemen, who were passing in the square below, stopped and looked\r
+up at the great house.  They walked on till they met a policeman and\r
+brought him back.  The man rang the bell several times, but there was\r
+no answer.  Except for a light in one of the top windows, the house was\r
+all dark.  After a time, he went away and stood in an adjoining portico\r
+and watched.\r
+\r
+"Whose house is that, Constable?" asked the elder of the two gentlemen.\r
+\r
+"Mr. Dorian Gray's, sir," answered the policeman.\r
+\r
+They looked at each other, as they walked away, and sneered.  One of\r
+them was Sir Henry Ashton's uncle.\r
+\r
+Inside, in the servants' part of the house, the half-clad domestics\r
+were talking in low whispers to each other.  Old Mrs. Leaf was crying\r
+and wringing her hands.  Francis was as pale as death.\r
+\r
+After about a quarter of an hour, he got the coachman and one of the\r
+footmen and crept upstairs.  They knocked, but there was no reply.\r
+They called out.  Everything was still.  Finally, after vainly trying\r
+to force the door, they got on the roof and dropped down on to the\r
+balcony.  The windows yielded easily--their bolts were old.\r
+\r
+When they entered, they found hanging upon the wall a splendid portrait\r
+of their master as they had last seen him, in all the wonder of his\r
+exquisite youth and beauty.  Lying on the floor was a dead man, in\r
+evening dress, with a knife in his heart.  He was withered, wrinkled,\r
+and loathsome of visage.  It was not till they had examined the rings\r
+that they recognized who it was.\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+End of Project Gutenberg's The Picture of Dorian Gray, by Oscar Wilde\r
+\r
+*** END OF THIS PROJECT GUTENBERG EBOOK THE PICTURE OF DORIAN GRAY ***\r
+\r
+***** This file should be named 174.txt or 174.zip *****\r
+This and all associated files of various formats will be found in:\r
+        http://www.gutenberg.org/1/7/174/\r
+\r
+Produced by Judith Boss.  HTML version by Al Haines.\r
+\r
+Updated editions will replace the previous one--the old editions\r
+will be renamed.\r
+\r
+Creating the works from public domain print editions means that no\r
+one owns a United States copyright in these works, so the Foundation\r
+(and you!) can copy and distribute it in the United States without\r
+permission and without paying copyright royalties.  Special rules,\r
+set forth in the General Terms of Use part of this license, apply to\r
+copying and distributing Project Gutenberg-tm electronic works to\r
+protect the PROJECT GUTENBERG-tm concept and trademark.  Project\r
+Gutenberg is a registered trademark, and may not be used if you\r
+charge for the eBooks, unless you receive specific permission.  If you\r
+do not charge anything for copies of this eBook, complying with the\r
+rules is very easy.  You may use this eBook for nearly any purpose\r
+such as creation of derivative works, reports, performances and\r
+research.  They may be modified and printed and given away--you may do\r
+practically ANYTHING with public domain eBooks.  Redistribution is\r
+subject to the trademark license, especially commercial\r
+redistribution.\r
+\r
+\r
+\r
+*** START: FULL LICENSE ***\r
+\r
+THE FULL PROJECT GUTENBERG LICENSE\r
+PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK\r
+\r
+To protect the Project Gutenberg-tm mission of promoting the free\r
+distribution of electronic works, by using or distributing this work\r
+(or any other work associated in any way with the phrase "Project\r
+Gutenberg"), you agree to comply with all the terms of the Full Project\r
+Gutenberg-tm License (available with this file or online at\r
+http://gutenberg.net/license).\r
+\r
+\r
+Section 1.  General Terms of Use and Redistributing Project Gutenberg-tm\r
+electronic works\r
+\r
+1.A.  By reading or using any part of this Project Gutenberg-tm\r
+electronic work, you indicate that you have read, understand, agree to\r
+and accept all the terms of this license and intellectual property\r
+(trademark/copyright) agreement.  If you do not agree to abide by all\r
+the terms of this agreement, you must cease using and return or destroy\r
+all copies of Project Gutenberg-tm electronic works in your possession.\r
+If you paid a fee for obtaining a copy of or access to a Project\r
+Gutenberg-tm electronic work and you do not agree to be bound by the\r
+terms of this agreement, you may obtain a refund from the person or\r
+entity to whom you paid the fee as set forth in paragraph 1.E.8.\r
+\r
+1.B.  "Project Gutenberg" is a registered trademark.  It may only be\r
+used on or associated in any way with an electronic work by people who\r
+agree to be bound by the terms of this agreement.  There are a few\r
+things that you can do with most Project Gutenberg-tm electronic works\r
+even without complying with the full terms of this agreement.  See\r
+paragraph 1.C below.  There are a lot of things you can do with Project\r
+Gutenberg-tm electronic works if you follow the terms of this agreement\r
+and help preserve free future access to Project Gutenberg-tm electronic\r
+works.  See paragraph 1.E below.\r
+\r
+1.C.  The Project Gutenberg Literary Archive Foundation ("the Foundation"\r
+or PGLAF), owns a compilation copyright in the collection of Project\r
+Gutenberg-tm electronic works.  Nearly all the individual works in the\r
+collection are in the public domain in the United States.  If an\r
+individual work is in the public domain in the United States and you are\r
+located in the United States, we do not claim a right to prevent you from\r
+copying, distributing, performing, displaying or creating derivative\r
+works based on the work as long as all references to Project Gutenberg\r
+are removed.  Of course, we hope that you will support the Project\r
+Gutenberg-tm mission of promoting free access to electronic works by\r
+freely sharing Project Gutenberg-tm works in compliance with the terms of\r
+this agreement for keeping the Project Gutenberg-tm name associated with\r
+the work.  You can easily comply with the terms of this agreement by\r
+keeping this work in the same format with its attached full Project\r
+Gutenberg-tm License when you share it without charge with others.\r
+\r
+1.D.  The copyright laws of the place where you are located also govern\r
+what you can do with this work.  Copyright laws in most countries are in\r
+a constant state of change.  If you are outside the United States, check\r
+the laws of your country in addition to the terms of this agreement\r
+before downloading, copying, displaying, performing, distributing or\r
+creating derivative works based on this work or any other Project\r
+Gutenberg-tm work.  The Foundation makes no representations concerning\r
+the copyright status of any work in any country outside the United\r
+States.\r
+\r
+1.E.  Unless you have removed all references to Project Gutenberg:\r
+\r
+1.E.1.  The following sentence, with active links to, or other immediate\r
+access to, the full Project Gutenberg-tm License must appear prominently\r
+whenever any copy of a Project Gutenberg-tm work (any work on which the\r
+phrase "Project Gutenberg" appears, or with which the phrase "Project\r
+Gutenberg" is associated) is accessed, displayed, performed, viewed,\r
+copied or distributed:\r
+\r
+This eBook is for the use of anyone anywhere at no cost and with\r
+almost no restrictions whatsoever.  You may copy it, give it away or\r
+re-use it under the terms of the Project Gutenberg License included\r
+with this eBook or online at www.gutenberg.net\r
+\r
+1.E.2.  If an individual Project Gutenberg-tm electronic work is derived\r
+from the public domain (does not contain a notice indicating that it is\r
+posted with permission of the copyright holder), the work can be copied\r
+and distributed to anyone in the United States without paying any fees\r
+or charges.  If you are redistributing or providing access to a work\r
+with the phrase "Project Gutenberg" associated with or appearing on the\r
+work, you must comply either with the requirements of paragraphs 1.E.1\r
+through 1.E.7 or obtain permission for the use of the work and the\r
+Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or\r
+1.E.9.\r
+\r
+1.E.3.  If an individual Project Gutenberg-tm electronic work is posted\r
+with the permission of the copyright holder, your use and distribution\r
+must comply with both paragraphs 1.E.1 through 1.E.7 and any additional\r
+terms imposed by the copyright holder.  Additional terms will be linked\r
+to the Project Gutenberg-tm License for all works posted with the\r
+permission of the copyright holder found at the beginning of this work.\r
+\r
+1.E.4.  Do not unlink or detach or remove the full Project Gutenberg-tm\r
+License terms from this work, or any files containing a part of this\r
+work or any other work associated with Project Gutenberg-tm.\r
+\r
+1.E.5.  Do not copy, display, perform, distribute or redistribute this\r
+electronic work, or any part of this electronic work, without\r
+prominently displaying the sentence set forth in paragraph 1.E.1 with\r
+active links or immediate access to the full terms of the Project\r
+Gutenberg-tm License.\r
+\r
+1.E.6.  You may convert to and distribute this work in any binary,\r
+compressed, marked up, nonproprietary or proprietary form, including any\r
+word processing or hypertext form.  However, if you provide access to or\r
+distribute copies of a Project Gutenberg-tm work in a format other than\r
+"Plain Vanilla ASCII" or other format used in the official version\r
+posted on the official Project Gutenberg-tm web site (www.gutenberg.net),\r
+you must, at no additional cost, fee or expense to the user, provide a\r
+copy, a means of exporting a copy, or a means of obtaining a copy upon\r
+request, of the work in its original "Plain Vanilla ASCII" or other\r
+form.  Any alternate format must include the full Project Gutenberg-tm\r
+License as specified in paragraph 1.E.1.\r
+\r
+1.E.7.  Do not charge a fee for access to, viewing, displaying,\r
+performing, copying or distributing any Project Gutenberg-tm works\r
+unless you comply with paragraph 1.E.8 or 1.E.9.\r
+\r
+1.E.8.  You may charge a reasonable fee for copies of or providing\r
+access to or distributing Project Gutenberg-tm electronic works provided\r
+that\r
+\r
+- You pay a royalty fee of 20% of the gross profits you derive from\r
+     the use of Project Gutenberg-tm works calculated using the method\r
+     you already use to calculate your applicable taxes.  The fee is\r
+     owed to the owner of the Project Gutenberg-tm trademark, but he\r
+     has agreed to donate royalties under this paragraph to the\r
+     Project Gutenberg Literary Archive Foundation.  Royalty payments\r
+     must be paid within 60 days following each date on which you\r
+     prepare (or are legally required to prepare) your periodic tax\r
+     returns.  Royalty payments should be clearly marked as such and\r
+     sent to the Project Gutenberg Literary Archive Foundation at the\r
+     address specified in Section 4, "Information about donations to\r
+     the Project Gutenberg Literary Archive Foundation."\r
+\r
+- You provide a full refund of any money paid by a user who notifies\r
+     you in writing (or by e-mail) within 30 days of receipt that s/he\r
+     does not agree to the terms of the full Project Gutenberg-tm\r
+     License.  You must require such a user to return or\r
+     destroy all copies of the works possessed in a physical medium\r
+     and discontinue all use of and all access to other copies of\r
+     Project Gutenberg-tm works.\r
+\r
+- You provide, in accordance with paragraph 1.F.3, a full refund of any\r
+     money paid for a work or a replacement copy, if a defect in the\r
+     electronic work is discovered and reported to you within 90 days\r
+     of receipt of the work.\r
+\r
+- You comply with all other terms of this agreement for free\r
+     distribution of Project Gutenberg-tm works.\r
+\r
+1.E.9.  If you wish to charge a fee or distribute a Project Gutenberg-tm\r
+electronic work or group of works on different terms than are set\r
+forth in this agreement, you must obtain permission in writing from\r
+both the Project Gutenberg Literary Archive Foundation and Michael\r
+Hart, the owner of the Project Gutenberg-tm trademark.  Contact the\r
+Foundation as set forth in Section 3 below.\r
+\r
+1.F.\r
+\r
+1.F.1.  Project Gutenberg volunteers and employees expend considerable\r
+effort to identify, do copyright research on, transcribe and proofread\r
+public domain works in creating the Project Gutenberg-tm\r
+collection.  Despite these efforts, Project Gutenberg-tm electronic\r
+works, and the medium on which they may be stored, may contain\r
+"Defects," such as, but not limited to, incomplete, inaccurate or\r
+corrupt data, transcription errors, a copyright or other intellectual\r
+property infringement, a defective or damaged disk or other medium, a\r
+computer virus, or computer codes that damage or cannot be read by\r
+your equipment.\r
+\r
+1.F.2.  LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right\r
+of Replacement or Refund" described in paragraph 1.F.3, the Project\r
+Gutenberg Literary Archive Foundation, the owner of the Project\r
+Gutenberg-tm trademark, and any other party distributing a Project\r
+Gutenberg-tm electronic work under this agreement, disclaim all\r
+liability to you for damages, costs and expenses, including legal\r
+fees.  YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT\r
+LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE\r
+PROVIDED IN PARAGRAPH F3.  YOU AGREE THAT THE FOUNDATION, THE\r
+TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE\r
+LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR\r
+INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH\r
+DAMAGE.\r
+\r
+1.F.3.  LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a\r
+defect in this electronic work within 90 days of receiving it, you can\r
+receive a refund of the money (if any) you paid for it by sending a\r
+written explanation to the person you received the work from.  If you\r
+received the work on a physical medium, you must return the medium with\r
+your written explanation.  The person or entity that provided you with\r
+the defective work may elect to provide a replacement copy in lieu of a\r
+refund.  If you received the work electronically, the person or entity\r
+providing it to you may choose to give you a second opportunity to\r
+receive the work electronically in lieu of a refund.  If the second copy\r
+is also defective, you may demand a refund in writing without further\r
+opportunities to fix the problem.\r
+\r
+1.F.4.  Except for the limited right of replacement or refund set forth\r
+in paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER\r
+WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\r
+WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.\r
+\r
+1.F.5.  Some states do not allow disclaimers of certain implied\r
+warranties or the exclusion or limitation of certain types of damages.\r
+If any disclaimer or limitation set forth in this agreement violates the\r
+law of the state applicable to this agreement, the agreement shall be\r
+interpreted to make the maximum disclaimer or limitation permitted by\r
+the applicable state law.  The invalidity or unenforceability of any\r
+provision of this agreement shall not void the remaining provisions.\r
+\r
+1.F.6.  INDEMNITY - You agree to indemnify and hold the Foundation, the\r
+trademark owner, any agent or employee of the Foundation, anyone\r
+providing copies of Project Gutenberg-tm electronic works in accordance\r
+with this agreement, and any volunteers associated with the production,\r
+promotion and distribution of Project Gutenberg-tm electronic works,\r
+harmless from all liability, costs and expenses, including legal fees,\r
+that arise directly or indirectly from any of the following which you do\r
+or cause to occur: (a) distribution of this or any Project Gutenberg-tm\r
+work, (b) alteration, modification, or additions or deletions to any\r
+Project Gutenberg-tm work, and (c) any Defect you cause.\r
+\r
+\r
+Section  2.  Information about the Mission of Project Gutenberg-tm\r
+\r
+Project Gutenberg-tm is synonymous with the free distribution of\r
+electronic works in formats readable by the widest variety of computers\r
+including obsolete, old, middle-aged and new computers.  It exists\r
+because of the efforts of hundreds of volunteers and donations from\r
+people in all walks of life.\r
+\r
+Volunteers and financial support to provide volunteers with the\r
+assistance they need, is critical to reaching Project Gutenberg-tm's\r
+goals and ensuring that the Project Gutenberg-tm collection will\r
+remain freely available for generations to come.  In 2001, the Project\r
+Gutenberg Literary Archive Foundation was created to provide a secure\r
+and permanent future for Project Gutenberg-tm and future generations.\r
+To learn more about the Project Gutenberg Literary Archive Foundation\r
+and how your efforts and donations can help, see Sections 3 and 4\r
+and the Foundation web page at http://www.pglaf.org.\r
+\r
+\r
+Section 3.  Information about the Project Gutenberg Literary Archive\r
+Foundation\r
+\r
+The Project Gutenberg Literary Archive Foundation is a non profit\r
+501(c)(3) educational corporation organized under the laws of the\r
+state of Mississippi and granted tax exempt status by the Internal\r
+Revenue Service.  The Foundation's EIN or federal tax identification\r
+number is 64-6221541.  Its 501(c)(3) letter is posted at\r
+http://pglaf.org/fundraising.  Contributions to the Project Gutenberg\r
+Literary Archive Foundation are tax deductible to the full extent\r
+permitted by U.S. federal laws and your state's laws.\r
+\r
+The Foundation's principal office is located at 4557 Melan Dr. S.\r
+Fairbanks, AK, 99712., but its volunteers and employees are scattered\r
+throughout numerous locations.  Its business office is located at\r
+809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email\r
+business@pglaf.org.  Email contact links and up to date contact\r
+information can be found at the Foundation's web site and official\r
+page at http://pglaf.org\r
+\r
+For additional contact information:\r
+     Dr. Gregory B. Newby\r
+     Chief Executive and Director\r
+     gbnewby@pglaf.org\r
+\r
+\r
+Section 4.  Information about Donations to the Project Gutenberg\r
+Literary Archive Foundation\r
+\r
+Project Gutenberg-tm depends upon and cannot survive without wide\r
+spread public support and donations to carry out its mission of\r
+increasing the number of public domain and licensed works that can be\r
+freely distributed in machine readable form accessible by the widest\r
+array of equipment including outdated equipment.  Many small donations\r
+($1 to $5,000) are particularly important to maintaining tax exempt\r
+status with the IRS.\r
+\r
+The Foundation is committed to complying with the laws regulating\r
+charities and charitable donations in all 50 states of the United\r
+States.  Compliance requirements are not uniform and it takes a\r
+considerable effort, much paperwork and many fees to meet and keep up\r
+with these requirements.  We do not solicit donations in locations\r
+where we have not received written confirmation of compliance.  To\r
+SEND DONATIONS or determine the status of compliance for any\r
+particular state visit http://pglaf.org\r
+\r
+While we cannot and do not solicit contributions from states where we\r
+have not met the solicitation requirements, we know of no prohibition\r
+against accepting unsolicited donations from donors in such states who\r
+approach us with offers to donate.\r
+\r
+International donations are gratefully accepted, but we cannot make\r
+any statements concerning tax treatment of donations received from\r
+outside the United States.  U.S. laws alone swamp our small staff.\r
+\r
+Please check the Project Gutenberg Web pages for current donation\r
+methods and addresses.  Donations are accepted in a number of other\r
+ways including including checks, online payments and credit card\r
+donations.  To donate, please visit: http://pglaf.org/donate\r
+\r
+\r
+Section 5.  General Information About Project Gutenberg-tm electronic\r
+works.\r
+\r
+Professor Michael S. Hart is the originator of the Project Gutenberg-tm\r
+concept of a library of electronic works that could be freely shared\r
+with anyone.  For thirty years, he produced and distributed Project\r
+Gutenberg-tm eBooks with only a loose network of volunteer support.\r
+\r
+\r
+Project Gutenberg-tm eBooks are often created from several printed\r
+editions, all of which are confirmed as Public Domain in the U.S.\r
+unless a copyright notice is included.  Thus, we do not necessarily\r
+keep eBooks in compliance with any particular paper edition.\r
+\r
+\r
+Most people start at our Web site which has the main PG search facility:\r
+\r
+     http://www.gutenberg.net\r
+\r
+This Web site includes information about Project Gutenberg-tm,\r
+including how to make donations to the Project Gutenberg Literary\r
+Archive Foundation, how to help produce our new eBooks, and how to\r
+subscribe to our email newsletter to hear about new eBooks.\r
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-generic-sessions/CMakeLists.txt
new file mode 100644 (file)
index 0000000..dc9b0f4
--- /dev/null
@@ -0,0 +1,82 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-generic-sessions)
+set(SRCS minimal-http-server-generic-sessions.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
+require_lws_config(LWS_WITH_GENERIC_SESSIONS 1 requirements)
+require_lws_config(LWS_WITH_LIBUV 1 requirements)
+require_lws_config(LWS_WITH_PLUGINS 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/README.md b/minimal-examples/http-server/minimal-http-server-generic-sessions/README.md
new file mode 100644 (file)
index 0000000..976aea6
--- /dev/null
@@ -0,0 +1,26 @@
+# lws minimal http server with generic-sessions
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-tls
+[2018/03/20 13:23:13:0131] USER: LWS minimal http server TLS | visit https://localhost:7681
+[2018/03/20 13:23:13:0142] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/20 13:23:13:0142] NOTICE:  Using SSL mode
+[2018/03/20 13:23:13:0146] NOTICE:  SSL ECDH curve 'prime256v1'
+[2018/03/20 13:23:13:0146] NOTICE:  HTTP2 / ALPN enabled
+[2018/03/20 13:23:13:0195] NOTICE: lws_tls_client_create_vhost_context: doing cert filepath localhost-100y.cert
+[2018/03/20 13:23:13:0195] NOTICE: Loaded client cert localhost-100y.cert
+[2018/03/20 13:23:13:0195] NOTICE: lws_tls_client_create_vhost_context: doing private key filepath
+[2018/03/20 13:23:13:0196] NOTICE: Loaded client cert private key localhost-100y.key
+[2018/03/20 13:23:13:0196] NOTICE: created client ssl context for default
+[2018/03/20 13:23:14:0207] NOTICE:    vhost default: cert expiry: 730459d
+```
+
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-generic-sessions/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-generic-sessions/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/minimal-http-server-generic-sessions.c b/minimal-examples/http-server/minimal-http-server-generic-sessions/minimal-http-server-generic-sessions.c
new file mode 100644 (file)
index 0000000..f4e11e6
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * lws-minimal-http-server-generic-sessions
+ *
+ * Copyright (C) 2019 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates setting up and using generic sessions
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+struct lws_context *context;
+
+static const struct lws_protocol_vhost_options
+   pvo_mm1 = {
+       NULL, NULL, "message-db", (void *)"/var/www/sessions/messageboard.sqlite3"
+}, pvo_m1 = {
+       NULL, &pvo_mm1, "protocol-lws-messageboard", ""
+},
+
+   pvo13 = {
+       NULL, NULL, "email-confirm-url-base", (void *)"https://localhost:7681/"
+}, pvo12 = {
+       &pvo13, NULL, "urlroot", (void *)"https://127.0.0.1:7681/"
+}, pvo11 = {
+       &pvo12, NULL, "email-contact-person", (void *)"andy@warmcat.com"
+}, pvo10 = {
+       &pvo11, NULL, "email-helo", (void *)"warmcat.com"
+}, pvo9 = {
+       &pvo10, NULL, "email-expire", (void *)"3600"
+}, pvo8 = {
+       &pvo9,  NULL, "email-smtp-ip", (void *)"127.0.0.1"
+}, pvo7 = {
+       &pvo8,  NULL, "email-from", (void *)"noreply@warmcat.com"
+}, pvo6 = {
+       &pvo7,  NULL, "confounder", (void *)"some kind of secret confounder"
+}, pvo5 = {
+       &pvo6,  NULL, "timeout-anon-idle-secs", (void *)"1200"
+}, pvo4 = {
+       &pvo5,  NULL, "timeout-idle-secs", (void *)"6000"
+}, pvo3 = {
+       &pvo4,  NULL, "session-db", (void *)"/var/www/sessions/lws.sqlite3"
+}, pvo2 = {
+       &pvo3, NULL, "admin-password-sha256",
+       (void *)"25d08521d996bad92605f5a40fe71179dc968e70f669cb1db6190dcd53258200"      /* pvo value */
+}, pvo1 = {
+       &pvo2, NULL, "admin-user", (void *)"admin"
+}, pvo = {
+       &pvo_m1, &pvo1, "protocol-generic-sessions", ""
+},
+
+   interpret1 = {
+       NULL, NULL, ".js", "protocol-lws-messageboard"
+},
+
+   pvo_hsbph[] = {{
+       NULL, NULL,             "referrer-policy:", "no-referrer"
+}, {
+       &pvo_hsbph[0], NULL,    "x-xss-protection:", "1; mode=block"
+}, {
+       &pvo_hsbph[1], NULL,    "x-content-type-options:", "nosniff"
+}, {
+       &pvo_hsbph[2], NULL,    "content-security-policy:",
+                               "default-src 'self'; "
+                               "img-src https://www.gravatar.com 'self' data: ; "
+                               "script-src 'self'; "
+                               "font-src 'self'; "
+                               "style-src 'self'; "
+                               "connect-src 'self'; "
+                               "frame-ancestors 'self'; "
+                               "base-uri 'none'; "
+                               "form-action  'self';"
+}};
+
+ static const struct lws_http_mount mount2 = {
+       /* .mount_next */               NULL,   /* linked-list "next" */
+       /* .mountpoint */               "/needadmin",           /* mountpoint URL */
+       /* .origin */                   "./mount-origin/needadmin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 "protocol-lws-messageboard",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                &interpret1,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                7,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+ };
+
+ static const struct lws_http_mount mount1 = {
+       /* .mount_next */               &mount2,        /* linked-list "next" */
+       /* .mountpoint */               "/needauth",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin/needauth", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 "protocol-lws-messageboard",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                &interpret1,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                5,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+ };
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount1,        /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 "protocol-lws-messageboard",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                &interpret1,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       lws_context_destroy(context);
+
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p, *plugin_dirs[] = {
+               "/usr/local/share/libwebsockets-test-server/plugins",
+               NULL };
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server TLS | visit https://localhost:7681\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+       info.plugin_dirs = plugin_dirs;
+       info.pvo = &pvo;
+
+       if (lws_cmdline_option(argc, argv, "-h"))
+               info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.headers = &pvo_hsbph[3];
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6fdd6bf
--- /dev/null
@@ -0,0 +1,11 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/admin-login.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/admin-login.html
new file mode 100644 (file)
index 0000000..113df9c
--- /dev/null
@@ -0,0 +1,5 @@
+<html>
+This is an example destination that will appear after successful Admin login.
+
+This URL cannot be served if you're not logged in as admin.
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/example.js b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/example.js
new file mode 100644 (file)
index 0000000..1606ea0
--- /dev/null
@@ -0,0 +1,22 @@
+document.addEventListener("DOMContentLoaded", function() {
+
+       var transport_protocol = "";
+       
+       if ( performance && performance.timing.nextHopProtocol ) {
+           transport_protocol = performance.timing.nextHopProtocol;
+       } else if ( window.chrome && window.chrome.loadTimes ) {
+           transport_protocol = window.chrome.loadTimes().connectionInfo;
+       } else {
+       
+         var p = performance.getEntriesByType("resource");
+         for (var i=0; i < p.length; i++) {
+               var value = "nextHopProtocol" in p[i];
+                 if (value)
+                   transport_protocol = p[i].nextHopProtocol;
+           }
+          }
+          
+          if (transport_protocol == "h2")
+               document.getElementById("transport").innerHTML = "<img src=\"/http2.png\">";
+       }
+}, false);
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/failed-login.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/failed-login.html
new file mode 100644 (file)
index 0000000..9ab065b
--- /dev/null
@@ -0,0 +1,3 @@
+<html>
+This is an example destination that will appear after a failed login
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/http2.png
new file mode 100644 (file)
index 0000000..439bfa4
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/http2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/index.html
new file mode 100644 (file)
index 0000000..0695d5d
--- /dev/null
@@ -0,0 +1,57 @@
+<html>
+ <head>
+ <meta charset="UTF-8"> 
+  <script src="/lws-common.js"></script>
+  <link rel="stylesheet" type="text/css" href="lwsgs.css"/>
+  <script src="lwsgs.js"></script>
+  </head>
+
+  <body class="seats">
+    <table class="lwsgs">
+     <tr>
+      <td class="logo">
+       <img src="lwsgs.svg">
+      </td>
+      <td class="">
+       <div id=lwsgs class="lwsgs"></div>
+      </td>
+            <td class="rlogo">
+       <img src="strict-csp.svg">
+      </td>
+     </tr>
+     
+     <tr><td colspan="3" class="h99">
+        <table class="c100"><tr>
+        <td class="c">
+       <span id="nolog" class="group2">
+       This is a demo application for lws generic-sessions.<br><br>
+       It's a simple messageboard.<br><br>
+       What's interesting about it is there is <b>no serverside scripting</b>,<br>
+       instead client js makes a wss:// connection back to the server<br>
+       and then reacts to JSON from the ws protocol.  Sessions stuff is <br>
+       handled by lws generic sessions, making the <a href="https://libwebsockets.org/git/libwebsockets/tree/plugins/generic-sessions/protocol_generic_sessions.c">actual<br>
+       test application</a> <a href="https://libwebsockets.org/git/libwebsockets/tree/plugins/generic-sessions/protocol_lws_messageboard.c">very small</a>.<br><br>
+       And because it's natively websocket, it's naturally connected<br>
+       for dynamic events and easy to maintain.
+       <br><br>
+       Register / Login at the top right to see and create new messages.
+       </span>
+       <span id="logged" class="group2">
+       <div id="newmsg">
+               <form action="/msg" method="post" target="hidden">
+               New message<br>
+         <textarea id="msg" placeholder="type your message here" cols="40" rows="5" name="msg"></textarea><br>
+               <input type="submit" id="send" name="send" disabled=1>
+               </form>
+       </div>
+       </span>
+       <div id="dmessages">
+        <span id="messages" ></span>
+       </div>
+       <span id="debug" class="group2"></span>
+       </td></tr></table>
+     </td></tr>
+    </table>
+   <iframe name="hidden" class="hidden"></iframe>
+ </body>
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lws-common.js b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lws-common.js
new file mode 100644 (file)
index 0000000..5d56ca2
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * This section around grayOut came from here:
+ * http://www.codingforums.com/archive/index.php/t-151720.html
+ * Assumed public domain
+ *
+ * Init like this in your main html script, this also reapplies the gray
+ *
+ *    lws_gray_out(true,{'zindex':'499'});
+ *
+ * To remove the gray
+ *
+ *    lws_gray_out(false);
+ *
+ */
+
+function gsize(ptype)
+{
+       var h = document.compatMode === "CSS1Compat" &&
+               !window.opera ?
+                       document.documentElement.clientHeight :
+                                               document.body.clientHeight;
+       var w = document.compatMode === "CSS1Compat" &&
+               !window.opera ? 
+                       document.documentElement.clientWidth :
+                                               document.body.clientWidth;
+       var pageWidth, pageHeight, t;
+
+       if (document.body && 
+                   (document.body.scrollWidth || document.body.scrollHeight)) {
+               t = document.body.scrollWidth;
+               pageWidth = (w > t) ? ("" + w + "px") : ("" + (t) + "px");
+               t = document.body.scrollHeight;
+               pageHeight = (h > t) ? ("" + h + "px") : ("" + (t) + "px");
+       } else if (document.body.offsetWidth) {
+               t = document.body.offsetWidth;
+               pageWidth = (w > t) ? ("" + w + "px") : ("" + (t) + "px");
+               t = document.body.offsetHeight;
+               pageHeight =(h > t) ? ("" + h + "px") : ("" + (t) + "px");
+       } else {
+               pageWidth = "100%";
+               pageHeight = "100%";
+       }
+       return (ptype === 1) ? pageWidth : pageHeight;
+}
+
+function addEvent( obj, type, fn ) {
+       if ( obj.attachEvent ) {
+               obj["e" + type + fn] = fn;
+               obj[type+fn] = function() { obj["e" + type + fn]( window.event );};
+               obj.attachEvent("on" + type, obj[type + fn]);
+       } else
+               obj.addEventListener(type, fn, false);
+}
+
+function removeEvent( obj, type, fn ) {
+       if ( obj.detachEvent ) {
+               obj.detachEvent("on" + type, obj[type + fn]);
+               obj[type + fn] = null;
+       } else
+               obj.removeEventListener(type, fn, false);
+}
+
+function lws_gray_out(vis, _options) {
+
+       var options = _options || {};
+       var zindex = options.zindex || 50;
+       var opacity = options.opacity || 70;
+       var opaque = (opacity / 100);
+       var bgcolor = options.bgcolor || "#000000";
+       var dark = document.getElementById("darkenScreenObject");
+
+       if (!dark) {
+               var tbody = document.getElementsByTagName("body")[0];
+               var tnode = document.createElement("div");
+               tnode.style.position = "absolute";
+               tnode.style.top = "0px";
+               tnode.style.left = "0px";
+               tnode.style.overflow = "hidden";
+               tnode.style.display ="none";
+               tnode.id = "darkenScreenObject";
+               tbody.appendChild(tnode);
+               dark = document.getElementById("darkenScreenObject");
+       }
+       if (vis) {
+               dark.style.opacity = opaque;
+               dark.style.MozOpacity = opaque;
+               // dark.style.filter ='alpha(opacity='+opacity+')';
+               dark.style.zIndex = zindex;
+               dark.style.backgroundColor = bgcolor;
+               dark.style.width = gsize(1);
+               dark.style.height = gsize(0);
+               dark.style.display = "block";
+               addEvent(window, "resize",
+                       function() {
+                               dark.style.height = gsize(0);
+                               dark.style.width = gsize(1);
+                       }
+               );
+       } else {
+               dark.style.display = "none";
+               removeEvent(window, "resize",
+                       function() {
+                               dark.style.height = gsize(0);
+                               dark.style.width = gsize(1);
+                       }
+               );
+       }
+}
+
+/*
+ * end of grayOut related stuff
+ */
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+function lws_san(s)
+{
+       if (s.search("<") !== -1)
+               return "invalid string";
+       
+       return s;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs-logo.png b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs-logo.png
new file mode 100644 (file)
index 0000000..723a124
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs-logo.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs.css b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs.css
new file mode 100644 (file)
index 0000000..907851f
--- /dev/null
@@ -0,0 +1,144 @@
+.body { font-size: 12px }
+.gstitle { font-size: 18px }
+
+.group1 {
+       vertical-align:middle;
+       text-align:center;
+       background:#f0f0e0; 
+       padding:12px;
+       border-radius:10px;
+}
+.group2 {
+       display:block;
+       vertical-align:middle;
+       font-size: 22px;
+       text-align:center;
+       margin:auto;
+       align:center;
+       background-color: rgba(255, 255, 255, 0.8);
+       padding:12px;
+       border-radius:10px;
+}
+       
+body {
+       background-color: rgba(205, 205, 205, 1);
+}
+
+div.lwsgs {
+       z-index: 3;
+       text-align:right;
+       background-color: rgba(255, 255, 255, 0.8);
+}
+
+table.lwsgs {
+       width:100%;
+       height:100%;
+       transition: max-height 2s;
+}
+table.c100 {
+       text-align:center;
+       width:100%;
+}
+
+table.r {
+       vertical-align:top;
+       text-align:right;
+}
+
+table.l {
+       vertical-align:top;
+       text-align:left;
+}
+
+table.fixed {
+       table-layout: fixed;
+}
+
+td.logo {
+       vertical-align:top;
+       text-align:left;
+       width:200px
+}
+
+td.rlogo {
+       vertical-align:top;
+       text-align:right
+}
+
+td.lwsgs {
+       vertical-align:top;
+       float:right;
+}
+
+td.h99 {
+       height:99%;
+       vertical-align:middle;
+}
+
+td.c {
+       margin:auto;
+       align:center
+}
+
+td.tac {
+       text-align:center
+}
+
+td.ava {
+       display:inline-block;
+       vertical-align:top;
+       word-wrap:break-word;
+}
+
+iframe.hidden {
+       display:none;
+}
+
+div.hidden {
+       display:none;
+}
+
+div.hiddenr {
+       display:none;
+       text-align:right;
+}
+
+input {
+       margin: 2px;
+       padding: 2px;
+}
+
+input.em {
+       margin: 4px;
+       font-weight:bold;
+}
+
+input.wide {
+       margin: 6px;
+       padding: 6px;
+}
+
+input.hidden {
+       display: none;
+}
+
+form.r {
+       text-align:right;
+}
+
+span.bad {
+       color: red;
+}
+
+span.small {
+       font-size:8pt;
+}
+
+img.av {
+       width: 64px;
+       height: 64px;
+}
+
+.green {
+       color: green;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs.js b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/lwsgs.js
new file mode 100644 (file)
index 0000000..059ad11
--- /dev/null
@@ -0,0 +1,637 @@
+<!-- lwsgs rewrites the below $vars $v $v into the correct values on the fly -->
+
+var lwsgs_user = "$lwsgs_user";
+var lwsgs_auth = "$lwsgs_auth";
+var lwsgs_email = "$lwsgs_email";
+
+var lwsgs_html = '\
+       <div id="dlogin" class="hidden"> \
+        <form action="lwsgs-login" method="post"> \
+         <input type="hidden" name="admin" value="needadmin/admin-login.html"> \
+         <input type="hidden" name="good" value="index.html"> \
+         <input type="hidden" name="bad" value="failed-login.html"> \
+         <input type="hidden" name="forgot-good" value="sent-forgot-ok.html"> \
+         <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
+         <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
+         <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
+        <table class="r">\
+          <tr>\
+           <td>User Name\
+            <input type="text" size="10" id="username" name="username"></td>\
+           <td>Password\
+            <input type="password" id="password" size="10" name="password"><div id="pw1"></div></td>\
+            </tr><tr>\
+          <td colspan="2" class="c">\
+       <input type="submit" id="login" name="login" value="Login" class="em">\
+      &nbsp;<input type="submit" id="forgot" name="forgot" value="Forgot password">\
+           &nbsp;<input id="doreg" type="button" value="Sign up"></td>\
+          </tr>\
+         </table>\
+        </form>\
+       </div>\
+\
+       <div id="dlogout" class="hiddenr">\
+        <form action="lwsgs-logout" method="post" class="r">\
+         <input type="hidden" name="good" value="index.html">\
+         <table class="r">\
+          <tr><td><table><tr><td><span id=grav></span></td></tr><tr><td>\
+       <a href="#" id="clink">\
+       <span id="curuser"></span></a></td></tr></table></td>\
+           <td class="tac"><input type="submit" name="logout" value="Logout"></td>\
+          </tr></table></td></tr>\
+         </table>\
+        </form></div>\
+\
+       <div id="dregister" class="hidden">\
+        <form action="lwsgs-login" method="post">\
+         <input type="hidden" name="admin" value="needadmin/admin-login.html">\
+         <input type="hidden" name="good" value="successful-login.html">\
+         <input type="hidden" name="bad" value="failed-login.html">\
+         <input type="hidden" name="reg-good" value="post-register-ok.html">\
+         <input type="hidden" name="reg-bad" value="post-register-fail.html">\
+         <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\
+         <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
+         <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
+         <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
+         <table class="l">\
+            <tr>\
+             <td colspan=2 align=center>\
+               <span id="curuser"></span>\
+              <b>Please enter your details to register</b>:\
+             </td>\
+            </tr>\
+           <tr><td align=right>\
+            User Name:</td>\
+            <td><input type="text" size="10" id="rusername" name="username" &nbsp;<span id=uchk></span></td>\
+           </tr>\
+           <tr>\
+            <td align=right>Password:</td>\
+            <td><input type="password" size="10" id="rpassword" name="password">&nbsp;<span id="rpw1"></span></td>\
+           </tr>\
+           <tr>\
+           </tr>\
+           <tr>\
+            <td align=right><span id="pw2">Password (again):</span></td>\
+            <td><input type="password" size="10" id="password2" name="password2">&nbsp;<span id="match"></span></td>\
+           </tr>\
+           <tr>\
+            <td align=right>Email:</td>\
+            <td><input type="email" size="10" id="email" name="email"\
+                 placeholder="me@example.com" &nbsp;<span id=echk></span></td>\
+           </tr>\
+           <tr>\
+            <td colspan=2 align=center>\
+<input type="submit" id="register" name="register" value="Register" >\
+<input type="submit" id="rforgot" name="forgot" value="Forgot Password" class="hidden">\
+<input type="button" id="cancel" name="cancel" value="Cancel">\
+            </td>\
+           </tr>\
+         </table>\
+        </form>\
+       </div>\
+       \
+       <div id="dchange" class="hidden">\
+        <form action="lwsgs-change" method="post">\
+         <input type="hidden" id="cusername" name="username">\
+         <input type="hidden" name="admin" value="needadmin/admin-login.html">\
+         <input type="hidden" name="good" value="index.html">\
+         <input type="hidden" name="bad" value="failed-login.html">\
+         <input type="hidden" name="reg-good" value="post-register-ok.html">\
+         <input type="hidden" name="reg-bad" value="post-register-fail.html">\
+         <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\
+         <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
+         <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
+         <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
+         <table class="l">\
+            <tr>\
+             <td colspan=2 align=center>\
+               <span id="ccuruser"></span>\
+              <b>Please enter your details to change</b>:\
+             </td>\
+            </tr>\
+           <tr><td align=right id="ccurpw_name">\
+            Current Password:</td>\
+            <td><input type="password" size="10" id="ccurpw" name="curpw"\
+                >&nbsp;<span id=cuchk></span></td>\
+           </tr>\
+           <tr>\
+            <td align=right>Password:</td>\
+            <td><input type="password" size="10" id="cpassword" name="password"\
+                 &nbsp;<span id="cpw1"></span></td>\
+           </tr>\
+           <tr>\
+            <td align=right><span id="pw2">Password (again)</span></td>\
+            <td><input type="password" size="10" id="cpassword2" name="password2"\
+                >&nbsp;<span id="cmatch"></span></td>\
+           </tr>\
+       <!-- not supported yet\
+           <tr>\
+            <td align=right id="cemail_name">Email:</td>\
+            <td><input type="email" size="10" id="cemail" name="email"\
+                 placeholder="?" \
+                 &nbsp;<span id=cechk></span></td>\
+           </tr> -->\
+           <tr>\
+            <td colspan=2 align=center>\
+             <input type="submit" id="change" name="change"\
+              value="Change" class="wide">\
+             <input type="submit" id="cforgot" name="forgot"\
+              value="Forgot Password" class="wide hidden">\
+             <input type="button" id="cancel2" name="cancel"\
+              value="Cancel" class="wide">\
+            </td>\
+           </tr>\
+           <tr>\
+            <td colspan=2>\
+             <input type="checkbox" id="showdel" name="showdel"\
+              > Show Delete&nbsp;\
+             <input type="submit" id="delete" name="delete" \
+              value="Delete Account" class="wide hidden">\
+            </td>\
+           </tr>\
+         </table>\
+        </form>\
+       </div>\
+       \
+       <div id="dadmin" class="hidden">\
+         Admin settings TBD\
+       </div>\
+';
+
+/*-- this came from
+  -- https://raw.githubusercontent.com/blueimp/JavaScript-MD5/master/js/md5.min.js
+  -- under MIT license */
+!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
+
+if (lwsgs_user.substring(0, 1) == "$") {
+       alert("lwsgs.js: lws generic sessions misconfigured and not providing vars");
+}
+function lwsgs_san(s)
+{
+       if (s.search("<") != -1)
+               return "invalid string";
+       
+       return s;
+}
+
+function lwsgs_update()
+{
+       var en_login = 1, en_forgot = 1;
+       
+       if (document.getElementById('password').value.length &&
+           document.getElementById('password').value.length < 8)
+               en_login = 0;
+       
+       if (!document.getElementById('username').value ||
+           !document.getElementById('password').value)
+               en_login = 0;
+       
+       if (!document.getElementById('username').value ||
+            document.getElementById('password').value)
+               en_forgot = 0;
+       
+       document.getElementById('login').disabled = !en_login;
+       document.getElementById('forgot').disabled = !en_forgot;
+       
+       if (lwsgs_user)
+               document.getElementById("curuser").innerHTML = lwsgs_san(lwsgs_user);
+
+       if (lwsgs_user === "")
+               document.getElementById("dlogin").style.display = "inline";
+       else
+               document.getElementById("dlogout").style.display = "inline";
+ }
+
+function lwsgs_open_registration()
+{
+       document.getElementById("dadmin").style.display = "none";
+       document.getElementById("dlogin").style.display = "none";
+       document.getElementById("dlogout").style.display = "none";
+       document.getElementById("dchange").style.display = "none";
+       document.getElementById("dregister").style.display = "inline";
+}
+
+function lwsgs_cancel_registration()
+{
+       document.getElementById("dadmin").style.display = "none";
+       document.getElementById("dregister").style.display = "none";
+       document.getElementById("dchange").style.display = "none";
+
+       if (lwsgs_user === "")
+               document.getElementById("dlogin").style.display = "inline";
+       else
+               document.getElementById("dlogout").style.display = "inline";
+}
+
+function lwsgs_select_change()
+{
+       document.getElementById("dlogin").style.display = "none";
+       document.getElementById("dlogout").style.display = "none";
+       document.getElementById("dregister").style.display = "none";
+       if (lwsgs_auth & 2) {
+               document.getElementById("dadmin").style.display = "inline";
+               document.getElementById("dchange").style.display = "none";
+       } else {
+               document.getElementById("dadmin").style.display = "none";
+               document.getElementById("dchange").style.display = "inline";
+       }
+
+       event.preventDefault()
+}
+
+var lwsgs_user_check = '0';
+var lwsgs_email_check = '0';
+
+function lwsgs_rupdate()
+{
+       var en_register = 1, en_forgot = 0;
+
+       if (document.getElementById('rpassword').value ==
+           document.getElementById('password2').value) {
+               if (document.getElementById('rpassword').value.length)
+                       document.getElementById('match').innerHTML = 
+                               "<b class=\"green\">\u2713</b>";
+               else
+                       document.getElementById('match').innerHTML = "";
+               document.getElementById('pw2').style = "";
+       } else {
+               if (document.getElementById('password2').value ||
+                   document.getElementById('email').value) { // ie, he is filling in "register" path and cares
+                       document.getElementById('match').innerHTML =
+                               "<span class=\"bad\">\u2718 <b>Passwords do not match</b></span>";
+               } else
+                       document.getElementById('match').innerHTML =
+                               "<span class=\"bad\">\u2718 Passwords do not match</span>";
+
+               en_register = 0;
+       }
+
+       if (document.getElementById('rpassword').value.length &&
+           document.getElementById('rpassword').value.length < 8) {
+               en_register = 0;
+               document.getElementById('rpw1').innerHTML = "Need 8 chars";
+       } else
+               if (document.getElementById('rpassword').value.length)
+                       document.getElementById('rpw1').innerHTML = "<b class=\"green\">\u2713</b>";
+               else
+                       document.getElementById('rpw1').innerHTML = "";
+
+       if (!document.getElementById('rpassword').value ||
+           !document.getElementById('password2').value ||
+           !document.getElementById('rusername').value ||
+           !document.getElementById('email').value ||
+           lwsgs_email_check === '1'||
+           lwsgs_user_check === '1')
+               en_register = 0;
+
+       document.getElementById('register').disabled = !en_register;
+       document.getElementById('rpassword').disabled = lwsgs_user_check === '1';
+       document.getElementById('password2').disabled = lwsgs_user_check === '1';
+       document.getElementById('email').disabled = lwsgs_user_check === '1';
+
+       if (lwsgs_user_check === '0') {
+               var uc = document.getElementById('uchk');
+
+               if (uc) {
+                       if (document.getElementById('rusername').value)
+                               uc.innerHTML = "<b class=\"green\">\u2713</b>";
+                       else
+                               uc.innerHTML = "";
+               }
+       } else {
+               if (document.getElementById('uchk'))
+                       ocument.getElementById('uchk').innerHTML = "<b class=\"red\">\u2718 Already registered</b>";
+               en_forgot = 1;
+       }
+
+       if (lwsgs_email_check === '0') {
+               var ec = document.getElementById('echk');
+
+               if (ec) {
+                       if (document.getElementById('email').value)
+                               ec.innerHTML = "<b class=\"green\">\u2713</b>";
+                       else
+                               ec.innerHTML = "";
+               }
+       } else {
+               if (document.getElementById('echk'))
+                       document.getElementById('echk').innerHTML = "<b class=\"red\">\u2718 Already registered</b>";
+               en_forgot = 1;
+       }
+
+       if (en_forgot)
+               document.getElementById('rforgot').style.display = "inline";
+       else
+               document.getElementById('rforgot').style.display = "none";
+
+       if (lwsgs_user_check === '1')
+               op = '0.5';
+       else
+               op = '1.0';
+       document.getElementById('rpassword').style.opacity = op;
+       document.getElementById('password2').style.opacity = op;
+       document.getElementById('email').style.opacity = op;
+ }
+
+function lwsgs_cupdate()
+{
+       var en_change = 1, en_forgot = 1, pwok = 1;
+       
+       if (lwsgs_auth & 8) {
+               document.getElementById('ccurpw').style.display = "none";
+               document.getElementById('ccurpw_name').style.display = "none";
+       } else {
+               if (!document.getElementById('ccurpw').value ||
+                   document.getElementById('ccurpw').value.length < 8) {
+                       en_change = 0;
+                       pwok = 0;
+                       document.getElementById('cuchk').innerHTML = "<b class=\"red\">\u2718</b>";
+               } else {
+                       en_forgot = 0;
+                       document.getElementById('cuchk').innerHTML = "";
+               }
+               document.getElementById('ccurpw').style.display = "inline";
+               document.getElementById('ccurpw_name').style.display = "inline";
+       }
+
+       if (document.getElementById('cpassword').value ==
+           document.getElementById('cpassword2').value) {
+               if (document.getElementById('cpassword').value.length)
+                       document.getElementById('cmatch').innerHTML = "<b class=\"green\">\u2713</b>";
+               else
+                       document.getElementById('cmatch').innerHTML = "";
+               document.getElementById('pw2').style = "";
+       } else {
+               if (document.getElementById('cpassword2').value //||
+                   //document.getElementById('cemail').value
+               ) { // ie, he is filling in "register" path and cares
+                       document.getElementById('cmatch').innerHTML =
+                               "<span class=\"red\">\u2718 <b>Passwords do not match</b></span>";
+               } else
+                       document.getElementById('cmatch').innerHTML = "<span class=\"red\">\u2718 Passwords do not match</span>";
+
+               en_change = 0;
+       }
+
+       if (document.getElementById('cpassword').value.length &&
+           document.getElementById('cpassword').value.length < 8) {
+               en_change = 0;
+               document.getElementById('cpw1').innerHTML = "Need 8 chars";
+       } else {
+               var cpw = document.getElementById('cpw1');
+
+               if (cpw) {
+                       if (document.getElementById('cpassword').value.length)
+                               cpw.innerHTML = "<b class=\"green\">\u2713</b>";
+                       else
+                               cpw.innerHTML = "";
+               }
+       }
+
+       if (!document.getElementById('cpassword').value ||
+           !document.getElementById('cpassword2').value ||
+           pwok === 0)
+               en_change = 0;
+       
+       if (document.getElementById('showdel').checked)
+               document.getElementById('delete').style.display = "inline";
+       else
+               document.getElementById('delete').style.display = "none";
+
+       document.getElementById('change').disabled = !en_change;
+       document.getElementById('cpassword').disabled = pwok === 0;
+       document.getElementById('cpassword2').disabled = pwok === 0;
+       document.getElementById('showdel').disabled = pwok === 0;
+       document.getElementById('delete').disabled = pwok === 0;
+       //document.getElementById('cemail').disabled = pwok === 0;
+
+       /*
+       if (lwsgs_auth & 8) {
+               document.getElementById('cemail').style.display = "none";
+               document.getElementById('cemail_name').style.display = "none";
+       } else {
+               document.getElementById('cemail').style.display = "inline";
+               document.getElementById('cemail_name').style.display = "inline";
+               if (lwsgs_email_check === '0'  &&
+                   document.getElementById('cemail').value != lwsgs_email) {
+                       if (document.getElementById('cemail').value)
+                               document.getElementById('cechk').innerHTML = "<b style=\"color:green\">\u2713</b>";
+                       else
+                               document.getElementById('cechk').innerHTML = "";
+               } else {
+                       document.getElementById('cechk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>";
+                       en_forgot = 1;
+               }
+       } */
+       
+       if (lwsgs_auth & 8)
+               en_forgot = 0;
+
+       if (en_forgot)
+               document.getElementById('cforgot').style.display = "inline";
+       else
+               document.getElementById('cforgot').style.display = "none";
+
+       if (pwok === 0)
+               op = '0.5';
+       else
+               op = '1.0';
+       document.getElementById('cpassword').style.opacity = op;
+       document.getElementById('cpassword2').style.opacity = op;
+       // document.getElementById('cemail').style.opacity = op;
+ }
+
+function lwsgs_check_user()
+{
+    var xmlHttp = new XMLHttpRequest();
+    xmlHttp.onreadystatechange = function() { 
+        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
+            lwsgs_user_check = xmlHttp.responseText;
+           lwsgs_rupdate();
+        }
+    }
+    xmlHttp.open("GET", "lwsgs-check/username="+document.getElementById('rusername').value, true);
+    xmlHttp.send(null);
+}
+
+function lwsgs_check_email(id)
+{
+    var xmlHttp = new XMLHttpRequest();
+    xmlHttp.onreadystatechange = function() { 
+        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
+            lwsgs_email_check = xmlHttp.responseText;
+           lwsgs_rupdate();
+        }
+    }
+    xmlHttp.open("GET", "lwsgs-check/email="+document.getElementById(id).value, true);
+    xmlHttp.send(null);
+}
+
+function rupdate_user()
+{
+       lwsgs_rupdate();
+       lwsgs_check_user();
+}
+
+function rupdate_email()
+{
+       lwsgs_rupdate();
+       lwsgs_check_email('email');
+}
+
+function cupdate_email()
+{
+       lwsgs_cupdate();
+       lwsgs_check_email('cemail');
+}
+
+
+function lwsgs_initial()
+{
+       document.getElementById('lwsgs').innerHTML = lwsgs_html;
+
+       if (lwsgs_user) {
+               document.getElementById("curuser").innerHTML =
+                       "currently logged in as " + lwsgs_san(lwsgs_user) + "</br>";
+
+               document.getElementById("ccuruser").innerHTML =
+                 "<span class=\"gstitle\">Login settings for " +
+                 lwsgs_san(lwsgs_user) + "</span></br>";
+       }
+
+       document.getElementById('username').oninput = lwsgs_update;
+       document.getElementById('username').onchange = lwsgs_update;
+       document.getElementById('password').oninput = lwsgs_update;
+       document.getElementById('password').onchange = lwsgs_update;
+       document.getElementById('doreg').onclick = lwsgs_open_registration;
+       document.getElementById('clink').onclick = lwsgs_select_change;
+       document.getElementById('cancel').onclick =lwsgs_cancel_registration;
+       document.getElementById('cancel2').onclick =lwsgs_cancel_registration;
+       document.getElementById('rpassword').oninput = lwsgs_rupdate;
+       document.getElementById('password2').oninput = lwsgs_rupdate;
+       document.getElementById('rusername').oninput = rupdate_user;
+       document.getElementById('email').oninput  = rupdate_email;
+       document.getElementById('ccurpw').oninput = lwsgs_cupdate;
+       document.getElementById('cpassword').oninput = lwsgs_cupdate;
+       document.getElementById('cpassword2').oninput = lwsgs_cupdate;
+<!--   document.getElementById('cemail').oninput = cupdate_email;-->
+       document.getElementById('showdel').onchange = lwsgs_cupdate;
+
+       if (lwsgs_email)
+               document.getElementById('grav').innerHTML =
+                       "<img class='av' " +
+                       "src=\"https://www.gravatar.com/avatar/" +
+                       md5(lwsgs_email) +
+                       "?d=identicon\">";
+       //if (lwsgs_email)
+               //document.getElementById('cemail').placeholder = lwsgs_email;
+       document.getElementById('cusername').value = lwsgs_user;
+       lwsgs_update();
+       lwsgs_cupdate();
+}
+
+window.addEventListener("load", function() {
+       lwsgs_initial();
+       document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline-block";
+       document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline-block";
+
+       document.getElementById("msg").onkeyup = mupd;
+       document.getElementById("msg").onchange = mupd;
+
+       var ws;
+
+       function mb_format(s)
+       {
+               var r = "", n, wos = 0;
+               
+               for (n = 0; n < s.length; n++) {
+                       if (s[n] == ' ')
+                               wos = 0;
+                       else {
+                               wos++;
+                               if (wos === 40) {
+                                       wos = 0;
+                                       r = r + ' ';
+                               }
+                       }
+                       if (s[n] == '<') {
+                               r = r + "&lt;";
+                               continue;
+                       }
+                       if (s[n] == '\n') {
+                               r = r + "<br>";
+                               continue;
+                       }
+                               
+                       r = r + s[n];
+               }
+               
+               return r;
+       }
+
+       function add_div(n, m)
+       {
+               var q = document.getElementById(n);
+               var d = new Date(m.time * 1000), s = d.toTimeString(), t;
+               
+               t = s.indexOf('(');
+               if (t)
+                       s = s.substring(0, t);
+               
+               q.innerHTML = "<br><div class=\"group2\"><table class=\"fixed\"><tr><td>" +
+                       "<img class=\"av\" src=\"https://www.gravatar.com/avatar/" + md5(m.email) +
+                       "?d=identicon\"><br>" +
+                       "<b>" + lwsgs_san(m.username) + "</b><br>" +
+                       "<span class=\"small\">" + d.toDateString() +
+                         "<br>" + s + "</span><br>" +
+                       "IP: " + lwsgs_san(m.ip) +
+                       "</td><td class=\"ava\"><span>" +
+                       mb_format(m.content) +
+                       "</span></td></tr></table></div><br>" + q.innerHTML;
+       }
+
+       function get_appropriate_ws_url()
+       {
+               var pcol;
+               var u = document.URL;
+
+               if (u.substring(0, 5) == "https") {
+                       pcol = "wss://";
+                       u = u.substr(8);
+               } else {
+                       pcol = "ws://";
+                       if (u.substring(0, 4) == "http")
+                               u = u.substr(7);
+               }
+               u = u.split('/');
+
+               return pcol + u[0] + "/xxx";
+       }
+
+       if (lwsgs_user) {
+               if (typeof MozWebSocket != "undefined")
+                       ws = new MozWebSocket(get_appropriate_ws_url(),
+                                          "protocol-lws-messageboard");
+               else
+                       ws = new WebSocket(get_appropriate_ws_url(),
+                                          "protocol-lws-messageboard");
+
+               try {
+                       ws.onopen = function() {
+                               document.getElementById("debug").textContent = "ws opened";
+                       }
+                       ws.onmessage =function got_packet(msg) {
+                               add_div("messages", JSON.parse(msg.data));
+                       }
+                       ws.onclose = function(){
+                       }
+               } catch(exception) {
+                       alert('<p>Error' + exception);  
+               }
+       }
+
+       function mupd()
+       {
+               document.getElementById("send").disabled = !document.getElementById("msg").value;
+       }
+}, false);
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/md5.min.js b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/md5.min.js
new file mode 100644 (file)
index 0000000..4bd9de1
--- /dev/null
@@ -0,0 +1,2 @@
+!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
+//# sourceMappingURL=md5.min.js.map
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/needadmin/admin-login.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/needadmin/admin-login.html
new file mode 100644 (file)
index 0000000..113df9c
--- /dev/null
@@ -0,0 +1,5 @@
+<html>
+This is an example destination that will appear after successful Admin login.
+
+This URL cannot be served if you're not logged in as admin.
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/needauth/successful-login.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/needauth/successful-login.html
new file mode 100644 (file)
index 0000000..dfc25cf
--- /dev/null
@@ -0,0 +1,4 @@
+<html>
+This is an example destination that will appear after successful non-Admin login
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-forgot-fail.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-forgot-fail.html
new file mode 100644 (file)
index 0000000..ead3d13
--- /dev/null
@@ -0,0 +1,5 @@
+<html>
+Sorry, something went wrong.
+
+Click <a href="../">here</a> to continue.
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-forgot-ok.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-forgot-ok.html
new file mode 100644 (file)
index 0000000..3e8e9cf
--- /dev/null
@@ -0,0 +1,6 @@
+<html>
+This is a one-time password recovery login.
+
+Please click <a href="./">here</a> and click your username at the top to reset your password.
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-register-fail.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-register-fail.html
new file mode 100644 (file)
index 0000000..063c3c5
--- /dev/null
@@ -0,0 +1 @@
+Registration failed, sorry
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-register-ok.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-register-ok.html
new file mode 100644 (file)
index 0000000..c00c3f3
--- /dev/null
@@ -0,0 +1,27 @@
+<html>
+ <head>
+  <script src="lwsgs.js" nonce=lwscaro></script>
+ </head>
+ <body>
+  <table>
+    <tr>
+     <td colspan=2 align=center>
+      <img src="lwsgs-logo.png">
+     </td>
+    </tr>
+    <tr>
+     <td>
+      Your registration as <span id="u"></span> is accepted,<br>
+      you will receive an email shortly with instructions<br>
+      to verify and enable the account for normal use.<br><br>
+      The link is only valid for an hour, after that if it has<br>
+      not been verified your account will be deleted.
+     </td>
+    </tr>
+   </table>
+  </body>
+ <script nonce=lwscaro>
+       document.getElementById('u').innerHTML = "<b>" + lwsgs_san(lwsgs_user) + "</b>";
+ </script>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-verify-fail.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-verify-fail.html
new file mode 100644 (file)
index 0000000..d1d89ca
--- /dev/null
@@ -0,0 +1,20 @@
+<html>
+ <head>
+  <script src="lwsgs.js"></script>
+ </head>
+ <body>
+  <table>
+    <tr>
+     <td colspan=2 align=center>
+      <img src="lwsws-logo.png">
+     </td>
+    </tr>
+    <tr>
+     <td>
+       Sorry, the link was invalid.
+     </td>
+    </tr>
+   </table>
+  </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-verify-ok.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/post-verify-ok.html
new file mode 100644 (file)
index 0000000..ae647fc
--- /dev/null
@@ -0,0 +1,25 @@
+<html>
+ <head>
+  <script src="lwsgs.js"></script>
+ </head>
+ <body>
+  <table>
+    <tr>
+     <td colspan=2 align=center>
+      <img src="lwsgs-logo.png">
+     </td>
+    </tr>
+    <tr>
+     <td>
+        Thanks for signing up, your registration as <span id="u"></span> is verified.<br>
+       <br>
+       Click <a href="/lwsgs">here</a> to continue.
+     </td>
+    </tr>
+   </table>
+  </body>
+ <script nonce="lwscaro">
+       document.getElementById('u').innerHTML = "<b>" + san(lwsgs_user) + "</b>";
+ </script>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/seats.jpg b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/seats.jpg
new file mode 100644 (file)
index 0000000..5bed40d
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/seats.jpg differ
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/sent-forgot-fail.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/sent-forgot-fail.html
new file mode 100644 (file)
index 0000000..ead3d13
--- /dev/null
@@ -0,0 +1,5 @@
+<html>
+Sorry, something went wrong.
+
+Click <a href="../">here</a> to continue.
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/sent-forgot-ok.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/sent-forgot-ok.html
new file mode 100644 (file)
index 0000000..83df751
--- /dev/null
@@ -0,0 +1,4 @@
+An email has been sent to your registered address.
+
+Please follow the instructions to reset your password.
+
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/successful-login.html b/minimal-examples/http-server/minimal-http-server-generic-sessions/mount-origin/successful-login.html
new file mode 100644 (file)
index 0000000..dfc25cf
--- /dev/null
@@ -0,0 +1,4 @@
+<html>
+This is an example destination that will appear after successful non-Admin login
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-mimetypes/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d270d7b
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-mimetypes)
+set(SRCS minimal-http-server-mimetypes.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/README.md b/minimal-examples/http-server/minimal-http-server-mimetypes/README.md
new file mode 100644 (file)
index 0000000..3240d30
--- /dev/null
@@ -0,0 +1,21 @@
+# lws minimal http server mimetypes
+
+This is the same as the basic minimal http server, but it demonstrates how to
+add support for extra mimetypes to a mount.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server
+[2018/03/04 09:30:02:7986] USER: LWS minimal http server | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681 and click on the link to download the test.tar.bz2 file.
+
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c b/minimal-examples/http-server/minimal-http-server-mimetypes/minimal-http-server-mimetypes.c
new file mode 100644 (file)
index 0000000..6603821
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * lws-minimal-http-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_protocol_vhost_options pvo_mime = {
+       NULL,                           /* "next" pvo linked-list */
+       NULL,                           /* "child" pvo linked-list */
+       ".bz2",                         /* file suffix to match */
+       "application/x-bzip2"           /* mimetype to use */
+};
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          &pvo_mime,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/index.html
new file mode 100644 (file)
index 0000000..5c60e88
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server + mimetypes example</b>.
+               <br>
+               This shows how to teach a mount new bindings between file<br>
+               suffix and mimetype used to serve it.<p>
+
+               Lws has a bunch of built-in ones, but you can add as many<br>
+               as you like when defining the mount.<p>
+
+               For example, lws doesn't know the suffix <b>[.tar].bz2</b><br>
+               implies the mimetype <i>application/x-bzip2</i>, but we taught<br>
+               this mount about that relationship in the example code, so it<br>
+               knows how to serve <a href="test.tar.bz2">this example test.tar.bz2</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/test.tar.bz2 b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/test.tar.bz2
new file mode 100644 (file)
index 0000000..730b7ee
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-mimetypes/mount-origin/test.tar.bz2 differ
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-multivhost/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ace0d7c
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-multivhost)
+set(SRCS minimal-http-server.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/README.md b/minimal-examples/http-server/minimal-http-server-multivhost/README.md
new file mode 100644 (file)
index 0000000..26c99a1
--- /dev/null
@@ -0,0 +1,48 @@
+# lws minimal http server multivhost
+
+This creates a single server that creates three vhosts listening on both :7681 and
+:7682.  Two separate vhosts share listening on :7682.
+
+|vhost|listens on port|serves|
+---|---|---
+localhost1|7681|./mount-origin-localhost1
+localhost2|7682|./mount-origin-localhost2
+localhost3|7682|./mount-origin-localhost3
+
+Notice the last two both listen on 7682.  If you visit http://localhost:7682,
+by default you will get mapped to the first one, localhost2.
+
+However if you edit /etc/hosts on your machine and add
+
+```
+127.0.0.1 localhost3
+```
+
+so that you can visit http://localhost3:7682 in your browser, lws will use the
+`Host: localhost3` header sent by your browser to select the localhost3 vhost
+for the connection, and you will be served content from ./mount-origin-localhost3
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+--die-after-vhost | For testing failure handling
+
+```
+ $ ./lws-minimal-http-server-multivhost
+[2018/03/16 09:37:20:0866] USER: LWS minimal http server-multivhost | visit http://localhost:7681 / 7682
+[2018/03/16 09:37:20:0867] NOTICE: Creating Vhost 'localhost1' port 7681, 1 protocols, IPv6 off
+[2018/03/16 09:37:20:0868] NOTICE: Creating Vhost 'localhost2' port 7682, 1 protocols, IPv6 off
+[2018/03/16 09:37:20:0869] NOTICE: Creating Vhost 'localhost3' port 7682, 1 protocols, IPv6 off
+[2018/03/16 09:37:20:0869] NOTICE:  using listen skt from vhost localhost2
+```
+
+Visit http://localhost:7681 and http://localhost:7682
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/minimal-http-server.c b/minimal-examples/http-server/minimal-http-server-multivhost/minimal-http-server.c
new file mode 100644 (file)
index 0000000..b3677a2
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * lws-minimal-http-server-multivhost
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_http_mount mount_localhost1 = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin-localhost1",
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+}, mount_localhost2 = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin-localhost2",
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+}, mount_localhost3 = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin-localhost3",
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+void vh_destruction_notification(struct lws_vhost *vh, void *arg)
+{
+       lwsl_user("%s: called, arg: %p\n", __func__, arg);
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server-multivhost | visit http://localhost:7681 / 7682\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       /*
+        * Because of LWS_SERVER_OPTION_EXPLICIT_VHOSTS, this only creates
+        * the context and no longer creates a default vhost
+        */
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       /* it's our job now to create the vhosts we want:
+        *
+        *   - "localhost1" listen on 7681 and serve ./mount-origin-localhost1/
+        *   - "localhost2" listen on 7682 and serve ./mount-origin-localhost2/
+        *   - "localhost3" share 7682 and serve ./mount-origin-localhost3/
+        *
+        * Note lws supports dynamic vhost creation and destruction at runtime.
+        * When using multi-vhost with your own protocols, you must provide a
+        * pvo for each vhost naming each protocol you want enabled on it.
+        * minimal-ws-server-threads demonstrates how to provide pvos.
+        */
+
+       info.port = 7681;
+       info.mounts = &mount_localhost1;
+       info.error_document_404 = "/404.html";
+       info.vhost_name = "localhost1";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create first vhost\n");
+               goto bail;
+       }
+
+       info.port = 7682;
+       info.mounts = &mount_localhost2;
+       info.error_document_404 = "/404.html";
+       info.vhost_name = "localhost2";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create second vhost\n");
+               goto bail;
+       }
+
+       /* a second vhost listens on port 7682 */
+       info.mounts = &mount_localhost3;
+       info.error_document_404 = "/404.html";
+       info.vhost_name = "localhost3";
+       info.finalize = vh_destruction_notification;
+       info.finalize_arg = NULL;
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create third vhost\n");
+               goto bail;
+       }
+
+       if (lws_cmdline_option(argc, argv, "--die-after-vhost")) {
+               lwsl_warn("bailing after creating vhosts\n");
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/404.html b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/404.html
new file mode 100644 (file)
index 0000000..8f66287
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404 (vhost localhost1)</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/favicon.ico b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/index.html b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/index.html
new file mode 100644 (file)
index 0000000..042a0b9
--- /dev/null
@@ -0,0 +1,18 @@
+ <html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+
+               Hello from the <b>minimal http server multivhost example</b>.<br>
+               <br>
+               This was served from <i>./mount-origin-<b>localhost1</b>/index.html</i><br>
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost1/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/404.html b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/404.html
new file mode 100644 (file)
index 0000000..3f1a438
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404 (vhost localhost2)</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/favicon.ico b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/index.html b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/index.html
new file mode 100644 (file)
index 0000000..2a12308
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server multivhost example</b>.<br>
+               <br>
+               This was served from <i>./mount-origin-<b>localhost2</b>/index.html</i><br>
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost2/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/404.html b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/404.html
new file mode 100644 (file)
index 0000000..c891f79
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404 (vhost localhost3)</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/favicon.ico b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/index.html b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/index.html
new file mode 100644 (file)
index 0000000..a38b75c
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server multivhost example</b>.<br>
+               <br>
+               This was served from <i>./mount-origin-<b>localhost3</b>/index.html</i><br>
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-multivhost/mount-origin-localhost3/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-proxy/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4c582a0
--- /dev/null
@@ -0,0 +1,80 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-proxy)
+set(SRCS minimal-http-server-proxy.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITH_HTTP_PROXY 1 requirements)
+require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-proxy/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-proxy/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c b/minimal-examples/http-server/minimal-http-server-proxy/minimal-http-server-proxy.c
new file mode 100644 (file)
index 0000000..af9b1a2
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * lws-minimal-http-server-proxy
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal tls reverse proxy
+ */
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "warmcat.com/", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_HTTPS,  /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server proxy | visit https://localhost:7681\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/http2.png
new file mode 100644 (file)
index 0000000..439bfa4
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/http2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-proxy/mount-origin/index.html
new file mode 100644 (file)
index 0000000..35c26ca
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+       
+               Hello from the <b>minimal https server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+               <br>
+               <div id="transport"></div>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-smp/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-smp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ab2718e
--- /dev/null
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-smp)
+set(SRCS minimal-http-server-smp.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared pthread)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-smp/README.md b/minimal-examples/http-server/minimal-http-server-smp/README.md
new file mode 100644 (file)
index 0000000..6bc0096
--- /dev/null
@@ -0,0 +1,34 @@
+# lws minimal http server with multithreaded service
+
+Lws supports multithreaded service... build lws with `-DLWS_MAP_SMP=<max number of threads>`, the
+default is 1.  If nonzero, some extra pthreads locking is built into lws and it supports multiple
+independent service threads.
+
+![lws-smp-overview](../../doc-assets/lws-smp-ov.png)
+
+When an incoming connection is accepted, it is bound to the pt with the lowest current wsi
+count, to keep the load on the threads balanced.  Only the pt the wsi is bound to can service
+the thread, so although there can be as many wsi being serviced simultaneously as there are
+service threads, a wsi can only be service by the pt it is bound to.
+
+The effectiveness of the scalability depends on the load.  Here is an example of roughly what can be expected
+
+![lws-smp-example](../../doc-assets/lws-smp-example.png)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-smp
+[2018/03/07 17:44:20:2409] USER: LWS minimal http server SMP | visit http://localhost:7681
+[2018/03/07 17:44:20:2410] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+[2018/03/07 17:44:20:2411] NOTICE:   Service threads: 10
+```
+
+Visit http://localhost:7681 and use ab or other testing tools
+
diff --git a/minimal-examples/http-server/minimal-http-server-smp/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-smp/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-smp/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-smp/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-smp/minimal-http-server-smp.c b/minimal-examples/http-server/minimal-http-server-smp/minimal-http-server-smp.c
new file mode 100644 (file)
index 0000000..ae07e4a
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * lws-minimal-http-server-smp
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal multithreaded http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ *
+ * Also for simplicity the number of threads is set in the code... note that
+ * the real number of threads possible is decided by the LWS_MAX_SMP that lws
+ * was configured with, by default that is 1.  Lws will limit the number of
+ * requested threads to the number possible.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+#define COUNT_THREADS 8
+
+static struct lws_context *context;
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void *thread_service(void *threadid)
+{
+       while (lws_service_tsi(context, 10000,
+                              (int)(lws_intptr_t)threadid) >= 0 &&
+              !interrupted)
+               ;
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+       lws_cancel_service(context);
+}
+
+int main(int argc, const char **argv)
+{
+       pthread_t pthread_service[COUNT_THREADS];
+       struct lws_context_creation_info info;
+       void *retval;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server SMP | visit http://127.0.0.1:7681\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       if ((p = lws_cmdline_option(argc, argv, "-t"))) {
+               info.count_threads = atoi(p);
+               if (info.count_threads < 1 || info.count_threads > LWS_MAX_SMP)
+                       return 1;
+       } else
+               info.count_threads = COUNT_THREADS;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       lwsl_notice("  Service threads: %d\n", lws_get_count_threads(context));
+
+       /* start all the service threads */
+
+       for (n = 0; n < lws_get_count_threads(context); n++)
+               if (pthread_create(&pthread_service[n], NULL, thread_service,
+                                  (void *)(lws_intptr_t)n))
+                       lwsl_err("Failed to start service thread\n");
+
+       /* wait for all the service threads to exit */
+
+       while ((--n) >= 0)
+               pthread_join(pthread_service[n], &retval);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-smp/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-smp/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-smp/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-smp/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-smp/mount-origin/index.html
new file mode 100644 (file)
index 0000000..e677926
--- /dev/null
@@ -0,0 +1,12 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server SMP example</b>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-smp/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-smp/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-smp/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-smp/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-sse-ring/CMakeLists.txt
new file mode 100644 (file)
index 0000000..464cfbe
--- /dev/null
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-sse-ring)
+set(SRCS minimal-http-server-sse-ring.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/README.md b/minimal-examples/http-server/minimal-http-server-sse-ring/README.md
new file mode 100644 (file)
index 0000000..08c21bb
--- /dev/null
@@ -0,0 +1,27 @@
+# lws minimal http Server Side Events + ringbuffer
+
+This demonstates serving both normal content and
+content over Server Side Events, where all clients
+see the same data via a ringbuffer.
+
+Two separate threads generate content into the
+ringbuffer at random intervals.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-sse
+[2018/04/20 06:09:56:9974] USER: LWS minimal http Server-Side Events + ring | visit http://localhost:7681
+[2018/04/20 06:09:57:0148] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+```
+
+Visit http://localhost:7681, which connects back to the server using SSE
+and displays the incoming data.  Connecting from multiple browsers shows
+the same content from the server ringbuffer.
+
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c b/minimal-examples/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c
new file mode 100644 (file)
index 0000000..2e51c2f
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ * lws-minimal-http-server-sse
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that can serve both normal static
+ * content and server-side event connections.
+ *
+ * To keep it simple, it serves the static stuff from the subdirectory
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <pthread.h>
+#include <time.h>
+
+/* one of these created for each message in the ringbuffer */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/*
+ * Unlike ws, http is a stateless protocol.  This pss only exists for the
+ * duration of a single http transaction.  With http/1.1 keep-alive and http/2,
+ * that is unrelated to (shorter than) the lifetime of the network connection.
+ */
+struct pss {
+       struct pss *pss_list;
+       struct lws *wsi;
+       uint32_t tail;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct vhd {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct pss *pss_list; /* linked-list of live pss*/
+       pthread_t pthread_spam[2];
+
+       pthread_mutex_t lock_ring; /* serialize access to the ring buffer */
+       struct lws_ring *ring; /* ringbuffer holding unsent messages */
+       char finished;
+};
+
+static int interrupted;
+
+#if defined(WIN32)
+static void usleep(unsigned long l) { Sleep(l / 1000); }
+#endif
+
+
+/* destroys the message when everyone has had a copy of it */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+/*
+ * This runs under the "spam thread" thread context only.
+ *
+ * We spawn two threads that generate messages with this.
+ *
+ */
+
+static void *
+thread_spam(void *d)
+{
+       struct vhd *vhd = (struct vhd *)d;
+       struct msg amsg;
+       int len = 128, index = 1, n;
+
+       do {
+               /* don't generate output if nobody connected */
+               if (!vhd->pss_list)
+                       goto wait;
+
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+
+               /* only create if space in ringbuffer */
+               n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       goto wait_unlock;
+               }
+
+               amsg.payload = malloc(len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       goto wait_unlock;
+               }
+               n = lws_snprintf((char *)amsg.payload, len,
+                                "%s: tid: %p, msg: %d", __func__,
+                                (void *)pthread_self(), index++);
+               amsg.len = n;
+               n = lws_ring_insert(vhd->ring, &amsg, 1);
+               if (n != 1) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+               } else
+                       /*
+                        * This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
+                        * in the lws service thread context.
+                        */
+                       lws_cancel_service(vhd->context);
+
+wait_unlock:
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+
+wait:
+               /* rand() would make more sense but coverity shrieks */
+               usleep(100000 + (time(NULL) & 0xffff));
+
+       } while (!vhd->finished);
+
+       lwsl_notice("thread_spam %p exiting\n", (void *)pthread_self());
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+
+static int
+callback_sse(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+            void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get(
+                       lws_get_vhost(wsi), lws_get_protocol(wsi));
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
+               *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - 1];
+       const struct msg *pmsg;
+       void *retval;
+       int n;
+
+       switch (reason) {
+
+       /* --- vhost protocol lifecycle --- */
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi), sizeof(struct vhd));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring)
+                       return 1;
+
+               pthread_mutex_init(&vhd->lock_ring, NULL);
+
+               /* start the content-creating threads */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (pthread_create(&vhd->pthread_spam[n], NULL,
+                                          thread_spam, vhd)) {
+                               lwsl_err("thread creation failed\n");
+                               goto init_fail;
+                       }
+
+               return 0;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               init_fail:
+               vhd->finished = 1;
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (vhd->pthread_spam[n])
+                               pthread_join(vhd->pthread_spam[n], &retval);
+
+               if (vhd->ring)
+                       lws_ring_destroy(vhd->ring);
+
+               pthread_mutex_destroy(&vhd->lock_ring);
+               return 0;
+
+       /* --- http connection lifecycle --- */
+
+       case LWS_CALLBACK_HTTP:
+               /*
+                * `in` contains the url part after our mountpoint /sse, if any
+                * you can use this to determine what data to return and store
+                * that in the pss
+                */
+               lwsl_info("%s: LWS_CALLBACK_HTTP: '%s'\n", __func__,
+                         (const char *)in);
+
+               /* SSE requires a http OK response with this content-type */
+
+               if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
+                                               "text/event-stream",
+                                               LWS_ILLEGAL_HTTP_CONTENT_LEN,
+                                               &p, end))
+                       return 1;
+
+               if (lws_finalize_write_http_header(wsi, start, &p, end))
+                       return 1;
+
+               /* add ourselves to the list of live pss held in the vhd */
+
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               pss->tail = lws_ring_get_oldest_tail(vhd->ring);
+               pss->wsi = wsi;
+
+               /*
+                * This tells lws we are no longer a normal http stream,
+                * but are an "immortal" (plus or minus whatever timeout you
+                * set on it afterwards) SSE stream.  In http/2 case that also
+                * stops idle timeouts being applied to the network connection
+                * while this wsi is still open.
+                */
+               lws_http_mark_sse(wsi);
+
+               /* write the body separately */
+
+               lws_callback_on_writable(wsi);
+
+               return 0;
+
+       case LWS_CALLBACK_CLOSED_HTTP:
+               /* remove our closing pss from the list of live pss */
+
+               lws_ll_fwd_remove(struct pss, pss_list, pss, vhd->pss_list);
+               return 0;
+
+       /* --- data transfer --- */
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+
+               lwsl_info("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
+
+               pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
+               if (!pmsg)
+                       break;
+
+               p += lws_snprintf((char *)p, end - p,
+                                 "data: %s\x0d\x0a\x0d\x0a",
+                                 (const char *)pmsg->payload);
+
+               if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start),
+                             LWS_WRITE_HTTP) != lws_ptr_diff(p, start))
+                       return 1;
+
+               lws_ring_consume_and_update_oldest_tail(
+                       vhd->ring,      /* lws_ring object */
+                       struct pss,     /* type of objects with tails */
+                       &pss->tail,     /* tail of guy doing the consuming */
+                       1,      /* number of payload objects being consumed */
+                       vhd->pss_list,  /* head of list of objects with tails */
+                       tail,   /* member name of tail in objects with tails */
+                       pss_list /* member name of next object in objects with tails */
+               );
+
+               if (lws_ring_get_element(vhd->ring, &pss->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(pss->wsi);
+
+               return 0;
+
+       case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
+               if (!vhd)
+                       break;
+               /*
+                * let everybody know we want to write something on them
+                * as soon as they are ready
+                */
+               lws_start_foreach_llp(struct pss **, ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               return 0;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       { "sse", callback_sse, sizeof(struct pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/* override the default mount for /sse in the URL space */
+
+static const struct lws_http_mount mount_sse = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/sse",         /* mountpoint URL */
+       /* .origin */                   NULL,           /* protocol */
+       /* .def */                      NULL,
+       /* .protocol */                 "sse",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_CALLBACK, /* dynamic */
+       /* .mountpoint_len */           4,                /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_sse,     /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http Server-Side Events + ring | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/example.js b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/example.js
new file mode 100644 (file)
index 0000000..b32bf0b
--- /dev/null
@@ -0,0 +1,38 @@
+document.addEventListener("DOMContentLoaded", function() {
+
+       var head = 0, tail = 0, ring = new Array();
+
+       es = new EventSource("/sse/sourcename");
+       try {
+               es.onopen = function() {
+                       // console.log("EventSource opened");
+                       document.getElementById("r").disabled = 0;
+               };
+
+               es.onmessage = function got_packet(msg) {
+                       var n, s = "";
+
+                       // console.log(msg.data);
+                       ring[head] = msg.data + "\n";
+                       head = (head + 1) % 50;
+                       if (tail === head)
+                               tail = (tail + 1) % 50;
+       
+                       n = tail;
+                       do {
+                               s = s + ring[n];
+                               n = (n + 1) % 50;
+                       } while (n !== head);
+       
+                       document.getElementById("r").value = s; 
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+
+               /* there is no onclose() for EventSource */
+       
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+
+}, false);
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/index.html
new file mode 100644 (file)
index 0000000..576c9eb
--- /dev/null
@@ -0,0 +1,23 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http Server Side Events + Ring example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               It connects back to the server at <i>/sse/sourcename</i> using EventSource()<br>
+               and displays the perioding incoming event data below.
+               <p>
+               The data is being produced by two asynchronous threads at the server,
+               which each sleep for a random period inbetween samples.
+               <p>
+               <textarea id=r readonly cols=60 rows=20></textarea><br>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-sse/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-sse/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c22a71f
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-sse)
+set(SRCS minimal-http-server-sse.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-sse/README.md b/minimal-examples/http-server/minimal-http-server-sse/README.md
new file mode 100644 (file)
index 0000000..cc8f478
--- /dev/null
@@ -0,0 +1,25 @@
+# lws minimal http Server Side Events
+
+This demonstates serving both normal content and
+content over Server Side Events.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+You can give -s to listen using https on port :443
+
+```
+ $ ./lws-minimal-http-server-sse
+[2018/04/20 06:09:56:9974] USER: LWS minimal http Server-Side Events | visit http://localhost:7681
+[2018/04/20 06:09:57:0148] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+```
+
+Visit http://localhost:7681, which connects back to the server using SSE
+and displays the incoming data.  Connecting from multiple browsers shows
+content individual to the connection.
+
diff --git a/minimal-examples/http-server/minimal-http-server-sse/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-sse/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-sse/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-sse/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-sse/minimal-http-server-sse.c b/minimal-examples/http-server/minimal-http-server-sse/minimal-http-server-sse.c
new file mode 100644 (file)
index 0000000..cb60774
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * lws-minimal-http-server-sse
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal http server that can serve both normal static
+ * content and server-side event connections.
+ *
+ * To keep it simple, it serves the static stuff from the subdirectory
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <pthread.h>
+
+/*
+ * Unlike ws, http is a stateless protocol.  This pss only exists for the
+ * duration of a single http transaction.  With http/1.1 keep-alive and http/2,
+ * that is unrelated to (shorter than) the lifetime of the network connection.
+ */
+struct pss {
+       time_t established;
+};
+
+static int interrupted;
+
+#define SECS_REPORT 3
+
+static int
+callback_sse(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+            void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
+               *p = start, *end = &buf[sizeof(buf) - 1];
+
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
+               /*
+                * `in` contains the url part after our mountpoint /sse, if any
+                * you can use this to determine what data to return and store
+                * that in the pss
+                */
+               lwsl_notice("%s: LWS_CALLBACK_HTTP: '%s'\n", __func__,
+                           (const char *)in);
+
+               pss->established = time(NULL);
+
+               /* SSE requires a response with this content-type */
+
+               if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
+                                               "text/event-stream",
+                                               LWS_ILLEGAL_HTTP_CONTENT_LEN,
+                                               &p, end))
+                       return 1;
+
+               if (lws_finalize_write_http_header(wsi, start, &p, end))
+                       return 1;
+
+               /*
+                * This tells lws we are no longer a normal http stream,
+                * but are an "immortal" (plus or minus whatever timeout you
+                * set on it afterwards) SSE stream.  In http/2 case that also
+                * stops idle timeouts being applied to the network connection
+                * while this wsi is still open.
+                */
+               lws_http_mark_sse(wsi);
+
+               /* write the body separately */
+
+               lws_callback_on_writable(wsi);
+
+               return 0;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+
+               lwsl_notice("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
+
+               if (!pss)
+                       break;
+
+               /*
+                * to keep this demo as simple as possible, each client has his
+                * own private data and timer.
+                */
+
+               p += lws_snprintf((char *)p, end - p,
+                                 "data: %llu\x0d\x0a\x0d\x0a",
+                                 (unsigned long long)time(NULL) -
+                                 pss->established);
+
+               if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start),
+                             LWS_WRITE_HTTP) != lws_ptr_diff(p, start))
+                       return 1;
+
+               lws_set_timer_usecs(wsi, SECS_REPORT * LWS_USEC_PER_SEC);
+
+               return 0;
+
+       case LWS_CALLBACK_TIMER:
+
+               lwsl_notice("%s: LWS_CALLBACK_TIMER\n", __func__);
+               lws_callback_on_writable(wsi);
+
+               return 0;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       { "sse", callback_sse, sizeof(struct pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+/* override the default mount for /sse in the URL space */
+
+static const struct lws_http_mount mount_sse = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/sse",         /* mountpoint URL */
+       /* .origin */                   NULL,           /* protocol */
+       /* .def */                      NULL,
+       /* .protocol */                 "sse",
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_CALLBACK, /* dynamic */
+       /* .mountpoint_len */           4,                /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/* default mount serves the URL space from ./mount-origin */
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               &mount_sse,     /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http Server-Side Events | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       info.port = 7681;
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.port = 443;
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/example.js b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/example.js
new file mode 100644 (file)
index 0000000..af73512
--- /dev/null
@@ -0,0 +1,38 @@
+document.addEventListener("DOMContentLoaded", function() {
+
+var head = 0, tail = 0, ring = new Array();
+
+       es = new EventSource("/sse/sourcename");
+       try {
+               es.onopen = function() {
+                       // console.log("EventSource opened");
+                       document.getElementById("r").disabled = 0;
+               };
+
+               es.onmessage = function got_packet(msg) {
+                       var n, s = "";
+       
+                       // console.log(msg.data);
+                       ring[head] = msg.data + "\n";
+                       head = (head + 1) % 50;
+                       if (tail === head)
+                               tail = (tail + 1) % 50;
+       
+                       n = tail;
+                       do {
+                               s = s + ring[n];
+                               n = (n + 1) % 50;
+                       } while (n !== head);
+       
+                       document.getElementById("r").value = s; 
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+
+               /* there is no onclose() for EventSource */
+
+       } catch(exception) {
+               alert("<p>Error" + exception);  
+       }
+
+}, false);
diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/index.html
new file mode 100644 (file)
index 0000000..42f6b83
--- /dev/null
@@ -0,0 +1,20 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http Server Side Events example</b>.
+               <p>
+               This is a static page served from ./mount-origin/index.html.
+               <p>
+               It connects back to the server at <i>/sse/sourcename</i> using EventSource()<br>
+               and displays the periodic incoming event data below.
+               <p>
+               <textarea id=r readonly cols=40 rows=20></textarea><br>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-tls-80/CMakeLists.txt
new file mode 100644 (file)
index 0000000..01305f8
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-tls-80)
+set(SRCS minimal-http-server-tls-80.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/README.md b/minimal-examples/http-server/minimal-http-server-tls-80/README.md
new file mode 100644 (file)
index 0000000..83a7a94
--- /dev/null
@@ -0,0 +1,64 @@
+# lws minimal http server with tls and port 80 redirect
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Because this listens on low ports (80 + 443), it must be run as root.
+
+```
+ $ sudo ./lws-minimal-http-server-tls-80
+[2018/03/20 13:23:13:0131] USER: LWS minimal http server TLS | visit https://localhost:7681
+[2018/03/20 13:23:13:0142] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/20 13:23:13:0142] NOTICE:  Using SSL mode
+[2018/03/20 13:23:13:0146] NOTICE:  SSL ECDH curve 'prime256v1'
+[2018/03/20 13:23:13:0146] NOTICE:  HTTP2 / ALPN enabled
+[2018/03/20 13:23:13:0195] NOTICE: lws_tls_client_create_vhost_context: doing cert filepath localhost-100y.cert
+[2018/03/20 13:23:13:0195] NOTICE: Loaded client cert localhost-100y.cert
+[2018/03/20 13:23:13:0195] NOTICE: lws_tls_client_create_vhost_context: doing private key filepath
+[2018/03/20 13:23:13:0196] NOTICE: Loaded client cert private key localhost-100y.key
+[2018/03/20 13:23:13:0196] NOTICE: created client ssl context for default
+[2018/03/20 13:23:14:0207] NOTICE:    vhost default: cert expiry: 730459d
+```
+
+Visit http://localhost
+
+This will go first to port 80 using http, where it will be redirected to
+https and port 443
+
+```
+07:41:48.596918 IP localhost.http > localhost.52662: Flags [P.], seq 1:100, ack 416, win 350, options [nop,nop,TS val 3906619933 ecr 3906619933], length 99: HTTP: HTTP/1.1 301 Redirect
+       0x0000:  4500 0097 3f8f 4000 4006 fccf 7f00 0001  E...?.@.@.......
+       0x0010:  7f00 0001 0050 cdb6 6601 dfa7 922a 4c06  .....P..f....*L.
+       0x0020:  8018 015e fe8b 0000 0101 080a e8da 4a1d  ...^..........J.
+       0x0030:  e8da 4a1d 4854 5450 2f31 2e31 2033 3031  ..J.HTTP/1.1.301
+       0x0040:  2052 6564 6972 6563 740d 0a6c 6f63 6174  .Redirect..locat
+       0x0050:  696f 6e3a 2068 7474 7073 3a2f 2f6c 6f63  ion:.https://loc
+       0x0060:  616c 686f 7374 2f0d 0a63 6f6e 7465 6e74  alhost/..content
+       0x0070:  2d74 7970 653a 2074 6578 742f 6874 6d6c  -type:.text/html
+       0x0080:  0d0a 636f 6e74 656e 742d 6c65 6e67 7468  ..content-length
+       0x0090:  3a20 300d 0a0d 0a
+```
+
+Because :443 uses a selfsigned certificate, you will have to make an exception for it in your browser.
+
+## Certificate creation
+
+The selfsigned certs provided were created with
+
+```
+echo -e "GB\nErewhon\nAll around\nlibwebsockets-test\n\nlocalhost\nnone@invalid.org\n" | openssl req -new -newkey rsa:4096 -days 36500 -nodes -x509 -keyout "localhost-100y.key" -out "localhost-100y.cert"
+```
+
+they cover "localhost" and last 100 years from 2018-03-20.
+
+You can replace them with commercial certificates matching your hostname.
+
+## HTTP/2
+
+If you built lws with `-DLWS_WITH_HTTP2=1` at cmake, this simple server is also http/2 capable
+out of the box.  If the index.html was loaded over http/2, it will display an HTTP 2 png.
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-tls-80/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-tls-80/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c b/minimal-examples/http-server/minimal-http-server-tls-80/minimal-http-server-tls-80.c
new file mode 100644 (file)
index 0000000..178500d
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * lws-minimal-http-server-tls-80
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with three extra lines giving it tls (ssl) capabilities, which in
+ * turn allow operation with HTTP/2 if lws was configured for it.
+ *
+ * In addition, it runs a vhost on port 80 with the job of redirecting
+ * and upgrading http clients that came in on port 80 to https on port 443.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_http_mount mount80 = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "localhost/",
+       /* .def */                      "/",    /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_REDIR_HTTPS, /* https redir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server TLS + 80 | visit https://localhost\n");
+       lwsl_user(" Run as ROOT so can listen on 443\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+
+       info.port = 80;
+       info.mounts = &mount80;
+       info.vhost_name = "localhost80";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create tls vhost\n");
+               goto bail;
+       }
+
+       info.port = 443;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+       info.vhost_name = "localhost";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create tls vhost\n");
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/404.html
new file mode 100644 (file)
index 0000000..aa63b71
--- /dev/null
@@ -0,0 +1,13 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/example.js b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/example.js
new file mode 100644 (file)
index 0000000..389dc7f
--- /dev/null
@@ -0,0 +1,21 @@
+document.addEventListener("DOMContentLoaded", function() {
+
+       var transport_protocol = "";
+       
+       if (performance && performance.timing.nextHopProtocol) {
+           transport_protocol = performance.timing.nextHopProtocol;
+       } else if (window.chrome && window.chrome.loadTimes) {
+           transport_protocol = window.chrome.loadTimes().connectionInfo;
+       } else {
+         var p = performance.getEntriesByType("resource");
+         for (var i = 0; i < p.length; i++) {
+               var value = "nextHopProtocol" in p[i];
+                 if (value)
+                   transport_protocol = p[i].nextHopProtocol;
+        }
+       }
+          
+       if (transport_protocol === "h2")
+               document.getElementById("transport").innerHTML = "<img src=\"/http2.png\">";
+
+}, false);
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/http2.png
new file mode 100644 (file)
index 0000000..439bfa4
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/http2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/index.html
new file mode 100644 (file)
index 0000000..0753551
--- /dev/null
@@ -0,0 +1,18 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal https server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+               <br>
+               <div id="transport"></div>
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-tls-80/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-tls-mem/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e6ea90a
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-tls-mem)
+set(SRCS minimal-http-server-tls-mem.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/README.md b/minimal-examples/http-server/minimal-http-server-tls-mem/README.md
new file mode 100644 (file)
index 0000000..e139c54
--- /dev/null
@@ -0,0 +1,60 @@
+# lws minimal http server with tls and certs from memory
+
+This is the same as the minimal-http-server-tls example, but shows how
+to init the vhost with both PEM or DER certs from memory instead of files.
+
+The server listens on port 7681 (initialized with PEM in-memory certs) and
+port 7682 (initialized with DER in-memory certs).
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-tls-mem
+[2019/02/14 14:46:40:9783] USER: LWS minimal http server TLS | visit https://localhost:7681
+[2019/02/14 14:46:40:9784] NOTICE:  Using SSL mode
+[2019/02/14 14:46:40:9784] NOTICE: lws_tls_server_vhost_backend_init: vh first: mem CA OK
+parsing as der
+[2019/02/14 14:46:40:9849] NOTICE: no client cert required
+[2019/02/14 14:46:40:9849] NOTICE: created client ssl context for first
+[2019/02/14 14:46:40:9849] NOTICE:  Using SSL mode
+[2019/02/14 14:46:40:9850] NOTICE: lws_tls_server_vhost_backend_init: vh second: mem CA OK
+parsing as der
+[2019/02/14 14:46:40:9894] NOTICE: no client cert required
+[2019/02/14 14:46:40:9894] NOTICE: created client ssl context for second
+[2019/02/14 14:46:40:9894] NOTICE:    vhost first: cert expiry: 36167d
+[2019/02/14 14:46:40:9894] NOTICE:    vhost second: cert expiry: 36167d
+[2018/03/20 13:23:14:0207] NOTICE:    vhost default: cert expiry: 730459d
+```
+
+Visit https://127.0.0.1:7681 and https://127.0.0.1:7682
+
+Because it uses a selfsigned certificate, you will have to make an exception for it in your browser.
+
+## Certificate creation
+
+The selfsigned certs provided were created with
+
+```
+echo -e "GB\nErewhon\nAll around\nlibwebsockets-test\n\nlocalhost\nnone@invalid.org\n" | openssl req -new -newkey rsa:4096 -days 36500 -nodes -x509 -keyout "localhost-100y.key" -out "localhost-100y.cert"
+```
+
+they cover "localhost" and last 100 years from 2018-03-20.
+
+You can replace them with commercial certificates matching your hostname.
+
+The der content was made from PEM like this
+
+```
+ $ cat ../minimal-http-server-tls/localhost-100y.key | grep -v ^- | base64 -d | hexdump -C  | tr -s ' ' | cut -d' ' -f2- | cut -d' ' -f-16 | sed "s/|.*//g" | sed "s/0000.*//g" | sed "s/^/0x/g" | sed "s/\ /\,\ 0x/g" | sed "s/\$/,/g" | sed "s/0x,//g"
+```
+
+## HTTP/2
+
+If you built lws with `-DLWS_WITH_HTTP2=1` at cmake, this simple server is also http/2 capable
+out of the box.  If the index.html was loaded over http/2, it will display an HTTP 2 png.
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c b/minimal-examples/http-server/minimal-http-server-tls-mem/minimal-http-server-tls-mem.c
new file mode 100644 (file)
index 0000000..b3953fb
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ * lws-minimal-http-server-tls
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with three extra lines giving it tls (ssl) capabilities, which in
+ * turn allow operation with HTTP/2 if lws was configured for it.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/* the cert and key as PEM */
+
+static const char *cert_pem =
+       "-----BEGIN CERTIFICATE-----\n"
+       "MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD\n"
+       "VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb\n"
+       "MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx\n"
+       "HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3\n"
+       "WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl\n"
+       "d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0\n"
+       "cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA\n"
+       "aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW\n"
+       "aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8\n"
+       "Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek\n"
+       "LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH\n"
+       "KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6\n"
+       "jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ\n"
+       "Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz\n"
+       "TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK\n"
+       "Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0\n"
+       "nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo\n"
+       "GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p\n"
+       "sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU\n"
+       "9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar\n"
+       "jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow\n"
+       "YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA\n"
+       "xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P\n"
+       "wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34\n"
+       "H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv\n"
+       "xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk\n"
+       "ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g\n"
+       "1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA\n"
+       "AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg\n"
+       "mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s\n"
+       "8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX\n"
+       "e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=\n"
+       "-----END CERTIFICATE-----\n",
+
+               *key_pem =
+       "-----BEGIN PRIVATE KEY-----\n"
+       "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ\n"
+       "PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK\n"
+       "nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ\n"
+       "toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU\n"
+       "0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT\n"
+       "J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS\n"
+       "Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN\n"
+       "uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9\n"
+       "fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn\n"
+       "zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au\n"
+       "ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB\n"
+       "QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f\n"
+       "qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+\n"
+       "vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9\n"
+       "fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A\n"
+       "Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT\n"
+       "G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/\n"
+       "HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8\n"
+       "YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl\n"
+       "xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs\n"
+       "esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw\n"
+       "zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz\n"
+       "mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw\n"
+       "au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77\n"
+       "40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5\n"
+       "YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH\n"
+       "PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj\n"
+       "W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR\n"
+       "naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6\n"
+       "2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m\n"
+       "39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79\n"
+       "J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC\n"
+       "R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp\n"
+       "Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh\n"
+       "BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE\n"
+       "fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ\n"
+       "x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI\n"
+       "UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM\n"
+       "OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L\n"
+       "65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A\n"
+       "aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5\n"
+       "SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S\n"
+       "me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I\n"
+       "G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK\n"
+       "TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY\n"
+       "56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2\n"
+       "gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr\n"
+       "Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E\n"
+       "NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs\n"
+       "fBrpEY1IATtPq1taBZZogRqI3rOkkPk=\n"
+       "-----END PRIVATE KEY-----\n"
+       ;
+
+static const uint8_t cert_der[] = {
+       0x30, 0x82, 0x05, 0xe6, 0x30, 0x82, 0x03, 0xce, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00,
+       0xda, 0xb9, 0xd0, 0x8b, 0xb0, 0x3c, 0x52, 0xa0, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+       0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x81, 0x86, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+       0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08,
+       0x0c, 0x07, 0x45, 0x72, 0x65, 0x77, 0x68, 0x6f, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+       0x04, 0x07, 0x0c, 0x0a, 0x41, 0x6c, 0x6c, 0x20, 0x61, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x31, 0x1b,
+       0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x12, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x73,
+       0x6f, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06,
+       0x03, 0x55, 0x04, 0x03, 0x0c, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31,
+       0x1f, 0x30, 0x1d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10,
+       0x6e, 0x6f, 0x6e, 0x65, 0x40, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x2e, 0x6f, 0x72, 0x67,
+       0x30, 0x20, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x33, 0x32, 0x30, 0x30, 0x34, 0x31, 0x36, 0x30, 0x37,
+       0x5a, 0x18, 0x0f, 0x32, 0x31, 0x31, 0x38, 0x30, 0x32, 0x32, 0x34, 0x30, 0x34, 0x31, 0x36, 0x30,
+       0x37, 0x5a, 0x30, 0x81, 0x86, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+       0x47, 0x42, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x07, 0x45, 0x72, 0x65,
+       0x77, 0x68, 0x6f, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0a, 0x41,
+       0x6c, 0x6c, 0x20, 0x61, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+       0x04, 0x0a, 0x0c, 0x12, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74,
+       0x73, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+       0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09,
+       0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x6e, 0x6f, 0x6e, 0x65, 0x40,
+       0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x82, 0x02, 0x22, 0x30,
+       0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+       0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xa3, 0x62, 0xdb, 0x96,
+       0x68, 0x80, 0x82, 0x63, 0x4b, 0x49, 0x3e, 0xe6, 0xf1, 0xa4, 0x88, 0x08, 0x2f, 0xe5, 0x96, 0x9b,
+       0x3f, 0xdf, 0x98, 0xaf, 0x08, 0x42, 0xbd, 0x75, 0x5a, 0xd7, 0x9e, 0xeb, 0xf2, 0x14, 0xc9, 0x49,
+       0x68, 0xe4, 0x8e, 0xb4, 0xda, 0x6a, 0xb5, 0xa9, 0xc2, 0xe1, 0x4f, 0xf9, 0x26, 0xa6, 0x84, 0x7c,
+       0x0e, 0x2d, 0xc3, 0x02, 0x61, 0xca, 0x9d, 0x25, 0x9d, 0x3d, 0x6b, 0x67, 0xd4, 0x1b, 0x57, 0x2c,
+       0x4a, 0xcb, 0x95, 0x48, 0x87, 0x81, 0x90, 0xeb, 0x65, 0x62, 0x27, 0x98, 0x40, 0x63, 0x28, 0xcd,
+       0x43, 0x65, 0xff, 0x82, 0xbc, 0xd1, 0x99, 0xf8, 0x4c, 0xcf, 0x80, 0x1b, 0xf9, 0x9d, 0x37, 0xa4,
+       0x2d, 0x67, 0x1f, 0x23, 0x96, 0x59, 0xb6, 0x81, 0xae, 0x20, 0xfd, 0x43, 0x97, 0xf2, 0x24, 0x34,
+       0x3c, 0x3c, 0xcc, 0x5c, 0xf8, 0x72, 0x98, 0x8c, 0x7b, 0xf0, 0x45, 0x19, 0xe9, 0xb2, 0xc5, 0xd1,
+       0xe1, 0x2e, 0xb2, 0x87, 0x4a, 0x6f, 0x04, 0xa3, 0xe9, 0xd3, 0xef, 0x7e, 0x2d, 0x22, 0xd9, 0xc7,
+       0x29, 0x3f, 0xe6, 0xe8, 0x34, 0x94, 0xd3, 0x19, 0x59, 0xd7, 0x77, 0x7a, 0x7a, 0x12, 0xd1, 0x9b,
+       0xbf, 0xfe, 0x37, 0x1e, 0x3b, 0x33, 0x75, 0xcc, 0x4d, 0x11, 0xf9, 0xa8, 0xa3, 0xff, 0xed, 0x34,
+       0xc4, 0xda, 0xcd, 0x14, 0xeb, 0xe3, 0x34, 0xb6, 0xc1, 0x88, 0xdb, 0x3a, 0x51, 0x8b, 0xe9, 0xba,
+       0x8f, 0x38, 0x4d, 0xc8, 0xc0, 0x53, 0x27, 0x5b, 0xb9, 0xf2, 0xa0, 0x1e, 0xdd, 0x95, 0xb9, 0xff,
+       0xe6, 0x00, 0x8a, 0xe6, 0x58, 0x00, 0x1e, 0xa7, 0xe5, 0xb8, 0x54, 0xa7, 0x8a, 0x05, 0xb8, 0x1e,
+       0x70, 0x61, 0xb7, 0x01, 0xcb, 0x05, 0x51, 0xf2, 0xe8, 0xc8, 0x9e, 0x91, 0x7c, 0x6e, 0xe5, 0x90,
+       0x52, 0x3c, 0xb9, 0x37, 0xca, 0x52, 0x36, 0x9e, 0xec, 0xcd, 0xd6, 0x2c, 0x9c, 0xb2, 0x69, 0xbc,
+       0x07, 0x74, 0xb2, 0x26, 0xeb, 0x34, 0xf8, 0xc2, 0xd0, 0x54, 0x02, 0x36, 0xba, 0x4d, 0x8e, 0x02,
+       0x66, 0x20, 0xad, 0xfe, 0x98, 0xa9, 0x38, 0x91, 0x75, 0xfb, 0x65, 0x3c, 0x1e, 0x7e, 0x80, 0x33,
+       0x4c, 0xae, 0x25, 0xda, 0x91, 0xcd, 0xb8, 0x2e, 0x77, 0x41, 0x57, 0x3f, 0x10, 0x5f, 0xbe, 0x18,
+       0x12, 0xc0, 0xc6, 0x6b, 0xc2, 0x0e, 0xaf, 0x59, 0xa4, 0xc2, 0x18, 0x8b, 0xb3, 0xa6, 0xce, 0x49,
+       0x00, 0x28, 0xa0, 0xbd, 0x51, 0xee, 0x84, 0x7f, 0x6d, 0x7b, 0x2c, 0x54, 0x02, 0x14, 0x80, 0x4a,
+       0x23, 0x3b, 0xfd, 0x72, 0x08, 0xbd, 0x7f, 0x03, 0xcc, 0x2e, 0x1a, 0xca, 0x95, 0xea, 0x15, 0x44,
+       0xdb, 0x1e, 0x70, 0x1b, 0x02, 0x3f, 0x9e, 0xbd, 0x5a, 0x02, 0x57, 0x85, 0x49, 0xf0, 0x7f, 0x69,
+       0x68, 0x9f, 0x87, 0xc4, 0x66, 0xbd, 0xfe, 0xbd, 0x1b, 0x9c, 0xf6, 0xc8, 0x5f, 0xaa, 0x75, 0x74,
+       0x9c, 0xf3, 0x75, 0x20, 0xc4, 0xa7, 0xcd, 0x70, 0x9a, 0xb2, 0xde, 0xc8, 0xd9, 0xf8, 0xae, 0x45,
+       0x77, 0x48, 0xcf, 0xde, 0x8a, 0x8e, 0x51, 0x90, 0xa4, 0xfe, 0x17, 0x7c, 0xd5, 0x40, 0xf9, 0x11,
+       0x8b, 0xed, 0xa3, 0x27, 0x58, 0xe1, 0x48, 0x69, 0x5a, 0xca, 0x58, 0xbc, 0xc0, 0xb6, 0x0c, 0xe8,
+       0x18, 0xc4, 0xef, 0x3f, 0xf0, 0x2e, 0x7a, 0x12, 0x97, 0x9d, 0xc0, 0x49, 0x85, 0x8b, 0x56, 0xd2,
+       0x5b, 0x53, 0x8a, 0x85, 0x71, 0xfb, 0x9c, 0x93, 0x61, 0x20, 0x19, 0x5a, 0x5f, 0x88, 0xb2, 0xc9,
+       0x97, 0x8d, 0xe7, 0xf1, 0x26, 0xa6, 0x22, 0xdb, 0xfe, 0xd0, 0x5a, 0x6b, 0xf5, 0x40, 0x2f, 0x69,
+       0xb0, 0xd7, 0x23, 0x4c, 0xc6, 0x81, 0x40, 0xb3, 0x74, 0xdd, 0x3d, 0x50, 0x7a, 0x56, 0xec, 0xed,
+       0x8d, 0xbb, 0xb3, 0x17, 0x44, 0x9c, 0xd5, 0x2d, 0x87, 0x89, 0x08, 0xfb, 0x02, 0x03, 0x01, 0x00,
+       0x01, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+       0xf6, 0x66, 0x14, 0xdb, 0x7b, 0x56, 0xdb, 0x3b, 0x28, 0x9a, 0x42, 0x93, 0x01, 0x76, 0xab, 0x8e,
+       0xbd, 0xaf, 0x8e, 0xeb, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+       0x14, 0xf6, 0x66, 0x14, 0xdb, 0x7b, 0x56, 0xdb, 0x3b, 0x28, 0x9a, 0x42, 0x93, 0x01, 0x76, 0xab,
+       0x8e, 0xbd, 0xaf, 0x8e, 0xeb, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+       0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+       0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x36, 0x32, 0x01, 0x32, 0xba, 0x30,
+       0x60, 0xd0, 0x9b, 0x84, 0x02, 0x5d, 0x3f, 0xb7, 0x61, 0x96, 0x14, 0xf6, 0x45, 0x41, 0x51, 0x75,
+       0xe4, 0x54, 0x24, 0x3d, 0x08, 0xc6, 0xb1, 0xff, 0x86, 0x4b, 0xdb, 0xea, 0x6c, 0x87, 0x1e, 0x72,
+       0xbc, 0x9c, 0xe6, 0x1e, 0xcc, 0x53, 0xe3, 0x52, 0x59, 0x91, 0x29, 0x48, 0x0d, 0x10, 0x3b, 0x80,
+       0xc5, 0xb9, 0xd7, 0x67, 0x33, 0xdd, 0x09, 0x13, 0x55, 0xf5, 0x5d, 0xa6, 0x4a, 0x16, 0xd7, 0xbc,
+       0x2c, 0xa2, 0x0d, 0x8e, 0xd6, 0x09, 0x01, 0x36, 0x06, 0x7e, 0x38, 0xcf, 0x6e, 0x8e, 0xd2, 0xe5,
+       0x95, 0x93, 0xee, 0xc3, 0x34, 0xd2, 0xc7, 0xf4, 0x19, 0xe4, 0xc1, 0x4b, 0x4e, 0x9c, 0xcf, 0x4f,
+       0xc2, 0xd9, 0x83, 0xf6, 0x98, 0x56, 0x7b, 0x19, 0xb8, 0xab, 0x61, 0xa7, 0x4e, 0xc8, 0x8b, 0xe9,
+       0x49, 0x7a, 0x73, 0x2d, 0x10, 0x95, 0x32, 0x56, 0x29, 0x52, 0xc4, 0x51, 0x04, 0x3a, 0xc9, 0xd6,
+       0xb9, 0xf3, 0x67, 0xb6, 0xdc, 0x9d, 0x40, 0x5e, 0xab, 0x6a, 0x15, 0xca, 0x5f, 0xa0, 0x4d, 0xf8,
+       0x1f, 0x76, 0x9f, 0x12, 0x21, 0xb2, 0xf3, 0xcd, 0x9b, 0xf9, 0x90, 0x62, 0xc2, 0x47, 0x95, 0xfa,
+       0x8a, 0xba, 0x5d, 0x51, 0x7c, 0xb0, 0x5c, 0xab, 0xf7, 0x36, 0x2b, 0xbf, 0xd0, 0xaf, 0x59, 0x36,
+       0x25, 0x92, 0x94, 0xd0, 0x7c, 0xb4, 0xd9, 0x4a, 0xc8, 0x0f, 0x74, 0x41, 0xd8, 0x55, 0xc8, 0xef,
+       0xc5, 0x0d, 0x83, 0xf9, 0x7c, 0x83, 0x47, 0x46, 0x91, 0x2d, 0x19, 0x6f, 0xc5, 0x46, 0xbd, 0x74,
+       0x71, 0x85, 0x1c, 0xb2, 0x02, 0x1b, 0x7e, 0x09, 0xba, 0xae, 0x40, 0x8b, 0xa9, 0x4c, 0xd4, 0x4b,
+       0x28, 0x0f, 0xc1, 0xd2, 0xb0, 0x9a, 0x4c, 0x72, 0x6a, 0xc7, 0xec, 0xc5, 0xb0, 0xd9, 0xc2, 0xa4,
+       0xba, 0x30, 0xb7, 0xac, 0xc7, 0x45, 0x4e, 0xdb, 0x5e, 0xf3, 0x7c, 0x05, 0xd6, 0xeb, 0x85, 0xe0,
+       0x58, 0xd4, 0x0b, 0xbd, 0xbe, 0x4a, 0x67, 0x10, 0x37, 0xb0, 0x37, 0xf3, 0xa0, 0x42, 0xfe, 0x79,
+       0x36, 0x4d, 0x3b, 0x09, 0x6b, 0x04, 0xc3, 0xce, 0xac, 0x0e, 0xbb, 0xf5, 0x5d, 0x66, 0xfd, 0xa0,
+       0xd5, 0x6a, 0x53, 0x1e, 0x5b, 0xa6, 0x94, 0x29, 0x59, 0x78, 0xff, 0x86, 0xfe, 0x39, 0x12, 0xc8,
+       0x3c, 0x2a, 0x36, 0x74, 0xee, 0xd5, 0xaa, 0x1d, 0x0e, 0x65, 0x1a, 0xe3, 0x16, 0x68, 0x75, 0xf8,
+       0x4f, 0xd4, 0x75, 0x8f, 0xc1, 0x42, 0x85, 0x72, 0xaf, 0x28, 0x42, 0xbd, 0x78, 0xf1, 0x06, 0x00,
+       0x00, 0xe9, 0x5b, 0x50, 0xe2, 0x50, 0x53, 0xb4, 0x30, 0x45, 0x67, 0x75, 0x55, 0xb9, 0xf0, 0x84,
+       0x3b, 0x50, 0x59, 0x70, 0xbd, 0xd8, 0x0d, 0xb0, 0xd6, 0x7f, 0xf1, 0x91, 0x94, 0x91, 0xd4, 0x13,
+       0x3f, 0x35, 0x44, 0x83, 0x86, 0x40, 0x52, 0x51, 0x4d, 0x56, 0x8c, 0xc6, 0xd6, 0x83, 0xa1, 0xa0,
+       0x9a, 0x72, 0x19, 0x2d, 0x17, 0xab, 0x40, 0x2b, 0xb5, 0x3a, 0x8c, 0xeb, 0xf3, 0xba, 0xce, 0x42,
+       0xa4, 0x1a, 0x90, 0xf9, 0x32, 0xb7, 0xc0, 0x54, 0x48, 0xd2, 0xb7, 0x2b, 0x8d, 0xa3, 0xda, 0xa7,
+       0x1f, 0x84, 0x03, 0x8d, 0x75, 0x19, 0x7c, 0x1e, 0xaf, 0x10, 0xb3, 0x9a, 0x6e, 0xa7, 0x2f, 0xac,
+       0xf2, 0xc7, 0x42, 0x18, 0x39, 0x70, 0x47, 0x72, 0x4d, 0x08, 0xcb, 0xfa, 0xbb, 0x8f, 0x0e, 0x2b,
+       0xce, 0xc5, 0xe2, 0x67, 0x08, 0xc6, 0x19, 0x12, 0x79, 0xf1, 0x49, 0x50, 0x52, 0x08, 0xdb, 0x9a,
+       0x42, 0x18, 0xde, 0x56, 0xb4, 0x4e, 0x29, 0xe6, 0x5f, 0xbd, 0x72, 0x73, 0xb5, 0x1a, 0xb2, 0x17,
+       0x7b, 0x61, 0xe5, 0xff, 0xb3, 0x34, 0x73, 0xf9, 0x5b, 0x67, 0x81, 0x6f, 0x5e, 0x00, 0x11, 0x95,
+       0xec, 0x76, 0xae, 0x48, 0x12, 0xd0, 0xa6, 0xb4, 0xe8, 0x71,
+}, key_der[] = {
+       0x30, 0x82, 0x09, 0x43, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+       0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x09, 0x2d, 0x30, 0x82, 0x09, 0x29, 0x02, 0x01,
+       0x00, 0x02, 0x82, 0x02, 0x01, 0x00, 0xa3, 0x62, 0xdb, 0x96, 0x68, 0x80, 0x82, 0x63, 0x4b, 0x49,
+       0x3e, 0xe6, 0xf1, 0xa4, 0x88, 0x08, 0x2f, 0xe5, 0x96, 0x9b, 0x3f, 0xdf, 0x98, 0xaf, 0x08, 0x42,
+       0xbd, 0x75, 0x5a, 0xd7, 0x9e, 0xeb, 0xf2, 0x14, 0xc9, 0x49, 0x68, 0xe4, 0x8e, 0xb4, 0xda, 0x6a,
+       0xb5, 0xa9, 0xc2, 0xe1, 0x4f, 0xf9, 0x26, 0xa6, 0x84, 0x7c, 0x0e, 0x2d, 0xc3, 0x02, 0x61, 0xca,
+       0x9d, 0x25, 0x9d, 0x3d, 0x6b, 0x67, 0xd4, 0x1b, 0x57, 0x2c, 0x4a, 0xcb, 0x95, 0x48, 0x87, 0x81,
+       0x90, 0xeb, 0x65, 0x62, 0x27, 0x98, 0x40, 0x63, 0x28, 0xcd, 0x43, 0x65, 0xff, 0x82, 0xbc, 0xd1,
+       0x99, 0xf8, 0x4c, 0xcf, 0x80, 0x1b, 0xf9, 0x9d, 0x37, 0xa4, 0x2d, 0x67, 0x1f, 0x23, 0x96, 0x59,
+       0xb6, 0x81, 0xae, 0x20, 0xfd, 0x43, 0x97, 0xf2, 0x24, 0x34, 0x3c, 0x3c, 0xcc, 0x5c, 0xf8, 0x72,
+       0x98, 0x8c, 0x7b, 0xf0, 0x45, 0x19, 0xe9, 0xb2, 0xc5, 0xd1, 0xe1, 0x2e, 0xb2, 0x87, 0x4a, 0x6f,
+       0x04, 0xa3, 0xe9, 0xd3, 0xef, 0x7e, 0x2d, 0x22, 0xd9, 0xc7, 0x29, 0x3f, 0xe6, 0xe8, 0x34, 0x94,
+       0xd3, 0x19, 0x59, 0xd7, 0x77, 0x7a, 0x7a, 0x12, 0xd1, 0x9b, 0xbf, 0xfe, 0x37, 0x1e, 0x3b, 0x33,
+       0x75, 0xcc, 0x4d, 0x11, 0xf9, 0xa8, 0xa3, 0xff, 0xed, 0x34, 0xc4, 0xda, 0xcd, 0x14, 0xeb, 0xe3,
+       0x34, 0xb6, 0xc1, 0x88, 0xdb, 0x3a, 0x51, 0x8b, 0xe9, 0xba, 0x8f, 0x38, 0x4d, 0xc8, 0xc0, 0x53,
+       0x27, 0x5b, 0xb9, 0xf2, 0xa0, 0x1e, 0xdd, 0x95, 0xb9, 0xff, 0xe6, 0x00, 0x8a, 0xe6, 0x58, 0x00,
+       0x1e, 0xa7, 0xe5, 0xb8, 0x54, 0xa7, 0x8a, 0x05, 0xb8, 0x1e, 0x70, 0x61, 0xb7, 0x01, 0xcb, 0x05,
+       0x51, 0xf2, 0xe8, 0xc8, 0x9e, 0x91, 0x7c, 0x6e, 0xe5, 0x90, 0x52, 0x3c, 0xb9, 0x37, 0xca, 0x52,
+       0x36, 0x9e, 0xec, 0xcd, 0xd6, 0x2c, 0x9c, 0xb2, 0x69, 0xbc, 0x07, 0x74, 0xb2, 0x26, 0xeb, 0x34,
+       0xf8, 0xc2, 0xd0, 0x54, 0x02, 0x36, 0xba, 0x4d, 0x8e, 0x02, 0x66, 0x20, 0xad, 0xfe, 0x98, 0xa9,
+       0x38, 0x91, 0x75, 0xfb, 0x65, 0x3c, 0x1e, 0x7e, 0x80, 0x33, 0x4c, 0xae, 0x25, 0xda, 0x91, 0xcd,
+       0xb8, 0x2e, 0x77, 0x41, 0x57, 0x3f, 0x10, 0x5f, 0xbe, 0x18, 0x12, 0xc0, 0xc6, 0x6b, 0xc2, 0x0e,
+       0xaf, 0x59, 0xa4, 0xc2, 0x18, 0x8b, 0xb3, 0xa6, 0xce, 0x49, 0x00, 0x28, 0xa0, 0xbd, 0x51, 0xee,
+       0x84, 0x7f, 0x6d, 0x7b, 0x2c, 0x54, 0x02, 0x14, 0x80, 0x4a, 0x23, 0x3b, 0xfd, 0x72, 0x08, 0xbd,
+       0x7f, 0x03, 0xcc, 0x2e, 0x1a, 0xca, 0x95, 0xea, 0x15, 0x44, 0xdb, 0x1e, 0x70, 0x1b, 0x02, 0x3f,
+       0x9e, 0xbd, 0x5a, 0x02, 0x57, 0x85, 0x49, 0xf0, 0x7f, 0x69, 0x68, 0x9f, 0x87, 0xc4, 0x66, 0xbd,
+       0xfe, 0xbd, 0x1b, 0x9c, 0xf6, 0xc8, 0x5f, 0xaa, 0x75, 0x74, 0x9c, 0xf3, 0x75, 0x20, 0xc4, 0xa7,
+       0xcd, 0x70, 0x9a, 0xb2, 0xde, 0xc8, 0xd9, 0xf8, 0xae, 0x45, 0x77, 0x48, 0xcf, 0xde, 0x8a, 0x8e,
+       0x51, 0x90, 0xa4, 0xfe, 0x17, 0x7c, 0xd5, 0x40, 0xf9, 0x11, 0x8b, 0xed, 0xa3, 0x27, 0x58, 0xe1,
+       0x48, 0x69, 0x5a, 0xca, 0x58, 0xbc, 0xc0, 0xb6, 0x0c, 0xe8, 0x18, 0xc4, 0xef, 0x3f, 0xf0, 0x2e,
+       0x7a, 0x12, 0x97, 0x9d, 0xc0, 0x49, 0x85, 0x8b, 0x56, 0xd2, 0x5b, 0x53, 0x8a, 0x85, 0x71, 0xfb,
+       0x9c, 0x93, 0x61, 0x20, 0x19, 0x5a, 0x5f, 0x88, 0xb2, 0xc9, 0x97, 0x8d, 0xe7, 0xf1, 0x26, 0xa6,
+       0x22, 0xdb, 0xfe, 0xd0, 0x5a, 0x6b, 0xf5, 0x40, 0x2f, 0x69, 0xb0, 0xd7, 0x23, 0x4c, 0xc6, 0x81,
+       0x40, 0xb3, 0x74, 0xdd, 0x3d, 0x50, 0x7a, 0x56, 0xec, 0xed, 0x8d, 0xbb, 0xb3, 0x17, 0x44, 0x9c,
+       0xd5, 0x2d, 0x87, 0x89, 0x08, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x02, 0x00, 0x55,
+       0x9e, 0xf0, 0xc4, 0x19, 0x6f, 0x7e, 0xe4, 0xda, 0x07, 0x40, 0x57, 0x76, 0x3a, 0x6a, 0xaf, 0x1f,
+       0xaa, 0x89, 0x0a, 0x42, 0xa6, 0xc2, 0x34, 0xb7, 0x77, 0x82, 0x21, 0x85, 0xc1, 0x89, 0x1e, 0xcc,
+       0x75, 0xe8, 0x25, 0xf8, 0x3a, 0x0e, 0x2e, 0xe8, 0x67, 0x13, 0x5c, 0x2b, 0x2c, 0x37, 0xe4, 0xb1,
+       0x44, 0x82, 0x19, 0x20, 0xb5, 0x0a, 0x84, 0xad, 0x0a, 0xa8, 0xdf, 0x95, 0x4f, 0x22, 0x81, 0xfe,
+       0xbd, 0x75, 0x29, 0x58, 0xe8, 0xe7, 0x0a, 0x63, 0x38, 0x9a, 0xe1, 0x40, 0xf7, 0xf7, 0x17, 0xea,
+       0x66, 0x0c, 0x73, 0xc4, 0xe6, 0x26, 0xc8, 0x34, 0x7b, 0x02, 0xdd, 0x04, 0x23, 0x99, 0x57, 0x0f,
+       0xb0, 0x3c, 0x00, 0x65, 0x6a, 0xac, 0xfe, 0xd1, 0x43, 0xa2, 0x48, 0xc3, 0x1f, 0xb6, 0x99, 0x3d,
+       0x7f, 0x3f, 0x49, 0xc0, 0x67, 0x7c, 0x11, 0x1c, 0x81, 0xb1, 0x3f, 0xad, 0x93, 0x74, 0x22, 0xe8,
+       0x3d, 0x2f, 0x3d, 0x95, 0x6c, 0x0b, 0x52, 0xaa, 0xc7, 0x12, 0xff, 0x73, 0x02, 0x05, 0x77, 0x71,
+       0xdf, 0xd9, 0x90, 0x6d, 0x25, 0x77, 0xb4, 0x28, 0x19, 0xf5, 0xa6, 0x4b, 0x56, 0x86, 0xde, 0x40,
+       0x2a, 0xac, 0x7d, 0x9a, 0x57, 0x76, 0x3a, 0xf9, 0x7b, 0x36, 0x38, 0x22, 0x0b, 0x51, 0x71, 0xf6,
+       0xbf, 0x9f, 0x67, 0x0f, 0xe2, 0x39, 0xa6, 0xc5, 0x17, 0x04, 0x00, 0xe1, 0xda, 0xfe, 0x47, 0xc9,
+       0x84, 0x30, 0xaf, 0xfb, 0x6d, 0xde, 0x15, 0x5d, 0xf4, 0x35, 0xa3, 0xf4, 0x06, 0x19, 0xb3, 0x13,
+       0x1b, 0xeb, 0xa5, 0x16, 0xbb, 0x22, 0x0f, 0x23, 0xfe, 0xac, 0x12, 0x00, 0x68, 0x60, 0xb4, 0x8b,
+       0xb8, 0x03, 0x8c, 0xb0, 0x08, 0x05, 0x07, 0x83, 0x84, 0xfe, 0x34, 0xf5, 0x98, 0x6c, 0xc0, 0x81,
+       0x1c, 0xfc, 0x60, 0x6d, 0x38, 0x35, 0x37, 0xef, 0x66, 0xb6, 0x09, 0x02, 0xbf, 0xbb, 0x84, 0x3f,
+       0x1c, 0x14, 0x2f, 0xb8, 0x1b, 0x4a, 0x14, 0xd9, 0x06, 0x52, 0x8a, 0x0b, 0x80, 0x20, 0x9b, 0x17,
+       0x1c, 0xe0, 0x35, 0x41, 0x9c, 0xf3, 0x71, 0x81, 0xff, 0xa2, 0x30, 0x6c, 0x43, 0x3b, 0x47, 0x9b,
+       0x97, 0xaa, 0xc1, 0x62, 0x13, 0xbd, 0x4b, 0xa6, 0x6a, 0xe8, 0x0f, 0x28, 0xca, 0x4e, 0x54, 0x3c,
+       0x61, 0x99, 0x29, 0x21, 0xc2, 0xcd, 0x54, 0xbc, 0x34, 0xba, 0xca, 0x06, 0x60, 0x71, 0x66, 0xda,
+       0xbb, 0xc2, 0xc8, 0x45, 0x65, 0x7e, 0xc1, 0x37, 0x51, 0xbf, 0x1c, 0x17, 0x24, 0xc5, 0x93, 0x9d,
+       0x12, 0x78, 0xe7, 0x05, 0xd9, 0x02, 0xf6, 0xc7, 0x32, 0xa6, 0x99, 0xb6, 0x44, 0xa5, 0x78, 0x25,
+       0xc4, 0x11, 0xd1, 0xd2, 0x18, 0xe0, 0xa2, 0x7d, 0x08, 0x28, 0x90, 0xc6, 0x7e, 0x8a, 0xf8, 0x6c,
+       0x73, 0xbb, 0x36, 0xdf, 0xb5, 0x11, 0xc7, 0xbc, 0xbb, 0x6a, 0x13, 0x10, 0xab, 0xe9, 0xcf, 0x96,
+       0x88, 0x9f, 0x8e, 0x0e, 0x78, 0x2e, 0x66, 0x02, 0x94, 0x46, 0xcb, 0xcd, 0xff, 0xd1, 0xbb, 0xec,
+       0x7a, 0xc9, 0xd6, 0x8c, 0x31, 0x3f, 0x6c, 0x6a, 0x68, 0x4f, 0xca, 0x85, 0xbb, 0x2f, 0xb4, 0xba,
+       0xb0, 0xc4, 0x3c, 0xd2, 0x1d, 0xe3, 0x85, 0xdc, 0x26, 0x6d, 0x48, 0x44, 0x89, 0x46, 0xe7, 0xa1,
+       0x2b, 0xc4, 0x2d, 0xe5, 0xd2, 0xcd, 0x75, 0xc2, 0xb2, 0x29, 0x4e, 0x65, 0xd7, 0x72, 0x4a, 0xb0,
+       0xcc, 0x54, 0x7d, 0xb3, 0x6c, 0xfb, 0x7f, 0x4c, 0xe3, 0x7b, 0x2c, 0x6a, 0x66, 0x0e, 0x0d, 0x4c,
+       0xf2, 0x3b, 0xc2, 0x43, 0x37, 0x33, 0xc0, 0x57, 0x96, 0xfa, 0x76, 0x19, 0x30, 0x48, 0x7a, 0x8c,
+       0x6b, 0x58, 0x1e, 0x15, 0xdd, 0x80, 0x2b, 0xc2, 0xef, 0x10, 0x17, 0xcd, 0x10, 0x06, 0x05, 0x73,
+       0x9a, 0x01, 0xe5, 0xdb, 0x89, 0xd3, 0x83, 0x4d, 0x14, 0x1f, 0x53, 0xa3, 0x66, 0xc0, 0x01, 0x02,
+       0x82, 0x01, 0x01, 0x00, 0xce, 0xc5, 0xfb, 0x52, 0x0d, 0xb4, 0xaa, 0x1b, 0x2b, 0x5c, 0x5a, 0xa3,
+       0xd8, 0x3f, 0x74, 0x99, 0x1c, 0x05, 0x83, 0x03, 0x43, 0xb8, 0x00, 0x21, 0x0c, 0xf9, 0xe0, 0xb0,
+       0x6a, 0xef, 0x40, 0x4a, 0xeb, 0x65, 0xd0, 0x80, 0xe5, 0x34, 0x33, 0x09, 0xf2, 0x70, 0xb6, 0xa6,
+       0x1d, 0xb9, 0x04, 0xc7, 0xb9, 0x84, 0x70, 0xd6, 0xa7, 0x67, 0x06, 0x40, 0x9a, 0x20, 0xee, 0x96,
+       0x7f, 0xde, 0xa4, 0x28, 0x81, 0x08, 0x68, 0xda, 0x05, 0x27, 0x88, 0xa0, 0xe2, 0x7c, 0xde, 0xfb,
+       0xe3, 0x44, 0x1d, 0xca, 0x49, 0x65, 0x4f, 0x34, 0xd5, 0x44, 0xea, 0xa6, 0x3f, 0xcf, 0x9e, 0x7e,
+       0xb7, 0x88, 0xbe, 0xa9, 0x73, 0x1e, 0x6b, 0xaa, 0x68, 0x67, 0xc6, 0xb3, 0x9a, 0x13, 0x91, 0x96,
+       0x96, 0x8f, 0x9b, 0x2e, 0xf8, 0x1f, 0x9b, 0x4f, 0xef, 0x6b, 0x23, 0x06, 0x5c, 0xc1, 0xfb, 0x39,
+       0x61, 0x12, 0x0d, 0x85, 0x04, 0x71, 0xd7, 0xba, 0x9a, 0xfb, 0xec, 0x61, 0xe6, 0x67, 0xc4, 0xdb,
+       0x97, 0x3e, 0x33, 0xd7, 0xe2, 0x20, 0x14, 0xe2, 0x35, 0x2a, 0x38, 0x95, 0x3c, 0x56, 0x30, 0x14,
+       0xa1, 0x9c, 0xaf, 0x31, 0xac, 0x66, 0x8c, 0x12, 0x63, 0x7b, 0x5b, 0x4a, 0x93, 0x31, 0xb1, 0x47,
+       0x3e, 0x04, 0x33, 0xe4, 0x57, 0x31, 0x46, 0x30, 0x82, 0xab, 0x01, 0xe2, 0x97, 0x03, 0x41, 0x78,
+       0xb0, 0xd3, 0xa7, 0xf6, 0x44, 0x08, 0x40, 0x7b, 0xcb, 0x7e, 0x24, 0x85, 0x58, 0x79, 0xdf, 0x59,
+       0x81, 0x13, 0x69, 0x8d, 0xcd, 0x25, 0x48, 0x41, 0xc1, 0x99, 0x3f, 0x52, 0x3f, 0x0e, 0xf5, 0xe3,
+       0x5b, 0xb5, 0x14, 0x35, 0xd8, 0x05, 0xc2, 0x28, 0xbf, 0x19, 0x6f, 0xba, 0x33, 0x4b, 0x94, 0x0f,
+       0x2d, 0xb7, 0x51, 0x54, 0x29, 0x6c, 0x5c, 0xdc, 0x57, 0xca, 0x35, 0x0b, 0x69, 0xd9, 0x73, 0x81,
+       0x5b, 0xe3, 0x3c, 0x01, 0x02, 0x82, 0x01, 0x01, 0x00, 0xca, 0x48, 0x99, 0x05, 0xc3, 0x0b, 0x91,
+       0x9d, 0xa5, 0x49, 0x4b, 0xa5, 0xb1, 0x38, 0xa8, 0xd7, 0xf0, 0xc0, 0xae, 0xf7, 0xf7, 0x0a, 0x3e,
+       0x7c, 0x01, 0xbf, 0x69, 0xa6, 0x23, 0x68, 0xe0, 0x1b, 0x11, 0xd3, 0xc3, 0x9b, 0x2b, 0xdd, 0xa8,
+       0x66, 0x17, 0x97, 0x93, 0x6f, 0xc6, 0x68, 0xd7, 0xd0, 0x68, 0xc3, 0x2b, 0x4d, 0xfa, 0xda, 0xfa,
+       0xd9, 0x91, 0x68, 0x20, 0x10, 0x3d, 0x51, 0xb7, 0x3d, 0x7a, 0xc1, 0x00, 0x53, 0xc9, 0x77, 0x7e,
+       0x08, 0x1d, 0x7c, 0xcf, 0x36, 0x72, 0xe4, 0x7d, 0xb0, 0x67, 0x1f, 0x41, 0x5a, 0x02, 0x87, 0xcb,
+       0x4c, 0x83, 0xa0, 0x4f, 0xf0, 0x80, 0x4b, 0x3a, 0x66, 0xd2, 0x52, 0x13, 0x77, 0x3c, 0x6d, 0xa6,
+       0xdf, 0xd2, 0x3c, 0xd3, 0x6b, 0xb4, 0x7c, 0x53, 0x55, 0x40, 0x22, 0x4a, 0x87, 0x1d, 0x66, 0xd4,
+       0xc1, 0x45, 0x2c, 0xeb, 0xbb, 0x95, 0x57, 0x03, 0x4b, 0xd2, 0x4d, 0xfa, 0x86, 0x15, 0x3d, 0xbe,
+       0x8c, 0x0d, 0xf0, 0x4b, 0x9b, 0x98, 0xce, 0x88, 0xfb, 0x98, 0x90, 0x56, 0x78, 0x80, 0x7e, 0xfd,
+       0x27, 0xb8, 0x17, 0x23, 0x4f, 0xd8, 0x2a, 0x16, 0x89, 0xef, 0x25, 0xed, 0x85, 0x85, 0x64, 0x76,
+       0xb4, 0x85, 0xe8, 0x4a, 0x28, 0x7a, 0xbe, 0x11, 0x66, 0x09, 0x9a, 0xeb, 0x60, 0xdd, 0xd5, 0x53,
+       0x73, 0x4a, 0xad, 0xc9, 0x06, 0x8e, 0xab, 0x62, 0x31, 0x7b, 0x2e, 0xf7, 0x7e, 0x47, 0x00, 0xc2,
+       0x47, 0x5b, 0x61, 0x1e, 0xb9, 0x9f, 0xfc, 0x85, 0xe9, 0x97, 0x1a, 0x4d, 0x56, 0x4a, 0x0c, 0x57,
+       0x1b, 0x73, 0x6e, 0xba, 0xdb, 0x82, 0x70, 0xb6, 0xe5, 0x09, 0xaf, 0x45, 0x87, 0x34, 0xae, 0x54,
+       0xbf, 0x92, 0xf3, 0x38, 0xc9, 0x08, 0x4c, 0x1f, 0x77, 0x80, 0xec, 0x8c, 0x9c, 0x0d, 0x93, 0x29,
+       0x63, 0xed, 0x31, 0x9b, 0xb2, 0x3b, 0x8d, 0x34, 0xfb, 0x02, 0x82, 0x01, 0x00, 0x62, 0xb3, 0x28,
+       0x83, 0x03, 0x5d, 0xd0, 0xb1, 0x05, 0x62, 0xa1, 0x35, 0x82, 0x7c, 0xcf, 0xb8, 0x62, 0x22, 0xd3,
+       0x65, 0xd4, 0x86, 0x59, 0x31, 0x6d, 0x93, 0x3d, 0x48, 0x98, 0xd2, 0xb9, 0x7a, 0xc9, 0xa0, 0xa1,
+       0x05, 0x55, 0xe3, 0x33, 0xd5, 0xb4, 0xaf, 0x4e, 0xd0, 0x3e, 0x71, 0xd9, 0xb1, 0x48, 0x81, 0xca,
+       0xa6, 0xfb, 0xe3, 0x76, 0x9d, 0x91, 0xb4, 0xd4, 0x8e, 0x6c, 0x5d, 0x27, 0x38, 0xda, 0x56, 0xdc,
+       0x4d, 0xed, 0x95, 0xf0, 0x66, 0xf3, 0x95, 0xad, 0x8e, 0xc8, 0xed, 0xf3, 0xd6, 0x62, 0x70, 0x84,
+       0x7d, 0x70, 0xab, 0xe3, 0xe2, 0x15, 0xa5, 0x92, 0x3f, 0x64, 0x76, 0x56, 0xa4, 0x65, 0xfa, 0x08,
+       0x64, 0xa0, 0x4f, 0xa1, 0x0e, 0x8c, 0x26, 0x79, 0x21, 0x4b, 0x9f, 0x22, 0xf1, 0x29, 0xa9, 0x54,
+       0xa6, 0xb4, 0x5f, 0x0c, 0xa9, 0xf5, 0xce, 0xf6, 0x8f, 0x6e, 0x21, 0x82, 0xe8, 0x92, 0xb5, 0x90,
+       0xc7, 0x57, 0x41, 0x97, 0x95, 0x27, 0xb9, 0x32, 0xc3, 0xab, 0x0f, 0x1b, 0x0a, 0x1a, 0xbb, 0x3b,
+       0x9c, 0xba, 0xc9, 0xfb, 0x96, 0x68, 0xe5, 0xaf, 0x2f, 0xb9, 0xf1, 0x23, 0xc3, 0x6f, 0x4a, 0xc7,
+       0xe3, 0xe3, 0x2e, 0xb7, 0xe6, 0x02, 0x1a, 0xff, 0x47, 0x45, 0x78, 0x16, 0x19, 0x11, 0xf1, 0xc8,
+       0x52, 0x51, 0x9d, 0x35, 0x5a, 0x26, 0xc1, 0x7c, 0x18, 0x13, 0x38, 0x04, 0xfd, 0xcd, 0x7d, 0xae,
+       0xe2, 0x28, 0xc1, 0x7e, 0xc7, 0x53, 0xf3, 0x60, 0xc4, 0xc5, 0x93, 0x31, 0x98, 0x69, 0x6b, 0x39,
+       0x71, 0x81, 0xeb, 0x17, 0xc9, 0xb7, 0xa5, 0xf9, 0x83, 0x5c, 0x7c, 0x34, 0x38, 0x7b, 0x74, 0x4c,
+       0x38, 0xcc, 0xf7, 0x64, 0x58, 0x9a, 0x31, 0xa2, 0x6c, 0x18, 0x63, 0x5f, 0xe3, 0xef, 0x9d, 0xf5,
+       0x39, 0x8c, 0x82, 0x4e, 0x0d, 0xb3, 0xaa, 0x03, 0xb3, 0xa4, 0xdb, 0xf4, 0x01, 0x02, 0x82, 0x01,
+       0x01, 0x00, 0x96, 0x33, 0x77, 0xe4, 0x8e, 0x62, 0x8d, 0xba, 0x88, 0x1b, 0xb7, 0x9f, 0x0d, 0xcb,
+       0xeb, 0x9b, 0x84, 0x7a, 0x1e, 0xb1, 0xa2, 0xef, 0x29, 0x5c, 0x7d, 0x13, 0xbb, 0x88, 0x10, 0xac,
+       0xf4, 0x13, 0x45, 0x96, 0x7f, 0x9d, 0x3d, 0xe2, 0x36, 0x03, 0xb0, 0xaa, 0xed, 0x60, 0x46, 0xec,
+       0x5c, 0xab, 0xb4, 0xce, 0x8e, 0xde, 0x35, 0x51, 0xda, 0x88, 0x28, 0xef, 0x2f, 0x37, 0xbf, 0xc0,
+       0x68, 0x96, 0xaf, 0x0a, 0x96, 0x8a, 0xa0, 0x83, 0x28, 0xc3, 0x2f, 0xda, 0x18, 0x26, 0xef, 0x02,
+       0xf8, 0xcd, 0x3e, 0x95, 0x37, 0xba, 0x75, 0x3c, 0x8d, 0xd9, 0x7f, 0xb7, 0x4f, 0x04, 0x5e, 0xce,
+       0xfd, 0x4b, 0x92, 0x0a, 0x3d, 0xc8, 0x00, 0xc7, 0xce, 0xec, 0x4d, 0x38, 0xbb, 0x28, 0x33, 0x79,
+       0x49, 0x8b, 0x78, 0xb6, 0xbd, 0xae, 0x3c, 0x47, 0xb9, 0xdc, 0xd4, 0xd7, 0xb9, 0x26, 0xad, 0x8a,
+       0x51, 0xb9, 0x40, 0x2c, 0x84, 0xc4, 0x81, 0x0b, 0x3a, 0xec, 0xd6, 0x00, 0xc2, 0xb3, 0x83, 0xb0,
+       0x80, 0x88, 0x89, 0x4d, 0x4b, 0xd7, 0xe8, 0x59, 0xe2, 0xf2, 0x56, 0x40, 0x60, 0x09, 0x0e, 0x92,
+       0x99, 0xef, 0xcb, 0xf2, 0xd6, 0xbe, 0x99, 0x40, 0xf2, 0xdf, 0xb2, 0xba, 0xbc, 0x2d, 0xf8, 0x8e,
+       0x1f, 0x6f, 0x2b, 0xdc, 0xab, 0xc0, 0x5e, 0x97, 0xe3, 0x82, 0x2d, 0x46, 0x83, 0x89, 0x69, 0xf0,
+       0x9a, 0x55, 0xf1, 0x88, 0xfb, 0x5e, 0xf9, 0xab, 0xf7, 0x96, 0x72, 0xa4, 0xd7, 0xe2, 0xaf, 0x88,
+       0x1b, 0x8b, 0x4a, 0x96, 0xce, 0x2c, 0x2f, 0x89, 0xa0, 0x38, 0x92, 0xea, 0xfa, 0xb6, 0xb9, 0xd1,
+       0xa6, 0x0c, 0xc5, 0xb7, 0x2e, 0xa2, 0x69, 0x9c, 0xb4, 0xf3, 0x17, 0x53, 0xa0, 0xab, 0xad, 0x8c,
+       0x90, 0xa4, 0xf4, 0xc7, 0x30, 0xd5, 0x43, 0x43, 0x2d, 0xad, 0xb4, 0x57, 0x6c, 0xab, 0xd8, 0x8a,
+       0x4e, 0x77, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc9, 0xad, 0xff, 0xcc, 0xaf, 0x3d, 0xf9, 0x52, 0xfb,
+       0x1b, 0xf7, 0x92, 0x0f, 0xd9, 0x06, 0xf4, 0x7d, 0x24, 0x1d, 0x48, 0x9f, 0x69, 0xf7, 0xad, 0x40,
+       0x98, 0x60, 0x3e, 0x3b, 0x45, 0xe2, 0x85, 0xa8, 0x9d, 0x37, 0x56, 0x6a, 0xb9, 0x0b, 0xd9, 0xd8,
+       0xe7, 0xab, 0x3d, 0xc3, 0xb3, 0x94, 0x3b, 0xca, 0x5e, 0xac, 0x15, 0xe5, 0x25, 0x89, 0x8a, 0x65,
+       0x08, 0x4e, 0xe3, 0x6f, 0x77, 0x96, 0xfc, 0x59, 0x0f, 0x62, 0x2a, 0xe0, 0xd7, 0x19, 0x6d, 0x54,
+       0x82, 0x32, 0x81, 0xc0, 0x53, 0x38, 0x73, 0x63, 0x76, 0xeb, 0x76, 0x0b, 0x52, 0x23, 0x16, 0xb6,
+       0x80, 0x6b, 0xde, 0x18, 0x07, 0xb3, 0x67, 0x7f, 0x2a, 0x28, 0x85, 0x36, 0xe9, 0xd9, 0x33, 0xed,
+       0xd7, 0x84, 0x09, 0x8e, 0x2f, 0xae, 0xc4, 0x64, 0xc2, 0x1a, 0x53, 0x5b, 0x42, 0xc6, 0x54, 0x2a,
+       0x63, 0x71, 0x0a, 0x1a, 0x2a, 0xfc, 0xa6, 0x02, 0x80, 0xa6, 0x02, 0xcf, 0x15, 0xda, 0x83, 0x2b,
+       0x66, 0x2c, 0x35, 0x61, 0x0f, 0x6e, 0x39, 0x4a, 0x16, 0xc0, 0xea, 0xa6, 0xd7, 0x06, 0x6a, 0x99,
+       0x57, 0x0e, 0x5e, 0xf3, 0xc8, 0x4b, 0x68, 0x16, 0x02, 0xcd, 0xdf, 0x42, 0x55, 0xa3, 0x1f, 0xd8,
+       0x64, 0x71, 0x04, 0xcc, 0xb1, 0x46, 0x97, 0x40, 0x33, 0x83, 0xd1, 0xaa, 0xa4, 0x49, 0x8d, 0xc4,
+       0x36, 0xa3, 0xaf, 0x6c, 0x25, 0x75, 0xfe, 0x85, 0x29, 0x46, 0x2d, 0xf4, 0xef, 0xa9, 0x21, 0x0a,
+       0x80, 0x17, 0x23, 0x56, 0xca, 0x4a, 0x7f, 0xc0, 0xbd, 0x1d, 0xca, 0x0c, 0xfd, 0x78, 0x07, 0x9b,
+       0x68, 0x1c, 0x8f, 0xc5, 0xe4, 0xe4, 0xd2, 0x12, 0x21, 0xa1, 0x84, 0x77, 0xac, 0x81, 0x1a, 0xec,
+       0x7c, 0x1a, 0xe9, 0x11, 0x8d, 0x48, 0x01, 0x3b, 0x4f, 0xab, 0x5b, 0x5a, 0x05, 0x96, 0x68, 0x81,
+       0x1a, 0x88, 0xde, 0xb3, 0xa4, 0x90, 0xf9,
+
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */, ret = 1;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server TLS | visit https://localhost:7681\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-h"))
+               info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.server_ssl_cert_mem                = cert_pem;
+       info.server_ssl_cert_mem_len            = strlen(cert_pem);
+       info.server_ssl_private_key_mem         = key_pem;
+       info.server_ssl_private_key_mem_len     = strlen(key_pem);
+       info.vhost_name = "first";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create first vhost\n");
+               goto bail;
+       }
+
+       info.port = 7682;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.server_ssl_cert_mem                = cert_der;
+       info.server_ssl_cert_mem_len            = sizeof(cert_der);
+       info.server_ssl_private_key_mem         = key_der;
+       info.server_ssl_private_key_mem_len     = sizeof(key_der);
+       info.vhost_name = "second";
+
+       if (!lws_create_vhost(context, &info)) {
+               lwsl_err("Failed to create second vhost\n");
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       ret = 0;
+
+bail:
+       lws_context_destroy(context);
+
+       return ret;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6fdd6bf
--- /dev/null
@@ -0,0 +1,11 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/example.js b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/example.js
new file mode 100644 (file)
index 0000000..1606ea0
--- /dev/null
@@ -0,0 +1,22 @@
+document.addEventListener("DOMContentLoaded", function() {
+
+       var transport_protocol = "";
+       
+       if ( performance && performance.timing.nextHopProtocol ) {
+           transport_protocol = performance.timing.nextHopProtocol;
+       } else if ( window.chrome && window.chrome.loadTimes ) {
+           transport_protocol = window.chrome.loadTimes().connectionInfo;
+       } else {
+       
+         var p = performance.getEntriesByType("resource");
+         for (var i=0; i < p.length; i++) {
+               var value = "nextHopProtocol" in p[i];
+                 if (value)
+                   transport_protocol = p[i].nextHopProtocol;
+           }
+          }
+          
+          if (transport_protocol == "h2")
+               document.getElementById("transport").innerHTML = "<img src=\"/http2.png\">";
+       }
+}, false);
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/http2.png
new file mode 100644 (file)
index 0000000..439bfa4
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/http2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/index.html
new file mode 100644 (file)
index 0000000..c9a1293
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal https server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+               <br>
+               <div id="transport"></div>
+       </body>
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-tls-mem/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-tls/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-tls/CMakeLists.txt
new file mode 100644 (file)
index 0000000..758d973
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server-tls)
+set(SRCS minimal-http-server-tls.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_OPENSSL_SUPPORT 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/http-server/minimal-http-server-tls/README.md b/minimal-examples/http-server/minimal-http-server-tls/README.md
new file mode 100644 (file)
index 0000000..b10ffed
--- /dev/null
@@ -0,0 +1,45 @@
+# lws minimal http server with tls
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server-tls
+[2018/03/20 13:23:13:0131] USER: LWS minimal http server TLS | visit https://localhost:7681
+[2018/03/20 13:23:13:0142] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/20 13:23:13:0142] NOTICE:  Using SSL mode
+[2018/03/20 13:23:13:0146] NOTICE:  SSL ECDH curve 'prime256v1'
+[2018/03/20 13:23:13:0146] NOTICE:  HTTP2 / ALPN enabled
+[2018/03/20 13:23:13:0195] NOTICE: lws_tls_client_create_vhost_context: doing cert filepath localhost-100y.cert
+[2018/03/20 13:23:13:0195] NOTICE: Loaded client cert localhost-100y.cert
+[2018/03/20 13:23:13:0195] NOTICE: lws_tls_client_create_vhost_context: doing private key filepath
+[2018/03/20 13:23:13:0196] NOTICE: Loaded client cert private key localhost-100y.key
+[2018/03/20 13:23:13:0196] NOTICE: created client ssl context for default
+[2018/03/20 13:23:14:0207] NOTICE:    vhost default: cert expiry: 730459d
+```
+
+Visit https://localhost:7681
+
+Because it uses a selfsigned certificate, you will have to make an exception for it in your browser.
+
+## Certificate creation
+
+The selfsigned certs provided were created with
+
+```
+echo -e "GB\nErewhon\nAll around\nlibwebsockets-test\n\nlocalhost\nnone@invalid.org\n" | openssl req -new -newkey rsa:4096 -days 36500 -nodes -x509 -keyout "localhost-100y.key" -out "localhost-100y.cert"
+```
+
+they cover "localhost" and last 100 years from 2018-03-20.
+
+You can replace them with commercial certificates matching your hostname.
+
+## HTTP/2
+
+If you built lws with `-DLWS_WITH_HTTP2=1` at cmake, this simple server is also http/2 capable
+out of the box.  If the index.html was loaded over http/2, it will display an HTTP 2 png.
diff --git a/minimal-examples/http-server/minimal-http-server-tls/localhost-100y.cert b/minimal-examples/http-server/minimal-http-server-tls/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/http-server/minimal-http-server-tls/localhost-100y.key b/minimal-examples/http-server/minimal-http-server-tls/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c
new file mode 100644 (file)
index 0000000..3cda698
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * lws-minimal-http-server-tls
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with three extra lines giving it tls (ssl) capabilities, which in
+ * turn allow operation with HTTP/2 if lws was configured for it.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ *
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server TLS | visit https://localhost:7681\n");
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+       info.ssl_cert_filepath = "localhost-100y.cert";
+       info.ssl_private_key_filepath = "localhost-100y.key";
+
+       if (lws_cmdline_option(argc, argv, "-h"))
+               info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/404.html
new file mode 100644 (file)
index 0000000..6fdd6bf
--- /dev/null
@@ -0,0 +1,11 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/example.js b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/example.js
new file mode 100644 (file)
index 0000000..1606ea0
--- /dev/null
@@ -0,0 +1,22 @@
+document.addEventListener("DOMContentLoaded", function() {
+
+       var transport_protocol = "";
+       
+       if ( performance && performance.timing.nextHopProtocol ) {
+           transport_protocol = performance.timing.nextHopProtocol;
+       } else if ( window.chrome && window.chrome.loadTimes ) {
+           transport_protocol = window.chrome.loadTimes().connectionInfo;
+       } else {
+       
+         var p = performance.getEntriesByType("resource");
+         for (var i=0; i < p.length; i++) {
+               var value = "nextHopProtocol" in p[i];
+                 if (value)
+                   transport_protocol = p[i].nextHopProtocol;
+           }
+          }
+          
+          if (transport_protocol == "h2")
+               document.getElementById("transport").innerHTML = "<img src=\"/http2.png\">";
+       }
+}, false);
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/http2.png b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/http2.png
new file mode 100644 (file)
index 0000000..439bfa4
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/http2.png differ
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/index.html
new file mode 100644 (file)
index 0000000..c9a1293
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal https server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+               <br>
+               <div id="transport"></div>
+       </body>
+</html>
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server-tls/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server-tls/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a0fa4ec
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-http-server)
+set(SRCS minimal-http-server.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/http-server/minimal-http-server/README.md b/minimal-examples/http-server/minimal-http-server/README.md
new file mode 100644 (file)
index 0000000..cc8794b
--- /dev/null
@@ -0,0 +1,18 @@
+# lws minimal http server
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-http-server
+[2018/03/04 09:30:02:7986] USER: LWS minimal http server | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681
+
diff --git a/minimal-examples/http-server/minimal-http-server/minimal-http-server.c b/minimal-examples/http-server/minimal-http-server/minimal-http-server.c
new file mode 100644 (file)
index 0000000..1cd2622
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * lws-minimal-http-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal http server | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/http-server/minimal-http-server/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server/mount-origin/favicon.ico differ
diff --git a/minimal-examples/http-server/minimal-http-server/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server/mount-origin/index.html
new file mode 100644 (file)
index 0000000..bc9ffa4
--- /dev/null
@@ -0,0 +1,15 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal http server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/http-server/minimal-http-server/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/http-server/minimal-http-server/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/http-server/minimal-http-server/mount-origin/strict-csp.svg b/minimal-examples/http-server/minimal-http-server/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/raw/README.md b/minimal-examples/raw/README.md
new file mode 100644 (file)
index 0000000..ea3bd7a
--- /dev/null
@@ -0,0 +1,11 @@
+|name|demonstrates|
+---|---
+minimal-raw-adopt-tcp|Shows how to have lws adopt an existing tcp socket something else had connected
+minimal-raw-adopt-udp|Shows how to create a udp socket and read and write on it
+minimal-raw-fallback-http|Shows how to run a normal http(s) server that falls back to a specified role + protocol
+minimal-raw-file|Shows how to adopt a file descriptor (device node, fifo, file, etc) into the lws event loop and handle events
+minimal-raw-netcat|Writes stdin to a remote server and prints results on stdout
+minimal-raw-proxy-fallback|Shows how to run a normal http(s) server that falls back to a proxied connection to a specified IP and port
+minimal-raw-proxy|Shows how to set up a vhost so it listens for connections and proxies them to a specified IP and port
+minimal-raw-vhost|Shows how to set up a vhost that listens and accepts RAW socket connections
+
diff --git a/minimal-examples/raw/minimal-raw-adopt-tcp/CMakeLists.txt b/minimal-examples/raw/minimal-raw-adopt-tcp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6ee4fb8
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-adopt-tcp)
+set(SRCS minimal-raw-adopt-tcp.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif() 
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-adopt-tcp/README.md b/minimal-examples/raw/minimal-raw-adopt-tcp/README.md
new file mode 100644 (file)
index 0000000..605c722
--- /dev/null
@@ -0,0 +1,57 @@
+# lws minimal ws server raw adopt tcp
+
+This example is only meaningful if you are integrating lws in another
+app which generates its own connected sockets.  In some cases you may
+want lws to "adopt" the socket.
+
+(If you simply want a connected client raw socket using lws alone, you
+can just use lws_client_connect_via_info() with info.method = "RAW".
+http-client/minimal-http-client shows how to do that, just set
+info.method to "RAW".)
+
+This example demonstrates how to adopt a foreign, connected socket into lws
+as a raw wsi, bound to a specific lws protocol.
+
+The example connects a socket itself to libwebsockets.org:80, and then
+has lws adopt it as a raw wsi.  The lws protocol writes "GET / HTTP/1.1"
+to the socket and hexdumps what was sent back.
+
+The socket won't close until the server side times it out, since it's
+a raw socket that doesn't understand it's looking at http.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-raw-adopt-tcp
+[2018/03/23 09:03:57:1960] USER: LWS minimal raw adopt tcp
+[2018/03/23 09:03:57:1961] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/23 09:03:57:2079] USER: Starting connect...
+[2018/03/23 09:03:57:4963] USER: Connected...
+[2018/03/23 09:03:57:4963] USER: LWS_CALLBACK_RAW_ADOPT
+[2018/03/23 09:03:57:7842] USER: LWS_CALLBACK_RAW_RX (186)
+[2018/03/23 09:03:57:7842] NOTICE: 
+[2018/03/23 09:03:57:7842] NOTICE: 0000: 48 54 54 50 2F 31 2E 31 20 33 30 31 20 52 65 64    HTTP/1.1 301 Red
+[2018/03/23 09:03:57:7842] NOTICE: 0010: 69 72 65 63 74 0D 0A 73 65 72 76 65 72 3A 20 6C    irect..server: l
+[2018/03/23 09:03:57:7842] NOTICE: 0020: 77 73 77 73 0D 0A 53 74 72 69 63 74 2D 54 72 61    wsws..Strict-Tra
+[2018/03/23 09:03:57:7843] NOTICE: 0030: 6E 73 70 6F 72 74 2D 53 65 63 75 72 69 74 79 3A    nsport-Security:
+[2018/03/23 09:03:57:7843] NOTICE: 0040: 20 6D 61 78 2D 61 67 65 3D 31 35 37 36 38 30 30     max-age=1576800
+[2018/03/23 09:03:57:7843] NOTICE: 0050: 30 20 3B 20 69 6E 63 6C 75 64 65 53 75 62 44 6F    0 ; includeSubDo
+[2018/03/23 09:03:57:7843] NOTICE: 0060: 6D 61 69 6E 73 0D 0A 6C 6F 63 61 74 69 6F 6E 3A    mains..location:
+[2018/03/23 09:03:57:7843] NOTICE: 0070: 20 68 74 74 70 73 3A 2F 2F 6C 69 62 77 65 62 73     https://libwebs
+[2018/03/23 09:03:57:7843] NOTICE: 0080: 6F 63 6B 65 74 73 2E 6F 72 67 0D 0A 63 6F 6E 74    ockets.org..cont
+[2018/03/23 09:03:57:7843] NOTICE: 0090: 65 6E 74 2D 74 79 70 65 3A 20 74 65 78 74 2F 68    ent-type: text/h
+[2018/03/23 09:03:57:7843] NOTICE: 00A0: 74 6D 6C 0D 0A 63 6F 6E 74 65 6E 74 2D 6C 65 6E    tml..content-len
+[2018/03/23 09:03:57:7843] NOTICE: 00B0: 67 74 68 3A 20 30 0D 0A 0D 0A                      gth: 0....      
+[2018/03/23 09:03:57:7843] NOTICE: 
+[2018/03/23 09:04:03:3627] USER: LWS_CALLBACK_RAW_CLOSE
+
+```
+
+Note the example does everything itself, after 5s idle the remote server closes the connection
+after which the example continues until you ^C it.
diff --git a/minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c b/minimal-examples/raw/minimal-raw-adopt-tcp/minimal-raw-adopt-tcp.c
new file mode 100644 (file)
index 0000000..1134234
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * lws-minimal-raw-adopt-tcp
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates integrating somebody else's connected tcp
+ * socket into the lws event loop as a RAW wsi.  It's interesting in
+ * the kind of situation where you already have a connected socket
+ * in your application, and you need to hand it over to lws to deal with.
+ *
+ * Lws supports "adopting" these foreign sockets.
+ *
+ * If you simply want a connected client raw socket using lws alone, you
+ * can just use lws_client_connect_via_info() with info.method = "RAW".
+ *
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#if !defined(WIN32)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#endif
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+static int
+callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+
+       switch (reason) {
+
+       /* callbacks related to raw socket descriptor */
+
+        case LWS_CALLBACK_RAW_ADOPT:
+               lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
+               lws_callback_on_writable(wsi);
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
+               lwsl_hexdump_level(LLL_NOTICE, in, len);
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+               if (lws_write(wsi,
+                             (uint8_t *)"GET / HTTP/1.1\xd\xa\xd\xa", 18,
+                             LWS_WRITE_RAW) != 18) {
+                       lwsl_notice("%s: raw write failed\n", __func__);
+                       return 1;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static struct lws_protocols protocols[] = {
+       { "raw-test", callback_raw_test, 0, 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       lws_sock_file_fd_type sock;
+       struct addrinfo h, *r, *rp;
+       struct lws_vhost *vhost;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw adopt tcp\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.port = CONTEXT_PORT_NO_LISTEN_SERVER;
+       info.protocols = protocols;
+
+       vhost = lws_create_vhost(context, &info);
+       if (!vhost) {
+               lwsl_err("lws vhost creation failed\n");
+               goto bail;
+       }
+
+       /*
+        * Connect our own "foreign" socket to libwebsockets.org:80
+        *
+        * Normally you would do this with lws_client_connect_via_info() inside
+        * the lws event loop, hiding all this detail.  But this example
+        * demonstrates how to integrate an externally-connected "foreign"
+        * socket, so we create one by hand.
+        */
+
+       memset(&h, 0, sizeof(h));
+       h.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
+       h.ai_socktype = SOCK_STREAM;
+       h.ai_protocol = IPPROTO_TCP;
+
+       n = getaddrinfo("libwebsockets.org", "80", &h, &r);
+       if (n) {
+               lwsl_err("%s: problem resolving libwebsockets.org: %s\n", __func__, gai_strerror(n));
+               return 1;
+       }
+
+       for (rp = r; rp; rp = rp->ai_next) {
+               sock.sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+               if (sock.sockfd != LWS_SOCK_INVALID)
+                       break;
+       }
+       if (!rp) {
+               lwsl_err("%s: unable to create INET socket\n", __func__);
+               freeaddrinfo(r);
+
+               return 1;
+       }
+
+       lwsl_user("Starting connect...\n");
+       if (connect(sock.sockfd, rp->ai_addr, sizeof(*rp->ai_addr)) < 0) {
+               lwsl_err("%s: unable to connect to libwebsockets.org:80\n", __func__);
+               freeaddrinfo(r);
+               return 1;
+       }
+
+       freeaddrinfo(r);
+       signal(SIGINT, sigint_handler);
+       lwsl_user("Connected...\n");
+
+       /* our foreign socket is connected... adopt it into lws */
+
+       if (!lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_SOCKET, sock,
+                                      protocols[0].name, NULL)) {
+               lwsl_err("%s: foreign socket adoption failed\n", __func__);
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt b/minimal-examples/raw/minimal-raw-adopt-udp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7262705
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-adopt-udp)
+set(SRCS minimal-raw-adopt-udp.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif() 
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/README.md b/minimal-examples/raw/minimal-raw-adopt-udp/README.md
new file mode 100644 (file)
index 0000000..edaf8d2
--- /dev/null
@@ -0,0 +1,49 @@
+# lws minimal ws server raw adopt udp
+
+This example demonstrates echoing packets on a UDP socket in lws.
+
+A "foreign" UDP socket is created, bound (so it can "listen"), and
+adopted into lws event loop.  It acts like a tcp RAW mode connection in
+lws and uses the same callbacks.
+
+Writing is a bit different for UDP.  By default, the system has no
+idea about the receiver state and so asking for a callback_on_writable()
+always believes that the socket is writeable... the callback will
+happen next time around the event loop if there are no pending partials.
+
+With UDP, there is no "connection".  You need to write with sendto() and
+direct the packets to a specific destination.  You can learn the source
+of the last packet that arrived at the LWS_CALLBACK_RAW_RX callback by
+getting a `struct lws_udp *` from `lws_get_udp(wsi)`.  To be able to
+send back to that guy, you should take a copy of the `struct lws_udp *` and
+use the .sa and .salen members in your sendto().
+
+However the kernel may not accept to buffer / write everything you wanted to send.
+So you are responsible to watch the result of sendto() and resend the
+unsent part next time.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-raw-adopt-udp
+$ ./lws-minimal-raw-adopt-udp 
+[2018/03/24 08:12:37:8869] USER: LWS minimal raw adopt udp | nc -u 127.0.0.1 7681
+[2018/03/24 08:12:37:8870] NOTICE: Creating Vhost 'default' (no listener), 1 protocols, IPv6 off
+[2018/03/24 08:12:37:8878] USER: LWS_CALLBACK_RAW_ADOPT
+[2018/03/24 08:12:41:5656] USER: LWS_CALLBACK_RAW_RX (6)
+[2018/03/24 08:12:41:5656] NOTICE: 
+[2018/03/24 08:12:41:5656] NOTICE: 0000: 68 65 6C 6C 6F 0A                                  hello.          
+[2018/03/24 08:12:41:5656] NOTICE: 
+```
+
+```
+ $ nc -u 127.0.0.1 7681
+hello
+hello
+```
diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c
new file mode 100644 (file)
index 0000000..5689563
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * lws-minimal-raw-adopt-udp
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates integrating a connected udp
+ * socket into the lws event loop as a RAW wsi.  It's interesting in
+ * the kind of situation where you already have a connected socket
+ * in your application, and you need to hand it over to lws to deal with.
+ *
+ * Lws supports "adopting" these foreign sockets, and also has a helper API
+ * to create, bind, and adopt them inside lws.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#if !defined(WIN32)
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#endif
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+static uint8_t sendbuf[4096];
+static size_t sendlen;
+struct lws_udp udp;
+
+static int
+callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       ssize_t n;
+       int fd;
+
+       switch (reason) {
+
+       /* callbacks related to raw socket descriptor */
+
+        case LWS_CALLBACK_RAW_ADOPT:
+               lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
+               lwsl_hexdump_level(LLL_NOTICE, in, len);
+               /*
+                * Take a copy of the buffer and the source socket address...
+                */
+               udp = *(lws_get_udp(wsi));
+               sendlen = len;
+               if (sendlen > sizeof(sendbuf))
+                       sendlen = sizeof(sendbuf);
+               memcpy(sendbuf, in, sendlen);
+               /*
+                * ... and we send it next time around the event loop.  This
+                * can be extended to having a ringbuffer of different send
+                * buffers and targets queued.
+                *
+                * Note that UDP is ALWAYS writable as far as poll() knows
+                * because there is no mechanism like the tcp window to
+                * understand that packets are not being acknowledged.  But
+                * this allows the event loop to share out the work.
+                */
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+
+               if (!sendlen)
+                       break;
+
+               fd = lws_get_socket_fd(wsi);
+               if (fd < 0) /* keep Coverity happy: actually it cannot be < 0 */
+                       break;
+
+               /*
+                * We can write directly on the UDP socket, specifying
+                * the peer the write is directed to.
+                *
+                * However the kernel may only accept parts of large sendto()s,
+                * leaving you to try to resend the remainder later.  However
+                * depending on how your protocol on top of UDP works, that
+                * may involve sticking new headers before the remainder.
+                *
+                * For clarity partial sends just drop the remainder here.
+                */
+               n = sendto(fd,
+#if defined(WIN32)
+                               (const char *)
+#endif
+                       sendbuf, sendlen, 0, &udp.sa, udp.salen);
+               if (n < (ssize_t)len)
+                       lwsl_notice("%s: send returned %d\n", __func__, (int)n);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static struct lws_protocols protocols[] = {
+       { "raw-test", callback_raw_test, 0, 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw adopt udp | nc -u 127.0.0.1 7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.port = CONTEXT_PORT_NO_LISTEN_SERVER;
+       info.protocols = protocols;
+
+       vhost = lws_create_vhost(context, &info);
+       if (!vhost) {
+               lwsl_err("lws vhost creation failed\n");
+               goto bail;
+       }
+
+       /*
+        * Create our own "foreign" UDP socket bound to 7681/udp
+        */
+       if (!lws_create_adopt_udp(vhost, 7681, LWS_CAUDP_BIND,
+                                 protocols[0].name, NULL)) {
+               lwsl_err("%s: foreign socket adoption failed\n", __func__);
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/CMakeLists.txt b/minimal-examples/raw/minimal-raw-fallback-http-server/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f0cb7b4
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-fallback-http-server)
+set(SRCS minimal-raw-fallback-http-server.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/README.md b/minimal-examples/raw/minimal-raw-fallback-http-server/README.md
new file mode 100644 (file)
index 0000000..9a827c5
--- /dev/null
@@ -0,0 +1,41 @@
+# lws minimal raw fallback http server
+
+This is the same as the minimal http server, with one difference...
+if you connect to localhost:7681 with something that doesn't send
+recognizable http, then the connection will be switched to a
+raw-skt role and bind to a protocol that echoes anything sent back
+to the sender.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-s|Configure the server for tls / https and `LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT`
+-h|(needs -s) Configure the vhost also for `LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER`, allowing http service on tls port (caution... it's insecure then)
+-u|(needs -s) Configure the vhost also for `LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS`, so the server issues a redirect to https to clients that attempt to connect to a server configured for tls with http.
+
+```
+ $ ./lws-minimal-raw-fallback-http-server
+[2018/11/29 14:27:34:3014] USER: LWS minimal raw fallback http server | visit http://localhost:7681
+[2018/11/29 14:27:34:3243] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+```
+
+Visit http://127.0.0.1:7681
+
+This allows testing of various combinations of special features for unexpected
+content on an http(s) listening socket.
+
+|cmdline args|http://127.0.0.1:7681|https://127.0.0.1:7681|ssh -p7681 127.0.0.1|flags|
+|---|---|---|---|---|
+|none|served|no tls|echos hello|LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG
+|-s|echos http GET|served|echos hello|LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT
+|-s -h|served|served|echos hello|LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT, LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER
+|-s -u|redirected to https|served|echos hello|LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT, LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS
+
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/localhost-100y.cert b/minimal-examples/raw/minimal-raw-fallback-http-server/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/localhost-100y.key b/minimal-examples/raw/minimal-raw-fallback-http-server/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c b/minimal-examples/raw/minimal-raw-fallback-http-server/minimal-raw-fallback-http-server.c
new file mode 100644 (file)
index 0000000..1a7edaa
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * lws-minimal-raw-fallback http-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff from the subdirectory 
+ * "./mount-origin" of the directory it was started in.
+ * You can change that by changing mount.origin below.
+ *
+ * In addition, if the connection does to seem to be talking http, then it
+ * falls back to a raw echo protocol.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+struct pss__raw_echo {
+       uint8_t buf[2048];
+       int len;
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static int
+callback_raw_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+                 void *in, size_t len)
+{
+       struct pss__raw_echo *pss = (struct pss__raw_echo *)user;
+
+       switch (reason) {
+       case LWS_CALLBACK_RAW_ADOPT:
+               lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n");
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len);
+               if (len > sizeof(pss->buf))
+                       len = sizeof(pss->buf);
+               memcpy(pss->buf, in, len);
+               pss->len = len;
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n");
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+               lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n");
+               lws_write(wsi, pss->buf, pss->len, LWS_WRITE_HTTP);
+               break;
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       { "raw-echo", callback_raw_echo, sizeof(struct pss__raw_echo), 2048 },
+       { NULL, NULL, 0, 0 }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw fallback http server | "
+                 "visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE |
+               LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG;
+       info.listen_accept_role = "raw-skt";
+       info.listen_accept_protocol = "raw-echo";
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                               LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+
+               if (lws_cmdline_option(argc, argv, "-u"))
+                       info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
+
+               if (lws_cmdline_option(argc, argv, "-h"))
+                       info.options |= LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER;
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/404.html b/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/favicon.ico b/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/favicon.ico differ
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/index.html b/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/index.html
new file mode 100644 (file)
index 0000000..573e515
--- /dev/null
@@ -0,0 +1,15 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal raw fallback http server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/strict-csp.svg b/minimal-examples/raw/minimal-raw-fallback-http-server/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/raw/minimal-raw-file/CMakeLists.txt b/minimal-examples/raw/minimal-raw-file/CMakeLists.txt
new file mode 100644 (file)
index 0000000..dc0f863
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-file)
+set(SRCS minimal-raw-file.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/raw/minimal-raw-file/README.md b/minimal-examples/raw/minimal-raw-file/README.md
new file mode 100644 (file)
index 0000000..47fba24
--- /dev/null
@@ -0,0 +1,48 @@
+# lws minimal ws server
+
+This demonstrates adopting a file descriptor into the lws event
+loop.  The filepath to open and adopt is given as an argument to the example app, eg
+
+```
+ $ ./lws-minimal-raw-file <file>
+```
+
+On a Linux system, some example files for testing might be
+
+ - /proc/self/fd/0      (stdin)
+ - /dev/ttyUSB0         (a USB <-> serial converter)
+ - /dev/input/event<n>  (needs root... input device events)
+
+The example application opens the file in the protocol init
+handler, and hexdumps data from the file to the lws log
+as it becomes available.
+
+This isn't very useful standalone as shown here for clarity, but you can
+freely combine raw file descriptor adoption with other lws server
+and client features.
+
+Becuase raw file events have their own callback reasons, the handlers can
+be integrated in a single protocol that also handles http and ws
+server and client callbacks without conflict.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-raw-file /proc/self/fd/0
+[2018/03/22 10:48:53:9709] USER: LWS minimal raw file
+[2018/03/22 10:48:53:9876] NOTICE: Creating Vhost 'default' port -2, 1 protocols, IPv6 off
+[2018/03/22 10:48:55:0037] NOTICE: LWS_CALLBACK_RAW_ADOPT_FILE
+
+[2018/03/22 10:48:55:9370] NOTICE: LWS_CALLBACK_RAW_RX_FILE
+[2018/03/22 10:48:55:9377] NOTICE: 
+[2018/03/22 10:48:55:9408] NOTICE: 0000: 0A                                                 .               
+
+```
+
+The example logs above show the result of typing the Enter key.
diff --git a/minimal-examples/raw/minimal-raw-file/minimal-raw-file.c b/minimal-examples/raw/minimal-raw-file/minimal-raw-file.c
new file mode 100644 (file)
index 0000000..21c5c48
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * lws-minimal-raw-file
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates adopting a file descriptor into the lws event
+ * loop.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+struct raw_vhd {
+//     lws_sock_file_fd_type u;
+       int filefd;
+};
+
+static char filepath[256];
+
+static int
+callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
+                                    lws_get_vhost(wsi), lws_get_protocol(wsi));
+       lws_sock_file_fd_type u;
+       uint8_t buf[1024];
+       int n;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi), sizeof(struct raw_vhd));
+               vhd->filefd = lws_open(filepath, O_RDWR);
+               if (vhd->filefd == -1) {
+                       lwsl_err("Unable to open %s\n", filepath);
+
+                       return 1;
+               }
+               u.filefd = (lws_filefd_type)(long long)vhd->filefd;
+               if (!lws_adopt_descriptor_vhost(lws_get_vhost(wsi),
+                                               LWS_ADOPT_RAW_FILE_DESC, u,
+                                               "raw-test", NULL)) {
+                       lwsl_err("Failed to adopt fifo descriptor\n");
+                       close(vhd->filefd);
+                       vhd->filefd = -1;
+
+                       return 1;
+               }
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               if (vhd && vhd->filefd != -1)
+                       close(vhd->filefd);
+               break;
+
+       /* callbacks related to raw file descriptor */
+
+       case LWS_CALLBACK_RAW_ADOPT_FILE:
+               lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n");
+               break;
+
+       case LWS_CALLBACK_RAW_RX_FILE:
+               lwsl_notice("LWS_CALLBACK_RAW_RX_FILE\n");
+               n = read(vhd->filefd, buf, sizeof(buf));
+               if (n < 0) {
+                       lwsl_err("Reading from %s failed\n", filepath);
+
+                       return 1;
+               }
+               lwsl_hexdump_level(LLL_NOTICE, buf, n);
+               break;
+
+       case LWS_CALLBACK_RAW_CLOSE_FILE:
+               lwsl_notice("LWS_CALLBACK_RAW_CLOSE_FILE\n");
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE_FILE:
+               lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n");
+               /*
+                * you can call lws_callback_on_writable() on a raw file wsi as
+                * usual, and then write directly into the raw filefd here.
+                */
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static struct lws_protocols protocols[] = {
+       { "raw-test", callback_raw_test, 0, 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw file\n");
+       if (argc < 2) {
+               lwsl_user("Usage: %s <file to monitor>  "
+                         " eg, /dev/ttyUSB0 or /dev/input/event0 or "
+                         "/proc/self/fd/0\n", argv[0]);
+
+               return 1;
+       }
+
+       signal(SIGINT, sigint_handler);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN_SERVER; /* no listen socket for demo */
+       info.protocols = protocols;
+
+       lws_strncpy(filepath, argv[1], sizeof(filepath));
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-netcat/CMakeLists.txt b/minimal-examples/raw/minimal-raw-netcat/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ba3997d
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-netcat)
+set(SRCS minimal-raw-netcat.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif() 
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-netcat/README.md b/minimal-examples/raw/minimal-raw-netcat/README.md
new file mode 100644 (file)
index 0000000..b50483c
--- /dev/null
@@ -0,0 +1,38 @@
+# lws minimal raw netcat
+
+This example shows to to create a "netcat" that copies its stdin to
+a remote socket and prints what is returned in stdout.
+
+It has some advantage over the real netcat, it will wait 1s after stdin closes
+to print results that are in flight.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ echo -e -n "GET / http/1.1\r\n\r\n"| ./lws-minimal-raw-netcat
+[2018/05/02 08:53:53:2665] USER: LWS minimal raw netcat [--server ip] [--port port]
+[2018/05/02 08:53:53:2667] NOTICE: Creating Vhost 'default' (no listener), 1 protocols, IPv6 off
+[2018/05/02 08:53:53:2703] USER: Starting connect...
+[2018/05/02 08:53:53:5644] USER: Connected to libwebsockets.org:80...
+[2018/05/02 08:53:53:5645] USER: LWS_CALLBACK_RAW_ADOPT
+[2018/05/02 08:53:53:5645] USER: LWS_CALLBACK_RAW_ADOPT_FILE
+[2018/05/02 08:53:53:5646] USER: LWS_CALLBACK_RAW_RX_FILE
+[2018/05/02 08:53:53:5646] USER: LWS_CALLBACK_RAW_CLOSE_FILE
+[2018/05/02 08:53:53:8600] USER: LWS_CALLBACK_RAW_RX (186)
+HTTP/1.1 301 Redirect
+server: lwsws
+Strict-Transport-Security: max-age=15768000 ; includeSubDomains
+location: https://libwebsockets.org
+content-type: text/html
+content-length: 0
+
+```
+
+Note the example does everything itself, after 5s idle the remote server closes the connection
+after which the example continues until you ^C it.
diff --git a/minimal-examples/raw/minimal-raw-netcat/minimal-raw-netcat.c b/minimal-examples/raw/minimal-raw-netcat/minimal-raw-netcat.c
new file mode 100644 (file)
index 0000000..1d8b59d
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * lws-minimal-raw-netcat
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates sending stdin to a remote socket and printing
+ * what is returned to stdout.
+ *
+ * All the logging is on stderr, so you can tune it out with 2>log
+ * or whatever.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#if !defined(WIN32)
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+static struct lws *raw_wsi, *stdin_wsi;
+static uint8_t buf[LWS_PRE + 4096];
+static int waiting, interrupted;
+static struct lws_context *context;
+static int us_wait_after_input_close = LWS_USEC_PER_SEC / 10;
+
+static int
+callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
+                 void *user, void *in, size_t len)
+{
+       const char *cp = (const char *)in;
+
+       switch (reason) {
+
+       /* callbacks related to file descriptor */
+
+        case LWS_CALLBACK_RAW_ADOPT_FILE:
+               lwsl_user("LWS_CALLBACK_RAW_ADOPT_FILE\n");
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE_FILE:
+               lwsl_user("LWS_CALLBACK_RAW_CLOSE_FILE\n");
+               /* stdin close, wait 1s then close the raw skt */
+               stdin_wsi = NULL; /* invalid now we close */
+               if (raw_wsi)
+                       lws_set_timer_usecs(raw_wsi, us_wait_after_input_close);
+               else {
+                       interrupted = 1;
+                       lws_cancel_service(context);
+               }
+               break;
+
+       case LWS_CALLBACK_RAW_RX_FILE:
+               lwsl_user("LWS_CALLBACK_RAW_RX_FILE\n");
+               waiting = read(0, buf, sizeof(buf));
+               lwsl_notice("raw file read %d\n", waiting);
+               if (waiting < 0)
+                       return -1;
+
+               if (raw_wsi)
+                       lws_callback_on_writable(raw_wsi);
+               lws_rx_flow_control(wsi, 0);
+               break;
+
+
+       /* callbacks related to raw socket descriptor */
+
+        case LWS_CALLBACK_RAW_ADOPT:
+               lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
+               lws_callback_on_writable(wsi);
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
+               /*
+                * If the socket to the remote server closed, we must close
+                * and drop any remaining stdin
+                */
+               interrupted = 1;
+               lws_cancel_service(context);
+               /* our pointer to this wsi is invalid now we close */
+               raw_wsi = NULL;
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
+               while (len--)
+                       putchar(*cp++);
+               fflush(stdout);
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+               lwsl_user("LWS_CALLBACK_RAW_WRITEABLE\n");
+               // lwsl_hexdump_info(buf, waiting);
+               if (stdin_wsi)
+                       lws_rx_flow_control(stdin_wsi, 1);
+               if (lws_write(wsi, buf, waiting, LWS_WRITE_RAW) != waiting) {
+                       lwsl_notice("%s: raw skt write failed\n", __func__);
+
+                       return -1;
+               }
+               break;
+
+       case LWS_CALLBACK_TIMER:
+               lwsl_user("LWS_CALLBACK_TIMER\n");
+               interrupted = 1;
+               lws_cancel_service(context);
+               return -1;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static struct lws_protocols protocols[] = {
+       { "raw-test", callback_raw_test, 0, 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       const char *server = "libwebsockets.org", *port = "80";
+       struct lws_context_creation_info info;
+       lws_sock_file_fd_type sock;
+       struct addrinfo h, *r, *rp;
+       struct lws_vhost *vhost;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw netcat [--server ip] [--port port] [-w ms]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       info.port = CONTEXT_PORT_NO_LISTEN_SERVER;
+       info.protocols = protocols;
+
+       vhost = lws_create_vhost(context, &info);
+       if (!vhost) {
+               lwsl_err("lws vhost creation failed\n");
+               goto bail;
+       }
+
+       /*
+        * Connect our own "foreign" socket to libwebsockets.org:80
+        *
+        * Normally you would do this with lws_client_connect_via_info() inside
+        * the lws event loop, hiding all this detail.  But this example
+        * demonstrates how to integrate an externally-connected "foreign"
+        * socket, so we create one by hand.
+        */
+
+       memset(&h, 0, sizeof(h));
+       h.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
+       h.ai_socktype = SOCK_STREAM;
+       h.ai_protocol = IPPROTO_TCP;
+
+       if ((p = lws_cmdline_option(argc, argv, "--port")))
+               port = p;
+
+       if ((p = lws_cmdline_option(argc, argv, "--server")))
+               server = p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-w")))
+               us_wait_after_input_close = 1000 * atoi(p);
+
+       n = getaddrinfo(server, port, &h, &r);
+       if (n) {
+               lwsl_err("%s: problem resolving %s: %s\n", __func__, 
+                        server, gai_strerror(n));
+               return 1;
+       }
+
+       for (rp = r; rp; rp = rp->ai_next) {
+               sock.sockfd = socket(rp->ai_family, rp->ai_socktype,
+                                    rp->ai_protocol);
+               if (sock.sockfd != LWS_SOCK_INVALID)
+                       break;
+       }
+       if (!rp) {
+               lwsl_err("%s: unable to create INET socket\n", __func__);
+               freeaddrinfo(r);
+
+               return 1;
+       }
+
+       lwsl_user("Starting connect to %s:%s...\n", server, port);
+       if (connect(sock.sockfd, rp->ai_addr, sizeof(*rp->ai_addr)) < 0) {
+               lwsl_err("%s: unable to connect\n", __func__);
+               freeaddrinfo(r);
+               return 1;
+       }
+
+       freeaddrinfo(r);
+       signal(SIGINT, sigint_handler);
+       lwsl_user("Connected...\n");
+
+       /* our foreign socket is connected... adopt it into lws */
+
+       raw_wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_SOCKET, sock,
+                                            protocols[0].name, NULL);
+       if (!raw_wsi) {
+               lwsl_err("%s: foreign socket adoption failed\n", __func__);
+               goto bail;
+       }
+
+       sock.filefd = 0;
+       stdin_wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_RAW_FILE_DESC,
+                                              sock, protocols[0].name, NULL);
+       if (!stdin_wsi) {
+               lwsl_err("%s: stdin adoption failed\n", __func__);
+               goto bail;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+bail:
+
+       lwsl_user("%s: destroying context\n", __func__);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/CMakeLists.txt b/minimal-examples/raw/minimal-raw-proxy-fallback/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c0f72ce
--- /dev/null
@@ -0,0 +1,84 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-proxy-fallback)
+set(SRCS minimal-raw-proxy-fallback.c)
+
+# NOTE... if you are building this standalone, you must point LWS_PLUGINS_DIR
+# to the lws plugins dir so it can pick up the plugin source.  Eg,
+# cmake . -DLWS_PLUGINS_DIR=~/libwebsockets/plugins
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif() 
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_RAW_PROXY 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+       
+       if (LWS_PLUGINS_DIR)
+               include_directories(${LWS_PLUGINS_DIR})
+       endif()
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/README.md b/minimal-examples/raw/minimal-raw-proxy-fallback/README.md
new file mode 100644 (file)
index 0000000..f673f46
--- /dev/null
@@ -0,0 +1,49 @@
+# lws minimal ws server raw proxy fallback
+
+This demonstrates how a vhost doing normal http or http(s) duty can be also be
+bound to a specific role and protocol as a fallback if the incoming protocol is
+unexpected for tls or http.  The example makes the fallback role + protocol
+an lws plugin that performs raw packet proxying.
+
+By default the fallback in the example will proxy 127.0.0.1:22, which is usually
+your ssh server listen port, on 127.0.0.1:7681.  You should be able to ssh into
+port 7681 the same as you can port 22.  At the same time, you should be able to
+visit http://127.0.0.1:7681 in a browser (and if you give -s, to
+https://127.0.0.1:7681 while your ssh client can still connect to the same
+port.
+
+## build
+
+To build this standalone, you must tell cmake where the lws source tree
+./plugins directory can be found, since it relies on including the source
+of the raw-proxy plugin.
+
+```
+ $ cmake . -DLWS_PLUGINS_DIR=~/libwebsockets/plugins && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-r ipv4:address:port|Configure the remote IP and port that will be proxied, by default ipv4:127.0.0.1:22
+-s|Configure the server for tls / https and `LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT`
+-h|(needs -s) Configure the vhost also for `LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER`, allowing http service on tls port (caution... it's insecure then)
+-u|(needs -s) Configure the vhost also for `LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS`, so the server issues a redirect to https to clients that attempt to connect to a server configured for tls with http.
+```
+ $ ./lws-minimal-raw-proxy
+[2018/11/30 19:22:35:7290] USER: LWS minimal raw proxy-fallback
+[2018/11/30 19:22:35:7291] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/11/30 19:22:35:7336] NOTICE: callback_raw_proxy: onward ipv4 127.0.0.1:22
+...
+```
+
+```
+ $ ssh -p7681 me@127.0.0.1
+Last login: Fri Nov 30 19:29:23 2018 from 127.0.0.1
+[me@learn ~]$
+```
+
+At the same time, visiting http(s)://127.0.0.1:7681 in a browser works fine.
+
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/localhost-100y.cert b/minimal-examples/raw/minimal-raw-proxy-fallback/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/localhost-100y.key b/minimal-examples/raw/minimal-raw-proxy-fallback/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c b/minimal-examples/raw/minimal-raw-proxy-fallback/minimal-raw-proxy-fallback.c
new file mode 100644 (file)
index 0000000..98572ae
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * lws-minimal-raw-proxy-fallback
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a normal http / https server which if it receives something
+ * it can't make sense of at the start, falls back to becoming a raw tcp proxy
+ * to a specified address and port.
+ *
+ * Incoming connections cause an outgoing connection to be initiated, and if
+ * successfully established then traffic coming in one side is placed on a
+ * ringbuffer and sent out the opposite side as soon as possible.
+ *
+ * If it receives expected packets for an http(s) connection, it acts like a
+ * normal h1 / h2 webserver.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#define LWS_PLUGIN_STATIC
+#include "../plugins/raw-proxy/protocol_lws_raw_proxy.c"
+
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_RAW_PROXY,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static int interrupted;
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+static struct lws_protocol_vhost_options pvo1 = {
+        NULL,
+        NULL,
+        "onward",              /* pvo name */
+        "ipv4:127.0.0.1:22"    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+        NULL,                  /* "next" pvo linked-list */
+        &pvo1,                 /* "child" pvo linked-list */
+        "raw-proxy",           /* protocol name we belong to on this vhost */
+        ""                     /* ignored */
+};
+
+
+int main(int argc, const char **argv)
+{
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       char outward[256];
+       const char *p;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw proxy fallback | visit http://localhost:7681\n");
+
+       if ((p = lws_cmdline_option(argc, argv, "-r"))) {
+               lws_strncpy(outward, p, sizeof(outward));
+               pvo1.value = outward;
+       }
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       info.mounts = &mount;
+       info.error_document_404 = "/404.html";
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE |
+               LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG;
+       info.listen_accept_role = "raw-proxy";
+       info.listen_accept_protocol = "raw-proxy";
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                               LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+
+               if (lws_cmdline_option(argc, argv, "-u"))
+                       info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
+
+               if (lws_cmdline_option(argc, argv, "-h"))
+                       info.options |= LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER;
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/404.html b/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/404.html
new file mode 100644 (file)
index 0000000..3e5a14b
--- /dev/null
@@ -0,0 +1,9 @@
+<meta charset="UTF-8"> 
+<html>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <h1>404</h1>
+               Sorry, that file doesn't exist.
+       </body>
+</html>
+
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/favicon.ico b/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/favicon.ico differ
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/index.html b/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/index.html
new file mode 100644 (file)
index 0000000..573e515
--- /dev/null
@@ -0,0 +1,15 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               Hello from the <b>minimal raw fallback http server example</b>.
+               <br>
+               You can confirm the 404 page handler by going to this
+               nonexistant <a href="notextant.html">page</a>.
+       </body>
+</html>
+
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/strict-csp.svg b/minimal-examples/raw/minimal-raw-proxy-fallback/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/raw/minimal-raw-proxy/CMakeLists.txt b/minimal-examples/raw/minimal-raw-proxy/CMakeLists.txt
new file mode 100644 (file)
index 0000000..da033dd
--- /dev/null
@@ -0,0 +1,84 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-proxy)
+set(SRCS minimal-raw-proxy.c)
+
+# NOTE... if you are building this standalone, you must point LWS_PLUGINS_DIR
+# to the lws plugins dir so it can pick up the plugin source.  Eg,
+# cmake . -DLWS_PLUGINS_DIR=~/libwebsockets/plugins
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif() 
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_RAW_PROXY 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+       
+       if (LWS_PLUGINS_DIR)
+               include_directories(${LWS_PLUGINS_DIR})
+       endif()
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-proxy/README.md b/minimal-examples/raw/minimal-raw-proxy/README.md
new file mode 100644 (file)
index 0000000..53793a8
--- /dev/null
@@ -0,0 +1,41 @@
+# lws minimal ws server raw proxy
+
+This demonstrates how a vhost can be bound to a specific role and protocol,
+with the example using a lws plugin that performs raw packet proxying.
+
+By default the example will proxy 127.0.0.1:22, usually your ssh server
+listen port, on 127.0.0.1:7681.  You should be able to ssh into port 7681
+the same as you can port 22.  But your ssh server is only listening on port 22...
+
+## build
+
+To build this standalone, you must tell cmake where the lws source tree
+./plugins directory can be found, since it relies on including the source
+of the raw-proxy plugin.
+
+```
+ $ cmake . -DLWS_PLUGINS_DIR=~/libwebsockets/plugins && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-r ipv4:address:port|Configure the remote IP and port that will be proxied, by default ipv4:127.0.0.1:22
+
+```
+ $ ./lws-minimal-raw-proxy
+[2018/11/30 19:22:35:7290] USER: LWS minimal raw proxy | nc localhost 7681
+[2018/11/30 19:22:35:7291] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/11/30 19:22:35:7336] NOTICE: callback_raw_proxy: onward ipv4 127.0.0.1:22
+...
+```
+
+```
+ $ ssh -p7681 me@127.0.0.1
+Last login: Fri Nov 30 19:29:23 2018 from 127.0.0.1
+[me@learn ~]$
+```
+
+
diff --git a/minimal-examples/raw/minimal-raw-proxy/minimal-raw-proxy.c b/minimal-examples/raw/minimal-raw-proxy/minimal-raw-proxy.c
new file mode 100644 (file)
index 0000000..0999780
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * lws-minimal-raw-proxy
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a vhost that acts as a raw tcp proxy.  Incoming connections
+ * cause an outgoing connection to be initiated, and if successfully established
+ * then traffic coming in one side is placed on a ringbuffer and sent out the
+ * opposite side as soon as possible.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#define LWS_PLUGIN_STATIC
+#include "../plugins/raw-proxy/protocol_lws_raw_proxy.c"
+
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_RAW_PROXY,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+static struct lws_protocol_vhost_options pvo1 = {
+        NULL,
+        NULL,
+        "onward",          /* pvo name */
+        "ipv4:127.0.0.1:22"    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+        NULL,           /* "next" pvo linked-list */
+        &pvo1,       /* "child" pvo linked-list */
+        "raw-proxy",      /* protocol name we belong to on this vhost */
+        ""              /* ignored */
+};
+
+
+int main(int argc, const char **argv)
+{
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       char outward[256];
+       const char *p;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw proxy\n");
+
+       if ((p = lws_cmdline_option(argc, argv, "-r"))) {
+               lws_strncpy(outward, p, sizeof(outward));
+               pvo1.value = outward;
+       }
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG;
+       info.listen_accept_role = "raw-proxy";
+       info.listen_accept_protocol = "raw-proxy";
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/raw/minimal-raw-vhost/CMakeLists.txt b/minimal-examples/raw/minimal-raw-vhost/CMakeLists.txt
new file mode 100644 (file)
index 0000000..db4810b
--- /dev/null
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-raw-vhost)
+set(SRCS minimal-raw-vhost.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif() 
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/raw/minimal-raw-vhost/README.md b/minimal-examples/raw/minimal-raw-vhost/README.md
new file mode 100644 (file)
index 0000000..7992bd3
--- /dev/null
@@ -0,0 +1,42 @@
+# lws minimal ws server raw vhost
+
+This demonstrates setting up a vhost to listen and accept raw sockets.
+Raw sockets are just sockets... lws does not send anything on them or
+interpret by itself what it receives on them.  So you can implement
+arbitrary tcp protocols using them.
+
+This isn't very useful standalone as shown here for clarity, but you can
+freely combine a raw socket vhost with other lws server
+and client features and other vhosts handling http or ws.
+
+Becuase raw socket events have their own callback reasons, the handlers can
+be integrated in a single protocol that also handles http and ws
+server and client callbacks without conflict.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+ -s means listen using tls
+
+```
+ $ ./lws-minimal-raw-vhost
+[2018/03/22 14:49:47:9516] USER: LWS minimal raw vhost
+[2018/03/22 14:49:47:9673] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+[2018/03/22 14:49:52:3789] USER: LWS_CALLBACK_RAW_ADOPT
+[2018/03/22 14:49:57:4271] USER: LWS_CALLBACK_RAW_CLOSE
+```
+
+```
+ $ nc localhost 7681
+hello
+hello
+```
+
+Connect one or more sessions to the server using netcat... lines you type
+into netcat are sent to the server, which echos them to all connected clients.
+
diff --git a/minimal-examples/raw/minimal-raw-vhost/localhost-100y.cert b/minimal-examples/raw/minimal-raw-vhost/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/raw/minimal-raw-vhost/localhost-100y.key b/minimal-examples/raw/minimal-raw-vhost/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/raw/minimal-raw-vhost/minimal-raw-vhost.c b/minimal-examples/raw/minimal-raw-vhost/minimal-raw-vhost.c
new file mode 100644 (file)
index 0000000..097abef
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * lws-minimal-raw-vhost
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates integrating a raw tcp listener into the lws event loop.
+ *
+ * This demo doesn't have any http or ws support.  You can connect to it
+ * using netcat.  If you make multiple connections to it, things typed in one
+ * netcat session are broadcast to all netcat connections.
+ *
+ * $ nc localhost 7681
+ *
+ * You can add more vhosts with things like http or ws support, it's as it is
+ * for clarity.
+ *
+ * The main point is the apis and ways of managing raw sockets are almost
+ * identical to http or ws mode sockets in lws.  The callback names for raw
+ * wsi are changed to be specific to RAW mode is all.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+struct raw_pss {
+       struct raw_pss *pss_list;
+       struct lws *wsi;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct raw_vhd {
+       struct raw_pss *pss_list; /* linked-list of live pss*/
+
+       int len;
+       uint8_t buf[4096];
+};
+
+static int
+callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct raw_pss *pss = (struct raw_pss *)user;
+       struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
+                                    lws_get_vhost(wsi), lws_get_protocol(wsi));
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi), sizeof(struct raw_vhd));
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               break;
+
+       /* callbacks related to raw socket descriptor */
+
+        case LWS_CALLBACK_RAW_ADOPT:
+               lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
+               pss->wsi = wsi;
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
+               lws_ll_fwd_remove(struct raw_pss, pss_list, pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               lwsl_user("LWS_CALLBACK_RAW_RX: %d\n", (int)len);
+               vhd->len = len;
+               if (vhd->len > (int)sizeof(vhd->buf))
+                       vhd->len = sizeof(vhd->buf);
+               memcpy(vhd->buf, in, vhd->len);
+               lws_start_foreach_llp(struct raw_pss **, ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+               if (lws_write(wsi, vhd->buf, vhd->len, LWS_WRITE_RAW) !=
+                   vhd->len) {
+                       lwsl_notice("%s: raw write failed\n", __func__);
+                       return 1;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static struct lws_protocols protocols[] = {
+       { "raw-test", callback_raw_test, sizeof(struct raw_pss), 0 },
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal raw vhost | nc localhost 7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.protocols = protocols;
+       info.options = LWS_SERVER_OPTION_ONLY_RAW; /* vhost accepts RAW */
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/selftests-library.sh b/minimal-examples/selftests-library.sh
new file mode 100755 (executable)
index 0000000..154e05b
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+if [ -z "$1" -o -z "$2" ] ; then
+       echo "required args missing"
+       exit 1
+fi
+
+IDX=$3
+TOT=$4
+MYTEST=`echo $0 | sed "s/\/[^\/]*\$//g" |sed "s/.*\///g"`
+mkdir -p $2/$MYTEST
+rm -f $2/$MYTEST/*.log $2/$MYTEST/*.result
+FAILS=0
+WHICH=$IDX
+SPID=
+SCRIPT_DIR=`dirname $0`
+SCRIPT_DIR=`readlink -f $SCRIPT_DIR`
+LOGPATH=$2
+
+feedback() {
+       if [ "$2" != "0" ] ; then
+               FAILS=$(( $FAILS + 1 ))
+               echo -n -e "\e[31m"
+       fi
+       T="  ---  killed  ---  "
+       if [ ! -z "`cat $LOGPATH/$MYTEST/$3.time`" ] ; then
+               T="`cat $LOGPATH/$MYTEST/$3.time | grep real | sed "s/.*\ //g"`"
+               T="$T `cat $LOGPATH/$MYTEST/$3.time | grep user | sed "s/.*\ //g"`"
+               T="$T `cat $LOGPATH/$MYTEST/$3.time | grep sys | sed "s/.*\ //g"`"
+       fi
+       printf "%-35s [ %3s/%3s ]: %3s : %8s : %s\n" $1 $WHICH $TOT $2 "$T" $3
+       if [ "$2" != "0" ] ; then
+               echo -n -e "\e[0m"
+       fi
+       WHICH=$(( $WHICH + 1))
+}
+
+spawn() {
+       if [ ! -z "$1" ] ; then
+               if [ `ps $1 | wc -l` -eq 2 ]; then
+#                      echo "prerequisite still up"
+                       return 0
+               fi
+       fi
+
+       QQ=`pwd`
+       cd $SCRIPT_DIR
+       cd $2
+       $3 $4 $5 > $LOGPATH/$MYTEST/serverside.log 2> $LOGPATH/$MYTEST/serverside.log &
+       SPID=$!
+       cd $QQ
+       sleep 0.5s
+#      echo "launched prerequisite $SPID"
+}
+
+dotest() {
+       T=$3
+       (
+               {
+                       /usr/bin/time -p $1/lws-$MYTEST $4 $5 $6 $7 $8 $9 > $2/$MYTEST/$T.log 2> $2/$MYTEST/$T.log ;
+                       echo $? > $2/$MYTEST/$T.result
+               } 2> $2/$MYTEST/$T.time >/dev/null
+       ) >/dev/null 2> /dev/null &
+       W=$!
+       WT=0
+       while [ $WT -le 820 ] ; do
+               kill -0 $W 2>/dev/null
+               if [ $? -ne 0 ] ; then
+                       WT=10000
+               else
+                       if [ $WT -ge 800 ] ; then
+                               WT=10000
+                               kill $W 2>/dev/null
+                               wait $W 2>/dev/null
+                       fi
+               fi
+               sleep 0.1s
+               WT=$(( $WT + 1 ))
+       done
+
+       R=254
+       if [ -e $2/$MYTEST/$T.result ] ; then
+               R=`cat $2/$MYTEST/$T.result`
+               cat $2/$MYTEST/$T.log | tail -n 3 > $2/$MYTEST/$T.time
+               if [ $R -ne 0 ] ; then
+                       pwd
+                       echo
+                       cat $2/$MYTEST/$T.log
+                       echo
+               fi
+       fi
+
+       feedback $MYTEST $R $T
+}
+
diff --git a/minimal-examples/selftests.sh b/minimal-examples/selftests.sh
new file mode 100755 (executable)
index 0000000..77fbdd4
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# run this from your build dir having configured
+# -DLWS_WITH_MINIMAL_EXAMPLES=1 to get all the examples
+# that apply built into ./bin
+#
+# Eg,
+#
+# build $ ../minimal-examples/selftests.sh
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: lws minimal example selftests"
+echo
+
+LOGGING_PATH=/tmp/logs
+
+# for mebedtls, we need the CA certs in ./build where we run from
+
+cp ../minimal-examples/http-client/minimal-http-client-multi/warmcat.com.cer .
+cp ../minimal-examples/http-client/minimal-http-client-post/libwebsockets.org.cer .
+
+MINEX=`dirname $0`
+MINEX=`realpath $MINEX`
+TESTS=0
+for i in `find $MINEX -name selftest.sh` ; do
+       BN=`echo -n "$i" | sed "s/\/[^\/]*\$//g" | sed "s/.*\///g"`
+       if [ -e `pwd`/bin/lws-$BN ] ; then
+               C=`cat $i | grep COUNT_TESTS= | cut -d= -f2`
+               TESTS=$(( $TESTS + $C ))
+       fi
+done
+
+FAILS=0
+WH=1
+
+for i in `find $MINEX -name selftest.sh` ; do
+       BN=`echo -n "$i" | sed "s/\/[^\/]*\$//g" | sed "s/.*\///g"`
+       if [ -e `pwd`/bin/lws-$BN ] ; then
+               C=`cat $i | grep COUNT_TESTS= | cut -d= -f2`
+               sh $i `pwd`/bin $LOGGING_PATH $WH $TESTS $MINEX
+               FAILS=$(( $FAILS + $? ))
+       
+               L=`ps fax | grep lws- | cut -d' ' -f2`
+               kill $L 2>/dev/null
+               kill -9 $L 2>/dev/null
+               wait $L 2>/dev/null
+       
+               WH=$(( $WH + $C ))
+       fi
+done
+
+if [ $FAILS -eq 0 ] ; then
+       echo "All $TESTS passed"
+       exit 0
+else
+       echo "Failed: $FAILS / $TESTS"
+       exit 1
+fi
+
+
diff --git a/minimal-examples/ws-client/README.md b/minimal-examples/ws-client/README.md
new file mode 100644 (file)
index 0000000..10db2fa
--- /dev/null
@@ -0,0 +1,8 @@
+|name|demonstrates|
+---|---
+minimal-ws-client-echo|Simple client that connects to a ws server and echos anything the server sends
+minimal-ws-client-ping|Ws ping test client
+minimal-ws-client-pmd-bulk|Client that sends bulk multifragment data to the minimal-ws-server-pmd-bulk example
+minimal-ws-client-rx|Connects to the dumb-increment-protocol wss server at https://libwebsockets.org and demonstrates receiving ws data
+minimal-ws-client-spam|Spams ws connections in parallel to a server for stability testing
+minimal-ws-client-tx|Connects to the minimal-ws-broker example as a publisher, demonstrating sending ws data
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-echo/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d5162b0
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-echo)
+set(SRCS minimal-ws-client-echo.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/README.md b/minimal-examples/ws-client/minimal-ws-client-echo/README.md
new file mode 100644 (file)
index 0000000..5153896
--- /dev/null
@@ -0,0 +1,37 @@
+# lws minimal ws client + permessage-deflate echo
+
+This example opens a ws client connection to localhost:7681 and
+echoes back anything that comes from the server.
+
+You can use it for testing lws against Autobahn.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-p port|Port to connect to
+-u url|URL path part to connect to
+-o|Finish after one connection
+--ssl|Open client connection with ssl
+-i <iface>|Bind the client connection to interface iface
+
+```
+ $ ./lws-minimal-ws-client-echo
+[2018/04/22 20:03:50:2343] USER: LWS minimal ws client echo + permessage-deflate + multifragment bulk message
+[2018/04/22 20:03:50:2344] USER:    lws-minimal-ws-client-echo [-n (no exts)] [-u url] [-o (once)]
+[2018/04/22 20:03:50:2344] USER: options 0
+[2018/04/22 20:03:50:2345] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2018/04/22 20:03:51:2356] USER: connecting to localhost:9001//runCase?case=362&agent=libwebsockets
+[2018/04/22 20:03:51:2385] NOTICE: checking client ext permessage-deflate
+[2018/04/22 20:03:51:2386] NOTICE: instantiating client ext permessage-deflate
+[2018/04/22 20:03:51:2386] USER: LWS_CALLBACK_CLIENT_ESTABLISHED
+...
+```
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c b/minimal-examples/ws-client/minimal-ws-client-echo/minimal-ws-client-echo.c
new file mode 100644 (file)
index 0000000..a74d454
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * lws-minimal-ws-client-echo
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws client that echoes back what it was sent, in a
+ * way compatible with autobahn -m fuzzingserver
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_client_echo.c"
+
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_CLIENT_ECHO,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static struct lws_context *context;
+static int interrupted, port = 7681, options = 0;
+static const char *url = "/", *ads = "localhost", *iface = NULL;
+
+/* pass pointers to shared vars to the protocol */
+
+static const struct lws_protocol_vhost_options pvo_iface = {
+       NULL,
+       NULL,
+       "iface",                /* pvo name */
+       (void *)&iface  /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_ads = {
+       &pvo_iface,
+       NULL,
+       "ads",          /* pvo name */
+       (void *)&ads    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_url = {
+       &pvo_ads,
+       NULL,
+       "url",          /* pvo name */
+       (void *)&url    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_options = {
+       &pvo_url,
+       NULL,
+       "options",              /* pvo name */
+       (void *)&options        /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_port = {
+       &pvo_options,
+       NULL,
+       "port",         /* pvo name */
+       (void *)&port   /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_interrupted = {
+       &pvo_port,
+       NULL,
+       "interrupted",          /* pvo name */
+       (void *)&interrupted    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,           /* "next" pvo linked-list */
+       &pvo_interrupted,       /* "child" pvo linked-list */
+       "lws-minimal-client-echo",      /* protocol name we belong to on this vhost */
+       ""              /* ignored */
+};
+static const struct lws_extension extensions[] = {
+       {
+               "permessage-deflate",
+               lws_extension_callback_pm_deflate,
+               "permessage-deflate"
+                "; client_no_context_takeover"
+                "; client_max_window_bits"
+       },
+       { NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p;
+       int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n");
+       lwsl_user("   lws-minimal-ws-client-echo [-n (no exts)] [-u url] [-p port] [-o (once)]\n");
+
+       if ((p = lws_cmdline_option(argc, argv, "-u")))
+               url = p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-p")))
+               port = atoi(p);
+
+       if (lws_cmdline_option(argc, argv, "-o"))
+               options |= 1;
+
+       if (lws_cmdline_option(argc, argv, "--ssl"))
+               options |= 2;
+
+       if ((p = lws_cmdline_option(argc, argv, "-s")))
+               ads = p;
+
+       if ((p = lws_cmdline_option(argc, argv, "-i")))
+               iface = p;
+
+       lwsl_user("options %d, ads %s\n", options, ads);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       if (!lws_cmdline_option(argc, argv, "-n"))
+               info.extensions = extensions;
+       info.pt_serv_buf_size = 32 * 1024;
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
+                      LWS_SERVER_OPTION_VALIDATE_UTF8;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+       if (lws_cmdline_option(argc, argv, "--libuv"))
+               info.options |= LWS_SERVER_OPTION_LIBUV;
+       else
+               signal(SIGINT, sigint_handler);
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (!lws_service(context, 0) && !interrupted)
+               ;
+
+       lws_context_destroy(context);
+
+       n = (options & 1) ? interrupted != 2 : interrupted == 3;
+       lwsl_user("Completed %d %s\n", interrupted, !n ? "OK" : "failed");
+
+       return n;
+}
diff --git a/minimal-examples/ws-client/minimal-ws-client-echo/protocol_lws_minimal_client_echo.c b/minimal-examples/ws-client/minimal-ws-client-echo/protocol_lws_minimal_client_echo.c
new file mode 100644 (file)
index 0000000..7023e76
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-client-echo"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The protocol shows how to send and receive bulk messages over a ws connection
+ * that optionally may have the permessage-deflate extension negotiated on it.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+#define RING_DEPTH 1024
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+       char binary;
+       char first;
+       char final;
+};
+
+struct per_session_data__minimal_client_echo {
+       struct lws_ring *ring;
+       uint32_t tail;
+       char flow_controlled;
+       uint8_t completed:1;
+       uint8_t write_consume_pending:1;
+};
+
+struct vhd_minimal_client_echo {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       struct lws *client_wsi;
+
+       int *interrupted;
+       int *options;
+       const char **url;
+       const char **ads;
+       const char **iface;
+       int *port;
+};
+
+static int
+connect_client(struct vhd_minimal_client_echo *vhd)
+{
+       struct lws_client_connect_info i;
+       char host[128];
+
+       lws_snprintf(host, sizeof(host), "%s:%u", *vhd->ads, *vhd->port);
+
+       memset(&i, 0, sizeof(i));
+
+       i.context = vhd->context;
+       i.port = *vhd->port;
+       i.address = *vhd->ads;
+       i.path = *vhd->url;
+       i.host = host;
+       i.origin = host;
+       i.ssl_connection = 0;
+       if ((*vhd->options) & 2)
+               i.ssl_connection |= LCCSCF_USE_SSL;
+       i.vhost = vhd->vhost;
+       i.iface = *vhd->iface;
+       //i.protocol = ;
+       i.pwsi = &vhd->client_wsi;
+
+       lwsl_user("connecting to %s:%d/%s\n", i.address, i.port, i.path);
+
+       return !lws_client_connect_via_info(&i);
+}
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static void
+schedule_callback(struct lws *wsi, int reason, int secs)
+{
+       lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+               lws_get_protocol(wsi), reason, secs);
+}
+
+static int
+callback_minimal_client_echo(struct lws *wsi, enum lws_callback_reasons reason,
+                         void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal_client_echo *pss =
+                       (struct per_session_data__minimal_client_echo *)user;
+       struct vhd_minimal_client_echo *vhd = (struct vhd_minimal_client_echo *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi));
+       const struct msg *pmsg;
+       struct msg amsg;
+       int n, m, flags;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct vhd_minimal_client_echo));
+               if (!vhd)
+                       return -1;
+
+               vhd->context = lws_get_context(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               /* get the pointer to "interrupted" we were passed in pvo */
+               vhd->interrupted = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "interrupted")->value;
+               vhd->port = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "port")->value;
+               vhd->options = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "options")->value;
+               vhd->ads = (const char **)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "ads")->value;
+               vhd->url = (const char **)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "url")->value;
+               vhd->iface = (const char **)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "iface")->value;
+
+               if (connect_client(vhd))
+                       schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED\n");
+               pss->ring = lws_ring_create(sizeof(struct msg), RING_DEPTH,
+                                           __minimal_destroy_message);
+               if (!pss->ring)
+                       return 1;
+               pss->tail = 0;
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+
+               lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE\n");
+
+               if (pss->write_consume_pending) {
+                       /* perform the deferred fifo consume */
+                       lws_ring_consume_single_tail(pss->ring, &pss->tail, 1);
+                       pss->write_consume_pending = 0;
+               }
+               pmsg = lws_ring_get_element(pss->ring, &pss->tail);
+               if (!pmsg) {
+                       lwsl_user(" (nothing in ring)\n");
+                       break;
+               }
+
+               flags = lws_write_ws_flags(
+                           pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+                           pmsg->first, pmsg->final);
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) +
+                             LWS_PRE, pmsg->len, flags);
+               if (m < (int)pmsg->len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
+                               m, flags, pmsg->first, pmsg->final);
+
+               if ((*vhd->options & 1) && pmsg && pmsg->final)
+                       pss->completed = 1;
+
+               /*
+                * Workaround deferred deflate in pmd extension by only
+                * consuming the fifo entry when we are certain it has been
+                * fully deflated at the next WRITABLE callback.  You only need
+                * this if you're using pmd.
+                */
+               pss->write_consume_pending = 1;
+               lws_callback_on_writable(wsi);
+
+               if (pss->flow_controlled &&
+                   (int)lws_ring_get_count_free_elements(pss->ring) > RING_DEPTH - 5) {
+                       lws_rx_flow_control(wsi, 1);
+                       pss->flow_controlled = 0;
+               }
+
+               break;
+
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+
+               lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d (rpp %5d, first %d, last %d, bin %d)\n",
+                       (int)len, (int)lws_remaining_packet_payload(wsi),
+                       lws_is_first_fragment(wsi),
+                       lws_is_final_fragment(wsi),
+                       lws_frame_is_binary(wsi));
+
+               // lwsl_hexdump_notice(in, len);
+
+               amsg.first = lws_is_first_fragment(wsi);
+               amsg.final = lws_is_final_fragment(wsi);
+               amsg.binary = lws_frame_is_binary(wsi);
+               n = (int)lws_ring_get_count_free_elements(pss->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+
+               amsg.len = len;
+               /* notice we over-allocate by LWS_PRE */
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               memcpy((char *)amsg.payload + LWS_PRE, in, len);
+               if (!lws_ring_insert(pss->ring, &amsg, 1)) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+               lws_callback_on_writable(wsi);
+
+               if (!pss->flow_controlled && n < 3) {
+                       pss->flow_controlled = 1;
+                       lws_rx_flow_control(wsi, 0);
+               }
+               break;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               vhd->client_wsi = NULL;
+               //schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               //if (*vhd->options & 1) {
+                       if (!*vhd->interrupted)
+                               *vhd->interrupted = 3;
+                       lws_cancel_service(lws_get_context(wsi));
+               //}
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               lwsl_user("LWS_CALLBACK_CLIENT_CLOSED\n");
+               lws_ring_destroy(pss->ring);
+               vhd->client_wsi = NULL;
+               // schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               //if (*vhd->options & 1) {
+                       if (!*vhd->interrupted)
+                               *vhd->interrupted = 1 + pss->completed;
+                       lws_cancel_service(lws_get_context(wsi));
+       //      }
+               break;
+
+       /* rate-limited client connect retries */
+
+       case LWS_CALLBACK_USER:
+               lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
+               if (connect_client(vhd))
+                       schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_CLIENT_ECHO \
+       { \
+               "lws-minimal-client-echo", \
+               callback_minimal_client_echo, \
+               sizeof(struct per_session_data__minimal_client_echo), \
+               1024, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_CLIENT_ECHO
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_client_echo(struct lws_context *context,
+                              struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_client_echo(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-client/minimal-ws-client-ping/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-ping/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b9a265e
--- /dev/null
@@ -0,0 +1,90 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-ping)
+set(SRCS minimal-ws-client-ping.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared pthread)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/ws-client/minimal-ws-client-ping/README.md b/minimal-examples/ws-client/minimal-ws-client-ping/README.md
new file mode 100644 (file)
index 0000000..13be92c
--- /dev/null
@@ -0,0 +1,42 @@
+# lws minimal ws client PING
+
+This connects to libwebsockets.org using the lws-mirror-protocol.
+
+It then sends a ws PING every 5s and records any PONG coming back.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## Commandline Options
+
+Option|Meaning
+---|---
+-d|Set logging verbosity
+--server|Use a specific server instead of libwebsockets.org, eg `--server localhost`.  Implies LCCSCF_ALLOW_SELFSIGNED
+--port|Use a specific port instead of 443, eg `--port 7681`
+-z|Send zero-length pings for testing
+--protocol|Use a specific ws subprotocol rather than lws-mirror-protocol, eg, `--protocol myprotocol`
+
+## usage
+
+Just run it, wait for the connect and then there will be PINGs sent
+at 5s intervals.
+
+```
+ $ ./lws-minimal-ws-client-ping
+[2018/05/09 16:55:03:1160] USER: LWS minimal ws client PING
+[2018/05/09 16:55:03:1379] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2018/05/09 16:55:03:1715] NOTICE: client loaded CA for verification ./libwebsockets.org.cer
+[2018/05/09 16:55:03:1717] NOTICE: created client ssl context for default
+[2018/05/09 16:55:04:8332] USER: callback_minimal_broker: established
+[2018/05/09 16:55:09:8389] USER: Sending PING 10...
+[2018/05/09 16:55:10:1491] USER: LWS_CALLBACK_CLIENT_RECEIVE_PONG
+[2018/05/09 16:55:10:1494] NOTICE: 
+[2018/05/09 16:55:10:1514] NOTICE: 0000: 70 69 6E 67 20 62 6F 64 79 21                      ping body!      
+[2018/05/09 16:55:10:1515] NOTICE: 
+...
+```
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-ping/libwebsockets.org.cer b/minimal-examples/ws-client/minimal-ws-client-ping/libwebsockets.org.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c b/minimal-examples/ws-client/minimal-ws-client-ping/minimal-ws-client-ping.c
new file mode 100644 (file)
index 0000000..49b5d6c
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * lws-minimal-ws-client-ping
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws client that sends pings from time to time and
+ * shows when it receives the PONG
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+static struct lws_context *context;
+static struct lws *client_wsi;
+static int interrupted, zero_length_ping, port = 443,
+          ssl_connection = LCCSCF_USE_SSL;
+static const char *server_address = "libwebsockets.org", *pro = "lws-mirror-protocol";
+
+struct pss {
+       int send_a_ping;
+};
+
+static int
+connect_client(void)
+{
+       struct lws_client_connect_info i;
+
+       memset(&i, 0, sizeof(i));
+
+       i.context = context;
+       i.port = port;
+       i.address = server_address;
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+       i.ssl_connection = ssl_connection;
+       i.protocol = pro;
+       i.local_protocol_name = "lws-ping-test";
+       i.pwsi = &client_wsi;
+
+       return !lws_client_connect_via_info(&i);
+}
+
+static int
+callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       int n;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               goto try;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi = NULL;
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi), LWS_CALLBACK_USER, 1);
+               break;
+
+       /* --- client callbacks --- */
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("%s: established\n", __func__);
+               lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC);
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+               if (pss->send_a_ping) {
+                       uint8_t ping[LWS_PRE + 125];
+                       int m;
+
+                       pss->send_a_ping = 0;
+                       n = 0;
+                       if (!zero_length_ping)
+                               n = lws_snprintf((char *)ping + LWS_PRE, 125,
+                                       "ping body!");
+
+                       lwsl_user("Sending PING %d...\n", n);
+
+                       m = lws_write(wsi, ping + LWS_PRE, n, LWS_WRITE_PING);
+                       if (m < n) {
+                               lwsl_err("sending ping failed: %d\n", m);
+
+                               return -1;
+                       }
+                       
+                       lws_callback_on_writable(wsi);
+               }
+               break;
+
+       case LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL:
+               client_wsi = NULL;
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                              lws_get_protocol(wsi),
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_CLIENT_RECEIVE_PONG:
+               lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE_PONG\n");
+               lwsl_hexdump_notice(in, len);
+               break;
+
+       case LWS_CALLBACK_TIMER:
+               /* we want to send a ws PING every few seconds */
+               pss->send_a_ping = 1;
+               lws_callback_on_writable(wsi);
+               lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC);
+               break;
+
+       /* rate-limited client connect retries */
+
+       case LWS_CALLBACK_USER:
+               lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
+try:
+               if (connect_client())
+                       lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                                      lws_get_protocol(wsi),
+                                                      LWS_CALLBACK_USER, 1);
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "lws-ping-test",
+               callback_minimal_broker,
+               sizeof(struct pss),
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client PING\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
+#endif
+
+       if (lws_cmdline_option(argc, argv, "-z"))
+               zero_length_ping = 1;
+
+       if ((p = lws_cmdline_option(argc, argv, "--protocol")))
+               pro = p;
+
+       if ((p = lws_cmdline_option(argc, argv, "--server"))) {
+               server_address = p;
+               pro = "lws-minimal";
+               ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       }
+
+       if ((p = lws_cmdline_option(argc, argv, "--port")))
+               port = atoi(p);
+
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed\n");
+
+       return 0;
+}
diff --git a/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ace89a5
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-pmd-bulk)
+set(SRCS minimal-ws-client-pmd-bulk.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+#require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/README.md b/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/README.md
new file mode 100644 (file)
index 0000000..f43a458
--- /dev/null
@@ -0,0 +1,164 @@
+# lws minimal ws client + permessage-deflate for bulk traffic
+
+This example opens a client connection to localhost:7681 where it
+expects to find minimal-ws-server-pmd-bulk running.
+
+It sends and receives a large, multifragment message, and then exits.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Both the server and client side must use the same options
+
+ - `-n` disable permessage-deflate extension
+ - `-c` send compressible text instead of uncompressible binary data
+
+```
+ $ ./lws-minimal-ws-client-pmd-bulk
+[2018/04/05 12:08:58:9120] USER: LWS minimal ws client + permessage-deflate + multifragment bulk message
+[2018/04/05 12:08:58:9120] USER:    ./lws-minimal-ws-client-pmd-bulk [-n (no exts)] [-c (compressible)]
+[2018/04/05 12:08:58:9120] NOTICE: Creating Vhost 'default' (serving disabled), 2 protocols, IPv6 on
+[2018/04/05 12:08:59:9139] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9139] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9139] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9139] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9139] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9140] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9140] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9140] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9140] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9140] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9140] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9141] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9141] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9141] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9142] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9142] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9142] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9142] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9142] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9142] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9143] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9143] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9143] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9143] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9143] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9143] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9144] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9144] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9144] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9144] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9144] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9144] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9145] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9145] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9145] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9145] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9146] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9146] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9146] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9146] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9146] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9146] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9147] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9147] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9147] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9147] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9147] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9148] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9148] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9148] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9148] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9148] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9148] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9149] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9149] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9149] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9149] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9149] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9149] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9150] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9150] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9150] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9150] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9150] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9150] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9151] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9151] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9151] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9151] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9152] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9152] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9152] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9152] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9152] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9152] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9153] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9153] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9153] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9153] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9153] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9153] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9154] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9154] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9154] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9154] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9154] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9154] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9155] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9155] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9155] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9155] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9155] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9155] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9156] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9156] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9156] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9156] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9157] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9157] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9157] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9157] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9157] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9158] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9158] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9158] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9158] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9158] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9158] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9159] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9159] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9159] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9159] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9159] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9159] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9160] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9160] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9160] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9160] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9160] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9160] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9161] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9161] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9161] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9161] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9161] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9161] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9162] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9162] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9162] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9162] USER: LWS_CALLBACK_CLIENT_RECEIVE: 1024 (rpp     0, last 0)
+[2018/04/05 12:08:59:9162] USER: LWS_CALLBACK_CLIENT_RECEIVE:  580 (rpp     0, last 1)
+[2018/04/05 12:08:59:9180] USER: Completed OK
+```
+
+Visit http://localhost:7681 in your browser
+
+One or another kind of bulk ws transfer is made to the browser.
+
+The ws connection is made via permessage-deflate extension.
diff --git a/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c b/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/minimal-ws-client-pmd-bulk.c
new file mode 100644 (file)
index 0000000..1f6aa47
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * lws-minimal-ws-client-pmd-bulk
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws client that sends bulk data in multiple
+ * ws fragments, in a way compatible with per-message deflate.
+ *
+ * It shows how to send huge messages without needing a lot of memory.
+ * 
+ * Build and start the minimal-examples/ws-server/minmal-ws-server-pmd-bulk
+ * example first.  Running this sends a large message to the server and
+ * exits.
+ *
+ * If you give both sides the -n commandline option, it disables permessage-
+ * deflate compression extension.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_pmd_bulk.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL_PMD_BULK,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted, options;
+
+/* pass pointers to shared vars to the protocol */
+
+static const struct lws_protocol_vhost_options pvo_options = {
+       NULL,
+       NULL,
+       "options",              /* pvo name */
+       (void *)&options        /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_interrupted = {
+       &pvo_options,
+       NULL,
+       "interrupted",          /* pvo name */
+       (void *)&interrupted    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,           /* "next" pvo linked-list */
+       &pvo_interrupted,       /* "child" pvo linked-list */
+       "lws-minimal-pmd-bulk", /* protocol name we belong to on this vhost */
+       ""              /* ignored */
+};
+static const struct lws_extension extensions[] = {
+       {
+               "permessage-deflate",
+               lws_extension_callback_pm_deflate,
+               "permessage-deflate"
+                "; client_no_context_takeover"
+                "; client_max_window_bits"
+       },
+       { NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client + permessage-deflate + multifragment bulk message\n");
+       lwsl_user("   needs minimal-ws-server-pmd-bulk running to communicate with\n");
+       lwsl_user("   %s [-n (no exts)] [-c (compressible)]\n", argv[0]);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       if (!lws_cmdline_option(argc, argv, "-n"))
+               info.extensions = extensions;
+       info.pt_serv_buf_size = 32 * 1024;
+
+       if (lws_cmdline_option(argc, argv, "-c"))
+               options |= 1;
+
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       lwsl_user("Completed %s\n", interrupted == 2 ? "OK" : "failed");
+
+       return interrupted != 2;
+}
diff --git a/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/protocol_lws_minimal_pmd_bulk.c b/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/protocol_lws_minimal_pmd_bulk.c
new file mode 100644 (file)
index 0000000..a1b38c4
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-pmd-bulk"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The protocol shows how to send and receive bulk messages over a ws connection
+ * that optionally may have the permessage-deflate extension negotiated on it.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/*
+ * We will produce a large ws message either from this text repeated many times,
+ * or from 0x40 + a 6-bit pseudorandom number
+ */
+
+static const char * const redundant_string =
+       "No one would have believed in the last years of the nineteenth "
+       "century that this world was being watched keenly and closely by "
+       "intelligences greater than man's and yet as mortal as his own; that as "
+       "men busied themselves about their various concerns they were "
+       "scrutinised and studied, perhaps almost as narrowly as a man with a "
+       "microscope might scrutinise the transient creatures that swarm and "
+       "multiply in a drop of water.  With infinite complacency men went to "
+       "and fro over this globe about their little affairs, serene in their "
+       "assurance of their empire over matter. It is possible that the "
+       "infusoria under the microscope do the same.  No one gave a thought to "
+       "the older worlds of space as sources of human danger, or thought of "
+       "them only to dismiss the idea of life upon them as impossible or "
+       "improbable.  It is curious to recall some of the mental habits of "
+       "those departed days.  At most terrestrial men fancied there might be "
+       "other men upon Mars, perhaps inferior to themselves and ready to "
+       "welcome a missionary enterprise. Yet across the gulf of space, minds "
+       "that are to our minds as ours are to those of the beasts that perish, "
+       "intellects vast and cool and unsympathetic, regarded this earth with "
+       "envious eyes, and slowly and surely drew their plans against us.  And "
+       "early in the twentieth century came the great disillusionment. "
+;
+
+/* this reflects the length of the string above */
+#define REPEAT_STRING_LEN 1337
+/* this is the total size of the ws message we will send */
+#define MESSAGE_SIZE (100 * REPEAT_STRING_LEN)
+/* this is how much we will send each time the connection is writable */
+#define MESSAGE_CHUNK_SIZE (1 * 1024)
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal_pmd_bulk {
+       int position_tx, position_rx;
+       uint64_t rng_rx, rng_tx;
+};
+
+struct vhd_minimal_pmd_bulk {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       struct lws *client_wsi;
+
+       int *interrupted;
+       int *options;
+};
+
+static uint64_t rng(uint64_t *r)
+{
+        *r ^= *r << 21;
+        *r ^= *r >> 35;
+        *r ^= *r << 4;
+
+        return *r;
+}
+
+static int
+connect_client(struct vhd_minimal_pmd_bulk *vhd)
+{
+       struct lws_client_connect_info i;
+
+       memset(&i, 0, sizeof(i));
+
+       i.context = vhd->context;
+       i.port = 7681;
+       i.address = "localhost";
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+       i.ssl_connection = 0;
+       i.vhost = vhd->vhost;
+       i.protocol = "lws-minimal-pmd-bulk";
+       i.pwsi = &vhd->client_wsi;
+
+       return !lws_client_connect_via_info(&i);
+}
+
+static void
+schedule_callback(struct lws *wsi, int reason, int secs)
+{
+       lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+               lws_get_protocol(wsi), reason, secs);
+}
+
+static int
+callback_minimal_pmd_bulk(struct lws *wsi, enum lws_callback_reasons reason,
+                         void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal_pmd_bulk *pss =
+                       (struct per_session_data__minimal_pmd_bulk *)user;
+       struct vhd_minimal_pmd_bulk *vhd = (struct vhd_minimal_pmd_bulk *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi));
+       uint8_t buf[LWS_PRE + MESSAGE_CHUNK_SIZE], *start = &buf[LWS_PRE], *p;
+       int n, m, flags;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct vhd_minimal_pmd_bulk));
+               if (!vhd)
+                       return -1;
+
+               vhd->context = lws_get_context(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               /* get the pointer to "interrupted" we were passed in pvo */
+               vhd->interrupted = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "interrupted")->value;
+               vhd->options = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "options")->value;
+
+               if (connect_client(vhd))
+                       schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               pss->rng_tx = 4;
+               pss->rng_rx = 4;
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+
+               /*
+                * when we connect, we will send the server a message
+                */
+
+               if (pss->position_tx == MESSAGE_SIZE)
+                       break;
+
+               /* fill up one chunk's worth of message content */
+
+               p = start;
+               n = MESSAGE_CHUNK_SIZE;
+               if (n > MESSAGE_SIZE - pss->position_tx)
+                       n = MESSAGE_SIZE - pss->position_tx;
+
+               flags = lws_write_ws_flags(LWS_WRITE_BINARY, !pss->position_tx,
+                                          pss->position_tx + n == MESSAGE_SIZE);
+
+               /*
+                * select between producing compressible repeated text,
+                * or uncompressible PRNG output
+                */
+
+               if (*vhd->options & 1) {
+                       while (n) {
+                               size_t s;
+
+                               m = pss->position_tx % REPEAT_STRING_LEN;
+                               s = REPEAT_STRING_LEN - m;
+                               if (s > (size_t)n)
+                                       s = n;
+                               memcpy(p, &redundant_string[m], s);
+                               pss->position_tx += s;
+                               p += s;
+                               n -= s;
+                       }
+               } else {
+                       pss->position_tx += n;
+                       while (n--)
+                               *p++ = rng(&pss->rng_tx);
+               }
+
+               n = lws_ptr_diff(p, start);
+               m = lws_write(wsi, start, n, flags);
+               if (m < n) {
+                       lwsl_err("ERROR %d writing ws\n", m);
+                       return -1;
+               }
+               if (pss->position_tx != MESSAGE_SIZE) /* if more to do... */
+                       lws_callback_on_writable(wsi);
+               else
+                       /* if we sent and received everything */
+                       if (pss->position_rx == MESSAGE_SIZE)
+                               *vhd->interrupted = 2;
+               break;
+
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+
+               /*
+                * When we connect, the server will send us a message too
+                */
+
+               lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d (rpp %5d, last %d)\n",
+                       (int)len, (int)lws_remaining_packet_payload(wsi),
+                       lws_is_final_fragment(wsi));
+
+               if (*vhd->options & 1) {
+                       while (len) {
+                               size_t s;
+
+                               m = pss->position_rx % REPEAT_STRING_LEN;
+                               s = REPEAT_STRING_LEN - m;
+                               if (s > len)
+                                       s = len;
+                               if (memcmp(in, &redundant_string[m], s)) {
+                                       lwsl_user("echo'd data doesn't match\n");
+                                       return -1;
+                               }
+                               pss->position_rx += s;
+                               in = ((unsigned char *)in) + s;
+                               len -= s;
+                       }
+               } else {
+                       p = (uint8_t *)in;
+                       pss->position_rx += len;
+                       while (len--)
+                               if (*p++ != (uint8_t)rng(&pss->rng_rx)) {
+                                       lwsl_user("echo'd data doesn't match\n");
+                                       return -1;
+                               }
+               }
+
+               /* if we sent and received everything */
+
+               if (pss->position_rx == MESSAGE_SIZE &&
+                   pss->position_tx == MESSAGE_SIZE)
+                       *vhd->interrupted = 2;
+
+               break;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               vhd->client_wsi = NULL;
+               schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               vhd->client_wsi = NULL;
+               schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               break;
+
+       /* rate-limited client connect retries */
+
+       case LWS_CALLBACK_USER:
+               lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
+               if (connect_client(vhd))
+                       schedule_callback(wsi, LWS_CALLBACK_USER, 1);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_PMD_BULK \
+       { \
+               "lws-minimal-pmd-bulk", \
+               callback_minimal_pmd_bulk, \
+               sizeof(struct per_session_data__minimal_pmd_bulk), \
+               4096, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_PMD_BULK
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_pmd_bulk(struct lws_context *context,
+                              struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_pmd_bulk(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-rx/CMakeLists.txt
new file mode 100644 (file)
index 0000000..fb8c938
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-rx)
+set(SRCS minimal-ws-client.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/README.md b/minimal-examples/ws-client/minimal-ws-client-rx/README.md
new file mode 100644 (file)
index 0000000..5c267e1
--- /dev/null
@@ -0,0 +1,39 @@
+# lws minimal ws client rx
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+The application goes to https://libwebsockets.org and makes a wss connection
+using the dumb-increment-protocol.  It shows the incrementing number it is
+being sent over ws as it arrives.
+
+This example only receives things to keep it simple.  See minimal-ws-client-tx
+for code related to sending things.  Of course rx and tx are supported in the
+same protocol.
+
+```
+./lws-minimal-ws-client-rx
+[2018/03/14 11:57:24:0689] USER: LWS minimal ws client rx
+[2018/03/14 11:57:24:0705] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 off
+[2018/03/14 11:57:24:0710] NOTICE: created client ssl context for default
+[2018/03/14 11:57:24:0788] NOTICE: lws_client_connect_2: 0x15b8310: address libwebsockets.org
+[2018/03/14 11:57:24:7643] NOTICE: lws_client_connect_2: 0x15b8310: address libwebsockets.org
+[2018/03/14 11:57:26:9191] USER: RX: 0
+[2018/03/14 11:57:26:9318] USER: RX: 1
+[2018/03/14 11:57:27:2182] USER: RX: 2
+[2018/03/14 11:57:27:2336] USER: RX: 3
+[2018/03/14 11:57:27:2838] USER: RX: 4
+[2018/03/14 11:57:27:5173] USER: RX: 5
+[2018/03/14 11:57:27:5352] USER: RX: 6
+[2018/03/14 11:57:27:5854] USER: RX: 7
+[2018/03/14 11:57:27:8156] USER: RX: 8
+[2018/03/14 11:57:27:8359] USER: RX: 9
+^C[2018/03/14 11:57:27:9884] USER: Completed
+```
+
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/libwebsockets.org.cer b/minimal-examples/ws-client/minimal-ws-client-rx/libwebsockets.org.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c
new file mode 100644 (file)
index 0000000..e309260
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * lws-minimal-ws-client
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the a minimal ws client using lws.
+ *
+ * It connects to https://libwebsockets.org/ and makes a
+ * wss connection to the dumb-increment protocol there.  While
+ * connected, it prints the numbers it is being sent by
+ * dumb-increment protocol.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+static int interrupted, rx_seen, test;
+static struct lws *client_wsi;
+
+static int
+callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
+             void *user, void *in, size_t len)
+{
+       switch (reason) {
+
+       /* because we are protocols[0] ... */
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               client_wsi = NULL;
+               break;
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("%s: established\n", __func__);
+               break;
+
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+               lwsl_user("RX: %s\n", (const char *)in);
+               rx_seen++;
+               if (test && rx_seen == 10)
+                       interrupted = 1;
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               client_wsi = NULL;
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "dumb-increment-protocol",
+               callback_dumb_increment,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_client_connect_info i;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+               /* for LLL_ verbosity above NOTICE to be built into lws, lws
+                * must have been configured with -DCMAKE_BUILD_TYPE=DEBUG
+                * instead of =RELEASE */
+               /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+               /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+               /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       test = !!lws_cmdline_option(argc, argv, "-t");
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client rx [-d <logs>] [--h2] [-t (test)]\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
+#endif
+
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
+       i.context = context;
+       i.port = 443;
+       i.address = "libwebsockets.org";
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+       i.ssl_connection = LCCSCF_USE_SSL;
+       i.protocol = protocols[0].name; /* "dumb-increment-protocol" */
+       i.pwsi = &client_wsi;
+
+       if (lws_cmdline_option(argc, argv, "--h2"))
+               i.alpn = "h2";
+
+       lws_client_connect_via_info(&i);
+
+       while (n >= 0 && client_wsi && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       lwsl_user("Completed %s\n", rx_seen > 10 ? "OK" : "Failed");
+
+       return rx_seen > 10;
+}
diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/selftest.sh b/minimal-examples/ws-client/minimal-ws-client-rx/selftest.sh
new file mode 100644 (file)
index 0000000..070ef7f
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 warmcat -t
+
+exit $FAILS
diff --git a/minimal-examples/ws-client/minimal-ws-client-spam/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-spam/CMakeLists.txt
new file mode 100644 (file)
index 0000000..25b9d72
--- /dev/null
@@ -0,0 +1,90 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-spam)
+set(SRCS minimal-ws-client-spam.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared pthread)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/ws-client/minimal-ws-client-spam/README.md b/minimal-examples/ws-client/minimal-ws-client-spam/README.md
new file mode 100644 (file)
index 0000000..db4b7f7
--- /dev/null
@@ -0,0 +1,53 @@
+# lws minimal ws client SPAM
+
+This connects to libwebsockets.org using the lws-mirror-protocol.
+
+By default is has 10 concurrent connections and connects 100 times.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## Commandline Options
+
+Option|Meaning
+---|---
+-d|Set logging verbosity
+--server|Use a specific server instead of libwebsockets.org, eg `--server localhost`.  Implies LCCSCF_ALLOW_SELFSIGNED
+--port|Use a specific port instead of 443, eg `--port 7681`
+-c|Amount of concurrent connections
+-l|Test limit (total number of connections to make)
+
+## usage
+
+Just run it, it will repeatedly connect and reconnect to libwebsockets.org
+until it hits the test limit.
+
+You can also direct it to use the lws test server in tls mode by running that
+with `libwebsockets-test-server -s` and running this using, eg
+
+```
+ $ ./lws-minimal-ws-client-spam -c 20 -l 200 --server localhost --port 7681
+```
+
+```
+ $ ./lws-minimal-ws-client-spam
+[2018/11/15 09:53:19:9639] USER: LWS minimal ws client SPAM
+[2018/11/15 09:53:19:9647] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off
+[2018/11/15 09:53:19:9695] NOTICE: created client ssl context for default
+[2018/11/15 09:53:21:0976] USER: callback_minimal_spam: established (try 10, est 0, closed 0, err 0)
+[2018/11/15 09:53:21:1041] USER: callback_minimal_spam: established (try 10, est 1, closed 0, err 0)
+[2018/11/15 09:53:21:1089] USER: callback_minimal_spam: established (try 10, est 2, closed 0, err 0)
+[2018/11/15 09:53:21:1132] USER: callback_minimal_spam: established (try 10, est 3, closed 0, err 0)
+[2018/11/15 09:53:21:1166] USER: callback_minimal_spam: established (try 10, est 4, closed 0, err 0)
+[2018/11/15 09:53:21:1531] USER: callback_minimal_spam: established (try 10, est 5, closed 0, err 0)
+[2018/11/15 09:53:21:1563] USER: callback_minimal_spam: established (try 10, est 6, closed 0, err 0)
+[2018/11/15 09:53:21:1589] USER: callback_minimal_spam: established (try 10, est 7, closed 0, err 0)
+[2018/11/15 09:53:21:1616] USER: callback_minimal_spam: established (try 10, est 8, closed 0, err 0)
+[2018/11/15 09:53:21:1671] USER: callback_minimal_spam: established (try 10, est 9, closed 0, err 0)
+[2018/11/15 09:53:21:3778] USER: callback_minimal_spam: reopening (try 11, est 10, closed 1, err 0)
+...
+```
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-spam/libwebsockets.org.cer b/minimal-examples/ws-client/minimal-ws-client-spam/libwebsockets.org.cer
new file mode 100644 (file)
index 0000000..67de129
--- /dev/null
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c b/minimal-examples/ws-client/minimal-ws-client-spam/minimal-ws-client-spam.c
new file mode 100644 (file)
index 0000000..ec6f523
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * lws-minimal-ws-client-spam
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws client that makes continuous mass ws connections
+ * asynchronously
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+enum {
+       CLIENT_IDLE,
+       CLIENT_CONNECTING,
+       CLIENT_AWAITING_SEND,
+};
+
+struct client {
+       struct lws *wsi;
+       int index;
+       int state;
+};
+
+static struct lws_context *context;
+static struct client clients[200];
+static int interrupted, port = 443, ssl_connection = LCCSCF_USE_SSL;
+static const char *server_address = "libwebsockets.org",
+                 *pro = "lws-mirror-protocol";
+static int concurrent = 3, conn, tries, est, errors, closed, sent, limit = 15;
+
+struct pss {
+       int conn;
+};
+
+static int
+connect_client(int idx)
+{
+       struct lws_client_connect_info i;
+
+       if (tries == limit) {
+               lwsl_user("Reached limit... finishing\n");
+               return 0;
+       }
+
+       memset(&i, 0, sizeof(i));
+
+       i.context = context;
+       i.port = port;
+       i.address = server_address;
+       i.path = "/";
+       i.host = i.address;
+       i.origin = i.address;
+       i.ssl_connection = ssl_connection;
+       i.protocol = pro;
+       i.local_protocol_name = pro;
+       i.pwsi = &clients[idx].wsi;
+
+       clients[idx].state = CLIENT_CONNECTING;
+       tries++;
+
+       if (!lws_client_connect_via_info(&i)) {
+               clients[idx].wsi = NULL;
+               clients[idx].state = CLIENT_IDLE;
+
+               return 1;
+       }
+
+       return 0;
+}
+
+static int
+callback_minimal_spam(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct pss *pss = (struct pss *)user;
+       uint8_t ping[LWS_PRE + 125];
+       int n, m;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               for (n = 0; n < concurrent; n++) {
+                       clients[n].index = n;
+                       connect_client(n);
+               }
+               break;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               errors++;
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s (try %d, est %d, closed %d, err %d)\n",
+                        in ? (char *)in : "(null)", tries, est, closed, errors);
+               for (n = 0; n < concurrent; n++) {
+                       if (clients[n].wsi == wsi) {
+                               clients[n].wsi = NULL;
+                               clients[n].state = CLIENT_IDLE;
+                               connect_client(n);
+                               break;
+                       }
+               }
+               if (tries == closed + errors)
+                       interrupted = 1;
+               break;
+
+       /* --- client callbacks --- */
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("%s: established (try %d, est %d, closed %d, err %d)\n",
+                               __func__, tries, est, closed, errors);
+               est++;
+               pss->conn = conn++;
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               closed++;
+               if (tries == closed + errors)
+                       interrupted = 1;
+               if (tries == limit) {
+                       lwsl_user("%s: leaving CLOSED (try %d, est %d, sent %d, closed %d, err %d)\n",
+                                       __func__, tries, est, sent, closed, errors);
+                       break;
+               }
+
+               for (n = 0; n < concurrent; n++) {
+                       if (clients[n].wsi == wsi) {
+                               connect_client(n);
+                               lwsl_user("%s: reopening (try %d, est %d, closed %d, err %d)\n",
+                                               __func__, tries, est, closed, errors);
+                               break;
+                       }
+               }
+               if (n == concurrent)
+                       lwsl_user("CLOSED: can't find client wsi\n");
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+               n = lws_snprintf((char *)ping + LWS_PRE, sizeof(ping) - LWS_PRE,
+                                         "hello %d", pss->conn);
+
+               m = lws_write(wsi, ping + LWS_PRE, n, LWS_WRITE_TEXT);
+               if (m < n) {
+                       lwsl_err("sending ping failed: %d\n", m);
+
+                       return -1;
+               }
+               lws_set_timeout(wsi, PENDING_TIMEOUT_USER_OK, LWS_TO_KILL_ASYNC);
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "lws-spam-test",
+               callback_minimal_spam,
+               sizeof(struct pss),
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client SPAM\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+#if defined(LWS_WITH_MBEDTLS)
+       /*
+        * OpenSSL uses the system trust store.  mbedTLS has to be told which
+        * CA to trust explicitly.
+        */
+       info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
+#endif
+
+       if ((p = lws_cmdline_option(argc, argv, "--server"))) {
+               server_address = p;
+               ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
+       }
+
+       if ((p = lws_cmdline_option(argc, argv, "--port")))
+               port = atoi(p);
+
+       if ((p = lws_cmdline_option(argc, argv, "-l")))
+               limit = atoi(p);
+
+       if ((p = lws_cmdline_option(argc, argv, "-c")))
+               concurrent = atoi(p);
+
+       if (lws_cmdline_option(argc, argv, "-n")) {
+               ssl_connection = 0;
+               info.options = 0;
+       }
+
+       if (concurrent < 0 ||
+           concurrent > (int)LWS_ARRAY_SIZE(clients)) {
+               lwsl_err("%s: -c %d larger than max concurrency %d\n", __func__,
+                               concurrent, (int)LWS_ARRAY_SIZE(clients));
+
+               return 1;
+       }
+
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and n (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + concurrent + 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       if (tries == limit && closed == tries) {
+               lwsl_user("Completed\n");
+               return 0;
+       }
+
+       lwsl_err("Failed\n");
+
+       return 1;
+}
diff --git a/minimal-examples/ws-client/minimal-ws-client-spam/selftest.sh b/minimal-examples/ws-client/minimal-ws-client-spam/selftest.sh
new file mode 100755 (executable)
index 0000000..b9f2cde
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# $1: path to minimal example binaries...
+#     if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
+#     that will be ./bin from your build dir
+#
+# $2: path for logs and results.  The results will go
+#     in a subdir named after the directory this script
+#     is in
+#
+# $3: offset for test index count
+#
+# $4: total test count
+#
+# $5: path to ./minimal-examples dir in lws
+#
+# Test return code 0: OK, 254: timed out, other: error indication
+
+. $5/selftests-library.sh
+
+COUNT_TESTS=1
+
+dotest $1 $2 warmcat
+
+exit $FAILS
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-tx/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-tx/CMakeLists.txt
new file mode 100644 (file)
index 0000000..47f2dc6
--- /dev/null
@@ -0,0 +1,90 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-client-tx)
+set(SRCS minimal-ws-client.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared pthread)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/ws-client/minimal-ws-client-tx/README.md b/minimal-examples/ws-client/minimal-ws-client-tx/README.md
new file mode 100644 (file)
index 0000000..4a606d4
--- /dev/null
@@ -0,0 +1,33 @@
+# lws minimal ws client tx
+
+This demonstrates a ws "publisher" to go with the minimal-ws-broker example.
+
+Two threads are spawned that produce messages to be sent to the broker,
+via a local ringbuffer.  Locking is provided to make ringbuffer access threadsafe.
+
+When a nailed-up client connection to the broker is established, the
+ringbuffer is sent to the broker, which distributes the events to all
+connected clients.
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+This example connects to ws-server/minimal-ws-broker, so you need to build and run
+that in another terminal.
+
+```
+ $ ./lws-minimal-ws-client-tx
+[2018/03/16 16:04:33:5774] USER: LWS minimal ws client tx
+[2018/03/16 16:04:33:5774] USER:   Run minimal-ws-broker and browse to that
+[2018/03/16 16:04:33:5774] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 off
+[2018/03/16 16:04:34:5794] USER: callback_minimal_broker: established
+```
+
+If you open a browser on http://localhost:7681 , you will see the subscribed
+messages from the threads in this app via the broker app.
+
diff --git a/minimal-examples/ws-client/minimal-ws-client-tx/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client-tx/minimal-ws-client.c
new file mode 100644 (file)
index 0000000..137201d
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * lws-minimal-ws-client-tx
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws "publisher" to go with the minimal-ws-broker
+ * example.
+ *
+ * Two threads are spawned that produce messages to be sent to the broker,
+ * via a local ringbuffer.  Locking is provided to make ringbuffer access
+ * threadsafe.
+ *
+ * When a nailed-up client connection to the broker is established, the
+ * ringbuffer is sent to the broker, which distributes the events to all
+ * connected clients.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+static int interrupted;
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+       pthread_t pthread_spam[2];
+
+       pthread_mutex_t lock_ring; /* serialize access to the ring buffer */
+       struct lws_ring *ring; /* ringbuffer holding unsent messages */
+       uint32_t tail;
+
+       struct lws_client_connect_info i;
+       struct lws *client_wsi;
+
+       int counter;
+       char finished;
+       char established;
+};
+
+#if defined(WIN32)
+static void usleep(unsigned long l) { Sleep(l / 1000); }
+#endif
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static void *
+thread_spam(void *d)
+{
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)d;
+       struct msg amsg;
+       int len = 128, index = 1, n;
+
+       do {
+               /* don't generate output if client not connected */
+               if (!vhd->established)
+                       goto wait;
+
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+
+               /* only create if space in ringbuffer */
+               n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       goto wait_unlock;
+               }
+
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       goto wait_unlock;
+               }
+               n = lws_snprintf((char *)amsg.payload + LWS_PRE, len,
+                                "tid: %p, msg: %d",
+                                (void *)pthread_self(), index++);
+               amsg.len = n;
+               n = lws_ring_insert(vhd->ring, &amsg, 1);
+               if (n != 1) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+               } else
+                       /*
+                        * This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
+                        * in the lws service thread context.
+                        */
+                       lws_cancel_service(vhd->context);
+
+wait_unlock:
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+
+wait:
+               usleep(100000);
+
+       } while (!vhd->finished);
+
+       lwsl_notice("thread_spam %p exiting\n", (void *)pthread_self());
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+static int
+connect_client(struct per_vhost_data__minimal *vhd)
+{
+       vhd->i.context = vhd->context;
+       vhd->i.port = 7681;
+       vhd->i.address = "localhost";
+       vhd->i.path = "/publisher";
+       vhd->i.host = vhd->i.address;
+       vhd->i.origin = vhd->i.address;
+       vhd->i.ssl_connection = 0;
+
+       vhd->i.protocol = "lws-minimal-broker";
+       vhd->i.pwsi = &vhd->client_wsi;
+
+       return !lws_client_connect_via_info(&vhd->i);
+}
+
+static int
+callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct msg *pmsg;
+       void *retval;
+       int n, m, r = 0;
+
+       switch (reason) {
+
+       /* --- protocol lifecycle callbacks --- */
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring)
+                       return 1;
+
+               pthread_mutex_init(&vhd->lock_ring, NULL);
+
+               /* start the content-creating threads */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (pthread_create(&vhd->pthread_spam[n], NULL,
+                                          thread_spam, vhd)) {
+                               lwsl_err("thread creation failed\n");
+                               r = 1;
+                               goto init_fail;
+                       }
+
+               if (connect_client(vhd))
+                       lws_timed_callback_vh_protocol(vhd->vhost,
+                                       vhd->protocol, LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+init_fail:
+               vhd->finished = 1;
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (vhd->pthread_spam[n])
+                               pthread_join(vhd->pthread_spam[n], &retval);
+
+               if (vhd->ring)
+                       lws_ring_destroy(vhd->ring);
+
+               pthread_mutex_destroy(&vhd->lock_ring);
+
+               return r;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               vhd->client_wsi = NULL;
+               lws_timed_callback_vh_protocol(vhd->vhost,
+                               vhd->protocol, LWS_CALLBACK_USER, 1);
+               break;
+
+       /* --- client callbacks --- */
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_user("%s: established\n", __func__);
+               vhd->established = 1;
+               break;
+
+       case LWS_CALLBACK_CLIENT_WRITEABLE:
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+               pmsg = lws_ring_get_element(vhd->ring, &vhd->tail);
+               if (!pmsg)
+                       goto skip;
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
+                             pmsg->len, LWS_WRITE_TEXT);
+               if (m < (int)pmsg->len) {
+                       pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock */
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1);
+
+               /* more to do for us? */
+               if (lws_ring_get_element(vhd->ring, &vhd->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(wsi);
+
+skip:
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+               break;
+
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               vhd->client_wsi = NULL;
+               vhd->established = 0;
+               lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
+               /*
+                * When the "spam" threads add a message to the ringbuffer,
+                * they create this event in the lws service thread context
+                * using lws_cancel_service().
+                *
+                * We respond by scheduling a writable callback for the
+                * connected client, if any.
+                */
+               if (vhd && vhd->client_wsi && vhd->established)
+                       lws_callback_on_writable(vhd->client_wsi);
+               break;
+
+       /* rate-limited client connect retries */
+
+       case LWS_CALLBACK_USER:
+               lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
+               if (connect_client(vhd))
+                       lws_timed_callback_vh_protocol(vhd->vhost,
+                                               vhd->protocol,
+                                               LWS_CALLBACK_USER, 1);
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+       {
+               "lws-minimal-broker",
+               callback_minimal_broker,
+               0,
+               0,
+       },
+       { NULL, NULL, 0, 0 }
+};
+
+static void
+sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client tx\n");
+       lwsl_user("  Run minimal-ws-broker and browse to that\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+       info.protocols = protocols;
+       /*
+        * since we know this lws context is only ever going to be used with
+        * one client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
+        * will use.
+        */
+       info.fd_limit_per_thread = 1 + 1 + 1;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+       lwsl_user("Completed\n");
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/README.md b/minimal-examples/ws-server/README.md
new file mode 100644 (file)
index 0000000..b8e7ec2
--- /dev/null
@@ -0,0 +1,13 @@
+|Example|Demonstrates|
+---|---
+minimal-ws-broker|Simple ws server with a publish / broker / subscribe architecture
+minimal-ws-server-echo|Simple ws server that listens and echos back anything clients send
+minimal-ws-server-pmd-bulk|Simple ws server showing how to pass bulk data with permessage-deflate
+minimal-ws-server-pmd-corner|Corner-case tests for permessage-deflate
+minimal-ws-server-pmd|Simple ws server with permessage-deflate support
+minimal-ws-server-ring|Like minimal-ws-server but holds the chat in a multi-tail ringbuffer
+minimal-ws-server-threadpool|Demonstrates how to use a worker thread pool with lws
+minimal-ws-server-threads-smp|SMP ws server where data is produced by different threads with multiple lws service threads too
+minimal-ws-server-threads|Simple ws server where data is produced by different threads
+minimal-ws-server|Serves an index.html over http that opens a ws shared chat client in a browser
+
diff --git a/minimal-examples/ws-server/minimal-ws-broker/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-broker/CMakeLists.txt
new file mode 100644 (file)
index 0000000..719147d
--- /dev/null
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-broker)
+set(SRCS minimal-ws-broker.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/ws-server/minimal-ws-broker/README.md b/minimal-examples/ws-server/minimal-ws-broker/README.md
new file mode 100644 (file)
index 0000000..e6405c2
--- /dev/null
@@ -0,0 +1,26 @@
+# lws minimal ws broker
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-ws-broker
+[2018/03/15 12:23:12:1559] USER: LWS minimal ws broker | visit http://localhost:7681
+[2018/03/15 12:23:12:1560] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+```
+
+Visit http://localhost:7681 on multiple browser windows
+
+The page opens a subscribe mode ws connection back to the broker,
+and a publisher mode ws connection back to the broker.
+
+The textarea shows the data from the subscription connection.
+
+If you type text is in the text box and press send, the text
+is passed to the broker on the publisher ws connection and
+sent to all subscribers.
diff --git a/minimal-examples/ws-server/minimal-ws-broker/minimal-ws-broker.c b/minimal-examples/ws-server/minimal-ws-broker/minimal-ws-broker.c
new file mode 100644 (file)
index 0000000..88ee988
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * lws-minimal-ws-broker
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with an added publish / broker / subscribe ws server.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws broker | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-broker/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-broker/mount-origin/example.js
new file mode 100644 (file)
index 0000000..61c7617
--- /dev/null
@@ -0,0 +1,83 @@
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       subscriber_ws = new_ws(get_appropriate_ws_url(""), "lws-minimal-broker");
+       try {
+               subscriber_ws.onopen = function() {
+                       document.getElementById("b").disabled = 0;
+               };
+       
+               subscriber_ws.onmessage =function got_packet(msg) {
+                       document.getElementById("r").value =
+                               document.getElementById("r").value + msg.data + "\n";
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               subscriber_ws.onclose = function(){
+                       document.getElementById("b").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+       
+       publisher_ws = new_ws(get_appropriate_ws_url("/publisher"), "lws-minimal-broker");
+       try {
+               publisher_ws.onopen = function() {
+                       document.getElementById("m").disabled = 0;
+               };
+       
+               publisher_ws.onmessage =function got_packet(msg) {
+               };
+       
+               publisher_ws.onclose = function(){
+                       document.getElementById("m").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+
+       function sendmsg()
+       {
+               publisher_ws.send(document.getElementById("m").value);
+               document.getElementById("m").value = "";
+       }
+
+       document.getElementById("b").addEventListener("click", sendmsg);
+
+}, false);
+       
diff --git a/minimal-examples/ws-server/minimal-ws-broker/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-broker/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-broker/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-broker/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-broker/mount-origin/index.html
new file mode 100644 (file)
index 0000000..c733b95
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               LWS chat <b>minimal ws broker example</b>.<br>
+               This page opens two separate ws connections...<br>
+               A subscriber ws connection fills this textarea<br>
+               with data it receives from the broker...
+               <br>
+               <br>
+               <textarea id=r readonly cols=40 rows=10></textarea><br>
+               <br>
+               ... and a publisher ws connection sends the string<br>
+               in the box below to the broker when you press Send.<br>
+               <input type="text" id=m cols=40 rows=1>
+               <button id=b>Send</button>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-broker/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-broker/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-broker/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-broker/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-broker/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-broker/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..4ee8981
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-broker"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This implements a minimal "broker", for systems that look like this
+ *
+ * [ publisher  ws client ] <-> [ ws server  broker ws server ] <-> [ ws client subscriber ]
+ *
+ * The "publisher" role is to add data to the broker.
+ *
+ * The "subscriber" role is to hear about all data added to the system.
+ *
+ * The "broker" role is to manage incoming data from publishers and pass it out
+ * to subscribers.
+ *
+ * Any number of publishers and subscribers are supported.
+ *
+ * This example implements a single ws server, using one ws protocol, that treats ws
+ * connections as being in publisher or subscriber mode according to the URL the ws
+ * connection was made to.  ws connections to "/publisher" URL are understood to be
+ * publishing data and to any other URL, subscribing.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       uint32_t tail;
+       char publishing; /* nonzero: peer is publishing to us */
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+
+       struct lws_ring *ring; /* ringbuffer holding unsent messages */
+};
+
+/* destroys the message when everyone has had a copy of it */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct msg *pmsg;
+       struct msg amsg;
+       char buf[32];
+       int n, m;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring)
+                       return 1;
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               lws_ring_destroy(vhd->ring);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               pss->tail = lws_ring_get_oldest_tail(vhd->ring);
+               pss->wsi = wsi;
+               if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI) > 0)
+                       pss->publishing = !strcmp(buf, "/publisher");
+               if (!pss->publishing)
+                       /* add subscribers to the list of live pss held in the vhd */
+                       lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               /* remove our closing pss from the list of live pss */
+               lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
+                                 pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+
+               if (pss->publishing)
+                       break;
+
+               pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
+               if (!pmsg)
+                       break;
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
+                             pmsg->len, LWS_WRITE_TEXT);
+               if (m < (int)pmsg->len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lws_ring_consume_and_update_oldest_tail(
+                       vhd->ring,      /* lws_ring object */
+                       struct per_session_data__minimal, /* type of objects with tails */
+                       &pss->tail,     /* tail of guy doing the consuming */
+                       1,              /* number of payload objects being consumed */
+                       vhd->pss_list,  /* head of list of objects with tails */
+                       tail,           /* member name of tail in objects with tails */
+                       pss_list        /* member name of next object in objects with tails */
+               );
+
+               /* more to do? */
+               if (lws_ring_get_element(vhd->ring, &pss->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(pss->wsi);
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+
+               if (!pss->publishing)
+                       break;
+
+               /*
+                * For test, our policy is ignore publishing when there are
+                * no subscribers connected.
+                */
+               if (!vhd->pss_list)
+                       break;
+
+               n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+
+               amsg.len = len;
+               /* notice we over-allocate by LWS_PRE */
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               memcpy((char *)amsg.payload + LWS_PRE, in, len);
+               if (!lws_ring_insert(vhd->ring, &amsg, 1)) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping 2!\n");
+                       break;
+               }
+
+               /*
+                * let every subscriber know we want to write something
+                * on them as soon as they are ready
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       if (!(*ppss)->publishing)
+                               lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal-broker", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               128, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-echo/CMakeLists.txt
new file mode 100644 (file)
index 0000000..78823ea
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-echo)
+set(SRCS minimal-ws-server-echo.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/README.md b/minimal-examples/ws-server/minimal-ws-server-echo/README.md
new file mode 100644 (file)
index 0000000..bf65c6e
--- /dev/null
@@ -0,0 +1,30 @@
+# lws minimal ws server + permessage-deflate echo
+
+This example serves no-protocl-name ws on localhost:7681
+and echoes back anything that comes from the client.
+
+You can use it for testing lws against Autobahn (use the
+-p option to tell it to listen on 9001 for that)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+Commandline option|Meaning
+---|---
+-d <loglevel>|Debug verbosity in decimal, eg, -d15
+-p port|Port to connect to
+-u url|URL path part to connect to
+-o|Finish after one connection
+
+```
+ $ ./lws-minimal-ws-server-echo
+[2018/04/24 10:29:34:6212] USER: LWS minimal ws server echo + permessage-deflate + multifragment bulk message
+[2018/04/24 10:29:34:6213] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 off
+...
+```
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c b/minimal-examples/ws-server/minimal-ws-server-echo/minimal-ws-server-echo.c
new file mode 100644 (file)
index 0000000..e3b217f
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * lws-minimal-ws-server-echo
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a ws server that echoes back what it was sent, in a way
+ * compatible with autobahn -m fuzzingclient
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_server_echo.c"
+
+static struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted, port = 7681, options;
+
+/* pass pointers to shared vars to the protocol */
+
+static const struct lws_protocol_vhost_options pvo_options = {
+       NULL,
+       NULL,
+       "options",              /* pvo name */
+       (void *)&options        /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_interrupted = {
+       &pvo_options,
+       NULL,
+       "interrupted",          /* pvo name */
+       (void *)&interrupted    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,                           /* "next" pvo linked-list */
+       &pvo_interrupted,               /* "child" pvo linked-list */
+       "lws-minimal-server-echo",      /* protocol name we belong to on this vhost */
+       ""                              /* ignored */
+};
+static const struct lws_extension extensions[] = {
+       {
+               "permessage-deflate",
+               lws_extension_callback_pm_deflate,
+               "permessage-deflate"
+                "; client_no_context_takeover"
+                "; client_max_window_bits"
+       },
+       { NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n");
+       lwsl_user("   lws-minimal-ws-client-echo [-n (no exts)] [-p port] [-o (once)]\n");
+
+
+       if ((p = lws_cmdline_option(argc, argv, "-p")))
+               port = atoi(p);
+
+       if (lws_cmdline_option(argc, argv, "-o"))
+               options |= 1;
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = port;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       if (!lws_cmdline_option(argc, argv, "-n"))
+               info.extensions = extensions;
+       info.pt_serv_buf_size = 32 * 1024;
+       info.options = LWS_SERVER_OPTION_VALIDATE_UTF8 |
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       lwsl_user("Completed %s\n", interrupted == 2 ? "OK" : "failed");
+
+       return interrupted != 2;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-echo/protocol_lws_minimal_server_echo.c b/minimal-examples/ws-server/minimal-ws-server-echo/protocol_lws_minimal_server_echo.c
new file mode 100644 (file)
index 0000000..b2a5531
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-server-echo"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The protocol shows how to send and receive bulk messages over a ws connection
+ * that optionally may have the permessage-deflate extension negotiated on it.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+#define RING_DEPTH 4096
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+       char binary;
+       char first;
+       char final;
+};
+
+struct per_session_data__minimal_server_echo {
+       struct lws_ring *ring;
+       uint32_t msglen;
+       uint32_t tail;
+       uint8_t completed:1;
+       uint8_t flow_controlled:1;
+       uint8_t write_consume_pending:1;
+};
+
+struct vhd_minimal_server_echo {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+
+       int *interrupted;
+       int *options;
+};
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+#include <assert.h>
+static int
+callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason,
+                         void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal_server_echo *pss =
+                       (struct per_session_data__minimal_server_echo *)user;
+       struct vhd_minimal_server_echo *vhd = (struct vhd_minimal_server_echo *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi));
+       const struct msg *pmsg;
+       struct msg amsg;
+       int m, n, flags;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct vhd_minimal_server_echo));
+               if (!vhd)
+                       return -1;
+
+               vhd->context = lws_get_context(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               /* get the pointers we were passed in pvo */
+
+               vhd->interrupted = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "interrupted")->value;
+               vhd->options = (int *)lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "options")->value;
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* generate a block of output before travis times us out */
+               lwsl_warn("LWS_CALLBACK_ESTABLISHED\n");
+               pss->ring = lws_ring_create(sizeof(struct msg), RING_DEPTH,
+                                           __minimal_destroy_message);
+               if (!pss->ring)
+                       return 1;
+               pss->tail = 0;
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+
+               lwsl_user("LWS_CALLBACK_SERVER_WRITEABLE\n");
+
+               if (pss->write_consume_pending) {
+                       /* perform the deferred fifo consume */
+                       lws_ring_consume_single_tail(pss->ring, &pss->tail, 1);
+                       pss->write_consume_pending = 0;
+               }
+
+               pmsg = lws_ring_get_element(pss->ring, &pss->tail);
+               if (!pmsg) {
+                       lwsl_user(" (nothing in ring)\n");
+                       break;
+               }
+
+               flags = lws_write_ws_flags(
+                           pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
+                           pmsg->first, pmsg->final);
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) +
+                             LWS_PRE, pmsg->len, flags);
+               if (m < (int)pmsg->len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
+                               m, flags, pmsg->first, pmsg->final);
+               /*
+                * Workaround deferred deflate in pmd extension by only
+                * consuming the fifo entry when we are certain it has been
+                * fully deflated at the next WRITABLE callback.  You only need
+                * this if you're using pmd.
+                */
+               pss->write_consume_pending = 1;
+               lws_callback_on_writable(wsi);
+
+               if (pss->flow_controlled &&
+                   (int)lws_ring_get_count_free_elements(pss->ring) > RING_DEPTH - 5) {
+                       lws_rx_flow_control(wsi, 1);
+                       pss->flow_controlled = 0;
+               }
+
+               if ((*vhd->options & 1) && pmsg && pmsg->final)
+                       pss->completed = 1;
+
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+
+               lwsl_user("LWS_CALLBACK_RECEIVE: %4d (rpp %5d, first %d, "
+                         "last %d, bin %d, msglen %d (+ %d = %d))\n",
+                         (int)len, (int)lws_remaining_packet_payload(wsi),
+                         lws_is_first_fragment(wsi),
+                         lws_is_final_fragment(wsi),
+                         lws_frame_is_binary(wsi), pss->msglen, (int)len,
+                         (int)pss->msglen + (int)len);
+
+               if (len) {
+                       ;
+                       //puts((const char *)in);
+                       //lwsl_hexdump_notice(in, len);
+               }
+
+               amsg.first = lws_is_first_fragment(wsi);
+               amsg.final = lws_is_final_fragment(wsi);
+               amsg.binary = lws_frame_is_binary(wsi);
+               n = (int)lws_ring_get_count_free_elements(pss->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+
+               if (amsg.final)
+                       pss->msglen = 0;
+               else
+                       pss->msglen += len;
+
+               amsg.len = len;
+               /* notice we over-allocate by LWS_PRE */
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               memcpy((char *)amsg.payload + LWS_PRE, in, len);
+               if (!lws_ring_insert(pss->ring, &amsg, 1)) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+               lws_callback_on_writable(wsi);
+
+               if (n < 3 && !pss->flow_controlled) {
+                       pss->flow_controlled = 1;
+                       lws_rx_flow_control(wsi, 0);
+               }
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               lwsl_user("LWS_CALLBACK_CLOSED\n");
+               lws_ring_destroy(pss->ring);
+
+               if (*vhd->options & 1) {
+                       if (!*vhd->interrupted)
+                               *vhd->interrupted = 1 + pss->completed;
+                       lws_cancel_service(lws_get_context(wsi));
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO \
+       { \
+               "lws-minimal-server-echo", \
+               callback_minimal_server_echo, \
+               sizeof(struct per_session_data__minimal_server_echo), \
+               1024, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_SERVER_ECHO
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_server_echo(struct lws_context *context,
+                              struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_server_echo(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d27769f
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-pmd-bulk)
+set(SRCS minimal-ws-server-pmd-bulk.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+#require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/README.md b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/README.md
new file mode 100644 (file)
index 0000000..274dbf9
--- /dev/null
@@ -0,0 +1,21 @@
+# lws minimal ws server + permessage-deflate for bulk traffic
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server-pmd-bulk
+[2018/03/04 09:30:02:7986] USER: LWS minimal ws server | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681 in your browser
+
+One or another kind of bulk ws transfer is made to the browser.
+
+The ws connection is made via permessage-deflate extension.
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/minimal-ws-server-pmd-bulk.c
new file mode 100644 (file)
index 0000000..6f655c4
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_pmd_bulk.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL_PMD_BULK,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted, options;
+
+/* pass pointers to shared vars to the protocol */
+
+static const struct lws_protocol_vhost_options pvo_options = {
+        NULL,
+        NULL,
+        "options",              /* pvo name */
+        (void *)&options        /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo_interrupted = {
+        &pvo_options,
+        NULL,
+        "interrupted",          /* pvo name */
+        (void *)&interrupted    /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+        NULL,           /* "next" pvo linked-list */
+        &pvo_interrupted,       /* "child" pvo linked-list */
+        "lws-minimal-pmd-bulk", /* protocol name we belong to on this vhost */
+        ""              /* ignored */
+};
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static const struct lws_extension extensions[] = {
+       {
+               "permessage-deflate",
+               lws_extension_callback_pm_deflate,
+               "permessage-deflate"
+                "; client_no_context_takeover"
+                "; client_max_window_bits"
+       },
+       { NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server + permessage-deflate | visit http://localhost:7681\n");
+       lwsl_user("   %s [-n (no exts)] [-c (compressible)] [-b (blob)]\n", argv[0]);
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.pvo = &pvo;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (!lws_cmdline_option(argc, argv, "-n"))
+               info.extensions = extensions;
+
+       if (lws_cmdline_option(argc, argv, "-c"))
+               options |= 1; /* send compressible text */
+
+       if (lws_cmdline_option(argc, argv, "-b"))
+               options |= 2; /* send in one giant blob */
+
+       info.pt_serv_buf_size = 32 * 1024;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/example.js
new file mode 100644 (file)
index 0000000..d1c49a7
--- /dev/null
@@ -0,0 +1,65 @@
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal-pmd-bulk");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("r").disabled = 0;
+                       document.getElementById("status").textContent = "ws open "+ ws.extensions;
+               };
+       
+               ws.onmessage = function got_packet(msg) {
+                       console.log("Received ws message len " + msg.data.size);
+                       document.getElementById("r").value =
+                               document.getElementById("r").value + "\nReceived: " + msg.data.size + " bytes\n";
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+       
+                       /* echo it back */
+                       ws.send(msg.data);
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("r").disabled = 1;
+                       document.getElementById("status").textContent = "ws closed";
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+
+}, false);
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/index.html
new file mode 100644 (file)
index 0000000..f54f1cc
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               LWS bulk transfer example</b>.<br>
+               A large ws message is sent to all browsers open on this page.<br>
+               The browser js echoes the large ws message back to the server.<br>
+               <br>
+               <span id=status>Ws closed</span><br>
+               <br>
+               <textarea id=r readonly cols=40 rows=10></textarea><br>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/protocol_lws_minimal_pmd_bulk.c b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/protocol_lws_minimal_pmd_bulk.c
new file mode 100644 (file)
index 0000000..09e4307
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * ws protocol handler plugin for "lws-minimal-pmd-bulk"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The protocol shows how to send and receive bulk messages over a ws connection
+ * that optionally may have the permessage-deflate extension negotiated on it.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/*
+ * We will produce a large ws message either from this text repeated many times,
+ * or from 0x40 + a 6-bit pseudorandom number
+ */
+
+static const char * const redundant_string =
+       "No one would have believed in the last years of the nineteenth "
+       "century that this world was being watched keenly and closely by "
+       "intelligences greater than man's and yet as mortal as his own; that as "
+       "men busied themselves about their various concerns they were "
+       "scrutinised and studied, perhaps almost as narrowly as a man with a "
+       "microscope might scrutinise the transient creatures that swarm and "
+       "multiply in a drop of water.  With infinite complacency men went to "
+       "and fro over this globe about their little affairs, serene in their "
+       "assurance of their empire over matter. It is possible that the "
+       "infusoria under the microscope do the same.  No one gave a thought to "
+       "the older worlds of space as sources of human danger, or thought of "
+       "them only to dismiss the idea of life upon them as impossible or "
+       "improbable.  It is curious to recall some of the mental habits of "
+       "those departed days.  At most terrestrial men fancied there might be "
+       "other men upon Mars, perhaps inferior to themselves and ready to "
+       "welcome a missionary enterprise. Yet across the gulf of space, minds "
+       "that are to our minds as ours are to those of the beasts that perish, "
+       "intellects vast and cool and unsympathetic, regarded this earth with "
+       "envious eyes, and slowly and surely drew their plans against us.  And "
+       "early in the twentieth century came the great disillusionment. "
+;
+
+/* this reflects the length of the string above */
+#define REPEAT_STRING_LEN 1337
+/* this is the total size of the ws message we will send */
+#define MESSAGE_SIZE (100 * REPEAT_STRING_LEN)
+/* this is how much we will send each time the connection is writable */
+#define MESSAGE_CHUNK_SIZE (1 * 1024)
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal_pmd_bulk {
+       int position_tx, position_rx;
+       uint64_t rng_rx, rng_tx;
+};
+
+struct vhd_minimal_pmd_bulk {
+        int *interrupted;
+        /*
+         * b0 = 1: test compressible text, = 0: test uncompressible binary
+         * b1 = 1: send as a single blob, = 0: send as fragments
+         */
+       int *options;
+};
+
+static uint64_t rng(uint64_t *r)
+{
+       *r ^= *r << 21;
+       *r ^= *r >> 35;
+       *r ^= *r << 4;
+
+       return *r;
+}
+
+static int
+callback_minimal_pmd_bulk(struct lws *wsi, enum lws_callback_reasons reason,
+                         void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal_pmd_bulk *pss =
+                       (struct per_session_data__minimal_pmd_bulk *)user;
+        struct vhd_minimal_pmd_bulk *vhd = (struct vhd_minimal_pmd_bulk *)
+                        lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                lws_get_protocol(wsi));
+       uint8_t buf[LWS_PRE + MESSAGE_SIZE], *start = &buf[LWS_PRE], *p;
+       int n, m, flags, olen, amount;
+
+       switch (reason) {
+        case LWS_CALLBACK_PROTOCOL_INIT:
+                vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                                lws_get_protocol(wsi),
+                                sizeof(struct vhd_minimal_pmd_bulk));
+                if (!vhd)
+                        return -1;
+
+                /* get the pointer to "interrupted" we were passed in pvo */
+                vhd->interrupted = (int *)lws_pvo_search(
+                        (const struct lws_protocol_vhost_options *)in,
+                        "interrupted")->value;
+                vhd->options = (int *)lws_pvo_search(
+                        (const struct lws_protocol_vhost_options *)in,
+                        "options")->value;
+                break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               pss->rng_tx = 4;
+               pss->rng_rx = 4;
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               if (pss->position_tx == MESSAGE_SIZE)
+                       break;
+
+               amount = MESSAGE_CHUNK_SIZE;
+               if ((*vhd->options) & 2) {
+                       amount = MESSAGE_SIZE;
+                       lwsl_user("(writing as one blob of %d)\n", amount);
+               }
+
+               /* fill up one chunk's worth of message content */
+
+               p = start;
+               n = amount;
+               if (n > MESSAGE_SIZE - pss->position_tx)
+                       n = MESSAGE_SIZE - pss->position_tx;
+
+               flags = lws_write_ws_flags(LWS_WRITE_BINARY, !pss->position_tx,
+                                          pss->position_tx + n == MESSAGE_SIZE);
+
+               /*
+                * select between producing compressible repeated text,
+                * or uncompressible PRNG output
+                */
+
+               if (*vhd->options & 1) {
+                       while (n) {
+                               size_t s;
+
+                               m = pss->position_tx % REPEAT_STRING_LEN;
+                               s = REPEAT_STRING_LEN - m;
+                               if (s > (size_t)n)
+                                       s = n;
+                               memcpy(p, &redundant_string[m], s);
+                               pss->position_tx += s;
+                               p += s;
+                               n -= s;
+                       }
+               } else {
+                       pss->position_tx += n;
+                       while (n--)
+                               *p++ = rng(&pss->rng_tx);
+               }
+
+               n = lws_ptr_diff(p, start);
+               m = lws_write(wsi, start, n, flags);
+               lwsl_user("LWS_CALLBACK_SERVER_WRITEABLE: wrote %d\n", n);
+               if (m < n) {
+                       lwsl_err("ERROR %d / %d writing ws\n", m, n);
+                       return -1;
+               }
+               if (pss->position_tx != MESSAGE_SIZE) /* if more to do... */
+                       lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+               lwsl_user("LWS_CALLBACK_RECEIVE: %4d (pss->pos=%d, rpp %5d, last %d)\n",
+                               (int)len, (int)pss->position_rx, (int)lws_remaining_packet_payload(wsi),
+                               lws_is_final_fragment(wsi));
+               olen = len;
+
+               if (*vhd->options & 1) {
+                       while (len) {
+                               size_t s;
+                               m = pss->position_rx % REPEAT_STRING_LEN;
+                               s = REPEAT_STRING_LEN - m;
+                               if (s > len)
+                                       s = len;
+                               if (memcmp(in, &redundant_string[m], s)) {
+                                       lwsl_user("echo'd data doesn't match\n");
+                                       return -1;
+                               }
+                               pss->position_rx += s;
+                               in = ((char *)in) + s;
+                               len -= s;
+                       }
+               } else {
+                       p = (uint8_t *)in;
+                       pss->position_rx += len;
+                       while (len--) {
+                               if (*p++ != (uint8_t)rng(&pss->rng_rx)) {
+                                       lwsl_user("echo'd data doesn't match: 0x%02X 0x%02X (%d)\n",
+                                               *(p - 1), (int)(0x40 + (pss->rng_rx & 0x3f)),
+                                               (int)((pss->position_rx - olen) + olen - len));
+                                       lwsl_hexdump_notice(in, olen);
+                                       return -1;
+                               }
+                       }
+                       if (pss->position_rx == MESSAGE_SIZE)
+                               pss->position_rx = 0;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL_PMD_BULK \
+       { \
+               "lws-minimal-pmd-bulk", \
+               callback_minimal_pmd_bulk, \
+               sizeof(struct per_session_data__minimal_pmd_bulk), \
+               4096, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL_PMD_BULK
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal_pmd_bulk(struct lws_context *context,
+                              struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal_pmd_bulk(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1098d50
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-pmd-corner)
+set(SRCS minimal-ws-server-pmd-corner.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/README.md b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/README.md
new file mode 100644 (file)
index 0000000..eb5a738
--- /dev/null
@@ -0,0 +1,24 @@
+# lws minimal ws server + permessage-deflate corner case tests
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server-pmd-corner
+[2018/11/21 16:47:49:0171] USER: LWS minimal ws server + permessage-deflate Corner Cases | visit http://localhost:7681
+[2018/11/21 16:47:49:0172] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+
+```
+
+Visit http://localhost:7681 
+
+5 ws connections are made via permessage-deflate extension.
+
+When the ws connection is established, various amounts of data are sent
+resulting in ciphertext packets of a known size.
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/minimal-ws-server-pmd-corner.c
new file mode 100644 (file)
index 0000000..7a31a1f
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin",  /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static const struct lws_extension extensions[] = {
+       {
+               "permessage-deflate",
+               lws_extension_callback_pm_deflate,
+               "permessage-deflate"
+                "; client_no_context_takeover"
+                "; client_max_window_bits"
+       },
+       { NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server + permessage-deflate Corner Cases | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.extensions = extensions;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/example.js
new file mode 100644 (file)
index 0000000..ec3a99c
--- /dev/null
@@ -0,0 +1,88 @@
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+var ws = new Array();
+
+function conn(n)
+{
+       ws[n] = new_ws(get_appropriate_ws_url("/" + (n + 1)), "lws-minimal");
+       ws[n].n = n;
+       try {
+               ws[n].onopen = function() {
+                       document.getElementById("r").disabled = 0;
+                       document.getElementById("status").textContent =
+                               document.getElementById("status").textContent + " " +
+                               "ws open "+ ws[n].extensions;
+               };
+       
+               ws[n].onmessage = function got_packet(msg) {
+                       if (typeof msg.data !== "string") {
+                               //console.log(msg.data);
+                               document.getElementById("r").value =
+                                       document.getElementById("r").value +
+                                       ws[n].n + " " + "blob uncompressed length " +
+                                               msg.data.size  + "\n";
+                       } else
+                               document.getElementById("r").value =
+                                       document.getElementById("r").value + msg.data + "\n";
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws[n].onclose = function(){
+                       document.getElementById("r").disabled = 1;
+                       document.getElementById("status").textContent = "ws closed";
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+}
+
+window.addEventListener("load", function() {
+       
+       var n;
+
+       /*
+        * we make 5 individual connections.  Because if we don't, by default pmd
+        * will reuse its dictionary to make subsequent tests very short. 
+        */
+       
+       for (n = 0; n < 5; n++)
+               conn(n);
+       
+       console.log("load");
+               
+}, false);
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/index.html
new file mode 100644 (file)
index 0000000..45b0d81
--- /dev/null
@@ -0,0 +1,21 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+       
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+       
+               LWS <b>pmd corner case test</b>.<br>
+               A ws link is made back to the server and results shown here.<br>
+               It should show four binary blobs of increasing size.
+               <br>
+               <br>
+               <span id=status>Ws closed</span><br>
+               <br>
+               <textarea id=r readonly cols=40 rows=10></textarea>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-corner/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-server-pmd-corner/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..1558b37
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * ws protocol handler plugin for "lws-minimal"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This version holds a single message at a time, which may be lost if a new
+ * message comes.  See the minimal-ws-server-ring sample for the same thing
+ * but using an lws_ring ringbuffer to hold up to 8 messages at a time.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/*
+ * This came from...
+ *
+ * cat /dev/urandom | hexdump -C -n 1024 | tr -s ' ' | cut -d' ' -f 2-17 | head -n-1 | sed "s/\ /, 0x/g" | sed "s/^/0x/g" | sed "s/\$/,/g"
+ *
+ * ...then the length tuned by hand to get the ciphertext sizes that we want to
+ * confirm are OK.
+ *
+ * We can only pass in a maximum of one compression buffer of input at a time,
+ * which is 1024 by default.
+ */
+
+unsigned char uncompressible[] = {
+       0xfe, 0xcc, 0x47, 0xcb, 0x10, 0xf4, 0x3c, 0x85,
+       0x8e, 0xd4, 0xe2, 0xf6, 0xd1, 0xd1, 0xdb, 0x64,
+       0x94, 0x50, 0xf6, 0x14, 0x25, 0x03, 0x09, 0x3a,
+       0xb1, 0x47, 0x86, 0xa8, 0x3c, 0x4f, 0x3b, 0x98,
+       0x7b, 0x3e, 0x67, 0x3e, 0x22, 0xc5, 0x4c, 0x45,
+       0xf4, 0xf7, 0xb5, 0x79, 0xc0, 0x26, 0x6e, 0x5c,
+       0xf4, 0x10, 0x04, 0xa9, 0x3c, 0x4f, 0xed, 0xc5,
+       0x3d, 0xd4, 0x9f, 0x9f, 0xa3, 0xdb, 0x29, 0xeb,
+       0x1e, 0xe1, 0x52, 0xab, 0xb5, 0x75, 0x25, 0x86,
+       0x86, 0x02, 0x2c, 0x9d, 0x9c, 0x86, 0x46, 0x92,
+       0xe9, 0x04, 0xd8, 0x2c, 0x7d, 0x8a, 0x56, 0xe1,
+       0xe1, 0xb6, 0x84, 0x4d, 0x17, 0x30, 0x01, 0x60,
+       0xa6, 0xf4, 0xba, 0xc9, 0x5a, 0x29, 0xe3, 0x05,
+       0xe1, 0xb4, 0x0b, 0x23, 0x74, 0x93, 0x25, 0x76,
+       0xce, 0x15, 0xe4, 0x82, 0x9f, 0xbf, 0xe8, 0x6a,
+       0x4a, 0xc5, 0xc2, 0x22, 0x91, 0x80, 0xb5, 0xd7,
+       0xb3, 0xce, 0x70, 0x0e, 0xf7, 0xbb, 0x2f, 0xc5,
+       0x83, 0x39, 0x86, 0xe5, 0x3e, 0xb7, 0x83, 0x87,
+       0xc2, 0xeb, 0xc8, 0xed, 0x59, 0x26, 0xc1, 0xe6,
+       0x80, 0x17, 0x3c, 0x29, 0x53, 0x4c, 0x1c, 0x3f,
+       0x54, 0xbe, 0x34, 0x26, 0x72, 0xed, 0x38, 0x10,
+       0xd1, 0x37, 0x07, 0x2d, 0x12, 0x31, 0x9b, 0xc5,
+       0x92, 0x09, 0x13, 0x5d, 0x8e, 0xef, 0xdb, 0x52,
+       0x7f, 0x7d, 0x6f, 0x62, 0x1e, 0x17, 0xd2, 0xf9,
+       0x72, 0x74, 0xc7, 0xd6, 0x1f, 0x8b, 0x9c, 0x4c,
+       0x26, 0xd2, 0x6f, 0x7c, 0x33, 0x06, 0xee, 0xc2,
+       0xa3, 0x41, 0x43, 0x4f, 0x40, 0x2a, 0x9c, 0xb3,
+       0x4a, 0xb1, 0x88, 0x4e, 0x6f, 0xf2, 0xb7, 0x38,
+       0xde, 0x87, 0x0d, 0xdc, 0x15, 0x6a, 0x36, 0x6b,
+       0xf3, 0x6c, 0x61, 0xf5, 0x24, 0x8e, 0xb6, 0xcc,
+       0x8a, 0x3a, 0xa0, 0xb4, 0x9b, 0xae, 0x85, 0x87,
+       0x75, 0xf5, 0xbd, 0x50, 0x1f, 0xb5, 0x0c, 0xdb,
+       0x6c, 0x68, 0x59, 0xef, 0x37, 0x5a, 0x2a, 0x85,
+       0xf0, 0xce, 0x4d, 0x58, 0xa1, 0xa5, 0xde, 0x73,
+       0x9b, 0x1a, 0x3d, 0x8a, 0x00, 0xba, 0x2f, 0xe2,
+       0xda, 0xad, 0x3c, 0x63, 0x8a, 0x33, 0x39, 0xc4,
+       0x07, 0x29, 0x1d, 0xa7, 0x40, 0x3b, 0xa4, 0xa6,
+       0xae, 0xee, 0x37, 0x08, 0x83, 0xd1, 0x72, 0x66,
+       0x3d, 0x43, 0xe3, 0x7a, 0x48, 0xfc, 0xf8, 0xd4,
+       0xe3, 0xab, 0xd0, 0xe9, 0xb1, 0xf4, 0x4d, 0x3c,
+       0x6b, 0x58, 0xde, 0x3c, 0x91, 0x0d, 0x3e, 0xec,
+       0x35, 0x6d, 0x53, 0xe6, 0xb6, 0x4b, 0xc0, 0x80,
+       0x18, 0xab, 0x96, 0x7f, 0x05, 0xd7, 0xd4, 0x81,
+       0x0f, 0x92, 0x2b, 0xaf, 0x72, 0x59, 0xc2, 0x14,
+       0xca, 0x62, 0x82, 0xac, 0xe3, 0x17, 0x43, 0x61,
+       0x4d, 0x1e, 0xfc, 0x72, 0xaf, 0xfc, 0x55, 0x2a,
+       0x2b, 0xb6, 0x8e, 0x6e, 0xe6, 0x86, 0xeb, 0xcc,
+       0x26, 0x6c, 0xdf, 0xac, 0x02, 0x58, 0xa1, 0x5d,
+       0x1b, 0x07, 0xe2, 0x5d, 0x50, 0xb9, 0xbf, 0x2e,
+       0x1f, 0x49, 0x39, 0xe6, 0x7f, 0x2f, 0x0e, 0x9d,
+       0x09, 0x42, 0xc7, 0xa1, 0xcc, 0xeb, 0x5b, 0x06,
+       0x1c, 0x11, 0x9f, 0xea, 0xc1, 0x96, 0x82, 0xa9,
+       0x30, 0x6a, 0xda, 0x98, 0x87, 0x43, 0xfd, 0x25,
+       0xe7, 0x27, 0x53, 0x9a, 0xb3, 0x2f, 0x19, 0xa9,
+       0x1a, 0xf4, 0xd6, 0xf3, 0x9e, 0xba, 0x9a, 0x91,
+       0x52, 0x8f, 0x20, 0x6b, 0x4c, 0x3a, 0x2a, 0x3d,
+       0xa0, 0xff, 0x8d, 0x61, 0x04, 0xee, 0x26, 0x55,
+       0xdd, 0xd7, 0x67, 0xe4, 0x84, 0x0d, 0xf1, 0x5d,
+       0xc7, 0xeb, 0xb3, 0x8c, 0x67, 0xa2, 0xc8, 0x1f,
+       0x53, 0x02, 0xc4, 0x8c, 0x89, 0xd5, 0x51, 0xc8,
+       0x8b, 0xb7, 0xc8, 0x11, 0xbe, 0x0e, 0xc2, 0xb1,
+       0x00, 0x35, 0x81, 0x96, 0xac, 0x90, 0x9c, 0xbc,
+       0x09, 0x82, 0x75, 0xc3, 0xe7, 0x66, 0x4e, 0x68,
+       0xdc, 0xa1, 0xf0, 0xd0, 0x2d, 0x49, 0x3b, 0x47,
+       0xba, 0x19, 0xc8, 0x9b, 0x90, 0x12, 0xc0, 0xdf,
+       0xda, 0x32, 0x0f, 0x79, 0x6d, 0x1a, 0x5f, 0x92,
+       0x51, 0x70, 0xfc, 0xca, 0x08, 0xd4, 0x7f, 0x1a,
+       0x56, 0x04, 0x99, 0x33, 0x89, 0x3d, 0x6f, 0x89,
+       0x10, 0x25, 0x81, 0xe2, 0xbd, 0x06, 0xd6, 0xaa,
+       0x02, 0x8e, 0x4c, 0xa3, 0x60, 0xfd, 0xaf, 0x9c,
+       0x81, 0x75, 0xaf, 0x2f, 0xe1, 0x72, 0xe0, 0x6e,
+       0x15, 0xdd, 0xbb, 0x92, 0xd1, 0xbe, 0x8e, 0x9b,
+       0xfb, 0x82, 0xb9, 0x47, 0x6f, 0x02, 0x28, 0x2a,
+       0x67, 0x50, 0xed, 0x24, 0x9b, 0x4d, 0x69, 0xd7,
+       0xa9, 0x66, 0x3e, 0x14, 0x4b, 0x00, 0x2a, 0xe4,
+       0x3d, 0x63, 0xb2, 0x10, 0xd4, 0x05, 0x9d, 0xe3,
+       0xde, 0xce, 0xd8, 0x04, 0x41, 0x03, 0xb5, 0xda,
+       0xb0, 0x6f, 0xca, 0x63, 0x64, 0x04, 0xff, 0x07,
+       0x58, 0x5f, 0x96, 0xf7, 0x6c, 0xb7, 0x67, 0x05,
+       0xd6, 0x85, 0xf2, 0x1e, 0xc1, 0xdc, 0x76, 0x12,
+       0x50, 0x83, 0x78, 0xa2, 0x51, 0x94, 0xe1, 0x2e,
+       0xb8, 0x97, 0x5b, 0x08, 0x81, 0xac, 0x59, 0x43,
+       0xe9, 0x01, 0x09, 0xa2, 0xed, 0x10, 0x4f, 0xb1,
+       0x5b, 0xb8, 0x67, 0xe8, 0x61, 0x8d, 0xc8, 0xd9,
+       0xc3, 0x5f, 0x65, 0xd7, 0xaa, 0x30, 0x0e, 0xc9,
+       0x43, 0x98, 0x1d, 0xf1, 0xa5, 0x28, 0xd5, 0xa1,
+       0x6b, 0x8f, 0x89, 0x76, 0x97, 0xa1, 0x3e, 0x6f,
+       0x39, 0xf4, 0xb9, 0x6b, 0xa7, 0xfe, 0x58, 0x24,
+       0xcd, 0x75, 0xa8, 0xec, 0x9e, 0x1c, 0x8e, 0x02,
+       0x2a, 0xce, 0xe9, 0x0a, 0x24, 0x31, 0x89, 0x5a,
+       0xd5, 0xdd, 0x70, 0x8e, 0x5f, 0xee, 0xc1, 0x34,
+       0xf8, 0xe2, 0x8a, 0xca, 0xf1, 0xf2, 0x71, 0x4c,
+       0x31, 0x56, 0xeb, 0x03, 0xf9, 0x6c, 0x0d, 0xa9,
+       0x65, 0x6e, 0x88, 0x4f, 0x8e, 0x80, 0x69, 0xd7,
+       0xd4, 0x63, 0x45, 0x9c, 0xab, 0x8c, 0x3d, 0x08,
+       0x8b, 0xd9, 0x97, 0xdc, 0x88, 0x59, 0x19, 0x2d,
+       0xb2, 0x84, 0xf4, 0x78, 0x3e, 0xce, 0x80, 0xba,
+       0xeb, 0x34, 0x5a, 0x9e, 0x8e, 0x98, 0xc4, 0x45,
+       0x9d, 0x59, 0xb2, 0x7e, 0xc1, 0x7e, 0x5b, 0x89,
+       0xd0, 0x02, 0xcb, 0xa4, 0xf1, 0xf2, 0xa7, 0x3a,
+       0x05, 0xc3, 0x7d, 0x43, 0x64, 0x7f, 0xf0, 0xc1,
+       0xf8, 0x71, 0x3b, 0x38, 0x39, 0xc7, 0x1b, 0xf4,
+       0x2f, 0x5a, 0x5c, 0x43, 0x1b, 0xe3, 0x93, 0xe8,
+       0x79, 0xe8, 0x35, 0x63, 0x34, 0x7e, 0x25, 0x41,
+       0x6f, 0x08, 0xce, 0x6f, 0x95, 0x2a, 0xc2, 0xdc,
+       0x65, 0xe2, 0xa5, 0xc0, 0xfd, 0xf1, 0x78, 0x32,
+       0x23, 0x09, 0x75, 0x99, 0x12, 0x7a, 0x83, 0xfd,
+       0xae, 0x1e, 0xb2, 0xe9, 0x12, 0x5c, 0x3d, 0x03,
+       0x68, 0x12, 0x1e, 0xe3, 0x8f, 0xff, 0x47, 0xe3,
+       0xb4, 0x7e, 0x9b, 0x7e, 0x60, 0x2e, 0xf4, 0x06,
+       0xba, 0x10, 0x08, 0x6b, 0xf9, 0x25, 0x59, 0xf3,
+       0x61, 0x13, 0x2b, 0xd1, 0x2f, 0x04, 0x5f, 0xd6,
+       0xd3, 0x42, 0xf6, 0x21, 0x57, 0xf6, 0xd3, 0xb3,
+       0xec, 0xec, 0x07, 0x33, 0xbf, 0x69, 0x04, 0xec,
+       0x88, 0x8d, 0x06, 0x2b, 0xfa, 0xee, 0xb2, 0x7b,
+       0x41, 0x2a, 0x49, 0x0f, 0x30, 0x52, 0x41, 0x29,
+       0x70, 0xd0, 0xf6, 0xb6, 0xbf, 0x27, 0x1a, 0x56,
+       0x9a, 0x4b, 0x2a, 0x67, 0xfb, 0xc8, 0x16, 0x46,
+       0x59, 0xc7, 0xf5, 0x5f, 0x20, 0x10, 0x25, 0x6c,
+       0x1e, 0x36, 0x20, 0x0c, 0x3e, 0x7e, 0x15, 0x6c,
+       0xa2, 0xbd, 0x22, 0xc4, 0x3d, 0xc9, 0x74, 0x56,
+       0xab, 0x31, 0x92, 0xb8, 0x9f, 0xa1, 0x05, 0x2e,
+       0xc4, 0xdb, 0x32, 0x91, 0xcb, 0x0f, 0x4a, 0x73,
+       0x7f, 0xe1, 0xe6, 0x65, 0x2e, 0x5e, 0xa6, 0xaf,
+       0xae, 0xa9, 0x04, 0x14, 0x83, 0xef, 0x19, 0x70,
+       0x5e, 0xcb, 0xf5, 0x87, 0xcc, 0x45, 0xf7, 0x60,
+       0xd7, 0x9d, 0x1e, 0x2e, /* 1012 */
+
+       /* up to here, this generates a 1022-byte single packet of compressed
+        * data that is well-formed and produces 1012 bytes of plaintext.
+        *
+        * The compressed packet ends
+        *
+        *  03F0: 70 5E CB F5 87 CC 45 F7 60 D7 9D 1E 2E 00
+        */
+
+       0x54, /* 1013 */
+
+       /* up to here, this generates a 1023-byte single packet of compressed
+        * data that is well-formed and produces 1013 bytes of plaintext.
+        *
+        * The compressed packet ends
+        *
+        *  03F0: 70 5E CB F5 87 CC 45 F7 60 D7 9D 1E 2E 54 00
+        */
+
+       0x83, /* 1014 */
+
+       /* up to here, a 1023-byte + 3-byte (1 byte payload) packet
+        * of uncompressed length 1014 */
+
+       0x09, 0x99, 0xf9, 0x71, 0x9f, 0x15, 0x49, 0xda, 0xa8, 0x99, /* 1024 */
+
+       /* up to here, a 1023-byte (1020 payload) + 3-byte (1 payload) packet
+        * of uncompressed length 1019 */
+
+       0xf5, 0xe6, 0xa1, 0x71, 0x64, 0x9a, 0x95, 0xed,
+
+
+};
+
+/* generates ciphertext:        1022  1023  1023 + 3 1023 + 3 */
+static int corner_lengths[] = {
+/* bytes plaintext,    ciphertext */
+       1012,   /*      1019 */
+       1013,   /*      1020 */
+       1014,   /*      1021 */
+       1019,   /*      1021 */
+       1024,   /*      1021*/
+};
+
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal {
+       int which;
+       int last; /* 0 no test, else test number in corner_lengths[] + 1 */
+};
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       unsigned char buf[LWS_PRE + 2048];
+       int m;
+
+       switch (reason) {
+       case LWS_CALLBACK_ESTABLISHED:
+               if (lws_hdr_copy(wsi, (char *)buf, sizeof(buf),
+                                WSI_TOKEN_GET_URI) < 0)
+                       return -1;
+
+               pss->last = atoi((char *)buf + 1);
+
+               if (pss->last > (int)LWS_ARRAY_SIZE(corner_lengths))
+                       pss->last = 0;
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               if (!pss->last)
+                       break;
+
+               lwsl_err("%s: writable %d, %d\n", __func__, pss->last,
+                               corner_lengths[pss->last - 1]);
+
+               memcpy(buf + LWS_PRE, uncompressible,
+                      corner_lengths[pss->last - 1]);
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, buf + LWS_PRE, corner_lengths[pss->last - 1],
+                               LWS_WRITE_BINARY);
+               if (m < corner_lengths[pss->last - 1]) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               pss->last = 0;
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               2048, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-pmd/CMakeLists.txt
new file mode 100644 (file)
index 0000000..db9f03e
--- /dev/null
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 2.8.9)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-pmd)
+set(SRCS minimal-ws-server-pmd.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/README.md b/minimal-examples/ws-server/minimal-ws-server-pmd/README.md
new file mode 100644 (file)
index 0000000..468f74f
--- /dev/null
@@ -0,0 +1,23 @@
+# lws minimal ws server + permessage-deflate
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server
+[2018/03/04 09:30:02:7986] USER: LWS minimal ws server | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681 on multiple browser windows
+
+Text you type in any browser window is sent to all of them.
+
+For simplicity of this example, only one line of text is cached at the server.
+
+The ws connection is made via permessage-deflate extension.
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c b/minimal-examples/ws-server/minimal-ws-server-pmd/minimal-ws-server-pmd.c
new file mode 100644 (file)
index 0000000..2b7b567
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin",  /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+static const struct lws_extension extensions[] = {
+       {
+               "permessage-deflate",
+               lws_extension_callback_pm_deflate,
+               "permessage-deflate"
+                "; client_no_context_takeover"
+                "; client_max_window_bits"
+       },
+       { NULL, NULL, NULL /* terminator */ }
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server + permessage-deflate | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.extensions = extensions;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/example.js
new file mode 100644 (file)
index 0000000..4760a20
--- /dev/null
@@ -0,0 +1,71 @@
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("m").disabled = 0;
+                       document.getElementById("b").disabled = 0;
+                       document.getElementById("status").textContent = "ws open "+ ws.extensions;
+               };
+       
+               ws.onmessage =function got_packet(msg) {
+                       document.getElementById("r").value =
+                               document.getElementById("r").value + msg.data + "\n";
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("m").disabled = 1;
+                       document.getElementById("b").disabled = 1;
+                       document.getElementById("status").textContent = "ws closed";
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+       
+       function sendmsg()
+       {
+               ws.send(document.getElementById("m").value);
+               document.getElementById("m").value = "";
+       }
+       
+       document.getElementById("b").addEventListener("click", sendmsg);
+
+}, false);
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/index.html
new file mode 100644 (file)
index 0000000..43c548a
--- /dev/null
@@ -0,0 +1,21 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+       
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+       
+               LWS chat <b>minimal ws server example</b>.<br>
+               Chat is sent to all browsers open on this page.<br>
+               <br>
+               <span id=status>Ws closed</span><br>
+               <br>
+               <textarea id=r readonly cols=40 rows=10></textarea><br>
+               <input type="text" id=m cols=40 rows=1>
+               <button id=b>Send</button>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-pmd/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-server-pmd/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..be72f82
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * ws protocol handler plugin for "lws-minimal"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This version holds a single message at a time, which may be lost if a new
+ * message comes.  See the minimal-ws-server-ring sample for the same thing
+ * but using an lws_ring ringbuffer to hold up to 8 messages at a time.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       int last; /* the last message number we sent */
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+
+       struct msg amsg; /* the one pending message... */
+       int current; /* the current message number we are caching */
+};
+
+/* destroys the message when everyone has had a copy of it */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       int m;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* add ourselves to the list of live pss held in the vhd */
+               pss->pss_list = vhd->pss_list;
+               vhd->pss_list = pss;
+               pss->wsi = wsi;
+               pss->last = vhd->current;
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               /* remove our closing pss from the list of live pss */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       if (*ppss == pss) {
+                               *ppss = pss->pss_list;
+                               break;
+                       }
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               if (!vhd->amsg.payload)
+                       break;
+
+               if (pss->last == vhd->current)
+                       break;
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)vhd->amsg.payload) +
+                             LWS_PRE, vhd->amsg.len, LWS_WRITE_TEXT);
+               if (m < (int)vhd->amsg.len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               pss->last = vhd->current;
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+               if (vhd->amsg.payload)
+                       __minimal_destroy_message(&vhd->amsg);
+
+               vhd->amsg.len = len;
+               /* notice we over-allocate by LWS_PRE */
+               vhd->amsg.payload = malloc(LWS_PRE + len);
+               if (!vhd->amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               memcpy((char *)vhd->amsg.payload + LWS_PRE, in, len);
+               vhd->current++;
+
+               /*
+                * let everybody know we want to write something on them
+                * as soon as they are ready
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               128, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-ring/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e199801
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-ring)
+set(SRCS minimal-ws-server-ring.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/README.md b/minimal-examples/ws-server/minimal-ws-server-ring/README.md
new file mode 100644 (file)
index 0000000..25eb0ae
--- /dev/null
@@ -0,0 +1,24 @@
+# lws minimal ws server (lws_ring)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server
+[2018/03/04 09:30:02:7986] USER: LWS minimal ws server (lws_ring) | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681 on multiple browser windows
+
+Text you type in any browser window is sent to all of them.
+
+A ringbuffer holds up to 8 lines of text.
+
+This also demonstrates how the ringbuffer can take action against lagging or
+disconnected clients that cause the ringbuffer to fill.
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c b/minimal-examples/ws-server/minimal-ws-server-ring/minimal-ws-server-ring.c
new file mode 100644 (file)
index 0000000..c87ad20
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with an added websocket chat server using a ringbuffer.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server (lws_ring) | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/example.js
new file mode 100644 (file)
index 0000000..be1037f
--- /dev/null
@@ -0,0 +1,68 @@
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("m").disabled = 0;
+                       document.getElementById("b").disabled = 0;
+               };
+       
+               ws.onmessage =function got_packet(msg) {
+                       document.getElementById("r").value =
+                               document.getElementById("r").value + msg.data + "\n";
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("m").disabled = 1;
+                       document.getElementById("b").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+       
+       function sendmsg()
+       {
+               ws.send(document.getElementById("m").value);
+               document.getElementById("m").value = "";
+       }
+       
+       document.getElementById("b").addEventListener("click", sendmsg);
+
+}, false);
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/index.html
new file mode 100644 (file)
index 0000000..7081c31
--- /dev/null
@@ -0,0 +1,20 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+       
+               LWS chat <b>minimal ws server example</b>.<br>
+               Chat is sent to all browsers open on this page.
+               <br>
+               <br>
+               <textarea id=r readonly cols=40 rows=10></textarea><br>
+
+               <input type="text" id=m cols=40 rows=1>
+               <button id=b>Send</button>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-ring/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-ring/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-server-ring/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..7b51066
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * ws protocol handler plugin for "lws-minimal"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This version uses an lws_ring ringbuffer to cache up to 8 messages at a time,
+ * so it's not so easy to lose messages.
+ *
+ * This also demonstrates how to "cull", ie, kill, connections that can't
+ * keep up for some reason.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       uint32_t tail;
+
+       unsigned int culled:1;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+
+       struct lws_ring *ring; /* ringbuffer holding unsent messages */
+};
+
+static void
+cull_lagging_clients(struct per_vhost_data__minimal *vhd)
+{
+       uint32_t oldest_tail = lws_ring_get_oldest_tail(vhd->ring);
+       struct per_session_data__minimal *old_pss = NULL;
+       int most = 0, before = lws_ring_get_count_waiting_elements(vhd->ring,
+                                       &oldest_tail), m;
+
+       /*
+        * At least one guy with the oldest tail has lagged too far, filling
+        * the ringbuffer with stuff waiting for them, while new stuff is
+        * coming in, and they must close, freeing up ringbuffer entries.
+        */
+
+       lws_start_foreach_llp_safe(struct per_session_data__minimal **,
+                             ppss, vhd->pss_list, pss_list) {
+
+               if ((*ppss)->tail == oldest_tail) {
+                       old_pss = *ppss;
+
+                       lwsl_user("Killing lagging client %p\n", (*ppss)->wsi);
+
+                       lws_set_timeout((*ppss)->wsi, PENDING_TIMEOUT_LAGGING,
+                                       /*
+                                        * we may kill the wsi we came in on,
+                                        * so the actual close is deferred
+                                        */
+                                       LWS_TO_KILL_ASYNC);
+
+                       /*
+                        * We might try to write something before we get a
+                        * chance to close.  But this pss is now detached
+                        * from the ring buffer.  Mark this pss as culled so we
+                        * don't try to do anything more with it.
+                        */
+
+                       (*ppss)->culled = 1;
+
+                       /*
+                        * Because we can't kill it synchronously, but we
+                        * know it's closing momentarily and don't want its
+                        * participation any more, remove its pss from the
+                        * vhd pss list early.  (This is safe to repeat
+                        * uselessly later in the close flow).
+                        *
+                        * Notice this changes *ppss!
+                        */
+
+                       lws_ll_fwd_remove(struct per_session_data__minimal,
+                                         pss_list, (*ppss), vhd->pss_list);
+
+                       /* use the changed *ppss so we won't skip anything */
+
+                       continue;
+
+               } else {
+                       /*
+                        * so this guy is a survivor of the cull.  Let's track
+                        * what is the largest number of pending ring elements
+                        * for any survivor.
+                        */
+                       m = lws_ring_get_count_waiting_elements(vhd->ring,
+                                                       &((*ppss)->tail));
+                       if (m > most)
+                               most = m;
+               }
+
+       } lws_end_foreach_llp_safe(ppss);
+
+       /* it would mean we lost track of oldest... but Coverity insists */
+       if (!old_pss)
+               return;
+
+       /*
+        * Let's recover (ie, free up) all the ring slots between the
+        * original oldest's last one and the "worst" survivor.
+        */
+
+       lws_ring_consume_and_update_oldest_tail(vhd->ring,
+               struct per_session_data__minimal, &old_pss->tail, before - most,
+               vhd->pss_list, tail, pss_list);
+
+       lwsl_user("%s: shrunk ring from %d to %d\n", __func__, before, most);
+}
+
+/* destroys the message when everyone has had a copy of it */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct msg *pmsg;
+       struct msg amsg;
+       int n, m;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring)
+                       return 1;
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               lws_ring_destroy(vhd->ring);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* add ourselves to the list of live pss held in the vhd */
+               lwsl_user("LWS_CALLBACK_ESTABLISHED: wsi %p\n", wsi);
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               pss->tail = lws_ring_get_oldest_tail(vhd->ring);
+               pss->wsi = wsi;
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               lwsl_user("LWS_CALLBACK_CLOSED: wsi %p\n", wsi);
+               /* remove our closing pss from the list of live pss */
+               lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
+                                 pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               if (pss->culled)
+                       break;
+               pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
+               if (!pmsg)
+                       break;
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) +
+                             LWS_PRE, pmsg->len, LWS_WRITE_TEXT);
+               if (m < (int)pmsg->len) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lws_ring_consume_and_update_oldest_tail(
+                       vhd->ring,      /* lws_ring object */
+                       struct per_session_data__minimal, /* type of objects with tails */
+                       &pss->tail,     /* tail of guy doing the consuming */
+                       1,              /* number of payload objects being consumed */
+                       vhd->pss_list,  /* head of list of objects with tails */
+                       tail,           /* member name of tail in objects with tails */
+                       pss_list        /* member name of next object in objects with tails */
+               );
+
+               /* more to do for us? */
+               if (lws_ring_get_element(vhd->ring, &pss->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(pss->wsi);
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+               n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               if (!n) {
+                       /* forcibly make space */
+                       cull_lagging_clients(vhd);
+                       n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               }
+               if (!n)
+                       break;
+
+               lwsl_user("LWS_CALLBACK_RECEIVE: free space %d\n", n);
+
+               amsg.len = len;
+               /* notice we over-allocate by LWS_PRE... */
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               /* ...and we copy the payload in at +LWS_PRE */
+               memcpy((char *)amsg.payload + LWS_PRE, in, len);
+               if (!lws_ring_insert(vhd->ring, &amsg, 1)) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+                       break;
+               }
+
+               /*
+                * let everybody know we want to write something on them
+                * as soon as they are ready
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               0, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-threadpool/CMakeLists.txt
new file mode 100644 (file)
index 0000000..951e9f6
--- /dev/null
@@ -0,0 +1,92 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-threadpool)
+set(SRCS minimal-ws-server-threadpool.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+require_lws_config(LWS_WITH_THREADPOOL 1 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/README.md b/minimal-examples/ws-server/minimal-ws-server-threadpool/README.md
new file mode 100644 (file)
index 0000000..c8a91df
--- /dev/null
@@ -0,0 +1,26 @@
+# lws minimal ws server (threadpool)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+Pthreads is required on your system.
+
+This demonstrates how to cleanly assign tasks bound to a wsi to a thread pool,
+with a queue if the pool is occupied.
+
+It creates a threadpool with 3 worker threads and a maxiumum queue size of 4.
+
+The web page at http://localhost:7681 then starts up 8 x ws connections.
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server-threadpool 
+[2018/03/13 13:09:52:2208] USER: LWS minimal ws server + threadpool | visit http://localhost:7681
+[2018/03/13 13:09:52:2365] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+```
+
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c b/minimal-examples/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c
new file mode 100644 (file)
index 0000000..e0d8a9d
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * lws-minimal-ws-server=threadpool
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal ws server that can cooperate with
+ * other threads cleanly.  Two other threads are started, which fill
+ * a ringbuffer with strings at 10Hz.
+ *
+ * The actual work and thread spawning etc are done in the protocol
+ * implementation in protocol_lws_minimal.c.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal_threadpool.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/*
+ * This demonstrates how to pass a pointer into a specific protocol handler
+ * running on a specific vhost.  In this case, it's our default vhost and
+ * we pass the pvo named "config" with the value a const char * "myconfig".
+ *
+ * This is the preferred way to pass configuration into a specific vhost +
+ * protocol instance.
+ */
+
+static const struct lws_protocol_vhost_options pvo_ops = {
+       NULL,
+       NULL,
+       "config",               /* pvo name */
+       (void *)"myconfig"      /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,           /* "next" pvo linked-list */
+       &pvo_ops,       /* "child" pvo linked-list */
+       "lws-minimal",  /* protocol name we belong to on this vhost */
+       ""              /* ignored */
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server + threadpool | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.pvo = &pvo; /* per-vhost options */
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       /* start the threads that create content */
+
+       while (!interrupted)
+               if (lws_service(context, 0))
+                       interrupted = 1;
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/example.js
new file mode 100644 (file)
index 0000000..783ae13
--- /dev/null
@@ -0,0 +1,78 @@
+var head = 0, tail = 0, ring = new Array();
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       var n, wsa = new Array, alive = 0;
+       
+       for (n = 0; n < 8; n++) {
+       
+               ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
+               wsa.push(ws);
+               try {
+                       ws.onopen = function() {
+                               document.getElementById("r").disabled = 0;
+                               alive++;
+                       };
+
+                       ws.onmessage = function got_packet(msg) {
+                               var n, s = "";
+               
+                               ring[head] = msg.data + "\n";
+                               head = (head + 1) % 50;
+                               if (tail === head)
+                                       tail = (tail + 1) % 50;
+               
+                               n = tail;
+                               do {
+                                       s = s + ring[n];
+                                       n = (n + 1) % 50;
+                               } while (n !== head);
+               
+                               document.getElementById("r").value = s; 
+                               document.getElementById("r").scrollTop =
+                                       document.getElementById("r").scrollHeight;
+                       };
+
+                       ws.onclose = function(){
+                               if (--alive === 0)
+                                       document.getElementById("r").disabled = 1;
+                       };
+               } catch(exception) {
+                       alert("<p>Error " + exception);  
+               }
+       }
+}, false);
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html
new file mode 100644 (file)
index 0000000..ab3a306
--- /dev/null
@@ -0,0 +1,19 @@
+ <html>
+  <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               <b>Minimal ws server threadpool example</b>.<br>
+               8 x ws connections are opened back to the example server.<br>
+               There are three threads in the pool to service them, the<br>
+               remainder are queued until a thread in the pool is free.<p>
+               The textarea show the last 50 lines received.
+               <br>
+               <br>
+               <textarea id=r readonly cols=40 rows=50></textarea><br>
+       </body>
+</html>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c b/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c
new file mode 100644 (file)
index 0000000..aea48e7
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * ws protocol handler plugin for "lws-minimal" demonstrating lws threadpool
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The main reason some things are as they are is that the task lifecycle may
+ * be unrelated to the wsi lifecycle that queued that task.
+ *
+ * Consider the task may call an external library and run for 30s without
+ * "checking in" to see if it should stop.  The wsi that started the task may
+ * have closed at any time before the 30s are up, with the browser window
+ * closing or whatever.
+ *
+ * So data shared between the asynchronous task and the wsi must have its
+ * lifecycle determined by the task, not the wsi.  That means a separate struct
+ * that can be freed by the task.
+ *
+ * In the case the wsi outlives the task, the tasks do not get destroyed until
+ * the service thread has called lws_threadpool_task_status() on the completed
+ * task.  So there is no danger of the shared task private data getting randomly
+ * freed.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+struct per_vhost_data__minimal {
+       struct lws_threadpool *tp;
+       const char *config;
+};
+
+struct task_data {
+       char result[64];
+
+       uint64_t pos, end;
+};
+
+/*
+ * Create the private data for the task
+ *
+ * Notice we hand over responsibility for the cleanup and freeing of the
+ * allocated task_data to the threadpool, because the wsi it was originally
+ * bound to may close while the thread is still running.  So we allocate
+ * something discrete for the task private data that can be definitively owned
+ * and freed by the threadpool, not the wsi... the pss won't do, as it only
+ * exists for the lifecycle of the wsi connection.
+ *
+ * When the task is created, we also tell it how to destroy the private data
+ * by giving it args.cleanup as cleanup_task_private_data() defined below.
+ */
+
+static struct task_data *
+create_task_private_data(void)
+{
+       struct task_data *priv = malloc(sizeof(*priv));
+
+       return priv;
+}
+
+/*
+ * Destroy the private data for the task
+ *
+ * Notice the wsi the task was originally bound to may be long gone, in the
+ * case we are destroying the lws context and the thread was doing something
+ * for a long time without checking in.
+ */
+static void
+cleanup_task_private_data(struct lws *wsi, void *user)
+{
+       struct task_data *priv = (struct task_data *)user;
+
+       free(priv);
+}
+
+/*
+ * This runs in its own thread, from the threadpool.
+ *
+ * The implementation behind this in lws uses pthreads, but no pthreadisms are
+ * required in the user code.
+ *
+ * The example counts to 10M, "checking in" to see if it should stop after every
+ * 100K and pausing to sync with the service thread to send a ws message every
+ * 1M.  It resumes after the service thread determines the wsi is writable and
+ * the LWS_CALLBACK_SERVER_WRITEABLE indicates the task thread can continue by
+ * calling lws_threadpool_task_sync().
+ */
+
+static enum lws_threadpool_task_return
+task_function(void *user, enum lws_threadpool_task_status s)
+{
+       struct task_data *priv = (struct task_data *)user;
+       int budget = 100 * 1000;
+
+       if (priv->pos == priv->end)
+               return LWS_TP_RETURN_FINISHED;
+
+       /*
+        * Preferably replace this with ~100ms of your real task, so it
+        * can "check in" at short intervals to see if it has been asked to
+        * stop.
+        *
+        * You can just run tasks atomically here with the thread dedicated
+        * to it, but it will cause odd delays while shutting down etc and
+        * the task will run to completion even if the wsi that started it
+        * has since closed.
+        */
+
+       while (budget--)
+               priv->pos++;
+
+       usleep(100000);
+
+       if (!(priv->pos % (1000 * 1000))) {
+               lws_snprintf(priv->result + LWS_PRE,
+                            sizeof(priv->result) - LWS_PRE,
+                            "pos %llu", (unsigned long long)priv->pos);
+
+               return LWS_TP_RETURN_SYNC;
+       }
+
+       return LWS_TP_RETURN_CHECKING_IN;
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct lws_protocol_vhost_options *pvo;
+       struct lws_threadpool_create_args cargs;
+       struct lws_threadpool_task_args args;
+       struct lws_threadpool_task *task;
+       struct task_data *priv;
+       int n, m, r = 0;
+       char name[32];
+       void *_user;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               /* create our per-vhost struct */
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               if (!vhd)
+                       return 1;
+
+               /* recover the pointer to the globals struct */
+               pvo = lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "config");
+               if (!pvo || !pvo->value) {
+                       lwsl_err("%s: Can't find \"config\" pvo\n", __func__);
+                       return 1;
+               }
+               vhd->config = pvo->value;
+
+               memset(&cargs, 0, sizeof(cargs));
+
+               cargs.max_queue_depth = 8;
+               cargs.threads = 3;
+               vhd->tp = lws_threadpool_create(lws_get_context(wsi),
+                               &cargs, "%s",
+                               lws_get_vhost_name(lws_get_vhost(wsi)));
+               if (!vhd->tp)
+                       return 1;
+
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                              lws_get_protocol(wsi),
+                                              LWS_CALLBACK_USER, 1);
+
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               lws_threadpool_finish(vhd->tp);
+               lws_threadpool_destroy(vhd->tp);
+               break;
+
+       case LWS_CALLBACK_USER:
+
+               /*
+                * in debug mode, dump the threadpool stat to the logs once
+                * a second
+                */
+               lws_threadpool_dump(vhd->tp);
+               lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
+                                              lws_get_protocol(wsi),
+                                              LWS_CALLBACK_USER, 1);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+
+               memset(&args, 0, sizeof(args));
+               priv = args.user = create_task_private_data();
+               if (!args.user)
+                       return 1;
+
+               priv->pos = 0;
+               priv->end = 10 * 1000 * 1000;
+
+               /* queue the task... the task takes on responsibility for
+                * destroying args.user.  pss->priv just has a copy of it */
+
+               args.wsi = wsi;
+               args.task = task_function;
+               args.cleanup = cleanup_task_private_data;
+
+               lws_get_peer_simple(wsi, name, sizeof(name));
+
+               if (!lws_threadpool_enqueue(vhd->tp, &args, "ws %s", name)) {
+                       lwsl_user("%s: Couldn't enqueue task\n", __func__);
+                       cleanup_task_private_data(wsi, priv);
+                       return 1;
+               }
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_THREADPOOL, 30);
+
+               /*
+                * so the asynchronous worker will let us know the next step
+                * by causing LWS_CALLBACK_SERVER_WRITEABLE
+                */
+
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               break;
+
+       case LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL:
+               lwsl_debug("LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL: %p\n", wsi);
+               lws_threadpool_dequeue(wsi);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+
+               /*
+                * even completed tasks wait in a queue until we call the
+                * below on them.  Then they may destroy themselves and their
+                * args.user data (by calling the cleanup callback).
+                *
+                * If you need to get things from the still-valid private task
+                * data, copy it here before calling
+                * lws_threadpool_task_status() that may free the task and the
+                * private task data.
+                */
+
+               n = lws_threadpool_task_status_wsi(wsi, &task, &_user);
+               lwsl_debug("%s: LWS_CALLBACK_SERVER_WRITEABLE: status %d\n",
+                          __func__, n);
+               switch(n) {
+
+               case LWS_TP_STATUS_FINISHED:
+               case LWS_TP_STATUS_STOPPED:
+               case LWS_TP_STATUS_QUEUED:
+               case LWS_TP_STATUS_RUNNING:
+               case LWS_TP_STATUS_STOPPING:
+                       return 0;
+
+               case LWS_TP_STATUS_SYNCING:
+                       /* the task has paused for us to do something */
+                       break;
+               default:
+                       return -1;
+               }
+
+               priv = (struct task_data *)_user;
+
+               lws_set_timeout(wsi, PENDING_TIMEOUT_THREADPOOL_TASK, 5);
+
+               n = strlen(priv->result + LWS_PRE);
+               m = lws_write(wsi, (unsigned char *)priv->result + LWS_PRE,
+                             n, LWS_WRITE_TEXT);
+               if (m < n) {
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       lws_threadpool_task_sync(task, 1);
+                       return -1;
+               }
+
+               /*
+                * service thread has done whatever it wanted to do with the
+                * data the task produced: if it's waiting to do more it can
+                * continue now.
+                */
+               lws_threadpool_task_sync(task, 0);
+               break;
+
+       default:
+               break;
+       }
+
+       return r;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               0, \
+               128, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-threads-smp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..32ecbf5
--- /dev/null
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-threads-smp)
+set(SRCS minimal-ws-server.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/README.md b/minimal-examples/ws-server/minimal-ws-server-threads-smp/README.md
new file mode 100644 (file)
index 0000000..81be312
--- /dev/null
@@ -0,0 +1,39 @@
+# lws minimal ws server (threads) + SMP
+
+This demonstrates both independent threads creating content
+as in the -threads example, and multiple service threads
+as in the http-server-smp example (but with ws).
+
+## build
+
+You must first build libwebsockets itself with cmake `-DLWS_MAX_SMP=8`
+or some other number greater than one.
+
+```
+ $ cmake . && make
+```
+
+Pthreads is required on your system.
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server-threads-smp
+[2019/01/28 06:59:17:4217] USER: LWS minimal ws server + threads + smp | visit http://localhost:7681
+[2019/01/28 06:59:17:4219] NOTICE:   Service threads: 2
+[2019/01/28 06:59:17:4220] NOTICE: LWS_CALLBACK_EVENT_WAIT_CANCELLED in svc tid 0x7fec48af8700
+[2019/01/28 06:59:17:4220] NOTICE: LWS_CALLBACK_EVENT_WAIT_CANCELLED in svc tid 0x7fec48af8700
+...
+```
+
+Visit http://localhost:7681 on multiple browser windows.  You may need to open
+4 before the second service thread is used (check "svc tid" in the browser output).
+
+Two lws service threads are started.
+
+Two separate asynchronous threads generate strings and add them to a ringbuffer,
+signalling all lws service threads to send new entries to all the browser windows.
+
+This demonstrates how to safely manage asynchronously generated content
+and hook it up to the lws service threads.
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c b/minimal-examples/ws-server/minimal-ws-server-threads-smp/minimal-ws-server.c
new file mode 100644 (file)
index 0000000..8303f5c
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal ws server that can cooperate with
+ * other threads cleanly.  Two other threads are started, which fill
+ * a ringbuffer with strings at 10Hz.
+ *
+ * The actual work and thread spawning etc are done in the protocol
+ * implementation in protocol_lws_minimal.c.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+#define COUNT_THREADS 2
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static struct lws_context *context;
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/*
+ * This demonstrates how to pass a pointer into a specific protocol handler
+ * running on a specific vhost.  In this case, it's our default vhost and
+ * we pass the pvo named "config" with the value a const char * "myconfig".
+ *
+ * This is the preferred way to pass configuration into a specific vhost +
+ * protocol instance.
+ */
+
+static const struct lws_protocol_vhost_options pvo_ops = {
+       NULL,
+       NULL,
+       "config",               /* pvo name */
+       (void *)"myconfig"      /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,           /* "next" pvo linked-list */
+       &pvo_ops,       /* "child" pvo linked-list */
+       "lws-minimal",  /* protocol name we belong to on this vhost */
+       ""              /* ignored */
+};
+
+void *thread_service(void *threadid)
+{
+       while (lws_service_tsi(context, 1000,
+                              (int)(lws_intptr_t)threadid) >= 0 &&
+              !interrupted)
+               ;
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
+       pthread_t pthread_service[COUNT_THREADS];
+       struct lws_context_creation_info info;
+       const char *p;
+       void *retval;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server + threads + smp | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.pvo = &pvo; /* per-vhost options */
+       info.count_threads = COUNT_THREADS;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       lwsl_notice("  Service threads: %d\n", lws_get_count_threads(context));
+
+       /* start all the service threads */
+
+       for (n = 0; n < lws_get_count_threads(context); n++)
+               if (pthread_create(&pthread_service[n], NULL, thread_service,
+                                  (void *)(lws_intptr_t)n))
+                       lwsl_err("Failed to start service thread\n");
+
+       /* wait for all the service threads to exit */
+
+       while ((--n) >= 0)
+               pthread_join(pthread_service[n], &retval);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/example.js
new file mode 100644 (file)
index 0000000..a6ff663
--- /dev/null
@@ -0,0 +1,71 @@
+var head = 0, tail = 0, ring = new Array();
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("r").disabled = 0;
+               };
+       
+               ws.onmessage =function got_packet(msg) {
+                       var n, s = "";
+       
+                       ring[head] = msg.data + "\n";
+                       head = (head + 1) % 50;
+                       if (tail === head)
+                               tail = (tail + 1) % 50;
+       
+                       n = tail;
+                       do {
+                               s = s + ring[n];
+                               n = (n + 1) % 50;
+                       } while (n !== head);
+       
+                       document.getElementById("r").value = s; 
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("r").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+
+}, false);
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/index.html
new file mode 100644 (file)
index 0000000..13145f6
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+
+               <b>Minimal ws server threads SMP example</b>.<br>
+               Strings generated by server threads are sent to
+               all browsers open on this page.<br>
+               The textarea show the last 50 lines received.
+               <br>
+               <br>
+               <textarea id=r readonly cols=80 rows=50></textarea><br>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-threads-smp/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads-smp/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-server-threads-smp/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..3943307
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * ws protocol handler plugin for "lws-minimal" demonstrating multithread
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/* one of these created for each message in the ringbuffer */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/*
+ * One of these is created for each client connecting to us.
+ *
+ * It is ONLY read or written from the lws service thread context.
+ */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       uint32_t tail;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+       pthread_t pthread_spam[2];
+
+       pthread_mutex_t lock_ring; /* serialize access to the ring buffer */
+       struct lws_ring *ring; /* {lock_ring} ringbuffer holding unsent content */
+
+       const char *config;
+       char finished;
+};
+
+#if defined(WIN32)
+static void usleep(unsigned long l) { Sleep(l / 1000); }
+#endif
+
+/*
+ * This runs under both lws service and "spam threads" contexts.
+ * Access is serialized by vhd->lock_ring.
+ */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+/*
+ * This runs under the "spam thread" thread context only.
+ *
+ * We spawn two threads that generate messages with this.
+ *
+ */
+
+static void *
+thread_spam(void *d)
+{
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)d;
+       struct msg amsg;
+       int len = 128, index = 1, n;
+
+       do {
+               /* don't generate output if nobody connected */
+               if (!vhd->pss_list)
+                       goto wait;
+
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+
+               /* only create if space in ringbuffer */
+               n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       goto wait_unlock;
+               }
+
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       goto wait_unlock;
+               }
+               n = lws_snprintf((char *)amsg.payload + LWS_PRE, len,
+                                "%s: spam tid: %p, msg: %d", vhd->config,
+                                (void *)pthread_self(), index++);
+               amsg.len = n;
+               n = lws_ring_insert(vhd->ring, &amsg, 1);
+               if (n != 1) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+               } else
+                       /*
+                        * This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
+                        * in the lws service thread context.
+                        */
+                       lws_cancel_service(vhd->context);
+
+wait_unlock:
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+
+wait:
+               usleep(100000);
+
+       } while (!vhd->finished);
+
+       lwsl_notice("thread_spam %p exiting\n", (void *)pthread_self());
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+/* this runs under the lws service thread context only */
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct lws_protocol_vhost_options *pvo;
+       const struct msg *pmsg;
+       char temp[LWS_PRE + 256];
+       void *retval;
+       int n, m, r = 0;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               /* create our per-vhost struct */
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               if (!vhd)
+                       return 1;
+
+               pthread_mutex_init(&vhd->lock_ring, NULL);
+
+               /* recover the pointer to the globals struct */
+               pvo = lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "config");
+               if (!pvo || !pvo->value) {
+                       lwsl_err("%s: Can't find \"config\" pvo\n", __func__);
+                       return 1;
+               }
+               vhd->config = pvo->value;
+
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring) {
+                       lwsl_err("%s: failed to create ring\n", __func__);
+                       return 1;
+               }
+
+               /* start the content-creating threads */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (pthread_create(&vhd->pthread_spam[n], NULL,
+                                          thread_spam, vhd)) {
+                               lwsl_err("thread creation failed\n");
+                               r = 1;
+                               goto init_fail;
+                       }
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+init_fail:
+               vhd->finished = 1;
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (vhd->pthread_spam[n])
+                               pthread_join(vhd->pthread_spam[n], &retval);
+
+               if (vhd->ring)
+                       lws_ring_destroy(vhd->ring);
+
+               pthread_mutex_destroy(&vhd->lock_ring);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* add ourselves to the list of live pss held in the vhd */
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               pss->tail = lws_ring_get_oldest_tail(vhd->ring);
+               pss->wsi = wsi;
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               /* remove our closing pss from the list of live pss */
+               lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
+                                 pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+
+               pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
+               if (!pmsg) {
+                       pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+                       break;
+               }
+
+               n = lws_snprintf(temp + LWS_PRE, sizeof(temp) - LWS_PRE,
+                             "svc tid:%p, %s", (void *)pthread_self(),
+                             (char *)pmsg->payload + LWS_PRE);
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, (unsigned char *)temp + LWS_PRE, n,
+                             LWS_WRITE_TEXT);
+               if (m < n) {
+                       pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lws_ring_consume_and_update_oldest_tail(
+                       vhd->ring,      /* lws_ring object */
+                       struct per_session_data__minimal, /* type of objects with tails */
+                       &pss->tail,     /* tail of guy doing the consuming */
+                       1,              /* number of payload objects being consumed */
+                       vhd->pss_list,  /* head of list of objects with tails */
+                       tail,           /* member name of tail in objects with tails */
+                       pss_list        /* member name of next object in objects with tails */
+               );
+
+               /* more to do? */
+               if (lws_ring_get_element(vhd->ring, &pss->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(pss->wsi);
+
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+               break;
+
+       case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
+               lwsl_notice("LWS_CALLBACK_EVENT_WAIT_CANCELLED in svc tid %p\n",
+                               (void *)pthread_self());
+               if (!vhd)
+                       break;
+               /*
+                * When the "spam" threads add a message to the ringbuffer,
+                * they create this event in the lws service thread context
+                * using lws_cancel_service().
+                *
+                * We respond by scheduling a writable callback for all
+                * connected clients.
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return r;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               128, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-threads/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bf67791
--- /dev/null
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckIncludeFile)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server-threads)
+set(SRCS minimal-ws-server.c)
+
+MACRO(require_pthreads result)
+       CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
+       if (NOT LWS_HAVE_PTHREAD_H)
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(result 0)
+               else()
+                       message(FATAL_ERROR "threading support requires pthreads")
+               endif()
+       endif()
+ENDMACRO()
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_pthreads(requirements)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared pthread)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets pthread)
+       endif()
+endif()
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/README.md b/minimal-examples/ws-server/minimal-ws-server-threads/README.md
new file mode 100644 (file)
index 0000000..123b7bb
--- /dev/null
@@ -0,0 +1,25 @@
+# lws minimal ws server (threads)
+
+## build
+
+```
+ $ cmake . && make
+```
+
+Pthreads is required on your system.
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server-threads
+[2018/03/13 13:09:52:2208] USER: LWS minimal ws server + threads | visit http://localhost:7681
+[2018/03/13 13:09:52:2365] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
+```
+
+Visit http://localhost:7681 on multiple browser windows
+
+Two asynchronous threads generate strings and add them to a ringbuffer,
+signalling lws to send new entries to all the browser windows.
+
+This demonstrates how to safely manage asynchronously generated content
+and hook it up to the lws service thread.
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/minimal-ws-server.c b/minimal-examples/ws-server/minimal-ws-server-threads/minimal-ws-server.c
new file mode 100644 (file)
index 0000000..40d7fc7
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates a minimal ws server that can cooperate with
+ * other threads cleanly.  Two other threads are started, which fill
+ * a ringbuffer with strings at 10Hz.
+ *
+ * The actual work and thread spawning etc are done in the protocol
+ * implementation in protocol_lws_minimal.c.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin", /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+/*
+ * This demonstrates how to pass a pointer into a specific protocol handler
+ * running on a specific vhost.  In this case, it's our default vhost and
+ * we pass the pvo named "config" with the value a const char * "myconfig".
+ *
+ * This is the preferred way to pass configuration into a specific vhost +
+ * protocol instance.
+ */
+
+static const struct lws_protocol_vhost_options pvo_ops = {
+       NULL,
+       NULL,
+       "config",               /* pvo name */
+       (void *)"myconfig"      /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,           /* "next" pvo linked-list */
+       &pvo_ops,       /* "child" pvo linked-list */
+       "lws-minimal",  /* protocol name we belong to on this vhost */
+       ""              /* ignored */
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server + threads | visit http://localhost:7681\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.pvo = &pvo; /* per-vhost options */
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       /* start the threads that create content */
+
+       while (!interrupted)
+               if (lws_service(context, 0))
+                       interrupted = 1;
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/example.js
new file mode 100644 (file)
index 0000000..a6ff663
--- /dev/null
@@ -0,0 +1,71 @@
+var head = 0, tail = 0, ring = new Array();
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("r").disabled = 0;
+               };
+       
+               ws.onmessage =function got_packet(msg) {
+                       var n, s = "";
+       
+                       ring[head] = msg.data + "\n";
+                       head = (head + 1) % 50;
+                       if (tail === head)
+                               tail = (tail + 1) % 50;
+       
+                       n = tail;
+                       do {
+                               s = s + ring[n];
+                               n = (n + 1) % 50;
+                       } while (n !== head);
+       
+                       document.getElementById("r").value = s; 
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("r").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+
+}, false);
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/index.html
new file mode 100644 (file)
index 0000000..8bd248c
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg"><br>
+               <img src="strict-csp.svg"><br>
+
+               <b>Minimal ws server threads example</b>.<br>
+               Strings generated by server threads are sent to
+               all browsers open on this page.<br>
+               The textarea show the last 50 lines received.
+               <br>
+               <br>
+               <textarea id=r readonly cols=40 rows=50></textarea><br>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server-threads/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server-threads/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-server-threads/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..3abd727
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * ws protocol handler plugin for "lws-minimal" demonstrating multithread
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/* one of these created for each message in the ringbuffer */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/*
+ * One of these is created for each client connecting to us.
+ *
+ * It is ONLY read or written from the lws service thread context.
+ */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       uint32_t tail;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+       pthread_t pthread_spam[2];
+
+       pthread_mutex_t lock_ring; /* serialize access to the ring buffer */
+       struct lws_ring *ring; /* {lock_ring} ringbuffer holding unsent content */
+
+       const char *config;
+       char finished;
+};
+
+#if defined(WIN32)
+static void usleep(unsigned long l) { Sleep(l / 1000); }
+#endif
+
+/*
+ * This runs under both lws service and "spam threads" contexts.
+ * Access is serialized by vhd->lock_ring.
+ */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+/*
+ * This runs under the "spam thread" thread context only.
+ *
+ * We spawn two threads that generate messages with this.
+ *
+ */
+
+static void *
+thread_spam(void *d)
+{
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)d;
+       struct msg amsg;
+       int len = 128, index = 1, n;
+
+       do {
+               /* don't generate output if nobody connected */
+               if (!vhd->pss_list)
+                       goto wait;
+
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+
+               /* only create if space in ringbuffer */
+               n = (int)lws_ring_get_count_free_elements(vhd->ring);
+               if (!n) {
+                       lwsl_user("dropping!\n");
+                       goto wait_unlock;
+               }
+
+               amsg.payload = malloc(LWS_PRE + len);
+               if (!amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       goto wait_unlock;
+               }
+               n = lws_snprintf((char *)amsg.payload + LWS_PRE, len,
+                                "%s: tid: %p, msg: %d", vhd->config,
+                                (void *)pthread_self(), index++);
+               amsg.len = n;
+               n = lws_ring_insert(vhd->ring, &amsg, 1);
+               if (n != 1) {
+                       __minimal_destroy_message(&amsg);
+                       lwsl_user("dropping!\n");
+               } else
+                       /*
+                        * This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
+                        * in the lws service thread context.
+                        */
+                       lws_cancel_service(vhd->context);
+
+wait_unlock:
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+
+wait:
+               usleep(100000);
+
+       } while (!vhd->finished);
+
+       lwsl_notice("thread_spam %p exiting\n", (void *)pthread_self());
+
+       pthread_exit(NULL);
+
+       return NULL;
+}
+
+/* this runs under the lws service thread context only */
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct lws_protocol_vhost_options *pvo;
+       const struct msg *pmsg;
+       void *retval;
+       int n, m, r = 0;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               /* create our per-vhost struct */
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               if (!vhd)
+                       return 1;
+
+               pthread_mutex_init(&vhd->lock_ring, NULL);
+
+               /* recover the pointer to the globals struct */
+               pvo = lws_pvo_search(
+                       (const struct lws_protocol_vhost_options *)in,
+                       "config");
+               if (!pvo || !pvo->value) {
+                       lwsl_err("%s: Can't find \"config\" pvo\n", __func__);
+                       return 1;
+               }
+               vhd->config = pvo->value;
+
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               vhd->ring = lws_ring_create(sizeof(struct msg), 8,
+                                           __minimal_destroy_message);
+               if (!vhd->ring) {
+                       lwsl_err("%s: failed to create ring\n", __func__);
+                       return 1;
+               }
+
+               /* start the content-creating threads */
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (pthread_create(&vhd->pthread_spam[n], NULL,
+                                          thread_spam, vhd)) {
+                               lwsl_err("thread creation failed\n");
+                               r = 1;
+                               goto init_fail;
+                       }
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+init_fail:
+               vhd->finished = 1;
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
+                       if (vhd->pthread_spam[n])
+                               pthread_join(vhd->pthread_spam[n], &retval);
+
+               if (vhd->ring)
+                       lws_ring_destroy(vhd->ring);
+
+               pthread_mutex_destroy(&vhd->lock_ring);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* add ourselves to the list of live pss held in the vhd */
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               pss->tail = lws_ring_get_oldest_tail(vhd->ring);
+               pss->wsi = wsi;
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               /* remove our closing pss from the list of live pss */
+               lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
+                                 pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
+
+               pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
+               if (!pmsg) {
+                       pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+                       break;
+               }
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
+                             pmsg->len, LWS_WRITE_TEXT);
+               if (m < (int)pmsg->len) {
+                       pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+                       lwsl_err("ERROR %d writing to ws socket\n", m);
+                       return -1;
+               }
+
+               lws_ring_consume_and_update_oldest_tail(
+                       vhd->ring,      /* lws_ring object */
+                       struct per_session_data__minimal, /* type of objects with tails */
+                       &pss->tail,     /* tail of guy doing the consuming */
+                       1,              /* number of payload objects being consumed */
+                       vhd->pss_list,  /* head of list of objects with tails */
+                       tail,           /* member name of tail in objects with tails */
+                       pss_list        /* member name of next object in objects with tails */
+               );
+
+               /* more to do? */
+               if (lws_ring_get_element(vhd->ring, &pss->tail))
+                       /* come back as soon as we can write more */
+                       lws_callback_on_writable(pss->wsi);
+
+               pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+               break;
+
+       case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
+               if (!vhd)
+                       break;
+               /*
+                * When the "spam" threads add a message to the ringbuffer,
+                * they create this event in the lws service thread context
+                * using lws_cancel_service().
+                *
+                * We respond by scheduling a writable callback for all
+                * connected clients.
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return r;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               128, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/minimal-examples/ws-server/minimal-ws-server/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6b938b1
--- /dev/null
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-minimal-ws-server)
+set(SRCS minimal-ws-server.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+       if (DEFINED ${reqconfig})
+       if (${reqconfig})
+               set (rq 1)
+       else()
+               set (rq 0)
+       endif()
+       else()
+               set(rq 0)
+       endif()
+
+       if (${_val} EQUAL ${rq})
+               set(SAME 1)
+       else()
+               set(SAME 0)
+       endif()
+
+       if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+               if (${_val})
+                       message("${SAMP}: skipping as lws being built without ${reqconfig}")
+               else()
+                       message("${SAMP}: skipping as lws built with ${reqconfig}")
+               endif()
+               set(${result} 0)
+       else()
+               if (LWS_WITH_MINIMAL_EXAMPLES)
+                       set(MET ${SAME})
+               else()
+                       CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+                       if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+                               set(HAS_${reqconfig} 0)
+                       else()
+                               set(HAS_${reqconfig} 1)
+                       endif()
+                       if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+                               set(MET 1)
+                       else()
+                               set(MET 0)
+                       endif()
+               endif()
+               if (NOT MET)
+                       if (${_val})
+                               message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+                       else()
+                               message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+                       endif()
+               endif()
+       
+       endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_WS 1 requirements)
+require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
+
+if (requirements)
+       add_executable(${SAMP} ${SRCS})
+
+       if (websockets_shared)
+               target_link_libraries(${SAMP} websockets_shared)
+               add_dependencies(${SAMP} websockets_shared)
+       else()
+               target_link_libraries(${SAMP} websockets)
+       endif()
+endif()
\ No newline at end of file
diff --git a/minimal-examples/ws-server/minimal-ws-server/README.md b/minimal-examples/ws-server/minimal-ws-server/README.md
new file mode 100644 (file)
index 0000000..9b0a094
--- /dev/null
@@ -0,0 +1,29 @@
+# lws minimal ws server
+
+## build
+
+```
+ $ cmake . && make
+```
+
+## Commandline Options
+
+Option|Meaning
+---|---
+-d|Set logging verbosity
+-s|Serve using TLS selfsigned cert (ie, connect to it with https://...)
+-h|Strict Host: header checking against vhost name (localhost) and port
+
+## usage
+
+```
+ $ ./lws-minimal-ws-server
+[2018/03/04 09:30:02:7986] USER: LWS minimal ws server | visit http://localhost:7681
+[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
+```
+
+Visit http://localhost:7681 on multiple browser windows
+
+Text you type in any browser window is sent to all of them.
+
+For simplicity of this example, only one line of text is cached at the server.
diff --git a/minimal-examples/ws-server/minimal-ws-server/localhost-100y.cert b/minimal-examples/ws-server/minimal-ws-server/localhost-100y.cert
new file mode 100644 (file)
index 0000000..6f372db
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
+VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb
+MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3
+WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl
+d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0
+cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA
+aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW
+aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8
+Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek
+LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH
+KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6
+jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ
+Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz
+TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK
+Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0
+nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo
+GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p
+sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU
+9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar
+jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow
+YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA
+xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P
+wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34
+H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv
+xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk
+ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g
+1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA
+AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg
+mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s
+8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX
+e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/ws-server/minimal-ws-server/localhost-100y.key b/minimal-examples/ws-server/minimal-ws-server/localhost-100y.key
new file mode 100644 (file)
index 0000000..148f859
--- /dev/null
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ
+PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK
+nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ
+toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU
+0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT
+J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS
+Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN
+uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9
+fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn
+zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au
+ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB
+QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f
+qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+
+vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9
+fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A
+Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT
+G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/
+HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8
+YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl
+xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs
+esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw
+zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz
+mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw
+au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77
+40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5
+YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH
+PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj
+W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR
+naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6
+2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m
+39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79
+J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC
+R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp
+Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh
+BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE
+fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ
+x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI
+UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM
+OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L
+65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A
+aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5
+SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S
+me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I
+G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK
+TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY
+56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2
+gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr
+Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E
+NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs
+fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
+-----END PRIVATE KEY-----
diff --git a/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c b/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c
new file mode 100644 (file)
index 0000000..12e828a
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * lws-minimal-ws-server
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates the most minimal http server you can make with lws,
+ * with an added websocket chat server.
+ *
+ * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
+ * the directory it was started in.
+ * You can change that by changing mount.origin.
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+#include <signal.h>
+
+#define LWS_PLUGIN_STATIC
+#include "protocol_lws_minimal.c"
+
+static struct lws_protocols protocols[] = {
+       { "http", lws_callback_http_dummy, 0, 0 },
+       LWS_PLUGIN_PROTOCOL_MINIMAL,
+       { NULL, NULL, 0, 0 } /* terminator */
+};
+
+static int interrupted;
+
+static const struct lws_http_mount mount = {
+       /* .mount_next */               NULL,           /* linked-list "next" */
+       /* .mountpoint */               "/",            /* mountpoint URL */
+       /* .origin */                   "./mount-origin",  /* serve from dir */
+       /* .def */                      "index.html",   /* default filename */
+       /* .protocol */                 NULL,
+       /* .cgienv */                   NULL,
+       /* .extra_mimetypes */          NULL,
+       /* .interpret */                NULL,
+       /* .cgi_timeout */              0,
+       /* .cache_max_age */            0,
+       /* .auth_mask */                0,
+       /* .cache_reusable */           0,
+       /* .cache_revalidate */         0,
+       /* .cache_intermediaries */     0,
+       /* .origin_protocol */          LWSMPRO_FILE,   /* files in a dir */
+       /* .mountpoint_len */           1,              /* char count */
+       /* .basic_auth_login_file */    NULL,
+};
+
+void sigint_handler(int sig)
+{
+       interrupted = 1;
+}
+
+int main(int argc, const char **argv)
+{
+       struct lws_context_creation_info info;
+       struct lws_context *context;
+       const char *p;
+       int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /* for LLL_ verbosity above NOTICE to be built into lws,
+                        * lws must have been configured and built with
+                        * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
+                       /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
+                       /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
+                       /* | LLL_DEBUG */;
+
+       signal(SIGINT, sigint_handler);
+
+       if ((p = lws_cmdline_option(argc, argv, "-d")))
+               logs = atoi(p);
+
+       lws_set_log_level(logs, NULL);
+       lwsl_user("LWS minimal ws server | visit http://localhost:7681 (-s = use TLS / https)\n");
+
+       memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
+       info.port = 7681;
+       info.mounts = &mount;
+       info.protocols = protocols;
+       info.vhost_name = "localhost";
+       info.ws_ping_pong_interval = 10;
+       info.options =
+               LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
+
+       if (lws_cmdline_option(argc, argv, "-s")) {
+               lwsl_user("Server using TLS\n");
+               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+               info.ssl_cert_filepath = "localhost-100y.cert";
+               info.ssl_private_key_filepath = "localhost-100y.key";
+       }
+
+       if (lws_cmdline_option(argc, argv, "-h"))
+               info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("lws init failed\n");
+               return 1;
+       }
+
+       while (n >= 0 && !interrupted)
+               n = lws_service(context, 0);
+
+       lws_context_destroy(context);
+
+       return 0;
+}
diff --git a/minimal-examples/ws-server/minimal-ws-server/mount-origin/example.js b/minimal-examples/ws-server/minimal-ws-server/mount-origin/example.js
new file mode 100644 (file)
index 0000000..189f464
--- /dev/null
@@ -0,0 +1,69 @@
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+       
+       ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
+       try {
+               ws.onopen = function() {
+                       document.getElementById("m").disabled = 0;
+                       document.getElementById("b").disabled = 0;
+               };
+       
+               ws.onmessage =function got_packet(msg) {
+                       document.getElementById("r").value =
+                               document.getElementById("r").value + msg.data + "\n";
+                       document.getElementById("r").scrollTop =
+                               document.getElementById("r").scrollHeight;
+               };
+       
+               ws.onclose = function(){
+                       document.getElementById("m").disabled = 1;
+                       document.getElementById("b").disabled = 1;
+               };
+       } catch(exception) {
+               alert("<p>Error " + exception);  
+       }
+       
+       function sendmsg()
+       {
+               ws.send(document.getElementById("m").value);
+               document.getElementById("m").value = "";
+       }
+       
+       document.getElementById("b").addEventListener("click", sendmsg);
+       
+}, false);
+
diff --git a/minimal-examples/ws-server/minimal-ws-server/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server/mount-origin/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server/mount-origin/favicon.ico differ
diff --git a/minimal-examples/ws-server/minimal-ws-server/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server/mount-origin/index.html
new file mode 100644 (file)
index 0000000..9c1dc9a
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <script src="/example.js"></script>
+ </head>
+       <body>
+               <img src="libwebsockets.org-logo.svg">
+               <img src="strict-csp.svg"><br>
+       
+               LWS chat <b>minimal ws server example</b>.<br>
+               Chat is sent to all browsers open on this page.
+               <br>
+               <br>
+               <textarea id=r readonly cols=40 rows=10></textarea><br>
+               <input type="text" id=m cols=40 rows=1>
+               <button id=b>Send</button>
+       </body>
+</html>
+
diff --git a/minimal-examples/ws-server/minimal-ws-server/mount-origin/libwebsockets.org-logo.svg b/minimal-examples/ws-server/minimal-ws-server/mount-origin/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server/mount-origin/strict-csp.svg b/minimal-examples/ws-server/minimal-ws-server/mount-origin/strict-csp.svg
new file mode 100644 (file)
index 0000000..cd128f1
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
+   <stop stop-color="#0aa70b" offset="0"/>
+   <stop stop-color="#3bff39" offset="1"/>
+  </linearGradient>
+  <filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
+   <feGaussianBlur stdDeviation="0.58510713"/>
+  </filter>
+ </defs>
+ <g transform="translate(342.15 43.638)">
+  <circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
+  <circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
+  <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
+   <path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
+   <path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+   <path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
+   <path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
+   <path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
+   <path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
+  </g>
+  <g fill="#fff">
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
+    <path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
+    <path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
+    <path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
+    <path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
+    <path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
+    <path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
+    <path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
+    <path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
+    <path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
+    <path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
+    <path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
+    <path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
+    <path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
+    <path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
+   </g>
+   <g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
+    <path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
+    <path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
+    <path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
+    <path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
+    <path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
+    <path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/minimal-examples/ws-server/minimal-ws-server/protocol_lws_minimal.c b/minimal-examples/ws-server/minimal-ws-server/protocol_lws_minimal.c
new file mode 100644 (file)
index 0000000..3c8160b
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * ws protocol handler plugin for "lws-minimal"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This version holds a single message at a time, which may be lost if a new
+ * message comes.  See the minimal-ws-server-ring sample for the same thing
+ * but using an lws_ring ringbuffer to hold up to 8 messages at a time.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+
+/* one of these created for each message */
+
+struct msg {
+       void *payload; /* is malloc'd */
+       size_t len;
+};
+
+/* one of these is created for each client connecting to us */
+
+struct per_session_data__minimal {
+       struct per_session_data__minimal *pss_list;
+       struct lws *wsi;
+       int last; /* the last message number we sent */
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct per_vhost_data__minimal {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       struct per_session_data__minimal *pss_list; /* linked-list of live pss*/
+
+       struct msg amsg; /* the one pending message... */
+       int current; /* the current message number we are caching */
+};
+
+/* destroys the message when everyone has had a copy of it */
+
+static void
+__minimal_destroy_message(void *_msg)
+{
+       struct msg *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
+static int
+callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__minimal *pss =
+                       (struct per_session_data__minimal *)user;
+       struct per_vhost_data__minimal *vhd =
+                       (struct per_vhost_data__minimal *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       int m;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__minimal));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED:
+               /* add ourselves to the list of live pss held in the vhd */
+               lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
+               pss->wsi = wsi;
+               pss->last = vhd->current;
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               /* remove our closing pss from the list of live pss */
+               lws_ll_fwd_remove(struct per_session_data__minimal, pss_list,
+                                 pss, vhd->pss_list);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               if (!vhd->amsg.payload)
+                       break;
+
+               if (pss->last == vhd->current)
+                       break;
+
+               /* notice we allowed for LWS_PRE in the payload already */
+               m = lws_write(wsi, ((unsigned char *)vhd->amsg.payload) +
+                             LWS_PRE, vhd->amsg.len, LWS_WRITE_TEXT);
+               if (m < (int)vhd->amsg.len) {
+                       lwsl_err("ERROR %d writing to ws\n", m);
+                       return -1;
+               }
+
+               pss->last = vhd->current;
+               break;
+
+       case LWS_CALLBACK_RECEIVE:
+               if (vhd->amsg.payload)
+                       __minimal_destroy_message(&vhd->amsg);
+
+               vhd->amsg.len = len;
+               /* notice we over-allocate by LWS_PRE */
+               vhd->amsg.payload = malloc(LWS_PRE + len);
+               if (!vhd->amsg.payload) {
+                       lwsl_user("OOM: dropping\n");
+                       break;
+               }
+
+               memcpy((char *)vhd->amsg.payload + LWS_PRE, in, len);
+               vhd->current++;
+
+               /*
+                * let everybody know we want to write something on them
+                * as soon as they are ready
+                */
+               lws_start_foreach_llp(struct per_session_data__minimal **,
+                                     ppss, vhd->pss_list) {
+                       lws_callback_on_writable((*ppss)->wsi);
+               } lws_end_foreach_llp(ppss, pss_list);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_MINIMAL \
+       { \
+               "lws-minimal", \
+               callback_minimal, \
+               sizeof(struct per_session_data__minimal), \
+               128, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+/* boilerplate needed if we are built as a dynamic plugin */
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_MINIMAL
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_minimal(struct lws_context *context,
+                     struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_minimal(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/module.json b/module.json
deleted file mode 100644 (file)
index ba2c35b..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "name": "websockets",
-  "version": "1.6.0",
-  "description": "Libwebsockets",
-  "keywords": [
-    "lws",
-    "libwebsockets",
-    "websockets",
-    "ws"
-  ],
-  "author": "Andy Green <andy@warmcat.com>",
-  "homepage": "https://libwebsockets.org",
-  "license": "LGPL2.1-SLE",
-  
-  "extraIncludes": [ "build/frdm-k64f-gcc/generated/include" ],
-  "dependencies": {
-    "mbed-drivers": "",
-    "sal-stack-lwip": "",
-    "sockets": ""
-  }
-}
index 36172d4..c33f683 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for "dumb increment"
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
@@ -24,7 +24,7 @@
 
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 
 #include <string.h>
 
@@ -138,7 +138,7 @@ init_protocol_example_standalone(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
diff --git a/plugins/acme-client/protocol_lws_acme_client.c b/plugins/acme-client/protocol_lws_acme_client.c
new file mode 100644 (file)
index 0000000..b25cce5
--- /dev/null
@@ -0,0 +1,1625 @@
+/*
+ * libwebsockets ACME client protocol plugin
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ *
+ *
+ *  Acme is in a big messy transition at the moment from a homebrewed api
+ *  to an IETF one.  The old repo for the homebrew api (they currently
+ *  implement) is marked up as deprecated and "not accurate[ly] reflect[ing]"
+ *  what they implement, but the IETF standard, currently at v7 is not yet
+ *  implemented at let's encrypt (ETA Jan 2018).
+ *
+ *  This implementation follows draft 7 of the IETF standard, and falls back
+ *  to whatever differences exist for Boulder's tls-sni-01 challenge.  The
+ *  tls-sni-02 support is there but nothing to test it against at the time of
+ *  writing (Nov 1 2017).
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+typedef enum {
+       ACME_STATE_DIRECTORY,    /* get the directory JSON using GET + parse */
+       ACME_STATE_NEW_REG,      /* register a new RSA key + email combo */
+       ACME_STATE_NEW_AUTH,     /* start the process to request a cert */
+       ACME_STATE_ACCEPT_CHALL, /* notify server ready for one challenge */
+       ACME_STATE_POLLING,      /* he should be trying our challenge */
+       ACME_STATE_POLLING_CSR,  /* sent CSR, checking result */
+
+       ACME_STATE_FINISHED
+} lws_acme_state;
+
+struct acme_connection {
+       char buf[4096];
+       char replay_nonce[64];
+       char chall_token[64];
+       char challenge_uri[256];
+       char detail[64];
+       char status[16];
+       char san_a[100];
+       char san_b[100];
+       char urls[6][100]; /* directory contents */
+       lws_acme_state state;
+       struct lws_client_connect_info i;
+       struct lejp_ctx jctx;
+       struct lws_context_creation_info ci;
+       struct lws_vhost *vhost;
+
+       struct lws *cwsi;
+
+       const char *real_vh_name;
+       const char *real_vh_iface;
+
+       char *alloc_privkey_pem;
+
+       char *dest;
+       int pos;
+       int len;
+       int resp;
+       int cpos;
+
+       int real_vh_port;
+       int goes_around;
+
+       size_t len_privkey_pem;
+
+       unsigned int yes:2;
+       unsigned int use:1;
+       unsigned int is_sni_02:1;
+};
+
+struct per_vhost_data__lws_acme_client {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+
+       /*
+        * the vhd is allocated for every vhost using the plugin.
+        * But ac is only allocated when we are doing the server auth.
+        */
+       struct acme_connection *ac;
+
+       struct lws_jwk jwk;
+       struct lws_genrsa_ctx rsactx;
+
+       char *pvo_data;
+       char *pvop[LWS_TLS_TOTAL_COUNT];
+       const char *pvop_active[LWS_TLS_TOTAL_COUNT];
+       int count_live_pss;
+       char *dest;
+       int pos;
+       int len;
+
+       int fd_updated_cert; /* these are opened while we have root... */
+       int fd_updated_key; /* ...if nonempty next startup will replace old */
+};
+
+static int
+callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
+                    void *user, void *in, size_t len);
+
+#define LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT \
+       { \
+               "lws-acme-client", \
+               callback_acme_client, \
+               0, \
+               512, \
+               0, NULL, 0 \
+       }
+
+static const struct lws_protocols acme_protocols[] = {
+       LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT,
+       { NULL, NULL, 0, 0, 0, NULL, 0 }
+};
+
+/* directory JSON parsing */
+
+static const char * const jdir_tok[] = {
+       "key-change",
+       "meta.terms-of-service",
+       "new-authz",
+       "new-cert",
+       "new-reg",
+       "revoke-cert",
+};
+enum enum_jhdr_tok {
+       JAD_KEY_CHANGE_URL,
+       JAD_TOS_URL,
+       JAD_NEW_AUTHZ_URL,
+       JAD_NEW_CERT_URL,
+       JAD_NEW_REG_URL,
+       JAD_REVOKE_CERT_URL,
+};
+static signed char
+cb_dir(struct lejp_ctx *ctx, char reason)
+{
+       struct per_vhost_data__lws_acme_client *s =
+                       (struct per_vhost_data__lws_acme_client *)ctx->user;
+
+       if (reason == LEJPCB_VAL_STR_START && ctx->path_match) {
+               s->pos = 0;
+               s->len = sizeof(s->ac->urls[0]) - 1;
+               s->dest = s->ac->urls[ctx->path_match - 1];
+
+               return 0;
+       }
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       if (s->pos + ctx->npos > s->len) {
+               lwsl_notice("url too long\n");
+
+               return -1;
+       }
+
+       memcpy(s->dest + s->pos, ctx->buf, ctx->npos);
+       s->pos += ctx->npos;
+       s->dest[s->pos] = '\0';
+
+       return 0;
+}
+
+/* authz JSON parsing */
+
+static const char * const jauthz_tok[] = {
+       "identifier.type",
+       "identifier.value",
+       "status",
+       "expires",
+       "challenges[].type",
+       "challenges[].status",
+       "challenges[].uri",
+       "challenges[].token",
+       "detail"
+};
+enum enum_jauthz_tok {
+       JAAZ_ID_TYPE,
+       JAAZ_ID_VALUE,
+       JAAZ_STATUS,
+       JAAZ_EXPIRES,
+       JAAZ_CHALLENGES_TYPE,
+       JAAZ_CHALLENGES_STATUS,
+       JAAZ_CHALLENGES_URI,
+       JAAZ_CHALLENGES_TOKEN,
+       JAAZ_DETAIL,
+};
+static signed char
+cb_authz(struct lejp_ctx *ctx, char reason)
+{
+       struct acme_connection *s = (struct acme_connection *)ctx->user;
+
+       if (reason == LEJPCB_CONSTRUCTED) {
+               s->yes = 0;
+               s->use = 0;
+               s->chall_token[0] = '\0';
+               s->is_sni_02 = 0;
+       }
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       switch (ctx->path_match - 1) {
+       case JAAZ_ID_TYPE:
+               break;
+       case JAAZ_ID_VALUE:
+               break;
+       case JAAZ_STATUS:
+               break;
+       case JAAZ_EXPIRES:
+               break;
+       case JAAZ_DETAIL:
+               lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf);
+               break;
+       case JAAZ_CHALLENGES_TYPE:
+               if (s->is_sni_02)
+                       break;
+               s->use = !strcmp(ctx->buf, "tls-sni-01") ||
+                        !strcmp(ctx->buf, "tls-sni-02");
+               s->is_sni_02 = !strcmp(ctx->buf, "tls-sni-02");
+               break;
+       case JAAZ_CHALLENGES_STATUS:
+               lws_strncpy(s->status, ctx->buf, sizeof(s->status));
+               break;
+       case JAAZ_CHALLENGES_URI:
+               if (s->use) {
+                       lws_strncpy(s->challenge_uri, ctx->buf,
+                               sizeof(s->challenge_uri));
+                       s->yes |= 2;
+               }
+               break;
+       case JAAZ_CHALLENGES_TOKEN:
+               lwsl_notice("JAAZ_CHALLENGES_TOKEN: %s %d\n", ctx->buf, s->use);
+               if (s->use) {
+                       lws_strncpy(s->chall_token, ctx->buf,
+                               sizeof(s->chall_token));
+                       s->yes |= 1;
+               }
+               break;
+       }
+
+       return 0;
+}
+
+/* challenge accepted JSON parsing */
+
+static const char * const jchac_tok[] = {
+       "type",
+       "status",
+       "uri",
+       "token",
+       "error.detail"
+};
+enum enum_jchac_tok {
+       JCAC_TYPE,
+       JCAC_STATUS,
+       JCAC_URI,
+       JCAC_TOKEN,
+       JCAC_DETAIL,
+};
+static signed char
+cb_chac(struct lejp_ctx *ctx, char reason)
+{
+       struct acme_connection *s = (struct acme_connection *)ctx->user;
+
+       if (reason == LEJPCB_CONSTRUCTED) {
+               s->yes = 0;
+               s->use = 0;
+       }
+
+       if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+               return 0;
+
+       switch (ctx->path_match - 1) {
+       case JCAC_TYPE:
+               if (strcmp(ctx->buf, "tls-sni-01") &&
+                   strcmp(ctx->buf, "tls-sni-02"))
+                       return 1;
+               break;
+       case JCAC_STATUS:
+               lws_strncpy(s->status, ctx->buf, sizeof(s->status));
+               break;
+       case JCAC_URI:
+               s->yes |= 2;
+               break;
+       case JCAC_TOKEN:
+               lws_strncpy(s->chall_token, ctx->buf,
+                               sizeof(s->chall_token));
+               s->yes |= 1;
+               break;
+       case JCAC_DETAIL:
+               lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf);
+               break;
+       }
+
+       return 0;
+}
+
+/* https://github.com/letsencrypt/boulder/blob/release/docs/acme-divergences.md
+ *
+ * 7.1:
+ *
+ * Boulder does not implement the new-order resource.
+ * Instead of new-order Boulder implements the new-cert resource that is
+ * defined in draft-ietf-acme-02 Section 6.5.
+ *
+ * Boulder also doesn't implement the new-nonce endpoint.
+ *
+ * Boulder implements the new-account resource only under the new-reg key.
+ *
+ * Boulder implements Link: rel="next" headers from new-reg to new-authz, and
+ * new-authz to new-cert, as specified in draft-02, but these links are not
+ * provided in the latest draft, and clients should use URLs from the directory
+ * instead.
+ *
+ * Boulder does not provide the "index" link relation pointing at the
+ * directory URL.
+ *
+ * (ie, just use new-cert instead of new-order, use the directory for links)
+ */
+
+static int
+lws_acme_report_status(struct lws_vhost *v, int state, const char *json)
+{
+       lws_callback_vhost_protocols_vhost(v, LWS_CALLBACK_VHOST_CERT_UPDATE,
+                                          (void *)json, state);
+
+       return 0;
+}
+
+/*
+ * Notice: trashes i and url
+ */
+static struct lws *
+lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh,
+                       struct lws **pwsi, struct lws_client_connect_info *i,
+                       char *url, const char *method)
+{
+       const char *prot, *p;
+       char path[200], _url[256];
+       struct lws *wsi;
+
+       memset(i, 0, sizeof(*i));
+       i->port = 443;
+       lws_strncpy(_url, url, sizeof(_url));
+       if (lws_parse_uri(_url, &prot, &i->address, &i->port, &p)) {
+               lwsl_err("unable to parse uri %s\n", url);
+
+               return NULL;
+       }
+
+       /* add back the leading / on path */
+       path[0] = '/';
+       lws_strncpy(path + 1, p, sizeof(path) - 1);
+       i->path = path;
+       i->context = context;
+       i->vhost = vh;
+       i->ssl_connection = 1;
+       i->host = i->address;
+       i->origin = i->address;
+       i->method = method;
+       i->pwsi = pwsi;
+       i->protocol = "lws-acme-client";
+
+       wsi = lws_client_connect_via_info(i);
+       if (!wsi) {
+               lws_snprintf(path, sizeof(path) - 1,
+                            "Unable to connect to %s", url);
+               lwsl_notice("%s: %s\n", __func__, path);
+               lws_acme_report_status(vh, LWS_CUS_FAILED, path);
+       }
+
+       return wsi;
+}
+
+static void
+lws_acme_finished(struct per_vhost_data__lws_acme_client *vhd)
+{
+       lwsl_debug("%s\n", __func__);
+
+       if (vhd->ac) {
+               if (vhd->ac->vhost)
+                       lws_vhost_destroy(vhd->ac->vhost);
+               if (vhd->ac->alloc_privkey_pem)
+                       free(vhd->ac->alloc_privkey_pem);
+               free(vhd->ac);
+       }
+
+       lws_genrsa_destroy(&vhd->rsactx);
+       lws_jwk_destroy(&vhd->jwk);
+
+       vhd->ac = NULL;
+#if defined(LWS_WITH_ESP32)
+       lws_esp32.acme = 0; /* enable scanning */
+#endif
+}
+
+static const char * const pvo_names[] = {
+       "country",
+       "state",
+       "locality",
+       "organization",
+       "common-name",
+       "email",
+       "directory-url",
+       "auth-path",
+       "cert-path",
+       "key-path",
+};
+
+static int
+lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client *vhd,
+                              int bits)
+{
+       int n;
+
+       if (!lws_jwk_load(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH],
+                         NULL, NULL))
+               return 0;
+
+       vhd->jwk.kty = LWS_GENCRYPTO_KTY_RSA;
+       lwsl_notice("Generating ACME %d-bit keypair... "
+                   "will take a little while\n", bits);
+       n = lws_genrsa_new_keypair(vhd->context, &vhd->rsactx, LGRSAM_PKCS1_1_5,
+                                  vhd->jwk.e, bits);
+       if (n) {
+               lwsl_notice("failed to create keypair\n");
+
+               return 1;
+       }
+
+       lwsl_notice("...keypair generated\n");
+
+       if (lws_jwk_save(&vhd->jwk,
+                   vhd->pvop[LWS_TLS_SET_AUTH_PATH])) {
+               lwsl_notice("unable to save %s\n",
+                     vhd->pvop[LWS_TLS_SET_AUTH_PATH]);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+static int
+lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd,
+                          struct lws_vhost *v)
+{
+       char buf[128];
+
+       /* ...and we were given enough info to do the update? */
+
+       if (!vhd->pvop[LWS_TLS_REQ_ELEMENT_COMMON_NAME])
+               return -1;
+
+       /*
+        * ...well... we should try to do something about it then...
+        */
+       lwsl_notice("%s: ACME cert needs creating / updating:  "
+                   "vhost %s\n", __func__, lws_get_vhost_name(vhd->vhost));
+
+       vhd->ac = malloc(sizeof(*vhd->ac));
+       memset(vhd->ac, 0, sizeof(*vhd->ac));
+
+       /*
+        * So if we don't have it, the first job is get the directory.
+        *
+        * If we already have the directory, jump straight into trying
+        * to register our key.
+        *
+        * We always try to register the keys... if it's not the first
+        * time, we will get a JSON body in the (legal, nonfatal)
+        * response like this
+        *
+        * {
+        *   "type": "urn:acme:error:malformed",
+        *   "detail": "Registration key is already in use",
+        *   "status": 409
+        * }
+        */
+       if (!vhd->ac->urls[0][0]) {
+               vhd->ac->state = ACME_STATE_DIRECTORY;
+               lws_snprintf(buf, sizeof(buf) - 1, "%s",
+                            vhd->pvop_active[LWS_TLS_SET_DIR_URL]);
+       } else {
+               vhd->ac->state = ACME_STATE_NEW_REG;
+               lws_snprintf(buf, sizeof(buf) - 1, "%s",
+                            vhd->ac->urls[JAD_NEW_REG_URL]);
+       }
+
+       vhd->ac->real_vh_port = lws_get_vhost_port(vhd->vhost);
+       vhd->ac->real_vh_name = lws_get_vhost_name(vhd->vhost);
+       vhd->ac->real_vh_iface = lws_get_vhost_iface(vhd->vhost);
+
+       lws_acme_report_status(vhd->vhost, LWS_CUS_STARTING, NULL);
+
+#if defined(LWS_WITH_ESP32)
+       lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS,
+                              "Generating keys, please wait");
+       if (lws_acme_load_create_auth_keys(vhd, 2048))
+               goto bail;
+       lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS,
+                              "Auth keys created");
+#endif
+
+       if (lws_acme_client_connect(vhd->context, vhd->vhost,
+                                   &vhd->ac->cwsi, &vhd->ac->i, buf, "GET"))
+               return 0;
+
+#if defined(LWS_WITH_ESP32)
+bail:
+#endif
+       free(vhd->ac);
+       vhd->ac = NULL;
+
+       return 1;
+}
+
+static int
+callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
+                    void *user, void *in, size_t len)
+{
+       struct per_vhost_data__lws_acme_client *vhd =
+                       (struct per_vhost_data__lws_acme_client *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start,
+            *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL;
+       const struct lws_protocol_vhost_options *pvo;
+       struct lws_acme_cert_aging_args *caa;
+       struct acme_connection *ac = NULL;
+       struct lws_genhash_ctx hctx;
+       unsigned char **pp, *pend;
+       const char *content_type;
+       struct lws_jwe jwe;
+       struct lws *cwsi;
+       int n, m;
+
+       if (vhd)
+               ac = vhd->ac;
+
+       lws_jwe_init(&jwe, lws_get_context(wsi));
+
+       switch ((int)reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__lws_acme_client));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               /* compute how much we need to hold all the pvo payloads */
+               m = 0;
+               pvo = (const struct lws_protocol_vhost_options *)in;
+               while (pvo) {
+                       m += strlen(pvo->value) + 1;
+                       pvo = pvo->next;
+               }
+               p = vhd->pvo_data = malloc(m);
+               if (!p)
+                       return -1;
+
+               pvo = (const struct lws_protocol_vhost_options *)in;
+               while (pvo) {
+                       start = p;
+                       n = strlen(pvo->value) + 1;
+                       memcpy(start, pvo->value, n);
+                       p += n;
+
+                       for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++)
+                               if (!strcmp(pvo->name, pvo_names[m]))
+                                       vhd->pvop[m] = start;
+
+                       pvo = pvo->next;
+               }
+
+               n = 0;
+               for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++)
+                       if (!vhd->pvop[m] && m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME) {
+                               lwsl_notice("%s: require pvo '%s'\n", __func__,
+                                               pvo_names[m]);
+                               n |= 1;
+                       } else
+                               if (vhd->pvop[m])
+                                       lwsl_info("  %s: %s\n", pvo_names[m],
+                                                       vhd->pvop[m]);
+               if (n) {
+                       free(vhd->pvo_data);
+                       vhd->pvo_data = NULL;
+
+                       return -1;
+               }
+
+#if !defined(LWS_WITH_ESP32)
+               /*
+                * load (or create) the registration keypair while we
+                * still have root
+                */
+               if (lws_acme_load_create_auth_keys(vhd, 4096))
+                       return 1;
+
+               /*
+                * in case we do an update, open the update files while we
+                * still have root
+                */
+               lws_snprintf(buf, sizeof(buf) - 1, "%s.upd",
+                            vhd->pvop[LWS_TLS_SET_CERT_PATH]);
+               vhd->fd_updated_cert = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT |
+                                                LWS_O_TRUNC, 0600);
+               if (vhd->fd_updated_cert < 0) {
+                       lwsl_err("unable to create update cert file %s\n", buf);
+                       return -1;
+               }
+               lws_snprintf(buf, sizeof(buf) - 1, "%s.upd",
+                            vhd->pvop[LWS_TLS_SET_KEY_PATH]);
+               vhd->fd_updated_key = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT |
+                                               LWS_O_TRUNC, 0600);
+               if (vhd->fd_updated_key < 0) {
+                       lwsl_err("unable to create update key file %s\n", buf);
+                       return -1;
+               }
+#endif
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               if (vhd && vhd->pvo_data) {
+                       free(vhd->pvo_data);
+                       vhd->pvo_data = NULL;
+               }
+               if (vhd)
+                       lws_acme_finished(vhd);
+               break;
+
+       case LWS_CALLBACK_VHOST_CERT_AGING:
+               if (!vhd)
+                       break;
+
+               caa = (struct lws_acme_cert_aging_args *)in;
+               /*
+                * Somebody is telling us about a cert some vhost is using.
+                *
+                * First see if the cert is getting close enough to expiry that
+                * we *want* to do something about it.
+                */
+               if ((int)(ssize_t)len > 14)
+                       break;
+
+               /*
+                * ...is this a vhost we were configured on?
+                */
+               if (vhd->vhost != caa->vh)
+                       return 1;
+
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pvop);n++)
+                       if (caa->element_overrides[n])
+                               vhd->pvop_active[n] = caa->element_overrides[n];
+                       else
+                               vhd->pvop_active[n] = vhd->pvop[n];
+
+               lwsl_notice("starting acme acquisition on %s: %s\n",
+                               lws_get_vhost_name(caa->vh), vhd->pvop_active[LWS_TLS_SET_DIR_URL]);
+
+               lws_acme_start_acquisition(vhd, caa->vh);
+               break;
+
+       /*
+        * Client
+        */
+
+       case LWS_CALLBACK_CLIENT_ESTABLISHED:
+               lwsl_notice("%s: CLIENT_ESTABLISHED\n", __func__);
+               break;
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_notice("%s: CLIENT_CONNECTION_ERROR: %p\n", __func__, wsi);
+               break;
+
+       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+               lwsl_notice("%s: CLOSED_CLIENT_HTTP: %p\n", __func__, wsi);
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               lwsl_notice("%s: CLOSED: %p\n", __func__, wsi);
+               break;
+
+       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+               lwsl_notice("lws_http_client_http_response %d\n",
+                           lws_http_client_http_response(wsi));
+               if (!ac)
+                       break;
+               ac->resp = lws_http_client_http_response(wsi);
+               /* we get a new nonce each time */
+               if (lws_hdr_total_length(wsi, WSI_TOKEN_REPLAY_NONCE) &&
+                   lws_hdr_copy(wsi, ac->replay_nonce,
+                                sizeof(ac->replay_nonce),
+                                WSI_TOKEN_REPLAY_NONCE) < 0) {
+                       lwsl_notice("%s: nonce too large\n", __func__);
+
+                       goto failed;
+               }
+
+               switch (ac->state) {
+               case ACME_STATE_DIRECTORY:
+                       lejp_construct(&ac->jctx, cb_dir, vhd, jdir_tok,
+                                      LWS_ARRAY_SIZE(jdir_tok));
+                       break;
+               case ACME_STATE_NEW_REG:
+                       break;
+               case ACME_STATE_NEW_AUTH:
+                       lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok,
+                                       LWS_ARRAY_SIZE(jauthz_tok));
+                       break;
+
+               case ACME_STATE_POLLING:
+               case ACME_STATE_ACCEPT_CHALL:
+                       lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok,
+                                       LWS_ARRAY_SIZE(jchac_tok));
+                       break;
+
+               case ACME_STATE_POLLING_CSR:
+                       ac->cpos = 0;
+                       if (ac->resp != 201)
+                               break;
+                       /*
+                        * He acknowledges he will create the cert...
+                        * get the URL to GET it from in the Location
+                        * header.
+                        */
+                       if (lws_hdr_copy(wsi, ac->challenge_uri,
+                                        sizeof(ac->challenge_uri),
+                                        WSI_TOKEN_HTTP_LOCATION) < 0) {
+                               lwsl_notice("%s: missing cert location:\n",
+                                           __func__);
+
+                               goto failed;
+                       }
+
+                       lwsl_notice("told to fetch cert from %s\n",
+                                       ac->challenge_uri);
+                       break;
+
+               default:
+                       break;
+               }
+               break;
+
+       case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+               if (!ac)
+                       break;
+               switch (ac->state) {
+
+               case ACME_STATE_DIRECTORY:
+                       break;
+               case ACME_STATE_NEW_REG:
+                       p += lws_snprintf(p, end - p, "{"
+                                         "\"resource\":\"new-reg\","
+                                         "\"contact\":["
+                                         "\"mailto:%s\""
+                                         "],\"agreement\":\"%s\""
+                                         "}",
+                                         vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL],
+                                         ac->urls[JAD_TOS_URL]);
+
+                       puts(start);
+pkt_add_hdrs:
+                       if (lws_gencrypto_jwe_alg_to_definition("RSA1_5", &jwe.jose.alg)) {
+                               ac->len = 0;
+                               lwsl_notice("%s: no RSA1_5\n", __func__);
+                               goto failed;
+                       }
+                       jwe.jws.jwk = &vhd->jwk;
+                       ac->len = lws_jwe_create_packet(&jwe,
+                                                       start, p - start,
+                                                       ac->replay_nonce,
+                                                       &ac->buf[LWS_PRE],
+                                                       sizeof(ac->buf) -
+                                                                LWS_PRE,
+                                                       lws_get_context(wsi));
+                       if (ac->len < 0) {
+                               ac->len = 0;
+                               lwsl_notice("lws_jwe_create_packet failed\n");
+                               goto failed;
+                       }
+
+                       pp = (unsigned char **)in;
+                       pend = (*pp) + len;
+
+                       ac->pos = 0;
+                       content_type =         "application/jose+json";
+                       if (ac->state == ACME_STATE_POLLING_CSR)
+                               content_type = "application/pkix-cert";
+
+                       if (lws_add_http_header_by_token(wsi,
+                                   WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                       (uint8_t *)content_type, 21, pp, pend)) {
+                               lwsl_notice("could not add content type\n");
+                               goto failed;
+                       }
+
+                       n = sprintf(buf, "%d", ac->len);
+                       if (lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_CONTENT_LENGTH,
+                                       (uint8_t *)buf, n, pp, pend)) {
+                               lwsl_notice("could not add content length\n");
+                               goto failed;
+                       }
+
+                       lws_client_http_body_pending(wsi, 1);
+                       lws_callback_on_writable(wsi);
+                       lwsl_notice("prepare to send ACME_STATE_NEW_REG\n");
+                       break;
+               case ACME_STATE_NEW_AUTH:
+                       p += lws_snprintf(p, end - p,
+                                       "{"
+                                        "\"resource\":\"new-authz\","
+                                        "\"identifier\":{"
+                                         "\"type\":\"http-01\","
+                                         "\"value\":\"%s\""
+                                        "}"
+                                       "}", vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]);
+                       goto pkt_add_hdrs;
+
+               case ACME_STATE_ACCEPT_CHALL:
+                       /*
+                        * Several of the challenges in this document makes use
+                        * of a key authorization string.  A key authorization
+                        * expresses a domain holder's authorization for a
+                        * specified key to satisfy a specified challenge, by
+                        * concatenating the token for the challenge with a key
+                        * fingerprint, separated by a "." character:
+                        *
+                        * key-authz = token || '.' ||
+                        *             base64(JWK_Thumbprint(accountKey))
+                        *
+                        * The "JWK_Thumbprint" step indicates the computation
+                        * specified in [RFC7638], using the SHA-256 digest.  As
+                        * specified in the individual challenges below, the
+                        * token for a challenge is a JSON string comprised
+                        * entirely of characters in the base64 alphabet.
+                        * The "||" operator indicates concatenation of strings.
+                        *
+                        *    keyAuthorization (required, string):  The key
+                        *  authorization for this challenge.  This value MUST
+                        *  match the token from the challenge and the client's
+                        *  account key.
+                        *
+                        * draft acme-01 tls-sni-01:
+                        *
+                        *    {
+                        *         "keyAuthorization": "evaGxfADs...62jcerQ",
+                        *    }   (Signed as JWS)
+                        *
+                        * draft acme-07 tls-sni-02:
+                        *
+                        * POST /acme/authz/1234/1
+                        * Host: example.com
+                        * Content-Type: application/jose+json
+                        *
+                        * {
+                        *  "protected": base64url({
+                        *    "alg": "ES256",
+                        *    "kid": "https://example.com/acme/acct/1",
+                        *    "nonce": "JHb54aT_KTXBWQOzGYkt9A",
+                        *    "url": "https://example.com/acme/authz/1234/1"
+                        *  }),
+                        *  "payload": base64url({
+                        *     "keyAuthorization": "evaGxfADs...62jcerQ"
+                        *  }),
+                        * "signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4"
+                        * }
+                        *
+                        * On receiving a response, the server MUST verify that
+                        * the key authorization in the response matches the
+                        * "token" value in the challenge and the client's
+                        * account key.  If they do not match, then the server
+                        * MUST return an HTTP error in response to the POST
+                        * request in which the client sent the challenge.
+                        */
+
+                       lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest);
+                       p = start;
+                       end = &buf[sizeof(buf) - 1];
+
+                       p += lws_snprintf(p, end - p,
+                                         "{\"resource\":\"challenge\","
+                                         "\"type\":\"tls-sni-0%d\","
+                                         "\"keyAuthorization\":\"%s.",
+                                         1 + ac->is_sni_02,
+                                         ac->chall_token);
+                       n = lws_jws_base64_enc(digest, 32, p, end - p);
+                       if (n < 0)
+                               goto failed;
+                       p += n;
+                       p += lws_snprintf(p, end - p, "\"}");
+                       puts(start);
+                       goto pkt_add_hdrs;
+
+               case ACME_STATE_POLLING:
+                       break;
+
+               case ACME_STATE_POLLING_CSR:
+                       /*
+                        * "To obtain a certificate for the domain, the agent
+                        * constructs a PKCS#10 Certificate Signing Request that
+                        * asks the Let’s Encrypt CA to issue a certificate for
+                        * example.com with a specified public key. As usual,
+                        * the CSR includes a signature by the private key
+                        * corresponding to the public key in the CSR. The agent
+                        * also signs the whole CSR with the authorized
+                        * key for example.com so that the Let’s Encrypt CA
+                        * knows it’s authorized."
+                        *
+                        * IOW we must create a new RSA keypair which will be
+                        * the cert public + private key, and put the public
+                        * key in the CSR.  The CSR, just for transport, is also
+                        * signed with our JWK, showing that as the owner of the
+                        * authorized JWK, the request should be allowed.
+                        *
+                        * The cert comes back with our public key in it showing
+                        * that the owner of the matching private key (we
+                        * created that keypair) is the owner of the cert.
+                        *
+                        * We feed the CSR the elements we want in the cert,
+                        * like the CN etc, and it gives us the b64URL-encoded
+                        * CSR and the PEM-encoded (public +)private key in
+                        * memory buffers.
+                        */
+                       if (ac->goes_around)
+                               break;
+
+                       p += lws_snprintf(p, end - p,
+                                         "{\"resource\":\"new-cert\","
+                                         "\"csr\":\"");
+                       n = lws_tls_acme_sni_csr_create(vhd->context,
+                                                       &vhd->pvop_active[0],
+                                                       (uint8_t *)p, end - p,
+                                                       &ac->alloc_privkey_pem,
+                                                       &ac->len_privkey_pem);
+                       if (n < 0) {
+                               lwsl_notice("CSR generation failed\n");
+                               goto failed;
+                       }
+                       p += n;
+                       p += lws_snprintf(p, end - p, "\"}");
+                       puts(start);
+                       goto pkt_add_hdrs;
+
+               default:
+                       break;
+               }
+               break;
+
+       case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
+               lwsl_notice("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n");
+               if (!ac)
+                       break;
+               if (ac->pos == ac->len)
+                       break;
+
+               ac->buf[LWS_PRE + ac->len] = '\0';
+               if (lws_write(wsi, (uint8_t *)ac->buf + LWS_PRE,
+                             ac->len, LWS_WRITE_HTTP_FINAL) < 0)
+                       return -1;
+               lwsl_notice("wrote %d\n", ac->len);
+               ac->pos = ac->len;
+               lws_client_http_body_pending(wsi, 0);
+               break;
+
+       /* chunked content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+               if (!ac)
+                       return -1;
+               switch (ac->state) {
+               case ACME_STATE_POLLING:
+               case ACME_STATE_ACCEPT_CHALL:
+               case ACME_STATE_NEW_AUTH:
+               case ACME_STATE_DIRECTORY:
+                       ((char *)in)[len] = '\0';
+                       puts(in);
+                       m = (int)(signed char)lejp_parse(&ac->jctx,
+                                                        (uint8_t *)in, len);
+                       if (m < 0 && m != LEJP_CONTINUE) {
+                               lwsl_notice("lejp parse failed %d\n", m);
+                               goto failed;
+                       }
+                       break;
+               case ACME_STATE_NEW_REG:
+                       ((char *)in)[len] = '\0';
+                       puts(in);
+                       break;
+               case ACME_STATE_POLLING_CSR:
+                       /* it should be the DER cert! */
+                       if (ac->cpos + len > sizeof(ac->buf)) {
+                               lwsl_notice("Incoming cert is too large!\n");
+                               goto failed;
+                       }
+                       memcpy(&ac->buf[ac->cpos], in, len);
+                       ac->cpos += len;
+                       break;
+               default:
+                       break;
+               }
+               break;
+
+       /* unchunked content */
+       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+               lwsl_notice("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n", __func__);
+               {
+                       char buffer[2048 + LWS_PRE];
+                       char *px = buffer + LWS_PRE;
+                       int lenx = sizeof(buffer) - LWS_PRE;
+
+                       if (lws_http_client_read(wsi, &px, &lenx) < 0)
+                               return -1;
+               }
+               break;
+
+       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+               lwsl_notice("%s: COMPLETED_CLIENT_HTTP\n", __func__);
+
+               if (!ac)
+                       return -1;
+               switch (ac->state) {
+               case ACME_STATE_DIRECTORY:
+                       lejp_destruct(&ac->jctx);
+
+                       /* check dir validity */
+
+                       for (n = 0; n < 6; n++)
+                               lwsl_notice("   %d: %s\n", n, ac->urls[n]);
+
+                       /*
+                        * So... having the directory now... we try to
+                        * register our keys next.  It's OK if it ends up
+                        * they're already registered... this eliminates any
+                        * gaps where we stored the key but registration did
+                        * not complete for some reason...
+                        */
+                       ac->state = ACME_STATE_NEW_REG;
+                       lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL);
+
+                       strcpy(buf, ac->urls[JAD_NEW_REG_URL]);
+                       cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
+                                                      &ac->cwsi, &ac->i, buf,
+                                                      "POST");
+                       if (!cwsi) {
+                               lwsl_notice("%s: failed to connect to acme\n",
+                                           __func__);
+                               goto failed;
+                       }
+                       return -1; /* close the completed client connection */
+
+               case ACME_STATE_NEW_REG:
+                       if ((ac->resp >= 200 && ac->resp < 299) ||
+                            ac->resp == 409) {
+                               /*
+                                * Our account already existed, or exists now.
+                                *
+                                * Move on to requesting a cert auth.
+                                */
+                               ac->state = ACME_STATE_NEW_AUTH;
+                               lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH,
+                                                       NULL);
+
+                               strcpy(buf, ac->urls[JAD_NEW_AUTHZ_URL]);
+                               cwsi = lws_acme_client_connect(vhd->context,
+                                                       vhd->vhost, &ac->cwsi,
+                                                       &ac->i, buf, "POST");
+                               if (!cwsi)
+                                       lwsl_notice("%s: failed to connect\n",
+                                                   __func__);
+                               return -1; /* close the completed client connection */
+                       } else {
+                               lwsl_notice("new-reg replied %d\n", ac->resp);
+                               goto failed;
+                       }
+                       return -1; /* close the completed client connection */
+
+               case ACME_STATE_NEW_AUTH:
+                       lejp_destruct(&ac->jctx);
+                       if (ac->resp / 100 == 4) {
+                               lws_snprintf(buf, sizeof(buf),
+                                            "Auth failed: %s", ac->detail);
+                               failreason = buf;
+                               lwsl_notice("auth failed\n");
+                               goto failed;
+                       }
+                       lwsl_notice("chall: %s (%d)\n", ac->chall_token, ac->resp);
+                       if (!ac->chall_token[0]) {
+                               lwsl_notice("no challenge\n");
+                               goto failed;
+                       }
+
+
+                       ac->state = ACME_STATE_ACCEPT_CHALL;
+                       lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE,
+                                               NULL);
+
+                       /* tls-sni-01 ... what a mess.
+                        * The stuff in
+                        * https://tools.ietf.org/html/
+                        *              draft-ietf-acme-acme-01#section-7.3
+                        * "requires" n but it's missing from let's encrypt
+                        * tls-sni-01 challenge.  The go docs say that they just
+                        * implement one hashing round regardless
+                        * https://godoc.org/golang.org/x/crypto/acme
+                        *
+                        * The go way is what is actually implemented today by
+                        * letsencrypt
+                        *
+                        * "A client responds to this challenge by constructing
+                        * a key authorization from the "token" value provided
+                        * in the challenge and the client's account key.  The
+                        * client first computes the SHA-256 digest Z0 of the
+                        * UTF8-encoded key authorization, and encodes Z0 in
+                        * UTF-8 lower-case hexadecimal form."
+                        */
+
+                       /* tls-sni-02
+                        *
+                        * SAN A MUST be constructed as follows: compute the
+                        * SHA-256 digest of the UTF-8-encoded challenge token
+                        * and encode it in lowercase hexadecimal form.  The
+                        * dNSName is "x.y.token.acme.invalid", where x
+                        * is the first half of the hexadecimal representation
+                        * and y is the second half.
+                        */
+
+                       memset(&ac->ci, 0, sizeof(ac->ci));
+
+                       /* first compute the key authorization */
+
+                       lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest);
+                       p = start;
+                       end = &buf[sizeof(buf) - 1];
+
+                       p += lws_snprintf(p, end - p, "%s.", ac->chall_token);
+                       n = lws_jws_base64_enc(digest, 32, p, end - p);
+                       if (n < 0)
+                               goto failed;
+                       p += n;
+
+                       if (lws_genhash_init(&hctx, LWS_GENHASH_TYPE_SHA256))
+                               return -1;
+
+                       if (lws_genhash_update(&hctx, (uint8_t *)start,
+                                               lws_ptr_diff(p, start))) {
+                               lws_genhash_destroy(&hctx, NULL);
+
+                               return -1;
+                       }
+                       if (lws_genhash_destroy(&hctx, digest))
+                               return -1;
+
+                       p = buf;
+                       for (n = 0; n < 32; n++) {
+                               p += lws_snprintf(p, end - p, "%02x",
+                                                 digest[n] & 0xff);
+                               if (n == (32 / 2) - 1)
+                                       p = buf + 64;
+                       }
+
+                       p = ac->san_a;
+                       if (ac->is_sni_02) {
+                               lws_snprintf(p, sizeof(ac->san_a),
+                                            "%s.%s.token.acme.invalid",
+                                            buf, buf + 64);
+
+                               /*
+                                * SAN B MUST be constructed as follows: compute
+                                * the SHA-256 digest of the UTF-8 encoded key
+                                * authorization and encode it in lowercase
+                                * hexadecimal form.  The dNSName is
+                                * "x.y.ka.acme.invalid" where x is the first
+                                * half of the hexadecimal representation and y
+                                * is the second half.
+                                */
+                               lws_jwk_rfc7638_fingerprint(&vhd->jwk,
+                                                           (char *)digest);
+
+                               p = buf;
+                               for (n = 0; n < 32; n++) {
+                                       p += lws_snprintf(p, end - p, "%02x",
+                                                         digest[n] & 0xff);
+                                       if (n == (32 / 2) - 1)
+                                               p = buf + 64;
+                               }
+
+                               p = ac->san_b;
+                               lws_snprintf(p, sizeof(ac->san_b),
+                                            "%s.%s.ka.acme.invalid",
+                                            buf, buf + 64);
+                       } else {
+                               lws_snprintf(p, sizeof(ac->san_a),
+                                    "%s.%s.acme.invalid", buf, buf + 64);
+                               ac->san_b[0] = '\0';
+                       }
+
+                       lwsl_notice("san_a: '%s'\n", ac->san_a);
+                       lwsl_notice("san_b: '%s'\n", ac->san_b);
+
+                       /*
+                        * tls-sni-01:
+                        *
+                        * The client then configures the TLS server at the
+                        * domain such that when a handshake is initiated with
+                        * the Server Name Indication extension set to
+                        * "<Zi[0:32]>.<Zi[32:64]>.acme.invalid", the
+                        * corresponding generated certificate is presented.
+                        *
+                        * tls-sni-02:
+                        *
+                        *  The client MUST ensure that the certificate is
+                        *  served to TLS connections specifying a Server Name
+                        *  Indication (SNI) value of SAN A.
+                        */
+                       ac->ci.vhost_name = ac->san_a;
+
+                       /*
+                        * we bind to exact iface of real vhost, so we can
+                        * share the listen socket by SNI
+                        */
+                       ac->ci.iface = ac->real_vh_iface;
+
+                       /* listen on the same port as the vhost that triggered
+                        * us */
+                       ac->ci.port = ac->real_vh_port;
+                       /* Skip filling in any x509 info into the ssl_ctx.
+                        * It will be done at the callback
+                        * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS
+                        * in this callback handler (below)
+                        */
+                       ac->ci.options = LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX |
+                                        LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT |
+                                        LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+                       /* make ourselves protocols[0] for the new vhost */
+                       ac->ci.protocols = acme_protocols;
+                       /*
+                        * vhost .user points to the ac associated with the
+                        * temporary vhost
+                        */
+                       ac->ci.user = ac;
+
+                       ac->vhost = lws_create_vhost(lws_get_context(wsi),
+                                                    &ac->ci);
+                       if (!ac->vhost)
+                               goto failed;
+
+                       /*
+                        * The challenge-specific vhost is up... let the ACME
+                        * server know we are ready to roll...
+                        */
+
+                       ac->goes_around = 0;
+                       cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
+                                                      &ac->cwsi, &ac->i,
+                                                      ac->challenge_uri,
+                                                      "POST");
+                       if (!cwsi) {
+                               lwsl_notice("%s: failed to connect\n",
+                                           __func__);
+                               goto failed;
+                       }
+                       return -1; /* close the completed client connection */
+
+               case ACME_STATE_ACCEPT_CHALL:
+                       /*
+                        * he returned something like this (which we parsed)
+                        *
+                        * {
+                        *   "type": "tls-sni-01",
+                        *   "status": "pending",
+                        *   "uri": "https://acme-staging.api.letsencrypt.org/
+                        *              acme/challenge/xCt7bT3FaxoIQU3Qry87t5h
+                        *              uKDcC-L-0ERcD5DLAZts/71100507",
+                        *   "token": "j2Vs-vLI_dsza4A35SFHIU03aIe2PzFRijbqCY
+                        *              dIVeE",
+                        *   "keyAuthorization": "j2Vs-vLI_dsza4A35SFHIU03aIe2
+                        *              PzFRijbqCYdIVeE.nmOtdFd8Jikn6K8NnYYmT5
+                        *              vCM_PwSDT8nLdOYoFXhRU"
+                        * }
+                        *
+                        */
+                       lwsl_notice("%s: COMPLETED accept chall: %s\n",
+                                       __func__, ac->challenge_uri);
+poll_again:
+                       ac->state = ACME_STATE_POLLING;
+                       lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, NULL);
+
+                       if (ac->goes_around++ == 20) {
+                               lwsl_notice("%s: too many chall retries\n",
+                                           __func__);
+
+                               goto failed;
+                       }
+
+                       lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
+                                       LWS_CALLBACK_USER + 0xac33, ac->goes_around == 1 ? 10 : 2);
+                       return -1; /* close the completed client connection */
+
+               case ACME_STATE_POLLING:
+
+                       if (ac->resp == 202 &&
+                           strcmp(ac->status, "invalid") &&
+                           strcmp(ac->status, "valid")) {
+                               lwsl_notice("status: %s\n", ac->status);
+                               goto poll_again;
+                       }
+
+                       if (!strcmp(ac->status, "invalid")) {
+                               lwsl_notice("%s: polling failed\n", __func__);
+                               lws_snprintf(buf, sizeof(buf),
+                                            "Challenge Invalid: %s", ac->detail);
+                               failreason = buf;
+                               goto failed;
+                       }
+
+                       lwsl_notice("Challenge passed\n");
+
+                       /*
+                        * The challenge was validated... so delete the
+                        * temp SNI vhost now its job is done
+                        */
+                       if (ac->vhost)
+                               lws_vhost_destroy(ac->vhost);
+                       ac->vhost = NULL;
+
+                       /*
+                        * now our JWK is accepted as authorized to make
+                        * requests for the domain, next move is create the
+                        * CSR signed with the JWK, and send it to the ACME
+                        * server to request the actual certs.
+                        */
+                       ac->state = ACME_STATE_POLLING_CSR;
+                       lws_acme_report_status(vhd->vhost, LWS_CUS_REQ, NULL);
+                       ac->goes_around = 0;
+
+                       strcpy(buf, ac->urls[JAD_NEW_CERT_URL]);
+                       cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
+                                                      &ac->cwsi, &ac->i, buf,
+                                                      "POST");
+                       if (!cwsi) {
+                               lwsl_notice("%s: failed to connect to acme\n",
+                                           __func__);
+
+                               goto failed;
+                       }
+                       return -1; /* close the completed client connection */
+
+               case ACME_STATE_POLLING_CSR:
+                       /*
+                        * (after POSTing the CSR)...
+                        *
+                        * If the CA decides to issue a certificate, then the
+                        * server creates a new certificate resource and
+                        * returns a URI for it in the Location header field
+                        * of a 201 (Created) response.
+                        *
+                        * HTTP/1.1 201 Created
+                        * Location: https://example.com/acme/cert/asdf
+                        *
+                        * If the certificate is available at the time of the
+                        * response, it is provided in the body of the response.
+                        * If the CA has not yet issued the certificate, the
+                        * body of this response will be empty.  The client
+                        * should then send a GET request to the certificate URI
+                        * to poll for the certificate.  As long as the
+                        * certificate is unavailable, the server MUST provide a
+                        * 202 (Accepted) response and include a Retry-After
+                        * header to indicate when the server believes the
+                        * certificate will be issued.
+                        */
+                       if (ac->resp < 200 || ac->resp > 202) {
+                               lwsl_notice("CSR poll failed on resp %d\n",
+                                           ac->resp);
+                               goto failed;
+                       }
+
+                       if (ac->resp == 200) {
+                               char *pp;
+                               int max;
+
+                               lwsl_notice("The cert was sent..\n");
+
+                               lws_acme_report_status(vhd->vhost,
+                                               LWS_CUS_ISSUE, NULL);
+
+                               /*
+                                * That means we have the issued cert DER in
+                                * ac->buf, length in ac->cpos; and the key in
+                                * ac->alloc_privkey_pem, length in
+                                * ac->len_privkey_pem.
+                                *
+                                * We write out a PEM copy of the cert, and a
+                                * PEM copy of the private key, using the
+                                * write-only fds we opened while we still
+                                * had root.
+                                *
+                                * Estimate the size of the PEM version of the
+                                * cert and allocate a temp buffer for it.
+                                *
+                                * This is a bit complicated because first we
+                                * drop the b64url version into the buffer at
+                                * +384, then we add the header at 0 and move
+                                * lines of it back + '\n' to make PEM.
+                                *
+                                * This avoids the need for two fullsize
+                                * allocations.
+                                */
+
+                               max = (ac->cpos * 4) / 3 + 16 + 384;
+
+                               start = p = malloc(max);
+                               if (!p)
+                                       goto failed;
+
+                               n = lws_b64_encode_string(ac->buf, ac->cpos,
+                                                         start + 384, max - 384);
+                               if (n < 0) {
+                                       free(start);
+                                       goto failed;
+                               }
+
+                               pp = start + 384;
+                               p += lws_snprintf(start, 64, "%s",
+                                               "-----BEGIN CERTIFICATE-----\n");
+
+                               while (n) {
+                                       m = 65;
+                                       if (n < m)
+                                               m = n;
+                                       memcpy(p, pp, m);
+                                       n -= m;
+                                       p += m;
+                                       pp += m;
+                                       if (n)
+                                               *p++ = '\n';
+                               }
+                               p += lws_snprintf(p,
+                                                 max - lws_ptr_diff(p, start),
+                                                 "%s",
+                                                 "\n-----END CERTIFICATE-----\n");
+
+                               n = lws_plat_write_cert(vhd->vhost, 0,
+                                               vhd->fd_updated_cert, start,
+                                               lws_ptr_diff(p, start));
+                               free(start);
+                               if (n) {
+                                       lwsl_err("unable to write ACME cert! %d\n", n);
+                                       goto failed;
+                               }
+                               /*
+                                * don't close it... we may update the certs
+                                * again
+                                */
+
+                               if (lws_plat_write_cert(vhd->vhost, 1,
+                                                       vhd->fd_updated_key,
+                                                       ac->alloc_privkey_pem,
+                                                       ac->len_privkey_pem)) {
+                                       lwsl_err("unable to write ACME key!\n");
+                                       goto failed;
+                               }
+
+                               /*
+                                * we have written the persistent copies
+                                */
+
+                               lwsl_notice("%s: Updated certs written for %s "
+                                           "to %s.upd and %s.upd\n", __func__,
+                                           vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME],
+                                           vhd->pvop_active[LWS_TLS_SET_CERT_PATH],
+                                           vhd->pvop_active[LWS_TLS_SET_KEY_PATH]);
+
+                               /* notify lws there was a cert update */
+
+                               if (lws_tls_cert_updated(vhd->context,
+                                       vhd->pvop_active[LWS_TLS_SET_CERT_PATH],
+                                       vhd->pvop_active[LWS_TLS_SET_KEY_PATH],
+                                       ac->buf, ac->cpos,
+                                       ac->alloc_privkey_pem,
+                                       ac->len_privkey_pem)) {
+                                       lwsl_notice("problem setting certs\n");
+                               }
+
+                               lws_acme_finished(vhd);
+                               lws_acme_report_status(vhd->vhost,
+                                                       LWS_CUS_SUCCESS, NULL);
+
+                               return 0;
+                       }
+
+                       lws_acme_report_status(vhd->vhost, LWS_CUS_CONFIRM, NULL);
+
+                       /* he is preparing the cert, go again with a GET */
+
+                       if (ac->goes_around++ == 30) {
+                               lwsl_notice("%s: too many retries\n",
+                                           __func__);
+
+                               goto failed;
+                       }
+
+                       strcpy(buf, ac->challenge_uri);
+                       cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
+                                                      &ac->cwsi, &ac->i, buf,
+                                                      "GET");
+                       if (!cwsi) {
+                               lwsl_notice("%s: failed to connect to acme\n",
+                                           __func__);
+
+                               goto failed;
+                       }
+                       return -1; /* close the completed client connection */
+
+               default:
+                       break;
+               }
+               break;
+
+               case LWS_CALLBACK_USER + 0xac33:
+                       if (!vhd)
+                               break;
+                       cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
+                                                      &ac->cwsi, &ac->i,
+                                                      ac->challenge_uri,
+                                                      "GET");
+                       if (!cwsi) {
+                               lwsl_notice("%s: failed to connect\n", __func__);
+                               goto failed;
+                       }
+                       break;
+
+       case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
+               /*
+                * This goes to vhost->protocols[0], but for our temp certs
+                * vhost we created, we have arranged that to be our protocol,
+                * so the callback will come here.
+                *
+                * When we created the temp vhost, we set its pvo to point
+                * to the ac associated with the temp vhost.
+                */
+               lwsl_debug("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS\n");
+               ac = (struct acme_connection *)lws_get_vhost_user(
+                                                       (struct lws_vhost *)in);
+
+               lws_acme_report_status((struct lws_vhost *)in,
+                                       LWS_CUS_CREATE_REQ,
+                                       "creating challenge cert");
+
+               if (lws_tls_acme_sni_cert_create((struct lws_vhost *)in,
+                                                ac->san_a, ac->san_b)) {
+                       lwsl_err("%s: creating the sni test cert failed\n", __func__);
+
+                       return -1;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+
+failed:
+       lwsl_err("%s: failed out\n", __func__);
+       lws_acme_report_status(vhd->vhost, LWS_CUS_FAILED, failreason);
+       lws_acme_finished(vhd);
+
+       return -1;
+}
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_lws_acme_client(struct lws_context *context,
+                             struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_lws_acme_client(struct lws_context *context)
+{
+       return 0;
+}
+
+#endif
diff --git a/plugins/deaddrop/README.md b/plugins/deaddrop/README.md
new file mode 100644 (file)
index 0000000..8b113ad
--- /dev/null
@@ -0,0 +1,91 @@
+# Deaddrop: File upload and sharing plugin
+
+## Building the plugin
+
+Just configure lws with `cmake .. -DLWS_WITH_PLUGINS=1` and build lws as normal.
+
+## Configurable settings
+
+|pvo name|value meaning|
+|---|---|
+|upload-dir|A writeable directory where uploaded files will go|
+|max-size|Maximum individual file size in bytes|
+|basic-auth|Path to basic auth credential file so wss can also be protected|
+
+## Required mounts
+
+To use deaddrop meaningfully, all the mounts and the ws protocol must be
+protected by basic auth.  And to use basic auth securely, the connection must
+be protected from snooping by tls.
+
+1) Set the basic-auth pvo to require valid credentials as described above
+
+2) Protect your basic fileserving mount by the same basic auth file... this is
+   used to serve index.html, the css etc.
+
+3) Add a callback mount into "lws-deaddrop" protocol at "upload"... so if your
+   URL for deaddrop is "/tools/share", this would be at "/tools/share/upload".
+   It must also be protected by the basic auth file.
+
+4) Add a fileserving mount at the url "get" (continuing the example above, it
+   would be "/tools/share/get" whose origin matches the "upload-dir" pvo
+   value you selected.  This mount needs any additional mimtype mappings since
+   it's where the uploaded files are shared from.
+
+## Using with C
+
+See ./minimal-examples/http-server/minimal-example-http-server-deaddrop for
+how to use the plugin directly with C.
+
+## Using with lwsws / lejp-conf
+
+As a plugin, you can configure the mounts and pvos per-vhost easily in JSON.
+
+All the snippets here 
+
+The mountpoints would look something like this (added to vhost/mounts)
+
+```
+       {
+         "mountpoint": "/tools/share",
+         "origin": "file:///var/www/deaddrop",
+         "default": "index.html",
+         "basic-auth": "/var/www/ba"
+        }, {
+         "mountpoint": "/tools/share/upload",
+         "origin": "callback://lws-deaddrop",
+         "basic-auth": "/var/www/ba"
+        }, {
+         "mountpoint": "/tools/share/get",
+         "origin": "file:///var/cache/deaddrop-uploads",
+         "basic-auth": "/var/www/ba",
+
+        "extra-mimetypes": {
+               ".bin": "application/octet-stream",
+               ".ttf": "application/x-font-truetype",
+               ".otf": "application/font-sfnt",
+               ".zip": "application/zip",
+               ".webm": "video/webm",
+               ".romfs": "application/octet-stream",
+               ".pdf": "application/pdf",
+               ".odt": "application/vnd.oasis.opendocument.text",
+               ".tgz": "application/x-gzip",
+               ".tar.gz": "application/x-gzip"
+         }
+       }
+```
+
+This enables the plugin on the vhost, configures the pvos, and makes
+the wss serving also depend on having a valid basic auth credential.
+
+```
+         "ws-protocols": [{
+                  "lws-deaddrop": {
+                  "status": "ok",
+                  "upload-dir": "/var/cache/deaddrop-uploads",
+                  "max-size": "52428800",
+                  "basic-auth": "/var/www/ba"
+                }
+          }],
+```
+
diff --git a/plugins/deaddrop/assets/deaddrop.css b/plugins/deaddrop/assets/deaddrop.css
new file mode 100644 (file)
index 0000000..549e362
--- /dev/null
@@ -0,0 +1,70 @@
+.td { padding: 8px }
+.h1 { }
+.dd-fileinfo { font-size: 8pt; }
+table td { 
+  display: table-cell;
+  vertical-align: top;
+  background-color: rgba(247, 247, 232, 0.6);
+  text-align: center
+}
+table {
+       border: 2px solid #ccc;
+       padding: 4px;
+       border-radius: 12px;
+       transition: background-color 0.5s ease;
+}
+table.nb { border: 0px; border-radius: 0px; transition: opacity 0.5s; }
+table.noconn { background-color: #ddd;  }
+
+div { transition: opacity 0.5s; }
+div.da { padding-left: 20px; padding-right:20px; }
+div.trot {
+       animation: scale 0.5s linear infinite;
+}
+div.uplbox { padding-bottom: 8px; }
+div.disa { opacity: 0.2; }
+
+td.ogn { text-align:left; font-size: 8pt; padding-left: 4px; padding-right: 4px;}
+td.dow { text-align:left; font-size: 9pt; padding-left: 4px; padding-right: 4px;}
+td.r { text-align: right; }
+td.err { color: red; font-weight: bold; }
+td.vm { display: table-cell; vertical-align: middle; padding-top: 12px; padding-bottom: 12px; }
+
+h3 { font-size: 12pt; margin-bottom: 6px; }
+span { font-size: 9pt; }
+a { font-size: 9pt; }
+
+input.ubtn { font-size: 16pt; margin-top: 4px; text-align: center }
+
+img.working {
+       display: inline-block;
+       float:left;
+       background: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKICAgICB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzMHB4IiB2aWV3Qm94PSIwIDAgMjQgMzAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUwIDUwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICA8cmVjdCB4PSIwIiB5PSIxMCIgd2lkdGg9IjQiIGhlaWdodD0iMTAiIGZpbGw9IiMzMzMiIG9wYWNpdHk9IjAuMiI+CiAgICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9Im9wYWNpdHkiIGF0dHJpYnV0ZVR5cGU9IlhNTCIgdmFsdWVzPSIwLjI7IDE7IC4yIiBiZWdpbj0iMHMiIGR1cj0iMC42cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImhlaWdodCIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjEwOyAyMDsgMTAiIGJlZ2luPSIwcyIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ieSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjEwOyA1OyAxMCIgYmVnaW49IjBzIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0iOCIgeT0iMTAiIHdpZHRoPSI0IiBoZWlnaHQ9IjEwIiBmaWxsPSIjMzMzIiAgb3BhY2l0eT0iMC4yIj4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ib3BhY2l0eSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjAuMjsgMTsgLjIiIGJlZ2luPSIwLjE1cyIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iaGVpZ2h0IiBhdHRyaWJ1dGVUeXBlPSJYTUwiIHZhbHVlcz0iMTA7IDIwOyAxMCIgYmVnaW49IjAuMTVzIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJ5IiBhdHRyaWJ1dGVUeXBlPSJYTUwiIHZhbHVlcz0iMTA7IDU7IDEwIiBiZWdpbj0iMC4xNXMiIGR1cj0iMC42cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICA8L3JlY3Q+CiAgICA8cmVjdCB4PSIxNiIgeT0iMTAiIHdpZHRoPSI0IiBoZWlnaHQ9IjEwIiBmaWxsPSIjMzMzIiAgb3BhY2l0eT0iMC4yIj4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ib3BhY2l0eSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjAuMjsgMTsgLjIiIGJlZ2luPSIwLjNzIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJoZWlnaHQiIGF0dHJpYnV0ZVR5cGU9IlhNTCIgdmFsdWVzPSIxMDsgMjA7IDEwIiBiZWdpbj0iMC4zcyIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ieSIgYXR0cmlidXRlVHlwZT0iWE1MIiB2YWx1ZXM9IjEwOyA1OyAxMCIgYmVnaW49IjAuM3MiIGR1cj0iMC42cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICA8L3JlY3Q+Cjwvc3ZnPg==");
+       width:0px;
+       height:0px;
+       cursor:pointer;
+       padding:0.6em 1em;
+       background-repeat: no-repeat;
+       vertical-align:middle;
+       color: rgba(0, 0, 0, 0);
+}
+
+img.delbtn {
+       display: inline-block;
+       float:left;
+       background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNC4yMTcxMzMgMzQuMTQ0MjQ5IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KCTxkZWZzPgoJCTxyYWRpYWxHcmFkaWVudCBpZD0iYSIgY3g9IjEzNTguNSIgY3k9Ijk3Ny43MiIgcj0iNC4xMTYiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMS41MDM1IDAgMCAyLjQ5NDQgLTI1NzcuNCAtMTgyMy44KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjZmZmIiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIiBvZmZzZXQ9IjEiLz4KCQk8L3JhZGlhbEdyYWRpZW50PgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjEzNjYuMyIgeDI9IjEzNDQuOCIgeTE9Ijk3OC4xOSIgeTI9Ijk1OC43MyIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMjE3NCAtMTA3Ni4xKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgoJCQk8c3RvcCBzdG9wLWNvbG9yPSIjOWM5NzlkIiBvZmZzZXQ9IjAiLz4KCQkJPHN0b3Agc3RvcC1jb2xvcj0iI2Y0ZmVmMyIgc3RvcC1vcGFjaXR5PSIuNzQwMTYiIG9mZnNldD0iMSIvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4MzQuMjkgMTIzLjc1KSI+CgkJPGcgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4KCQkJPGNpcmNsZSBjeD0iLTgxNy4yMiIgY3k9Ii0xMDYuNjgiIHI9IjE2LjA5IiBmaWxsPSJ1cmwoI2IpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMS45NjUiLz4KCQkJPHBhdGggZD0ibS04MjUuNjctMTE0LjcxYzE3LjMzIDE3LjMzIDE3IDE2Ljk5OSAxNyAxNi45OTkiIGZpbGw9IiNmNTUiIHN0cm9rZT0iI2EwMCIgc3Ryb2tlLXdpZHRoPSIxLjY2NSIvPgoJCQk8cGF0aCBkPSJtLTgwOC45Ni0xMTQuNjFjLTE3LjMzIDE3LjMzLTE3IDE2Ljk5OS0xNyAxNi45OTkiIGZpbGw9IiNmNTUiIHN0cm9rZT0iI2EwMCIgc3Ryb2tlLXdpZHRoPSIxLjY2NSIvPgoJCTwvZz4KCQk8ZWxsaXBzZSB0cmFuc2Zvcm09InJvdGF0ZSg1NS44ODUpIiBjeD0iLTUzNC45NiIgY3k9IjYxNS4wNyIgcng9IjYuMTg4MyIgcnk9IjEwLjI2NyIgZmlsbD0idXJsKCNhKSIvPgoJPC9nPgo8L3N2Zz4=");
+       width:0px;
+       height:0px;
+       cursor:pointer;
+       padding:0.45em;
+       background-repeat: no-repeat;
+       vertical-align:middle;
+       color: rgba(0, 0, 0, 0);
+}
+
+@keyframes scale {
+  50% {
+    opacity: 0.5;
+    transform:scale(1.1) rotate(2deg);
+  }
+}
diff --git a/plugins/deaddrop/assets/deaddrop.js b/plugins/deaddrop/assets/deaddrop.js
new file mode 100644 (file)
index 0000000..ebb6e12
--- /dev/null
@@ -0,0 +1,300 @@
+(function() {
+
+       var server_max_size = 0, ws;
+
+       function san(s)
+       {
+               if (!s)
+                       return "";
+
+               return s.replace(/&/g, "&amp;").
+               replace(/\</g, "&lt;").
+               replace(/\>/g, "&gt;").
+               replace(/\"/g, "&quot;").
+               replace(/%/g, "&#37;");
+       }
+
+       function lws_urlencode(s)
+       {
+               return encodeURI(s).replace(/@/g, "%40");
+       }
+
+       function trim(num)
+       {
+               var s = num.toString();
+
+               if (!s.indexOf("."))
+                       return s;
+
+               while (s.length && s[s.length - 1] === "0")
+                       s = s.substring(0, s.length - 1);
+
+               if (s[s.length - 1] === ".")
+                       s = s.substring(0, s.length - 1);
+
+               return s;
+       }
+
+       function humanize(n)
+       {
+               if (n < 1024)
+                       return n + "B";
+
+               if (n < 1024 * 1024)
+                       return trim((n / 1024).toFixed(2)) + "KiB";
+
+               if (n < 1024 * 1024 * 1024)
+                       return trim((n / (1024 * 1024)).toFixed(2)) + "MiB";
+
+               return trim((n / (1024 * 1024 * 1024)).toFixed(2)) + "GiB";
+       }
+
+       function da_enter(e)
+       {
+               var da = document.getElementById("da");
+
+               e.preventDefault();
+               da.classList.add("trot");
+       }
+
+       function da_leave(e)
+       {
+               var da = document.getElementById("da");
+
+               e.preventDefault();     
+               da.classList.remove("trot");
+       }
+
+       function da_over(e)
+       {
+               var da = document.getElementById("da");
+
+               e.preventDefault();             
+               da.classList.add("trot");
+       }
+
+       function clear_errors() {
+               var t = document.getElementById("ongoing");
+
+               for (n = 0; n < t.rows.length; n++)
+                       if (t.rows[n].cells[0].classList.contains("err"))
+                               t.deleteRow(n);
+       }
+
+       function do_upload(file) {
+               var formData = new FormData();
+               var t = document.getElementById("ongoing");
+
+               formData.append("file", file);
+
+               var row = t.insertRow(0), c1 = row.insertCell(0),
+               c2 = row.insertCell(1), c3 = row.insertCell(2);
+
+               c1.classList.add("ogn");
+               c1.classList.add("r");
+
+               if (file.size > server_max_size) {
+                       c1.innerHTML = "Too Large";
+                       c1.classList.add("err");
+               } else
+                       c1.innerHTML = "<img class=\"working\">";
+
+               c2.classList.add("ogn");
+               c2.classList.add("r");
+               c2.innerHTML = humanize(file.size);
+
+               c3.classList.add("ogn");
+               c3.innerHTML = file.name;
+
+               if (file.size > server_max_size)
+                       return;
+
+               fetch("upload/" + lws_urlencode(file.name), {
+                       method: "POST",
+                       body: formData
+               })
+               .then((e) => { /* this just means we got a response code */                       
+                       var us = e.url.split("/"), ul = us[us.length - 1], n;
+
+                       for (n = 0; n < t.rows.length; n++)
+                               if (ul === lws_urlencode(
+                                             t.rows[n].cells[2].textContent)) {
+                                       if (e.ok === true) {
+                                               t.deleteRow(n);
+                                       } else {
+                                               t.rows[n].cells[0].textContent =
+                                       "Failed " + san(e.status.toString());
+                                               t.rows[n].cells[0].
+                                                       classList.add("err");
+                                       }
+                                       break;
+                               }
+               })
+               .catch((e) => {
+                       var us = e.url.split("/"), ul = us[us.length - 1], n;
+
+                       for (n = 0; n < t.rows.length; n++)
+                               if (ul === lws_urlencode(
+                                         t.rows[n].cells[2].textContent)) {
+                                       t.rows[n].cells[0] = "FAIL";
+                                       break;
+                               }
+               });
+       }
+
+       function da_drop(e) {
+               var da = document.getElementById("da");
+
+               e.preventDefault();             
+               da.classList.remove("trot");
+
+               clear_errors();
+
+               ([...e.dataTransfer.files]).forEach(do_upload);
+       }
+
+       function upl_button(e) {
+               var fi = document.getElementById("file"),
+               da = document.getElementById("da");
+
+               clear_errors();
+               e.preventDefault();
+
+               ([...fi.files]).forEach(do_upload);
+       }
+
+       function body_drop(e) {
+               e.preventDefault();
+       }
+
+       function inp() {
+               var fi = document.getElementById("file"),
+               upl = document.getElementById("upl");
+               console.log("inp");
+               upl.disabled = !fi.files.length;
+       }
+
+       function delfile(e)
+       {
+               e.stopPropagation();
+               e.preventDefault();
+
+               ws.send("{\"del\":\"" + decodeURI(e.target.getAttribute("file")) +
+               "\"}");
+       }
+
+       function get_appropriate_ws_url(extra_url)
+       {
+               var pcol;
+               var u = document.URL;
+
+               /*
+                * We open the websocket encrypted if this page came on an
+                * https:// url itself, otherwise unencrypted
+                */
+
+               if (u.substring(0, 5) === "https") {
+                       pcol = "wss://";
+                       u = u.substr(8);
+               } else {
+                       pcol = "ws://";
+                       if (u.substring(0, 4) === "http")
+                               u = u.substr(7);
+               }
+
+               u = u.split("/");
+
+               /* + "/xxx" bit is for IE10 workaround */
+
+               return pcol + u[0] + "/" + extra_url;
+       }
+
+       function new_ws(urlpath, protocol)
+       {
+               if (typeof MozWebSocket != "undefined")
+                       return new MozWebSocket(urlpath, protocol);
+
+               return new WebSocket(urlpath, protocol);
+       }
+
+       document.addEventListener("DOMContentLoaded", function() {
+               var da = document.getElementById("da"),
+               fi = document.getElementById("file"),
+               upl = document.getElementById("upl");
+
+               da.addEventListener("dragenter", da_enter, false);
+               da.addEventListener("dragleave", da_leave, false);
+               da.addEventListener("dragover", da_over, false);
+               da.addEventListener("drop", da_drop, false);
+
+               upl.addEventListener("click", upl_button, false);               
+               fi.addEventListener("change", inp, false);
+
+               window.addEventListener("dragover", body_drop, false);
+               window.addEventListener("drop", body_drop, false);
+
+               ws = new_ws(get_appropriate_ws_url(""), "lws-deaddrop");
+               try {
+                       ws.onopen = function() {
+                               var dd = document.getElementById("ddrop"),
+                               da = document.getElementById("da");
+
+                               dd.classList.remove("noconn");
+                               da.classList.remove("disa");
+                       };
+
+                       ws.onmessage = function got_packet(msg) {
+                               var j = JSON.parse(msg.data), s = "", n,
+                               t = document.getElementById("dd-list");
+
+                               server_max_size = j.max_size;
+                               document.getElementById("size").innerHTML =
+                                       "Server maximum file size " +
+                                       humanize(j.max_size);
+
+                               s += "<table class=\"nb\">";
+                               for (n = 0; n < j.files.length; n++) {
+                                       var date = new Date(j.files[n].mtime * 1000);
+                                       s += "<tr><td class=\"dow r\">" +
+                                       humanize(j.files[n].size) +
+                                       "</td><td class=\"dow\">" +
+                                       date.toDateString() + " " +
+                                       date.toLocaleTimeString() +
+                                       "</td><td>";
+                                       if (j.files[n].yours === 1)
+                                               s += "<img id=\"d" + n +
+                                         "\" class=\"delbtn\" file=\"" +
+                                               lws_urlencode(san(j.files[n].name)) + "\">";
+                                       else
+                                               s += " ";
+
+                                       s += "</td><td class=\"ogn\"><a href=\"get/" +
+                                       lws_urlencode(san(j.files[n].name)) +
+                                         "\" download>" +
+                                       san(j.files[n].name) + "</a></td></tr>";
+                               }
+                               s += "</table>";
+
+                               t.innerHTML = s;
+
+                               for (n = 0; n < j.files.length; n++) {
+                                       var d = document.getElementById("d" + n);
+                                       if (d)
+                                               d.addEventListener("click",
+                                                               delfile, false);
+                               }
+                       };
+
+                       ws.onclose = function() {
+                               var dd = document.getElementById("ddrop"),
+                               da = document.getElementById("da");
+
+                               dd.classList.add("noconn");
+                               da.classList.add("disa");
+                       };
+               } catch(exception) {
+                       alert("<p>Error " + exception);
+               }
+
+       });
+}());
diff --git a/plugins/deaddrop/assets/drop.svg b/plugins/deaddrop/assets/drop.svg
new file mode 100644 (file)
index 0000000..f413cf0
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="49.443mm" height="49.443mm" version="1.1" viewBox="0 0 49.442779 49.442779" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+       <defs>
+               <filter id="d" x="-.088487" y="-.064772" width="1.177" height="1.1295" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.15643472"/>
+               </filter>
+               <filter id="e" x="-.088487" y="-.064772" width="1.177" height="1.1295" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.15643472"/>
+               </filter>
+               <linearGradient id="c" x1="208.34" x2="352.29" y1="32.317" y2="159.6" gradientTransform="matrix(.26458 0 0 .26458 -586.44 -188.57)" gradientUnits="userSpaceOnUse">
+                       <stop stop-opacity=".49606" offset="0"/>
+                       <stop stop-color="#fff6d5" stop-opacity="0" offset="1"/>
+               </linearGradient>
+               <linearGradient id="b" x1="365.17" x2="216.93" y1="163.64" y2="36.61" gradientTransform="matrix(.26458 0 0 .26458 -586.5 -188.43)" gradientUnits="userSpaceOnUse">
+                       <stop stop-color="#fff" offset="0"/>
+                       <stop stop-color="#fff" stop-opacity=".007874" offset="1"/>
+               </linearGradient>
+               <linearGradient id="a" x1="-529.78" x2="-494.03" y1="-175.41" y2="-147.94" gradientUnits="userSpaceOnUse">
+                       <stop stop-color="#c1dc5f" stop-opacity=".61417" offset="0"/>
+                       <stop stop-color="#a9aa52" stop-opacity=".8937" offset="1"/>
+               </linearGradient>
+       </defs>
+       <g transform="translate(536.12 186.96)">
+               <circle cx="-511.4" cy="-162.24" r="24.457" fill="url(#a)"/>
+               <g transform="matrix(.78726 0 0 .78726 -108.47 -33.661)">
+                       <g stroke="#000">
+                               <path transform="matrix(2.9413 0 0 2.9413 -4498.4 -3189.2)" d="m1354 1027.2v5.7964h4.2429v-4.5102l-1.211-1.211-0.075-0.075z" filter="url(#e)" stroke-width=".26458px"/>
+                               <path transform="matrix(2.9413 0 0 2.9413 -4498.4 -3189.2)" d="m1353.2 1025.7v5.7964h4.2429v-4.5101l-1.2111-1.2111-0.075-0.075z" filter="url(#d)" stroke-width=".26458px"/>
+                               <path d="m-516.24-168.48v17.049h12.479v-13.266l-3.5622-3.5619-0.22056-0.22058z" fill="#ececec" stroke-width=".77821px"/>
+                       </g>
+                       <g fill="none" stroke="#4d4d4d" stroke-width=".77821px">
+                               <path d="m-513.59-164.94h7.2225"/>
+                               <path d="m-513.56-162.83h7.2222"/>
+                               <path d="m-513.57-160.59h7.2222"/>
+                               <path d="m-513.55-158.48h7.2225"/>
+                               <path d="m-513.57-156.27h7.2222"/>
+                               <path d="m-513.55-154.16h7.2225"/>
+                       </g>
+                       <path d="m-518.5-172.78v17.048h12.479v-13.265l-3.5622-3.5622-0.22057-0.22059z" fill="#fff" stroke="#000" stroke-width=".77821px"/>
+                       <g fill="none" stroke="#000" stroke-width=".77821px">
+                               <path d="m-515.99-169.38h7.2225"/>
+                               <path d="m-515.82-167.13h7.2222"/>
+                               <path d="m-515.83-164.89h7.2223"/>
+                               <path d="m-515.81-162.78h7.2225"/>
+                               <path d="m-515.83-160.57h7.2223"/>
+                               <path d="m-515.81-158.46h7.2225"/>
+                       </g>
+               </g>
+               <g>
+                       <g>
+                               <g dominant-baseline="auto" fill="#540" stroke-width=".023836" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="to upload">
+                                       <path d="m-518.24-173.3q0.0983 0 0.1743-0.0268v0.17877q-0.0983 0.0447-0.25251 0.0447-0.37541 0-0.37541-0.56088v-1.7206h-0.21899v-0.12514l0.21452-0.0626 0.0693-0.57653h0.14749v0.57653h0.38435v0.18771h-0.38435v1.6603q0 0.24581 0.0536 0.33519 0.0536 0.0894 0.1877 0.0894z"/>
+                                       <path d="m-516.32-174.37q0 0.61228-0.19441 0.93854-0.19218 0.32401-0.55195 0.32401-0.3553 0-0.54525-0.32401-0.1877-0.32626-0.1877-0.93854 0-1.2536 0.74189-1.2536 0.3486 0 0.54301 0.32848 0.19441 0.32849 0.19441 0.92513zm-1.2581 0q0 0.52513 0.12514 0.79329 0.12514 0.26815 0.39106 0.26815 0.52066 0 0.52066-1.0614 0-1.0525-0.52066-1.0525-0.27262 0-0.39553 0.26368-0.12067 0.26369-0.12067 0.78882z"/>
+                                       <path d="m-514.72-175.57v1.5821q0 0.35978 0.0849 0.52514 0.0872 0.16536 0.26592 0.16536 0.27486 0 0.40223-0.22123 0.12961-0.22346 0.12961-0.71954v-1.3318h0.21005v2.4246h-0.17877l-0.0268-0.33966h-0.0179q-0.0782 0.18547-0.21676 0.28603-0.13854 0.0983-0.30167 0.0983-0.29273 0-0.42904-0.20781-0.13408-0.20782-0.13408-0.67933v-1.5821z"/>
+                                       <path d="m-512.19-173.11q-0.18323 0-0.33519-0.10055-0.14972-0.10279-0.22793-0.27933h-0.0201l0.0156 0.26592v1.1687h-0.21228v-3.524h0.1743l0.0223 0.33296h0.0201q0.0894-0.18324 0.22793-0.28156 0.14078-0.0983 0.30614-0.0983 0.71061 0 0.71061 1.2536 0 0.60558-0.1743 0.93407-0.1743 0.32848-0.50726 0.32848zm-0.0425-2.315q-0.26592 0-0.39553 0.23463-0.12961 0.23464-0.12961 0.73519v0.0693q0 0.55866 0.13185 0.82234 0.13184 0.26145 0.40223 0.26145 0.25027 0 0.37094-0.25922 0.1229-0.26145 0.1229-0.81116 0-0.52513-0.11396-0.78882-0.11397-0.26368-0.38882-0.26368z"/>
+                                       <path d="m-510.77-173.15h-0.21228v-3.477h0.21228z"/>
+                                       <path d="m-508.76-174.37q0 0.61228-0.19441 0.93854-0.19217 0.32401-0.55195 0.32401-0.3553 0-0.54524-0.32401-0.18771-0.32626-0.18771-0.93854 0-1.2536 0.74189-1.2536 0.3486 0 0.54301 0.32848 0.19441 0.32849 0.19441 0.92513zm-1.2581 0q0 0.52513 0.12514 0.79329 0.12513 0.26815 0.39105 0.26815 0.52067 0 0.52067-1.0614 0-1.0525-0.52067-1.0525-0.27262 0-0.39552 0.26368-0.12067 0.26369-0.12067 0.78882z"/>
+                                       <path d="m-507.28-173.15-0.0268-0.33966h-9e-3q-0.1743 0.38435-0.52737 0.38435-0.23687 0-0.38212-0.1877-0.14302-0.18994-0.14302-0.50726 0-0.34636 0.20782-0.54748 0.20782-0.20335 0.58324-0.22122l0.26145-0.0134v-0.20112q0-0.33966-0.0849-0.49385-0.0849-0.15642-0.28156-0.15642-0.20782 0-0.42458 0.13631l-0.0916-0.16759q0.25252-0.15642 0.52961-0.15642 0.29943 0 0.43128 0.18994 0.13184 0.1877 0.13184 0.63016v1.6514zm-0.5162-0.13631q0.22793 0 0.35307-0.22346 0.12738-0.2257 0.12738-0.63686v-0.25251l-0.25252 0.0134q-0.29273 0.0156-0.43574 0.16313-0.14078 0.14525-0.14078 0.42681 0 0.26368 0.0938 0.38659 0.0939 0.1229 0.25474 0.1229z"/>
+                                       <path d="m-505.87-173.11q-0.70614 0-0.70614-1.2536 0-0.61675 0.17654-0.93854 0.17653-0.32401 0.52066-0.32401 0.16313 0 0.30838 0.0939 0.14748 0.0938 0.23463 0.25921h0.0179l-9e-3 -0.27038v-1.0883h0.21229v3.477h-0.1743l-0.0179-0.33966h-0.0201q-0.0872 0.18547-0.2257 0.28603-0.13854 0.0983-0.31731 0.0983zm0.0134-0.18547q0.25474 0 0.39105-0.23463 0.13855-0.23687 0.13855-0.69497v-0.13854q0-0.54971-0.13408-0.80446-0.13184-0.25698-0.40446-0.25698-0.26145 0-0.37542 0.27486-0.11396 0.27262-0.11396 0.79105 0 0.5229 0.11843 0.79328 0.11844 0.27039 0.37989 0.27039z"/>
+                               </g>
+                               <g dominant-baseline="auto" fill="#540" stroke-width=".023836" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="Drop files here">
+                                       <path d="m-520.62-179.04q0 1.6648-1.0391 1.6648h-0.581v-3.267h0.59441q0.50502 0 0.76424 0.40894 0.26144 0.40669 0.26144 1.1933zm-0.23016 0.0134q0-0.69049-0.20335-1.0525-0.20335-0.362-0.60111-0.362h-0.36648v2.8648h0.3486q0.42458 0 0.62346-0.37094 0.19888-0.37095 0.19888-1.0793z"/>
+                                       <path d="m-519.37-179.85q0.11397 0 0.21229 0.0313l-0.0514 0.21228q-0.0804-0.0335-0.16536-0.0335-0.1229 0-0.23016 0.12737-0.10503 0.12514-0.16536 0.35083-0.0603 0.2257-0.0603 0.49832v1.2849h-0.21229v-2.4246h0.1743l0.0224 0.42457h0.0156q0.16983-0.4715 0.46033-0.4715z"/>
+                                       <path d="m-517.43-178.6q0 0.61228-0.19441 0.93853-0.19218 0.32402-0.55195 0.32402-0.3553 0-0.54524-0.32402-0.18771-0.32625-0.18771-0.93853 0-1.2536 0.74189-1.2536 0.3486 0 0.54301 0.32849t0.19441 0.92513zm-1.2581 0q0 0.52513 0.12513 0.79328 0.12514 0.26816 0.39106 0.26816 0.52066 0 0.52066-1.0614 0-1.0525-0.52066-1.0525-0.27262 0-0.39552 0.26368-0.12067 0.26368-0.12067 0.78882z"/>
+                                       <path d="m-516.11-177.33q-0.18324 0-0.33519-0.10056-0.14972-0.10279-0.22793-0.27932h-0.0201l0.0156 0.26592v1.1687h-0.21229v-3.524h0.1743l0.0224 0.33296h0.0201q0.0894-0.18324 0.22793-0.28156 0.14078-0.0983 0.30614-0.0983 0.71061 0 0.71061 1.2536 0 0.60558-0.1743 0.93406-0.1743 0.32849-0.50726 0.32849zm-0.0425-2.315q-0.26592 0-0.39552 0.23463-0.12961 0.23464-0.12961 0.73519v0.0693q0 0.55865 0.13184 0.82234 0.13184 0.26145 0.40223 0.26145 0.25028 0 0.37095-0.25922 0.1229-0.26145 0.1229-0.81116 0-0.52514-0.11397-0.78882-0.11396-0.26368-0.38882-0.26368z"/>
+                                       <path d="m-513.42-179.61h-0.37988v2.2368h-0.21006v-2.2368h-0.30837v-0.12067l0.30837-0.0916v-0.18324q0-0.46256 0.1095-0.66591 0.10949-0.20335 0.37988-0.20335 0.15642 0 0.2838 0.0559l-0.0737 0.19665q-0.11843-0.0559-0.21452-0.0559-0.10949 0-0.16536 0.0648-0.0559 0.0648-0.0827 0.21005t-0.0268 0.40223v0.20335h0.37988zm0.60558 2.2368h-0.21229v-2.4246h0.21229zm-0.24357-3.0972q0-0.10056 0.0402-0.15642 0.0425-0.0559 0.10726-0.0559 0.0603 0 0.0961 0.0559t0.0358 0.15642q0 0.0961-0.0358 0.15419-0.0358 0.0559-0.0961 0.0559-0.0648 0-0.10726-0.0559-0.0402-0.0581-0.0402-0.15419z"/>
+                                       <path d="m-511.95-177.38h-0.21229v-3.477h0.21229z"/>
+                                       <path d="m-510.61-177.33q-0.38435 0-0.59217-0.32625-0.20558-0.32849-0.20558-0.91396 0-0.62122 0.18323-0.94747 0.18548-0.32849 0.53184-0.32849 0.30167 0 0.47597 0.28827 0.1743 0.28603 0.1743 0.77317v0.19665h-1.1486q4e-3 0.5296 0.14972 0.79328 0.14525 0.26369 0.44022 0.26369 0.22793 0 0.48044-0.14972v0.20558q-0.2324 0.14525-0.48938 0.14525zm-0.0961-2.324q-0.43798 0-0.48044 0.87373h0.93184q0-0.39999-0.12291-0.63686-0.12067-0.23687-0.32849-0.23687z"/>
+                                       <path d="m-508.55-177.99q0 0.30614-0.16089 0.48044-0.16089 0.17206-0.4648 0.17206-0.16536 0-0.2905-0.0425t-0.19441-0.0939v-0.24804q0.0827 0.0827 0.21899 0.13408 0.13631 0.0492 0.27933 0.0492 0.18771 0 0.29497-0.12291 0.10726-0.1229 0.10726-0.32848 0-0.1609-0.0782-0.27039-0.076-0.11173-0.29274-0.25251-0.24804-0.15642-0.33966-0.25028-0.0894-0.0938-0.14078-0.21005-0.0492-0.1162-0.0492-0.27709 0-0.26145 0.17653-0.43128 0.17654-0.17207 0.44916-0.17207 0.29273 0 0.48938 0.13855l-0.1095 0.18547q-0.18323-0.1229-0.38882-0.1229-0.1877 0-0.29943 0.11173-0.11174 0.10949-0.11174 0.2905 0 0.16089 0.076 0.27038 0.076 0.10727 0.32179 0.26145 0.24133 0.15866 0.33072 0.25475 0.0894 0.0939 0.13184 0.21005 0.0447 0.11397 0.0447 0.26369z"/>
+                                       <path d="m-506.11-177.38v-1.6715q0-0.32849-0.0872-0.4648-0.0849-0.13854-0.27932-0.13854-0.25698 0-0.38659 0.22793t-0.12961 0.71954v1.3274h-0.21228v-3.477h0.21228v1.1285q0 0.15418-9e-3 0.25698h0.0179q0.0715-0.18101 0.21452-0.27933 0.14525-0.10056 0.30168-0.10056 0.30837 0 0.43798 0.20112 0.12961 0.20111 0.12961 0.59887v1.6715z"/>
+                                       <path d="m-504.57-177.33q-0.38435 0-0.59217-0.32625-0.20558-0.32849-0.20558-0.91396 0-0.62122 0.18324-0.94747 0.18547-0.32849 0.53183-0.32849 0.30168 0 0.47597 0.28827 0.1743 0.28603 0.1743 0.77317v0.19665h-1.1486q4e-3 0.5296 0.14971 0.79328 0.14525 0.26369 0.44022 0.26369 0.22793 0 0.48044-0.14972v0.20558q-0.2324 0.14525-0.48938 0.14525zm-0.0961-2.324q-0.43799 0-0.48045 0.87373h0.93184q0-0.39999-0.12291-0.63686-0.12067-0.23687-0.32848-0.23687z"/>
+                                       <path d="m-502.79-179.85q0.11396 0 0.21228 0.0313l-0.0514 0.21228q-0.0805-0.0335-0.16536-0.0335-0.12291 0-0.23017 0.12737-0.10502 0.12514-0.16536 0.35083-0.0603 0.2257-0.0603 0.49832v1.2849h-0.21229v-2.4246h0.1743l0.0223 0.42457h0.0156q0.16983-0.4715 0.46033-0.4715z"/>
+                                       <path d="m-501.52-177.33q-0.38435 0-0.59217-0.32625-0.20559-0.32849-0.20559-0.91396 0-0.62122 0.18324-0.94747 0.18547-0.32849 0.53184-0.32849 0.30167 0 0.47597 0.28827 0.1743 0.28603 0.1743 0.77317v0.19665h-1.1486q4e-3 0.5296 0.14972 0.79328 0.14525 0.26369 0.44022 0.26369 0.22793 0 0.48044-0.14972v0.20558q-0.2324 0.14525-0.48938 0.14525zm-0.0961-2.324q-0.43798 0-0.48044 0.87373h0.93183q0-0.39999-0.1229-0.63686-0.12067-0.23687-0.32849-0.23687z"/>
+                               </g>
+                               <g dominant-baseline="auto" fill="#786721" stroke-width=".21029" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="multiple selection OK">
+                                       <path d="m-521.09-144.73v-1.2141q0-0.44852-0.26286-0.44852-0.18073 0-0.26123 0.15772-0.0789 0.15608-0.0789 0.46167v1.0432h-0.15443v-1.2141q0-0.22672-0.0641-0.3368-0.0641-0.11172-0.19879-0.11172-0.17744 0-0.25794 0.16101t-0.0805 0.52245v0.97918h-0.15608v-1.7826h0.13143l0.0131 0.24808h0.0148q0.10679-0.28258 0.36309-0.28258 0.14786 0 0.23329 0.0789 0.0854 0.0789 0.12322 0.23001 0.0641-0.16429 0.15772-0.23658 0.0937-0.0723 0.24316-0.0723 0.20208 0 0.29572 0.15608 0.0936 0.15443 0.0936 0.49616v1.1648z"/>
+                                       <path d="m-520.31-146.51v1.1632q0 0.26451 0.0624 0.38609 0.0641 0.12157 0.19551 0.12157 0.20208 0 0.29572-0.16265 0.0953-0.16429 0.0953-0.52902v-0.97918h0.15444v1.7826h-0.13144l-0.0197-0.24972h-0.0132q-0.0575 0.13636-0.15936 0.21029-0.10186 0.0723-0.22179 0.0723-0.21523 0-0.31545-0.15279-0.0986-0.15279-0.0986-0.49945v-1.1632z"/>
+                                       <path d="m-518.88-144.73h-0.15608v-2.5564h0.15608z"/>
+                                       <path d="m-518.07-144.84q0.0723 0 0.12815-0.0197v0.13143q-0.0723 0.0329-0.18565 0.0329-0.27601 0-0.27601-0.41237v-1.265h-0.16101v-0.092l0.15772-0.046 0.0509-0.42387h0.10843v0.42387h0.28258v0.13801h-0.28258v1.2207q0 0.18072 0.0394 0.24644 0.0394 0.0657 0.138 0.0657z"/>
+                                       <path d="m-517.51-144.73h-0.15608v-1.7826h0.15608zm-0.17908-2.2771q0-0.0739 0.0296-0.115 0.0312-0.0411 0.0789-0.0411 0.0444 0 0.0707 0.0411t0.0263 0.115q0 0.0707-0.0263 0.11336-0.0263 0.0411-0.0707 0.0411-0.0476 0-0.0789-0.0411-0.0296-0.0427-0.0296-0.11336z"/>
+                                       <path d="m-516.46-144.69q-0.13471 0-0.24643-0.0739-0.11008-0.0756-0.16758-0.20537h-0.0148l0.0115 0.19551v0.85925h-0.15608v-2.5909h0.12815l0.0164 0.2448h0.0148q0.0657-0.13472 0.16758-0.20701 0.1035-0.0723 0.22508-0.0723 0.52245 0 0.52245 0.92168 0 0.44523-0.12815 0.68674t-0.37295 0.24151zm-0.0312-1.7021q-0.19551 0-0.2908 0.17251-0.0953 0.17251-0.0953 0.54052v0.0509q0 0.41073 0.0969 0.6046 0.0969 0.19222 0.29573 0.19222 0.18401 0 0.27273-0.19058 0.0904-0.19222 0.0904-0.59638 0-0.38609-0.0838-0.57995-0.0838-0.19387-0.28587-0.19387z"/>
+                                       <path d="m-515.41-144.73h-0.15608v-2.5564h0.15608z"/>
+                                       <path d="m-514.43-144.69q-0.28259 0-0.43538-0.23987-0.15115-0.24151-0.15115-0.67195 0-0.45673 0.13472-0.6966 0.13636-0.24151 0.39102-0.24151 0.22179 0 0.34994 0.21194 0.12815 0.21029 0.12815 0.56845v0.14458h-0.84446q3e-3 0.38937 0.11007 0.58323 0.10679 0.19387 0.32366 0.19387 0.16758 0 0.35323-0.11008v0.15115q-0.17087 0.10679-0.3598 0.10679zm-0.0707-1.7086q-0.32201 0-0.35323 0.64238h0.6851q0-0.29408-0.0904-0.46823-0.0887-0.17415-0.24151-0.17415z"/>
+                                       <path d="m-512.29-145.17q0 0.22508-0.11829 0.35323-0.11829 0.1265-0.34173 0.1265-0.12157 0-0.21358-0.0312-0.092-0.0312-0.14293-0.069v-0.18236q0.0608 0.0608 0.16101 0.0986 0.10021 0.0362 0.20536 0.0362 0.13801 0 0.21687-0.0904 0.0789-0.0904 0.0789-0.24151 0-0.11829-0.0575-0.1988-0.0559-0.0821-0.21523-0.18565-0.18236-0.115-0.24972-0.184-0.0657-0.069-0.10351-0.15444-0.0361-0.0854-0.0361-0.20372 0-0.19222 0.12979-0.31709 0.12979-0.1265 0.33023-0.1265 0.21522 0 0.3598 0.10186l-0.0805 0.13636q-0.13472-0.0904-0.28587-0.0904-0.13801 0-0.22015 0.0822-0.0821 0.0805-0.0821 0.21358 0 0.11829 0.0559 0.19879 0.0559 0.0789 0.23658 0.19222 0.17744 0.11665 0.24315 0.1873 0.0657 0.069 0.0969 0.15443 0.0329 0.0838 0.0329 0.19387z"/>
+                                       <path d="m-511.42-144.69q-0.28258 0-0.43537-0.23987-0.15115-0.24151-0.15115-0.67195 0-0.45673 0.13472-0.6966 0.13636-0.24151 0.39101-0.24151 0.2218 0 0.34995 0.21194 0.12814 0.21029 0.12814 0.56845v0.14458h-0.84446q3e-3 0.38937 0.11008 0.58323 0.10679 0.19387 0.32365 0.19387 0.16758 0 0.35323-0.11008v0.15115q-0.17086 0.10679-0.3598 0.10679zm-0.0706-1.7086q-0.32202 0-0.35323 0.64238h0.6851q0-0.29408-0.0904-0.46823-0.0887-0.17415-0.24151-0.17415z"/>
+                                       <path d="m-510.45-144.73h-0.15608v-2.5564h0.15608z"/>
+                                       <path d="m-509.47-144.69q-0.28258 0-0.43537-0.23987-0.15115-0.24151-0.15115-0.67195 0-0.45673 0.13472-0.6966 0.13636-0.24151 0.39102-0.24151 0.22179 0 0.34994 0.21194 0.12815 0.21029 0.12815 0.56845v0.14458h-0.84447q3e-3 0.38937 0.11008 0.58323 0.10679 0.19387 0.32366 0.19387 0.16757 0 0.35322-0.11008v0.15115q-0.17086 0.10679-0.3598 0.10679zm-0.0706-1.7086q-0.32201 0-0.35323 0.64238h0.6851q0-0.29408-0.0904-0.46823-0.0887-0.17415-0.24151-0.17415z"/>
+                                       <path d="m-508.2-144.69q-0.2678 0-0.40416-0.23165-0.13472-0.2333-0.13472-0.68346 0-0.45673 0.13636-0.69495 0.13801-0.23987 0.40581-0.23987 0.16593 0 0.27601 0.0608l-0.0608 0.138q-0.11008-0.0509-0.20208-0.0509-0.39266 0-0.39266 0.78368 0 0.7771 0.39266 0.7771 0.11665 0 0.25301-0.0526v0.13143q-0.0542 0.0296-0.13308 0.046-0.0789 0.0164-0.13636 0.0164z"/>
+                                       <path d="m-507.28-144.84q0.0723 0 0.12815-0.0197v0.13143q-0.0723 0.0329-0.18565 0.0329-0.27601 0-0.27601-0.41237v-1.265h-0.161v-0.092l0.15772-0.046 0.0509-0.42387h0.10843v0.42387h0.28258v0.13801h-0.28258v1.2207q0 0.18072 0.0394 0.24644 0.0394 0.0657 0.138 0.0657z"/>
+                                       <path d="m-506.72-144.73h-0.15608v-1.7826h0.15608zm-0.17908-2.2771q0-0.0739 0.0296-0.115 0.0312-0.0411 0.0789-0.0411 0.0444 0 0.0707 0.0411t0.0263 0.115q0 0.0707-0.0263 0.11336-0.0263 0.0411-0.0707 0.0411-0.0476 0-0.0789-0.0411-0.0296-0.0427-0.0296-0.11336z"/>
+                                       <path d="m-505.24-145.62q0 0.45016-0.14293 0.69003-0.14129 0.23822-0.4058 0.23822-0.26123 0-0.40088-0.23822-0.138-0.23987-0.138-0.69003 0-0.92168 0.54545-0.92168 0.25629 0 0.39923 0.24151 0.14293 0.24151 0.14293 0.68017zm-0.92496 0q0 0.38609 0.092 0.58324t0.28751 0.19715q0.3828 0 0.3828-0.78039 0-0.77382-0.3828-0.77382-0.20043 0-0.2908 0.19387-0.0887 0.19386-0.0887 0.57995z"/>
+                                       <path d="m-504.04-144.73v-1.2289q0-0.44359-0.26287-0.44359-0.20044 0-0.29408 0.16593-0.092 0.16594-0.092 0.52738v0.97918h-0.15607v-1.7826h0.13143l0.0131 0.24808h0.0148q0.0559-0.13472 0.16101-0.20865 0.10514-0.0739 0.22508-0.0739 0.20701 0 0.31051 0.13965 0.1035 0.138 0.1035 0.44523v1.2322z"/>
+                                       <path d="m-501.45-145.93q0 0.59638-0.17908 0.91675-0.17743 0.32037-0.51423 0.32037-0.34009 0-0.51588-0.32201-0.17579-0.32366-0.17579-0.9184 0-0.62102 0.17415-0.92496t0.52245-0.30394q0.33515 0 0.51095 0.32037 0.17743 0.31872 0.17743 0.91182zm-1.2174 0q0 0.53559 0.13307 0.80996 0.13472 0.27273 0.39102 0.27273 0.25794 0 0.39101-0.27109 0.13472-0.27108 0.13472-0.8116 0-0.53395-0.13143-0.80503-0.13143-0.27273-0.38937-0.27273-0.26451 0-0.39759 0.27601-0.13143 0.27437-0.13143 0.80175z"/>
+                                       <path d="m-499.91-144.73h-0.18072l-0.56517-1.1977-0.18565 0.2448v0.95289h-0.16101v-2.402h0.16101v1.2716q0.0838-0.15772 0.70974-1.2716h0.1758l-0.60296 1.0564z"/>
+                               </g>
+                       </g>
+                       <path d="m-511.33-186.63a24.457 24.457 0 0 0 -24.457 24.457 24.457 24.457 0 0 0 24.457 24.457 24.457 24.457 0 0 0 24.457 -24.457 24.457 24.457 0 0 0 -24.457 -24.457zm0.0667 1.7198a22.804 22.804 0 0 1 22.804 22.804 22.804 22.804 0 0 1 -22.804 22.804 22.804 22.804 0 0 1 -22.804 -22.804 22.804 22.804 0 0 1 22.804 -22.804z" fill="url(#c)"/>
+                       <path d="m-511.4-186.5a24.457 24.457 0 0 0 -24.457 24.457 24.457 24.457 0 0 0 24.457 24.457 24.457 24.457 0 0 0 24.457 -24.457 24.457 24.457 0 0 0 -24.457 -24.457zm0.0667 1.7198a22.804 22.804 0 0 1 22.804 22.804 22.804 22.804 0 0 1 -22.804 22.804 22.804 22.804 0 0 1 -22.804 -22.804 22.804 22.804 0 0 1 22.804 -22.804z" fill="url(#b)"/>
+               </g>
+               <circle cx="-511.47" cy="-162.18" r="24.457" fill="none" stroke="#d4aa00" stroke-dasharray="1.58700002, 1.58700002" stroke-linecap="round" stroke-linejoin="round" stroke-width=".529"/>
+       </g>
+</svg>
diff --git a/plugins/deaddrop/assets/index.html b/plugins/deaddrop/assets/index.html
new file mode 100644 (file)
index 0000000..a3f80f1
--- /dev/null
@@ -0,0 +1,34 @@
+<html>
+ <head>
+  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+  <link rel="stylesheet" type="text/css" href="deaddrop.css"/>
+  <script src="deaddrop.js"></script>
+ </head>
+ <body>
+       <h1>LWS Deaddrop</h1>
+       <div class="uplbox">
+        <table id="ddrop" class="noconn">
+         <tr><td class="vm">
+               <div id="da" class="da"><img class="disa" src="drop.svg"></div>
+         </td><td>
+          <h3>...or select files to upload:</h3>
+          <span id="size"></span><br>
+         <form name=multipart action="upload" method="post"
+                enctype="multipart/form-data">
+           <input multiple type="file" name="file" id="file" size="20">
+               <span id="file_info" class="dd-fileinfo"></span>
+           <br>
+           <input id="upl" type="submit" name="Upload" value="Upload"
+                   class="ubtn" disabled>
+         </form>
+          <table id="ongoing" class="nb"></table>
+        </td></tr>
+        <tr><td colspan="2">
+               <div id="dd-list" class="dd-list">
+               <!-- deaddrop-list -->
+               </div>
+        </td></tr>
+        </table>
+       </div>
+ </body>
+</html>
\ No newline at end of file
diff --git a/plugins/deaddrop/protocol_lws_deaddrop.c b/plugins/deaddrop/protocol_lws_deaddrop.c
new file mode 100644 (file)
index 0000000..7c40f69
--- /dev/null
@@ -0,0 +1,702 @@
+/*
+ * lws protocol handler plugin for "Dead Drop"
+ *
+ * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#ifdef WIN32
+#include <io.h>
+#endif
+#include <stdio.h>
+#include <errno.h>
+
+struct dir_entry {
+       lws_list_ptr next; /* sorted by mtime */
+       char user[32];
+       unsigned long long size;
+       time_t mtime;
+};
+/* filename follows */
+
+#define lp_to_dir_entry(p, _n) lws_list_ptr_container(p, struct dir_entry, _n)
+
+struct pss_deaddrop;
+
+struct vhd_deaddrop {
+       struct lws_context *context;
+       struct lws_vhost *vh;
+       const struct lws_protocols *protocol;
+
+       struct pss_deaddrop *pss_head;
+
+       const char *upload_dir;
+
+       struct lwsac *lwsac_head;
+       struct dir_entry *dire_head;
+       int filelist_version;
+
+       unsigned long long max_size;
+};
+
+struct pss_deaddrop {
+       struct lws_spa *spa;
+       struct vhd_deaddrop *vhd;
+       struct lws *wsi;
+       char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE];
+       char filename[256];
+       char user[32];
+       unsigned long long file_length;
+       lws_filefd_type fd;
+       int response_code;
+
+       struct pss_deaddrop *pss_list;
+
+       struct lwsac *lwsac_head;
+       struct dir_entry *dire;
+       int filelist_version;
+
+       uint8_t completed:1;
+       uint8_t sent_headers:1;
+       uint8_t sent_body:1;
+       uint8_t first:1;
+};
+
+static const char * const param_names[] = {
+       "text",
+       "send",
+       "file",
+       "upload",
+};
+
+enum enum_param_names {
+       EPN_TEXT,
+       EPN_SEND,
+       EPN_FILE,
+       EPN_UPLOAD,
+};
+
+static int
+de_mtime_sort(lws_list_ptr a, lws_list_ptr b)
+{
+       struct dir_entry *p1 = lp_to_dir_entry(a, next),
+                        *p2 = lp_to_dir_entry(b, next);
+
+       return (int)(p2->mtime - p1->mtime);
+}
+
+static void
+start_sending_dir(struct pss_deaddrop *pss)
+{
+       if (pss->vhd->lwsac_head)
+               lwsac_reference(pss->vhd->lwsac_head);
+       pss->lwsac_head = pss->vhd->lwsac_head;
+       pss->dire = pss->vhd->dire_head;
+       pss->filelist_version = pss->vhd->filelist_version;
+       pss->first = 1;
+}
+
+static int
+scan_upload_dir(struct vhd_deaddrop *vhd)
+{
+       char filepath[256], subdir[3][128], *p;
+       int m, sp = 0, initial, found = 0;
+       struct lwsac *lwsac_head = NULL;
+       lws_list_ptr sorted_head = NULL;
+       struct dir_entry *dire;
+       struct dirent *de;
+       struct stat s;
+       DIR *dir[3];
+
+       initial = strlen(vhd->upload_dir) + 1;
+       lws_strncpy(subdir[sp], vhd->upload_dir, sizeof(subdir[sp]));
+       dir[sp] = opendir(vhd->upload_dir);
+       if (!dir[sp]) {
+               lwsl_err("%s: Unable to walk upload dir '%s'\n", __func__,
+                        vhd->upload_dir);
+               return -1;
+       }
+
+       do {
+               de = readdir(dir[sp]);
+               if (!de) {
+                       closedir(dir[sp]);
+                       if (!sp)
+                               break;
+                       sp--;
+                       continue;
+               }
+
+               p = filepath;
+
+               for (m = 0; m <= sp; m++)
+                       p += lws_snprintf(p, (filepath + sizeof(filepath)) - p,
+                                         "%s/", subdir[m]);
+
+               lws_snprintf(p, (filepath + sizeof(filepath)) - p, "%s",
+                                 de->d_name);
+
+               /* ignore temp files */
+               if (de->d_name[strlen(de->d_name) - 1] == '~')
+                       continue;
+
+               if (stat(filepath, &s))
+                       continue;
+
+               if (S_ISDIR(s.st_mode)) {
+                       if (!strcmp(de->d_name, ".") ||
+                           !strcmp(de->d_name, ".."))
+                               continue;
+                       sp++;
+                       if (sp == LWS_ARRAY_SIZE(dir)) {
+                               lwsl_err("%s: Skipping too-deep subdir %s\n",
+                                        __func__, filepath);
+                               sp--;
+                               continue;
+                       }
+                       lws_strncpy(subdir[sp], de->d_name, sizeof(subdir[sp]));
+                       dir[sp] = opendir(filepath);
+                       if (!dir[sp]) {
+                               lwsl_err("%s: Unable to open subdir '%s'\n",
+                                        __func__, filepath);
+                               goto bail;
+                       }
+                       continue;
+               }
+
+               m = strlen(filepath + initial) + 1;
+               dire = lwsac_use(&lwsac_head, sizeof(*dire) + m, 0);
+               if (!dire) {
+                       lwsac_free(&lwsac_head);
+
+                       goto bail;
+               }
+
+               dire->next = NULL;
+               dire->size = s.st_size;
+               dire->mtime = s.st_mtime;
+               dire->user[0] = '\0';
+               if (sp)
+                       lws_strncpy(dire->user, subdir[1], sizeof(dire->user));
+
+               found++;
+
+               memcpy(&dire[1], filepath + initial, m);
+
+               lws_list_ptr_insert(&sorted_head, &dire->next, de_mtime_sort);
+       } while (1);
+
+       /* the old lwsac continues to live while someone else is consuming it */
+       if (vhd->lwsac_head)
+               lwsac_detach(&vhd->lwsac_head);
+
+       /* we replace it with the fresh one */
+       vhd->lwsac_head = lwsac_head;
+       if (sorted_head)
+               vhd->dire_head = lp_to_dir_entry(sorted_head, next);
+       else
+               vhd->dire_head = NULL;
+
+       vhd->filelist_version++;
+
+       lwsl_info("%s: found %d\n", __func__, found);
+
+       lws_start_foreach_llp(struct pss_deaddrop **, ppss, vhd->pss_head) {
+               start_sending_dir(*ppss);
+               lws_callback_on_writable((*ppss)->wsi);
+       } lws_end_foreach_llp(ppss, pss_list);
+
+       return 0;
+
+bail:
+       while (sp >= 0)
+               closedir(dir[sp--]);
+
+       return -1;
+}
+
+static int
+file_upload_cb(void *data, const char *name, const char *filename,
+              char *buf, int len, enum lws_spa_fileupload_states state)
+{
+       struct pss_deaddrop *pss = (struct pss_deaddrop *)data;
+       char filename2[256];
+       int n;
+
+       (void)n;
+
+       switch (state) {
+       case LWS_UFS_OPEN:
+               lws_urldecode(filename2, filename, sizeof(filename2) - 1);
+               lws_filename_purify_inplace(filename2);
+               if (pss->user[0]) {
+                       lws_filename_purify_inplace(pss->user);
+                       lws_snprintf(pss->filename, sizeof(pss->filename),
+                                    "%s/%s", pss->vhd->upload_dir, pss->user);
+                       if (mkdir(pss->filename
+#if !defined(WIN32)
+                               , 0700
+#endif
+                               ) < 0)
+                               lwsl_debug("%s: mkdir failed\n", __func__);
+                       lws_snprintf(pss->filename, sizeof(pss->filename),
+                                    "%s/%s/%s~", pss->vhd->upload_dir,
+                                    pss->user, filename2);
+               } else
+                       lws_snprintf(pss->filename, sizeof(pss->filename),
+                                    "%s/%s~", pss->vhd->upload_dir, filename2);
+               lwsl_notice("%s: filename '%s'\n", __func__, pss->filename);
+
+               pss->fd = (lws_filefd_type)(long long)lws_open(pss->filename,
+                             O_CREAT | O_TRUNC | O_RDWR, 0600);
+               if (pss->fd == LWS_INVALID_FILE) {
+                       pss->response_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
+                       lwsl_err("%s: unable to open %s (errno %d)\n", __func__,
+                                       pss->filename, errno);
+                       return -1;
+               }
+               break;
+
+       case LWS_UFS_FINAL_CONTENT:
+       case LWS_UFS_CONTENT:
+               if (len) {
+                       pss->file_length += len;
+
+                       /* if the file length is too big, drop it */
+                       if (pss->file_length > pss->vhd->max_size) {
+                               pss->response_code =
+                                       HTTP_STATUS_REQ_ENTITY_TOO_LARGE;
+                               close((int)(long long)pss->fd);
+                               pss->fd = LWS_INVALID_FILE;
+                               unlink(pss->filename);
+
+                               return -1;
+                       }
+
+                       if (pss->fd != LWS_INVALID_FILE) {
+                               n = write((int)(long long)pss->fd, buf, len);
+                               lwsl_debug("%s: write %d says %d\n", __func__,
+                                          len, n);
+                               lws_set_timeout(pss->wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30);
+                       }
+               }
+               if (state == LWS_UFS_CONTENT)
+                       break;
+
+               if (pss->fd != LWS_INVALID_FILE)
+                       close((int)(long long)pss->fd);
+
+               /* the temp filename without the ~ */
+               lws_strncpy(filename2, pss->filename, sizeof(filename2));
+               filename2[strlen(filename2) - 1] = '\0';
+               if (rename(pss->filename, filename2) < 0)
+                       lwsl_err("%s: unable to rename\n", __func__);
+
+               pss->fd = LWS_INVALID_FILE;
+               pss->response_code = HTTP_STATUS_OK;
+               scan_upload_dir(pss->vhd);
+
+               break;
+       case LWS_UFS_CLOSE:
+               break;
+       }
+
+       return 0;
+}
+
+/*
+ * returns length in bytes
+ */
+
+static int
+format_result(struct pss_deaddrop *pss)
+{
+       unsigned char *p, *start, *end;
+
+       p = (unsigned char *)pss->result + LWS_PRE;
+       start = p;
+       end = p + sizeof(pss->result) - LWS_PRE - 1;
+
+       p += lws_snprintf((char *)p, end -p,
+                       "<!DOCTYPE html><html lang=\"en\"><head>"
+                       "<meta charset=utf-8 http-equiv=\"Content-Language\" "
+                       "content=\"en\"/>"
+                       "</head>");
+       p += lws_snprintf((char *)p, end - p, "</body></html>");
+
+       return (int)lws_ptr_diff(p, start);
+}
+
+static int
+callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
+                 void *user, void *in, size_t len)
+{
+       struct vhd_deaddrop *vhd = (struct vhd_deaddrop *)
+                               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                        lws_get_protocol(wsi));
+       struct pss_deaddrop *pss = (struct pss_deaddrop *)user;
+       uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
+               *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - LWS_PRE - 1];
+       char fname[256], *wp;
+       const char *cp;
+       int n, m, was;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
+               lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                                           lws_get_protocol(wsi),
+                                           sizeof(struct vhd_deaddrop));
+
+               vhd = (struct vhd_deaddrop *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                lws_get_protocol(wsi));
+
+               vhd->context = lws_get_context(wsi);
+               vhd->vh = lws_get_vhost(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->max_size = 20 * 1024 * 1024; /* default without pvo */
+
+               if (!lws_pvo_get_str(in, "max-size", &cp))
+                       vhd->max_size = atoll(cp);
+               if (lws_pvo_get_str(in, "upload-dir", &vhd->upload_dir)) {
+                       lwsl_err("%s: requires 'upload-dir' pvo\n", __func__);
+                       return -1;
+               }
+
+               scan_upload_dir(vhd);
+
+               lwsl_notice("  deaddrop: vh %s, upload dir %s, max size %llu\n",
+                           lws_get_vhost_name(vhd->vh), vhd->upload_dir,
+                           vhd->max_size);
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               lwsac_free(&vhd->lwsac_head);
+               break;
+
+       /* WS-related */
+
+       case LWS_CALLBACK_ESTABLISHED:
+               pss->vhd = vhd;
+               pss->wsi = wsi;
+               /* add ourselves to the list of live pss held in the vhd */
+               pss->pss_list = vhd->pss_head;
+               vhd->pss_head = pss;
+
+               m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
+                                WSI_TOKEN_HTTP_AUTHORIZATION);
+               if (m > 0)
+                       lwsl_info("%s: basic auth user: %s\n",
+                                 __func__, pss->user);
+               else
+                       pss->user[0] = '\0';
+
+               start_sending_dir(pss);
+               lws_callback_on_writable(wsi);
+               return 0;
+
+       case LWS_CALLBACK_CLOSED:
+               if (pss->lwsac_head)
+                       lwsac_unreference(&pss->lwsac_head);
+               /* remove our closing pss from the list of live pss */
+               lws_start_foreach_llp(struct pss_deaddrop **,
+                                     ppss, vhd->pss_head) {
+                       if (*ppss == pss) {
+                               *ppss = pss->pss_list;
+                               break;
+                       }
+               } lws_end_foreach_llp(ppss, pss_list);
+               return 0;
+
+       case LWS_CALLBACK_RECEIVE:
+               /* we get this kind of thing {"del":"agreen/no-entry.svg"} */
+               if (!pss || len < 10)
+                       break;
+
+               if (strncmp((const char *)in, "{\"del\":\"", 8))
+                       break;
+
+               cp = strchr((const char *)in, '/');
+               if (cp) {
+                       n = ((void *)cp - in) - 8;
+
+                       if ((int)strlen(pss->user) != n ||
+                           memcmp(pss->user, ((const char *)in) + 8, n)) {
+                               lwsl_notice("%s: del: auth mismatch "
+                                           " '%s' '%s' (%d)\n",
+                                           __func__, pss->user,
+                                           ((const char *)in) + 8, n);
+                               break;
+                       }
+               }
+
+               lws_strncpy(fname, ((const char *)in) + 8, sizeof(fname));
+               lws_filename_purify_inplace(fname);
+               wp = strchr((const char *)fname, '\"');
+               if (wp)
+                       *wp = '\0';
+
+               lws_snprintf((char *)buf, sizeof(buf), "%s/%s", vhd->upload_dir,
+                            fname);
+
+               lwsl_notice("%s: del: path %s\n", __func__, (const char *)buf);
+
+               if (unlink((const char *)buf) < 0)
+                       lwsl_err("%s: unlink %s failed\n", __func__,
+                                       (const char *)buf);
+
+               scan_upload_dir(vhd);
+               break;
+
+       case LWS_CALLBACK_SERVER_WRITEABLE:
+               if (pss->lwsac_head && !pss->dire)
+                       return 0;
+
+               was = 0;
+               if (pss->first) {
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                                         "{\"max_size\":%llu, \"files\": [",
+                                         vhd->max_size);
+                       was = 1;
+               }
+
+               m = 5;
+               while (m-- && pss->dire) {
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                                         "%c{\"name\":\"%s\", "
+                                         "\"size\":%llu,"
+                                         "\"mtime\":%llu,"
+                                         "\"yours\":%d}",
+                                         pss->first ? ' ' : ',',
+                                         (const char *)&pss->dire[1],
+                                         pss->dire->size,
+                                         (unsigned long long)pss->dire->mtime,
+                                         !strcmp(pss->user, pss->dire->user) &&
+                                                 pss->user[0]);
+                       pss->first = 0;
+                       pss->dire = lp_to_dir_entry(pss->dire->next, next);
+               }
+
+               if (!pss->dire) {
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                                         "]}");
+                       if (pss->lwsac_head) {
+                               lwsac_unreference(&pss->lwsac_head);
+                               pss->lwsac_head = NULL;
+                       }
+               }
+
+               n = lws_write(wsi, start, lws_ptr_diff(p, start),
+                             lws_write_ws_flags(LWS_WRITE_TEXT, was,
+                                                !pss->dire));
+               if (n < 0) {
+                       lwsl_notice("%s: ws write failed\n", __func__);
+                       return 1;
+               }
+               if (pss->dire) {
+                       lws_callback_on_writable(wsi);
+
+                       return 0;
+               }
+
+               /* ie, we finished */
+
+               if (pss->filelist_version != pss->vhd->filelist_version) {
+                       lwsl_info("%s: restart send\n", __func__);
+                       /* what we just sent is already out of date */
+                       start_sending_dir(pss);
+                       lws_callback_on_writable(wsi);
+               }
+
+               return 0;
+
+       /* POST-related */
+
+       case LWS_CALLBACK_HTTP_BODY:
+
+               /* create the POST argument parser if not already existing */
+               if (!pss->spa) {
+                       pss->vhd = vhd;
+                       pss->wsi = wsi;
+                       pss->spa = lws_spa_create(wsi, param_names,
+                                                 LWS_ARRAY_SIZE(param_names),
+                                                 1024, file_upload_cb, pss);
+                       if (!pss->spa)
+                               return -1;
+
+                       pss->filename[0] = '\0';
+                       pss->file_length = 0;
+                       /* catchall */
+                       pss->response_code = HTTP_STATUS_SERVICE_UNAVAILABLE;
+
+                       m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
+                                        WSI_TOKEN_HTTP_AUTHORIZATION);
+                       if (m > 0)
+                               lwsl_info("basic auth user: %s\n", pss->user);
+                       else
+                               pss->user[0] = '\0';
+               }
+
+               /* let it parse the POST data */
+               if (lws_spa_process(pss->spa, in, (int)len)) {
+                       lwsl_notice("spa saw a problem\n");
+                       /* some problem happened */
+                       lws_spa_finalize(pss->spa);
+
+                       pss->completed = 1;
+                       lws_callback_on_writable(wsi);
+               }
+               break;
+
+       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+               /* call to inform no more payload data coming */
+               lws_spa_finalize(pss->spa);
+
+               pss->completed = 1;
+               lws_callback_on_writable(wsi);
+               break;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               if (!pss->completed)
+                       break;
+
+               p = (unsigned char *)pss->result + LWS_PRE;
+               start = p;
+               end = p + sizeof(pss->result) - LWS_PRE - 1;
+
+               if (!pss->sent_headers) {
+                       n = format_result(pss);
+
+                       if (lws_add_http_header_status(wsi, pss->response_code,
+                                                      &p, end))
+                               goto bail;
+
+                       if (lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                       (unsigned char *)"text/html", 9,
+                                       &p, end))
+                               goto bail;
+                       if (lws_add_http_header_content_length(wsi, n, &p, end))
+                               goto bail;
+                       if (lws_finalize_http_header(wsi, &p, end))
+                               goto bail;
+
+                       /* first send the headers ... */
+                       n = lws_write(wsi, start, lws_ptr_diff(p, start),
+                                     LWS_WRITE_HTTP_HEADERS |
+                                     LWS_WRITE_H2_STREAM_END);
+                       if (n < 0)
+                               goto bail;
+
+                       pss->sent_headers = 1;
+                       lws_callback_on_writable(wsi);
+                       break;
+               }
+
+               if (!pss->sent_body) {
+                       n = format_result(pss);
+                       n = lws_write(wsi, (unsigned char *)start, n,
+                                     LWS_WRITE_HTTP_FINAL);
+
+                       pss->sent_body = 1;
+                       if (n < 0) {
+                               lwsl_err("%s: writing body failed\n", __func__);
+                               return 1;
+                       }
+                       goto try_to_reuse;
+               }
+               break;
+
+       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
+               /* called when our wsi user_space is going to be destroyed */
+               if (pss->spa) {
+                       lws_spa_destroy(pss->spa);
+                       pss->spa = NULL;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+
+bail:
+
+       return 1;
+
+try_to_reuse:
+       if (lws_http_transaction_completed(wsi))
+               return -1;
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_DEADDROP \
+       { \
+               "lws-deaddrop", \
+               callback_deaddrop, \
+               sizeof(struct pss_deaddrop), \
+               1024, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_DEADDROP
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_deaddrop(struct lws_context *context,
+                      struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_deaddrop(struct lws_context *context)
+{
+       return 0;
+}
+
+#endif
index 276d1cc..d40904a 100644 (file)
@@ -1,52 +1,44 @@
 <html>
  <head>
-  <script src="/lws-common.js" nonce="lwscaro"></script>
-  <script src="lwsgs.js" nonce="lwscaro"></script>
-  <style>
-       .body { font-size: 12 }
-       .gstitle { font-size: 18 }
-       .group1 { vertical-align:middle;text-align:center;background:#f0f0e0; 
-               padding:12px; -webkit-border-radius:10px; 
-               -moz-border-radius:10px;border-radius:10px; }
-       .group2 { vertical-align:middle; font-size: 22;text-align:center;
-               margin:auto; align:center;
-               background-color: rgba(255, 255, 255, 0.8); padding:12px;
-               display:inline-block; -webkit-border-radius:10px; 
-               -moz-border-radius:10px; border-radius:10px; }
-  </style>
+ <meta charset="UTF-8"> 
+  <script src="/lws-common.js"></script>
+  <link rel="stylesheet" type="text/css" href="lwsgs.css"/>
+  <script src="lwsgs.js"></script>
   </head>
-  <body style="background-image:url(seats.jpg)">
-    <table style="width:100%;height:100%;transition: max-height 2s;">
+
+  <body class="seats">
+    <table class="lwsgs">
      <tr>
-      <td style="vertical-align:top;text-align:left;width=200px">
+      <td class="logo">
        <img src="lwsgs-logo.png">
       </td>
-      <td style="vertical-align:top;float:right">
-       <div id=lwsgs style="zIndex: 1000;text-align:right;background-color: rgba(255, 255, 255, 0.8);"></div>
+      <td class="">
+       <div id=lwsgs class="lwsgs"></div>
       </td>
      </tr>
      
-     <tr><td colspan=2 style="height:99%;vertical-align:middle;">
-        <table style="text-align:center;width:100%"><tr>
-        <td style="margin:auto;align:center">
-       <span id="nolog" class="group2" style="display:none;">
+     <tr><td colspan=2 class="h99">
+        <table class="c100"><tr>
+        <td class="c">
+       <span id="nolog" class="group2">
        This is a demo application for lws generic-sessions.<br><br>
        It's a simple messageboard.<br><br>
        What's interesting about it is there is <b>no serverside scripting</b>,<br>
        instead client js makes a wss:// connection back to the server<br>
        and then reacts to JSON from the ws protocol.  Sessions stuff is <br>
-       handled by lws generic sessions, making the <a href="https://github.com/warmcat/libwebsockets/blob/master/plugins/generic-sessions/protocol_lws_messageboard.c">actual<br>
-       test application</a> <a href="https://github.com/warmcat/libwebsockets/blob/master/plugins/generic-sessions/index.html">very small</a>.<br><br>
+       handled by lws generic sessions, making the <a href="https://libwebsockets.org/git/libwebsockets/tree/plugins/generic-sessions/protocol_generic_sessions.c">actual<br>
+       test application</a> <a href="https://libwebsockets.org/git/libwebsockets/tree/plugins/generic-sessions/protocol_lws_messageboard.c">very small</a>.<br><br>
        And because it's natively websocket, it's naturally connected<br>
        for dynamic events and easy to maintain.
        <br><br>
        Register / Login at the top right to see and create new messages.
        </span>
-       <span id="logged" class="group2" style="display:none">
+       <span id="logged" class="group2">
        <div id="newmsg">
                <form action="msg" method="post" target="hidden">
                New message<br>
-         <textarea id="msg" placeholder="type your message here" cols="40" rows="5" name="msg"></textarea><br>
+         <textarea id="msg" placeholder="type your message here" cols="40" rows="5" name="msg">
+               </textarea><br>
                <input type="submit" id="send" name="send" disabled=1>
                </form>
        </div>
      </td></tr>
     </table>
    </form>
-   <iframe name="hidden" style="display:none"></iframe>
-<script nonce="lwscaro">lwsgs_initial();
-document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline-block";
-document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline-block";
-
-document.getElementById("msg").onkeyup = mupd;
-document.getElementById("msg").onchange = mupd;
-
-var ws;
-
-function mb_format(s)
-{
-       var r = "", n, wos = 0;
-       
-       for (n = 0; n < s.length; n++) {
-               if (s[n] == ' ')
-                       wos = 0;
-               else {
-                       wos++;
-                       if (wos == 40) {
-                               wos = 0;
-                               r = r + ' ';
-                       }
-               }
-               if (s[n] == '<') {
-                       r = r + "&lt;";
-                       continue;
-               }
-               if (s[n] == '\n') {
-                       r = r + "<br>";
-                       continue;
-               }
-                       
-               r = r + s[n];
-       }
-       
-       return r;
-}
-
-function add_div(n, m)
-{
-       var q = document.getElementById(n);
-       var d = new Date(m.time * 1000);
-       
-       q.innerHTML = "<br><div style=\"margin:2px\" class=\"group2\"><table style=\"table-layout: fixed;\"><tr><td>" +
-               "<img src=\"https://www.gravatar.com/avatar/" + md5(m.email) +
-               "?d=identicon\"><br>" +
-               "<b>" + lwsgs_san(m.username) + "</b><br>" +
-               "<span style=\"font-size:8pt\">" + d.toDateString() +
-                 "<br>" + d.toTimeString() + "</span><br>" +
-               "IP: " + lwsgs_san(m.ip) +
-               "</td><td style=\"display:inline-block;vertical-align:top;word-wrap:break-word;\"><span>" +
-               mb_format(m.content) +
-               "</span></td></tr></table></div><br>" + q.innerHTML;
-}
-
-function get_appropriate_ws_url()
-{
-       var pcol;
-       var u = document.URL;
-
-       if (u.substring(0, 5) == "https") {
-               pcol = "wss://";
-               u = u.substr(8);
-       } else {
-               pcol = "ws://";
-               if (u.substring(0, 4) == "http")
-                       u = u.substr(7);
-       }
-       u = u.split('/');
-
-       return pcol + u[0] + "/xxx";
-}
-
-if (lwsgs_user) {
-       if (typeof MozWebSocket != "undefined")
-               ws = new MozWebSocket(get_appropriate_ws_url(),
-                                  "protocol-lws-messageboard");
-       else
-               ws = new WebSocket(get_appropriate_ws_url(),
-                                  "protocol-lws-messageboard");
-
-       try {
-               ws.onopen = function() {
-                       document.getElementById("debug").textContent = "ws opened";
-               }
-               ws.onmessage =function got_packet(msg) {
-                       add_div("messages", JSON.parse(msg.data));
-               }
-               ws.onclose = function(){
-               }
-       } catch(exception) {
-               alert('<p>Error' + exception);  
-       }
-}
-
-function mupd()
-{
-       document.getElementById("send").disabled = !document.getElementById("msg").value;
-}
-   </script>
+   <iframe name="hidden" class="hidden"></iframe>
  </body>
 </html>
diff --git a/plugins/generic-sessions/assets/lwsgs.css b/plugins/generic-sessions/assets/lwsgs.css
new file mode 100644 (file)
index 0000000..9dfde75
--- /dev/null
@@ -0,0 +1,134 @@
+.body { font-size: 12px }
+.gstitle { font-size: 18px }
+
+.group1 {
+       vertical-align:middle;
+       text-align:center;
+       background:#f0f0e0; 
+       padding:12px;
+       border-radius:10px;
+}
+.group2 {
+       display:none;
+       vertical-align:middle;
+       font-size: 22px;
+       text-align:center;
+       margin:auto;
+       align:center;
+       background-color: rgba(255, 255, 255, 0.8);
+       padding:12px;
+       border-radius:10px;
+}
+       
+body.seats {
+       background-image:url(seats.jpg)
+}
+
+div.lwsgs {
+       z-index: 3;
+       text-align:right;
+       background-color: rgba(255, 255, 255, 0.8);
+}
+
+table.lwsgs {
+       width:100%;
+       height:100%;
+       transition: max-height 2s;
+}
+table.c100 {
+       text-align:center;
+       width:100%;
+}
+
+table.r {
+       vertical-align:top;
+       text-align:right;
+}
+
+table.l {
+       vertical-align:top;
+       text-align:left;
+}
+
+table.fixed {
+       table-layout: fixed;
+}
+
+td.logo {
+       vertical-align:top;
+       text-align:left;
+       width:200px
+}
+
+td.lwsgs {
+       vertical-align:top;
+       float:right;
+}
+
+td.h99 {
+       height:99%;
+       vertical-align:middle;
+}
+
+td.c {
+       margin:auto;
+       align:center
+}
+
+td.tac {
+       text-align:center
+}
+
+td.ava {
+       display:inline-block;
+       vertical-align:top;
+       word-wrap:break-word;
+}
+
+iframe.hidden {
+       display:none;
+}
+
+div.hidden {
+       display:none;
+}
+
+div.hiddenr {
+       display:none;
+       text-align:right;
+}
+
+input {
+       margin: 2px;
+       padding: 2px;
+}
+
+input.em {
+       margin: 4px;
+       font-weight:bold;
+}
+
+input.wide {
+       margin: 6px;
+       padding: 6px;
+}
+
+input.hidden {
+       display: none;
+}
+
+form.r {
+       text-align:right;
+}
+
+span.bad {
+       color: red;
+}
+
+span.small {
+       font-size:8pt;
+}
+
+.green {
+       color: green;
+}
index 1d63b5f..b9bfe16 100644 (file)
@@ -5,7 +5,7 @@ var lwsgs_auth = "$lwsgs_auth";
 var lwsgs_email = "$lwsgs_email";
 
 var lwsgs_html = '\
-       <div id="dlogin" style="display:none"> \
+       <div id="dlogin" class="hidden"> \
         <form action="lwsgs-login" method="post"> \
          <input type="hidden" name="admin" value="needadmin/admin-login.html"> \
          <input type="hidden" name="good" value="index.html"> \
@@ -14,35 +14,35 @@ var lwsgs_html = '\
          <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
          <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
          <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
-        <table style="vertical-align:top;text-align:right"\
+        <table class="r">\
           <tr>\
            <td>User Name\
             <input type="text" size="10" id="username" name="username"></td>\
            <td>Password\
             <input type="password" id="password" size="10" name="password"><div id="pw1"></div></td>\
             </tr><tr>\
-          <td colspan="2" style="text-align:center"><input type="submit" id="login" name="login" value="Login" style="margin: 4px; padding: 2px; font-weight=bold;">\
-      &nbsp;<input type="submit" id="forgot" name="forgot" value="Forgot password" style="margin: 2px; padding: 2px">\
-           &nbsp;<input id="doreg" type="button" value="Sign up" style="margin: 2px; padding: 2px"></td>\
+          <td colspan="2" class="c"><input type="submit" id="login" name="login" value="Login" class="em">\
+      &nbsp;<input type="submit" id="forgot" name="forgot" value="Forgot password">\
+           &nbsp;<input id="doreg" type="button" value="Sign up"></td>\
           </tr>\
          </table>\
         </form>\
        </div>\
 \
-       <div id="dlogout" style="display:none;text-align:right">\
-        <form action="lwsgs-logout" method="post" style="text-align:right">\
+       <div id="dlogout" class="hiddenr">\
+        <form action="lwsgs-logout" method="post" class="r">\
          <input type="hidden" name="good" value="index.html">\
-         <table style="vertical-align:top;text-align:right">\
+         <table class="r">\
           <tr><td><span id=grav></span></td>\
-          <td style="text-align:center"><table><tr><td style="text-align:center">\
+          <td class="tac"><table><tr><td class="tac">\
                <a href="#" id="clink">\
                <span id="curuser"></span></a></td></tr><tr>\
-           <td style="text-align:center"><input type="submit" name="logout" value="Logout" style="margin: 2px; padding: 2px"></td>\
+           <td class="tac"><input type="submit" name="logout" value="Logout"></td>\
           </tr></table></td></tr>\
          </table>\
         </form></div>\
 \
-       <div id="dregister" style="display:none">\
+       <div id="dregister" class="hidden">\
         <form action="lwsgs-login" method="post">\
          <input type="hidden" name="admin" value="needadmin/admin-login.html">\
          <input type="hidden" name="good" value="successful-login.html">\
@@ -53,14 +53,10 @@ var lwsgs_html = '\
          <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
          <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
          <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
-         <table style="vertical-align:top;text-align:left">\
+         <table class="l">\
             <tr>\
              <td colspan=2 align=center>\
                <span id="curuser"></span>\
-       <script>\
-               if (lwsgs_user)\
-                       document.getElementById("curuser").innerHTML = "currently logged in as " + lwsgs_san(lwsgs_user) + "</br>";\
-       </script>\
               <b>Please enter your details to register</b>:\
              </td>\
             </tr>\
@@ -85,16 +81,16 @@ var lwsgs_html = '\
            </tr>\
            <tr>\
             <td colspan=2 align=center>\
-<input type="submit" id="register" name="register" value="Register" style="margin: 2px; padding: 2px">\
-<input type="submit" id="rforgot" name="forgot" value="Forgot Password" style="margin: 2px; padding: 2px;display: none">\
-<input type="button" id="cancel" name="cancel" value="Cancel" style="margin: 2px; padding: 2px;">\
+<input type="submit" id="register" name="register" value="Register" >\
+<input type="submit" id="rforgot" name="forgot" value="Forgot Password" class="hidden">\
+<input type="button" id="cancel" name="cancel" value="Cancel">\
             </td>\
            </tr>\
          </table>\
         </form>\
        </div>\
        \
-       <div id="dchange" style="display:none">\
+       <div id="dchange" class="hidden">\
         <form action="lwsgs-change" method="post">\
          <input type="hidden" id="cusername" name="username">\
          <input type="hidden" name="admin" value="needadmin/admin-login.html">\
@@ -106,16 +102,10 @@ var lwsgs_html = '\
          <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
          <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
          <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
-         <table style="vertical-align:top;text-align:left">\
+         <table class="l">\
             <tr>\
              <td colspan=2 align=center>\
                <span id="ccuruser"></span>\
-       <script nonce="lwscaro">\
-               if (lwsgs_user)\
-                       document.getElementById("ccuruser").innerHTML =\
-                         "<span class=\"gstitle\">Login settings for " +\
-                         lwsgs_san(lwsgs_user) + "</span></br>";\
-       </script>\
               <b>Please enter your details to change</b>:\
              </td>\
             </tr>\
@@ -144,11 +134,11 @@ var lwsgs_html = '\
            <tr>\
             <td colspan=2 align=center>\
              <input type="submit" id="change" name="change"\
-              value="Change" style="margin: 6px; padding: 6px">\
+              value="Change" class="wide">\
              <input type="submit" id="cforgot" name="forgot"\
-              value="Forgot Password" style="margin: 6px; padding: 6px;display: none">\
+              value="Forgot Password" class="wide hidden">\
              <input type="button" id="cancel2" name="cancel"\
-              value="Cancel" style="margin: 6px; padding: 6px;">\
+              value="Cancel" class="wide">\
             </td>\
            </tr>\
            <tr>\
@@ -156,14 +146,14 @@ var lwsgs_html = '\
              <input type="checkbox" id="showdel" name="showdel"\
               > Show Delete&nbsp;\
              <input type="submit" id="delete" name="delete" \
-              value="Delete Account" style="margin: 6px; padding: 6px;display: none">\
+              value="Delete Account" class="wide hidden">\
             </td>\
            </tr>\
          </table>\
         </form>\
        </div>\
        \
-       <div id="dadmin" style="display:none">\
+       <div id="dadmin" class="hidden">\
          Admin settings TBD\
        </div>\
 ';
@@ -260,7 +250,7 @@ function lwsgs_rupdate()
            document.getElementById('password2').value) {
                if (document.getElementById('rpassword').value.length)
                        document.getElementById('match').innerHTML = 
-                               "<b style=\"color:green\">\u2713</b>";
+                               "<b class=\"green\">\u2713</b>";
                else
                        document.getElementById('match').innerHTML = "";
                document.getElementById('pw2').style = "";
@@ -268,10 +258,10 @@ function lwsgs_rupdate()
                if (document.getElementById('password2').value ||
                    document.getElementById('email').value) { // ie, he is filling in "register" path and cares
                        document.getElementById('match').innerHTML =
-                               "<span style=\"color: red\">\u2718 <b>Passwords do not match</b></span>";
+                               "<span class=\"bad\">\u2718 <b>Passwords do not match</b></span>";
                } else
                        document.getElementById('match').innerHTML =
-                               "<span style=\"color: gray\">\u2718 Passwords do not match</span>";
+                               "<span class=\"bad\">\u2718 Passwords do not match</span>";
 
                en_register = 0;
        }
@@ -282,7 +272,7 @@ function lwsgs_rupdate()
                document.getElementById('rpw1').innerHTML = "Need 8 chars";
        } else
                if (document.getElementById('rpassword').value.length)
-                       document.getElementById('rpw1').innerHTML = "<b style=\"color:green\">\u2713</b>";
+                       document.getElementById('rpw1').innerHTML = "<b class=\"green\">\u2713</b>";
                else
                        document.getElementById('rpw1').innerHTML = "";
 
@@ -304,13 +294,13 @@ function lwsgs_rupdate()
 
                if (uc) {
                        if (document.getElementById('rusername').value)
-                               uc.innerHTML = "<b style=\"color:green\">\u2713</b>";
+                               uc.innerHTML = "<b class=\"green\">\u2713</b>";
                        else
                                uc.innerHTML = "";
                }
        } else {
                if (document.getElementById('uchk'))
-                       ocument.getElementById('uchk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>";
+                       ocument.getElementById('uchk').innerHTML = "<b class=\"red\">\u2718 Already registered</b>";
                en_forgot = 1;
        }
 
@@ -319,13 +309,13 @@ function lwsgs_rupdate()
 
                if (ec) {
                        if (document.getElementById('email').value)
-                               ec.innerHTML = "<b style=\"color:green\">\u2713</b>";
+                               ec.innerHTML = "<b class=\"green\">\u2713</b>";
                        else
                                ec.innerHTML = "";
                }
        } else {
                if (document.getElementById('echk'))
-                       document.getElementById('echk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>";
+                       document.getElementById('echk').innerHTML = "<b class=\"red\">\u2718 Already registered</b>";
                en_forgot = 1;
        }
 
@@ -355,7 +345,7 @@ function lwsgs_cupdate()
                    document.getElementById('ccurpw').value.length < 8) {
                        en_change = 0;
                        pwok = 0;
-                       document.getElementById('cuchk').innerHTML = "<b style=\"color:red\">\u2718</b>";
+                       document.getElementById('cuchk').innerHTML = "<b class=\"red\">\u2718</b>";
                } else {
                        en_forgot = 0;
                        document.getElementById('cuchk').innerHTML = "";
@@ -367,7 +357,7 @@ function lwsgs_cupdate()
        if (document.getElementById('cpassword').value ==
            document.getElementById('cpassword2').value) {
                if (document.getElementById('cpassword').value.length)
-                       document.getElementById('cmatch').innerHTML = "<b style=\"color:green\">\u2713</b>";
+                       document.getElementById('cmatch').innerHTML = "<b class=\"green\">\u2713</b>";
                else
                        document.getElementById('cmatch').innerHTML = "";
                document.getElementById('pw2').style = "";
@@ -376,9 +366,9 @@ function lwsgs_cupdate()
                    //document.getElementById('cemail').value
                ) { // ie, he is filling in "register" path and cares
                        document.getElementById('cmatch').innerHTML =
-                               "<span style=\"color: red\">\u2718 <b>Passwords do not match</b></span>";
+                               "<span class=\"red\">\u2718 <b>Passwords do not match</b></span>";
                } else
-                       document.getElementById('cmatch').innerHTML = "<span style=\"color: gray\">\u2718 Passwords do not match</span>";
+                       document.getElementById('cmatch').innerHTML = "<span class=\"red\">\u2718 Passwords do not match</span>";
 
                en_change = 0;
        }
@@ -392,7 +382,7 @@ function lwsgs_cupdate()
 
                if (cpw) {
                        if (document.getElementById('cpassword').value.length)
-                               cpw.innerHTML = "<b style=\"color:green\">\u2713</b>";
+                               cpw.innerHTML = "<b class=\"green\">\u2713</b>";
                        else
                                cpw.innerHTML = "";
                }
@@ -400,7 +390,7 @@ function lwsgs_cupdate()
 
        if (!document.getElementById('cpassword').value ||
            !document.getElementById('cpassword2').value ||
-           pwok == 0)
+           pwok === 0)
                en_change = 0;
        
        if (document.getElementById('showdel').checked)
@@ -442,7 +432,7 @@ function lwsgs_cupdate()
        else
                document.getElementById('cforgot').style.display = "none";
 
-       if (pwok == 0)
+       if (pwok === 0)
                op = '0.5';
        else
                op = '1.0';
@@ -455,7 +445,7 @@ function lwsgs_check_user()
 {
     var xmlHttp = new XMLHttpRequest();
     xmlHttp.onreadystatechange = function() { 
-        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
+        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
             lwsgs_user_check = xmlHttp.responseText;
            lwsgs_rupdate();
         }
@@ -468,7 +458,7 @@ function lwsgs_check_email(id)
 {
     var xmlHttp = new XMLHttpRequest();
     xmlHttp.onreadystatechange = function() { 
-        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
+        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
             lwsgs_email_check = xmlHttp.responseText;
            lwsgs_rupdate();
         }
@@ -500,6 +490,15 @@ function lwsgs_initial()
 {
        document.getElementById('lwsgs').innerHTML = lwsgs_html;
 
+       if (lwsgs_user) {
+               document.getElementById("curuser").innerHTML =
+                       "currently logged in as " + lwsgs_san(lwsgs_user) + "</br>";
+
+               document.getElementById("ccuruser").innerHTML =
+                 "<span class=\"gstitle\">Login settings for " +
+                 lwsgs_san(lwsgs_user) + "</span></br>";
+       }
+
        document.getElementById('username').oninput = lwsgs_update;
        document.getElementById('username').onchange = lwsgs_update;
        document.getElementById('password').oninput = lwsgs_update;
@@ -528,3 +527,105 @@ function lwsgs_initial()
        lwsgs_update();
        lwsgs_cupdate();
 }
+
+window.addEventListener("load", function() {
+       lwsgs_initial();
+       document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline-block";
+       document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline-block";
+
+       document.getElementById("msg").onkeyup = mupd;
+       document.getElementById("msg").onchange = mupd;
+
+       var ws;
+
+       function mb_format(s)
+       {
+               var r = "", n, wos = 0;
+               
+               for (n = 0; n < s.length; n++) {
+                       if (s[n] == ' ')
+                               wos = 0;
+                       else {
+                               wos++;
+                               if (wos === 40) {
+                                       wos = 0;
+                                       r = r + ' ';
+                               }
+                       }
+                       if (s[n] == '<') {
+                               r = r + "&lt;";
+                               continue;
+                       }
+                       if (s[n] == '\n') {
+                               r = r + "<br>";
+                               continue;
+                       }
+                               
+                       r = r + s[n];
+               }
+               
+               return r;
+       }
+
+       function add_div(n, m)
+       {
+               var q = document.getElementById(n);
+               var d = new Date(m.time * 1000);
+               
+               q.innerHTML = "<br><div class=\"group2\"><table class=\"fixed\"><tr><td>" +
+                       "<img src=\"https://www.gravatar.com/avatar/" + md5(m.email) +
+                       "?d=identicon\"><br>" +
+                       "<b>" + lwsgs_san(m.username) + "</b><br>" +
+                       "<span class=\"small\">" + d.toDateString() +
+                         "<br>" + d.toTimeString() + "</span><br>" +
+                       "IP: " + lwsgs_san(m.ip) +
+                       "</td><td class=\"ava\"><span>" +
+                       mb_format(m.content) +
+                       "</span></td></tr></table></div><br>" + q.innerHTML;
+       }
+
+       function get_appropriate_ws_url()
+       {
+               var pcol;
+               var u = document.URL;
+
+               if (u.substring(0, 5) == "https") {
+                       pcol = "wss://";
+                       u = u.substr(8);
+               } else {
+                       pcol = "ws://";
+                       if (u.substring(0, 4) == "http")
+                               u = u.substr(7);
+               }
+               u = u.split('/');
+
+               return pcol + u[0] + "/xxx";
+       }
+
+       if (lwsgs_user) {
+               if (typeof MozWebSocket != "undefined")
+                       ws = new MozWebSocket(get_appropriate_ws_url(),
+                                          "protocol-lws-messageboard");
+               else
+                       ws = new WebSocket(get_appropriate_ws_url(),
+                                          "protocol-lws-messageboard");
+
+               try {
+                       ws.onopen = function() {
+                               document.getElementById("debug").textContent = "ws opened";
+                       }
+                       ws.onmessage =function got_packet(msg) {
+                               add_div("messages", JSON.parse(msg.data));
+                       }
+                       ws.onclose = function(){
+                       }
+               } catch(exception) {
+                       alert('<p>Error' + exception);  
+               }
+       }
+
+       function mupd()
+       {
+               document.getElementById("send").disabled = !document.getElementById("msg").value;
+       }
+}, false);
index 88494dc..d4fd35a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for "generic sessions"
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 
 #include "private-lwsgs.h"
 
+static int
+lwsgs_smtp_client_done(struct lws_smtp_email *e, void *buf, size_t len)
+{
+       free(e);
+
+       return 0;
+}
+
+static int
+lwsgs_smtp_client_done_sentvfy(struct lws_smtp_email *e, void *buf, size_t len)
+{
+       struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)e->data;
+       const char *username = (const char *)e->extra;
+       char s[200], esc[96];
+
+       lwsl_notice("%s: registration email sent: %s\n", __func__, username);
+
+       /* mark the user as having sent the verification email */
+       lws_snprintf(s, sizeof(s) - 1,
+                "update users set verified=1 where username='%s' and verified==0;",
+                lws_sql_purify(esc, username, sizeof(esc) - 1));
+       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
+               lwsl_err("%s: Unable to update user: %s\n", __func__,
+                        sqlite3_errmsg(vhd->pdb));
+               return 1;
+       }
+
+       free(e);
+
+       return 0;
+}
+
 /* handle account confirmation links */
 
 int
 lwsgs_handler_confirm(struct per_vhost_data__gs *vhd, struct lws *wsi,
                      struct per_session_data__gs *pss)
 {
-       char cookie[1024], s[256], esc[50];
+       char cookie[1024], s[256], esc[90];
        struct lws_gs_event_args a;
        struct lwsgs_user u;
 
        if (lws_hdr_copy_fragment(wsi, cookie, sizeof(cookie),
-                                 WSI_TOKEN_HTTP_URI_ARGS, 0) < 0)
+                                 WSI_TOKEN_HTTP_URI_ARGS, 0) < 0) {
+               lwsl_err("%s: missing URI_ARGS\n", __func__);
                goto verf_fail;
+       }
 
-       if (strncmp(cookie, "token=", 6))
+       if (strncmp(cookie, "token=", 6)) {
+               lwsl_err("%s: missing URI_ARGS token=\n", __func__);
                goto verf_fail;
+       }
 
        u.username[0] = '\0';
+       u.verified = -1;
        lws_snprintf(s, sizeof(s) - 1,
                 "select username,email,verified from users where token = '%s';",
                 lws_sql_purify(esc, &cookie[6], sizeof(esc) - 1));
+       puts(s);
        if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) !=
            SQLITE_OK) {
                lwsl_err("Unable to lookup token: %s\n",
@@ -50,7 +88,8 @@ lwsgs_handler_confirm(struct per_vhost_data__gs *vhd, struct lws *wsi,
        }
 
        if (!u.username[0] || u.verified != 1) {
-               lwsl_notice("verify token doesn't map to unverified user\n");
+               lwsl_notice("verify token %s doesn't map to unverified user (user='%s', verified=%d)\n",
+                               &cookie[6], u.username, u.verified);
                goto verf_fail;
        }
 
@@ -72,7 +111,8 @@ lwsgs_handler_confirm(struct per_vhost_data__gs *vhd, struct lws *wsi,
        a.event = LWSGSE_CREATED;
        a.username = u.username;
        a.email = u.email;
-       lws_callback_vhost_protocols(wsi, LWS_CALLBACK_GS_EVENT, &a, 0);
+       lws_callback_vhost_protocols_vhost(lws_get_vhost(wsi),
+                                          LWS_CALLBACK_GS_EVENT, &a, 0);
 
        lws_snprintf(pss->onward, sizeof(pss->onward),
                 "%s/post-verify-ok.html", vhd->email_confirm_url);
@@ -110,7 +150,7 @@ int
 lwsgs_handler_forgot(struct per_vhost_data__gs *vhd, struct lws *wsi,
                     struct per_session_data__gs *pss)
 {
-       char cookie[1024], s[256], esc[50];
+       char cookie[1024], s[256], esc[96];
        struct lwsgs_user u;
        const char *a;
 
@@ -195,26 +235,24 @@ forgot_fail:
 
 int
 lwsgs_handler_check(struct per_vhost_data__gs *vhd,
-                   struct lws *wsi, struct per_session_data__gs *pss)
+                   struct lws *wsi, struct per_session_data__gs *pss,
+                   const char *in)
 {
        static const char * const colname[] = { "username", "email" };
-       char cookie[1024], s[256], esc[50], *pc;
-       unsigned char *p, *start, *end, buffer[LWS_PRE + 256];
+       char s[256], esc[96], *pc;
+       unsigned char *p, *start, *end, buffer[LWS_PRE + 1024];
        struct lwsgs_user u;
        int n;
 
        /*
-        * either /check?email=xxx@yyy   or: /check?username=xxx
+        * either /check/email=xxx@yyy   or: /check/username=xxx
         * returns '0' if not already registered, else '1'
         */
 
        u.username[0] = '\0';
-       if (lws_hdr_copy_fragment(wsi, cookie, sizeof(cookie),
-                                 WSI_TOKEN_HTTP_URI_ARGS, 0) < 0)
-               goto reply;
 
-       n = !strncmp(cookie, "email=", 6);
-       pc = strchr(cookie, '=');
+       n = !strncmp(in, "email=", 6);
+       pc = strchr(in, '=');
        if (!pc) {
                lwsl_notice("cookie has no =\n");
                goto reply;
@@ -261,9 +299,11 @@ reply:
                lwsl_err("_write returned %d from %ld\n", n, (long)(p - start));
                return -1;
        }
-       n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP);
-       if (n != 1)
-               return -1;
+
+       pss->check_response_value = s[0];
+       pss->check_response = 1;
+
+       lws_callback_on_writable(wsi);
 
        return 0;
 }
@@ -274,7 +314,7 @@ int
 lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi,
                              struct per_session_data__gs *pss)
 {
-       char s[256], esc[50], username[50];
+       char s[256], esc[96], username[96];
        struct lwsgs_user u;
        lwsgw_hash sid;
        int n = 0;
@@ -289,7 +329,7 @@ lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi,
                                return 1;
 
                        /* did a forgot pw ? */
-                       if (u.last_forgot_validated > lws_now_secs() - 300) {
+                       if (u.last_forgot_validated > (time_t)lws_now_secs() - 300) {
                                n |= LWSGS_AUTH_FORGOT_FLOW;
                                lwsl_debug("within forgot password flow\n");
                        }
@@ -311,8 +351,8 @@ lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi,
 
                lwsl_debug("current pw checks out\n");
 
-               strncpy(u.username, lws_spa_get_string(pss->spa, FGS_USERNAME), sizeof(u.username) - 1);
-               u.username[sizeof(u.username) - 1] = '\0';
+               lws_strncpy(u.username, lws_spa_get_string(pss->spa, FGS_USERNAME),
+                           sizeof(u.username));
        }
 
        /* does he want to delete his account? */
@@ -325,7 +365,8 @@ lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi,
                a.event = LWSGSE_DELETED;
                a.username = u.username;
                a.email = "";
-               lws_callback_vhost_protocols(wsi, LWS_CALLBACK_GS_EVENT, &a, 0);
+               lws_callback_vhost_protocols_vhost(lws_get_vhost(wsi),
+                                                  LWS_CALLBACK_GS_EVENT, &a, 0);
 
                lws_snprintf(s, sizeof(s) - 1,
                         "delete from users where username='%s';"
@@ -358,15 +399,14 @@ sql:
 
 int
 lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd,
-                            struct lws *wsi,
-                            struct per_session_data__gs *pss)
+                            struct lws *wsi, struct per_session_data__gs *pss)
 {
+       char esc[96], esc1[96], esc2[96], esc3[96], esc4[96];
        char s[LWSGS_EMAIL_CONTENT_SIZE];
-       unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE];
-       char esc[50], esc1[50], esc2[50], esc3[50], esc4[50];
+       unsigned char sid_rand[32];
+       lws_smtp_email_t *em;
        struct lwsgs_user u;
        lwsgw_hash hash;
-       unsigned char sid_rand[20];
        int n;
 
        lwsl_notice("FORGOT %s %s\n",
@@ -420,7 +460,19 @@ lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd,
                lwsl_err("Problem getting random for token\n");
                return 1;
        }
-       sha1_to_lwsgw_hash(sid_rand, &hash);
+       sha256_to_lwsgw_hash(sid_rand, &hash);
+
+       lws_snprintf(s, sizeof(s) - 1,
+                "update users set token='%s',token_time='%ld' where username='%s';",
+                hash.id, (long)lws_now_secs(),
+                lws_sql_purify(esc, u.username, sizeof(esc) - 1));
+       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) !=
+           SQLITE_OK) {
+               lwsl_err("Unable to set token: %s\n",
+                        sqlite3_errmsg(vhd->pdb));
+               return 1;
+       }
+
        n = lws_snprintf(s, sizeof(s),
                "From: Forgot Password Assistant Noreply <%s>\n"
                "To: %s <%s>\n"
@@ -429,12 +481,12 @@ lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd,
                  "Hello, %s\n\n"
                  "We received a password reset request from IP %s for this email,\n"
                  "to confirm you want to do that, please click the link below.\n\n",
-               lws_sql_purify(esc, vhd->email.email_from, sizeof(esc) - 1),
+               lws_sql_purify(esc, vhd->email_from, sizeof(esc) - 1),
                lws_sql_purify(esc1, u.username, sizeof(esc1) - 1),
                lws_sql_purify(esc2, u.email, sizeof(esc2) - 1),
                lws_sql_purify(esc3, u.username, sizeof(esc3) - 1),
                lws_sql_purify(esc4, pss->ip, sizeof(esc4) - 1));
-       lws_snprintf(s + n, sizeof(s) -n,
+       n += lws_snprintf(s + n, sizeof(s) - n,
                  "%s/lwsgs-forgot?token=%s"
                   "&good=%s"
                   "&bad=%s\n\n"
@@ -453,27 +505,15 @@ lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd,
                              sizeof(esc3) - 1),
                vhd->email_contact_person);
 
-       lws_snprintf((char *)buffer, sizeof(buffer) - 1,
-                "insert into email(username, content)"
-                " values ('%s', '%s');",
-               lws_sql_purify(esc, u.username, sizeof(esc) - 1), s);
-       if (sqlite3_exec(vhd->pdb, (char *)buffer, NULL,
-                        NULL, NULL) != SQLITE_OK) {
-               lwsl_err("Unable to insert email: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
+       puts(s);
 
-       lws_snprintf(s, sizeof(s) - 1,
-                "update users set token='%s',token_time='%ld' where username='%s';",
-                hash.id, (long)lws_now_secs(),
-                lws_sql_purify(esc, u.username, sizeof(esc) - 1));
-       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) !=
-           SQLITE_OK) {
-               lwsl_err("Unable to set token: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
+       em = lws_smtp_client_alloc_email_helper(s, n, vhd->email_from, u.email,
+                                               u.username, strlen(u.username),
+                                               vhd, lwsgs_smtp_client_done);
+       if (!em)
+               return 1;
+       if (lws_smtp_client_add_email(vhd->smtp_client, em))
                return 1;
-       }
 
        return 0;
 }
@@ -484,11 +524,13 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd,
                             struct per_session_data__gs *pss)
 {
        unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE];
-       char esc[50], esc1[50], esc2[50], esc3[50], esc4[50];
+       char esc[96], esc1[96], esc2[96], esc3[96], esc4[96];
        char s[LWSGS_EMAIL_CONTENT_SIZE];
-       unsigned char sid_rand[20];
+       unsigned char sid_rand[32];
+       lws_smtp_email_t *em;
        struct lwsgs_user u;
        lwsgw_hash hash;
+       size_t n;
 
        lwsl_notice("REGISTER %s %s %s\n",
                        lws_spa_get_string(pss->spa, FGS_USERNAME),
@@ -548,7 +590,7 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd,
                lwsl_err("Problem getting random for token\n");
                return 1;
        }
-       sha1_to_lwsgw_hash(sid_rand, &hash);
+       sha256_to_lwsgw_hash(sid_rand, &hash);
 
        lws_snprintf((char *)buffer, sizeof(buffer) - 1,
                 "insert into users(username,"
@@ -568,11 +610,11 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd,
                return 1;
        }
 
-       lws_snprintf(s, sizeof(s),
+       n = lws_snprintf(s, sizeof(s),
                "From: Noreply <%s>\n"
                "To: %s <%s>\n"
-                 "Subject: Registration verification\n"
-                 "\n"
+               "Subject: Registration verification\n"
+               "\n"
                  "Hello, %s\n\n"
                  "We received a registration from IP %s using this email,\n"
                  "to confirm it is legitimate, please click the link below.\n\n"
@@ -583,7 +625,7 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd,
                "automated email, you can contact a real person at\n"
                "%s.\n"
                "\n.\n",
-               lws_sql_purify(esc, vhd->email.email_from, sizeof(esc) - 1),
+               lws_sql_purify(esc, vhd->email_from, sizeof(esc) - 1),
                lws_sql_purify(esc1, lws_spa_get_string(pss->spa, FGS_USERNAME), sizeof(esc1) - 1),
                lws_sql_purify(esc2, lws_spa_get_string(pss->spa, FGS_EMAIL), sizeof(esc2) - 1),
                lws_sql_purify(esc3, lws_spa_get_string(pss->spa, FGS_USERNAME), sizeof(esc3) - 1),
@@ -591,16 +633,16 @@ lwsgs_handler_register_form(struct per_vhost_data__gs *vhd,
                vhd->email_confirm_url, hash.id,
                vhd->email_contact_person);
 
-       lws_snprintf((char *)buffer, sizeof(buffer) - 1,
-                "insert into email(username, content) values ('%s', '%s');",
-               lws_sql_purify(esc, lws_spa_get_string(pss->spa, FGS_USERNAME),
-                              sizeof(esc) - 1), s);
+       em = lws_smtp_client_alloc_email_helper(s, n, vhd->email_from,
+                               lws_spa_get_string(pss->spa, FGS_EMAIL),
+                               lws_spa_get_string(pss->spa, FGS_USERNAME),
+                               strlen(lws_spa_get_string(pss->spa, FGS_USERNAME)),
+                               vhd, lwsgs_smtp_client_done_sentvfy);
+       if (!em)
+               return 1;
 
-       if (sqlite3_exec(vhd->pdb, (char *)buffer, NULL, NULL, NULL) != SQLITE_OK) {
-               lwsl_err("Unable to insert email: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
+       if (lws_smtp_client_add_email(vhd->smtp_client, em))
                return 1;
-       }
 
        return 0;
 }
index 68d1d45..841ed1a 100644 (file)
@@ -21,7 +21,7 @@
 
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 
 #include <sqlite3.h>
 #include <string.h>
@@ -62,24 +62,28 @@ struct lwsgs_user {
 };
 
 struct per_vhost_data__gs {
-       struct lws_email email;
+       lws_abs_t *smtp_client;
        struct lwsgs_user u;
+       lws_token_map_t transport_tokens[3];
+       lws_token_map_t protocol_tokens[2];
+       char helo[64], ip[64];
        struct lws_context *context;
        char session_db[256];
        char admin_user[32];
+       char urlroot[48];
        char confounder[32];
        char email_contact_person[128];
        char email_title[128];
        char email_template[128];
        char email_confirm_url[128];
-       lwsgw_hash admin_password_sha1;
+       char email_from[128];
+       lwsgw_hash admin_password_sha256;
        sqlite3 *pdb;
        int timeout_idle_secs;
        int timeout_absolute_secs;
        int timeout_anon_absolute_secs;
        int timeout_email_secs;
        time_t last_session_expire;
-       char email_inited;
 };
 
 struct per_session_data__gs {
@@ -94,8 +98,10 @@ struct per_session_data__gs {
        char ip[46];
        struct lws_process_html_state phs;
        int spos;
+       char check_response_value;
 
        unsigned int logging_out:1;
+       unsigned int check_response:1;
 };
 
 /* utils.c */
@@ -117,7 +123,7 @@ int
 lwsgs_check_credentials(struct per_vhost_data__gs *vhd,
                        const char *username, const char *password);
 void
-sha1_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash);
+sha256_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash);
 unsigned int
 lwsgs_now_secs(void);
 int
@@ -149,7 +155,7 @@ lwsgs_handler_forgot(struct per_vhost_data__gs *vhd, struct lws *wsi,
                     struct per_session_data__gs *pss);
 int
 lwsgs_handler_check(struct per_vhost_data__gs *vhd, struct lws *wsi,
-                     struct per_session_data__gs *pss);
+                     struct per_session_data__gs *pss, const char *in);
 int
 lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi,
                              struct per_session_data__gs *pss);
index 521b539..3888820 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for "generic sessions"
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -20,6 +20,7 @@
  */
 
 #include "private-lwsgs.h"
+#include <stdlib.h>
 
 /* keep changes in sync with the enum in lwsgs.h */
 static const char * const param_names[] = {
@@ -50,139 +51,6 @@ struct lwsgs_fill_args {
 
 static const struct lws_protocols protocols[];
 
-static int
-lwsgs_lookup_callback_email(void *priv, int cols, char **col_val,
-                           char **col_name)
-{
-       struct lwsgs_fill_args *a = (struct lwsgs_fill_args *)priv;
-       int n;
-
-       for (n = 0; n < cols; n++) {
-               if (!strcmp(col_name[n], "content")) {
-                       strncpy(a->buf, col_val[n], a->len - 1);
-                       a->buf[a->len - 1] = '\0';
-                       continue;
-               }
-       }
-       return 0;
-}
-
-static int
-lwsgs_email_cb_get_body(struct lws_email *email, char *buf, int len)
-{
-       struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)email->data;
-       struct lwsgs_fill_args a;
-       char ss[150], esc[50];
-
-       a.buf = buf;
-       a.len = len;
-
-       lws_snprintf(ss, sizeof(ss) - 1,
-                "select content from email where username='%s';",
-                lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1));
-
-       strncpy(buf, "failed", len);
-       if (sqlite3_exec(vhd->pdb, ss, lwsgs_lookup_callback_email, &a,
-                        NULL) != SQLITE_OK) {
-               lwsl_err("Unable to lookup email: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
-
-               return 1;
-       }
-
-       return 0;
-}
-
-static int
-lwsgs_email_cb_sent(struct lws_email *email)
-{
-       struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)email->data;
-       char s[200], esc[50];
-
-       /* mark the user as having sent the verification email */
-       lws_snprintf(s, sizeof(s) - 1,
-                "update users set verified=1 where username='%s' and verified==0;",
-                lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1));
-       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
-               lwsl_err("%s: Unable to update user: %s\n", __func__,
-                        sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
-
-       lws_snprintf(s, sizeof(s) - 1,
-                "delete from email where username='%s';",
-                lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1));
-       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
-               lwsl_err("%s: Unable to delete email text: %s\n", __func__,
-                        sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
-
-       return 0;
-}
-
-static int
-lwsgs_email_cb_on_next(struct lws_email *email)
-{
-       struct per_vhost_data__gs *vhd = lws_container_of(email,
-                       struct per_vhost_data__gs, email);
-       char s[LWSGS_EMAIL_CONTENT_SIZE], esc[50];
-       time_t now = lws_now_secs();
-
-       /*
-        * users not verified in 24h get deleted
-        */
-       lws_snprintf(s, sizeof(s) - 1, "delete from users where ((verified != %d)"
-                " and (creation_time <= %lu));", LWSGS_VERIFIED_ACCEPTED,
-                (unsigned long)now - vhd->timeout_email_secs);
-       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
-               lwsl_err("Unable to expire users: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
-
-       lws_snprintf(s, sizeof(s) - 1, "update users set token_time=0 where "
-                "(token_time <= %lu);",
-                (unsigned long)now - vhd->timeout_email_secs);
-       if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
-               lwsl_err("Unable to expire users: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
-
-       vhd->u.username[0] = '\0';
-       lws_snprintf(s, sizeof(s) - 1, "select username from email limit 1;");
-       if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &vhd->u,
-                        NULL) != SQLITE_OK) {
-               lwsl_err("Unable to lookup user: %s\n", sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
-
-       lws_snprintf(s, sizeof(s) - 1,
-                "select username, creation_time, email, ip, verified, token"
-                " from users where username='%s' limit 1;",
-                lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1));
-       if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &vhd->u,
-                        NULL) != SQLITE_OK) {
-               lwsl_err("Unable to lookup user: %s\n",
-                        sqlite3_errmsg(vhd->pdb));
-               return 1;
-       }
-
-       if (!vhd->u.username[0])
-               /*
-                * nothing to do, we are idle and no suitable
-                * accounts waiting for verification.  When a new user
-                * is added we will get kicked to try again.
-                */
-               return 1;
-
-       strncpy(email->email_to, vhd->u.email, sizeof(email->email_to) - 1);
-
-       return 0;
-}
-
-
 struct lwsgs_subst_args
 {
        struct per_session_data__gs *pss;
@@ -196,7 +64,7 @@ lwsgs_subst(void *data, int index)
        struct lwsgs_subst_args *a = (struct lwsgs_subst_args *)data;
        struct lwsgs_user u;
        lwsgw_hash sid;
-       char esc[50], s[100];
+       char esc[96], s[100];
        int n;
 
        a->pss->result[0] = '\0';
@@ -220,7 +88,7 @@ lwsgs_subst(void *data, int index)
        } else
                lwsl_notice("no sid\n");
 
-       strncpy(a->pss->result + 32, u.email, 100);
+       lws_strncpy(a->pss->result + 32, u.email, 100);
 
        switch (index) {
        case 0:
@@ -238,6 +106,21 @@ lwsgs_subst(void *data, int index)
 }
 
 static int
+lws_get_effective_host(struct lws *wsi, char *buf, size_t buflen)
+{
+       /* h2 */
+       if (lws_hdr_copy(wsi, buf, buflen - 1,
+                        WSI_TOKEN_HTTP_COLON_AUTHORITY) > 0)
+               return 0;
+
+       /* h1 */
+       if (lws_hdr_copy(wsi, buf, buflen - 1,  WSI_TOKEN_HOST) > 0)
+               return 0;
+
+       return 1;
+}
+
+static int
 callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                          void *user, void *in, size_t len)
 {
@@ -245,23 +128,25 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
        const struct lws_protocol_vhost_options *pvo;
        struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)
                        lws_protocol_vh_priv_get(lws_get_vhost(wsi),
-                                       &protocols[0]);
+                               lws_vhost_name_to_protocol(lws_get_vhost(wsi),
+                                               "protocol-generic-sessions"));
        char cookie[1024], username[32], *pc = cookie;
        unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE];
-       struct lws_process_html_args *args;
+       struct lws_process_html_args *args = in;
        struct lws_session_info *sinfo;
        char s[LWSGS_EMAIL_CONTENT_SIZE];
        unsigned char *p, *start, *end;
+       const char *cp, *cp1;
        sqlite3_stmt *sm;
        lwsgw_hash sid;
-       const char *cp;
+       lws_abs_t abs;
        int n;
 
        switch (reason) {
        case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
 
                vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
-                       &protocols[0], sizeof(struct per_vhost_data__gs));
+                       lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs));
                if (!vhd)
                        return 1;
                vhd->context = lws_get_context(wsi);
@@ -271,51 +156,50 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                vhd->timeout_absolute_secs = 36000;
                vhd->timeout_anon_absolute_secs = 1200;
                vhd->timeout_email_secs = 24 * 3600;
-               strcpy(vhd->email.email_helo, "unconfigured.com");
-               strcpy(vhd->email.email_from, "noreply@unconfigured.com");
-               strcpy(vhd->email_title, "Registration Email from unconfigured");
-               strcpy(vhd->email.email_smtp_ip, "127.0.0.1");
 
-               vhd->email.on_next = lwsgs_email_cb_on_next;
-               vhd->email.on_get_body = lwsgs_email_cb_get_body;
-               vhd->email.on_sent = lwsgs_email_cb_sent;
-               vhd->email.data = (void *)vhd;
+
+               strcpy(vhd->helo, "unconfigured.com");
+               strcpy(vhd->ip, "127.0.0.1");
+               strcpy(vhd->email_from, "noreply@unconfigured.com");
+               strcpy(vhd->email_title, "Registration Email from unconfigured");
+               vhd->urlroot[0] = '\0';
 
                pvo = (const struct lws_protocol_vhost_options *)in;
                while (pvo) {
                        if (!strcmp(pvo->name, "admin-user"))
-                               strncpy(vhd->admin_user, pvo->value,
-                                       sizeof(vhd->admin_user) - 1);
-                       if (!strcmp(pvo->name, "admin-password-sha1"))
-                               strncpy(vhd->admin_password_sha1.id, pvo->value,
-                                       sizeof(vhd->admin_password_sha1.id) - 1);
+                               lws_strncpy(vhd->admin_user, pvo->value,
+                                       sizeof(vhd->admin_user));
+                       if (!strcmp(pvo->name, "urlroot"))
+                               lws_strncpy(vhd->urlroot, pvo->value,
+                                       sizeof(vhd->urlroot));
+                       if (!strcmp(pvo->name, "admin-password-sha256"))
+                               lws_strncpy(vhd->admin_password_sha256.id, pvo->value,
+                                       sizeof(vhd->admin_password_sha256.id));
                        if (!strcmp(pvo->name, "session-db"))
-                               strncpy(vhd->session_db, pvo->value,
-                                       sizeof(vhd->session_db) - 1);
+                               lws_strncpy(vhd->session_db, pvo->value,
+                                       sizeof(vhd->session_db));
                        if (!strcmp(pvo->name, "confounder"))
-                               strncpy(vhd->confounder, pvo->value,
-                                       sizeof(vhd->confounder) - 1);
+                               lws_strncpy(vhd->confounder, pvo->value,
+                                       sizeof(vhd->confounder));
                        if (!strcmp(pvo->name, "email-from"))
-                               strncpy(vhd->email.email_from, pvo->value,
-                                       sizeof(vhd->email.email_from) - 1);
+                               lws_strncpy(vhd->email_from, pvo->value,
+                                       sizeof(vhd->email_from));
                        if (!strcmp(pvo->name, "email-helo"))
-                               strncpy(vhd->email.email_helo, pvo->value,
-                                       sizeof(vhd->email.email_helo) - 1);
+                               lws_strncpy(vhd->helo, pvo->value, sizeof(vhd->helo));
                        if (!strcmp(pvo->name, "email-template"))
-                               strncpy(vhd->email_template, pvo->value,
-                                       sizeof(vhd->email_template) - 1);
+                               lws_strncpy(vhd->email_template, pvo->value,
+                                       sizeof(vhd->email_template));
                        if (!strcmp(pvo->name, "email-title"))
-                               strncpy(vhd->email_title, pvo->value,
-                                       sizeof(vhd->email_title) - 1);
+                               lws_strncpy(vhd->email_title, pvo->value,
+                                       sizeof(vhd->email_title));
                        if (!strcmp(pvo->name, "email-contact-person"))
-                               strncpy(vhd->email_contact_person, pvo->value,
-                                       sizeof(vhd->email_contact_person) - 1);
+                               lws_strncpy(vhd->email_contact_person, pvo->value,
+                                       sizeof(vhd->email_contact_person));
                        if (!strcmp(pvo->name, "email-confirm-url-base"))
-                               strncpy(vhd->email_confirm_url, pvo->value,
-                                       sizeof(vhd->email_confirm_url) - 1);
+                               lws_strncpy(vhd->email_confirm_url, pvo->value,
+                                       sizeof(vhd->email_confirm_url));
                        if (!strcmp(pvo->name, "email-server-ip"))
-                               strncpy(vhd->email.email_smtp_ip, pvo->value,
-                                       sizeof(vhd->email.email_smtp_ip) - 1);
+                               lws_strncpy(vhd->ip, pvo->value, sizeof(vhd->ip));
 
                        if (!strcmp(pvo->name, "timeout-idle-secs"))
                                vhd->timeout_idle_secs = atoi(pvo->value);
@@ -328,18 +212,17 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                        pvo = pvo->next;
                }
                if (!vhd->admin_user[0] ||
-                   !vhd->admin_password_sha1.id[0] ||
+                   !vhd->admin_password_sha256.id[0] ||
                    !vhd->session_db[0]) {
                        lwsl_err("generic-sessions: "
                                 "You must give \"admin-user\", "
-                                "\"admin-password-sha1\", "
+                                "\"admin-password-sha256\", "
                                 "and \"session_db\" per-vhost options\n");
                        return 1;
                }
 
-               if (sqlite3_open_v2(vhd->session_db, &vhd->pdb,
-                                   SQLITE_OPEN_READWRITE |
-                                   SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
+               if (lws_struct_sq3_open(lws_get_context(wsi),
+                                       vhd->session_db, &vhd->pdb)) {
                        lwsl_err("Unable to open session db %s: %s\n",
                                 vhd->session_db, sqlite3_errmsg(vhd->pdb));
 
@@ -348,7 +231,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
 
                if (sqlite3_prepare(vhd->pdb,
                                    "create table if not exists sessions ("
-                                   " name char(40),"
+                                   " name char(65),"
                                    " username varchar(32),"
                                    " expire integer"
                                    ");",
@@ -373,10 +256,10 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                                 " creation_time integer,"
                                 " ip varchar(46),"
                                 " email varchar(100),"
-                                " pwhash varchar(42),"
-                                " pwsalt varchar(42),"
+                                " pwhash varchar(65),"
+                                " pwsalt varchar(65),"
                                 " pwchange_time integer,"
-                                " token varchar(42),"
+                                " token varchar(65),"
                                 " verified integer,"
                                 " token_time integer,"
                                 " last_forgot_validated integer,"
@@ -389,22 +272,38 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                        return 1;
                }
 
-               sprintf(s, "create table if not exists email ("
-                                " username varchar(32),"
-                                " content blob,"
-                                " primary key (username)"
-                                ");");
-               if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
-                       lwsl_err("Unable to create user table: %s\n",
-                                sqlite3_errmsg(vhd->pdb));
+               memset(&abs, 0, sizeof(abs));
+               abs.vh = lws_get_vhost(wsi);
+
+               /* select the protocol and bind its tokens */
 
+               abs.ap = lws_abs_protocol_get_by_name("smtp");
+               if (!abs.ap)
+                       return 1;
+
+               vhd->protocol_tokens[0].name_index = LTMI_PSMTP_V_HELO;
+               vhd->protocol_tokens[0].u.value = vhd->helo;
+
+               abs.ap_tokens = vhd->protocol_tokens;
+
+               /* select the transport and bind its tokens */
+
+               abs.at = lws_abs_transport_get_by_name("raw_skt");
+               if (!abs.at)
                        return 1;
-               }
 
-               lws_email_init(&vhd->email, lws_uv_getloop(vhd->context, 0),
-                               LWSGS_EMAIL_CONTENT_SIZE);
+               vhd->transport_tokens[0].name_index = LTMI_PEER_V_DNS_ADDRESS;
+               vhd->transport_tokens[0].u.value = vhd->ip;
+               vhd->transport_tokens[1].name_index = LTMI_PEER_LV_PORT;
+               vhd->transport_tokens[1].u.lvalue = 25;
 
-               vhd->email_inited = 1;
+               abs.at_tokens = vhd->transport_tokens;
+
+               vhd->smtp_client = lws_abs_bind_and_create_instance(&abs);
+               if (!vhd->smtp_client)
+                       return 1;
+
+               lwsl_notice("%s: created SMTP client\n", __func__);
                break;
 
        case LWS_CALLBACK_PROTOCOL_DESTROY:
@@ -413,71 +312,119 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                        sqlite3_close(vhd->pdb);
                        vhd->pdb = NULL;
                }
-               if (vhd->email_inited) {
-                       lws_email_destroy(&vhd->email);
-                       vhd->email_inited = 0;
-               }
+               if (vhd->smtp_client)
+                       lws_abs_destroy_instance(&vhd->smtp_client);
                break;
 
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+                if (!pss->check_response)
+                        break;
+                pss->check_response = 0;
+               n = lws_write(wsi, (unsigned char *)&pss->check_response_value,
+                               1, LWS_WRITE_HTTP | LWS_WRITE_H2_STREAM_END);
+               if (n != 1)
+                       return -1;
+               goto try_to_reuse;
+
        case LWS_CALLBACK_HTTP:
-               lwsl_info("LWS_CALLBACK_HTTP: %s\n", (const char *)in);
+               if (!pss) {
+                       lwsl_err("%s: no valid pss\n", __func__);
+                       return 1;
+               }
 
                pss->login_session.id[0] = '\0';
                pss->phs.pos = 0;
-               strncpy(pss->onward, (char *)in, sizeof(pss->onward) - 1);
-               pss->onward[sizeof(pss->onward) - 1] = '\0';
 
-               if (!strcmp((const char *)in, "/lwsgs-forgot")) {
+               cp = in;
+               if ((*(const char *)in == '/'))
+                       cp++;
+
+               if (lws_get_effective_host(wsi, cookie, sizeof(cookie))) {
+                       lwsl_err("%s: HTTP: no effective host\n", __func__);
+                       return 1;
+               }
+
+               lwsl_notice("LWS_CALLBACK_HTTP: %s, HOST '%s'\n",
+                               (const char *)in, cookie);
+
+               n = strlen(cp);
+
+               lws_snprintf(pss->onward, sizeof(pss->onward),
+                            "%s%s", vhd->urlroot, (const char *)in);
+
+               if (n >= 12 &&
+                   !strcmp(cp + n - 12, "lwsgs-forgot")) {
                        lwsgs_handler_forgot(vhd, wsi, pss);
                        goto redirect_with_cookie;
                }
 
-               if (!strcmp((const char *)in, "/lwsgs-confirm")) {
+               if (n >= 13 &&
+                   !strcmp(cp + n - 13, "lwsgs-confirm")) {
                        lwsgs_handler_confirm(vhd, wsi, pss);
                        goto redirect_with_cookie;
                }
-               if (!strcmp((const char *)in, "/lwsgs-check")) {
-                       lwsgs_handler_check(vhd, wsi, pss);
-                       goto try_to_reuse;
+               cp = strstr(cp, "lwsgs-check/");
+               if (cp) {
+                       lwsgs_handler_check(vhd, wsi, pss, cp + 12);
+                       /* second, async part will complete transaction */
+                       break;
                }
 
-               if (!strcmp((const char *)in, "/lwsgs-login"))
+               if (n >= 11 && !strcmp(cp + n - 11, "lwsgs-login"))
                        break;
-               if (!strcmp((const char *)in, "/lwsgs-logout"))
+               if (n >= 12 && !strcmp(cp + n - 12, "lwsgs-logout"))
                        break;
-               if (!strcmp((const char *)in, "/lwsgs-forgot"))
+               if (n >= 12 && !strcmp(cp + n - 12, "lwsgs-forgot"))
                        break;
-               if (!strcmp((const char *)in, "/lwsgs-change"))
+               if (n >= 12 && !strcmp(cp + n - 12, "lwsgs-change"))
                        break;
 
                /* if no legitimate url for GET, return 404 */
 
-               lwsl_err("http doing 404 on %s\n", (const char *)in);
+               lwsl_err("http doing 404 on %s\n", cp);
                lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
-               goto try_to_reuse;
 
+               return -1;
+               //goto try_to_reuse;
+
+       case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
+               args = (struct lws_process_html_args *)in;
+               if (!args->chunked)
+                       break;
        case LWS_CALLBACK_CHECK_ACCESS_RIGHTS:
                n = 0;
                username[0] = '\0';
                sid.id[0] = '\0';
                args = (struct lws_process_html_args *)in;
-               lwsl_debug("LWS_CALLBACK_CHECK_ACCESS_RIGHTS\n");
+               lwsl_notice("%s: LWS_CALLBACK_CHECK_ACCESS_RIGHTS: need 0x%x\n",
+                               __func__, args->max_len);
                if (!lwsgs_get_sid_from_wsi(wsi, &sid)) {
-                       if (lwsgs_lookup_session(vhd, &sid, username, sizeof(username))) {
-                               static const char * const oprot[] = {
-                                       "http://", "https://"
-                               };
-                               lwsl_notice("session lookup for %s failed, probably expired\n", sid.id);
+                       if (lwsgs_lookup_session(vhd, &sid, username,
+                                                sizeof(username))) {
+
+                               /*
+                                * if we're authenticating for ws, we don't
+                                * want to redirect it or gain a cookie on that,
+                                * he'll need to get the cookie from http
+                                * interactions outside of this.
+                                */
+                               if (args->chunked) {
+                                       lwsl_notice("%s: ws auth failed\n",
+                                                       __func__);
+
+                                       return 1;
+                               }
+
+                               lwsl_notice("session lookup for %s failed, "
+                                           "probably expired\n", sid.id);
                                pss->delete_session = sid;
                                args->final = 1; /* signal we dealt with it */
-                               if (lws_hdr_copy(wsi, cookie, sizeof(cookie) - 1,
-                                            WSI_TOKEN_HOST) < 0)
-                                       return 1;
                                lws_snprintf(pss->onward, sizeof(pss->onward) - 1,
-                                        "%s%s%s", oprot[!!lws_is_ssl(wsi)],
-                                           cookie, args->p);
-                               lwsl_notice("redirecting to ourselves with cookie refresh\n");
-                               /* we need a redirect to ourselves, session cookie is expired */
+                                        "%s%s", vhd->urlroot, args->p);
+                               lwsl_notice("redirecting to ourselves with "
+                                           "cookie refresh\n");
+                               /* we need a redirect to ourselves,
+                                * session cookie is expired */
                                goto redirect_with_cookie;
                        }
                } else
@@ -519,10 +466,9 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                                 sqlite3_errmsg(vhd->pdb));
                        break;
                }
-               strncpy(sinfo->username, u.username, sizeof(sinfo->username) - 1);
-               sinfo->username[sizeof(sinfo->username) - 1] = '\0';
-               strncpy(sinfo->email, u.email, sizeof(sinfo->email) - 1);
-               strncpy(sinfo->session, sid.id, sizeof(sinfo->session) - 1);
+               lws_strncpy(sinfo->username, u.username, sizeof(sinfo->username));
+               lws_strncpy(sinfo->email, u.email, sizeof(sinfo->email));
+               lws_strncpy(sinfo->session, sid.id, sizeof(sinfo->session));
                sinfo->mask = lwsgs_get_auth_level(vhd, username);
                lws_get_peer_simple(wsi, sinfo->ip, sizeof(sinfo->ip));
        }
@@ -545,7 +491,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                        a.wsi = wsi;
 
                        pss->phs.vars = vars;
-                       pss->phs.count_vars = ARRAY_SIZE(vars);
+                       pss->phs.count_vars = LWS_ARRAY_SIZE(vars);
                        pss->phs.replace = lwsgs_subst;
                        pss->phs.data = &a;
 
@@ -560,7 +506,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
 
                if (!pss->spa) {
                        pss->spa = lws_spa_create(wsi, param_names,
-                                               ARRAY_SIZE(param_names), 1024,
+                                       LWS_ARRAY_SIZE(param_names), 1024,
                                                NULL, NULL);
                        if (!pss->spa)
                                return -1;
@@ -577,10 +523,18 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                if (!pss->spa)
                        break;
 
-               lwsl_info("LWS_CALLBACK_HTTP_BODY_COMPLETION: %s\n", pss->onward);
+               cp1 = (const char *)pss->onward;
+               if (*cp1 == '/')
+                       cp1++;
+
+
                lws_spa_finalize(pss->spa);
+               n = strlen(cp1);
+
+               if (lws_get_effective_host(wsi, cookie, sizeof(cookie)))
+                       return 1;
 
-               if (!strcmp((char *)pss->onward, "/lwsgs-change")) {
+               if (!strcmp(cp1 + n - 12, "lwsgs-change")) {
                        if (!lwsgs_handler_change_password(vhd, wsi, pss)) {
                                cp = lws_spa_get_string(pss->spa, FGS_GOOD);
                                goto pass;
@@ -589,12 +543,15 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                        cp = lws_spa_get_string(pss->spa, FGS_BAD);
                        lwsl_notice("user/password no good %s\n",
                                lws_spa_get_string(pss->spa, FGS_USERNAME));
-                       strncpy(pss->onward, cp, sizeof(pss->onward) - 1);
+                       lws_snprintf(pss->onward, sizeof(pss->onward),
+                                    "%s%s", vhd->urlroot, cp);
+
                        pss->onward[sizeof(pss->onward) - 1] = '\0';
                        goto completion_flow;
                }
 
-               if (!strcmp((char *)pss->onward, "/lwsgs-login")) {
+               if (!strcmp(cp1 + n - 11, "lwsgs-login")) {
+                       lwsl_err("%s: lwsgs-login\n", __func__);
                        if (lws_spa_get_string(pss->spa, FGS_FORGOT) &&
                            lws_spa_get_string(pss->spa, FGS_FORGOT)[0]) {
                                if (lwsgs_handler_forgot_pw_form(vhd, wsi, pss)) {
@@ -602,7 +559,7 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                                        goto reg_done;
                                }
                                /* get the email monitor to take a look */
-                               lws_email_check(&vhd->email);
+                               lws_smtp_client_kick(vhd->smtp_client);
                                n = FGS_FORGOT_GOOD;
                                goto reg_done;
                        }
@@ -610,8 +567,8 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                        if (!lws_spa_get_string(pss->spa, FGS_USERNAME) ||
                            !lws_spa_get_string(pss->spa, FGS_PASSWORD)) {
                                lwsl_notice("username '%s' or pw '%s' missing\n",
-                                               lws_spa_get_string(pss->spa, FGS_USERNAME),
-                                               lws_spa_get_string(pss->spa, FGS_PASSWORD));
+                                       lws_spa_get_string(pss->spa, FGS_USERNAME),
+                                       lws_spa_get_string(pss->spa, FGS_PASSWORD));
                                return -1;
                        }
 
@@ -624,12 +581,13 @@ callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason,
                                        n = FGS_REG_GOOD;
 
                                        /* get the email monitor to take a look */
-                                       lws_email_check(&vhd->email);
+                                       lws_smtp_client_kick(vhd->smtp_client);
                                }
 reg_done:
-                               strncpy(pss->onward, lws_spa_get_string(pss->spa, n),
-                                       sizeof(pss->onward) - 1);
-                               pss->onward[sizeof(pss->onward) - 1] = '\0';
+                               lws_snprintf(pss->onward, sizeof(pss->onward),
+                                            "%s%s", vhd->urlroot,
+                                            lws_spa_get_string(pss->spa, n));
+
                                pss->login_expires = 0;
                                pss->logging_out = 1;
                                goto completion_flow;
@@ -653,29 +611,32 @@ reg_done:
 
                        /* check users in database */
 
-                       if (!lwsgs_check_credentials(vhd, lws_spa_get_string(pss->spa, FGS_USERNAME),
-                                                    lws_spa_get_string(pss->spa, FGS_PASSWORD))) {
-                               lwsl_info("pw hash check met\n");
+                       if (!lwsgs_check_credentials(vhd,
+                                       lws_spa_get_string(pss->spa, FGS_USERNAME),
+                                       lws_spa_get_string(pss->spa, FGS_PASSWORD))) {
+                               lwsl_notice("pw hash check met\n");
                                cp = lws_spa_get_string(pss->spa, FGS_GOOD);
                                goto pass;
                        } else
-                               lwsl_notice("user/password no good %s\n",
-                                               lws_spa_get_string(pss->spa, FGS_USERNAME));
+                               lwsl_notice("user/password no good %s %s\n",
+                                               lws_spa_get_string(pss->spa, FGS_USERNAME),
+                                               lws_spa_get_string(pss->spa, FGS_PASSWORD));
 
                        if (!lws_spa_get_string(pss->spa, FGS_BAD)) {
                                lwsl_info("No admin or good target url in form\n");
                                return -1;
                        }
 
-                       strncpy(pss->onward, lws_spa_get_string(pss->spa, FGS_BAD),
-                               sizeof(pss->onward) - 1);
-                       pss->onward[sizeof(pss->onward) - 1] = '\0';
-                       lwsl_debug("failed\n");
+                       lws_snprintf(pss->onward, sizeof(pss->onward),
+                                    "%s%s", vhd->urlroot,
+                                    lws_spa_get_string(pss->spa, FGS_BAD));
+
+                       lwsl_notice("failed: %s\n", pss->onward);
 
                        goto completion_flow;
                }
 
-               if (!strcmp((char *)pss->onward, "/lwsgs-logout")) {
+               if (!strcmp(cp1 + n - 12, "lwsgs-logout")) {
 
                        lwsl_notice("/logout\n");
 
@@ -684,6 +645,11 @@ reg_done:
                                return 1;
                        }
 
+                       /*
+                        * We keep the same session, but mark it as not
+                        * being associated to any authenticated user
+                        */
+
                        lwsgw_update_session(vhd, &pss->login_session, "");
 
                        if (!lws_spa_get_string(pss->spa, FGS_GOOD)) {
@@ -691,8 +657,9 @@ reg_done:
                                return -1;
                        }
 
-                       strncpy(pss->onward, lws_spa_get_string(pss->spa, FGS_GOOD), sizeof(pss->onward) - 1);
-                       pss->onward[sizeof(pss->onward) - 1] = '\0';
+                       lws_snprintf(pss->onward, sizeof(pss->onward),
+                                    "%s%s", vhd->urlroot,
+                                    lws_spa_get_string(pss->spa, FGS_GOOD));
 
                        pss->login_expires = 0;
                        pss->logging_out = 1;
@@ -703,8 +670,8 @@ reg_done:
                break;
 
 pass:
-               strncpy(pss->onward, cp, sizeof(pss->onward) - 1);
-               pss->onward[sizeof(pss->onward) - 1] = '\0';
+               lws_snprintf(pss->onward, sizeof(pss->onward),
+                            "%s%s", vhd->urlroot, cp);
 
                if (lwsgs_get_sid_from_wsi(wsi, &sid))
                        sid.id[0] = '\0';
@@ -716,18 +683,18 @@ pass:
                        /* we need to create a new, authorized session */
 
                        if (lwsgs_new_session_id(vhd, &pss->login_session,
-                                                lws_spa_get_string(pss->spa, FGS_USERNAME),
-                                                pss->login_expires))
+                                       lws_spa_get_string(pss->spa, FGS_USERNAME),
+                                       pss->login_expires))
                                goto try_to_reuse;
 
-                       lwsl_info("Creating new session: %s\n",
+                       lwsl_notice("Creating new session: %s\n",
                                    pss->login_session.id);
                } else {
                        /*
                         * we can just update the existing session to be
                         * authorized
                         */
-                       lwsl_info("Authorizing existing session %s", sid.id);
+                       lwsl_notice("Authorizing existing session %s", sid.id);
                        lwsgw_update_session(vhd, &sid,
                                lws_spa_get_string(pss->spa, FGS_USERNAME));
                        pss->login_session = sid;
@@ -747,14 +714,17 @@ completion_flow:
        case LWS_CALLBACK_ADD_HEADERS:
                lwsgw_expire_old_sessions(vhd);
 
-               args = (struct lws_process_html_args *)in;
+               lwsl_warn("ADD_HEADERS\n");
 
+               args = (struct lws_process_html_args *)in;
+               if (!pss)
+                       return 1;
                if (pss->delete_session.id[0]) {
                        pc = cookie;
                        lwsgw_cookie_from_session(&pss->delete_session, 0, &pc,
                                                  cookie + sizeof(cookie) - 1);
 
-                       lwsl_info("deleting cookie '%s'\n", cookie);
+                       lwsl_notice("deleting cookie '%s'\n", cookie);
 
                        if (lws_add_http_header_by_name(wsi,
                                        (unsigned char *)"set-cookie:",
@@ -800,13 +770,24 @@ redirect_with_cookie:
        start = p;
        end = p + sizeof(buffer) - LWS_PRE;
 
+       lwsl_warn("%s: redirect_with_cookie\n", __func__);
+
        if (lws_add_http_header_status(wsi, HTTP_STATUS_SEE_OTHER, &p, end))
                return 1;
 
-       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION,
-                       (unsigned char *)pss->onward,
-                       strlen(pss->onward), &p, end))
-               return 1;
+       {
+               char loc[1024], uria[128];
+
+               uria[0] = '\0';
+               lws_hdr_copy_fragment(wsi, uria, sizeof(uria),
+                                         WSI_TOKEN_HTTP_URI_ARGS, 0);
+               n = lws_snprintf(loc, sizeof(loc), "%s?%s",
+                               pss->onward, uria);
+               lwsl_notice("%s: redirect to '%s'\n", __func__, loc);
+               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION,
+                               (unsigned char *)loc, n, &p, end))
+                       return 1;
+       }
 
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
                        (unsigned char *)"text/html", 9, &p, end))
@@ -823,16 +804,20 @@ redirect_with_cookie:
                if (lws_add_http_header_by_name(wsi,
                                (unsigned char *)"set-cookie:",
                                (unsigned char *)cookie, pc - cookie,
-                               &p, end))
+                               &p, end)) {
+                       lwsl_err("fail0\n");
                        return 1;
+               }
        }
 
        if (!pss->login_session.id[0]) {
                pss->login_expires = lws_now_secs() +
                                     vhd->timeout_anon_absolute_secs;
                if (lwsgs_new_session_id(vhd, &pss->login_session, "",
-                                        pss->login_expires))
+                                        pss->login_expires)) {
+                       lwsl_err("fail1\n");
                        return 1;
+               }
        } else
                pss->login_expires = lws_now_secs() +
                                     vhd->timeout_absolute_secs;
@@ -848,21 +833,26 @@ redirect_with_cookie:
                                          pss->login_expires, &pc,
                                          cookie + sizeof(cookie) - 1);
 
-               lwsl_info("setting cookie '%s'\n", cookie);
+               lwsl_err("%s: setting cookie '%s'\n", __func__, cookie);
 
                pss->logging_out = 0;
 
                if (lws_add_http_header_by_name(wsi,
                                (unsigned char *)"set-cookie:",
                                (unsigned char *)cookie, pc - cookie,
-                               &p, end))
+                               &p, end)) {
+                       lwsl_err("fail2\n");
                        return 1;
+               }
        }
 
        if (lws_finalize_http_header(wsi, &p, end))
                return 1;
 
-       n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+       // lwsl_hexdump_notice(start, p - start);
+
+       n = lws_write(wsi, start, p - start, LWS_WRITE_H2_STREAM_END |
+                                            LWS_WRITE_HTTP_HEADERS);
        if (n < 0)
                return 1;
 
@@ -895,7 +885,7 @@ init_protocol_generic_sessions(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index 8e66cd5..4fb973f 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for messageboard "generic sessions" demo
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 
 #include <sqlite3.h>
 #include <string.h>
+#include <stdlib.h>
 
 struct per_vhost_data__gs_mb {
        struct lws_vhost *vh;
@@ -40,6 +41,7 @@ struct per_session_data__gs_mb {
        struct lws_spa *spa;
        unsigned long last_idx;
        unsigned int our_form:1;
+       char second_http_part;
 };
 
 static const char * const param_names[] = {
@@ -83,23 +85,19 @@ lookup_cb(void *priv, int cols, char **col_val, char **col_name)
                        continue;
                }
                if (!strcmp(col_name[n], "username")) {
-                       strncpy(m->username, col_val[n], sizeof(m->username) - 1);
-                       m->username[sizeof(m->username) - 1] = '\0';
+                       lws_strncpy(m->username, col_val[n], sizeof(m->username));
                        continue;
                }
                if (!strcmp(col_name[n], "email")) {
-                       strncpy(m->email, col_val[n], sizeof(m->email) - 1);
-                       m->email[sizeof(m->email) - 1] = '\0';
+                       lws_strncpy(m->email, col_val[n], sizeof(m->email));
                        continue;
                }
                if (!strcmp(col_name[n], "ip")) {
-                       strncpy(m->ip, col_val[n], sizeof(m->ip) - 1);
-                       m->ip[sizeof(m->ip) - 1] = '\0';
+                       lws_strncpy(m->ip, col_val[n], sizeof(m->ip));
                        continue;
                }
                if (!strcmp(col_name[n], "content")) {
-                       strncpy(m->content, col_val[n], sizeof(m->content) - 1);
-                       m->content[sizeof(m->content) - 1] = '\0';
+                       lws_strncpy(m->content, col_val[n], sizeof(m->content));
                        continue;
                }
        }
@@ -159,12 +157,13 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
        const struct lws_protocol_vhost_options *pvo;
        struct per_vhost_data__gs_mb *vhd = (struct per_vhost_data__gs_mb *)
                lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi));
-       unsigned char *p, *start, *end, buffer[LWS_PRE + 256];
+       unsigned char *p, *start, *end, buffer[LWS_PRE + 4096];
        char s[512];
        int n;
 
        switch (reason) {
        case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
+
                vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
                        lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs_mb));
                if (!vhd)
@@ -189,9 +188,8 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                        return 1;
                }
 
-               if (sqlite3_open_v2(vhd->message_db, &vhd->pdb,
-                                   SQLITE_OPEN_READWRITE |
-                                   SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) {
+               if (lws_struct_sq3_open(lws_get_context(wsi),
+                                       vhd->message_db, &vhd->pdb)) {
                        lwsl_err("Unable to open message db %s: %s\n",
                                 vhd->message_db, sqlite3_errmsg(vhd->pdb));
 
@@ -212,7 +210,7 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                break;
 
        case LWS_CALLBACK_PROTOCOL_DESTROY:
-               if (vhd->pdb)
+               if (vhd && vhd->pdb)
                        sqlite3_close(vhd->pdb);
                goto passthru;
 
@@ -228,6 +226,14 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                lws_callback_on_writable(wsi);
                break;
 
+       case LWS_CALLBACK_CLOSED:
+               lwsl_debug("%s: LWS_CALLBACK_CLOSED\n", __func__);
+               if (pss && pss->pss_gs) {
+                       free(pss->pss_gs);
+                       pss->pss_gs = NULL;
+               }
+               break;
+
        case LWS_CALLBACK_SERVER_WRITEABLE:
                {
                        struct message m;
@@ -281,7 +287,8 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                pss->our_form = 0;
 
                /* ie, it's our messageboard new message form */
-               if (!strcmp((const char *)in, "/msg")) {
+               if (!strcmp((const char *)in, "/msg") ||
+                   !strcmp((const char *)in, "msg")) {
                        pss->our_form = 1;
                        break;
                }
@@ -296,7 +303,7 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                        break;
                if (!pss->spa) {
                        pss->spa = lws_spa_create(wsi, param_names,
-                                               ARRAY_SIZE(param_names),
+                                       LWS_ARRAY_SIZE(param_names),
                                                MAX_MSG_LEN + 1024, NULL, NULL);
                        if (!pss->spa)
                                return -1;
@@ -308,6 +315,18 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                }
                break;
 
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               if (!pss->second_http_part)
+                       goto passthru;
+
+               s[0] = '0';
+               n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP|
+                               LWS_WRITE_H2_STREAM_END);
+               if (n != 1)
+                       return -1;
+
+               goto try_to_reuse;
+
        case LWS_CALLBACK_HTTP_BODY_COMPLETION:
                if (!pss->our_form)
                        goto passthru;
@@ -328,20 +347,18 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
                        return -1;
                if (lws_finalize_http_header(wsi, &p, end))
                        return -1;
+
                n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
                if (n != (p - start)) {
                        lwsl_err("_write returned %d from %ld\n", n, (long)(p - start));
                        return -1;
                }
-               s[0] = '0';
-               n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP);
-               if (n != 1)
-                       return -1;
-
-               goto try_to_reuse;
+               pss->second_http_part = 1;
+               lws_callback_on_writable(wsi);
+               break;
 
        case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
-               if (!pss || pss->pss_gs)
+               if (!pss || !vhd || pss->pss_gs)
                        break;
 
                pss->pss_gs = malloc(vhd->gsp->per_session_data_size);
@@ -367,7 +384,10 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
 
        default:
 passthru:
-               return vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len);
+               if (!pss || !vhd)
+                       break;
+
+               return vhd->gsp->callback(wsi, reason, pss->pss_gs, in, len);
        }
 
        return 0;
@@ -400,7 +420,7 @@ init_protocol_lws_messageboard(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index c90b91d..0b8959b 100644 (file)
  */
 
 #include "private-lwsgs.h"
+#include <stdlib.h>
 
 void
-sha1_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash)
+sha256_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash)
 {
        static const char *hex = "0123456789abcdef";
        char *p = shash->id;
        int n;
 
-       for (n = 0; n < 20; n++) {
+       for (n = 0; n < (int)lws_genhash_size(LWS_GENHASH_TYPE_SHA256); n++) {
                *p++ = hex[(hash[n] >> 4) & 0xf];
                *p++ = hex[hash[n] & 15];
        }
@@ -47,9 +48,9 @@ lwsgw_check_admin(struct per_vhost_data__gs *vhd,
                return 0;
 
        lws_SHA1((unsigned char *)password, strlen(password), hash_bin.bin);
-       sha1_to_lwsgw_hash(hash_bin.bin, &pw_hash);
+       sha256_to_lwsgw_hash(hash_bin.bin, &pw_hash);
 
-       return !strcmp(vhd->admin_password_sha1.id, pw_hash.id);
+       return !strcmp(vhd->admin_password_sha256.id, pw_hash.id);
 }
 
 /*
@@ -105,7 +106,7 @@ lwsgw_update_session(struct per_vhost_data__gs *vhd,
                     lwsgw_hash *hash, const char *user)
 {
        time_t n = lws_now_secs();
-       char s[200], esc[50], esc1[50];
+       char s[200], esc[96], esc1[96];
 
        if (user[0])
                n += vhd->timeout_absolute_secs;
@@ -124,6 +125,8 @@ lwsgw_update_session(struct per_vhost_data__gs *vhd,
                return 1;
        }
 
+       puts(s);
+
        return 0;
 }
 
@@ -145,7 +148,7 @@ lwsgw_session_from_cookie(const char *cookie, lwsgw_hash *sid)
                return 1;
        }
 
-       for (n = 0; n < sizeof(sid->id) - 1 && *p; n++) {
+       for (n = 0; n < (int)sizeof(sid->id) - 1 && *p; n++) {
                /* our SID we issue only has these chars */
                if ((*p >= '0' && *p <= '9') ||
                    (*p >= 'a' && *p <= 'f'))
@@ -156,7 +159,7 @@ lwsgw_session_from_cookie(const char *cookie, lwsgw_hash *sid)
                }
        }
 
-       if (n < sizeof(sid->id) - 1) {
+       if (n < (int)sizeof(sid->id) - 1) {
                lwsl_info("cookie id too short\n");
                return 1;
        }
@@ -182,7 +185,7 @@ lwsgs_get_sid_from_wsi(struct lws *wsi, lwsgw_hash *sid)
        }
        /* extract the sid from the cookie */
        if (lwsgw_session_from_cookie(cookie, sid)) {
-               lwsl_info("session from cookie failed\n");
+               lwsl_info("%s: session from cookie failed\n", __func__);
                return 1;
        }
 
@@ -205,8 +208,7 @@ lwsgs_lookup_callback(void *priv, int cols, char **col_val, char **col_name)
        if (cols)
                lla->results = 0;
        if (col_val && col_val[0]) {
-               strncpy(lla->username, col_val[0], lla->len);
-               lla->username[lla->len - 1] = '\0';
+               lws_strncpy(lla->username, col_val[0], lla->len + 1);
                lwsl_info("%s: %s\n", __func__, lla->username);
        }
 
@@ -218,7 +220,7 @@ lwsgs_lookup_session(struct per_vhost_data__gs *vhd,
                     const lwsgw_hash *sid, char *username, int len)
 {
        struct lla lla = { username, len, 1 };
-       char s[150], esc[50];
+       char s[150], esc[96];
 
        lwsgw_expire_old_sessions(vhd);
 
@@ -245,13 +247,11 @@ lwsgs_lookup_callback_user(void *priv, int cols, char **col_val, char **col_name
 
        for (n = 0; n < cols; n++) {
                if (!strcmp(col_name[n], "username")) {
-                       strncpy(u->username, col_val[n], sizeof(u->username) - 1);
-                       u->username[sizeof(u->username) - 1] = '\0';
+                       lws_strncpy(u->username, col_val[n], sizeof(u->username));
                        continue;
                }
                if (!strcmp(col_name[n], "ip")) {
-                       strncpy(u->ip, col_val[n], sizeof(u->ip) - 1);
-                       u->ip[sizeof(u->ip) - 1] = '\0';
+                       lws_strncpy(u->ip, col_val[n], sizeof(u->ip));
                        continue;
                }
                if (!strcmp(col_name[n], "creation_time")) {
@@ -266,8 +266,7 @@ lwsgs_lookup_callback_user(void *priv, int cols, char **col_val, char **col_name
                        continue;
                }
                if (!strcmp(col_name[n], "email")) {
-                       strncpy(u->email, col_val[n], sizeof(u->email) - 1);
-                       u->email[sizeof(u->email) - 1] = '\0';
+                       lws_strncpy(u->email, col_val[n], sizeof(u->email));
                        continue;
                }
                if (!strcmp(col_name[n], "verified")) {
@@ -275,18 +274,15 @@ lwsgs_lookup_callback_user(void *priv, int cols, char **col_val, char **col_name
                        continue;
                }
                if (!strcmp(col_name[n], "pwhash")) {
-                       strncpy(u->pwhash.id, col_val[n], sizeof(u->pwhash.id) - 1);
-                       u->pwhash.id[sizeof(u->pwhash.id) - 1] = '\0';
+                       lws_strncpy(u->pwhash.id, col_val[n], sizeof(u->pwhash.id));
                        continue;
                }
                if (!strcmp(col_name[n], "pwsalt")) {
-                       strncpy(u->pwsalt.id, col_val[n], sizeof(u->pwsalt.id) - 1);
-                       u->pwsalt.id[sizeof(u->pwsalt.id) - 1] = '\0';
+                       lws_strncpy(u->pwsalt.id, col_val[n], sizeof(u->pwsalt.id));
                        continue;
                }
                if (!strcmp(col_name[n], "token")) {
-                       strncpy(u->token.id, col_val[n], sizeof(u->token.id) - 1);
-                       u->token.id[sizeof(u->token.id) - 1] = '\0';
+                       lws_strncpy(u->token.id, col_val[n], sizeof(u->token.id));
                        continue;
                }
        }
@@ -297,7 +293,7 @@ int
 lwsgs_lookup_user(struct per_vhost_data__gs *vhd,
                  const char *username, struct lwsgs_user *u)
 {
-       char s[150], esc[50];
+       char s[150], esc[96];
 
        u->username[0] = '\0';
        lws_snprintf(s, sizeof(s) - 1,
@@ -320,17 +316,19 @@ int
 lwsgs_new_session_id(struct per_vhost_data__gs *vhd,
                     lwsgw_hash *sid, const char *username, int exp)
 {
-       unsigned char sid_rand[20];
+       unsigned char sid_rand[32];
        const char *u;
-       char s[300], esc[50], esc1[50];
+       char s[300], esc[96], esc1[96];
 
        if (username)
                u = username;
        else
                u = "";
 
-       if (!sid)
+       if (!sid) {
+               lwsl_err("%s: NULL sid\n", __func__);
                return 1;
+       }
 
        memset(sid, 0, sizeof(*sid));
 
@@ -338,7 +336,7 @@ lwsgs_new_session_id(struct per_vhost_data__gs *vhd,
                           sizeof(sid_rand))
                return 1;
 
-       sha1_to_lwsgw_hash(sid_rand, sid);
+       sha256_to_lwsgw_hash(sid_rand, sid);
 
        lws_snprintf(s, sizeof(s) - 1,
                 "insert into sessions(name, username, expire) "
@@ -353,29 +351,30 @@ lwsgs_new_session_id(struct per_vhost_data__gs *vhd,
                return 1;
        }
 
+       lwsl_notice("%s: created session %s\n", __func__, sid->id);
+
        return 0;
 }
 
 int
-lwsgs_get_auth_level(struct per_vhost_data__gs *vhd,
-                    const char *username)
+lwsgs_get_auth_level(struct per_vhost_data__gs *vhd, const char *username)
 {
        struct lwsgs_user u;
        int n = 0;
 
        /* we are logged in as some kind of user */
        if (username[0]) {
-               n |= LWSGS_AUTH_LOGGED_IN;
                /* we are logged in as admin */
                if (!strcmp(username, vhd->admin_user))
-                       n |= LWSGS_AUTH_VERIFIED | LWSGS_AUTH_ADMIN; /* automatically verified */
+                       /* automatically verified */
+                       n |= LWSGS_AUTH_VERIFIED | LWSGS_AUTH_ADMIN;
        }
 
        if (!lwsgs_lookup_user(vhd, username, &u)) {
                if ((u.verified & 0xff) == LWSGS_VERIFIED_ACCEPTED)
-                       n |= LWSGS_AUTH_VERIFIED;
+                       n |= LWSGS_AUTH_LOGGED_IN | LWSGS_AUTH_VERIFIED;
 
-               if (u.last_forgot_validated > lws_now_secs() - 300)
+               if (u.last_forgot_validated > (time_t)lws_now_secs() - 300)
                        n |= LWSGS_AUTH_FORGOT_FLOW;
        }
 
@@ -386,24 +385,31 @@ int
 lwsgs_check_credentials(struct per_vhost_data__gs *vhd,
                        const char *username, const char *password)
 {
-       unsigned char buffer[300];
+       struct lws_genhash_ctx hash_ctx;
        lwsgw_hash_bin hash_bin;
        struct lwsgs_user u;
        lwsgw_hash hash;
-       int n;
 
        if (lwsgs_lookup_user(vhd, username, &u))
                return -1;
 
        lwsl_info("user %s found, salt '%s'\n", username, u.pwsalt.id);
 
-       /* [password in ascii][salt] */
-       n = lws_snprintf((char *)buffer, sizeof(buffer) - 1,
-                    "%s-%s-%s", password, vhd->confounder, u.pwsalt.id);
+       /* sha256sum of password + salt */
+
+       if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256) ||
+           lws_genhash_update(&hash_ctx, password, strlen(password)) ||
+           lws_genhash_update(&hash_ctx, "-", 1) ||
+           lws_genhash_update(&hash_ctx, vhd->confounder, strlen(vhd->confounder)) ||
+           lws_genhash_update(&hash_ctx, "-", 1) ||
+           lws_genhash_update(&hash_ctx, u.pwsalt.id, strlen(u.pwsalt.id)) ||
+           lws_genhash_destroy(&hash_ctx, hash_bin.bin)) {
+               lws_genhash_destroy(&hash_ctx, NULL);
+
+               return 1;
+       }
 
-       /* sha1sum of password + salt */
-       lws_SHA1(buffer, n, hash_bin.bin);
-       sha1_to_lwsgw_hash(&hash_bin.bin[0], &hash);
+       sha256_to_lwsgw_hash(&hash_bin.bin[0], &hash);
 
        return !!strcmp(hash.id, u.pwhash.id);
 }
@@ -414,11 +420,9 @@ int
 lwsgs_hash_password(struct per_vhost_data__gs *vhd,
                    const char *password, struct lwsgs_user *u)
 {
+       unsigned char sid_rand[32];
+       struct lws_genhash_ctx hash_ctx;
        lwsgw_hash_bin hash_bin;
-       lwsgw_hash hash;
-       unsigned char sid_rand[20];
-       unsigned char buffer[150];
-       int n;
 
        /* create a random salt as big as the hash */
 
@@ -428,23 +432,31 @@ lwsgs_hash_password(struct per_vhost_data__gs *vhd,
                lwsl_err("Problem getting random for salt\n");
                return 1;
        }
-       sha1_to_lwsgw_hash(sid_rand, &u->pwsalt);
-
+       sha256_to_lwsgw_hash(sid_rand, &u->pwsalt);
+/*
        if (lws_get_random(vhd->context, sid_rand,
                           sizeof(sid_rand)) !=
                           sizeof(sid_rand)) {
                lwsl_err("Problem getting random for token\n");
                return 1;
        }
-       sha1_to_lwsgw_hash(sid_rand, &hash);
+       sha256_to_lwsgw_hash(sid_rand, &hash);
+*/
+       /* sha256sum of password + salt */
+
+       if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256) ||
+           lws_genhash_update(&hash_ctx, password, strlen(password)) ||
+           lws_genhash_update(&hash_ctx, "-", 1) ||
+           lws_genhash_update(&hash_ctx, vhd->confounder, strlen(vhd->confounder)) ||
+           lws_genhash_update(&hash_ctx, "-", 1) ||
+           lws_genhash_update(&hash_ctx, u->pwsalt.id, strlen(u->pwsalt.id)) ||
+           lws_genhash_destroy(&hash_ctx, hash_bin.bin)) {
+               lws_genhash_destroy(&hash_ctx, NULL);
 
-       /* [password in ascii][salt] */
-       n = lws_snprintf((char *)buffer, sizeof(buffer) - 1,
-                   "%s-%s-%s", password, vhd->confounder, u->pwsalt.id);
+               return 1;
+       }
 
-       /* sha1sum of password + salt */
-       lws_SHA1(buffer, n, hash_bin.bin);
-       sha1_to_lwsgw_hash(&hash_bin.bin[0], &u->pwhash);
+       sha256_to_lwsgw_hash(&hash_bin.bin[0], &u->pwhash);
 
        return 0;
 }
index 9bd299f..dc93895 100644 (file)
@@ -3,12 +3,12 @@ function lwsgt_get_appropriate_ws_url()
        var pcol;
        var u = document.URL;
 
-       if (u.substring(0, 5) == "https") {
+       if (u.substring(0, 5) === "https") {
                pcol = "wss://";
                u = u.substr(8);
        } else {
                pcol = "ws://";
-               if (u.substring(0, 4) == "http")
+               if (u.substring(0, 4) === "http")
                        u = u.substr(7);
        }
 
@@ -25,7 +25,8 @@ function lwsgt_app_hdr(j, bc, ws)
                if (!j.cols[n].hide)
                        m++;
 
-       s = "<tr><td colspan=\"" + m + "\" class=\"lwsgt_title\">" + ws.lwsgt_title + "</td></tr>"
+       s = "<tr><td colspan=\"" + m + "\" class=\"lwsgt_title\">" +
+               ws.lwsgt_title + "</td></tr>";
 
        if (!!bc) {
                s += "<tr><td colspan=\"" + m + "\" class=\"lwsgt_breadcrumbs\">";
@@ -34,7 +35,8 @@ function lwsgt_app_hdr(j, bc, ws)
                        if (!bc[n].url && bc[n].url !== "")
                                s += " " + lws_san(bc[n].name) + " ";
                        else {
-                               s = s + "<a href=# id=\"bc_"+ ws.divname + ws.bcq + "\" h=\"" + ws.lwsgt_cb + "\" p=\""+ws.lwsgt_parent+"\" aa=\"="+
+                               s += "<a href=# id=\"bc_"+ ws.divname + ws.bcq + "\" h=\"" +
+                                   ws.lwsgt_cb + "\" p=\""+ws.lwsgt_parent+"\" aa=\"="+
                                        lws_san(encodeURI(bc[n].url))+"\" m=\"-1\" n=\"-1\">" +
                                        lws_san(bc[n].name) + "</a> ";
                                ws.bcq++;
@@ -45,18 +47,25 @@ function lwsgt_app_hdr(j, bc, ws)
        s += "<tr>";
        for (n = 0; n < j.cols.length; n++)
                if (!j.cols[n].hide)
-                       s = s + "<td class=\"lwsgt_hdr\">" + lws_san(j.cols[n].name) + "</td>";
+                       s = s + "<td class=\"lwsgt_hdr\">" + lws_san(j.cols[n].name) +
+                               "</td>";
        
        s += "</tr>";
        
        return s;
-} 
+}
+
+function lwsgt_click_callthru()
+{
+       window[this.getAttribute("h")](this.getAttribute("p"), this.getAttribute("aa"), this.getAttribute("m"), this.getAttribute("n"));
+       event.preventDefault();
+}
 
 function lwsgt_initial(title, pcol, divname, cb, gname)
 {
        this.divname = divname;
        
-       lws_gray_out(true,{'zindex':'499'});
+       lws_gray_out(true,{"zindex":"499"});
 
        if (typeof MozWebSocket != "undefined")
                this.lwsgt_ws = new MozWebSocket(lwsgt_get_appropriate_ws_url(), pcol);
@@ -71,7 +80,7 @@ function lwsgt_initial(title, pcol, divname, cb, gname)
                        lws_gray_out(false);
                //      document.getElementById("debug").textContent =
                //              "ws opened " + lwsgt_get_appropriate_ws_url();
-               }
+               };
                this.lwsgt_ws.onmessage = function got_packet(msg) {
                        var s, m, n, j = JSON.parse(msg.data);
                        document.getElementById("debug").textContent = msg.data;
@@ -114,24 +123,20 @@ function lwsgt_initial(title, pcol, divname, cb, gname)
                                s = s + "</table>";
                                document.getElementById(this.divname).innerHTML = s;
                                for (n = 0; n < q; n++)
-                                       document.getElementById(this.divname + n).onclick = lwsgt_click_callthru;
+                                       document.getElementById(this.divname + n).onclick =
+                                               lwsgt_click_callthru;
 
                                for (n = 0; n < this.bcq; n++)
-                                       document.getElementById("bc_" + this.divname + n).onclick = lwsgt_click_callthru;
+                                       document.getElementById("bc_" + this.divname + n).onclick =
+                                               lwsgt_click_callthru;
 
                        }               
-               }
+               };
                this.lwsgt_ws.onclose = function(){
-                       lws_gray_out(true,{'zindex':'499'});
-               }
+                       lws_gray_out(true,{"zindex":"499"});
+               };
        } catch(exception) {
-               alert('<p>Error' + exception);  
+               alert("<p>Error" + exception);  
        }
 }
 
-function lwsgt_click_callthru()
-{
-       window[this.getAttribute("h")](this.getAttribute("p"), this.getAttribute("aa"), this.getAttribute("m"), this.getAttribute("n"));
-       event.preventDefault();
-}
-
index 49a2bb4..026ae57 100644 (file)
@@ -21,8 +21,9 @@
 
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 
+#include <stdlib.h>
 #include <string.h>
 #include <uv.h>
 
@@ -229,7 +230,7 @@ callback_lws_table_dirlisting(struct lws *wsi, enum lws_callback_reasons reason,
                if (len > sizeof(pss->reldir) - 1)
                        len = sizeof(pss->reldir) - 1;
                if (!strstr(in, "..") && !strchr(in, '~'))
-                       strncpy(pss->reldir, in, len);
+                       lws_strncpy(pss->reldir, in, len + 1);
                else
                        len = 0;
                pss->reldir[len] = '\0';
@@ -261,21 +262,19 @@ callback_lws_table_dirlisting(struct lws *wsi, enum lws_callback_reasons reason,
                                s1[0] = '\0';
                                q += strlen(q);
                        } else {
-                               n = q1 - q;
-                               if (n > sizeof(s) - 1)
+                               n = lws_ptr_diff(q1, q);
+                               if (n > (int)sizeof(s) - 1)
                                        n = sizeof(s) - 1;
                                if (first) {
                                        strcpy(s1, "/");
                                        strcpy(s, "top");
                                } else {
-                                       strncpy(s, q, n);
-                                       s[n] = '\0';
+                                       lws_strncpy(s, q, n + 1);
 
-                                       n = q1 - pss->reldir;
-                                       if (n > sizeof(s1) - 1)
+                                       n = lws_ptr_diff(q1, pss->reldir);
+                                       if (n > (int)sizeof(s1) - 1)
                                                n = sizeof(s1) - 1;
-                                       strncpy(s1, pss->reldir, n);
-                                       s1[n] = '\0';
+                                       lws_strncpy(s1, pss->reldir, n + 1);
                                }
                                q = q1 + 1;
                        }
@@ -381,7 +380,7 @@ init_protocol_lws_table_dirlisting(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index 687a4cd..9a7a031 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for "client_loopback_test"
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
@@ -20,7 +20,7 @@
 
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #include <string.h>
 
 struct per_session_data__client_loopback_test {
@@ -150,8 +150,9 @@ callback_client_loopback_test(struct lws *wsi, enum lws_callback_reasons reason,
                break;
 
        case LWS_CALLBACK_CLIENT_RECEIVE:
-               strncpy(buf, in, sizeof(buf) - 1);
-               lwsl_notice("Client connection received %ld from server '%s'\n", (long)len, buf);
+               lws_strncpy(buf, in, sizeof(buf));
+               lwsl_notice("Client connection received %ld from server '%s'\n",
+                           (long)len, buf);
 
                /* OK we are done with the client connection */
                return -1;
@@ -183,7 +184,7 @@ init_protocol_client_loopback_test(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index 8d14260..11d6fef 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for "dumb increment"
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
 #if !defined (LWS_PLUGIN_STATIC)
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #endif
 
 #include <string.h>
 
-#if defined(LWS_WITH_ESP8266)
-#define DUMB_PERIOD 50
-#else
-#define DUMB_PERIOD 50
-#endif
-
-struct per_vhost_data__dumb_increment {
-       uv_timer_t timeout_watcher;
-       struct lws_context *context;
-       struct lws_vhost *vhost;
-       const struct lws_protocols *protocol;
-};
+#define DUMB_PERIOD_US 50000
 
-struct per_session_data__dumb_increment {
+struct pss__dumb_increment {
        int number;
 };
 
-static void
-uv_timeout_cb_dumb_increment(uv_timer_t *w
-#if UV_VERSION_MAJOR == 0
-               , int status
-#endif
-)
-{
-       struct per_vhost_data__dumb_increment *vhd = lws_container_of(w,
-                       struct per_vhost_data__dumb_increment, timeout_watcher);
-       lws_callback_on_writable_all_protocol_vhost(vhd->vhost, vhd->protocol);
-}
+struct vhd__dumb_increment {
+       const unsigned int *options;
+};
 
 static int
 callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                        void *user, void *in, size_t len)
 {
-       struct per_session_data__dumb_increment *pss =
-                       (struct per_session_data__dumb_increment *)user;
-       struct per_vhost_data__dumb_increment *vhd =
-                       (struct per_vhost_data__dumb_increment *)
-                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
-                                       lws_get_protocol(wsi));
-       unsigned char buf[LWS_PRE + 20];
-       unsigned char *p = &buf[LWS_PRE];
+       struct pss__dumb_increment *pss = (struct pss__dumb_increment *)user;
+       struct vhd__dumb_increment *vhd =
+                               (struct vhd__dumb_increment *)
+                               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                               lws_get_protocol(wsi));
+       uint8_t buf[LWS_PRE + 20], *p = &buf[LWS_PRE];
+       const struct lws_protocol_vhost_options *opt;
        int n, m;
 
        switch (reason) {
        case LWS_CALLBACK_PROTOCOL_INIT:
                vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
-                               lws_get_protocol(wsi),
-                               sizeof(struct per_vhost_data__dumb_increment));
-               vhd->context = lws_get_context(wsi);
-               vhd->protocol = lws_get_protocol(wsi);
-               vhd->vhost = lws_get_vhost(wsi);
-
-               uv_timer_init(lws_uv_getloop(vhd->context, 0),
-                             &vhd->timeout_watcher);
-               uv_timer_start(&vhd->timeout_watcher,
-                              uv_timeout_cb_dumb_increment, DUMB_PERIOD, DUMB_PERIOD);
-
-               break;
-
-       case LWS_CALLBACK_PROTOCOL_DESTROY:
+                       lws_get_protocol(wsi),
+                       sizeof(struct vhd__dumb_increment));
                if (!vhd)
-                       break;
-               lwsl_notice("di: LWS_CALLBACK_PROTOCOL_DESTROY: v=%p, ctx=%p\n", vhd, vhd->context);
-               uv_timer_stop(&vhd->timeout_watcher);
-               uv_close((uv_handle_t *)&vhd->timeout_watcher, NULL);
+                       return -1;
+               if ((opt = lws_pvo_search(
+                               (const struct lws_protocol_vhost_options *)in,
+                               "options")))
+                       vhd->options = (unsigned int *)opt->value;
                break;
 
        case LWS_CALLBACK_ESTABLISHED:
                pss->number = 0;
+               if (!vhd->options || !((*vhd->options) & 1))
+                       lws_set_timer_usecs(wsi, DUMB_PERIOD_US);
                break;
 
        case LWS_CALLBACK_SERVER_WRITEABLE:
-               n = lws_snprintf((char *)p, sizeof(buf) - LWS_PRE, "%d", pss->number++);
+               n = lws_snprintf((char *)p, sizeof(buf) - LWS_PRE, "%d",
+                                pss->number++);
                m = lws_write(wsi, p, n, LWS_WRITE_TEXT);
                if (m < n) {
                        lwsl_err("ERROR %d writing to di socket\n", n);
@@ -109,9 +81,9 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
        case LWS_CALLBACK_RECEIVE:
                if (len < 6)
                        break;
-               if (strcmp((const char *)in, "reset\n") == 0)
+               if (strncmp((const char *)in, "reset\n", 6) == 0)
                        pss->number = 0;
-               if (strcmp((const char *)in, "closeme\n") == 0) {
+               if (strncmp((const char *)in, "closeme\n", 8) == 0) {
                        lwsl_notice("dumb_inc: closing as requested\n");
                        lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY,
                                         (unsigned char *)"seeya", 5);
@@ -119,6 +91,14 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                }
                break;
 
+       case LWS_CALLBACK_TIMER:
+               if (!vhd->options || !((*vhd->options) & 1)) {
+                       lws_callback_on_writable_all_protocol_vhost(
+                               lws_get_vhost(wsi), lws_get_protocol(wsi));
+                       lws_set_timer_usecs(wsi, DUMB_PERIOD_US);
+               }
+               break;
+
        default:
                break;
        }
@@ -130,8 +110,9 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
        { \
                "dumb-increment-protocol", \
                callback_dumb_increment, \
-               sizeof(struct per_session_data__dumb_increment), \
+               sizeof(struct pss__dumb_increment), \
                10, /* rx buf size must be >= permessage-deflate rx size */ \
+               0, NULL, 0 \
        }
 
 #if !defined (LWS_PLUGIN_STATIC)
@@ -151,7 +132,7 @@ init_protocol_dumb_increment(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index a1dc14b..74f2493 100644 (file)
@@ -33,6 +33,7 @@ struct per_session_data__esplws_ota {
        esp_ota_handle_t otahandle;
        const esp_partition_t *part;
        long file_length;
+       long last_rep;
        nvs_handle nvh;
        TimerHandle_t reboot_timer;
 };
@@ -71,9 +72,9 @@ ota_choose_part(void)
                if (part == bootpart)
                        goto next;
 
+               /* OTA Partition numbering is from _OTA_MIN to less than _OTA_MAX */
                if (part->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MIN ||
-                   part->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MIN +
-                                    ESP_PARTITION_SUBTYPE_APP_OTA_MAX)
+                   part->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MAX)
                        goto next;
 
                break;
@@ -103,7 +104,7 @@ ota_file_upload_cb(void *data, const char *name, const char *filename,
        switch (state) {
        case LWS_UFS_OPEN:
                lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
-               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               lws_strncpy(pss->filename, filename, sizeof(pss->filename));
                if (strcmp(name, "ota"))
                        return 1;
 
@@ -111,12 +112,13 @@ ota_file_upload_cb(void *data, const char *name, const char *filename,
                if (!pss->part)
                        return 1;
 
-               if (esp_ota_begin(pss->part, (long)-1, &pss->otahandle) != ESP_OK) {
+               if (esp_ota_begin(pss->part, OTA_SIZE_UNKNOWN, &pss->otahandle) != ESP_OK) {
                        lwsl_err("OTA: Failed to begin\n");
                        return 1;
                }
 
                pss->file_length = 0;
+               pss->last_rep = -1;
                break;
 
        case LWS_UFS_FINAL_CONTENT:
@@ -126,9 +128,11 @@ ota_file_upload_cb(void *data, const char *name, const char *filename,
                        return 1;
                }
 
-               lwsl_debug("writing 0x%lx... 0x%lx\n",
-                          pss->part->address + pss->file_length,
-                          pss->part->address + pss->file_length + len);
+               if ((pss->file_length & ~0xffff) != (pss->last_rep & ~0xffff)) {
+                       lwsl_notice("writing 0x%lx...\n",
+                                       pss->part->address + pss->file_length);
+                       pss->last_rep = pss->file_length;
+               }
                if (esp_ota_write(pss->otahandle, buf, len) != ESP_OK) {
                        lwsl_err("OTA: Failed to write\n");
                        return 1;
@@ -192,10 +196,11 @@ 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);
+               // 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, 30);
                if (!pss->spa) {
                        pss->spa = lws_spa_create(wsi, ota_param_names,
-                                       ARRAY_SIZE(ota_param_names), 4096,
+                                       LWS_ARRAY_SIZE(ota_param_names), 4096,
                                        ota_file_upload_cb, pss);
                        if (!pss->spa)
                                return -1;
@@ -203,6 +208,7 @@ callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason,
                        pss->filename[0] = '\0';
                        pss->file_length = 0;
                }
+               lws_esp32.upload = 1;
 
                /* let it parse the POST data */
                if (lws_spa_process(pss->spa, in, len))
@@ -228,7 +234,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 +242,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,
@@ -257,6 +265,7 @@ callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason,
                        lws_spa_destroy(pss->spa);
                        pss->spa = NULL;
                }
+               lws_esp32.upload = 0;
                break;
 
        default:
index 686599c..80ac558 100644 (file)
@@ -56,6 +56,8 @@ struct per_session_data__esplws_scan {
        unsigned char changed_partway:1;
 };
 
+#define max_aps 12
+
 struct per_vhost_data__esplws_scan {
        wifi_ap_record_t ap_records[10];
        TimerHandle_t timer, reboot_timer;
@@ -63,16 +65,22 @@ struct per_vhost_data__esplws_scan {
        struct lws_context *context;
        struct lws_vhost *vhost;
        const struct lws_protocols *protocol;
+       struct lws_wifi_scan *known_aps_list;
 
        const esp_partition_t *part;
        esp_ota_handle_t otahandle;
        long file_length;
        long content_length;
 
+       int cert_remaining_days;
+
        struct lws *cwsi;
-       char json[1024];
+       char json[2048];
        int json_len;
 
+       int acme_state;
+       char acme_msg[256];
+
        uint16_t count_ap_records;
        char count_live_pss;
        unsigned char scan_ongoing:1;
@@ -115,6 +123,9 @@ static const char * const param_names[] = {
        "pri",
        "serial",
        "opts",
+       "group",
+       "role",
+       "updsettings",
 };
 
 enum enum_param_names {
@@ -123,6 +134,9 @@ enum enum_param_names {
        EPN_PRI,
        EPN_SERIAL,
        EPN_OPTS,
+       EPN_GROUP,
+       EPN_ROLE,
+       EPN_UPDSETTINGS,
 };
 
 
@@ -157,6 +171,12 @@ scan_start(struct per_vhost_data__esplws_scan *vhd)
        if (vhd->scan_ongoing)
                return;
 
+       if (lws_esp32.acme)
+               return;
+
+       if (lws_esp32.upload)
+               return;
+
        vhd->scan_ongoing = 1;
        lws_esp32.scan_consumer = scan_finished;
        lws_esp32.scan_consumer_arg = vhd;
@@ -165,10 +185,25 @@ scan_start(struct per_vhost_data__esplws_scan *vhd)
                lwsl_err("scan start failed %d\n", n);
 }
 
+static int  scan_defer;
+
 static void timer_cb(TimerHandle_t t)
 {
        struct per_vhost_data__esplws_scan *vhd = pvTimerGetTimerID(t);
 
+//     if (!lws_esp32.inet && ((scan_defer++) & 1))
+/*
+ * AP mode + scan does not work well on ESP32... if we didn't connect to an AP
+ * ourselves, just scan once at boot.  Then leave us on the AP channel.
+ *
+ * Do the callback for everyone to keep the heartbeat alive.
+ */
+       if (!lws_esp32.inet && scan_defer++) {
+                lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
+
+                return;
+       }
+
        scan_start(vhd);
 }
 
@@ -180,7 +215,7 @@ static void reboot_timer_cb(TimerHandle_t t)
 static int
 client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
 {
-#if CONFIG_LWS_IS_FACTORY_APPLICATION == 'y' && defined(CONFIG_LWS_OTA_SERVER_BASE_URL) && \
+#if defined(CONFIG_LWS_IS_FACTORY_APPLICATION)  && defined(CONFIG_LWS_OTA_SERVER_BASE_URL) && \
     defined(CONFIG_LWS_OTA_SERVER_FQDN)
        static struct lws_client_connect_info i;
        char path[256];
@@ -212,31 +247,138 @@ client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
        return 0; /* ongoing */
 }
 
+static int
+lws_wifi_scan_rssi(struct lws_wifi_scan *p)
+{
+       if (!p->count)
+               return -127;
+
+       return p->rssi / p->count;
+}
+
+/*
+ * Insert new lws_wifi_scan into linkedlist in rssi-sorted order, trimming the
+ * list if needed to keep it at or below max_aps entries.
+ */
+
+static int
+lws_wifi_scan_insert_trim(struct lws_wifi_scan **list, struct lws_wifi_scan *ns)
+{
+       int count = 0, ins = 1, worst;
+       struct lws_wifi_scan *newlist, **pworst, *pp1;
+
+       lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
+               /* try to find existing match */
+               if (!strcmp((*pp)->ssid, ns->ssid) &&
+                   !memcmp((*pp)->bssid, ns->bssid, 6)) {
+                       if ((*pp)->count > 127) {
+                               (*pp)->count /= 2;
+                               (*pp)->rssi /= 2;
+                       }
+                       (*pp)->rssi += ns->rssi;
+                       (*pp)->count++;
+                       ins = 0;
+                       break;
+               }
+       } lws_end_foreach_llp(pp, next);
+
+       if (ins) {
+               lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
+                       /* trim any excess guys */
+                       if (count++ >= max_aps - 1) {
+                               pp1 = *pp;
+                               *pp = (*pp)->next;
+                               free(pp1);
+                               continue; /* stay where we are */
+                       }
+               } lws_end_foreach_llp(pp, next);
+
+               /* we are inserting... so alloc a copy of him */
+               pp1 = malloc(sizeof(*pp1));
+               if (!pp1)
+                       return -1;
+
+               memcpy(pp1, ns, sizeof(*pp1));
+               pp1->next = *list;
+               *list = pp1;
+       }
+
+       /* sort the list ... worst first, but added at the newlist head */
+
+       newlist = NULL;
+
+       /* while anybody left on the old list */
+       while (*list) {
+               worst = 0;
+               pworst = NULL;
+
+               /* who is the worst guy still left on the old list? */
+               lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
+                       if (lws_wifi_scan_rssi(*pp) <= worst) {
+                               worst = lws_wifi_scan_rssi(*pp);
+                               pworst = pp;
+                       }
+               } lws_end_foreach_llp(pp, next);
+
+               if (pworst) {
+                       /* move the worst to the head of the new list */
+                       pp1 = *pworst;
+                       *pworst = (*pworst)->next;
+                       pp1->next = newlist;
+                       newlist = pp1;
+               }
+       }
+
+       *list = newlist;
+
+       return 0;
+}
+
 static void
 scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
 {
        struct per_vhost_data__esplws_scan *vhd = v;
        struct per_session_data__esplws_scan *p = vhd->live_pss_list;
+       struct lws_wifi_scan lws;
+       wifi_ap_record_t *r;
+       int m;
 
        lwsl_notice("%s: count %d\n", __func__, count);
 
        vhd->scan_ongoing = 0;
 
-       if (count < ARRAY_SIZE(vhd->ap_records))
+       if (count < LWS_ARRAY_SIZE(vhd->ap_records))
                vhd->count_ap_records = count;
        else
-               vhd->count_ap_records = ARRAY_SIZE(vhd->ap_records);
+               vhd->count_ap_records = LWS_ARRAY_SIZE(vhd->ap_records);
 
        memcpy(vhd->ap_records, recs, vhd->count_ap_records * sizeof(*recs));
        
        while (p) {
-               if (p->scan_state != SCAN_STATE_INITIAL && p->scan_state != SCAN_STATE_NONE)
+               if (p->scan_state != SCAN_STATE_INITIAL &&
+                   p->scan_state != SCAN_STATE_NONE)
                        p->changed_partway = 1;
                else
                        p->scan_state = SCAN_STATE_INITIAL;
                p = p->next;
        }
 
+       /* convert to generic, cumulative scan results */
+
+       for (m = 0; m < vhd->count_ap_records; m++) {
+
+               r = &vhd->ap_records[m];
+
+               lws.authmode = r->authmode;
+               lws.channel = r->primary;
+               lws.rssi = r->rssi;
+               lws.count = 1;
+               memcpy(&lws.bssid, r->bssid, 6);
+               lws_strncpy(lws.ssid, (const char *)r->ssid, sizeof(lws.ssid));
+
+               lws_wifi_scan_insert_trim(&vhd->known_aps_list, &lws);
+       }
+
        lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
 
        if (lws_esp32.inet && !vhd->cwsi && !vhd->checked_updates)
@@ -249,7 +391,7 @@ scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
                esp_wifi_connect();
 }
 
-static const char *ssl_names[] = { "ssl-pub.pem", "ssl-pri.pem" };
+static const char *ssl_names[] = { "ap-cert.pem", "ap-key.pem" };
 
 static int
 file_upload_cb(void *data, const char *name, const char *filename,
@@ -265,7 +407,7 @@ file_upload_cb(void *data, const char *name, const char *filename,
                        return -1;
 
                lwsl_notice("LWS_UFS_OPEN Filename %s\n", filename);
-               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               lws_strncpy(pss->filename, filename, sizeof(pss->filename));
                if (!strcmp(name, "pub") || !strcmp(name, "pri")) {
                        if (nvs_open("lws-station", NVS_READWRITE, &pss->nvh))
                                return 1;
@@ -292,6 +434,7 @@ file_upload_cb(void *data, const char *name, const char *filename,
                n = 0;
                if (!strcmp(name, "pri"))
                        n = 1;
+               lwsl_notice("writing %s\n", ssl_names[n]);
                n = nvs_set_blob(pss->nvh, ssl_names[n], pss->buffer, pss->file_length);
                if (n == ESP_OK)
                        nvs_commit(pss->nvh);
@@ -316,7 +459,9 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                        lws_get_protocol(wsi));
        unsigned char *start = pss->buffer + LWS_PRE - 1, *p = start,
                      *end = pss->buffer + sizeof(pss->buffer) - 1;
-       wifi_ap_record_t *r;
+       union lws_tls_cert_info_results ir;
+       struct lws_wifi_scan *lwscan;
+       char subject[64];
        int n, m;
        nvs_handle nvh;
        size_t s;
@@ -335,7 +480,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                          (TimerCallbackFunction_t)timer_cb);
                vhd->scan_ongoing = 0;
                strcpy(vhd->json, " { }");
-               scan_start(vhd);
+       //      scan_start(vhd);
                break;
 
        case LWS_CALLBACK_PROTOCOL_DESTROY:
@@ -348,21 +493,20 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
        case LWS_CALLBACK_ESTABLISHED:
                lwsl_notice("%s: ESTABLISHED\n", __func__);
                if (!vhd->live_pss_list) {
-                       scan_start(vhd);
+               //      scan_start(vhd);
                        xTimerStart(vhd->timer, 0);
                }
                vhd->count_live_pss++;
                pss->next = vhd->live_pss_list;
                vhd->live_pss_list = pss;
                /* if we have scan results, update them.  Otherwise wait */
-               if (vhd->count_ap_records) {
+//             if (vhd->count_ap_records) {
                        pss->scan_state = SCAN_STATE_INITIAL;
                        lws_callback_on_writable(wsi);
-               }
+//             }
                break;
 
        case LWS_CALLBACK_SERVER_WRITEABLE:
-
                if (vhd->autonomous_update_sampled) {
                        p += snprintf((char *)p, end - p,
                                      " {\n \"auton\":\"1\",\n \"pos\": \"%ld\",\n"
@@ -378,14 +522,18 @@ 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_NONE:
+
+                       /* fallthru */
+
                case SCAN_STATE_INITIAL:
 
                        gettimeofday(&t, NULL);
-                       if (t.tv_sec - pss->last_send.tv_sec < 10)
-                               return 0;
+               //      if (t.tv_sec - pss->last_send.tv_sec < 10)
+               //              return 0;
 
                        pss->last_send = t;
 
@@ -394,9 +542,9 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                return -1;
                        }
                        n = 0;
-                       if (nvs_get_blob(nvh, "ssl-pub.pem", NULL, &s) == ESP_OK)
+                       if (nvs_get_blob(nvh, "ap-cert.pem", NULL, &s) == ESP_OK)
                                n = 1;
-                       if (nvs_get_blob(nvh, "ssl-pri.pem", NULL, &s) == ESP_OK)
+                       if (nvs_get_blob(nvh, "ap-key.pem", NULL, &s) == ESP_OK)
                                n |= 2;
                        s = sizeof(group) - 1;
                        group[0] = '\0';
@@ -406,6 +554,26 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 
                        nvs_close(nvh);
 
+                       ir.ns.name[0] = '\0';
+                       subject[0] = '\0';
+
+                       if (t.tv_sec > 1464083026 &&
+                           !lws_tls_vhost_cert_info(vhd->vhost,
+                                      LWS_TLS_CERT_INFO_VALIDITY_TO, &ir, 0)) {
+                               vhd->cert_remaining_days =
+                                            (ir.time - t.tv_sec) / (24 * 3600);
+                               ir.ns.name[0] = '\0';
+                               lws_tls_vhost_cert_info(vhd->vhost,
+                                       LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
+                                               sizeof(ir.ns.name));
+                               lws_strncpy(subject, ir.ns.name, sizeof(subject));
+
+                               ir.ns.name[0] = '\0';
+                               lws_tls_vhost_cert_info(vhd->vhost,
+                                       LWS_TLS_CERT_INFO_ISSUER_NAME, &ir,
+                                               sizeof(ir.ns.name));
+                       }
+
                        /*
                         * this value in the JSON is just
                         * used for UI indication.  Each conditional feature confirms
@@ -418,12 +586,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"
@@ -439,10 +616,17 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                      " \"conn_ip\":\"%s\",\n"
                                      " \"conn_mask\":\"%s\",\n"
                                      " \"conn_gw\":\"%s\",\n"
+                                     " \"certdays\":\"%d\",\n"
+                                     " \"unixtime\":\"%llu\",\n"
+                                     " \"certissuer\":\"%s\",\n"
+                                     " \"certsubject\":\"%s\",\n"
+                                     " \"le_dns\":\"%s\",\n"
+                                     " \"le_email\":\"%s\",\n"
+                                     " \"acme_state\":\"%d\",\n"
+                                     " \"acme_msg\":\"%s\",\n"
+                                     " \"button\":\"%d\",\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 +639,23 @@ 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,
+                                     vhd->cert_remaining_days,
+                                     (unsigned long long)t.tv_sec,
+                                     ir.ns.name, subject,
+                                     lws_esp32.le_dns,
+                                     lws_esp32.le_email,
+                                     vhd->acme_state,
+                                     vhd->acme_msg,
+                                     ((volatile struct lws_esp32 *)(&lws_esp32))->button_is_down,
+                                     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;
@@ -488,7 +684,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                        }
 
                        for (m = 0; m < 4; m++) {
-                               char name[10], ssid[32];
+                               char name[10], ssid[65];
                                unsigned int pp = 0, use = 0;
 
                                if (m)
@@ -512,6 +708,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                        ssid, pp, use);
                        }
                        nvs_close(nvh);
+                       pss->ap_record = 0;
 
                        p += snprintf((char *)p, end - p,
                                       "], \"aps\":[\n");
@@ -521,27 +718,36 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                        break;
 
                case SCAN_STATE_LIST:
+                       lwscan = vhd->known_aps_list;
+
+                       n = pss->ap_record;
+                       while (lwscan && n--)
+                               lwscan = lwscan->next;
+
                        for (m = 0; m < 6; m++) {
                                n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
-                               if (pss->ap_record >= vhd->count_ap_records)
+                               if (!lwscan)
                                        goto scan_state_final;
 
                                if (pss->subsequent)
                                        *p++ = ',';
                                pss->subsequent = 1;
+                               pss->ap_record++;
 
-                               r = &vhd->ap_records[(int)pss->ap_record++];
                                p += snprintf((char *)p, end - p,
                                              "{\"ssid\":\"%s\",\n"
                                               "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\",\n"
                                               "\"rssi\":\"%d\",\n"
                                               "\"chan\":\"%d\",\n"
                                               "\"auth\":\"%d\"}\n",
-                                               r->ssid,
-                                               r->bssid[0], r->bssid[1], r->bssid[2],
-                                               r->bssid[3], r->bssid[4], r->bssid[5],
-                                               r->rssi, r->primary, r->authmode);
-                               if (pss->ap_record >= vhd->count_ap_records)
+                                              lwscan->ssid,
+                                              lwscan->bssid[0], lwscan->bssid[1], lwscan->bssid[2],
+                                              lwscan->bssid[3], lwscan->bssid[4], lwscan->bssid[5],
+                                              lws_wifi_scan_rssi(lwscan),
+                                              lwscan->channel, lwscan->authmode);
+
+                               lwscan = lwscan->next;
+                               if (!lwscan)
                                        pss->scan_state = SCAN_STATE_FINAL;
                        }
                        break;
@@ -551,6 +757,7 @@ scan_state_final:
                        n = LWS_WRITE_CONTINUATION;
                        p += sprintf((char *)p, "]\n}\n");
                        if (pss->changed_partway) {
+                               pss->changed_partway = 0;
                                pss->subsequent = 0;
                                pss->scan_state = SCAN_STATE_INITIAL;
                        } else {
@@ -562,7 +769,6 @@ scan_state_final:
                        return 0;
                }
 issue:
-//             lwsl_notice("issue: %d (%d)\n", p - start, n);
                m = lws_write(wsi, (unsigned char *)start, p - start, n);
                if (m < 0) {
                        lwsl_err("ERROR %d writing to di socket\n", m);
@@ -574,6 +780,16 @@ issue:
 
                break;
 
+       case LWS_CALLBACK_VHOST_CERT_UPDATE:
+               lwsl_notice("LWS_CALLBACK_VHOST_CERT_UPDATE: %d\n", (int)len);
+               vhd->acme_state = (int)len;
+               if (in) {
+                       lws_strncpy(vhd->acme_msg, in, sizeof(vhd->acme_msg));
+                       lwsl_notice("acme_msg: %s\n", (char *)in);
+               }
+               lws_callback_on_writable_all_protocol_vhost(vhd->vhost, vhd->protocol);
+               break;
+
        case LWS_CALLBACK_RECEIVE:
                {
                        const char *sect = "\"app\": {", *b;
@@ -596,6 +812,10 @@ issue:
                        if (strstr((const char *)in, "\"reset\""))
                                goto sched_reset;
 
+                       if (!strncmp((const char *)in, "{\"job\":\"start-le\"", 17))
+                               goto start_le;
+
+
                        if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
                                lwsl_err("Unable to open nvs\n");
                                break;
@@ -606,7 +826,7 @@ issue:
 
                        lwsl_notice("si %d\n", si);
 
-                       for (n = 0; n < ARRAY_SIZE(store_json); n++) {
+                       for (n = 0; n < LWS_ARRAY_SIZE(store_json); n++) {
                                if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j))
                                        continue;
 
@@ -614,16 +834,6 @@ issue:
                                if (n == 8 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
                                        continue;
 
-                               //lwsl_notice("%s: %s '%s'\n", __func__, store_json[n].nvs, p);
-                               if (n == 9) {
-                                       strncpy(lws_esp32.group, p, sizeof(lws_esp32.group) - 1);
-                                       lws_esp32.group[sizeof(lws_esp32.group) - 1] = '\0';
-                               }
-                               if (n == 10) {
-                                       strncpy(lws_esp32.role, p, sizeof(lws_esp32.role) - 1);
-                                       lws_esp32.role[sizeof(lws_esp32.role) - 1] = '\0';
-                               }
-
                                if (lws_nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
                                        lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs);
                                        goto bail_nvs;
@@ -631,20 +841,15 @@ issue:
 
                                if (si != -1 && n < 8) {
                                        if (!(n & 1)) {
-                                               strncpy(lws_esp32.ssid[(n >> 1) & 3], p,
+                                               lws_strncpy(lws_esp32.ssid[(n >> 1) & 3], p,
                                                                sizeof(lws_esp32.ssid[0]));
-                                               lws_esp32.ssid[(n >> 1) & 3]
-                                                       [sizeof(lws_esp32.ssid[0]) - 1] = '\0';
                                                lws_snprintf(use, sizeof(use) - 1, "%duse", si);
                                                lwsl_notice("resetting %s to 0\n", use);
                                                nvs_set_u32(nvh, use, 0);
 
-                                       } else {
-                                               strncpy(lws_esp32.password[(n >> 1) & 3], p,
+                                       } else
+                                               lws_strncpy(lws_esp32.password[(n >> 1) & 3], p,
                                                                sizeof(lws_esp32.password[0]));
-                                               lws_esp32.password[(n >> 1) & 3]
-                                                       [sizeof(lws_esp32.password[0]) - 1] = '\0';
-                                       }
                                }
 
                        }
@@ -719,6 +924,75 @@ auton:
                                vhd->autonomous_update = 0;
 
                        break;
+
+start_le:
+                       lws_esp32.acme = 1; /* hold off scanning */
+                       puts(in);
+                       /*
+                        * {"job":"start-le","cn":"home.warmcat.com",
+                        * "email":"andy@warmcat.com", "staging":"true"}
+                        */
+
+                       if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
+                               lwsl_err("Unable to open nvs\n");
+                               break;
+                       }
+
+                       n = 0;
+                       b = strstr(in, ",\"cn\":\"");
+                       if (b) {
+                               b += 7;
+                               while (*b && *b != '\"' && n < sizeof(lws_esp32.le_dns) - 1)
+                                       lws_esp32.le_dns[n++] = *b++;
+                       }
+                       lws_esp32.le_dns[n] = '\0';
+
+                       lws_nvs_set_str(nvh, "acme-cn", lws_esp32.le_dns);
+                       n = 0;
+                       b = strstr(in, ",\"email\":\"");
+                       if (b) {
+                               b += 10;
+                               while (*b && *b != '\"' && n < sizeof(lws_esp32.le_email) - 1)
+                                       lws_esp32.le_email[n++] = *b++;
+                       }
+                       lws_esp32.le_email[n] = '\0';
+                       lws_nvs_set_str(nvh, "acme-email", lws_esp32.le_email);
+                       nvs_commit(nvh);
+
+                       nvs_close(nvh);
+
+                       n = 1;
+                       b = strstr(in, ",\"staging\":\"");
+                       if (b)
+                               lwsl_notice("staging: %s\n", b);
+                       if (b && b[12] == 'f')
+                               n = 0;
+
+                       lwsl_notice("cn: %s, email: %s, staging: %d\n", lws_esp32.le_dns, lws_esp32.le_email, n);
+
+                       {
+                               struct lws_acme_cert_aging_args caa;
+
+                               memset(&caa, 0, sizeof(caa));
+                               caa.vh = vhd->vhost;
+
+                               caa.element_overrides[LWS_TLS_REQ_ELEMENT_COMMON_NAME] = lws_esp32.le_dns;
+                               caa.element_overrides[LWS_TLS_REQ_ELEMENT_EMAIL] = lws_esp32.le_email;
+
+                               if (n)
+                                       caa.element_overrides[LWS_TLS_SET_DIR_URL] =
+                                                       "https://acme-staging.api.letsencrypt.org/directory"; /* staging */
+                               else
+                                       caa.element_overrides[LWS_TLS_SET_DIR_URL] =
+                                               "https://acme-v01.api.letsencrypt.org/directory"; /* real */
+
+                               lws_callback_vhost_protocols_vhost(vhd->vhost,
+                                               LWS_CALLBACK_VHOST_CERT_AGING,
+                                                       (void *)&caa, 0);
+                       }
+
+                       break;
+
                }
 
        case LWS_CALLBACK_CLOSED:
@@ -744,10 +1018,9 @@ auton:
 
        case LWS_CALLBACK_HTTP_BODY:
                /* create the POST argument parser if not already existing */
-               lwsl_notice("LWS_CALLBACK_HTTP_BODY (scan)\n");
                if (!pss->spa) {
                        pss->spa = lws_spa_create(wsi, param_names,
-                                       ARRAY_SIZE(param_names), 1024,
+                                       LWS_ARRAY_SIZE(param_names), 1024,
                                        file_upload_cb, pss);
                        if (!pss->spa)
                                return -1;
@@ -755,7 +1028,7 @@ auton:
                        pss->filename[0] = '\0';
                        pss->file_length = 0;
                }
-
+               //puts((const char *)in);
                /* let it parse the POST data */
                if (lws_spa_process(pss->spa, in, len))
                        return -1;
@@ -766,6 +1039,14 @@ auton:
                /* call to inform no more payload data coming */
                lws_spa_finalize(pss->spa);
 
+               for (n = 0; n < LWS_ARRAY_SIZE(param_names); n++)
+                       if (lws_spa_get_string(pss->spa, n))
+                               lwsl_notice(" Param %s: %s\n", param_names[n],
+                                           lws_spa_get_string(pss->spa, n));
+                       else
+                               lwsl_notice(" Param %s: (none)\n",
+                                           param_names[n]);
+
                if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
                        lwsl_err("Unable to open nvs\n");
                        break;
@@ -791,12 +1072,29 @@ auton:
                                nvs_commit(nvh);
                        }
                }
+
+               if (lws_spa_get_string(pss->spa, EPN_GROUP)) {
+                       if (lws_nvs_set_str(nvh, "group", lws_spa_get_string(pss->spa, EPN_GROUP)) != ESP_OK) {
+                               lwsl_err("Unable to store group in nvm\n");
+                               goto bail_nvs;
+                       }
+
+                       nvs_commit(nvh);
+               }
+
+               if (lws_spa_get_string(pss->spa, EPN_ROLE)) {
+                       if (lws_nvs_set_str(nvh, "role", lws_spa_get_string(pss->spa, EPN_ROLE)) != ESP_OK) {
+                               lwsl_err("Unable to store group in nvm\n");
+                               goto bail_nvs;
+                       }
+
+                       nvs_commit(nvh);
+               }
+
                nvs_close(nvh);
 
                pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
-                               "<html>Rebooting after storing certs...<br>connect to AP '<b>config-%s-%s</b>' and continue here: "
-                               "<a href=\"https://192.168.4.1\">https://192.168.4.1</a></html>",
-                               lws_esp32.model, lws_spa_get_string(pss->spa, EPN_SERIAL));
+                               "<html>OK</html>");
 
                if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
                        goto bail;
@@ -810,15 +1108,13 @@ auton:
                        goto bail;
 
                n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
-               if (n < 0)
-                       goto bail;
-
-               lws_callback_on_writable(wsi);
-               break;
+               goto bail;
 
        case LWS_CALLBACK_HTTP_WRITEABLE:
                lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
                           pss->result_len);
+               if (!pss->result_len)
+                       break;
                n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
                              pss->result_len, LWS_WRITE_HTTP);
                if (n < 0)
diff --git a/plugins/protocol_fulltext_demo.c b/plugins/protocol_fulltext_demo.c
new file mode 100644 (file)
index 0000000..046fedc
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * ws protocol handler plugin for "fulltext demo"
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * These test plugins are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef WIN32
+#include <io.h>
+#endif
+#include <stdio.h>
+
+struct vhd_fts_demo {
+       const char *indexpath;
+};
+
+struct pss_fts_demo {
+       struct lwsac *result;
+       struct lws_fts_result_autocomplete *ac;
+       struct lws_fts_result_filepath *fp;
+
+       uint32_t *li;
+       int done;
+
+       uint8_t first:1;
+       uint8_t ac_done:1;
+
+       uint8_t fp_init_done:1;
+};
+
+static int
+callback_fts(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+            void *in, size_t len)
+{
+       struct vhd_fts_demo *vhd = (struct vhd_fts_demo *)
+               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                        lws_get_protocol(wsi));
+       struct pss_fts_demo *pss = (struct pss_fts_demo *)user;
+       uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
+               *end = &buf[sizeof(buf) - LWS_PRE - 1];
+       struct lws_fts_search_params params;
+       const char *ccp = (const char *)in;
+       struct lws_fts_result *result;
+       struct lws_fts_file *jtf;
+       int n;
+
+       switch (reason) {
+
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                            lws_get_protocol(wsi),sizeof(struct vhd_fts_demo));
+               if (!vhd)
+                       return 1;
+               if (lws_pvo_get_str(in, "indexpath",
+                                   (const char **)&vhd->indexpath))
+                       return 1;
+
+               return 0;
+
+       case LWS_CALLBACK_HTTP:
+
+               pss->first = 1;
+               pss->ac_done = 0;
+
+               /*
+                * we have a "subdirectory" selecting the task
+                *
+                * /a/ = autocomplete
+                * /r/ = results
+                */
+
+               if (strncmp(ccp, "/a/", 3) && strncmp(ccp, "/r/", 3))
+                       goto reply_404;
+
+               memset(&params, 0, sizeof(params));
+
+               params.needle = ccp + 3;
+               if (*(ccp + 1) == 'a')
+                       params.flags = LWSFTS_F_QUERY_AUTOCOMPLETE;
+               if (*(ccp + 1) == 'r')
+                       params.flags = LWSFTS_F_QUERY_FILES |
+                                      LWSFTS_F_QUERY_FILE_LINES |
+                                      LWSFTS_F_QUERY_QUOTE_LINE;
+               params.max_autocomplete = 10;
+               params.max_files = 10;
+
+               jtf = lws_fts_open(vhd->indexpath);
+               if (!jtf) {
+                       lwsl_err("unable to open %s\n", vhd->indexpath);
+                       /* we'll inform the client in the JSON */
+                       goto reply_200;
+               }
+
+               result = lws_fts_search(jtf, &params);
+               lws_fts_close(jtf);
+               if (result) {
+                       pss->result = params.results_head;
+                       pss->ac = result->autocomplete_head;
+                       pss->fp = result->filepath_head;
+               }
+               /* NULL result will be told in the json as "indexed": 0 */
+
+reply_200:
+               if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
+                                               "text/html",
+                                       LWS_ILLEGAL_HTTP_CONTENT_LEN, &p, end))
+                       return 1;
+
+               if (lws_finalize_write_http_header(wsi, start, &p, end))
+                       return 1;
+
+               lws_callback_on_writable(wsi);
+               return 0;
+
+reply_404:
+               if (lws_add_http_common_headers(wsi, HTTP_STATUS_NOT_FOUND,
+                                               "text/html",
+                                       LWS_ILLEGAL_HTTP_CONTENT_LEN, &p, end))
+                       return 1;
+
+               if (lws_finalize_write_http_header(wsi, start, &p, end))
+                       return 1;
+               return lws_http_transaction_completed(wsi);
+
+       case LWS_CALLBACK_CLOSED_HTTP:
+               if (pss && pss->result)
+                       lwsac_free(&pss->result);
+               break;
+
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+
+               if (!pss)
+                       break;
+
+               n = LWS_WRITE_HTTP;
+               if (pss->first)
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                               "{\"indexed\": %d, \"ac\": [", !!pss->result);
+
+               while (pss->ac && lws_ptr_diff(end, p) > 256) {
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                               "%c{\"ac\": \"%s\",\"matches\": %d,"
+                               "\"agg\": %d, \"elided\": %d}",
+                               pss->first ? ' ' : ',', (char *)(pss->ac + 1),
+                               pss->ac->instances, pss->ac->agg_instances,
+                               pss->ac->elided);
+
+                       pss->first = 0;
+                       pss->ac = pss->ac->next;
+               }
+
+               if (!pss->ac_done && !pss->ac && pss->fp) {
+                       pss->ac_done = 1;
+
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                                         "], \"fp\": [");
+               }
+
+               while (pss->fp && lws_ptr_diff(end, p) > 256) {
+                       if (!pss->fp_init_done) {
+                               p += lws_snprintf((char *)p,
+                                       lws_ptr_diff(end, p),
+                                       "%c{\"path\": \"%s\",\"matches\": %d,"
+                                       "\"origlines\": %d,"
+                                       "\"hits\": [", pss->first ? ' ' : ',',
+                                       ((char *)(pss->fp + 1)) +
+                                               pss->fp->matches_length,
+                                       pss->fp->matches,
+                                       pss->fp->lines_in_file);
+
+                               pss->li = ((uint32_t *)(pss->fp + 1));
+                               pss->done = 0;
+                               pss->fp_init_done = 1;
+                               pss->first = 0;
+                       } else {
+                               while (pss->done < pss->fp->matches &&
+                                      lws_ptr_diff(end, p) > 256) {
+
+                                       p += lws_snprintf((char *)p,
+                                               lws_ptr_diff(end, p),
+                                               "%c\n{\"l\":%d,\"o\":%d,"
+                                               "\"s\":\"%s\"}",
+                                               !pss->done ? ' ' : ',',
+                                               pss->li[0], pss->li[1],
+                                               *((const char **)&pss->li[2]));
+                                       pss->li += 2 + (sizeof(const char *) /
+                                                       sizeof(uint32_t));
+                                       pss->done++;
+                               }
+
+                               if (pss->done == pss->fp->matches) {
+                                       *p++ = ']';
+                                       pss->fp_init_done = 0;
+                                       pss->fp = pss->fp->next;
+                                       if (!pss->fp)
+                                               *p++ = '}';
+                               }
+                       }
+               }
+
+               if (!pss->ac && !pss->fp) {
+                       n = LWS_WRITE_HTTP_FINAL;
+                       p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
+                                               "]}");
+               }
+
+               if (lws_write(wsi, (uint8_t *)start,
+                             lws_ptr_diff(p, start), n) !=
+                                             lws_ptr_diff(p, start))
+                       return 1;
+
+               if (n == LWS_WRITE_HTTP_FINAL) {
+                       if (pss->result)
+                               lwsac_free(&pss->result);
+                       if (lws_http_transaction_completed(wsi))
+                               return -1;
+               } else
+                       lws_callback_on_writable(wsi);
+
+               return 0;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+
+#define LWS_PLUGIN_PROTOCOL_FULLTEXT_DEMO \
+       { \
+               "lws-test-fts", \
+               callback_fts, \
+               sizeof(struct pss_fts_demo), \
+               0, \
+               0, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_FULLTEXT_DEMO
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_fulltext_demo(struct lws_context *context,
+                       struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_fulltext_demo(struct lws_context *context)
+{
+       return 0;
+}
+
+#endif
diff --git a/plugins/protocol_lws_meta.c b/plugins/protocol_lws_meta.c
deleted file mode 100644 (file)
index db25bad..0000000
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * lws meta protocol handler
- *
- * Copyright (C) 2017 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- *
- */
-
-#if !defined (LWS_PLUGIN_STATIC)
-#define LWS_DLL
-#define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
-#endif
-
-#include <string.h>
-#include <stdlib.h>
-
-#define MAX_SUBCHANNELS 8
-
-enum lws_meta_parser_state {
-       MP_IDLE, /* in body of message */
-
-       MP_CMD, /* await cmd */
-
-       MP_OPEN_SUBCHANNEL_PROTOCOL,
-       MP_OPEN_SUBCHANNEL_URL,
-       MP_OPEN_SUBCHANNEL_COOKIE,
-
-       MP_CLOSE_CHID,
-       MP_CLOSE_LEN,
-       MP_CLOSE_CODEM,
-       MP_CLOSE_CODEL,
-       MP_CLOSE_PAYLOAD,
-
-       MP_WRITE_CHID,
-};
-
-enum {
-       PENDING_TYPE_OPEN_RESULT = 0,
-       PENDING_TYPE_CHILD_CLOSE
-};
-
-/*
- * while we haven't reported the result yet, we keep a linked-list of
- * connection opens and their result.
- */
-struct pending_conn {
-       struct pending_conn *next;
-       char protocol[123];
-       char cookie[8];
-       int ch;
-       int len;
-
-       unsigned char type;
-};
-
-/*
- * the parent, lws-meta connection
- */
-struct per_session_data__lws_meta {
-       struct lws *wsi[MAX_SUBCHANNELS + 1];
-       char told_closing[MAX_SUBCHANNELS + 1];
-       struct pending_conn *first;
-       struct pending_conn *pend;
-       char suburl[64];
-       unsigned char close[126];
-       int active_subchannel_tx, active_subchannel_rx;
-       enum lws_meta_parser_state state;
-       int pos;
-       int count_pending;
-       int round_robin;
-       int close_status_16;
-       int close_len;
-       int which_close;
-       int ch;
-};
-
-static int
-lws_find_free_channel(struct per_session_data__lws_meta *pss)
-{
-       int n;
-
-       for (n = 1; n <= MAX_SUBCHANNELS; n++)
-               if (pss->wsi[n] == NULL)
-                       return n;
-
-       return 0; /* none free */
-}
-
-static struct lws *
-lws_get_channel_wsi(struct per_session_data__lws_meta *pss, int ch)
-{
-       if (!ch)
-               return 0;
-       return pss->wsi[ch];
-}
-
-static int
-lws_get_channel_id(struct lws *wsi)
-{
-       return (lws_intptr_t)lws_get_opaque_parent_data(wsi);
-}
-
-static void
-lws_set_channel_id(struct lws *wsi, int id)
-{
-       lws_set_opaque_parent_data(wsi, (void *)(lws_intptr_t)id);
-}
-
-static struct pending_conn *
-new_pending(struct per_session_data__lws_meta *pss)
-{
-       struct pending_conn *pend;
-
-       if (pss->count_pending >= MAX_SUBCHANNELS * 2) {
-               lwsl_notice("too many pending open subchannel\n");
-
-               return NULL;
-       }
-
-       pss->count_pending++;
-
-       pend = malloc(sizeof(*pend));
-       if (!pend) {
-               lwsl_notice("OOM\n");
-
-               return NULL;
-       }
-
-       memset(pend, 0, sizeof(*pend));
-
-       return pend;
-}
-
-static int
-callback_lws_meta(struct lws *wsi, enum lws_callback_reasons reason,
-                   void *user, void *in, size_t len)
-{
-       struct per_session_data__lws_meta *pss =
-                       (struct per_session_data__lws_meta *)user;
-       struct lws_write_passthru *pas;
-       struct pending_conn *pend, *pend1;
-       struct lws *cwsi;
-       lws_sock_file_fd_type fd;
-       unsigned char *bin, buf[LWS_PRE + 512], *start = &buf[LWS_PRE],
-                       *end = &buf[sizeof(buf) - 1], *p = start;
-       int n, m;
-
-       switch (reason) {
-
-       case LWS_CALLBACK_ESTABLISHED:
-               lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
-               pss->state = MP_CMD;
-               pss->pos = 0;
-               break;
-
-       case LWS_CALLBACK_CLOSED:
-               break;
-
-       case LWS_CALLBACK_CHILD_CLOSING:
-               cwsi = (struct lws *)in;
-
-               /* remove it from our tracking */
-               pss->wsi[lws_get_channel_id(cwsi)] = NULL;
-
-               if (pss->told_closing[lws_get_channel_id(cwsi)]) {
-                       pss->told_closing[lws_get_channel_id(cwsi)] = 0;
-                       break;
-               }
-
-               pend = new_pending(pss);
-               if (!pend)
-                       return -1;
-
-               /* note which channel id */
-               pend->ch = lws_get_channel_id(cwsi);
-
-               if (lws_get_close_length(cwsi)) {
-                       pend->len = lws_get_close_length(cwsi);
-                       memcpy(pend->protocol, lws_get_close_payload(cwsi),
-                                       pend->len);
-               }
-
-               pend->type = PENDING_TYPE_CHILD_CLOSE;
-               pend->next = pss->first;
-               pss->first = pend;
-
-               /*
-                * nothing else will complete from this wsi, so abandon
-                * tracking in-process messages from this wsi.
-                */
-
-               if (pss->active_subchannel_tx == pend->ch)
-                       pss->active_subchannel_tx = 0;
-
-               if (pss->active_subchannel_rx == pend->ch)
-                       pss->active_subchannel_rx = 0;
-               break;
-
-       case LWS_CALLBACK_SERVER_WRITEABLE:
-
-               if (!pss->active_subchannel_tx) {
-
-                       /* not in the middle of a message...
-                        *
-                        * PRIORITY 1: pending open and close notifications
-                        */
-
-                       pend = pss->first;
-                       while (pend && p < end - 128) {
-                               switch (pend->type) {
-                               case PENDING_TYPE_OPEN_RESULT:
-                                       lwsl_debug("open result %s %s\n",
-                                               pend->cookie, pend->protocol);
-                                       *p++ = LWS_META_CMD_OPEN_RESULT;
-                                       memcpy(p, pend->cookie,
-                                              strlen(pend->cookie) + 1);
-                                       p += strlen(pend->cookie) + 1;
-                                       *p++ = LWS_META_TRANSPORT_OFFSET +
-                                                       pend->ch;
-                                       memcpy(p, pend->protocol,
-                                              strlen(pend->protocol) + 1);
-                                       p += strlen(pend->protocol) + 1;
-                                       break;
-                               case PENDING_TYPE_CHILD_CLOSE:
-                                       *p++ = LWS_META_CMD_CLOSE_NOTIFY;
-                                       *p++ = LWS_META_TRANSPORT_OFFSET +
-                                                       pend->ch;
-                                       for (n = 0; n < pend->len; n++)
-                                               *p++ = pend->protocol[n];
-                                       break;
-                               }
-
-                               pss->count_pending--;
-                               pend1 = pend;
-                               pend = pend->next;
-                               free(pend1);
-                               pss->first = pend;
-                       }
-
-                       if (p != start) {
-                               if (lws_write(wsi, start, p - start,
-                                             LWS_WRITE_BINARY) < 0)
-                                       return 1;
-                               if (pend) /* still more */
-                                       lws_callback_on_writable(wsi);
-                               break;
-                       }
-
-                       /* PRIORITY 2: pick a child for the writable callback */
-
-                       cwsi = NULL;
-                       for (n = 0; n < MAX_SUBCHANNELS; n++) {
-                               m = ((pss->round_robin + n) % MAX_SUBCHANNELS) + 1;
-                               if (pss->wsi[m] &&
-                                   lws_get_child_pending_on_writable(pss->wsi[m])) {
-                                       pss->round_robin = m;
-                                       cwsi = pss->wsi[m];
-                                       break;
-                               }
-                       }
-               } else
-                       /* one child is in middle of message, stay with it */
-                       cwsi = pss->wsi[pss->active_subchannel_tx];
-
-               if (!cwsi)
-                       break;
-
-               lws_clear_child_pending_on_writable(cwsi);
-               if (lws_handle_POLLOUT_event(cwsi, NULL))
-                       return -1;
-               break;
-
-       case LWS_CALLBACK_RECEIVE:
-               bin = (unsigned char *)in;
-
-               /*
-                * at the start of a message, we may have one or more
-                * lws_meta command blocks.
-                */
-               while (pss->state != MP_IDLE &&
-                      (unsigned int)(bin - (unsigned char *)in) < len) {
-
-                       switch (pss->state) {
-                       case MP_IDLE: /* in body of message */
-
-                               if (!lws_is_first_fragment(wsi))
-                                       break;
-
-                               pss->state = MP_CMD;
-
-                               /* fallthru */
-
-                       case MP_CMD: /* await cmd */
-
-                               pss->pos = 0;
-
-                               switch (*bin++) {
-                               case LWS_META_CMD_OPEN_SUBCHANNEL:
-
-                                       pss->pend = new_pending(pss);
-                                       if (!pss->pend)
-                                               return -1;
-
-                                       pss->state = MP_OPEN_SUBCHANNEL_PROTOCOL;
-
-                                       break;
-                               case LWS_META_CMD_CLOSE_NOTIFY:
-                               case LWS_META_CMD_CLOSE_RQ:
-                                       pss->which_close = bin[-1];
-                                       pss->state = MP_CLOSE_CHID;
-                                       break;
-                               case LWS_META_CMD_WRITE:
-                                       pss->state = MP_WRITE_CHID;
-                                       break;
-
-                               // open result is also illegal to receive
-                               default:
-                                       lwsl_notice("bad lws_meta cmd 0x%x\n",
-                                                   bin[-1]);
-
-                                       return -1;
-                               }
-
-                               break;
-
-                       case MP_OPEN_SUBCHANNEL_PROTOCOL:
-                               pss->pend->protocol[pss->pos++] = *bin++;
-                               if (pss->pos == sizeof(pss->pend->protocol) - 1) {
-                                       lwsl_notice("protocol name too long\n");
-                                       return -1;
-                               }
-
-                               if (bin[-1] != '\0')
-                                       break;
-
-                               pss->state = MP_OPEN_SUBCHANNEL_URL;
-                               pss->pos = 0;
-                               break;
-
-                       case MP_OPEN_SUBCHANNEL_URL:
-                               pss->suburl[pss->pos++] = *bin++;
-                               if (pss->pos == sizeof(pss->suburl) - 1) {
-                                       lwsl_notice("suburl too long\n");
-                                       return -1;
-                               }
-
-                               if (bin[-1] != '\0')
-                                       break;
-
-                               pss->state = MP_OPEN_SUBCHANNEL_COOKIE;
-                               pss->pos = 0;
-                               break;
-
-                       case MP_OPEN_SUBCHANNEL_COOKIE:
-                               pss->pend->cookie[pss->pos++] = *bin++;
-                               if (pss->pos == sizeof(pss->pend->cookie) - 1) {
-                                       lwsl_notice("cookie too long\n");
-                                       return -1;
-                               }
-
-                               if (bin[-1] != '\0')
-                                       break;
-
-                               lwsl_debug("%s: %s / %s / %s\n", __func__,
-                                           pss->pend->protocol,
-                                           pss->suburl,
-                                           pss->pend->cookie);
-
-                               pss->pend->ch = lws_find_free_channel(pss);
-                               if (pss->pend->ch) {
-
-                                       fd.sockfd = 0; // not going to be used
-
-                                       cwsi = lws_adopt_descriptor_vhost(
-                                                       lws_get_vhost(wsi),
-                                                       LWS_ADOPT_WS_PARENTIO,
-                                                       fd, pss->pend->protocol,
-                                                       wsi);
-
-                                       if (!cwsi) {
-                                               lwsl_notice("open failed\n");
-                                               pss->pend->ch = 0;
-                                       } else {
-                                               pss->wsi[pss->pend->ch] = cwsi;
-                                               lws_set_channel_id(cwsi,
-                                                               pss->pend->ch);
-                                               lwsl_debug("cwsi %p on parent %p open OK %s\n",
-                                                       cwsi, wsi, pss->pend->protocol);
-                                       }
-
-                               } else
-                                       lwsl_notice("no free subchannels\n");
-
-                               pss->pend->type = PENDING_TYPE_OPEN_RESULT;
-                               pss->pend->next = pss->first;
-                               pss->first = pss->pend;
-
-                               lws_callback_on_writable(wsi);
-
-                               pss->state = MP_CMD;
-                               pss->pos = 0;
-                               break;
-
-                       case MP_CLOSE_CHID:
-                               pss->ch = (*bin++) - LWS_META_TRANSPORT_OFFSET;
-                               pss->state = MP_CLOSE_LEN;
-                               pss->pos = 0;
-                               break;
-                       case MP_CLOSE_LEN:
-                               pss->close_len = (*bin++) -
-                                       LWS_META_TRANSPORT_OFFSET;
-                               lwsl_debug("close len %d\n", pss->close_len);
-                               pss->state = MP_CLOSE_CODEM;
-                               pss->pos = 0;
-                               break;
-                       case MP_CLOSE_CODEM:
-                               pss->close[pss->pos++] = *bin;
-                               pss->close_status_16 = (*bin++) * 256;
-                               pss->state = MP_CLOSE_CODEL;
-                               break;
-                       case MP_CLOSE_CODEL:
-                               pss->close[pss->pos++] = *bin;
-                               pss->close_status_16 |= *bin++;
-                               pss->state = MP_CLOSE_PAYLOAD;
-                               break;
-                       case MP_CLOSE_PAYLOAD:
-                               pss->close[pss->pos++] = *bin++;
-                               if (pss->pos == sizeof(pss->close) - 1) {
-                                       lwsl_notice("close payload too long\n");
-                                       return -1;
-                               }
-                               if (--pss->close_len)
-                                       break;
-
-                               pss->state = MP_CMD;
-
-                               cwsi = lws_get_channel_wsi(pss, pss->ch);
-                               if (!cwsi) {
-                                       lwsl_notice("close (%d) bad ch %d\n",
-                                               pss->which_close, pss->ch);
-                                       break;
-                               }
-
-                               if (pss->which_close == LWS_META_CMD_CLOSE_RQ) {
-                                       if (lws_get_protocol(cwsi)->callback(
-                                           cwsi,
-                                           LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
-                                           lws_wsi_user(cwsi), &pss->close,
-                                           pss->pos))
-                                               return -1;
-
-                                       /*
-                                        * we need to echo back the close payload
-                                        * when we send the close notification
-                                        */
-                                       lws_close_reason(cwsi,
-                                                        pss->close_status_16,
-                                                        &pss->close[2],
-                                                        pss->pos - 2);
-                               }
-
-                               /* so force him closed */
-
-                               lws_set_timeout(cwsi,
-                                       PENDING_TIMEOUT_KILLED_BY_PARENT,
-                                       LWS_TO_KILL_SYNC);
-                               break;
-
-                       case MP_WRITE_CHID:
-                               pss->active_subchannel_rx = (*bin++) -
-                                       LWS_META_TRANSPORT_OFFSET;
-                               pss->state = MP_IDLE;
-                               break;
-                       }
-               }
-
-               len -= bin - (unsigned char *)in;
-
-               if (!len)
-                       break;
-
-               cwsi = lws_get_channel_wsi(pss, pss->active_subchannel_rx);
-               if (!cwsi) {
-                       lwsl_notice("bad ch %d\n", pss->active_subchannel_rx);
-
-                       return -1;
-               }
-
-               lwsl_debug("%s: RX len %d\n", __func__, (int)len);
-
-               if (lws_get_protocol(cwsi)->callback(cwsi,
-                                       LWS_CALLBACK_RECEIVE,
-                                       lws_wsi_user(cwsi), bin, len))
-                       lws_set_timeout(cwsi,
-                               PENDING_TIMEOUT_KILLED_BY_PARENT,
-                               LWS_TO_KILL_SYNC);
-
-               if (lws_is_final_fragment(wsi)) {
-                       pss->active_subchannel_rx = 0;
-                       pss->state = MP_CMD;
-               }
-               break;
-
-       /*
-        * child wrote something via lws_write.... which passed it up to us to
-        * deal with, because we are the parent.  Prepend two bytes for
-        * lws-meta command and channel index, and send it out on parent
-        */
-       case LWS_CALLBACK_CHILD_WRITE_VIA_PARENT:
-               pas = in;
-               bin = ((unsigned char *)pas->buf);
-
-               if ((pas->wp & 7) == 4 /*LWS_WRITE_CLOSE */) {
-                       *p++ = LWS_META_CMD_CLOSE_NOTIFY;
-                       *p++ = LWS_META_TRANSPORT_OFFSET +
-                                       lws_get_channel_id(pas->wsi);
-                       *p++ = (unsigned char)pas->len +
-                                       LWS_META_TRANSPORT_OFFSET - 2;
-                       *p++ = *bin++;
-                       *p++ = *bin++;
-                       for (n = 0; n < (int)pas->len - 2; n++)
-                               *p++ = bin[n];
-
-                       if (lws_write(wsi, start, p - start,
-                                     LWS_WRITE_BINARY) < 0)
-                               return 1;
-
-                       pss->told_closing[lws_get_channel_id(pas->wsi)] = 1;
-                       break;
-               }
-
-               if ((pas->wp & 7) == LWS_WRITE_TEXT ||
-                   (pas->wp & 7) == LWS_WRITE_BINARY) {
-
-                       if (pas->wp & LWS_WRITE_NO_FIN)
-                               pss->active_subchannel_tx =
-                                               lws_get_channel_id(pas->wsi);
-
-                       /* start of message, prepend the subchannel id */
-
-                       bin -= 2;
-                       bin[0] = LWS_META_CMD_WRITE;
-                       bin[1] = lws_get_channel_id(pas->wsi) +
-                                       LWS_META_TRANSPORT_OFFSET;
-                       if (lws_write(wsi, bin, pas->len + 2, pas->wp) < 0)
-                               return 1;
-               } else
-                       if (lws_write(wsi, bin, pas->len, pas->wp) < 0)
-                               return 1;
-
-               /* track EOM */
-
-               if (!(pas->wp & LWS_WRITE_NO_FIN))
-                       pss->active_subchannel_tx = 0;
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-#define LWS_PLUGIN_PROTOCOL_LWS_META { \
-               "lws-meta", \
-               callback_lws_meta, \
-               sizeof(struct per_session_data__lws_meta), \
-               1024, /* rx buf size must be >= permessage-deflate rx size */ \
-               0, NULL, 0 \
-       }
-
-#if !defined (LWS_PLUGIN_STATIC)
-
-static const struct lws_protocols protocols[] = {
-       LWS_PLUGIN_PROTOCOL_LWS_META
-};
-
-LWS_EXTERN LWS_VISIBLE int
-init_protocol_lws_meta(struct lws_context *context,
-                            struct lws_plugin_capability *c)
-{
-       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
-               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
-                        c->api_magic);
-               return 1;
-       }
-
-       c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
-       c->extensions = NULL;
-       c->count_extensions = 0;
-
-       return 0;
-}
-
-LWS_EXTERN LWS_VISIBLE int
-destroy_protocol_lws_meta(struct lws_context *context)
-{
-       return 0;
-}
-#endif
index afac1b6..70f501c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets-test-server - libwebsockets test implementation
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
  * The test apps are intended to be adapted for use in your code, which
  * may be proprietary.  So unlike the library itself, they are licensed
  * Public Domain.
+ *
+ * Notice that the lws_pthread... locking apis are all zero-footprint
+ * NOPs in the case LWS_MAX_SMP == 1, which is the default.  When lws
+ * is built for multiple service threads though, they resolve to their
+ * pthreads equivalents.
  */
 
 #if !defined (LWS_PLUGIN_STATIC)
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #endif
 
 #include <string.h>
 #include <stdlib.h>
 
-/* lws-mirror_protocol */
-
-#if defined(LWS_WITH_ESP8266)
-#define MAX_MESSAGE_QUEUE 64
-#else
-#define MAX_MESSAGE_QUEUE 512
-#endif
+#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 ((2 * QUEUELEN) / 3)
 
-#define MAX_MIRROR_INSTANCES 10
+#define MAX_MIRROR_INSTANCES 3
 
-struct lws_mirror_instance;
+struct mirror_instance;
 
 struct per_session_data__lws_mirror {
        struct lws *wsi;
-       struct lws_mirror_instance *mi;
+       struct mirror_instance *mi;
        struct per_session_data__lws_mirror *same_mi_pss_list;
-       int ringbuffer_tail;
+       uint32_t tail;
 };
 
+/* this is the element in the ring */
 struct a_message {
        void *payload;
        size_t len;
 };
 
-struct lws_mirror_instance {
-       struct lws_mirror_instance *next;
+struct mirror_instance {
+       struct mirror_instance *next;
+       lws_pthread_mutex(lock) /* protects all mirror instance data */
        struct per_session_data__lws_mirror *same_mi_pss_list;
+       /**< must hold the the per_vhost_data__lws_mirror.lock as well
+        * to change mi list membership */
+       struct lws_ring *ring;
+       int messages_allocated;
        char name[30];
-       struct a_message ringbuffer[MAX_MESSAGE_QUEUE];
-       int ringbuffer_head;
+       char rx_enabled;
 };
 
 struct per_vhost_data__lws_mirror {
-       struct lws_mirror_instance *mi_list;
+       lws_pthread_mutex(lock) /* protects mi_list membership changes */
+       struct mirror_instance *mi_list;
 };
 
+
+/* enable or disable rx from all connections to this mirror instance */
+static void
+__mirror_rxflow_instance(struct mirror_instance *mi, int enable)
+{
+       lws_start_foreach_ll(struct per_session_data__lws_mirror *,
+                            pss, mi->same_mi_pss_list) {
+               lws_rx_flow_control(pss->wsi, enable);
+       } lws_end_foreach_ll(pss, same_mi_pss_list);
+
+       mi->rx_enabled = enable;
+}
+
+/*
+ * Find out which connection to this mirror instance has the longest number
+ * of still unread elements in the ringbuffer and update the lws_ring "oldest
+ * tail" with it.  Elements behind the "oldest tail" are freed and recycled for
+ * new head content.  Elements after the "oldest tail" are still waiting to be
+ * read by somebody.
+ *
+ * If the oldest tail moved on from before, check if it created enough space
+ * in the queue to re-enable RX flow control for the mirror instance.
+ *
+ * Mark connections that are at the oldest tail as being on a 3s timeout to
+ * transmit something, otherwise the connection will be closed.  Without this,
+ * a choked or nonresponsive connection can block the FIFO from freeing up any
+ * new space for new data.
+ *
+ * You can skip calling this if on your connection, before processing, the tail
+ * was not equal to the current worst, ie,  if the tail you will work on is !=
+ * lws_ring_get_oldest_tail(ring) then no need to call this when the tail
+ * has changed; it wasn't the oldest so it won't change the oldest.
+ *
+ * Returns 0 if oldest unchanged or 1 if oldest changed from this call.
+ */
+static int
+__mirror_update_worst_tail(struct mirror_instance *mi)
+{
+       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);
+
+       lws_start_foreach_ll(struct per_session_data__lws_mirror *,
+                            pss, mi->same_mi_pss_list) {
+               wai = (uint32_t)lws_ring_get_count_waiting_elements(mi->ring,
+                                                               &pss->tail);
+               if (wai >= worst) {
+                       worst = wai;
+                       worst_tail = pss->tail;
+                       worst_pss = pss;
+               }
+       } lws_end_foreach_ll(pss, same_mi_pss_list);
+
+       if (!worst_pss)
+               return 0;
+
+       lws_ring_update_oldest_tail(mi->ring, worst_tail);
+       if (oldest == lws_ring_get_oldest_tail(mi->ring))
+               return 0;
+       /*
+        * The oldest tail did move on.  Check if we should re-enable rx flow
+        * for the mirror instance since we made some space now.
+        */
+       if (!mi->rx_enabled && /* rx is disabled */
+           lws_ring_get_count_free_elements(mi->ring) >= RXFLOW_MAX)
+               /* there is enough space, let's re-enable rx for our instance */
+               __mirror_rxflow_instance(mi, 1);
+
+       /* if nothing in queue, no timeout needed */
+       if (!worst)
+               return 1;
+
+       /*
+        * The guy(s) with the oldest tail block the ringbuffer from recycling
+        * the FIFO entries he has not read yet.  Don't allow those guys to
+        * block the FIFO operation for very long.
+        */
+       lws_start_foreach_ll(struct per_session_data__lws_mirror *,
+                            pss, mi->same_mi_pss_list) {
+               if (pss->tail == worst_tail)
+                       /*
+                        * Our policy is if you are the slowest connection,
+                        * you had better transmit something to help with that
+                        * within 3s, or we will hang up on you to stop you
+                        * blocking the FIFO for everyone else.
+                        */
+                       lws_set_timeout(pss->wsi,
+                                       PENDING_TIMEOUT_USER_REASON_BASE, 3);
+       } lws_end_foreach_ll(pss, same_mi_pss_list);
+
+       return 1;
+}
+
+static void
+__mirror_callback_all_in_mi_on_writable(struct mirror_instance *mi)
+{
+       /* ask for WRITABLE callback for every wsi on this mi */
+       lws_start_foreach_ll(struct per_session_data__lws_mirror *,
+                            pss, mi->same_mi_pss_list) {
+               lws_callback_on_writable(pss->wsi);
+       } lws_end_foreach_ll(pss, same_mi_pss_list);
+}
+
+static void
+__mirror_destroy_message(void *_msg)
+{
+       struct a_message *msg = _msg;
+
+       free(msg->payload);
+       msg->payload = NULL;
+       msg->len = 0;
+}
+
 static int
 callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
                    void *user, void *in, size_t len)
@@ -72,13 +195,15 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
        struct per_vhost_data__lws_mirror *v =
                        (struct per_vhost_data__lws_mirror *)
                        lws_protocol_vh_priv_get(lws_get_vhost(wsi),
-                                       lws_get_protocol(wsi));
-       struct lws_mirror_instance *mi = NULL;
-       char name[30];
-       int n, m, count_mi = 0;
+                                                lws_get_protocol(wsi));
+       char name[300], update_worst, sent_something, *pn = name;
+       struct mirror_instance *mi = NULL;
+       const struct a_message *msg;
+       struct a_message amsg;
+       uint32_t oldest_tail;
+       int n, count_mi = 0;
 
        switch (reason) {
-
        case LWS_CALLBACK_ESTABLISHED:
                lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
 
@@ -86,176 +211,243 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
                 * mirror instance name... defaults to "", but if URL includes
                 * "?mirror=xxx", will be "xxx"
                 */
-
                name[0] = '\0';
-               lws_get_urlarg_by_name(wsi, "mirror", name, sizeof(name) - 1);
+               if (!lws_get_urlarg_by_name(wsi, "mirror", name,
+                                          sizeof(name) - 1))
+                       lwsl_debug("get urlarg failed\n");
+               if (strchr(name, '='))
+                       pn = strchr(name, '=') + 1;
 
-               lwsl_notice("mirror %s\n", name);
+               //lwsl_notice("%s: mirror name '%s'\n", __func__, pn);
 
                /* is there already a mirror instance of this name? */
 
-               lws_start_foreach_ll(struct lws_mirror_instance *,
-                                    mi1, v->mi_list) {
+               lws_pthread_mutex_lock(&v->lock); /* vhost lock { */
+
+               lws_start_foreach_ll(struct mirror_instance *, mi1,
+                                    v->mi_list) {
                        count_mi++;
-                       if (strcmp(name, mi1->name))
-                               continue;
-                       /* yes... we will join it */
-                       lwsl_notice("Joining existing mi %p '%s'\n", mi1, name);
-                       mi = mi1;
-                       break;
+                       if (!strcmp(pn, mi1->name)) {
+                               /* yes... we will join it */
+                               mi = mi1;
+                               break;
+                       }
                } lws_end_foreach_ll(mi1, next);
 
                if (!mi) {
 
                        /* no existing mirror instance for name */
-
-                       if (count_mi == MAX_MIRROR_INSTANCES)
+                       if (count_mi == MAX_MIRROR_INSTANCES) {
+                               lws_pthread_mutex_unlock(&v->lock); /* } vh lock */
                                return -1;
+                       }
 
                        /* create one with this name, and join it */
-
                        mi = malloc(sizeof(*mi));
+                       if (!mi)
+                               goto bail1;
                        memset(mi, 0, sizeof(*mi));
+                       mi->ring = lws_ring_create(sizeof(struct a_message),
+                                                  QUEUELEN,
+                                                  __mirror_destroy_message);
+                       if (!mi->ring) {
+                               free(mi);
+                               goto bail1;
+                       }
+
                        mi->next = v->mi_list;
                        v->mi_list = mi;
-                       strcpy(mi->name, name);
-                       mi->ringbuffer_head = 0;
+                       lws_snprintf(mi->name, sizeof(mi->name) - 1, "%s", pn);
+                       mi->rx_enabled = 1;
+
+                       lws_pthread_mutex_init(&mi->lock);
 
-                       lwsl_notice("Created new mi %p '%s'\n", mi, name);
+                       lwsl_notice("Created new mi %p '%s'\n", mi, pn);
                }
 
                /* add our pss to list of guys bound to this mi */
 
-               pss->same_mi_pss_list = mi->same_mi_pss_list;
-               mi->same_mi_pss_list = pss;
+               lws_ll_fwd_insert(pss, same_mi_pss_list, mi->same_mi_pss_list);
 
                /* init the pss */
 
                pss->mi = mi;
-               pss->ringbuffer_tail = mi->ringbuffer_head;
+               pss->tail = lws_ring_get_oldest_tail(mi->ring);
                pss->wsi = wsi;
 
+               lws_pthread_mutex_unlock(&v->lock); /* } vhost lock */
                break;
 
-       case LWS_CALLBACK_CLOSED:
+bail1:
+               lws_pthread_mutex_unlock(&v->lock); /* } vhost lock */
+               return 1;
 
+       case LWS_CALLBACK_CLOSED:
                /* detach our pss from the mirror instance */
-
                mi = pss->mi;
                if (!mi)
                        break;
 
-               lws_start_foreach_llp(struct per_session_data__lws_mirror **,
-                       ppss, mi->same_mi_pss_list) {
-                       if (*ppss == pss) {
-
-                               *ppss = pss->same_mi_pss_list;
-                               break;
-                       }
-               } lws_end_foreach_llp(ppss, same_mi_pss_list);
+               lws_pthread_mutex_lock(&v->lock); /* vhost lock { */
 
+               /* remove our closing pss from its mirror instance list */
+               lws_ll_fwd_remove(struct per_session_data__lws_mirror,
+                                 same_mi_pss_list, pss, mi->same_mi_pss_list);
                pss->mi = NULL;
 
-               if (mi->same_mi_pss_list)
+               if (mi->same_mi_pss_list) {
+                       /*
+                        * Still other pss using the mirror instance.  The pss
+                        * going away may have had the oldest tail, reconfirm
+                        * using the remaining pss what is the current oldest
+                        * tail.  If the oldest tail moves on, this call also
+                        * will re-enable rx flow control when appropriate.
+                        */
+                       lws_pthread_mutex_lock(&mi->lock); /* mi lock { */
+                       __mirror_update_worst_tail(mi);
+                       lws_pthread_mutex_unlock(&mi->lock); /* } mi lock */
+                       lws_pthread_mutex_unlock(&v->lock); /* } vhost lock */
                        break;
+               }
 
-               /* last pss unbound from mi... delete mi */
+               /* No more pss using the mirror instance... delete mi */
 
-               lws_start_foreach_llp(struct lws_mirror_instance **,
+               lws_start_foreach_llp(struct mirror_instance **,
                                pmi, v->mi_list) {
-                       if (*pmi != mi)
-                               continue;
+                       if (*pmi == mi) {
+                               *pmi = (*pmi)->next;
 
-                       *pmi = (*pmi)->next;
+                               lws_ring_destroy(mi->ring);
+                               lws_pthread_mutex_destroy(&mi->lock);
 
-                       lwsl_info("%s: mirror cleaniup %p\n", __func__, v);
-                       for (n = 0; n < ARRAY_SIZE(mi->ringbuffer); n++)
-                               if (mi->ringbuffer[n].payload) {
-                                       free(mi->ringbuffer[n].payload);
-                                       mi->ringbuffer[n].payload = NULL;
-                               }
-
-                       free(mi);
-                       break;
+                               free(mi);
+                               break;
+                       }
                } lws_end_foreach_llp(pmi, next);
 
+               lws_pthread_mutex_unlock(&v->lock); /* } vhost lock */
                break;
 
+       case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
+               return 1; /* disallow compression */
+
        case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
                lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
                                lws_get_protocol(wsi),
                                sizeof(struct per_vhost_data__lws_mirror));
+               v = (struct per_vhost_data__lws_mirror *)
+                               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                        lws_get_protocol(wsi));
+               lws_pthread_mutex_init(&v->lock);
                break;
 
-       case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               lws_pthread_mutex_destroy(&v->lock);
                break;
 
        case LWS_CALLBACK_SERVER_WRITEABLE:
-               while (pss->ringbuffer_tail != pss->mi->ringbuffer_head) {
-                       m = pss->mi->ringbuffer[pss->ringbuffer_tail].len;
-                       n = lws_write(wsi, (unsigned char *)
-                                       pss->mi->ringbuffer[pss->ringbuffer_tail].payload +
-                                  LWS_PRE, m, LWS_WRITE_TEXT);
+               lws_pthread_mutex_lock(&pss->mi->lock); /* instance lock { */
+               oldest_tail = lws_ring_get_oldest_tail(pss->mi->ring);
+               update_worst = oldest_tail == pss->tail;
+               sent_something = 0;
+
+               do {
+                       msg = lws_ring_get_element(pss->mi->ring, &pss->tail);
+                       if (!msg)
+                               break;
+
+                       if (!msg->payload) {
+                               lwsl_err("%s: NULL payload: worst = %d,"
+                                        " pss->tail = %d\n", __func__,
+                                        oldest_tail, pss->tail);
+                               if (lws_ring_consume(pss->mi->ring, &pss->tail,
+                                                    NULL, 1))
+                                       continue;
+                               break;
+                       }
+
+                       n = lws_write(wsi, (unsigned char *)msg->payload +
+                                     LWS_PRE, msg->len, LWS_WRITE_TEXT);
                        if (n < 0) {
-                               lwsl_err("ERROR %d writing to mirror socket\n", n);
-                               return -1;
+                               lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
+
+                               goto bail2;
                        }
-                       if (n < m)
-                               lwsl_err("mirror partial write %d vs %d\n", n, m);
+                       sent_something = 1;
+                       lws_ring_consume(pss->mi->ring, &pss->tail, NULL, 1);
 
-                       if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1))
-                               pss->ringbuffer_tail = 0;
-                       else
-                               pss->ringbuffer_tail++;
+               } while (!lws_send_pipe_choked(wsi));
 
-                       if (((pss->mi->ringbuffer_head - pss->ringbuffer_tail) &
-                           (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15))
-                               lws_rx_flow_allow_all_protocol(lws_get_context(wsi),
-                                              lws_get_protocol(wsi));
+               /* if any left for us to send, ask for writeable again */
+               if (lws_ring_get_count_waiting_elements(pss->mi->ring,
+                                                       &pss->tail))
+                       lws_callback_on_writable(wsi);
 
-                       if (lws_send_pipe_choked(wsi)) {
-                               lws_callback_on_writable(wsi);
-                               break;
-                       }
-               }
+               if (!sent_something || !update_worst)
+                       goto done1;
+
+               /*
+                * We are no longer holding the oldest tail (since we sent
+                * something.  So free us of the timeout related to hogging the
+                * oldest tail.
+                */
+               lws_set_timeout(pss->wsi, NO_PENDING_TIMEOUT, 0);
+               /*
+                * If we were originally at the oldest fifo position of
+                * all the tails, now we used some up we may have
+                * changed the oldest fifo position and made some space.
+                */
+               __mirror_update_worst_tail(pss->mi);
+
+done1:
+               lws_pthread_mutex_unlock(&pss->mi->lock); /* } instance lock */
                break;
 
+bail2:
+               lws_pthread_mutex_unlock(&pss->mi->lock); /* } instance lock */
+
+               return -1;
+
        case LWS_CALLBACK_RECEIVE:
-               if (((pss->mi->ringbuffer_head - pss->ringbuffer_tail) &
-                   (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) {
-                       lwsl_err("dropping!\n");
-                       goto choke;
+               lws_pthread_mutex_lock(&pss->mi->lock); /* mi lock { */
+               n = (int)lws_ring_get_count_free_elements(pss->mi->ring);
+               if (!n) {
+                       lwsl_notice("dropping!\n");
+                       if (pss->mi->rx_enabled)
+                               __mirror_rxflow_instance(pss->mi, 0);
+                       goto req_writable;
                }
 
-               if (pss->mi->ringbuffer[pss->mi->ringbuffer_head].payload)
-                       free(pss->mi->ringbuffer[pss->mi->ringbuffer_head].payload);
+               amsg.payload = malloc(LWS_PRE + len);
+               amsg.len = len;
+               if (!amsg.payload) {
+                       lwsl_notice("OOM: dropping\n");
+                       goto done2;
+               }
 
-               pss->mi->ringbuffer[pss->mi->ringbuffer_head].payload = malloc(LWS_PRE + len);
-               pss->mi->ringbuffer[pss->mi->ringbuffer_head].len = len;
-               memcpy((char *)pss->mi->ringbuffer[pss->mi->ringbuffer_head].payload +
-                      LWS_PRE, in, len);
-               if (pss->mi->ringbuffer_head == (MAX_MESSAGE_QUEUE - 1))
-                       pss->mi->ringbuffer_head = 0;
-               else
-                       pss->mi->ringbuffer_head++;
+               memcpy((char *)amsg.payload + LWS_PRE, in, len);
+               if (!lws_ring_insert(pss->mi->ring, &amsg, 1)) {
+                       __mirror_destroy_message(&amsg);
+                       lwsl_notice("dropping!\n");
+                       if (pss->mi->rx_enabled)
+                               __mirror_rxflow_instance(pss->mi, 0);
+                       goto req_writable;
+               }
 
-               if (((pss->mi->ringbuffer_head - pss->ringbuffer_tail) &
-                   (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2))
-                       goto done;
+               if (pss->mi->rx_enabled &&
+                   lws_ring_get_count_free_elements(pss->mi->ring) <
+                                                                   RXFLOW_MIN)
+                       __mirror_rxflow_instance(pss->mi, 0);
 
-choke:
-               lwsl_debug("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi);
-               lws_rx_flow_control(wsi, 0);
+req_writable:
+               __mirror_callback_all_in_mi_on_writable(pss->mi);
 
-done:
-               /*
-                *  ask for WRITABLE callback for every wsi bound to this
-                * mirror instance
-                */
-               lws_start_foreach_ll(struct per_session_data__lws_mirror *,
-                                       pss1, pss->mi->same_mi_pss_list) {
-                       lws_callback_on_writable(pss1->wsi);
-               } lws_end_foreach_ll(pss1, same_mi_pss_list);
+done2:
+               lws_pthread_mutex_unlock(&pss->mi->lock); /* } mi lock */
+               break;
+
+       case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
+               lwsl_info("LWS_CALLBACK_EVENT_WAIT_CANCELLED\n");
                break;
 
        default:
@@ -269,7 +461,8 @@ done:
                "lws-mirror-protocol", \
                callback_lws_mirror, \
                sizeof(struct per_session_data__lws_mirror), \
-               128, /* rx buf size must be >= permessage-deflate rx size */ \
+               4096, /* rx buf size must be >= permessage-deflate rx size */ \
+               0, NULL, 0 \
        }
 
 #if !defined (LWS_PLUGIN_STATIC)
@@ -289,7 +482,7 @@ init_protocol_lws_mirror(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index 35867e6..51dbe4f 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for testing raw file and raw socket
  *
- * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
@@ -42,7 +42,8 @@
  * RAW Socket Descriptor Testing
  * =============================
  *
- * 1) You must give the vhost the option flag LWS_SERVER_OPTION_FALLBACK_TO_RAW
+ * 1) You must give the vhost the option flag
+ *     LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG
  *
  * 2) Enable on a vhost like this
  *
@@ -67,7 +68,7 @@
 #if !defined (LWS_PLUGIN_STATIC)
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #endif
 
 #include <string.h>
@@ -88,8 +89,8 @@ struct per_session_data__raw_test {
 };
 
 static int
-callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
-                       void *user, void *in, size_t len)
+callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+                 void *in, size_t len)
 {
        struct per_session_data__raw_test *pss =
                        (struct per_session_data__raw_test *)user;
@@ -111,14 +112,17 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
                vhd->vhost = lws_get_vhost(wsi);
                {
                        const struct lws_protocol_vhost_options *pvo =
-                                       (const struct lws_protocol_vhost_options *)in;
+                               (const struct lws_protocol_vhost_options *)in;
                        while (pvo) {
                                if (!strcmp(pvo->name, "fifo-path"))
-                                       strncpy(vhd->fifo_path, pvo->value, sizeof(vhd->fifo_path) - 1);
+                                       lws_strncpy(vhd->fifo_path, pvo->value,
+                                                       sizeof(vhd->fifo_path));
                                pvo = pvo->next;
                        }
                        if (vhd->fifo_path[0] == '\0') {
-                               lwsl_err("%s: Missing pvo \"fifo-path\", raw file fd testing disabled\n", __func__);
+                               lwsl_err("%s: Missing pvo \"fifo-path\", "
+                                        "raw file fd testing disabled\n",
+                                        __func__);
                                break;
                        }
                }
@@ -127,7 +131,7 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
                        lwsl_err("mkfifo failed\n");
                        return 1;
                }
-               vhd->fifo = open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
+               vhd->fifo = lws_open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
                if (vhd->fifo == -1) {
                        lwsl_err("opening fifo failed\n");
                        unlink(vhd->fifo_path);
@@ -135,7 +139,8 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
                }
                lwsl_notice("FIFO %s created\n", vhd->fifo_path);
                u.filefd = vhd->fifo;
-               if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u,
+               if (!lws_adopt_descriptor_vhost(vhd->vhost,
+                                               LWS_ADOPT_RAW_FILE_DESC, u,
                                                "protocol-lws-raw-test",
                                                NULL)) {
                        lwsl_err("Failed to adopt fifo descriptor\n");
@@ -148,7 +153,7 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
        case LWS_CALLBACK_PROTOCOL_DESTROY:
                if (!vhd)
                        break;
-               if (vhd->fifo >- 0) {
+               if (vhd->fifo >= 0) {
                        close(vhd->fifo);
                        unlink(vhd->fifo_path);
                }
@@ -176,16 +181,18 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
                                return 1;
                        }
                        /*
-                        * When nobody opened the other side of the FIFO, the FIFO fd acts well and
-                        * only signals POLLIN when somebody opened and wrote to it.
+                        * When nobody opened the other side of the FIFO, the
+                        * FIFO fd acts well and only signals POLLIN when
+                        * somebody opened and wrote to it.
                         *
-                        * But if the other side of the FIFO closed it, we will see an endless
-                        * POLLIN and 0 available to read.
+                        * But if the other side of the FIFO closed it, we will
+                        * see an endless POLLIN and 0 available to read.
                         *
-                        * The only way to handle it is to reopen the FIFO our side and wait for a
-                        * new peer.  This is a quirk of FIFOs not of LWS.
+                        * The only way to handle it is to reopen the FIFO our
+                        * side and wait for a new peer.  This is a quirk of
+                        * FIFOs not of LWS.
                         */
-                       if (n == 0) { /* peer closed - do reopen in close processing */
+                       if (n == 0) { /* peer closed - reopen in close processing */
                                vhd->zero_length_read = 1;
                                return 1;
                        }
@@ -200,15 +207,19 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
                if (vhd->zero_length_read) {
                        vhd->zero_length_read = 0;
                        close(vhd->fifo);
-                       /* the wsi that adopted the fifo file is closing... reopen the fifo and readopt */
-                       vhd->fifo = open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
+                       /* the wsi that adopted the fifo file is closing...
+                        * reopen the fifo and readopt
+                        */
+                       vhd->fifo = lws_open(vhd->fifo_path,
+                                            O_NONBLOCK | O_RDONLY);
                        if (vhd->fifo == -1) {
                                lwsl_err("opening fifo failed\n");
                                return 1;
                        }
                        lwsl_notice("FIFO %s reopened\n", vhd->fifo_path);
                        u.filefd = vhd->fifo;
-                       if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test", NULL)) {
+                       if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u,
+                                       "protocol-lws-raw-test", NULL)) {
                                lwsl_err("Failed to adopt fifo descriptor\n");
                                close(vhd->fifo);
                                return 1;
@@ -278,7 +289,7 @@ init_protocol_lws_raw_test(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index 96b2ef2..0e6bda1 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets-test-server - libwebsockets test implementation
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
 
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #include <string.h>
 #include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <fcntl.h>
-
-struct lws_ss_load_sample {
-       time_t t;
-       int load_x100;
-};
+#include <stdio.h>
 
 struct lws_ss_filepath {
        struct lws_ss_filepath *next;
@@ -38,22 +36,21 @@ struct lws_ss_filepath {
 struct lws_ss_dumps {
        char buf[32768];
        int length;
-
-       struct lws_ss_load_sample load[64];
-       int load_head;
-       int load_tail;
 };
 
-struct per_session_data__server_status {
+struct pss {
        int ver;
        int pos;
 };
 
-struct per_vhost_data__lws_server_status {
-       uv_timer_t timeout_watcher;
+struct vhd {
        struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
        int hide_vhosts;
        int tow_flag;
+       int period_s;
+       int clients;
        struct lws_ss_dumps d;
        struct lws_ss_filepath *fp;
 };
@@ -61,61 +58,41 @@ struct per_vhost_data__lws_server_status {
 static const struct lws_protocols protocols[1];
 
 static void
-uv_timeout_cb_server_status(uv_timer_t *w
-#if UV_VERSION_MAJOR == 0
-               , int status
-#endif
-)
+update(struct vhd *v)
 {
-       struct per_vhost_data__lws_server_status *v = lws_container_of(w,
-                       struct per_vhost_data__lws_server_status,
-                       timeout_watcher);
        struct lws_ss_filepath *fp;
-       char *p = v->d.buf + LWS_PRE, contents[256], pure[256];
-       int n, l, first = 1, fd;
-
-       l = sizeof(v->d.buf) - LWS_PRE - 1;
-
-       n = lws_snprintf(p, l, "{\"i\":");
-       p += n;
-       l -= n;
+       char contents[256], pure[256], *p = v->d.buf + LWS_PRE,
+            *end = v->d.buf + sizeof(v->d.buf) - LWS_PRE - 1;
+       int n, first = 1, fd;
 
-       n = lws_json_dump_context(v->context, p, l, v->hide_vhosts);
-       p += n;
-       l -= n;
-
-       n = lws_snprintf(p, l, ", \"files\": [");
-       p += n;
-       l -= n;
+       p += lws_snprintf(p, lws_ptr_diff(end, p), "{\"i\":");
+       p += lws_json_dump_context(v->context, p, lws_ptr_diff(end, p),
+                                  v->hide_vhosts);
+       p += lws_snprintf(p, lws_ptr_diff(end, p), ", \"files\": [");
 
        fp = v->fp;
        while (fp) {
-               if (!first) {
-                       n = lws_snprintf(p, l, ",");
-                       p += n;
-                       l -= n;
-               }
-               fd = open(fp->filepath, LWS_O_RDONLY);
+               if (!first)
+                       p += lws_snprintf(p, lws_ptr_diff(end, p), ",");
+
+               fd = lws_open(fp->filepath, LWS_O_RDONLY);
                if (fd >= 0) {
                        n = read(fd, contents, sizeof(contents) - 1);
                        if (n >= 0) {
                                contents[n] = '\0';
                                lws_json_purify(pure, contents, sizeof(pure));
 
-                               n = lws_snprintf(p, l, "{\"path\":\"%s\",\"val\":\"%s\"}",
-                                                fp->filepath, pure);
-                               p += n;
-                               l -= n;
+                               p += lws_snprintf(p, lws_ptr_diff(end, p),
+                                       "{\"path\":\"%s\",\"val\":\"%s\"}",
+                                               fp->filepath, pure);
                                first = 0;
                        }
                        close(fd);
                }
+
                fp = fp->next;
        }
-       n = lws_snprintf(p, l, "]}");
-       p += n;
-       l -= n;
-
+       p += lws_snprintf(p, lws_ptr_diff(end, p), "]}");
        v->d.length = p - (v->d.buf + LWS_PRE);
 
        lws_callback_on_writable_all_protocol(v->context, &protocols[0]);
@@ -127,18 +104,36 @@ callback_lws_server_status(struct lws *wsi, enum lws_callback_reasons reason,
 {
        const struct lws_protocol_vhost_options *pvo =
                        (const struct lws_protocol_vhost_options *)in;
-       struct per_vhost_data__lws_server_status *v =
-                       (struct per_vhost_data__lws_server_status *)
+       struct vhd *v = (struct vhd *)
                        lws_protocol_vh_priv_get(lws_get_vhost(wsi),
                                        lws_get_protocol(wsi));
        struct lws_ss_filepath *fp, *fp1, **fp_old;
-       int m, period = 1000;
+       int m;
 
        switch (reason) {
 
        case LWS_CALLBACK_ESTABLISHED:
                lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
-               lws_callback_on_writable(wsi);
+               if (!v->clients++) {
+                       lws_timed_callback_vh_protocol(v->vhost, v->protocol,
+                                                      LWS_CALLBACK_USER, v->period_s);
+                       lwsl_info("%s: starting updates\n", __func__);
+               }
+               update(v);
+
+               break;
+
+       case LWS_CALLBACK_CLOSED:
+               if (!--v->clients)
+                       lwsl_notice("%s: stopping updates\n", __func__);
+
+               break;
+
+       case LWS_CALLBACK_USER:
+               update(v);
+               if (v->clients)
+                       lws_timed_callback_vh_protocol(v->vhost, v->protocol,
+                                                      LWS_CALLBACK_USER, v->period_s);
                break;
 
        case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
@@ -146,11 +141,10 @@ callback_lws_server_status(struct lws *wsi, enum lws_callback_reasons reason,
                        break;
 
                lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
-                               lws_get_protocol(wsi),
-                               sizeof(struct per_vhost_data__lws_server_status));
-               v = (struct per_vhost_data__lws_server_status *)
-                               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
-                               lws_get_protocol(wsi));
+                                           lws_get_protocol(wsi),
+                                           sizeof(struct vhd));
+               v = (struct vhd *)lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                          lws_get_protocol(wsi));
 
                fp_old = &v->fp;
 
@@ -158,28 +152,31 @@ callback_lws_server_status(struct lws *wsi, enum lws_callback_reasons reason,
                        if (!strcmp(pvo->name, "hide-vhosts"))
                                v->hide_vhosts = atoi(pvo->value);
                        if (!strcmp(pvo->name, "update-ms"))
-                               period = atoi(pvo->value);
+                               v->period_s = (atoi(pvo->value) + 500) / 1000;
+                       else
+                               v->period_s = 5;
                        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_start(&v->timeout_watcher,
-                               uv_timeout_cb_server_status, 2000, period);
+               v->vhost = lws_get_vhost(wsi);
+               v->protocol = lws_get_protocol(wsi);
+
+               /* get the initial data */
+               update(v);
                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);
-               uv_close((uv_handle_t *)&v->timeout_watcher, NULL);
                fp = v->fp;
                while (fp) {
                        fp1= fp->next;
@@ -206,14 +203,14 @@ static const struct lws_protocols protocols[] = {
        {
                "lws-server-status",
                callback_lws_server_status,
-               sizeof(struct per_session_data__server_status),
+               sizeof(struct pss),
                1024,
        },
 };
 
 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",
@@ -222,7 +219,7 @@ init_protocol_lws_server_status(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
@@ -234,4 +231,3 @@ destroy_protocol_lws_server_status(struct lws_context *context)
 {
        return 0;
 }
-
diff --git a/plugins/protocol_lws_sshd_demo.c b/plugins/protocol_lws_sshd_demo.c
new file mode 100644 (file)
index 0000000..934d84b
--- /dev/null
@@ -0,0 +1,482 @@
+/*
+ * ws protocol handler plugin for sshd demo
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * These test plugins are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <lws-ssh.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#define TEST_SERVER_KEY_PATH "/etc/lws-test-sshd-server-key"
+
+struct per_vhost_data__lws_sshd_demo {
+       const struct lws_protocols *ssh_base_protocol;
+       int privileged_fd;
+};
+
+/*
+ *  This is a copy of the lws ssh test public key, you can find it in
+ *  /usr[/local]/share/libwebsockets-test-server/lws-ssh-test-keys.pub
+ *  and the matching private key there too in .../lws-ssh-test-keys
+ *
+ *  If the vhost with this protocol is using localhost:2222, you can test with
+ *  the matching private key like this:
+ *
+ *  ssh -p 2222 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser@127.0.0.1
+ *
+ *  These keys are distributed for testing!  Don't use them on a real system
+ *  unless you want anyone with a copy of lws to access it.
+ */
+static const char *authorized_key =
+       "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnWiP+c+kSD6Lk+C6NA9KruApa45sbt"
+       "94/dxT0bCITlAA/+PBk6mR1lwWgXYozOMdrHrqx34piqDyXnc4HabqCaOm/FrYhkCPL8z"
+       "a26PMYqteSosuwKv//5iT6ZWhNnsMwExBwtV6MIq0MxAeWqxRnYNWpNM8iN6sFzkdG/YF"
+       "dyHrIBTgwzM77NLCMl6GEkJErRCFppC2SwYxGa3BRrgUwX3LkV8HpMIaYHFo1Qgj7Scqm"
+       "HwS2R75SOqi2aOWDpKjznARg9JgzDWSQi4seBMV2oL0BTwJANSDf+p0sQLsaKGJhpVpBQ"
+       "yS2wUeyuGyytupWzEluQrajMZq52iotcogv5BfeulfTTFbJP4kuHOsSP0lsQ2lpMDQANS"
+       "HEvXxzHJLDLXM9gXJzwJ+ZiRt6R+bfmP1nfN3MiWtxcIbBanWwQK6xTCKBe4wPaKta5EU"
+       "6wsLPeakOIVzoeaOu/HsbtPZlwX0Mu/oUFcfKyKAhlkU15MOAIEfUPo8Yh52bWMlIlpZa"
+       "4xWbLMGw3GrsrPPdcsAauyqvY4/NjjWQbWhP1SuUfvv5709PIiOUjVKK2HUwmR1ouch6X"
+       "MQGXfMR1h1Wjvc+bkNs17gCIrQnFilAZLC3Sm3Opiz/4LO99Hw448G0RM2vQn0mJE46w"
+       "Eu/B10U6Jf4Efojhh1dk85BD1LTIb+N3Q== ssh-test-key@lws";
+
+enum states {
+       SSH_TEST_GREET,
+       SSH_TEST_PRESSED,
+       SSH_TEST_DONE,
+};
+
+static const char * const strings[] =
+       {
+               /* SSH_TEST_GREET */
+               "Thanks for logging to lws sshd server demo.\n\r"
+               "\n\r"
+               "This demo is very simple, it waits for you to press\n\r"
+               "a key, and acknowledges it.  Then press another key\n\r"
+               "and it will exit.  But actually that demos the basic\n\r"
+               "sshd functions underneath.  You can use the ops struct\n\r"
+               "members to add a pty / shell or whatever you want.\n\r"
+               "\n\r"
+               "Press a key...\n\r",
+
+               /* SSH_TEST_PRESSED */
+               "Thanks for pressing a key.  Press another to exit.\n\r",
+
+               /* SSH_TEST_DONE */
+               "Bye!\n\r"
+       };
+
+struct sshd_instance_priv {
+       struct lws *wsi;
+       enum states state;
+       const char *ptr;
+       int pos;
+       int len;
+};
+
+static void
+enter_state(struct sshd_instance_priv *priv, enum states state)
+{
+       priv->state = state;
+       priv->ptr = strings[state];
+       priv->pos = 0;
+       priv->len = (int)strlen(priv->ptr);
+
+       lws_callback_on_writable(priv->wsi);
+}
+
+/* ops: channel lifecycle */
+
+static int
+ssh_ops_channel_create(struct lws *wsi, void **_priv)
+{
+       struct sshd_instance_priv *priv;
+
+       priv = malloc(sizeof(struct sshd_instance_priv));
+       *_priv = priv;
+       if (!priv)
+               return 1;
+
+       memset(priv, 0, sizeof(*priv));
+       priv->wsi = wsi;
+
+       return 0;
+}
+
+static int
+ssh_ops_channel_destroy(void *_priv)
+{
+       struct sshd_instance_priv *priv = _priv;
+
+       free(priv);
+
+       return 0;
+}
+
+/* ops: IO */
+
+static int
+ssh_ops_tx_waiting(void *_priv)
+{
+       struct sshd_instance_priv *priv = _priv;
+
+       if (priv->state == SSH_TEST_DONE &&
+           priv->pos == priv->len)
+               return -1; /* exit */
+
+       if (priv->pos != priv->len)
+               return LWS_STDOUT;
+
+       return 0;
+}
+
+static size_t
+ssh_ops_tx(void *_priv, int stdch, uint8_t *buf, size_t len)
+{
+       struct sshd_instance_priv *priv = _priv;
+       size_t chunk = len;
+
+       if (stdch != LWS_STDOUT)
+               return 0;
+
+       if ((size_t)(priv->len - priv->pos) < chunk)
+               chunk = priv->len - priv->pos;
+
+       if (!chunk)
+               return 0;
+
+       memcpy(buf, priv->ptr + priv->pos, chunk);
+       priv->pos += (int)chunk;
+
+       if (priv->state == SSH_TEST_DONE && priv->pos == priv->len) {
+               /*
+                * we are sending the last thing we want to send
+                * before exiting.  Make it ask again at ssh_ops_tx_waiting()
+                * and we will exit then, after this has been sent
+                */
+               lws_callback_on_writable(priv->wsi);
+       }
+
+       return chunk;
+}
+
+
+static int
+ssh_ops_rx(void *_priv, struct lws *wsi, const uint8_t *buf, uint32_t len)
+{
+       struct sshd_instance_priv *priv = _priv;
+
+       if (priv->state < SSH_TEST_DONE)
+               enter_state(priv, priv->state + 1);
+       else
+               return -1;
+
+       return 0;
+}
+
+/* ops: storage for the (autogenerated) persistent server key */
+
+static size_t
+ssh_ops_get_server_key(struct lws *wsi, uint8_t *buf, size_t len)
+{
+       struct per_vhost_data__lws_sshd_demo *vhd =
+                       (struct per_vhost_data__lws_sshd_demo *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                lws_get_protocol(wsi));
+       int n;
+
+       if (lseek(vhd->privileged_fd, 0, SEEK_SET) < 0)
+               return 0;
+       n = read(vhd->privileged_fd, buf, (int)len);
+       if (n < 0) {
+               lwsl_err("%s: read failed: %d\n", __func__, n);
+               n = 0;
+       }
+
+       return n;
+}
+
+static size_t
+ssh_ops_set_server_key(struct lws *wsi, uint8_t *buf, size_t len)
+{
+       struct per_vhost_data__lws_sshd_demo *vhd =
+                       (struct per_vhost_data__lws_sshd_demo *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                lws_get_protocol(wsi));
+       int n;
+
+       n = write(vhd->privileged_fd, buf, (int)len);
+       if (n < 0) {
+               lwsl_err("%s: read failed: %d\n", __func__, errno);
+               n = 0;
+       }
+
+       return n;
+}
+
+/* ops: auth */
+
+static int
+ssh_ops_is_pubkey_authorized(const char *username, const char *type,
+                                const uint8_t *peer, int peer_len)
+{
+       char *aps, *p, *ps;
+       int n = (int)strlen(type), alen = 2048, ret = 2, len;
+       size_t s = 0;
+
+       lwsl_info("%s: checking pubkey for %s\n", __func__, username);
+
+       s = strlen(authorized_key) + 1;
+
+       aps = malloc(s);
+       if (!aps) {
+               lwsl_notice("OOM 1\n");
+               goto bail_p1;
+       }
+       memcpy(aps, authorized_key, s);
+
+       /* we only understand RSA */
+       if (strcmp(type, "ssh-rsa")) {
+               lwsl_notice("type is not ssh-rsa\n");
+               goto bail_p1;
+       }
+       p = aps;
+
+       if (strncmp(p, type, n)) {
+               lwsl_notice("lead-in string  does not match %s\n", type);
+               goto bail_p1;
+       }
+
+       p += n;
+       if (*p != ' ') {
+               lwsl_notice("missing space at end of lead-in\n");
+               goto bail_p1;
+       }
+
+       p++;
+       ps = malloc(alen);
+       if (!ps) {
+               lwsl_notice("OOM 2\n");
+               free(aps);
+               goto bail;
+       }
+       len = lws_b64_decode_string(p, ps, alen);
+       free(aps);
+       if (len < 0) {
+               lwsl_notice("key too big\n");
+               goto bail;
+       }
+
+       if (peer_len > len) {
+               lwsl_notice("peer_len %d bigger than decoded len %d\n",
+                               peer_len, len);
+               goto bail;
+       }
+
+       /*
+        * once we are past that, it's the same <len32>name
+        * <len32>E<len32>N that the peer sends us
+        */
+       if (memcmp(peer, ps, peer_len)) {
+               lwsl_info("%s: factors mismatch, rejecting key\n", __func__);
+               goto bail;
+       }
+
+       lwsl_info("pubkey authorized\n");
+
+       ret = 0;
+bail:
+       free(ps);
+
+       return ret;
+
+bail_p1:
+       if (aps)
+               free(aps);
+
+       return 1;
+}
+
+static int
+ssh_ops_shell(void *_priv, struct lws *wsi, lws_ssh_finish_exec finish, void *finish_handle)
+{
+       struct sshd_instance_priv *priv = _priv;
+
+       /* for this demo, we don't open a real shell */
+
+       enter_state(priv, SSH_TEST_GREET);
+
+       return 0;
+}
+
+/* ops: banner */
+
+static size_t
+ssh_ops_banner(char *buf, size_t max_len, char *lang, size_t max_lang_len)
+{
+       int n = lws_snprintf(buf, max_len, "\n"
+                     " |\\---/|  lws-ssh Test Server\n"
+                     " | o_o |  SSH Terminal Server\n"
+                     "  \\_^_/   Copyright (C) 2017 Crash Barrier Ltd\n\n");
+
+       lws_snprintf(lang, max_lang_len, "en/US");
+
+       return n;
+}
+
+static void
+ssh_ops_disconnect_reason(uint32_t reason, const char *desc,
+                         const char *desc_lang)
+{
+       lwsl_notice("DISCONNECT reason 0x%X, %s (lang %s)\n", reason, desc,
+                   desc_lang);
+}
+
+
+static const struct lws_ssh_ops ssh_ops = {
+       .channel_create                 = ssh_ops_channel_create,
+       .channel_destroy                = ssh_ops_channel_destroy,
+       .tx_waiting                     = ssh_ops_tx_waiting,
+       .tx                             = ssh_ops_tx,
+       .rx                             = ssh_ops_rx,
+       .get_server_key                 = ssh_ops_get_server_key,
+       .set_server_key                 = ssh_ops_set_server_key,
+       .set_env                        = NULL,
+       .pty_req                        = NULL,
+       .child_process_io               = NULL,
+       .child_process_terminated       = NULL,
+       .exec                           = NULL,
+       .shell                          = ssh_ops_shell,
+       .is_pubkey_authorized           = ssh_ops_is_pubkey_authorized,
+       .banner                         = ssh_ops_banner,
+       .disconnect_reason              = ssh_ops_disconnect_reason,
+       .server_string                  = "SSH-2.0-Libwebsockets",
+       .api_version                    = 2,
+};
+
+static int
+callback_lws_sshd_demo(struct lws *wsi, enum lws_callback_reasons reason,
+                      void *user, void *in, size_t len)
+{
+       struct per_vhost_data__lws_sshd_demo *vhd =
+                       (struct per_vhost_data__lws_sshd_demo *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                                lws_get_protocol(wsi));
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                                                 lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__lws_sshd_demo));
+               /*
+                * During this we still have the privs / caps we were started
+                * with.  So open an fd on the server key, either just for read
+                * or for creat / trunc if doesn't exist.  This allows us to
+                * deal with it down /etc/.. when just after this we will lose
+                * the privileges needed to read / write /etc/...
+                */
+               vhd->privileged_fd = lws_open(TEST_SERVER_KEY_PATH, O_RDONLY);
+               if (vhd->privileged_fd == -1)
+                       vhd->privileged_fd = lws_open(TEST_SERVER_KEY_PATH,
+                                       O_CREAT | O_TRUNC | O_RDWR, 0600);
+               if (vhd->privileged_fd == -1) {
+                       lwsl_err("%s: Can't open %s\n", __func__,
+                                TEST_SERVER_KEY_PATH);
+                       return -1;
+               }
+               break;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               close(vhd->privileged_fd);
+               break;
+
+       case LWS_CALLBACK_VHOST_CERT_AGING:
+               break;
+
+       case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
+               break;
+
+       default:
+               if (!vhd->ssh_base_protocol) {
+                       vhd->ssh_base_protocol = lws_vhost_name_to_protocol(
+                                                       lws_get_vhost(wsi),
+                                                       "lws-ssh-base");
+                       if (vhd->ssh_base_protocol)
+                               user = lws_adjust_protocol_psds(wsi,
+                               vhd->ssh_base_protocol->per_session_data_size);
+               }
+
+               if (vhd->ssh_base_protocol)
+                       return vhd->ssh_base_protocol->callback(wsi, reason,
+                                                               user, in, len);
+               else
+                       lwsl_notice("can't find lws-ssh-base\n");
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_LWS_SSHD_DEMO \
+       { \
+               "lws-sshd-demo", \
+               callback_lws_sshd_demo, \
+               0, \
+               1024, /* rx buf size must be >= permessage-deflate rx size */ \
+               0, (void *)&ssh_ops, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+               
+static const struct lws_protocols protocols[] = {
+               LWS_PLUGIN_PROTOCOL_LWS_SSHD_DEMO
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_lws_sshd_demo(struct lws_context *context,
+                            struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_lws_sshd_demo(struct lws_context *context)
+{
+       return 0;
+}
+
+#endif
index 5b32139..8364be4 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets-test-server - libwebsockets test implementation
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
@@ -21,7 +21,7 @@
 #if !defined (LWS_PLUGIN_STATIC)
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #endif
 
 #include <time.h>
@@ -43,12 +43,13 @@ struct per_session_data__lws_status {
        struct per_session_data__lws_status *next;
        struct lws *wsi;
        time_t time_est;
-       char user_agent[128];
+       char user_agent[256];
 
        e_walk walk;
        struct per_session_data__lws_status *walk_next;
        unsigned char subsequent:1;
        unsigned char changed_partway:1;
+       unsigned char wss_over_h2:1;
 };
 
 struct per_vhost_data__lws_status {
@@ -62,18 +63,15 @@ struct per_vhost_data__lws_status {
 static void
 trigger_resend(struct per_vhost_data__lws_status *vhd)
 {
-       struct per_session_data__lws_status *pss = vhd->live_pss_list;
-
-       while (pss) {
+       lws_start_foreach_ll(struct per_session_data__lws_status *, pss,
+                            vhd->live_pss_list) {
                if (pss->walk == WALK_NONE) {
                        pss->subsequent = 0;
                        pss->walk_next = vhd->live_pss_list;
                        pss->walk = WALK_INITIAL;
                } else
                        pss->changed_partway = 1;
-
-               pss = pss->next;
-       }
+       } lws_end_foreach_ll(pss, next);
 
        lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
 }
@@ -85,8 +83,7 @@ callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
                    void *user, void *in, size_t len)
 {
        struct per_session_data__lws_status *pss =
-                       (struct per_session_data__lws_status *)user,
-                       *pss1, *pss2;
+                       (struct per_session_data__lws_status *)user;
        struct per_vhost_data__lws_status *vhd =
                        (struct per_vhost_data__lws_status *)
                        lws_protocol_vh_priv_get(lws_get_vhost(wsi),
@@ -118,23 +115,28 @@ callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
                pss->next = vhd->live_pss_list;
                vhd->live_pss_list = pss;
 
+               pss->wss_over_h2 = !!len;
+
                time(&pss->time_est);
                pss->wsi = wsi;
-               strcpy(pss->user_agent, "unknown");
-               lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent),
-                            WSI_TOKEN_HTTP_USER_AGENT);
+
+               if (lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent),
+                            WSI_TOKEN_HTTP_USER_AGENT) < 0) /* too big */
+                       strcpy(pss->user_agent, "unknown");
                trigger_resend(vhd);
                break;
 
        case LWS_CALLBACK_SERVER_WRITEABLE:
                switch (pss->walk) {
                case WALK_INITIAL:
-                       n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;;
+                       n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;
                        p += lws_snprintf(p, end - p,
                                      "{ \"version\":\"%s\","
+                                     " \"wss_over_h2\":\"%d\","
                                      " \"hostname\":\"%s\","
                                      " \"wsi\":\"%d\", \"conns\":[",
                                      lws_get_library_version(),
+                                     pss->wss_over_h2,
                                      lws_canonical_hostname(vhd->context),
                                      vhd->count_live_pss);
                        pss->walk = WALK_LIST;
@@ -150,14 +152,14 @@ callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
                        pss->subsequent = 1;
 
                        m = 0;
-                       pss2 = vhd->live_pss_list;
-                       while (pss2) {
+                       lws_start_foreach_ll(struct per_session_data__lws_status *,
+                                            pss2, vhd->live_pss_list) {
                                if (pss2 == pss->walk_next) {
                                        m = 1;
                                        break;
                                }
-                               pss2 = pss2->next;
-                       }
+                       } lws_end_foreach_ll(pss2, next);
+
                        if (!m) {
                                /* our next guy went away */
                                pss->walk = WALK_FINAL;
@@ -165,6 +167,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\","
@@ -178,8 +181,9 @@ callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
                case WALK_FINAL:
 walk_final:
                        n = LWS_WRITE_CONTINUATION;
-                       p += sprintf(p, "]}");
+                       p += lws_snprintf(p, 4, "]}");
                        if (pss->changed_partway) {
+                               pss->changed_partway = 0;
                                pss->subsequent = 0;
                                pss->walk_next = vhd->live_pss_list;
                                pss->walk = WALK_INITIAL;
@@ -202,26 +206,18 @@ walk_final:
 
        case LWS_CALLBACK_RECEIVE:
                lwsl_notice("pmd test: RX len %d\n", (int)len);
-               puts(in);
                break;
 
        case LWS_CALLBACK_CLOSED:
-               pss1 = vhd->live_pss_list;
-               pss2 = NULL;
-
-               while (pss1) {
-                       if (pss1 == pss) {
-                               if (pss2)
-                                       pss2->next = pss->next;
-                               else
-                                       vhd->live_pss_list = pss->next;
-
+               // lwsl_debug("****** LWS_CALLBACK_CLOSED\n");
+               lws_start_foreach_llp(struct per_session_data__lws_status **,
+                       ppss, vhd->live_pss_list) {
+                       if (*ppss == pss) {
+                               *ppss = pss->next;
                                break;
                        }
+               } lws_end_foreach_llp(ppss, next);
 
-                       pss2 = pss1;
-                       pss1 = pss1->next;
-               }
                trigger_resend(vhd);
                break;
 
@@ -238,6 +234,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)
@@ -258,7 +255,7 @@ init_protocol_lws_status(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
index 7560bf5..02503c3 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ws protocol handler plugin for "POST demo"
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
 #if !defined (LWS_PLUGIN_STATIC)
 #define LWS_DLL
 #define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
 #endif
 
+#include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
 #include <sys/types.h>
 
 struct per_session_data__post_demo {
        struct lws_spa *spa;
-       char result[LWS_PRE + 512];
-       int result_len;
-
+       char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE];
        char filename[64];
        long file_length;
-#if !defined(LWS_WITH_ESP8266)
+#if !defined(LWS_WITH_ESP32)
        lws_filefd_type fd;
 #endif
+       uint8_t completed:1;
+       uint8_t sent_headers:1;
+       uint8_t sent_body:1;
 };
 
 static const char * const param_names[] = {
@@ -65,16 +67,20 @@ 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_ESP32)
        int n;
 
+       (void)n;
+#endif
+
        switch (state) {
        case LWS_UFS_OPEN:
-               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
+               lws_strncpy(pss->filename, filename, sizeof(pss->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)
-               pss->fd = (lws_filefd_type)open("/tmp/post-file",
+#if !defined(LWS_WITH_ESP32)
+               pss->fd = (lws_filefd_type)(long long)lws_open("/tmp/post-file",
                               O_CREAT | O_TRUNC | O_RDWR, 0600);
 #endif
                break;
@@ -87,32 +93,80 @@ file_upload_cb(void *data, const char *name, const char *filename,
                        if (pss->file_length > 100000)
                                return 1;
 
-#if !defined(LWS_WITH_ESP8266)
-                       n = write((int)pss->fd, buf, len);
-                       lwsl_notice("%s: write %d says %d\n", __func__, len, n);
+#if !defined(LWS_WITH_ESP32)
+                       n = write((int)(long long)pss->fd, buf, len);
+                       lwsl_info("%s: write %d says %d\n", __func__, len, n);
 #else
                        lwsl_notice("%s: Received chunk size %d\n", __func__, len);
 #endif
                }
                if (state == LWS_UFS_CONTENT)
                        break;
-#if !defined(LWS_WITH_ESP8266)
-               close((int)pss->fd);
+#if !defined(LWS_WITH_ESP32)
+               close((int)(long long)pss->fd);
                pss->fd = LWS_INVALID_FILE;
 #endif
                break;
+       case LWS_UFS_CLOSE:
+               break;
        }
 
        return 0;
 }
 
+/*
+ * returns length in bytes
+ */
+
+static int
+format_result(struct per_session_data__post_demo *pss)
+{
+       unsigned char *p, *start, *end;
+       int n;
+
+       p = (unsigned char *)pss->result + LWS_PRE;
+       start = p;
+       end = p + sizeof(pss->result) - LWS_PRE - 1;
+
+       p += lws_snprintf((char *)p, end -p,
+                       "<!DOCTYPE html><html lang=\"en\"><head>"
+                       "<meta charset=utf-8 http-equiv=\"Content-Language\" "
+                       "content=\"en\"/>"
+         "<title>LWS Server Status</title>"
+         "</head><body><h1>Form results (after urldecoding)</h1>"
+         "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>");
+
+       for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
+               if (!lws_spa_get_string(pss->spa, n))
+                       p += lws_snprintf((char *)p, end - p,
+                           "<tr><td><b>%s</b></td><td>0"
+                           "</td><td>NULL</td></tr>",
+                           param_names[n]);
+               else
+                       p += lws_snprintf((char *)p, end - p,
+                           "<tr><td><b>%s</b></td><td>%d"
+                           "</td><td>%s</td></tr>",
+                           param_names[n],
+                           lws_spa_get_length(pss->spa, n),
+                           lws_spa_get_string(pss->spa, n));
+       }
+
+       p += lws_snprintf((char *)p, end - p,
+                       "</table><br><b>filename:</b> %s, "
+                       "<b>length</b> %ld",
+                       pss->filename, pss->file_length);
+
+       p += lws_snprintf((char *)p, end - p, "</body></html>");
+
+       return (int)lws_ptr_diff(p, start);
+}
+
 static int
 callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
                   void *user, void *in, size_t len)
 {
        struct per_session_data__post_demo *pss =
                        (struct per_session_data__post_demo *)user;
-       unsigned char *buffer;
        unsigned char *p, *start, *end;
        int n;
 
@@ -121,7 +175,7 @@ callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
                /* create the POST argument parser if not already existing */
                if (!pss->spa) {
                        pss->spa = lws_spa_create(wsi, param_names,
-                                       ARRAY_SIZE(param_names), 1024,
+                                       LWS_ARRAY_SIZE(param_names), 1024,
                                        file_upload_cb, pss);
                        if (!pss->spa)
                                return -1;
@@ -131,68 +185,68 @@ callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
                }
 
                /* let it parse the POST data */
-               if (lws_spa_process(pss->spa, in, len))
+               if (lws_spa_process(pss->spa, in, (int)len))
                        return -1;
                break;
 
        case LWS_CALLBACK_HTTP_BODY_COMPLETION:
-               lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+               lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION: %p\n", wsi);
                /* call to inform no more payload data coming */
                lws_spa_finalize(pss->spa);
 
-               p = (unsigned char *)pss->result + LWS_PRE;
-               end = p + sizeof(pss->result) - LWS_PRE - 1;
-               p += sprintf((char *)p,
-                       "<html><body><h1>Form results (after urldecoding)</h1>"
-                       "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>");
-
-               for (n = 0; n < ARRAY_SIZE(param_names); n++)
-                       p += lws_snprintf((char *)p, end - p,
-                                   "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>",
-                                   param_names[n],
-                                   lws_spa_get_length(pss->spa, n),
-                                   lws_spa_get_string(pss->spa, n));
-
-               p += lws_snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld",
-                               pss->filename, pss->file_length);
+               pss->completed = 1;
+               lws_callback_on_writable(wsi);
+               break;
 
-               p += lws_snprintf((char *)p, end - p, "</body></html>");
-               pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE);
+       case LWS_CALLBACK_HTTP_WRITEABLE:
+               if (!pss->completed)
+                       break;
 
-               n = LWS_PRE + 1024;
-               buffer = malloc(n);
-               p = buffer + LWS_PRE;
+               p = (unsigned char *)pss->result + LWS_PRE;
                start = p;
-               end = p + n - LWS_PRE - 1;
+               end = p + sizeof(pss->result) - LWS_PRE - 1;
 
-               if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
-                       goto bail;
+               if (!pss->sent_headers) {
+                       n = format_result(pss);
+
+                       if (lws_add_http_header_status(wsi, HTTP_STATUS_OK,
+                                                      &p, end))
+                               goto bail;
+
+                       if (lws_add_http_header_by_token(wsi,
+                                       WSI_TOKEN_HTTP_CONTENT_TYPE,
+                                       (unsigned char *)"text/html", 9,
+                                       &p, end))
+                               goto bail;
+                       if (lws_add_http_header_content_length(wsi, n, &p, end))
+                               goto bail;
+                       if (lws_finalize_http_header(wsi, &p, end))
+                               goto bail;
+
+                       /* first send the headers ... */
+                       n = lws_write(wsi, start, lws_ptr_diff(p, start),
+                                     LWS_WRITE_HTTP_HEADERS);
+                       if (n < 0)
+                               goto bail;
+
+                       pss->sent_headers = 1;
+                       lws_callback_on_writable(wsi);
+                       break;
+               }
 
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
-                               (unsigned char *)"text/html", 9, &p, end))
-                       goto bail;
-               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
-                       goto bail;
-               if (lws_finalize_http_header(wsi, &p, end))
-                       goto bail;
+               if (!pss->sent_body) {
+                       n = format_result(pss);
 
-               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
-               if (n < 0)
-                       goto bail;
-               free(buffer);
+                       n = lws_write(wsi, (unsigned char *)start, n,
+                                     LWS_WRITE_HTTP_FINAL);
 
-               lws_callback_on_writable(wsi);
+                       pss->sent_body = 1;
+                       if (n < 0)
+                               return 1;
+                       goto try_to_reuse;
+               }
                break;
 
-       case LWS_CALLBACK_HTTP_WRITEABLE:
-               lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",
-                          pss->result_len);
-               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
-                             pss->result_len, LWS_WRITE_HTTP);
-               if (n < 0)
-                       return 1;
-               goto try_to_reuse;
-
        case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
                /* called when our wsi user_space is going to be destroyed */
                if (pss->spa) {
@@ -208,7 +262,6 @@ callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
        return 0;
 
 bail:
-       free(buffer);
 
        return 1;
 
@@ -225,6 +278,7 @@ try_to_reuse:
                callback_post_demo, \
                sizeof(struct per_session_data__post_demo), \
                1024, \
+               0, NULL, 0 \
        }
 
 #if !defined (LWS_PLUGIN_STATIC)
@@ -244,7 +298,7 @@ init_protocol_post_demo(struct lws_context *context,
        }
 
        c->protocols = protocols;
-       c->count_protocols = ARRAY_SIZE(protocols);
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
        c->extensions = NULL;
        c->count_extensions = 0;
 
diff --git a/plugins/raw-proxy/README.md b/plugins/raw-proxy/README.md
new file mode 100644 (file)
index 0000000..bc5987c
--- /dev/null
@@ -0,0 +1,66 @@
+# raw-proxy plugin
+
+## Enabling for build
+
+```
+$ cmake .. -DLWS_ROLE_RAW_PROXY=1
+```
+
+## configuration pvo
+
+|pvo|value meaning|
+|---|---|
+|onward|The onward proxy destination, in the form `ipv4:addr[:port]`|
+
+## Note for vhost selection
+
+Notice that since it proxies the packets "raw", there's no SNI or Host:
+header to resolve amongst multiple vhosts on the same listen port.  So the
+vhost you associate with this protocol must be alone on its own port.
+
+It's also possible to apply this or other role + protocols as a fallback after
+http[s] processing rejected the first packet from an incoming connection.
+See `./READMEs/README-http-fallback.md`
+
+## Note for packet size
+
+For throughput, since often one side is localhost that can handle larger
+packets easily, you should create the context used with this plugin with
+
+```
+       info.pt_serv_buf_size = 8192;
+```
+
+lwsws already does this.
+
+## Using with C
+
+See the minimal example `./minimal-example/raw/minimal-raw-proxy` for
+a working example of a vhost that accepts connections and then
+proxies them using this plugin.  The example is almost all boilerplate
+for setting up the context and the pvo.
+
+## Using with lwsws
+
+For a usage where the plugin "owns" the whole vhost, you should enable the
+plugin protocol on the vhost as usual, and specify the "onward" pvo with:
+
+```
+                "ws-protocols": [{
+                        "raw-proxy": {
+                         "status": "ok",
+                         "onward": "ipv4:remote.address.com:port"
+                        }
+                 }],
+```
+
+and then define the vhost with:
+
+```
+    "apply-listen-accept": "1",
+    "listen-accept-role": "raw-proxy",
+    "listen-accept-protocol": "raw-proxy"
+```
+
+which tells it to apply the role and protocol as soon as a connection is
+accepted on the vhost.
diff --git a/plugins/raw-proxy/protocol_lws_raw_proxy.c b/plugins/raw-proxy/protocol_lws_raw_proxy.c
new file mode 100644 (file)
index 0000000..bd2b82a
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+ * libwebsockets - plugin for raw proxying
+ *
+ * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined (LWS_PLUGIN_STATIC)
+#define LWS_DLL
+#define LWS_INTERNAL
+#include <libwebsockets.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#define RING_DEPTH 8
+
+struct packet {
+       void *payload;
+       uint32_t len;
+       uint32_t ticket;
+};
+
+enum {
+       ACC,
+       ONW
+};
+
+/*
+ * Because both sides of the connection want to share this, we allocate it
+ * during accepted adoption and both sides have a pss that is just a wrapper
+ * pointing to this.
+ *
+ * The last one of the accepted side and the onward side to close frees it.
+ * This removes any chance of one side or the other having an invalidated
+ * pointer to the pss.
+ */
+
+struct conn {
+       struct lws *wsi[2];
+
+       /* rings containing unsent rx from accepted and onward sides */
+       struct lws_ring *r[2];
+       uint32_t t[2]; /* ring tail */
+
+       uint32_t ticket_next;
+       uint32_t ticket_retired;
+
+       char rx_enabled[2];
+       char closed[2];
+       char established[2];
+};
+
+struct raw_pss {
+       struct conn *conn;
+};
+
+/* one of these is created for each vhost our protocol is used with */
+
+struct raw_vhd {
+       char addr[128];
+       uint16_t port;
+       char ipv6;
+};
+
+static void
+__destroy_packet(void *_pkt)
+{
+       struct packet *pkt = _pkt;
+
+       free(pkt->payload);
+       pkt->payload = NULL;
+       pkt->len = 0;
+}
+
+static void
+destroy_conn(struct raw_vhd *vhd, struct raw_pss *pss)
+{
+       struct conn *conn = pss->conn;
+
+       if (conn->r[ACC])
+               lws_ring_destroy(conn->r[ACC]);
+       if (conn->r[ONW])
+               lws_ring_destroy(conn->r[ONW]);
+
+       pss->conn = NULL;
+
+       free(conn);
+}
+
+static int
+connect_client(struct raw_vhd *vhd, struct raw_pss *pss)
+{
+       struct lws_client_connect_info i;
+       char host[128];
+       struct lws *cwsi;
+
+       lws_snprintf(host, sizeof(host), "%s:%u", vhd->addr, vhd->port);
+
+       memset(&i, 0, sizeof(i));
+
+       i.method = "RAW";
+       i.context = lws_get_context(pss->conn->wsi[ACC]);
+       i.port = vhd->port;
+       i.address = vhd->addr;
+       i.host = host;
+       i.origin = host;
+       i.ssl_connection = 0;
+       i.vhost = lws_get_vhost(pss->conn->wsi[ACC]);
+       i.local_protocol_name = "raw-proxy";
+       i.protocol = "raw-proxy";
+       i.path = "/";
+       /*
+        * The "onward" client wsi has its own pss but shares the "conn"
+        * created when the inbound connection was accepted.  We need to stash
+        * the address of the shared conn and apply it to the client psss
+        * when the client connection completes.
+        */
+       i.opaque_user_data = pss->conn;
+       i.pwsi = &pss->conn->wsi[ONW];
+
+       lwsl_info("%s: onward: %s:%d%s\n", __func__, i.address, i.port, i.path);
+
+       cwsi = lws_client_connect_via_info(&i);
+       if (!cwsi)
+               lwsl_err("%s: client connect failed early\n", __func__);
+
+       return !cwsi;
+}
+
+static int
+flow_control(struct conn *conn, int side, int enable)
+{
+       if (conn->closed[side] ||
+           enable == conn->rx_enabled[side] ||
+           !conn->established[side])
+               return 0;
+
+       if (lws_rx_flow_control(conn->wsi[side], enable))
+               return 1;
+
+       conn->rx_enabled[side] = enable;
+       lwsl_info("%s: %s side: %s\n", __func__, side ? "ONW" : "ACC",
+                 enable ? "rx enabled" : "rx flow controlled");
+
+       return 0;
+}
+
+static int
+callback_raw_proxy(struct lws *wsi, enum lws_callback_reasons reason,
+                  void *user, void *in, size_t len)
+{
+       struct raw_pss *pss = (struct raw_pss *)user;
+       struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
+                                    lws_get_vhost(wsi), lws_get_protocol(wsi));
+       const struct packet *ppkt;
+       struct conn *conn = NULL;
+       struct lws_tokenize ts;
+       lws_tokenize_elem e;
+       struct packet pkt;
+       const char *cp;
+       int n;
+
+       if (pss)
+               conn = pss->conn;
+
+       switch (reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi), sizeof(struct raw_vhd));
+               if (lws_pvo_get_str(in, "onward", &cp)) {
+                       lwsl_err("%s: vh %s: pvo 'onward' required\n", __func__,
+                                lws_get_vhost_name(lws_get_vhost(wsi)));
+
+                       return -1;
+               }
+               lws_tokenize_init(&ts, cp, LWS_TOKENIZE_F_DOT_NONTERM |
+                                          LWS_TOKENIZE_F_MINUS_NONTERM |
+                                          LWS_TOKENIZE_F_NO_FLOATS);
+               ts.len = strlen(cp);
+
+               if (lws_tokenize(&ts) != LWS_TOKZE_TOKEN)
+                       goto bad_onward;
+               if (!strncmp(ts.token, "ipv6", ts.token_len))
+                       vhd->ipv6 = 1;
+               else
+                       if (strncmp(ts.token, "ipv4", ts.token_len))
+                               goto bad_onward;
+
+               /* then the colon */
+               if (lws_tokenize(&ts) != LWS_TOKZE_DELIMITER)
+                       goto bad_onward;
+
+               e = lws_tokenize(&ts);
+               if (!vhd->ipv6) {
+                       if (e != LWS_TOKZE_TOKEN ||
+                           ts.token_len + 1 >= (int)sizeof(vhd->addr))
+                               goto bad_onward;
+
+                       lws_strncpy(vhd->addr, ts.token, ts.token_len + 1);
+                       e = lws_tokenize(&ts);
+                       if (e == LWS_TOKZE_DELIMITER) {
+                               /* there should be a port then */
+                               e = lws_tokenize(&ts);
+                               if (e != LWS_TOKZE_INTEGER)
+                                       goto bad_onward;
+                               vhd->port = atoi(ts.token);
+                               e = lws_tokenize(&ts);
+                       }
+                       if (e != LWS_TOKZE_ENDED)
+                               goto bad_onward;
+               } else
+                       lws_strncpy(vhd->addr, ts.token, sizeof(vhd->addr));
+
+               lwsl_notice("%s: vh %s: onward %s:%s:%d\n", __func__,
+                           lws_get_vhost_name(lws_get_vhost(wsi)),
+                           vhd->ipv6 ? "ipv6": "ipv4", vhd->addr, vhd->port);
+               break;
+
+bad_onward:
+               lwsl_err("%s: onward pvo format must be ipv4:addr[:port] "
+                        " or ipv6:addr, not '%s'\n", __func__, cp);
+               return -1;
+
+       case LWS_CALLBACK_PROTOCOL_DESTROY:
+               break;
+
+       /* callbacks related to client "onward side" */
+
+       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+               lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
+                        in ? (char *)in : "(null)");
+               break;
+
+        case LWS_CALLBACK_RAW_PROXY_CLI_ADOPT:
+               lwsl_debug("LWS_CALLBACK_RAW_CLI_ADOPT: pss %p\n", pss);
+               if (conn || !pss)
+                       break;
+               conn = pss->conn = lws_get_opaque_user_data(wsi);
+               conn->established[ONW] = 1;
+               /* they start enabled */
+               conn->rx_enabled[ACC] = 1;
+               conn->rx_enabled[ONW] = 1;
+
+               /* he disabled his rx while waiting for use to be established */
+               flow_control(conn, ACC, 1);
+
+               lws_callback_on_writable(wsi);
+               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+               break;
+
+       case LWS_CALLBACK_RAW_PROXY_CLI_CLOSE:
+               lwsl_debug("LWS_CALLBACK_RAW_PROXY_CLI_CLOSE\n");
+               if (!conn)
+                       break;
+
+               conn->closed[ONW] = 1;
+
+               if (conn->closed[ACC])
+                       destroy_conn(vhd, pss);
+
+               break;
+
+       case LWS_CALLBACK_RAW_PROXY_CLI_RX:
+               lwsl_debug("LWS_CALLBACK_RAW_PROXY_CLI_RX: %d\n", (int)len);
+
+               if (!conn)
+                       return 0;
+
+               if (!pss || !conn->wsi[ACC] || conn->closed[ACC]) {
+                       lwsl_info(" pss %p, wsi[ACC] %p, closed[ACC] %d\n",
+                                 pss, conn->wsi[ACC], conn->closed[ACC]);
+                       return -1;
+               }
+               pkt.payload = malloc(len);
+               if (!pkt.payload) {
+                       lwsl_notice("OOM: dropping\n");
+                       return -1;
+               }
+               pkt.len = len;
+               pkt.ticket = conn->ticket_next++;
+
+               memcpy(pkt.payload, in, len);
+               if (!lws_ring_insert(conn->r[ONW], &pkt, 1)) {
+                       __destroy_packet(&pkt);
+                       lwsl_notice("dropping!\n");
+                       return -1;
+               }
+
+               lwsl_debug("After onward RX: acc free: %d...\n",
+                          (int)lws_ring_get_count_free_elements(conn->r[ONW]));
+
+               if (conn->rx_enabled[ONW] &&
+                   lws_ring_get_count_free_elements(conn->r[ONW]) < 2)
+                       flow_control(conn, ONW, 0);
+
+               if (!conn->closed[ACC])
+                       lws_callback_on_writable(conn->wsi[ACC]);
+               break;
+
+       case LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE\n");
+
+               if (!conn)
+                       break;
+
+               ppkt = lws_ring_get_element(conn->r[ACC], &conn->t[ACC]);
+               if (!ppkt) {
+                       lwsl_info("%s: CLI_WRITABLE had nothing in acc ring\n",
+                                 __func__);
+                       break;
+               }
+
+               if (ppkt->ticket != conn->ticket_retired + 1) {
+                       lwsl_info("%s: acc ring has %d but next %d\n", __func__,
+                                 ppkt->ticket, conn->ticket_retired + 1);
+                       lws_callback_on_writable(conn->wsi[ACC]);
+                       break;
+               }
+
+               n = lws_write(wsi, ppkt->payload, ppkt->len, LWS_WRITE_RAW);
+               if (n < 0) {
+                       lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
+
+                       return -1;
+               }
+
+               conn->ticket_retired = ppkt->ticket;
+               lws_ring_consume(conn->r[ACC], &conn->t[ACC], NULL, 1);
+               lws_ring_update_oldest_tail(conn->r[ACC], conn->t[ACC]);
+
+               lwsl_debug("acc free: %d...\n",
+                         (int)lws_ring_get_count_free_elements(conn->r[ACC]));
+
+               if (!conn->rx_enabled[ACC] &&
+                   lws_ring_get_count_free_elements(conn->r[ACC]) > 2)
+                       flow_control(conn, ACC, 1);
+
+               ppkt = lws_ring_get_element(conn->r[ACC], &conn->t[ACC]);
+               lwsl_debug("%s: CLI_WRITABLE: next acc pkt %p idx %d vs %d\n",
+                          __func__, ppkt, ppkt ? ppkt->ticket : 0,
+                                          conn->ticket_retired + 1);
+
+               if (ppkt && ppkt->ticket == conn->ticket_retired + 1)
+                       lws_callback_on_writable(wsi);
+               else {
+                       /*
+                        * defer checking for accepted side closing until we
+                        * sent everything in the ring to onward
+                        */
+                       if (conn->closed[ACC])
+                               /*
+                                * there is never going to be any more... but
+                                * we may have some tx still in tx buflist /
+                                * partial
+                                */
+                               return lws_raw_transaction_completed(wsi);
+
+                       if (lws_ring_get_element(conn->r[ONW], &conn->t[ONW]))
+                               lws_callback_on_writable(conn->wsi[ACC]);
+               }
+               break;
+
+       /* callbacks related to raw socket descriptor "accepted side" */
+
+        case LWS_CALLBACK_RAW_PROXY_SRV_ADOPT:
+               lwsl_debug("LWS_CALLBACK_RAW_SRV_ADOPT\n");
+               if (!pss)
+                       return -1;
+               conn = pss->conn = malloc(sizeof(struct conn));
+               if (!pss->conn)
+                       return -1;
+               memset(conn, 0, sizeof(*conn));
+
+               conn->wsi[ACC] = wsi;
+               conn->ticket_next = 1;
+
+               conn->r[ACC] = lws_ring_create(sizeof(struct packet),
+                                              RING_DEPTH, __destroy_packet);
+               if (!conn->r[ACC]) {
+                       lwsl_err("%s: OOM\n", __func__);
+                       return -1;
+               }
+               conn->r[ONW] = lws_ring_create(sizeof(struct packet),
+                                              RING_DEPTH, __destroy_packet);
+               if (!conn->r[ONW]) {
+                       lws_ring_destroy(conn->r[ACC]);
+                       conn->r[ACC] = NULL;
+                       lwsl_err("%s: OOM\n", __func__);
+
+                       return -1;
+               }
+
+               conn->established[ACC] = 1;
+
+               /* disable any rx until the client side is up */
+               flow_control(conn, ACC, 0);
+
+               lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+               /* try to create the onward client connection */
+               connect_client(vhd, pss);
+                break;
+
+       case LWS_CALLBACK_RAW_PROXY_SRV_CLOSE:
+               lwsl_debug("LWS_CALLBACK_RAW_PROXY_SRV_CLOSE:\n");
+
+               if (!conn)
+                       break;
+
+               conn->closed[ACC] = 1;
+               if (conn->closed[ONW])
+                       destroy_conn(vhd, pss);
+               break;
+
+       case LWS_CALLBACK_RAW_PROXY_SRV_RX:
+               lwsl_debug("LWS_CALLBACK_RAW_PROXY_SRV_RX: rx %d\n", (int)len);
+
+               if (!conn || !conn->wsi[ONW]) {
+                       lwsl_err("%s: LWS_CALLBACK_RAW_PROXY_SRV_RX: "
+                                "conn->wsi[ONW] NULL\n", __func__);
+                       return -1;
+               }
+               if (conn->closed[ONW]) {
+                       lwsl_info(" closed[ONW] %d\n", conn->closed[ONW]);
+                       return -1;
+               }
+
+               pkt.payload = malloc(len);
+               if (!pkt.payload) {
+                       lwsl_notice("OOM: dropping\n");
+                       return -1;
+               }
+               pkt.len = len;
+               pkt.ticket = conn->ticket_next++;
+
+               memcpy(pkt.payload, in, len);
+               if (!lws_ring_insert(conn->r[ACC], &pkt, 1)) {
+                       __destroy_packet(&pkt);
+                       lwsl_notice("dropping!\n");
+                       return -1;
+               }
+
+               lwsl_debug("After acc RX: acc free: %d...\n",
+                          (int)lws_ring_get_count_free_elements(conn->r[ACC]));
+
+               if (conn->rx_enabled[ACC] &&
+                   lws_ring_get_count_free_elements(conn->r[ACC]) <= 2)
+                       flow_control(conn, ACC, 0);
+
+               if (conn->established[ONW] && !conn->closed[ONW])
+                       lws_callback_on_writable(conn->wsi[ONW]);
+               break;
+
+       case LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE:
+               lwsl_debug("LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE\n");
+
+               if (!conn || !conn->established[ONW] || conn->closed[ONW])
+                       break;
+
+               ppkt = lws_ring_get_element(conn->r[ONW], &conn->t[ONW]);
+               if (!ppkt) {
+                       lwsl_info("%s: SRV_WRITABLE nothing in onw ring\n",
+                                 __func__);
+                       break;
+               }
+
+               if (ppkt->ticket != conn->ticket_retired + 1) {
+                       lwsl_info("%s: onw ring has %d but next %d\n", __func__,
+                                 ppkt->ticket, conn->ticket_retired + 1);
+                       lws_callback_on_writable(conn->wsi[ONW]);
+                       break;
+               }
+
+               n = lws_write(wsi, ppkt->payload, ppkt->len, LWS_WRITE_RAW);
+               if (n < 0) {
+                       lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
+
+                       return -1;
+               }
+
+               conn->ticket_retired = ppkt->ticket;
+               lws_ring_consume(conn->r[ONW], &conn->t[ONW], NULL, 1);
+               lws_ring_update_oldest_tail(conn->r[ONW], conn->t[ONW]);
+
+               lwsl_debug("onward free: %d... waiting %d\n",
+                         (int)lws_ring_get_count_free_elements(conn->r[ONW]),
+                         (int)lws_ring_get_count_waiting_elements(conn->r[ONW],
+                                                               &conn->t[ONW]));
+
+               if (!conn->rx_enabled[ONW] &&
+                   lws_ring_get_count_free_elements(conn->r[ONW]) > 2)
+                       flow_control(conn, ONW, 1);
+
+               ppkt = lws_ring_get_element(conn->r[ONW], &conn->t[ONW]);
+               lwsl_debug("%s: SRV_WRITABLE: next onw pkt %p idx %d vs %d\n",
+                          __func__, ppkt, ppkt ? ppkt->ticket : 0,
+                                          conn->ticket_retired + 1);
+
+               if (ppkt && ppkt->ticket == conn->ticket_retired + 1)
+                       lws_callback_on_writable(wsi);
+               else {
+                       /*
+                        * defer checking for onward side closing until we
+                        * sent everything in the ring to accepted side
+                        */
+                       if (conn->closed[ONW])
+                               /*
+                                * there is never going to be any more... but
+                                * we may have some tx still in tx buflist /
+                                * partial
+                                */
+                               return lws_raw_transaction_completed(wsi);
+
+               if (lws_ring_get_element(conn->r[ACC], &conn->t[ACC]))
+                       lws_callback_on_writable(conn->wsi[ONW]);
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+#define LWS_PLUGIN_PROTOCOL_RAW_PROXY { \
+               "raw-proxy", \
+               callback_raw_proxy, \
+               sizeof(struct raw_pss), \
+               8192, \
+               8192, NULL, 0 \
+       }
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+static const struct lws_protocols protocols[] = {
+       LWS_PLUGIN_PROTOCOL_RAW_PROXY
+};
+
+LWS_EXTERN LWS_VISIBLE int
+init_protocol_lws_raw_proxy(struct lws_context *context,
+                           struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_EXTERN LWS_VISIBLE int
+destroy_protocol_lws_raw_proxy(struct lws_context *context)
+{
+       return 0;
+}
+#endif
+
+
diff --git a/plugins/server-status.css b/plugins/server-status.css
new file mode 100644 (file)
index 0000000..e6948b7
--- /dev/null
@@ -0,0 +1,197 @@
+
+span.title {
+       font-size:18pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#000000;
+}
+span.mount {
+       font-size:10pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#000000;
+}
+span.mountname {
+       font-size:14pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#404010;
+}
+span.n {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#808020;
+}
+span.sn {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#808020;
+}
+span.v {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#202020;
+}
+span.m1 {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#202020;
+}
+span.m2 {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#202020;
+}
+
+.browser { font-size:12pt; font-family: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; border-radius:10px;}
+.group2 { vertical-align:middle;
+       text-align:center;
+       background:#f0f0e0; 
+       padding:12px; 
+       -webkit-border-radius:10px; 
+       border-radius:10px; }
+.explain { vertical-align:middle;
+       text-align:center;
+       background:#f0f0c0; padding:12px;
+       -webkit-border-radius:10px;
+       border-radius:10px;
+       color:#404000;
+       padding:3px;
+}
+td.wsstatus { vertical-align:middle; width:200px; height:50px;
+       text-align:center;
+       background:#f0f0c0; padding:6px;
+       -webkit-border-radius:8px;
+       border-radius:8px;
+       color:#404000; }
+.tdform { vertical-align:middle; width:200px; height:50px;
+       text-align:center;
+       background:#f0f0d0; padding:6px;
+       -webkit-border-radius:8px;
+       margin:10px;
+       border-radius:8px;
+       border: 1px solid black;
+       border-collapse: collapse;font-size:18pt; font-family: Arial; font-weight:normal; text-align:center; color:#000000; 
+       color:#404000; }
+       
+td.l { vertical-align:middle;
+       text-align:center;
+       background:#d0d0b0; 
+       padding:3px; 
+       -webkit-border-radius:3px;
+       border-radius:3px; }
+       
+td.bigger { font-size:120%; }
+
+div.bgw {  background:white }
+div.conninfo {
+       border: solid 2px #e0d040;
+       padding: 4px;
+       width: 500px;
+       height:350px;
+       overflow: auto;
+}
+span.f12 { font-size:12pt }
+       
+.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; border-radius:10px; }
+.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; border-radius:10px; }
+.tabs {
+  position: relative;   
+  min-height: 750px; /* This part sucks */
+  clear: both;
+  margin: 25px 0;
+}
+.tab {
+  float: left;
+}
+.tab label {
+  background: #eee; 
+  padding: 10px; 
+  border: 1px solid #ccc; 
+  margin-left: -1px; 
+  position: relative;
+  left: 1px; 
+}
+.tab [type=radio] {
+  display: none;   
+}
+.content {
+  position: absolute;
+  top: 28px;
+  left: 0;
+  background: white;
+  right: 0;
+  bottom: 0;
+  padding: 20px;
+  border: 1px solid #ccc; 
+}
+[type=radio]:checked ~ label {
+  background: white;
+  border-bottom: 1px solid white;
+  z-index: 2;
+}
+[type=radio]:checked ~ label ~ .content {
+  z-index: 1;
+}
+
+       td.wsstatus { vertical-align:middle; width:200px; height:50px;
+               text-align:center;
+               background:#f0f0c0; padding:6px;
+               -webkit-border-radius:8px;
+               border-radius:8px;
+               color:#404000; }
+       td.l { vertical-align:middle;
+               text-align:center;
+               background:#d0d0b0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.dl { vertical-align:middle;
+               text-align:center;
+               background:#c0c0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c { vertical-align:middle;
+               text-align:center;
+               background:#c0c0a0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c0 { vertical-align:middle;
+               text-align:center;
+               background:#b0b090; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.dc0 { vertical-align:middle;
+               text-align:center;
+               background:#a0a0a0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c1 { vertical-align:middle;
+               text-align:center;
+               background:#c0c0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.t { vertical-align:middle;
+               text-align:center;
+               background:#e0e0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
index a495132..0c03863 100644 (file)
 <html lang="en">
 <head>
  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ <link rel="stylesheet" type="text/css" href="server-status.css"/>
  <script src="/lws-common.js"></script>
+ <script type='text/javascript' src='server-status.js'></script>
  <title>LWS Server Status</title>
-<style type="text/css">
-       span.title { font-size:18pt; font: Arial; font-weight:normal;
-                       text-align:center; color:#000000; }
-       span.mount { font-size:10pt; font: Arial; font-weight:normal;
-                       text-align:center; color:#000000; }
-       span.mountname { font-size:14pt; font: Arial; font-weight:bold;
-                       text-align:center; color:#404010; }
-       span.n { font-size:12pt; font: Arial; font-weight:normal;
-                       text-align:center; color:#808020; }
-       span.v { font-size:12pt; font: Arial; font-weight:bold;
-                       text-align:center; color:#202020; }
-       span.m1 { font-size:12pt; font: Arial; font-weight:bold;
-                       text-align:center; color:#202020; }
-       span.m2 { font-size:12pt; font: Arial; font-weight:normal;
-                       text-align:center; color:#202020; }
-       .browser { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px;}
-       .group2 { vertical-align:middle;
-               text-align:center;
-               background:#f0f0e0; 
-               padding:12px; 
-               -webkit-border-radius:10px; 
-               -moz-border-radius:10px;
-               border-radius:10px; }
-       .explain { vertical-align:middle;
-               text-align:center;
-               background:#f0f0c0; padding:12px;
-               -webkit-border-radius:10px;
-               -moz-border-radius:10px;
-               border-radius:10px;
-               color:#404000; }
-       td.wsstatus { vertical-align:middle; width:200px; height:50px;
-               text-align:center;
-               background:#f0f0c0; padding:6px;
-               -webkit-border-radius:8px;
-               -moz-border-radius:8px;
-               border-radius:8px;
-               color:#404000; }
-       td.l { vertical-align:middle;
-               text-align:center;
-               background:#d0d0b0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       td.dl { vertical-align:middle;
-               text-align:center;
-               background:#c0c0c0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       td.c { vertical-align:middle;
-               text-align:center;
-               background:#c0c0a0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       td.c0 { vertical-align:middle;
-               text-align:center;
-               background:#b0b090; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       td.dc0 { vertical-align:middle;
-               text-align:center;
-               background:#a0a0a0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       td.c1 { vertical-align:middle;
-               text-align:center;
-               background:#c0c0c0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       td.t { vertical-align:middle;
-               text-align:center;
-               background:#e0e0c0; 
-               padding:3px; 
-               -webkit-border-radius:3px; 
-               -moz-border-radius:3px;
-               border-radius:3px; }
-       .content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
-       .canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
-.tabs {
-  position: relative;   
-  min-height: 750px; /* This part sucks */
-  clear: both;
-  margin: 25px 0;
-}
-.tab {
-  float: left;
-}
-.tab label {
-  background: #eee; 
-  padding: 10px; 
-  border: 1px solid #ccc; 
-  margin-left: -1px; 
-  position: relative;
-  left: 1px; 
-}
-.tab [type=radio] {
-  display: none;   
-}
-.content {
-  position: absolute;
-  top: 28px;
-  left: 0;
-  background: white;
-  right: 0;
-  bottom: 0;
-  padding: 20px;
-  border: 1px solid #ccc; 
-}
-[type=radio]:checked ~ label {
-  background: white;
-  border-bottom: 1px solid white;
-  z-index: 2;
-}
-[type=radio]:checked ~ label ~ .content {
-  z-index: 1;
-}
-</style>
 </head>
 
 <body>
 <article>
 
 <table>
-<tr><td><img src="./lwsws-logo.png"></td><td><span id=title class=title>Server status</span></td></tr>
+<tr><td><img src="./lwsws-logo.png"></td><td>
+<span id=title class=title>Server status</span></td></tr>
 <tr><td align=center colspan=2>
 <div id="conninfo">...</div>
 <div id="json"></div>
 </td></tr>
-
-
 </table>
-
 </article>
-
-<script nonce="lwscaro">
-
-lws_gray_out(true,{'zindex':'499'});
-
-/*
- * We display untrusted stuff in html context... reject anything
- * that has HTML stuff in it
- */
-
-function san(s)
-{
-       if (s.search("<") != -1)
-               return "invalid string";
-       
-       return s;
-}
-
-function humanize(s)
-{
-       i = parseInt(s);
-       
-       if (i > 1000000000)
-               return (i / 1000000000).toFixed(3) + "G";
-       
-       if (i > 1000000)
-               return (i / 1000000).toFixed(3) + "M";
-       
-       if (i > 1000)
-               return (i / 1000).toFixed(3) + "K";
-       
-       return s;
-}
-
-       var pos = 0;
-
-function get_appropriate_ws_url()
-{
-       var pcol;
-       var u = document.URL;
-
-       /*
-        * We open the websocket encrypted if this page came on an
-        * https:// url itself, otherwise unencrypted
-        */
-
-       if (u.substring(0, 5) == "https") {
-               pcol = "wss://";
-               u = u.substr(8);
-       } else {
-               pcol = "ws://";
-               if (u.substring(0, 4) == "http")
-                       u = u.substr(7);
-       }
-
-       u = u.split('/');
-
-       /* + "/xxx" bit is for IE10 workaround */
-
-       return pcol + u[0] + "/xxx";
-}
-
-
-       var socket_status, jso, s;
-
-       if (typeof MozWebSocket != "undefined") {
-               socket_status = new MozWebSocket(get_appropriate_ws_url(),
-                                  "lws-server-status");
-       } else {
-               socket_status = new WebSocket(get_appropriate_ws_url(),
-                                  "lws-server-status");
-       }
-
-
-       try {
-               socket_status.onopen = function() {
-                       document.getElementById("title").innerHTML = "Server Status (Active)";
-                       lws_gray_out(false);
-               }
-
-               socket_status.onmessage =function got_packet(msg) {
-                       var u, ci, n;
-                       //document.getElementById("json").innerHTML = "<pre>"+msg.data+"</pre>";
-                       jso = JSON.parse(msg.data);
-                       u = parseInt(san(jso.i.uptime));
-
-                       if (parseInt(jso.i.contexts[0].deprecated) == 0)
-                               s = "<table><tr><td></td><td class=\"c0\">";
-                       else
-                               s = "<table><tr><td></td><td class=\"dc0\">";
-                       s +=
-                         "Server</td><td>" +
-                         "<span class=n>Version:</span> <span class=v>" +
-                          san(jso.i.version) + "</span><br>" +
-                         "<span class=n>Uptime:</span> <span class=v>" +
-                         ((u / (24 * 3600)) | 0) + "d " +
-                         (((u % (24 * 3600)) / 3600) | 0) + "h " +
-                         (((u % 3600) / 60) | 0) + "m</span>";
-                       if (jso.i.l1)
-                               s = s + ", <span class=n>Load:</span> <span class=v>" + san(jso.i.l1) + " ";
-                       if (jso.i.l2)
-                               s = s + san(jso.i.l2) + " ";
-                       if (jso.i.l3)
-                               s = s + san(jso.i.l3);
-                       if (jso.i.l1)
-                               s =s + "<span>";
-                               
-                       for (n = 0; n < jso.files.length; n++) {
-                               s += "<br><span class=n>" + san(jso.files[n].path) + ":</span><br>    " + san(jso.files[n].val);
-                       }
-                       s += "</td></tr>";
-
-                       for (ci = 0; ci < jso.i.contexts.length; ci++) {
-
-                               if (parseInt(jso.i.contexts[ci].deprecated) == 0)
-                                       s += "<tr><td></td><td class=\"c\">" +
-                                         "Active Context</td><td>";
-                               else
-                                       s += "<tr><td></td><td class=\"c1\">" +
-                                         "Deprecated Context " + ci + "</td><td>";
-
-                                 u = parseInt(san(jso.i.contexts[ci].context_uptime));
-                                 s += "<span class=n>Uptime:</span> <span class=v>" +
-                                 ((u / (24 * 3600)) | 0) + "d " +
-                                 (((u % (24 * 3600)) / 3600) | 0) + "h " +
-                                 (((u % 3600) / 60) | 0) + "m</span>";
-
-                               s = s +
-                                 "<br>" +
-                                 "<span class=n>Listening wsi:</span> <span class=v>" + san(jso.i.contexts[ci].listen_wsi) + "</span>, " +
-                                 "<span class=n>Current wsi alive:</span> <span class=v>" + (parseInt(san(jso.i.contexts[ci].wsi_alive)) -
-                                       parseInt(san(jso.i.contexts[ci].listen_wsi))) + "</span><br>" +
-                                 "<span class=n>Total Rx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].rx)) +"</span>, " +
-                                 "<span class=n>Total Tx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].tx)) +"</span><br>" +
-                                 "<span class=n>Total connections:</span> <span class=v>" + san(jso.i.contexts[ci].conn) +"</span>, " +
-                                 "<span class=n>Total HTTP Transactions:</span> <span class=v>" + san(jso.i.contexts[ci].trans) +"</span><br>" +
-                                 "<span class=n>Total ws upgrades:</span> <span class=v>" + san(jso.i.contexts[ci].ws_upg) +"</span>, " +
-                                 "<span class=n>Total h2 upgrades:</span> <span class=v>" + san(jso.i.contexts[ci].http2_upg) +"</span>, " +
-                                 "<span class=n>Total Rejected:</span> <span class=v>" + san(jso.i.contexts[ci].rejected) +"</span><br>" +
-                                 "<span class=n>Current cgi alive:</span> <span class=v>" + san(jso.i.contexts[ci].cgi_alive) + "</span>, " +
-                                 "<span class=n>Total CGI spawned:</span> <span class=v>" + san(jso.i.contexts[ci].cgi_spawned) +
-                                 "</span><table>";
-                               
-                               for (n = 0; n < jso.i.contexts[ci].pt.length; n++) {
-
-                                       if (parseInt(jso.i.contexts[ci].deprecated) == 0)
-                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"l\">service thread " + (n + 1);
-                                       else
-                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"dl\">service thread " + (n + 1);
-                                       s += "</td><td>" +
-                                       "<span class=n>fds:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].fds_count) + " / " +
-                                                 san(jso.i.contexts[ci].pt_fd_max) + "</span>, ";
-                                       s = s + "<span class=n>ah pool:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].ah_pool_inuse) + " / " +
-                                                     san(jso.i.contexts[ci].ah_pool_max) + "</span>, " +
-                                       "<span class=n>ah waiting list:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].ah_wait_list);
-       
-                                       s = s + "</span></td></tr>";
-       
-                               }
-                               for (n = 0; n < jso.i.contexts[ci].vhosts.length; n++) {
-                                       if (parseInt(jso.i.contexts[ci].deprecated) == 0)
-                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"l\">vhost " + (n + 1);
-                                       else
-                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"dl\">vhost " + (n + 1);
-                                       s += "</td><td><span class=\"mountname\">";
-                                       if (jso.i.contexts[ci].vhosts[n].use_ssl == '1')
-                                               s = s + "https://";
-                                       else
-                                               s = s + "http://";
-                                       s = s + san(jso.i.contexts[ci].vhosts[n].name) + ":" +
-                                               san(jso.i.contexts[ci].vhosts[n].port) + "</span>";
-                                       if (jso.i.contexts[ci].vhosts[n].sts == '1')
-                                               s = s + " (STS)";
-                                       s = s +"<br>" +
-                                       "<span class=n>rx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].vhosts[n].rx)) + "B</span>, " +
-                                       "<span class=n>tx</span> <span class=v>" + humanize(san(jso.i.contexts[ci].vhosts[n].tx)) + "B</span><br>" +
-                                       "<span class=n>vh connections</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].conn) + "</span>, " +
-                                       "<span class=n>vh http transactions</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].trans) + "</span><br>" +
-                                       "<span class=n>vh upgrades to ws:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].ws_upg) + "</span>, " +
-                                       "<span class=n>to http/2:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].http2_upg) + "</span>, " +
-                                       "<span class=n>rejected:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].rejected) + "</span><br>" +
-                                       "<table style=\"margin-left:16px\"><tr><td class=t>Mountpoint</td><td class=t>Origin</td><td class=t>Cache Policy</td></tr>";
-
-                                       var m;
-                                       for (m = 0; m < jso.i.contexts[ci].vhosts[n].mounts.length; m++) {
-                                               s = s + "<tr><td>";
-                                               s = s + "<span class=\"m1\">" + san(jso.i.contexts[ci].vhosts[n].mounts[m].mountpoint) +
-                                                       "</span></td><td><span class=\"m2\">" +
-                                                       san(jso.i.contexts[ci].vhosts[n].mounts[m].origin) +
-                                                       "</span></td><td>";
-                                               if (parseInt(san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age)))
-                                                       s = s + "<span class=n>max-age:</span> <span class=v>" +
-                                                       san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age) +
-                                                       "</span>, <span class=n>reuse:</span> <span class=v>" +
-                                                       san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_reuse) +
-                                                       "</span>, <span class=n>reval:</span> <span class=v>" +
-                                                       san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_revalidate) +
-                                                       "</span>, <span class=n>inter:</span> <span class=v>" +
-                                                       san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_intermediaries);
-                                               s = s + "</span></td></tr>"
-                                       }
-                                       s = s + "</table>";
-                                       s = s + "</td></tr>";
-                               }
-
-                               s += "</table></td></tr>";
-                               
-                       } // context
-                       s = s + "</table>";
-                       
-                       document.getElementById("conninfo").innerHTML = s;
-               } 
-
-               socket_status.onclose = function(){
-                       document.getElementById("title").innerHTML = "Server Status (Disconnected)";
-                       lws_gray_out(true,{'zindex':'499'});
-               }
-       } catch(exception) {
-               alert('<p>Error' + exception);  
-       }
-</script>
-
 </body>
 </html>
diff --git a/plugins/server-status.js b/plugins/server-status.js
new file mode 100644 (file)
index 0000000..eafbc3b
--- /dev/null
@@ -0,0 +1,253 @@
+(function() {
+
+/*
+ * We display untrusted stuff in html context... reject anything
+ * that has HTML stuff in it
+ */
+
+function san(s)
+{
+       if (s.search("<") !== -1)
+               return "invalid string";
+       
+       return s;
+}
+
+function humanize(s)
+{
+       var i = parseInt(s, 10);
+       
+       if (i >= (1024 * 1024 * 1024))
+               return (i / (1024 * 1024 * 1024)).toFixed(3) + "Gi";
+       
+       if (i >= (1024 * 1024))
+               return (i / (1024 * 1024)).toFixed(3) + "Mi";
+       
+       if (i > 1024)
+               return (i / 1024).toFixed(3) + "Ki";
+       
+       return s;
+}
+
+       var pos = 0;
+
+function get_appropriate_ws_url()
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/xxx";
+}
+
+
+       var socket_status, jso, s;
+
+function ws_open_server_status()
+{      
+       socket_status = new WebSocket(get_appropriate_ws_url(),
+                                  "lws-server-status");
+
+       try {
+               socket_status.onopen = function() {
+                       document.getElementById("title").innerHTML = "Server Status (Active)";
+                       lws_gray_out(false);
+               };
+
+               socket_status.onmessage =function got_packet(msg) {
+                       var u, ci, n;
+                       //document.getElementById("json").innerHTML = "<pre>"+msg.data+"</pre>";
+                       if (msg.data.length < 100)
+                               return;
+                       jso = JSON.parse(msg.data);
+                       u = parseInt(san(jso.i.uptime), 10);
+
+                       if (parseInt(jso.i.contexts[0].deprecated, 10) === 0)
+                               s = "<table><tr><td></td><td class=\"c0\">";
+                       else
+                               s = "<table><tr><td></td><td class=\"dc0\">";
+                       s +=
+                         "Server</td><td>" +
+                         "<span class=\"sn\">Server Version:</span> <span class=\"v\">" +
+                          san(jso.i.version) + "</span><br>" +
+                         "<span class=\"sn\">Host Uptime:</span> <span class=\"v\">" +
+                         ((u / (24 * 3600)) | 0) + "d " +
+                         (((u % (24 * 3600)) / 3600) | 0) + "h " +
+                         (((u % 3600) / 60) | 0) + "m</span>";
+                       if (jso.i.l1)
+                               s = s + ", <span class=\"sn\">Host Load:</span> <span class=\"v\">" + san(jso.i.l1) + " ";
+                       if (jso.i.l2)
+                               s = s + san(jso.i.l2) + " ";
+                       if (jso.i.l3)
+                               s = s + san(jso.i.l3);
+                       if (jso.i.l1)
+                               s =s + "</span>";
+                               
+                       if (jso.i.statm) {
+                               var sm = jso.i.statm.split(" ");
+                               s += ", <span class=\"sn\">Virt stack + heap Usage:</span> <span class=\"v\">" +
+                                       humanize(parseInt(sm[5], 10) * 4096) + "B</span>";
+                       }
+                       s += ", <span class=\"sn\">lws heap usage:</span> <span class=\"v\">" +
+                       humanize(jso.i.heap) + "B</span>";
+
+                               
+                       for (n = 0; n < jso.files.length; n++) {
+                               s += "<br><span class=n>" + san(jso.files[n].path) + ":</span><br>    " + san(jso.files[n].val);
+                       }
+                       s += "</td></tr>";
+
+                       for (ci = 0; ci < jso.i.contexts.length; ci++) {
+
+                               if (parseInt(jso.i.contexts[ci].deprecated, 10) === 0)
+                                       s += "<tr><td></td><td class=\"c\">" +
+                                         "Active Context</td><td>";
+                               else
+                                       s += "<tr><td></td><td class=\"c1\">" +
+                                         "Deprecated Context " + ci + "</td><td>";
+
+                                 u = parseInt(san(jso.i.contexts[ci].context_uptime), 10);
+                                 s += "<span class=n>Server Uptime:</span> <span class=v>" +
+                                 ((u / (24 * 3600)) | 0) + "d " +
+                                 (((u % (24 * 3600)) / 3600) | 0) + "h " +
+                                 (((u % 3600) / 60) | 0) + "m</span>";
+
+                               s = s +
+                                 "<br>" +
+                                 "<span class=n>Listening wsi:</span> <span class=v>" + san(jso.i.contexts[ci].listen_wsi) + "</span>, " +
+                                 "<span class=n>Current wsi alive:</span> <span class=v>" + (parseInt(san(jso.i.contexts[ci].wsi_alive), 10) -
+                                                 parseInt(san(jso.i.contexts[ci].listen_wsi), 10)) + "</span><br>" +
+                                 "<span class=n>Total Rx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].rx)) +"B</span>, " +
+                                 "<span class=n>Total Tx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].tx)) +"B</span><br>" +
+                                 
+                                 "<span class=n>CONNECTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].h1_conn) +"</span>, " +
+                                 "<span class=n>Websocket:</span> <span class=v>" + san(jso.i.contexts[ci].ws_upg) +"</span>, " +
+                                 "<span class=n>H2 upgrade:</span> <span class=v>" + san(jso.i.contexts[ci].h2_upg) +"</span>, " +
+                                 "<span class=n>H2 ALPN:</span> <span class=v>" + san(jso.i.contexts[ci].h2_alpn) +"</span>, " +
+                                 "<span class=n>Rejected:</span> <span class=v>" + san(jso.i.contexts[ci].rejected) +"</span><br>" +
+
+                                 "<span class=n>TRANSACTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].h1_trans) + "</span>, " +
+                                 "<span class=n>H2:</span> <span class=v>" + san(jso.i.contexts[ci].h2_trans) +"</span>, " +
+                                  "<span class=n>Total H2 substreams:</span> <span class=v>" + san(jso.i.contexts[ci].h2_subs) +"</span><br>" +
+
+                                 "<span class=n>CGI: alive:</span> <span class=v>" + san(jso.i.contexts[ci].cgi_alive) + "</span>, " +
+                                 "<span class=n>spawned:</span> <span class=v>" + san(jso.i.contexts[ci].cgi_spawned) +
+                                 "</span><table>";
+                               
+                               for (n = 0; n < jso.i.contexts[ci].pt.length; n++) {
+
+                                       if (parseInt(jso.i.contexts[ci].deprecated, 10) === 0)
+                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"l\">service thread " + (n + 1);
+                                       else
+                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"dl\">service thread " + (n + 1);
+                                       s += "</td><td>" +
+                                       "<span class=n>fds:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].fds_count) + " / " +
+                                                 san(jso.i.contexts[ci].pt_fd_max) + "</span>, ";
+                                       s = s + "<span class=n>ah pool:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].ah_pool_inuse) + " / " +
+                                                     san(jso.i.contexts[ci].ah_pool_max) + "</span>, " +
+                                       "<span class=n>ah waiting list:</span> <span class=v>" + san(jso.i.contexts[ci].pt[n].ah_wait_list);
+       
+                                       s = s + "</span></td></tr>";
+       
+                               }
+                               for (n = 0; n < jso.i.contexts[ci].vhosts.length; n++) {
+                                       if (parseInt(jso.i.contexts[ci].deprecated, 10) === 0)
+                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"l\">vhost " + (n + 1);
+                                       else
+                                               s += "<tr><td>&nbsp;&nbsp;</td><td class=\"dl\">vhost " + (n + 1);
+                                       s += "</td><td><span class=\"mountname\">";
+                                       if (jso.i.contexts[ci].vhosts[n].use_ssl === "1")
+                                               s = s + "https://";
+                                       else
+                                               s = s + "http://";
+                                       s = s + san(jso.i.contexts[ci].vhosts[n].name) + ":" +
+                                               san(jso.i.contexts[ci].vhosts[n].port) + "</span>";
+                                       if (jso.i.contexts[ci].vhosts[n].sts === "1")
+                                               s = s + " (STS)";
+                                       s = s +"<br>" +
+                                       
+                                         "<span class=n>Total Rx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].vhosts[n].rx)) +"B</span>, " +
+                                         "<span class=n>Total Tx:</span> <span class=v>" + humanize(san(jso.i.contexts[ci].vhosts[n].tx)) +"B</span><br>" +
+                                         
+                                         "<span class=n>CONNECTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h1_conn) +"</span>, " +
+                                         "<span class=n>Websocket:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].ws_upg) +"</span>, " +
+                                         "<span class=n>H2 upgrade:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_upg) +"</span>, " +
+                                         "<span class=n>H2 ALPN:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_alpn) +"</span>, " +
+                                         "<span class=n>Rejected:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].rejected) +"</span><br>" +
+                                       
+                                         "<span class=n>TRANSACTIONS: HTTP/1.x:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h1_trans) + "</span>, " +
+                                         "<span class=n>H2:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_trans) +"</span>, " +
+                                         "<span class=n>Total H2 substreams:</span> <span class=v>" + san(jso.i.contexts[ci].vhosts[n].h2_subs) +"</span><br>";
+                                       
+                                       if (jso.i.contexts[ci].vhosts[n].mounts) {
+                                               s = s + "<table><tr><td class=t>Mountpoint</td><td class=t>Origin</td><td class=t>Cache Policy</td></tr>";
+       
+                                               var m;
+                                               for (m = 0; m < jso.i.contexts[ci].vhosts[n].mounts.length; m++) {
+                                                       s = s + "<tr><td>";
+                                                       s = s + "<span class=\"m1\">" + san(jso.i.contexts[ci].vhosts[n].mounts[m].mountpoint) +
+                                                               "</span></td><td><span class=\"m2\">" +
+                                                               san(jso.i.contexts[ci].vhosts[n].mounts[m].origin) +
+                                                               "</span></td><td>";
+                                                       if (parseInt(san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age), 10))
+                                                               s = s + "<span class=n>max-age:</span> <span class=v>" +
+                                                               san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_max_age) +
+                                                               "</span>, <span class=n>reuse:</span> <span class=v>" +
+                                                               san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_reuse) +
+                                                               "</span>, <span class=n>reval:</span> <span class=v>" +
+                                                               san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_revalidate) +
+                                                               "</span>, <span class=n>inter:</span> <span class=v>" +
+                                                               san(jso.i.contexts[ci].vhosts[n].mounts[m].cache_intermediaries);
+                                                       s = s + "</span></td></tr>";
+                                               }
+                                               s = s + "</table>";
+                                       }
+                                       s = s + "</td></tr>";
+                               }
+
+                               s += "</table></td></tr>";
+                               
+                       } // context
+                       s = s + "</table>";
+                       
+                       document.getElementById("conninfo").innerHTML = s;
+               };
+
+               socket_status.onclose = function(){
+                       document.getElementById("title").innerHTML = "Server Status (Disconnected)";
+                       lws_gray_out(true,{"zindex":"499"});
+               };
+       } catch(exception) {
+               alert("<p>Error" + exception);  
+       }
+}
+
+/* stuff that has to be delayed until all the page assets are loaded */
+
+window.addEventListener("load", function() {
+
+       lws_gray_out(true,{"zindex":"499"});
+       
+       ws_open_server_status();
+       
+}, false);
+
+}());
+
diff --git a/plugins/ssh-base/crypto/chacha.c b/plugins/ssh-base/crypto/chacha.c
new file mode 100644 (file)
index 0000000..694de25
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+#include <libwebsockets.h>
+#include "lws-ssh.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+struct chacha_ctx {
+       u_int input[16];
+};
+
+#define CHACHA_MINKEYLEN       16
+#define CHACHA_NONCELEN                8
+#define CHACHA_CTRLEN          8
+#define CHACHA_STATELEN                (CHACHA_NONCELEN+CHACHA_CTRLEN)
+#define CHACHA_BLOCKLEN                64
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct chacha_ctx chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+  (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+  (((u32)((p)[0])      ) | \
+   ((u32)((p)[1]) <<  8) | \
+   ((u32)((p)[2]) << 16) | \
+   ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+  do { \
+    (p)[0] = U8V((v)      ); \
+    (p)[1] = U8V((v) >>  8); \
+    (p)[2] = U8V((v) >> 16); \
+    (p)[3] = U8V((v) >> 24); \
+  } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits)
+{
+  const char *constants;
+
+  x->input[4] = U8TO32_LITTLE(k + 0);
+  x->input[5] = U8TO32_LITTLE(k + 4);
+  x->input[6] = U8TO32_LITTLE(k + 8);
+  x->input[7] = U8TO32_LITTLE(k + 12);
+  if (kbits == 256) { /* recommended */
+    k += 16;
+    constants = sigma;
+  } else { /* kbits == 128 */
+    constants = tau;
+  }
+  x->input[8] = U8TO32_LITTLE(k + 0);
+  x->input[9] = U8TO32_LITTLE(k + 4);
+  x->input[10] = U8TO32_LITTLE(k + 8);
+  x->input[11] = U8TO32_LITTLE(k + 12);
+  x->input[0] = U8TO32_LITTLE(constants + 0);
+  x->input[1] = U8TO32_LITTLE(constants + 4);
+  x->input[2] = U8TO32_LITTLE(constants + 8);
+  x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+void
+chacha_ivsetup(chacha_ctx *x, const u8 *iv, const u8 *counter)
+{
+  x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0);
+  x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4);
+  x->input[14] = U8TO32_LITTLE(iv + 0);
+  x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+  u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+  u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+  u8 *ctarget = NULL;
+  u8 tmp[64];
+  u_int i;
+
+  if (!bytes) return;
+
+  j0 = x->input[0];
+  j1 = x->input[1];
+  j2 = x->input[2];
+  j3 = x->input[3];
+  j4 = x->input[4];
+  j5 = x->input[5];
+  j6 = x->input[6];
+  j7 = x->input[7];
+  j8 = x->input[8];
+  j9 = x->input[9];
+  j10 = x->input[10];
+  j11 = x->input[11];
+  j12 = x->input[12];
+  j13 = x->input[13];
+  j14 = x->input[14];
+  j15 = x->input[15];
+
+  for (;;) {
+    if (bytes < 64) {
+      for (i = 0;i < bytes;++i) tmp[i] = m[i];
+      m = tmp;
+      ctarget = c;
+      c = tmp;
+    }
+    x0 = j0;
+    x1 = j1;
+    x2 = j2;
+    x3 = j3;
+    x4 = j4;
+    x5 = j5;
+    x6 = j6;
+    x7 = j7;
+    x8 = j8;
+    x9 = j9;
+    x10 = j10;
+    x11 = j11;
+    x12 = j12;
+    x13 = j13;
+    x14 = j14;
+    x15 = j15;
+    for (i = 20;i > 0;i -= 2) {
+      QUARTERROUND( x0, x4, x8,x12)
+      QUARTERROUND( x1, x5, x9,x13)
+      QUARTERROUND( x2, x6,x10,x14)
+      QUARTERROUND( x3, x7,x11,x15)
+      QUARTERROUND( x0, x5,x10,x15)
+      QUARTERROUND( x1, x6,x11,x12)
+      QUARTERROUND( x2, x7, x8,x13)
+      QUARTERROUND( x3, x4, x9,x14)
+    }
+    x0 = PLUS(x0,j0);
+    x1 = PLUS(x1,j1);
+    x2 = PLUS(x2,j2);
+    x3 = PLUS(x3,j3);
+    x4 = PLUS(x4,j4);
+    x5 = PLUS(x5,j5);
+    x6 = PLUS(x6,j6);
+    x7 = PLUS(x7,j7);
+    x8 = PLUS(x8,j8);
+    x9 = PLUS(x9,j9);
+    x10 = PLUS(x10,j10);
+    x11 = PLUS(x11,j11);
+    x12 = PLUS(x12,j12);
+    x13 = PLUS(x13,j13);
+    x14 = PLUS(x14,j14);
+    x15 = PLUS(x15,j15);
+
+    x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+    x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+    x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+    x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+    x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+    x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+    x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+    x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+    x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+    x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+    x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+    x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+    x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+    x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+    x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+    x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+
+    j12 = PLUSONE(j12);
+    if (!j12)
+      j13 = PLUSONE(j13);
+      /* stopping at 2^70 bytes per nonce is user's responsibility */
+
+    U32TO8_LITTLE(c + 0,x0);
+    U32TO8_LITTLE(c + 4,x1);
+    U32TO8_LITTLE(c + 8,x2);
+    U32TO8_LITTLE(c + 12,x3);
+    U32TO8_LITTLE(c + 16,x4);
+    U32TO8_LITTLE(c + 20,x5);
+    U32TO8_LITTLE(c + 24,x6);
+    U32TO8_LITTLE(c + 28,x7);
+    U32TO8_LITTLE(c + 32,x8);
+    U32TO8_LITTLE(c + 36,x9);
+    U32TO8_LITTLE(c + 40,x10);
+    U32TO8_LITTLE(c + 44,x11);
+    U32TO8_LITTLE(c + 48,x12);
+    U32TO8_LITTLE(c + 52,x13);
+    U32TO8_LITTLE(c + 56,x14);
+    U32TO8_LITTLE(c + 60,x15);
+
+    if (bytes <= 64) {
+      if (bytes < 64) {
+        for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+      }
+      x->input[12] = j12;
+      x->input[13] = j13;
+      return;
+    }
+    bytes -= 64;
+    c += 64;
+    m += 64;
+  }
+}
+
+struct lws_cipher_chacha {
+       struct chacha_ctx ccctx[2];
+};
+
+#define K_1(_keys) &((struct lws_cipher_chacha *)_keys->cipher)->ccctx[0]
+#define K_2(_keys) &((struct lws_cipher_chacha *)_keys->cipher)->ccctx[1]
+
+int
+lws_chacha_activate(struct lws_ssh_keys *keys)
+{
+       if (keys->cipher) {
+               free(keys->cipher);
+               keys->cipher = NULL;
+       }
+
+       keys->cipher = malloc(sizeof(struct lws_cipher_chacha));
+       if (!keys->cipher)
+               return 1;
+
+       memset(keys->cipher, 0, sizeof(struct lws_cipher_chacha));
+
+       /* uses 2 x 256-bit keys, so 512 bits (64 bytes) needed */
+       chacha_keysetup(K_2(keys), keys->key[SSH_KEYIDX_ENC], 256);
+       chacha_keysetup(K_1(keys), &keys->key[SSH_KEYIDX_ENC][32], 256);
+
+       keys->valid = 1;
+       keys->full_length = 1;
+       keys->padding_alignment = 8; // CHACHA_BLOCKLEN;
+       keys->MAC_length = POLY1305_TAGLEN;
+
+       return 0;
+}
+
+void
+lws_chacha_destroy(struct lws_ssh_keys *keys)
+{
+       if (keys->cipher) {
+               free(keys->cipher);
+               keys->cipher = NULL;
+       }
+}
+
+uint32_t
+lws_chachapoly_get_length(struct lws_ssh_keys *keys, uint32_t seq,
+                         const uint8_t *in4)
+{
+        uint8_t buf[4], seqbuf[8];
+
+       /*
+        * When receiving a packet, the length must be decrypted first.  When 4
+        * bytes of ciphertext length have been received, they may be decrypted
+        * using the K_1 key, a nonce consisting of the packet sequence number
+        * encoded as a uint64 under the usual SSH wire encoding and a zero
+        * block counter to obtain the plaintext length.
+        */
+        POKE_U64(seqbuf, seq);
+       chacha_ivsetup(K_1(keys), seqbuf, NULL);
+        chacha_encrypt_bytes(K_1(keys), in4, buf, 4);
+
+       return PEEK_U32(buf);
+}
+
+/*
+ * chachapoly_crypt() operates as following:
+ * En/decrypt with header key 'aadlen' bytes from 'src', storing result
+ * to 'dest'. The ciphertext here is treated as additional authenticated
+ * data for MAC calculation.
+ * En/decrypt 'len' bytes at offset 'aadlen' from 'src' to 'dest'. Use
+ * POLY1305_TAGLEN bytes at offset 'len'+'aadlen' as the authentication
+ * tag. This tag is written on encryption and verified on decryption.
+ */
+int
+chachapoly_crypt(struct lws_ssh_keys *keys, u_int seqnr, u_char *dest,
+    const u_char *src, u_int len, u_int aadlen, u_int authlen, int do_encrypt)
+{
+        u_char seqbuf[8];
+        const u_char one[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; /* NB little-endian */
+        u_char expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN];
+        int r = 1;
+
+        /*
+         * Run ChaCha20 once to generate the Poly1305 key. The IV is the
+         * packet sequence number.
+         */
+        memset(poly_key, 0, sizeof(poly_key));
+        POKE_U64(seqbuf, seqnr);
+        chacha_ivsetup(K_2(keys), seqbuf, NULL);
+        chacha_encrypt_bytes(K_2(keys),
+            poly_key, poly_key, sizeof(poly_key));
+
+        /* If decrypting, check tag before anything else */
+        if (!do_encrypt) {
+                const u_char *tag = src + aadlen + len;
+
+                poly1305_auth(expected_tag, src, aadlen + len, poly_key);
+                if (lws_timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN)) {
+                        r = 2;
+                        goto out;
+                }
+        }
+
+        /* Crypt additional data */
+        if (aadlen) {
+                chacha_ivsetup(K_1(keys), seqbuf, NULL);
+                chacha_encrypt_bytes(K_1(keys), src, dest, aadlen);
+        }
+
+        /* Set Chacha's block counter to 1 */
+        chacha_ivsetup(K_2(keys), seqbuf, one);
+        chacha_encrypt_bytes(K_2(keys), src + aadlen, dest + aadlen, len);
+
+        /* If encrypting, calculate and append tag */
+        if (do_encrypt) {
+                poly1305_auth(dest + aadlen + len, dest, aadlen + len,
+                    poly_key);
+        }
+        r = 0;
+ out:
+        lws_explicit_bzero(expected_tag, sizeof(expected_tag));
+        lws_explicit_bzero(seqbuf, sizeof(seqbuf));
+        lws_explicit_bzero(poly_key, sizeof(poly_key));
+        return r;
+}
+
+int
+lws_chacha_decrypt(struct lws_ssh_keys *keys, uint32_t seq,
+                  const uint8_t *ct, uint32_t len, uint8_t *pt)
+{
+       return chachapoly_crypt(keys, seq, pt, ct, len - POLY1305_TAGLEN - 4, 4,
+                        POLY1305_TAGLEN, 0);
+}
+
+int
+lws_chacha_encrypt(struct lws_ssh_keys *keys, uint32_t seq,
+                  const uint8_t *ct, uint32_t len, uint8_t *pt)
+{
+       return chachapoly_crypt(keys, seq, pt, ct, len - 4, 4, 0, 1);
+}
+
diff --git a/plugins/ssh-base/crypto/ed25519.c b/plugins/ssh-base/crypto/ed25519.c
new file mode 100644 (file)
index 0000000..72b3ae7
--- /dev/null
@@ -0,0 +1,221 @@
+/* $OpenBSD: ed25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/ed25519.c
+ *
+ * Modified to use lws genhash by Andy Green <andy@warmcat.com>
+ */
+
+#include <libwebsockets.h>
+#include <lws-ssh.h>
+#include "ge25519.h"
+
+int
+crypto_hash_sha512(uint8_t *hash64, const uint8_t *data, size_t len)
+{
+       struct lws_genhash_ctx ctx;
+       int ret;
+
+       if (lws_genhash_init(&ctx, LWS_GENHASH_TYPE_SHA512)) {
+               lwsl_notice("Failed to init SHA512\n");
+               return 0;
+       }
+
+       ret = lws_genhash_update(&ctx, data, len);
+
+       if (lws_genhash_destroy(&ctx, hash64))
+               lwsl_notice("genhash destroy failed\n");
+
+       return ret ? 0 : 64;
+}
+
+
+static void
+get_hram(unsigned char *hram, const unsigned char *sm,
+        const unsigned char *pk, unsigned char *playground,
+        size_t smlen)
+{
+       unsigned long long i;
+
+       for (i =  0; i < 32; ++i)
+               playground[i] = sm[i];
+       for (i = 32; i < 64; ++i)
+               playground[i] = pk[i-32];
+       for (i = 64; i < smlen; ++i)
+               playground[i] = sm[i];
+
+       crypto_hash_sha512(hram, playground, smlen);
+}
+
+
+int crypto_sign_ed25519_keypair(
+    struct lws_context *context,
+    unsigned char *pk,
+    unsigned char *sk
+    )
+{
+  sc25519 scsk;
+  ge25519 gepk;
+  unsigned char extsk[64];
+  int i;
+
+  lws_get_random(context, sk, 32);
+  crypto_hash_sha512(extsk, sk, 32);
+  extsk[0] &= 248;
+  extsk[31] &= 127;
+  extsk[31] |= 64;
+
+  sc25519_from32bytes(&scsk,extsk);
+  
+  ge25519_scalarmult_base(&gepk, &scsk);
+  ge25519_pack(pk, &gepk);
+  for(i=0;i<32;i++)
+    sk[32 + i] = pk[i];
+  return 0;
+}
+
+int crypto_sign_ed25519(
+    unsigned char *sm,
+    unsigned long long *smlen,
+    const unsigned char *m, size_t mlen,
+    const unsigned char *sk
+    )
+{
+  sc25519 sck, scs, scsk;
+  ge25519 ger;
+  unsigned char r[32];
+  unsigned char s[32];
+  unsigned char extsk[64];
+  unsigned long long i;
+  unsigned char hmg[crypto_hash_sha512_BYTES];
+  unsigned char hram[crypto_hash_sha512_BYTES];
+
+  crypto_hash_sha512(extsk, sk, 32);
+  extsk[0] &= 248;
+  extsk[31] &= 127;
+  extsk[31] |= 64;
+
+  *smlen = mlen+64;
+  for(i=0;i<mlen;i++)
+    sm[64 + i] = m[i];
+  for(i=0;i<32;i++)
+    sm[32 + i] = extsk[32+i];
+
+  crypto_hash_sha512(hmg, sm+32, mlen+32);
+  /* Generate k as h(extsk[32],...,extsk[63],m) */
+
+  /* Computation of R */
+  sc25519_from64bytes(&sck, hmg);
+  ge25519_scalarmult_base(&ger, &sck);
+  ge25519_pack(r, &ger);
+  
+  /* Computation of s */
+  for (i = 0; i < 32; i++)
+    sm[i] = r[i];
+
+  get_hram(hram, sm, sk + 32, sm, (size_t)mlen + 64);
+
+  sc25519_from64bytes(&scs, hram);
+  sc25519_from32bytes(&scsk, extsk);
+  sc25519_mul(&scs, &scs, &scsk);
+  
+  sc25519_add(&scs, &scs, &sck);
+
+  sc25519_to32bytes(s,&scs); /* cat s */
+  for (i = 0; i < 32; i++)
+    sm[32 + i] = s[i]; 
+
+  return 0;
+}
+
+int crypto_verify_32(const unsigned char *x,const unsigned char *y)
+{
+  unsigned int differentbits = 0;
+#define F(i) differentbits |= x[i] ^ y[i];
+  F(0)
+  F(1)
+  F(2)
+  F(3)
+  F(4)
+  F(5)
+  F(6)
+  F(7)
+  F(8)
+  F(9)
+  F(10)
+  F(11)
+  F(12)
+  F(13)
+  F(14)
+  F(15)
+  F(16)
+  F(17)
+  F(18)
+  F(19)
+  F(20)
+  F(21)
+  F(22)
+  F(23)
+  F(24)
+  F(25)
+  F(26)
+  F(27)
+  F(28)
+  F(29)
+  F(30)
+  F(31)
+  return (1 & ((differentbits - 1) >> 8)) - 1;
+}
+
+int crypto_sign_ed25519_open(
+    unsigned char *m,unsigned long long *mlen,
+    const unsigned char *sm,unsigned long long smlen,
+    const unsigned char *pk
+    )
+{
+  unsigned int i;
+  int ret;
+  unsigned char t2[32];
+  ge25519 get1, get2;
+  sc25519 schram, scs;
+  unsigned char hram[crypto_hash_sha512_BYTES];
+
+  *mlen = (unsigned long long) -1;
+  if (smlen < 64) {
+         lwsl_notice("a\n");
+
+         return -1;
+  }
+
+  if (ge25519_unpackneg_vartime(&get1, pk)) {
+         lwsl_notice("b\n");
+         return -1;
+  }
+
+  get_hram(hram,sm,pk,m, (size_t)smlen);
+
+  sc25519_from64bytes(&schram, hram);
+
+  sc25519_from32bytes(&scs, sm+32);
+
+  ge25519_double_scalarmult_vartime(&get2, &get1, &schram, &ge25519_base, &scs);
+  ge25519_pack(t2, &get2);
+
+  ret = crypto_verify_32(sm, t2);
+  lwsl_notice("vf says %d\n", ret);
+
+  if (!ret)
+  {
+    for(i=0;i<smlen-64;i++)
+      m[i] = sm[i + 64];
+    *mlen = smlen-64;
+  }
+  else
+  {
+    for(i=0;i<smlen-64;i++)
+      m[i] = 0;
+  }
+  return ret;
+}
diff --git a/plugins/ssh-base/crypto/fe25519.c b/plugins/ssh-base/crypto/fe25519.c
new file mode 100644 (file)
index 0000000..fdc2e2a
--- /dev/null
@@ -0,0 +1,338 @@
+/* $OpenBSD: fe25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.c
+ */
+
+#include "libwebsockets.h"
+
+#define WINDOWSIZE 1 /* Should be 1,2, or 4 */
+#define WINDOWMASK ((1<<WINDOWSIZE)-1)
+
+#include "fe25519.h"
+
+static uint32_t fe_equal(uint32_t a,uint32_t b) /* 16-bit inputs */
+{
+  uint32_t x = a ^ b; /* 0: yes; 1..65535: no */
+  x -= 1; /* 4294967295: yes; 0..65534: no */
+  x >>= 31; /* 1: yes; 0: no */
+  return x;
+}
+
+static uint32_t ge(uint32_t a,uint32_t b) /* 16-bit inputs */
+{
+  unsigned int x = a;
+  x -= (unsigned int) b; /* 0..65535: yes; 4294901761..4294967295: no */
+  x >>= 31; /* 0: yes; 1: no */
+  x ^= 1; /* 1: yes; 0: no */
+  return x;
+}
+
+static uint32_t times19(uint32_t a)
+{
+  return (a << 4) + (a << 1) + a;
+}
+
+static uint32_t times38(uint32_t a)
+{
+  return (a << 5) + (a << 2) + (a << 1);
+}
+
+static void fe_reduce_add_sub(fe25519 *r)
+{
+  uint32_t t;
+  int i,rep;
+
+  for(rep=0;rep<4;rep++)
+  {
+    t = r->v[31] >> 7;
+    r->v[31] &= 127;
+    t = times19(t);
+    r->v[0] += t;
+    for(i=0;i<31;i++)
+    {
+      t = r->v[i] >> 8;
+      r->v[i+1] += t;
+      r->v[i] &= 255;
+    }
+  }
+}
+
+static void reduce_mul(fe25519 *r)
+{
+  uint32_t t;
+  int i,rep;
+
+  for(rep=0;rep<2;rep++)
+  {
+    t = r->v[31] >> 7;
+    r->v[31] &= 127;
+    t = times19(t);
+    r->v[0] += t;
+    for(i=0;i<31;i++)
+    {
+      t = r->v[i] >> 8;
+      r->v[i+1] += t;
+      r->v[i] &= 255;
+    }
+  }
+}
+
+/* reduction modulo 2^255-19 */
+void fe25519_freeze(fe25519 *r) 
+{
+  int i;
+  uint32_t m = fe_equal(r->v[31],127);
+
+  for(i=30;i>0;i--)
+    m &= fe_equal(r->v[i],255);
+  m &= ge(r->v[0],237);
+
+  m = -(int32_t)m;
+
+  r->v[31] -= m&127;
+  for(i=30;i>0;i--)
+    r->v[i] -= m&255;
+  r->v[0] -= m&237;
+}
+
+void fe25519_unpack(fe25519 *r, const unsigned char x[32])
+{
+  int i;
+  for(i=0;i<32;i++) r->v[i] = x[i];
+  r->v[31] &= 127;
+}
+
+/* Assumes input x being reduced below 2^255 */
+void fe25519_pack(unsigned char r[32], const fe25519 *x)
+{
+  int i;
+  fe25519 y = *x;
+  fe25519_freeze(&y);
+  for(i=0;i<32;i++) 
+    r[i] = y.v[i];
+}
+
+int fe25519_iszero(const fe25519 *x)
+{
+  int i;
+  int r;
+  fe25519 t = *x;
+  fe25519_freeze(&t);
+  r = fe_equal(t.v[0],0);
+  for(i=1;i<32;i++) 
+    r &= fe_equal(t.v[i],0);
+  return r;
+}
+
+int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y)
+{
+  int i;
+  fe25519 t1 = *x;
+  fe25519 t2 = *y;
+  fe25519_freeze(&t1);
+  fe25519_freeze(&t2);
+  for(i=0;i<32;i++)
+    if(t1.v[i] != t2.v[i]) return 0;
+  return 1;
+}
+
+void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b)
+{
+  int i;
+  uint32_t mask = b;
+  mask = -(int32_t)mask;
+  for(i=0;i<32;i++) r->v[i] ^= mask & (x->v[i] ^ r->v[i]);
+}
+
+unsigned char fe25519_getparity(const fe25519 *x)
+{
+  fe25519 t = *x;
+  fe25519_freeze(&t);
+  return (unsigned char)(t.v[0] & 1);
+}
+
+void fe25519_setone(fe25519 *r)
+{
+  int i;
+  r->v[0] = 1;
+  for(i=1;i<32;i++) r->v[i]=0;
+}
+
+void fe25519_setzero(fe25519 *r)
+{
+  int i;
+  for(i=0;i<32;i++) r->v[i]=0;
+}
+
+void fe25519_neg(fe25519 *r, const fe25519 *x)
+{
+  fe25519 t;
+  int i;
+  for(i=0;i<32;i++) t.v[i]=x->v[i];
+  fe25519_setzero(r);
+  fe25519_sub(r, r, &t);
+}
+
+void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y)
+{
+  int i;
+  for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i];
+  fe_reduce_add_sub(r);
+}
+
+void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y)
+{
+  int i;
+  uint32_t t[32];
+  t[0] = x->v[0] + 0x1da;
+  t[31] = x->v[31] + 0xfe;
+  for(i=1;i<31;i++) t[i] = x->v[i] + 0x1fe;
+  for(i=0;i<32;i++) r->v[i] = t[i] - y->v[i];
+  fe_reduce_add_sub(r);
+}
+
+void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y)
+{
+  int i,j;
+  uint32_t t[63];
+  for(i=0;i<63;i++)t[i] = 0;
+
+  for(i=0;i<32;i++)
+    for(j=0;j<32;j++)
+      t[i+j] += x->v[i] * y->v[j];
+
+  for(i=32;i<63;i++)
+    r->v[i-32] = t[i-32] + times38(t[i]); 
+  r->v[31] = t[31]; /* result now in r[0]...r[31] */
+
+  reduce_mul(r);
+}
+
+void fe25519_square(fe25519 *r, const fe25519 *x)
+{
+  fe25519_mul(r, x, x);
+}
+
+void fe25519_invert(fe25519 *r, const fe25519 *x)
+{
+       fe25519 z2;
+       fe25519 z9;
+       fe25519 z11;
+       fe25519 z2_5_0;
+       fe25519 z2_10_0;
+       fe25519 z2_20_0;
+       fe25519 z2_50_0;
+       fe25519 z2_100_0;
+       fe25519 t0;
+       fe25519 t1;
+       int i;
+       
+       /* 2 */ fe25519_square(&z2,x);
+       /* 4 */ fe25519_square(&t1,&z2);
+       /* 8 */ fe25519_square(&t0,&t1);
+       /* 9 */ fe25519_mul(&z9,&t0,x);
+       /* 11 */ fe25519_mul(&z11,&z9,&z2);
+       /* 22 */ fe25519_square(&t0,&z11);
+       /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0,&t0,&z9);
+
+       /* 2^6 - 2^1 */ fe25519_square(&t0,&z2_5_0);
+       /* 2^7 - 2^2 */ fe25519_square(&t1,&t0);
+       /* 2^8 - 2^3 */ fe25519_square(&t0,&t1);
+       /* 2^9 - 2^4 */ fe25519_square(&t1,&t0);
+       /* 2^10 - 2^5 */ fe25519_square(&t0,&t1);
+       /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0,&t0,&z2_5_0);
+
+       /* 2^11 - 2^1 */ fe25519_square(&t0,&z2_10_0);
+       /* 2^12 - 2^2 */ fe25519_square(&t1,&t0);
+       /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); }
+       /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0,&t1,&z2_10_0);
+
+       /* 2^21 - 2^1 */ fe25519_square(&t0,&z2_20_0);
+       /* 2^22 - 2^2 */ fe25519_square(&t1,&t0);
+       /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); }
+       /* 2^40 - 2^0 */ fe25519_mul(&t0,&t1,&z2_20_0);
+
+       /* 2^41 - 2^1 */ fe25519_square(&t1,&t0);
+       /* 2^42 - 2^2 */ fe25519_square(&t0,&t1);
+       /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fe25519_square(&t1,&t0); fe25519_square(&t0,&t1); }
+       /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t0,&z2_10_0);
+
+       /* 2^51 - 2^1 */ fe25519_square(&t0,&z2_50_0);
+       /* 2^52 - 2^2 */ fe25519_square(&t1,&t0);
+       /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); }
+       /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0,&t1,&z2_50_0);
+
+       /* 2^101 - 2^1 */ fe25519_square(&t1,&z2_100_0);
+       /* 2^102 - 2^2 */ fe25519_square(&t0,&t1);
+       /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fe25519_square(&t1,&t0); fe25519_square(&t0,&t1); }
+       /* 2^200 - 2^0 */ fe25519_mul(&t1,&t0,&z2_100_0);
+
+       /* 2^201 - 2^1 */ fe25519_square(&t0,&t1);
+       /* 2^202 - 2^2 */ fe25519_square(&t1,&t0);
+       /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); }
+       /* 2^250 - 2^0 */ fe25519_mul(&t0,&t1,&z2_50_0);
+
+       /* 2^251 - 2^1 */ fe25519_square(&t1,&t0);
+       /* 2^252 - 2^2 */ fe25519_square(&t0,&t1);
+       /* 2^253 - 2^3 */ fe25519_square(&t1,&t0);
+       /* 2^254 - 2^4 */ fe25519_square(&t0,&t1);
+       /* 2^255 - 2^5 */ fe25519_square(&t1,&t0);
+       /* 2^255 - 21 */ fe25519_mul(r,&t1,&z11);
+}
+
+void fe25519_pow2523(fe25519 *r, const fe25519 *x)
+{
+       fe25519 z2;
+       fe25519 z9;
+       fe25519 z11;
+       fe25519 z2_5_0;
+       fe25519 z2_10_0;
+       fe25519 z2_20_0;
+       fe25519 z2_50_0;
+       fe25519 z2_100_0;
+       fe25519 t;
+       int i;
+               
+       /* 2 */ fe25519_square(&z2,x);
+       /* 4 */ fe25519_square(&t,&z2);
+       /* 8 */ fe25519_square(&t,&t);
+       /* 9 */ fe25519_mul(&z9,&t,x);
+       /* 11 */ fe25519_mul(&z11,&z9,&z2);
+       /* 22 */ fe25519_square(&t,&z11);
+       /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0,&t,&z9);
+
+       /* 2^6 - 2^1 */ fe25519_square(&t,&z2_5_0);
+       /* 2^10 - 2^5 */ for (i = 1;i < 5;i++) { fe25519_square(&t,&t); }
+       /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0,&t,&z2_5_0);
+
+       /* 2^11 - 2^1 */ fe25519_square(&t,&z2_10_0);
+       /* 2^20 - 2^10 */ for (i = 1;i < 10;i++) { fe25519_square(&t,&t); }
+       /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0,&t,&z2_10_0);
+
+       /* 2^21 - 2^1 */ fe25519_square(&t,&z2_20_0);
+       /* 2^40 - 2^20 */ for (i = 1;i < 20;i++) { fe25519_square(&t,&t); }
+       /* 2^40 - 2^0 */ fe25519_mul(&t,&t,&z2_20_0);
+
+       /* 2^41 - 2^1 */ fe25519_square(&t,&t);
+       /* 2^50 - 2^10 */ for (i = 1;i < 10;i++) { fe25519_square(&t,&t); }
+       /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t,&z2_10_0);
+
+       /* 2^51 - 2^1 */ fe25519_square(&t,&z2_50_0);
+       /* 2^100 - 2^50 */ for (i = 1;i < 50;i++) { fe25519_square(&t,&t); }
+       /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0,&t,&z2_50_0);
+
+       /* 2^101 - 2^1 */ fe25519_square(&t,&z2_100_0);
+       /* 2^200 - 2^100 */ for (i = 1;i < 100;i++) { fe25519_square(&t,&t); }
+       /* 2^200 - 2^0 */ fe25519_mul(&t,&t,&z2_100_0);
+
+       /* 2^201 - 2^1 */ fe25519_square(&t,&t);
+       /* 2^250 - 2^50 */ for (i = 1;i < 50;i++) { fe25519_square(&t,&t); }
+       /* 2^250 - 2^0 */ fe25519_mul(&t,&t,&z2_50_0);
+
+       /* 2^251 - 2^1 */ fe25519_square(&t,&t);
+       /* 2^252 - 2^2 */ fe25519_square(&t,&t);
+       /* 2^252 - 3 */ fe25519_mul(r,&t,x);
+}
diff --git a/plugins/ssh-base/crypto/fe25519.h b/plugins/ssh-base/crypto/fe25519.h
new file mode 100644 (file)
index 0000000..c1f5bf5
--- /dev/null
@@ -0,0 +1,68 @@
+/* $OpenBSD: fe25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.h
+ */
+
+#ifndef FE25519_H
+#define FE25519_H
+
+#define fe25519              crypto_sign_ed25519_ref_fe25519
+#define fe25519_freeze       crypto_sign_ed25519_ref_fe25519_freeze
+#define fe25519_unpack       crypto_sign_ed25519_ref_fe25519_unpack
+#define fe25519_pack         crypto_sign_ed25519_ref_fe25519_pack
+#define fe25519_iszero       crypto_sign_ed25519_ref_fe25519_iszero
+#define fe25519_iseq_vartime crypto_sign_ed25519_ref_fe25519_iseq_vartime
+#define fe25519_cmov         crypto_sign_ed25519_ref_fe25519_cmov
+#define fe25519_setone       crypto_sign_ed25519_ref_fe25519_setone
+#define fe25519_setzero      crypto_sign_ed25519_ref_fe25519_setzero
+#define fe25519_neg          crypto_sign_ed25519_ref_fe25519_neg
+#define fe25519_getparity    crypto_sign_ed25519_ref_fe25519_getparity
+#define fe25519_add          crypto_sign_ed25519_ref_fe25519_add
+#define fe25519_sub          crypto_sign_ed25519_ref_fe25519_sub
+#define fe25519_mul          crypto_sign_ed25519_ref_fe25519_mul
+#define fe25519_square       crypto_sign_ed25519_ref_fe25519_square
+#define fe25519_invert       crypto_sign_ed25519_ref_fe25519_invert
+#define fe25519_pow2523      crypto_sign_ed25519_ref_fe25519_pow2523
+
+typedef struct 
+{
+       uint32_t v[32];
+}
+fe25519;
+
+void fe25519_freeze(fe25519 *r);
+
+void fe25519_unpack(fe25519 *r, const unsigned char x[32]);
+
+void fe25519_pack(unsigned char r[32], const fe25519 *x);
+
+int fe25519_iszero(const fe25519 *x);
+
+int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y);
+
+void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b);
+
+void fe25519_setone(fe25519 *r);
+
+void fe25519_setzero(fe25519 *r);
+
+void fe25519_neg(fe25519 *r, const fe25519 *x);
+
+unsigned char fe25519_getparity(const fe25519 *x);
+
+void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y);
+
+void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y);
+
+void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y);
+
+void fe25519_square(fe25519 *r, const fe25519 *x);
+
+void fe25519_invert(fe25519 *r, const fe25519 *x);
+
+void fe25519_pow2523(fe25519 *r, const fe25519 *x);
+
+#endif
diff --git a/plugins/ssh-base/crypto/ge25519.c b/plugins/ssh-base/crypto/ge25519.c
new file mode 100644 (file)
index 0000000..0c6273b
--- /dev/null
@@ -0,0 +1,321 @@
+/* $OpenBSD: ge25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.c
+ */
+
+#include <libwebsockets.h>
+
+#include "fe25519.h"
+#include "sc25519.h"
+#include "ge25519.h"
+
+/* 
+ * Arithmetic on the twisted Edwards curve -x^2 + y^2 = 1 + dx^2y^2 
+ * with d = -(121665/121666) = 37095705934669439343138083508754565189542113879843219016388785533085940283555
+ * Base point: (15112221349535400772501151409588531511454012693041857206046113283949847762202,46316835694926478169428394003475163141307993866256225615783033603165251855960);
+ */
+
+/* d */
+static const fe25519 ge25519_ecd = {{0xA3, 0x78, 0x59, 0x13, 0xCA, 0x4D, 0xEB, 0x75, 0xAB, 0xD8, 0x41, 0x41, 0x4D, 0x0A, 0x70, 0x00, 
+                      0x98, 0xE8, 0x79, 0x77, 0x79, 0x40, 0xC7, 0x8C, 0x73, 0xFE, 0x6F, 0x2B, 0xEE, 0x6C, 0x03, 0x52}};
+/* 2*d */
+static const fe25519 ge25519_ec2d = {{0x59, 0xF1, 0xB2, 0x26, 0x94, 0x9B, 0xD6, 0xEB, 0x56, 0xB1, 0x83, 0x82, 0x9A, 0x14, 0xE0, 0x00, 
+                       0x30, 0xD1, 0xF3, 0xEE, 0xF2, 0x80, 0x8E, 0x19, 0xE7, 0xFC, 0xDF, 0x56, 0xDC, 0xD9, 0x06, 0x24}};
+/* sqrt(-1) */
+static const fe25519 ge25519_sqrtm1 = {{0xB0, 0xA0, 0x0E, 0x4A, 0x27, 0x1B, 0xEE, 0xC4, 0x78, 0xE4, 0x2F, 0xAD, 0x06, 0x18, 0x43, 0x2F, 
+                         0xA7, 0xD7, 0xFB, 0x3D, 0x99, 0x00, 0x4D, 0x2B, 0x0B, 0xDF, 0xC1, 0x4F, 0x80, 0x24, 0x83, 0x2B}};
+
+#define ge25519_p3 ge25519
+
+typedef struct
+{
+  fe25519 x;
+  fe25519 z;
+  fe25519 y;
+  fe25519 t;
+} ge25519_p1p1;
+
+typedef struct
+{
+  fe25519 x;
+  fe25519 y;
+  fe25519 z;
+} ge25519_p2;
+
+typedef struct
+{
+  fe25519 x;
+  fe25519 y;
+} ge25519_aff;
+
+
+/* Packed coordinates of the base point */
+const ge25519 ge25519_base = {{{0x1A, 0xD5, 0x25, 0x8F, 0x60, 0x2D, 0x56, 0xC9, 0xB2, 0xA7, 0x25, 0x95, 0x60, 0xC7, 0x2C, 0x69, 
+                                0x5C, 0xDC, 0xD6, 0xFD, 0x31, 0xE2, 0xA4, 0xC0, 0xFE, 0x53, 0x6E, 0xCD, 0xD3, 0x36, 0x69, 0x21}},
+                              {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 
+                                0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}},
+                              {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+                              {{0xA3, 0xDD, 0xB7, 0xA5, 0xB3, 0x8A, 0xDE, 0x6D, 0xF5, 0x52, 0x51, 0x77, 0x80, 0x9F, 0xF0, 0x20, 
+                                0x7D, 0xE3, 0xAB, 0x64, 0x8E, 0x4E, 0xEA, 0x66, 0x65, 0x76, 0x8B, 0xD7, 0x0F, 0x5F, 0x87, 0x67}}};
+
+/* Multiples of the base point in affine representation */
+static const ge25519_aff ge25519_base_multiples_affine[425] = {
+#include "ge25519_base.data"
+};
+
+static void p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p)
+{
+  fe25519_mul(&r->x, &p->x, &p->t);
+  fe25519_mul(&r->y, &p->y, &p->z);
+  fe25519_mul(&r->z, &p->z, &p->t);
+}
+
+static void p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p)
+{
+  p1p1_to_p2((ge25519_p2 *)r, p);
+  fe25519_mul(&r->t, &p->x, &p->y);
+}
+
+static void ge25519_mixadd2(ge25519_p3 *r, const ge25519_aff *q)
+{
+  fe25519 a,b,t1,t2,c,d,e,f,g,h,qt;
+  fe25519_mul(&qt, &q->x, &q->y);
+  fe25519_sub(&a, &r->y, &r->x); /* A = (Y1-X1)*(Y2-X2) */
+  fe25519_add(&b, &r->y, &r->x); /* B = (Y1+X1)*(Y2+X2) */
+  fe25519_sub(&t1, &q->y, &q->x);
+  fe25519_add(&t2, &q->y, &q->x);
+  fe25519_mul(&a, &a, &t1);
+  fe25519_mul(&b, &b, &t2);
+  fe25519_sub(&e, &b, &a); /* E = B-A */
+  fe25519_add(&h, &b, &a); /* H = B+A */
+  fe25519_mul(&c, &r->t, &qt); /* C = T1*k*T2 */
+  fe25519_mul(&c, &c, &ge25519_ec2d);
+  fe25519_add(&d, &r->z, &r->z); /* D = Z1*2 */
+  fe25519_sub(&f, &d, &c); /* F = D-C */
+  fe25519_add(&g, &d, &c); /* G = D+C */
+  fe25519_mul(&r->x, &e, &f);
+  fe25519_mul(&r->y, &h, &g);
+  fe25519_mul(&r->z, &g, &f);
+  fe25519_mul(&r->t, &e, &h);
+}
+
+static void add_p1p1(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_p3 *q)
+{
+  fe25519 a, b, c, d, t;
+  
+  fe25519_sub(&a, &p->y, &p->x); /* A = (Y1-X1)*(Y2-X2) */
+  fe25519_sub(&t, &q->y, &q->x);
+  fe25519_mul(&a, &a, &t);
+  fe25519_add(&b, &p->x, &p->y); /* B = (Y1+X1)*(Y2+X2) */
+  fe25519_add(&t, &q->x, &q->y);
+  fe25519_mul(&b, &b, &t);
+  fe25519_mul(&c, &p->t, &q->t); /* C = T1*k*T2 */
+  fe25519_mul(&c, &c, &ge25519_ec2d);
+  fe25519_mul(&d, &p->z, &q->z); /* D = Z1*2*Z2 */
+  fe25519_add(&d, &d, &d);
+  fe25519_sub(&r->x, &b, &a); /* E = B-A */
+  fe25519_sub(&r->t, &d, &c); /* F = D-C */
+  fe25519_add(&r->z, &d, &c); /* G = D+C */
+  fe25519_add(&r->y, &b, &a); /* H = B+A */
+}
+
+/* See http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd */
+static void dbl_p1p1(ge25519_p1p1 *r, const ge25519_p2 *p)
+{
+  fe25519 a,b,c,d;
+  fe25519_square(&a, &p->x);
+  fe25519_square(&b, &p->y);
+  fe25519_square(&c, &p->z);
+  fe25519_add(&c, &c, &c);
+  fe25519_neg(&d, &a);
+
+  fe25519_add(&r->x, &p->x, &p->y);
+  fe25519_square(&r->x, &r->x);
+  fe25519_sub(&r->x, &r->x, &a);
+  fe25519_sub(&r->x, &r->x, &b);
+  fe25519_add(&r->z, &d, &b);
+  fe25519_sub(&r->t, &r->z, &c);
+  fe25519_sub(&r->y, &d, &b);
+}
+
+/* Constant-time version of: if(b) r = p */
+static void cmov_aff(ge25519_aff *r, const ge25519_aff *p, unsigned char b)
+{
+  fe25519_cmov(&r->x, &p->x, b);
+  fe25519_cmov(&r->y, &p->y, b);
+}
+
+static unsigned char ge_equal(signed char b,signed char c)
+{
+  unsigned char ub = b;
+  unsigned char uc = c;
+  unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */
+  uint32_t y = x; /* 0: yes; 1..255: no */
+  y -= 1; /* 4294967295: yes; 0..254: no */
+  y >>= 31; /* 1: yes; 0: no */
+  return y;
+}
+
+static unsigned char negative(signed char b)
+{
+  unsigned long long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */
+  x >>= 63; /* 1: yes; 0: no */
+  return (unsigned char)x;
+}
+
+static void choose_t(ge25519_aff *t, unsigned long long pos, signed char b)
+{
+  /* constant time */
+  fe25519 v;
+  *t = ge25519_base_multiples_affine[5*pos+0];
+  cmov_aff(t, &ge25519_base_multiples_affine[5*pos+1],ge_equal(b,1) | ge_equal(b,-1));
+  cmov_aff(t, &ge25519_base_multiples_affine[5*pos+2],ge_equal(b,2) | ge_equal(b,-2));
+  cmov_aff(t, &ge25519_base_multiples_affine[5*pos+3],ge_equal(b,3) | ge_equal(b,-3));
+  cmov_aff(t, &ge25519_base_multiples_affine[5*pos+4],ge_equal(b,-4));
+  fe25519_neg(&v, &t->x);
+  fe25519_cmov(&t->x, &v, negative(b));
+}
+
+static void setneutral(ge25519 *r)
+{
+  fe25519_setzero(&r->x);
+  fe25519_setone(&r->y);
+  fe25519_setone(&r->z);
+  fe25519_setzero(&r->t);
+}
+
+/* ********************************************************************
+ *                    EXPORTED FUNCTIONS
+ ******************************************************************** */
+
+/* return 0 on success, -1 otherwise */
+int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p[32])
+{
+  unsigned char par;
+  fe25519 t, chk, num, den, den2, den4, den6;
+  fe25519_setone(&r->z);
+  par = p[31] >> 7;
+  fe25519_unpack(&r->y, p); 
+  fe25519_square(&num, &r->y); /* x = y^2 */
+  fe25519_mul(&den, &num, &ge25519_ecd); /* den = dy^2 */
+  fe25519_sub(&num, &num, &r->z); /* x = y^2-1 */
+  fe25519_add(&den, &r->z, &den); /* den = dy^2+1 */
+
+  /* Computation of sqrt(num/den) */
+  /* 1.: computation of num^((p-5)/8)*den^((7p-35)/8) = (num*den^7)^((p-5)/8) */
+  fe25519_square(&den2, &den);
+  fe25519_square(&den4, &den2);
+  fe25519_mul(&den6, &den4, &den2);
+  fe25519_mul(&t, &den6, &num);
+  fe25519_mul(&t, &t, &den);
+
+  fe25519_pow2523(&t, &t);
+  /* 2. computation of r->x = t * num * den^3 */
+  fe25519_mul(&t, &t, &num);
+  fe25519_mul(&t, &t, &den);
+  fe25519_mul(&t, &t, &den);
+  fe25519_mul(&r->x, &t, &den);
+
+  /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */
+  fe25519_square(&chk, &r->x);
+  fe25519_mul(&chk, &chk, &den);
+  if (!fe25519_iseq_vartime(&chk, &num))
+    fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1);
+
+  /* 4. Now we have one of the two square roots, except if input was not a square */
+  fe25519_square(&chk, &r->x);
+  fe25519_mul(&chk, &chk, &den);
+  if (!fe25519_iseq_vartime(&chk, &num))
+    return -1;
+
+  /* 5. Choose the desired square root according to parity: */
+  if(fe25519_getparity(&r->x) != (1-par))
+    fe25519_neg(&r->x, &r->x);
+
+  fe25519_mul(&r->t, &r->x, &r->y);
+  return 0;
+}
+
+void ge25519_pack(unsigned char r[32], const ge25519_p3 *p)
+{
+  fe25519 tx, ty, zi;
+  fe25519_invert(&zi, &p->z); 
+  fe25519_mul(&tx, &p->x, &zi);
+  fe25519_mul(&ty, &p->y, &zi);
+  fe25519_pack(r, &ty);
+  r[31] ^= fe25519_getparity(&tx) << 7;
+}
+
+int ge25519_isneutral_vartime(const ge25519_p3 *p)
+{
+  int ret = 1;
+  if(!fe25519_iszero(&p->x)) ret = 0;
+  if(!fe25519_iseq_vartime(&p->y, &p->z)) ret = 0;
+  return ret;
+}
+
+/* computes [s1]p1 + [s2]p2 */
+void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge25519_p3 *p1, const sc25519 *s1, const ge25519_p3 *p2, const sc25519 *s2)
+{
+  ge25519_p1p1 tp1p1;
+  ge25519_p3 pre[16];
+  unsigned char b[127];
+  int i;
+
+  /* precomputation                                                        s2 s1 */
+  setneutral(pre);                                                      /* 00 00 */
+  pre[1] = *p1;                                                         /* 00 01 */
+  dbl_p1p1(&tp1p1,(ge25519_p2 *)p1);      p1p1_to_p3( &pre[2], &tp1p1); /* 00 10 */
+  add_p1p1(&tp1p1,&pre[1], &pre[2]);      p1p1_to_p3( &pre[3], &tp1p1); /* 00 11 */
+  pre[4] = *p2;                                                         /* 01 00 */
+  add_p1p1(&tp1p1,&pre[1], &pre[4]);      p1p1_to_p3( &pre[5], &tp1p1); /* 01 01 */
+  add_p1p1(&tp1p1,&pre[2], &pre[4]);      p1p1_to_p3( &pre[6], &tp1p1); /* 01 10 */
+  add_p1p1(&tp1p1,&pre[3], &pre[4]);      p1p1_to_p3( &pre[7], &tp1p1); /* 01 11 */
+  dbl_p1p1(&tp1p1,(ge25519_p2 *)p2);      p1p1_to_p3( &pre[8], &tp1p1); /* 10 00 */
+  add_p1p1(&tp1p1,&pre[1], &pre[8]);      p1p1_to_p3( &pre[9], &tp1p1); /* 10 01 */
+  dbl_p1p1(&tp1p1,(ge25519_p2 *)&pre[5]); p1p1_to_p3(&pre[10], &tp1p1); /* 10 10 */
+  add_p1p1(&tp1p1,&pre[3], &pre[8]);      p1p1_to_p3(&pre[11], &tp1p1); /* 10 11 */
+  add_p1p1(&tp1p1,&pre[4], &pre[8]);      p1p1_to_p3(&pre[12], &tp1p1); /* 11 00 */
+  add_p1p1(&tp1p1,&pre[1],&pre[12]);      p1p1_to_p3(&pre[13], &tp1p1); /* 11 01 */
+  add_p1p1(&tp1p1,&pre[2],&pre[12]);      p1p1_to_p3(&pre[14], &tp1p1); /* 11 10 */
+  add_p1p1(&tp1p1,&pre[3],&pre[12]);      p1p1_to_p3(&pre[15], &tp1p1); /* 11 11 */
+
+  sc25519_2interleave2(b,s1,s2);
+
+  /* scalar multiplication */
+  *r = pre[b[126]];
+  for(i=125;i>=0;i--)
+  {
+    dbl_p1p1(&tp1p1, (ge25519_p2 *)r);
+    p1p1_to_p2((ge25519_p2 *) r, &tp1p1);
+    dbl_p1p1(&tp1p1, (ge25519_p2 *)r);
+    if(b[i]!=0)
+    {
+      p1p1_to_p3(r, &tp1p1);
+      add_p1p1(&tp1p1, r, &pre[b[i]]);
+    }
+    if(i != 0) p1p1_to_p2((ge25519_p2 *)r, &tp1p1);
+    else p1p1_to_p3(r, &tp1p1);
+  }
+}
+
+void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s)
+{
+  signed char b[85];
+  int i;
+  ge25519_aff t;
+  sc25519_window3(b,s);
+
+  choose_t((ge25519_aff *)r, 0, b[0]);
+  fe25519_setone(&r->z);
+  fe25519_mul(&r->t, &r->x, &r->y);
+  for(i=1;i<85;i++)
+  {
+    choose_t(&t, (unsigned long long) i, b[i]);
+    ge25519_mixadd2(r, &t);
+  }
+}
diff --git a/plugins/ssh-base/crypto/ge25519.h b/plugins/ssh-base/crypto/ge25519.h
new file mode 100644 (file)
index 0000000..a097637
--- /dev/null
@@ -0,0 +1,43 @@
+/* $OpenBSD: ge25519.h,v 1.4 2015/02/16 18:26:26 miod Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.h
+ */
+
+#ifndef GE25519_H
+#define GE25519_H
+
+#include "fe25519.h"
+#include "sc25519.h"
+
+#define ge25519                           crypto_sign_ed25519_ref_ge25519
+#define ge25519_base                      crypto_sign_ed25519_ref_ge25519_base
+#define ge25519_unpackneg_vartime         crypto_sign_ed25519_ref_unpackneg_vartime
+#define ge25519_pack                      crypto_sign_ed25519_ref_pack
+#define ge25519_isneutral_vartime         crypto_sign_ed25519_ref_isneutral_vartime
+#define ge25519_double_scalarmult_vartime crypto_sign_ed25519_ref_double_scalarmult_vartime
+#define ge25519_scalarmult_base           crypto_sign_ed25519_ref_scalarmult_base
+
+typedef struct
+{
+  fe25519 x;
+  fe25519 y;
+  fe25519 z;
+  fe25519 t;
+} ge25519;
+
+extern const ge25519 ge25519_base;
+
+int ge25519_unpackneg_vartime(ge25519 *r, const unsigned char p[32]);
+
+void ge25519_pack(unsigned char r[32], const ge25519 *p);
+
+int ge25519_isneutral_vartime(const ge25519 *p);
+
+void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const sc25519 *s1, const ge25519 *p2, const sc25519 *s2);
+
+void ge25519_scalarmult_base(ge25519 *r, const sc25519 *s);
+
+#endif
diff --git a/plugins/ssh-base/crypto/ge25519_base.data b/plugins/ssh-base/crypto/ge25519_base.data
new file mode 100644 (file)
index 0000000..66fb1b6
--- /dev/null
@@ -0,0 +1,858 @@
+/* $OpenBSD: ge25519_base.data,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519_base.data
+ */
+
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21}} ,
+ {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}},
+{{{0x0e, 0xce, 0x43, 0x28, 0x4e, 0xa1, 0xc5, 0x83, 0x5f, 0xa4, 0xd7, 0x15, 0x45, 0x8e, 0x0d, 0x08, 0xac, 0xe7, 0x33, 0x18, 0x7d, 0x3b, 0x04, 0x3d, 0x6c, 0x04, 0x5a, 0x9f, 0x4c, 0x38, 0xab, 0x36}} ,
+ {{0xc9, 0xa3, 0xf8, 0x6a, 0xae, 0x46, 0x5f, 0x0e, 0x56, 0x51, 0x38, 0x64, 0x51, 0x0f, 0x39, 0x97, 0x56, 0x1f, 0xa2, 0xc9, 0xe8, 0x5e, 0xa2, 0x1d, 0xc2, 0x29, 0x23, 0x09, 0xf3, 0xcd, 0x60, 0x22}}},
+{{{0x5c, 0xe2, 0xf8, 0xd3, 0x5f, 0x48, 0x62, 0xac, 0x86, 0x48, 0x62, 0x81, 0x19, 0x98, 0x43, 0x63, 0x3a, 0xc8, 0xda, 0x3e, 0x74, 0xae, 0xf4, 0x1f, 0x49, 0x8f, 0x92, 0x22, 0x4a, 0x9c, 0xae, 0x67}} ,
+ {{0xd4, 0xb4, 0xf5, 0x78, 0x48, 0x68, 0xc3, 0x02, 0x04, 0x03, 0x24, 0x67, 0x17, 0xec, 0x16, 0x9f, 0xf7, 0x9e, 0x26, 0x60, 0x8e, 0xa1, 0x26, 0xa1, 0xab, 0x69, 0xee, 0x77, 0xd1, 0xb1, 0x67, 0x12}}},
+{{{0x70, 0xf8, 0xc9, 0xc4, 0x57, 0xa6, 0x3a, 0x49, 0x47, 0x15, 0xce, 0x93, 0xc1, 0x9e, 0x73, 0x1a, 0xf9, 0x20, 0x35, 0x7a, 0xb8, 0xd4, 0x25, 0x83, 0x46, 0xf1, 0xcf, 0x56, 0xdb, 0xa8, 0x3d, 0x20}} ,
+ {{0x2f, 0x11, 0x32, 0xca, 0x61, 0xab, 0x38, 0xdf, 0xf0, 0x0f, 0x2f, 0xea, 0x32, 0x28, 0xf2, 0x4c, 0x6c, 0x71, 0xd5, 0x80, 0x85, 0xb8, 0x0e, 0x47, 0xe1, 0x95, 0x15, 0xcb, 0x27, 0xe8, 0xd0, 0x47}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xc8, 0x84, 0xa5, 0x08, 0xbc, 0xfd, 0x87, 0x3b, 0x99, 0x8b, 0x69, 0x80, 0x7b, 0xc6, 0x3a, 0xeb, 0x93, 0xcf, 0x4e, 0xf8, 0x5c, 0x2d, 0x86, 0x42, 0xb6, 0x71, 0xd7, 0x97, 0x5f, 0xe1, 0x42, 0x67}} ,
+ {{0xb4, 0xb9, 0x37, 0xfc, 0xa9, 0x5b, 0x2f, 0x1e, 0x93, 0xe4, 0x1e, 0x62, 0xfc, 0x3c, 0x78, 0x81, 0x8f, 0xf3, 0x8a, 0x66, 0x09, 0x6f, 0xad, 0x6e, 0x79, 0x73, 0xe5, 0xc9, 0x00, 0x06, 0xd3, 0x21}}},
+{{{0xf8, 0xf9, 0x28, 0x6c, 0x6d, 0x59, 0xb2, 0x59, 0x74, 0x23, 0xbf, 0xe7, 0x33, 0x8d, 0x57, 0x09, 0x91, 0x9c, 0x24, 0x08, 0x15, 0x2b, 0xe2, 0xb8, 0xee, 0x3a, 0xe5, 0x27, 0x06, 0x86, 0xa4, 0x23}} ,
+ {{0xeb, 0x27, 0x67, 0xc1, 0x37, 0xab, 0x7a, 0xd8, 0x27, 0x9c, 0x07, 0x8e, 0xff, 0x11, 0x6a, 0xb0, 0x78, 0x6e, 0xad, 0x3a, 0x2e, 0x0f, 0x98, 0x9f, 0x72, 0xc3, 0x7f, 0x82, 0xf2, 0x96, 0x96, 0x70}}},
+{{{0x81, 0x6b, 0x88, 0xe8, 0x1e, 0xc7, 0x77, 0x96, 0x0e, 0xa1, 0xa9, 0x52, 0xe0, 0xd8, 0x0e, 0x61, 0x9e, 0x79, 0x2d, 0x95, 0x9c, 0x8d, 0x96, 0xe0, 0x06, 0x40, 0x5d, 0x87, 0x28, 0x5f, 0x98, 0x70}} ,
+ {{0xf1, 0x79, 0x7b, 0xed, 0x4f, 0x44, 0xb2, 0xe7, 0x08, 0x0d, 0xc2, 0x08, 0x12, 0xd2, 0x9f, 0xdf, 0xcd, 0x93, 0x20, 0x8a, 0xcf, 0x33, 0xca, 0x6d, 0x89, 0xb9, 0x77, 0xc8, 0x93, 0x1b, 0x4e, 0x60}}},
+{{{0x26, 0x4f, 0x7e, 0x97, 0xf6, 0x40, 0xdd, 0x4f, 0xfc, 0x52, 0x78, 0xf9, 0x90, 0x31, 0x03, 0xe6, 0x7d, 0x56, 0x39, 0x0b, 0x1d, 0x56, 0x82, 0x85, 0xf9, 0x1a, 0x42, 0x17, 0x69, 0x6c, 0xcf, 0x39}} ,
+ {{0x69, 0xd2, 0x06, 0x3a, 0x4f, 0x39, 0x2d, 0xf9, 0x38, 0x40, 0x8c, 0x4c, 0xe7, 0x05, 0x12, 0xb4, 0x78, 0x8b, 0xf8, 0xc0, 0xec, 0x93, 0xde, 0x7a, 0x6b, 0xce, 0x2c, 0xe1, 0x0e, 0xa9, 0x34, 0x44}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x0b, 0xa4, 0x3c, 0xb0, 0x0f, 0x7a, 0x51, 0xf1, 0x78, 0xd6, 0xd9, 0x6a, 0xfd, 0x46, 0xe8, 0xb8, 0xa8, 0x79, 0x1d, 0x87, 0xf9, 0x90, 0xf2, 0x9c, 0x13, 0x29, 0xf8, 0x0b, 0x20, 0x64, 0xfa, 0x05}} ,
+ {{0x26, 0x09, 0xda, 0x17, 0xaf, 0x95, 0xd6, 0xfb, 0x6a, 0x19, 0x0d, 0x6e, 0x5e, 0x12, 0xf1, 0x99, 0x4c, 0xaa, 0xa8, 0x6f, 0x79, 0x86, 0xf4, 0x72, 0x28, 0x00, 0x26, 0xf9, 0xea, 0x9e, 0x19, 0x3d}}},
+{{{0x87, 0xdd, 0xcf, 0xf0, 0x5b, 0x49, 0xa2, 0x5d, 0x40, 0x7a, 0x23, 0x26, 0xa4, 0x7a, 0x83, 0x8a, 0xb7, 0x8b, 0xd2, 0x1a, 0xbf, 0xea, 0x02, 0x24, 0x08, 0x5f, 0x7b, 0xa9, 0xb1, 0xbe, 0x9d, 0x37}} ,
+ {{0xfc, 0x86, 0x4b, 0x08, 0xee, 0xe7, 0xa0, 0xfd, 0x21, 0x45, 0x09, 0x34, 0xc1, 0x61, 0x32, 0x23, 0xfc, 0x9b, 0x55, 0x48, 0x53, 0x99, 0xf7, 0x63, 0xd0, 0x99, 0xce, 0x01, 0xe0, 0x9f, 0xeb, 0x28}}},
+{{{0x47, 0xfc, 0xab, 0x5a, 0x17, 0xf0, 0x85, 0x56, 0x3a, 0x30, 0x86, 0x20, 0x28, 0x4b, 0x8e, 0x44, 0x74, 0x3a, 0x6e, 0x02, 0xf1, 0x32, 0x8f, 0x9f, 0x3f, 0x08, 0x35, 0xe9, 0xca, 0x16, 0x5f, 0x6e}} ,
+ {{0x1c, 0x59, 0x1c, 0x65, 0x5d, 0x34, 0xa4, 0x09, 0xcd, 0x13, 0x9c, 0x70, 0x7d, 0xb1, 0x2a, 0xc5, 0x88, 0xaf, 0x0b, 0x60, 0xc7, 0x9f, 0x34, 0x8d, 0xd6, 0xb7, 0x7f, 0xea, 0x78, 0x65, 0x8d, 0x77}}},
+{{{0x56, 0xa5, 0xc2, 0x0c, 0xdd, 0xbc, 0xb8, 0x20, 0x6d, 0x57, 0x61, 0xb5, 0xfb, 0x78, 0xb5, 0xd4, 0x49, 0x54, 0x90, 0x26, 0xc1, 0xcb, 0xe9, 0xe6, 0xbf, 0xec, 0x1d, 0x4e, 0xed, 0x07, 0x7e, 0x5e}} ,
+ {{0xc7, 0xf6, 0x6c, 0x56, 0x31, 0x20, 0x14, 0x0e, 0xa8, 0xd9, 0x27, 0xc1, 0x9a, 0x3d, 0x1b, 0x7d, 0x0e, 0x26, 0xd3, 0x81, 0xaa, 0xeb, 0xf5, 0x6b, 0x79, 0x02, 0xf1, 0x51, 0x5c, 0x75, 0x55, 0x0f}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x0a, 0x34, 0xcd, 0x82, 0x3c, 0x33, 0x09, 0x54, 0xd2, 0x61, 0x39, 0x30, 0x9b, 0xfd, 0xef, 0x21, 0x26, 0xd4, 0x70, 0xfa, 0xee, 0xf9, 0x31, 0x33, 0x73, 0x84, 0xd0, 0xb3, 0x81, 0xbf, 0xec, 0x2e}} ,
+ {{0xe8, 0x93, 0x8b, 0x00, 0x64, 0xf7, 0x9c, 0xb8, 0x74, 0xe0, 0xe6, 0x49, 0x48, 0x4d, 0x4d, 0x48, 0xb6, 0x19, 0xa1, 0x40, 0xb7, 0xd9, 0x32, 0x41, 0x7c, 0x82, 0x37, 0xa1, 0x2d, 0xdc, 0xd2, 0x54}}},
+{{{0x68, 0x2b, 0x4a, 0x5b, 0xd5, 0xc7, 0x51, 0x91, 0x1d, 0xe1, 0x2a, 0x4b, 0xc4, 0x47, 0xf1, 0xbc, 0x7a, 0xb3, 0xcb, 0xc8, 0xb6, 0x7c, 0xac, 0x90, 0x05, 0xfd, 0xf3, 0xf9, 0x52, 0x3a, 0x11, 0x6b}} ,
+ {{0x3d, 0xc1, 0x27, 0xf3, 0x59, 0x43, 0x95, 0x90, 0xc5, 0x96, 0x79, 0xf5, 0xf4, 0x95, 0x65, 0x29, 0x06, 0x9c, 0x51, 0x05, 0x18, 0xda, 0xb8, 0x2e, 0x79, 0x7e, 0x69, 0x59, 0x71, 0x01, 0xeb, 0x1a}}},
+{{{0x15, 0x06, 0x49, 0xb6, 0x8a, 0x3c, 0xea, 0x2f, 0x34, 0x20, 0x14, 0xc3, 0xaa, 0xd6, 0xaf, 0x2c, 0x3e, 0xbd, 0x65, 0x20, 0xe2, 0x4d, 0x4b, 0x3b, 0xeb, 0x9f, 0x4a, 0xc3, 0xad, 0xa4, 0x3b, 0x60}} ,
+ {{0xbc, 0x58, 0xe6, 0xc0, 0x95, 0x2a, 0x2a, 0x81, 0x9a, 0x7a, 0xf3, 0xd2, 0x06, 0xbe, 0x48, 0xbc, 0x0c, 0xc5, 0x46, 0xe0, 0x6a, 0xd4, 0xac, 0x0f, 0xd9, 0xcc, 0x82, 0x34, 0x2c, 0xaf, 0xdb, 0x1f}}},
+{{{0xf7, 0x17, 0x13, 0xbd, 0xfb, 0xbc, 0xd2, 0xec, 0x45, 0xb3, 0x15, 0x31, 0xe9, 0xaf, 0x82, 0x84, 0x3d, 0x28, 0xc6, 0xfc, 0x11, 0xf5, 0x41, 0xb5, 0x8b, 0xd3, 0x12, 0x76, 0x52, 0xe7, 0x1a, 0x3c}} ,
+ {{0x4e, 0x36, 0x11, 0x07, 0xa2, 0x15, 0x20, 0x51, 0xc4, 0x2a, 0xc3, 0x62, 0x8b, 0x5e, 0x7f, 0xa6, 0x0f, 0xf9, 0x45, 0x85, 0x6c, 0x11, 0x86, 0xb7, 0x7e, 0xe5, 0xd7, 0xf9, 0xc3, 0x91, 0x1c, 0x05}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xea, 0xd6, 0xde, 0x29, 0x3a, 0x00, 0xb9, 0x02, 0x59, 0xcb, 0x26, 0xc4, 0xba, 0x99, 0xb1, 0x97, 0x2f, 0x8e, 0x00, 0x92, 0x26, 0x4f, 0x52, 0xeb, 0x47, 0x1b, 0x89, 0x8b, 0x24, 0xc0, 0x13, 0x7d}} ,
+ {{0xd5, 0x20, 0x5b, 0x80, 0xa6, 0x80, 0x20, 0x95, 0xc3, 0xe9, 0x9f, 0x8e, 0x87, 0x9e, 0x1e, 0x9e, 0x7a, 0xc7, 0xcc, 0x75, 0x6c, 0xa5, 0xf1, 0x91, 0x1a, 0xa8, 0x01, 0x2c, 0xab, 0x76, 0xa9, 0x59}}},
+{{{0xde, 0xc9, 0xb1, 0x31, 0x10, 0x16, 0xaa, 0x35, 0x14, 0x6a, 0xd4, 0xb5, 0x34, 0x82, 0x71, 0xd2, 0x4a, 0x5d, 0x9a, 0x1f, 0x53, 0x26, 0x3c, 0xe5, 0x8e, 0x8d, 0x33, 0x7f, 0xff, 0xa9, 0xd5, 0x17}} ,
+ {{0x89, 0xaf, 0xf6, 0xa4, 0x64, 0xd5, 0x10, 0xe0, 0x1d, 0xad, 0xef, 0x44, 0xbd, 0xda, 0x83, 0xac, 0x7a, 0xa8, 0xf0, 0x1c, 0x07, 0xf9, 0xc3, 0x43, 0x6c, 0x3f, 0xb7, 0xd3, 0x87, 0x22, 0x02, 0x73}}},
+{{{0x64, 0x1d, 0x49, 0x13, 0x2f, 0x71, 0xec, 0x69, 0x87, 0xd0, 0x42, 0xee, 0x13, 0xec, 0xe3, 0xed, 0x56, 0x7b, 0xbf, 0xbd, 0x8c, 0x2f, 0x7d, 0x7b, 0x9d, 0x28, 0xec, 0x8e, 0x76, 0x2f, 0x6f, 0x08}} ,
+ {{0x22, 0xf5, 0x5f, 0x4d, 0x15, 0xef, 0xfc, 0x4e, 0x57, 0x03, 0x36, 0x89, 0xf0, 0xeb, 0x5b, 0x91, 0xd6, 0xe2, 0xca, 0x01, 0xa5, 0xee, 0x52, 0xec, 0xa0, 0x3c, 0x8f, 0x33, 0x90, 0x5a, 0x94, 0x72}}},
+{{{0x8a, 0x4b, 0xe7, 0x38, 0xbc, 0xda, 0xc2, 0xb0, 0x85, 0xe1, 0x4a, 0xfe, 0x2d, 0x44, 0x84, 0xcb, 0x20, 0x6b, 0x2d, 0xbf, 0x11, 0x9c, 0xd7, 0xbe, 0xd3, 0x3e, 0x5f, 0xbf, 0x68, 0xbc, 0xa8, 0x07}} ,
+ {{0x01, 0x89, 0x28, 0x22, 0x6a, 0x78, 0xaa, 0x29, 0x03, 0xc8, 0x74, 0x95, 0x03, 0x3e, 0xdc, 0xbd, 0x07, 0x13, 0xa8, 0xa2, 0x20, 0x2d, 0xb3, 0x18, 0x70, 0x42, 0xfd, 0x7a, 0xc4, 0xd7, 0x49, 0x72}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x02, 0xff, 0x32, 0x2b, 0x5c, 0x93, 0x54, 0x32, 0xe8, 0x57, 0x54, 0x1a, 0x8b, 0x33, 0x60, 0x65, 0xd3, 0x67, 0xa4, 0xc1, 0x26, 0xc4, 0xa4, 0x34, 0x1f, 0x9b, 0xa7, 0xa9, 0xf4, 0xd9, 0x4f, 0x5b}} ,
+ {{0x46, 0x8d, 0xb0, 0x33, 0x54, 0x26, 0x5b, 0x68, 0xdf, 0xbb, 0xc5, 0xec, 0xc2, 0xf9, 0x3c, 0x5a, 0x37, 0xc1, 0x8e, 0x27, 0x47, 0xaa, 0x49, 0x5a, 0xf8, 0xfb, 0x68, 0x04, 0x23, 0xd1, 0xeb, 0x40}}},
+{{{0x65, 0xa5, 0x11, 0x84, 0x8a, 0x67, 0x9d, 0x9e, 0xd1, 0x44, 0x68, 0x7a, 0x34, 0xe1, 0x9f, 0xa3, 0x54, 0xcd, 0x07, 0xca, 0x79, 0x1f, 0x54, 0x2f, 0x13, 0x70, 0x4e, 0xee, 0xa2, 0xfa, 0xe7, 0x5d}} ,
+ {{0x36, 0xec, 0x54, 0xf8, 0xce, 0xe4, 0x85, 0xdf, 0xf6, 0x6f, 0x1d, 0x90, 0x08, 0xbc, 0xe8, 0xc0, 0x92, 0x2d, 0x43, 0x6b, 0x92, 0xa9, 0x8e, 0xab, 0x0a, 0x2e, 0x1c, 0x1e, 0x64, 0x23, 0x9f, 0x2c}}},
+{{{0xa7, 0xd6, 0x2e, 0xd5, 0xcc, 0xd4, 0xcb, 0x5a, 0x3b, 0xa7, 0xf9, 0x46, 0x03, 0x1d, 0xad, 0x2b, 0x34, 0x31, 0x90, 0x00, 0x46, 0x08, 0x82, 0x14, 0xc4, 0xe0, 0x9c, 0xf0, 0xe3, 0x55, 0x43, 0x31}} ,
+ {{0x60, 0xd6, 0xdd, 0x78, 0xe6, 0xd4, 0x22, 0x42, 0x1f, 0x00, 0xf9, 0xb1, 0x6a, 0x63, 0xe2, 0x92, 0x59, 0xd1, 0x1a, 0xb7, 0x00, 0x54, 0x29, 0xc9, 0xc1, 0xf6, 0x6f, 0x7a, 0xc5, 0x3c, 0x5f, 0x65}}},
+{{{0x27, 0x4f, 0xd0, 0x72, 0xb1, 0x11, 0x14, 0x27, 0x15, 0x94, 0x48, 0x81, 0x7e, 0x74, 0xd8, 0x32, 0xd5, 0xd1, 0x11, 0x28, 0x60, 0x63, 0x36, 0x32, 0x37, 0xb5, 0x13, 0x1c, 0xa0, 0x37, 0xe3, 0x74}} ,
+ {{0xf1, 0x25, 0x4e, 0x11, 0x96, 0x67, 0xe6, 0x1c, 0xc2, 0xb2, 0x53, 0xe2, 0xda, 0x85, 0xee, 0xb2, 0x9f, 0x59, 0xf3, 0xba, 0xbd, 0xfa, 0xcf, 0x6e, 0xf9, 0xda, 0xa4, 0xb3, 0x02, 0x8f, 0x64, 0x08}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x34, 0x94, 0xf2, 0x64, 0x54, 0x47, 0x37, 0x07, 0x40, 0x8a, 0x20, 0xba, 0x4a, 0x55, 0xd7, 0x3f, 0x47, 0xba, 0x25, 0x23, 0x14, 0xb0, 0x2c, 0xe8, 0x55, 0xa8, 0xa6, 0xef, 0x51, 0xbd, 0x6f, 0x6a}} ,
+ {{0x71, 0xd6, 0x16, 0x76, 0xb2, 0x06, 0xea, 0x79, 0xf5, 0xc4, 0xc3, 0x52, 0x7e, 0x61, 0xd1, 0xe1, 0xad, 0x70, 0x78, 0x1d, 0x16, 0x11, 0xf8, 0x7c, 0x2b, 0xfc, 0x55, 0x9f, 0x52, 0xf8, 0xf5, 0x16}}},
+{{{0x34, 0x96, 0x9a, 0xf6, 0xc5, 0xe0, 0x14, 0x03, 0x24, 0x0e, 0x4c, 0xad, 0x9e, 0x9a, 0x70, 0x23, 0x96, 0xb2, 0xf1, 0x2e, 0x9d, 0xc3, 0x32, 0x9b, 0x54, 0xa5, 0x73, 0xde, 0x88, 0xb1, 0x3e, 0x24}} ,
+ {{0xf6, 0xe2, 0x4c, 0x1f, 0x5b, 0xb2, 0xaf, 0x82, 0xa5, 0xcf, 0x81, 0x10, 0x04, 0xef, 0xdb, 0xa2, 0xcc, 0x24, 0xb2, 0x7e, 0x0b, 0x7a, 0xeb, 0x01, 0xd8, 0x52, 0xf4, 0x51, 0x89, 0x29, 0x79, 0x37}}},
+{{{0x74, 0xde, 0x12, 0xf3, 0x68, 0xb7, 0x66, 0xc3, 0xee, 0x68, 0xdc, 0x81, 0xb5, 0x55, 0x99, 0xab, 0xd9, 0x28, 0x63, 0x6d, 0x8b, 0x40, 0x69, 0x75, 0x6c, 0xcd, 0x5c, 0x2a, 0x7e, 0x32, 0x7b, 0x29}} ,
+ {{0x02, 0xcc, 0x22, 0x74, 0x4d, 0x19, 0x07, 0xc0, 0xda, 0xb5, 0x76, 0x51, 0x2a, 0xaa, 0xa6, 0x0a, 0x5f, 0x26, 0xd4, 0xbc, 0xaf, 0x48, 0x88, 0x7f, 0x02, 0xbc, 0xf2, 0xe1, 0xcf, 0xe9, 0xdd, 0x15}}},
+{{{0xed, 0xb5, 0x9a, 0x8c, 0x9a, 0xdd, 0x27, 0xf4, 0x7f, 0x47, 0xd9, 0x52, 0xa7, 0xcd, 0x65, 0xa5, 0x31, 0x22, 0xed, 0xa6, 0x63, 0x5b, 0x80, 0x4a, 0xad, 0x4d, 0xed, 0xbf, 0xee, 0x49, 0xb3, 0x06}} ,
+ {{0xf8, 0x64, 0x8b, 0x60, 0x90, 0xe9, 0xde, 0x44, 0x77, 0xb9, 0x07, 0x36, 0x32, 0xc2, 0x50, 0xf5, 0x65, 0xdf, 0x48, 0x4c, 0x37, 0xaa, 0x68, 0xab, 0x9a, 0x1f, 0x3e, 0xff, 0x89, 0x92, 0xa0, 0x07}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x7d, 0x4f, 0x9c, 0x19, 0xc0, 0x4a, 0x31, 0xec, 0xf9, 0xaa, 0xeb, 0xb2, 0x16, 0x9c, 0xa3, 0x66, 0x5f, 0xd1, 0xd4, 0xed, 0xb8, 0x92, 0x1c, 0xab, 0xda, 0xea, 0xd9, 0x57, 0xdf, 0x4c, 0x2a, 0x48}} ,
+ {{0x4b, 0xb0, 0x4e, 0x6e, 0x11, 0x3b, 0x51, 0xbd, 0x6a, 0xfd, 0xe4, 0x25, 0xa5, 0x5f, 0x11, 0x3f, 0x98, 0x92, 0x51, 0x14, 0xc6, 0x5f, 0x3c, 0x0b, 0xa8, 0xf7, 0xc2, 0x81, 0x43, 0xde, 0x91, 0x73}}},
+{{{0x3c, 0x8f, 0x9f, 0x33, 0x2a, 0x1f, 0x43, 0x33, 0x8f, 0x68, 0xff, 0x1f, 0x3d, 0x73, 0x6b, 0xbf, 0x68, 0xcc, 0x7d, 0x13, 0x6c, 0x24, 0x4b, 0xcc, 0x4d, 0x24, 0x0d, 0xfe, 0xde, 0x86, 0xad, 0x3b}} ,
+ {{0x79, 0x51, 0x81, 0x01, 0xdc, 0x73, 0x53, 0xe0, 0x6e, 0x9b, 0xea, 0x68, 0x3f, 0x5c, 0x14, 0x84, 0x53, 0x8d, 0x4b, 0xc0, 0x9f, 0x9f, 0x89, 0x2b, 0x8c, 0xba, 0x86, 0xfa, 0xf2, 0xcd, 0xe3, 0x2d}}},
+{{{0x06, 0xf9, 0x29, 0x5a, 0xdb, 0x3d, 0x84, 0x52, 0xab, 0xcc, 0x6b, 0x60, 0x9d, 0xb7, 0x4a, 0x0e, 0x36, 0x63, 0x91, 0xad, 0xa0, 0x95, 0xb0, 0x97, 0x89, 0x4e, 0xcf, 0x7d, 0x3c, 0xe5, 0x7c, 0x28}} ,
+ {{0x2e, 0x69, 0x98, 0xfd, 0xc6, 0xbd, 0xcc, 0xca, 0xdf, 0x9a, 0x44, 0x7e, 0x9d, 0xca, 0x89, 0x6d, 0xbf, 0x27, 0xc2, 0xf8, 0xcd, 0x46, 0x00, 0x2b, 0xb5, 0x58, 0x4e, 0xb7, 0x89, 0x09, 0xe9, 0x2d}}},
+{{{0x54, 0xbe, 0x75, 0xcb, 0x05, 0xb0, 0x54, 0xb7, 0xe7, 0x26, 0x86, 0x4a, 0xfc, 0x19, 0xcf, 0x27, 0x46, 0xd4, 0x22, 0x96, 0x5a, 0x11, 0xe8, 0xd5, 0x1b, 0xed, 0x71, 0xc5, 0x5d, 0xc8, 0xaf, 0x45}} ,
+ {{0x40, 0x7b, 0x77, 0x57, 0x49, 0x9e, 0x80, 0x39, 0x23, 0xee, 0x81, 0x0b, 0x22, 0xcf, 0xdb, 0x7a, 0x2f, 0x14, 0xb8, 0x57, 0x8f, 0xa1, 0x39, 0x1e, 0x77, 0xfc, 0x0b, 0xa6, 0xbf, 0x8a, 0x0c, 0x6c}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x77, 0x3a, 0xd4, 0xd8, 0x27, 0xcf, 0xe8, 0xa1, 0x72, 0x9d, 0xca, 0xdd, 0x0d, 0x96, 0xda, 0x79, 0xed, 0x56, 0x42, 0x15, 0x60, 0xc7, 0x1c, 0x6b, 0x26, 0x30, 0xf6, 0x6a, 0x95, 0x67, 0xf3, 0x0a}} ,
+ {{0xc5, 0x08, 0xa4, 0x2b, 0x2f, 0xbd, 0x31, 0x81, 0x2a, 0xa6, 0xb6, 0xe4, 0x00, 0x91, 0xda, 0x3d, 0xb2, 0xb0, 0x96, 0xce, 0x8a, 0xd2, 0x8d, 0x70, 0xb3, 0xd3, 0x34, 0x01, 0x90, 0x8d, 0x10, 0x21}}},
+{{{0x33, 0x0d, 0xe7, 0xba, 0x4f, 0x07, 0xdf, 0x8d, 0xea, 0x7d, 0xa0, 0xc5, 0xd6, 0xb1, 0xb0, 0xe5, 0x57, 0x1b, 0x5b, 0xf5, 0x45, 0x13, 0x14, 0x64, 0x5a, 0xeb, 0x5c, 0xfc, 0x54, 0x01, 0x76, 0x2b}} ,
+ {{0x02, 0x0c, 0xc2, 0xaf, 0x96, 0x36, 0xfe, 0x4a, 0xe2, 0x54, 0x20, 0x6a, 0xeb, 0xb2, 0x9f, 0x62, 0xd7, 0xce, 0xa2, 0x3f, 0x20, 0x11, 0x34, 0x37, 0xe0, 0x42, 0xed, 0x6f, 0xf9, 0x1a, 0xc8, 0x7d}}},
+{{{0xd8, 0xb9, 0x11, 0xe8, 0x36, 0x3f, 0x42, 0xc1, 0xca, 0xdc, 0xd3, 0xf1, 0xc8, 0x23, 0x3d, 0x4f, 0x51, 0x7b, 0x9d, 0x8d, 0xd8, 0xe4, 0xa0, 0xaa, 0xf3, 0x04, 0xd6, 0x11, 0x93, 0xc8, 0x35, 0x45}} ,
+ {{0x61, 0x36, 0xd6, 0x08, 0x90, 0xbf, 0xa7, 0x7a, 0x97, 0x6c, 0x0f, 0x84, 0xd5, 0x33, 0x2d, 0x37, 0xc9, 0x6a, 0x80, 0x90, 0x3d, 0x0a, 0xa2, 0xaa, 0xe1, 0xb8, 0x84, 0xba, 0x61, 0x36, 0xdd, 0x69}}},
+{{{0x6b, 0xdb, 0x5b, 0x9c, 0xc6, 0x92, 0xbc, 0x23, 0xaf, 0xc5, 0xb8, 0x75, 0xf8, 0x42, 0xfa, 0xd6, 0xb6, 0x84, 0x94, 0x63, 0x98, 0x93, 0x48, 0x78, 0x38, 0xcd, 0xbb, 0x18, 0x34, 0xc3, 0xdb, 0x67}} ,
+ {{0x96, 0xf3, 0x3a, 0x09, 0x56, 0xb0, 0x6f, 0x7c, 0x51, 0x1e, 0x1b, 0x39, 0x48, 0xea, 0xc9, 0x0c, 0x25, 0xa2, 0x7a, 0xca, 0xe7, 0x92, 0xfc, 0x59, 0x30, 0xa3, 0x89, 0x85, 0xdf, 0x6f, 0x43, 0x38}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x79, 0x84, 0x44, 0x19, 0xbd, 0xe9, 0x54, 0xc4, 0xc0, 0x6e, 0x2a, 0xa8, 0xa8, 0x9b, 0x43, 0xd5, 0x71, 0x22, 0x5f, 0xdc, 0x01, 0xfa, 0xdf, 0xb3, 0xb8, 0x47, 0x4b, 0x0a, 0xa5, 0x44, 0xea, 0x29}} ,
+ {{0x05, 0x90, 0x50, 0xaf, 0x63, 0x5f, 0x9d, 0x9e, 0xe1, 0x9d, 0x38, 0x97, 0x1f, 0x6c, 0xac, 0x30, 0x46, 0xb2, 0x6a, 0x19, 0xd1, 0x4b, 0xdb, 0xbb, 0x8c, 0xda, 0x2e, 0xab, 0xc8, 0x5a, 0x77, 0x6c}}},
+{{{0x2b, 0xbe, 0xaf, 0xa1, 0x6d, 0x2f, 0x0b, 0xb1, 0x8f, 0xe3, 0xe0, 0x38, 0xcd, 0x0b, 0x41, 0x1b, 0x4a, 0x15, 0x07, 0xf3, 0x6f, 0xdc, 0xb8, 0xe9, 0xde, 0xb2, 0xa3, 0x40, 0x01, 0xa6, 0x45, 0x1e}} ,
+ {{0x76, 0x0a, 0xda, 0x8d, 0x2c, 0x07, 0x3f, 0x89, 0x7d, 0x04, 0xad, 0x43, 0x50, 0x6e, 0xd2, 0x47, 0xcb, 0x8a, 0xe6, 0x85, 0x1a, 0x24, 0xf3, 0xd2, 0x60, 0xfd, 0xdf, 0x73, 0xa4, 0x0d, 0x73, 0x0e}}},
+{{{0xfd, 0x67, 0x6b, 0x71, 0x9b, 0x81, 0x53, 0x39, 0x39, 0xf4, 0xb8, 0xd5, 0xc3, 0x30, 0x9b, 0x3b, 0x7c, 0xa3, 0xf0, 0xd0, 0x84, 0x21, 0xd6, 0xbf, 0xb7, 0x4c, 0x87, 0x13, 0x45, 0x2d, 0xa7, 0x55}} ,
+ {{0x5d, 0x04, 0xb3, 0x40, 0x28, 0x95, 0x2d, 0x30, 0x83, 0xec, 0x5e, 0xe4, 0xff, 0x75, 0xfe, 0x79, 0x26, 0x9d, 0x1d, 0x36, 0xcd, 0x0a, 0x15, 0xd2, 0x24, 0x14, 0x77, 0x71, 0xd7, 0x8a, 0x1b, 0x04}}},
+{{{0x5d, 0x93, 0xc9, 0xbe, 0xaa, 0x90, 0xcd, 0x9b, 0xfb, 0x73, 0x7e, 0xb0, 0x64, 0x98, 0x57, 0x44, 0x42, 0x41, 0xb1, 0xaf, 0xea, 0xc1, 0xc3, 0x22, 0xff, 0x60, 0x46, 0xcb, 0x61, 0x81, 0x70, 0x61}} ,
+ {{0x0d, 0x82, 0xb9, 0xfe, 0x21, 0xcd, 0xc4, 0xf5, 0x98, 0x0c, 0x4e, 0x72, 0xee, 0x87, 0x49, 0xf8, 0xa1, 0x95, 0xdf, 0x8f, 0x2d, 0xbd, 0x21, 0x06, 0x7c, 0x15, 0xe8, 0x12, 0x6d, 0x93, 0xd6, 0x38}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x91, 0xf7, 0x51, 0xd9, 0xef, 0x7d, 0x42, 0x01, 0x13, 0xe9, 0xb8, 0x7f, 0xa6, 0x49, 0x17, 0x64, 0x21, 0x80, 0x83, 0x2c, 0x63, 0x4c, 0x60, 0x09, 0x59, 0x91, 0x92, 0x77, 0x39, 0x51, 0xf4, 0x48}} ,
+ {{0x60, 0xd5, 0x22, 0x83, 0x08, 0x2f, 0xff, 0x99, 0x3e, 0x69, 0x6d, 0x88, 0xda, 0xe7, 0x5b, 0x52, 0x26, 0x31, 0x2a, 0xe5, 0x89, 0xde, 0x68, 0x90, 0xb6, 0x22, 0x5a, 0xbd, 0xd3, 0x85, 0x53, 0x31}}},
+{{{0xd8, 0xce, 0xdc, 0xf9, 0x3c, 0x4b, 0xa2, 0x1d, 0x2c, 0x2f, 0x36, 0xbe, 0x7a, 0xfc, 0xcd, 0xbc, 0xdc, 0xf9, 0x30, 0xbd, 0xff, 0x05, 0xc7, 0xe4, 0x8e, 0x17, 0x62, 0xf8, 0x4d, 0xa0, 0x56, 0x79}} ,
+ {{0x82, 0xe7, 0xf6, 0xba, 0x53, 0x84, 0x0a, 0xa3, 0x34, 0xff, 0x3c, 0xa3, 0x6a, 0xa1, 0x37, 0xea, 0xdd, 0xb6, 0x95, 0xb3, 0x78, 0x19, 0x76, 0x1e, 0x55, 0x2f, 0x77, 0x2e, 0x7f, 0xc1, 0xea, 0x5e}}},
+{{{0x83, 0xe1, 0x6e, 0xa9, 0x07, 0x33, 0x3e, 0x83, 0xff, 0xcb, 0x1c, 0x9f, 0xb1, 0xa3, 0xb4, 0xc9, 0xe1, 0x07, 0x97, 0xff, 0xf8, 0x23, 0x8f, 0xce, 0x40, 0xfd, 0x2e, 0x5e, 0xdb, 0x16, 0x43, 0x2d}} ,
+ {{0xba, 0x38, 0x02, 0xf7, 0x81, 0x43, 0x83, 0xa3, 0x20, 0x4f, 0x01, 0x3b, 0x8a, 0x04, 0x38, 0x31, 0xc6, 0x0f, 0xc8, 0xdf, 0xd7, 0xfa, 0x2f, 0x88, 0x3f, 0xfc, 0x0c, 0x76, 0xc4, 0xa6, 0x45, 0x72}}},
+{{{0xbb, 0x0c, 0xbc, 0x6a, 0xa4, 0x97, 0x17, 0x93, 0x2d, 0x6f, 0xde, 0x72, 0x10, 0x1c, 0x08, 0x2c, 0x0f, 0x80, 0x32, 0x68, 0x27, 0xd4, 0xab, 0xdd, 0xc5, 0x58, 0x61, 0x13, 0x6d, 0x11, 0x1e, 0x4d}} ,
+ {{0x1a, 0xb9, 0xc9, 0x10, 0xfb, 0x1e, 0x4e, 0xf4, 0x84, 0x4b, 0x8a, 0x5e, 0x7b, 0x4b, 0xe8, 0x43, 0x8c, 0x8f, 0x00, 0xb5, 0x54, 0x13, 0xc5, 0x5c, 0xb6, 0x35, 0x4e, 0x9d, 0xe4, 0x5b, 0x41, 0x6d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x15, 0x7d, 0x12, 0x48, 0x82, 0x14, 0x42, 0xcd, 0x32, 0xd4, 0x4b, 0xc1, 0x72, 0x61, 0x2a, 0x8c, 0xec, 0xe2, 0xf8, 0x24, 0x45, 0x94, 0xe3, 0xbe, 0xdd, 0x67, 0xa8, 0x77, 0x5a, 0xae, 0x5b, 0x4b}} ,
+ {{0xcb, 0x77, 0x9a, 0x20, 0xde, 0xb8, 0x23, 0xd9, 0xa0, 0x0f, 0x8c, 0x7b, 0xa5, 0xcb, 0xae, 0xb6, 0xec, 0x42, 0x67, 0x0e, 0x58, 0xa4, 0x75, 0x98, 0x21, 0x71, 0x84, 0xb3, 0xe0, 0x76, 0x94, 0x73}}},
+{{{0xdf, 0xfc, 0x69, 0x28, 0x23, 0x3f, 0x5b, 0xf8, 0x3b, 0x24, 0x37, 0xf3, 0x1d, 0xd5, 0x22, 0x6b, 0xd0, 0x98, 0xa8, 0x6c, 0xcf, 0xff, 0x06, 0xe1, 0x13, 0xdf, 0xb9, 0xc1, 0x0c, 0xa9, 0xbf, 0x33}} ,
+ {{0xd9, 0x81, 0xda, 0xb2, 0x4f, 0x82, 0x9d, 0x43, 0x81, 0x09, 0xf1, 0xd2, 0x01, 0xef, 0xac, 0xf4, 0x2d, 0x7d, 0x01, 0x09, 0xf1, 0xff, 0xa5, 0x9f, 0xe5, 0xca, 0x27, 0x63, 0xdb, 0x20, 0xb1, 0x53}}},
+{{{0x67, 0x02, 0xe8, 0xad, 0xa9, 0x34, 0xd4, 0xf0, 0x15, 0x81, 0xaa, 0xc7, 0x4d, 0x87, 0x94, 0xea, 0x75, 0xe7, 0x4c, 0x94, 0x04, 0x0e, 0x69, 0x87, 0xe7, 0x51, 0x91, 0x10, 0x03, 0xc7, 0xbe, 0x56}} ,
+ {{0x32, 0xfb, 0x86, 0xec, 0x33, 0x6b, 0x2e, 0x51, 0x2b, 0xc8, 0xfa, 0x6c, 0x70, 0x47, 0x7e, 0xce, 0x05, 0x0c, 0x71, 0xf3, 0xb4, 0x56, 0xa6, 0xdc, 0xcc, 0x78, 0x07, 0x75, 0xd0, 0xdd, 0xb2, 0x6a}}},
+{{{0xc6, 0xef, 0xb9, 0xc0, 0x2b, 0x22, 0x08, 0x1e, 0x71, 0x70, 0xb3, 0x35, 0x9c, 0x7a, 0x01, 0x92, 0x44, 0x9a, 0xf6, 0xb0, 0x58, 0x95, 0xc1, 0x9b, 0x02, 0xed, 0x2d, 0x7c, 0x34, 0x29, 0x49, 0x44}} ,
+ {{0x45, 0x62, 0x1d, 0x2e, 0xff, 0x2a, 0x1c, 0x21, 0xa4, 0x25, 0x7b, 0x0d, 0x8c, 0x15, 0x39, 0xfc, 0x8f, 0x7c, 0xa5, 0x7d, 0x1e, 0x25, 0xa3, 0x45, 0xd6, 0xab, 0xbd, 0xcb, 0xc5, 0x5e, 0x78, 0x77}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xd0, 0xd3, 0x42, 0xed, 0x1d, 0x00, 0x3c, 0x15, 0x2c, 0x9c, 0x77, 0x81, 0xd2, 0x73, 0xd1, 0x06, 0xd5, 0xc4, 0x7f, 0x94, 0xbb, 0x92, 0x2d, 0x2c, 0x4b, 0x45, 0x4b, 0xe9, 0x2a, 0x89, 0x6b, 0x2b}} ,
+ {{0xd2, 0x0c, 0x88, 0xc5, 0x48, 0x4d, 0xea, 0x0d, 0x4a, 0xc9, 0x52, 0x6a, 0x61, 0x79, 0xe9, 0x76, 0xf3, 0x85, 0x52, 0x5c, 0x1b, 0x2c, 0xe1, 0xd6, 0xc4, 0x0f, 0x18, 0x0e, 0x4e, 0xf6, 0x1c, 0x7f}}},
+{{{0xb4, 0x04, 0x2e, 0x42, 0xcb, 0x1f, 0x2b, 0x11, 0x51, 0x7b, 0x08, 0xac, 0xaa, 0x3e, 0x9e, 0x52, 0x60, 0xb7, 0xc2, 0x61, 0x57, 0x8c, 0x84, 0xd5, 0x18, 0xa6, 0x19, 0xfc, 0xb7, 0x75, 0x91, 0x1b}} ,
+ {{0xe8, 0x68, 0xca, 0x44, 0xc8, 0x38, 0x38, 0xcc, 0x53, 0x0a, 0x32, 0x35, 0xcc, 0x52, 0xcb, 0x0e, 0xf7, 0xc5, 0xe7, 0xec, 0x3d, 0x85, 0xcc, 0x58, 0xe2, 0x17, 0x47, 0xff, 0x9f, 0xa5, 0x30, 0x17}}},
+{{{0xe3, 0xae, 0xc8, 0xc1, 0x71, 0x75, 0x31, 0x00, 0x37, 0x41, 0x5c, 0x0e, 0x39, 0xda, 0x73, 0xa0, 0xc7, 0x97, 0x36, 0x6c, 0x5b, 0xf2, 0xee, 0x64, 0x0a, 0x3d, 0x89, 0x1e, 0x1d, 0x49, 0x8c, 0x37}} ,
+ {{0x4c, 0xe6, 0xb0, 0xc1, 0xa5, 0x2a, 0x82, 0x09, 0x08, 0xad, 0x79, 0x9c, 0x56, 0xf6, 0xf9, 0xc1, 0xd7, 0x7c, 0x39, 0x7f, 0x93, 0xca, 0x11, 0x55, 0xbf, 0x07, 0x1b, 0x82, 0x29, 0x69, 0x95, 0x5c}}},
+{{{0x87, 0xee, 0xa6, 0x56, 0x9e, 0xc2, 0x9a, 0x56, 0x24, 0x42, 0x85, 0x4d, 0x98, 0x31, 0x1e, 0x60, 0x4d, 0x87, 0x85, 0x04, 0xae, 0x46, 0x12, 0xf9, 0x8e, 0x7f, 0xe4, 0x7f, 0xf6, 0x1c, 0x37, 0x01}} ,
+ {{0x73, 0x4c, 0xb6, 0xc5, 0xc4, 0xe9, 0x6c, 0x85, 0x48, 0x4a, 0x5a, 0xac, 0xd9, 0x1f, 0x43, 0xf8, 0x62, 0x5b, 0xee, 0x98, 0x2a, 0x33, 0x8e, 0x79, 0xce, 0x61, 0x06, 0x35, 0xd8, 0xd7, 0xca, 0x71}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x72, 0xd3, 0xae, 0xa6, 0xca, 0x8f, 0xcd, 0xcc, 0x78, 0x8e, 0x19, 0x4d, 0xa7, 0xd2, 0x27, 0xe9, 0xa4, 0x3c, 0x16, 0x5b, 0x84, 0x80, 0xf9, 0xd0, 0xcc, 0x6a, 0x1e, 0xca, 0x1e, 0x67, 0xbd, 0x63}} ,
+ {{0x7b, 0x6e, 0x2a, 0xd2, 0x87, 0x48, 0xff, 0xa1, 0xca, 0xe9, 0x15, 0x85, 0xdc, 0xdb, 0x2c, 0x39, 0x12, 0x91, 0xa9, 0x20, 0xaa, 0x4f, 0x29, 0xf4, 0x15, 0x7a, 0xd2, 0xf5, 0x32, 0xcc, 0x60, 0x04}}},
+{{{0xe5, 0x10, 0x47, 0x3b, 0xfa, 0x90, 0xfc, 0x30, 0xb5, 0xea, 0x6f, 0x56, 0x8f, 0xfb, 0x0e, 0xa7, 0x3b, 0xc8, 0xb2, 0xff, 0x02, 0x7a, 0x33, 0x94, 0x93, 0x2a, 0x03, 0xe0, 0x96, 0x3a, 0x6c, 0x0f}} ,
+ {{0x5a, 0x63, 0x67, 0xe1, 0x9b, 0x47, 0x78, 0x9f, 0x38, 0x79, 0xac, 0x97, 0x66, 0x1d, 0x5e, 0x51, 0xee, 0x24, 0x42, 0xe8, 0x58, 0x4b, 0x8a, 0x03, 0x75, 0x86, 0x37, 0x86, 0xe2, 0x97, 0x4e, 0x3d}}},
+{{{0x3f, 0x75, 0x8e, 0xb4, 0xff, 0xd8, 0xdd, 0xd6, 0x37, 0x57, 0x9d, 0x6d, 0x3b, 0xbd, 0xd5, 0x60, 0x88, 0x65, 0x9a, 0xb9, 0x4a, 0x68, 0x84, 0xa2, 0x67, 0xdd, 0x17, 0x25, 0x97, 0x04, 0x8b, 0x5e}} ,
+ {{0xbb, 0x40, 0x5e, 0xbc, 0x16, 0x92, 0x05, 0xc4, 0xc0, 0x4e, 0x72, 0x90, 0x0e, 0xab, 0xcf, 0x8a, 0xed, 0xef, 0xb9, 0x2d, 0x3b, 0xf8, 0x43, 0x5b, 0xba, 0x2d, 0xeb, 0x2f, 0x52, 0xd2, 0xd1, 0x5a}}},
+{{{0x40, 0xb4, 0xab, 0xe6, 0xad, 0x9f, 0x46, 0x69, 0x4a, 0xb3, 0x8e, 0xaa, 0xea, 0x9c, 0x8a, 0x20, 0x16, 0x5d, 0x8c, 0x13, 0xbd, 0xf6, 0x1d, 0xc5, 0x24, 0xbd, 0x90, 0x2a, 0x1c, 0xc7, 0x13, 0x3b}} ,
+ {{0x54, 0xdc, 0x16, 0x0d, 0x18, 0xbe, 0x35, 0x64, 0x61, 0x52, 0x02, 0x80, 0xaf, 0x05, 0xf7, 0xa6, 0x42, 0xd3, 0x8f, 0x2e, 0x79, 0x26, 0xa8, 0xbb, 0xb2, 0x17, 0x48, 0xb2, 0x7a, 0x0a, 0x89, 0x14}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x20, 0xa8, 0x88, 0xe3, 0x91, 0xc0, 0x6e, 0xbb, 0x8a, 0x27, 0x82, 0x51, 0x83, 0xb2, 0x28, 0xa9, 0x83, 0xeb, 0xa6, 0xa9, 0x4d, 0x17, 0x59, 0x22, 0x54, 0x00, 0x50, 0x45, 0xcb, 0x48, 0x4b, 0x18}} ,
+ {{0x33, 0x7c, 0xe7, 0x26, 0xba, 0x4d, 0x32, 0xfe, 0x53, 0xf4, 0xfa, 0x83, 0xe3, 0xa5, 0x79, 0x66, 0x73, 0xef, 0x80, 0x23, 0x68, 0xc2, 0x60, 0xdd, 0xa9, 0x33, 0xdc, 0x03, 0x7a, 0xe0, 0xe0, 0x3e}}},
+{{{0x34, 0x5c, 0x13, 0xfb, 0xc0, 0xe3, 0x78, 0x2b, 0x54, 0x58, 0x22, 0x9b, 0x76, 0x81, 0x7f, 0x93, 0x9c, 0x25, 0x3c, 0xd2, 0xe9, 0x96, 0x21, 0x26, 0x08, 0xf5, 0xed, 0x95, 0x11, 0xae, 0x04, 0x5a}} ,
+ {{0xb9, 0xe8, 0xc5, 0x12, 0x97, 0x1f, 0x83, 0xfe, 0x3e, 0x94, 0x99, 0xd4, 0x2d, 0xf9, 0x52, 0x59, 0x5c, 0x82, 0xa6, 0xf0, 0x75, 0x7e, 0xe8, 0xec, 0xcc, 0xac, 0x18, 0x21, 0x09, 0x67, 0x66, 0x67}}},
+{{{0xb3, 0x40, 0x29, 0xd1, 0xcb, 0x1b, 0x08, 0x9e, 0x9c, 0xb7, 0x53, 0xb9, 0x3b, 0x71, 0x08, 0x95, 0x12, 0x1a, 0x58, 0xaf, 0x7e, 0x82, 0x52, 0x43, 0x4f, 0x11, 0x39, 0xf4, 0x93, 0x1a, 0x26, 0x05}} ,
+ {{0x6e, 0x44, 0xa3, 0xf9, 0x64, 0xaf, 0xe7, 0x6d, 0x7d, 0xdf, 0x1e, 0xac, 0x04, 0xea, 0x3b, 0x5f, 0x9b, 0xe8, 0x24, 0x9d, 0x0e, 0xe5, 0x2e, 0x3e, 0xdf, 0xa9, 0xf7, 0xd4, 0x50, 0x71, 0xf0, 0x78}}},
+{{{0x3e, 0xa8, 0x38, 0xc2, 0x57, 0x56, 0x42, 0x9a, 0xb1, 0xe2, 0xf8, 0x45, 0xaa, 0x11, 0x48, 0x5f, 0x17, 0xc4, 0x54, 0x27, 0xdc, 0x5d, 0xaa, 0xdd, 0x41, 0xbc, 0xdf, 0x81, 0xb9, 0x53, 0xee, 0x52}} ,
+ {{0xc3, 0xf1, 0xa7, 0x6d, 0xb3, 0x5f, 0x92, 0x6f, 0xcc, 0x91, 0xb8, 0x95, 0x05, 0xdf, 0x3c, 0x64, 0x57, 0x39, 0x61, 0x51, 0xad, 0x8c, 0x38, 0x7b, 0xc8, 0xde, 0x00, 0x34, 0xbe, 0xa1, 0xb0, 0x7e}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x25, 0x24, 0x1d, 0x8a, 0x67, 0x20, 0xee, 0x42, 0xeb, 0x38, 0xed, 0x0b, 0x8b, 0xcd, 0x46, 0x9d, 0x5e, 0x6b, 0x1e, 0x24, 0x9d, 0x12, 0x05, 0x1a, 0xcc, 0x05, 0x4e, 0x92, 0x38, 0xe1, 0x1f, 0x50}} ,
+ {{0x4e, 0xee, 0x1c, 0x91, 0xe6, 0x11, 0xbd, 0x8e, 0x55, 0x1a, 0x18, 0x75, 0x66, 0xaf, 0x4d, 0x7b, 0x0f, 0xae, 0x6d, 0x85, 0xca, 0x82, 0x58, 0x21, 0x9c, 0x18, 0xe0, 0xed, 0xec, 0x22, 0x80, 0x2f}}},
+{{{0x68, 0x3b, 0x0a, 0x39, 0x1d, 0x6a, 0x15, 0x57, 0xfc, 0xf0, 0x63, 0x54, 0xdb, 0x39, 0xdb, 0xe8, 0x5c, 0x64, 0xff, 0xa0, 0x09, 0x4f, 0x3b, 0xb7, 0x32, 0x60, 0x99, 0x94, 0xfd, 0x94, 0x82, 0x2d}} ,
+ {{0x24, 0xf6, 0x5a, 0x44, 0xf1, 0x55, 0x2c, 0xdb, 0xea, 0x7c, 0x84, 0x7c, 0x01, 0xac, 0xe3, 0xfd, 0xc9, 0x27, 0xc1, 0x5a, 0xb9, 0xde, 0x4f, 0x5a, 0x90, 0xdd, 0xc6, 0x67, 0xaa, 0x6f, 0x8a, 0x3a}}},
+{{{0x78, 0x52, 0x87, 0xc9, 0x97, 0x63, 0xb1, 0xdd, 0x54, 0x5f, 0xc1, 0xf8, 0xf1, 0x06, 0xa6, 0xa8, 0xa3, 0x88, 0x82, 0xd4, 0xcb, 0xa6, 0x19, 0xdd, 0xd1, 0x11, 0x87, 0x08, 0x17, 0x4c, 0x37, 0x2a}} ,
+ {{0xa1, 0x0c, 0xf3, 0x08, 0x43, 0xd9, 0x24, 0x1e, 0x83, 0xa7, 0xdf, 0x91, 0xca, 0xbd, 0x69, 0x47, 0x8d, 0x1b, 0xe2, 0xb9, 0x4e, 0xb5, 0xe1, 0x76, 0xb3, 0x1c, 0x93, 0x03, 0xce, 0x5f, 0xb3, 0x5a}}},
+{{{0x1d, 0xda, 0xe4, 0x61, 0x03, 0x50, 0xa9, 0x8b, 0x68, 0x18, 0xef, 0xb2, 0x1c, 0x84, 0x3b, 0xa2, 0x44, 0x95, 0xa3, 0x04, 0x3b, 0xd6, 0x99, 0x00, 0xaf, 0x76, 0x42, 0x67, 0x02, 0x7d, 0x85, 0x56}} ,
+ {{0xce, 0x72, 0x0e, 0x29, 0x84, 0xb2, 0x7d, 0xd2, 0x45, 0xbe, 0x57, 0x06, 0xed, 0x7f, 0xcf, 0xed, 0xcd, 0xef, 0x19, 0xd6, 0xbc, 0x15, 0x79, 0x64, 0xd2, 0x18, 0xe3, 0x20, 0x67, 0x3a, 0x54, 0x0b}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x52, 0xfd, 0x04, 0xc5, 0xfb, 0x99, 0xe7, 0xe8, 0xfb, 0x8c, 0xe1, 0x42, 0x03, 0xef, 0x9d, 0xd9, 0x9e, 0x4d, 0xf7, 0x80, 0xcf, 0x2e, 0xcc, 0x9b, 0x45, 0xc9, 0x7b, 0x7a, 0xbc, 0x37, 0xa8, 0x52}} ,
+ {{0x96, 0x11, 0x41, 0x8a, 0x47, 0x91, 0xfe, 0xb6, 0xda, 0x7a, 0x54, 0x63, 0xd1, 0x14, 0x35, 0x05, 0x86, 0x8c, 0xa9, 0x36, 0x3f, 0xf2, 0x85, 0x54, 0x4e, 0x92, 0xd8, 0x85, 0x01, 0x46, 0xd6, 0x50}}},
+{{{0x53, 0xcd, 0xf3, 0x86, 0x40, 0xe6, 0x39, 0x42, 0x95, 0xd6, 0xcb, 0x45, 0x1a, 0x20, 0xc8, 0x45, 0x4b, 0x32, 0x69, 0x04, 0xb1, 0xaf, 0x20, 0x46, 0xc7, 0x6b, 0x23, 0x5b, 0x69, 0xee, 0x30, 0x3f}} ,
+ {{0x70, 0x83, 0x47, 0xc0, 0xdb, 0x55, 0x08, 0xa8, 0x7b, 0x18, 0x6d, 0xf5, 0x04, 0x5a, 0x20, 0x0c, 0x4a, 0x8c, 0x60, 0xae, 0xae, 0x0f, 0x64, 0x55, 0x55, 0x2e, 0xd5, 0x1d, 0x53, 0x31, 0x42, 0x41}}},
+{{{0xca, 0xfc, 0x88, 0x6b, 0x96, 0x78, 0x0a, 0x8b, 0x83, 0xdc, 0xbc, 0xaf, 0x40, 0xb6, 0x8d, 0x7f, 0xef, 0xb4, 0xd1, 0x3f, 0xcc, 0xa2, 0x74, 0xc9, 0xc2, 0x92, 0x55, 0x00, 0xab, 0xdb, 0xbf, 0x4f}} ,
+ {{0x93, 0x1c, 0x06, 0x2d, 0x66, 0x65, 0x02, 0xa4, 0x97, 0x18, 0xfd, 0x00, 0xe7, 0xab, 0x03, 0xec, 0xce, 0xc1, 0xbf, 0x37, 0xf8, 0x13, 0x53, 0xa5, 0xe5, 0x0c, 0x3a, 0xa8, 0x55, 0xb9, 0xff, 0x68}}},
+{{{0xe4, 0xe6, 0x6d, 0x30, 0x7d, 0x30, 0x35, 0xc2, 0x78, 0x87, 0xf9, 0xfc, 0x6b, 0x5a, 0xc3, 0xb7, 0x65, 0xd8, 0x2e, 0xc7, 0xa5, 0x0c, 0xc6, 0xdc, 0x12, 0xaa, 0xd6, 0x4f, 0xc5, 0x38, 0xbc, 0x0e}} ,
+ {{0xe2, 0x3c, 0x76, 0x86, 0x38, 0xf2, 0x7b, 0x2c, 0x16, 0x78, 0x8d, 0xf5, 0xa4, 0x15, 0xda, 0xdb, 0x26, 0x85, 0xa0, 0x56, 0xdd, 0x1d, 0xe3, 0xb3, 0xfd, 0x40, 0xef, 0xf2, 0xd9, 0xa1, 0xb3, 0x04}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xdb, 0x49, 0x0e, 0xe6, 0x58, 0x10, 0x7a, 0x52, 0xda, 0xb5, 0x7d, 0x37, 0x6a, 0x3e, 0xa1, 0x78, 0xce, 0xc7, 0x1c, 0x24, 0x23, 0xdb, 0x7d, 0xfb, 0x8c, 0x8d, 0xdc, 0x30, 0x67, 0x69, 0x75, 0x3b}} ,
+ {{0xa9, 0xea, 0x6d, 0x16, 0x16, 0x60, 0xf4, 0x60, 0x87, 0x19, 0x44, 0x8c, 0x4a, 0x8b, 0x3e, 0xfb, 0x16, 0x00, 0x00, 0x54, 0xa6, 0x9e, 0x9f, 0xef, 0xcf, 0xd9, 0xd2, 0x4c, 0x74, 0x31, 0xd0, 0x34}}},
+{{{0xa4, 0xeb, 0x04, 0xa4, 0x8c, 0x8f, 0x71, 0x27, 0x95, 0x85, 0x5d, 0x55, 0x4b, 0xb1, 0x26, 0x26, 0xc8, 0xae, 0x6a, 0x7d, 0xa2, 0x21, 0xca, 0xce, 0x38, 0xab, 0x0f, 0xd0, 0xd5, 0x2b, 0x6b, 0x00}} ,
+ {{0xe5, 0x67, 0x0c, 0xf1, 0x3a, 0x9a, 0xea, 0x09, 0x39, 0xef, 0xd1, 0x30, 0xbc, 0x33, 0xba, 0xb1, 0x6a, 0xc5, 0x27, 0x08, 0x7f, 0x54, 0x80, 0x3d, 0xab, 0xf6, 0x15, 0x7a, 0xc2, 0x40, 0x73, 0x72}}},
+{{{0x84, 0x56, 0x82, 0xb6, 0x12, 0x70, 0x7f, 0xf7, 0xf0, 0xbd, 0x5b, 0xa9, 0xd5, 0xc5, 0x5f, 0x59, 0xbf, 0x7f, 0xb3, 0x55, 0x22, 0x02, 0xc9, 0x44, 0x55, 0x87, 0x8f, 0x96, 0x98, 0x64, 0x6d, 0x15}} ,
+ {{0xb0, 0x8b, 0xaa, 0x1e, 0xec, 0xc7, 0xa5, 0x8f, 0x1f, 0x92, 0x04, 0xc6, 0x05, 0xf6, 0xdf, 0xa1, 0xcc, 0x1f, 0x81, 0xf5, 0x0e, 0x9c, 0x57, 0xdc, 0xe3, 0xbb, 0x06, 0x87, 0x1e, 0xfe, 0x23, 0x6c}}},
+{{{0xd8, 0x2b, 0x5b, 0x16, 0xea, 0x20, 0xf1, 0xd3, 0x68, 0x8f, 0xae, 0x5b, 0xd0, 0xa9, 0x1a, 0x19, 0xa8, 0x36, 0xfb, 0x2b, 0x57, 0x88, 0x7d, 0x90, 0xd5, 0xa6, 0xf3, 0xdc, 0x38, 0x89, 0x4e, 0x1f}} ,
+ {{0xcc, 0x19, 0xda, 0x9b, 0x3b, 0x43, 0x48, 0x21, 0x2e, 0x23, 0x4d, 0x3d, 0xae, 0xf8, 0x8c, 0xfc, 0xdd, 0xa6, 0x74, 0x37, 0x65, 0xca, 0xee, 0x1a, 0x19, 0x8e, 0x9f, 0x64, 0x6f, 0x0c, 0x8b, 0x5a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x25, 0xb9, 0xc2, 0xf0, 0x72, 0xb8, 0x15, 0x16, 0xcc, 0x8d, 0x3c, 0x6f, 0x25, 0xed, 0xf4, 0x46, 0x2e, 0x0c, 0x60, 0x0f, 0xe2, 0x84, 0x34, 0x55, 0x89, 0x59, 0x34, 0x1b, 0xf5, 0x8d, 0xfe, 0x08}} ,
+ {{0xf8, 0xab, 0x93, 0xbc, 0x44, 0xba, 0x1b, 0x75, 0x4b, 0x49, 0x6f, 0xd0, 0x54, 0x2e, 0x63, 0xba, 0xb5, 0xea, 0xed, 0x32, 0x14, 0xc9, 0x94, 0xd8, 0xc5, 0xce, 0xf4, 0x10, 0x68, 0xe0, 0x38, 0x27}}},
+{{{0x74, 0x1c, 0x14, 0x9b, 0xd4, 0x64, 0x61, 0x71, 0x5a, 0xb6, 0x21, 0x33, 0x4f, 0xf7, 0x8e, 0xba, 0xa5, 0x48, 0x9a, 0xc7, 0xfa, 0x9a, 0xf0, 0xb4, 0x62, 0xad, 0xf2, 0x5e, 0xcc, 0x03, 0x24, 0x1a}} ,
+ {{0xf5, 0x76, 0xfd, 0xe4, 0xaf, 0xb9, 0x03, 0x59, 0xce, 0x63, 0xd2, 0x3b, 0x1f, 0xcd, 0x21, 0x0c, 0xad, 0x44, 0xa5, 0x97, 0xac, 0x80, 0x11, 0x02, 0x9b, 0x0c, 0xe5, 0x8b, 0xcd, 0xfb, 0x79, 0x77}}},
+{{{0x15, 0xbe, 0x9a, 0x0d, 0xba, 0x38, 0x72, 0x20, 0x8a, 0xf5, 0xbe, 0x59, 0x93, 0x79, 0xb7, 0xf6, 0x6a, 0x0c, 0x38, 0x27, 0x1a, 0x60, 0xf4, 0x86, 0x3b, 0xab, 0x5a, 0x00, 0xa0, 0xce, 0x21, 0x7d}} ,
+ {{0x6c, 0xba, 0x14, 0xc5, 0xea, 0x12, 0x9e, 0x2e, 0x82, 0x63, 0xce, 0x9b, 0x4a, 0xe7, 0x1d, 0xec, 0xf1, 0x2e, 0x51, 0x1c, 0xf4, 0xd0, 0x69, 0x15, 0x42, 0x9d, 0xa3, 0x3f, 0x0e, 0xbf, 0xe9, 0x5c}}},
+{{{0xe4, 0x0d, 0xf4, 0xbd, 0xee, 0x31, 0x10, 0xed, 0xcb, 0x12, 0x86, 0xad, 0xd4, 0x2f, 0x90, 0x37, 0x32, 0xc3, 0x0b, 0x73, 0xec, 0x97, 0x85, 0xa4, 0x01, 0x1c, 0x76, 0x35, 0xfe, 0x75, 0xdd, 0x71}} ,
+ {{0x11, 0xa4, 0x88, 0x9f, 0x3e, 0x53, 0x69, 0x3b, 0x1b, 0xe0, 0xf7, 0xba, 0x9b, 0xad, 0x4e, 0x81, 0x5f, 0xb5, 0x5c, 0xae, 0xbe, 0x67, 0x86, 0x37, 0x34, 0x8e, 0x07, 0x32, 0x45, 0x4a, 0x67, 0x39}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x90, 0x70, 0x58, 0x20, 0x03, 0x1e, 0x67, 0xb2, 0xc8, 0x9b, 0x58, 0xc5, 0xb1, 0xeb, 0x2d, 0x4a, 0xde, 0x82, 0x8c, 0xf2, 0xd2, 0x14, 0xb8, 0x70, 0x61, 0x4e, 0x73, 0xd6, 0x0b, 0x6b, 0x0d, 0x30}} ,
+ {{0x81, 0xfc, 0x55, 0x5c, 0xbf, 0xa7, 0xc4, 0xbd, 0xe2, 0xf0, 0x4b, 0x8f, 0xe9, 0x7d, 0x99, 0xfa, 0xd3, 0xab, 0xbc, 0xc7, 0x83, 0x2b, 0x04, 0x7f, 0x0c, 0x19, 0x43, 0x03, 0x3d, 0x07, 0xca, 0x40}}},
+{{{0xf9, 0xc8, 0xbe, 0x8c, 0x16, 0x81, 0x39, 0x96, 0xf6, 0x17, 0x58, 0xc8, 0x30, 0x58, 0xfb, 0xc2, 0x03, 0x45, 0xd2, 0x52, 0x76, 0xe0, 0x6a, 0x26, 0x28, 0x5c, 0x88, 0x59, 0x6a, 0x5a, 0x54, 0x42}} ,
+ {{0x07, 0xb5, 0x2e, 0x2c, 0x67, 0x15, 0x9b, 0xfb, 0x83, 0x69, 0x1e, 0x0f, 0xda, 0xd6, 0x29, 0xb1, 0x60, 0xe0, 0xb2, 0xba, 0x69, 0xa2, 0x9e, 0xbd, 0xbd, 0xe0, 0x1c, 0xbd, 0xcd, 0x06, 0x64, 0x70}}},
+{{{0x41, 0xfa, 0x8c, 0xe1, 0x89, 0x8f, 0x27, 0xc8, 0x25, 0x8f, 0x6f, 0x5f, 0x55, 0xf8, 0xde, 0x95, 0x6d, 0x2f, 0x75, 0x16, 0x2b, 0x4e, 0x44, 0xfd, 0x86, 0x6e, 0xe9, 0x70, 0x39, 0x76, 0x97, 0x7e}} ,
+ {{0x17, 0x62, 0x6b, 0x14, 0xa1, 0x7c, 0xd0, 0x79, 0x6e, 0xd8, 0x8a, 0xa5, 0x6d, 0x8c, 0x93, 0xd2, 0x3f, 0xec, 0x44, 0x8d, 0x6e, 0x91, 0x01, 0x8c, 0x8f, 0xee, 0x01, 0x8f, 0xc0, 0xb4, 0x85, 0x0e}}},
+{{{0x02, 0x3a, 0x70, 0x41, 0xe4, 0x11, 0x57, 0x23, 0xac, 0xe6, 0xfc, 0x54, 0x7e, 0xcd, 0xd7, 0x22, 0xcb, 0x76, 0x9f, 0x20, 0xce, 0xa0, 0x73, 0x76, 0x51, 0x3b, 0xa4, 0xf8, 0xe3, 0x62, 0x12, 0x6c}} ,
+ {{0x7f, 0x00, 0x9c, 0x26, 0x0d, 0x6f, 0x48, 0x7f, 0x3a, 0x01, 0xed, 0xc5, 0x96, 0xb0, 0x1f, 0x4f, 0xa8, 0x02, 0x62, 0x27, 0x8a, 0x50, 0x8d, 0x9a, 0x8b, 0x52, 0x0f, 0x1e, 0xcf, 0x41, 0x38, 0x19}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xf5, 0x6c, 0xd4, 0x2f, 0x0f, 0x69, 0x0f, 0x87, 0x3f, 0x61, 0x65, 0x1e, 0x35, 0x34, 0x85, 0xba, 0x02, 0x30, 0xac, 0x25, 0x3d, 0xe2, 0x62, 0xf1, 0xcc, 0xe9, 0x1b, 0xc2, 0xef, 0x6a, 0x42, 0x57}} ,
+ {{0x34, 0x1f, 0x2e, 0xac, 0xd1, 0xc7, 0x04, 0x52, 0x32, 0x66, 0xb2, 0x33, 0x73, 0x21, 0x34, 0x54, 0xf7, 0x71, 0xed, 0x06, 0xb0, 0xff, 0xa6, 0x59, 0x6f, 0x8a, 0x4e, 0xfb, 0x02, 0xb0, 0x45, 0x6b}}},
+{{{0xf5, 0x48, 0x0b, 0x03, 0xc5, 0x22, 0x7d, 0x80, 0x08, 0x53, 0xfe, 0x32, 0xb1, 0xa1, 0x8a, 0x74, 0x6f, 0xbd, 0x3f, 0x85, 0xf4, 0xcf, 0xf5, 0x60, 0xaf, 0x41, 0x7e, 0x3e, 0x46, 0xa3, 0x5a, 0x20}} ,
+ {{0xaa, 0x35, 0x87, 0x44, 0x63, 0x66, 0x97, 0xf8, 0x6e, 0x55, 0x0c, 0x04, 0x3e, 0x35, 0x50, 0xbf, 0x93, 0x69, 0xd2, 0x8b, 0x05, 0x55, 0x99, 0xbe, 0xe2, 0x53, 0x61, 0xec, 0xe8, 0x08, 0x0b, 0x32}}},
+{{{0xb3, 0x10, 0x45, 0x02, 0x69, 0x59, 0x2e, 0x97, 0xd9, 0x64, 0xf8, 0xdb, 0x25, 0x80, 0xdc, 0xc4, 0xd5, 0x62, 0x3c, 0xed, 0x65, 0x91, 0xad, 0xd1, 0x57, 0x81, 0x94, 0xaa, 0xa1, 0x29, 0xfc, 0x68}} ,
+ {{0xdd, 0xb5, 0x7d, 0xab, 0x5a, 0x21, 0x41, 0x53, 0xbb, 0x17, 0x79, 0x0d, 0xd1, 0xa8, 0x0c, 0x0c, 0x20, 0x88, 0x09, 0xe9, 0x84, 0xe8, 0x25, 0x11, 0x67, 0x7a, 0x8b, 0x1a, 0xe4, 0x5d, 0xe1, 0x5d}}},
+{{{0x37, 0xea, 0xfe, 0x65, 0x3b, 0x25, 0xe8, 0xe1, 0xc2, 0xc5, 0x02, 0xa4, 0xbe, 0x98, 0x0a, 0x2b, 0x61, 0xc1, 0x9b, 0xe2, 0xd5, 0x92, 0xe6, 0x9e, 0x7d, 0x1f, 0xca, 0x43, 0x88, 0x8b, 0x2c, 0x59}} ,
+ {{0xe0, 0xb5, 0x00, 0x1d, 0x2a, 0x6f, 0xaf, 0x79, 0x86, 0x2f, 0xa6, 0x5a, 0x93, 0xd1, 0xfe, 0xae, 0x3a, 0xee, 0xdb, 0x7c, 0x61, 0xbe, 0x7c, 0x01, 0xf9, 0xfe, 0x52, 0xdc, 0xd8, 0x52, 0xa3, 0x42}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x22, 0xaf, 0x13, 0x37, 0xbd, 0x37, 0x71, 0xac, 0x04, 0x46, 0x63, 0xac, 0xa4, 0x77, 0xed, 0x25, 0x38, 0xe0, 0x15, 0xa8, 0x64, 0x00, 0x0d, 0xce, 0x51, 0x01, 0xa9, 0xbc, 0x0f, 0x03, 0x1c, 0x04}} ,
+ {{0x89, 0xf9, 0x80, 0x07, 0xcf, 0x3f, 0xb3, 0xe9, 0xe7, 0x45, 0x44, 0x3d, 0x2a, 0x7c, 0xe9, 0xe4, 0x16, 0x5c, 0x5e, 0x65, 0x1c, 0xc7, 0x7d, 0xc6, 0x7a, 0xfb, 0x43, 0xee, 0x25, 0x76, 0x46, 0x72}}},
+{{{0x02, 0xa2, 0xed, 0xf4, 0x8f, 0x6b, 0x0b, 0x3e, 0xeb, 0x35, 0x1a, 0xd5, 0x7e, 0xdb, 0x78, 0x00, 0x96, 0x8a, 0xa0, 0xb4, 0xcf, 0x60, 0x4b, 0xd4, 0xd5, 0xf9, 0x2d, 0xbf, 0x88, 0xbd, 0x22, 0x62}} ,
+ {{0x13, 0x53, 0xe4, 0x82, 0x57, 0xfa, 0x1e, 0x8f, 0x06, 0x2b, 0x90, 0xba, 0x08, 0xb6, 0x10, 0x54, 0x4f, 0x7c, 0x1b, 0x26, 0xed, 0xda, 0x6b, 0xdd, 0x25, 0xd0, 0x4e, 0xea, 0x42, 0xbb, 0x25, 0x03}}},
+{{{0x51, 0x16, 0x50, 0x7c, 0xd5, 0x5d, 0xf6, 0x99, 0xe8, 0x77, 0x72, 0x4e, 0xfa, 0x62, 0xcb, 0x76, 0x75, 0x0c, 0xe2, 0x71, 0x98, 0x92, 0xd5, 0xfa, 0x45, 0xdf, 0x5c, 0x6f, 0x1e, 0x9e, 0x28, 0x69}} ,
+ {{0x0d, 0xac, 0x66, 0x6d, 0xc3, 0x8b, 0xba, 0x16, 0xb5, 0xe2, 0xa0, 0x0d, 0x0c, 0xbd, 0xa4, 0x8e, 0x18, 0x6c, 0xf2, 0xdc, 0xf9, 0xdc, 0x4a, 0x86, 0x25, 0x95, 0x14, 0xcb, 0xd8, 0x1a, 0x04, 0x0f}}},
+{{{0x97, 0xa5, 0xdb, 0x8b, 0x2d, 0xaa, 0x42, 0x11, 0x09, 0xf2, 0x93, 0xbb, 0xd9, 0x06, 0x84, 0x4e, 0x11, 0xa8, 0xa0, 0x25, 0x2b, 0xa6, 0x5f, 0xae, 0xc4, 0xb4, 0x4c, 0xc8, 0xab, 0xc7, 0x3b, 0x02}} ,
+ {{0xee, 0xc9, 0x29, 0x0f, 0xdf, 0x11, 0x85, 0xed, 0xce, 0x0d, 0x62, 0x2c, 0x8f, 0x4b, 0xf9, 0x04, 0xe9, 0x06, 0x72, 0x1d, 0x37, 0x20, 0x50, 0xc9, 0x14, 0xeb, 0xec, 0x39, 0xa7, 0x97, 0x2b, 0x4d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x69, 0xd1, 0x39, 0xbd, 0xfb, 0x33, 0xbe, 0xc4, 0xf0, 0x5c, 0xef, 0xf0, 0x56, 0x68, 0xfc, 0x97, 0x47, 0xc8, 0x72, 0xb6, 0x53, 0xa4, 0x0a, 0x98, 0xa5, 0xb4, 0x37, 0x71, 0xcf, 0x66, 0x50, 0x6d}} ,
+ {{0x17, 0xa4, 0x19, 0x52, 0x11, 0x47, 0xb3, 0x5c, 0x5b, 0xa9, 0x2e, 0x22, 0xb4, 0x00, 0x52, 0xf9, 0x57, 0x18, 0xb8, 0xbe, 0x5a, 0xe3, 0xab, 0x83, 0xc8, 0x87, 0x0a, 0x2a, 0xd8, 0x8c, 0xbb, 0x54}}},
+{{{0xa9, 0x62, 0x93, 0x85, 0xbe, 0xe8, 0x73, 0x4a, 0x0e, 0xb0, 0xb5, 0x2d, 0x94, 0x50, 0xaa, 0xd3, 0xb2, 0xea, 0x9d, 0x62, 0x76, 0x3b, 0x07, 0x34, 0x4e, 0x2d, 0x70, 0xc8, 0x9a, 0x15, 0x66, 0x6b}} ,
+ {{0xc5, 0x96, 0xca, 0xc8, 0x22, 0x1a, 0xee, 0x5f, 0xe7, 0x31, 0x60, 0x22, 0x83, 0x08, 0x63, 0xce, 0xb9, 0x32, 0x44, 0x58, 0x5d, 0x3a, 0x9b, 0xe4, 0x04, 0xd5, 0xef, 0x38, 0xef, 0x4b, 0xdd, 0x19}}},
+{{{0x4d, 0xc2, 0x17, 0x75, 0xa1, 0x68, 0xcd, 0xc3, 0xc6, 0x03, 0x44, 0xe3, 0x78, 0x09, 0x91, 0x47, 0x3f, 0x0f, 0xe4, 0x92, 0x58, 0xfa, 0x7d, 0x1f, 0x20, 0x94, 0x58, 0x5e, 0xbc, 0x19, 0x02, 0x6f}} ,
+ {{0x20, 0xd6, 0xd8, 0x91, 0x54, 0xa7, 0xf3, 0x20, 0x4b, 0x34, 0x06, 0xfa, 0x30, 0xc8, 0x6f, 0x14, 0x10, 0x65, 0x74, 0x13, 0x4e, 0xf0, 0x69, 0x26, 0xce, 0xcf, 0x90, 0xf4, 0xd0, 0xc5, 0xc8, 0x64}}},
+{{{0x26, 0xa2, 0x50, 0x02, 0x24, 0x72, 0xf1, 0xf0, 0x4e, 0x2d, 0x93, 0xd5, 0x08, 0xe7, 0xae, 0x38, 0xf7, 0x18, 0xa5, 0x32, 0x34, 0xc2, 0xf0, 0xa6, 0xec, 0xb9, 0x61, 0x7b, 0x64, 0x99, 0xac, 0x71}} ,
+ {{0x25, 0xcf, 0x74, 0x55, 0x1b, 0xaa, 0xa9, 0x38, 0x41, 0x40, 0xd5, 0x95, 0x95, 0xab, 0x1c, 0x5e, 0xbc, 0x41, 0x7e, 0x14, 0x30, 0xbe, 0x13, 0x89, 0xf4, 0xe5, 0xeb, 0x28, 0xc0, 0xc2, 0x96, 0x3a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x2b, 0x77, 0x45, 0xec, 0x67, 0x76, 0x32, 0x4c, 0xb9, 0xdf, 0x25, 0x32, 0x6b, 0xcb, 0xe7, 0x14, 0x61, 0x43, 0xee, 0xba, 0x9b, 0x71, 0xef, 0xd2, 0x48, 0x65, 0xbb, 0x1b, 0x8a, 0x13, 0x1b, 0x22}} ,
+ {{0x84, 0xad, 0x0c, 0x18, 0x38, 0x5a, 0xba, 0xd0, 0x98, 0x59, 0xbf, 0x37, 0xb0, 0x4f, 0x97, 0x60, 0x20, 0xb3, 0x9b, 0x97, 0xf6, 0x08, 0x6c, 0xa4, 0xff, 0xfb, 0xb7, 0xfa, 0x95, 0xb2, 0x51, 0x79}}},
+{{{0x28, 0x5c, 0x3f, 0xdb, 0x6b, 0x18, 0x3b, 0x5c, 0xd1, 0x04, 0x28, 0xde, 0x85, 0x52, 0x31, 0xb5, 0xbb, 0xf6, 0xa9, 0xed, 0xbe, 0x28, 0x4f, 0xb3, 0x7e, 0x05, 0x6a, 0xdb, 0x95, 0x0d, 0x1b, 0x1c}} ,
+ {{0xd5, 0xc5, 0xc3, 0x9a, 0x0a, 0xd0, 0x31, 0x3e, 0x07, 0x36, 0x8e, 0xc0, 0x8a, 0x62, 0xb1, 0xca, 0xd6, 0x0e, 0x1e, 0x9d, 0xef, 0xab, 0x98, 0x4d, 0xbb, 0x6c, 0x05, 0xe0, 0xe4, 0x5d, 0xbd, 0x57}}},
+{{{0xcc, 0x21, 0x27, 0xce, 0xfd, 0xa9, 0x94, 0x8e, 0xe1, 0xab, 0x49, 0xe0, 0x46, 0x26, 0xa1, 0xa8, 0x8c, 0xa1, 0x99, 0x1d, 0xb4, 0x27, 0x6d, 0x2d, 0xc8, 0x39, 0x30, 0x5e, 0x37, 0x52, 0xc4, 0x6e}} ,
+ {{0xa9, 0x85, 0xf4, 0xe7, 0xb0, 0x15, 0x33, 0x84, 0x1b, 0x14, 0x1a, 0x02, 0xd9, 0x3b, 0xad, 0x0f, 0x43, 0x6c, 0xea, 0x3e, 0x0f, 0x7e, 0xda, 0xdd, 0x6b, 0x4c, 0x7f, 0x6e, 0xd4, 0x6b, 0xbf, 0x0f}}},
+{{{0x47, 0x9f, 0x7c, 0x56, 0x7c, 0x43, 0x91, 0x1c, 0xbb, 0x4e, 0x72, 0x3e, 0x64, 0xab, 0xa0, 0xa0, 0xdf, 0xb4, 0xd8, 0x87, 0x3a, 0xbd, 0xa8, 0x48, 0xc9, 0xb8, 0xef, 0x2e, 0xad, 0x6f, 0x84, 0x4f}} ,
+ {{0x2d, 0x2d, 0xf0, 0x1b, 0x7e, 0x2a, 0x6c, 0xf8, 0xa9, 0x6a, 0xe1, 0xf0, 0x99, 0xa1, 0x67, 0x9a, 0xd4, 0x13, 0xca, 0xca, 0xba, 0x27, 0x92, 0xaa, 0xa1, 0x5d, 0x50, 0xde, 0xcc, 0x40, 0x26, 0x0a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x9f, 0x3e, 0xf2, 0xb2, 0x90, 0xce, 0xdb, 0x64, 0x3e, 0x03, 0xdd, 0x37, 0x36, 0x54, 0x70, 0x76, 0x24, 0xb5, 0x69, 0x03, 0xfc, 0xa0, 0x2b, 0x74, 0xb2, 0x05, 0x0e, 0xcc, 0xd8, 0x1f, 0x6a, 0x1f}} ,
+ {{0x19, 0x5e, 0x60, 0x69, 0x58, 0x86, 0xa0, 0x31, 0xbd, 0x32, 0xe9, 0x2c, 0x5c, 0xd2, 0x85, 0xba, 0x40, 0x64, 0xa8, 0x74, 0xf8, 0x0e, 0x1c, 0xb3, 0xa9, 0x69, 0xe8, 0x1e, 0x40, 0x64, 0x99, 0x77}}},
+{{{0x6c, 0x32, 0x4f, 0xfd, 0xbb, 0x5c, 0xbb, 0x8d, 0x64, 0x66, 0x4a, 0x71, 0x1f, 0x79, 0xa3, 0xad, 0x8d, 0xf9, 0xd4, 0xec, 0xcf, 0x67, 0x70, 0xfa, 0x05, 0x4a, 0x0f, 0x6e, 0xaf, 0x87, 0x0a, 0x6f}} ,
+ {{0xc6, 0x36, 0x6e, 0x6c, 0x8c, 0x24, 0x09, 0x60, 0xbe, 0x26, 0xd2, 0x4c, 0x5e, 0x17, 0xca, 0x5f, 0x1d, 0xcc, 0x87, 0xe8, 0x42, 0x6a, 0xcb, 0xcb, 0x7d, 0x92, 0x05, 0x35, 0x81, 0x13, 0x60, 0x6b}}},
+{{{0xf4, 0x15, 0xcd, 0x0f, 0x0a, 0xaf, 0x4e, 0x6b, 0x51, 0xfd, 0x14, 0xc4, 0x2e, 0x13, 0x86, 0x74, 0x44, 0xcb, 0x66, 0x6b, 0xb6, 0x9d, 0x74, 0x56, 0x32, 0xac, 0x8d, 0x8e, 0x8c, 0x8c, 0x8c, 0x39}} ,
+ {{0xca, 0x59, 0x74, 0x1a, 0x11, 0xef, 0x6d, 0xf7, 0x39, 0x5c, 0x3b, 0x1f, 0xfa, 0xe3, 0x40, 0x41, 0x23, 0x9e, 0xf6, 0xd1, 0x21, 0xa2, 0xbf, 0xad, 0x65, 0x42, 0x6b, 0x59, 0x8a, 0xe8, 0xc5, 0x7f}}},
+{{{0x64, 0x05, 0x7a, 0x84, 0x4a, 0x13, 0xc3, 0xf6, 0xb0, 0x6e, 0x9a, 0x6b, 0x53, 0x6b, 0x32, 0xda, 0xd9, 0x74, 0x75, 0xc4, 0xba, 0x64, 0x3d, 0x3b, 0x08, 0xdd, 0x10, 0x46, 0xef, 0xc7, 0x90, 0x1f}} ,
+ {{0x7b, 0x2f, 0x3a, 0xce, 0xc8, 0xa1, 0x79, 0x3c, 0x30, 0x12, 0x44, 0x28, 0xf6, 0xbc, 0xff, 0xfd, 0xf4, 0xc0, 0x97, 0xb0, 0xcc, 0xc3, 0x13, 0x7a, 0xb9, 0x9a, 0x16, 0xe4, 0xcb, 0x4c, 0x34, 0x63}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x07, 0x4e, 0xd3, 0x2d, 0x09, 0x33, 0x0e, 0xd2, 0x0d, 0xbe, 0x3e, 0xe7, 0xe4, 0xaa, 0xb7, 0x00, 0x8b, 0xe8, 0xad, 0xaa, 0x7a, 0x8d, 0x34, 0x28, 0xa9, 0x81, 0x94, 0xc5, 0xe7, 0x42, 0xac, 0x47}} ,
+ {{0x24, 0x89, 0x7a, 0x8f, 0xb5, 0x9b, 0xf0, 0xc2, 0x03, 0x64, 0xd0, 0x1e, 0xf5, 0xa4, 0xb2, 0xf3, 0x74, 0xe9, 0x1a, 0x16, 0xfd, 0xcb, 0x15, 0xea, 0xeb, 0x10, 0x6c, 0x35, 0xd1, 0xc1, 0xa6, 0x28}}},
+{{{0xcc, 0xd5, 0x39, 0xfc, 0xa5, 0xa4, 0xad, 0x32, 0x15, 0xce, 0x19, 0xe8, 0x34, 0x2b, 0x1c, 0x60, 0x91, 0xfc, 0x05, 0xa9, 0xb3, 0xdc, 0x80, 0x29, 0xc4, 0x20, 0x79, 0x06, 0x39, 0xc0, 0xe2, 0x22}} ,
+ {{0xbb, 0xa8, 0xe1, 0x89, 0x70, 0x57, 0x18, 0x54, 0x3c, 0xf6, 0x0d, 0x82, 0x12, 0x05, 0x87, 0x96, 0x06, 0x39, 0xe3, 0xf8, 0xb3, 0x95, 0xe5, 0xd7, 0x26, 0xbf, 0x09, 0x5a, 0x94, 0xf9, 0x1c, 0x63}}},
+{{{0x2b, 0x8c, 0x2d, 0x9a, 0x8b, 0x84, 0xf2, 0x56, 0xfb, 0xad, 0x2e, 0x7f, 0xb7, 0xfc, 0x30, 0xe1, 0x35, 0x89, 0xba, 0x4d, 0xa8, 0x6d, 0xce, 0x8c, 0x8b, 0x30, 0xe0, 0xda, 0x29, 0x18, 0x11, 0x17}} ,
+ {{0x19, 0xa6, 0x5a, 0x65, 0x93, 0xc3, 0xb5, 0x31, 0x22, 0x4f, 0xf3, 0xf6, 0x0f, 0xeb, 0x28, 0xc3, 0x7c, 0xeb, 0xce, 0x86, 0xec, 0x67, 0x76, 0x6e, 0x35, 0x45, 0x7b, 0xd8, 0x6b, 0x92, 0x01, 0x65}}},
+{{{0x3d, 0xd5, 0x9a, 0x64, 0x73, 0x36, 0xb1, 0xd6, 0x86, 0x98, 0x42, 0x3f, 0x8a, 0xf1, 0xc7, 0xf5, 0x42, 0xa8, 0x9c, 0x52, 0xa8, 0xdc, 0xf9, 0x24, 0x3f, 0x4a, 0xa1, 0xa4, 0x5b, 0xe8, 0x62, 0x1a}} ,
+ {{0xc5, 0xbd, 0xc8, 0x14, 0xd5, 0x0d, 0xeb, 0xe1, 0xa5, 0xe6, 0x83, 0x11, 0x09, 0x00, 0x1d, 0x55, 0x83, 0x51, 0x7e, 0x75, 0x00, 0x81, 0xb9, 0xcb, 0xd8, 0xc5, 0xe5, 0xa1, 0xd9, 0x17, 0x6d, 0x1f}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xea, 0xf9, 0xe4, 0xe9, 0xe1, 0x52, 0x3f, 0x51, 0x19, 0x0d, 0xdd, 0xd9, 0x9d, 0x93, 0x31, 0x87, 0x23, 0x09, 0xd5, 0x83, 0xeb, 0x92, 0x09, 0x76, 0x6e, 0xe3, 0xf8, 0xc0, 0xa2, 0x66, 0xb5, 0x36}} ,
+ {{0x3a, 0xbb, 0x39, 0xed, 0x32, 0x02, 0xe7, 0x43, 0x7a, 0x38, 0x14, 0x84, 0xe3, 0x44, 0xd2, 0x5e, 0x94, 0xdd, 0x78, 0x89, 0x55, 0x4c, 0x73, 0x9e, 0xe1, 0xe4, 0x3e, 0x43, 0xd0, 0x4a, 0xde, 0x1b}}},
+{{{0xb2, 0xe7, 0x8f, 0xe3, 0xa3, 0xc5, 0xcb, 0x72, 0xee, 0x79, 0x41, 0xf8, 0xdf, 0xee, 0x65, 0xc5, 0x45, 0x77, 0x27, 0x3c, 0xbd, 0x58, 0xd3, 0x75, 0xe2, 0x04, 0x4b, 0xbb, 0x65, 0xf3, 0xc8, 0x0f}} ,
+ {{0x24, 0x7b, 0x93, 0x34, 0xb5, 0xe2, 0x74, 0x48, 0xcd, 0xa0, 0x0b, 0x92, 0x97, 0x66, 0x39, 0xf4, 0xb0, 0xe2, 0x5d, 0x39, 0x6a, 0x5b, 0x45, 0x17, 0x78, 0x1e, 0xdb, 0x91, 0x81, 0x1c, 0xf9, 0x16}}},
+{{{0x16, 0xdf, 0xd1, 0x5a, 0xd5, 0xe9, 0x4e, 0x58, 0x95, 0x93, 0x5f, 0x51, 0x09, 0xc3, 0x2a, 0xc9, 0xd4, 0x55, 0x48, 0x79, 0xa4, 0xa3, 0xb2, 0xc3, 0x62, 0xaa, 0x8c, 0xe8, 0xad, 0x47, 0x39, 0x1b}} ,
+ {{0x46, 0xda, 0x9e, 0x51, 0x3a, 0xe6, 0xd1, 0xa6, 0xbb, 0x4d, 0x7b, 0x08, 0xbe, 0x8c, 0xd5, 0xf3, 0x3f, 0xfd, 0xf7, 0x44, 0x80, 0x2d, 0x53, 0x4b, 0xd0, 0x87, 0x68, 0xc1, 0xb5, 0xd8, 0xf7, 0x07}}},
+{{{0xf4, 0x10, 0x46, 0xbe, 0xb7, 0xd2, 0xd1, 0xce, 0x5e, 0x76, 0xa2, 0xd7, 0x03, 0xdc, 0xe4, 0x81, 0x5a, 0xf6, 0x3c, 0xde, 0xae, 0x7a, 0x9d, 0x21, 0x34, 0xa5, 0xf6, 0xa9, 0x73, 0xe2, 0x8d, 0x60}} ,
+ {{0xfa, 0x44, 0x71, 0xf6, 0x41, 0xd8, 0xc6, 0x58, 0x13, 0x37, 0xeb, 0x84, 0x0f, 0x96, 0xc7, 0xdc, 0xc8, 0xa9, 0x7a, 0x83, 0xb2, 0x2f, 0x31, 0xb1, 0x1a, 0xd8, 0x98, 0x3f, 0x11, 0xd0, 0x31, 0x3b}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x81, 0xd5, 0x34, 0x16, 0x01, 0xa3, 0x93, 0xea, 0x52, 0x94, 0xec, 0x93, 0xb7, 0x81, 0x11, 0x2d, 0x58, 0xf9, 0xb5, 0x0a, 0xaa, 0x4f, 0xf6, 0x2e, 0x3f, 0x36, 0xbf, 0x33, 0x5a, 0xe7, 0xd1, 0x08}} ,
+ {{0x1a, 0xcf, 0x42, 0xae, 0xcc, 0xb5, 0x77, 0x39, 0xc4, 0x5b, 0x5b, 0xd0, 0x26, 0x59, 0x27, 0xd0, 0x55, 0x71, 0x12, 0x9d, 0x88, 0x3d, 0x9c, 0xea, 0x41, 0x6a, 0xf0, 0x50, 0x93, 0x93, 0xdd, 0x47}}},
+{{{0x6f, 0xc9, 0x51, 0x6d, 0x1c, 0xaa, 0xf5, 0xa5, 0x90, 0x3f, 0x14, 0xe2, 0x6e, 0x8e, 0x64, 0xfd, 0xac, 0xe0, 0x4e, 0x22, 0xe5, 0xc1, 0xbc, 0x29, 0x0a, 0x6a, 0x9e, 0xa1, 0x60, 0xcb, 0x2f, 0x0b}} ,
+ {{0xdc, 0x39, 0x32, 0xf3, 0xa1, 0x44, 0xe9, 0xc5, 0xc3, 0x78, 0xfb, 0x95, 0x47, 0x34, 0x35, 0x34, 0xe8, 0x25, 0xde, 0x93, 0xc6, 0xb4, 0x76, 0x6d, 0x86, 0x13, 0xc6, 0xe9, 0x68, 0xb5, 0x01, 0x63}}},
+{{{0x1f, 0x9a, 0x52, 0x64, 0x97, 0xd9, 0x1c, 0x08, 0x51, 0x6f, 0x26, 0x9d, 0xaa, 0x93, 0x33, 0x43, 0xfa, 0x77, 0xe9, 0x62, 0x9b, 0x5d, 0x18, 0x75, 0xeb, 0x78, 0xf7, 0x87, 0x8f, 0x41, 0xb4, 0x4d}} ,
+ {{0x13, 0xa8, 0x82, 0x3e, 0xe9, 0x13, 0xad, 0xeb, 0x01, 0xca, 0xcf, 0xda, 0xcd, 0xf7, 0x6c, 0xc7, 0x7a, 0xdc, 0x1e, 0x6e, 0xc8, 0x4e, 0x55, 0x62, 0x80, 0xea, 0x78, 0x0c, 0x86, 0xb9, 0x40, 0x51}}},
+{{{0x27, 0xae, 0xd3, 0x0d, 0x4c, 0x8f, 0x34, 0xea, 0x7d, 0x3c, 0xe5, 0x8a, 0xcf, 0x5b, 0x92, 0xd8, 0x30, 0x16, 0xb4, 0xa3, 0x75, 0xff, 0xeb, 0x27, 0xc8, 0x5c, 0x6c, 0xc2, 0xee, 0x6c, 0x21, 0x0b}} ,
+ {{0xc3, 0xba, 0x12, 0x53, 0x2a, 0xaa, 0x77, 0xad, 0x19, 0x78, 0x55, 0x8a, 0x2e, 0x60, 0x87, 0xc2, 0x6e, 0x91, 0x38, 0x91, 0x3f, 0x7a, 0xc5, 0x24, 0x8f, 0x51, 0xc5, 0xde, 0xb0, 0x53, 0x30, 0x56}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x02, 0xfe, 0x54, 0x12, 0x18, 0xca, 0x7d, 0xa5, 0x68, 0x43, 0xa3, 0x6d, 0x14, 0x2a, 0x6a, 0xa5, 0x8e, 0x32, 0xe7, 0x63, 0x4f, 0xe3, 0xc6, 0x44, 0x3e, 0xab, 0x63, 0xca, 0x17, 0x86, 0x74, 0x3f}} ,
+ {{0x1e, 0x64, 0xc1, 0x7d, 0x52, 0xdc, 0x13, 0x5a, 0xa1, 0x9c, 0x4e, 0xee, 0x99, 0x28, 0xbb, 0x4c, 0xee, 0xac, 0xa9, 0x1b, 0x89, 0xa2, 0x38, 0x39, 0x7b, 0xc4, 0x0f, 0x42, 0xe6, 0x89, 0xed, 0x0f}}},
+{{{0xf3, 0x3c, 0x8c, 0x80, 0x83, 0x10, 0x8a, 0x37, 0x50, 0x9c, 0xb4, 0xdf, 0x3f, 0x8c, 0xf7, 0x23, 0x07, 0xd6, 0xff, 0xa0, 0x82, 0x6c, 0x75, 0x3b, 0xe4, 0xb5, 0xbb, 0xe4, 0xe6, 0x50, 0xf0, 0x08}} ,
+ {{0x62, 0xee, 0x75, 0x48, 0x92, 0x33, 0xf2, 0xf4, 0xad, 0x15, 0x7a, 0xa1, 0x01, 0x46, 0xa9, 0x32, 0x06, 0x88, 0xb6, 0x36, 0x47, 0x35, 0xb9, 0xb4, 0x42, 0x85, 0x76, 0xf0, 0x48, 0x00, 0x90, 0x38}}},
+{{{0x51, 0x15, 0x9d, 0xc3, 0x95, 0xd1, 0x39, 0xbb, 0x64, 0x9d, 0x15, 0x81, 0xc1, 0x68, 0xd0, 0xb6, 0xa4, 0x2c, 0x7d, 0x5e, 0x02, 0x39, 0x00, 0xe0, 0x3b, 0xa4, 0xcc, 0xca, 0x1d, 0x81, 0x24, 0x10}} ,
+ {{0xe7, 0x29, 0xf9, 0x37, 0xd9, 0x46, 0x5a, 0xcd, 0x70, 0xfe, 0x4d, 0x5b, 0xbf, 0xa5, 0xcf, 0x91, 0xf4, 0xef, 0xee, 0x8a, 0x29, 0xd0, 0xe7, 0xc4, 0x25, 0x92, 0x8a, 0xff, 0x36, 0xfc, 0xe4, 0x49}}},
+{{{0xbd, 0x00, 0xb9, 0x04, 0x7d, 0x35, 0xfc, 0xeb, 0xd0, 0x0b, 0x05, 0x32, 0x52, 0x7a, 0x89, 0x24, 0x75, 0x50, 0xe1, 0x63, 0x02, 0x82, 0x8e, 0xe7, 0x85, 0x0c, 0xf2, 0x56, 0x44, 0x37, 0x83, 0x25}} ,
+ {{0x8f, 0xa1, 0xce, 0xcb, 0x60, 0xda, 0x12, 0x02, 0x1e, 0x29, 0x39, 0x2a, 0x03, 0xb7, 0xeb, 0x77, 0x40, 0xea, 0xc9, 0x2b, 0x2c, 0xd5, 0x7d, 0x7e, 0x2c, 0xc7, 0x5a, 0xfd, 0xff, 0xc4, 0xd1, 0x62}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x1d, 0x88, 0x98, 0x5b, 0x4e, 0xfc, 0x41, 0x24, 0x05, 0xe6, 0x50, 0x2b, 0xae, 0x96, 0x51, 0xd9, 0x6b, 0x72, 0xb2, 0x33, 0x42, 0x98, 0x68, 0xbb, 0x10, 0x5a, 0x7a, 0x8c, 0x9d, 0x07, 0xb4, 0x05}} ,
+ {{0x2f, 0x61, 0x9f, 0xd7, 0xa8, 0x3f, 0x83, 0x8c, 0x10, 0x69, 0x90, 0xe6, 0xcf, 0xd2, 0x63, 0xa3, 0xe4, 0x54, 0x7e, 0xe5, 0x69, 0x13, 0x1c, 0x90, 0x57, 0xaa, 0xe9, 0x53, 0x22, 0x43, 0x29, 0x23}}},
+{{{0xe5, 0x1c, 0xf8, 0x0a, 0xfd, 0x2d, 0x7e, 0xf5, 0xf5, 0x70, 0x7d, 0x41, 0x6b, 0x11, 0xfe, 0xbe, 0x99, 0xd1, 0x55, 0x29, 0x31, 0xbf, 0xc0, 0x97, 0x6c, 0xd5, 0x35, 0xcc, 0x5e, 0x8b, 0xd9, 0x69}} ,
+ {{0x8e, 0x4e, 0x9f, 0x25, 0xf8, 0x81, 0x54, 0x2d, 0x0e, 0xd5, 0x54, 0x81, 0x9b, 0xa6, 0x92, 0xce, 0x4b, 0xe9, 0x8f, 0x24, 0x3b, 0xca, 0xe0, 0x44, 0xab, 0x36, 0xfe, 0xfb, 0x87, 0xd4, 0x26, 0x3e}}},
+{{{0x0f, 0x93, 0x9c, 0x11, 0xe7, 0xdb, 0xf1, 0xf0, 0x85, 0x43, 0x28, 0x15, 0x37, 0xdd, 0xde, 0x27, 0xdf, 0xad, 0x3e, 0x49, 0x4f, 0xe0, 0x5b, 0xf6, 0x80, 0x59, 0x15, 0x3c, 0x85, 0xb7, 0x3e, 0x12}} ,
+ {{0xf5, 0xff, 0xcc, 0xf0, 0xb4, 0x12, 0x03, 0x5f, 0xc9, 0x84, 0xcb, 0x1d, 0x17, 0xe0, 0xbc, 0xcc, 0x03, 0x62, 0xa9, 0x8b, 0x94, 0xa6, 0xaa, 0x18, 0xcb, 0x27, 0x8d, 0x49, 0xa6, 0x17, 0x15, 0x07}}},
+{{{0xd9, 0xb6, 0xd4, 0x9d, 0xd4, 0x6a, 0xaf, 0x70, 0x07, 0x2c, 0x10, 0x9e, 0xbd, 0x11, 0xad, 0xe4, 0x26, 0x33, 0x70, 0x92, 0x78, 0x1c, 0x74, 0x9f, 0x75, 0x60, 0x56, 0xf4, 0x39, 0xa8, 0xa8, 0x62}} ,
+ {{0x3b, 0xbf, 0x55, 0x35, 0x61, 0x8b, 0x44, 0x97, 0xe8, 0x3a, 0x55, 0xc1, 0xc8, 0x3b, 0xfd, 0x95, 0x29, 0x11, 0x60, 0x96, 0x1e, 0xcb, 0x11, 0x9d, 0xc2, 0x03, 0x8a, 0x1b, 0xc6, 0xd6, 0x45, 0x3d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x7e, 0x0e, 0x50, 0xb2, 0xcc, 0x0d, 0x6b, 0xa6, 0x71, 0x5b, 0x42, 0xed, 0xbd, 0xaf, 0xac, 0xf0, 0xfc, 0x12, 0xa2, 0x3f, 0x4e, 0xda, 0xe8, 0x11, 0xf3, 0x23, 0xe1, 0x04, 0x62, 0x03, 0x1c, 0x4e}} ,
+ {{0xc8, 0xb1, 0x1b, 0x6f, 0x73, 0x61, 0x3d, 0x27, 0x0d, 0x7d, 0x7a, 0x25, 0x5f, 0x73, 0x0e, 0x2f, 0x93, 0xf6, 0x24, 0xd8, 0x4f, 0x90, 0xac, 0xa2, 0x62, 0x0a, 0xf0, 0x61, 0xd9, 0x08, 0x59, 0x6a}}},
+{{{0x6f, 0x2d, 0x55, 0xf8, 0x2f, 0x8e, 0xf0, 0x18, 0x3b, 0xea, 0xdd, 0x26, 0x72, 0xd1, 0xf5, 0xfe, 0xe5, 0xb8, 0xe6, 0xd3, 0x10, 0x48, 0x46, 0x49, 0x3a, 0x9f, 0x5e, 0x45, 0x6b, 0x90, 0xe8, 0x7f}} ,
+ {{0xd3, 0x76, 0x69, 0x33, 0x7b, 0xb9, 0x40, 0x70, 0xee, 0xa6, 0x29, 0x6b, 0xdd, 0xd0, 0x5d, 0x8d, 0xc1, 0x3e, 0x4a, 0xea, 0x37, 0xb1, 0x03, 0x02, 0x03, 0x35, 0xf1, 0x28, 0x9d, 0xff, 0x00, 0x13}}},
+{{{0x7a, 0xdb, 0x12, 0xd2, 0x8a, 0x82, 0x03, 0x1b, 0x1e, 0xaf, 0xf9, 0x4b, 0x9c, 0xbe, 0xae, 0x7c, 0xe4, 0x94, 0x2a, 0x23, 0xb3, 0x62, 0x86, 0xe7, 0xfd, 0x23, 0xaa, 0x99, 0xbd, 0x2b, 0x11, 0x6c}} ,
+ {{0x8d, 0xa6, 0xd5, 0xac, 0x9d, 0xcc, 0x68, 0x75, 0x7f, 0xc3, 0x4d, 0x4b, 0xdd, 0x6c, 0xbb, 0x11, 0x5a, 0x60, 0xe5, 0xbd, 0x7d, 0x27, 0x8b, 0xda, 0xb4, 0x95, 0xf6, 0x03, 0x27, 0xa4, 0x92, 0x3f}}},
+{{{0x22, 0xd6, 0xb5, 0x17, 0x84, 0xbf, 0x12, 0xcc, 0x23, 0x14, 0x4a, 0xdf, 0x14, 0x31, 0xbc, 0xa1, 0xac, 0x6e, 0xab, 0xfa, 0x57, 0x11, 0x53, 0xb3, 0x27, 0xe6, 0xf9, 0x47, 0x33, 0x44, 0x34, 0x1e}} ,
+ {{0x79, 0xfc, 0xa6, 0xb4, 0x0b, 0x35, 0x20, 0xc9, 0x4d, 0x22, 0x84, 0xc4, 0xa9, 0x20, 0xec, 0x89, 0x94, 0xba, 0x66, 0x56, 0x48, 0xb9, 0x87, 0x7f, 0xca, 0x1e, 0x06, 0xed, 0xa5, 0x55, 0x59, 0x29}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x56, 0xe1, 0xf5, 0xf1, 0xd5, 0xab, 0xa8, 0x2b, 0xae, 0x89, 0xf3, 0xcf, 0x56, 0x9f, 0xf2, 0x4b, 0x31, 0xbc, 0x18, 0xa9, 0x06, 0x5b, 0xbe, 0xb4, 0x61, 0xf8, 0xb2, 0x06, 0x9c, 0x81, 0xab, 0x4c}} ,
+ {{0x1f, 0x68, 0x76, 0x01, 0x16, 0x38, 0x2b, 0x0f, 0x77, 0x97, 0x92, 0x67, 0x4e, 0x86, 0x6a, 0x8b, 0xe5, 0xe8, 0x0c, 0xf7, 0x36, 0x39, 0xb5, 0x33, 0xe6, 0xcf, 0x5e, 0xbd, 0x18, 0xfb, 0x10, 0x1f}}},
+{{{0x83, 0xf0, 0x0d, 0x63, 0xef, 0x53, 0x6b, 0xb5, 0x6b, 0xf9, 0x83, 0xcf, 0xde, 0x04, 0x22, 0x9b, 0x2c, 0x0a, 0xe0, 0xa5, 0xd8, 0xc7, 0x9c, 0xa5, 0xa3, 0xf6, 0x6f, 0xcf, 0x90, 0x6b, 0x68, 0x7c}} ,
+ {{0x33, 0x15, 0xd7, 0x7f, 0x1a, 0xd5, 0x21, 0x58, 0xc4, 0x18, 0xa5, 0xf0, 0xcc, 0x73, 0xa8, 0xfd, 0xfa, 0x18, 0xd1, 0x03, 0x91, 0x8d, 0x52, 0xd2, 0xa3, 0xa4, 0xd3, 0xb1, 0xea, 0x1d, 0x0f, 0x00}}},
+{{{0xcc, 0x48, 0x83, 0x90, 0xe5, 0xfd, 0x3f, 0x84, 0xaa, 0xf9, 0x8b, 0x82, 0x59, 0x24, 0x34, 0x68, 0x4f, 0x1c, 0x23, 0xd9, 0xcc, 0x71, 0xe1, 0x7f, 0x8c, 0xaf, 0xf1, 0xee, 0x00, 0xb6, 0xa0, 0x77}} ,
+ {{0xf5, 0x1a, 0x61, 0xf7, 0x37, 0x9d, 0x00, 0xf4, 0xf2, 0x69, 0x6f, 0x4b, 0x01, 0x85, 0x19, 0x45, 0x4d, 0x7f, 0x02, 0x7c, 0x6a, 0x05, 0x47, 0x6c, 0x1f, 0x81, 0x20, 0xd4, 0xe8, 0x50, 0x27, 0x72}}},
+{{{0x2c, 0x3a, 0xe5, 0xad, 0xf4, 0xdd, 0x2d, 0xf7, 0x5c, 0x44, 0xb5, 0x5b, 0x21, 0xa3, 0x89, 0x5f, 0x96, 0x45, 0xca, 0x4d, 0xa4, 0x21, 0x99, 0x70, 0xda, 0xc4, 0xc4, 0xa0, 0xe5, 0xf4, 0xec, 0x0a}} ,
+ {{0x07, 0x68, 0x21, 0x65, 0xe9, 0x08, 0xa0, 0x0b, 0x6a, 0x4a, 0xba, 0xb5, 0x80, 0xaf, 0xd0, 0x1b, 0xc5, 0xf5, 0x4b, 0x73, 0x50, 0x60, 0x2d, 0x71, 0x69, 0x61, 0x0e, 0xc0, 0x20, 0x40, 0x30, 0x19}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xd0, 0x75, 0x57, 0x3b, 0xeb, 0x5c, 0x14, 0x56, 0x50, 0xc9, 0x4f, 0xb8, 0xb8, 0x1e, 0xa3, 0xf4, 0xab, 0xf5, 0xa9, 0x20, 0x15, 0x94, 0x82, 0xda, 0x96, 0x1c, 0x9b, 0x59, 0x8c, 0xff, 0xf4, 0x51}} ,
+ {{0xc1, 0x3a, 0x86, 0xd7, 0xb0, 0x06, 0x84, 0x7f, 0x1b, 0xbd, 0xd4, 0x07, 0x78, 0x80, 0x2e, 0xb1, 0xb4, 0xee, 0x52, 0x38, 0xee, 0x9a, 0xf9, 0xf6, 0xf3, 0x41, 0x6e, 0xd4, 0x88, 0x95, 0xac, 0x35}}},
+{{{0x41, 0x97, 0xbf, 0x71, 0x6a, 0x9b, 0x72, 0xec, 0xf3, 0xf8, 0x6b, 0xe6, 0x0e, 0x6c, 0x69, 0xa5, 0x2f, 0x68, 0x52, 0xd8, 0x61, 0x81, 0xc0, 0x63, 0x3f, 0xa6, 0x3c, 0x13, 0x90, 0xe6, 0x8d, 0x56}} ,
+ {{0xe8, 0x39, 0x30, 0x77, 0x23, 0xb1, 0xfd, 0x1b, 0x3d, 0x3e, 0x74, 0x4d, 0x7f, 0xae, 0x5b, 0x3a, 0xb4, 0x65, 0x0e, 0x3a, 0x43, 0xdc, 0xdc, 0x41, 0x47, 0xe6, 0xe8, 0x92, 0x09, 0x22, 0x48, 0x4c}}},
+{{{0x85, 0x57, 0x9f, 0xb5, 0xc8, 0x06, 0xb2, 0x9f, 0x47, 0x3f, 0xf0, 0xfa, 0xe6, 0xa9, 0xb1, 0x9b, 0x6f, 0x96, 0x7d, 0xf9, 0xa4, 0x65, 0x09, 0x75, 0x32, 0xa6, 0x6c, 0x7f, 0x47, 0x4b, 0x2f, 0x4f}} ,
+ {{0x34, 0xe9, 0x59, 0x93, 0x9d, 0x26, 0x80, 0x54, 0xf2, 0xcc, 0x3c, 0xc2, 0x25, 0x85, 0xe3, 0x6a, 0xc1, 0x62, 0x04, 0xa7, 0x08, 0x32, 0x6d, 0xa1, 0x39, 0x84, 0x8a, 0x3b, 0x87, 0x5f, 0x11, 0x13}}},
+{{{0xda, 0x03, 0x34, 0x66, 0xc4, 0x0c, 0x73, 0x6e, 0xbc, 0x24, 0xb5, 0xf9, 0x70, 0x81, 0x52, 0xe9, 0xf4, 0x7c, 0x23, 0xdd, 0x9f, 0xb8, 0x46, 0xef, 0x1d, 0x22, 0x55, 0x7d, 0x71, 0xc4, 0x42, 0x33}} ,
+ {{0xc5, 0x37, 0x69, 0x5b, 0xa8, 0xc6, 0x9d, 0xa4, 0xfc, 0x61, 0x6e, 0x68, 0x46, 0xea, 0xd7, 0x1c, 0x67, 0xd2, 0x7d, 0xfa, 0xf1, 0xcc, 0x54, 0x8d, 0x36, 0x35, 0xc9, 0x00, 0xdf, 0x6c, 0x67, 0x50}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x9a, 0x4d, 0x42, 0x29, 0x5d, 0xa4, 0x6b, 0x6f, 0xa8, 0x8a, 0x4d, 0x91, 0x7b, 0xd2, 0xdf, 0x36, 0xef, 0x01, 0x22, 0xc5, 0xcc, 0x8d, 0xeb, 0x58, 0x3d, 0xb3, 0x50, 0xfc, 0x8b, 0x97, 0x96, 0x33}} ,
+ {{0x93, 0x33, 0x07, 0xc8, 0x4a, 0xca, 0xd0, 0xb1, 0xab, 0xbd, 0xdd, 0xa7, 0x7c, 0xac, 0x3e, 0x45, 0xcb, 0xcc, 0x07, 0x91, 0xbf, 0x35, 0x9d, 0xcb, 0x7d, 0x12, 0x3c, 0x11, 0x59, 0x13, 0xcf, 0x5c}}},
+{{{0x45, 0xb8, 0x41, 0xd7, 0xab, 0x07, 0x15, 0x00, 0x8e, 0xce, 0xdf, 0xb2, 0x43, 0x5c, 0x01, 0xdc, 0xf4, 0x01, 0x51, 0x95, 0x10, 0x5a, 0xf6, 0x24, 0x24, 0xa0, 0x19, 0x3a, 0x09, 0x2a, 0xaa, 0x3f}} ,
+ {{0xdc, 0x8e, 0xeb, 0xc6, 0xbf, 0xdd, 0x11, 0x7b, 0xe7, 0x47, 0xe6, 0xce, 0xe7, 0xb6, 0xc5, 0xe8, 0x8a, 0xdc, 0x4b, 0x57, 0x15, 0x3b, 0x66, 0xca, 0x89, 0xa3, 0xfd, 0xac, 0x0d, 0xe1, 0x1d, 0x7a}}},
+{{{0x89, 0xef, 0xbf, 0x03, 0x75, 0xd0, 0x29, 0x50, 0xcb, 0x7d, 0xd6, 0xbe, 0xad, 0x5f, 0x7b, 0x00, 0x32, 0xaa, 0x98, 0xed, 0x3f, 0x8f, 0x92, 0xcb, 0x81, 0x56, 0x01, 0x63, 0x64, 0xa3, 0x38, 0x39}} ,
+ {{0x8b, 0xa4, 0xd6, 0x50, 0xb4, 0xaa, 0x5d, 0x64, 0x64, 0x76, 0x2e, 0xa1, 0xa6, 0xb3, 0xb8, 0x7c, 0x7a, 0x56, 0xf5, 0x5c, 0x4e, 0x84, 0x5c, 0xfb, 0xdd, 0xca, 0x48, 0x8b, 0x48, 0xb9, 0xba, 0x34}}},
+{{{0xc5, 0xe3, 0xe8, 0xae, 0x17, 0x27, 0xe3, 0x64, 0x60, 0x71, 0x47, 0x29, 0x02, 0x0f, 0x92, 0x5d, 0x10, 0x93, 0xc8, 0x0e, 0xa1, 0xed, 0xba, 0xa9, 0x96, 0x1c, 0xc5, 0x76, 0x30, 0xcd, 0xf9, 0x30}} ,
+ {{0x95, 0xb0, 0xbd, 0x8c, 0xbc, 0xa7, 0x4f, 0x7e, 0xfd, 0x4e, 0x3a, 0xbf, 0x5f, 0x04, 0x79, 0x80, 0x2b, 0x5a, 0x9f, 0x4f, 0x68, 0x21, 0x19, 0x71, 0xc6, 0x20, 0x01, 0x42, 0xaa, 0xdf, 0xae, 0x2c}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x90, 0x6e, 0x7e, 0x4b, 0x71, 0x93, 0xc0, 0x72, 0xed, 0xeb, 0x71, 0x24, 0x97, 0x26, 0x9c, 0xfe, 0xcb, 0x3e, 0x59, 0x19, 0xa8, 0x0f, 0x75, 0x7d, 0xbe, 0x18, 0xe6, 0x96, 0x1e, 0x95, 0x70, 0x60}} ,
+ {{0x89, 0x66, 0x3e, 0x1d, 0x4c, 0x5f, 0xfe, 0xc0, 0x04, 0x43, 0xd6, 0x44, 0x19, 0xb5, 0xad, 0xc7, 0x22, 0xdc, 0x71, 0x28, 0x64, 0xde, 0x41, 0x38, 0x27, 0x8f, 0x2c, 0x6b, 0x08, 0xb8, 0xb8, 0x7b}}},
+{{{0x3d, 0x70, 0x27, 0x9d, 0xd9, 0xaf, 0xb1, 0x27, 0xaf, 0xe3, 0x5d, 0x1e, 0x3a, 0x30, 0x54, 0x61, 0x60, 0xe8, 0xc3, 0x26, 0x3a, 0xbc, 0x7e, 0xf5, 0x81, 0xdd, 0x64, 0x01, 0x04, 0xeb, 0xc0, 0x1e}} ,
+ {{0xda, 0x2c, 0xa4, 0xd1, 0xa1, 0xc3, 0x5c, 0x6e, 0x32, 0x07, 0x1f, 0xb8, 0x0e, 0x19, 0x9e, 0x99, 0x29, 0x33, 0x9a, 0xae, 0x7a, 0xed, 0x68, 0x42, 0x69, 0x7c, 0x07, 0xb3, 0x38, 0x2c, 0xf6, 0x3d}}},
+{{{0x64, 0xaa, 0xb5, 0x88, 0x79, 0x65, 0x38, 0x8c, 0x94, 0xd6, 0x62, 0x37, 0x7d, 0x64, 0xcd, 0x3a, 0xeb, 0xff, 0xe8, 0x81, 0x09, 0xc7, 0x6a, 0x50, 0x09, 0x0d, 0x28, 0x03, 0x0d, 0x9a, 0x93, 0x0a}} ,
+ {{0x42, 0xa3, 0xf1, 0xc5, 0xb4, 0x0f, 0xd8, 0xc8, 0x8d, 0x15, 0x31, 0xbd, 0xf8, 0x07, 0x8b, 0xcd, 0x08, 0x8a, 0xfb, 0x18, 0x07, 0xfe, 0x8e, 0x52, 0x86, 0xef, 0xbe, 0xec, 0x49, 0x52, 0x99, 0x08}}},
+{{{0x0f, 0xa9, 0xd5, 0x01, 0xaa, 0x48, 0x4f, 0x28, 0x66, 0x32, 0x1a, 0xba, 0x7c, 0xea, 0x11, 0x80, 0x17, 0x18, 0x9b, 0x56, 0x88, 0x25, 0x06, 0x69, 0x12, 0x2c, 0xea, 0x56, 0x69, 0x41, 0x24, 0x19}} ,
+ {{0xde, 0x21, 0xf0, 0xda, 0x8a, 0xfb, 0xb1, 0xb8, 0xcd, 0xc8, 0x6a, 0x82, 0x19, 0x73, 0xdb, 0xc7, 0xcf, 0x88, 0xeb, 0x96, 0xee, 0x6f, 0xfb, 0x06, 0xd2, 0xcd, 0x7d, 0x7b, 0x12, 0x28, 0x8e, 0x0c}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x93, 0x44, 0x97, 0xce, 0x28, 0xff, 0x3a, 0x40, 0xc4, 0xf5, 0xf6, 0x9b, 0xf4, 0x6b, 0x07, 0x84, 0xfb, 0x98, 0xd8, 0xec, 0x8c, 0x03, 0x57, 0xec, 0x49, 0xed, 0x63, 0xb6, 0xaa, 0xff, 0x98, 0x28}} ,
+ {{0x3d, 0x16, 0x35, 0xf3, 0x46, 0xbc, 0xb3, 0xf4, 0xc6, 0xb6, 0x4f, 0xfa, 0xf4, 0xa0, 0x13, 0xe6, 0x57, 0x45, 0x93, 0xb9, 0xbc, 0xd6, 0x59, 0xe7, 0x77, 0x94, 0x6c, 0xab, 0x96, 0x3b, 0x4f, 0x09}}},
+{{{0x5a, 0xf7, 0x6b, 0x01, 0x12, 0x4f, 0x51, 0xc1, 0x70, 0x84, 0x94, 0x47, 0xb2, 0x01, 0x6c, 0x71, 0xd7, 0xcc, 0x17, 0x66, 0x0f, 0x59, 0x5d, 0x5d, 0x10, 0x01, 0x57, 0x11, 0xf5, 0xdd, 0xe2, 0x34}} ,
+ {{0x26, 0xd9, 0x1f, 0x5c, 0x58, 0xac, 0x8b, 0x03, 0xd2, 0xc3, 0x85, 0x0f, 0x3a, 0xc3, 0x7f, 0x6d, 0x8e, 0x86, 0xcd, 0x52, 0x74, 0x8f, 0x55, 0x77, 0x17, 0xb7, 0x8e, 0xb7, 0x88, 0xea, 0xda, 0x1b}}},
+{{{0xb6, 0xea, 0x0e, 0x40, 0x93, 0x20, 0x79, 0x35, 0x6a, 0x61, 0x84, 0x5a, 0x07, 0x6d, 0xf9, 0x77, 0x6f, 0xed, 0x69, 0x1c, 0x0d, 0x25, 0x76, 0xcc, 0xf0, 0xdb, 0xbb, 0xc5, 0xad, 0xe2, 0x26, 0x57}} ,
+ {{0xcf, 0xe8, 0x0e, 0x6b, 0x96, 0x7d, 0xed, 0x27, 0xd1, 0x3c, 0xa9, 0xd9, 0x50, 0xa9, 0x98, 0x84, 0x5e, 0x86, 0xef, 0xd6, 0xf0, 0xf8, 0x0e, 0x89, 0x05, 0x2f, 0xd9, 0x5f, 0x15, 0x5f, 0x73, 0x79}}},
+{{{0xc8, 0x5c, 0x16, 0xfe, 0xed, 0x9f, 0x26, 0x56, 0xf6, 0x4b, 0x9f, 0xa7, 0x0a, 0x85, 0xfe, 0xa5, 0x8c, 0x87, 0xdd, 0x98, 0xce, 0x4e, 0xc3, 0x58, 0x55, 0xb2, 0x7b, 0x3d, 0xd8, 0x6b, 0xb5, 0x4c}} ,
+ {{0x65, 0x38, 0xa0, 0x15, 0xfa, 0xa7, 0xb4, 0x8f, 0xeb, 0xc4, 0x86, 0x9b, 0x30, 0xa5, 0x5e, 0x4d, 0xea, 0x8a, 0x9a, 0x9f, 0x1a, 0xd8, 0x5b, 0x53, 0x14, 0x19, 0x25, 0x63, 0xb4, 0x6f, 0x1f, 0x5d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xac, 0x8f, 0xbc, 0x1e, 0x7d, 0x8b, 0x5a, 0x0b, 0x8d, 0xaf, 0x76, 0x2e, 0x71, 0xe3, 0x3b, 0x6f, 0x53, 0x2f, 0x3e, 0x90, 0x95, 0xd4, 0x35, 0x14, 0x4f, 0x8c, 0x3c, 0xce, 0x57, 0x1c, 0x76, 0x49}} ,
+ {{0xa8, 0x50, 0xe1, 0x61, 0x6b, 0x57, 0x35, 0xeb, 0x44, 0x0b, 0x0c, 0x6e, 0xf9, 0x25, 0x80, 0x74, 0xf2, 0x8f, 0x6f, 0x7a, 0x3e, 0x7f, 0x2d, 0xf3, 0x4e, 0x09, 0x65, 0x10, 0x5e, 0x03, 0x25, 0x32}}},
+{{{0xa9, 0x60, 0xdc, 0x0f, 0x64, 0xe5, 0x1d, 0xe2, 0x8d, 0x4f, 0x79, 0x2f, 0x0e, 0x24, 0x02, 0x00, 0x05, 0x77, 0x43, 0x25, 0x3d, 0x6a, 0xc7, 0xb7, 0xbf, 0x04, 0x08, 0x65, 0xf4, 0x39, 0x4b, 0x65}} ,
+ {{0x96, 0x19, 0x12, 0x6b, 0x6a, 0xb7, 0xe3, 0xdc, 0x45, 0x9b, 0xdb, 0xb4, 0xa8, 0xae, 0xdc, 0xa8, 0x14, 0x44, 0x65, 0x62, 0xce, 0x34, 0x9a, 0x84, 0x18, 0x12, 0x01, 0xf1, 0xe2, 0x7b, 0xce, 0x50}}},
+{{{0x41, 0x21, 0x30, 0x53, 0x1b, 0x47, 0x01, 0xb7, 0x18, 0xd8, 0x82, 0x57, 0xbd, 0xa3, 0x60, 0xf0, 0x32, 0xf6, 0x5b, 0xf0, 0x30, 0x88, 0x91, 0x59, 0xfd, 0x90, 0xa2, 0xb9, 0x55, 0x93, 0x21, 0x34}} ,
+ {{0x97, 0x67, 0x9e, 0xeb, 0x6a, 0xf9, 0x6e, 0xd6, 0x73, 0xe8, 0x6b, 0x29, 0xec, 0x63, 0x82, 0x00, 0xa8, 0x99, 0x1c, 0x1d, 0x30, 0xc8, 0x90, 0x52, 0x90, 0xb6, 0x6a, 0x80, 0x4e, 0xff, 0x4b, 0x51}}},
+{{{0x0f, 0x7d, 0x63, 0x8c, 0x6e, 0x5c, 0xde, 0x30, 0xdf, 0x65, 0xfa, 0x2e, 0xb0, 0xa3, 0x25, 0x05, 0x54, 0xbd, 0x25, 0xba, 0x06, 0xae, 0xdf, 0x8b, 0xd9, 0x1b, 0xea, 0x38, 0xb3, 0x05, 0x16, 0x09}} ,
+ {{0xc7, 0x8c, 0xbf, 0x64, 0x28, 0xad, 0xf8, 0xa5, 0x5a, 0x6f, 0xc9, 0xba, 0xd5, 0x7f, 0xd5, 0xd6, 0xbd, 0x66, 0x2f, 0x3d, 0xaa, 0x54, 0xf6, 0xba, 0x32, 0x22, 0x9a, 0x1e, 0x52, 0x05, 0xf4, 0x1d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xaa, 0x1f, 0xbb, 0xeb, 0xfe, 0xe4, 0x87, 0xfc, 0xb1, 0x2c, 0xb7, 0x88, 0xf4, 0xc6, 0xb9, 0xf5, 0x24, 0x46, 0xf2, 0xa5, 0x9f, 0x8f, 0x8a, 0x93, 0x70, 0x69, 0xd4, 0x56, 0xec, 0xfd, 0x06, 0x46}} ,
+ {{0x4e, 0x66, 0xcf, 0x4e, 0x34, 0xce, 0x0c, 0xd9, 0xa6, 0x50, 0xd6, 0x5e, 0x95, 0xaf, 0xe9, 0x58, 0xfa, 0xee, 0x9b, 0xb8, 0xa5, 0x0f, 0x35, 0xe0, 0x43, 0x82, 0x6d, 0x65, 0xe6, 0xd9, 0x00, 0x0f}}},
+{{{0x7b, 0x75, 0x3a, 0xfc, 0x64, 0xd3, 0x29, 0x7e, 0xdd, 0x49, 0x9a, 0x59, 0x53, 0xbf, 0xb4, 0xa7, 0x52, 0xb3, 0x05, 0xab, 0xc3, 0xaf, 0x16, 0x1a, 0x85, 0x42, 0x32, 0xa2, 0x86, 0xfa, 0x39, 0x43}} ,
+ {{0x0e, 0x4b, 0xa3, 0x63, 0x8a, 0xfe, 0xa5, 0x58, 0xf1, 0x13, 0xbd, 0x9d, 0xaa, 0x7f, 0x76, 0x40, 0x70, 0x81, 0x10, 0x75, 0x99, 0xbb, 0xbe, 0x0b, 0x16, 0xe9, 0xba, 0x62, 0x34, 0xcc, 0x07, 0x6d}}},
+{{{0xc3, 0xf1, 0xc6, 0x93, 0x65, 0xee, 0x0b, 0xbc, 0xea, 0x14, 0xf0, 0xc1, 0xf8, 0x84, 0x89, 0xc2, 0xc9, 0xd7, 0xea, 0x34, 0xca, 0xa7, 0xc4, 0x99, 0xd5, 0x50, 0x69, 0xcb, 0xd6, 0x21, 0x63, 0x7c}} ,
+ {{0x99, 0xeb, 0x7c, 0x31, 0x73, 0x64, 0x67, 0x7f, 0x0c, 0x66, 0xaa, 0x8c, 0x69, 0x91, 0xe2, 0x26, 0xd3, 0x23, 0xe2, 0x76, 0x5d, 0x32, 0x52, 0xdf, 0x5d, 0xc5, 0x8f, 0xb7, 0x7c, 0x84, 0xb3, 0x70}}},
+{{{0xeb, 0x01, 0xc7, 0x36, 0x97, 0x4e, 0xb6, 0xab, 0x5f, 0x0d, 0x2c, 0xba, 0x67, 0x64, 0x55, 0xde, 0xbc, 0xff, 0xa6, 0xec, 0x04, 0xd3, 0x8d, 0x39, 0x56, 0x5e, 0xee, 0xf8, 0xe4, 0x2e, 0x33, 0x62}} ,
+ {{0x65, 0xef, 0xb8, 0x9f, 0xc8, 0x4b, 0xa7, 0xfd, 0x21, 0x49, 0x9b, 0x92, 0x35, 0x82, 0xd6, 0x0a, 0x9b, 0xf2, 0x79, 0xf1, 0x47, 0x2f, 0x6a, 0x7e, 0x9f, 0xcf, 0x18, 0x02, 0x3c, 0xfb, 0x1b, 0x3e}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x2f, 0x8b, 0xc8, 0x40, 0x51, 0xd1, 0xac, 0x1a, 0x0b, 0xe4, 0xa9, 0xa2, 0x42, 0x21, 0x19, 0x2f, 0x7b, 0x97, 0xbf, 0xf7, 0x57, 0x6d, 0x3f, 0x3d, 0x4f, 0x0f, 0xe2, 0xb2, 0x81, 0x00, 0x9e, 0x7b}} ,
+ {{0x8c, 0x85, 0x2b, 0xc4, 0xfc, 0xf1, 0xab, 0xe8, 0x79, 0x22, 0xc4, 0x84, 0x17, 0x3a, 0xfa, 0x86, 0xa6, 0x7d, 0xf9, 0xf3, 0x6f, 0x03, 0x57, 0x20, 0x4d, 0x79, 0xf9, 0x6e, 0x71, 0x54, 0x38, 0x09}}},
+{{{0x40, 0x29, 0x74, 0xa8, 0x2f, 0x5e, 0xf9, 0x79, 0xa4, 0xf3, 0x3e, 0xb9, 0xfd, 0x33, 0x31, 0xac, 0x9a, 0x69, 0x88, 0x1e, 0x77, 0x21, 0x2d, 0xf3, 0x91, 0x52, 0x26, 0x15, 0xb2, 0xa6, 0xcf, 0x7e}} ,
+ {{0xc6, 0x20, 0x47, 0x6c, 0xa4, 0x7d, 0xcb, 0x63, 0xea, 0x5b, 0x03, 0xdf, 0x3e, 0x88, 0x81, 0x6d, 0xce, 0x07, 0x42, 0x18, 0x60, 0x7e, 0x7b, 0x55, 0xfe, 0x6a, 0xf3, 0xda, 0x5c, 0x8b, 0x95, 0x10}}},
+{{{0x62, 0xe4, 0x0d, 0x03, 0xb4, 0xd7, 0xcd, 0xfa, 0xbd, 0x46, 0xdf, 0x93, 0x71, 0x10, 0x2c, 0xa8, 0x3b, 0xb6, 0x09, 0x05, 0x70, 0x84, 0x43, 0x29, 0xa8, 0x59, 0xf5, 0x8e, 0x10, 0xe4, 0xd7, 0x20}} ,
+ {{0x57, 0x82, 0x1c, 0xab, 0xbf, 0x62, 0x70, 0xe8, 0xc4, 0xcf, 0xf0, 0x28, 0x6e, 0x16, 0x3c, 0x08, 0x78, 0x89, 0x85, 0x46, 0x0f, 0xf6, 0x7f, 0xcf, 0xcb, 0x7e, 0xb8, 0x25, 0xe9, 0x5a, 0xfa, 0x03}}},
+{{{0xfb, 0x95, 0x92, 0x63, 0x50, 0xfc, 0x62, 0xf0, 0xa4, 0x5e, 0x8c, 0x18, 0xc2, 0x17, 0x24, 0xb7, 0x78, 0xc2, 0xa9, 0xe7, 0x6a, 0x32, 0xd6, 0x29, 0x85, 0xaf, 0xcb, 0x8d, 0x91, 0x13, 0xda, 0x6b}} ,
+ {{0x36, 0x0a, 0xc2, 0xb6, 0x4b, 0xa5, 0x5d, 0x07, 0x17, 0x41, 0x31, 0x5f, 0x62, 0x46, 0xf8, 0x92, 0xf9, 0x66, 0x48, 0x73, 0xa6, 0x97, 0x0d, 0x7d, 0x88, 0xee, 0x62, 0xb1, 0x03, 0xa8, 0x3f, 0x2c}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x4a, 0xb1, 0x70, 0x8a, 0xa9, 0xe8, 0x63, 0x79, 0x00, 0xe2, 0x25, 0x16, 0xca, 0x4b, 0x0f, 0xa4, 0x66, 0xad, 0x19, 0x9f, 0x88, 0x67, 0x0c, 0x8b, 0xc2, 0x4a, 0x5b, 0x2b, 0x6d, 0x95, 0xaf, 0x19}} ,
+ {{0x8b, 0x9d, 0xb6, 0xcc, 0x60, 0xb4, 0x72, 0x4f, 0x17, 0x69, 0x5a, 0x4a, 0x68, 0x34, 0xab, 0xa1, 0x45, 0x32, 0x3c, 0x83, 0x87, 0x72, 0x30, 0x54, 0x77, 0x68, 0xae, 0xfb, 0xb5, 0x8b, 0x22, 0x5e}}},
+{{{0xf1, 0xb9, 0x87, 0x35, 0xc5, 0xbb, 0xb9, 0xcf, 0xf5, 0xd6, 0xcd, 0xd5, 0x0c, 0x7c, 0x0e, 0xe6, 0x90, 0x34, 0xfb, 0x51, 0x42, 0x1e, 0x6d, 0xac, 0x9a, 0x46, 0xc4, 0x97, 0x29, 0x32, 0xbf, 0x45}} ,
+ {{0x66, 0x9e, 0xc6, 0x24, 0xc0, 0xed, 0xa5, 0x5d, 0x88, 0xd4, 0xf0, 0x73, 0x97, 0x7b, 0xea, 0x7f, 0x42, 0xff, 0x21, 0xa0, 0x9b, 0x2f, 0x9a, 0xfd, 0x53, 0x57, 0x07, 0x84, 0x48, 0x88, 0x9d, 0x52}}},
+{{{0xc6, 0x96, 0x48, 0x34, 0x2a, 0x06, 0xaf, 0x94, 0x3d, 0xf4, 0x1a, 0xcf, 0xf2, 0xc0, 0x21, 0xc2, 0x42, 0x5e, 0xc8, 0x2f, 0x35, 0xa2, 0x3e, 0x29, 0xfa, 0x0c, 0x84, 0xe5, 0x89, 0x72, 0x7c, 0x06}} ,
+ {{0x32, 0x65, 0x03, 0xe5, 0x89, 0xa6, 0x6e, 0xb3, 0x5b, 0x8e, 0xca, 0xeb, 0xfe, 0x22, 0x56, 0x8b, 0x5d, 0x14, 0x4b, 0x4d, 0xf9, 0xbe, 0xb5, 0xf5, 0xe6, 0x5c, 0x7b, 0x8b, 0xf4, 0x13, 0x11, 0x34}}},
+{{{0x07, 0xc6, 0x22, 0x15, 0xe2, 0x9c, 0x60, 0xa2, 0x19, 0xd9, 0x27, 0xae, 0x37, 0x4e, 0xa6, 0xc9, 0x80, 0xa6, 0x91, 0x8f, 0x12, 0x49, 0xe5, 0x00, 0x18, 0x47, 0xd1, 0xd7, 0x28, 0x22, 0x63, 0x39}} ,
+ {{0xe8, 0xe2, 0x00, 0x7e, 0xf2, 0x9e, 0x1e, 0x99, 0x39, 0x95, 0x04, 0xbd, 0x1e, 0x67, 0x7b, 0xb2, 0x26, 0xac, 0xe6, 0xaa, 0xe2, 0x46, 0xd5, 0xe4, 0xe8, 0x86, 0xbd, 0xab, 0x7c, 0x55, 0x59, 0x6f}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x24, 0x64, 0x6e, 0x9b, 0x35, 0x71, 0x78, 0xce, 0x33, 0x03, 0x21, 0x33, 0x36, 0xf1, 0x73, 0x9b, 0xb9, 0x15, 0x8b, 0x2c, 0x69, 0xcf, 0x4d, 0xed, 0x4f, 0x4d, 0x57, 0x14, 0x13, 0x82, 0xa4, 0x4d}} ,
+ {{0x65, 0x6e, 0x0a, 0xa4, 0x59, 0x07, 0x17, 0xf2, 0x6b, 0x4a, 0x1f, 0x6e, 0xf6, 0xb5, 0xbc, 0x62, 0xe4, 0xb6, 0xda, 0xa2, 0x93, 0xbc, 0x29, 0x05, 0xd2, 0xd2, 0x73, 0x46, 0x03, 0x16, 0x40, 0x31}}},
+{{{0x4c, 0x73, 0x6d, 0x15, 0xbd, 0xa1, 0x4d, 0x5c, 0x13, 0x0b, 0x24, 0x06, 0x98, 0x78, 0x1c, 0x5b, 0xeb, 0x1f, 0x18, 0x54, 0x43, 0xd9, 0x55, 0x66, 0xda, 0x29, 0x21, 0xe8, 0xb8, 0x3c, 0x42, 0x22}} ,
+ {{0xb4, 0xcd, 0x08, 0x6f, 0x15, 0x23, 0x1a, 0x0b, 0x22, 0xed, 0xd1, 0xf1, 0xa7, 0xc7, 0x73, 0x45, 0xf3, 0x9e, 0xce, 0x76, 0xb7, 0xf6, 0x39, 0xb6, 0x8e, 0x79, 0xbe, 0xe9, 0x9b, 0xcf, 0x7d, 0x62}}},
+{{{0x92, 0x5b, 0xfc, 0x72, 0xfd, 0xba, 0xf1, 0xfd, 0xa6, 0x7c, 0x95, 0xe3, 0x61, 0x3f, 0xe9, 0x03, 0xd4, 0x2b, 0xd4, 0x20, 0xd9, 0xdb, 0x4d, 0x32, 0x3e, 0xf5, 0x11, 0x64, 0xe3, 0xb4, 0xbe, 0x32}} ,
+ {{0x86, 0x17, 0x90, 0xe7, 0xc9, 0x1f, 0x10, 0xa5, 0x6a, 0x2d, 0x39, 0xd0, 0x3b, 0xc4, 0xa6, 0xe9, 0x59, 0x13, 0xda, 0x1a, 0xe6, 0xa0, 0xb9, 0x3c, 0x50, 0xb8, 0x40, 0x7c, 0x15, 0x36, 0x5a, 0x42}}},
+{{{0xb4, 0x0b, 0x32, 0xab, 0xdc, 0x04, 0x51, 0x55, 0x21, 0x1e, 0x0b, 0x75, 0x99, 0x89, 0x73, 0x35, 0x3a, 0x91, 0x2b, 0xfe, 0xe7, 0x49, 0xea, 0x76, 0xc1, 0xf9, 0x46, 0xb9, 0x53, 0x02, 0x23, 0x04}} ,
+ {{0xfc, 0x5a, 0x1e, 0x1d, 0x74, 0x58, 0x95, 0xa6, 0x8f, 0x7b, 0x97, 0x3e, 0x17, 0x3b, 0x79, 0x2d, 0xa6, 0x57, 0xef, 0x45, 0x02, 0x0b, 0x4d, 0x6e, 0x9e, 0x93, 0x8d, 0x2f, 0xd9, 0x9d, 0xdb, 0x04}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xc0, 0xd7, 0x56, 0x97, 0x58, 0x91, 0xde, 0x09, 0x4f, 0x9f, 0xbe, 0x63, 0xb0, 0x83, 0x86, 0x43, 0x5d, 0xbc, 0xe0, 0xf3, 0xc0, 0x75, 0xbf, 0x8b, 0x8e, 0xaa, 0xf7, 0x8b, 0x64, 0x6e, 0xb0, 0x63}} ,
+ {{0x16, 0xae, 0x8b, 0xe0, 0x9b, 0x24, 0x68, 0x5c, 0x44, 0xc2, 0xd0, 0x08, 0xb7, 0x7b, 0x62, 0xfd, 0x7f, 0xd8, 0xd4, 0xb7, 0x50, 0xfd, 0x2c, 0x1b, 0xbf, 0x41, 0x95, 0xd9, 0x8e, 0xd8, 0x17, 0x1b}}},
+{{{0x86, 0x55, 0x37, 0x8e, 0xc3, 0x38, 0x48, 0x14, 0xb5, 0x97, 0xd2, 0xa7, 0x54, 0x45, 0xf1, 0x35, 0x44, 0x38, 0x9e, 0xf1, 0x1b, 0xb6, 0x34, 0x00, 0x3c, 0x96, 0xee, 0x29, 0x00, 0xea, 0x2c, 0x0b}} ,
+ {{0xea, 0xda, 0x99, 0x9e, 0x19, 0x83, 0x66, 0x6d, 0xe9, 0x76, 0x87, 0x50, 0xd1, 0xfd, 0x3c, 0x60, 0x87, 0xc6, 0x41, 0xd9, 0x8e, 0xdb, 0x5e, 0xde, 0xaa, 0x9a, 0xd3, 0x28, 0xda, 0x95, 0xea, 0x47}}},
+{{{0xd0, 0x80, 0xba, 0x19, 0xae, 0x1d, 0xa9, 0x79, 0xf6, 0x3f, 0xac, 0x5d, 0x6f, 0x96, 0x1f, 0x2a, 0xce, 0x29, 0xb2, 0xff, 0x37, 0xf1, 0x94, 0x8f, 0x0c, 0xb5, 0x28, 0xba, 0x9a, 0x21, 0xf6, 0x66}} ,
+ {{0x02, 0xfb, 0x54, 0xb8, 0x05, 0xf3, 0x81, 0x52, 0x69, 0x34, 0x46, 0x9d, 0x86, 0x76, 0x8f, 0xd7, 0xf8, 0x6a, 0x66, 0xff, 0xe6, 0xa7, 0x90, 0xf7, 0x5e, 0xcd, 0x6a, 0x9b, 0x55, 0xfc, 0x9d, 0x48}}},
+{{{0xbd, 0xaa, 0x13, 0xe6, 0xcd, 0x45, 0x4a, 0xa4, 0x59, 0x0a, 0x64, 0xb1, 0x98, 0xd6, 0x34, 0x13, 0x04, 0xe6, 0x97, 0x94, 0x06, 0xcb, 0xd4, 0x4e, 0xbb, 0x96, 0xcd, 0xd1, 0x57, 0xd1, 0xe3, 0x06}} ,
+ {{0x7a, 0x6c, 0x45, 0x27, 0xc4, 0x93, 0x7f, 0x7d, 0x7c, 0x62, 0x50, 0x38, 0x3a, 0x6b, 0xb5, 0x88, 0xc6, 0xd9, 0xf1, 0x78, 0x19, 0xb9, 0x39, 0x93, 0x3d, 0xc9, 0xe0, 0x9c, 0x3c, 0xce, 0xf5, 0x72}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x24, 0xea, 0x23, 0x7d, 0x56, 0x2c, 0xe2, 0x59, 0x0e, 0x85, 0x60, 0x04, 0x88, 0x5a, 0x74, 0x1e, 0x4b, 0xef, 0x13, 0xda, 0x4c, 0xff, 0x83, 0x45, 0x85, 0x3f, 0x08, 0x95, 0x2c, 0x20, 0x13, 0x1f}} ,
+ {{0x48, 0x5f, 0x27, 0x90, 0x5c, 0x02, 0x42, 0xad, 0x78, 0x47, 0x5c, 0xb5, 0x7e, 0x08, 0x85, 0x00, 0xfa, 0x7f, 0xfd, 0xfd, 0xe7, 0x09, 0x11, 0xf2, 0x7e, 0x1b, 0x38, 0x6c, 0x35, 0x6d, 0x33, 0x66}}},
+{{{0x93, 0x03, 0x36, 0x81, 0xac, 0xe4, 0x20, 0x09, 0x35, 0x4c, 0x45, 0xb2, 0x1e, 0x4c, 0x14, 0x21, 0xe6, 0xe9, 0x8a, 0x7b, 0x8d, 0xfe, 0x1e, 0xc6, 0x3e, 0xc1, 0x35, 0xfa, 0xe7, 0x70, 0x4e, 0x1d}} ,
+ {{0x61, 0x2e, 0xc2, 0xdd, 0x95, 0x57, 0xd1, 0xab, 0x80, 0xe8, 0x63, 0x17, 0xb5, 0x48, 0xe4, 0x8a, 0x11, 0x9e, 0x72, 0xbe, 0x85, 0x8d, 0x51, 0x0a, 0xf2, 0x9f, 0xe0, 0x1c, 0xa9, 0x07, 0x28, 0x7b}}},
+{{{0xbb, 0x71, 0x14, 0x5e, 0x26, 0x8c, 0x3d, 0xc8, 0xe9, 0x7c, 0xd3, 0xd6, 0xd1, 0x2f, 0x07, 0x6d, 0xe6, 0xdf, 0xfb, 0x79, 0xd6, 0x99, 0x59, 0x96, 0x48, 0x40, 0x0f, 0x3a, 0x7b, 0xb2, 0xa0, 0x72}} ,
+ {{0x4e, 0x3b, 0x69, 0xc8, 0x43, 0x75, 0x51, 0x6c, 0x79, 0x56, 0xe4, 0xcb, 0xf7, 0xa6, 0x51, 0xc2, 0x2c, 0x42, 0x0b, 0xd4, 0x82, 0x20, 0x1c, 0x01, 0x08, 0x66, 0xd7, 0xbf, 0x04, 0x56, 0xfc, 0x02}}},
+{{{0x24, 0xe8, 0xb7, 0x60, 0xae, 0x47, 0x80, 0xfc, 0xe5, 0x23, 0xe7, 0xc2, 0xc9, 0x85, 0xe6, 0x98, 0xa0, 0x29, 0x4e, 0xe1, 0x84, 0x39, 0x2d, 0x95, 0x2c, 0xf3, 0x45, 0x3c, 0xff, 0xaf, 0x27, 0x4c}} ,
+ {{0x6b, 0xa6, 0xf5, 0x4b, 0x11, 0xbd, 0xba, 0x5b, 0x9e, 0xc4, 0xa4, 0x51, 0x1e, 0xbe, 0xd0, 0x90, 0x3a, 0x9c, 0xc2, 0x26, 0xb6, 0x1e, 0xf1, 0x95, 0x7d, 0xc8, 0x6d, 0x52, 0xe6, 0x99, 0x2c, 0x5f}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x85, 0xe0, 0x24, 0x32, 0xb4, 0xd1, 0xef, 0xfc, 0x69, 0xa2, 0xbf, 0x8f, 0x72, 0x2c, 0x95, 0xf6, 0xe4, 0x6e, 0x7d, 0x90, 0xf7, 0x57, 0x81, 0xa0, 0xf7, 0xda, 0xef, 0x33, 0x07, 0xe3, 0x6b, 0x78}} ,
+ {{0x36, 0x27, 0x3e, 0xc6, 0x12, 0x07, 0xab, 0x4e, 0xbe, 0x69, 0x9d, 0xb3, 0xbe, 0x08, 0x7c, 0x2a, 0x47, 0x08, 0xfd, 0xd4, 0xcd, 0x0e, 0x27, 0x34, 0x5b, 0x98, 0x34, 0x2f, 0x77, 0x5f, 0x3a, 0x65}}},
+{{{0x13, 0xaa, 0x2e, 0x4c, 0xf0, 0x22, 0xb8, 0x6c, 0xb3, 0x19, 0x4d, 0xeb, 0x6b, 0xd0, 0xa4, 0xc6, 0x9c, 0xdd, 0xc8, 0x5b, 0x81, 0x57, 0x89, 0xdf, 0x33, 0xa9, 0x68, 0x49, 0x80, 0xe4, 0xfe, 0x21}} ,
+ {{0x00, 0x17, 0x90, 0x30, 0xe9, 0xd3, 0x60, 0x30, 0x31, 0xc2, 0x72, 0x89, 0x7a, 0x36, 0xa5, 0xbd, 0x39, 0x83, 0x85, 0x50, 0xa1, 0x5d, 0x6c, 0x41, 0x1d, 0xb5, 0x2c, 0x07, 0x40, 0x77, 0x0b, 0x50}}},
+{{{0x64, 0x34, 0xec, 0xc0, 0x9e, 0x44, 0x41, 0xaf, 0xa0, 0x36, 0x05, 0x6d, 0xea, 0x30, 0x25, 0x46, 0x35, 0x24, 0x9d, 0x86, 0xbd, 0x95, 0xf1, 0x6a, 0x46, 0xd7, 0x94, 0x54, 0xf9, 0x3b, 0xbd, 0x5d}} ,
+ {{0x77, 0x5b, 0xe2, 0x37, 0xc7, 0xe1, 0x7c, 0x13, 0x8c, 0x9f, 0x7b, 0x7b, 0x2a, 0xce, 0x42, 0xa3, 0xb9, 0x2a, 0x99, 0xa8, 0xc0, 0xd8, 0x3c, 0x86, 0xb0, 0xfb, 0xe9, 0x76, 0x77, 0xf7, 0xf5, 0x56}}},
+{{{0xdf, 0xb3, 0x46, 0x11, 0x6e, 0x13, 0xb7, 0x28, 0x4e, 0x56, 0xdd, 0xf1, 0xac, 0xad, 0x58, 0xc3, 0xf8, 0x88, 0x94, 0x5e, 0x06, 0x98, 0xa1, 0xe4, 0x6a, 0xfb, 0x0a, 0x49, 0x5d, 0x8a, 0xfe, 0x77}} ,
+ {{0x46, 0x02, 0xf5, 0xa5, 0xaf, 0xc5, 0x75, 0x6d, 0xba, 0x45, 0x35, 0x0a, 0xfe, 0xc9, 0xac, 0x22, 0x91, 0x8d, 0x21, 0x95, 0x33, 0x03, 0xc0, 0x8a, 0x16, 0xf3, 0x39, 0xe0, 0x01, 0x0f, 0x53, 0x3c}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x34, 0x75, 0x37, 0x1f, 0x34, 0x4e, 0xa9, 0x1d, 0x68, 0x67, 0xf8, 0x49, 0x98, 0x96, 0xfc, 0x4c, 0x65, 0x97, 0xf7, 0x02, 0x4a, 0x52, 0x6c, 0x01, 0xbd, 0x48, 0xbb, 0x1b, 0xed, 0xa4, 0xe2, 0x53}} ,
+ {{0x59, 0xd5, 0x9b, 0x5a, 0xa2, 0x90, 0xd3, 0xb8, 0x37, 0x4c, 0x55, 0x82, 0x28, 0x08, 0x0f, 0x7f, 0xaa, 0x81, 0x65, 0xe0, 0x0c, 0x52, 0xc9, 0xa3, 0x32, 0x27, 0x64, 0xda, 0xfd, 0x34, 0x23, 0x5a}}},
+{{{0xb5, 0xb0, 0x0c, 0x4d, 0xb3, 0x7b, 0x23, 0xc8, 0x1f, 0x8a, 0x39, 0x66, 0xe6, 0xba, 0x4c, 0x10, 0x37, 0xca, 0x9c, 0x7c, 0x05, 0x9e, 0xff, 0xc0, 0xf8, 0x8e, 0xb1, 0x8f, 0x6f, 0x67, 0x18, 0x26}} ,
+ {{0x4b, 0x41, 0x13, 0x54, 0x23, 0x1a, 0xa4, 0x4e, 0xa9, 0x8b, 0x1e, 0x4b, 0xfc, 0x15, 0x24, 0xbb, 0x7e, 0xcb, 0xb6, 0x1e, 0x1b, 0xf5, 0xf2, 0xc8, 0x56, 0xec, 0x32, 0xa2, 0x60, 0x5b, 0xa0, 0x2a}}},
+{{{0xa4, 0x29, 0x47, 0x86, 0x2e, 0x92, 0x4f, 0x11, 0x4f, 0xf3, 0xb2, 0x5c, 0xd5, 0x3e, 0xa6, 0xb9, 0xc8, 0xe2, 0x33, 0x11, 0x1f, 0x01, 0x8f, 0xb0, 0x9b, 0xc7, 0xa5, 0xff, 0x83, 0x0f, 0x1e, 0x28}} ,
+ {{0x1d, 0x29, 0x7a, 0xa1, 0xec, 0x8e, 0xb5, 0xad, 0xea, 0x02, 0x68, 0x60, 0x74, 0x29, 0x1c, 0xa5, 0xcf, 0xc8, 0x3b, 0x7d, 0x8b, 0x2b, 0x7c, 0xad, 0xa4, 0x40, 0x17, 0x51, 0x59, 0x7c, 0x2e, 0x5d}}},
+{{{0x0a, 0x6c, 0x4f, 0xbc, 0x3e, 0x32, 0xe7, 0x4a, 0x1a, 0x13, 0xc1, 0x49, 0x38, 0xbf, 0xf7, 0xc2, 0xd3, 0x8f, 0x6b, 0xad, 0x52, 0xf7, 0xcf, 0xbc, 0x27, 0xcb, 0x40, 0x67, 0x76, 0xcd, 0x6d, 0x56}} ,
+ {{0xe5, 0xb0, 0x27, 0xad, 0xbe, 0x9b, 0xf2, 0xb5, 0x63, 0xde, 0x3a, 0x23, 0x95, 0xb7, 0x0a, 0x7e, 0xf3, 0x9e, 0x45, 0x6f, 0x19, 0x39, 0x75, 0x8f, 0x39, 0x3d, 0x0f, 0xc0, 0x9f, 0xf1, 0xe9, 0x51}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x88, 0xaa, 0x14, 0x24, 0x86, 0x94, 0x11, 0x12, 0x3e, 0x1a, 0xb5, 0xcc, 0xbb, 0xe0, 0x9c, 0xd5, 0x9c, 0x6d, 0xba, 0x58, 0x72, 0x8d, 0xfb, 0x22, 0x7b, 0x9f, 0x7c, 0x94, 0x30, 0xb3, 0x51, 0x21}} ,
+ {{0xf6, 0x74, 0x3d, 0xf2, 0xaf, 0xd0, 0x1e, 0x03, 0x7c, 0x23, 0x6b, 0xc9, 0xfc, 0x25, 0x70, 0x90, 0xdc, 0x9a, 0xa4, 0xfb, 0x49, 0xfc, 0x3d, 0x0a, 0x35, 0x38, 0x6f, 0xe4, 0x7e, 0x50, 0x01, 0x2a}}},
+{{{0xd6, 0xe3, 0x96, 0x61, 0x3a, 0xfd, 0xef, 0x9b, 0x1f, 0x90, 0xa4, 0x24, 0x14, 0x5b, 0xc8, 0xde, 0x50, 0xb1, 0x1d, 0xaf, 0xe8, 0x55, 0x8a, 0x87, 0x0d, 0xfe, 0xaa, 0x3b, 0x82, 0x2c, 0x8d, 0x7b}} ,
+ {{0x85, 0x0c, 0xaf, 0xf8, 0x83, 0x44, 0x49, 0xd9, 0x45, 0xcf, 0xf7, 0x48, 0xd9, 0x53, 0xb4, 0xf1, 0x65, 0xa0, 0xe1, 0xc3, 0xb3, 0x15, 0xed, 0x89, 0x9b, 0x4f, 0x62, 0xb3, 0x57, 0xa5, 0x45, 0x1c}}},
+{{{0x8f, 0x12, 0xea, 0xaf, 0xd1, 0x1f, 0x79, 0x10, 0x0b, 0xf6, 0xa3, 0x7b, 0xea, 0xac, 0x8b, 0x57, 0x32, 0x62, 0xe7, 0x06, 0x12, 0x51, 0xa0, 0x3b, 0x43, 0x5e, 0xa4, 0x20, 0x78, 0x31, 0xce, 0x0d}} ,
+ {{0x84, 0x7c, 0xc2, 0xa6, 0x91, 0x23, 0xce, 0xbd, 0xdc, 0xf9, 0xce, 0xd5, 0x75, 0x30, 0x22, 0xe6, 0xf9, 0x43, 0x62, 0x0d, 0xf7, 0x75, 0x9d, 0x7f, 0x8c, 0xff, 0x7d, 0xe4, 0x72, 0xac, 0x9f, 0x1c}}},
+{{{0x88, 0xc1, 0x99, 0xd0, 0x3c, 0x1c, 0x5d, 0xb4, 0xef, 0x13, 0x0f, 0x90, 0xb9, 0x36, 0x2f, 0x95, 0x95, 0xc6, 0xdc, 0xde, 0x0a, 0x51, 0xe2, 0x8d, 0xf3, 0xbc, 0x51, 0xec, 0xdf, 0xb1, 0xa2, 0x5f}} ,
+ {{0x2e, 0x68, 0xa1, 0x23, 0x7d, 0x9b, 0x40, 0x69, 0x85, 0x7b, 0x42, 0xbf, 0x90, 0x4b, 0xd6, 0x40, 0x2f, 0xd7, 0x52, 0x52, 0xb2, 0x21, 0xde, 0x64, 0xbd, 0x88, 0xc3, 0x6d, 0xa5, 0xfa, 0x81, 0x3f}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xfb, 0xfd, 0x47, 0x7b, 0x8a, 0x66, 0x9e, 0x79, 0x2e, 0x64, 0x82, 0xef, 0xf7, 0x21, 0xec, 0xf6, 0xd8, 0x86, 0x09, 0x31, 0x7c, 0xdd, 0x03, 0x6a, 0x58, 0xa0, 0x77, 0xb7, 0x9b, 0x8c, 0x87, 0x1f}} ,
+ {{0x55, 0x47, 0xe4, 0xa8, 0x3d, 0x55, 0x21, 0x34, 0xab, 0x1d, 0xae, 0xe0, 0xf4, 0xea, 0xdb, 0xc5, 0xb9, 0x58, 0xbf, 0xc4, 0x2a, 0x89, 0x31, 0x1a, 0xf4, 0x2d, 0xe1, 0xca, 0x37, 0x99, 0x47, 0x59}}},
+{{{0xc7, 0xca, 0x63, 0xc1, 0x49, 0xa9, 0x35, 0x45, 0x55, 0x7e, 0xda, 0x64, 0x32, 0x07, 0x50, 0xf7, 0x32, 0xac, 0xde, 0x75, 0x58, 0x9b, 0x11, 0xb2, 0x3a, 0x1f, 0xf5, 0xf7, 0x79, 0x04, 0xe6, 0x08}} ,
+ {{0x46, 0xfa, 0x22, 0x4b, 0xfa, 0xe1, 0xfe, 0x96, 0xfc, 0x67, 0xba, 0x67, 0x97, 0xc4, 0xe7, 0x1b, 0x86, 0x90, 0x5f, 0xee, 0xf4, 0x5b, 0x11, 0xb2, 0xcd, 0xad, 0xee, 0xc2, 0x48, 0x6c, 0x2b, 0x1b}}},
+{{{0xe3, 0x39, 0x62, 0xb4, 0x4f, 0x31, 0x04, 0xc9, 0xda, 0xd5, 0x73, 0x51, 0x57, 0xc5, 0xb8, 0xf3, 0xa3, 0x43, 0x70, 0xe4, 0x61, 0x81, 0x84, 0xe2, 0xbb, 0xbf, 0x4f, 0x9e, 0xa4, 0x5e, 0x74, 0x06}} ,
+ {{0x29, 0xac, 0xff, 0x27, 0xe0, 0x59, 0xbe, 0x39, 0x9c, 0x0d, 0x83, 0xd7, 0x10, 0x0b, 0x15, 0xb7, 0xe1, 0xc2, 0x2c, 0x30, 0x73, 0x80, 0x3a, 0x7d, 0x5d, 0xab, 0x58, 0x6b, 0xc1, 0xf0, 0xf4, 0x22}}},
+{{{0xfe, 0x7f, 0xfb, 0x35, 0x7d, 0xc6, 0x01, 0x23, 0x28, 0xc4, 0x02, 0xac, 0x1f, 0x42, 0xb4, 0x9d, 0xfc, 0x00, 0x94, 0xa5, 0xee, 0xca, 0xda, 0x97, 0x09, 0x41, 0x77, 0x87, 0x5d, 0x7b, 0x87, 0x78}} ,
+ {{0xf5, 0xfb, 0x90, 0x2d, 0x81, 0x19, 0x9e, 0x2f, 0x6d, 0x85, 0x88, 0x8c, 0x40, 0x5c, 0x77, 0x41, 0x4d, 0x01, 0x19, 0x76, 0x60, 0xe8, 0x4c, 0x48, 0xe4, 0x33, 0x83, 0x32, 0x6c, 0xb4, 0x41, 0x03}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xff, 0x10, 0xc2, 0x09, 0x4f, 0x6e, 0xf4, 0xd2, 0xdf, 0x7e, 0xca, 0x7b, 0x1c, 0x1d, 0xba, 0xa3, 0xb6, 0xda, 0x67, 0x33, 0xd4, 0x87, 0x36, 0x4b, 0x11, 0x20, 0x05, 0xa6, 0x29, 0xc1, 0x87, 0x17}} ,
+ {{0xf6, 0x96, 0xca, 0x2f, 0xda, 0x38, 0xa7, 0x1b, 0xfc, 0xca, 0x7d, 0xfe, 0x08, 0x89, 0xe2, 0x47, 0x2b, 0x6a, 0x5d, 0x4b, 0xfa, 0xa1, 0xb4, 0xde, 0xb6, 0xc2, 0x31, 0x51, 0xf5, 0xe0, 0xa4, 0x0b}}},
+{{{0x5c, 0xe5, 0xc6, 0x04, 0x8e, 0x2b, 0x57, 0xbe, 0x38, 0x85, 0x23, 0xcb, 0xb7, 0xbe, 0x4f, 0xa9, 0xd3, 0x6e, 0x12, 0xaa, 0xd5, 0xb2, 0x2e, 0x93, 0x29, 0x9a, 0x4a, 0x88, 0x18, 0x43, 0xf5, 0x01}} ,
+ {{0x50, 0xfc, 0xdb, 0xa2, 0x59, 0x21, 0x8d, 0xbd, 0x7e, 0x33, 0xae, 0x2f, 0x87, 0x1a, 0xd0, 0x97, 0xc7, 0x0d, 0x4d, 0x63, 0x01, 0xef, 0x05, 0x84, 0xec, 0x40, 0xdd, 0xa8, 0x0a, 0x4f, 0x70, 0x0b}}},
+{{{0x41, 0x69, 0x01, 0x67, 0x5c, 0xd3, 0x8a, 0xc5, 0xcf, 0x3f, 0xd1, 0x57, 0xd1, 0x67, 0x3e, 0x01, 0x39, 0xb5, 0xcb, 0x81, 0x56, 0x96, 0x26, 0xb6, 0xc2, 0xe7, 0x5c, 0xfb, 0x63, 0x97, 0x58, 0x06}} ,
+ {{0x0c, 0x0e, 0xf3, 0xba, 0xf0, 0xe5, 0xba, 0xb2, 0x57, 0x77, 0xc6, 0x20, 0x9b, 0x89, 0x24, 0xbe, 0xf2, 0x9c, 0x8a, 0xba, 0x69, 0xc1, 0xf1, 0xb0, 0x4f, 0x2a, 0x05, 0x9a, 0xee, 0x10, 0x7e, 0x36}}},
+{{{0x3f, 0x26, 0xe9, 0x40, 0xe9, 0x03, 0xad, 0x06, 0x69, 0x91, 0xe0, 0xd1, 0x89, 0x60, 0x84, 0x79, 0xde, 0x27, 0x6d, 0xe6, 0x76, 0xbd, 0xea, 0xe6, 0xae, 0x48, 0xc3, 0x67, 0xc0, 0x57, 0xcd, 0x2f}} ,
+ {{0x7f, 0xc1, 0xdc, 0xb9, 0xc7, 0xbc, 0x86, 0x3d, 0x55, 0x4b, 0x28, 0x7a, 0xfb, 0x4d, 0xc7, 0xf8, 0xbc, 0x67, 0x2a, 0x60, 0x4d, 0x8f, 0x07, 0x0b, 0x1a, 0x17, 0xbf, 0xfa, 0xac, 0xa7, 0x3d, 0x1a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x91, 0x3f, 0xed, 0x5e, 0x18, 0x78, 0x3f, 0x23, 0x2c, 0x0d, 0x8c, 0x44, 0x00, 0xe8, 0xfb, 0xe9, 0x8e, 0xd6, 0xd1, 0x36, 0x58, 0x57, 0x9e, 0xae, 0x4b, 0x5c, 0x0b, 0x07, 0xbc, 0x6b, 0x55, 0x2b}} ,
+ {{0x6f, 0x4d, 0x17, 0xd7, 0xe1, 0x84, 0xd9, 0x78, 0xb1, 0x90, 0xfd, 0x2e, 0xb3, 0xb5, 0x19, 0x3f, 0x1b, 0xfa, 0xc0, 0x68, 0xb3, 0xdd, 0x00, 0x2e, 0x89, 0xbd, 0x7e, 0x80, 0x32, 0x13, 0xa0, 0x7b}}},
+{{{0x1a, 0x6f, 0x40, 0xaf, 0x44, 0x44, 0xb0, 0x43, 0x8f, 0x0d, 0xd0, 0x1e, 0xc4, 0x0b, 0x19, 0x5d, 0x8e, 0xfe, 0xc1, 0xf3, 0xc5, 0x5c, 0x91, 0xf8, 0x04, 0x4e, 0xbe, 0x90, 0xb4, 0x47, 0x5c, 0x3f}} ,
+ {{0xb0, 0x3b, 0x2c, 0xf3, 0xfe, 0x32, 0x71, 0x07, 0x3f, 0xaa, 0xba, 0x45, 0x60, 0xa8, 0x8d, 0xea, 0x54, 0xcb, 0x39, 0x10, 0xb4, 0xf2, 0x8b, 0xd2, 0x14, 0x82, 0x42, 0x07, 0x8e, 0xe9, 0x7c, 0x53}}},
+{{{0xb0, 0xae, 0xc1, 0x8d, 0xc9, 0x8f, 0xb9, 0x7a, 0x77, 0xef, 0xba, 0x79, 0xa0, 0x3c, 0xa8, 0xf5, 0x6a, 0xe2, 0x3f, 0x5d, 0x00, 0xe3, 0x4b, 0x45, 0x24, 0x7b, 0x43, 0x78, 0x55, 0x1d, 0x2b, 0x1e}} ,
+ {{0x01, 0xb8, 0xd6, 0x16, 0x67, 0xa0, 0x15, 0xb9, 0xe1, 0x58, 0xa4, 0xa7, 0x31, 0x37, 0x77, 0x2f, 0x8b, 0x12, 0x9f, 0xf4, 0x3f, 0xc7, 0x36, 0x66, 0xd2, 0xa8, 0x56, 0xf7, 0x7f, 0x74, 0xc6, 0x41}}},
+{{{0x5d, 0xf8, 0xb4, 0xa8, 0x30, 0xdd, 0xcc, 0x38, 0xa5, 0xd3, 0xca, 0xd8, 0xd1, 0xf8, 0xb2, 0x31, 0x91, 0xd4, 0x72, 0x05, 0x57, 0x4a, 0x3b, 0x82, 0x4a, 0xc6, 0x68, 0x20, 0xe2, 0x18, 0x41, 0x61}} ,
+ {{0x19, 0xd4, 0x8d, 0x47, 0x29, 0x12, 0x65, 0xb0, 0x11, 0x78, 0x47, 0xb5, 0xcb, 0xa3, 0xa5, 0xfa, 0x05, 0x85, 0x54, 0xa9, 0x33, 0x97, 0x8d, 0x2b, 0xc2, 0xfe, 0x99, 0x35, 0x28, 0xe5, 0xeb, 0x63}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xb1, 0x3f, 0x3f, 0xef, 0xd8, 0xf4, 0xfc, 0xb3, 0xa0, 0x60, 0x50, 0x06, 0x2b, 0x29, 0x52, 0x70, 0x15, 0x0b, 0x24, 0x24, 0xf8, 0x5f, 0x79, 0x18, 0xcc, 0xff, 0x89, 0x99, 0x84, 0xa1, 0xae, 0x13}} ,
+ {{0x44, 0x1f, 0xb8, 0xc2, 0x01, 0xc1, 0x30, 0x19, 0x55, 0x05, 0x60, 0x10, 0xa4, 0x6c, 0x2d, 0x67, 0x70, 0xe5, 0x25, 0x1b, 0xf2, 0xbf, 0xdd, 0xfb, 0x70, 0x2b, 0xa1, 0x8c, 0x9c, 0x94, 0x84, 0x08}}},
+{{{0xe7, 0xc4, 0x43, 0x4d, 0xc9, 0x2b, 0x69, 0x5d, 0x1d, 0x3c, 0xaf, 0xbb, 0x43, 0x38, 0x4e, 0x98, 0x3d, 0xed, 0x0d, 0x21, 0x03, 0xfd, 0xf0, 0x99, 0x47, 0x04, 0xb0, 0x98, 0x69, 0x55, 0x72, 0x0f}} ,
+ {{0x5e, 0xdf, 0x15, 0x53, 0x3b, 0x86, 0x80, 0xb0, 0xf1, 0x70, 0x68, 0x8f, 0x66, 0x7c, 0x0e, 0x49, 0x1a, 0xd8, 0x6b, 0xfe, 0x4e, 0xef, 0xca, 0x47, 0xd4, 0x03, 0xc1, 0x37, 0x50, 0x9c, 0xc1, 0x16}}},
+{{{0xcd, 0x24, 0xc6, 0x3e, 0x0c, 0x82, 0x9b, 0x91, 0x2b, 0x61, 0x4a, 0xb2, 0x0f, 0x88, 0x55, 0x5f, 0x5a, 0x57, 0xff, 0xe5, 0x74, 0x0b, 0x13, 0x43, 0x00, 0xd8, 0x6b, 0xcf, 0xd2, 0x15, 0x03, 0x2c}} ,
+ {{0xdc, 0xff, 0x15, 0x61, 0x2f, 0x4a, 0x2f, 0x62, 0xf2, 0x04, 0x2f, 0xb5, 0x0c, 0xb7, 0x1e, 0x3f, 0x74, 0x1a, 0x0f, 0xd7, 0xea, 0xcd, 0xd9, 0x7d, 0xf6, 0x12, 0x0e, 0x2f, 0xdb, 0x5a, 0x3b, 0x16}}},
+{{{0x1b, 0x37, 0x47, 0xe3, 0xf5, 0x9e, 0xea, 0x2c, 0x2a, 0xe7, 0x82, 0x36, 0xf4, 0x1f, 0x81, 0x47, 0x92, 0x4b, 0x69, 0x0e, 0x11, 0x8c, 0x5d, 0x53, 0x5b, 0x81, 0x27, 0x08, 0xbc, 0xa0, 0xae, 0x25}} ,
+ {{0x69, 0x32, 0xa1, 0x05, 0x11, 0x42, 0x00, 0xd2, 0x59, 0xac, 0x4d, 0x62, 0x8b, 0x13, 0xe2, 0x50, 0x5d, 0xa0, 0x9d, 0x9b, 0xfd, 0xbb, 0x12, 0x41, 0x75, 0x41, 0x9e, 0xcc, 0xdc, 0xc7, 0xdc, 0x5d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xd9, 0xe3, 0x38, 0x06, 0x46, 0x70, 0x82, 0x5e, 0x28, 0x49, 0x79, 0xff, 0x25, 0xd2, 0x4e, 0x29, 0x8d, 0x06, 0xb0, 0x23, 0xae, 0x9b, 0x66, 0xe4, 0x7d, 0xc0, 0x70, 0x91, 0xa3, 0xfc, 0xec, 0x4e}} ,
+ {{0x62, 0x12, 0x37, 0x6a, 0x30, 0xf6, 0x1e, 0xfb, 0x14, 0x5c, 0x0d, 0x0e, 0xb7, 0x81, 0x6a, 0xe7, 0x08, 0x05, 0xac, 0xaa, 0x38, 0x46, 0xe2, 0x73, 0xea, 0x4b, 0x07, 0x81, 0x43, 0x7c, 0x9e, 0x5e}}},
+{{{0xfc, 0xf9, 0x21, 0x4f, 0x2e, 0x76, 0x9b, 0x1f, 0x28, 0x60, 0x77, 0x43, 0x32, 0x9d, 0xbe, 0x17, 0x30, 0x2a, 0xc6, 0x18, 0x92, 0x66, 0x62, 0x30, 0x98, 0x40, 0x11, 0xa6, 0x7f, 0x18, 0x84, 0x28}} ,
+ {{0x3f, 0xab, 0xd3, 0xf4, 0x8a, 0x76, 0xa1, 0x3c, 0xca, 0x2d, 0x49, 0xc3, 0xea, 0x08, 0x0b, 0x85, 0x17, 0x2a, 0xc3, 0x6c, 0x08, 0xfd, 0x57, 0x9f, 0x3d, 0x5f, 0xdf, 0x67, 0x68, 0x42, 0x00, 0x32}}},
+{{{0x51, 0x60, 0x1b, 0x06, 0x4f, 0x8a, 0x21, 0xba, 0x38, 0xa8, 0xba, 0xd6, 0x40, 0xf6, 0xe9, 0x9b, 0x76, 0x4d, 0x56, 0x21, 0x5b, 0x0a, 0x9b, 0x2e, 0x4f, 0x3d, 0x81, 0x32, 0x08, 0x9f, 0x97, 0x5b}} ,
+ {{0xe5, 0x44, 0xec, 0x06, 0x9d, 0x90, 0x79, 0x9f, 0xd3, 0xe0, 0x79, 0xaf, 0x8f, 0x10, 0xfd, 0xdd, 0x04, 0xae, 0x27, 0x97, 0x46, 0x33, 0x79, 0xea, 0xb8, 0x4e, 0xca, 0x5a, 0x59, 0x57, 0xe1, 0x0e}}},
+{{{0x1a, 0xda, 0xf3, 0xa5, 0x41, 0x43, 0x28, 0xfc, 0x7e, 0xe7, 0x71, 0xea, 0xc6, 0x3b, 0x59, 0xcc, 0x2e, 0xd3, 0x40, 0xec, 0xb3, 0x13, 0x6f, 0x44, 0xcd, 0x13, 0xb2, 0x37, 0xf2, 0x6e, 0xd9, 0x1c}} ,
+ {{0xe3, 0xdb, 0x60, 0xcd, 0x5c, 0x4a, 0x18, 0x0f, 0xef, 0x73, 0x36, 0x71, 0x8c, 0xf6, 0x11, 0xb4, 0xd8, 0xce, 0x17, 0x5e, 0x4f, 0x26, 0x77, 0x97, 0x5f, 0xcb, 0xef, 0x91, 0xeb, 0x6a, 0x62, 0x7a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x18, 0x4a, 0xa2, 0x97, 0x08, 0x81, 0x2d, 0x83, 0xc4, 0xcc, 0xf0, 0x83, 0x7e, 0xec, 0x0d, 0x95, 0x4c, 0x5b, 0xfb, 0xfa, 0x98, 0x80, 0x4a, 0x66, 0x56, 0x0c, 0x51, 0xb3, 0xf2, 0x04, 0x5d, 0x27}} ,
+ {{0x3b, 0xb9, 0xb8, 0x06, 0x5a, 0x2e, 0xfe, 0xc3, 0x82, 0x37, 0x9c, 0xa3, 0x11, 0x1f, 0x9c, 0xa6, 0xda, 0x63, 0x48, 0x9b, 0xad, 0xde, 0x2d, 0xa6, 0xbc, 0x6e, 0x32, 0xda, 0x27, 0x65, 0xdd, 0x57}}},
+{{{0x84, 0x4f, 0x37, 0x31, 0x7d, 0x2e, 0xbc, 0xad, 0x87, 0x07, 0x2a, 0x6b, 0x37, 0xfc, 0x5f, 0xeb, 0x4e, 0x75, 0x35, 0xa6, 0xde, 0xab, 0x0a, 0x19, 0x3a, 0xb7, 0xb1, 0xef, 0x92, 0x6a, 0x3b, 0x3c}} ,
+ {{0x3b, 0xb2, 0x94, 0x6d, 0x39, 0x60, 0xac, 0xee, 0xe7, 0x81, 0x1a, 0x3b, 0x76, 0x87, 0x5c, 0x05, 0x94, 0x2a, 0x45, 0xb9, 0x80, 0xe9, 0x22, 0xb1, 0x07, 0xcb, 0x40, 0x9e, 0x70, 0x49, 0x6d, 0x12}}},
+{{{0xfd, 0x18, 0x78, 0x84, 0xa8, 0x4c, 0x7d, 0x6e, 0x59, 0xa6, 0xe5, 0x74, 0xf1, 0x19, 0xa6, 0x84, 0x2e, 0x51, 0xc1, 0x29, 0x13, 0xf2, 0x14, 0x6b, 0x5d, 0x53, 0x51, 0xf7, 0xef, 0xbf, 0x01, 0x22}} ,
+ {{0xa4, 0x4b, 0x62, 0x4c, 0xe6, 0xfd, 0x72, 0x07, 0xf2, 0x81, 0xfc, 0xf2, 0xbd, 0x12, 0x7c, 0x68, 0x76, 0x2a, 0xba, 0xf5, 0x65, 0xb1, 0x1f, 0x17, 0x0a, 0x38, 0xb0, 0xbf, 0xc0, 0xf8, 0xf4, 0x2a}}},
+{{{0x55, 0x60, 0x55, 0x5b, 0xe4, 0x1d, 0x71, 0x4c, 0x9d, 0x5b, 0x9f, 0x70, 0xa6, 0x85, 0x9a, 0x2c, 0xa0, 0xe2, 0x32, 0x48, 0xce, 0x9e, 0x2a, 0xa5, 0x07, 0x3b, 0xc7, 0x6c, 0x86, 0x77, 0xde, 0x3c}} ,
+ {{0xf7, 0x18, 0x7a, 0x96, 0x7e, 0x43, 0x57, 0xa9, 0x55, 0xfc, 0x4e, 0xb6, 0x72, 0x00, 0xf2, 0xe4, 0xd7, 0x52, 0xd3, 0xd3, 0xb6, 0x85, 0xf6, 0x71, 0xc7, 0x44, 0x3f, 0x7f, 0xd7, 0xb3, 0xf2, 0x79}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x46, 0xca, 0xa7, 0x55, 0x7b, 0x79, 0xf3, 0xca, 0x5a, 0x65, 0xf6, 0xed, 0x50, 0x14, 0x7b, 0xe4, 0xc4, 0x2a, 0x65, 0x9e, 0xe2, 0xf9, 0xca, 0xa7, 0x22, 0x26, 0x53, 0xcb, 0x21, 0x5b, 0xa7, 0x31}} ,
+ {{0x90, 0xd7, 0xc5, 0x26, 0x08, 0xbd, 0xb0, 0x53, 0x63, 0x58, 0xc3, 0x31, 0x5e, 0x75, 0x46, 0x15, 0x91, 0xa6, 0xf8, 0x2f, 0x1a, 0x08, 0x65, 0x88, 0x2f, 0x98, 0x04, 0xf1, 0x7c, 0x6e, 0x00, 0x77}}},
+{{{0x81, 0x21, 0x61, 0x09, 0xf6, 0x4e, 0xf1, 0x92, 0xee, 0x63, 0x61, 0x73, 0x87, 0xc7, 0x54, 0x0e, 0x42, 0x4b, 0xc9, 0x47, 0xd1, 0xb8, 0x7e, 0x91, 0x75, 0x37, 0x99, 0x28, 0xb8, 0xdd, 0x7f, 0x50}} ,
+ {{0x89, 0x8f, 0xc0, 0xbe, 0x5d, 0xd6, 0x9f, 0xa0, 0xf0, 0x9d, 0x81, 0xce, 0x3a, 0x7b, 0x98, 0x58, 0xbb, 0xd7, 0x78, 0xc8, 0x3f, 0x13, 0xf1, 0x74, 0x19, 0xdf, 0xf8, 0x98, 0x89, 0x5d, 0xfa, 0x5f}}},
+{{{0x9e, 0x35, 0x85, 0x94, 0x47, 0x1f, 0x90, 0x15, 0x26, 0xd0, 0x84, 0xed, 0x8a, 0x80, 0xf7, 0x63, 0x42, 0x86, 0x27, 0xd7, 0xf4, 0x75, 0x58, 0xdc, 0x9c, 0xc0, 0x22, 0x7e, 0x20, 0x35, 0xfd, 0x1f}} ,
+ {{0x68, 0x0e, 0x6f, 0x97, 0xba, 0x70, 0xbb, 0xa3, 0x0e, 0xe5, 0x0b, 0x12, 0xf4, 0xa2, 0xdc, 0x47, 0xf8, 0xe6, 0xd0, 0x23, 0x6c, 0x33, 0xa8, 0x99, 0x46, 0x6e, 0x0f, 0x44, 0xba, 0x76, 0x48, 0x0f}}},
+{{{0xa3, 0x2a, 0x61, 0x37, 0xe2, 0x59, 0x12, 0x0e, 0x27, 0xba, 0x64, 0x43, 0xae, 0xc0, 0x42, 0x69, 0x79, 0xa4, 0x1e, 0x29, 0x8b, 0x15, 0xeb, 0xf8, 0xaf, 0xd4, 0xa2, 0x68, 0x33, 0xb5, 0x7a, 0x24}} ,
+ {{0x2c, 0x19, 0x33, 0xdd, 0x1b, 0xab, 0xec, 0x01, 0xb0, 0x23, 0xf8, 0x42, 0x2b, 0x06, 0x88, 0xea, 0x3d, 0x2d, 0x00, 0x2a, 0x78, 0x45, 0x4d, 0x38, 0xed, 0x2e, 0x2e, 0x44, 0x49, 0xed, 0xcb, 0x33}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xa0, 0x68, 0xe8, 0x41, 0x8f, 0x91, 0xf8, 0x11, 0x13, 0x90, 0x2e, 0xa7, 0xab, 0x30, 0xef, 0xad, 0xa0, 0x61, 0x00, 0x88, 0xef, 0xdb, 0xce, 0x5b, 0x5c, 0xbb, 0x62, 0xc8, 0x56, 0xf9, 0x00, 0x73}} ,
+ {{0x3f, 0x60, 0xc1, 0x82, 0x2d, 0xa3, 0x28, 0x58, 0x24, 0x9e, 0x9f, 0xe3, 0x70, 0xcc, 0x09, 0x4e, 0x1a, 0x3f, 0x11, 0x11, 0x15, 0x07, 0x3c, 0xa4, 0x41, 0xe0, 0x65, 0xa3, 0x0a, 0x41, 0x6d, 0x11}}},
+{{{0x31, 0x40, 0x01, 0x52, 0x56, 0x94, 0x5b, 0x28, 0x8a, 0xaa, 0x52, 0xee, 0xd8, 0x0a, 0x05, 0x8d, 0xcd, 0xb5, 0xaa, 0x2e, 0x38, 0xaa, 0xb7, 0x87, 0xf7, 0x2b, 0xfb, 0x04, 0xcb, 0x84, 0x3d, 0x54}} ,
+ {{0x20, 0xef, 0x59, 0xde, 0xa4, 0x2b, 0x93, 0x6e, 0x2e, 0xec, 0x42, 0x9a, 0xd4, 0x2d, 0xf4, 0x46, 0x58, 0x27, 0x2b, 0x18, 0x8f, 0x83, 0x3d, 0x69, 0x9e, 0xd4, 0x3e, 0xb6, 0xc5, 0xfd, 0x58, 0x03}}},
+{{{0x33, 0x89, 0xc9, 0x63, 0x62, 0x1c, 0x17, 0xb4, 0x60, 0xc4, 0x26, 0x68, 0x09, 0xc3, 0x2e, 0x37, 0x0f, 0x7b, 0xb4, 0x9c, 0xb6, 0xf9, 0xfb, 0xd4, 0x51, 0x78, 0xc8, 0x63, 0xea, 0x77, 0x47, 0x07}} ,
+ {{0x32, 0xb4, 0x18, 0x47, 0x79, 0xcb, 0xd4, 0x5a, 0x07, 0x14, 0x0f, 0xa0, 0xd5, 0xac, 0xd0, 0x41, 0x40, 0xab, 0x61, 0x23, 0xe5, 0x2a, 0x2a, 0x6f, 0xf7, 0xa8, 0xd4, 0x76, 0xef, 0xe7, 0x45, 0x6c}}},
+{{{0xa1, 0x5e, 0x60, 0x4f, 0xfb, 0xe1, 0x70, 0x6a, 0x1f, 0x55, 0x4f, 0x09, 0xb4, 0x95, 0x33, 0x36, 0xc6, 0x81, 0x01, 0x18, 0x06, 0x25, 0x27, 0xa4, 0xb4, 0x24, 0xa4, 0x86, 0x03, 0x4c, 0xac, 0x02}} ,
+ {{0x77, 0x38, 0xde, 0xd7, 0x60, 0x48, 0x07, 0xf0, 0x74, 0xa8, 0xff, 0x54, 0xe5, 0x30, 0x43, 0xff, 0x77, 0xfb, 0x21, 0x07, 0xff, 0xb2, 0x07, 0x6b, 0xe4, 0xe5, 0x30, 0xfc, 0x19, 0x6c, 0xa3, 0x01}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x13, 0xc5, 0x2c, 0xac, 0xd3, 0x83, 0x82, 0x7c, 0x29, 0xf7, 0x05, 0xa5, 0x00, 0xb6, 0x1f, 0x86, 0x55, 0xf4, 0xd6, 0x2f, 0x0c, 0x99, 0xd0, 0x65, 0x9b, 0x6b, 0x46, 0x0d, 0x43, 0xf8, 0x16, 0x28}} ,
+ {{0x1e, 0x7f, 0xb4, 0x74, 0x7e, 0xb1, 0x89, 0x4f, 0x18, 0x5a, 0xab, 0x64, 0x06, 0xdf, 0x45, 0x87, 0xe0, 0x6a, 0xc6, 0xf0, 0x0e, 0xc9, 0x24, 0x35, 0x38, 0xea, 0x30, 0x54, 0xb4, 0xc4, 0x52, 0x54}}},
+{{{0xe9, 0x9f, 0xdc, 0x3f, 0xc1, 0x89, 0x44, 0x74, 0x27, 0xe4, 0xc1, 0x90, 0xff, 0x4a, 0xa7, 0x3c, 0xee, 0xcd, 0xf4, 0x1d, 0x25, 0x94, 0x7f, 0x63, 0x16, 0x48, 0xbc, 0x64, 0xfe, 0x95, 0xc4, 0x0c}} ,
+ {{0x8b, 0x19, 0x75, 0x6e, 0x03, 0x06, 0x5e, 0x6a, 0x6f, 0x1a, 0x8c, 0xe3, 0xd3, 0x28, 0xf2, 0xe0, 0xb9, 0x7a, 0x43, 0x69, 0xe6, 0xd3, 0xc0, 0xfe, 0x7e, 0x97, 0xab, 0x6c, 0x7b, 0x8e, 0x13, 0x42}}},
+{{{0xd4, 0xca, 0x70, 0x3d, 0xab, 0xfb, 0x5f, 0x5e, 0x00, 0x0c, 0xcc, 0x77, 0x22, 0xf8, 0x78, 0x55, 0xae, 0x62, 0x35, 0xfb, 0x9a, 0xc6, 0x03, 0xe4, 0x0c, 0xee, 0xab, 0xc7, 0xc0, 0x89, 0x87, 0x54}} ,
+ {{0x32, 0xad, 0xae, 0x85, 0x58, 0x43, 0xb8, 0xb1, 0xe6, 0x3e, 0x00, 0x9c, 0x78, 0x88, 0x56, 0xdb, 0x9c, 0xfc, 0x79, 0xf6, 0xf9, 0x41, 0x5f, 0xb7, 0xbc, 0x11, 0xf9, 0x20, 0x36, 0x1c, 0x53, 0x2b}}},
+{{{0x5a, 0x20, 0x5b, 0xa1, 0xa5, 0x44, 0x91, 0x24, 0x02, 0x63, 0x12, 0x64, 0xb8, 0x55, 0xf6, 0xde, 0x2c, 0xdb, 0x47, 0xb8, 0xc6, 0x0a, 0xc3, 0x00, 0x78, 0x93, 0xd8, 0xf5, 0xf5, 0x18, 0x28, 0x0a}} ,
+ {{0xd6, 0x1b, 0x9a, 0x6c, 0xe5, 0x46, 0xea, 0x70, 0x96, 0x8d, 0x4e, 0x2a, 0x52, 0x21, 0x26, 0x4b, 0xb1, 0xbb, 0x0f, 0x7c, 0xa9, 0x9b, 0x04, 0xbb, 0x51, 0x08, 0xf1, 0x9a, 0xa4, 0x76, 0x7c, 0x18}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xfa, 0x94, 0xf7, 0x40, 0xd0, 0xd7, 0xeb, 0xa9, 0x82, 0x36, 0xd5, 0x15, 0xb9, 0x33, 0x7a, 0xbf, 0x8a, 0xf2, 0x63, 0xaa, 0x37, 0xf5, 0x59, 0xac, 0xbd, 0xbb, 0x32, 0x36, 0xbe, 0x73, 0x99, 0x38}} ,
+ {{0x2c, 0xb3, 0xda, 0x7a, 0xd8, 0x3d, 0x99, 0xca, 0xd2, 0xf4, 0xda, 0x99, 0x8e, 0x4f, 0x98, 0xb7, 0xf4, 0xae, 0x3e, 0x9f, 0x8e, 0x35, 0x60, 0xa4, 0x33, 0x75, 0xa4, 0x04, 0x93, 0xb1, 0x6b, 0x4d}}},
+{{{0x97, 0x9d, 0xa8, 0xcd, 0x97, 0x7b, 0x9d, 0xb9, 0xe7, 0xa5, 0xef, 0xfd, 0xa8, 0x42, 0x6b, 0xc3, 0x62, 0x64, 0x7d, 0xa5, 0x1b, 0xc9, 0x9e, 0xd2, 0x45, 0xb9, 0xee, 0x03, 0xb0, 0xbf, 0xc0, 0x68}} ,
+ {{0xed, 0xb7, 0x84, 0x2c, 0xf6, 0xd3, 0xa1, 0x6b, 0x24, 0x6d, 0x87, 0x56, 0x97, 0x59, 0x79, 0x62, 0x9f, 0xac, 0xed, 0xf3, 0xc9, 0x89, 0x21, 0x2e, 0x04, 0xb3, 0xcc, 0x2f, 0xbe, 0xd6, 0x0a, 0x4b}}},
+{{{0x39, 0x61, 0x05, 0xed, 0x25, 0x89, 0x8b, 0x5d, 0x1b, 0xcb, 0x0c, 0x55, 0xf4, 0x6a, 0x00, 0x8a, 0x46, 0xe8, 0x1e, 0xc6, 0x83, 0xc8, 0x5a, 0x76, 0xdb, 0xcc, 0x19, 0x7a, 0xcc, 0x67, 0x46, 0x0b}} ,
+ {{0x53, 0xcf, 0xc2, 0xa1, 0xad, 0x6a, 0xf3, 0xcd, 0x8f, 0xc9, 0xde, 0x1c, 0xf8, 0x6c, 0x8f, 0xf8, 0x76, 0x42, 0xe7, 0xfe, 0xb2, 0x72, 0x21, 0x0a, 0x66, 0x74, 0x8f, 0xb7, 0xeb, 0xe4, 0x6f, 0x01}}},
+{{{0x22, 0x8c, 0x6b, 0xbe, 0xfc, 0x4d, 0x70, 0x62, 0x6e, 0x52, 0x77, 0x99, 0x88, 0x7e, 0x7b, 0x57, 0x7a, 0x0d, 0xfe, 0xdc, 0x72, 0x92, 0xf1, 0x68, 0x1d, 0x97, 0xd7, 0x7c, 0x8d, 0x53, 0x10, 0x37}} ,
+ {{0x53, 0x88, 0x77, 0x02, 0xca, 0x27, 0xa8, 0xe5, 0x45, 0xe2, 0xa8, 0x48, 0x2a, 0xab, 0x18, 0xca, 0xea, 0x2d, 0x2a, 0x54, 0x17, 0x37, 0x32, 0x09, 0xdc, 0xe0, 0x4a, 0xb7, 0x7d, 0x82, 0x10, 0x7d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x8a, 0x64, 0x1e, 0x14, 0x0a, 0x57, 0xd4, 0xda, 0x5c, 0x96, 0x9b, 0x01, 0x4c, 0x67, 0xbf, 0x8b, 0x30, 0xfe, 0x08, 0xdb, 0x0d, 0xd5, 0xa8, 0xd7, 0x09, 0x11, 0x85, 0xa2, 0xd3, 0x45, 0xfb, 0x7e}} ,
+ {{0xda, 0x8c, 0xc2, 0xd0, 0xac, 0x18, 0xe8, 0x52, 0x36, 0xd4, 0x21, 0xa3, 0xdd, 0x57, 0x22, 0x79, 0xb7, 0xf8, 0x71, 0x9d, 0xc6, 0x91, 0x70, 0x86, 0x56, 0xbf, 0xa1, 0x11, 0x8b, 0x19, 0xe1, 0x0f}}},
+{{{0x18, 0x32, 0x98, 0x2c, 0x8f, 0x91, 0xae, 0x12, 0xf0, 0x8c, 0xea, 0xf3, 0x3c, 0xb9, 0x5d, 0xe4, 0x69, 0xed, 0xb2, 0x47, 0x18, 0xbd, 0xce, 0x16, 0x52, 0x5c, 0x23, 0xe2, 0xa5, 0x25, 0x52, 0x5d}} ,
+ {{0xb9, 0xb1, 0xe7, 0x5d, 0x4e, 0xbc, 0xee, 0xbb, 0x40, 0x81, 0x77, 0x82, 0x19, 0xab, 0xb5, 0xc6, 0xee, 0xab, 0x5b, 0x6b, 0x63, 0x92, 0x8a, 0x34, 0x8d, 0xcd, 0xee, 0x4f, 0x49, 0xe5, 0xc9, 0x7e}}},
+{{{0x21, 0xac, 0x8b, 0x22, 0xcd, 0xc3, 0x9a, 0xe9, 0x5e, 0x78, 0xbd, 0xde, 0xba, 0xad, 0xab, 0xbf, 0x75, 0x41, 0x09, 0xc5, 0x58, 0xa4, 0x7d, 0x92, 0xb0, 0x7f, 0xf2, 0xa1, 0xd1, 0xc0, 0xb3, 0x6d}} ,
+ {{0x62, 0x4f, 0xd0, 0x75, 0x77, 0xba, 0x76, 0x77, 0xd7, 0xb8, 0xd8, 0x92, 0x6f, 0x98, 0x34, 0x3d, 0xd6, 0x4e, 0x1c, 0x0f, 0xf0, 0x8f, 0x2e, 0xf1, 0xb3, 0xbd, 0xb1, 0xb9, 0xec, 0x99, 0xb4, 0x07}}},
+{{{0x60, 0x57, 0x2e, 0x9a, 0x72, 0x1d, 0x6b, 0x6e, 0x58, 0x33, 0x24, 0x8c, 0x48, 0x39, 0x46, 0x8e, 0x89, 0x6a, 0x88, 0x51, 0x23, 0x62, 0xb5, 0x32, 0x09, 0x36, 0xe3, 0x57, 0xf5, 0x98, 0xde, 0x6f}} ,
+ {{0x8b, 0x2c, 0x00, 0x48, 0x4a, 0xf9, 0x5b, 0x87, 0x69, 0x52, 0xe5, 0x5b, 0xd1, 0xb1, 0xe5, 0x25, 0x25, 0xe0, 0x9c, 0xc2, 0x13, 0x44, 0xe8, 0xb9, 0x0a, 0x70, 0xad, 0xbd, 0x0f, 0x51, 0x94, 0x69}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xa2, 0xdc, 0xab, 0xa9, 0x25, 0x2d, 0xac, 0x5f, 0x03, 0x33, 0x08, 0xe7, 0x7e, 0xfe, 0x95, 0x36, 0x3c, 0x5b, 0x3a, 0xd3, 0x05, 0x82, 0x1c, 0x95, 0x2d, 0xd8, 0x77, 0x7e, 0x02, 0xd9, 0x5b, 0x70}} ,
+ {{0xc2, 0xfe, 0x1b, 0x0c, 0x67, 0xcd, 0xd6, 0xe0, 0x51, 0x8e, 0x2c, 0xe0, 0x79, 0x88, 0xf0, 0xcf, 0x41, 0x4a, 0xad, 0x23, 0xd4, 0x46, 0xca, 0x94, 0xa1, 0xc3, 0xeb, 0x28, 0x06, 0xfa, 0x17, 0x14}}},
+{{{0x7b, 0xaa, 0x70, 0x0a, 0x4b, 0xfb, 0xf5, 0xbf, 0x80, 0xc5, 0xcf, 0x08, 0x7a, 0xdd, 0xa1, 0xf4, 0x9d, 0x54, 0x50, 0x53, 0x23, 0x77, 0x23, 0xf5, 0x34, 0xa5, 0x22, 0xd1, 0x0d, 0x96, 0x2e, 0x47}} ,
+ {{0xcc, 0xb7, 0x32, 0x89, 0x57, 0xd0, 0x98, 0x75, 0xe4, 0x37, 0x99, 0xa9, 0xe8, 0xba, 0xed, 0xba, 0xeb, 0xc7, 0x4f, 0x15, 0x76, 0x07, 0x0c, 0x4c, 0xef, 0x9f, 0x52, 0xfc, 0x04, 0x5d, 0x58, 0x10}}},
+{{{0xce, 0x82, 0xf0, 0x8f, 0x79, 0x02, 0xa8, 0xd1, 0xda, 0x14, 0x09, 0x48, 0xee, 0x8a, 0x40, 0x98, 0x76, 0x60, 0x54, 0x5a, 0xde, 0x03, 0x24, 0xf5, 0xe6, 0x2f, 0xe1, 0x03, 0xbf, 0x68, 0x82, 0x7f}} ,
+ {{0x64, 0xe9, 0x28, 0xc7, 0xa4, 0xcf, 0x2a, 0xf9, 0x90, 0x64, 0x72, 0x2c, 0x8b, 0xeb, 0xec, 0xa0, 0xf2, 0x7d, 0x35, 0xb5, 0x90, 0x4d, 0x7f, 0x5b, 0x4a, 0x49, 0xe4, 0xb8, 0x3b, 0xc8, 0xa1, 0x2f}}},
+{{{0x8b, 0xc5, 0xcc, 0x3d, 0x69, 0xa6, 0xa1, 0x18, 0x44, 0xbc, 0x4d, 0x77, 0x37, 0xc7, 0x86, 0xec, 0x0c, 0xc9, 0xd6, 0x44, 0xa9, 0x23, 0x27, 0xb9, 0x03, 0x34, 0xa7, 0x0a, 0xd5, 0xc7, 0x34, 0x37}} ,
+ {{0xf9, 0x7e, 0x3e, 0x66, 0xee, 0xf9, 0x99, 0x28, 0xff, 0xad, 0x11, 0xd8, 0xe2, 0x66, 0xc5, 0xcd, 0x0f, 0x0d, 0x0b, 0x6a, 0xfc, 0x7c, 0x24, 0xa8, 0x4f, 0xa8, 0x5e, 0x80, 0x45, 0x8b, 0x6c, 0x41}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xef, 0x1e, 0xec, 0xf7, 0x8d, 0x77, 0xf2, 0xea, 0xdb, 0x60, 0x03, 0x21, 0xc0, 0xff, 0x5e, 0x67, 0xc3, 0x71, 0x0b, 0x21, 0xb4, 0x41, 0xa0, 0x68, 0x38, 0xc6, 0x01, 0xa3, 0xd3, 0x51, 0x3c, 0x3c}} ,
+ {{0x92, 0xf8, 0xd6, 0x4b, 0xef, 0x42, 0x13, 0xb2, 0x4a, 0xc4, 0x2e, 0x72, 0x3f, 0xc9, 0x11, 0xbd, 0x74, 0x02, 0x0e, 0xf5, 0x13, 0x9d, 0x83, 0x1a, 0x1b, 0xd5, 0x54, 0xde, 0xc4, 0x1e, 0x16, 0x6c}}},
+{{{0x27, 0x52, 0xe4, 0x63, 0xaa, 0x94, 0xe6, 0xc3, 0x28, 0x9c, 0xc6, 0x56, 0xac, 0xfa, 0xb6, 0xbd, 0xe2, 0xcc, 0x76, 0xc6, 0x27, 0x27, 0xa2, 0x8e, 0x78, 0x2b, 0x84, 0x72, 0x10, 0xbd, 0x4e, 0x2a}} ,
+ {{0xea, 0xa7, 0x23, 0xef, 0x04, 0x61, 0x80, 0x50, 0xc9, 0x6e, 0xa5, 0x96, 0xd1, 0xd1, 0xc8, 0xc3, 0x18, 0xd7, 0x2d, 0xfd, 0x26, 0xbd, 0xcb, 0x7b, 0x92, 0x51, 0x0e, 0x4a, 0x65, 0x57, 0xb8, 0x49}}},
+{{{0xab, 0x55, 0x36, 0xc3, 0xec, 0x63, 0x55, 0x11, 0x55, 0xf6, 0xa5, 0xc7, 0x01, 0x5f, 0xfe, 0x79, 0xd8, 0x0a, 0xf7, 0x03, 0xd8, 0x98, 0x99, 0xf5, 0xd0, 0x00, 0x54, 0x6b, 0x66, 0x28, 0xf5, 0x25}} ,
+ {{0x7a, 0x8d, 0xa1, 0x5d, 0x70, 0x5d, 0x51, 0x27, 0xee, 0x30, 0x65, 0x56, 0x95, 0x46, 0xde, 0xbd, 0x03, 0x75, 0xb4, 0x57, 0x59, 0x89, 0xeb, 0x02, 0x9e, 0xcc, 0x89, 0x19, 0xa7, 0xcb, 0x17, 0x67}}},
+{{{0x6a, 0xeb, 0xfc, 0x9a, 0x9a, 0x10, 0xce, 0xdb, 0x3a, 0x1c, 0x3c, 0x6a, 0x9d, 0xea, 0x46, 0xbc, 0x45, 0x49, 0xac, 0xe3, 0x41, 0x12, 0x7c, 0xf0, 0xf7, 0x4f, 0xf9, 0xf7, 0xff, 0x2c, 0x89, 0x04}} ,
+ {{0x30, 0x31, 0x54, 0x1a, 0x46, 0xca, 0xe6, 0xc6, 0xcb, 0xe2, 0xc3, 0xc1, 0x8b, 0x75, 0x81, 0xbe, 0xee, 0xf8, 0xa3, 0x11, 0x1c, 0x25, 0xa3, 0xa7, 0x35, 0x51, 0x55, 0xe2, 0x25, 0xaa, 0xe2, 0x3a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xb4, 0x48, 0x10, 0x9f, 0x8a, 0x09, 0x76, 0xfa, 0xf0, 0x7a, 0xb0, 0x70, 0xf7, 0x83, 0x80, 0x52, 0x84, 0x2b, 0x26, 0xa2, 0xc4, 0x5d, 0x4f, 0xba, 0xb1, 0xc8, 0x40, 0x0d, 0x78, 0x97, 0xc4, 0x60}} ,
+ {{0xd4, 0xb1, 0x6c, 0x08, 0xc7, 0x40, 0x38, 0x73, 0x5f, 0x0b, 0xf3, 0x76, 0x5d, 0xb2, 0xa5, 0x2f, 0x57, 0x57, 0x07, 0xed, 0x08, 0xa2, 0x6c, 0x4f, 0x08, 0x02, 0xb5, 0x0e, 0xee, 0x44, 0xfa, 0x22}}},
+{{{0x0f, 0x00, 0x3f, 0xa6, 0x04, 0x19, 0x56, 0x65, 0x31, 0x7f, 0x8b, 0xeb, 0x0d, 0xe1, 0x47, 0x89, 0x97, 0x16, 0x53, 0xfa, 0x81, 0xa7, 0xaa, 0xb2, 0xbf, 0x67, 0xeb, 0x72, 0x60, 0x81, 0x0d, 0x48}} ,
+ {{0x7e, 0x13, 0x33, 0xcd, 0xa8, 0x84, 0x56, 0x1e, 0x67, 0xaf, 0x6b, 0x43, 0xac, 0x17, 0xaf, 0x16, 0xc0, 0x52, 0x99, 0x49, 0x5b, 0x87, 0x73, 0x7e, 0xb5, 0x43, 0xda, 0x6b, 0x1d, 0x0f, 0x2d, 0x55}}},
+{{{0xe9, 0x58, 0x1f, 0xff, 0x84, 0x3f, 0x93, 0x1c, 0xcb, 0xe1, 0x30, 0x69, 0xa5, 0x75, 0x19, 0x7e, 0x14, 0x5f, 0xf8, 0xfc, 0x09, 0xdd, 0xa8, 0x78, 0x9d, 0xca, 0x59, 0x8b, 0xd1, 0x30, 0x01, 0x13}} ,
+ {{0xff, 0x76, 0x03, 0xc5, 0x4b, 0x89, 0x99, 0x70, 0x00, 0x59, 0x70, 0x9c, 0xd5, 0xd9, 0x11, 0x89, 0x5a, 0x46, 0xfe, 0xef, 0xdc, 0xd9, 0x55, 0x2b, 0x45, 0xa7, 0xb0, 0x2d, 0xfb, 0x24, 0xc2, 0x29}}},
+{{{0x38, 0x06, 0xf8, 0x0b, 0xac, 0x82, 0xc4, 0x97, 0x2b, 0x90, 0xe0, 0xf7, 0xa8, 0xab, 0x6c, 0x08, 0x80, 0x66, 0x90, 0x46, 0xf7, 0x26, 0x2d, 0xf8, 0xf1, 0xc4, 0x6b, 0x4a, 0x82, 0x98, 0x8e, 0x37}} ,
+ {{0x8e, 0xb4, 0xee, 0xb8, 0xd4, 0x3f, 0xb2, 0x1b, 0xe0, 0x0a, 0x3d, 0x75, 0x34, 0x28, 0xa2, 0x8e, 0xc4, 0x92, 0x7b, 0xfe, 0x60, 0x6e, 0x6d, 0xb8, 0x31, 0x1d, 0x62, 0x0d, 0x78, 0x14, 0x42, 0x11}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x5e, 0xa8, 0xd8, 0x04, 0x9b, 0x73, 0xc9, 0xc9, 0xdc, 0x0d, 0x73, 0xbf, 0x0a, 0x0a, 0x73, 0xff, 0x18, 0x1f, 0x9c, 0x51, 0xaa, 0xc6, 0xf1, 0x83, 0x25, 0xfd, 0xab, 0xa3, 0x11, 0xd3, 0x01, 0x24}} ,
+ {{0x4d, 0xe3, 0x7e, 0x38, 0x62, 0x5e, 0x64, 0xbb, 0x2b, 0x53, 0xb5, 0x03, 0x68, 0xc4, 0xf2, 0x2b, 0x5a, 0x03, 0x32, 0x99, 0x4a, 0x41, 0x9a, 0xe1, 0x1a, 0xae, 0x8c, 0x48, 0xf3, 0x24, 0x32, 0x65}}},
+{{{0xe8, 0xdd, 0xad, 0x3a, 0x8c, 0xea, 0xf4, 0xb3, 0xb2, 0xe5, 0x73, 0xf2, 0xed, 0x8b, 0xbf, 0xed, 0xb1, 0x0c, 0x0c, 0xfb, 0x2b, 0xf1, 0x01, 0x48, 0xe8, 0x26, 0x03, 0x8e, 0x27, 0x4d, 0x96, 0x72}} ,
+ {{0xc8, 0x09, 0x3b, 0x60, 0xc9, 0x26, 0x4d, 0x7c, 0xf2, 0x9c, 0xd4, 0xa1, 0x3b, 0x26, 0xc2, 0x04, 0x33, 0x44, 0x76, 0x3c, 0x02, 0xbb, 0x11, 0x42, 0x0c, 0x22, 0xb7, 0xc6, 0xe1, 0xac, 0xb4, 0x0e}}},
+{{{0x6f, 0x85, 0xe7, 0xef, 0xde, 0x67, 0x30, 0xfc, 0xbf, 0x5a, 0xe0, 0x7b, 0x7a, 0x2a, 0x54, 0x6b, 0x5d, 0x62, 0x85, 0xa1, 0xf8, 0x16, 0x88, 0xec, 0x61, 0xb9, 0x96, 0xb5, 0xef, 0x2d, 0x43, 0x4d}} ,
+ {{0x7c, 0x31, 0x33, 0xcc, 0xe4, 0xcf, 0x6c, 0xff, 0x80, 0x47, 0x77, 0xd1, 0xd8, 0xe9, 0x69, 0x97, 0x98, 0x7f, 0x20, 0x57, 0x1d, 0x1d, 0x4f, 0x08, 0x27, 0xc8, 0x35, 0x57, 0x40, 0xc6, 0x21, 0x0c}}},
+{{{0xd2, 0x8e, 0x9b, 0xfa, 0x42, 0x8e, 0xdf, 0x8f, 0xc7, 0x86, 0xf9, 0xa4, 0xca, 0x70, 0x00, 0x9d, 0x21, 0xbf, 0xec, 0x57, 0x62, 0x30, 0x58, 0x8c, 0x0d, 0x35, 0xdb, 0x5d, 0x8b, 0x6a, 0xa0, 0x5a}} ,
+ {{0xc1, 0x58, 0x7c, 0x0d, 0x20, 0xdd, 0x11, 0x26, 0x5f, 0x89, 0x3b, 0x97, 0x58, 0xf8, 0x8b, 0xe3, 0xdf, 0x32, 0xe2, 0xfc, 0xd8, 0x67, 0xf2, 0xa5, 0x37, 0x1e, 0x6d, 0xec, 0x7c, 0x27, 0x20, 0x79}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xd0, 0xe9, 0xc0, 0xfa, 0x95, 0x45, 0x23, 0x96, 0xf1, 0x2c, 0x79, 0x25, 0x14, 0xce, 0x40, 0x14, 0x44, 0x2c, 0x36, 0x50, 0xd9, 0x63, 0x56, 0xb7, 0x56, 0x3b, 0x9e, 0xa7, 0xef, 0x89, 0xbb, 0x0e}} ,
+ {{0xce, 0x7f, 0xdc, 0x0a, 0xcc, 0x82, 0x1c, 0x0a, 0x78, 0x71, 0xe8, 0x74, 0x8d, 0x01, 0x30, 0x0f, 0xa7, 0x11, 0x4c, 0xdf, 0x38, 0xd7, 0xa7, 0x0d, 0xf8, 0x48, 0x52, 0x00, 0x80, 0x7b, 0x5f, 0x0e}}},
+{{{0x25, 0x83, 0xe6, 0x94, 0x7b, 0x81, 0xb2, 0x91, 0xae, 0x0e, 0x05, 0xc9, 0xa3, 0x68, 0x2d, 0xd9, 0x88, 0x25, 0x19, 0x2a, 0x61, 0x61, 0x21, 0x97, 0x15, 0xa1, 0x35, 0xa5, 0x46, 0xc8, 0xa2, 0x0e}} ,
+ {{0x1b, 0x03, 0x0d, 0x8b, 0x5a, 0x1b, 0x97, 0x4b, 0xf2, 0x16, 0x31, 0x3d, 0x1f, 0x33, 0xa0, 0x50, 0x3a, 0x18, 0xbe, 0x13, 0xa1, 0x76, 0xc1, 0xba, 0x1b, 0xf1, 0x05, 0x7b, 0x33, 0xa8, 0x82, 0x3b}}},
+{{{0xba, 0x36, 0x7b, 0x6d, 0xa9, 0xea, 0x14, 0x12, 0xc5, 0xfa, 0x91, 0x00, 0xba, 0x9b, 0x99, 0xcc, 0x56, 0x02, 0xe9, 0xa0, 0x26, 0x40, 0x66, 0x8c, 0xc4, 0xf8, 0x85, 0x33, 0x68, 0xe7, 0x03, 0x20}} ,
+ {{0x50, 0x5b, 0xff, 0xa9, 0xb2, 0xf1, 0xf1, 0x78, 0xcf, 0x14, 0xa4, 0xa9, 0xfc, 0x09, 0x46, 0x94, 0x54, 0x65, 0x0d, 0x9c, 0x5f, 0x72, 0x21, 0xe2, 0x97, 0xa5, 0x2d, 0x81, 0xce, 0x4a, 0x5f, 0x79}}},
+{{{0x3d, 0x5f, 0x5c, 0xd2, 0xbc, 0x7d, 0x77, 0x0e, 0x2a, 0x6d, 0x22, 0x45, 0x84, 0x06, 0xc4, 0xdd, 0xc6, 0xa6, 0xc6, 0xd7, 0x49, 0xad, 0x6d, 0x87, 0x91, 0x0e, 0x3a, 0x67, 0x1d, 0x2c, 0x1d, 0x56}} ,
+ {{0xfe, 0x7a, 0x74, 0xcf, 0xd4, 0xd2, 0xe5, 0x19, 0xde, 0xd0, 0xdb, 0x70, 0x23, 0x69, 0xe6, 0x6d, 0xec, 0xec, 0xcc, 0x09, 0x33, 0x6a, 0x77, 0xdc, 0x6b, 0x22, 0x76, 0x5d, 0x92, 0x09, 0xac, 0x2d}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x23, 0x15, 0x17, 0xeb, 0xd3, 0xdb, 0x12, 0x5e, 0x01, 0xf0, 0x91, 0xab, 0x2c, 0x41, 0xce, 0xac, 0xed, 0x1b, 0x4b, 0x2d, 0xbc, 0xdb, 0x17, 0x66, 0x89, 0x46, 0xad, 0x4b, 0x1e, 0x6f, 0x0b, 0x14}} ,
+ {{0x11, 0xce, 0xbf, 0xb6, 0x77, 0x2d, 0x48, 0x22, 0x18, 0x4f, 0xa3, 0x5d, 0x4a, 0xb0, 0x70, 0x12, 0x3e, 0x54, 0xd7, 0xd8, 0x0e, 0x2b, 0x27, 0xdc, 0x53, 0xff, 0xca, 0x8c, 0x59, 0xb3, 0x4e, 0x44}}},
+{{{0x07, 0x76, 0x61, 0x0f, 0x66, 0xb2, 0x21, 0x39, 0x7e, 0xc0, 0xec, 0x45, 0x28, 0x82, 0xa1, 0x29, 0x32, 0x44, 0x35, 0x13, 0x5e, 0x61, 0x5e, 0x54, 0xcb, 0x7c, 0xef, 0xf6, 0x41, 0xcf, 0x9f, 0x0a}} ,
+ {{0xdd, 0xf9, 0xda, 0x84, 0xc3, 0xe6, 0x8a, 0x9f, 0x24, 0xd2, 0x96, 0x5d, 0x39, 0x6f, 0x58, 0x8c, 0xc1, 0x56, 0x93, 0xab, 0xb5, 0x79, 0x3b, 0xd2, 0xa8, 0x73, 0x16, 0xed, 0xfa, 0xb4, 0x2f, 0x73}}},
+{{{0x8b, 0xb1, 0x95, 0xe5, 0x92, 0x50, 0x35, 0x11, 0x76, 0xac, 0xf4, 0x4d, 0x24, 0xc3, 0x32, 0xe6, 0xeb, 0xfe, 0x2c, 0x87, 0xc4, 0xf1, 0x56, 0xc4, 0x75, 0x24, 0x7a, 0x56, 0x85, 0x5a, 0x3a, 0x13}} ,
+ {{0x0d, 0x16, 0xac, 0x3c, 0x4a, 0x58, 0x86, 0x3a, 0x46, 0x7f, 0x6c, 0xa3, 0x52, 0x6e, 0x37, 0xe4, 0x96, 0x9c, 0xe9, 0x5c, 0x66, 0x41, 0x67, 0xe4, 0xfb, 0x79, 0x0c, 0x05, 0xf6, 0x64, 0xd5, 0x7c}}},
+{{{0x28, 0xc1, 0xe1, 0x54, 0x73, 0xf2, 0xbf, 0x76, 0x74, 0x19, 0x19, 0x1b, 0xe4, 0xb9, 0xa8, 0x46, 0x65, 0x73, 0xf3, 0x77, 0x9b, 0x29, 0x74, 0x5b, 0xc6, 0x89, 0x6c, 0x2c, 0x7c, 0xf8, 0xb3, 0x0f}} ,
+ {{0xf7, 0xd5, 0xe9, 0x74, 0x5d, 0xb8, 0x25, 0x16, 0xb5, 0x30, 0xbc, 0x84, 0xc5, 0xf0, 0xad, 0xca, 0x12, 0x28, 0xbc, 0x9d, 0xd4, 0xfa, 0x82, 0xe6, 0xe3, 0xbf, 0xa2, 0x15, 0x2c, 0xd4, 0x34, 0x10}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x61, 0xb1, 0x46, 0xba, 0x0e, 0x31, 0xa5, 0x67, 0x6c, 0x7f, 0xd6, 0xd9, 0x27, 0x85, 0x0f, 0x79, 0x14, 0xc8, 0x6c, 0x2f, 0x5f, 0x5b, 0x9c, 0x35, 0x3d, 0x38, 0x86, 0x77, 0x65, 0x55, 0x6a, 0x7b}} ,
+ {{0xd3, 0xb0, 0x3a, 0x66, 0x60, 0x1b, 0x43, 0xf1, 0x26, 0x58, 0x99, 0x09, 0x8f, 0x2d, 0xa3, 0x14, 0x71, 0x85, 0xdb, 0xed, 0xf6, 0x26, 0xd5, 0x61, 0x9a, 0x73, 0xac, 0x0e, 0xea, 0xac, 0xb7, 0x0c}}},
+{{{0x5e, 0xf4, 0xe5, 0x17, 0x0e, 0x10, 0x9f, 0xe7, 0x43, 0x5f, 0x67, 0x5c, 0xac, 0x4b, 0xe5, 0x14, 0x41, 0xd2, 0xbf, 0x48, 0xf5, 0x14, 0xb0, 0x71, 0xc6, 0x61, 0xc1, 0xb2, 0x70, 0x58, 0xd2, 0x5a}} ,
+ {{0x2d, 0xba, 0x16, 0x07, 0x92, 0x94, 0xdc, 0xbd, 0x50, 0x2b, 0xc9, 0x7f, 0x42, 0x00, 0xba, 0x61, 0xed, 0xf8, 0x43, 0xed, 0xf5, 0xf9, 0x40, 0x60, 0xb2, 0xb0, 0x82, 0xcb, 0xed, 0x75, 0xc7, 0x65}}},
+{{{0x80, 0xba, 0x0d, 0x09, 0x40, 0xa7, 0x39, 0xa6, 0x67, 0x34, 0x7e, 0x66, 0xbe, 0x56, 0xfb, 0x53, 0x78, 0xc4, 0x46, 0xe8, 0xed, 0x68, 0x6c, 0x7f, 0xce, 0xe8, 0x9f, 0xce, 0xa2, 0x64, 0x58, 0x53}} ,
+ {{0xe8, 0xc1, 0xa9, 0xc2, 0x7b, 0x59, 0x21, 0x33, 0xe2, 0x43, 0x73, 0x2b, 0xac, 0x2d, 0xc1, 0x89, 0x3b, 0x15, 0xe2, 0xd5, 0xc0, 0x97, 0x8a, 0xfd, 0x6f, 0x36, 0x33, 0xb7, 0xb9, 0xc3, 0x88, 0x09}}},
+{{{0xd0, 0xb6, 0x56, 0x30, 0x5c, 0xae, 0xb3, 0x75, 0x44, 0xa4, 0x83, 0x51, 0x6e, 0x01, 0x65, 0xef, 0x45, 0x76, 0xe6, 0xf5, 0xa2, 0x0d, 0xd4, 0x16, 0x3b, 0x58, 0x2f, 0xf2, 0x2f, 0x36, 0x18, 0x3f}} ,
+ {{0xfd, 0x2f, 0xe0, 0x9b, 0x1e, 0x8c, 0xc5, 0x18, 0xa9, 0xca, 0xd4, 0x2b, 0x35, 0xb6, 0x95, 0x0a, 0x9f, 0x7e, 0xfb, 0xc4, 0xef, 0x88, 0x7b, 0x23, 0x43, 0xec, 0x2f, 0x0d, 0x0f, 0x7a, 0xfc, 0x5c}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x8d, 0xd2, 0xda, 0xc7, 0x44, 0xd6, 0x7a, 0xdb, 0x26, 0x7d, 0x1d, 0xb8, 0xe1, 0xde, 0x9d, 0x7a, 0x7d, 0x17, 0x7e, 0x1c, 0x37, 0x04, 0x8d, 0x2d, 0x7c, 0x5e, 0x18, 0x38, 0x1e, 0xaf, 0xc7, 0x1b}} ,
+ {{0x33, 0x48, 0x31, 0x00, 0x59, 0xf6, 0xf2, 0xca, 0x0f, 0x27, 0x1b, 0x63, 0x12, 0x7e, 0x02, 0x1d, 0x49, 0xc0, 0x5d, 0x79, 0x87, 0xef, 0x5e, 0x7a, 0x2f, 0x1f, 0x66, 0x55, 0xd8, 0x09, 0xd9, 0x61}}},
+{{{0x54, 0x83, 0x02, 0x18, 0x82, 0x93, 0x99, 0x07, 0xd0, 0xa7, 0xda, 0xd8, 0x75, 0x89, 0xfa, 0xf2, 0xd9, 0xa3, 0xb8, 0x6b, 0x5a, 0x35, 0x28, 0xd2, 0x6b, 0x59, 0xc2, 0xf8, 0x45, 0xe2, 0xbc, 0x06}} ,
+ {{0x65, 0xc0, 0xa3, 0x88, 0x51, 0x95, 0xfc, 0x96, 0x94, 0x78, 0xe8, 0x0d, 0x8b, 0x41, 0xc9, 0xc2, 0x58, 0x48, 0x75, 0x10, 0x2f, 0xcd, 0x2a, 0xc9, 0xa0, 0x6d, 0x0f, 0xdd, 0x9c, 0x98, 0x26, 0x3d}}},
+{{{0x2f, 0x66, 0x29, 0x1b, 0x04, 0x89, 0xbd, 0x7e, 0xee, 0x6e, 0xdd, 0xb7, 0x0e, 0xef, 0xb0, 0x0c, 0xb4, 0xfc, 0x7f, 0xc2, 0xc9, 0x3a, 0x3c, 0x64, 0xef, 0x45, 0x44, 0xaf, 0x8a, 0x90, 0x65, 0x76}} ,
+ {{0xa1, 0x4c, 0x70, 0x4b, 0x0e, 0xa0, 0x83, 0x70, 0x13, 0xa4, 0xaf, 0xb8, 0x38, 0x19, 0x22, 0x65, 0x09, 0xb4, 0x02, 0x4f, 0x06, 0xf8, 0x17, 0xce, 0x46, 0x45, 0xda, 0x50, 0x7c, 0x8a, 0xd1, 0x4e}}},
+{{{0xf7, 0xd4, 0x16, 0x6c, 0x4e, 0x95, 0x9d, 0x5d, 0x0f, 0x91, 0x2b, 0x52, 0xfe, 0x5c, 0x34, 0xe5, 0x30, 0xe6, 0xa4, 0x3b, 0xf3, 0xf3, 0x34, 0x08, 0xa9, 0x4a, 0xa0, 0xb5, 0x6e, 0xb3, 0x09, 0x0a}} ,
+ {{0x26, 0xd9, 0x5e, 0xa3, 0x0f, 0xeb, 0xa2, 0xf3, 0x20, 0x3b, 0x37, 0xd4, 0xe4, 0x9e, 0xce, 0x06, 0x3d, 0x53, 0xed, 0xae, 0x2b, 0xeb, 0xb6, 0x24, 0x0a, 0x11, 0xa3, 0x0f, 0xd6, 0x7f, 0xa4, 0x3a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xdb, 0x9f, 0x2c, 0xfc, 0xd6, 0xb2, 0x1e, 0x2e, 0x52, 0x7a, 0x06, 0x87, 0x2d, 0x86, 0x72, 0x2b, 0x6d, 0x90, 0x77, 0x46, 0x43, 0xb5, 0x7a, 0xf8, 0x60, 0x7d, 0x91, 0x60, 0x5b, 0x9d, 0x9e, 0x07}} ,
+ {{0x97, 0x87, 0xc7, 0x04, 0x1c, 0x38, 0x01, 0x39, 0x58, 0xc7, 0x85, 0xa3, 0xfc, 0x64, 0x00, 0x64, 0x25, 0xa2, 0xbf, 0x50, 0x94, 0xca, 0x26, 0x31, 0x45, 0x0a, 0x24, 0xd2, 0x51, 0x29, 0x51, 0x16}}},
+{{{0x4d, 0x4a, 0xd7, 0x98, 0x71, 0x57, 0xac, 0x7d, 0x8b, 0x37, 0xbd, 0x63, 0xff, 0x87, 0xb1, 0x49, 0x95, 0x20, 0x7c, 0xcf, 0x7c, 0x59, 0xc4, 0x91, 0x9c, 0xef, 0xd0, 0xdb, 0x60, 0x09, 0x9d, 0x46}} ,
+ {{0xcb, 0x78, 0x94, 0x90, 0xe4, 0x45, 0xb3, 0xf6, 0xd9, 0xf6, 0x57, 0x74, 0xd5, 0xf8, 0x83, 0x4f, 0x39, 0xc9, 0xbd, 0x88, 0xc2, 0x57, 0x21, 0x1f, 0x24, 0x32, 0x68, 0xf8, 0xc7, 0x21, 0x5f, 0x0b}}},
+{{{0x2a, 0x36, 0x68, 0xfc, 0x5f, 0xb6, 0x4f, 0xa5, 0xe3, 0x9d, 0x24, 0x2f, 0xc0, 0x93, 0x61, 0xcf, 0xf8, 0x0a, 0xed, 0xe1, 0xdb, 0x27, 0xec, 0x0e, 0x14, 0x32, 0x5f, 0x8e, 0xa1, 0x62, 0x41, 0x16}} ,
+ {{0x95, 0x21, 0x01, 0xce, 0x95, 0x5b, 0x0e, 0x57, 0xc7, 0xb9, 0x62, 0xb5, 0x28, 0xca, 0x11, 0xec, 0xb4, 0x46, 0x06, 0x73, 0x26, 0xff, 0xfb, 0x66, 0x7d, 0xee, 0x5f, 0xb2, 0x56, 0xfd, 0x2a, 0x08}}},
+{{{0x92, 0x67, 0x77, 0x56, 0xa1, 0xff, 0xc4, 0xc5, 0x95, 0xf0, 0xe3, 0x3a, 0x0a, 0xca, 0x94, 0x4d, 0x9e, 0x7e, 0x3d, 0xb9, 0x6e, 0xb6, 0xb0, 0xce, 0xa4, 0x30, 0x89, 0x99, 0xe9, 0xad, 0x11, 0x59}} ,
+ {{0xf6, 0x48, 0x95, 0xa1, 0x6f, 0x5f, 0xb7, 0xa5, 0xbb, 0x30, 0x00, 0x1c, 0xd2, 0x8a, 0xd6, 0x25, 0x26, 0x1b, 0xb2, 0x0d, 0x37, 0x6a, 0x05, 0xf4, 0x9d, 0x3e, 0x17, 0x2a, 0x43, 0xd2, 0x3a, 0x06}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x32, 0x99, 0x93, 0xd1, 0x9a, 0x72, 0xf3, 0xa9, 0x16, 0xbd, 0xb4, 0x4c, 0xdd, 0xf9, 0xd4, 0xb2, 0x64, 0x9a, 0xd3, 0x05, 0xe4, 0xa3, 0x73, 0x1c, 0xcb, 0x7e, 0x57, 0x67, 0xff, 0x04, 0xb3, 0x10}} ,
+ {{0xb9, 0x4b, 0xa4, 0xad, 0xd0, 0x6d, 0x61, 0x23, 0xb4, 0xaf, 0x34, 0xa9, 0xaa, 0x65, 0xec, 0xd9, 0x69, 0xe3, 0x85, 0xcd, 0xcc, 0xe7, 0xb0, 0x9b, 0x41, 0xc1, 0x1c, 0xf9, 0xa0, 0xfa, 0xb7, 0x13}}},
+{{{0x04, 0xfd, 0x88, 0x3c, 0x0c, 0xd0, 0x09, 0x52, 0x51, 0x4f, 0x06, 0x19, 0xcc, 0xc3, 0xbb, 0xde, 0x80, 0xc5, 0x33, 0xbc, 0xf9, 0xf3, 0x17, 0x36, 0xdd, 0xc6, 0xde, 0xe8, 0x9b, 0x5d, 0x79, 0x1b}} ,
+ {{0x65, 0x0a, 0xbe, 0x51, 0x57, 0xad, 0x50, 0x79, 0x08, 0x71, 0x9b, 0x07, 0x95, 0x8f, 0xfb, 0xae, 0x4b, 0x38, 0xba, 0xcf, 0x53, 0x2a, 0x86, 0x1e, 0xc0, 0x50, 0x5c, 0x67, 0x1b, 0xf6, 0x87, 0x6c}}},
+{{{0x4f, 0x00, 0xb2, 0x66, 0x55, 0xed, 0x4a, 0xed, 0x8d, 0xe1, 0x66, 0x18, 0xb2, 0x14, 0x74, 0x8d, 0xfd, 0x1a, 0x36, 0x0f, 0x26, 0x5c, 0x8b, 0x89, 0xf3, 0xab, 0xf2, 0xf3, 0x24, 0x67, 0xfd, 0x70}} ,
+ {{0xfd, 0x4e, 0x2a, 0xc1, 0x3a, 0xca, 0x8f, 0x00, 0xd8, 0xec, 0x74, 0x67, 0xef, 0x61, 0xe0, 0x28, 0xd0, 0x96, 0xf4, 0x48, 0xde, 0x81, 0xe3, 0xef, 0xdc, 0xaa, 0x7d, 0xf3, 0xb6, 0x55, 0xa6, 0x65}}},
+{{{0xeb, 0xcb, 0xc5, 0x70, 0x91, 0x31, 0x10, 0x93, 0x0d, 0xc8, 0xd0, 0xef, 0x62, 0xe8, 0x6f, 0x82, 0xe3, 0x69, 0x3d, 0x91, 0x7f, 0x31, 0xe1, 0x26, 0x35, 0x3c, 0x4a, 0x2f, 0xab, 0xc4, 0x9a, 0x5e}} ,
+ {{0xab, 0x1b, 0xb5, 0xe5, 0x2b, 0xc3, 0x0e, 0x29, 0xb0, 0xd0, 0x73, 0xe6, 0x4f, 0x64, 0xf2, 0xbc, 0xe4, 0xe4, 0xe1, 0x9a, 0x52, 0x33, 0x2f, 0xbd, 0xcc, 0x03, 0xee, 0x8a, 0xfa, 0x00, 0x5f, 0x50}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xf6, 0xdb, 0x0d, 0x22, 0x3d, 0xb5, 0x14, 0x75, 0x31, 0xf0, 0x81, 0xe2, 0xb9, 0x37, 0xa2, 0xa9, 0x84, 0x11, 0x9a, 0x07, 0xb5, 0x53, 0x89, 0x78, 0xa9, 0x30, 0x27, 0xa1, 0xf1, 0x4e, 0x5c, 0x2e}} ,
+ {{0x8b, 0x00, 0x54, 0xfb, 0x4d, 0xdc, 0xcb, 0x17, 0x35, 0x40, 0xff, 0xb7, 0x8c, 0xfe, 0x4a, 0xe4, 0x4e, 0x99, 0x4e, 0xa8, 0x74, 0x54, 0x5d, 0x5c, 0x96, 0xa3, 0x12, 0x55, 0x36, 0x31, 0x17, 0x5c}}},
+{{{0xce, 0x24, 0xef, 0x7b, 0x86, 0xf2, 0x0f, 0x77, 0xe8, 0x5c, 0x7d, 0x87, 0x38, 0x2d, 0xef, 0xaf, 0xf2, 0x8c, 0x72, 0x2e, 0xeb, 0xb6, 0x55, 0x4b, 0x6e, 0xf1, 0x4e, 0x8a, 0x0e, 0x9a, 0x6c, 0x4c}} ,
+ {{0x25, 0xea, 0x86, 0xc2, 0xd1, 0x4f, 0xb7, 0x3e, 0xa8, 0x5c, 0x8d, 0x66, 0x81, 0x25, 0xed, 0xc5, 0x4c, 0x05, 0xb9, 0xd8, 0xd6, 0x70, 0xbe, 0x73, 0x82, 0xe8, 0xa1, 0xe5, 0x1e, 0x71, 0xd5, 0x26}}},
+{{{0x4e, 0x6d, 0xc3, 0xa7, 0x4f, 0x22, 0x45, 0x26, 0xa2, 0x7e, 0x16, 0xf7, 0xf7, 0x63, 0xdc, 0x86, 0x01, 0x2a, 0x71, 0x38, 0x5c, 0x33, 0xc3, 0xce, 0x30, 0xff, 0xf9, 0x2c, 0x91, 0x71, 0x8a, 0x72}} ,
+ {{0x8c, 0x44, 0x09, 0x28, 0xd5, 0x23, 0xc9, 0x8f, 0xf3, 0x84, 0x45, 0xc6, 0x9a, 0x5e, 0xff, 0xd2, 0xc7, 0x57, 0x93, 0xa3, 0xc1, 0x69, 0xdd, 0x62, 0x0f, 0xda, 0x5c, 0x30, 0x59, 0x5d, 0xe9, 0x4c}}},
+{{{0x92, 0x7e, 0x50, 0x27, 0x72, 0xd7, 0x0c, 0xd6, 0x69, 0x96, 0x81, 0x35, 0x84, 0x94, 0x35, 0x8b, 0x6c, 0xaa, 0x62, 0x86, 0x6e, 0x1c, 0x15, 0xf3, 0x6c, 0xb3, 0xff, 0x65, 0x1b, 0xa2, 0x9b, 0x59}} ,
+ {{0xe2, 0xa9, 0x65, 0x88, 0xc4, 0x50, 0xfa, 0xbb, 0x3b, 0x6e, 0x5f, 0x44, 0x01, 0xca, 0x97, 0xd4, 0xdd, 0xf6, 0xcd, 0x3f, 0x3f, 0xe5, 0x97, 0x67, 0x2b, 0x8c, 0x66, 0x0f, 0x35, 0x9b, 0xf5, 0x07}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xf1, 0x59, 0x27, 0xd8, 0xdb, 0x5a, 0x11, 0x5e, 0x82, 0xf3, 0x38, 0xff, 0x1c, 0xed, 0xfe, 0x3f, 0x64, 0x54, 0x3f, 0x7f, 0xd1, 0x81, 0xed, 0xef, 0x65, 0xc5, 0xcb, 0xfd, 0xe1, 0x80, 0xcd, 0x11}} ,
+ {{0xe0, 0xdb, 0x22, 0x28, 0xe6, 0xff, 0x61, 0x9d, 0x41, 0x14, 0x2d, 0x3b, 0x26, 0x22, 0xdf, 0xf1, 0x34, 0x81, 0xe9, 0x45, 0xee, 0x0f, 0x98, 0x8b, 0xa6, 0x3f, 0xef, 0xf7, 0x43, 0x19, 0xf1, 0x43}}},
+{{{0xee, 0xf3, 0x00, 0xa1, 0x50, 0xde, 0xc0, 0xb6, 0x01, 0xe3, 0x8c, 0x3c, 0x4d, 0x31, 0xd2, 0xb0, 0x58, 0xcd, 0xed, 0x10, 0x4a, 0x7a, 0xef, 0x80, 0xa9, 0x19, 0x32, 0xf3, 0xd8, 0x33, 0x8c, 0x06}} ,
+ {{0xcb, 0x7d, 0x4f, 0xff, 0x30, 0xd8, 0x12, 0x3b, 0x39, 0x1c, 0x06, 0xf9, 0x4c, 0x34, 0x35, 0x71, 0xb5, 0x16, 0x94, 0x67, 0xdf, 0xee, 0x11, 0xde, 0xa4, 0x1d, 0x88, 0x93, 0x35, 0xa9, 0x32, 0x10}}},
+{{{0xe9, 0xc3, 0xbc, 0x7b, 0x5c, 0xfc, 0xb2, 0xf9, 0xc9, 0x2f, 0xe5, 0xba, 0x3a, 0x0b, 0xab, 0x64, 0x38, 0x6f, 0x5b, 0x4b, 0x93, 0xda, 0x64, 0xec, 0x4d, 0x3d, 0xa0, 0xf5, 0xbb, 0xba, 0x47, 0x48}} ,
+ {{0x60, 0xbc, 0x45, 0x1f, 0x23, 0xa2, 0x3b, 0x70, 0x76, 0xe6, 0x97, 0x99, 0x4f, 0x77, 0x54, 0x67, 0x30, 0x9a, 0xe7, 0x66, 0xd6, 0xcd, 0x2e, 0x51, 0x24, 0x2c, 0x42, 0x4a, 0x11, 0xfe, 0x6f, 0x7e}}},
+{{{0x87, 0xc0, 0xb1, 0xf0, 0xa3, 0x6f, 0x0c, 0x93, 0xa9, 0x0a, 0x72, 0xef, 0x5c, 0xbe, 0x65, 0x35, 0xa7, 0x6a, 0x4e, 0x2c, 0xbf, 0x21, 0x23, 0xe8, 0x2f, 0x97, 0xc7, 0x3e, 0xc8, 0x17, 0xac, 0x1e}} ,
+ {{0x7b, 0xef, 0x21, 0xe5, 0x40, 0xcc, 0x1e, 0xdc, 0xd6, 0xbd, 0x97, 0x7a, 0x7c, 0x75, 0x86, 0x7a, 0x25, 0x5a, 0x6e, 0x7c, 0xe5, 0x51, 0x3c, 0x1b, 0x5b, 0x82, 0x9a, 0x07, 0x60, 0xa1, 0x19, 0x04}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x96, 0x88, 0xa6, 0xab, 0x8f, 0xe3, 0x3a, 0x49, 0xf8, 0xfe, 0x34, 0xe7, 0x6a, 0xb2, 0xfe, 0x40, 0x26, 0x74, 0x57, 0x4c, 0xf6, 0xd4, 0x99, 0xce, 0x5d, 0x7b, 0x2f, 0x67, 0xd6, 0x5a, 0xe4, 0x4e}} ,
+ {{0x5c, 0x82, 0xb3, 0xbd, 0x55, 0x25, 0xf6, 0x6a, 0x93, 0xa4, 0x02, 0xc6, 0x7d, 0x5c, 0xb1, 0x2b, 0x5b, 0xff, 0xfb, 0x56, 0xf8, 0x01, 0x41, 0x90, 0xc6, 0xb6, 0xac, 0x4f, 0xfe, 0xa7, 0x41, 0x70}}},
+{{{0xdb, 0xfa, 0x9b, 0x2c, 0xd4, 0x23, 0x67, 0x2c, 0x8a, 0x63, 0x6c, 0x07, 0x26, 0x48, 0x4f, 0xc2, 0x03, 0xd2, 0x53, 0x20, 0x28, 0xed, 0x65, 0x71, 0x47, 0xa9, 0x16, 0x16, 0x12, 0xbc, 0x28, 0x33}} ,
+ {{0x39, 0xc0, 0xfa, 0xfa, 0xcd, 0x33, 0x43, 0xc7, 0x97, 0x76, 0x9b, 0x93, 0x91, 0x72, 0xeb, 0xc5, 0x18, 0x67, 0x4c, 0x11, 0xf0, 0xf4, 0xe5, 0x73, 0xb2, 0x5c, 0x1b, 0xc2, 0x26, 0x3f, 0xbf, 0x2b}}},
+{{{0x86, 0xe6, 0x8c, 0x1d, 0xdf, 0xca, 0xfc, 0xd5, 0xf8, 0x3a, 0xc3, 0x44, 0x72, 0xe6, 0x78, 0x9d, 0x2b, 0x97, 0xf8, 0x28, 0x45, 0xb4, 0x20, 0xc9, 0x2a, 0x8c, 0x67, 0xaa, 0x11, 0xc5, 0x5b, 0x2f}} ,
+ {{0x17, 0x0f, 0x86, 0x52, 0xd7, 0x9d, 0xc3, 0x44, 0x51, 0x76, 0x32, 0x65, 0xb4, 0x37, 0x81, 0x99, 0x46, 0x37, 0x62, 0xed, 0xcf, 0x64, 0x9d, 0x72, 0x40, 0x7a, 0x4c, 0x0b, 0x76, 0x2a, 0xfb, 0x56}}},
+{{{0x33, 0xa7, 0x90, 0x7c, 0xc3, 0x6f, 0x17, 0xa5, 0xa0, 0x67, 0x72, 0x17, 0xea, 0x7e, 0x63, 0x14, 0x83, 0xde, 0xc1, 0x71, 0x2d, 0x41, 0x32, 0x7a, 0xf3, 0xd1, 0x2b, 0xd8, 0x2a, 0xa6, 0x46, 0x36}} ,
+ {{0xac, 0xcc, 0x6b, 0x7c, 0xf9, 0xb8, 0x8b, 0x08, 0x5c, 0xd0, 0x7d, 0x8f, 0x73, 0xea, 0x20, 0xda, 0x86, 0xca, 0x00, 0xc7, 0xad, 0x73, 0x4d, 0xe9, 0xe8, 0xa9, 0xda, 0x1f, 0x03, 0x06, 0xdd, 0x24}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x9c, 0xb2, 0x61, 0x0a, 0x98, 0x2a, 0xa5, 0xd7, 0xee, 0xa9, 0xac, 0x65, 0xcb, 0x0a, 0x1e, 0xe2, 0xbe, 0xdc, 0x85, 0x59, 0x0f, 0x9c, 0xa6, 0x57, 0x34, 0xa5, 0x87, 0xeb, 0x7b, 0x1e, 0x0c, 0x3c}} ,
+ {{0x2f, 0xbd, 0x84, 0x63, 0x0d, 0xb5, 0xa0, 0xf0, 0x4b, 0x9e, 0x93, 0xc6, 0x34, 0x9a, 0x34, 0xff, 0x73, 0x19, 0x2f, 0x6e, 0x54, 0x45, 0x2c, 0x92, 0x31, 0x76, 0x34, 0xf1, 0xb2, 0x26, 0xe8, 0x74}}},
+{{{0x0a, 0x67, 0x90, 0x6d, 0x0c, 0x4c, 0xcc, 0xc0, 0xe6, 0xbd, 0xa7, 0x5e, 0x55, 0x8c, 0xcd, 0x58, 0x9b, 0x11, 0xa2, 0xbb, 0x4b, 0xb1, 0x43, 0x04, 0x3c, 0x55, 0xed, 0x23, 0xfe, 0xcd, 0xb1, 0x53}} ,
+ {{0x05, 0xfb, 0x75, 0xf5, 0x01, 0xaf, 0x38, 0x72, 0x58, 0xfc, 0x04, 0x29, 0x34, 0x7a, 0x67, 0xa2, 0x08, 0x50, 0x6e, 0xd0, 0x2b, 0x73, 0xd5, 0xb8, 0xe4, 0x30, 0x96, 0xad, 0x45, 0xdf, 0xa6, 0x5c}}},
+{{{0x0d, 0x88, 0x1a, 0x90, 0x7e, 0xdc, 0xd8, 0xfe, 0xc1, 0x2f, 0x5d, 0x67, 0xee, 0x67, 0x2f, 0xed, 0x6f, 0x55, 0x43, 0x5f, 0x87, 0x14, 0x35, 0x42, 0xd3, 0x75, 0xae, 0xd5, 0xd3, 0x85, 0x1a, 0x76}} ,
+ {{0x87, 0xc8, 0xa0, 0x6e, 0xe1, 0xb0, 0xad, 0x6a, 0x4a, 0x34, 0x71, 0xed, 0x7c, 0xd6, 0x44, 0x03, 0x65, 0x4a, 0x5c, 0x5c, 0x04, 0xf5, 0x24, 0x3f, 0xb0, 0x16, 0x5e, 0x8c, 0xb2, 0xd2, 0xc5, 0x20}}},
+{{{0x98, 0x83, 0xc2, 0x37, 0xa0, 0x41, 0xa8, 0x48, 0x5c, 0x5f, 0xbf, 0xc8, 0xfa, 0x24, 0xe0, 0x59, 0x2c, 0xbd, 0xf6, 0x81, 0x7e, 0x88, 0xe6, 0xca, 0x04, 0xd8, 0x5d, 0x60, 0xbb, 0x74, 0xa7, 0x0b}} ,
+ {{0x21, 0x13, 0x91, 0xbf, 0x77, 0x7a, 0x33, 0xbc, 0xe9, 0x07, 0x39, 0x0a, 0xdd, 0x7d, 0x06, 0x10, 0x9a, 0xee, 0x47, 0x73, 0x1b, 0x15, 0x5a, 0xfb, 0xcd, 0x4d, 0xd0, 0xd2, 0x3a, 0x01, 0xba, 0x54}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x48, 0xd5, 0x39, 0x4a, 0x0b, 0x20, 0x6a, 0x43, 0xa0, 0x07, 0x82, 0x5e, 0x49, 0x7c, 0xc9, 0x47, 0xf1, 0x7c, 0x37, 0xb9, 0x23, 0xef, 0x6b, 0x46, 0x45, 0x8c, 0x45, 0x76, 0xdf, 0x14, 0x6b, 0x6e}} ,
+ {{0x42, 0xc9, 0xca, 0x29, 0x4c, 0x76, 0x37, 0xda, 0x8a, 0x2d, 0x7c, 0x3a, 0x58, 0xf2, 0x03, 0xb4, 0xb5, 0xb9, 0x1a, 0x13, 0x2d, 0xde, 0x5f, 0x6b, 0x9d, 0xba, 0x52, 0xc9, 0x5d, 0xb3, 0xf3, 0x30}}},
+{{{0x4c, 0x6f, 0xfe, 0x6b, 0x0c, 0x62, 0xd7, 0x48, 0x71, 0xef, 0xb1, 0x85, 0x79, 0xc0, 0xed, 0x24, 0xb1, 0x08, 0x93, 0x76, 0x8e, 0xf7, 0x38, 0x8e, 0xeb, 0xfe, 0x80, 0x40, 0xaf, 0x90, 0x64, 0x49}} ,
+ {{0x4a, 0x88, 0xda, 0xc1, 0x98, 0x44, 0x3c, 0x53, 0x4e, 0xdb, 0x4b, 0xb9, 0x12, 0x5f, 0xcd, 0x08, 0x04, 0xef, 0x75, 0xe7, 0xb1, 0x3a, 0xe5, 0x07, 0xfa, 0xca, 0x65, 0x7b, 0x72, 0x10, 0x64, 0x7f}}},
+{{{0x3d, 0x81, 0xf0, 0xeb, 0x16, 0xfd, 0x58, 0x33, 0x8d, 0x7c, 0x1a, 0xfb, 0x20, 0x2c, 0x8a, 0xee, 0x90, 0xbb, 0x33, 0x6d, 0x45, 0xe9, 0x8e, 0x99, 0x85, 0xe1, 0x08, 0x1f, 0xc5, 0xf1, 0xb5, 0x46}} ,
+ {{0xe4, 0xe7, 0x43, 0x4b, 0xa0, 0x3f, 0x2b, 0x06, 0xba, 0x17, 0xae, 0x3d, 0xe6, 0xce, 0xbd, 0xb8, 0xed, 0x74, 0x11, 0x35, 0xec, 0x96, 0xfe, 0x31, 0xe3, 0x0e, 0x7a, 0x4e, 0xc9, 0x1d, 0xcb, 0x20}}},
+{{{0xe0, 0x67, 0xe9, 0x7b, 0xdb, 0x96, 0x5c, 0xb0, 0x32, 0xd0, 0x59, 0x31, 0x90, 0xdc, 0x92, 0x97, 0xac, 0x09, 0x38, 0x31, 0x0f, 0x7e, 0xd6, 0x5d, 0xd0, 0x06, 0xb6, 0x1f, 0xea, 0xf0, 0x5b, 0x07}} ,
+ {{0x81, 0x9f, 0xc7, 0xde, 0x6b, 0x41, 0x22, 0x35, 0x14, 0x67, 0x77, 0x3e, 0x90, 0x81, 0xb0, 0xd9, 0x85, 0x4c, 0xca, 0x9b, 0x3f, 0x04, 0x59, 0xd6, 0xaa, 0x17, 0xc3, 0x88, 0x34, 0x37, 0xba, 0x43}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x4c, 0xb6, 0x69, 0xc8, 0x81, 0x95, 0x94, 0x33, 0x92, 0x34, 0xe9, 0x3c, 0x84, 0x0d, 0x3d, 0x5a, 0x37, 0x9c, 0x22, 0xa0, 0xaa, 0x65, 0xce, 0xb4, 0xc2, 0x2d, 0x66, 0x67, 0x02, 0xff, 0x74, 0x10}} ,
+ {{0x22, 0xb0, 0xd5, 0xe6, 0xc7, 0xef, 0xb1, 0xa7, 0x13, 0xda, 0x60, 0xb4, 0x80, 0xc1, 0x42, 0x7d, 0x10, 0x70, 0x97, 0x04, 0x4d, 0xda, 0x23, 0x89, 0xc2, 0x0e, 0x68, 0xcb, 0xde, 0xe0, 0x9b, 0x29}}},
+{{{0x33, 0xfe, 0x42, 0x2a, 0x36, 0x2b, 0x2e, 0x36, 0x64, 0x5c, 0x8b, 0xcc, 0x81, 0x6a, 0x15, 0x08, 0xa1, 0x27, 0xe8, 0x57, 0xe5, 0x78, 0x8e, 0xf2, 0x58, 0x19, 0x12, 0x42, 0xae, 0xc4, 0x63, 0x3e}} ,
+ {{0x78, 0x96, 0x9c, 0xa7, 0xca, 0x80, 0xae, 0x02, 0x85, 0xb1, 0x7c, 0x04, 0x5c, 0xc1, 0x5b, 0x26, 0xc1, 0xba, 0xed, 0xa5, 0x59, 0x70, 0x85, 0x8c, 0x8c, 0xe8, 0x87, 0xac, 0x6a, 0x28, 0x99, 0x35}}},
+{{{0x9f, 0x04, 0x08, 0x28, 0xbe, 0x87, 0xda, 0x80, 0x28, 0x38, 0xde, 0x9f, 0xcd, 0xe4, 0xe3, 0x62, 0xfb, 0x2e, 0x46, 0x8d, 0x01, 0xb3, 0x06, 0x51, 0xd4, 0x19, 0x3b, 0x11, 0xfa, 0xe2, 0xad, 0x1e}} ,
+ {{0xa0, 0x20, 0x99, 0x69, 0x0a, 0xae, 0xa3, 0x70, 0x4e, 0x64, 0x80, 0xb7, 0x85, 0x9c, 0x87, 0x54, 0x43, 0x43, 0x55, 0x80, 0x6d, 0x8d, 0x7c, 0xa9, 0x64, 0xca, 0x6c, 0x2e, 0x21, 0xd8, 0xc8, 0x6c}}},
+{{{0x91, 0x4a, 0x07, 0xad, 0x08, 0x75, 0xc1, 0x4f, 0xa4, 0xb2, 0xc3, 0x6f, 0x46, 0x3e, 0xb1, 0xce, 0x52, 0xab, 0x67, 0x09, 0x54, 0x48, 0x6b, 0x6c, 0xd7, 0x1d, 0x71, 0x76, 0xcb, 0xff, 0xdd, 0x31}} ,
+ {{0x36, 0x88, 0xfa, 0xfd, 0xf0, 0x36, 0x6f, 0x07, 0x74, 0x88, 0x50, 0xd0, 0x95, 0x38, 0x4a, 0x48, 0x2e, 0x07, 0x64, 0x97, 0x11, 0x76, 0x01, 0x1a, 0x27, 0x4d, 0x8e, 0x25, 0x9a, 0x9b, 0x1c, 0x22}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xbe, 0x57, 0xbd, 0x0e, 0x0f, 0xac, 0x5e, 0x76, 0xa3, 0x71, 0xad, 0x2b, 0x10, 0x45, 0x02, 0xec, 0x59, 0xd5, 0x5d, 0xa9, 0x44, 0xcc, 0x25, 0x4c, 0xb3, 0x3c, 0x5b, 0x69, 0x07, 0x55, 0x26, 0x6b}} ,
+ {{0x30, 0x6b, 0xd4, 0xa7, 0x51, 0x29, 0xe3, 0xf9, 0x7a, 0x75, 0x2a, 0x82, 0x2f, 0xd6, 0x1d, 0x99, 0x2b, 0x80, 0xd5, 0x67, 0x1e, 0x15, 0x9d, 0xca, 0xfd, 0xeb, 0xac, 0x97, 0x35, 0x09, 0x7f, 0x3f}}},
+{{{0x35, 0x0d, 0x34, 0x0a, 0xb8, 0x67, 0x56, 0x29, 0x20, 0xf3, 0x19, 0x5f, 0xe2, 0x83, 0x42, 0x73, 0x53, 0xa8, 0xc5, 0x02, 0x19, 0x33, 0xb4, 0x64, 0xbd, 0xc3, 0x87, 0x8c, 0xd7, 0x76, 0xed, 0x25}} ,
+ {{0x47, 0x39, 0x37, 0x76, 0x0d, 0x1d, 0x0c, 0xf5, 0x5a, 0x6d, 0x43, 0x88, 0x99, 0x15, 0xb4, 0x52, 0x0f, 0x2a, 0xb3, 0xb0, 0x3f, 0xa6, 0xb3, 0x26, 0xb3, 0xc7, 0x45, 0xf5, 0x92, 0x5f, 0x9b, 0x17}}},
+{{{0x9d, 0x23, 0xbd, 0x15, 0xfe, 0x52, 0x52, 0x15, 0x26, 0x79, 0x86, 0xba, 0x06, 0x56, 0x66, 0xbb, 0x8c, 0x2e, 0x10, 0x11, 0xd5, 0x4a, 0x18, 0x52, 0xda, 0x84, 0x44, 0xf0, 0x3e, 0xe9, 0x8c, 0x35}} ,
+ {{0xad, 0xa0, 0x41, 0xec, 0xc8, 0x4d, 0xb9, 0xd2, 0x6e, 0x96, 0x4e, 0x5b, 0xc5, 0xc2, 0xa0, 0x1b, 0xcf, 0x0c, 0xbf, 0x17, 0x66, 0x57, 0xc1, 0x17, 0x90, 0x45, 0x71, 0xc2, 0xe1, 0x24, 0xeb, 0x27}}},
+{{{0x2c, 0xb9, 0x42, 0xa4, 0xaf, 0x3b, 0x42, 0x0e, 0xc2, 0x0f, 0xf2, 0xea, 0x83, 0xaf, 0x9a, 0x13, 0x17, 0xb0, 0xbd, 0x89, 0x17, 0xe3, 0x72, 0xcb, 0x0e, 0x76, 0x7e, 0x41, 0x63, 0x04, 0x88, 0x71}} ,
+ {{0x75, 0x78, 0x38, 0x86, 0x57, 0xdd, 0x9f, 0xee, 0x54, 0x70, 0x65, 0xbf, 0xf1, 0x2c, 0xe0, 0x39, 0x0d, 0xe3, 0x89, 0xfd, 0x8e, 0x93, 0x4f, 0x43, 0xdc, 0xd5, 0x5b, 0xde, 0xf9, 0x98, 0xe5, 0x7b}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xe7, 0x3b, 0x65, 0x11, 0xdf, 0xb2, 0xf2, 0x63, 0x94, 0x12, 0x6f, 0x5c, 0x9e, 0x77, 0xc1, 0xb6, 0xd8, 0xab, 0x58, 0x7a, 0x1d, 0x95, 0x73, 0xdd, 0xe7, 0xe3, 0x6f, 0xf2, 0x03, 0x1d, 0xdb, 0x76}} ,
+ {{0xae, 0x06, 0x4e, 0x2c, 0x52, 0x1b, 0xbc, 0x5a, 0x5a, 0xa5, 0xbe, 0x27, 0xbd, 0xeb, 0xe1, 0x14, 0x17, 0x68, 0x26, 0x07, 0x03, 0xd1, 0x18, 0x0b, 0xdf, 0xf1, 0x06, 0x5c, 0xa6, 0x1b, 0xb9, 0x24}}},
+{{{0xc5, 0x66, 0x80, 0x13, 0x0e, 0x48, 0x8c, 0x87, 0x31, 0x84, 0xb4, 0x60, 0xed, 0xc5, 0xec, 0xb6, 0xc5, 0x05, 0x33, 0x5f, 0x2f, 0x7d, 0x40, 0xb6, 0x32, 0x1d, 0x38, 0x74, 0x1b, 0xf1, 0x09, 0x3d}} ,
+ {{0xd4, 0x69, 0x82, 0xbc, 0x8d, 0xf8, 0x34, 0x36, 0x75, 0x55, 0x18, 0x55, 0x58, 0x3c, 0x79, 0xaf, 0x26, 0x80, 0xab, 0x9b, 0x95, 0x00, 0xf1, 0xcb, 0xda, 0xc1, 0x9f, 0xf6, 0x2f, 0xa2, 0xf4, 0x45}}},
+{{{0x17, 0xbe, 0xeb, 0x85, 0xed, 0x9e, 0xcd, 0x56, 0xf5, 0x17, 0x45, 0x42, 0xb4, 0x1f, 0x44, 0x4c, 0x05, 0x74, 0x15, 0x47, 0x00, 0xc6, 0x6a, 0x3d, 0x24, 0x09, 0x0d, 0x58, 0xb1, 0x42, 0xd7, 0x04}} ,
+ {{0x8d, 0xbd, 0xa3, 0xc4, 0x06, 0x9b, 0x1f, 0x90, 0x58, 0x60, 0x74, 0xb2, 0x00, 0x3b, 0x3c, 0xd2, 0xda, 0x82, 0xbb, 0x10, 0x90, 0x69, 0x92, 0xa9, 0xb4, 0x30, 0x81, 0xe3, 0x7c, 0xa8, 0x89, 0x45}}},
+{{{0x3f, 0xdc, 0x05, 0xcb, 0x41, 0x3c, 0xc8, 0x23, 0x04, 0x2c, 0x38, 0x99, 0xe3, 0x68, 0x55, 0xf9, 0xd3, 0x32, 0xc7, 0xbf, 0xfa, 0xd4, 0x1b, 0x5d, 0xde, 0xdc, 0x10, 0x42, 0xc0, 0x42, 0xd9, 0x75}} ,
+ {{0x2d, 0xab, 0x35, 0x4e, 0x87, 0xc4, 0x65, 0x97, 0x67, 0x24, 0xa4, 0x47, 0xad, 0x3f, 0x8e, 0xf3, 0xcb, 0x31, 0x17, 0x77, 0xc5, 0xe2, 0xd7, 0x8f, 0x3c, 0xc1, 0xcd, 0x56, 0x48, 0xc1, 0x6c, 0x69}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x14, 0xae, 0x5f, 0x88, 0x7b, 0xa5, 0x90, 0xdf, 0x10, 0xb2, 0x8b, 0x5e, 0x24, 0x17, 0xc3, 0xa3, 0xd4, 0x0f, 0x92, 0x61, 0x1a, 0x19, 0x5a, 0xad, 0x76, 0xbd, 0xd8, 0x1c, 0xdd, 0xe0, 0x12, 0x6d}} ,
+ {{0x8e, 0xbd, 0x70, 0x8f, 0x02, 0xa3, 0x24, 0x4d, 0x5a, 0x67, 0xc4, 0xda, 0xf7, 0x20, 0x0f, 0x81, 0x5b, 0x7a, 0x05, 0x24, 0x67, 0x83, 0x0b, 0x2a, 0x80, 0xe7, 0xfd, 0x74, 0x4b, 0x9e, 0x5c, 0x0d}}},
+{{{0x94, 0xd5, 0x5f, 0x1f, 0xa2, 0xfb, 0xeb, 0xe1, 0x07, 0x34, 0xf8, 0x20, 0xad, 0x81, 0x30, 0x06, 0x2d, 0xa1, 0x81, 0x95, 0x36, 0xcf, 0x11, 0x0b, 0xaf, 0xc1, 0x2b, 0x9a, 0x6c, 0x55, 0xc1, 0x16}} ,
+ {{0x36, 0x4f, 0xf1, 0x5e, 0x74, 0x35, 0x13, 0x28, 0xd7, 0x11, 0xcf, 0xb8, 0xde, 0x93, 0xb3, 0x05, 0xb8, 0xb5, 0x73, 0xe9, 0xeb, 0xad, 0x19, 0x1e, 0x89, 0x0f, 0x8b, 0x15, 0xd5, 0x8c, 0xe3, 0x23}}},
+{{{0x33, 0x79, 0xe7, 0x18, 0xe6, 0x0f, 0x57, 0x93, 0x15, 0xa0, 0xa7, 0xaa, 0xc4, 0xbf, 0x4f, 0x30, 0x74, 0x95, 0x5e, 0x69, 0x4a, 0x5b, 0x45, 0xe4, 0x00, 0xeb, 0x23, 0x74, 0x4c, 0xdf, 0x6b, 0x45}} ,
+ {{0x97, 0x29, 0x6c, 0xc4, 0x42, 0x0b, 0xdd, 0xc0, 0x29, 0x5c, 0x9b, 0x34, 0x97, 0xd0, 0xc7, 0x79, 0x80, 0x63, 0x74, 0xe4, 0x8e, 0x37, 0xb0, 0x2b, 0x7c, 0xe8, 0x68, 0x6c, 0xc3, 0x82, 0x97, 0x57}}},
+{{{0x22, 0xbe, 0x83, 0xb6, 0x4b, 0x80, 0x6b, 0x43, 0x24, 0x5e, 0xef, 0x99, 0x9b, 0xa8, 0xfc, 0x25, 0x8d, 0x3b, 0x03, 0x94, 0x2b, 0x3e, 0xe7, 0x95, 0x76, 0x9b, 0xcc, 0x15, 0xdb, 0x32, 0xe6, 0x66}} ,
+ {{0x84, 0xf0, 0x4a, 0x13, 0xa6, 0xd6, 0xfa, 0x93, 0x46, 0x07, 0xf6, 0x7e, 0x5c, 0x6d, 0x5e, 0xf6, 0xa6, 0xe7, 0x48, 0xf0, 0x06, 0xea, 0xff, 0x90, 0xc1, 0xcc, 0x4c, 0x19, 0x9c, 0x3c, 0x4e, 0x53}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x2a, 0x50, 0xe3, 0x07, 0x15, 0x59, 0xf2, 0x8b, 0x81, 0xf2, 0xf3, 0xd3, 0x6c, 0x99, 0x8c, 0x70, 0x67, 0xec, 0xcc, 0xee, 0x9e, 0x59, 0x45, 0x59, 0x7d, 0x47, 0x75, 0x69, 0xf5, 0x24, 0x93, 0x5d}} ,
+ {{0x6a, 0x4f, 0x1b, 0xbe, 0x6b, 0x30, 0xcf, 0x75, 0x46, 0xe3, 0x7b, 0x9d, 0xfc, 0xcd, 0xd8, 0x5c, 0x1f, 0xb4, 0xc8, 0xe2, 0x24, 0xec, 0x1a, 0x28, 0x05, 0x32, 0x57, 0xfd, 0x3c, 0x5a, 0x98, 0x10}}},
+{{{0xa3, 0xdb, 0xf7, 0x30, 0xd8, 0xc2, 0x9a, 0xe1, 0xd3, 0xce, 0x22, 0xe5, 0x80, 0x1e, 0xd9, 0xe4, 0x1f, 0xab, 0xc0, 0x71, 0x1a, 0x86, 0x0e, 0x27, 0x99, 0x5b, 0xfa, 0x76, 0x99, 0xb0, 0x08, 0x3c}} ,
+ {{0x2a, 0x93, 0xd2, 0x85, 0x1b, 0x6a, 0x5d, 0xa6, 0xee, 0xd1, 0xd1, 0x33, 0xbd, 0x6a, 0x36, 0x73, 0x37, 0x3a, 0x44, 0xb4, 0xec, 0xa9, 0x7a, 0xde, 0x83, 0x40, 0xd7, 0xdf, 0x28, 0xba, 0xa2, 0x30}}},
+{{{0xd3, 0xb5, 0x6d, 0x05, 0x3f, 0x9f, 0xf3, 0x15, 0x8d, 0x7c, 0xca, 0xc9, 0xfc, 0x8a, 0x7c, 0x94, 0xb0, 0x63, 0x36, 0x9b, 0x78, 0xd1, 0x91, 0x1f, 0x93, 0xd8, 0x57, 0x43, 0xde, 0x76, 0xa3, 0x43}} ,
+ {{0x9b, 0x35, 0xe2, 0xa9, 0x3d, 0x32, 0x1e, 0xbb, 0x16, 0x28, 0x70, 0xe9, 0x45, 0x2f, 0x8f, 0x70, 0x7f, 0x08, 0x7e, 0x53, 0xc4, 0x7a, 0xbf, 0xf7, 0xe1, 0xa4, 0x6a, 0xd8, 0xac, 0x64, 0x1b, 0x11}}},
+{{{0xb2, 0xeb, 0x47, 0x46, 0x18, 0x3e, 0x1f, 0x99, 0x0c, 0xcc, 0xf1, 0x2c, 0xe0, 0xe7, 0x8f, 0xe0, 0x01, 0x7e, 0x65, 0xb8, 0x0c, 0xd0, 0xfb, 0xc8, 0xb9, 0x90, 0x98, 0x33, 0x61, 0x3b, 0xd8, 0x27}} ,
+ {{0xa0, 0xbe, 0x72, 0x3a, 0x50, 0x4b, 0x74, 0xab, 0x01, 0xc8, 0x93, 0xc5, 0xe4, 0xc7, 0x08, 0x6c, 0xb4, 0xca, 0xee, 0xeb, 0x8e, 0xd7, 0x4e, 0x26, 0xc6, 0x1d, 0xe2, 0x71, 0xaf, 0x89, 0xa0, 0x2a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x98, 0x0b, 0xe4, 0xde, 0xdb, 0xa8, 0xfa, 0x82, 0x74, 0x06, 0x52, 0x6d, 0x08, 0x52, 0x8a, 0xff, 0x62, 0xc5, 0x6a, 0x44, 0x0f, 0x51, 0x8c, 0x1f, 0x6e, 0xb6, 0xc6, 0x2c, 0x81, 0xd3, 0x76, 0x46}} ,
+ {{0xf4, 0x29, 0x74, 0x2e, 0x80, 0xa7, 0x1a, 0x8f, 0xf6, 0xbd, 0xd6, 0x8e, 0xbf, 0xc1, 0x95, 0x2a, 0xeb, 0xa0, 0x7f, 0x45, 0xa0, 0x50, 0x14, 0x05, 0xb1, 0x57, 0x4c, 0x74, 0xb7, 0xe2, 0x89, 0x7d}}},
+{{{0x07, 0xee, 0xa7, 0xad, 0xb7, 0x09, 0x0b, 0x49, 0x4e, 0xbf, 0xca, 0xe5, 0x21, 0xe6, 0xe6, 0xaf, 0xd5, 0x67, 0xf3, 0xce, 0x7e, 0x7c, 0x93, 0x7b, 0x5a, 0x10, 0x12, 0x0e, 0x6c, 0x06, 0x11, 0x75}} ,
+ {{0xd5, 0xfc, 0x86, 0xa3, 0x3b, 0xa3, 0x3e, 0x0a, 0xfb, 0x0b, 0xf7, 0x36, 0xb1, 0x5b, 0xda, 0x70, 0xb7, 0x00, 0xa7, 0xda, 0x88, 0x8f, 0x84, 0xa8, 0xbc, 0x1c, 0x39, 0xb8, 0x65, 0xf3, 0x4d, 0x60}}},
+{{{0x96, 0x9d, 0x31, 0xf4, 0xa2, 0xbe, 0x81, 0xb9, 0xa5, 0x59, 0x9e, 0xba, 0x07, 0xbe, 0x74, 0x58, 0xd8, 0xeb, 0xc5, 0x9f, 0x3d, 0xd1, 0xf4, 0xae, 0xce, 0x53, 0xdf, 0x4f, 0xc7, 0x2a, 0x89, 0x4d}} ,
+ {{0x29, 0xd8, 0xf2, 0xaa, 0xe9, 0x0e, 0xf7, 0x2e, 0x5f, 0x9d, 0x8a, 0x5b, 0x09, 0xed, 0xc9, 0x24, 0x22, 0xf4, 0x0f, 0x25, 0x8f, 0x1c, 0x84, 0x6e, 0x34, 0x14, 0x6c, 0xea, 0xb3, 0x86, 0x5d, 0x04}}},
+{{{0x07, 0x98, 0x61, 0xe8, 0x6a, 0xd2, 0x81, 0x49, 0x25, 0xd5, 0x5b, 0x18, 0xc7, 0x35, 0x52, 0x51, 0xa4, 0x46, 0xad, 0x18, 0x0d, 0xc9, 0x5f, 0x18, 0x91, 0x3b, 0xb4, 0xc0, 0x60, 0x59, 0x8d, 0x66}} ,
+ {{0x03, 0x1b, 0x79, 0x53, 0x6e, 0x24, 0xae, 0x57, 0xd9, 0x58, 0x09, 0x85, 0x48, 0xa2, 0xd3, 0xb5, 0xe2, 0x4d, 0x11, 0x82, 0xe6, 0x86, 0x3c, 0xe9, 0xb1, 0x00, 0x19, 0xc2, 0x57, 0xf7, 0x66, 0x7a}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x0f, 0xe3, 0x89, 0x03, 0xd7, 0x22, 0x95, 0x9f, 0xca, 0xb4, 0x8d, 0x9e, 0x6d, 0x97, 0xff, 0x8d, 0x21, 0x59, 0x07, 0xef, 0x03, 0x2d, 0x5e, 0xf8, 0x44, 0x46, 0xe7, 0x85, 0x80, 0xc5, 0x89, 0x50}} ,
+ {{0x8b, 0xd8, 0x53, 0x86, 0x24, 0x86, 0x29, 0x52, 0x01, 0xfa, 0x20, 0xc3, 0x4e, 0x95, 0xcb, 0xad, 0x7b, 0x34, 0x94, 0x30, 0xb7, 0x7a, 0xfa, 0x96, 0x41, 0x60, 0x2b, 0xcb, 0x59, 0xb9, 0xca, 0x50}}},
+{{{0xc2, 0x5b, 0x9b, 0x78, 0x23, 0x1b, 0x3a, 0x88, 0x94, 0x5f, 0x0a, 0x9b, 0x98, 0x2b, 0x6e, 0x53, 0x11, 0xf6, 0xff, 0xc6, 0x7d, 0x42, 0xcc, 0x02, 0x80, 0x40, 0x0d, 0x1e, 0xfb, 0xaf, 0x61, 0x07}} ,
+ {{0xb0, 0xe6, 0x2f, 0x81, 0x70, 0xa1, 0x2e, 0x39, 0x04, 0x7c, 0xc4, 0x2c, 0x87, 0x45, 0x4a, 0x5b, 0x69, 0x97, 0xac, 0x6d, 0x2c, 0x10, 0x42, 0x7c, 0x3b, 0x15, 0x70, 0x60, 0x0e, 0x11, 0x6d, 0x3a}}},
+{{{0x9b, 0x18, 0x80, 0x5e, 0xdb, 0x05, 0xbd, 0xc6, 0xb7, 0x3c, 0xc2, 0x40, 0x4d, 0x5d, 0xce, 0x97, 0x8a, 0x34, 0x15, 0xab, 0x28, 0x5d, 0x10, 0xf0, 0x37, 0x0c, 0xcc, 0x16, 0xfa, 0x1f, 0x33, 0x0d}} ,
+ {{0x19, 0xf9, 0x35, 0xaa, 0x59, 0x1a, 0x0c, 0x5c, 0x06, 0xfc, 0x6a, 0x0b, 0x97, 0x53, 0x36, 0xfc, 0x2a, 0xa5, 0x5a, 0x9b, 0x30, 0xef, 0x23, 0xaf, 0x39, 0x5d, 0x9a, 0x6b, 0x75, 0x57, 0x48, 0x0b}}},
+{{{0x26, 0xdc, 0x76, 0x3b, 0xfc, 0xf9, 0x9c, 0x3f, 0x89, 0x0b, 0x62, 0x53, 0xaf, 0x83, 0x01, 0x2e, 0xbc, 0x6a, 0xc6, 0x03, 0x0d, 0x75, 0x2a, 0x0d, 0xe6, 0x94, 0x54, 0xcf, 0xb3, 0xe5, 0x96, 0x25}} ,
+ {{0xfe, 0x82, 0xb1, 0x74, 0x31, 0x8a, 0xa7, 0x6f, 0x56, 0xbd, 0x8d, 0xf4, 0xe0, 0x94, 0x51, 0x59, 0xde, 0x2c, 0x5a, 0xf4, 0x84, 0x6b, 0x4a, 0x88, 0x93, 0xc0, 0x0c, 0x9a, 0xac, 0xa7, 0xa0, 0x68}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x25, 0x0d, 0xd6, 0xc7, 0x23, 0x47, 0x10, 0xad, 0xc7, 0x08, 0x5c, 0x87, 0x87, 0x93, 0x98, 0x18, 0xb8, 0xd3, 0x9c, 0xac, 0x5a, 0x3d, 0xc5, 0x75, 0xf8, 0x49, 0x32, 0x14, 0xcc, 0x51, 0x96, 0x24}} ,
+ {{0x65, 0x9c, 0x5d, 0xf0, 0x37, 0x04, 0xf0, 0x34, 0x69, 0x2a, 0xf0, 0xa5, 0x64, 0xca, 0xde, 0x2b, 0x5b, 0x15, 0x10, 0xd2, 0xab, 0x06, 0xdd, 0xc4, 0xb0, 0xb6, 0x5b, 0xc1, 0x17, 0xdf, 0x8f, 0x02}}},
+{{{0xbd, 0x59, 0x3d, 0xbf, 0x5c, 0x31, 0x44, 0x2c, 0x32, 0x94, 0x04, 0x60, 0x84, 0x0f, 0xad, 0x00, 0xb6, 0x8f, 0xc9, 0x1d, 0xcc, 0x5c, 0xa2, 0x49, 0x0e, 0x50, 0x91, 0x08, 0x9a, 0x43, 0x55, 0x05}} ,
+ {{0x5d, 0x93, 0x55, 0xdf, 0x9b, 0x12, 0x19, 0xec, 0x93, 0x85, 0x42, 0x9e, 0x66, 0x0f, 0x9d, 0xaf, 0x99, 0xaf, 0x26, 0x89, 0xbc, 0x61, 0xfd, 0xff, 0xce, 0x4b, 0xf4, 0x33, 0x95, 0xc9, 0x35, 0x58}}},
+{{{0x12, 0x55, 0xf9, 0xda, 0xcb, 0x44, 0xa7, 0xdc, 0x57, 0xe2, 0xf9, 0x9a, 0xe6, 0x07, 0x23, 0x60, 0x54, 0xa7, 0x39, 0xa5, 0x9b, 0x84, 0x56, 0x6e, 0xaa, 0x8b, 0x8f, 0xb0, 0x2c, 0x87, 0xaf, 0x67}} ,
+ {{0x00, 0xa9, 0x4c, 0xb2, 0x12, 0xf8, 0x32, 0xa8, 0x7a, 0x00, 0x4b, 0x49, 0x32, 0xba, 0x1f, 0x5d, 0x44, 0x8e, 0x44, 0x7a, 0xdc, 0x11, 0xfb, 0x39, 0x08, 0x57, 0x87, 0xa5, 0x12, 0x42, 0x93, 0x0e}}},
+{{{0x17, 0xb4, 0xae, 0x72, 0x59, 0xd0, 0xaa, 0xa8, 0x16, 0x8b, 0x63, 0x11, 0xb3, 0x43, 0x04, 0xda, 0x0c, 0xa8, 0xb7, 0x68, 0xdd, 0x4e, 0x54, 0xe7, 0xaf, 0x5d, 0x5d, 0x05, 0x76, 0x36, 0xec, 0x0d}} ,
+ {{0x6d, 0x7c, 0x82, 0x32, 0x38, 0x55, 0x57, 0x74, 0x5b, 0x7d, 0xc3, 0xc4, 0xfb, 0x06, 0x29, 0xf0, 0x13, 0x55, 0x54, 0xc6, 0xa7, 0xdc, 0x4c, 0x9f, 0x98, 0x49, 0x20, 0xa8, 0xc3, 0x8d, 0xfa, 0x48}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x87, 0x47, 0x9d, 0xe9, 0x25, 0xd5, 0xe3, 0x47, 0x78, 0xdf, 0x85, 0xa7, 0x85, 0x5e, 0x7a, 0x4c, 0x5f, 0x79, 0x1a, 0xf3, 0xa2, 0xb2, 0x28, 0xa0, 0x9c, 0xdd, 0x30, 0x40, 0xd4, 0x38, 0xbd, 0x28}} ,
+ {{0xfc, 0xbb, 0xd5, 0x78, 0x6d, 0x1d, 0xd4, 0x99, 0xb4, 0xaa, 0x44, 0x44, 0x7a, 0x1b, 0xd8, 0xfe, 0xb4, 0x99, 0xb9, 0xcc, 0xe7, 0xc4, 0xd3, 0x3a, 0x73, 0x83, 0x41, 0x5c, 0x40, 0xd7, 0x2d, 0x55}}},
+{{{0x26, 0xe1, 0x7b, 0x5f, 0xe5, 0xdc, 0x3f, 0x7d, 0xa1, 0xa7, 0x26, 0x44, 0x22, 0x23, 0xc0, 0x8f, 0x7d, 0xf1, 0xb5, 0x11, 0x47, 0x7b, 0x19, 0xd4, 0x75, 0x6f, 0x1e, 0xa5, 0x27, 0xfe, 0xc8, 0x0e}} ,
+ {{0xd3, 0x11, 0x3d, 0xab, 0xef, 0x2c, 0xed, 0xb1, 0x3d, 0x7c, 0x32, 0x81, 0x6b, 0xfe, 0xf8, 0x1c, 0x3c, 0x7b, 0xc0, 0x61, 0xdf, 0xb8, 0x75, 0x76, 0x7f, 0xaa, 0xd8, 0x93, 0xaf, 0x3d, 0xe8, 0x3d}}},
+{{{0xfd, 0x5b, 0x4e, 0x8d, 0xb6, 0x7e, 0x82, 0x9b, 0xef, 0xce, 0x04, 0x69, 0x51, 0x52, 0xff, 0xef, 0xa0, 0x52, 0xb5, 0x79, 0x17, 0x5e, 0x2f, 0xde, 0xd6, 0x3c, 0x2d, 0xa0, 0x43, 0xb4, 0x0b, 0x19}} ,
+ {{0xc0, 0x61, 0x48, 0x48, 0x17, 0xf4, 0x9e, 0x18, 0x51, 0x2d, 0xea, 0x2f, 0xf2, 0xf2, 0xe0, 0xa3, 0x14, 0xb7, 0x8b, 0x3a, 0x30, 0xf5, 0x81, 0xc1, 0x5d, 0x71, 0x39, 0x62, 0x55, 0x1f, 0x60, 0x5a}}},
+{{{0xe5, 0x89, 0x8a, 0x76, 0x6c, 0xdb, 0x4d, 0x0a, 0x5b, 0x72, 0x9d, 0x59, 0x6e, 0x63, 0x63, 0x18, 0x7c, 0xe3, 0xfa, 0xe2, 0xdb, 0xa1, 0x8d, 0xf4, 0xa5, 0xd7, 0x16, 0xb2, 0xd0, 0xb3, 0x3f, 0x39}} ,
+ {{0xce, 0x60, 0x09, 0x6c, 0xf5, 0x76, 0x17, 0x24, 0x80, 0x3a, 0x96, 0xc7, 0x94, 0x2e, 0xf7, 0x6b, 0xef, 0xb5, 0x05, 0x96, 0xef, 0xd3, 0x7b, 0x51, 0xda, 0x05, 0x44, 0x67, 0xbc, 0x07, 0x21, 0x4e}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xe9, 0x73, 0x6f, 0x21, 0xb9, 0xde, 0x22, 0x7d, 0xeb, 0x97, 0x31, 0x10, 0xa3, 0xea, 0xe1, 0xc6, 0x37, 0xeb, 0x8f, 0x43, 0x58, 0xde, 0x41, 0x64, 0x0e, 0x3e, 0x07, 0x99, 0x3d, 0xf1, 0xdf, 0x1e}} ,
+ {{0xf8, 0xad, 0x43, 0xc2, 0x17, 0x06, 0xe2, 0xe4, 0xa9, 0x86, 0xcd, 0x18, 0xd7, 0x78, 0xc8, 0x74, 0x66, 0xd2, 0x09, 0x18, 0xa5, 0xf1, 0xca, 0xa6, 0x62, 0x92, 0xc1, 0xcb, 0x00, 0xeb, 0x42, 0x2e}}},
+{{{0x7b, 0x34, 0x24, 0x4c, 0xcf, 0x38, 0xe5, 0x6c, 0x0a, 0x01, 0x2c, 0x22, 0x0b, 0x24, 0x38, 0xad, 0x24, 0x7e, 0x19, 0xf0, 0x6c, 0xf9, 0x31, 0xf4, 0x35, 0x11, 0xf6, 0x46, 0x33, 0x3a, 0x23, 0x59}} ,
+ {{0x20, 0x0b, 0xa1, 0x08, 0x19, 0xad, 0x39, 0x54, 0xea, 0x3e, 0x23, 0x09, 0xb6, 0xe2, 0xd2, 0xbc, 0x4d, 0xfc, 0x9c, 0xf0, 0x13, 0x16, 0x22, 0x3f, 0xb9, 0xd2, 0x11, 0x86, 0x90, 0x55, 0xce, 0x3c}}},
+{{{0xc4, 0x0b, 0x4b, 0x62, 0x99, 0x37, 0x84, 0x3f, 0x74, 0xa2, 0xf9, 0xce, 0xe2, 0x0b, 0x0f, 0x2a, 0x3d, 0xa3, 0xe3, 0xdb, 0x5a, 0x9d, 0x93, 0xcc, 0xa5, 0xef, 0x82, 0x91, 0x1d, 0xe6, 0x6c, 0x68}} ,
+ {{0xa3, 0x64, 0x17, 0x9b, 0x8b, 0xc8, 0x3a, 0x61, 0xe6, 0x9d, 0xc6, 0xed, 0x7b, 0x03, 0x52, 0x26, 0x9d, 0x3a, 0xb3, 0x13, 0xcc, 0x8a, 0xfd, 0x2c, 0x1a, 0x1d, 0xed, 0x13, 0xd0, 0x55, 0x57, 0x0e}}},
+{{{0x1a, 0xea, 0xbf, 0xfd, 0x4a, 0x3c, 0x8e, 0xec, 0x29, 0x7e, 0x77, 0x77, 0x12, 0x99, 0xd7, 0x84, 0xf9, 0x55, 0x7f, 0xf1, 0x8b, 0xb4, 0xd2, 0x95, 0xa3, 0x8d, 0xf0, 0x8a, 0xa7, 0xeb, 0x82, 0x4b}} ,
+ {{0x2c, 0x28, 0xf4, 0x3a, 0xf6, 0xde, 0x0a, 0xe0, 0x41, 0x44, 0x23, 0xf8, 0x3f, 0x03, 0x64, 0x9f, 0xc3, 0x55, 0x4c, 0xc6, 0xc1, 0x94, 0x1c, 0x24, 0x5d, 0x5f, 0x92, 0x45, 0x96, 0x57, 0x37, 0x14}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xc1, 0xcd, 0x90, 0x66, 0xb9, 0x76, 0xa0, 0x5b, 0xa5, 0x85, 0x75, 0x23, 0xf9, 0x89, 0xa5, 0x82, 0xb2, 0x6f, 0xb1, 0xeb, 0xc4, 0x69, 0x6f, 0x18, 0x5a, 0xed, 0x94, 0x3d, 0x9d, 0xd9, 0x2c, 0x1a}} ,
+ {{0x35, 0xb0, 0xe6, 0x73, 0x06, 0xb7, 0x37, 0xe0, 0xf8, 0xb0, 0x22, 0xe8, 0xd2, 0xed, 0x0b, 0xef, 0xe6, 0xc6, 0x5a, 0x99, 0x9e, 0x1a, 0x9f, 0x04, 0x97, 0xe4, 0x4d, 0x0b, 0xbe, 0xba, 0x44, 0x40}}},
+{{{0xc1, 0x56, 0x96, 0x91, 0x5f, 0x1f, 0xbb, 0x54, 0x6f, 0x88, 0x89, 0x0a, 0xb2, 0xd6, 0x41, 0x42, 0x6a, 0x82, 0xee, 0x14, 0xaa, 0x76, 0x30, 0x65, 0x0f, 0x67, 0x39, 0xa6, 0x51, 0x7c, 0x49, 0x24}} ,
+ {{0x35, 0xa3, 0x78, 0xd1, 0x11, 0x0f, 0x75, 0xd3, 0x70, 0x46, 0xdb, 0x20, 0x51, 0xcb, 0x92, 0x80, 0x54, 0x10, 0x74, 0x36, 0x86, 0xa9, 0xd7, 0xa3, 0x08, 0x78, 0xf1, 0x01, 0x29, 0xf8, 0x80, 0x3b}}},
+{{{0xdb, 0xa7, 0x9d, 0x9d, 0xbf, 0xa0, 0xcc, 0xed, 0x53, 0xa2, 0xa2, 0x19, 0x39, 0x48, 0x83, 0x19, 0x37, 0x58, 0xd1, 0x04, 0x28, 0x40, 0xf7, 0x8a, 0xc2, 0x08, 0xb7, 0xa5, 0x42, 0xcf, 0x53, 0x4c}} ,
+ {{0xa7, 0xbb, 0xf6, 0x8e, 0xad, 0xdd, 0xf7, 0x90, 0xdd, 0x5f, 0x93, 0x89, 0xae, 0x04, 0x37, 0xe6, 0x9a, 0xb7, 0xe8, 0xc0, 0xdf, 0x16, 0x2a, 0xbf, 0xc4, 0x3a, 0x3c, 0x41, 0xd5, 0x89, 0x72, 0x5a}}},
+{{{0x1f, 0x96, 0xff, 0x34, 0x2c, 0x13, 0x21, 0xcb, 0x0a, 0x89, 0x85, 0xbe, 0xb3, 0x70, 0x9e, 0x1e, 0xde, 0x97, 0xaf, 0x96, 0x30, 0xf7, 0x48, 0x89, 0x40, 0x8d, 0x07, 0xf1, 0x25, 0xf0, 0x30, 0x58}} ,
+ {{0x1e, 0xd4, 0x93, 0x57, 0xe2, 0x17, 0xe7, 0x9d, 0xab, 0x3c, 0x55, 0x03, 0x82, 0x2f, 0x2b, 0xdb, 0x56, 0x1e, 0x30, 0x2e, 0x24, 0x47, 0x6e, 0xe6, 0xff, 0x33, 0x24, 0x2c, 0x75, 0x51, 0xd4, 0x67}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0x2b, 0x06, 0xd9, 0xa1, 0x5d, 0xe1, 0xf4, 0xd1, 0x1e, 0x3c, 0x9a, 0xc6, 0x29, 0x2b, 0x13, 0x13, 0x78, 0xc0, 0xd8, 0x16, 0x17, 0x2d, 0x9e, 0xa9, 0xc9, 0x79, 0x57, 0xab, 0x24, 0x91, 0x92, 0x19}} ,
+ {{0x69, 0xfb, 0xa1, 0x9c, 0xa6, 0x75, 0x49, 0x7d, 0x60, 0x73, 0x40, 0x42, 0xc4, 0x13, 0x0a, 0x95, 0x79, 0x1e, 0x04, 0x83, 0x94, 0x99, 0x9b, 0x1e, 0x0c, 0xe8, 0x1f, 0x54, 0xef, 0xcb, 0xc0, 0x52}}},
+{{{0x14, 0x89, 0x73, 0xa1, 0x37, 0x87, 0x6a, 0x7a, 0xcf, 0x1d, 0xd9, 0x2e, 0x1a, 0x67, 0xed, 0x74, 0xc0, 0xf0, 0x9c, 0x33, 0xdd, 0xdf, 0x08, 0xbf, 0x7b, 0xd1, 0x66, 0xda, 0xe6, 0xc9, 0x49, 0x08}} ,
+ {{0xe9, 0xdd, 0x5e, 0x55, 0xb0, 0x0a, 0xde, 0x21, 0x4c, 0x5a, 0x2e, 0xd4, 0x80, 0x3a, 0x57, 0x92, 0x7a, 0xf1, 0xc4, 0x2c, 0x40, 0xaf, 0x2f, 0xc9, 0x92, 0x03, 0xe5, 0x5a, 0xbc, 0xdc, 0xf4, 0x09}}},
+{{{0xf3, 0xe1, 0x2b, 0x7c, 0x05, 0x86, 0x80, 0x93, 0x4a, 0xad, 0xb4, 0x8f, 0x7e, 0x99, 0x0c, 0xfd, 0xcd, 0xef, 0xd1, 0xff, 0x2c, 0x69, 0x34, 0x13, 0x41, 0x64, 0xcf, 0x3b, 0xd0, 0x90, 0x09, 0x1e}} ,
+ {{0x9d, 0x45, 0xd6, 0x80, 0xe6, 0x45, 0xaa, 0xf4, 0x15, 0xaa, 0x5c, 0x34, 0x87, 0x99, 0xa2, 0x8c, 0x26, 0x84, 0x62, 0x7d, 0xb6, 0x29, 0xc0, 0x52, 0xea, 0xf5, 0x81, 0x18, 0x0f, 0x35, 0xa9, 0x0e}}},
+{{{0xe7, 0x20, 0x72, 0x7c, 0x6d, 0x94, 0x5f, 0x52, 0x44, 0x54, 0xe3, 0xf1, 0xb2, 0xb0, 0x36, 0x46, 0x0f, 0xae, 0x92, 0xe8, 0x70, 0x9d, 0x6e, 0x79, 0xb1, 0xad, 0x37, 0xa9, 0x5f, 0xc0, 0xde, 0x03}} ,
+ {{0x15, 0x55, 0x37, 0xc6, 0x1c, 0x27, 0x1c, 0x6d, 0x14, 0x4f, 0xca, 0xa4, 0xc4, 0x88, 0x25, 0x46, 0x39, 0xfc, 0x5a, 0xe5, 0xfe, 0x29, 0x11, 0x69, 0xf5, 0x72, 0x84, 0x4d, 0x78, 0x9f, 0x94, 0x15}}},
+{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, 
+ {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}},
+{{{0xec, 0xd3, 0xff, 0x57, 0x0b, 0xb0, 0xb2, 0xdc, 0xf8, 0x4f, 0xe2, 0x12, 0xd5, 0x36, 0xbe, 0x6b, 0x09, 0x43, 0x6d, 0xa3, 0x4d, 0x90, 0x2d, 0xb8, 0x74, 0xe8, 0x71, 0x45, 0x19, 0x8b, 0x0c, 0x6a}} ,
+ {{0xb8, 0x42, 0x1c, 0x03, 0xad, 0x2c, 0x03, 0x8e, 0xac, 0xd7, 0x98, 0x29, 0x13, 0xc6, 0x02, 0x29, 0xb5, 0xd4, 0xe7, 0xcf, 0xcc, 0x8b, 0x83, 0xec, 0x35, 0xc7, 0x9c, 0x74, 0xb7, 0xad, 0x85, 0x5f}}},
+{{{0x78, 0x84, 0xe1, 0x56, 0x45, 0x69, 0x68, 0x5a, 0x4f, 0xb8, 0xb1, 0x29, 0xff, 0x33, 0x03, 0x31, 0xb7, 0xcb, 0x96, 0x25, 0xe6, 0xe6, 0x41, 0x98, 0x1a, 0xbb, 0x03, 0x56, 0xf2, 0xb2, 0x91, 0x34}} ,
+ {{0x2c, 0x6c, 0xf7, 0x66, 0xa4, 0x62, 0x6b, 0x39, 0xb3, 0xba, 0x65, 0xd3, 0x1c, 0xf8, 0x11, 0xaa, 0xbe, 0xdc, 0x80, 0x59, 0x87, 0xf5, 0x7b, 0xe5, 0xe3, 0xb3, 0x3e, 0x39, 0xda, 0xbe, 0x88, 0x09}}},
+{{{0x8b, 0xf1, 0xa0, 0xf5, 0xdc, 0x29, 0xb4, 0xe2, 0x07, 0xc6, 0x7a, 0x00, 0xd0, 0x89, 0x17, 0x51, 0xd4, 0xbb, 0xd4, 0x22, 0xea, 0x7e, 0x7d, 0x7c, 0x24, 0xea, 0xf2, 0xe8, 0x22, 0x12, 0x95, 0x06}} ,
+ {{0xda, 0x7c, 0xa4, 0x0c, 0xf4, 0xba, 0x6e, 0xe1, 0x89, 0xb5, 0x59, 0xca, 0xf1, 0xc0, 0x29, 0x36, 0x09, 0x44, 0xe2, 0x7f, 0xd1, 0x63, 0x15, 0x99, 0xea, 0x25, 0xcf, 0x0c, 0x9d, 0xc0, 0x44, 0x6f}}},
+{{{0x1d, 0x86, 0x4e, 0xcf, 0xf7, 0x37, 0x10, 0x25, 0x8f, 0x12, 0xfb, 0x19, 0xfb, 0xe0, 0xed, 0x10, 0xc8, 0xe2, 0xf5, 0x75, 0xb1, 0x33, 0xc0, 0x96, 0x0d, 0xfb, 0x15, 0x6c, 0x0d, 0x07, 0x5f, 0x05}} ,
+ {{0x69, 0x3e, 0x47, 0x97, 0x2c, 0xaf, 0x52, 0x7c, 0x78, 0x83, 0xad, 0x1b, 0x39, 0x82, 0x2f, 0x02, 0x6f, 0x47, 0xdb, 0x2a, 0xb0, 0xe1, 0x91, 0x99, 0x55, 0xb8, 0x99, 0x3a, 0xa0, 0x44, 0x11, 0x51}}}
diff --git a/plugins/ssh-base/crypto/poly1305.c b/plugins/ssh-base/crypto/poly1305.c
new file mode 100644 (file)
index 0000000..6542667
--- /dev/null
@@ -0,0 +1,172 @@
+/* 
+ * Public Domain poly1305 from Andrew Moon
+ * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna
+ */
+
+/* $OpenBSD: poly1305.c,v 1.3 2013/12/19 22:57:13 djm Exp $ */
+
+#include <libwebsockets.h>
+#include "lws-ssh.h"
+
+#define mul32x32_64(a,b) ((uint64_t)(a) * (b))
+
+#define U8TO32_LE(p) \
+       (((uint32_t)((p)[0])) | \
+        ((uint32_t)((p)[1]) <<  8) | \
+        ((uint32_t)((p)[2]) << 16) | \
+        ((uint32_t)((p)[3]) << 24))
+
+#define U32TO8_LE(p, v) \
+       do { \
+               (p)[0] = (uint8_t)((v)); \
+               (p)[1] = (uint8_t)((v) >>  8); \
+               (p)[2] = (uint8_t)((v) >> 16); \
+               (p)[3] = (uint8_t)((v) >> 24); \
+       } while (0)
+
+void
+poly1305_auth(unsigned char out[POLY1305_TAGLEN],
+             const unsigned char *m, size_t inlen,
+             const unsigned char key[POLY1305_KEYLEN])
+{
+       uint32_t t0,t1,t2,t3;
+       uint32_t h0,h1,h2,h3,h4;
+       uint32_t r0,r1,r2,r3,r4;
+       uint32_t s1,s2,s3,s4;
+       uint32_t b, nb;
+       size_t j;
+       uint64_t t[5];
+       uint64_t f0,f1,f2,f3;
+       uint32_t g0,g1,g2,g3,g4;
+       uint64_t c;
+       unsigned char mp[16];
+
+       /* clamp key */
+       t0 = U8TO32_LE(key + 0);
+       t1 = U8TO32_LE(key + 4);
+       t2 = U8TO32_LE(key + 8);
+       t3 = U8TO32_LE(key + 12);
+
+       /* precompute multipliers */
+       r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6;
+       r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12;
+       r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18;
+       r3 = t2 & 0x3f03fff; t3 >>= 8;
+       r4 = t3 & 0x00fffff;
+
+       s1 = r1 * 5;
+       s2 = r2 * 5;
+       s3 = r3 * 5;
+       s4 = r4 * 5;
+
+       /* init state */
+       h0 = 0;
+       h1 = 0;
+       h2 = 0;
+       h3 = 0;
+       h4 = 0;
+
+       /* full blocks */
+       if (inlen < 16)
+               goto poly1305_donna_atmost15bytes;
+
+poly1305_donna_16bytes:
+       m += 16;
+       inlen -= 16;
+
+       t0 = U8TO32_LE(m - 16);
+       t1 = U8TO32_LE(m - 12);
+       t2 = U8TO32_LE(m - 8);
+       t3 = U8TO32_LE(m - 4);
+
+       h0 += t0 & 0x3ffffff;
+       h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff;
+       h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff;
+       h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff;
+       h4 += (t3 >> 8) | (1 << 24);
+
+poly1305_donna_mul:
+       t[0]  = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) +
+               mul32x32_64(h2,s3) + mul32x32_64(h3,s2) +
+               mul32x32_64(h4,s1);
+       t[1]  = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) +
+               mul32x32_64(h2,s4) + mul32x32_64(h3,s3) +
+               mul32x32_64(h4,s2);
+       t[2]  = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) +
+               mul32x32_64(h2,r0) + mul32x32_64(h3,s4) +
+               mul32x32_64(h4,s3);
+       t[3]  = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) +
+               mul32x32_64(h2,r1) + mul32x32_64(h3,r0) +
+               mul32x32_64(h4,s4);
+       t[4]  = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) +
+               mul32x32_64(h2,r2) + mul32x32_64(h3,r1) +
+               mul32x32_64(h4,r0);
+
+                   h0 = (uint32_t)t[0] & 0x3ffffff; c =           (t[0] >> 26);
+       t[1] += c;  h1 = (uint32_t)t[1] & 0x3ffffff; b = (uint32_t)(t[1] >> 26);
+       t[2] += b;  h2 = (uint32_t)t[2] & 0x3ffffff; b = (uint32_t)(t[2] >> 26);
+       t[3] += b;  h3 = (uint32_t)t[3] & 0x3ffffff; b = (uint32_t)(t[3] >> 26);
+       t[4] += b;  h4 = (uint32_t)t[4] & 0x3ffffff; b = (uint32_t)(t[4] >> 26);
+       h0 += b * 5;
+
+       if (inlen >= 16)
+               goto poly1305_donna_16bytes;
+
+       /* final bytes */
+poly1305_donna_atmost15bytes:
+       if (!inlen)
+               goto poly1305_donna_finish;
+
+       for (j = 0; j < inlen; j++)
+               mp[j] = m[j];
+       mp[j++] = 1;
+       for (; j < 16; j++)
+               mp[j] = 0;
+       inlen = 0;
+
+       t0 = U8TO32_LE(mp + 0);
+       t1 = U8TO32_LE(mp + 4);
+       t2 = U8TO32_LE(mp + 8);
+       t3 = U8TO32_LE(mp + 12);
+
+       h0 += t0 & 0x3ffffff;
+       h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff;
+       h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff;
+       h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff;
+       h4 += (t3 >> 8);
+
+       goto poly1305_donna_mul;
+
+poly1305_donna_finish:
+                    b = h0 >> 26; h0 = h0 & 0x3ffffff;
+       h1 +=     b; b = h1 >> 26; h1 = h1 & 0x3ffffff;
+       h2 +=     b; b = h2 >> 26; h2 = h2 & 0x3ffffff;
+       h3 +=     b; b = h3 >> 26; h3 = h3 & 0x3ffffff;
+       h4 +=     b; b = h4 >> 26; h4 = h4 & 0x3ffffff;
+       h0 += b * 5; b = h0 >> 26; h0 = h0 & 0x3ffffff;
+       h1 +=     b;
+
+       g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff;
+       g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff;
+       g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff;
+       g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff;
+       g4 = h4 + b - (1 << 26);
+
+       b = (g4 >> 31) - 1;
+       nb = ~b;
+       h0 = (h0 & nb) | (g0 & b);
+       h1 = (h1 & nb) | (g1 & b);
+       h2 = (h2 & nb) | (g2 & b);
+       h3 = (h3 & nb) | (g3 & b);
+       h4 = (h4 & nb) | (g4 & b);
+
+       f0 = ((h0      ) | (h1 << 26)) + (uint64_t)U8TO32_LE(&key[16]);
+       f1 = ((h1 >>  6) | (h2 << 20)) + (uint64_t)U8TO32_LE(&key[20]);
+       f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t)U8TO32_LE(&key[24]);
+       f3 = ((h3 >> 18) | (h4 <<  8)) + (uint64_t)U8TO32_LE(&key[28]);
+
+       U32TO8_LE(&out[ 0], f0); f1 += (f0 >> 32);
+       U32TO8_LE(&out[ 4], f1); f2 += (f1 >> 32);
+       U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32);
+       U32TO8_LE(&out[12], f3);
+}
diff --git a/plugins/ssh-base/crypto/sc25519.c b/plugins/ssh-base/crypto/sc25519.c
new file mode 100644 (file)
index 0000000..fdb5a80
--- /dev/null
@@ -0,0 +1,307 @@
+/* $OpenBSD: sc25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.c
+ */
+
+#include <libwebsockets.h>
+
+#include "sc25519.h"
+
+/*Arithmetic modulo the group order m = 2^252 +  27742317777372353535851937790883648493 = 7237005577332262213973186563042994240857116359379907606001950938285454250989 */
+
+static const uint32_t m[32] = {0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14,
+                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10};
+
+static const uint32_t mu[33] = {0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21,
+                                     0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F};
+
+static uint32_t lt(uint32_t a,uint32_t b) /* 16-bit inputs */
+{
+  unsigned int x = a;
+  x -= (unsigned int) b; /* 0..65535: no; 4294901761..4294967295: yes */
+  x >>= 31; /* 0: no; 1: yes */
+  return x;
+}
+
+/* Reduce coefficients of r before calling sc_reduce_add_sub */
+static void sc_reduce_add_sub(sc25519 *r)
+{
+  uint32_t pb = 0;
+  uint32_t b;
+  uint32_t mask;
+  int i;
+  unsigned char t[32];
+
+  for(i=0;i<32;i++) 
+  {
+    pb += m[i];
+    b = lt(r->v[i],pb);
+    t[i] = r->v[i]-pb+(b<<8);
+    pb = b;
+  }
+  mask = b - 1;
+  for(i=0;i<32;i++) 
+    r->v[i] ^= mask & (r->v[i] ^ t[i]);
+}
+
+/* Reduce coefficients of x before calling barrett_reduce */
+static void barrett_reduce(sc25519 *r, const uint32_t x[64])
+{
+  /* See HAC, Alg. 14.42 */
+  int i,j;
+  uint32_t q2[66];
+  uint32_t *q3 = q2 + 33;
+  uint32_t r1[33];
+  uint32_t r2[33];
+  uint32_t carry;
+  uint32_t pb = 0;
+  uint32_t b;
+
+  for (i = 0;i < 66;++i) q2[i] = 0;
+  for (i = 0;i < 33;++i) r2[i] = 0;
+
+  for(i=0;i<33;i++)
+    for(j=0;j<33;j++)
+      if(i+j >= 31) q2[i+j] += mu[i]*x[j+31];
+  carry = q2[31] >> 8;
+  q2[32] += carry;
+  carry = q2[32] >> 8;
+  q2[33] += carry;
+
+  for(i=0;i<33;i++)r1[i] = x[i];
+  for(i=0;i<32;i++)
+    for(j=0;j<33;j++)
+      if(i+j < 33) r2[i+j] += m[i]*q3[j];
+
+  for(i=0;i<32;i++)
+  {
+    carry = r2[i] >> 8;
+    r2[i+1] += carry;
+    r2[i] &= 0xff;
+  }
+
+  for(i=0;i<32;i++) 
+  {
+    pb += r2[i];
+    b = lt(r1[i],pb);
+    r->v[i] = r1[i]-pb+(b<<8);
+    pb = b;
+  }
+
+  /* XXX: Can it really happen that r<0?, See HAC, Alg 14.42, Step 3 
+   * If so: Handle  it here!
+   */
+
+  sc_reduce_add_sub(r);
+  sc_reduce_add_sub(r);
+}
+
+void sc25519_from32bytes(sc25519 *r, const unsigned char x[32])
+{
+  int i;
+  uint32_t t[64];
+  for(i=0;i<32;i++) t[i] = x[i];
+  for(i=32;i<64;++i) t[i] = 0;
+  barrett_reduce(r, t);
+}
+
+void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16])
+{
+  int i;
+  for(i=0;i<16;i++) r->v[i] = x[i];
+}
+
+void sc25519_from64bytes(sc25519 *r, const unsigned char x[64])
+{
+  int i;
+  uint32_t t[64];
+  for(i=0;i<64;i++) t[i] = x[i];
+  barrett_reduce(r, t);
+}
+
+void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x)
+{
+  int i;
+  for(i=0;i<16;i++)
+    r->v[i] = x->v[i];
+  for(i=0;i<16;i++)
+    r->v[16+i] = 0;
+}
+
+void sc25519_to32bytes(unsigned char r[32], const sc25519 *x)
+{
+  int i;
+  for(i=0;i<32;i++) r[i] = x->v[i];
+}
+
+int sc25519_iszero_vartime(const sc25519 *x)
+{
+  int i;
+  for(i=0;i<32;i++)
+    if(x->v[i] != 0) return 0;
+  return 1;
+}
+
+int sc25519_isshort_vartime(const sc25519 *x)
+{
+  int i;
+  for(i=31;i>15;i--)
+    if(x->v[i] != 0) return 0;
+  return 1;
+}
+
+int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y)
+{
+  int i;
+  for(i=31;i>=0;i--)
+  {
+    if(x->v[i] < y->v[i]) return 1;
+    if(x->v[i] > y->v[i]) return 0;
+  }
+  return 0;
+}
+
+void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y)
+{
+  int i, carry;
+  for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i];
+  for(i=0;i<31;i++)
+  {
+    carry = r->v[i] >> 8;
+    r->v[i+1] += carry;
+    r->v[i] &= 0xff;
+  }
+  sc_reduce_add_sub(r);
+}
+
+void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y)
+{
+  uint32_t b = 0;
+  int i;
+  for(i=0;i<32;i++)
+  {
+    uint32_t t = x->v[i] - y->v[i] - b;
+    r->v[i] = t & 255;
+    b = (t >> 8) & 1;
+  }
+}
+
+void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y)
+{
+  int i,j,carry;
+  uint32_t t[64];
+  for(i=0;i<64;i++)t[i] = 0;
+
+  for(i=0;i<32;i++)
+    for(j=0;j<32;j++)
+      t[i+j] += x->v[i] * y->v[j];
+
+  /* Reduce coefficients */
+  for(i=0;i<63;i++)
+  {
+    carry = t[i] >> 8;
+    t[i+1] += carry;
+    t[i] &= 0xff;
+  }
+
+  barrett_reduce(r, t);
+}
+
+void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y)
+{
+  sc25519 t;
+  sc25519_from_shortsc(&t, y);
+  sc25519_mul(r, x, &t);
+}
+
+void sc25519_window3(signed char r[85], const sc25519 *s)
+{
+  char carry;
+  int i;
+  for(i=0;i<10;i++)
+  {
+    r[8*i+0]  =  s->v[3*i+0]       & 7;
+    r[8*i+1]  = (s->v[3*i+0] >> 3) & 7;
+    r[8*i+2]  = (s->v[3*i+0] >> 6) & 7;
+    r[8*i+2] ^= (s->v[3*i+1] << 2) & 7;
+    r[8*i+3]  = (s->v[3*i+1] >> 1) & 7;
+    r[8*i+4]  = (s->v[3*i+1] >> 4) & 7;
+    r[8*i+5]  = (s->v[3*i+1] >> 7) & 7;
+    r[8*i+5] ^= (s->v[3*i+2] << 1) & 7;
+    r[8*i+6]  = (s->v[3*i+2] >> 2) & 7;
+    r[8*i+7]  = (s->v[3*i+2] >> 5) & 7;
+  }
+  r[8*i+0]  =  s->v[3*i+0]       & 7;
+  r[8*i+1]  = (s->v[3*i+0] >> 3) & 7;
+  r[8*i+2]  = (s->v[3*i+0] >> 6) & 7;
+  r[8*i+2] ^= (s->v[3*i+1] << 2) & 7;
+  r[8*i+3]  = (s->v[3*i+1] >> 1) & 7;
+  r[8*i+4]  = (s->v[3*i+1] >> 4) & 7;
+
+  /* Making it signed */
+  carry = 0;
+  for(i=0;i<84;i++)
+  {
+    r[i] += carry;
+    r[i+1] += r[i] >> 3;
+    r[i] &= 7;
+    carry = r[i] >> 2;
+    r[i] -= carry<<3;
+  }
+  r[84] += carry;
+}
+
+void sc25519_window5(signed char r[51], const sc25519 *s)
+{
+  char carry;
+  int i;
+  for(i=0;i<6;i++)
+  {
+    r[8*i+0]  =  s->v[5*i+0]       & 31;
+    r[8*i+1]  = (s->v[5*i+0] >> 5) & 31;
+    r[8*i+1] ^= (s->v[5*i+1] << 3) & 31;
+    r[8*i+2]  = (s->v[5*i+1] >> 2) & 31;
+    r[8*i+3]  = (s->v[5*i+1] >> 7) & 31;
+    r[8*i+3] ^= (s->v[5*i+2] << 1) & 31;
+    r[8*i+4]  = (s->v[5*i+2] >> 4) & 31;
+    r[8*i+4] ^= (s->v[5*i+3] << 4) & 31;
+    r[8*i+5]  = (s->v[5*i+3] >> 1) & 31;
+    r[8*i+6]  = (s->v[5*i+3] >> 6) & 31;
+    r[8*i+6] ^= (s->v[5*i+4] << 2) & 31;
+    r[8*i+7]  = (s->v[5*i+4] >> 3) & 31;
+  }
+  r[8*i+0]  =  s->v[5*i+0]       & 31;
+  r[8*i+1]  = (s->v[5*i+0] >> 5) & 31;
+  r[8*i+1] ^= (s->v[5*i+1] << 3) & 31;
+  r[8*i+2]  = (s->v[5*i+1] >> 2) & 31;
+
+  /* Making it signed */
+  carry = 0;
+  for(i=0;i<50;i++)
+  {
+    r[i] += carry;
+    r[i+1] += r[i] >> 5;
+    r[i] &= 31;
+    carry = r[i] >> 4;
+    r[i] -= carry<<5;
+  }
+  r[50] += carry;
+}
+
+void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2)
+{
+  int i;
+  for(i=0;i<31;i++)
+  {
+    r[4*i]   = ( s1->v[i]       & 3) ^ (( s2->v[i]       & 3) << 2);
+    r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2);
+    r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2);
+    r[4*i+3] = ((s1->v[i] >> 6) & 3) ^ (((s2->v[i] >> 6) & 3) << 2);
+  }
+  r[124] = ( s1->v[31]       & 3) ^ (( s2->v[31]       & 3) << 2);
+  r[125] = ((s1->v[31] >> 2) & 3) ^ (((s2->v[31] >> 2) & 3) << 2);
+  r[126] = ((s1->v[31] >> 4) & 3) ^ (((s2->v[31] >> 4) & 3) << 2);
+}
diff --git a/plugins/ssh-base/crypto/sc25519.h b/plugins/ssh-base/crypto/sc25519.h
new file mode 100644 (file)
index 0000000..dace1a1
--- /dev/null
@@ -0,0 +1,78 @@
+/* $OpenBSD: sc25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */
+
+/*
+ * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange,
+ * Peter Schwabe, Bo-Yin Yang.
+ * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.h
+ */
+
+#ifndef SC25519_H
+#define SC25519_H
+
+#define sc25519                  crypto_sign_ed25519_ref_sc25519
+#define shortsc25519             crypto_sign_ed25519_ref_shortsc25519
+#define sc25519_from32bytes      crypto_sign_ed25519_ref_sc25519_from32bytes
+#define shortsc25519_from16bytes crypto_sign_ed25519_ref_shortsc25519_from16bytes
+#define sc25519_from64bytes      crypto_sign_ed25519_ref_sc25519_from64bytes
+#define sc25519_from_shortsc     crypto_sign_ed25519_ref_sc25519_from_shortsc
+#define sc25519_to32bytes        crypto_sign_ed25519_ref_sc25519_to32bytes
+#define sc25519_iszero_vartime   crypto_sign_ed25519_ref_sc25519_iszero_vartime
+#define sc25519_isshort_vartime  crypto_sign_ed25519_ref_sc25519_isshort_vartime
+#define sc25519_lt_vartime       crypto_sign_ed25519_ref_sc25519_lt_vartime
+#define sc25519_add              crypto_sign_ed25519_ref_sc25519_add
+#define sc25519_sub_nored        crypto_sign_ed25519_ref_sc25519_sub_nored
+#define sc25519_mul              crypto_sign_ed25519_ref_sc25519_mul
+#define sc25519_mul_shortsc      crypto_sign_ed25519_ref_sc25519_mul_shortsc
+#define sc25519_window3          crypto_sign_ed25519_ref_sc25519_window3
+#define sc25519_window5          crypto_sign_ed25519_ref_sc25519_window5
+#define sc25519_2interleave2     crypto_sign_ed25519_ref_sc25519_2interleave2
+
+typedef struct 
+{
+  uint32_t v[32];
+}
+sc25519;
+
+typedef struct 
+{
+  uint32_t v[16];
+}
+shortsc25519;
+
+void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]);
+
+void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]);
+
+void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]);
+
+void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x);
+
+void sc25519_to32bytes(unsigned char r[32], const sc25519 *x);
+
+int sc25519_iszero_vartime(const sc25519 *x);
+
+int sc25519_isshort_vartime(const sc25519 *x);
+
+int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y);
+
+void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y);
+
+void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y);
+
+void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y);
+
+void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y);
+
+/* Convert s into a representation of the form \sum_{i=0}^{84}r[i]2^3
+ * with r[i] in {-4,...,3}
+ */
+void sc25519_window3(signed char r[85], const sc25519 *s);
+
+/* Convert s into a representation of the form \sum_{i=0}^{50}r[i]2^5
+ * with r[i] in {-16,...,15}
+ */
+void sc25519_window5(signed char r[51], const sc25519 *s);
+
+void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2);
+
+#endif
diff --git a/plugins/ssh-base/crypto/smult_curve25519_ref.c b/plugins/ssh-base/crypto/smult_curve25519_ref.c
new file mode 100644 (file)
index 0000000..bd5250f
--- /dev/null
@@ -0,0 +1,265 @@
+/* $OpenBSD: smult_curve25519_ref.c,v 1.2 2013/11/02 22:02:14 markus Exp $ */
+/*
+version 20081011
+Matthew Dempsky
+Public domain.
+Derived from public domain code by D. J. Bernstein.
+*/
+
+static void add(unsigned int out[32],const unsigned int a[32],const unsigned int b[32])
+{
+  unsigned int j;
+  unsigned int u;
+  u = 0;
+  for (j = 0;j < 31;++j) { u += a[j] + b[j]; out[j] = u & 255; u >>= 8; }
+  u += a[31] + b[31]; out[31] = u;
+}
+
+static void sub(unsigned int out[32],const unsigned int a[32],const unsigned int b[32])
+{
+  unsigned int j;
+  unsigned int u;
+  u = 218;
+  for (j = 0;j < 31;++j) {
+    u += a[j] + 65280 - b[j];
+    out[j] = u & 255;
+    u >>= 8;
+  }
+  u += a[31] - b[31];
+  out[31] = u;
+}
+
+static void squeeze(unsigned int a[32])
+{
+  unsigned int j;
+  unsigned int u;
+  u = 0;
+  for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; }
+  u += a[31]; a[31] = u & 127;
+  u = 19 * (u >> 7);
+  for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; }
+  u += a[31]; a[31] = u;
+}
+
+static const unsigned int minusp[32] = {
+ 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128
+} ;
+
+static void freeze(unsigned int a[32])
+{
+  unsigned int aorig[32];
+  unsigned int j;
+  unsigned int negative;
+
+  for (j = 0;j < 32;++j) aorig[j] = a[j];
+  add(a,a,minusp);
+  negative = -(int)((a[31] >> 7) & 1);
+  for (j = 0;j < 32;++j) a[j] ^= negative & (aorig[j] ^ a[j]);
+}
+
+static void mult(unsigned int out[32],const unsigned int a[32],const unsigned int b[32])
+{
+  unsigned int i;
+
+  for (i = 0;i < 32;++i) {
+    unsigned int j;
+    unsigned int u;
+    u = 0;
+    for (j = 0;j <= i;++j) u += a[j] * b[i - j];
+    for (j = i + 1;j < 32;++j) u += 38 * a[j] * b[i + 32 - j];
+    out[i] = u;
+  }
+  squeeze(out);
+}
+
+static void mult121665(unsigned int out[32],const unsigned int a[32])
+{
+  unsigned int j;
+  unsigned int u;
+
+  u = 0;
+  for (j = 0;j < 31;++j) { u += 121665 * a[j]; out[j] = u & 255; u >>= 8; }
+  u += 121665 * a[31]; out[31] = u & 127;
+  u = 19 * (u >> 7);
+  for (j = 0;j < 31;++j) { u += out[j]; out[j] = u & 255; u >>= 8; }
+  u += out[j]; out[j] = u;
+}
+
+static void square(unsigned int out[32],const unsigned int a[32])
+{
+  unsigned int i;
+  unsigned int j;
+  unsigned int u;
+
+  for (i = 0;i < 32;++i) {
+    u = 0;
+    for (j = 0;j < i - j;++j) u += a[j] * a[i - j];
+    for (j = i + 1;j < i + 32 - j;++j)
+           if (i + 32 - j < 32 && j < 32)
+                   u += 38 * a[j] * a[i + 32 - j];
+    u *= 2;
+    if ((i & 1) == 0) {
+      u += a[i / 2] * a[i / 2];
+      u += 38 * a[i / 2 + 16] * a[i / 2 + 16];
+    }
+    out[i] = u;
+  }
+  squeeze(out);
+}
+
+static void smc_select(unsigned int p[64],unsigned int q[64],const unsigned int r[64],const unsigned int s[64],unsigned int b)
+{
+  unsigned int j;
+  unsigned int t;
+  unsigned int bminus1;
+
+  bminus1 = b - 1;
+  for (j = 0;j < 64;++j) {
+    t = bminus1 & (r[j] ^ s[j]);
+    p[j] = s[j] ^ t;
+    q[j] = r[j] ^ t;
+  }
+}
+
+static void mainloop(unsigned int work[64],const unsigned char e[32])
+{
+  unsigned int xzm1[64];
+  unsigned int xzm[64];
+  unsigned int xzmb[64];
+  unsigned int xzm1b[64];
+  unsigned int xznb[64];
+  unsigned int xzn1b[64];
+  unsigned int a0[64];
+  unsigned int a1[64];
+  unsigned int b0[64];
+  unsigned int b1[64];
+  unsigned int c1[64];
+  unsigned int r[32];
+  unsigned int s[32];
+  unsigned int t[32];
+  unsigned int u[32];
+  unsigned int j;
+  unsigned int b;
+  int pos;
+
+  for (j = 0;j < 32;++j) xzm1[j] = work[j];
+  xzm1[32] = 1;
+  for (j = 33;j < 64;++j) xzm1[j] = 0;
+
+  xzm[0] = 1;
+  for (j = 1;j < 64;++j) xzm[j] = 0;
+
+  for (pos = 254;pos >= 0;--pos) {
+    b = e[pos / 8] >> (pos & 7);
+    b &= 1;
+    smc_select(xzmb,xzm1b,xzm,xzm1,b);
+    add(a0,xzmb,xzmb + 32);
+    sub(a0 + 32,xzmb,xzmb + 32);
+    add(a1,xzm1b,xzm1b + 32);
+    sub(a1 + 32,xzm1b,xzm1b + 32);
+    square(b0,a0);
+    square(b0 + 32,a0 + 32);
+    mult(b1,a1,a0 + 32);
+    mult(b1 + 32,a1 + 32,a0);
+    add(c1,b1,b1 + 32);
+    sub(c1 + 32,b1,b1 + 32);
+    square(r,c1 + 32);
+    sub(s,b0,b0 + 32);
+    mult121665(t,s);
+    add(u,t,b0);
+    mult(xznb,b0,b0 + 32);
+    mult(xznb + 32,s,u);
+    square(xzn1b,c1);
+    mult(xzn1b + 32,r,work);
+    smc_select(xzm,xzm1,xznb,xzn1b,b);
+  }
+
+  for (j = 0;j < 64;++j) work[j] = xzm[j];
+}
+
+static void recip(unsigned int out[32],const unsigned int z[32])
+{
+  unsigned int z2[32];
+  unsigned int z9[32];
+  unsigned int z11[32];
+  unsigned int z2_5_0[32];
+  unsigned int z2_10_0[32];
+  unsigned int z2_20_0[32];
+  unsigned int z2_50_0[32];
+  unsigned int z2_100_0[32];
+  unsigned int t0[32];
+  unsigned int t1[32];
+  int i;
+
+  /* 2 */ square(z2,z);
+  /* 4 */ square(t1,z2);
+  /* 8 */ square(t0,t1);
+  /* 9 */ mult(z9,t0,z);
+  /* 11 */ mult(z11,z9,z2);
+  /* 22 */ square(t0,z11);
+  /* 2^5 - 2^0 = 31 */ mult(z2_5_0,t0,z9);
+
+  /* 2^6 - 2^1 */ square(t0,z2_5_0);
+  /* 2^7 - 2^2 */ square(t1,t0);
+  /* 2^8 - 2^3 */ square(t0,t1);
+  /* 2^9 - 2^4 */ square(t1,t0);
+  /* 2^10 - 2^5 */ square(t0,t1);
+  /* 2^10 - 2^0 */ mult(z2_10_0,t0,z2_5_0);
+
+  /* 2^11 - 2^1 */ square(t0,z2_10_0);
+  /* 2^12 - 2^2 */ square(t1,t0);
+  /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t0,t1); square(t1,t0); }
+  /* 2^20 - 2^0 */ mult(z2_20_0,t1,z2_10_0);
+
+  /* 2^21 - 2^1 */ square(t0,z2_20_0);
+  /* 2^22 - 2^2 */ square(t1,t0);
+  /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { square(t0,t1); square(t1,t0); }
+  /* 2^40 - 2^0 */ mult(t0,t1,z2_20_0);
+
+  /* 2^41 - 2^1 */ square(t1,t0);
+  /* 2^42 - 2^2 */ square(t0,t1);
+  /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t1,t0); square(t0,t1); }
+  /* 2^50 - 2^0 */ mult(z2_50_0,t0,z2_10_0);
+
+  /* 2^51 - 2^1 */ square(t0,z2_50_0);
+  /* 2^52 - 2^2 */ square(t1,t0);
+  /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); }
+  /* 2^100 - 2^0 */ mult(z2_100_0,t1,z2_50_0);
+
+  /* 2^101 - 2^1 */ square(t1,z2_100_0);
+  /* 2^102 - 2^2 */ square(t0,t1);
+  /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { square(t1,t0); square(t0,t1); }
+  /* 2^200 - 2^0 */ mult(t1,t0,z2_100_0);
+
+  /* 2^201 - 2^1 */ square(t0,t1);
+  /* 2^202 - 2^2 */ square(t1,t0);
+  /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); }
+  /* 2^250 - 2^0 */ mult(t0,t1,z2_50_0);
+
+  /* 2^251 - 2^1 */ square(t1,t0);
+  /* 2^252 - 2^2 */ square(t0,t1);
+  /* 2^253 - 2^3 */ square(t1,t0);
+  /* 2^254 - 2^4 */ square(t0,t1);
+  /* 2^255 - 2^5 */ square(t1,t0);
+  /* 2^255 - 21 */ mult(out,t1,z11);
+}
+
+int crypto_scalarmult_curve25519(unsigned char *q,
+  const unsigned char *n,
+  const unsigned char *p)
+{
+  unsigned int work[96];
+  unsigned char e[32];
+  unsigned int i;
+  for (i = 0;i < 32;++i) e[i] = n[i];
+  e[0] &= 248;
+  e[31] &= 127;
+  e[31] |= 64;
+  for (i = 0;i < 32;++i) work[i] = p[i];
+  mainloop(work,e);
+  recip(work + 32,work + 32);
+  mult(work + 64,work,work + 32);
+  freeze(work + 64);
+  for (i = 0;i < 32;++i) q[i] = work[64 + i];
+  return 0;
+}
diff --git a/plugins/ssh-base/include/lws-plugin-ssh.h b/plugins/ssh-base/include/lws-plugin-ssh.h
new file mode 100644 (file)
index 0000000..8626daa
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * libwebsockets - lws-plugin-ssh-base
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined(__LWS_PLUGIN_SSH_H__)
+#define __LWS_PLUGIN_SSH_H__
+
+#define LWS_CALLBACK_SSH_UART_SET_RXFLOW (LWS_CALLBACK_USER + 800)
+
+#define LWS_SSH_OPS_VERSION 2
+
+struct lws_ssh_pty {
+       char term[16];
+       char *modes;
+       uint32_t width_ch;
+       uint32_t height_ch;
+       uint32_t width_px;
+       uint32_t height_px;
+       uint32_t modes_len;
+};
+
+#define SSHMO_TTY_OP_END 0 /* Indicates end of options. */
+#define SSHMO_VINTR     1 /* Interrupt character; 255 if none.  Similarly
+                           * for the other characters.  Not all of these
+                           * characters are supported on all systems. */
+#define SSHMO_VQUIT     2 /* The quit character (sends SIGQUIT signal on
+                           * POSIX systems). */
+#define SSHMO_VERASE    3 /* Erase the character to left of the cursor. */
+#define SSHMO_VKILL     4 /* Kill the current input line. */
+#define SSHMO_VEOF      5 /* End-of-file character (sends EOF from the
+                           * terminal). */
+#define SSHMO_VEOL      6 /* End-of-line character in addition to
+                           * carriage return and/or linefeed. */
+#define SSHMO_VEOL2     7 /* Additional end-of-line character. */
+#define SSHMO_VSTART    8 /* Continues paused output (normally
+                           * control-Q). */
+#define SSHMO_VSTOP     9 /* Pauses output (normally control-S). */
+#define SSHMO_VSUSP    10 /* Suspends the current program. */
+#define SSHMO_VDSUSP   11 /* Another suspend character. */
+#define SSHMO_VREPRINT 12 /* Reprints the current input line. */
+#define SSHMO_VWERASE  13 /* Erases a word left of cursor. */
+#define SSHMO_VLNEXT   14 /* Enter the next character typed literally,
+                           * even if it is a special character */
+#define SSHMO_VFLUSH   15 /* Character to flush output. */
+#define SSHMO_VSWTCH   16 /* Switch to a different shell layer. */
+#define SSHMO_VSTATUS  17 /* Prints system status line (load, command,
+                           * pid, etc). */
+#define SSHMO_VDISCARD 18 /* Toggles the flushing of terminal output. */
+#define SSHMO_IGNPAR   30 /* The ignore parity flag.  The parameter
+                           * SHOULD be 0 if this flag is FALSE,
+                           * and 1 if it is TRUE. */
+#define SSHMO_PARMRK   31 /* Mark parity and framing errors. */
+#define SSHMO_INPCK    32 /* Enable checking of parity errors. */
+#define SSHMO_ISTRIP   33 /* Strip 8th bit off characters. */
+#define SSHMO_INLCR    34 /* Map NL into CR on input. */
+#define SSHMO_IGNCR    35 /* Ignore CR on input. */
+#define SSHMO_ICRNL    36 /* Map CR to NL on input. */
+#define SSHMO_IUCLC    37 /* Translate uppercase characters to lowercase. */
+#define SSHMO_IXON     38 /* Enable output flow control. */
+#define SSHMO_IXANY    39 /* Any char will restart after stop. */
+#define SSHMO_IXOFF    40 /* Enable input flow control. */
+#define SSHMO_IMAXBEL  41 /* Ring bell on input queue full. */
+#define SSHMO_ISIG     50 /* Enable signals INTR, QUIT, [D]SUSP. */
+#define SSHMO_ICANON   51 /* Canonicalize input lines. */
+#define SSHMO_XCASE    52 /* Enable input and output of uppercase
+                           * characters by preceding their lowercase
+                           * equivalents with "\". */
+#define SSHMO_ECHO     53 /* Enable echoing. */
+#define SSHMO_ECHOE    54 /* Visually erase chars. */
+#define SSHMO_ECHOK    55 /* Kill character discards current line. */
+#define SSHMO_ECHONL   56 /* Echo NL even if ECHO is off. */
+#define SSHMO_NOFLSH   57 /* Don't flush after interrupt. */
+#define SSHMO_TOSTOP   58 /* Stop background jobs from output. */
+#define SSHMO_IEXTEN   59 /* Enable extensions. */
+#define SSHMO_ECHOCTL  60 /* Echo control characters as ^(Char). */
+#define SSHMO_ECHOKE   61 /* Visual erase for line kill. */
+#define SSHMO_PENDIN   62 /* Retype pending input. */
+#define SSHMO_OPOST    70 /* Enable output processing. */
+#define SSHMO_OLCUC    71 /* Convert lowercase to uppercase. */
+#define SSHMO_ONLCR    72 /* Map NL to CR-NL. */
+#define SSHMO_OCRNL    73 /* Translate carriage return to newline (out). */
+#define SSHMO_ONOCR    74 /* Translate newline to CR-newline (out). */
+#define SSHMO_ONLRET   75 /* Newline performs a carriage return (out). */
+#define SSHMO_CS7      90 /* 7 bit mode. */
+#define SSHMO_CS8      91 /* 8 bit mode. */
+#define SSHMO_PARENB   92 /* Parity enable. */
+#define SSHMO_PARODD   93 /* Odd parity, else even. */
+#define SSHMO_TTY_OP_ISPEED    128 /* Specifies the input baud rate in
+                                    * bits per second. */
+#define SSHMO_TTY_OP_OSPEED    129 /* Specifies the output baud rate in
+                                    * bits per second. */
+
+/*! \defgroup ssh-base plugin: lws-ssh-base
+ * \ingroup Protocols-and-Plugins
+ *
+ * ##Plugin lws-ssh-base
+ *
+ * This is the interface to customize the ssh server per-vhost.  A pointer
+ * to your struct lws_ssh_ops with the members initialized is passed in using
+ * pvo when you create the vhost.  The pvo is attached to the protocol name
+ *
+ *  - "lws-ssh-base" - the ssh serving part
+ *
+ *  - "lws-telnetd-base" - the telnet serving part
+ *
+ *  This way you can have different instances of ssh servers wired up to
+ *  different IO and server keys per-vhost.
+ *
+ *  See also ./READMEs/README-plugin-sshd-base.md
+ */
+///@{
+
+typedef void (*lws_ssh_finish_exec)(void *handle, int retcode);
+
+struct lws_ssh_ops {
+       /**
+        * channel_create() - Channel created
+        *
+        * \param wsi: raw wsi representing this connection
+        * \param priv: pointer to void * you can allocate and attach to the
+        *              channel
+        *
+        * Called when new channel created, *priv should be set to any
+        * allocation your implementation needs
+        *
+        * You probably want to save the wsi inside your priv struct.  Calling
+        * lws_callback_on_writable() on this wsi causes your ssh server
+        * instance to call .tx_waiting() next time you can write something
+        * to the client.
+        */
+       int (*channel_create)(struct lws *wsi, void **priv);
+
+       /**
+        * channel_destroy() - Channel is being destroyed
+        *
+        * \param priv: void * you set when channel was created (or NULL)
+        *
+        * Called when channel destroyed, priv should be freed if you allocated
+        * into it.
+        */
+       int (*channel_destroy)(void *priv);
+
+       /**
+        * rx() - receive payload from peer
+        *
+        * \param priv: void * you set when this channel was created
+        * \param wsi:  struct lws * for the ssh connection
+        * \param buf:  pointer to start of received data
+        * \param len:  bytes of received data available at buf
+        *
+        * len bytes of payload from the peer arrived and is available at buf
+        */
+       int (*rx)(void *priv, struct lws *wsi, const uint8_t *buf, uint32_t len);
+
+       /**
+        * tx_waiting() - report if data waiting to transmit on the channel
+        *
+        * \param priv: void * you set when this channel was created
+        *
+        * returns a bitmask of LWS_STDOUT and LWS_STDERR, with the bits set
+        * if they have tx waiting to send, else 0 if nothing to send
+        *
+        * You should use one of the lws_callback_on_writable() family to
+        * trigger the ssh protocol to ask if you have any tx waiting.
+        *
+        * Returning -1 from here will close the tcp connection to the client.
+        */
+       int (*tx_waiting)(void *priv);
+
+       /**
+        * tx() - provide data to send on the channel
+        *
+        * \param priv: void * you set when this channel was created
+        * \param stdch: LWS_STDOUT or LWS_STDERR
+        * \param buf:  start of the buffer to copy the transmit data into
+        * \param len:  max length of the buffer in bytes
+        *
+        * copy and consume up to len bytes into *buf,
+        * return the actual copied count.
+        *
+        * You should use one of the lws_callback_on_writable() family to
+        * trigger the ssh protocol to ask if you have any tx waiting.  If you
+        * do you will get calls here to fetch it, for each of LWS_STDOUT or
+        * LWS_STDERR that were reported to be waiting by tx_waiting().
+        */
+       size_t (*tx)(void *priv, int stdch, uint8_t *buf, size_t len);
+
+       /**
+        * get_server_key() - retreive the secret keypair for this server
+        *
+        * \param wsi:  the wsi representing the connection to the client
+        * \param buf:  start of the buffer to copy the keypair into
+        * \param len:  length of the buffer in bytes
+        *
+        * load the server key into buf, max len len.  Returns length of buf
+        * set to key, or 0 if no key or other error.  If there is no key,
+        * the error isn't fatal... the plugin will generate a random key and
+        * store it using *get_server_key() for subsequent times.
+        */
+       size_t (*get_server_key)(struct lws *wsi, uint8_t *buf, size_t len);
+
+       /**
+        * set_server_key() - store the secret keypair of this server
+        *
+        * \param wsi:  the wsi representing the connection to the client
+        * \param buf:  start of the buffer containing the keypair
+        * \param len:  length of the keypair in bytes
+        *
+        * store the server key in buf, length len, to nonvolatile stg.
+        * Return length stored, 0 for fail.
+        */
+       size_t (*set_server_key)(struct lws *wsi, uint8_t *buf, size_t len);
+
+       /**
+        * set_env() - Set environment variable
+        *
+        * \param priv: void * you set when this channel was created
+        * \param name: env var name
+        * \param value: value to set env var to
+        *
+        * Client requested to set environment var.  Return nonzero to fail.
+        */
+       int (*set_env)(void *priv, const char *name, const char *value);
+
+       /**
+        * exec() - spawn command and wire up stdin/out/err to ssh channel
+        *
+        * \param priv: void * you set when this channel was created
+        * \param wsi: the struct lws the connection belongs to
+        * \param command:      string containing path to app and arguments
+        * \param finish: function to call to indicate the exec finished
+        * \param finish_handle: opaque handle identifying this exec for use with \p finish
+        *
+        * Client requested to exec something.  Return nonzero to fail.
+        */
+       int (*exec)(void *priv, struct lws *wsi, const char *command, lws_ssh_finish_exec finish, void *finish_handle);
+
+       /**
+        * shell() - Spawn shell that is appropriate for user
+        *
+        * \param priv: void * you set when this channel was created
+        * \param wsi: the struct lws the connection belongs to
+        * \param finish: function to call to indicate the exec finished
+        * \param finish_handle: opaque handle identifying this exec for use with \p finish
+        *
+        * Spawn the appropriate shell for this user.  Return 0 for OK
+        * or nonzero to fail.
+        */
+       int (*shell)(void *priv, struct lws *wsi, lws_ssh_finish_exec finish, void *finish_handle);
+
+       /**
+        * pty_req() - Create a Pseudo-TTY as described in pty
+        *
+        * \param priv: void * you set when this channel was created
+        * \param pty:  pointer to struct describing the desired pty
+        *
+        * Client requested a pty.  Return nonzero to fail.
+        */
+       int (*pty_req)(void *priv, struct lws_ssh_pty *pty);
+
+       /**
+        * child_process_io() - Child process has IO
+        *
+        * \param priv: void * you set when this channel was created
+        * \param wsi: the struct lws the connection belongs to
+        * \param args: information related to the cgi IO events
+        *
+        * Child process has IO
+        */
+       int (*child_process_io)(void *priv, struct lws *wsi,
+                               struct lws_cgi_args *args);
+
+       /**
+        * child_process_io() - Child process has terminated
+        *
+        * \param priv: void * you set when this channel was created
+        * \param wsi: the struct lws the connection belongs to
+        *
+        * Child process has terminated
+        */
+       int (*child_process_terminated)(void *priv, struct lws *wsi);
+
+       /**
+        * disconnect_reason() - Optional notification why connection is lost
+        *
+        * \param reason: one of the SSH_DISCONNECT_ constants
+        * \param desc: UTF-8 description of reason
+        * \param desc_lang: RFC3066 language for description
+        *
+        * The remote peer may tell us why it's going to disconnect.  Handling
+        * this is optional.
+        */
+       void (*disconnect_reason)(uint32_t reason, const char *desc,
+                                 const char *desc_lang);
+
+       /**
+        * is_pubkey_authorized() - check if auth pubkey is valid for user
+        *
+        * \param username:     username the key attempted to authenticate
+        * \param type:         "ssh-rsa"
+        * \param peer:         start of Public key peer used to authenticate
+        * \param peer_len:     length of Public key at peer
+        *
+        * We confirmed the client has the private key for this public key...
+        * but is that keypair something authorized for this username on this
+        * server? 0 = OK, 1 = fail
+        *
+        * Normally this checks for a copy of the same public key stored
+        * somewhere out of band, it's the same procedure as openssh does
+        * when looking in ~/.ssh/authorized_keys
+        */
+       int (*is_pubkey_authorized)(const char *username,
+                       const char *type, const uint8_t *peer, int peer_len);
+
+       /**
+        * banner() - copy the connection banner to buffer
+        *
+        * \param buf:  start of the buffer to copy to
+        * \param max_len: maximum number of bytes the buffer can hold
+        * \param lang: start of the buffer to copy language descriptor to
+        * \param max_lang_len: maximum number of bytes lang can hold
+        *
+        * Copy the text banner to be returned to client on connect,
+        * before auth, into buf.  The text should be in UTF-8.
+        * if none wanted then leave .banner as NULL.
+        *
+        * lang should have a RFC3066 language descriptor like "en/US"
+        * copied to it.
+        *
+        * Returns the number of bytes copies to buf.
+        */
+       size_t (*banner)(char *buf, size_t max_len, char *lang,
+                        size_t max_lang_len);
+
+       /**
+        * SSH version string sent to client (required)
+        * By convention a string like "SSH-2.0-Libwebsockets"
+        */
+       const char *server_string;
+
+       /**
+        * set to the API version you support (current is in
+        * LWS_SSH_OPS_VERSION) You should set it to an integer like 1,
+        * that reflects the latest api at the time your code was written.  If
+        * the ops api_version is not equal to the LWS_SSH_OPS_VERSION of the
+        * plugin, it will error out at runtime.
+        */
+       char api_version;
+};
+///@}
+
+#endif
+
diff --git a/plugins/ssh-base/include/lws-plugin-sshd-static-build-includes.h b/plugins/ssh-base/include/lws-plugin-sshd-static-build-includes.h
new file mode 100644 (file)
index 0000000..e134b4f
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * If you are including the plugin into your code using static build, you
+ * can simplify it by just including this file, which will include all the
+ * related code in one step without you having to get involved in the detail.
+ */
+
+#define LWS_PLUGIN_STATIC
+
+#include "../crypto/chacha.c"
+#include "../crypto/ed25519.c"
+#include "../crypto/fe25519.c"
+#include "../crypto/ge25519.c"
+#include "../crypto/poly1305.c"
+#include "../crypto/sc25519.c"
+#include "../crypto/smult_curve25519_ref.c"
+#include "../kex-25519.c"
+#include "../sshd.c"
+#include "../telnet.c"
diff --git a/plugins/ssh-base/include/lws-ssh.h b/plugins/ssh-base/include/lws-ssh.h
new file mode 100644 (file)
index 0000000..10de9ff
--- /dev/null
@@ -0,0 +1,604 @@
+/*
+ * libwebsockets - lws-plugin-ssh-base
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#if !defined(__LWS_SSH_H__)
+#define __LWS_SSH_H__
+
+#if defined(LWS_WITH_MBEDTLS)
+#include "mbedtls/sha1.h"
+#include "mbedtls/sha256.h"
+#include "mbedtls/sha512.h"
+#include "mbedtls/rsa.h"
+#endif
+
+#include "lws-plugin-ssh.h"
+
+#define LWS_SIZE_EC25519       32
+#define LWS_SIZE_EC25519_PUBKEY 32
+#define LWS_SIZE_EC25519_PRIKEY 64
+
+#define LWS_SIZE_SHA256                32
+#define LWS_SIZE_SHA512                64
+
+#define LWS_SIZE_AES256_KEY    32
+#define LWS_SIZE_AES256_IV     12
+#define LWS_SIZE_AES256_MAC    16
+#define LWS_SIZE_AES256_BLOCK  16
+
+#define LWS_SIZE_CHACHA256_KEY (2 * 32)
+#define POLY1305_TAGLEN                16
+#define POLY1305_KEYLEN                32
+
+#define crypto_hash_sha512_BYTES 64U
+
+#define PEEK_U64(p) \
+        (((uint64_t)(((const uint8_t *)(p))[0]) << 56) | \
+         ((uint64_t)(((const uint8_t *)(p))[1]) << 48) | \
+         ((uint64_t)(((const uint8_t *)(p))[2]) << 40) | \
+         ((uint64_t)(((const uint8_t *)(p))[3]) << 32) | \
+         ((uint64_t)(((const uint8_t *)(p))[4]) << 24) | \
+         ((uint64_t)(((const uint8_t *)(p))[5]) << 16) | \
+         ((uint64_t)(((const uint8_t *)(p))[6]) << 8) | \
+          (uint64_t)(((const uint8_t *)(p))[7]))
+#define PEEK_U32(p) \
+        (((uint32_t)(((const uint8_t *)(p))[0]) << 24) | \
+         ((uint32_t)(((const uint8_t *)(p))[1]) << 16) | \
+         ((uint32_t)(((const uint8_t *)(p))[2]) << 8) | \
+          (uint32_t)(((const uint8_t *)(p))[3]))
+#define PEEK_U16(p) \
+        (((uint16_t)(((const uint8_t *)(p))[0]) << 8) | \
+          (uint16_t)(((const uint8_t *)(p))[1]))
+
+#define POKE_U64(p, v) \
+        do { \
+                const uint64_t __v = (v); \
+                ((uint8_t *)(p))[0] = (__v >> 56) & 0xff; \
+                ((uint8_t *)(p))[1] = (__v >> 48) & 0xff; \
+                ((uint8_t *)(p))[2] = (__v >> 40) & 0xff; \
+                ((uint8_t *)(p))[3] = (__v >> 32) & 0xff; \
+                ((uint8_t *)(p))[4] = (__v >> 24) & 0xff; \
+                ((uint8_t *)(p))[5] = (__v >> 16) & 0xff; \
+                ((uint8_t *)(p))[6] = (__v >> 8) & 0xff; \
+                ((uint8_t *)(p))[7] = __v & 0xff; \
+        } while (0)
+#define POKE_U32(p, v) \
+        do { \
+                const uint32_t __v = (v); \
+                ((uint8_t *)(p))[0] = (__v >> 24) & 0xff; \
+                ((uint8_t *)(p))[1] = (__v >> 16) & 0xff; \
+                ((uint8_t *)(p))[2] = (__v >> 8) & 0xff; \
+                ((uint8_t *)(p))[3] = __v & 0xff; \
+        } while (0)
+#define POKE_U16(p, v) \
+        do { \
+                const uint16_t __v = (v); \
+                ((uint8_t *)(p))[0] = (__v >> 8) & 0xff; \
+                ((uint8_t *)(p))[1] = __v & 0xff; \
+        } while (0)
+
+
+enum {
+       SSH_MSG_DISCONNECT                                      = 1,
+       SSH_MSG_IGNORE                                          = 2,
+       SSH_MSG_UNIMPLEMENTED                                   = 3,
+       SSH_MSG_DEBUG                                           = 4,
+       SSH_MSG_SERVICE_REQUEST                                 = 5,
+       SSH_MSG_SERVICE_ACCEPT                                  = 6,
+       SSH_MSG_KEXINIT                                         = 20,
+       SSH_MSG_NEWKEYS                                         = 21,
+
+       /* 30 .. 49: KEX messages specific to KEX protocol */
+       SSH_MSG_KEX_ECDH_INIT                                   = 30,
+       SSH_MSG_KEX_ECDH_REPLY                                  = 31,
+
+       /* 50... userauth */
+
+       SSH_MSG_USERAUTH_REQUEST                                = 50,
+       SSH_MSG_USERAUTH_FAILURE                                = 51,
+       SSH_MSG_USERAUTH_SUCCESS                                = 52,
+       SSH_MSG_USERAUTH_BANNER                                 = 53,
+
+       /* 60... publickey */
+
+       SSH_MSG_USERAUTH_PK_OK                                  = 60,
+
+       /* 80... connection */
+
+       SSH_MSG_GLOBAL_REQUEST                                  = 80,
+       SSH_MSG_REQUEST_SUCCESS                                 = 81,
+       SSH_MSG_REQUEST_FAILURE                                 = 82,
+
+       SSH_MSG_CHANNEL_OPEN                                    = 90,
+       SSH_MSG_CHANNEL_OPEN_CONFIRMATION                       = 91,
+       SSH_MSG_CHANNEL_OPEN_FAILURE                            = 92,
+       SSH_MSG_CHANNEL_WINDOW_ADJUST                           = 93,
+       SSH_MSG_CHANNEL_DATA                                    = 94,
+       SSH_MSG_CHANNEL_EXTENDED_DATA                           = 95,
+       SSH_MSG_CHANNEL_EOF                                     = 96,
+       SSH_MSG_CHANNEL_CLOSE                                   = 97,
+       SSH_MSG_CHANNEL_REQUEST                                 = 98,
+       SSH_MSG_CHANNEL_SUCCESS                                 = 99,
+       SSH_MSG_CHANNEL_FAILURE                                 = 100,
+
+       SSH_EXTENDED_DATA_STDERR                                = 1,
+
+       SSH_CH_TYPE_SESSION                                     = 1,
+       SSH_CH_TYPE_SCP                                         = 2,
+       SSH_CH_TYPE_SFTP                                        = 3,
+
+       SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT              = 1,
+       SSH_DISCONNECT_PROTOCOL_ERROR                           = 2,
+       SSH_DISCONNECT_KEY_EXCHANGE_FAILED                      = 3,
+       SSH_DISCONNECT_RESERVED                                 = 4,
+       SSH_DISCONNECT_MAC_ERROR                                = 5,
+       SSH_DISCONNECT_COMPRESSION_ERROR                        = 6,
+       SSH_DISCONNECT_SERVICE_NOT_AVAILABLE                    = 7,
+       SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED           = 8,
+       SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE                  = 9,
+       SSH_DISCONNECT_CONNECTION_LOST                          = 10,
+       SSH_DISCONNECT_BY_APPLICATION                           = 11,
+       SSH_DISCONNECT_TOO_MANY_CONNECTIONS                     = 12,
+       SSH_DISCONNECT_AUTH_CANCELLED_BY_USER                   = 13,
+       SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE           = 14,
+       SSH_DISCONNECT_ILLEGAL_USER_NAME                        = 15,
+       
+       SSH_OPEN_ADMINISTRATIVELY_PROHIBITED                    = 1,
+       SSH_OPEN_CONNECT_FAILED                                 = 2,
+       SSH_OPEN_UNKNOWN_CHANNEL_TYPE                           = 3,
+       SSH_OPEN_RESOURCE_SHORTAGE                              = 4,
+
+       KEX_STATE_EXPECTING_CLIENT_OFFER                        = 0,
+       KEX_STATE_REPLIED_TO_OFFER,
+       KEX_STATE_CRYPTO_INITIALIZED,
+
+       SSH_KEYIDX_IV                                           = 0,
+       SSH_KEYIDX_ENC,
+       SSH_KEYIDX_INTEG,
+
+       /* things we may write on the connection */
+
+       SSH_WT_NONE                                             = 0,
+       SSH_WT_VERSION,
+       SSH_WT_OFFER,
+       SSH_WT_OFFER_REPLY,
+       SSH_WT_SEND_NEWKEYS,
+       SSH_WT_UA_ACCEPT,
+       SSH_WT_UA_FAILURE,
+       SSH_WT_UA_BANNER,
+       SSH_WT_UA_PK_OK,
+       SSH_WT_UA_SUCCESS,
+       SSH_WT_CH_OPEN_CONF,
+       SSH_WT_CH_FAILURE,
+       SSH_WT_CHRQ_SUCC,
+       SSH_WT_CHRQ_FAILURE,
+       SSH_WT_SCP_ACK_OKAY,
+       SSH_WT_SCP_ACK_ERROR,
+       SSH_WT_CH_CLOSE,
+       SSH_WT_CH_EOF,
+       SSH_WT_WINDOW_ADJUST,
+       SSH_WT_EXIT_STATUS,
+
+       /* RX parser states */
+
+       SSH_INITIALIZE_TRANSIENT                                = 0,
+       SSHS_IDSTRING,
+       SSHS_IDSTRING_CR,
+       SSHS_MSG_LEN,
+       SSHS_MSG_PADDING,
+       SSHS_MSG_ID,
+       SSH_KEX_STATE_COOKIE,
+       SSH_KEX_NL_KEX_ALGS_LEN,
+       SSH_KEX_NL_KEX_ALGS,
+       SSH_KEX_NL_SHK_ALGS_LEN,
+       SSH_KEX_NL_SHK_ALGS,
+       SSH_KEX_NL_EACTS_ALGS_LEN,
+       SSH_KEX_NL_EACTS_ALGS,
+       SSH_KEX_NL_EASTC_ALGS_LEN,
+       SSH_KEX_NL_EASTC_ALGS,
+       SSH_KEX_NL_MACTS_ALGS_LEN,
+       SSH_KEX_NL_MACTS_ALGS,
+       SSH_KEX_NL_MASTC_ALGS_LEN,
+       SSH_KEX_NL_MASTC_ALGS,
+       SSH_KEX_NL_CACTS_ALGS_LEN,
+       SSH_KEX_NL_CACTS_ALGS,
+       SSH_KEX_NL_CASTC_ALGS_LEN,
+       SSH_KEX_NL_CASTC_ALGS,
+       SSH_KEX_NL_LCTS_ALGS_LEN,
+       SSH_KEX_NL_LCTS_ALGS,
+       SSH_KEX_NL_LSTC_ALGS_LEN,
+       SSH_KEX_NL_LSTC_ALGS,
+       SSH_KEX_FIRST_PKT,
+       SSH_KEX_RESERVED,
+
+       SSH_KEX_STATE_ECDH_KEYLEN,
+       SSH_KEX_STATE_ECDH_Q_C,
+
+       SSHS_MSG_EAT_PADDING,
+       SSH_KEX_STATE_SKIP,
+
+       SSHS_GET_STRING_LEN,
+       SSHS_GET_STRING,
+       SSHS_GET_STRING_LEN_ALLOC,
+       SSHS_GET_STRING_ALLOC,
+       SSHS_DO_SERVICE_REQUEST,
+
+       SSHS_DO_UAR_SVC,
+       SSHS_DO_UAR_PUBLICKEY,
+       SSHS_NVC_DO_UAR_CHECK_PUBLICKEY,
+       SSHS_DO_UAR_SIG_PRESENT,
+       SSHS_NVC_DO_UAR_ALG,
+       SSHS_NVC_DO_UAR_PUBKEY_BLOB,
+       SSHS_NVC_DO_UAR_SIG,
+
+       SSHS_GET_U32,
+
+       SSHS_NVC_CHOPEN_TYPE,
+       SSHS_NVC_CHOPEN_SENDER_CH,
+       SSHS_NVC_CHOPEN_WINSIZE,
+       SSHS_NVC_CHOPEN_PKTSIZE,
+
+       SSHS_NVC_CHRQ_RECIP,
+       SSHS_NVC_CHRQ_TYPE,
+       SSHS_CHRQ_WANT_REPLY,
+        SSHS_NVC_CHRQ_TERM,
+        SSHS_NVC_CHRQ_TW,
+        SSHS_NVC_CHRQ_TH,
+       SSHS_NVC_CHRQ_TWP,
+        SSHS_NVC_CHRQ_THP,
+        SSHS_NVC_CHRQ_MODES,
+
+       SSHS_NVC_CHRQ_ENV_NAME,
+       SSHS_NVC_CHRQ_ENV_VALUE,
+
+       SSHS_NVC_CHRQ_EXEC_CMD,
+
+       SSHS_NVC_CHRQ_SUBSYSTEM,
+
+       SSHS_NVC_CH_EOF,
+       SSHS_NVC_CH_CLOSE,
+
+       SSHS_NVC_CD_RECIP,
+       SSHS_NVC_CD_DATA,
+       SSHS_NVC_CD_DATA_ALLOC,
+
+       SSHS_NVC_WA_RECIP,
+       SSHS_NVC_WA_ADD,
+
+       SSHS_NVC_DISCONNECT_REASON,
+       SSHS_NVC_DISCONNECT_DESC,
+       SSHS_NVC_DISCONNECT_LANG,
+
+       SSHS_SCP_COLLECTSTR                     = 0,
+       SSHS_SCP_PAYLOADIN                      = 1,
+
+
+       /* from https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13 */
+
+       SECSH_FILEXFER_VERSION                  = 6,
+
+       /* sftp packet types */
+
+       SSH_FXP_INIT                            = 1,
+       SSH_FXP_VERSION                         = 2,
+       SSH_FXP_OPEN                            = 3,
+       SSH_FXP_CLOSE                           = 4,
+       SSH_FXP_READ                            = 5,
+       SSH_FXP_WRITE                           = 6,
+       SSH_FXP_LSTAT                           = 7,
+       SSH_FXP_FSTAT                           = 8,
+       SSH_FXP_SETSTAT                         = 9,
+       SSH_FXP_FSETSTAT                        = 10,
+       SSH_FXP_OPENDIR                         = 11,
+       SSH_FXP_READDIR                         = 12,
+       SSH_FXP_REMOVE                          = 13,
+       SSH_FXP_MKDIR                           = 14,
+       SSH_FXP_RMDIR                           = 15,
+       SSH_FXP_REALPATH                        = 16,
+       SSH_FXP_STAT                            = 17,
+       SSH_FXP_RENAME                          = 18,
+       SSH_FXP_READLINK                        = 19,
+       SSH_FXP_LINK                            = 21,
+       SSH_FXP_BLOCK                           = 22,
+       SSH_FXP_UNBLOCK                         = 23,
+       SSH_FXP_STATUS                          = 101,
+       SSH_FXP_HANDLE                          = 102,
+       SSH_FXP_DATA                            = 103,
+       SSH_FXP_NAME                            = 104,
+       SSH_FXP_ATTRS                           = 105,
+       SSH_FXP_EXTENDED                        = 200,
+       SSH_FXP_EXTENDED_REPLY                  = 201,
+
+       /* sftp return codes */
+
+       SSH_FX_OK                               = 0,
+       SSH_FX_EOF                              = 1,
+       SSH_FX_NO_SUCH_FILE                     = 2,
+       SSH_FX_PERMISSION_DENIED                = 3,
+       SSH_FX_FAILURE                          = 4,
+       SSH_FX_BAD_MESSAGE                      = 5,
+       SSH_FX_NO_CONNECTION                    = 6,
+       SSH_FX_CONNECTION_LOST                  = 7,
+       SSH_FX_OP_UNSUPPORTED                   = 8,
+       SSH_FX_INVALID_HANDLE                   = 9,
+       SSH_FX_NO_SUCH_PATH                     = 10,
+       SSH_FX_FILE_ALREADY_EXISTS              = 11,
+       SSH_FX_WRITE_PROTECT                    = 12,
+       SSH_FX_NO_MEDIA                         = 13,
+       SSH_FX_NO_SPACE_ON_FILESYSTEM           = 14,
+       SSH_FX_QUOTA_EXCEEDED                   = 15,
+       SSH_FX_UNKNOWN_PRINCIPAL                = 16,
+       SSH_FX_LOCK_CONFLICT                    = 17,
+       SSH_FX_DIR_NOT_EMPTY                    = 18,
+       SSH_FX_NOT_A_DIRECTORY                  = 19,
+       SSH_FX_INVALID_FILENAME                 = 20,
+       SSH_FX_LINK_LOOP                        = 21,
+       SSH_FX_CANNOT_DELETE                    = 22,
+       SSH_FX_INVALID_PARAMETER                = 23,
+       SSH_FX_FILE_IS_A_DIRECTORY              = 24,
+       SSH_FX_BYTE_RANGE_LOCK_CONFLICT         = 25,
+       SSH_FX_BYTE_RANGE_LOCK_REFUSED          = 26,
+       SSH_FX_DELETE_PENDING                   = 27,
+       SSH_FX_FILE_CORRUPT                     = 28,
+       SSH_FX_OWNER_INVALID                    = 29,
+       SSH_FX_GROUP_INVALID                    = 30,
+       SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK      = 31,
+
+
+       SSH_PENDING_TIMEOUT_CONNECT_TO_SUCCESSFUL_AUTH =
+                       PENDING_TIMEOUT_USER_REASON_BASE + 0,
+
+       SSH_AUTH_STATE_NO_AUTH                  = 0,
+       SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS    = 1,
+};
+
+#define LWS_SSH_INITIAL_WINDOW 16384
+
+struct lws_ssh_userauth {
+       struct lws_genhash_ctx hash_ctx;
+       char *username;
+       char *service;
+       char *alg;
+       uint8_t *pubkey;
+       uint32_t pubkey_len;
+       uint8_t *sig;
+       uint32_t sig_len;
+       char sig_present;
+};
+
+struct lws_ssh_keys {
+       /* 3 == SSH_KEYIDX_IV (len=4), SSH_KEYIDX_ENC, SSH_KEYIDX_INTEG */
+       uint8_t key[3][LWS_SIZE_CHACHA256_KEY];
+
+       /* opaque allocation made when cipher activated */
+       void *cipher;
+
+       uint8_t MAC_length;
+       uint8_t padding_alignment; /* block size */
+       uint8_t valid:1;
+       uint8_t full_length:1;
+};
+
+struct lws_kex {
+       uint8_t kex_r[256];
+       uint8_t Q_C[LWS_SIZE_EC25519]; /* client eph public key aka 'e' */
+       uint8_t eph_pri_key[LWS_SIZE_EC25519]; /* server eph private key */
+       uint8_t Q_S[LWS_SIZE_EC25519]; /* server ephemeral public key */
+       uint8_t kex_cookie[16];
+       uint8_t *I_C; /* malloc'd copy of client KEXINIT payload */
+       uint8_t *I_S; /* malloc'd copy of server KEXINIT payload */
+       uint32_t I_C_payload_len;
+       uint32_t I_C_alloc_len;
+       uint32_t I_S_payload_len;
+       uint32_t kex_r_len;
+       uint8_t match_bitfield;
+       uint8_t newkeys; /* which sides newkeys have been applied */
+
+       struct lws_ssh_keys keys_next_cts;
+       struct lws_ssh_keys keys_next_stc;
+};
+
+struct lws_subprotocol_scp {
+       char fp[128];
+       uint64_t len;
+       uint32_t attr;
+       char cmd;
+       char ips;
+};
+
+typedef union {
+       struct lws_subprotocol_scp scp;
+} lws_subprotocol;
+
+struct per_session_data__sshd;
+
+struct lws_ssh_channel {
+       struct lws_ssh_channel *next;
+
+       struct per_session_data__sshd *pss;
+
+       lws_subprotocol *sub; /* NULL, or allocated subprotocol state */
+       void *priv; /* owned by user code */
+       int type;
+       uint32_t server_ch;
+       uint32_t sender_ch;
+       int32_t window;
+       int32_t peer_window_est;
+       uint32_t max_pkt;
+
+       uint32_t spawn_pid;
+       int retcode;
+
+       uint8_t scheduled_close:1;
+       uint8_t sent_close:1;
+       uint8_t received_close:1;
+};
+
+struct per_vhost_data__sshd;
+
+struct per_session_data__sshd {
+       struct per_session_data__sshd *next;
+       struct per_vhost_data__sshd *vhd;
+       struct lws *wsi;
+
+       struct lws_kex *kex;
+       char *disconnect_desc;
+
+       uint8_t K[LWS_SIZE_EC25519]; /* shared secret */
+       uint8_t session_id[LWS_SIZE_SHA256]; /* H from first working KEX */
+       char name[64];
+       char last_auth_req_username[32];
+       char last_auth_req_service[32];
+
+       struct lws_ssh_keys active_keys_cts;
+       struct lws_ssh_keys active_keys_stc;
+       struct lws_ssh_userauth *ua;
+       struct lws_ssh_channel *ch_list;
+       struct lws_ssh_channel *ch_temp;
+
+       uint8_t *last_alloc;
+
+       union {
+               struct lws_ssh_pty pty;
+               char aux[64];
+       } args;
+
+       uint32_t ssh_sequence_ctr_cts;
+       uint32_t ssh_sequence_ctr_stc;
+
+       uint64_t payload_bytes_cts;
+       uint64_t payload_bytes_stc;
+
+       uint32_t disconnect_reason;
+
+       char V_C[64]; /* Client version String */
+       uint8_t packet_assembly[2048];
+       uint32_t pa_pos;
+
+       uint32_t msg_len;
+       uint32_t pos;
+       uint32_t len;
+       uint32_t ctr;
+       uint32_t npos;
+       uint32_t reason;
+       uint32_t channel_doing_spawn;
+       int next_ch_num;
+
+       uint8_t K_S[LWS_SIZE_EC25519]; /* server public key */
+
+       uint32_t copy_to_I_C:1;
+       uint32_t okayed_userauth:1;
+       uint32_t sent_banner:1;
+       uint32_t seen_auth_req_before:1;
+       uint32_t serviced_stderr_last:1;
+       uint32_t kex_state;
+       uint32_t chrq_server_port;
+       uint32_t ch_recip;
+       uint32_t count_auth_attempts;
+
+       char parser_state;
+       char state_after_string;
+       char first_coming;
+       uint8_t rq_want_reply;
+       uint8_t ssh_auth_state;
+
+       uint8_t msg_id;
+       uint8_t msg_padding;
+       uint8_t write_task[8];
+       struct lws_ssh_channel *write_channel[8];
+       uint8_t wt_head, wt_tail;
+};
+
+struct per_vhost_data__sshd {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+       struct per_session_data__sshd *live_pss_list;
+       const struct lws_ssh_ops *ops;
+};
+
+
+struct host_keys {
+       uint8_t *data;
+       uint32_t len;
+};
+
+extern struct host_keys host_keys[];
+
+extern int
+crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n,
+                            const unsigned char *p);
+
+extern int
+ed25519_key_parse(uint8_t *p, size_t len, char *type, size_t type_len,
+                  uint8_t *pub, uint8_t *pri);
+
+extern int
+kex_ecdh(struct per_session_data__sshd *pss, uint8_t *result, uint32_t *plen);
+
+extern uint32_t
+lws_g32(uint8_t **p);
+
+extern uint32_t
+lws_p32(uint8_t *p, uint32_t v);
+
+extern int
+lws_timingsafe_bcmp(const void *a, const void *b, uint32_t len);
+
+extern const char *lws_V_S;
+
+extern int
+lws_chacha_activate(struct lws_ssh_keys *keys);
+
+extern void
+lws_chacha_destroy(struct lws_ssh_keys *keys);
+
+extern uint32_t
+lws_chachapoly_get_length(struct lws_ssh_keys *keys, uint32_t seq,
+                         const uint8_t *in4);
+
+extern void
+poly1305_auth(u_char out[POLY1305_TAGLEN], const u_char *m, size_t inlen,
+    const u_char key[POLY1305_KEYLEN]);
+
+extern int
+lws_chacha_decrypt(struct lws_ssh_keys *keys, uint32_t seq,
+                  const uint8_t *ct, uint32_t len, uint8_t *pt);
+extern int
+lws_chacha_encrypt(struct lws_ssh_keys *keys, uint32_t seq,
+                  const uint8_t *ct, uint32_t len, uint8_t *pt);
+
+extern void
+lws_pad_set_length(struct per_session_data__sshd *pss, void *start, uint8_t **p,
+                  struct lws_ssh_keys *keys);
+
+extern size_t
+get_gen_server_key_25519(struct per_session_data__sshd *pss, uint8_t *b, size_t len);
+
+extern int
+crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen,
+                   const unsigned char *m, size_t mlen,
+                   const unsigned char *sk);
+
+extern int
+crypto_sign_ed25519_keypair(struct lws_context *context, uint8_t *pk,
+                           uint8_t *sk);
+
+#endif
diff --git a/plugins/ssh-base/kex-25519.c b/plugins/ssh-base/kex-25519.c
new file mode 100644 (file)
index 0000000..2fd3aa4
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ * libwebsockets - lws-plugin-ssh-base - kex-25519.c
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+#include "libwebsockets.h"
+#include "lws-ssh.h"
+
+#include <string.h>
+
+/*
+ * ssh-keygen -t ed25519
+ * head -n-1 srv-key-25519 | tail -n +2 | base64 -d | hexdump -C
+ */
+
+static void
+lws_sized_blob(uint8_t **p, void *blob, uint32_t len)
+{
+       lws_p32((*p), len);
+       *p += 4;
+       memcpy(*p, blob, len);
+       *p += len;
+}
+
+static const char key_leadin[] = "openssh-key-v1\x00\x00\x00\x00\x04none"
+                                "\x00\x00\x00\x04none\x00"
+                                "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x33"
+                                "\x00\x00\x00\x0bssh-ed25519\x00\x00\x00\x20",
+                 key_sep[] =    "\x00\x00\x00\x90\xb1\x4f\xa7\x28"
+                                "\xb1\x4f\xa7\x28\x00\x00\x00\x0bssh-ed25519"
+                                "\x00\x00\x00\x20",
+                 key_privl[] =  "\x00\x00\x00\x40",
+                 key_trail[] =  "\x00\x00\x00\x0cself-gen@cbl\x01";
+
+static size_t
+lws_gen_server_key_ed25519(struct lws_context *context, uint8_t *buf256,
+                          size_t max_len)
+{
+       uint8_t *p = buf256 + sizeof(key_leadin) - 1;
+
+       if (max_len < sizeof(key_leadin) - 1 + 32 + sizeof(key_sep) - 1 + 32 +
+                     sizeof(key_privl) - 1 + 64 + sizeof(key_trail) - 1)
+               return 0;
+
+       memcpy(buf256, key_leadin, sizeof(key_leadin) - 1);
+       crypto_sign_ed25519_keypair(context, p, p + 32 + sizeof(key_sep) - 1 +
+                                   32 + sizeof(key_privl) - 1);
+       memcpy(p + 32 + sizeof(key_sep) - 1, p, 32);
+       p += 32;
+       memcpy(p, key_sep, sizeof(key_sep) - 1);
+       p += sizeof(key_sep) - 1 + 32;
+       memcpy(p, key_privl, sizeof(key_privl) - 1);
+       p += sizeof(key_privl) - 1 + 64;
+       memcpy(p, key_trail, sizeof(key_trail) - 1);
+       p += sizeof(key_trail) - 1;
+
+       lwsl_notice("%s: Generated key len %ld\n", __func__, (long)(p - buf256));
+
+       return p - buf256;
+}
+
+static int
+lws_mpint_rfc4251(uint8_t *dest, const uint8_t *src, int bytes, int uns)
+{
+       uint8_t *odest = dest;
+
+       while (!*src && bytes > 1) {
+               src++;
+               bytes--;
+       }
+
+       if (!*src) {
+               *dest++ = 0;
+               *dest++ = 0;
+               *dest++ = 0;
+               *dest++ = 0;
+
+               return 4;
+       }
+
+       if (uns && (*src) & 0x80)
+               bytes++;
+
+       *dest++ = bytes >> 24;
+       *dest++ = bytes >> 16;
+       *dest++ = bytes >> 8;
+       *dest++ = bytes;
+
+       if (uns && (*src) & 0x80) {
+               *dest++ = 0;
+               bytes--;
+       }
+
+       while (bytes--)
+               *dest++ = *src++;
+
+       return lws_ptr_diff(dest, odest);
+}
+
+int
+ed25519_key_parse(uint8_t *p, size_t len, char *type, size_t type_len,
+                 uint8_t *pub, uint8_t *pri)
+{
+       uint32_t l, publ, m;
+       uint8_t *op = p;
+
+       if (len < 180)
+               return 1;
+
+       if (memcmp(p, "openssh-key-v1", 14))
+               return 2;
+
+       p += 15;
+
+       l = lws_g32(&p); /* ciphername */
+       if (l != 4 || memcmp(p, "none", 4))
+               return 3;
+       p += l;
+
+       l = lws_g32(&p); /* kdfname */
+       if (l != 4 || memcmp(p, "none", 4))
+               return 4;
+       p += l;
+
+       l = lws_g32(&p); /* kdfoptions */
+       if (l)
+               return 5;
+
+       l = lws_g32(&p); /* number of keys */
+       if (l != 1)
+               return 6;
+
+       publ = lws_g32(&p); /* length of pubkey block */
+       if ((size_t)((p - op) + publ) >= len)
+               return 7;
+
+       l = lws_g32(&p); /* key type length */
+       if (l > 31)
+               return 8;
+       m = l;
+       if (m >= type_len)
+               m = (uint32_t)type_len -1 ;
+       lws_strncpy(type, (const char *)p, m + 1);
+
+       p += l;
+       l = lws_g32(&p); /* pub key length */
+       if (l != 32)
+               return 10;
+
+       p += l;
+
+       publ = lws_g32(&p); /* length of private key block */
+       if ((size_t)((p - op) + publ) != len)
+               return 11;
+
+       l = lws_g32(&p); /* checkint 1 */
+       if (lws_g32(&p) != l) /* must match checkint 2 */
+               return 12;
+
+       l = lws_g32(&p); /* key type length */
+
+       p += l;
+       l = lws_g32(&p); /* public key part length */
+       if (l != LWS_SIZE_EC25519_PUBKEY)
+               return 15;
+
+       if (pub)
+               memcpy(pub, p, LWS_SIZE_EC25519_PUBKEY);
+       p += l;
+       l = lws_g32(&p); /* private key part length */
+       if (l != LWS_SIZE_EC25519_PRIKEY)
+               return 16;
+
+       if (pri)
+               memcpy(pri, p, LWS_SIZE_EC25519_PRIKEY);
+
+       return 0;
+}
+
+static int
+_genhash_update_len(struct lws_genhash_ctx *ctx, const void *input, size_t ilen)
+{
+       uint32_t be;
+
+       lws_p32((uint8_t *)&be, (uint32_t)ilen);
+
+       if (lws_genhash_update(ctx, (uint8_t *)&be, 4))
+               return 1;
+       if (lws_genhash_update(ctx, input, ilen))
+               return 1;
+
+       return 0;
+}
+
+static int
+kex_ecdh_dv(uint8_t *dest, int dest_len, const uint8_t *kbi, int kbi_len,
+           const uint8_t *H, char c, const uint8_t *session_id)
+{
+       uint8_t pool[LWS_SIZE_SHA256];
+       struct lws_genhash_ctx ctx;
+       int n = 0, m;
+
+       /*
+        * Key data MUST be taken from the beginning of the hash output.
+        * As many bytes as needed are taken from the beginning of the hash
+        * value.
+        *
+        * If the key length needed is longer than the output of the HASH,
+        * the key is extended by computing HASH of the concatenation of K
+        * and H and the entire key so far, and appending the resulting
+        * bytes (as many as HASH generates) to the key.  This process is
+        * repeated until enough key material is available; the key is taken
+        * from the beginning of this value.  In other words:
+        *
+        * K1 = HASH(K || H || X || session_id)   (X is e.g., "A")
+        * K2 = HASH(K || H || K1)
+        * K3 = HASH(K || H || K1 || K2)
+        *      ...
+        * key = K1 || K2 || K3 || ...
+        */
+
+       while (n < dest_len) {
+
+               if (lws_genhash_init(&ctx, LWS_GENHASH_TYPE_SHA256))
+                       return 1;
+
+               if (lws_genhash_update(&ctx, kbi, kbi_len))
+                       goto hash_failed;
+               if (lws_genhash_update(&ctx, H, LWS_SIZE_SHA256))
+                       goto hash_failed;
+
+               if (!n) {
+                       if (lws_genhash_update(&ctx, (void *)&c, 1))
+                               goto hash_failed;
+                       if (lws_genhash_update(&ctx, session_id,
+                                             LWS_SIZE_EC25519))
+                               goto hash_failed;
+               } else
+                       if (lws_genhash_update(&ctx, pool, LWS_SIZE_EC25519))
+                               goto hash_failed;
+
+               lws_genhash_destroy(&ctx, pool);
+
+               m = LWS_SIZE_EC25519;
+               if (m > (dest_len - n))
+                       m = dest_len - n;
+
+               memcpy(dest, pool, m);
+               n += m;
+               dest += m;
+       }
+
+       return 0;
+
+hash_failed:
+       lws_genhash_destroy(&ctx, NULL);
+
+       return 1;
+}
+
+
+static const unsigned char basepoint[32] = { 9 };
+
+size_t
+get_gen_server_key_25519(struct per_session_data__sshd *pss, uint8_t *b,
+                        size_t len)
+{
+       size_t s, mylen;
+
+       mylen = pss->vhd->ops->get_server_key(pss->wsi, b, len);
+       if (mylen)
+               return mylen;
+
+       /* create one then */
+       lwsl_notice("Generating server hostkey\n");
+       s = lws_gen_server_key_ed25519(pss->vhd->context, b, len);
+       lwsl_notice("  gen key len %ld\n", (long)s);
+       if (!s)
+               return 0;
+       /* set the key */
+       if (!pss->vhd->ops->set_server_key(pss->wsi, b, s))
+               return 0;
+
+       /* new key stored OK */
+
+       return s;
+}
+
+int
+kex_ecdh(struct per_session_data__sshd *pss, uint8_t *reply, uint32_t *plen)
+{
+       uint8_t pri_key[64], temp[64], payload_sig[64 + 32], a, *lp, kbi[64];
+       struct lws_kex *kex = pss->kex;
+       struct lws_genhash_ctx ctx;
+        unsigned long long smlen;
+       uint8_t *p = reply + 5;
+       uint32_t be, kbi_len;
+       uint8_t servkey[256];
+       char keyt[33];
+       int r, c;
+
+       r = (int)get_gen_server_key_25519(pss, servkey, (int)sizeof(servkey));
+       if (!r) {
+               lwsl_err("%s: Failed to get or gen server key\n", __func__);
+
+               return 1;
+       }
+
+       r = ed25519_key_parse(servkey, r, keyt, sizeof(keyt),
+                             pss->K_S /* public key */, pri_key);
+       if (r) {
+               lwsl_notice("%s: server key parse failed: %d\n", __func__, r);
+
+               return 1;
+       }
+       keyt[32] = '\0';
+
+       lwsl_info("Server key type: %s\n", keyt);
+
+       /*
+        * 1) Generate ephemeral key pair [ eph_pri_key | kex->Q_S ]
+        * 2) Compute shared secret.
+        * 3) Generate and sign exchange hash.
+        *
+        * 1) A 32 bytes private key should be generated for each new
+        *    connection, using a secure PRNG. The following actions
+        *    must be done on the private key:
+        *
+        *     mysecret[0] &= 248;
+        *     mysecret[31] &= 127;
+        *     mysecret[31] |= 64;
+        */
+       lws_get_random(pss->vhd->context, kex->eph_pri_key, LWS_SIZE_EC25519);
+       kex->eph_pri_key[0] &= 248;
+       kex->eph_pri_key[31] &= 127;
+       kex->eph_pri_key[31] |= 64;
+
+       /*
+        * 2) The public key is calculated using the cryptographic scalar
+        *    multiplication:
+        *
+        *     const unsigned char privkey[32];
+        *     unsigned char pubkey[32];
+        *
+        *     crypto_scalarmult (pubkey, privkey, basepoint);
+        */
+       crypto_scalarmult_curve25519(kex->Q_S, kex->eph_pri_key, basepoint);
+
+       a = 0;
+       for (r = 0; r < (int)sizeof(kex->Q_S); r++)
+               a |= kex->Q_S[r];
+       if (!a) {
+               lwsl_notice("all zero pubkey\n");
+               return SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
+       }
+
+       /*
+        * The shared secret, k, is defined in SSH specifications to be a big
+        * integer.  This number is calculated using the following procedure:
+        *
+        * X is the 32 bytes point obtained by the scalar multiplication of
+        * the other side's public key and the local private key scalar.
+        */
+       crypto_scalarmult_curve25519(pss->K, kex->eph_pri_key, kex->Q_C);
+
+       /*
+        * The whole 32 bytes of the number X are then converted into a big
+        * integer k.  This conversion follows the network byte order. This
+        * step differs from RFC5656.
+        */
+       kbi_len = lws_mpint_rfc4251(kbi, pss->K, LWS_SIZE_EC25519, 1);
+
+       /*
+        * The exchange hash H is computed as the hash of the concatenation of
+        * the following:
+        *
+        *      string    V_C, the client's identification string (CR and LF
+         *                    excluded)
+        *      string    V_S, the server's identification string (CR and LF
+         *                    excluded)
+        *      string    I_C, the payload of the client's SSH_MSG_KEXINIT
+        *      string    I_S, the payload of the server's SSH_MSG_KEXINIT
+        *      string    K_S, the host key
+        *      mpint     Q_C, exchange value sent by the client
+        *      mpint     Q_S, exchange value sent by the server
+        *      mpint     K, the shared secret
+        *
+        * However there are a lot of unwritten details in the hash
+        * definition...
+        */
+
+       if (lws_genhash_init(&ctx, LWS_GENHASH_TYPE_SHA256)) {
+               lwsl_notice("genhash init failed\n");
+               return 1;
+       }
+
+       if (_genhash_update_len(&ctx, pss->V_C, strlen(pss->V_C)))
+               goto hash_probs;
+       if (_genhash_update_len(&ctx, pss->vhd->ops->server_string, /* aka V_S */
+                              strlen(pss->vhd->ops->server_string)))
+               goto hash_probs;
+       if (_genhash_update_len(&ctx, kex->I_C, kex->I_C_payload_len))
+               goto hash_probs;
+       if (_genhash_update_len(&ctx, kex->I_S, kex->I_S_payload_len))
+               goto hash_probs;
+       /*
+        * K_S (host public key)
+        *
+        * sum of name + key lengths and headers
+        * name length: name
+        * key length: key
+        * ---> */
+       lws_p32((uint8_t *)&be, 8 + (int)strlen(keyt) + LWS_SIZE_EC25519);
+       if (lws_genhash_update(&ctx, (void *)&be, 4))
+               goto hash_probs;
+
+       if (_genhash_update_len(&ctx, keyt, strlen(keyt)))
+               goto hash_probs;
+       if (_genhash_update_len(&ctx, pss->K_S, LWS_SIZE_EC25519))
+               goto hash_probs;
+       /* <---- */
+
+       if (_genhash_update_len(&ctx, kex->Q_C, LWS_SIZE_EC25519))
+               goto hash_probs;
+       if (_genhash_update_len(&ctx, kex->Q_S, LWS_SIZE_EC25519))
+               goto hash_probs;
+
+       if (lws_genhash_update(&ctx, kbi, kbi_len))
+               goto hash_probs;
+
+       if (lws_genhash_destroy(&ctx, temp))
+               goto hash_probs;
+
+       /*
+        * Sign the 32-byte SHA256 "exchange hash" in temp
+        * The signature is itself 64 bytes
+        */
+        smlen = LWS_SIZE_EC25519 + 64;
+        if (crypto_sign_ed25519(payload_sig, &smlen, temp, LWS_SIZE_EC25519,
+                               pri_key))
+               return 1;
+
+#if 0
+        l = LWS_SIZE_EC25519;
+        n = crypto_sign_ed25519_open(temp, &l, payload_sig, smlen, pss->K_S);
+
+        lwsl_notice("own sig sanity check says %d\n", n);
+#endif
+
+       /* sig [64] and payload [32] concatenated in payload_sig
+        *
+        * The server then responds with the following
+        *
+        *      uint32    packet length (exl self + mac)
+        *      byte      padding len
+        *      byte      SSH_MSG_KEX_ECDH_REPLY
+        *      string    server public host key and certificates (K_S)
+        *      string    Q_S (exchange value sent by the server)
+        *      string    signature of H
+        *      padding
+        */
+       *p++ = SSH_MSG_KEX_ECDH_REPLY;
+
+       /* server public host key and certificates (K_S) */
+
+       lp = p;
+       p +=4;
+       lws_sized_blob(&p, keyt, (int)strlen(keyt));
+       lws_sized_blob(&p, pss->K_S, LWS_SIZE_EC25519);
+       lws_p32(lp, lws_ptr_diff(p, lp) - 4);
+
+       /* Q_S (exchange value sent by the server) */
+       
+       lws_sized_blob(&p, kex->Q_S, LWS_SIZE_EC25519);
+
+       /* signature of H */
+
+       lp = p;
+       p +=4;
+       lws_sized_blob(&p, keyt, (int)strlen(keyt));
+       lws_sized_blob(&p, payload_sig, 64);
+       lws_p32(lp, lws_ptr_diff(p, lp) - 4);
+
+       /* end of message */
+
+       lws_pad_set_length(pss, reply, &p, &pss->active_keys_stc);
+       *plen = lws_ptr_diff(p, reply);
+
+       if (!pss->active_keys_stc.valid)
+               memcpy(pss->session_id, temp, LWS_SIZE_EC25519);
+
+       /* RFC4253 7.2:
+        *
+        * The key exchange produces two values: a shared secret K,
+        * and an exchange hash H.  Encryption and authentication
+        * keys are derived from these.  The exchange hash H from the
+        * first key exchange is additionally used as the session
+        * identifier, which is a unique identifier for this connection.
+        * It is used by authentication methods as a part of the data
+        * that is signed as a proof of possession of a private key.
+        * Once computed, the session identifier is not changed,
+        * even if keys are later re-exchanged.
+        *
+        * The hash alg used in the KEX must be used for key derivation.
+        *
+        * 1) Initial IV client to server:
+        *
+        *     HASH(K || H || "A" || session_id)
+        *
+        * (Here K is encoded as mpint and "A" as byte and session_id
+        * as raw data.  "A" means the single character A, ASCII 65).
+        *
+        *
+        */
+       for (c = 0; c < 3; c++) {
+               kex_ecdh_dv(kex->keys_next_cts.key[c], LWS_SIZE_CHACHA256_KEY,
+                           kbi, kbi_len, temp, 'A' + (c * 2), pss->session_id);
+               kex_ecdh_dv(kex->keys_next_stc.key[c], LWS_SIZE_CHACHA256_KEY,
+                           kbi, kbi_len, temp, 'B' + (c * 2), pss->session_id);
+       }
+
+       lws_explicit_bzero(temp, sizeof(temp));
+
+       return 0;
+
+hash_probs:
+       lws_genhash_destroy(&ctx, NULL);
+
+       return 1;
+}
diff --git a/plugins/ssh-base/sshd.c b/plugins/ssh-base/sshd.c
new file mode 100644 (file)
index 0000000..62e0151
--- /dev/null
@@ -0,0 +1,2588 @@
+/*
+ * libwebsockets - lws-plugin-ssh-base - sshd.c
+ *
+ * Copyright (C) 2017 - 2018 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "libwebsockets.h"
+#include "lws-ssh.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+void *sshd_zalloc(size_t s)
+{
+       void *p = malloc(s);
+
+       if (p)
+               memset(p, 0, s);
+
+       return p;
+}
+
+uint32_t
+lws_g32(uint8_t **p)
+{
+       uint32_t v = 0;
+
+       v = (v << 8) | *((*p)++);
+       v = (v << 8) | *((*p)++);
+       v = (v << 8) | *((*p)++);
+       v = (v << 8) | *((*p)++);
+
+       return v;
+}
+
+uint32_t
+lws_p32(uint8_t *p, uint32_t v)
+{
+       *p++ = v >> 24;
+       *p++ = v >> 16;
+       *p++ = v >> 8;
+       *p++ = v;
+
+       return v;
+}
+
+int
+lws_cstr(uint8_t **p, const char *s, uint32_t max)
+{
+       uint32_t n = (uint32_t)strlen(s);
+
+       if (n > max)
+               return 1;
+
+       lws_p32(*p, n);
+       *p += 4;
+       strcpy((char *)(*p), s);
+       *p += n;
+
+       return 0;
+}
+
+int
+lws_buf(uint8_t **p, void *s, uint32_t len)
+{
+       lws_p32(*p, len);
+       *p += 4;
+       memcpy((char *)(*p), s, len);
+       *p += len;
+
+       return 0;
+}
+
+void
+write_task(struct per_session_data__sshd *pss, struct lws_ssh_channel *ch,
+          int task)
+{
+       pss->write_task[pss->wt_head] = task;
+       pss->write_channel[pss->wt_head] = ch;
+       pss->wt_head = (pss->wt_head + 1) & 7;
+       lws_callback_on_writable(pss->wsi);
+}
+
+void
+write_task_insert(struct per_session_data__sshd *pss, struct lws_ssh_channel *ch,
+          int task)
+{
+       pss->wt_tail = (pss->wt_tail - 1) & 7;
+       pss->write_task[pss->wt_tail] = task;
+       pss->write_channel[pss->wt_tail] = ch;
+       lws_callback_on_writable(pss->wsi);
+}
+
+
+void
+lws_pad_set_length(struct per_session_data__sshd *pss, void *start, uint8_t **p,
+                  struct lws_ssh_keys *keys)
+{
+       uint32_t len = lws_ptr_diff(*p, start);
+       uint8_t padc = 4, *bs = start;
+
+       if (keys->full_length)
+               len -= 4;
+
+       if ((len + padc) & (keys->padding_alignment - 1))
+               padc += keys->padding_alignment -
+                       ((len + padc) & (keys->padding_alignment - 1));
+
+       bs[4] = padc;
+       len += padc;
+
+       if (!keys->valid) /* no crypto = pad with 00 */
+               while (padc--)
+                       *((*p)++) = 0;
+       else { /* crypto active = pad with random */
+               lws_get_random(pss->vhd->context, *p, padc);
+               (*p) += padc;
+       }
+       if (keys->full_length)
+               len += 4;
+
+       lws_p32(start, len - 4);
+}
+
+static uint32_t
+offer(struct per_session_data__sshd *pss, uint8_t *p, uint32_t len, int first,
+      int *payload_len)
+{
+       uint8_t *op = p, *lp, *end = p + len - 1;
+       int n, padc = 4, keylen;
+       char keyt[32];
+       uint8_t keybuf[256];
+
+       keylen = (int)get_gen_server_key_25519(pss, keybuf, (int)sizeof(keybuf));
+       if (!keylen) {
+               lwsl_notice("get_gen_server_key failed\n");
+               return 1;
+       }
+       lwsl_info("keylen %d\n", keylen);
+       n = ed25519_key_parse(keybuf, keylen,
+                             keyt, sizeof(keyt), NULL, NULL);
+       if (n) {
+               lwsl_notice("unable to parse server key: %d\n", n);
+               return 1;
+       }
+
+       /*
+        *     byte         SSH_MSG_KEXINIT
+        *     byte[16]     cookie (random bytes)
+        *     name-list    kex_algorithms
+        *     name-list    server_host_key_algorithms
+        *     name-list    encryption_algorithms_client_to_server
+        *     name-list    encryption_algorithms_server_to_client
+        *     name-list    mac_algorithms_client_to_server
+        *     name-list    mac_algorithms_server_to_client
+        *     name-list    compression_algorithms_client_to_server
+        *     name-list    compression_algorithms_server_to_client
+        *     name-list    langua->es_client_to_server
+        *     name-list    langua->es_server_to_client
+        *     boolean      first_kex_packet_follows
+        *     uint32       0 (reserved for future extension)
+        */
+
+       p += 5; /* msg len + padding */
+
+       *p++ = SSH_MSG_KEXINIT;
+       lws_get_random(pss->vhd->context, p, 16);
+       p += 16;
+
+       /* KEX algorithms */
+
+       lp = p;
+       p += 4;
+       n = lws_snprintf((char *)p, end - p, "curve25519-sha256@libssh.org");
+       p += lws_p32(lp, n);
+
+       /* Server Host Key Algorithms */
+
+       lp = p;
+       p += 4;
+       n = lws_snprintf((char *)p, end - p, "%s", keyt);
+       p += lws_p32(lp, n);
+
+       /* Encryption Algorithms: C -> S */
+
+       lp = p;
+       p += 4;
+//     n = lws_snprintf((char *)p, end - p, "aes256-gcm@openssh.com");
+       n = lws_snprintf((char *)p, end - p, "chacha20-poly1305@openssh.com");
+       p += lws_p32(lp, n);
+
+       /* Encryption Algorithms: S -> C */
+
+       lp = p;
+       p += 4;
+//     n = lws_snprintf((char *)p, end - p, "aes256-gcm@openssh.com");
+       n = lws_snprintf((char *)p, end - p, "chacha20-poly1305@openssh.com");
+       p += lws_p32(lp, n);
+
+       /* MAC Algorithms: C -> S */
+
+       lp = p;
+       p += 4;
+       /* bogus: chacha20 does not use MACs, but 'none' is not offered */
+       n = lws_snprintf((char *)p, end - p, "hmac-sha2-256");
+       p += lws_p32(lp, n);
+
+       /* MAC Algorithms: S -> C */
+
+       lp = p;
+       p += 4;
+       /* bogus: chacha20 does not use MACs, but 'none' is not offered */
+       n = lws_snprintf((char *)p, end - p, "hmac-sha2-256");
+       p += lws_p32(lp, n);
+
+       /* Compression Algorithms: C -> S */
+
+       lp = p;
+       p += 4;
+       n = lws_snprintf((char *)p, end - p, "none");
+       p += lws_p32(lp, n);
+
+       /* Compression Algorithms: S -> C */
+
+       lp = p;
+       p += 4;
+       n = lws_snprintf((char *)p, end - p, "none");
+       p += lws_p32(lp, n);
+
+       if (p - op < 13 + padc + 8)
+               return 0;
+
+       /* Languages: C -> S */
+
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+
+       /* Languages: S -> C */
+
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+
+       /* First KEX packet coming */
+
+       *p++ = !!first;
+
+       /* Reserved */
+
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+
+       len = lws_ptr_diff(p, op);
+       if (payload_len)
+               /* starts at buf + 5 and excludes padding */
+               *payload_len = len - 5;
+
+       /* we must give at least 4 bytes of 00 padding */
+
+       if ((len + padc) & 7)
+               padc += 8 - ((len + padc) & 7);
+
+       op[4] = padc;
+       len += padc;
+
+       while (padc--)
+               *p++ = 0;
+
+       /* recorded length does not include the uint32_t len itself */
+       lws_p32(op, len - 4);
+
+       return len;
+}
+
+static int
+handle_name(struct per_session_data__sshd *pss)
+{
+       struct lws_kex *kex = pss->kex;
+       char keyt[32];
+       uint8_t keybuf[256];
+       int n = 0, len;
+
+       switch (pss->parser_state) {
+       case SSH_KEX_NL_KEX_ALGS:
+               if (!strcmp(pss->name, "curve25519-sha256@libssh.org"))
+                       kex->match_bitfield |= 1;
+               break;
+       case SSH_KEX_NL_SHK_ALGS:
+               len = (int)get_gen_server_key_25519(pss, keybuf, (int)sizeof(keybuf));
+               if (!len)
+                       break;
+               if (ed25519_key_parse(keybuf, len,
+                                     keyt, sizeof(keyt),
+                                     NULL, NULL)) {
+                       lwsl_err("Unable to parse host key %d\n", n);
+               } else {
+                       if (!strcmp(pss->name, keyt)) {
+                               kex->match_bitfield |= 2;
+                               break;
+                       }
+               }
+               break;
+       case SSH_KEX_NL_EACTS_ALGS:
+               if (!strcmp(pss->name, "chacha20-poly1305@openssh.com"))
+                       kex->match_bitfield |= 4;
+               break;
+       case SSH_KEX_NL_EASTC_ALGS:
+               if (!strcmp(pss->name, "chacha20-poly1305@openssh.com"))
+                       kex->match_bitfield |= 8;
+               break;
+       case SSH_KEX_NL_MACTS_ALGS:
+               if (!strcmp(pss->name, "hmac-sha2-256"))
+                       kex->match_bitfield |= 16;
+               break;
+       case SSH_KEX_NL_MASTC_ALGS:
+               if (!strcmp(pss->name, "hmac-sha2-256"))
+                       kex->match_bitfield |= 32;
+               break;
+       case SSH_KEX_NL_CACTS_ALGS:
+               if (!strcmp(pss->name, "none"))
+                       kex->match_bitfield |= 64;
+               break;
+       case SSH_KEX_NL_CASTC_ALGS:
+               if (!strcmp(pss->name, "none"))
+                       kex->match_bitfield |= 128;
+               break;
+       case SSH_KEX_NL_LCTS_ALGS:
+       case SSH_KEX_NL_LSTC_ALGS:
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+
+static int
+lws_kex_create(struct per_session_data__sshd *pss)
+{
+       pss->kex = sshd_zalloc(sizeof(struct lws_kex));
+       lwsl_info("%s\n", __func__);
+       return !pss->kex;
+}
+
+static void
+lws_kex_destroy(struct per_session_data__sshd *pss)
+{
+       if (!pss->kex)
+               return;
+
+       lwsl_info("Destroying KEX\n");
+
+       if (pss->kex->I_C) {
+               free(pss->kex->I_C);
+               pss->kex->I_C = NULL;
+       }
+       if (pss->kex->I_S) {
+               free(pss->kex->I_S);
+               pss->kex->I_S = NULL;
+       }
+
+       lws_explicit_bzero(pss->kex, sizeof(*pss->kex));
+       free(pss->kex);
+       pss->kex = NULL;
+}
+
+static void
+ssh_free(void *p)
+{
+       if (!p)
+               return;
+
+       lwsl_debug("%s: FREE %p\n", __func__, p);
+       free(p);
+}
+
+#define ssh_free_set_NULL(x) if (x) { ssh_free(x); (x) = NULL; }
+
+static void
+lws_ua_destroy(struct per_session_data__sshd *pss)
+{
+       if (!pss->ua)
+               return;
+
+       lwsl_info("%s\n", __func__);
+
+       if (pss->ua->username)
+               ssh_free(pss->ua->username);
+       if (pss->ua->service)
+               ssh_free(pss->ua->service);
+       if (pss->ua->alg)
+               ssh_free(pss->ua->alg);
+       if (pss->ua->pubkey)
+               ssh_free(pss->ua->pubkey);
+       if (pss->ua->sig) {
+               lws_explicit_bzero(pss->ua->sig, pss->ua->sig_len);
+               ssh_free(pss->ua->sig);
+       }
+
+       lws_explicit_bzero(pss->ua, sizeof(*pss->ua));
+       free(pss->ua);
+       pss->ua = NULL;
+}
+
+
+static int
+rsa_hash_alg_from_ident(const char *ident)
+{
+       if (strcmp(ident, "ssh-rsa") == 0 ||
+           strcmp(ident, "ssh-rsa-cert-v01@openssh.com") == 0)
+               return LWS_GENHASH_TYPE_SHA1;
+       if (strcmp(ident, "rsa-sha2-256") == 0)
+               return LWS_GENHASH_TYPE_SHA256;
+       if (strcmp(ident, "rsa-sha2-512") == 0)
+               return LWS_GENHASH_TYPE_SHA512;
+
+        return -1;
+}
+
+static void
+state_get_string_alloc(struct per_session_data__sshd *pss, int next)
+{
+       pss->parser_state = SSHS_GET_STRING_LEN_ALLOC;
+        pss->state_after_string = next;
+}
+
+static void
+state_get_string(struct per_session_data__sshd *pss, int next)
+{
+       pss->parser_state = SSHS_GET_STRING_LEN;
+        pss->state_after_string = next;
+}
+
+static void
+state_get_u32(struct per_session_data__sshd *pss, int next)
+{
+       pss->parser_state = SSHS_GET_U32;
+        pss->state_after_string = next;
+}
+
+static struct lws_ssh_channel *
+ssh_get_server_ch(struct per_session_data__sshd *pss, uint32_t chi)
+{
+       struct lws_ssh_channel *ch = pss->ch_list;
+
+       while (ch) {
+               if (ch->server_ch == chi)
+                       return ch;
+               ch = ch->next;
+       }
+
+       return NULL;
+}
+
+#if 0
+static struct lws_ssh_channel *
+ssh_get_peer_ch(struct per_session_data__sshd *pss, uint32_t chi)
+{
+       struct lws_ssh_channel *ch = pss->ch_list;
+
+       while (ch) {
+               if (ch->sender_ch == chi)
+                       return ch;
+               ch = ch->next;
+       }
+
+       return NULL;
+}
+#endif
+
+static void
+ssh_destroy_channel(struct per_session_data__sshd *pss,
+                   struct lws_ssh_channel *ch)
+{
+       lws_start_foreach_llp(struct lws_ssh_channel **, ppch, pss->ch_list) {
+               if (*ppch == ch) {
+                       lwsl_info("Deleting ch %p\n", ch);
+                       if (pss->vhd && pss->vhd->ops &&
+                           pss->vhd->ops->channel_destroy)
+                               pss->vhd->ops->channel_destroy(ch->priv);
+                       *ppch = ch->next;
+                       if (ch->sub)
+                               free(ch->sub);
+                       free(ch);
+
+                       return;
+               }
+       } lws_end_foreach_llp(ppch, next);
+
+       lwsl_notice("Failed to delete ch\n");
+}
+
+static void
+lws_ssh_exec_finish(void *finish_handle, int retcode)
+{
+       struct lws_ssh_channel *ch = (struct lws_ssh_channel *)finish_handle;
+       struct per_session_data__sshd *pss = ch->pss;
+
+       ch->retcode = retcode;
+       write_task(pss, ch, SSH_WT_EXIT_STATUS);
+       ch->scheduled_close = 1;
+       write_task(pss, ch, SSH_WT_CH_CLOSE);
+}
+
+static int
+lws_ssh_parse_plaintext(struct per_session_data__sshd *pss, uint8_t *p, size_t len)
+{
+       struct lws_gencrypto_keyelem e[LWS_GENCRYPTO_RSA_KEYEL_COUNT];
+       struct lws_genrsa_ctx ctx;
+       struct lws_ssh_channel *ch;
+       struct lws_subprotocol_scp *scp;
+       uint8_t *pp, *ps, hash[64], *otmp;
+       uint32_t m;
+       int n;
+
+       while (len --) {
+again:
+               switch(pss->parser_state) {
+               case SSH_INITIALIZE_TRANSIENT:
+                       pss->parser_state = SSHS_IDSTRING;
+                       pss->ctr = 0;
+                       pss->copy_to_I_C = 0;
+
+                       /* fallthru */
+               case SSHS_IDSTRING:
+                       if (*p == 0x0d) {
+                               pss->V_C[pss->npos] = '\0';
+                               pss->npos = 0;
+                               lwsl_info("peer id: %s\n", pss->V_C);
+                               p++;
+                               pss->parser_state = SSHS_IDSTRING_CR;
+                               break;
+                       }
+                       if (pss->npos < sizeof(pss->V_C) - 1)
+                               pss->V_C[pss->npos++] = *p;
+                       p++;
+                       break;
+
+               case SSHS_IDSTRING_CR:
+                       if (*p++ != 0x0a) {
+                               lwsl_notice("mangled id string\n");
+                               return 1;
+                       }
+                       pss->ssh_sequence_ctr_cts = 0;
+                       pss->parser_state = SSHS_MSG_LEN;
+                       break;
+
+               case SSHS_MSG_LEN:
+                       pss->msg_len = (pss->msg_len << 8) | *p++;
+                       if (++pss->ctr != 4)
+                               break;
+
+                       if (pss->active_keys_cts.valid) {
+                               uint8_t b[4];
+
+                               POKE_U32(b, pss->msg_len);
+                               pss->msg_len = lws_chachapoly_get_length(
+                                       &pss->active_keys_cts,
+                                       pss->ssh_sequence_ctr_cts, b);
+                       } else
+                               pss->ssh_sequence_ctr_cts++;
+
+                       lwsl_info("msg len %d\n", pss->msg_len);
+
+                       pss->parser_state = SSHS_MSG_PADDING;
+                       pss->ctr = 0;
+                       pss->pos = 4;
+                       if (pss->msg_len < 2 + 4) {
+                               lwsl_notice("illegal msg size\n");
+                               goto bail;
+                       }
+                       break;
+
+               case SSHS_MSG_PADDING:
+                       pss->msg_padding = *p++;
+                       pss->parser_state = SSHS_MSG_ID;
+                       break;
+
+               case SSHS_MSG_ID:
+                       pss->msg_id = *p++;
+                       pss->ctr = 0;
+                       switch (pss->msg_id) {
+                       case SSH_MSG_DISCONNECT:
+                               /*
+                                *       byte      SSH_MSG_DISCONNECT
+                                *       uint32    reason code
+                                *       string    description in ISO-10646
+                                *                 UTF-8 encoding [RFC3629]
+                                *       string    language tag [RFC3066]
+                                */
+                               lwsl_notice("SSH_MSG_DISCONNECT\n");
+                               state_get_u32(pss, SSHS_NVC_DISCONNECT_REASON);
+                               break;
+                       case SSH_MSG_IGNORE:
+                               lwsl_notice("SSH_MSG_IGNORE\n");
+                               break;
+                       case SSH_MSG_UNIMPLEMENTED:
+                               lwsl_notice("SSH_MSG_UNIMPLEMENTED\n");
+                               break;
+                       case SSH_MSG_DEBUG:
+                               lwsl_notice("SSH_MSG_DEBUG\n");
+                               break;
+                       case SSH_MSG_SERVICE_REQUEST:
+                               lwsl_info("SSH_MSG_SERVICE_REQUEST\n");
+                               /* payload is a string */
+                               state_get_string(pss, SSHS_DO_SERVICE_REQUEST);
+                               break;
+                       case SSH_MSG_SERVICE_ACCEPT:
+                               lwsl_notice("SSH_MSG_ACCEPT\n");
+                               break;
+
+                       case SSH_MSG_KEXINIT:
+                               if (pss->kex_state !=
+                                           KEX_STATE_EXPECTING_CLIENT_OFFER) {
+                                       pss->parser_state = SSH_KEX_STATE_SKIP;
+                                       break;
+                               }
+                               if (!pss->kex) {
+                                       lwsl_notice("%s: SSH_MSG_KEXINIT: NULL pss->kex\n", __func__);
+                                       goto bail;
+                               }
+                               pss->parser_state = SSH_KEX_STATE_COOKIE;
+                               pss->kex->I_C_payload_len = 0;
+                               pss->kex->I_C_alloc_len = pss->msg_len;
+                               pss->kex->I_C = sshd_zalloc(pss->kex->I_C_alloc_len);
+                               if (!pss->kex->I_C) {
+                                       lwsl_notice("OOM 3\n");
+                                       goto bail;
+                               }
+                               pss->kex->I_C[pss->kex->I_C_payload_len++] =
+                                       pss->msg_id;
+                               pss->copy_to_I_C = 1;
+                               break;
+                       case SSH_MSG_KEX_ECDH_INIT:
+                               pss->parser_state = SSH_KEX_STATE_ECDH_KEYLEN;
+                               break;
+
+                       case SSH_MSG_NEWKEYS:
+                               if (pss->kex_state !=
+                                               KEX_STATE_REPLIED_TO_OFFER &&
+                                   pss->kex_state !=
+                                               KEX_STATE_CRYPTO_INITIALIZED) {
+                                       lwsl_notice("unexpected newkeys\n");
+
+                                       goto bail;
+                               }
+                               /*
+                                * it means we should now use the keys we
+                                * agreed on
+                                */
+                               lwsl_info("Activating CTS keys\n");
+                               pss->active_keys_cts = pss->kex->keys_next_cts;
+                               if (lws_chacha_activate(&pss->active_keys_cts))
+                                       goto bail;
+
+                               pss->kex->newkeys |= 2;
+                               if (pss->kex->newkeys == 3)
+                                       lws_kex_destroy(pss);
+
+                               if (pss->msg_padding) {
+                                       pss->copy_to_I_C = 0;
+                                       pss->parser_state =
+                                               SSHS_MSG_EAT_PADDING;
+                                       break;
+                               } else {
+                                       pss->parser_state = SSHS_MSG_LEN;
+                               }
+                               break;
+
+                       case SSH_MSG_USERAUTH_REQUEST:
+                               /*
+                                *    byte      SSH_MSG_USERAUTH_REQUEST
+                                *    string    user name in UTF-8
+                                *              encoding [RFC3629]
+                                *    string    service name in US-ASCII
+                                *    string    "publickey"
+                                *    boolean   FALSE
+                                *    string    public key alg
+                                *    string    public key blob
+                                */
+                               lwsl_info("SSH_MSG_USERAUTH_REQUEST\n");
+                               if (pss->ua) {
+                                       lwsl_notice("pss->ua overwrite\n");
+
+                                       goto bail;
+                               }
+
+                               pss->ua = sshd_zalloc(sizeof(*pss->ua));
+                               if (!pss->ua)
+                                       goto bail;
+
+                               state_get_string_alloc(pss, SSHS_DO_UAR_SVC);
+                               /* username is destroyed with userauth struct */
+                               if (!pss->sent_banner) {
+                                       if (pss->vhd->ops->banner)
+                                               write_task(pss, NULL,
+                                                          SSH_WT_UA_BANNER);
+                                       pss->sent_banner = 1;
+                               }
+                                break;
+                       case SSH_MSG_USERAUTH_FAILURE:
+                               goto bail;
+                       case SSH_MSG_USERAUTH_SUCCESS:
+                               goto bail;
+                       case SSH_MSG_USERAUTH_BANNER:
+                               goto bail;
+
+                       case SSH_MSG_CHANNEL_OPEN:
+                               state_get_string(pss, SSHS_NVC_CHOPEN_TYPE);
+                               break;
+
+                       case SSH_MSG_CHANNEL_REQUEST:
+                               /* RFC4254
+                                *
+                                *  byte      SSH_MSG_CHANNEL_REQUEST
+                                *  uint32    recipient channel
+                                *  string    "pty-req"
+                                *  boolean   want_reply
+                                *  string    TERM environment variable value
+                                *                      (e.g., vt100)
+                                *  uint32    terminal width, characters
+                                *                      (e.g., 80)
+                                *  uint32    terminal height, rows (e.g., 24)
+                                *  uint32    terminal width, px (e.g., 640)
+                                *  uint32    terminal height, px (e.g., 480)
+                                *  string    encoded terminal modes
+                                */
+                               state_get_u32(pss, SSHS_NVC_CHRQ_RECIP);
+                               break;
+
+                       case SSH_MSG_CHANNEL_EOF:
+                               /* RFC4254
+                                * When a party will no longer send more data
+                                * to a channel, it SHOULD send
+                                * SSH_MSG_CHANNEL_EOF.
+                                *
+                                *  byte      SSH_MSG_CHANNEL_EOF
+                                *  uint32    recipient channel
+                                */
+                               state_get_u32(pss, SSHS_NVC_CH_EOF);
+                               break;
+
+                       case SSH_MSG_CHANNEL_CLOSE:
+                               /* RFC4254
+                                *
+                                *  byte      SSH_MSG_CHANNEL_CLOSE
+                                *  uint32    recipient channel
+                                *
+                                * This message does not consume window space
+                                * and can be sent even if no window space is
+                                * available.
+                                *
+                                * It is RECOMMENDED that all data sent before
+                                * this message be delivered to the actual
+                                * destination, if possible.
+                                */
+                               state_get_u32(pss, SSHS_NVC_CH_CLOSE);
+                               break;
+
+                       case SSH_MSG_CHANNEL_DATA:
+                               /* RFC4254
+                                *
+                                *      byte      SSH_MSG_CHANNEL_DATA
+                                *      uint32    recipient channel
+                                *      string    data
+                                */
+                               state_get_u32(pss, SSHS_NVC_CD_RECIP);
+                               break;
+
+                       case SSH_MSG_CHANNEL_WINDOW_ADJUST:
+                               /* RFC452
+                                *
+                                * byte      SSH_MSG_CHANNEL_WINDOW_ADJUST
+                                * uint32    recipient channel
+                                * uint32    bytes to add
+                                */
+                               if (!pss->ch_list)
+                                       goto bail;
+
+                               state_get_u32(pss, SSHS_NVC_WA_RECIP);
+                               break;
+                       default:
+                               lwsl_notice("unk msg_id %d\n", pss->msg_id);
+
+                               goto bail;
+                       }
+                       break;
+
+               case SSH_KEX_STATE_COOKIE:
+                       if (pss->msg_len < 16 + 1 + 1 + (10 * 4) + 5) {
+                               lwsl_notice("sanity: kex length failed\n");
+                               goto bail;
+                       }
+                       pss->kex->kex_cookie[pss->ctr++] = *p++;
+                       if (pss->ctr != sizeof(pss->kex->kex_cookie))
+                               break;
+                       pss->parser_state = SSH_KEX_NL_KEX_ALGS_LEN;
+                       pss->ctr = 0;
+                       break;
+               case SSH_KEX_NL_KEX_ALGS_LEN:
+               case SSH_KEX_NL_SHK_ALGS_LEN:
+               case SSH_KEX_NL_EACTS_ALGS_LEN:
+               case SSH_KEX_NL_EASTC_ALGS_LEN:
+               case SSH_KEX_NL_MACTS_ALGS_LEN:
+               case SSH_KEX_NL_MASTC_ALGS_LEN:
+               case SSH_KEX_NL_CACTS_ALGS_LEN:
+               case SSH_KEX_NL_CASTC_ALGS_LEN:
+               case SSH_KEX_NL_LCTS_ALGS_LEN:
+               case SSH_KEX_NL_LSTC_ALGS_LEN:
+               case SSH_KEX_STATE_ECDH_KEYLEN:
+
+                       pss->len = (pss->len << 8) | *p++;
+                       if (++pss->ctr != 4)
+                               break;
+
+                       switch (pss->parser_state) {
+                       case SSH_KEX_STATE_ECDH_KEYLEN:
+                               pss->parser_state = SSH_KEX_STATE_ECDH_Q_C;
+                               break;
+                       default:
+                               pss->parser_state++;
+                               if (pss->len == 0)
+                                       pss->parser_state++;
+                               break;
+                       }
+                       pss->ctr = 0;
+                       pss->npos = 0;
+                       if (pss->msg_len - pss->pos < pss->len) {
+                               lwsl_notice("sanity: length  %d - %d < %d\n",
+                                           pss->msg_len, pss->pos, pss->len);
+                               goto bail;
+                       }
+                       break;
+
+               case SSH_KEX_NL_KEX_ALGS:
+               case SSH_KEX_NL_SHK_ALGS:
+               case SSH_KEX_NL_EACTS_ALGS:
+               case SSH_KEX_NL_EASTC_ALGS:
+               case SSH_KEX_NL_MACTS_ALGS:
+               case SSH_KEX_NL_MASTC_ALGS:
+               case SSH_KEX_NL_CACTS_ALGS:
+               case SSH_KEX_NL_CASTC_ALGS:
+               case SSH_KEX_NL_LCTS_ALGS:
+               case SSH_KEX_NL_LSTC_ALGS:
+                       if (*p != ',') {
+                               if (pss->npos < sizeof(pss->name) - 1)
+                                       pss->name[pss->npos++] = *p;
+                       } else {
+                               pss->name[pss->npos] = '\0';
+                               pss->npos = 0;
+                               handle_name(pss);
+                       }
+                       p++;
+                       if (!--pss->len) {
+                               pss->name[pss->npos] = '\0';
+                               if (pss->npos)
+                                       handle_name(pss);
+                               pss->parser_state++;
+                               break;
+                       }
+                       break;
+
+               case SSH_KEX_FIRST_PKT:
+                       pss->first_coming = !!*p++;
+                       pss->parser_state = SSH_KEX_RESERVED;
+                       break;
+
+               case SSH_KEX_RESERVED:
+                       pss->len = (pss->len << 8) | *p++;
+                       if (++pss->ctr != 4)
+                               break;
+                       pss->ctr = 0;
+                       if (pss->msg_padding) {
+                               pss->copy_to_I_C = 0;
+                               pss->parser_state = SSHS_MSG_EAT_PADDING;
+                               break;
+                       }
+                       pss->parser_state = SSHS_MSG_LEN;
+                       break;
+
+               case SSH_KEX_STATE_ECDH_Q_C:
+                       if (pss->len != 32) {
+                               lwsl_notice("wrong key len\n");
+                               goto bail;
+                       }
+                       pss->kex->Q_C[pss->ctr++] = *p++;
+                       if (pss->ctr != 32)
+                               break;
+                       lwsl_info("Q_C parsed\n");
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               case SSH_KEX_STATE_SKIP:
+                       if (pss->pos - 4 < pss->msg_len) {
+                               p++;
+                               break;
+                       }
+                       lwsl_debug("skip done pos %d, msg_len %d len=%ld, \n",
+                                      pss->pos, pss->msg_len, (long)len);
+                       pss->parser_state = SSHS_MSG_LEN;
+                       pss->ctr = 0;
+                       break;
+
+               case SSHS_MSG_EAT_PADDING:
+                       p++;
+                       if (--pss->msg_padding)
+                               break;
+                       if (pss->msg_len + 4 != pss->pos) {
+                               lwsl_notice("sanity: kex end mismatch %d %d\n",
+                                               pss->pos, pss->msg_len);
+                               goto bail;
+                       }
+
+                       switch (pss->msg_id) {
+                       case SSH_MSG_KEX_ECDH_INIT:
+                               if (pss->kex->match_bitfield != 0xff) {
+                                       lwsl_notice("unable to negotiate\n");
+                                       goto bail;
+                               }
+                               if (kex_ecdh(pss, pss->kex->kex_r,
+                                            &pss->kex->kex_r_len)) {
+                                       lwsl_notice("hex_ecdh failed\n");
+                                       goto bail;
+                               }
+                               write_task(pss, NULL, SSH_WT_OFFER_REPLY);
+                               break;
+                       }
+
+                       pss->parser_state = SSHS_MSG_LEN;
+                       pss->ctr = 0;
+                       break;
+
+               case SSHS_GET_STRING_LEN:
+                       pss->npos = 0;
+                       pss->len = (pss->len << 8) | *p++;
+                        if (++pss->ctr != 4)
+                                break;
+                        pss->ctr = 0;
+                       pss->parser_state = SSHS_GET_STRING;
+                       break;
+
+               case SSHS_GET_STRING:
+                       if (pss->npos >= sizeof(pss->name) - 1) {
+                               lwsl_notice("non-alloc string too big\n");
+                               goto bail;
+                       }
+                       pss->name[pss->npos++] = *p++;
+                       if (pss->npos != pss->len)
+                               break;
+
+                       pss->name[pss->npos] = '\0';
+                       pss->parser_state = pss->state_after_string;
+                       goto again;
+
+               case SSHS_GET_STRING_LEN_ALLOC:
+                       pss->npos = 0;
+                       pss->len = (pss->len << 8) | *p++;
+                        if (++pss->ctr != 4)
+                                break;
+                        pss->ctr = 0;
+                       pss->last_alloc = sshd_zalloc(pss->len + 1);
+                       lwsl_debug("SSHS_GET_STRING_LEN_ALLOC: %p, state %d\n",
+                                  pss->last_alloc, pss->state_after_string);
+                       if (!pss->last_alloc) {
+                               lwsl_notice("alloc string too big\n");
+                               goto bail;
+                       }
+                       pss->parser_state = SSHS_GET_STRING_ALLOC;
+                       break;
+
+               case SSHS_GET_STRING_ALLOC:
+                       if (pss->npos >= pss->len)
+                               goto bail;
+                       pss->last_alloc[pss->npos++] = *p++;
+                       if (pss->npos != pss->len)
+                               break;
+                       pss->last_alloc[pss->npos] = '\0';
+                       pss->parser_state = pss->state_after_string;
+                       goto again;
+
+               /*
+                * User Authentication
+                */
+
+               case SSHS_DO_SERVICE_REQUEST:
+                       pss->okayed_userauth = 1;
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       /*
+                        * this only 'accepts' that we can negotiate auth for
+                        * this service, not accepts the auth
+                        */
+                       write_task(pss, NULL, SSH_WT_UA_ACCEPT);
+                       break;
+
+               case SSHS_DO_UAR_SVC:
+                       pss->ua->username = (char *)pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+                       state_get_string_alloc(pss, SSHS_DO_UAR_PUBLICKEY);
+                       /* destroyed with UA struct */
+                       break;
+
+               case SSHS_DO_UAR_PUBLICKEY:
+                       pss->ua->service = (char *)pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+
+                       /* Sect 5, RFC4252
+                        *
+                        * The 'user name' and 'service name' are repeated in
+                        * every new authentication attempt, and MAY change.
+                        *
+                        * The server implementation MUST carefully check them
+                        * in every message, and MUST flush any accumulated
+                        * authentication states if they change.  If it is
+                        * unable to flush an authentication state, it MUST
+                        * disconnect if the 'user name' or 'service name'
+                        * changes.
+                        */
+
+                       if (pss->seen_auth_req_before && (
+                            strcmp(pss->ua->username,
+                                   pss->last_auth_req_username) ||
+                            strcmp(pss->ua->service,
+                                   pss->last_auth_req_service))) {
+                               lwsl_notice("username / svc changed\n");
+
+                               goto bail;
+                       }
+
+                       pss->seen_auth_req_before = 1;
+                       lws_strncpy(pss->last_auth_req_username,
+                                   pss->ua->username,
+                                   sizeof(pss->last_auth_req_username));
+                       lws_strncpy(pss->last_auth_req_service,
+                                   pss->ua->service,
+                                   sizeof(pss->last_auth_req_service));
+
+                       if (strcmp(pss->ua->service, "ssh-connection"))
+                               goto ua_fail;
+                       state_get_string(pss, SSHS_NVC_DO_UAR_CHECK_PUBLICKEY);
+                       break;
+
+               case SSHS_NVC_DO_UAR_CHECK_PUBLICKEY:
+                       if (!strcmp(pss->name, "none")) {
+                               /* we must fail it */
+                               lwsl_info("got 'none' req, refusing\n");
+                               goto ua_fail;
+                       }
+
+                       if (strcmp(pss->name, "publickey")) {
+                               lwsl_notice("expected 'publickey' got '%s'\n",
+                                           pss->name);
+                               goto ua_fail;
+                       }
+                       pss->parser_state = SSHS_DO_UAR_SIG_PRESENT;
+                       break;
+
+               case SSHS_DO_UAR_SIG_PRESENT:
+                       lwsl_info("SSHS_DO_UAR_SIG_PRESENT\n");
+                       pss->ua->sig_present = *p++;
+                       state_get_string_alloc(pss, SSHS_NVC_DO_UAR_ALG);
+                       /* destroyed with UA struct */
+                       break;
+
+               case SSHS_NVC_DO_UAR_ALG:
+                       pss->ua->alg = (char *)pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+                       if (rsa_hash_alg_from_ident(pss->ua->alg) < 0) {
+                               lwsl_notice("unknown alg\n");
+                               goto ua_fail;
+                       }
+                       state_get_string_alloc(pss, SSHS_NVC_DO_UAR_PUBKEY_BLOB);
+                       /* destroyed with UA struct */
+                       break;
+
+               case SSHS_NVC_DO_UAR_PUBKEY_BLOB:
+                       pss->ua->pubkey = pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+                       pss->ua->pubkey_len = pss->npos;
+                       /*
+                        * RFC4253
+                        *
+                        * ssh-rsa
+                        *
+                        * The structure inside the blob is
+                        *
+                        *   mpint e
+                        *   mpint n
+                        *
+                        * Let's see if this key is authorized 
+                        */
+                       
+                       n = 1;
+                       if (pss->vhd->ops && pss->vhd->ops->is_pubkey_authorized)
+                               n = pss->vhd->ops->is_pubkey_authorized(
+                                       pss->ua->username, pss->ua->alg,
+                                       pss->ua->pubkey, pss->ua->pubkey_len);
+                       if (n) {
+                               lwsl_info("rejecting peer pubkey\n");
+                               goto ua_fail;
+                       }
+
+                       if (pss->ua->sig_present) {
+                               state_get_string_alloc(pss, SSHS_NVC_DO_UAR_SIG);
+                               /* destroyed with UA struct */
+                               break;
+                       }
+
+                       /*
+                        * This key is at least one we would be prepared
+                        * to accept if he really has it... since no sig
+                        * client should resend everything with a sig
+                        * appended.  OK it and delete this initial UA
+                        */
+                       write_task(pss, NULL, SSH_WT_UA_PK_OK);
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               case SSHS_NVC_DO_UAR_SIG:
+                       /*
+                        * Now the pubkey is coming with a sig
+                        */
+                       /* Sect 5.1 RFC4252
+                        *
+                        * SSH_MSG_USERAUTH_SUCCESS MUST be sent only once.
+                        * When SSH_MSG_USERAUTH_SUCCESS has been sent, any
+                        * further authentication requests received after that
+                        * SHOULD be silently ignored.
+                        */
+                       if (pss->ssh_auth_state == SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) {
+                               lwsl_info("Silently ignoring auth req after accepted\n");
+                               goto ua_fail_silently;
+                       }
+                       lwsl_info("SSHS_DO_UAR_SIG\n");
+                       pss->ua->sig = pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+                       pss->ua->sig_len = pss->npos;
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+
+                       /*
+                        *   RFC 4252 p9
+                        *
+                        *   The value of 'signature' is a signature with
+                        *   the private host key of the following data, in
+                        *   this order:
+                        *
+                        *      string    session identifier
+                        *      byte      SSH_MSG_USERAUTH_REQUEST
+                        *      string    user name
+                        *      string    service name
+                        *      string    "publickey"
+                        *      boolean   TRUE
+                        *      string    public key algorithm name
+                        *      string    public key to be used for auth
+                        *
+                        * We reproduce the signature plaintext and the
+                        * hash, and then decrypt the incoming signed block.
+                        * What comes out is some ASN1, in there is the
+                        * hash decrypted.  We find it and confirm it
+                        * matches the hash we computed ourselves.
+                        *
+                        * First step is generate the sig plaintext
+                        */
+                       n = 4 + 32 +
+                           1 +
+                           4 + (int)strlen(pss->ua->username) +
+                           4 + (int)strlen(pss->ua->service) +
+                           4 + 9 +
+                           1 +
+                           4 + (int)strlen(pss->ua->alg) +
+                           4 + (int)pss->ua->pubkey_len;
+
+                       ps = sshd_zalloc(n);
+                       if (!ps) {
+                               lwsl_notice("OOM 4\n");
+                               goto ua_fail;
+                       }
+
+                       pp = ps;
+                       lws_buf(&pp, pss->session_id, 32);
+                       *pp++ = SSH_MSG_USERAUTH_REQUEST;
+                       lws_cstr(&pp, pss->ua->username, 64);
+                       lws_cstr(&pp, pss->ua->service, 64);
+                       lws_cstr(&pp, "publickey", 64);
+                       *pp++ = 1;
+                       lws_cstr(&pp, pss->ua->alg, 64);
+                       lws_buf(&pp, pss->ua->pubkey, pss->ua->pubkey_len);
+
+                       /* Next hash the plaintext */
+
+                       if (lws_genhash_init(&pss->ua->hash_ctx,
+                               rsa_hash_alg_from_ident(pss->ua->alg))) {
+                               lwsl_notice("genhash init failed\n");
+                               free(ps);
+                               goto ua_fail;
+                       }
+
+                       if (lws_genhash_update(&pss->ua->hash_ctx, ps, pp - ps)) {
+                               lwsl_notice("genhash update failed\n");
+                               free(ps);
+                               goto ua_fail;
+                       }
+                       lws_genhash_destroy(&pss->ua->hash_ctx, hash);
+                       free(ps);
+
+                       /*
+                        * Prepare the RSA decryption context: load in
+                        * the E and N factors
+                        */
+
+                       memset(e, 0, sizeof(e));
+                       pp = pss->ua->pubkey;
+                       m = lws_g32(&pp);
+                       pp += m;
+                       m = lws_g32(&pp);
+                       e[LWS_GENCRYPTO_RSA_KEYEL_E].buf = pp;
+                       e[LWS_GENCRYPTO_RSA_KEYEL_E].len = m;
+                       pp += m;
+                       m = lws_g32(&pp);
+                       e[LWS_GENCRYPTO_RSA_KEYEL_N].buf = pp;
+                       e[LWS_GENCRYPTO_RSA_KEYEL_N].len = m;
+
+                       if (lws_genrsa_create(&ctx, e, pss->vhd->context,
+                                             LGRSAM_PKCS1_1_5,
+                                             LWS_GENHASH_TYPE_UNKNOWN))
+                               goto ua_fail;
+
+                       /*
+                        * point to the encrypted signature payload we
+                        * were sent
+                        */
+                       pp = pss->ua->sig;
+                       m = lws_g32(&pp);
+                       pp += m;
+                       m = lws_g32(&pp);
+
+                       /*
+                        * decrypt it, resulting in an error, or some ASN1
+                        * including the decrypted signature
+                        */
+                       otmp = sshd_zalloc(m);
+                       if (!otmp)
+                               /* ua_fail1 frees bn_e, bn_n and rsa */
+                               goto ua_fail1;
+
+                       n = lws_genrsa_public_decrypt(&ctx, pp, m, otmp, m);
+                       if (n > 0) {
+                               /* the decrypted sig is in ASN1 format */
+                               m = 0;
+                               while ((int)m < n) {
+                                       /* sig payload */
+                                       if (otmp[m] == 0x04 &&
+                                           otmp[m + 1] == lws_genhash_size(
+                                                 pss->ua->hash_ctx.type)) {
+                                               m = memcmp(&otmp[m + 2], hash,
+                                               lws_genhash_size(pss->ua->hash_ctx.type));
+                                               break;
+                                       }
+                                       /* go into these */
+                                       if (otmp[m] == 0x30) {
+                                               m += 2;
+                                               continue;
+                                       }
+                                       /* otherwise skip payloads */
+                                       m += otmp[m + 1] + 2;
+                               }
+                       }
+
+                       free(otmp);
+                       lws_genrsa_destroy(&ctx);
+
+                       /*
+                        * if no good, m is nonzero and inform peer
+                        */
+                       if (n <= 0) {
+                               lwsl_notice("hash sig verify fail: %d\n", m);
+                               goto ua_fail;
+                       }
+
+                       /* if it checks out, inform peer */
+
+                       lwsl_info("sig check OK\n");
+
+                       /* Sect 5.1 RFC4252
+                        *
+                        * SSH_MSG_USERAUTH_SUCCESS MUST be sent only once.
+                        * When SSH_MSG_USERAUTH_SUCCESS has been sent, any
+                        * further authentication requests received after that
+                        * SHOULD be silently ignored.
+                        */
+                       pss->ssh_auth_state = SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS;
+
+                       write_task(pss, NULL, SSH_WT_UA_SUCCESS);
+                       lws_ua_destroy(pss);
+                       break;
+
+                       /*
+                        * Channels
+                        */
+
+               case SSHS_GET_U32:
+                       pss->len = (pss->len << 8) | *p++;
+                        if (++pss->ctr != 4)
+                                break;
+                        pss->ctr = 0;
+                       pss->parser_state = pss->state_after_string;
+                       goto again;
+
+                       /*
+                        * Channel: Disconnect
+                        */
+
+               case SSHS_NVC_DISCONNECT_REASON:
+                       pss->disconnect_reason = pss->len;
+                       state_get_string_alloc(pss, SSHS_NVC_DISCONNECT_DESC);
+                       break;
+
+               case SSHS_NVC_DISCONNECT_DESC:
+                       pss->disconnect_desc = (char *)pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+                       state_get_string(pss, SSHS_NVC_DISCONNECT_LANG);
+                       break;
+
+               case SSHS_NVC_DISCONNECT_LANG:
+                       lwsl_notice("SSHS_NVC_DISCONNECT_LANG\n");
+                       if (pss->vhd->ops && pss->vhd->ops->disconnect_reason)
+                               pss->vhd->ops->disconnect_reason(
+                                       pss->disconnect_reason,
+                                       pss->disconnect_desc, pss->name);
+                       ssh_free_set_NULL(pss->last_alloc);
+                       break;
+
+                       /*
+                        * Channel: Open
+                        */
+
+               case SSHS_NVC_CHOPEN_TYPE:
+                       /* channel open */
+                       if (strcmp(pss->name, "session")) {
+                               lwsl_notice("Failing on not session\n");
+                               pss->reason = 3;
+                               goto ch_fail;
+                       }
+                       lwsl_info("SSHS_NVC_CHOPEN_TYPE: creating session\n");
+                       pss->ch_temp = sshd_zalloc(sizeof(*pss->ch_temp));
+                       if (!pss->ch_temp)
+                               return -1;
+
+                       pss->ch_temp->type = SSH_CH_TYPE_SESSION;
+                       pss->ch_temp->pss = pss;
+                       state_get_u32(pss, SSHS_NVC_CHOPEN_SENDER_CH);
+                       break;
+
+               case SSHS_NVC_CHOPEN_SENDER_CH:
+                       pss->ch_temp->sender_ch = pss->len;
+                       state_get_u32(pss, SSHS_NVC_CHOPEN_WINSIZE);
+                       break;
+               case SSHS_NVC_CHOPEN_WINSIZE:
+                       lwsl_info("Initial window set to %d\n", pss->len);
+                       pss->ch_temp->window = pss->len;
+                       state_get_u32(pss, SSHS_NVC_CHOPEN_PKTSIZE);
+                       break;
+               case SSHS_NVC_CHOPEN_PKTSIZE:
+                       pss->ch_temp->max_pkt = pss->len;
+                       pss->ch_temp->peer_window_est = LWS_SSH_INITIAL_WINDOW;
+                       pss->ch_temp->server_ch = pss->next_ch_num++;
+                       /*
+                        * add us to channel list... leave as ch_temp
+                        * as write task needs it and will NULL down
+                        */
+                       lwsl_info("creating new session ch\n");
+                       pss->ch_temp->next = pss->ch_list;
+                       pss->ch_list = pss->ch_temp;
+                       if (pss->vhd->ops && pss->vhd->ops->channel_create)
+                               pss->vhd->ops->channel_create(pss->wsi,
+                                               &pss->ch_temp->priv);
+                       write_task(pss, pss->ch_temp, SSH_WT_CH_OPEN_CONF);
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               /*
+                * SSH_MSG_CHANNEL_REQUEST
+                */
+
+               case SSHS_NVC_CHRQ_RECIP:
+                       pss->ch_recip = pss->len;
+                       state_get_string(pss, SSHS_NVC_CHRQ_TYPE);
+                       break;
+
+               case SSHS_NVC_CHRQ_TYPE:
+                       pss->parser_state = SSHS_CHRQ_WANT_REPLY;
+                       break;
+
+               case SSHS_CHRQ_WANT_REPLY:
+                       pss->rq_want_reply = *p++;
+                       lwsl_info("SSHS_CHRQ_WANT_REPLY: %s, wantrep: %d\n",
+                                       pss->name, pss->rq_want_reply);
+
+                       pss->ch_temp = ssh_get_server_ch(pss, pss->ch_recip);
+
+                       /* after this they differ by the request */
+
+                       /*
+                        * a PTY for a shell
+                        */
+                       if (!strcmp(pss->name, "pty-req")) {
+                               state_get_string(pss, SSHS_NVC_CHRQ_TERM);
+                               break;
+                       }
+                       /*
+                        * a shell
+                        */
+                       if (!strcmp(pss->name, "shell")) {
+                               pss->channel_doing_spawn = pss->ch_temp->server_ch;
+                               if (pss->vhd->ops && pss->vhd->ops->shell &&
+                                   !pss->vhd->ops->shell(pss->ch_temp->priv,
+                                                         pss->wsi,
+                                                lws_ssh_exec_finish, pss->ch_temp)) {
+
+                                       if (pss->rq_want_reply)
+                                               write_task_insert(pss, pss->ch_temp,
+                                                          SSH_WT_CHRQ_SUCC);
+                                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                                       break;
+                               }
+
+                               goto chrq_fail;
+                       }
+                       /*
+                        * env vars to be set in the shell
+                        */
+                       if (!strcmp(pss->name, "env")) {
+                               state_get_string(pss, SSHS_NVC_CHRQ_ENV_NAME);
+                               break;
+                       }
+
+                       /*
+                        * exec something
+                        */
+                       if (!strcmp(pss->name, "exec")) {
+                               state_get_string_alloc(pss, SSHS_NVC_CHRQ_EXEC_CMD);
+                               break;
+                       }
+
+                       /*
+                        * spawn a subsystem
+                        */
+                       if (!strcmp(pss->name, "subsystem")) {
+                               lwsl_notice("subsystem\n");
+                               state_get_string_alloc(pss,
+                                                      SSHS_NVC_CHRQ_SUBSYSTEM);
+                               break;
+                       }
+
+                       if (pss->rq_want_reply)
+                               goto chrq_fail;
+
+                       pss->parser_state = SSH_KEX_STATE_SKIP;
+                       break;
+
+               /* CHRQ pty-req */
+
+               case SSHS_NVC_CHRQ_TERM:
+                       memcpy(pss->args.pty.term, pss->name,
+                               sizeof(pss->args.pty.term) - 1);
+                       state_get_u32(pss, SSHS_NVC_CHRQ_TW);
+                       break;
+               case SSHS_NVC_CHRQ_TW:
+                       pss->args.pty.width_ch = pss->len;
+                       state_get_u32(pss, SSHS_NVC_CHRQ_TH);
+                       break;
+               case SSHS_NVC_CHRQ_TH:
+                       pss->args.pty.height_ch = pss->len;
+                       state_get_u32(pss, SSHS_NVC_CHRQ_TWP);
+                       break;
+               case SSHS_NVC_CHRQ_TWP:
+                       pss->args.pty.width_px = pss->len;
+                       state_get_u32(pss, SSHS_NVC_CHRQ_THP);
+                       break;
+               case SSHS_NVC_CHRQ_THP:
+                       pss->args.pty.height_px = pss->len;
+                       state_get_string_alloc(pss, SSHS_NVC_CHRQ_MODES);
+                       break;
+               case SSHS_NVC_CHRQ_MODES:
+                       /* modes is a stream of byte-pairs, not a string */
+                       pss->args.pty.modes = (char *)pss->last_alloc;
+                       pss->last_alloc = NULL; /* it was adopted */
+                       pss->args.pty.modes_len = pss->npos;
+                       n = 0;
+                       if (pss->vhd->ops && pss->vhd->ops->pty_req)
+                               n = pss->vhd->ops->pty_req(pss->ch_temp->priv,
+                                                       &pss->args.pty);
+                       ssh_free_set_NULL(pss->args.pty.modes);
+                       if (n)
+                               goto chrq_fail;
+                       if (pss->rq_want_reply)
+                               write_task(pss, pss->ch_temp, SSH_WT_CHRQ_SUCC);
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               /* CHRQ env */
+
+               case SSHS_NVC_CHRQ_ENV_NAME:
+                       strcpy(pss->args.aux, pss->name);
+                       state_get_string(pss, SSHS_NVC_CHRQ_ENV_VALUE);
+                       break;
+
+               case SSHS_NVC_CHRQ_ENV_VALUE:
+                       if (pss->vhd->ops && pss->vhd->ops->set_env)
+                               if (pss->vhd->ops->set_env(pss->ch_temp->priv,
+                                               pss->args.aux, pss->name))
+                                       goto chrq_fail;
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               /* CHRQ exec */
+
+               case SSHS_NVC_CHRQ_EXEC_CMD:
+                       /*
+                        * byte      SSH_MSG_CHANNEL_REQUEST
+                        * uint32    recipient channel
+                        * string    "exec"
+                        * boolean   want reply
+                        * string    command
+                        *
+                        * This message will request that the server start the
+                        * execution of the given command.  The 'command' string
+                        * may contain a path.  Normal precautions MUST be taken
+                        * to prevent the execution of unauthorized commands.
+                        *
+                        * scp sends "scp -t /path/..."
+                        */
+                       lwsl_info("exec cmd: %s %02X\n", pss->last_alloc, *p);
+
+                       pss->channel_doing_spawn = pss->ch_temp->server_ch;
+
+                       if (pss->vhd->ops && pss->vhd->ops->exec &&
+                           !pss->vhd->ops->exec(pss->ch_temp->priv, pss->wsi,
+                                                (const char *)pss->last_alloc,
+                                                lws_ssh_exec_finish, pss->ch_temp)) {
+                               ssh_free_set_NULL(pss->last_alloc);
+                               if (pss->rq_want_reply)
+                                       write_task_insert(pss, pss->ch_temp,
+                                                  SSH_WT_CHRQ_SUCC);
+
+                               pss->parser_state = SSHS_MSG_EAT_PADDING;
+                               break;
+                       }
+
+                       /*
+                        * even if he doesn't want to exec it, we know how to
+                        * fake scp
+                        */
+
+                       /* we only alloc "exec" of scp for scp destination */
+                       n = 1;
+                       if (pss->last_alloc[0] != 's' ||
+                           pss->last_alloc[1] != 'c' ||
+                           pss->last_alloc[2] != 'p' ||
+                           pss->last_alloc[3] != ' ')
+                               /* disallow it */
+                               n = 0;
+
+                       ssh_free_set_NULL(pss->last_alloc);
+                       if (!n)
+                               goto chrq_fail;
+
+                       /* our channel speaks SCP protocol now */
+
+                       scp = sshd_zalloc(sizeof(*scp));
+                       if (!scp)
+                               return -1;
+
+                       pss->ch_temp->type = SSH_CH_TYPE_SCP;
+                       pss->ch_temp->sub = (lws_subprotocol *)scp;
+
+                       scp->ips = SSHS_SCP_COLLECTSTR;
+
+                       if (pss->rq_want_reply)
+                               write_task(pss, pss->ch_temp, SSH_WT_CHRQ_SUCC);
+
+                       /* we start the scp protocol first by sending an ACK */
+                       write_task(pss, pss->ch_temp, SSH_WT_SCP_ACK_OKAY);
+
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               case SSHS_NVC_CHRQ_SUBSYSTEM:
+                       lwsl_notice("subsystem: %s", pss->last_alloc);
+                       n = 0;
+#if 0
+                       if (!strcmp(pss->name, "sftp")) {
+                               lwsl_notice("SFTP session\n");
+                               pss->ch_temp->type = SSH_CH_TYPE_SFTP;
+                               n = 1;
+                       }
+#endif
+                       ssh_free_set_NULL(pss->last_alloc);
+//                     if (!n)
+                               goto ch_fail;
+#if 0
+                       if (pss->rq_want_reply)
+                               write_task(pss, ssh_get_server_ch(pss,
+                                       pss->ch_recip), SSH_WT_CHRQ_SUCC);
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+#endif
+
+               /* SSH_MSG_CHANNEL_DATA */
+
+               case SSHS_NVC_CD_RECIP:
+                       pss->ch_recip = pss->len;
+
+                       ch = ssh_get_server_ch(pss, pss->ch_recip);
+                       ch->peer_window_est -= pss->msg_len;
+
+                       if (pss->msg_len < sizeof(pss->name))
+                               state_get_string(pss, SSHS_NVC_CD_DATA);
+                       else
+                               state_get_string_alloc(pss,
+                                       SSHS_NVC_CD_DATA_ALLOC);
+                       break;
+
+               case SSHS_NVC_CD_DATA_ALLOC:
+               case SSHS_NVC_CD_DATA:
+                       /*
+                        * Actual protocol incoming payload
+                        */
+                       if (pss->parser_state == SSHS_NVC_CD_DATA_ALLOC)
+                               pp = pss->last_alloc;
+                       else
+                               pp = (uint8_t *)pss->name;
+                       lwsl_info("SSHS_NVC_CD_DATA\n");
+
+                       ch = ssh_get_server_ch(pss, pss->ch_recip);
+                       switch (ch->type) {
+                       case SSH_CH_TYPE_SCP:
+                               scp = &ch->sub->scp;
+                               switch (scp->ips) {
+                               case SSHS_SCP_COLLECTSTR:
+                                       /* gather the ascii-coded headers */
+                                       for (n = 0; n < (int)pss->npos; n++)
+                                               lwsl_notice("0x%02X %c\n",
+                                                           pp[n], pp[n]);
+
+                                       /* Header triggers the transfer? */
+                                       if (pp[0] == 'C' && pp[pss->npos - 1] == '\x0a') {
+                                               while (*pp != ' ' && *pp != '\x0a')
+                                                       pp++;
+                                               if (*pp++ != ' ') {
+                                                       write_task(pss, ch,
+                                                          SSH_WT_SCP_ACK_ERROR);
+                                                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                                                       break;
+                                               }
+                                               scp->len = atoll((const char *)pp);
+                                               lwsl_notice("scp payload %llu expected\n",
+                                                           (unsigned long long)scp->len);
+                                               scp->ips = SSHS_SCP_PAYLOADIN;
+                                       }
+                                       /* ack it */
+                                       write_task(pss, pss->ch_temp,
+                                                  SSH_WT_SCP_ACK_OKAY);
+                                       break;
+                               case SSHS_SCP_PAYLOADIN:
+                                       /* the scp file payload */
+                                       if (pss->vhd->ops)
+                                               pss->vhd->ops->rx(ch->priv,
+                                                       pss->wsi, pp, pss->npos);
+                                       if (scp->len >= pss->npos)
+                                               scp->len -= pss->npos;
+                                       else
+                                               scp->len = 0;
+                                       if (!scp->len) {
+                                               lwsl_notice("scp txfer completed\n");
+                                               scp->ips = SSHS_SCP_COLLECTSTR;
+                                               break;
+                                       }
+                                       break;
+                               }
+                               break;
+                       default: /* scp payload */
+                               if (pss->vhd->ops)
+                                       pss->vhd->ops->rx(ch->priv, pss->wsi,
+                                                         pp, pss->npos);
+                               break;
+                       }
+                       if (pss->parser_state == SSHS_NVC_CD_DATA_ALLOC)
+                               ssh_free_set_NULL(pss->last_alloc);
+
+                       if (ch->peer_window_est < 32768) {
+                               write_task(pss, ch, SSH_WT_WINDOW_ADJUST);
+                               ch->peer_window_est += 32768;
+                               lwsl_info("extra peer WINDOW_ADJUST (~ %d)\n",
+                                           ch->peer_window_est);
+                       }
+
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               case SSHS_NVC_WA_RECIP:
+                       pss->ch_recip = pss->len;
+                       state_get_u32(pss, SSHS_NVC_WA_ADD);
+                       break;
+
+               case SSHS_NVC_WA_ADD:
+                       ch = ssh_get_server_ch(pss, pss->ch_recip);
+                       if (ch) {
+                               ch->window += pss->len;
+                               lwsl_notice("got additional window %d (now %d)\n",
+                                               pss->len, ch->window);
+                       }
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+                       /*
+                        *  channel close
+                        */
+
+               case SSHS_NVC_CH_EOF:
+                       /*
+                        * No explicit response is sent to this
+                        * message.  However, the application may send
+                        * EOF to whatever is at the other end of the
+                        * channel.  Note that the channel remains open
+                        * after this message, and more data may still
+                        * be sent in the other direction.  This message
+                        * does not consume window space and can be sent
+                        * even if no window space is available.
+                        */
+                       lwsl_notice("SSH_MSG_CHANNEL_EOF: %d\n", pss->ch_recip);
+                       ch = ssh_get_server_ch(pss, pss->ch_recip);
+                       if (!ch) {
+                               lwsl_notice("unknown ch %d\n", pss->ch_recip);
+                               return -1;
+                       }
+
+                       if (!ch->scheduled_close) {
+                               lwsl_notice("scheduling CLOSE\n");
+                               ch->scheduled_close = 1;
+                               write_task(pss, ch, SSH_WT_CH_CLOSE);
+                       }
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+                       break;
+
+               case SSHS_NVC_CH_CLOSE:
+                       /*
+                        * When either party wishes to terminate the
+                        * channel, it sends SSH_MSG_CHANNEL_CLOSE.
+                        * Upon receiving this message, a party MUST
+                        * send back an SSH_MSG_CHANNEL_CLOSE unless it
+                        * has already sent this message for the
+                        * channel.  The channel is considered closed
+                        * for a party when it has both sent and
+                        * received SSH_MSG_CHANNEL_CLOSE, and the
+                        * party may then reuse the channel number.
+                        * A party MAY send SSH_MSG_CHANNEL_CLOSE
+                        * without having sent or received
+                        * SSH_MSG_CHANNEL_EOF.
+                        */
+                       lwsl_notice("SSH_MSG_CHANNEL_CLOSE ch %d\n",
+                                   pss->ch_recip);
+                       ch = ssh_get_server_ch(pss, pss->ch_recip);
+                       if (!ch)
+                               goto bail;
+
+                       pss->parser_state = SSHS_MSG_EAT_PADDING;
+
+                       if (ch->sent_close) {
+                               /*
+                                * This is acking our sent close...
+                                * we can destroy the channel with no
+                                * further communication.
+                                */
+                               ssh_destroy_channel(pss, ch);
+                               break;
+                       }
+
+                       ch->received_close = 1;
+                       ch->scheduled_close = 1;
+                       write_task(pss, ch, SSH_WT_CH_CLOSE);
+                       break;
+
+               default:
+                       break;
+
+chrq_fail:
+                       lwsl_notice("chrq_fail\n");
+                       write_task(pss, pss->ch_temp, SSH_WT_CHRQ_FAILURE);
+                       pss->parser_state = SSH_KEX_STATE_SKIP;
+                       break;
+
+ch_fail:
+                       if (pss->ch_temp) {
+                               free(pss->ch_temp);
+                               pss->ch_temp = NULL;
+                       }
+                       write_task(pss, pss->ch_temp, SSH_WT_CH_FAILURE);
+                       pss->parser_state = SSH_KEX_STATE_SKIP;
+                       break;
+
+ua_fail1:
+                       lws_genrsa_destroy(&ctx);
+ua_fail:
+                       write_task(pss, NULL, SSH_WT_UA_FAILURE);
+ua_fail_silently:
+                       lws_ua_destroy(pss);
+                       /* Sect 4, RFC4252
+                        *
+                        * Additionally, the implementation SHOULD limit the
+                        * number of failed authentication attempts a client
+                        * may perform in a single session (the RECOMMENDED
+                        * limit is 20 attempts).  If the threshold is
+                        * exceeded, the server SHOULD disconnect.
+                        */
+                       if (pss->count_auth_attempts++ > 20)
+                               goto bail;
+
+                       pss->parser_state = SSH_KEX_STATE_SKIP;
+                       break;
+               }
+
+               pss->pos++;
+       }
+
+       return 0;       
+bail:
+       lws_kex_destroy(pss);
+       lws_ua_destroy(pss);
+
+       return SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
+}
+
+static int
+parse(struct per_session_data__sshd *pss, uint8_t *p, size_t len)
+{
+       while (len--) {
+
+               if (pss->copy_to_I_C && pss->kex->I_C_payload_len <
+                               pss->kex->I_C_alloc_len &&
+                               pss->parser_state != SSHS_MSG_EAT_PADDING)
+                       pss->kex->I_C[pss->kex->I_C_payload_len++] = *p;
+
+               if (pss->active_keys_cts.valid &&
+                   pss->parser_state == SSHS_MSG_LEN)
+                       /* take a copy for full decrypt */
+                       pss->packet_assembly[pss->pa_pos++] = *p;
+
+               if (pss->active_keys_cts.valid &&
+                   pss->parser_state == SSHS_MSG_PADDING &&
+                   pss->msg_len) {
+                       /* we are going to have to decrypt it */
+                       uint32_t cp, l = pss->msg_len + 4 +
+                               pss->active_keys_cts.MAC_length;
+                       uint8_t pt[2048];
+
+                       len++;
+                       cp = (uint32_t)len;
+
+                       if (cp > l - pss->pa_pos)
+                               cp = l - pss->pa_pos;
+
+                       if (cp > sizeof(pss->packet_assembly) -
+                                       pss->pa_pos) {
+                               lwsl_err("Packet is too big to decrypt\n");
+
+                               goto bail;
+                       }
+                       if (pss->msg_len < 2 + 4) {
+                               lwsl_err("packet too small\n");
+
+                               goto bail;
+                       }
+
+                       memcpy(&pss->packet_assembly[pss->pa_pos], p, cp);
+                       pss->pa_pos += cp;
+                       len -= cp;
+                       p += cp;
+
+                       if (pss->pa_pos != l)
+                               return 0;
+
+                       /* decrypt it */
+                       cp = lws_chacha_decrypt(&pss->active_keys_cts,
+                                               pss->ssh_sequence_ctr_cts++,
+                                               pss->packet_assembly,
+                                               pss->pa_pos, pt);
+                       if (cp) {
+                               lwsl_notice("Decryption failed: %d\n", cp);
+                               goto bail;
+                       }
+
+                       if (lws_ssh_parse_plaintext(pss, pt + 4, pss->msg_len))
+                               goto bail;
+
+                       pss->pa_pos = 0;
+                       pss->ctr = 0;
+                       continue;
+               }
+
+               if (lws_ssh_parse_plaintext(pss, p, 1))
+                       goto bail;
+
+               p++;
+       }
+
+       return 0;
+
+bail:
+       lws_kex_destroy(pss);
+       lws_ua_destroy(pss);
+
+       return SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
+}
+
+static uint32_t
+pad_and_encrypt(uint8_t *dest, void *ps, uint8_t *pp,
+               struct per_session_data__sshd *pss, int skip_pad)
+{
+       uint32_t n;
+
+       if (!skip_pad)
+               lws_pad_set_length(pss, ps, &pp, &pss->active_keys_stc);
+       n = lws_ptr_diff(pp, ps);
+
+       if (!pss->active_keys_stc.valid) {
+               memcpy(dest, ps, n);
+               return n;
+       }
+
+       lws_chacha_encrypt(&pss->active_keys_stc, pss->ssh_sequence_ctr_stc,
+                          ps, n, dest);
+       n += pss->active_keys_stc.MAC_length;
+
+       return n;
+}
+
+static int
+lws_callback_raw_sshd(struct lws *wsi, enum lws_callback_reasons reason,
+                     void *user, void *in, size_t len)
+{
+       struct per_session_data__sshd *pss =
+                       (struct per_session_data__sshd *)user, **p;
+       struct per_vhost_data__sshd *vhd = NULL;
+       uint8_t buf[LWS_PRE + 1024], *pp, *ps = &buf[LWS_PRE + 512], *ps1 = NULL;
+       const struct lws_protocol_vhost_options *pvo;
+       const struct lws_protocols *prot;
+       struct lws_ssh_channel *ch;
+       char lang[10];
+       int n, m, o;
+
+       /*
+        * Because we are an abstract protocol plugin, we will get called by
+        * wsi that actually bind to a plugin "on top of us" that calls thru
+        * to our callback.
+        *
+        * Under those circumstances, we can't simply get a pointer to our own
+        * protocol from the wsi.  If there's a pss already, we can get it from
+        * there, but the first time for each connection we have to look it up.
+        */
+       if (pss && pss->vhd)
+               vhd = (struct per_vhost_data__sshd *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                               pss->vhd->protocol);
+       else
+               if (lws_get_vhost(wsi))
+                       vhd = (struct per_vhost_data__sshd *)
+                               lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                               lws_vhost_name_to_protocol(
+                                       lws_get_vhost(wsi), "lws-ssh-base"));
+
+       switch ((int)reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                                                 lws_get_protocol(wsi),
+                                                 sizeof(struct per_vhost_data__sshd));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               pvo = (const struct lws_protocol_vhost_options *)in;
+               while (pvo) {
+                       /*
+                        * the user code passes the ops struct address to us
+                        * using a pvo (per-vhost option)
+                        */
+                       if (!strcmp(pvo->name, "ops"))
+                               vhd->ops = (const struct lws_ssh_ops *)pvo->value;
+
+                       /*
+                        * the user code is telling us to get the ops struct
+                        * from another protocol's protocol.user pointer
+                        */
+                       if (!strcmp(pvo->name, "ops-from")) {
+                               prot = lws_vhost_name_to_protocol(vhd->vhost,
+                                                                 pvo->value);
+                               if (prot)
+                                       vhd->ops = (const struct lws_ssh_ops *)prot->user;
+                               else
+                                       lwsl_err("%s: can't find protocol %s\n",
+                                                   __func__, pvo->value);
+                       }
+
+                       pvo = pvo->next;
+               }
+
+               if (!vhd->ops) {
+                       lwsl_err("ssh pvo \"ops\" is mandatory\n");
+                       return 1;
+               }
+               /*
+                * The user code ops api_version has to be current
+                */
+               if (vhd->ops->api_version != LWS_SSH_OPS_VERSION) {
+                       lwsl_err("FATAL ops is api_version v%d but code is v%d\n",
+                               vhd->ops->api_version, LWS_SSH_OPS_VERSION);
+                       return 1;
+               }
+               break;
+
+        case LWS_CALLBACK_RAW_ADOPT:
+               lwsl_info("LWS_CALLBACK_RAW_ADOPT\n");
+               if (!vhd)
+                       return -1;
+               pss->next = vhd->live_pss_list;
+               vhd->live_pss_list = pss;
+               pss->parser_state = SSH_INITIALIZE_TRANSIENT;
+               pss->wsi = wsi;
+               pss->vhd = vhd;
+               pss->kex_state = KEX_STATE_EXPECTING_CLIENT_OFFER;
+               pss->active_keys_cts.padding_alignment = 8;
+               pss->active_keys_stc.padding_alignment = 8;
+               if (lws_kex_create(pss))
+                       return -1;
+               write_task(pss, NULL, SSH_WT_VERSION);
+
+               /* sect 4  RFC4252
+                *
+                * The server SHOULD have a timeout for authentication and
+                * disconnect if the authentication has not been accepted
+                * within the timeout period.
+                *
+                * The RECOMMENDED timeout period is 10 minutes.
+                */
+               lws_set_timeout(wsi,
+                      SSH_PENDING_TIMEOUT_CONNECT_TO_SUCCESSFUL_AUTH, 10 * 60);
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               if (!pss)
+                       return -1;
+               lwsl_info("LWS_CALLBACK_RAW_CLOSE\n");
+               lws_kex_destroy(pss);
+               lws_ua_destroy(pss);
+
+               ssh_free_set_NULL(pss->last_alloc);
+
+               while (pss->ch_list)
+                       ssh_destroy_channel(pss, pss->ch_list);
+
+               lws_chacha_destroy(&pss->active_keys_cts);
+               lws_chacha_destroy(&pss->active_keys_stc);
+
+               p = &vhd->live_pss_list;
+
+               while (*p) {
+                       if ((*p) == pss) {
+                               *p = pss->next;
+                               continue;
+                       }
+                       p = &((*p)->next);
+               }
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               if (!pss)
+                       return -1;
+               if (parse(pss, in, len))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_RAW_WRITEABLE:
+               if (!pss)
+                       break;
+               n = 0;
+               o = pss->write_task[pss->wt_tail];
+               ch = pss->write_channel[pss->wt_tail];
+
+               if (pss->wt_head == pss->wt_tail)
+                       o = SSH_WT_NONE;
+
+               switch (o) {
+               case SSH_WT_VERSION:
+                       if (!pss->vhd)
+                               break;
+                       n = lws_snprintf((char *)buf + LWS_PRE,
+                                        sizeof(buf) - LWS_PRE - 1, "%s\r\n",
+                                        pss->vhd->ops->server_string);
+                       write_task(pss, NULL, SSH_WT_OFFER);
+                       break;
+
+               case SSH_WT_OFFER:
+                       if (!pss->vhd)
+                               break;
+                       m = 0;
+                       n = offer(pss, buf + LWS_PRE,
+                                 sizeof(buf) - LWS_PRE, 0, &m);
+                       if (n == 0) {
+                               lwsl_notice("Too small\n");
+
+                               return -1;
+                       }
+
+                       if (!pss->kex) {
+                               lwsl_notice("%s: SSH_WT_OFFER: pss->kex is NULL\n",
+                                           __func__);
+                               return -1;
+                       }
+
+                       /* we need a copy of it to generate the hash later */
+                       if (pss->kex->I_S)
+                               free(pss->kex->I_S);
+                       pss->kex->I_S = sshd_zalloc(m);
+                       if (!pss->kex->I_S) {
+                               lwsl_notice("OOM 5: %d\n", m);
+
+                               return -1;
+                       }
+                       /* without length + padcount part */
+                       memcpy(pss->kex->I_S, buf + LWS_PRE + 5, m);
+                       pss->kex->I_S_payload_len = m; /* without padding */
+                       break;
+
+               case SSH_WT_OFFER_REPLY:
+                       memcpy(ps, pss->kex->kex_r, pss->kex->kex_r_len);
+                       n = pad_and_encrypt(&buf[LWS_PRE], ps,
+                                           ps + pss->kex->kex_r_len, pss, 1);
+                       pss->kex_state = KEX_STATE_REPLIED_TO_OFFER;
+                       /* afterwards, must do newkeys */
+                       write_task(pss, NULL, SSH_WT_SEND_NEWKEYS);
+                       break;
+
+               case SSH_WT_SEND_NEWKEYS:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_NEWKEYS;
+                       goto pac;
+
+               case SSH_WT_UA_ACCEPT:
+                       /*
+                        *  If the server supports the service (and permits
+                        *  the client to use it), it MUST respond with the
+                        *  following:
+                        *
+                        *      byte      SSH_MSG_SERVICE_ACCEPT
+                        *      string    service name
+                        */
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_SERVICE_ACCEPT;
+                       lws_p32(pp, pss->npos);
+                       pp += 4;
+                       strcpy((char *)pp, pss->name);
+                       pp += pss->npos;
+                       goto pac;
+
+               case SSH_WT_UA_FAILURE:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_USERAUTH_FAILURE;
+                       lws_p32(pp, 9);
+                       pp += 4;
+                       strcpy((char *)pp, "publickey");
+                       pp += 9;
+                       *pp++ = 0;
+                       goto pac;
+
+               case SSH_WT_UA_BANNER:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_USERAUTH_BANNER;
+                       if (pss->vhd && pss->vhd->ops->banner)
+                               n = (int)pss->vhd->ops->banner((char *)&buf[650],
+                                                         150 - 1,
+                                                         lang, (int)sizeof(lang));
+                       lws_p32(pp, n);
+                       pp += 4;
+                       strcpy((char *)pp, (char *)&buf[650]);
+                       pp += n;
+                       if (lws_cstr(&pp, lang, sizeof(lang)))
+                               goto bail;
+                       goto pac;
+
+               case SSH_WT_UA_PK_OK:
+                       /*
+                        *  The server MUST respond to this message with
+                        *  either SSH_MSG_USERAUTH_FAILURE or with the
+                        *  following:
+                        *
+                        *    byte      SSH_MSG_USERAUTH_PK_OK
+                        *    string    public key alg name from the request
+                        *    string    public key blob from the request
+                        */
+                       n = 74 + pss->ua->pubkey_len;
+                       if (n > (int)sizeof(buf) - LWS_PRE) {
+                               lwsl_notice("pubkey too large\n");
+                               goto bail;
+                       }
+                       ps1 = sshd_zalloc(n);
+                       if (!ps1)
+                               goto bail;
+                       ps = ps1;
+                       pp = ps1 + 5;
+                       *pp++ = SSH_MSG_USERAUTH_PK_OK;
+                       if (lws_cstr(&pp, pss->ua->alg, 64)) {
+                               free(ps1);
+                               goto bail;
+                       }
+                       lws_p32(pp, pss->ua->pubkey_len);
+                       pp += 4;
+                       memcpy(pp, pss->ua->pubkey, pss->ua->pubkey_len);
+                       pp += pss->ua->pubkey_len;
+
+                       /* we no longer need the UA now we judged it */
+                       lws_ua_destroy(pss);
+
+                       goto pac;
+
+               case SSH_WT_UA_SUCCESS:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_USERAUTH_SUCCESS;
+                       /* end SSH_PENDING_TIMEOUT_CONNECT_TO_SUCCESSFUL_AUTH */
+                       lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+                       goto pac;
+
+               case SSH_WT_CH_OPEN_CONF:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
+                       lws_p32(pp, pss->ch_temp->server_ch);
+                       pp += 4;
+                       lws_p32(pp, pss->ch_temp->sender_ch);
+                       pp += 4;
+                       /* tx initial window size towards us */
+                       lws_p32(pp, LWS_SSH_INITIAL_WINDOW);
+                       pp += 4;
+                       /* maximum packet size towards us */
+                       lws_p32(pp, 800);
+                       pp += 4;
+                       lwsl_info("SSH_WT_CH_OPEN_CONF\n");
+                       /* it's on the linked-list */
+                       pss->ch_temp = NULL;
+                       goto pac;
+
+               case SSH_WT_CH_FAILURE:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_OPEN_FAILURE;
+                       lws_p32(pp, ch->server_ch);
+                       pp += 4;
+                       lws_p32(pp, ch->sender_ch);
+                       pp += 4;
+                       lws_cstr(&pp, "reason", 64);
+                       lws_cstr(&pp, "en/US", 64);
+                       lwsl_info("SSH_WT_CH_FAILURE\n");
+                       goto pac;
+
+               case SSH_WT_CHRQ_SUCC:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_SUCCESS;
+                       lws_p32(pp, ch->server_ch);
+                       lwsl_info("SSH_WT_CHRQ_SUCC\n");
+                       pp += 4;
+                       goto pac;
+
+               case SSH_WT_CHRQ_FAILURE:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_FAILURE;
+                       lws_p32(pp, ch->server_ch);
+                       pp += 4;
+                       lwsl_info("SSH_WT_CHRQ_FAILURE\n");
+                       goto pac;
+
+               case SSH_WT_CH_CLOSE:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_CLOSE;
+                       lws_p32(pp, ch->server_ch);
+                       lwsl_info("SSH_WT_CH_CLOSE\n");
+                       pp += 4;
+                       goto pac;
+
+               case SSH_WT_CH_EOF:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_EOF;
+                       lws_p32(pp, ch->server_ch);
+                       lwsl_info("SSH_WT_CH_EOF\n");
+                       pp += 4;
+                       goto pac;
+
+               case SSH_WT_SCP_ACK_ERROR:
+               case SSH_WT_SCP_ACK_OKAY:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_DATA;
+                       /* ps + 6 */
+                       lws_p32(pp, ch->sender_ch);
+                       pp += 4;
+                       lws_p32(pp, 1);
+                       pp += 4;
+                       if (o == SSH_WT_SCP_ACK_ERROR)
+                               *pp++ = 2;
+                       else
+                               *pp++ = 0;
+                       lwsl_info("SSH_WT_SCP_ACK_OKAY\n");
+                       goto pac;
+
+               case SSH_WT_WINDOW_ADJUST:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_WINDOW_ADJUST;
+                       /* ps + 6 */
+                       lws_p32(pp, ch->sender_ch);
+                       pp += 4;
+                       lws_p32(pp, 32768);
+                       pp += 4;
+                       lwsl_info("send SSH_MSG_CHANNEL_WINDOW_ADJUST\n");
+                       goto pac;
+
+               case SSH_WT_EXIT_STATUS:
+                       pp = ps + 5;
+                       *pp++ = SSH_MSG_CHANNEL_REQUEST;
+                       lws_p32(pp, ch->sender_ch);
+                       pp += 4;
+                       lws_p32(pp, 11);
+                       pp += 4;
+                       strcpy((char *)pp, "exit-status");
+                       pp += 11;
+                       *pp++ = 0;
+                       lws_p32(pp, ch->retcode);
+                       pp += 4;
+                       lwsl_info("send SSH_MSG_CHANNEL_EXIT_STATUS\n");
+                       goto pac;
+
+               case SSH_WT_NONE:
+               default:
+                       /* sending payload */
+
+                       ch = ssh_get_server_ch(pss, 0);
+                       /* have a channel up to send on? */
+                       if (!ch)
+                               break;
+
+                       if (!pss->vhd || !pss->vhd->ops)
+                               break;
+                       n = pss->vhd->ops->tx_waiting(ch->priv);
+                       if (n < 0)
+                               return -1;
+                       if (!n)
+                               /* nothing to send */
+                               break;
+
+                       if (n == (LWS_STDOUT | LWS_STDERR)) {
+                               /* pick one using round-robin */
+                               if (pss->serviced_stderr_last)
+                                       n = LWS_STDOUT;
+                               else
+                                       n = LWS_STDERR;
+                       }
+
+                       pss->serviced_stderr_last = !!(n & LWS_STDERR);
+
+                       /* stdout or stderr */
+                       pp = ps + 5;
+                       if (n == LWS_STDOUT)
+                               *pp++ = SSH_MSG_CHANNEL_DATA;
+                       else
+                               *pp++ = SSH_MSG_CHANNEL_EXTENDED_DATA;
+                       /* ps + 6 */
+                       lws_p32(pp, pss->ch_list->server_ch);
+                       m = 14;
+                       if (n == LWS_STDERR) {
+                               pp += 4;
+                               /* data type code... 1 for stderr payload */
+                               lws_p32(pp, SSH_EXTENDED_DATA_STDERR);
+                               m = 18;
+                       }
+                       /* also skip another strlen u32 at + 10 / +14 */
+                       pp += 8;
+                       /* ps + 14 / + 18 */
+
+                       pp += pss->vhd->ops->tx(ch->priv, n, pp,
+                                               &buf[sizeof(buf) - 1] - pp);
+
+                       lws_p32(ps + m - 4, lws_ptr_diff(pp, (ps + m)));
+
+                       if (pss->vhd->ops->tx_waiting(ch->priv) > 0)
+                               lws_callback_on_writable(wsi);
+
+                       ch->window -= lws_ptr_diff(pp, ps) - m;
+                       //lwsl_debug("our send window: %d\n", ch->window);
+
+                       /* fallthru */
+pac:
+                       if (!pss->vhd)
+                               break;
+                       n = pad_and_encrypt(&buf[LWS_PRE], ps, pp, pss, 0);
+                       break;
+
+bail:
+                       lws_ua_destroy(pss);
+                       lws_kex_destroy(pss);
+
+                       return 1;
+
+               }
+
+               if (n > 0) {
+                       m = lws_write(wsi, (unsigned char *)buf + LWS_PRE, n,
+                                     LWS_WRITE_HTTP);
+
+                       switch(o) {
+                       case SSH_WT_SEND_NEWKEYS:
+                               lwsl_info("Activating STC keys\n");
+                               pss->active_keys_stc = pss->kex->keys_next_stc;
+                               lws_chacha_activate(&pss->active_keys_stc);
+                               pss->kex_state = KEX_STATE_CRYPTO_INITIALIZED;
+                               pss->kex->newkeys |= 1;
+                               if (pss->kex->newkeys == 3)
+                                       lws_kex_destroy(pss);
+                               break;
+                       case SSH_WT_UA_PK_OK:
+                               free(ps1);
+                               break;
+                       case SSH_WT_CH_CLOSE:
+                               if (ch->received_close) {
+                                       /*
+                                        * We are sending this at the behest of
+                                        * the remote peer...
+                                        * we can destroy the channel with no
+                                        * further communication.
+                                        */
+                                       ssh_destroy_channel(pss, ch);
+                                       break;
+                               }
+                               ch->sent_close = 1;
+                               break;
+                       }
+                       if (m < 0) {
+                               lwsl_err("ERR %d from write\n", m);
+                               goto bail;
+                       }
+
+                       if (o != SSH_WT_VERSION)
+                               pss->ssh_sequence_ctr_stc++;
+
+                       if (o != SSH_WT_NONE)
+                               pss->wt_tail =
+                                       (pss->wt_tail + 1) & 7;
+               } else
+                       if (o == SSH_WT_UA_PK_OK) /* free it either way */
+                               free(ps1);
+
+               ch = ssh_get_server_ch(pss, 0);
+
+               if (pss->wt_head != pss->wt_tail ||
+                   (ch && ch->priv && pss->vhd &&
+                    pss->vhd->ops->tx_waiting(ch->priv)))
+                      lws_callback_on_writable(wsi);
+
+               break;
+
+       case LWS_CALLBACK_SSH_UART_SET_RXFLOW:
+               /*
+                * this is sent to set rxflow state on any connections that
+                * sink on a particular sink.  The sink index affected is in len
+                *
+                * More than one protocol may sink to the same uart, and the
+                * protocol may select the sink itself, eg, in the URL used
+                * to set up the connection.
+                */
+               lwsl_notice("sshd LWS_CALLBACK_SSH_UART_SET_RXFLOW: wsi %p, %d\n",
+                               wsi, (int)len & 1);
+               lws_rx_flow_control(wsi, len & 1);
+               break;
+
+       case LWS_CALLBACK_CGI:
+               if (!pss)
+                       break;
+               if (pss->vhd && pss->vhd->ops &&
+                   pss->vhd->ops->child_process_io &&
+                   pss->vhd->ops->child_process_io(pss->ch_temp->priv,
+                                       pss->wsi, (struct lws_cgi_args *)in))
+                       return -1;
+               break;
+
+       case LWS_CALLBACK_CGI_PROCESS_ATTACH:
+               if (!pss)
+                       break;
+               ch = ssh_get_server_ch(pss, pss->channel_doing_spawn);
+               if (ch) {
+                       ch->spawn_pid = (int)len; /* child process PID */
+                       lwsl_notice("associated PID %d to ch %d\n", (int)len,
+                                   pss->channel_doing_spawn);
+               }
+               break;
+
+       case LWS_CALLBACK_CGI_TERMINATED:
+               if (!pss)
+                       break;
+               if (pss->vhd && pss->vhd->ops &&
+                   pss->vhd->ops->child_process_terminated)
+                   pss->vhd->ops->child_process_terminated(pss->ch_temp->priv,
+                                                           pss->wsi);
+               /*
+                * we have the child PID in len... we need to match it to a
+                * channel that is on the wsi
+                */
+               ch = pss->ch_list;
+
+               while (ch) {
+                       if (ch->spawn_pid == len) {
+                               lwsl_notice("starting close of ch with PID %d\n",
+                                           (int)len);
+                               ch->scheduled_close = 1;
+                               write_task(pss, ch, SSH_WT_CH_CLOSE);
+                               break;
+                       }
+                       ch = ch->next;
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+#define LWS_PLUGIN_PROTOCOL_LWS_RAW_SSHD { \
+               "lws-ssh-base", \
+               lws_callback_raw_sshd,  \
+               sizeof(struct per_session_data__sshd),  \
+               1024, 0, NULL, 900      \
+       }
+
+LWS_VISIBLE const struct lws_protocols protocols_sshd[] = {
+       LWS_PLUGIN_PROTOCOL_LWS_RAW_SSHD,
+       { NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */
+};
+
+#if !defined (LWS_PLUGIN_STATIC)
+
+LWS_VISIBLE int
+init_protocol_lws_ssh_base(struct lws_context *context,
+                            struct lws_plugin_capability *c)
+{
+       if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+               lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+                        c->api_magic);
+               return 1;
+       }
+
+       c->protocols = protocols_sshd;
+       c->count_protocols = LWS_ARRAY_SIZE(protocols_sshd);
+       c->extensions = NULL;
+       c->count_extensions = 0;
+
+       return 0;
+}
+
+LWS_VISIBLE int
+destroy_protocol_lws_ssh_base(struct lws_context *context)
+{
+       return 0;
+}
+#endif
diff --git a/plugins/ssh-base/telnet.c b/plugins/ssh-base/telnet.c
new file mode 100644 (file)
index 0000000..9d908fa
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * libwebsockets - lws-plugin-ssh-base
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include "libwebsockets.h"
+#include "lws-ssh.h"
+
+#include <string.h>
+
+struct per_vhost_data__telnet {
+       struct lws_context *context;
+       struct lws_vhost *vhost;
+       const struct lws_protocols *protocol;
+       struct per_session_data__telnet *live_pss_list;
+       const struct lws_ssh_ops *ops;
+};
+
+struct per_session_data__telnet {
+       struct per_session_data__telnet *next;
+       struct per_vhost_data__telnet *vhd;
+       uint32_t rx_tail;
+       void *priv;
+
+       uint32_t initial:1;
+
+       char state;
+       uint8_t cmd;
+};
+
+enum {
+       LTS_BINARY_XMIT,
+       LTS_ECHO,
+       LTS_SUPPRESS_GA,
+
+
+       LTSC_SUBOPT_END         = 240,
+       LTSC_BREAK              = 243,
+       LTSC_SUBOPT_START       = 250,
+       LTSC_WILL               = 251,
+       LTSC_WONT,
+       LTSC_DO,
+       LTSC_DONT,
+       LTSC_IAC,
+
+       LTST_WAIT_IAC           = 0,
+       LTST_GOT_IAC,
+       LTST_WAIT_OPT,
+};
+
+static int
+telnet_ld(struct per_session_data__telnet *pss, uint8_t c)
+{
+       switch (pss->state) {
+       case LTST_WAIT_IAC:
+               if (c == LTSC_IAC) {
+                       pss->state = LTST_GOT_IAC;
+                       return 0;
+               }
+               return 1;
+
+       case LTST_GOT_IAC:
+               pss->state = LTST_WAIT_IAC;
+
+               switch (c) {
+               case LTSC_BREAK:
+                       return 0;
+               case LTSC_WILL:
+               case LTSC_WONT:
+               case LTSC_DO:
+               case LTSC_DONT:
+                       pss->cmd = c;
+                       pss->state = LTST_WAIT_OPT;
+                       return 0;
+               case LTSC_IAC:
+                       return 1; /* double IAC */
+               }
+               return 0; /* ignore unknown */
+
+       case LTST_WAIT_OPT:
+               lwsl_notice(" tld: cmd %d: opt %d\n", pss->cmd, c);
+               pss->state = LTST_WAIT_IAC;
+               return 0;       
+       }
+
+       return 0;
+}
+
+static uint8_t init[] = {
+       LTSC_IAC, LTSC_WILL, 3,
+       LTSC_IAC, LTSC_WILL, 1,
+       LTSC_IAC, LTSC_DONT, 1,
+       LTSC_IAC, LTSC_DO,   0
+};
+
+static int
+lws_callback_raw_telnet(struct lws *wsi, enum lws_callback_reasons reason,
+                       void *user, void *in, size_t len)
+{
+       struct per_session_data__telnet *pss =
+                       (struct per_session_data__telnet *)user, **p;
+       struct per_vhost_data__telnet *vhd =
+                       (struct per_vhost_data__telnet *)
+                       lws_protocol_vh_priv_get(lws_get_vhost(wsi),
+                                       lws_get_protocol(wsi));
+       const struct lws_protocol_vhost_options *pvo =
+                       (const struct lws_protocol_vhost_options *)in;
+       int n, m;
+       uint8_t buf[LWS_PRE + 800], *pu = in;
+
+       switch ((int)reason) {
+       case LWS_CALLBACK_PROTOCOL_INIT:
+               vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
+                               lws_get_protocol(wsi),
+                               sizeof(struct per_vhost_data__telnet));
+               vhd->context = lws_get_context(wsi);
+               vhd->protocol = lws_get_protocol(wsi);
+               vhd->vhost = lws_get_vhost(wsi);
+
+               while (pvo) {
+                       if (!strcmp(pvo->name, "ops"))
+                               vhd->ops = (const struct lws_ssh_ops *)pvo->value;
+
+                       pvo = pvo->next;
+               }
+
+               if (!vhd->ops) {
+                       lwsl_err("telnet pvo \"ops\" is mandatory\n");
+                       return -1;
+               }
+               break;
+
+        case LWS_CALLBACK_RAW_ADOPT:
+               pss->next = vhd->live_pss_list;
+               vhd->live_pss_list = pss;
+               pss->vhd = vhd;
+               pss->state = LTST_WAIT_IAC;
+               pss->initial = 0;
+               if (vhd->ops->channel_create)
+                       vhd->ops->channel_create(wsi, &pss->priv);
+               lws_callback_on_writable(wsi);
+                break;
+
+       case LWS_CALLBACK_RAW_CLOSE:
+               p = &vhd->live_pss_list;
+
+               while (*p) {
+                       if ((*p) == pss) {
+                               if (vhd->ops->channel_destroy)
+                                       vhd->ops->channel_destroy(pss->priv);
+                               *p = pss->next;
+                               continue;
+                       }
+                       p = &((*p)->next);
+               }
+               break;
+
+       case LWS_CALLBACK_RAW_RX:
+               n = 0;
+
+               /* this stuff is coming in telnet line discipline, we
+                * have to strip IACs and process IAC repeats */
+
+               while (len--) {
+                       if (telnet_ld(pss, *pu))
+                               buf[n++] = *pu++;
+                       else
+                               pu++;
+
+                       if (n > 100 || !len)
+                               pss->vhd->ops->rx(pss->priv, wsi, buf, n);
+               }
+               break;
+
+        case LWS_CALLBACK_RAW_WRITEABLE:
+               n = 0;
+               if (!pss->initial) {
+                       memcpy(buf + LWS_PRE, init, sizeof(init));
+
+                       n = sizeof(init);
+                       pss->initial = 1;
+               } else {
+                       /* bring any waiting tx into second half of buffer
+                        * restrict how much we can send to 1/4 of the buffer,
+                        * because we have to apply telnet line discipline...
+                        * in the worst case of all 0xff, doubling the size
+                        */
+                       pu = buf + LWS_PRE + 400;
+                       m = (int)pss->vhd->ops->tx(pss->priv, LWS_STDOUT, pu,
+                                       ((int)sizeof(buf) - LWS_PRE - n - 401) / 2);
+
+                       /*
+                        * apply telnet line discipline and copy into place
+                        * in output buffer
+                        */
+                       while (m--) {
+                               if (*pu == 0xff)
+                                       buf[LWS_PRE + n++] = 0xff;
+                               buf[LWS_PRE + n++] = *pu++;
+                       }
+               }
+               if (n > 0) {
+                       m = lws_write(wsi, (unsigned char *)buf + LWS_PRE, n,
+                                     LWS_WRITE_HTTP);
+                       if (m < 0) {
+                               lwsl_err("ERROR %d writing to di socket\n", m);
+                               return -1;
+                       }
+               }
+
+               if (vhd->ops->tx_waiting(&pss->priv))
+                      lws_callback_on_writable(wsi);
+               break;
+
+        case LWS_CALLBACK_SSH_UART_SET_RXFLOW:
+               /*
+                * this is sent to set rxflow state on any connections that
+                * sink on a particular uart.  The uart index affected is in len
+                *
+                * More than one protocol may sink to the same uart, and the
+                * protocol may select the uart itself, eg, in the URL used
+                * to set up the connection.
+                */
+               lws_rx_flow_control(wsi, len & 1);
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+const struct lws_protocols protocols_telnet[] = {
+       {
+               "lws-telnetd-base",
+               lws_callback_raw_telnet,
+               sizeof(struct per_session_data__telnet),
+               1024, 0, NULL, 900
+       },
+       { NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */
+};
+
+
diff --git a/release-checklist b/release-checklist
deleted file mode 100644 (file)
index 69f04a8..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-Release Checklist
------------------
-
-0) QA
-
- a) ab
-
-   $ ab -n 100000 -c 200 http://localhost:7681/
-
- b) coverity
-   $ ../make-coverity-tarball.sh
-   https://scan.coverity.com/projects/warmcat-libwebsockets
-
- c) test servers + client + browser
- d) valgrind test servers + client + browser
-
- e) attack.sh
-     $ ./test-server/attack.sh
-
- f) Autobahn
-
-     $ wstest -m fuzzingserver &
-     $ ./autobahn-test.sh
-
-     Force update by browser using agent "libwebsockets"
-     http://localhost:8080/test_browser.html
-     
-     rsync -av ./reports/* root@warmcat.com:/var/www/libwebsockets.org
-
-1) api
-
-     $ cp build/doc/* .
-
-2) soname bump?
-
- a) We need one if we added / changed / removed apis
-
-  CMakeLists.txt
-
-   set(SOVERSION "6")
-
-  libwebsockets.spec
-
-  -/%{_libdir}/libwebsockets.so.6
-  +/%{_libdir}/libwebsockets.so.7
-
-3) changelog
-
- a) Add next version tag header.
-
- b) Classify as
-
-    - MINOR bug fixes
-    - MAJOR bug fixes
-    - SECURITY fixes
-
-4) main version bump
-
-  CMakeLists.txt
-
-   set(CPACK_PACKAGE_VERSION_MAJOR "1")
-   set(CPACK_PACKAGE_VERSION_MINOR "6")
-   set(CPACK_PACKAGE_VERSION_PATCH "0")
-
-5) specfile
-
- a) rpm version bump to match CMake one
-
-  libwebsockets.spec
-
-   Version: 1.6.0
-
- b) Summarize changelog
-
-  libwebsockets.spec
-
-%changelog
-* Sun Jan 17 2016 Andrew Cooks <acooks@linux.com> 1.6.4-1
-- Bump version to 1.6.4
-- MINOR fix xyz
-
-6) update api docs
-
- $ cmake ..
- $ cp doc/* ..
-
-7) signed tag
-
-  git tag -s vX.Y[.Z]
-
-8) git
-
- a) push
- b) final CI check, if fail delete tag, kill pushed tags, restart flow
-
-8) website
-
- a) update latest tag for release branch
diff --git a/scripts/FindLibWebSockets.cmake b/scripts/FindLibWebSockets.cmake
deleted file mode 100644 (file)
index e7d2839..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-# This module tries to find libWebsockets library and include files
-#
-# LIBWEBSOCKETS_INCLUDE_DIR, path where to find libwebsockets.h
-# LIBWEBSOCKETS_LIBRARY_DIR, path where to find libwebsockets.so
-# LIBWEBSOCKETS_LIBRARIES, the library to link against
-# LIBWEBSOCKETS_FOUND, If false, do not try to use libWebSockets
-#
-# This currently works probably only for Linux
-
-FIND_PATH ( LIBWEBSOCKETS_INCLUDE_DIR libwebsockets.h
-    /usr/local/include
-    /usr/include
-)
-
-FIND_LIBRARY ( LIBWEBSOCKETS_LIBRARIES websockets
-    /usr/local/lib
-    /usr/lib
-)
-
-GET_FILENAME_COMPONENT( LIBWEBSOCKETS_LIBRARY_DIR ${LIBWEBSOCKETS_LIBRARIES} PATH )
-
-SET ( LIBWEBSOCKETS_FOUND "NO" )
-IF ( LIBWEBSOCKETS_INCLUDE_DIR )
-    IF ( LIBWEBSOCKETS_LIBRARIES )
-        SET ( LIBWEBSOCKETS_FOUND "YES" )
-    ENDIF ( LIBWEBSOCKETS_LIBRARIES )
-ENDIF ( LIBWEBSOCKETS_INCLUDE_DIR )
-
-MARK_AS_ADVANCED(
-    LIBWEBSOCKETS_LIBRARY_DIR
-    LIBWEBSOCKETS_INCLUDE_DIR
-    LIBWEBSOCKETS_LIBRARIES
-)
similarity index 61%
rename from test-server/attack.sh
rename to scripts/attack.sh
index cb63db4..3f3a67a 100755 (executable)
@@ -2,6 +2,17 @@
 #
 # attack the test server and try to make it fall over
 #
+# Requires the library to have been built with
+#
+# cmake .. -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_MINIMAL_EXAMPLES=1
+#
+# run it from the build dir
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: lws attack.sh"
+echo
+
 SERVER=127.0.0.1
 PORT=7681
 LOG=/tmp/lwslog
@@ -9,6 +20,11 @@ LOG=/tmp/lwslog
 A=`which libwebsockets-test-server`
 INSTALLED=`dirname $A`
 
+SHAREDIR=$INSTALLED/../share/libwebsockets-test-server
+CORPUS=$SHAREDIR/test.html
+
+LWS_NC=./bin/lws-minimal-raw-netcat
+
 CPID=
 LEN=0
 
@@ -18,22 +34,31 @@ function check {
                echo "(killed it) *******"
                exit 1
        fi
-       dd if=$LOG bs=1 skip=$LEN 2>/dev/null
+       #dd if=$LOG bs=1 skip=$LEN 2>/dev/null
 
        if [ "$1" = "default" ] ; then
-               diff /tmp/lwscap $INSTALLED/../share/libwebsockets-test-server/test.html > /dev/null
+               diff /tmp/lwscap $CORPUS > /dev/null
                if [ $? -ne 0 ] ; then
-                       echo "FAIL: got something other than test.html back"
+                       echo "FAIL: got something other than $CORPUS back"
                        exit 1
                fi
        fi
        if [ "$1" = "defaultplusforbidden" ] ; then
-       cat $INSTALLED/../share/libwebsockets-test-server/test.html > /tmp/plusforb
-       echo -e -n "HTTP/1.1 403 Forbidden\x0d\x0acontent-type: text/html\x0d\x0acontent-length: 38\x0d\x0a\x0d\x0a<html><body><h1>403</h1></body></html>" >> /tmp/plusforb
+       cat $CORPUS > /tmp/plusforb
+       echo -e -n "HTTP/1.0 403 Forbidden\x0d\x0acontent-type: text/html\x0d\x0acontent-length: 173\x0d\x0a\x0d\x0a<html><head><meta charset=utf-8 http-equiv=\"Content-Language\" content=\"en\"/><link rel=\"stylesheet\" type=\"text/css\" href=\"/error.css\"/></head><body><h1>403</h1></body></html>" >> /tmp/plusforb
                diff /tmp/lwscap /tmp/plusforb > /dev/null
                if [ $? -ne 0 ] ; then
-                       echo "FAIL: got something other than test.html + forbidden back"
-                       exit 1
+                       cat $CORPUS > /tmp/plusforb
+
+                       echo -e -n "HTTP/1.1 403 Forbidden\x0d\x0acontent-type: text/html\x0d\x0acontent-length: 173\x0d\x0a\x0d\x0a<html><head><meta charset=utf-8 http-equiv=\"Content-Language\" content=\"en\"/><link rel=\"stylesheet\" type=\"text/css\" href=\"/error.css\"/></head><body><h1>403</h1></body></html>" >> /tmp/plusforb
+                       diff /tmp/lwscap /tmp/plusforb > /dev/null
+                       if [ $? -ne 0 ] ; then
+
+                               echo "FAIL: got something other than $CORPUS + forbidden back"
+                               tail -n 10 /tmp/lwscap
+                               tail -n 100 $LOG
+                               exit 1
+                       fi
                fi
        fi
 
@@ -44,8 +69,16 @@ function check {
                fi
        fi
 
+       if [ "$1" = "notfound" ] ; then
+               if [ -z "`grep '<h1>404</h1>' /tmp/lwscap`" ] ; then
+                       echo "FAIL: should have told not found"
+                       exit 1
+               fi
+       fi
+
+
        if [ "$1" = "rejected" ] ; then
-               if [ -z "`grep '<h1>406</h1>' /tmp/lwscap`" ] ; then
+               if [ -z "`grep '<h1>404</h1>' /tmp/lwscap`" ] ; then
                        echo "FAIL: should have told forbidden (test server has no dirs)"
                        exit 1
                fi
@@ -53,7 +86,7 @@ function check {
 
 
        if [ "$1" = "media" ] ; then
-               if [ -z "`grep '<h1>415</h1>' /tmp/lwscap`" ] ; then
+               if [ -z "`grep '<h1>404</h1>' /tmp/lwscap`" ] ; then
                        echo "FAIL: should have told unknown media type"
                        exit 1
                fi
@@ -68,7 +101,7 @@ function check {
        fi
 
        if [ "$1" == "1" ] ; then
-               a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 1\: | tr -s ' ' | cut -d' ' -f5-`"
+               a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 1\: | tr -s ' ' | cut -d' ' -f7-`"
                if [ "$a" != "$2" ] ; then
                        echo "Arg 1 '$a' not $2"
                        exit 1
@@ -76,14 +109,14 @@ function check {
        fi
 
        if [ "$1" == "2" ] ; then
-               a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 2\: | tr -s ' ' | cut -d' ' -f5-`"
+               a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 2\: | tr -s ' ' | cut -d' ' -f7-`"
                if [ "$a" != "$2" ] ; then
                        echo "Arg 2 '$a' not $2"
                        exit 1
                fi
        fi
        if [ "$1" == "3" ] ; then
-               a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 3\: | tr -s ' ' | cut -d' ' -f5-`"
+               a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 3\: | tr -s ' ' | cut -d' ' -f7-`"
                if [ "$a" != "$2" ] ; then
                        echo "Arg 3 '$a' not $2"
                        exit 1
@@ -98,7 +131,7 @@ function check {
 
 rm -rf $LOG
 killall libwebsockets-test-server 2>/dev/null
-libwebsockets-test-server -d15 2>> $LOG &
+libwebsockets-test-server -d15 2>> $LOG >/dev/null &
 CPID=$!
 
 echo "Started server on PID $CPID"
@@ -111,7 +144,8 @@ check
 echo
 echo "---- /cgi-bin/settingsjs?UPDATE_SETTINGS=1&Root_Channels_1_Channel_name_http_post=%3F&Root_Channels_1_Channel_location_http_post=%3F"
 rm -f /tmp/lwscap
-echo -e "GET /cgi-bin/settingsjs?UPDATE_SETTINGS=1&Root_Channels_1_Channel_name_http_post=%3F&Root_Channels_1_Channel_location_http_post=%3F HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -n -e "GET /cgi-bin/settingsjs?UPDATE_SETTINGS=1&Root_Channels_1_Channel_name_http_post=%3F&Root_Channels_1_Channel_location_http_post=%3F HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
+cat /tmp/lwscap
 check 1 "UPDATE_SETTINGS=1"
 check 2 "Root_Channels_1_Channel_name_http_post=?"
 check 3 "Root_Channels_1_Channel_location_http_post=?"
@@ -120,66 +154,66 @@ check
 echo
 echo "---- ? processing (/cgi-bin/settings.js?key1=value1)"
 rm -f /tmp/lwscap
-echo -e "GET /cgi-bin/settings.js?key1=value1 HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -n -e "GET /cgi-bin/settings.js?key1=value1 HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check 1 "key1=value1"
 check
 
 echo
 echo "---- ? processing (/t%3dest?key1%3d2=value1)"
 rm -f /tmp/lwscap
-echo -e "GET /t%3dest?key1%3d2=value1 HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -n -e "GET /t%3dest?key1%3d2=value1 HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check 0 "/t=est"
 check 1 "key1_2=value1"
 check
 
 echo
-echo "---- ? processing (%2f%2e%2e%2f%2e./test.html?arg=1)"
+echo "---- ? processing (%2f%2e%2e%2f%2e./xxtest.html?arg=1)"
 rm -f /tmp/lwscap
-echo -e "GET %2f%2e%2e%2f%2e./test.html?arg=1 HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo  -n -e "GET %2f%2e%2e%2f%2e./xxtest.html?arg=1 HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check 1 "arg=1"
 check
 
 echo
-echo "---- ? processing (%2f%2e%2e%2f%2e./test.html?arg=/../.)"
+echo "---- ? processing (%2f%2e%2e%2f%2e./xxtest.html?arg=/../.)"
 rm -f /tmp/lwscap
-echo -e "GET %2f%2e%2e%2f%2e./test.html?arg=/../. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -n -e "GET %2f%2e%2e%2f%2e./xxtest.html?arg=/../. HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check 1 "arg=/../."
 check
 
 echo
 echo "---- spam enough crap to not be GET"
-echo "not GET" | nc $SERVER $PORT
+echo "not GET" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null > /tmp/lwscap
 check
 
 echo
 echo "---- spam more than the name buffer of crap"
-dd if=/dev/urandom bs=1 count=80 2>/dev/null | nc -i1s $SERVER $PORT
+dd if=/dev/urandom bs=1 count=80 2>/dev/null | $LWS_NC --server $SERVER --port $PORT 2>/dev/null > /tmp/lwscap
 check
 
 echo
 echo "---- spam 10MB of crap"
-dd if=/dev/urandom bs=1 count=655360 | nc -i1s $SERVER $PORT
+dd if=/dev/urandom bs=1 count=655360 | $LWS_NC --server $SERVER --port $PORT 2>/dev/null > /tmp/lwscap
 check
 
 echo
 echo "---- malformed URI"
 echo "GET nonsense................................................................................................................" \
-       | nc -i1s $SERVER $PORT
+       | $LWS_NC --server $SERVER --port $PORT 2>/dev/null > /tmp/lwscap
 check
 
 echo
 echo "---- missing URI"
-echo -e "GET HTTP/1.1\x0d\x0a\x0d\x0a" | nc -i1s $SERVER $PORT >/tmp/lwscap
+echo -n -e "GET HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null >/tmp/lwscap
 check
 
 echo
 echo "---- repeated method"
-echo -e "GET blah HTTP/1.1\x0d\x0aGET blah HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT >/tmp/lwscap 
+echo -n -e "GET blah HTTP/1.0\x0d\x0aGET blah HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null >/tmp/lwscap 
 check
 
 echo
 echo "---- crazy header name part"
-echo -e "GET blah HTTP/1.1\x0d\x0a................................................................................................................" \
+echo -n -e "GET blah HTTP/1.0\x0d\x0a................................................................................................................" \
        "......................................................................................................................." \
        "......................................................................................................................." \
        "......................................................................................................................." \
@@ -196,12 +230,12 @@ echo -e "GET blah HTTP/1.1\x0d\x0a..............................................
        "......................................................................................................................." \
        "......................................................................................................................." \
        "......................................................................................................................." \
- | nc -i1s $SERVER $PORT
+ | $LWS_NC --server $SERVER --port $PORT 2>/dev/null
 check
 
 echo
 echo "---- excessive uri content"
-echo -e "GET ................................................................................................................" \
+echo -n -e "GET ................................................................................................................" \
        "......................................................................................................................." \
        "......................................................................................................................." \
        "......................................................................................................................." \
@@ -218,106 +252,92 @@ echo -e "GET ...................................................................
        "......................................................................................................................." \
        "......................................................................................................................." \
        "......................................................................................................................." \
- | nc -i1s $SERVER $PORT
+ | $LWS_NC --server $SERVER --port $PORT 2>/dev/null
 check
 
 echo
 echo "---- good request but http payload coming too (test.html served then forbidden)"
-echo -e "GET /test.html HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD........................................" \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-       "......................................................................................................................." \
-        | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -n -e "GET /test.html HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD........................................" \
+       | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check defaultplusforbidden
 check
 
 echo
 echo "---- nonexistent file"
 rm -f /tmp/lwscap
-echo -e "GET /nope HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
-check media
+echo -n -e "GET /nope HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
+cat /tmp/lwscap
+check notfound
 check
 
 echo
 echo "---- relative uri path"
 rm -f /tmp/lwscap
-echo -e "GET nope HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -n -e "GET nope HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check forbidden
 check
 
 echo
 echo "---- directory attack 1 (/../../../../etc/passwd should be /etc/passswd)"
 rm -f /tmp/lwscap
-echo -e "GET /../../../../etc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
-check rejected
+echo -n -e "GET /../../../../etc/passwd HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
+check notfound
 check
 
 echo
 echo "---- directory attack 2 (/../ should be /)"
 rm -f /tmp/lwscap
-echo -e -n "GET /../ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -e -n "GET /../ HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check default
 check
 
 echo
 echo "---- directory attack 3 (/./ should be /)"
 rm -f /tmp/lwscap
-echo -e -n "GET /./ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -e -n "GET /./ HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check default
 check
 
 echo
 echo "---- directory attack 4 (/blah/.. should be /)"
 rm -f /tmp/lwscap
-echo -e -n "GET /blah/.. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -e -n "GET /blah/.. HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check default
 check
 
 echo
 echo "---- directory attack 5 (/blah/../ should be /)"
 rm -f /tmp/lwscap
-echo -e -n "GET /blah/../ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -e -n "GET /blah/../ HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check default
 check
 
 echo
 echo "---- directory attack 6 (/blah/../. should be /)"
 rm -f /tmp/lwscap
-echo -e -n "GET /blah/../. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -e -n "GET /blah/../. HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
 check default
 check
 
 echo
 echo "---- directory attack 7 (/%2e%2e%2f../../../etc/passwd should be /etc/passswd)"
 rm -f /tmp/lwscap
-echo -e -n "GET /%2e%2e%2f../../../etc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
-check rejected
+echo -e -n "GET /%2e%2e%2f../../../etc/passwd HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
+check notfound
 check
 
 echo
 echo "---- directory attack 8 (%2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd should be /etc/passswd)"
 rm -f /tmp/lwscap
-echo -e -n "GET %2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
-check rejected
+echo -e -n "GET %2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd HTTP/1.0\x0d\x0a\x0d\x0a" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null | sed '1,/^\r$/d'> /tmp/lwscap
+check notfound
 check
 
 echo
 echo "---- http/1.1 pipelining"
 rm -f /tmp/lwscap
 wget -O/tmp/lwsdump http://localhost:7681/test.html http://localhost:7681/test.html http://localhost:7681/test.html http://localhost:7681/test.html http://localhost:7681/test.html http://localhost:7681/test.html http://localhost:7681/test.html http://localhost:7681/test.html 2>&1 | grep "Downloaded: 8 files" > /tmp/lwscap
-good=`cat $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html $INSTALLED/../share/libwebsockets-test-server/test.html | md5sum | cut -d' ' -f1`
+good=`cat $CORPUS $CORPUS $CORPUS $CORPUS $CORPUS $CORPUS $CORPUS $CORPUS | md5sum | cut -d' ' -f1`
 if [ "$good" != "`md5sum /tmp/lwsdump | cut -d' ' -f 1`" ] ; then
        echo "FAIL: mismatched content good=$good received=`md5sum /tmp/lwsdump`"
        exit 1
@@ -334,7 +354,7 @@ for i in \
 /...// \
 /.../a \
 /.../w \
-/.../? \
+"/.../?" \
 /.../% \
 /../.. \
 /.././ \
@@ -346,7 +366,7 @@ for i in \
 /../// \
 /..//a \
 /..//w \
-/..//? \
+"/..//?" \
 /..//% \
 /../a. \
 /../a/ \
@@ -534,16 +554,17 @@ for i in \
 /a/w/../a \
 /path/to/dir/../other/dir \
 ; do
-
-R=`rm -f /tmp/lwscap ; echo -n -e "GET $i HTTP/1.0\r\n\r\n" | nc localhost 7681 2>/dev/null >/tmp/lwscap; head -n1 /tmp/lwscap| cut -d' ' -f2`
-
-cat /tmp/lwscap | head -n1
-echo ==== $R
+LEN=`stat $LOG -c %s`
+rm -f /tmp/lwscap1
+echo -n -e "GET $i HTTP/1.0\r\n\r\n" | $LWS_NC --server $SERVER --port $PORT 2>/dev/null > /tmp/lwscap1
+R=`cat /tmp/lwscap1| head -n 1 | cut -d' ' -f 2`
+#cat $LOG
+#echo ==== $R
 
 
 if [ "$R" != "403" ]; then
-       U=`cat $LOG | grep lws_http_serve | tail -n 1 | cut -d':' -f3 | cut -d' ' -f2`
-       echo $U
+       U=`dd if=$LOG bs=1 skip=$LEN 2>/dev/null| grep "Method:" | tr -s ' ' | cut -d"'" -f4`
+#dd if=$LOG bs=1 skip=$LEN 2>/dev/null
        echo "- \"$i\" -> $R \"$U\"" >>/tmp/results
 else
        echo "- \"$i\" -> $R" >>/tmp/results
@@ -551,36 +572,36 @@ fi
 done
 
 cat <<EOF >/tmp/lwsresult1
-- "/..../" -> 406 "/..../"
-- "/.../." -> 406 "/.../"
-- "/...//" -> 406 "/.../"
-- "/.../a" -> 406 "/.../a"
-- "/.../w" -> 406 "/.../w"
-- "/.../?" -> 406 "/.../"
+- "/..../" -> 404 "/..../"
+- "/.../." -> 404 "/.../"
+- "/...//" -> 404 "/.../"
+- "/.../a" -> 404 "/.../a"
+- "/.../w" -> 404 "/.../w"
+- "/.../?" -> 404 "/.../"
 - "/.../%" -> 403
 - "/../.." -> 200 "/"
 - "/.././" -> 200 "/"
-- "/../.a" -> 415 "/.a"
-- "/../.w" -> 415 "/.w"
+- "/../.a" -> 404 "/.a"
+- "/../.w" -> 404 "/.w"
 - "/../.." -> 200 "/"
 - "/../.%" -> 403
 - "/..//." -> 200 "/"
 - "/..///" -> 200 "/"
-- "/..//a" -> 415 "/a"
-- "/..//w" -> 415 "/w"
-- "/..//1" -> 415 "/1"
+- "/..//a" -> 404 "/a"
+- "/..//w" -> 404 "/w"
+- "/..//?" -> 200 "/"
 - "/..//%" -> 403
-- "/../a." -> 415 "/a."
-- "/../a/" -> 406 "/a/"
-- "/../aa" -> 415 "/aa"
-- "/../aw" -> 415 "/aw"
-- "/../a?" -> 415 "/a"
+- "/../a." -> 404 "/a."
+- "/../a/" -> 404 "/a/"
+- "/../aa" -> 404 "/aa"
+- "/../aw" -> 404 "/aw"
+- "/../a?" -> 404 "/a"
 - "/../a%" -> 403
-- "/../w." -> 415 "/w."
-- "/../w/" -> 406 "/w/"
-- "/../wa" -> 415 "/wa"
-- "/../ww" -> 415 "/ww"
-- "/../w?" -> 415 "/w"
+- "/../w." -> 404 "/w."
+- "/../w/" -> 404 "/w/"
+- "/../wa" -> 404 "/wa"
+- "/../ww" -> 404 "/ww"
+- "/../w?" -> 404 "/w"
 - "/../w%" -> 403
 - "/../?." -> 200 "/"
 - "/../?/" -> 200 "/"
@@ -594,49 +615,49 @@ cat <<EOF >/tmp/lwsresult1
 - "/../%w" -> 403
 - "/../%?" -> 403
 - "/../%%" -> 403
-- "/./..." -> 415 "/..."
+- "/./..." -> 404 "/..."
 - "/./../" -> 200 "/"
-- "/./..a" -> 415 "/..a"
-- "/./..w" -> 415 "/..w"
+- "/./..a" -> 404 "/..a"
+- "/./..w" -> 404 "/..w"
 - "/./..?" -> 200 "/"
 - "/./..%" -> 403
 - "/.//.." -> 200 "/"
-- "/.a../" -> 406 "/.a../"
+- "/.a../" -> 404 "/.a../"
 - "/.a/.." -> 200 "/"
-- "/.w../" -> 406 "/.w../"
+- "/.w../" -> 404 "/.w../"
 - "/.w/.." -> 200 "/"
-- "/.?../" -> 415 "/."
+- "/.?../" -> 404 "/."
 - "/../.." -> 200 "/"
 - "/.%../" -> 403
 - "/.%/.." -> 403
-- "//...." -> 415 "/...."
-- "//.../" -> 406 "/.../"
-- "//...a" -> 415 "/...a"
-- "//...w" -> 415 "/...w"
-- "//...?" -> 415 "/..."
+- "//...." -> 404 "/...."
+- "//.../" -> 404 "/.../"
+- "//...a" -> 404 "/...a"
+- "//...w" -> 404 "/...w"
+- "//...?" -> 404 "/..."
 - "//...%" -> 403
 - "//../." -> 200 "/"
 - "//..//" -> 200 "/"
-- "//../a" -> 415 "/a"
-- "//../w" -> 415 "/w"
-- "//../1" -> 415 "/1"
+- "//../a" -> 404 "/a"
+- "//../w" -> 404 "/w"
+- "//../?" -> 200 "/"
 - "//../%" -> 403
-- "//..a." -> 415 "/..a."
-- "//..a/" -> 406 "/..a/"
-- "//..aa" -> 415 "/..aa"
-- "//..aw" -> 415 "/..aw"
-- "//..a?" -> 415 "/..a"
+- "//..a." -> 404 "/..a."
+- "//..a/" -> 404 "/..a/"
+- "//..aa" -> 404 "/..aa"
+- "//..aw" -> 404 "/..aw"
+- "//..a?" -> 404 "/..a"
 - "//..a%" -> 403
-- "//..w." -> 415 "/..w."
-- "//..w/" -> 406 "/..w/"
-- "//..wa" -> 415 "/..wa"
-- "//..ww" -> 415 "/..ww"
-- "//..w?" -> 415 "/..w"
+- "//..w." -> 404 "/..w."
+- "//..w/" -> 404 "/..w/"
+- "//..wa" -> 404 "/..wa"
+- "//..ww" -> 404 "/..ww"
+- "//..w?" -> 404 "/..w"
 - "//..w%" -> 403
 - "//..?." -> 200 "/"
 - "//..?/" -> 200 "/"
-- "//..?a" -> 415 "/a"
-- "//..?w" -> 415 "/w"
+- "//..?a" -> 404 "/a"
+- "//..?w" -> 404 "/w"
 - "//..??" -> 200 "/"
 - "//..?%" -> 403
 - "//..%." -> 403
@@ -646,65 +667,65 @@ cat <<EOF >/tmp/lwsresult1
 - "//..%?" -> 403
 - "//..%%" -> 403
 - "//./.." -> 200 "/"
-- "///..." -> 415 "/..."
+- "///..." -> 404 "/..."
 - "///../" -> 200 "/"
-- "///..a" -> 415 "/..a"
-- "///..w" -> 415 "/..w"
+- "///..a" -> 404 "/..a"
+- "///..w" -> 404 "/..w"
 - "///..?" -> 200 "/"
 - "///..%" -> 403
 - "////.." -> 200 "/"
-- "//a../" -> 406 "/a../"
+- "//a../" -> 404 "/a../"
 - "//a/.." -> 200 "/"
-- "//w../" -> 406 "/w../"
+- "//w../" -> 404 "/w../"
 - "//w/.." -> 200 "/"
 - "//?../" -> 200 "/"
 - "//?/.." -> 200 "/"
 - "//%../" -> 403
 - "//%/.." -> 403
-- "/a.../" -> 406 "/a.../"
-- "/a../." -> 406 "/a../"
-- "/a..//" -> 406 "/a../"
-- "/a../a" -> 406 "/a../a"
-- "/a../w" -> 406 "/a../w"
-- "/a../?" -> 406 "/a../"
+- "/a.../" -> 404 "/a.../"
+- "/a../." -> 404 "/a../"
+- "/a..//" -> 404 "/a../"
+- "/a../a" -> 404 "/a../a"
+- "/a../w" -> 404 "/a../w"
+- "/a../?" -> 404 "/a../"
 - "/a../%" -> 403
 - "/a./.." -> 200 "/"
-- "/a/..." -> 406 "/a/..."
+- "/a/..." -> 404 "/a/..."
 - "/a/../" -> 200 "/"
-- "/a/..a" -> 406 "/a/..a"
-- "/a/..w" -> 406 "/a/..w"
+- "/a/..a" -> 404 "/a/..a"
+- "/a/..w" -> 404 "/a/..w"
 - "/a/..?" -> 200 "/"
 - "/a/..%" -> 403
 - "/a//.." -> 200 "/"
-- "/aa../" -> 406 "/aa../"
+- "/aa../" -> 404 "/aa../"
 - "/aa/.." -> 200 "/"
-- "/aw../" -> 406 "/aw../"
+- "/aw../" -> 404 "/aw../"
 - "/aw/.." -> 200 "/"
-- "/a?../" -> 415 "/a"
-- "/a?/.." -> 415 "/a"
+- "/a?../" -> 404 "/a"
+- "/a?/.." -> 404 "/a"
 - "/a%../" -> 403
 - "/a%/.." -> 403
-- "/w.../" -> 406 "/w.../"
-- "/w../." -> 406 "/w../"
-- "/w..//" -> 406 "/w../"
-- "/w../a" -> 406 "/w../a"
-- "/w../w" -> 406 "/w../w"
-- "/w../?" -> 406 "/w../"
+- "/w.../" -> 404 "/w.../"
+- "/w../." -> 404 "/w../"
+- "/w..//" -> 404 "/w../"
+- "/w../a" -> 404 "/w../a"
+- "/w../w" -> 404 "/w../w"
+- "/w../?" -> 404 "/w../"
 - "/w../%" -> 403
 - "/w./.." -> 200 "/"
-- "/w/..." -> 406 "/w/..."
+- "/w/..." -> 404 "/w/..."
 - "/w/../" -> 200 "/"
-- "/w/..a" -> 406 "/w/..a"
-- "/w/..w" -> 406 "/w/..w"
+- "/w/..a" -> 404 "/w/..a"
+- "/w/..w" -> 404 "/w/..w"
 - "/w/..?" -> 200 "/"
 - "/w/..%" -> 403
 - "/w//.." -> 200 "/"
-- "/wa../" -> 406 "/wa../"
+- "/wa../" -> 404 "/wa../"
 - "/wa/.." -> 200 "/"
-- "/ww../" -> 406 "/ww../"
+- "/ww../" -> 404 "/ww../"
 - "/ww/.." -> 200 "/"
-- "/w?../" -> 415 "/w"
-- "/w?/.." -> 415 "/w"
+- "/w?../" -> 404 "/w"
+- "/w?/.." -> 404 "/w"
 - "/w%../" -> 403
 - "/w%/.." -> 403
 - "/?.../" -> 200 "/"
@@ -753,13 +774,13 @@ cat <<EOF >/tmp/lwsresult1
 - "/%?/.." -> 403
 - "/%%../" -> 403
 - "/%%/.." -> 403
-- "/a/w/../a" -> 406 "/a/a"
-- "/path/to/dir/../other/dir" -> 406 "/path/to/other/dir"
+- "/a/w/../a" -> 404 "/a/a"
+- "/path/to/dir/../other/dir" -> 404 "/path/to/other/dir"
 EOF
 
 if [ "`md5sum /tmp/results | cut -d' ' -f 1`" != "`md5sum /tmp/lwsresult1 | cut -d' ' -f1`" ] ; then
        echo "Differences..."
-       diff -urN /tmp/results /tmp/lwsresult1
+       diff -urN /tmp/lwsresult1 /tmp/results
        exit 1
 else
        echo "OK"
@@ -772,4 +793,19 @@ kill -2 $CPID
 
 exit 0
 
+# coverage...
+# run the test client against mirror for one period and exit
+killall libwebsockets-test-server 2>/dev/null
+libwebsockets-test-server -s 2>> $LOG &
+CPID=$!
+sleep 1s
+libwebsockets-test-client 127.0.0.1 -s -O
+
+# https://github.com/curl/curl/issues/1587
+curl -v -F text=hello -F send=SEND -F upload=@../README.md https://127.0.0.1:7681/formtest -k
+
+kill -2 $CPID
+
+exit 0
+
 
diff --git a/scripts/autobahn-test-client.sh b/scripts/autobahn-test-client.sh
new file mode 100755 (executable)
index 0000000..3db1ddf
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/bash
+#
+# Requires pip install autobahntestsuite
+#
+# you should run this from ./build, after building with
+# cmake .. -DLWS_WITH_MINIMAL_EXAMPLES=1
+#
+# It will use the minimal echo client and server to run
+# autobahn ws tests as both client and server.
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: autobahn as client"
+echo
+
+set -u
+
+PARALLEL=1
+N=1
+OS=`uname`
+
+CLIE=bin/lws-minimal-ws-client-echo
+SERV=bin/lws-minimal-ws-server-echo
+
+RESULT=0
+
+which wstest 2>/dev/null
+if [ $? -ne 0 ]; then
+       echo "wstest is not installed"
+       exit 8
+fi
+
+killall wstest 2>/dev/null
+
+#
+# 2.10 / 2.11:      There is no requirement to handle multiple PING / PONG
+#                   in flight in RFC6455.  lws doesn't waste memory on it
+#                   since it is useless.
+#
+# 12.3.1 / 12.3.2
+# 12.4.* / 12.5.*:  Autobahn has been broken for these tests since Aug 2017
+#                   https://github.com/crossbario/autobahn-testsuite/issues/71
+
+
+cat << EOF >fuzzingserver.json
+{
+   "url": "ws://127.0.0.1:9001",
+   "outdir": "./reports/clients",
+   "cases": ["*"],
+   "exclude-cases": [ "2.10", "2.11", "12.3.1", "12.3.2", "12.4.*", "12.5.*"],
+   "exclude-agent-cases": {}
+}
+EOF
+
+PYTHONHASHSEED=0 wstest -m fuzzingserver &
+Q=$!
+sleep 2s
+ps -p $Q > /dev/null
+if [ $? -ne 0 ] ; then
+       echo "Problem with autobahn wstest install"
+       exit 9
+fi
+
+# 1) lws-as-client tests first
+
+ok=1
+while [ $ok -eq 1 ] ; do
+               $CLIE -s 127.0.0.1 -p 9001 -u "/runCase?case=$N&agent=libwebsockets" -d3
+               if [ $? -ne 0 ]; then
+                       ok=0
+               fi
+       N=$(( $N + 1 ))
+done
+
+# generate the report in ./reports
+#
+$CLIE -s 127.0.0.1 -p 9001 -u "/updateReports?agent=libwebsockets" -o -d3
+sleep 2s
+killall wstest
+sleep 1s
+
+# this squashes the results into single lines like
+#
+#  "9.8.4": { "behavior": "OK", "behaviorClose": "OK", "duration": 1312, "remoteCloseCode": 1000, "reportfile": "libwebsockets_case_9_8_4.json"
+
+cat reports/clients/index.json | tr '\n' '!' | sed "s|\},\!|\n|g" | tr '!' ' ' | tr -s ' ' > /tmp/ji
+
+echo -n "AUTOBAHN SERVER / LWS CLIENT: Total tests: " `cat /tmp/ji | wc -l` " : "
+R="`cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | wc -l`"
+if [ "$R" == "0" ] ; then
+       echo "All pass"
+else
+       RESULT=1
+       echo -n "$R FAIL : "
+       cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | cut -d\" -f2 | tr '\n' ','
+       echo
+fi
+
+echo $RESULT
+exit $RESULT
+
diff --git a/scripts/autobahn-test-server.sh b/scripts/autobahn-test-server.sh
new file mode 100755 (executable)
index 0000000..0a445f4
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/bash
+#
+# Requires pip install autobahntestsuite
+#
+# you should run this from ./build, after building with
+# cmake .. -DLWS_WITH_MINIMAL_EXAMPLES=1
+#
+# It will use the minimal echo client and server to run
+# autobahn ws tests as both client and server.
+
+set -u
+
+PARALLEL=2
+N=1
+OS=`uname`
+
+CLIE=bin/lws-minimal-ws-client-echo
+SERV=bin/lws-minimal-ws-server-echo
+
+RESULT=0
+
+which wstest 2>/dev/null
+if [ $? -ne 0 ]; then
+       echo "wstest is not installed"
+       exit 8
+fi
+
+killall wstest 2>/dev/null
+
+#
+# 2.10 / 2.11:      There is no requirement to handle multiple PING / PONG
+#                   in flight on a single connection in RFC6455.  lws doesn't
+#                  waste memory on supporting it since it is useless.
+
+cat << EOF >fuzzingclient.json
+{ 
+   "outdir": "./reports/servers",
+   "servers": [
+      {
+         "url": "ws://127.0.0.1:9001"
+      }
+   ],
+   "cases": [ "12.2.13" ],
+   "exclude-cases": ["2.10", "2.11" ],
+   "exclude-agent-cases": {}
+}
+EOF
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: autobahn as server"
+echo
+
+$SERV -p 9001 -d3 &
+wstest -m fuzzingclient
+R=$?
+echo "Autobahn client exit $R"
+
+killall lws-minimal-ws-server-echo
+sleep 1s
+
+# repeat the client results
+
+R=`cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | wc -l`
+echo -n "AUTOBAHN SERVER / LWS CLIENT: Total tests: " `cat /tmp/ji | wc -l` " : "
+if [ "$R" == "0" ] ;then
+       echo "All pass"
+else
+       RESULT=1
+       echo -n "$R FAIL : "
+       cat /tmp/ji | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | cut -d\" -f2 | tr '\n' ','
+       echo
+fi
+
+# and then the server results
+
+cat reports/servers/index.json | tr '\n' '!' | sed "s|\},\!|\n|g" | tr '!' ' ' | tr -s ' ' > /tmp/jis
+R=`cat /tmp/jis | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | wc -l`
+
+echo -n "AUTOBAHN CLIENT / LWS SERVER: Total tests: " `cat /tmp/jis | wc -l` " : "
+if [ "$R" == "0" ] ;then
+       echo "All pass"
+else
+       RESULT=$(( $RESULT + 2 ))
+       echo -n "$R FAIL : "
+       cat /tmp/jis | grep -v '"behavior": "OK"' | grep -v '"behavior": "NON-STRICT"' | grep -v '"behavior": "INFORMATIONAL"' | cut -d\" -f2 | tr '\n' ','
+       echo
+fi
+
+echo $RESULT
+exit $RESULT
+
diff --git a/scripts/build-gcov.sh b/scripts/build-gcov.sh
new file mode 100755 (executable)
index 0000000..1db1d6c
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+cmake .. -DLWS_WITH_GCOV=1 && \
+make clean && \
+rm -f `find . -name "*.gcno" -o -name "*.gcda"` && \
+make -j16 && sudo make install
diff --git a/scripts/client-ca/certindex.txt b/scripts/client-ca/certindex.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/scripts/client-ca/create-ca.sh b/scripts/client-ca/create-ca.sh
new file mode 100755 (executable)
index 0000000..5c8e17a
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+openssl genrsa -out ca.key 2048 && \
+printf "\\n\\n\\n\\n\\n\\n\\n" | \
+openssl req -config tmp.cnf -x509 -new -nodes -key ca.key -sha256 -days 1024 -out ca.pem
+
diff --git a/scripts/client-ca/create-client-cert.sh b/scripts/client-ca/create-client-cert.sh
new file mode 100755 (executable)
index 0000000..cee8cbd
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+if [ -z "$1" ] ; then
+       echo "Usage $0 <name>"
+       exit 1
+fi
+
+openssl genrsa -out $1.key 4096 && \
+printf "\\n\\n\\n\\n\\nlocalhost\\n\\n1234\\n\\n" | \
+ openssl req -config tmp.cnf -new -key $1.key -out $1.csr && \
+openssl ca -config tmp.cnf \
+       -keyfile ca.key \
+       -cert ca.pem \
+       -extensions usr_cert \
+       -days 375 \
+       -notext \
+       -md sha256 \
+               -in $1.csr \
+       -out $1.pem && \
+openssl pkcs12 -export -in $1.pem -inkey $1.key -out $1.p12
+
diff --git a/scripts/client-ca/create-server-cert.sh b/scripts/client-ca/create-server-cert.sh
new file mode 100755 (executable)
index 0000000..46a1590
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+if [ -z "$1" ] ; then
+       echo "Usage $0 <name>"
+       exit 1
+fi
+
+openssl genrsa -out $1.key 4096 && \
+printf "\\n\\n\\n\\n\\nlocalhost\\n\\n1234\\n\\n" | \
+ openssl req -config tmp.cnf -new -key $1.key -out $1.csr && \
+openssl ca -config tmp.cnf \
+       -keyfile ca.key \
+       -cert ca.pem \
+       -extensions server_cert \
+       -days 375 \
+       -notext \
+       -md sha256 \
+               -in $1.csr \
+       -out $1.pem
+
diff --git a/scripts/client-ca/serial b/scripts/client-ca/serial
new file mode 100644 (file)
index 0000000..8995c80
--- /dev/null
@@ -0,0 +1 @@
+100003
diff --git a/scripts/client-ca/tmp.cnf b/scripts/client-ca/tmp.cnf
new file mode 100644 (file)
index 0000000..af7f77b
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# OpenSSL configuration file.
+#
+# Establish working directory.
+dir                                    = .
+[ ca ]
+default_ca                             = CA_default
+[ CA_default ]
+serial                                 = $dir/serial
+database                               = $dir/certindex.txt
+new_certs_dir                          = $dir/certs
+certificate                            = $dir/cacert.pem
+private_key                            = $dir/private/cakey.pem
+default_days                           = 365
+default_md                             = sha256
+preserve                               = no
+email_in_dn                            = no
+nameopt                                        = default_ca
+certopt                                        = default_ca
+policy                                 = policy_match
+[ policy_match ]
+countryName                            = match
+stateOrProvinceName                    = match
+organizationName                       = match
+organizationalUnitName                 = optional
+commonName                             = supplied
+emailAddress                           = optional
+[ usr_cert ]
+[ server_cert ] 
+[ req ]
+default_bits                           = 4096                  # Size of keys
+default_keyfile                                = key.pem               # name of generated keys
+default_md                             = sha256                                # message digest algorithm
+string_mask                            = nombstr               # permitted characters
+distinguished_name                     = req_distinguished_name
+req_extensions                         = v3_req
+[ req_distinguished_name ]
+# Variable name                                Prompt string
+#-------------------------       ----------------------------------
+0.organizationName                     = Organization Name (company)
+organizationalUnitName                 = Organizational Unit Name (department, division)
+emailAddress                           = Email Address
+emailAddress_max                       = 40
+localityName                           = Locality Name (city, district)
+stateOrProvinceName                    = State or Province Name (full name)
+countryName                            = Country Name (2 letter code)
+countryName_min                                = 2
+countryName_max                                = 2
+commonName                             = Common Name (hostname, IP, or your name)
+commonName_max                         = 64
+# Default values for the above, for consistency and less typing.
+# Variable name                                Value
+#------------------------        ------------------------------
+0.organizationName_default             = libwebsockets-test
+localityName_default                   = Xiaobitan
+stateOrProvinceName_default            = Taipei
+countryName_default                    = TW
+emailAddress                           = none@invalid
+[ v3_ca ]
+basicConstraints                       = CA:TRUE
+subjectKeyIdentifier                   = hash
+authorityKeyIdentifier                 = keyid:always,issuer:always
+[ v3_req ]
+basicConstraints                       = CA:FALSE
+subjectKeyIdentifier                   = hash
index c4ebc72..4a4de2b 100644 (file)
@@ -8,36 +8,49 @@
 
 SHELL=/bin/bash
 
+# check genromfs is available
+GENROMFS := $(shell command -v genromfs 2> /dev/null)
+# check xxd is available
+XXD := $(shell command -v xxd 2> /dev/null)
+
 ESPPORT ?= $(CONFIG_ESPTOOLPY_PORT)
 
-jbi=$(COMPONENT_PATH)/../build/json-buildinfo
+LWS_BUILD_PATH=$(PROJECT_PATH)/build
+
+jbi=$(LWS_BUILD_PATH)/json-buildinfo
 
 FAC=$(CONFIG_LWS_IS_FACTORY_APPLICATION)
 ifeq ($(FAC),)
        FAC=0
 endif
 export FAC
-DIRNAME:=$(shell basename $$(pwd) | tr -d '\n')
 
-$(COMPONENT_PATH)/../build/pack.img: $(APP_BIN)
+$(LWS_BUILD_PATH)/pack.img: $(APP_BIN)
+       if [ -z "$(GENROMFS)" ]; then \
+               echo "ERROR: genromfs is unavailable, please install or compile genromfs" ; \
+               exit 1 ; \
+       fi; \
+       if [ -z "$(XXD)" ]; then \
+               echo "ERROR: xxd is unavailable, please install or compile xxd (usually provided by vim package)" ; \
+               exit 1 ; \
+       fi; \
        GNUSTAT=stat ;\
        if [ `which gstat 2>/dev/null` ] ; then GNUSTAT=gstat ; fi ;\
-       DIRNAME=$$(basename $$(pwd) | tr -d '\n') ;\
-       genromfs -f $(COMPONENT_PATH)/../build/romfs.img -d $(COMPONENT_PATH)/../romfs-files ; \
-        RLEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/romfs.img) ;\
-        LEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
+       genromfs -f $(LWS_BUILD_PATH)/romfs.img -d $(PROJECT_PATH)/romfs-files ; \
+        RLEN=$$($$GNUSTAT -c %s $(LWS_BUILD_PATH)/romfs.img) ;\
+        LEN=$$($$GNUSTAT -c %s $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin) ;\
         printf "             Original length: 0x%06x (%8d)\n" $$LEN $$LEN ; \
-        printf %02x $$(( $$RLEN % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-        printf %02x $$(( ( $$RLEN / 256 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-        printf %02x $$(( ( $$RLEN / 65536 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-        printf %02x $$(( ( $$RLEN / 16777216 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-        cat $(COMPONENT_PATH)/../build/romfs.img >>$(COMPONENT_PATH)/../build/$$DIRNAME.bin ; \
-        LEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
+        printf %02x $$(( $$RLEN % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+        printf %02x $$(( ( $$RLEN / 256 ) % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+        printf %02x $$(( ( $$RLEN / 65536 ) % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+        printf %02x $$(( ( $$RLEN / 16777216 ) % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+        cat $(LWS_BUILD_PATH)/romfs.img >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ; \
+        LEN=$$($$GNUSTAT -c %s $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin) ;\
        UNIXTIME=$$(date +%s | tr -d '\n') ; \
        echo -n -e "{\r\n \"schema\": \"lws1\",\r\n \"model\": \"$(CONFIG_LWS_MODEL_NAME)\",\r\n \"builder\": \"" > $(jbi) ;\
        hostname | tr -d '\n' >> $(jbi) ;\
        echo -n -e "\",\r\n \"app\": \"" >> $(jbi) ;\
-       echo -n $$DIRNAME >> $(jbi) ;\
+       echo -n $(PROJECT_NAME) >> $(jbi) ;\
        echo -n -e "\",\r\n \"user\": \"" >> $(jbi) ;\
        whoami | tr -d '\n' >>$(jbi) ;\
        echo -n -e  "\",\r\n \"git\": \"" >> $(jbi) ;\
@@ -46,18 +59,18 @@ $(COMPONENT_PATH)/../build/pack.img: $(APP_BIN)
        date | tr -d '\n' >> $(jbi) ;\
        echo -n -e "\",\r\n \"unixtime\": \"" >> $(jbi) ;\
        echo -n $$UNIXTIME >> $(jbi) ;\
-       echo -n -e "\",\r\n \"file\": \""$$DIRNAME-$$UNIXTIME.bin >> $(jbi) ;\
+       echo -n -e "\",\r\n \"file\": \""$(PROJECT_NAME)-$$UNIXTIME.bin >> $(jbi) ;\
        echo -n -e "\",\r\n \"factory\": \"$(FAC)" >> $(jbi) ;\
        echo -n -e "\"\r\n}"  >> $(jbi) ;\
        JLEN=$$($$GNUSTAT -c %s $(jbi)) ;\
-       printf %02x $$(( $$JLEN % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-       printf %02x $$(( ( $$JLEN / 256 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-       printf %02x $$(( ( $$JLEN / 65536 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-       printf %02x $$(( ( $$JLEN / 16777216 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-       cat $(jbi) >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
-       cp $(COMPONENT_PATH)/../build/$$DIRNAME.bin $(COMPONENT_PATH)/../build/pack.img ;\
-        LEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
-       cp $(COMPONENT_PATH)/../build/$$DIRNAME.bin $(COMPONENT_PATH)/../build/$$DIRNAME-$$UNIXTIME.bin ;\
+       printf %02x $$(( $$JLEN % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+       printf %02x $$(( ( $$JLEN / 256 ) % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+       printf %02x $$(( ( $$JLEN / 65536 ) % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+       printf %02x $$(( ( $$JLEN / 16777216 ) % 256 )) | xxd -r -p >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+       cat $(jbi) >> $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin ;\
+       cp $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin $(LWS_BUILD_PATH)/pack.img ;\
+        LEN=$$($$GNUSTAT -c %s $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin) ;\
+       cp $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin $(LWS_BUILD_PATH)/$(PROJECT_NAME)-$$UNIXTIME.bin ;\
        printf "    After ROMFS + Build info: 0x%06x (%8d)\n" $$LEN $$LEN
 
 .PHONY: manifest
@@ -76,24 +89,23 @@ endif
        cat $(F)/build/json-buildinfo >> build/manifest.json
        echo -n -e "\r\n}\r\n" >> build/manifest.json
 
-all: $(COMPONENT_PATH)/../build/pack.img
+all: $(LWS_BUILD_PATH)/pack.img
 
-flash: $(COMPONENT_PATH)/../build/pack.img
+flash: $(LWS_BUILD_PATH)/pack.img
 
-flash_ota: $(COMPONENT_PATH)/../build/pack.img
-       DIRNAME=$$(basename $$(pwd) | tr -d '\n') ;\
+lws_flash_ota: $(LWS_BUILD_PATH)/pack.img
        $(IDF_PATH)/components/esptool_py/esptool/esptool.py \
                --chip esp32 \
                --port $(ESPPORT) \
                --baud $(CONFIG_ESPTOOLPY_BAUD) \
-               write_flash 0x110000 $(COMPONENT_PATH)/../build/$$DIRNAME.bin
+               write_flash 0x120000 $(LWS_BUILD_PATH)/$(PROJECT_NAME).bin
 
-erase_ota:
+lws_erase_ota:
        $(IDF_PATH)/components/esptool_py/esptool/esptool.py \
                --chip esp32 \
                --port $(ESPPORT) \
                --baud $(CONFIG_ESPTOOLPY_BAUD) \
-               erase_region 0x110000 0x2f0000
+               erase_region 0x120000 0x2e0000
 
 
 export A
diff --git a/scripts/gcov.sh b/scripts/gcov.sh
new file mode 100755 (executable)
index 0000000..ee71cc0
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gcov `find . -name *.c.gcno | grep -v test-apps` -b | sed "/\.h.\$/,/^$/d"
diff --git a/scripts/h2load-smp.sh b/scripts/h2load-smp.sh
new file mode 100755 (executable)
index 0000000..5eda06b
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# run from the build dir
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: h2load SMP"
+echo
+
+PW=`pwd`
+
+cd ../minimal-examples/http-server/minimal-http-server-smp
+$PW/bin/lws-minimal-http-server-smp -s &
+R=$!
+sleep 0.5s
+
+# check h1 with various loads
+
+h2load -n 10000 -c 1 --h1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 10000 -c 10 --h1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 100000 -c 100 --h1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+
+# check h2 with various loads
+
+h2load -n 10000 -c 1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 10000 -c 10 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 100000 -c 100 https://127.0.0.1:7681
+Q=$?
+
+kill $R
+wait $R
+exit $Q
+
diff --git a/scripts/h2load.sh b/scripts/h2load.sh
new file mode 100755 (executable)
index 0000000..e6d18c4
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# run from the build dir
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: h2load"
+echo
+
+PW=`pwd`
+
+cd ../minimal-examples/http-server/minimal-http-server-tls
+$PW/bin/lws-minimal-http-server-tls &
+R=$!
+sleep 0.5s
+
+# check h1 with various loads
+
+h2load -n 10000 -c 1 --h1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 10000 -c 10 --h1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 100000 -c 100 --h1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+
+# check h2 with various loads
+
+h2load -n 10000 -c 1 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 10000 -c 10 https://127.0.0.1:7681
+if [ $? -ne 0 ] ; then
+       Q=$?
+       kill $R
+       wait $R
+       exit $Q
+fi
+h2load -n 100000 -c 100 https://127.0.0.1:7681
+Q=$?
+
+kill $R
+wait $R
+exit $Q
+
diff --git a/scripts/h2spec.sh b/scripts/h2spec.sh
new file mode 100755 (executable)
index 0000000..57bb4e7
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# run from the build subdir
+#
+
+echo
+echo "----------------------------------------------"
+echo "-------   tests: h2spec"
+echo
+
+
+if [ ! -e h2spec ] ; then
+       wget https://github.com/summerwind/h2spec/releases/download/v2.1.0/h2spec_linux_amd64.tar.gz &&\
+       tar xf h2spec_linux_amd64.tar.gz
+       if [ ! -e h2spec ] ; then
+               echo "Couldn't get h2spec"
+               exit 1
+       fi
+fi
+
+cd ../minimal-examples/http-server/minimal-http-server-tls
+../../../build/bin/lws-minimal-http-server-tls&
+
+sleep 1s
+
+P=$!
+../../../build/h2spec -h 127.0.0.1 -p 7681 -t -k -S > /tmp/hlog
+kill $P 2>/dev/null
+wait $P 2>/dev/null
+
+if [ ! -z "`cat /tmp/hlog | grep "Failures:"`" ] ; then
+       cat /tmp/hlog | sed '/Failures:/,$!d'
+
+       exit 1
+fi
+
+cat /tmp/hlog | sed '/Finished\ in/,$!d'
+
+
+exit 0
+
diff --git a/scripts/libwebsockets.spec b/scripts/libwebsockets.spec
new file mode 100644 (file)
index 0000000..d5cc7b3
--- /dev/null
@@ -0,0 +1,180 @@
+Name: libwebsockets
+Version: 3.2.0
+Release: 1%{?dist}
+Summary: Websocket Server and Client Library
+
+Group: System Environment/Libraries
+License: LGPLv2 with exceptions
+URL: https://libwebsockets.org
+Source0: %{name}-%{version}.tar.gz
+BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
+
+BuildRequires: openssl-devel libuv-devel libev-devel cmake
+Requires: openssl
+
+%description
+Webserver server and client library
+
+%package devel
+Summary: Development files for libwebsockets
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}
+Requires: openssl-devel
+
+%description devel
+Development files for libwebsockets
+
+%prep
+%setup -q
+
+%build
+mkdir -p build
+cd build
+%cmake .. -DLWS_WITH_DISTRO_RECOMMENDED=1
+make
+
+%install
+rm -rf $RPM_BUILD_ROOT
+cd build
+make install DESTDIR=$RPM_BUILD_ROOT
+
+%post -p /sbin/ldconfig
+%postun -p /sbin/ldconfig
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root,-)
+%attr(755,root,root)
+"/usr/bin/libwebsockets-test-client"
+"/usr/bin/libwebsockets-test-lejp"
+"/usr/bin/libwebsockets-test-server"
+"/usr/bin/libwebsockets-test-server-extpoll"
+"/usr/bin/libwebsockets-test-sshd"
+"/usr/bin/lwsws"
+"/%{_libdir}/libwebsockets.so"
+"/%{_libdir}/libwebsockets.so.15"
+%dir "/usr/share/libwebsockets-test-server"
+"/usr/share/libwebsockets-test-server/candide.zip"
+"/usr/share/libwebsockets-test-server/favicon.ico"
+%dir "/usr/share/libwebsockets-test-server/generic-table"
+"/usr/share/libwebsockets-test-server/generic-table/index.html"
+"/usr/share/libwebsockets-test-server/generic-table/lwsgt.js"
+"/usr/share/libwebsockets-test-server/http2.png"
+"/usr/share/libwebsockets-test-server/leaf.jpg"
+"/usr/share/libwebsockets-test-server/libwebsockets-test-server.key.pem"
+"/usr/share/libwebsockets-test-server/libwebsockets-test-server.pem"
+"/usr/share/libwebsockets-test-server/libwebsockets.org-logo.svg"
+"/usr/share/libwebsockets-test-server/lws-cgi-test.sh"
+"/usr/share/libwebsockets-test-server/lws-common.js"
+"/usr/share/libwebsockets-test-server/lws-ssh-test-keys"
+"/usr/share/libwebsockets-test-server/lws-ssh-test-keys.pub"
+%dir "/usr/share/libwebsockets-test-server/plugins"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_client_loopback_test.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_dumb_increment.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_fulltext_demo.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_acme_client.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_mirror.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_raw_test.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_server_status.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_ssh_base.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_sshd_demo.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_status.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_lws_table_dirlisting.so"
+"/usr/share/libwebsockets-test-server/plugins/libprotocol_post_demo.so"
+%dir "/usr/share/libwebsockets-test-server/private"
+"/usr/share/libwebsockets-test-server/private/index.html"
+%dir "/usr/share/libwebsockets-test-server/server-status"
+"/usr/share/libwebsockets-test-server/server-status/lwsws-logo.png"
+"/usr/share/libwebsockets-test-server/server-status/server-status.css"
+"/usr/share/libwebsockets-test-server/server-status/server-status.html"
+"/usr/share/libwebsockets-test-server/server-status/server-status.js"
+"/usr/share/libwebsockets-test-server/test.css"
+"/usr/share/libwebsockets-test-server/test.html"
+"/usr/share/libwebsockets-test-server/test.js"
+"/usr/share/libwebsockets-test-server/wss-over-h2.png"
+%files devel
+%defattr(-,root,root,-)
+%dir "/usr/include/libwebsockets"
+"/usr/include/libwebsockets.h"
+"/usr/include/libwebsockets/lws-adopt.h"
+"/usr/include/libwebsockets/lws-callbacks.h"
+"/usr/include/libwebsockets/lws-cgi.h"
+"/usr/include/libwebsockets/lws-client.h"
+"/usr/include/libwebsockets/lws-context-vhost.h"
+"/usr/include/libwebsockets/lws-dbus.h"
+"/usr/include/libwebsockets/lws-diskcache.h"
+"/usr/include/libwebsockets/lws-esp32.h"
+"/usr/include/libwebsockets/lws-fts.h"
+"/usr/include/libwebsockets/lws-genhash.h"
+"/usr/include/libwebsockets/lws-genrsa.h"
+"/usr/include/libwebsockets/lws-http.h"
+"/usr/include/libwebsockets/lws-jose.h"
+"/usr/include/libwebsockets/lws-jwk.h"
+"/usr/include/libwebsockets/lws-jws.h"
+"/usr/include/libwebsockets/lws-lejp.h"
+"/usr/include/libwebsockets/lws-logs.h"
+"/usr/include/libwebsockets/lws-lwsac.h"
+"/usr/include/libwebsockets/lws-misc.h"
+"/usr/include/libwebsockets/lws-network-helper.h"
+"/usr/include/libwebsockets/lws-plugin-generic-sessions.h"
+"/usr/include/libwebsockets/lws-protocols-plugins.h"
+"/usr/include/libwebsockets/lws-purify.h"
+"/usr/include/libwebsockets/lws-ring.h"
+"/usr/include/libwebsockets/lws-service.h"
+"/usr/include/libwebsockets/lws-sha1-base64.h"
+"/usr/include/libwebsockets/lws-spa.h"
+"/usr/include/libwebsockets/lws-stats.h"
+"/usr/include/libwebsockets/lws-threadpool.h"
+"/usr/include/libwebsockets/lws-timeout-timer.h"
+"/usr/include/libwebsockets/lws-tokenize.h"
+"/usr/include/libwebsockets/lws-vfs.h"
+"/usr/include/libwebsockets/lws-write.h"
+"/usr/include/libwebsockets/lws-writeable.h"
+"/usr/include/libwebsockets/lws-ws-close.h"
+"/usr/include/libwebsockets/lws-ws-ext.h"
+"/usr/include/libwebsockets/lws-ws-state.h"
+"/usr/include/libwebsockets/lws-x509.h"
+"/usr/include/lws-plugin-ssh.h"
+"/usr/include/lws_config.h"
+%dir "/usr/lib/pkgconfig"
+"/%{_libdir}/pkgconfig/libwebsockets.pc"
+"/usr/lib/pkgconfig/libwebsockets_static.pc"
+%dir "/usr/lib/cmake"
+%dir "/usr/lib/cmake/libwebsockets"
+"/%{_libdir}/cmake/libwebsockets/LibwebsocketsConfig.cmake"
+"/%{_libdir}/cmake/libwebsockets/LibwebsocketsConfigVersion.cmake"
+"/%{_libdir}/cmake/libwebsockets/LibwebsocketsTargets-debug.cmake"
+"/%{_libdir}/cmake/libwebsockets/LibwebsocketsTargets.cmake"
+
+%changelog
+* Fri Aug 14 2019 Andy Green <andy@warmcat.com> 3.2.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 3.2.0 release (last LGPLv2.1+SLE)
+
+* Fri Nov 23 2018 Andy Green <andy@warmcat.com> 3.1.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 3.1.0 release
+
+* Fri May 4 2018 Andy Green <andy@warmcat.com> 3.0.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 3.0.0 release
+
+* Mon Oct 16 2017 Andy Green <andy@warmcat.com> 2.4.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 2.4.0 release
+
+* Fri Jul 28 2017 Andy Green <andy@warmcat.com> 2.3.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 2.3.0 release
+
+* Mon Mar 06 2017 Andy Green <andy@warmcat.com> 2.2.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 2.2.0 release
+
+* Thu Oct 06 2016 Andy Green <andy@warmcat.com> 2.1.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 2.1.0 release
+
+* Thu May 05 2016 Andy Green <andy@warmcat.com> 2.0.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 2.0.0 release
+
+* Tue Feb 16 2016 Andy Green <andy@warmcat.com> 1.7.0-1
+- MAJOR SONAMEBUMP APICHANGES Upstream 1.7.0 release
+
+* Sun Jan 17 2016 Andrew Cooks <acooks@linux.com> 1.6.0-1
+- Bump version to 1.6.0
diff --git a/scripts/test-dbus-proxy.sh b/scripts/test-dbus-proxy.sh
new file mode 100755 (executable)
index 0000000..1aec16f
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+echo "Starting $0"
+
+bin/lws-minimal-dbus-ws-proxy 2> /tmp/dbuss&
+
+echo "  server starting"
+sleep 1s
+PID_PROX=$!
+
+echo "  client starting"
+bin/lws-minimal-dbus-ws-proxy-testclient -x 10 2> /tmp/dbusc
+R=$?
+
+kill -2 $PID_PROX
+
+if [ $R -ne 0 ] ; then
+       echo "$0 FAILED"
+       cat /tmp/dbuss
+       cat /tmp/dbusc
+       exit 1
+fi
+
+if [ -z "`cat /tmp/dbusc | grep 'rx: 9, tx: 9'`" ] ; then
+       echo "$0 FAILED"
+       cat /tmp/dbuss
+       cat /tmp/dbusc
+       exit 1
+fi
+
+echo "$0 PASSED"
+
+exit 0
+
diff --git a/scripts/travis_control.sh b/scripts/travis_control.sh
new file mode 100755 (executable)
index 0000000..ef4b0ae
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then
+       if [ "$LWS_METHOD" != "mbedtls" ] ; then
+               mkdir build && cd build &&
+               cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. &&
+               cmake --build .
+       fi
+else
+       if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "linux" ]; then
+               mkdir build && cd build &&
+               if [ "$LWS_METHOD" = "lwsws" ] ; then
+                       cmake -DLWS_OPENSSL_LIBRARIES="/usr/local/lib/libssl.so;/usr/local/lib/libcrypto.so" \
+                             -DLWS_OPENSSL_INCLUDE_DIRS="/usr/local/include/openssl" $CMAKE_ARGS .. &&
+                       cmake --build . &&
+                       sudo make install &&
+                       ../minimal-examples/selftests.sh &&
+                       ../scripts/test-dbus-proxy.sh &&
+                       ../scripts/h2spec.sh &&
+                       ../scripts/attack.sh &&
+                       ../scripts/h2load.sh &&
+                       ../scripts/autobahn-test-client.sh
+               else
+                       if [ "$LWS_METHOD" = "lwsws2" ] ; then
+                               cmake -DLWS_OPENSSL_LIBRARIES="/usr/local/lib/libssl.so;/usr/local/lib/libcrypto.so" \
+                                     -DLWS_OPENSSL_INCLUDE_DIRS="/usr/local/include/openssl" $CMAKE_ARGS .. &&
+                               cmake --build . &&
+                               sudo make install &&
+                               ../scripts/autobahn-test-server.sh
+                       else
+                               if [ "$LWS_METHOD" = "smp" ] ; then
+                                       cmake -DLWS_OPENSSL_LIBRARIES="/usr/local/lib/libssl.so;/usr/local/lib/libcrypto.so" \
+                                             -DLWS_OPENSSL_INCLUDE_DIRS="/usr/local/include/openssl" $CMAKE_ARGS .. &&
+                                       cmake --build . &&
+                                       ../scripts/h2load-smp.sh
+                               else
+                                       if [ "$LWS_METHOD" = "mbedtls" ] ; then
+                                               cmake $CMAKE_ARGS .. &&
+                                               cmake --build . &&
+                                               sudo make install &&
+                                               ../minimal-examples/selftests.sh &&
+                                               ../scripts/h2spec.sh &&
+                                               ../scripts/h2load.sh &&
+                                               ../scripts/attack.sh
+                                       else
+                                               cmake $CMAKE_ARGS .. &&
+                                               cmake --build .
+                                       fi
+                               fi
+                       fi
+               fi
+       fi
+fi
+
diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh
new file mode 100755 (executable)
index 0000000..3956422
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+if [ "$COVERITY_SCAN_BRANCH" == 1 ]; then exit; fi
+
+if [ "$TRAVIS_OS_NAME" == "linux" ];
+then
+       sudo apt-get update -qq
+
+       if [ "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ];
+       then
+               sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev libdbus-1-dev
+               sudo apt-get remove python-six
+               sudo pip install "six>=1.9"
+               sudo pip install "Twisted==16.0.0"
+               sudo pip install "pyopenssl>=0.14"
+               sudo pip install autobahntestsuite
+               wget https://libwebsockets.org/openssl-1.1.0-trusty.tar.bz2 -O/tmp/openssl.tar.bz2
+               cd /
+               sudo tar xf /tmp/openssl.tar.bz2
+               sudo ldconfig
+               sudo update-ca-certificates
+       fi
+
+       if [ "$LWS_METHOD" == "mbedtls" ];
+       then
+               sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev
+               wget https://libwebsockets.org/openssl-1.1.0-trusty.tar.bz2 -O/tmp/openssl.tar.bz2
+               cd /
+               sudo tar xf /tmp/openssl.tar.bz2
+               sudo ldconfig
+               sudo update-ca-certificates
+       fi
+
+       if [ "$LWS_METHOD" == "smp" ];
+       then
+               sudo apt-get install -y -qq realpath libjemalloc1 libev4
+               wget https://libwebsockets.org/openssl-1.1.0-trusty.tar.bz2 -O/tmp/openssl.tar.bz2
+               cd /
+               sudo tar xf /tmp/openssl.tar.bz2
+               sudo ldconfig
+               sudo update-ca-certificates
+       fi
+
+       if [ "$LWS_METHOD" == "libev" ];
+       then
+               sudo apt-get install -y -qq libev-dev;
+       fi
+
+       if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ];
+       then
+               sudo apt-get install -y -qq libuv-dev;
+#libuv1 libuv1-dev;
+       fi
+
+fi
+
+if [ "$TRAVIS_OS_NAME" == "osx" ];
+then
+       if [ "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ];
+       then
+               brew update;
+               brew install dbus;
+       fi
+
+       if [ "$LWS_METHOD" == "libev" ];
+       then
+               brew update;
+               brew install libev;
+       fi
+
+       if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ];
+       then
+               brew update;
+               brew install libuv;
+       fi
+
+fi
+
+       
diff --git a/test-apps/1.png b/test-apps/1.png
new file mode 100644 (file)
index 0000000..4a022b9
Binary files /dev/null and b/test-apps/1.png differ
diff --git a/test-apps/2.png b/test-apps/2.png
new file mode 100644 (file)
index 0000000..d6bc9d1
Binary files /dev/null and b/test-apps/2.png differ
diff --git a/test-apps/3.png b/test-apps/3.png
new file mode 100644 (file)
index 0000000..0f4614d
Binary files /dev/null and b/test-apps/3.png differ
diff --git a/test-apps/4.png b/test-apps/4.png
new file mode 100644 (file)
index 0000000..4319faa
Binary files /dev/null and b/test-apps/4.png differ
diff --git a/test-apps/5.png b/test-apps/5.png
new file mode 100644 (file)
index 0000000..4a2838c
Binary files /dev/null and b/test-apps/5.png differ
diff --git a/test-apps/6.png b/test-apps/6.png
new file mode 100644 (file)
index 0000000..a6ee648
Binary files /dev/null and b/test-apps/6.png differ
diff --git a/test-apps/7.png b/test-apps/7.png
new file mode 100644 (file)
index 0000000..d02ab81
Binary files /dev/null and b/test-apps/7.png differ
diff --git a/test-apps/8.png b/test-apps/8.png
new file mode 100644 (file)
index 0000000..f2f6e80
Binary files /dev/null and b/test-apps/8.png differ
@@ -176,7 +176,7 @@ JNIEXPORT jboolean JNICALL jni_initLws(JNIEnv *env, jobject obj)
     memset(&info, 0, sizeof(info));
     info.port = CONTEXT_PORT_NO_LISTEN;
     info.protocols = protocols;
-#ifndef LWS_NO_EXTENSIONS
+#if !defined(LWS_WITHOUT_EXTENSIONS)
     info.extensions = exts;
 #endif
     info.gid = -1;
@@ -193,7 +193,7 @@ TARGET_X86_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -208,7 +208,7 @@ TARGET_X86_64_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -223,7 +223,7 @@ TARGET_ARM_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -238,7 +238,7 @@ TARGET_ARM_V7A_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -253,7 +253,7 @@ TARGET_ARM_V7A_HARD_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -268,7 +268,7 @@ TARGET_ARM64_V8A_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -283,7 +283,7 @@ TARGET_MIPS_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
@@ -298,7 +298,7 @@ TARGET_MIPS64_LWS_OPTIONS = \
  -DLWS_WITHOUT_DAEMONIZE=ON \
  -DLWS_WITHOUT_TESTAPPS=ON \
  -DLWS_IPV6=OFF \
- -DLWS_USE_BUNDLED_ZLIB=OFF \
+ -DLWS_WITH_BUNDLED_ZLIB=OFF \
  -DLWS_WITH_SSL=ON  \
  -DLWS_WITH_HTTP2=ON \
  -DCMAKE_BUILD_TYPE=Release
diff --git a/test-apps/android/app/src/main/libs/placeholder b/test-apps/android/app/src/main/libs/placeholder
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test-apps/candide.zip b/test-apps/candide.zip
new file mode 100644 (file)
index 0000000..82a6619
Binary files /dev/null and b/test-apps/candide.zip differ
diff --git a/test-apps/favicon.ico b/test-apps/favicon.ico
new file mode 100644 (file)
index 0000000..c0cc2e3
Binary files /dev/null and b/test-apps/favicon.ico differ
diff --git a/test-apps/http2.png b/test-apps/http2.png
new file mode 100644 (file)
index 0000000..b4129e7
Binary files /dev/null and b/test-apps/http2.png differ
diff --git a/test-apps/leaf.jpg b/test-apps/leaf.jpg
new file mode 100644 (file)
index 0000000..1a3f46b
Binary files /dev/null and b/test-apps/leaf.jpg differ
diff --git a/test-apps/libwebsockets.org-logo.svg b/test-apps/libwebsockets.org-logo.svg
new file mode 100644 (file)
index 0000000..7baea64
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="127.63mm" height="27.837mm" version="1.1" viewBox="0 0 127.63446 27.837189" xmlns="http://www.w3.org/2000/svg">
+       <defs>
+               <filter id="a" x="-.011681" y="-.053882" width="1.0234" height="1.1078" color-interpolation-filters="sRGB">
+                       <feGaussianBlur stdDeviation="0.10687168"/>
+               </filter>
+       </defs>
+       <g transform="translate(452.86 42.871)">
+               <rect x="-452.86" y="-42.871" width="127.63" height="27.837" fill="none"/>
+               <g transform="matrix(4.0081 0 0 4.0081 -211.01 -224.26)" fill="#fff" filter="url(#a)" stroke="#fff">
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                               <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                               <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                               <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                               <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                               <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                       </g>
+                       <g style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+               </g>
+               <g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".4463" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="libwebsockets.org">
+                               <g stroke-width=".4463">
+                                       <path d="m-52.015 48.429q0 0.12497 0.03213 0.17852 0.0357 0.05356 0.0964 0.05356 0.07498 0 0.17495-0.03927l0.02499 0.20709q-0.04642 0.02856-0.13211 0.04642-0.08212 0.01785-0.14996 0.01785-0.13568 0-0.22137-0.08212-0.08212-0.08569-0.08212-0.29635v-2.1601h0.25707z"/>
+                                       <path d="m-51.417 47.068h0.25707v1.7852h-0.25707zm-0.04642-0.54271q0-0.08569 0.04642-0.13925 0.04999-0.05356 0.12854-0.05356 0.07855 0 0.12854 0.05356 0.05356 0.04999 0.05356 0.13925 0 0.08569-0.05356 0.13568-0.04999 0.04642-0.12854 0.04642-0.07855 0-0.12854-0.04999-0.04642-0.04999-0.04642-0.13211z"/>
+                                       <path d="m-50.686 46.354h0.25707v0.84976h0.01071q0.14639-0.17852 0.38918-0.17852 0.27492 0 0.4106 0.2178 0.13925 0.2178 0.13925 0.6891 0 0.48201-0.18566 0.71766-0.18209 0.23565-0.51771 0.23565-0.16424 0-0.29992-0.03571-0.13568-0.03927-0.20352-0.08926zm0.25707 2.2387q0.04999 0.02856 0.1214 0.04641 0.07498 0.01428 0.1571 0.01428 0.18566 0 0.29278-0.17495 0.11068-0.17852 0.11068-0.54628 0-0.15353-0.02142-0.27492-0.01785-0.12496-0.0607-0.21423-0.03927-0.08926-0.10711-0.13568-0.06427-0.04999-0.1571-0.04999-0.12854 0-0.21423 0.07855-0.08212 0.07498-0.1214 0.20708z"/>
+                               </g>
+                               <path d="m-48.092 47.068 0.24993 0.91403 0.04284 0.29635h0.01428l0.0357-0.29992 0.16781-0.91046h0.38561l-0.43916 1.8031h-0.33562l-0.26778-0.99615-0.02856-0.22851h-0.02142l-0.02499 0.23565-0.25707 0.98901h-0.34633l-0.45702-1.8031h0.46059l0.1928 0.89618 0.03213 0.31777h0.01428l0.04642-0.32134 0.2178-0.89261z"/>
+                               <path d="m-45.889 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86762q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20709-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-45.569 46.354h0.42488v0.82834h0.01071q0.12854-0.1571 0.36776-0.1571 0.2535 0 0.39275 0.21066 0.14282 0.21066 0.14282 0.67838 0 0.507-0.19994 0.74622-0.19994 0.23565-0.54628 0.23565-0.18923 0-0.3499-0.0357-0.1571-0.03571-0.24279-0.07855zm0.42488 2.1137q0.07498 0.03927 0.19638 0.03927 0.13568 0 0.20708-0.12854 0.07141-0.13211 0.07141-0.43916 0-0.27135-0.05713-0.39632-0.05713-0.12854-0.17852-0.12854-0.17852 0-0.23922 0.18566z"/>
+                               <path d="m-43.386 48.379q0-0.07498-0.04999-0.12496-0.04642-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12496 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20709 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <path d="m-42.802 47.961q0-0.47487 0.18566-0.70695 0.18566-0.23208 0.51771-0.23208 0.35704 0 0.532 0.23565 0.17495 0.23565 0.17495 0.70338 0 0.47844-0.18566 0.71052-0.18566 0.22851-0.52128 0.22851-0.70338 0-0.70338-0.93902zm0.43916 0q0 0.26778 0.0607 0.41417t0.20352 0.14639q0.13568 0 0.19994-0.12497 0.06784-0.12854 0.06784-0.43559 0-0.27492-0.0607-0.41774-0.0607-0.14282-0.20708-0.14282-0.12497 0-0.19638 0.12854-0.06784 0.12497-0.06784 0.43202z"/>
+                               <path d="m-40.094 48.757q-0.08926 0.07141-0.21423 0.10711-0.12496 0.0357-0.24993 0.0357-0.18209 0-0.30706-0.06427-0.1214-0.06784-0.19994-0.18923-0.07855-0.12496-0.11425-0.29635-0.03213-0.17495-0.03213-0.38918 0-0.46773 0.16781-0.70338 0.16781-0.23565 0.49272-0.23565 0.16067 0 0.26064 0.02856 0.10354 0.02856 0.18209 0.07141l-0.09997 0.35347q-0.06427-0.03213-0.12497-0.04642-0.05713-0.01785-0.13925-0.01785-0.14996 0-0.22494 0.13211-0.07498 0.12854-0.07498 0.41774 0 0.24279 0.07498 0.39632 0.07855 0.15353 0.24636 0.15353 0.08926 0 0.14996-0.02142 0.06427-0.02499 0.11782-0.0607z"/>
+                               <path d="m-39.374 48.114h-0.09997v0.73908h-0.42488v-2.4993h0.42488v1.4746l0.08569-0.04999 0.29635-0.71052h0.46059l-0.32848 0.70695-0.15353 0.1214 0.16781 0.1214 0.36418 0.83548h-0.47844z"/>
+                               <path d="m-37.256 48.721q-0.08926 0.07855-0.24279 0.12854-0.15353 0.04999-0.32134 0.04999-0.18566 0-0.32134-0.06427-0.13211-0.06427-0.2178-0.18566-0.08569-0.1214-0.12854-0.29278-0.03927-0.17495-0.03927-0.39632 0-0.48201 0.18923-0.71052 0.1928-0.23208 0.532-0.23208 0.11425 0 0.22137 0.0357 0.10711 0.03213 0.18923 0.11425 0.08569 0.07855 0.13568 0.21423 0.05356 0.13211 0.05356 0.33562 0 0.07855-0.01071 0.16781-0.0071 0.08926-0.02499 0.1928h-0.86763q0.0071 0.22137 0.09283 0.33919 0.08569 0.11782 0.27492 0.11782 0.11425 0 0.20708-0.0357 0.0964-0.0357 0.14639-0.07498zm-0.55699-1.3389q-0.13568 0-0.20352 0.11068-0.06784 0.10711-0.07855 0.30349h0.49272q0.01071-0.20352-0.04284-0.30706-0.05356-0.10711-0.16781-0.10711z"/>
+                               <path d="m-37.075 47.068h0.19637v-0.33562l0.42488-0.13211v0.46773h0.34633v0.37847h-0.34633v0.77836q0 0.15353 0.02856 0.2178 0.03213 0.06427 0.11068 0.06427 0.05356 0 0.0964-0.01071t0.09283-0.03213l0.05356 0.33919q-0.07855 0.03928-0.18209 0.06427-0.10354 0.02856-0.2178 0.02856-0.20352 0-0.30706-0.11782-0.09997-0.11782-0.09997-0.39632v-0.93546h-0.19637z"/>
+                               <path d="m-35.297 48.379q0-0.07498-0.04999-0.12496-0.04641-0.05356-0.1214-0.0964-0.07498-0.04642-0.16067-0.09283-0.08212-0.04641-0.1571-0.11425-0.07498-0.06784-0.12496-0.16424-0.04642-0.0964-0.04642-0.24279 0-0.24993 0.13568-0.38561 0.13568-0.13568 0.39989-0.13568 0.1571 0 0.29635 0.0357 0.13925 0.03213 0.22137 0.08212l-0.09997 0.32848q-0.06784-0.02856-0.16424-0.05356-0.0964-0.02856-0.18923-0.02856-0.17495 0-0.17495 0.14639 0 0.06784 0.04642 0.11425 0.04999 0.04284 0.12497 0.08569 0.07498 0.04284 0.1571 0.08926 0.08569 0.04642 0.16067 0.11782 0.07498 0.06784 0.1214 0.16781 0.04999 0.09997 0.04999 0.24636 0 0.24636-0.14996 0.39632-0.14996 0.14996-0.4463 0.14996-0.14639 0-0.2892-0.0357-0.13925-0.0357-0.22494-0.09283l0.11782-0.34276q0.07498 0.04285 0.17138 0.07498 0.09997 0.03213 0.20708 0.03213 0.08212 0 0.13568-0.0357 0.05356-0.03928 0.05356-0.1214z"/>
+                               <g stroke-width=".4463">
+                                       <path d="m-34.662 48.693q0-0.09997 0.04642-0.14996 0.04999-0.04999 0.13211-0.04999 0.08212 0 0.12854 0.04999 0.04999 0.04999 0.04999 0.14996 0 0.10354-0.04999 0.15353-0.04642 0.04999-0.12854 0.04999-0.08212 0-0.13211-0.04999-0.04642-0.04999-0.04642-0.15353z"/>
+                                       <path d="m-34.035 47.961q0-0.48201 0.16424-0.70695 0.16781-0.22851 0.47487-0.22851 0.32848 0 0.48201 0.23208 0.1571 0.23208 0.1571 0.70338 0 0.48558-0.16781 0.71052-0.16781 0.22494-0.4713 0.22494-0.32848 0-0.48558-0.23208-0.15353-0.23208-0.15353-0.70338zm0.26778 0q0 0.1571 0.01785 0.28564 0.02142 0.12854 0.06427 0.22137 0.04642 0.09283 0.11782 0.14639 0.07141 0.04999 0.17138 0.04999 0.18566 0 0.2785-0.16424 0.09283-0.16781 0.09283-0.53914 0-0.15353-0.02142-0.28206-0.01785-0.13211-0.06427-0.22494-0.04285-0.09283-0.11425-0.14282-0.07141-0.05356-0.17138-0.05356-0.18209 0-0.27849 0.16781-0.09283 0.16781-0.09283 0.53557z"/>
+                                       <path d="m-32.415 47.068h0.18209l0.04642 0.18923h0.01071q0.04999-0.10354 0.12854-0.16067 0.08212-0.0607 0.19637-0.0607 0.08212 0 0.18566 0.03213l-0.04999 0.26064q-0.09283-0.03213-0.16424-0.03213-0.11426 0-0.18566 0.06784-0.07141 0.06427-0.09283 0.17495v1.3139h-0.25707z"/>
+                                       <path d="m-30.314 48.936q0 0.34633-0.15353 0.51057-0.15353 0.16424-0.4463 0.16424-0.17852 0-0.29278-0.03213-0.11425-0.02856-0.18566-0.06784l0.07498-0.22137q0.07141 0.03213 0.1571 0.0607 0.08569 0.02856 0.21066 0.02856 0.2178 0 0.29635-0.1214 0.08212-0.1214 0.08212-0.40703v-0.13211h-0.01071q-0.05713 0.08212-0.14639 0.12854-0.08926 0.04641-0.22851 0.04641-0.28921 0-0.42488-0.22137-0.13568-0.22494-0.13568-0.70338 0-0.46059 0.17495-0.69624 0.17852-0.23565 0.52486-0.23565 0.16781 0 0.2892 0.03213t0.21423 0.07498zm-0.25707-1.6103q-0.10711-0.05713-0.27492-0.05713-0.18209 0-0.29278 0.16781-0.11068 0.16424-0.11068 0.52842 0 0.14996 0.01785 0.27849 0.01785 0.12497 0.0607 0.22137 0.04285 0.09283 0.10711 0.14639 0.06784 0.04999 0.16424 0.04999 0.13568 0 0.21423-0.07141t0.11425-0.21423z"/>
+                               </g>
+                       </g>
+                       <g transform="matrix(4.0081 0 0 4.0081 -210.57 -224.31)" stroke-width=".20131" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="lightweight, portable C library">
+                               <path d="m-49.147 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-48.878 49.946h0.11596v0.80524h-0.11596zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-48.041 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04992 0.07408-0.04992 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-47.441 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04832-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-47.226 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-46.272 49.946 0.14333 0.47026 0.02899 0.15461h0.0032l0.02416-0.15783 0.10951-0.46704h0.10951l-0.21418 0.82295h-0.06603l-0.16266-0.52824-0.02255-0.13528h-0.0032l-0.02255 0.13689-0.15783 0.52662h-0.06603l-0.22064-0.82295h0.12401l0.12401 0.46865 0.01933 0.15622h0.0032l0.02899-0.15944 0.13206-0.46543z"/>
+                               <path d="m-45.286 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05315-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08535-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-45.084 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-44.247 50.789q0 0.15622-0.06925 0.2303-0.06925 0.07408-0.20131 0.07408-0.08052 0-0.13206-0.0145-0.05153-0.01288-0.08374-0.0306l0.03382-0.09985q0.03221 0.01449 0.07086 0.02738 0.03865 0.01288 0.09502 0.01288 0.09824 0 0.13367-0.05476 0.03704-0.05476 0.03704-0.18359v-0.05959h-0.0048q-0.02577 0.03704-0.06603 0.05798t-0.10307 0.02094q-0.13045 0-0.19165-0.09985-0.0612-0.10146-0.0612-0.31726 0-0.20775 0.07891-0.31404 0.08052-0.10629 0.23674-0.10629 0.07569 0 0.13045 0.01449 0.05476 0.01449 0.09663 0.03382zm-0.11595-0.72632q-0.04831-0.02577-0.12401-0.02577-0.08213 0-0.13206 0.07569-0.04993 0.07408-0.04993 0.23835 0 0.06764 0.0081 0.12562 0.0081 0.05637 0.02738 0.09985 0.01933 0.04187 0.04831 0.06603 0.0306 0.02255 0.07408 0.02255 0.0612 0 0.09663-0.03221t0.05154-0.09663z"/>
+                               <path d="m-43.647 50.752v-0.48958q0-0.11273-0.02738-0.17071-0.02577-0.05959-0.10468-0.05959-0.05637 0-0.10307 0.04026-0.04509 0.04026-0.0612 0.10146v0.57816h-0.11595v-1.1273h0.11595v0.39779h0.0048q0.03221-0.04187 0.07891-0.06764 0.04831-0.02738 0.11918-0.02738 0.05315 0 0.0918 0.0145 0.04026 0.01449 0.06603 0.04992t0.03865 0.09502q0.01288 0.05798 0.01288 0.14494v0.52018z"/>
+                               <path d="m-43.432 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01771 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-42.945 50.682q0-0.04026 0.02255-0.06442 0.02416-0.02416 0.0612-0.02416 0.04187 0 0.06764 0.03382 0.02738 0.03382 0.02738 0.10629 0 0.05315-0.01449 0.09502-0.01288 0.04348-0.03543 0.07569-0.02094 0.03221-0.0467 0.05315-0.02577 0.02094-0.04993 0.0306l-0.04026-0.05476q0.02094-0.01127 0.03865-0.0306 0.01933-0.01772 0.0306-0.04026 0.01288-0.02255 0.01933-0.04831 0.0064-0.02416 0.0064-0.04831-0.03221 0.0097-0.05959-0.01288-0.02738-0.02255-0.02738-0.07086z"/>
+                               <path d="m-42.373 49.946h0.08213l0.01771 0.08697h0.0064q0.05959-0.10629 0.18682-0.10629 0.12723 0 0.19004 0.09502 0.06442 0.09502 0.06442 0.31082 0 0.10146-0.02094 0.18359-0.02094 0.08052-0.05959 0.1385-0.03865 0.05637-0.09502 0.08696-0.05476 0.02899-0.1224 0.02899-0.0467 0-0.07408-0.0064-0.02738-0.0048-0.05959-0.02255v0.33176h-0.11595zm0.11595 0.67801q0.02255 0.01933 0.04992 0.0306 0.02899 0.01127 0.07569 0.01127 0.08535 0 0.13528-0.08697 0.04992-0.08696 0.04992-0.24801 0-0.06764-0.0097-0.1224-0.0081-0.05476-0.02738-0.09341-0.01933-0.04026-0.04992-0.0612-0.02899-0.02255-0.07247-0.02255-0.11756 0-0.15138 0.14333z"/>
+                               <path d="m-41.707 50.349q0-0.21742 0.07408-0.31887 0.07569-0.10307 0.21419-0.10307 0.14816 0 0.21741 0.10468 0.07086 0.10468 0.07086 0.31726 0 0.21902-0.07569 0.32048t-0.21258 0.10146q-0.14816 0-0.21902-0.10468-0.06925-0.10468-0.06925-0.31726zm0.12079 0q0 0.07086 0.0081 0.12884 0.0097 0.05798 0.02899 0.09985 0.02094 0.04187 0.05315 0.06603 0.03221 0.02255 0.0773 0.02255 0.08375 0 0.12562-0.07408 0.04187-0.07569 0.04187-0.24318 0-0.06925-0.0097-0.12723-0.0081-0.05959-0.02899-0.10146-0.01933-0.04187-0.05154-0.06442-0.03221-0.02416-0.0773-0.02416-0.08213 0-0.12562 0.07569-0.04187 0.07569-0.04187 0.24157z"/>
+                               <path d="m-40.977 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-40.579 49.946h0.09824v-0.15944l0.11595-0.03704v0.19648h0.17393v0.10468h-0.17393v0.47992q0 0.07086 0.01611 0.10307 0.01772 0.0306 0.05637 0.0306 0.03221 0 0.05476-0.0064 0.02416-0.0081 0.05154-0.01933l0.02255 0.0918q-0.03543 0.01772-0.07891 0.02738-0.04187 0.01127-0.08858 0.01127-0.08052 0-0.11595-0.05154-0.03382-0.05315-0.03382-0.17071v-0.49603h-0.09824z"/>
+                               <path d="m-40.063 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08697 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.01611 0 0.03221 0 0.01611 0 0.03382 0.0016 0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.0161-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08213 0.03543 0.06764 0 0.10468-0.03221 0.03704-0.03221 0.05153-0.07086z"/>
+                               <path d="m-39.41 49.624h0.11595v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08374 0.3237-0.08214 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11595 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08375 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-38.588 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-37.856 50.697q-0.03865 0.03543-0.09824 0.05476t-0.12562 0.01933q-0.07569 0-0.13206-0.02899-0.05476-0.0306-0.0918-0.08535-0.03543-0.05637-0.05314-0.13367-0.01611-0.0773-0.01611-0.17393 0-0.20614 0.07569-0.31404t0.21419-0.1079q0.04509 0 0.08858 0.01127 0.04509 0.01127 0.08052 0.04509t0.05637 0.09502q0.02255 0.0612 0.02255 0.15944 0 0.02738-0.0032 0.05959-0.0016 0.0306-0.0048 0.06442h-0.40906q0 0.06925 0.01127 0.12562 0.01127 0.05637 0.03543 0.09663 0.02416 0.03865 0.0612 0.0612 0.03865 0.02094 0.09502 0.02094 0.04348 0 0.08536-0.0161 0.04348-0.01611 0.06603-0.03865zm-0.09019-0.43161q0.0032-0.12079-0.03382-0.17715-0.03704-0.05637-0.10146-0.05637-0.07408 0-0.11756 0.05637-0.04348 0.05637-0.05154 0.17715z"/>
+                               <path d="m-36.734 50.708q-0.04026 0.03382-0.10146 0.04831t-0.12884 0.01449q-0.08535 0-0.15783-0.03221-0.07247-0.03221-0.12562-0.10146-0.05154-0.07086-0.08052-0.18198-0.02899-0.11112-0.02899-0.26734 0-0.16105 0.03221-0.27217 0.03382-0.11112 0.08858-0.18037t0.12562-0.09985q0.07247-0.0306 0.14816-0.0306 0.0773 0 0.12723 0.01127 0.05154 0.01127 0.08858 0.02738l-0.02899 0.10951q-0.03221-0.01772-0.07569-0.02738-0.04348-0.0097-0.09985-0.0097t-0.10629 0.02577q-0.04993 0.02416-0.08858 0.08052-0.03865 0.05476-0.0612 0.14494-0.02255 0.09019-0.02255 0.22064 0 0.23513 0.08052 0.3543 0.08052 0.11756 0.21419 0.11756 0.05476 0 0.09824-0.01449 0.04348-0.0161 0.07408-0.03704z"/>
+                               <path d="m-36.176 50.56q0 0.05637 0.01449 0.08052 0.0161 0.02416 0.04348 0.02416 0.03382 0 0.07891-0.01772l0.01127 0.09341q-0.02094 0.01288-0.05959 0.02094-0.03704 0.0081-0.06764 0.0081-0.0612 0-0.09985-0.03704-0.03704-0.03865-0.03704-0.13367v-0.97434h0.11595z"/>
+                               <path d="m-35.906 49.946h0.11595v0.80524h-0.11595zm-0.02094-0.24479q0-0.03865 0.02094-0.06281 0.02255-0.02416 0.05798-0.02416t0.05798 0.02416q0.02416 0.02255 0.02416 0.06281 0 0.03865-0.02416 0.0612-0.02255 0.02094-0.05798 0.02094t-0.05798-0.02255q-0.02094-0.02255-0.02094-0.05959z"/>
+                               <path d="m-35.576 49.624h0.11596v0.38329h0.0048q0.06603-0.08052 0.17554-0.08052 0.12401 0 0.1852 0.09824 0.06281 0.09824 0.06281 0.31082 0 0.21741-0.08375 0.3237-0.08213 0.10629-0.23352 0.10629-0.07408 0-0.13528-0.0161-0.0612-0.01772-0.0918-0.04026zm0.11596 1.0098q0.02255 0.01288 0.05476 0.02094 0.03382 0.0064 0.07086 0.0064 0.08374 0 0.13206-0.07891 0.04993-0.08052 0.04993-0.2464 0-0.06925-0.0097-0.12401-0.0081-0.05637-0.02738-0.09663-0.01771-0.04026-0.04831-0.0612-0.02899-0.02255-0.07086-0.02255-0.05798 0-0.09663 0.03543-0.03704 0.03382-0.05476 0.09341z"/>
+                               <path d="m-34.878 49.946h0.08213l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-34.446 49.995q0.0467-0.02899 0.11273-0.04509 0.06764-0.01611 0.14172-0.01611 0.06764 0 0.1079 0.02094 0.04187 0.01933 0.06442 0.05476 0.02416 0.03382 0.0306 0.07891 0.0081 0.04348 0.0081 0.0918 0 0.09663-0.0048 0.18842-0.0032 0.0918-0.0032 0.17393 0 0.0612 0.0032 0.11434 0.0048 0.05154 0.01611 0.09824h-0.08858l-0.02738-0.09502h-0.0064q-0.02416 0.04187-0.07086 0.07247t-0.12562 0.0306q-0.08696 0-0.14333-0.05959-0.05476-0.0612-0.05476-0.16749 0-0.06925 0.02255-0.11596 0.02416-0.0467 0.06603-0.07569 0.04348-0.02899 0.10146-0.04026 0.05959-0.01288 0.13206-0.01288 0.0161 0 0.03221 0t0.03382 0.0016q0.0048-0.04993 0.0048-0.08858 0-0.0918-0.02738-0.12884-0.02738-0.03704-0.09985-0.03704-0.04509 0-0.09824 0.01449-0.05315 0.01288-0.08858 0.03382zm0.34947 0.38974q-0.01611-0.0016-0.03221-0.0016-0.01611-0.0016-0.03221-0.0016-0.03865 0-0.07569 0.0064t-0.06603 0.02255q-0.02899 0.0161-0.0467 0.04348-0.01611 0.02738-0.01611 0.06925 0 0.06442 0.0306 0.09985 0.03221 0.03543 0.08214 0.03543 0.06764 0 0.10468-0.03221t0.05154-0.07086z"/>
+                               <path d="m-33.793 49.946h0.08214l0.02094 0.08535h0.0048q0.02255-0.0467 0.05798-0.07247 0.03704-0.02738 0.08858-0.02738 0.03704 0 0.08375 0.01449l-0.02255 0.11756q-0.04187-0.01449-0.07408-0.01449-0.05154 0-0.08374 0.0306-0.03221 0.02899-0.04187 0.07891v0.59265h-0.11595z"/>
+                               <path d="m-33.153 50.467 0.03382 0.15622h0.0081l0.02416-0.15622 0.1224-0.52018h0.11756l-0.19165 0.7231q-0.02255 0.08696-0.04509 0.16266-0.02255 0.07569-0.04992 0.13045-0.02577 0.05637-0.05959 0.08697-0.03221 0.03221-0.0773 0.03221t-0.07891-0.01449l0.01933-0.10951q0.02255 0.0081 0.04509 0.0032 0.02255-0.0048 0.04187-0.02738 0.02094-0.02255 0.03704-0.06764 0.01772-0.04348 0.0306-0.11434l-0.2609-0.80524h0.13206z"/>
+                       </g>
+                       <path d="m-435.92-23.597c0.28617-0.34918 0.57227-0.69834 0.85837-1.0475 0.42677 0.47526 0.85355 0.95052 1.2803 1.4258 0.76622 0.0048 1.5325 0.01002 2.2987 0.01443-0.82927-0.91657-1.6586-1.8331-2.4878-2.7497 0.40254-0.45586 0.80503-0.91173 1.2076-1.3676 0.78562 0.91658 1.5713 1.8332 2.3569 2.7497-4e-3 -0.87778-8e-3 -1.7556-0.0161-2.6333-0.40253-0.45101-0.80501-0.90202-1.2075-1.353 0.28858-0.42545 0.99829-0.86377 0.3475-1.2606-1.4591-1.6118-2.9183-3.2236-4.3774-4.8354-3.0679-0.01042-6.1393 0.04092-9.205-0.0084-0.72986-0.06429-1.6392-0.29547-1.8065-1.1337-0.35271-1.09 0.84574-2.3762 1.9465-1.8649 0.76081 0.14726 0.44105 1.6835-0.23166 1.1743 0.69856-1.0262-1.2808-0.90972-0.72049 0.09824 0.38397 0.88195 1.783 1.0275 2.3349 0.22513 0.57404-0.92504-0.20641-1.9788-1.0842-2.3446-0.87836-0.41949-1.9686-0.31147-2.7028 0.34337-1.0973 0.83626-1.6281 2.4707-0.91191 3.7193 0.4168 0.93386 1.3405 1.5318 2.3429 1.6481 1.343 0.16782 2.7026 0.06445 4.0539 0.09323h5.3734c1.0184 1.13 2.0368 2.2599 3.0553 3.3899-0.91656 1.0136-1.8331 2.0271-2.7497 3.0407-0.66422-0.85695-1.6664-1.5082-2.0708-2.5299-0.32706-1.1972 1.4194-2.1305 2.2518-1.2247 0.79933 0.44227-0.0473 1.8554-0.62433 1.0813 0.46733-0.15836 0.67752-0.90508-0.0577-0.86727-0.86169 0.32798-0.49311 1.6295 0.25772 1.8808 0.71628 0.34674 1.6137-0.30285 1.5227-1.0869 0.0733-1.1334-0.75524-2.3676-1.9525-2.4204-1.2813-0.24958-2.727 0.4999-3.0402 1.8142-0.43151 1.1314 0.27896 2.2662 1.0551 3.0447 0.91076 0.98537 1.8001 1.9916 2.7018 2.985z" fill="#f00"/>
+                       <path d="m-428.86-22.458c8e-3 -2.1947 0.012-4.3894 0.0201-6.5841-1.356-1.553-2.7839-3.046-4.0921-4.6391-0.4374-0.54095-0.77164-1.181-0.74606-1.8954-0.036-1.3281 0.79082-2.6298 2.0264-3.1348 0.95151-0.42136 2.0903-0.46194 3.022 0.03768 1.2998 0.66198 1.9155 2.4493 1.2087 3.7417-0.54185 0.79964-1.9325 0.78325-2.3809-0.10621-0.43247-0.56653-0.40691-1.7268 0.41575-1.8879 0.66914-0.01363 0.83223 0.96617 0.0962 1.0053-0.16353 0.63656 1.1345 0.49025 1.0924-0.18221 0.16593-0.92802-0.8623-1.6839-1.7291-1.5091-0.97624 0.09675-1.834 1.1261-1.4963 2.1064 0.35552 0.96342 1.2138 1.6073 1.8524 2.3761 1.0266 1.1181 2.05 2.2391 3.0765 3.3574-8e-3 2.445-0.012 4.89-0.0201 7.335-0.78189-0.0068-1.5639-0.01403-2.3458-0.02044z"/>
+                       <path d="m-429.09-21.883-6.584 0.02044c-1.5531-1.356-3.0461-2.7839-4.6392-4.092-0.54093-0.43739-1.181-0.77164-1.8954-0.74605-1.3281-0.03447-2.6298 0.79084-3.1348 2.0263-0.42133 0.95153-0.46193 2.0903 0.036 3.0221 0.66201 1.2998 2.4493 1.9155 3.7417 1.2087 0.79964-0.54184 0.78325-1.9325-0.10621-2.381-0.56654-0.43248-1.7268-0.40688-1.8879 0.41576-0.012 0.66918 0.96618 0.83223 1.0053 0.09607 0.63656-0.16373 0.49027 1.1345-0.18236 1.0924-0.92803 0.16585-1.6839-0.86229-1.5092-1.7291 0.0966-0.97624 1.1261-1.834 2.1064-1.4963 0.96341 0.35556 1.6073 1.2139 2.376 1.8524 1.1181 1.0266 2.2391 2.05 3.3574 3.0765l9.8442-0.02044c-1.143-0.9713-1.4343-1.4219-2.5296-2.3458z"/>
+               </g>
+       </g>
+</svg>
similarity index 90%
rename from test-server/lws-cgi-test.sh
rename to test-apps/lws-cgi-test.sh
index cc14af2..a40e2b8 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-echo -e -n "Content-type: text/html\x0d\x0a"
+echo -e -n "content-type: text/html\x0d\x0a"
+echo -e -n "transfer-encoding: chunked\x0d\x0a"
 echo -e -n "\x0d\x0a"
 
 echo "<html><body>"
diff --git a/test-apps/lws-common.js b/test-apps/lws-common.js
new file mode 100644 (file)
index 0000000..5d56ca2
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * This section around grayOut came from here:
+ * http://www.codingforums.com/archive/index.php/t-151720.html
+ * Assumed public domain
+ *
+ * Init like this in your main html script, this also reapplies the gray
+ *
+ *    lws_gray_out(true,{'zindex':'499'});
+ *
+ * To remove the gray
+ *
+ *    lws_gray_out(false);
+ *
+ */
+
+function gsize(ptype)
+{
+       var h = document.compatMode === "CSS1Compat" &&
+               !window.opera ?
+                       document.documentElement.clientHeight :
+                                               document.body.clientHeight;
+       var w = document.compatMode === "CSS1Compat" &&
+               !window.opera ? 
+                       document.documentElement.clientWidth :
+                                               document.body.clientWidth;
+       var pageWidth, pageHeight, t;
+
+       if (document.body && 
+                   (document.body.scrollWidth || document.body.scrollHeight)) {
+               t = document.body.scrollWidth;
+               pageWidth = (w > t) ? ("" + w + "px") : ("" + (t) + "px");
+               t = document.body.scrollHeight;
+               pageHeight = (h > t) ? ("" + h + "px") : ("" + (t) + "px");
+       } else if (document.body.offsetWidth) {
+               t = document.body.offsetWidth;
+               pageWidth = (w > t) ? ("" + w + "px") : ("" + (t) + "px");
+               t = document.body.offsetHeight;
+               pageHeight =(h > t) ? ("" + h + "px") : ("" + (t) + "px");
+       } else {
+               pageWidth = "100%";
+               pageHeight = "100%";
+       }
+       return (ptype === 1) ? pageWidth : pageHeight;
+}
+
+function addEvent( obj, type, fn ) {
+       if ( obj.attachEvent ) {
+               obj["e" + type + fn] = fn;
+               obj[type+fn] = function() { obj["e" + type + fn]( window.event );};
+               obj.attachEvent("on" + type, obj[type + fn]);
+       } else
+               obj.addEventListener(type, fn, false);
+}
+
+function removeEvent( obj, type, fn ) {
+       if ( obj.detachEvent ) {
+               obj.detachEvent("on" + type, obj[type + fn]);
+               obj[type + fn] = null;
+       } else
+               obj.removeEventListener(type, fn, false);
+}
+
+function lws_gray_out(vis, _options) {
+
+       var options = _options || {};
+       var zindex = options.zindex || 50;
+       var opacity = options.opacity || 70;
+       var opaque = (opacity / 100);
+       var bgcolor = options.bgcolor || "#000000";
+       var dark = document.getElementById("darkenScreenObject");
+
+       if (!dark) {
+               var tbody = document.getElementsByTagName("body")[0];
+               var tnode = document.createElement("div");
+               tnode.style.position = "absolute";
+               tnode.style.top = "0px";
+               tnode.style.left = "0px";
+               tnode.style.overflow = "hidden";
+               tnode.style.display ="none";
+               tnode.id = "darkenScreenObject";
+               tbody.appendChild(tnode);
+               dark = document.getElementById("darkenScreenObject");
+       }
+       if (vis) {
+               dark.style.opacity = opaque;
+               dark.style.MozOpacity = opaque;
+               // dark.style.filter ='alpha(opacity='+opacity+')';
+               dark.style.zIndex = zindex;
+               dark.style.backgroundColor = bgcolor;
+               dark.style.width = gsize(1);
+               dark.style.height = gsize(0);
+               dark.style.display = "block";
+               addEvent(window, "resize",
+                       function() {
+                               dark.style.height = gsize(0);
+                               dark.style.width = gsize(1);
+                       }
+               );
+       } else {
+               dark.style.display = "none";
+               removeEvent(window, "resize",
+                       function() {
+                               dark.style.height = gsize(0);
+                               dark.style.width = gsize(1);
+                       }
+               );
+       }
+}
+
+/*
+ * end of grayOut related stuff
+ */
+
+function new_ws(urlpath, protocol)
+{
+       if (typeof MozWebSocket != "undefined")
+               return new MozWebSocket(urlpath, protocol);
+
+       return new WebSocket(urlpath, protocol);
+}
+function lws_san(s)
+{
+       if (s.search("<") !== -1)
+               return "invalid string";
+       
+       return s;
+}
diff --git a/test-apps/lws-ssh-test-keys b/test-apps/lws-ssh-test-keys
new file mode 100644 (file)
index 0000000..2c409b2
--- /dev/null
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAp1oj/nPpEg+i5PgujQPSq7gKWuObG7feP3cU9GwiE5QAP/jw
+ZOpkdZcFoF2KMzjHax66sd+KYqg8l53OB2m6gmjpvxa2IZAjy/M2tujzGKrXkqLL
+sCr//+Yk+mVoTZ7DMBMQcLVejCKtDMQHlqsUZ2DVqTTPIjerBc5HRv2BXch6yAU4
+MMzO+zSwjJehhJCRK0QhaaQtksGMRmtwUa4FMF9y5FfB6TCGmBxaNUII+0nKph8E
+tke+Ujqotmjlg6So85wEYPSYMw1kkIuLHgTFdqC9AU8CQDUg3/qdLEC7GihiYaVa
+QUMktsFHsrhssrbqVsxJbkK2ozGaudoqLXKIL+QX3rpX00xWyT+JLhzrEj9JbENp
+aTA0ADUhxL18cxySwy1zPYFyc8CfmYkbekfm35j9Z3zdzIlrcXCGwWp1sECusUwi
+gXuMD2irWuRFOsLCz3mpDiFc6Hmjrvx7G7T2ZcF9DLv6FBXHysigIZZFNeTDgCBH
+1D6PGIedm1jJSJaWWuMVmyzBsNxq7Kzz3XLAGrsqr2OPzY41kG1oT9UrlH77+e9P
+TyIjlI1Sith1MJkdaLnIelzEBl3zEdYdVo73Pm5DbNe4AiK0JxYpQGSwt0ptzqYs
+/+CzvfR8OOPBtETNr0J9JiROOsBLvwddFOiX+BH6I4YdXZPOQQ9S0yG/jd0CAwEA
+AQKCAgAC3XY0SwO4fXAKf308iM44hmQW/kKPjOxPJdjD/n3u29/NOJPVBnZF1RoR
+jsho7BXt7Y7AsNULr1mqNtdqJRM+XFF0Jg1kMbWLLlTHeOGAkJw0NHlMQNA1L1l+
+t/G7MnahAhKL+27s80MHLuv6Vl95DZ1a0j6hlVZmOQvbWUe3tVD0z7IQk9EPV+2V
+2pq3TEpP9VClIFxvYMToB7raiyInm9q5sg7t0Rjczc91jfXdZ3wCsBFClaPagIqW
+5ODZCh6iXQ9uIYHhjd8k4l61WtuOll3mAdZGByLS8tVyBoGthvd4OH59E4szXce+
+dY3W2W7VoZW4P4gk7xp5CBUkxgsyz5yJR4rNKVV9JSDAGpfBGsoCFMwXjeq/u1JQ
+RRAViVmB8QYSoVmHf/f+IXYXuVbwzAZMoUf1DHjAG94bxyPi6tAshw79jDxJudnv
+L3mFVYrtRD+dXlihdrUX8UOBfnNd3oxA5FXHV8zOwhpZwr8rsfXLXKAtL/XPfiLj
+VbK9gAikS3dnVibioTMa8O8qUSRRb4zaqrV5bBgVvmNMNyP+bOXDBAf86sNt/blb
+Sf8P/yruWVOQTafhHtDbaEFgaDDZ7JabCiVu6Q2faBAVfnxho9ynL0AalM6fkrAP
+D2nWii4DeNKg4+yc9gDk9LlDX9SqLwND6wQYhpXhsH41ZDxvQQKCAQEAz2SoF0rO
+FvWAJElMLGtTIzxT8otABP5vWfhBzY+MtFo7iRlmMDEL+2fYYYK3PGzhSyNMwhAW
+QBrhqCZJc1DPtqubxeqkpnloHQvQy4YB8GeM6dAerj+/1k3Ox3tg2EaFaPuOOxhy
+krgr53K8+O6DFa3OMHVijPRKr0RgzNHJxkLcfaSrjCd27kPp68ndW79rt2hWgyCP
+P+97XEjBo7CegHcfOuZKcFtHVD9PyScP7Piv0wtuoCpi3/GyiBaQCpcyK02duQDs
+eFK9bLMtZvnKl0L+tLYjvyBjBjd6m3MuQR+4CH2mKB1zpdPftp2/yPjUTue3eGjB
+/hfy9ZEB3Qo+NQKCAQEAzpMRMztnvpYcPt3yhGGyqivtPL/2g6rsGwmpaTpWywF5
+1LfscLSpmJ0JYQsHn6Gpqcezsf/VOAUilE5L333ZFAfMOwi49YJ2b8Aok8tbt1+j
+fdl6o3weSyMTO/hBfR48Fk9tB1hZgh0u3xlYf+vRH/ilEFOgqhNmRVL19rr8lW1t
+8ZpHV/7lfX/2bLgW1g8ng+vS0OMe6ugdoRXVvJzUBccKqpksOTuUmmv7xyAKQoUe
+JbtOFRYKAeOo+eIeOxNbyl5kn7+Dh5P1049F+ERbiDSc20Q+8kyJoiVQbYVLJN2A
+2Mb7EUt/xolzQLnVjvTtE4LsN688g122+HfrLXmmCQKCAQBhqqZaKbk6KK0K6ZW8
+yWIiktN5wkgI0gWAWiAq/PInMOMeol50TXS2FWZaLWO7Sg8jAmGwdkD0OXSRak5m
+xuS6wsAeCW02lLAKFbljTx10qF888Oyx5IWkF4pMePbXgwZqtSR7Af1ayO6sFWWW
+2UPUHsCeI3mgpZ7SQSJQ8m7SNkR9yuGapC8m78amaq8a+N9yROmQ4PF1C4ONpxnB
+y3gpSW/knfTqSqIhs5sQQJwIXej3O0gCl1Nu4PTRj8aPpjpTGD8xk5TI6TYZjZvR
+Bct5RmyKj8fvxwG7OL89m5Vpx9Uz8nAgLhZ7Pnb5GfrqWvwomIjXZIYO8hpRuNMm
+1B8NAoIBAQCKqGDFOLy8StoOwL/GaCWa3/1P57I6UwJEa8nRHh2gCg+S3xnP1RR5
+of7nqpWlasgNdESD2CtwfNHnJl77Vufc8BcAESzFbpq9DAiwm7GmdoWxNceB8RAM
+czC38j1TFHZUq1+NrJn4IkqR6dtjkhA/G5EAUoHnZzogkj0TLhPY4SkJIPt+b1Pv
+V3M7Kp35dRabEDHjkG/yUXeB5rwe7E3Myvu34zSx/fITbSQFVtZMLDo+LWmN8csp
+1XxYrpSIJshYH9/+8ngBCynYpbTbnlaqKFaZP0fZL9K6ib1gpjX4Os3/tCBWTY0o
+4J4B9jsIyBJSJHEWN4Ow0bi9MxEi5yKxAoIBAQCJOAIDUxI/6FgXzpl3HBfbsRZS
+g0vI9pXRT5mAKx+ovVubXO7sJL1GlG9H4nKcVWTThzqyeskosBMF0RkKB7VAj2Ur
+pYFcVsrtM6U4wiSpiHXyPGSK6jevCJQZEQ8MKSg69HRTPFFXc39afXq1xqJVamtp
+5QjLttBeoidnzuKpNUdIqJzmehevipofB/lvXZ/e8CzNNh2ryW0gwFo8b+StuMou
+cIaynvwaVdMm5/4LsAwE08liLshMIxng6FNnxEMzZ6fiSFT1jq1qv54agqWfFEOJ
+57g8A/HpVKmSQXFyq1QEdKlA2vttuavq3iIW2OpUnxAnk8ZPo9oRoR8YQ7PH
+-----END RSA PRIVATE KEY-----
diff --git a/test-apps/lws-ssh-test-keys.pub b/test-apps/lws-ssh-test-keys.pub
new file mode 100644 (file)
index 0000000..ea854fe
--- /dev/null
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnWiP+c+kSD6Lk+C6NA9KruApa45sbt94/dxT0bCITlAA/+PBk6mR1lwWgXYozOMdrHrqx34piqDyXnc4HabqCaOm/FrYhkCPL8za26PMYqteSosuwKv//5iT6ZWhNnsMwExBwtV6MIq0MxAeWqxRnYNWpNM8iN6sFzkdG/YFdyHrIBTgwzM77NLCMl6GEkJErRCFppC2SwYxGa3BRrgUwX3LkV8HpMIaYHFo1Qgj7ScqmHwS2R75SOqi2aOWDpKjznARg9JgzDWSQi4seBMV2oL0BTwJANSDf+p0sQLsaKGJhpVpBQyS2wUeyuGyytupWzEluQrajMZq52iotcogv5BfeulfTTFbJP4kuHOsSP0lsQ2lpMDQANSHEvXxzHJLDLXM9gXJzwJ+ZiRt6R+bfmP1nfN3MiWtxcIbBanWwQK6xTCKBe4wPaKta5EU6wsLPeakOIVzoeaOu/HsbtPZlwX0Mu/oUFcfKyKAhlkU15MOAIEfUPo8Yh52bWMlIlpZa4xWbLMGw3GrsrPPdcsAauyqvY4/NjjWQbWhP1SuUfvv5709PIiOUjVKK2HUwmR1ouch6XMQGXfMR1h1Wjvc+bkNs17gCIrQnFilAZLC3Sm3Opiz/4LO99Hw448G0RM2vQn0mJE46wEu/B10U6Jf4Efojhh1dk85BD1LTIb+N3Q== agreen@build
similarity index 70%
rename from test-server/test-client.c
rename to test-apps/test-client.c
index 4575ee0..6a3764e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets-test-client - libwebsockets test implementation
  *
- * Copyright (C) 2011-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
@@ -22,7 +22,9 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
 #include <getopt.h>
+#endif
 #include <string.h>
 #include <signal.h>
 
 #include <unistd.h>
 #endif
 
-#include "../lib/libwebsockets.h"
+#include <libwebsockets.h>
+
+struct lws_poly_gen {
+       uint32_t cyc[2];
+};
+
+#define block_size (3 * 4096)
 
-static int deny_deflate, longlived, mirror_lifetime, test_post;
+static int deny_deflate, longlived, mirror_lifetime, test_post, once;
 static struct lws *wsi_dumb, *wsi_mirror;
 static struct lws *wsi_multi[3];
 static volatile int force_exit;
 static unsigned int opts, rl_multi[3];
-static int flag_no_mirror_traffic;
+static int flag_no_mirror_traffic, justmirror, flag_echo;
+static uint32_t count_blocks = 1024, txb, rxb, rx_count, errs;
+static struct lws_poly_gen tx = { { 0xabcde, 0x23456789 } },
+                          rx = { { 0xabcde, 0x23456789 } }
+;
 
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
 char crl_path[1024] = "";
 #endif
 
@@ -70,6 +82,19 @@ enum demo_protocols {
        DEMO_PROTOCOL_COUNT
 };
 
+static uint8_t
+lws_poly_rand(struct lws_poly_gen *p)
+{
+       p->cyc[0] = (p->cyc[0] & 1) ? (p->cyc[0] >> 1) ^ 0xb4bcd35c :
+                                     p->cyc[0] >> 1;
+       p->cyc[0] = (p->cyc[0] & 1) ? (p->cyc[0] >> 1) ^ 0xb4bcd35c :
+                                     p->cyc[0] >> 1;
+       p->cyc[1] = (p->cyc[1] & 1) ? (p->cyc[1] >> 1) ^ 0x7a5bc2e3 :
+                                     p->cyc[1] >> 1;
+
+       return p->cyc[0] ^ p->cyc[1];
+}
+
 static void show_http_content(const char *p, size_t l)
 {
        if (lwsl_visible(LLL_INFO)) {
@@ -93,6 +118,9 @@ static int
 callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                        void *user, void *in, size_t len)
 {
+#if defined(LWS_WITH_TLS)
+       union lws_tls_cert_info_results ci;
+#endif
        const char *which = "http";
        char which_wsi[10], buf[50 + LWS_PRE];
        int n;
@@ -125,7 +153,7 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                        wsi_mirror = NULL;
                }
 
-               for (n = 0; n < ARRAY_SIZE(wsi_multi); n++)
+               for (n = 0; n < (int)LWS_ARRAY_SIZE(wsi_multi); n++)
                        if (wsi == wsi_multi[n]) {
                                sprintf(which_wsi, "multi %d", n);
                                which = which_wsi;
@@ -137,7 +165,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                break;
 
        case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
-               if ((strcmp((const char *)in, "deflate-stream") == 0) && deny_deflate) {
+               if ((strcmp((const char *)in, "deflate-stream") == 0) &&
+                   deny_deflate) {
                        lwsl_notice("denied deflate-stream extension\n");
                        return 1;
                }
@@ -150,6 +179,26 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
        case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
                lwsl_notice("lws_http_client_http_response %d\n",
                                lws_http_client_http_response(wsi));
+#if defined(LWS_WITH_TLS)
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME,
+                                           &ci, sizeof(ci.ns.name)))
+                       lwsl_notice(" Peer Cert CN        : %s\n", ci.ns.name);
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_ISSUER_NAME,
+                                           &ci, sizeof(ci.ns.name)))
+                       lwsl_notice(" Peer Cert issuer    : %s\n", ci.ns.name);
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_FROM,
+                                           &ci, 0))
+                       lwsl_notice(" Peer Cert Valid from: %s", ctime(&ci.time));
+
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_TO,
+                                           &ci, 0))
+                       lwsl_notice(" Peer Cert Valid to  : %s", ctime(&ci.time));
+               if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_USAGE,
+                                           &ci, 0))
+                       lwsl_notice(" Peer Cert usage bits: 0x%x\n", ci.usage);
+#endif
                break;
 
        /* chunked content */
@@ -197,7 +246,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                                return -1;
                        if (lws_add_http_header_by_token(wsi,
                                        WSI_TOKEN_HTTP_CONTENT_TYPE,
-                                       (unsigned char *)"application/x-www-form-urlencoded", 33, p, end))
+                                       (unsigned char *)"application/x-www-form-urlencoded",
+                                       33, p, end))
                                return -1;
 
                        /* inform lws we have http body to send */
@@ -208,7 +258,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 
        case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
                strcpy(buf + LWS_PRE, "text=hello&send=Send+the+form");
-               n = lws_write(wsi, (unsigned char *)&buf[LWS_PRE], strlen(&buf[LWS_PRE]), LWS_WRITE_HTTP);
+               n = lws_write(wsi, (unsigned char *)&buf[LWS_PRE],
+                             strlen(&buf[LWS_PRE]), LWS_WRITE_HTTP);
                if (n < 0)
                        return -1;
                /* we only had one thing to send, so inform lws we are done
@@ -223,7 +274,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                force_exit = 1;
                break;
 
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) && \
+       !defined(LWS_WITH_MBEDTLS)
        case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
                if (crl_path[0]) {
                        /* Enable CRL checking of the server certificate */
@@ -231,13 +283,17 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
                        X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
                        SSL_CTX_set1_param((SSL_CTX*)user, param);
                        X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user);
-                       X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
-                       int n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM);
+                       X509_LOOKUP *lookup = X509_STORE_add_lookup(store,
+                                                       X509_LOOKUP_file());
+                       int n = X509_load_cert_crl_file(lookup, crl_path,
+                                                       X509_FILETYPE_PEM);
                        X509_VERIFY_PARAM_free(param);
                        if (n != 1) {
                                char errbuf[256];
                                n = ERR_get_error();
-                               lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n);
+                               lwsl_err("EXTRA_CLIENT_VERIFY_CERTS: "
+                                        "SSL error: %s (%d)\n",
+                                        ERR_error_string(n, errbuf), n);
                                return 1;
                        }
                }
@@ -259,7 +315,7 @@ static int
 callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
                    void *user, void *in, size_t len)
 {
-       unsigned char buf[LWS_PRE + 4096];
+       unsigned char buf[LWS_PRE + block_size], *p;
        unsigned int rands[4];
        int l = 0;
        int n;
@@ -269,13 +325,23 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
 
                lwsl_notice("mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
 
+               if (flag_echo) {
+                       rxb = txb = 0;
+                       rx.cyc[0] = tx.cyc[0] = 0xabcde;
+                       rx.cyc[1] = tx.cyc[1] = 0x23456789;
+
+                       lws_callback_on_writable(wsi);
+
+                       break;
+               }
+
                lws_get_random(lws_get_context(wsi), rands, sizeof(rands[0]));
                mirror_lifetime = 16384 + (rands[0] & 65535);
                /* useful to test single connection stability */
                if (longlived)
                        mirror_lifetime += 500000;
 
-               lwsl_info("opened mirror connection with "
+               lwsl_notice("opened mirror connection with "
                          "%d lifetime\n", mirror_lifetime);
 
                /*
@@ -291,16 +357,44 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
                        lws_callback_on_writable(wsi);
                break;
 
-       case LWS_CALLBACK_CLOSED:
-               lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime);
+       case LWS_CALLBACK_CLIENT_CLOSED:
+               lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d, "
+                           "rxb %d, rx_count %d\n", mirror_lifetime, rxb,
+                           rx_count);
                wsi_mirror = NULL;
+               if (flag_echo || once)
+                       force_exit = 1;
                break;
 
        case LWS_CALLBACK_CLIENT_WRITEABLE:
+               lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE\n");
                if (flag_no_mirror_traffic)
                        return 0;
+
+               if (flag_echo) {
+                       for (n = 0; n < (int)block_size; n++)
+                               buf[LWS_PRE + n] = lws_poly_rand(&tx);
+
+                       n = lws_write(wsi, &buf[LWS_PRE], block_size,
+                                     opts | LWS_WRITE_TEXT);
+                       if (n < 0) {
+                               lwsl_err("Error sending\n");
+                               return -1;
+                       }
+
+                       txb++;
+                       if (txb != count_blocks)
+                               lws_callback_on_writable(wsi);
+                       else {
+                               lwsl_notice("send completed: %d x %d\n",
+                                           count_blocks, block_size);
+                       }
+                       break;
+               }
+
                for (n = 0; n < 1; n++) {
-                       lws_get_random(lws_get_context(wsi), rands, sizeof(rands));
+                       lws_get_random(lws_get_context(wsi), rands,
+                                      sizeof(rands));
                        l += sprintf((char *)&buf[LWS_PRE + l],
                                        "c #%06X %u %u %u;",
                                        rands[0] & 0xffffff,    /* colour */
@@ -317,16 +411,43 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
                        lwsl_err("Partial write LWS_CALLBACK_CLIENT_WRITEABLE\n");
                        return -1;
                }
-
-               mirror_lifetime--;
+               if (!justmirror)
+                       mirror_lifetime--;
                if (!mirror_lifetime) {
-                       lwsl_info("closing mirror session\n");
+                       lwsl_notice("closing mirror session\n");
                        return -1;
                }
                /* get notified as soon as we can write again */
                lws_callback_on_writable(wsi);
+
+#if !defined(_WIN32) && !defined(WIN32)
+               usleep(50);
+#endif
                break;
 
+       case LWS_CALLBACK_CLIENT_RECEIVE:
+               if (flag_echo) {
+                       p = (unsigned char *)in;
+                       for (n = 0; n < (int)len; n++)
+                               if (*p++ != lws_poly_rand(&rx)) {
+                                       lwsl_err("mismatch at rxb %d offset %d\n", rxb + (n / block_size), n % block_size);
+                                       errs++;
+                                       force_exit = 1;
+                                       return -1;
+                               }
+                       rx_count += (unsigned int)(unsigned long long)len;
+                       while (rx_count >= block_size) {
+                               rx_count -= block_size;
+                               rxb++;
+                       }
+                       if (rx_count == 0 && rxb == count_blocks) {
+                               lwsl_notice("Everything received: errs %d\n",
+                                           errs);
+                               force_exit = 1;
+                               return -1;
+                       }
+               }
+               break;
        default:
                break;
        }
@@ -376,7 +497,7 @@ static const struct lws_protocols protocols[] = {
                "lws-mirror-protocol",
                callback_lws_mirror,
                0,
-               128,
+               4096,
        }, {
                "lws-test-raw-client",
                callback_test_raw_client,
@@ -407,6 +528,7 @@ void sighandler(int sig)
        force_exit = 1;
 }
 
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
 static struct option options[] = {
        { "help",       no_argument,            NULL, 'h' },
        { "debug",      required_argument,      NULL, 'd' },
@@ -415,19 +537,23 @@ static struct option options[] = {
        { "strict-ssl", no_argument,            NULL, 'S' },
        { "version",    required_argument,      NULL, 'v' },
        { "undeflated", no_argument,            NULL, 'u' },
+       { "echo",       no_argument,            NULL, 'e' },
        { "multi-test", no_argument,            NULL, 'm' },
        { "nomirror",   no_argument,            NULL, 'n' },
+       { "justmirror", no_argument,            NULL, 'j' },
        { "longlived",  no_argument,            NULL, 'l' },
        { "post",       no_argument,            NULL, 'o' },
+       { "once",       no_argument,            NULL, 'O' },
        { "pingpong-secs", required_argument,   NULL, 'P' },
        { "ssl-cert",  required_argument,       NULL, 'C' },
        { "ssl-key",  required_argument,        NULL, 'K' },
        { "ssl-ca",  required_argument,         NULL, 'A' },
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
        { "ssl-crl",  required_argument,                NULL, 'R' },
 #endif
        { NULL, 0, 0, 0 }
 };
+#endif
 
 static int ratelimit_connects(unsigned int *last, unsigned int secs)
 {
@@ -446,7 +572,8 @@ static int ratelimit_connects(unsigned int *last, unsigned int secs)
 int main(int argc, char **argv)
 {
        int n = 0, m, ret = 0, port = 7681, use_ssl = 0, ietf_version = -1;
-       unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0, do_multi = 0;
+       unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0,
+                    do_multi = 0;
        struct lws_context_creation_info info;
        struct lws_client_connect_info i;
        struct lws_context *context;
@@ -455,17 +582,22 @@ int main(int argc, char **argv)
        char cert_path[1024] = "";
        char key_path[1024] = "";
        char ca_path[1024] = "";
+       unsigned long last = lws_now_secs();
 
        memset(&info, 0, sizeof info);
 
        lwsl_notice("libwebsockets test client - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
+       lwsl_notice("(C) Copyright 2010-2018 Andy Green <andy@warmcat.com>\n");
 
        if (argc < 2)
                goto usage;
 
        while (n >= 0) {
-               n = getopt_long(argc, argv, "Snuv:hsp:d:lC:K:A:P:mo", options, NULL);
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
+       n = getopt_long(argc, argv, "Sjnuv:hsp:d:lC:K:A:P:moeO", options, NULL);
+#else
+       n = getopt(argc, argv, "Sjnuv:hsp:d:lC:K:A:P:moeO");
+#endif
                if (n < 0)
                        continue;
                switch (n) {
@@ -483,10 +615,16 @@ int main(int argc, char **argv)
                case 'p':
                        port = atoi(optarg);
                        break;
+               case 'e':
+                       flag_echo = 1;
+                       break;
                case 'P':
                        pp_secs = atoi(optarg);
                        lwsl_notice("Setting pingpong interval to %d\n", pp_secs);
                        break;
+               case 'j':
+                       justmirror = 1;
+                       break;
                case 'l':
                        longlived = 1;
                        break;
@@ -502,27 +640,26 @@ int main(int argc, char **argv)
                case 'o':
                        test_post = 1;
                        break;
+               case 'O':
+                       once = 1;
+                       break;
                case 'n':
                        flag_no_mirror_traffic = 1;
                        lwsl_notice("Disabled sending mirror data (for pingpong testing)\n");
                        break;
                case 'C':
-                       strncpy(cert_path, optarg, sizeof(cert_path) - 1);
-                       cert_path[sizeof(cert_path) - 1] = '\0';
+                       lws_strncpy(cert_path, optarg, sizeof(cert_path));
                        break;
                case 'K':
-                       strncpy(key_path, optarg, sizeof(key_path) - 1);
-                       key_path[sizeof(key_path) - 1] = '\0';
+                       lws_strncpy(key_path, optarg, sizeof(key_path));
                        break;
                case 'A':
-                       strncpy(ca_path, optarg, sizeof(ca_path) - 1);
-                       ca_path[sizeof(ca_path) - 1] = '\0';
+                       lws_strncpy(ca_path, optarg, sizeof(ca_path));
                        break;
 
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
                case 'R':
-                       strncpy(crl_path, optarg, sizeof(crl_path) - 1);
-                       crl_path[sizeof(crl_path) - 1] = '\0';
+                       lws_strncpy(crl_path, optarg, sizeof(crl_path));
                        break;
 #endif
                case 'h':
@@ -542,10 +679,12 @@ int main(int argc, char **argv)
                goto usage;
 
        /* add back the leading / on path */
-       path[0] = '/';
-       strncpy(path + 1, p, sizeof(path) - 2);
-       path[sizeof(path) - 1] = '\0';
-       i.path = path;
+       if (p[0] != '/') {
+               path[0] = '/';
+               lws_strncpy(path + 1, p, sizeof(path) - 1);
+               i.path = path;
+       } else
+               i.path = p;
 
        if (!strcmp(prot, "http") || !strcmp(prot, "ws"))
                use_ssl = 0;
@@ -553,6 +692,8 @@ int main(int argc, char **argv)
                if (!use_ssl)
                        use_ssl = LCCSCF_USE_SSL;
 
+       lwsl_debug("'%s' %p '%s' %p\n", i.address, i.address, i.path, i.path);
+
        /*
         * create the websockets context.  This tracks open connections and
         * knows how to route any traffic and which protocol version to use,
@@ -568,10 +709,20 @@ int main(int argc, char **argv)
        info.ws_ping_pong_interval = pp_secs;
        info.extensions = exts;
 
-#if defined(LWS_OPENSSL_SUPPORT)
+       /*
+        * since we know this lws context is only ever going to be used with
+        * a few client wsis / fds / sockets at a time, let lws know it doesn't
+        * have to use the default allocations for fd tables up to ulimit -n.
+        * It will just allocate for 2 internal and 4 that we might use.
+        */
+       info.fd_limit_per_thread = 2 + 4;
+
+#if defined(LWS_WITH_TLS)
        info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
 #endif
 
+       info.options |= LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW;
+
        if (use_ssl) {
                /*
                 * If the server wants us to present a valid SSL client certificate
@@ -579,24 +730,29 @@ int main(int argc, char **argv)
                 */
 
                if (cert_path[0])
-                       info.ssl_cert_filepath = cert_path;
+                       info.client_ssl_cert_filepath = cert_path;
                if (key_path[0])
-                       info.ssl_private_key_filepath = key_path;
+                       info.client_ssl_private_key_filepath = key_path;
 
                /*
                 * A CA cert and CRL can be used to validate the cert send by the server
                 */
                if (ca_path[0])
-                       info.ssl_ca_filepath = ca_path;
+                       info.client_ssl_ca_filepath = ca_path;
 
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
                else if (crl_path[0])
                        lwsl_notice("WARNING, providing a CRL requires a CA cert!\n");
 #endif
        }
 
-       if (use_ssl & LCCSCF_USE_SSL)
+       if (use_ssl & LCCSCF_USE_SSL) {
                lwsl_notice(" Using SSL\n");
+#if defined(LWS_WITH_MBEDTLS)
+               lwsl_notice("   (NOTE: mbedtls needs to be given the remote\n");
+               lwsl_notice("    CA cert to trust (with -A) to validate it)\n");
+#endif
+       }
        else
                lwsl_notice(" SSL disabled\n");
        if (use_ssl & LCCSCF_ALLOW_SELFSIGNED)
@@ -652,7 +808,7 @@ int main(int argc, char **argv)
        while (!force_exit) {
 
                if (do_multi) {
-                       for (n = 0; n < ARRAY_SIZE(wsi_multi); n++) {
+                       for (n = 0; n < (int)LWS_ARRAY_SIZE(wsi_multi); n++) {
                                if (!wsi_multi[n] && ratelimit_connects(&rl_multi[n], 2u)) {
                                        lwsl_notice("dumb %d: connecting\n", n);
                                        i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
@@ -663,7 +819,7 @@ int main(int argc, char **argv)
                } else {
 
                        if (do_ws) {
-                               if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
+                               if (!flag_echo && !justmirror && !wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
                                        lwsl_notice("dumb: connecting\n");
                                        i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
                                        i.pwsi = &wsi_dumb;
@@ -691,9 +847,15 @@ int main(int argc, char **argv)
                        if (m == 10) {
                                m = 0;
                                lwsl_notice("doing lws_callback_on_writable_all_protocol\n");
-                               lws_callback_on_writable_all_protocol(context, &protocols[PROTOCOL_DUMB_INCREMENT]);
+                               lws_callback_on_writable_all_protocol(context,
+                                          &protocols[PROTOCOL_DUMB_INCREMENT]);
                        }
                }
+
+               if (flag_echo && lws_now_secs() != last) {
+                       lwsl_notice("rxb %d, rx_count %d\n", rxb, rx_count);
+                       last = lws_now_secs();
+               }
        }
 
        lwsl_err("Exiting\n");
diff --git a/test-apps/test-lejp.c b/test-apps/test-lejp.c
new file mode 100644 (file)
index 0000000..43d11ec
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Lightweight Embedded JSON Parser
+ *
+ * Copyright (C) 2013-2017 Andy Green <andy@warmcat.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation:
+ *  version 2.1 of the License.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA  02110-1301  USA
+ */
+
+#include <libwebsockets.h>
+#include <string.h>
+
+
+static const char * const reason_names[] = {
+       "LEJPCB_CONSTRUCTED",
+       "LEJPCB_DESTRUCTED",
+       "LEJPCB_START",
+       "LEJPCB_COMPLETE",
+       "LEJPCB_FAILED",
+       "LEJPCB_PAIR_NAME",
+       "LEJPCB_VAL_TRUE",
+       "LEJPCB_VAL_FALSE",
+       "LEJPCB_VAL_NULL",
+       "LEJPCB_VAL_NUM_INT",
+       "LEJPCB_VAL_NUM_FLOAT",
+       "LEJPCB_VAL_STR_START",
+       "LEJPCB_VAL_STR_CHUNK",
+       "LEJPCB_VAL_STR_END",
+       "LEJPCB_ARRAY_START",
+       "LEJPCB_ARRAY_END",
+       "LEJPCB_OBJECT_START",
+       "LEJPCB_OBJECT_END",
+       "LEJPCB_OBJECT_END_PRE",
+};
+
+static const char * const tok[] = {
+       "dummy___"
+};
+
+static signed char
+cb(struct lejp_ctx *ctx, char reason)
+{
+       char buf[1024], *p = buf, *end = &buf[sizeof(buf)];
+       int n;
+
+       for (n = 0; n < ctx->sp; n++)
+               *p++ = ' ';
+       *p = '\0';
+
+       if (reason & LEJP_FLAG_CB_IS_VALUE) {
+               p += lws_snprintf(p, p - end, "   value '%s' ", ctx->buf);
+               if (ctx->ipos) {
+                       int n;
+
+                       p += lws_snprintf(p, p - end, "(array indexes: ");
+                       for (n = 0; n < ctx->ipos; n++)
+                               p += lws_snprintf(p, p - end, "%d ", ctx->i[n]);
+                       p += lws_snprintf(p, p - end, ") ");
+               }
+               lwsl_notice("%s (%s)\r\n", buf,
+                      reason_names[(unsigned int)
+                       (reason) & (LEJP_FLAG_CB_IS_VALUE - 1)]);
+
+               (void)reason_names; /* NO_LOGS... */
+               return 0;
+       }
+
+       switch (reason) {
+       case LEJPCB_COMPLETE:
+               lwsl_notice("%sParsing Completed (LEJPCB_COMPLETE)\n", buf);
+               break;
+       case LEJPCB_PAIR_NAME:
+               lwsl_notice("%spath: '%s' (LEJPCB_PAIR_NAME)\n", buf, ctx->path);
+               break;
+       }
+
+       lwsl_notice("%s%s: path %s match %d statckp %d\r\n", buf, reason_names[(unsigned int)
+               (reason) & (LEJP_FLAG_CB_IS_VALUE - 1)], ctx->path,
+               ctx->path_match, ctx->pst[ctx->pst_sp].ppos);
+
+       return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+       int fd, n = 1, ret = 1, m;
+       struct lejp_ctx ctx;
+       char buf[128];
+
+       lws_set_log_level(7, NULL);
+
+       lwsl_notice("libwebsockets-test-lejp  (C) 2017 - 2018 andy@warmcat.com\n");
+       lwsl_notice("  usage: cat my.json | libwebsockets-test-lejp\n\n");
+
+       lejp_construct(&ctx, cb, NULL, tok, LWS_ARRAY_SIZE(tok));
+
+       fd = 0;
+
+       while (n > 0) {
+               n = read(fd, buf, sizeof(buf));
+               if (n <= 0)
+                       continue;
+
+               m = lejp_parse(&ctx, (uint8_t *)buf, n);
+               if (m < 0 && m != LEJP_CONTINUE) {
+                       lwsl_err("parse failed %d\n", m);
+                       goto bail;
+               }
+       }
+       lwsl_notice("okay\n");
+       ret = 0;
+bail:
+       lejp_destruct(&ctx);
+
+       return ret;
+}
similarity index 68%
rename from test-server/test-server.c
rename to test-apps/test-server.c
index dd02fd2..bc70cb0 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * libwebsockets-test-server - libwebsockets test implementation
  *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
  *
  * This file is made available under the Creative Commons CC0 1.0
  * Universal Public Domain Dedication.
  * Public Domain.
  */
 
-#include "test-server.h"
+#include <libwebsockets.h>
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
+#include <getopt.h>
+#endif
+#include <signal.h>
+
+#if defined(WIN32) || defined(_WIN32)
+#else
+#include <unistd.h>
+#endif
 
 int close_testing;
 int max_poll_elements;
-int debug_level = 7;
+int debug_level = LLL_USER | 7;
 
-#ifdef EXTERNAL_POLL
+#if defined(LWS_WITH_EXTERNAL_POLL)
 struct lws_pollfd *pollfds;
 int *fd_lookup;
 int count_pollfds;
@@ -33,11 +44,12 @@ volatile int force_exit = 0, dynamic_vhost_enable = 0;
 struct lws_vhost *dynamic_vhost;
 struct lws_context *context;
 struct lws_plat_file_ops fops_plat;
+static int test_options;
 
 /* http server gets files from this path */
 #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
 char *resource_path = LOCAL_RESOURCE_PATH;
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
+#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
 char crl_path[1024] = "";
 #endif
 
@@ -66,75 +78,85 @@ char crl_path[1024] = "";
  */
 
 #define LWS_PLUGIN_STATIC
+#if defined(LWS_ROLE_WS)
 #include "../plugins/protocol_lws_mirror.c"
 #include "../plugins/protocol_lws_status.c"
-#include "../plugins/protocol_lws_meta.c"
-
-/* singlethreaded version --> no locks */
+#include "../plugins/protocol_dumb_increment.c"
+#include "../plugins/protocol_post_demo.c"
+#endif
 
-void test_server_lock(int care)
-{
-}
-void test_server_unlock(int care)
+static int
+lws_callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+                 void *in, size_t len)
 {
-}
+       const unsigned char *c;
+       char buf[1024];
+       int n = 0, hlen;
 
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *                             ascii string is sent down it every 50ms.
- *                             If you send "reset\n" on the websocket, then
- *                             the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *                             using this protocol, including the sender
- *
- *  lws-status:                        informs connected browsers of who else is
- *                             connected.
- */
+       switch (reason) {
+       case LWS_CALLBACK_HTTP:
 
-enum demo_protocols {
-       /* always first */
-       PROTOCOL_HTTP = 0,
+               /* non-mount-handled accesses will turn up here */
 
-       PROTOCOL_DUMB_INCREMENT,
-       PROTOCOL_LWS_MIRROR,
-       PROTOCOL_LWS_STATUS,
+               /* dump the headers */
 
-       PROTOCOL_LWS_META,
+               do {
+                       c = lws_token_to_string(n);
+                       if (!c) {
+                               n++;
+                               continue;
+                       }
 
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
+                       hlen = lws_hdr_total_length(wsi, n);
+                       if (!hlen || hlen > (int)sizeof(buf) - 1) {
+                               n++;
+                               continue;
+                       }
+
+                       if (lws_hdr_copy(wsi, buf, sizeof buf, n) < 0)
+                               fprintf(stderr, "    %s (too big)\n", (char *)c);
+                       else {
+                               buf[sizeof(buf) - 1] = '\0';
+
+                               fprintf(stderr, "    %s = %s\n", (char *)c, buf);
+                       }
+                       n++;
+               } while (c);
+
+               /* dump the individual URI Arg parameters */
+
+               n = 0;
+               while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf),
+                                            WSI_TOKEN_HTTP_URI_ARGS, n) > 0) {
+                       lwsl_notice("URI Arg %d: %s\n", ++n, buf);
+               }
+
+               if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
+                       return -1;
+
+               if (lws_http_transaction_completed(wsi))
+                       return -1;
+
+               return 0;
+       default:
+               break;
+       }
+
+       return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
 
 /* list of supported protocols and callbacks */
 
 static struct lws_protocols protocols[] = {
        /* first protocol must always be HTTP handler */
 
-       {
-               "http-only",            /* name */
-               callback_http,          /* callback */
-               sizeof (struct per_session_data__http), /* per_session_data_size */
-               0,                      /* max frame size / rx buffer */
-       },
-       {
-               "dumb-increment-protocol",
-               callback_dumb_increment,
-               sizeof(struct per_session_data__dumb_increment),
-               10, /* rx buf size must be >= permessage-deflate rx size
-                    * dumb-increment only sends very small packets, so we set
-                    * this accordingly.  If your protocol will send bigger
-                    * things, adjust this to match */
-       },
+       { "http-only", lws_callback_http, 0, 0, },
+#if defined(LWS_ROLE_WS)
+       LWS_PLUGIN_PROTOCOL_DUMB_INCREMENT,
        LWS_PLUGIN_PROTOCOL_MIRROR,
        LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-
-       LWS_PLUGIN_PROTOCOL_LWS_META,
+       LWS_PLUGIN_PROTOCOL_POST_DEMO,
+#endif
        { NULL, NULL, 0, 0 } /* terminator */
 };
 
@@ -174,6 +196,7 @@ void sighandler(int sig)
                 * port + 1
                 */
                dynamic_vhost_enable ^= 1;
+               lws_cancel_service(context);
                lwsl_notice("SIGUSR1: dynamic_vhost_enable: %d\n",
                                dynamic_vhost_enable);
                return;
@@ -189,16 +212,100 @@ static const struct lws_extension exts[] = {
                lws_extension_callback_pm_deflate,
                "permessage-deflate"
        },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
        { NULL, NULL, NULL /* terminator */ }
 };
 
+/*
+ * mount handlers for sections of the URL space
+ */
+
+static const struct lws_http_mount mount_ziptest = {
+       NULL,                   /* linked-list pointer to next*/
+       "/ziptest",             /* mountpoint in URL namespace on this vhost */
+       LOCAL_RESOURCE_PATH"/candide.zip",      /* handler */
+       NULL,   /* default filename if none given */
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_FILE,   /* origin points to a callback */
+       8,                      /* strlen("/ziptest"), ie length of the mountpoint */
+       NULL,
+
+       { NULL, NULL } // sentinel
+};
 
+static const struct lws_http_mount mount_post = {
+       (struct lws_http_mount *)&mount_ziptest, /* linked-list pointer to next*/
+       "/formtest",            /* mountpoint in URL namespace on this vhost */
+       "protocol-post-demo",   /* handler */
+       NULL,   /* default filename if none given */
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_CALLBACK,       /* origin points to a callback */
+       9,                      /* strlen("/formtest"), ie length of the mountpoint */
+       NULL,
+
+       { NULL, NULL } // sentinel
+};
 
+/*
+ * mount a filesystem directory into the URL space at /
+ * point it to our /usr/share directory with our assets in
+ * stuff from here is autoserved by the library
+ */
+
+static const struct lws_http_mount mount = {
+       (struct lws_http_mount *)&mount_post,   /* linked-list pointer to next*/
+       "/",            /* mountpoint in URL namespace on this vhost */
+       LOCAL_RESOURCE_PATH, /* where to go on the filesystem for that */
+       "test.html",    /* default filename if none given */
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       0,
+       0,
+       0,
+       0,
+       0,
+       0,
+       LWSMPRO_FILE,   /* mount type is a directory in a filesystem */
+       1,              /* strlen("/"), ie length of the mountpoint */
+       NULL,
+
+       { NULL, NULL } // sentinel
+};
+
+static const struct lws_protocol_vhost_options pvo_options = {
+       NULL,
+       NULL,
+       "options",              /* pvo name */
+       (void *)&test_options   /* pvo value */
+};
+
+static const struct lws_protocol_vhost_options pvo = {
+       NULL,                           /* "next" pvo linked-list */
+       &pvo_options,           /* "child" pvo linked-list */
+       "dumb-increment-protocol",      /* protocol name we belong to on this vhost */
+       ""                              /* ignored */
+};
+
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
 static struct option options[] = {
        { "help",       no_argument,            NULL, 'h' },
        { "debug",      required_argument,      NULL, 'd' },
@@ -210,27 +317,27 @@ static struct option options[] = {
        { "ssl-cert",  required_argument,       NULL, 'C' },
        { "ssl-key",  required_argument,        NULL, 'K' },
        { "ssl-ca",  required_argument,         NULL, 'A' },
-#if defined(LWS_OPENSSL_SUPPORT)
+#if defined(LWS_WITH_TLS)
        { "ssl-verify-client",  no_argument,            NULL, 'v' },
 #if defined(LWS_HAVE_SSL_CTX_set1_param)
        { "ssl-crl",  required_argument,                NULL, 'R' },
 #endif
 #endif
        { "libev",  no_argument,                NULL, 'e' },
+       { "unix-socket",  required_argument,    NULL, 'U' },
 #ifndef LWS_NO_DAEMONIZE
        { "daemonize",  no_argument,            NULL, 'D' },
 #endif
-       { "resource_path", required_argument,   NULL, 'r' },
        { "pingpong-secs", required_argument,   NULL, 'P' },
        { NULL, 0, 0, 0 }
 };
+#endif
 
 int main(int argc, char **argv)
 {
        struct lws_context_creation_info info;
        struct lws_vhost *vhost;
        char interface_name[128] = "";
-       unsigned int ms, oldms = 0;
        const char *iface = NULL;
        char cert_path[1024] = "";
        char key_path[1024] = "";
@@ -240,14 +347,6 @@ int main(int argc, char **argv)
        int pp_secs = 0;
        int opts = 0;
        int n = 0;
-#ifndef _WIN32
-/* LOG_PERROR is not POSIX standard, and may not be portable */
-#ifdef __sun
-       int syslog_options = LOG_PID;
-#else       
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#endif
 #ifndef LWS_NO_DAEMONIZE
        int daemonize = 0;
 #endif
@@ -260,7 +359,11 @@ int main(int argc, char **argv)
        info.port = 7681;
 
        while (n >= 0) {
-               n = getopt_long(argc, argv, "eci:hsap:d:Dr:C:K:A:R:vu:g:P:k", options, NULL);
+#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
+               n = getopt_long(argc, argv, "eci:hsap:d:DC:K:A:R:vu:g:P:kU:n", options, NULL);
+#else
+               n = getopt(argc, argv, "eci:hsap:d:DC:K:A:R:vu:g:P:kU:n");
+#endif
                if (n < 0)
                        continue;
                switch (n) {
@@ -270,9 +373,6 @@ int main(int argc, char **argv)
 #ifndef LWS_NO_DAEMONIZE
                case 'D':
                        daemonize = 1;
-                       #if !defined(_WIN32) && !defined(__sun)
-                       syslog_options &= ~LOG_PERROR;
-                       #endif
                        break;
 #endif
                case 'u':
@@ -284,6 +384,10 @@ int main(int argc, char **argv)
                case 'd':
                        debug_level = atoi(optarg);
                        break;
+               case 'n':
+                       /* no dumb increment send */
+                       test_options |= 1;
+                       break;
                case 's':
                        use_ssl = 1;
                        opts |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
@@ -295,9 +399,13 @@ int main(int argc, char **argv)
                        info.port = atoi(optarg);
                        break;
                case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
+                       lws_strncpy(interface_name, optarg, sizeof interface_name);
+                       iface = interface_name;
+                       break;
+               case 'U':
+                       lws_strncpy(interface_name, optarg, sizeof interface_name);
                        iface = interface_name;
+                       opts |= LWS_SERVER_OPTION_UNIX_SOCK;
                        break;
                case 'k':
                        info.bind_iface = 1;
@@ -312,27 +420,20 @@ int main(int argc, char **argv)
                                           "client after 50 dumb increments"
                                           "and suppresses lws_mirror spam\n");
                        break;
-               case 'r':
-                       resource_path = optarg;
-                       printf("Setting resource path to \"%s\"\n", resource_path);
-                       break;
                case 'C':
-                       strncpy(cert_path, optarg, sizeof(cert_path) - 1);
-                       cert_path[sizeof(cert_path) - 1] = '\0';
+                       lws_strncpy(cert_path, optarg, sizeof(cert_path));
                        break;
                case 'K':
-                       strncpy(key_path, optarg, sizeof(key_path) - 1);
-                       key_path[sizeof(key_path) - 1] = '\0';
+                       lws_strncpy(key_path, optarg, sizeof(key_path));
                        break;
                case 'A':
-                       strncpy(ca_path, optarg, sizeof(ca_path) - 1);
-                       ca_path[sizeof(ca_path) - 1] = '\0';
+                       lws_strncpy(ca_path, optarg, sizeof(ca_path));
                        break;
                case 'P':
                        pp_secs = atoi(optarg);
                        lwsl_notice("Setting pingpong interval to %d\n", pp_secs);
                        break;
-#if defined(LWS_OPENSSL_SUPPORT)
+#if defined(LWS_WITH_TLS)
                case 'v':
                        use_ssl = 1;
                        opts |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
@@ -340,16 +441,14 @@ int main(int argc, char **argv)
 
 #if defined(LWS_HAVE_SSL_CTX_set1_param)
                case 'R':
-                       strncpy(crl_path, optarg, sizeof(crl_path) - 1);
-                       crl_path[sizeof(crl_path) - 1] = '\0';
+                       lws_strncpy(crl_path, optarg, sizeof(crl_path));
                        break;
 #endif
 #endif
                case 'h':
                        fprintf(stderr, "Usage: test-server "
                                        "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
+                                       "[-d <log bitfield>]\n");
                        exit(1);
                }
        }
@@ -373,21 +472,19 @@ int main(int argc, char **argv)
        signal(SIGUSR1, sighandler);
 #endif
 
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
+       /* tell the library what debug level to emit and to send it to stderr */
+       lws_set_log_level(debug_level, NULL);
 
        lwsl_notice("libwebsockets test server - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
+       lwsl_notice("(C) Copyright 2010-2018 Andy Green <andy@warmcat.com>\n");
 
        printf("Using resource path \"%s\"\n", resource_path);
-#ifdef EXTERNAL_POLL
+#if defined(LWS_WITH_EXTERNAL_POLL)
+#if !defined(WIN32) && !defined(_WIN32) && !defined(__ANDROID__)
        max_poll_elements = getdtablesize();
+#else
+       max_poll_elements = sysconf(_SC_OPEN_MAX);
+#endif
        pollfds = malloc(max_poll_elements * sizeof (struct lws_pollfd));
        fd_lookup = malloc(max_poll_elements * sizeof (int));
        if (pollfds == NULL || fd_lookup == NULL) {
@@ -425,7 +522,6 @@ int main(int argc, char **argv)
        }
        info.gid = gid;
        info.uid = uid;
-       info.max_http_header_pool = 16;
        info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
        info.extensions = exts;
        info.timeout_secs = 5;
@@ -442,6 +538,9 @@ int main(int argc, char **argv)
                               "!DHE-RSA-AES256-SHA256:"
                               "!AES256-GCM-SHA384:"
                               "!AES256-SHA256";
+       info.mounts = &mount;
+       info.ip_limit_ah = 24; /* for testing */
+       info.ip_limit_wsi = 400; /* for testing */
 
        if (use_ssl)
                /* redirect guys coming on http */
@@ -453,6 +552,8 @@ int main(int argc, char **argv)
                return -1;
        }
 
+       info.pvo = &pvo;
+
        vhost = lws_create_vhost(context, &info);
        if (!vhost) {
                lwsl_err("vhost creation failed\n");
@@ -467,7 +568,7 @@ int main(int argc, char **argv)
 
        info.port++;
 
-#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT)
+#if !defined(LWS_NO_CLIENT) && defined(LWS_WITH_TLS)
        lws_init_vhost_client_ssl(&info, vhost);
 #endif
 
@@ -481,9 +582,6 @@ int main(int argc, char **argv)
        lws_get_fops(context)->open = test_server_fops_open;
 
        n = 0;
-#ifdef EXTERNAL_POLL
-       int ms_1sec = 0;
-#endif
        while (n >= 0 && !force_exit) {
                struct timeval tv;
 
@@ -495,19 +593,11 @@ int main(int argc, char **argv)
                 * as soon as it can take more packets (usually immediately)
                 */
 
-               ms = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
-               if ((ms - oldms) > 50) {
-                       lws_callback_on_writable_all_protocol(context,
-                               &protocols[PROTOCOL_DUMB_INCREMENT]);
-                       oldms = ms;
-               }
-
-#ifdef EXTERNAL_POLL
+#if defined(LWS_WITH_EXTERNAL_POLL)
                /*
                 * this represents an existing server's single poll action
                 * which also includes libwebsocket sockets
                 */
-
                n = poll(pollfds, count_pollfds, 50);
                if (n < 0)
                        continue;
@@ -529,25 +619,15 @@ int main(int argc, char **argv)
                                lwsl_notice("extpoll doing forced service!\n");
                                lws_service_tsi(context, -1, 0);
                        }
-               } else {
-                       /* no revents, but before polling again, make lws check for any timeouts */
-                       if (ms - ms_1sec > 1000) {
-                               lwsl_notice("1 per sec\n");
-                               lws_service_fd(context, NULL);
-                               ms_1sec = ms;
-                       }
                }
 #else
                /*
                 * If libwebsockets sockets are all we care about,
                 * you can use this api which takes care of the poll()
                 * and looping through finding who needed service.
-                *
-                * If no socket needs service, it'll return anyway after
-                * the number of ms in the second argument.
                 */
 
-               n = lws_service(context, 50);
+               n = lws_service(context, 0);
 #endif
 
                if (dynamic_vhost_enable && !dynamic_vhost) {
@@ -562,7 +642,7 @@ int main(int argc, char **argv)
 
        }
 
-#ifdef EXTERNAL_POLL
+#if defined(LWS_WITH_EXTERNAL_POLL)
 done:
 #endif
 
@@ -570,9 +650,5 @@ done:
 
        lwsl_notice("libwebsockets-test-server exited cleanly\n");
 
-#ifndef _WIN32
-       closelog();
-#endif
-
        return 0;
 }
diff --git a/test-apps/test-sshd.c b/test-apps/test-sshd.c
new file mode 100644 (file)
index 0000000..65bff38
--- /dev/null
@@ -0,0 +1,704 @@
+/*
+ * Example embedded sshd server using libwebsockets sshd plugin
+ *
+ * Written in 2010-2019 by Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * The test apps are intended to be adapted for use in your code, which
+ * may be proprietary. So unlike the library itself, they are licensed
+ * Public Domain.
+ *
+ *
+ * This test app listens on port 2200 for authorized ssh connections.  Run it
+ * using
+ *
+ * $ sudo libwebsockets-test-sshd
+ *
+ * Connect to it using the test private key with:
+ *
+ * $ ssh -p 2200 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser@127.0.0.1
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+
+/* import the whole of lws-plugin-sshd-base statically */
+#include <lws-plugin-sshd-static-build-includes.h>
+
+/*
+ * We store the test server's own key here (will be created with a new
+ * random key if it doesn't exist
+ *
+ * The /etc path is the only reason we have to run as root.
+ */
+#define TEST_SERVER_KEY_PATH "/etc/lws-test-sshd-server-key"
+
+/*
+ *  This is a copy of the lws ssh test public key, you can find it in
+ *  /usr[/local]/share/libwebsockets-test-server/lws-ssh-test-keys.pub
+ *  and the matching private key there too in .../lws-ssh-test-keys
+ *
+ *  These keys are distributed for testing!  Don't use them on a real system
+ *  unless you want anyone with a copy of lws to access it.
+ */
+static const char *authorized_key =
+       "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnWiP+c+kSD6Lk+C6NA9KruApa45sbt"
+       "94/dxT0bCITlAA/+PBk6mR1lwWgXYozOMdrHrqx34piqDyXnc4HabqCaOm/FrYhkCPL8z"
+       "a26PMYqteSosuwKv//5iT6ZWhNnsMwExBwtV6MIq0MxAeWqxRnYNWpNM8iN6sFzkdG/YF"
+       "dyHrIBTgwzM77NLCMl6GEkJErRCFppC2SwYxGa3BRrgUwX3LkV8HpMIaYHFo1Qgj7Scqm"
+       "HwS2R75SOqi2aOWDpKjznARg9JgzDWSQi4seBMV2oL0BTwJANSDf+p0sQLsaKGJhpVpBQ"
+       "yS2wUeyuGyytupWzEluQrajMZq52iotcogv5BfeulfTTFbJP4kuHOsSP0lsQ2lpMDQANS"
+       "HEvXxzHJLDLXM9gXJzwJ+ZiRt6R+bfmP1nfN3MiWtxcIbBanWwQK6xTCKBe4wPaKta5EU"
+       "6wsLPeakOIVzoeaOu/HsbtPZlwX0Mu/oUFcfKyKAhlkU15MOAIEfUPo8Yh52bWMlIlpZa"
+       "4xWbLMGw3GrsrPPdcsAauyqvY4/NjjWQbWhP1SuUfvv5709PIiOUjVKK2HUwmR1ouch6X"
+       "MQGXfMR1h1Wjvc+bkNs17gCIrQnFilAZLC3Sm3Opiz/4LO99Hw448G0RM2vQn0mJE46w"
+       "Eu/B10U6Jf4Efojhh1dk85BD1LTIb+N3Q== ssh-test-key@lws";
+
+static struct lws_context *context = NULL;
+static volatile char force_exit = 0;
+
+/*
+ * These are our "ops" that form our customization of, and interface to, the
+ * generic sshd plugin.
+ *
+ * The priv struct contains our data we want to associate to each channel
+ * individually.
+ */
+
+struct sshd_instance_priv {
+       struct lws_protocol_vhost_options *env;
+       struct lws_ring *ring_stdout;
+       struct lws_ring *ring_stderr;
+
+       struct lws      *wsi_stdout;
+       struct lws      *wsi_stderr;
+
+       uint32_t        pty_in_bloat_nl_to_crnl:1;
+       uint32_t        pty_in_echo:1;
+       uint32_t        pty_in_cr_to_nl:1;
+
+       uint32_t        insert_lf:1;
+};
+
+
+/* ops: channel lifecycle */
+
+static int
+ssh_ops_channel_create(struct lws *wsi, void **_priv)
+{
+       struct sshd_instance_priv *priv;
+
+       priv = malloc(sizeof(struct sshd_instance_priv));
+       *_priv = priv;
+       if (!priv)
+               return 1;
+
+       memset(priv, 0, sizeof(*priv));
+
+       priv->ring_stdout = lws_ring_create(1, 1024, NULL);
+       if (!priv->ring_stdout) {
+               free(priv);
+
+               return 1;
+       }
+
+       priv->ring_stderr = lws_ring_create(1, 1024, NULL);
+       if (!priv->ring_stderr) {
+               lws_ring_destroy(priv->ring_stdout);
+               free(priv);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+static int
+ssh_ops_channel_destroy(void *_priv)
+{
+       struct sshd_instance_priv *priv = _priv;
+       const struct lws_protocol_vhost_options *pvo = priv->env, *pvo1;
+
+       while (pvo) {
+               pvo1 = pvo;
+               free((char *)pvo->name);
+               free((char *)pvo->value);
+               pvo = pvo->next;
+               free((void *)pvo1);
+       }
+       priv->env = NULL;
+
+       lws_ring_destroy(priv->ring_stdout);
+       lws_ring_destroy(priv->ring_stderr);
+       free(priv);
+
+       return 0;
+}
+
+/* ops: IO */
+
+static int
+ssh_ops_tx_waiting(void *_priv)
+{
+       struct sshd_instance_priv *priv = _priv;
+       int s = 0;
+
+       if (lws_ring_get_count_waiting_elements(priv->ring_stdout, NULL))
+               s |= LWS_STDOUT;
+       if (lws_ring_get_count_waiting_elements(priv->ring_stderr, NULL))
+               s |= LWS_STDERR;
+
+       return s;
+}
+
+static size_t
+ssh_ops_tx(void *_priv, int stdch, uint8_t *buf, size_t len)
+{
+       struct sshd_instance_priv *priv = _priv;
+       struct lws_ring *r;
+       struct lws *wsi;
+       size_t n;
+
+       if (stdch == LWS_STDOUT) {
+               r = priv->ring_stdout;
+               wsi = priv->wsi_stdout;
+       } else {
+               r = priv->ring_stderr;
+               wsi = priv->wsi_stderr;
+       }
+
+       n = lws_ring_consume(r, NULL, buf, len);
+
+       if (n)
+               lws_rx_flow_control(wsi, 1);
+
+       return n;
+}
+
+
+static int
+ssh_ops_rx(void *_priv, struct lws *wsi, const uint8_t *buf, uint32_t len)
+{
+       struct sshd_instance_priv *priv = _priv;
+       struct lws *wsi_stdin = lws_cgi_get_stdwsi(wsi, LWS_STDIN);
+       int fd;
+       uint8_t bbuf[256];
+
+       if (!wsi_stdin)
+               return -1;
+
+       fd = lws_get_socket_fd(wsi_stdin);
+
+       if (*buf != 0x0d) {
+               if (write(fd, buf, len) != len)
+                       return -1;
+               if (priv->pty_in_echo) {
+                       if (!lws_ring_insert(priv->ring_stdout, buf, 1))
+                               lwsl_notice("dropping...\n");
+                       lws_callback_on_writable(wsi);
+               }
+       } else {
+               bbuf[0] = 0x0a;
+               bbuf[1] = 0x0a;
+               if (write(fd, bbuf, 1) != 1)
+                       return -1;
+
+               if (priv->pty_in_echo) {
+                       bbuf[0] = 0x0d;
+                       bbuf[1] = 0x0a;
+                       if (!lws_ring_insert(priv->ring_stdout, bbuf, 2))
+                               lwsl_notice("dropping...\n");
+                       lws_callback_on_writable(wsi);
+               }
+       }
+
+       return 0;
+}
+
+/* ops: storage for the (autogenerated) persistent server key */
+
+static size_t
+ssh_ops_get_server_key(struct lws *wsi, uint8_t *buf, size_t len)
+{
+       int fd = lws_open(TEST_SERVER_KEY_PATH, O_RDONLY), n;
+
+       if (fd == -1) {
+               lwsl_err("%s: unable to open %s for read: %s\n", __func__,
+                               TEST_SERVER_KEY_PATH, strerror(errno));
+
+               return 0;
+       }
+
+       n = read(fd, buf, len);
+       if (n < 0) {
+               lwsl_err("%s: read failed: %d\n", __func__, n);
+               n = 0;
+       }
+
+       close(fd);
+
+       return n;
+}
+
+static size_t
+ssh_ops_set_server_key(struct lws *wsi, uint8_t *buf, size_t len)
+{
+       int fd = lws_open(TEST_SERVER_KEY_PATH, O_CREAT | O_TRUNC | O_RDWR, 0600);
+       int n;
+
+       lwsl_notice("%s: %d\n", __func__, fd);
+       if (fd == -1) {
+               lwsl_err("%s: unable to open %s for write: %s\n", __func__,
+                               TEST_SERVER_KEY_PATH, strerror(errno));
+
+               return 0;
+       }
+
+       n = write(fd, buf, len);
+       if (n < 0) {
+               lwsl_err("%s: read failed: %d\n", __func__, errno);
+               n = 0;
+       }
+
+       close(fd);
+
+       return n;
+}
+
+/* ops: auth */
+
+static int
+ssh_ops_is_pubkey_authorized(const char *username, const char *type,
+                                const uint8_t *peer, int peer_len)
+{
+       char *aps, *p, *ps;
+       int n = strlen(type), alen = 2048, ret = 2, len;
+       size_t s = 0;
+
+       lwsl_info("%s: checking pubkey for %s\n", __func__, username);
+
+       s = strlen(authorized_key) + 1;
+
+       aps = malloc(s);
+       if (!aps) {
+               lwsl_notice("OOM 1\n");
+               goto bail_p1;
+       }
+       memcpy(aps, authorized_key, s);
+
+       /* this is all we understand at the moment */
+       if (strcmp(type, "ssh-rsa")) {
+               lwsl_notice("type is not ssh-rsa\n");
+               goto bail_p1;
+       }
+       p = aps;
+
+       if (strncmp(p, type, n)) {
+               lwsl_notice("lead-in string  does not match %s\n", type);
+               goto bail_p1;
+       }
+
+       p += n;
+       if (*p != ' ') {
+               lwsl_notice("missing space at end of lead-in\n");
+               goto bail_p1;
+       }
+
+
+       p++;
+       ps = malloc(alen);
+       if (!ps) {
+               lwsl_notice("OOM 2\n");
+               free(aps);
+               goto bail;
+       }
+       len = lws_b64_decode_string(p, ps, alen);
+       free(aps);
+       if (len < 0) {
+               lwsl_notice("key too big\n");
+               goto bail;
+       }
+
+       if (peer_len > len) {
+               lwsl_notice("peer_len %d bigger than decoded len %d\n",
+                               peer_len, len);
+               goto bail;
+       }
+
+       /*
+        * once we are past that, it's the same <len32>name
+        * <len32>E<len32>N that the peer sends us
+        */
+
+       if (lws_timingsafe_bcmp(peer, ps, peer_len)) {
+               lwsl_info("factors mismatch\n");
+               goto bail;
+       }
+
+       lwsl_info("pubkey authorized\n");
+
+       ret = 0;
+bail:
+       free(ps);
+
+       return ret;
+
+bail_p1:
+       if (aps)
+               free(aps);
+
+       return 1;
+}
+
+/* ops: spawn subprocess */
+
+static int
+ssh_cgi_env_add(struct sshd_instance_priv *priv, const char *name,
+               const char *value)
+{
+       struct lws_protocol_vhost_options *pvo = malloc(sizeof(*pvo));
+
+       if (!pvo)
+               return 1;
+
+       pvo->name = malloc(strlen(name) + 1);
+       if (!pvo->name) {
+               free(pvo);
+               return 1;
+       }
+
+       pvo->value = malloc(strlen(value) + 1);
+       if (!pvo->value) {
+               free((char *)pvo->name);
+               free(pvo);
+               return 1;
+       }
+
+       strcpy((char *)pvo->name, name);
+       strcpy((char *)pvo->value, value);
+
+       pvo->next = priv->env;
+       priv->env = pvo;
+
+       lwsl_notice("%s: ENV %s <- %s\n", __func__, name, value);
+
+       return 0;
+}
+
+static int
+ssh_ops_set_env(void *_priv, const char *name, const char *value)
+{
+       struct sshd_instance_priv *priv = _priv;
+
+       return ssh_cgi_env_add(priv, name, value);
+}
+
+
+static int
+ssh_ops_pty_req(void *_priv, struct lws_ssh_pty *pty)
+{
+       struct sshd_instance_priv *priv = _priv;
+       uint8_t *p = (uint8_t *)pty->modes, opc;
+       uint32_t arg;
+
+       lwsl_notice("%s: pty term %s, modes_len %d\n", __func__, pty->term,
+                   pty->modes_len);
+
+       ssh_cgi_env_add(priv, "TERM", pty->term);
+
+       while (p < (uint8_t *)pty->modes + pty->modes_len) {
+               if (*p >= 160)
+                       break;
+               if (!*p)
+                       break;
+               opc = *p++;
+
+               arg = *p++ << 24;
+               arg |= *p++ << 16;
+               arg |= *p++ << 8;
+               arg |= *p++;
+
+               lwsl_debug("pty opc %d: 0x%x\n", opc, arg);
+
+               switch (opc) {
+               case SSHMO_ICRNL:
+                       priv->pty_in_cr_to_nl = !!arg;
+                       lwsl_notice(" SSHMO_ICRNL: %d\n", !!arg);
+                       break;
+               case SSHMO_ONLCR:
+                       priv->pty_in_bloat_nl_to_crnl = !!arg;
+                       lwsl_notice(" SSHMO_ONLCR: %d\n", !!arg);
+                       break;
+               case SSHMO_ECHO:
+//                     priv->pty_in_echo = !!arg;
+                       lwsl_notice(" SSHMO_ECHO: %d\n", !!arg);
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static int
+ssh_ops_child_process_io(void *_priv, struct lws *wsi,
+                        struct lws_cgi_args *args)
+{
+       struct sshd_instance_priv *priv = _priv;
+       struct lws_ring *r = priv->ring_stdout;
+       void *rp;
+       size_t bytes;
+       int n, m;
+
+       priv->wsi_stdout = args->stdwsi[LWS_STDOUT];
+       priv->wsi_stderr = args->stdwsi[LWS_STDERR];
+
+       switch (args->ch) {
+       case LWS_STDIN:
+               lwsl_notice("STDIN\n");
+               break;
+
+       case LWS_STDERR:
+               r = priv->ring_stderr;
+               /* fallthru */
+       case LWS_STDOUT:
+               if (lws_ring_next_linear_insert_range(r, &rp, &bytes) ||
+                   bytes < 1) {
+                       lwsl_notice("bytes %d\n", (int)bytes);
+                       /* no room in the fifo */
+                       break;
+               }
+               if (priv->pty_in_bloat_nl_to_crnl) {
+                       uint8_t buf[256], *p, *d;
+
+                       if (bytes != 1)
+                               n = bytes / 2;
+                       else
+                               n = 1;
+                       if (n > (int)sizeof(buf))
+                               n = sizeof(buf);
+
+                       if (!n)
+                               break;
+
+                       m = lws_get_socket_fd(args->stdwsi[args->ch]);
+                       if (m < 0)
+                               return -1;
+                       n = read(m, buf, n);
+                       if (n < 0)
+                               return -1;
+                       if (n == 0) {
+                               lwsl_notice("zero length stdin %d\n", n);
+                               break;
+                       }
+                       m = 0;
+                       p = rp;
+                       d = buf;
+                       while (m++ < n) {
+                               if (priv->insert_lf) {
+                                       priv->insert_lf = 0;
+                                       *p++ = 0x0d;
+                               }
+                               if (*d == 0x0a)
+                                       priv->insert_lf = 1;
+
+                               *p++ = *d++;
+                       }
+                       n = (void *)p - rp;
+                       if (n < (int)bytes && priv->insert_lf) {
+                               priv->insert_lf = 0;
+                               *p++ = 0x0d;
+                               n++;
+                       }
+               } else {
+                       n = lws_get_socket_fd(args->stdwsi[args->ch]);
+                       if (n < 0)
+                               return -1;
+                       n = read(n, rp, bytes);
+                       if (n < 0)
+                               return -1;
+               }
+
+               lws_rx_flow_control(args->stdwsi[args->ch], 0);
+
+               lws_ring_bump_head(r, n);
+               lws_callback_on_writable(wsi);
+               break;
+       }
+
+       return 0;
+}
+
+static int
+ssh_ops_child_process_terminated(void *priv, struct lws *wsi)
+{
+       lwsl_notice("%s\n", __func__);
+       return -1;
+}
+
+static int
+ssh_ops_exec(void *_priv, struct lws *wsi, const char *command, lws_ssh_finish_exec finish, void *finish_handle)
+{
+       lwsl_notice("%s: EXEC %s\n", __func__, command);
+
+       /* we don't want to exec anything */
+       return 1;
+}
+
+static int
+ssh_ops_shell(void *_priv, struct lws *wsi, lws_ssh_finish_exec finish, void *finish_handle)
+{
+       struct sshd_instance_priv *priv = _priv;
+       const char *cmd[] = {
+               "/bin/bash",
+               "-i",
+               "-l",
+               NULL
+       };
+       lwsl_notice("%s: SHELL\n", __func__);
+
+       if (lws_cgi(wsi, cmd, -1, 0, priv->env)) {
+               lwsl_notice("shell spawn failed\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+/* ops: banner */
+
+static size_t
+ssh_ops_banner(char *buf, size_t max_len, char *lang, size_t max_lang_len)
+{
+       int n = lws_snprintf(buf, max_len, "\n"
+                     " |\\---/|  lws-ssh Test Server\n"
+                     " | o_o |  SSH Terminal Server\n"
+                     "  \\_^_/   Copyright (C) 2017 Crash Barrier Ltd\n\n");
+
+       lws_snprintf(lang, max_lang_len, "en/US");
+
+       return n;
+}
+
+static void
+ssh_ops_disconnect_reason(uint32_t reason, const char *desc,
+                         const char *desc_lang)
+{
+       lwsl_notice("DISCONNECT reason 0x%X, %s (lang %s)\n", reason, desc,
+                       desc_lang);
+}
+
+static const struct lws_ssh_ops ssh_ops = {
+       .channel_create                 = ssh_ops_channel_create,
+       .channel_destroy                = ssh_ops_channel_destroy,
+       .tx_waiting                     = ssh_ops_tx_waiting,
+       .tx                             = ssh_ops_tx,
+       .rx                             = ssh_ops_rx,
+       .get_server_key                 = ssh_ops_get_server_key,
+       .set_server_key                 = ssh_ops_set_server_key,
+       .set_env                        = ssh_ops_set_env,
+       .pty_req                        = ssh_ops_pty_req,
+       .child_process_io               = ssh_ops_child_process_io,
+       .child_process_terminated       = ssh_ops_child_process_terminated,
+       .exec                           = ssh_ops_exec,
+       .shell                          = ssh_ops_shell,
+       .is_pubkey_authorized           = ssh_ops_is_pubkey_authorized,
+       .banner                         = ssh_ops_banner,
+       .disconnect_reason              = ssh_ops_disconnect_reason,
+       .server_string                  = "SSH-2.0-Libwebsockets",
+       .api_version                    = 2,
+};
+
+/*
+ * use per-vhost options to bind the ops struct to the instance of the
+ * "lws_raw_sshd" protocol instantiated on our vhost
+ */
+
+static const struct lws_protocol_vhost_options pvo_ssh_ops = {
+       NULL,
+       NULL,
+       "ops",
+       (void *)&ssh_ops
+};
+
+static const struct lws_protocol_vhost_options pvo_ssh = {
+       NULL,
+       &pvo_ssh_ops,
+       "lws-ssh-base",
+       "" /* ignored, just matches the protocol name above */
+};
+
+void sighandler(int sig)
+{
+       force_exit = 1;
+       lws_cancel_service(context);
+}
+
+int main()
+{
+       static struct lws_context_creation_info info;
+       struct lws_vhost *vh_sshd;
+       int ret = 1, n;
+
+       /* info is on the stack, it must be cleared down before use */
+       memset(&info, 0, sizeof(info));
+
+       signal(SIGINT, sighandler);
+       lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE
+                       /*| LLL_INFO */
+                       /* | LLL_DEBUG */, NULL);
+
+       lwsl_notice("lws test-sshd -- Copyright (C) 2017 <andy@warmcat.com>\n");
+
+       /* create the lws context */
+
+       info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+                      LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+
+       context = lws_create_context(&info);
+       if (!context) {
+               lwsl_err("Failed to create context\n");
+               return 1;
+       }
+
+       /* create our listening vhost */
+
+       info.port = 2200;
+       info.options = LWS_SERVER_OPTION_ONLY_RAW;
+       info.vhost_name = "sshd";
+       info.protocols = protocols_sshd;
+       info.pvo = &pvo_ssh;
+
+       vh_sshd = lws_create_vhost(context, &info);
+       if (!vh_sshd) {
+               lwsl_err("Failed to create sshd vhost\n");
+               goto bail;
+       }
+
+       /* spin doing service */
+
+       n = 0;
+       while (!n  && !force_exit)
+               n = lws_service(context, 0);
+
+       ret = 0;
+
+       /* cleanup */
+
+bail:
+       lws_context_destroy(context);
+       lwsl_notice("exiting...\n");
+
+       return ret;
+}
diff --git a/test-apps/test.css b/test-apps/test.css
new file mode 100644 (file)
index 0000000..6cd32e7
--- /dev/null
@@ -0,0 +1,190 @@
+
+span.title {
+       font-size:18pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#000000;
+}
+span.mount {
+       font-size:10pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#000000;
+}
+span.mountname {
+       font-size:14pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#404010;
+}
+span.n {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#808020;
+}
+span.v {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#202020;
+}
+span.m1 {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:bold;
+       text-align:center;
+       color:#202020;
+}
+span.m2 {
+       font-size:12pt;
+       font-family: Arial;
+       font-weight:normal;
+       text-align:center;
+       color:#202020;
+}
+
+.browser { font-size:12pt; font-family: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; border-radius:10px;}
+.group2 { vertical-align:middle;
+       text-align:center;
+       background:#f0f0e0; 
+       padding:12px; 
+       -webkit-border-radius:10px; 
+       border-radius:10px; }
+.explain { vertical-align:middle;
+       text-align:center;
+       background:#f0f0c0; padding:12px;
+       -webkit-border-radius:10px;
+       border-radius:10px;
+       color:#404000;
+       padding:3px;
+}
+td.wsstatus { vertical-align:middle; width:200px; height:50px;
+       text-align:center;
+       background:#f0f0c0; padding:6px;
+       -webkit-border-radius:8px;
+       border-radius:8px;
+       color:#404000; }
+.tdform { vertical-align:middle; width:200px; height:50px;
+       text-align:center;
+       background:#f0f0d0; padding:6px;
+       -webkit-border-radius:8px;
+       margin:10px;
+       border-radius:8px;
+       border: 1px solid black;
+       border-collapse: collapse;font-size:18pt; font-family: Arial; font-weight:normal; text-align:center; color:#000000; 
+       color:#404000; }
+       
+td.l { vertical-align:middle;
+       text-align:center;
+       background:#d0d0b0; 
+       padding:3px; 
+       -webkit-border-radius:3px;
+       border-radius:3px; }
+       
+td.bigger { font-size:120%; }
+
+div.bgw {  background:white }
+div.conninfo {
+       border: solid 2px #e0d040;
+       padding: 4px;
+       width: 500px;
+       height:350px;
+       overflow: auto;
+}
+span.f12 { font-size:12pt }
+       
+.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; border-radius:10px; }
+.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; border-radius:10px; }
+.tabs {
+  position: relative;   
+  min-height: 750px; /* This part sucks */
+  clear: both;
+  margin: 25px 0;
+}
+.tab {
+  float: left;
+}
+.tab label {
+  background: #eee; 
+  padding: 10px; 
+  border: 1px solid #ccc; 
+  margin-left: -1px; 
+  position: relative;
+  left: 1px; 
+}
+.tab [type=radio] {
+  display: none;   
+}
+.content {
+  position: absolute;
+  top: 28px;
+  left: 0;
+  background: white;
+  right: 0;
+  bottom: 0;
+  padding: 20px;
+  border: 1px solid #ccc; 
+}
+[type=radio]:checked ~ label {
+  background: white;
+  border-bottom: 1px solid white;
+  z-index: 2;
+}
+[type=radio]:checked ~ label ~ .content {
+  z-index: 1;
+}
+
+       td.wsstatus { vertical-align:middle; width:200px; height:50px;
+               text-align:center;
+               background:#f0f0c0; padding:6px;
+               -webkit-border-radius:8px;
+               border-radius:8px;
+               color:#404000; }
+       td.l { vertical-align:middle;
+               text-align:center;
+               background:#d0d0b0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.dl { vertical-align:middle;
+               text-align:center;
+               background:#c0c0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c { vertical-align:middle;
+               text-align:center;
+               background:#c0c0a0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c0 { vertical-align:middle;
+               text-align:center;
+               background:#b0b090; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.dc0 { vertical-align:middle;
+               text-align:center;
+               background:#a0a0a0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.c1 { vertical-align:middle;
+               text-align:center;
+               background:#c0c0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
+       td.t { vertical-align:middle;
+               text-align:center;
+               background:#e0e0c0; 
+               padding:3px; 
+               -webkit-border-radius:3px; 
+               border-radius:3px; }
\ No newline at end of file
diff --git a/test-apps/test.html b/test-apps/test.html
new file mode 100644 (file)
index 0000000..f4b89a7
--- /dev/null
@@ -0,0 +1,258 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ <link rel="stylesheet" type="text/css" href="test.css"/>
+ <script type='text/javascript' src="/lws-common.js"></script>
+ <script type='text/javascript' src='test.js'></script>
+ <title>Minimal Websocket test app</title>
+</head>
+
+<body>
+<header></header>
+<article>
+
+<table><tr><td>
+
+<table width=600px>
+ <tr>
+  <td valign=middle align=center>
+   <a href="https://libwebsockets.org">
+    <img src="libwebsockets.org-logo.svg"></a></td><td>
+       <section class="browser">
+       <div id=brow>...</div></section>
+  </td>
+  <td width="64" height="64" id="wstransport"></td>
+  <td width="64" height="64" id="transport"></td>
+ </tr>
+
+</table>
+</td></tr>
+<tr><td colspan=2 align=center>
+Click <a href="leaf.jpg" target="_blank">Here</a> to
+have the test server send a big picture by http.
+</td></tr>
+<tr><td colspan=2>
+<div class="tabs">
+
+   <div class="tab">
+       <input type="radio" id="tab-1" name="tab-group-1" checked>
+       <label for="tab-1">Dumb Increment Demo</label>
+       
+       <div class="content">
+        <div id="dumb" class="group2">
+         <table>
+          <tr>
+          <td id=wsdi_statustd align=center class="wsstatus">
+            <span id=wsdi_status>Websocket connection not initialized
+               </span></td>
+           <td><span class="title">dumb increment-protocol</span></td>
+          </tr>
+          <tr>
+          <td class="explain" colspan=2>
+The incrementing number is coming from the server at 20Hz and is individual for
+each connection to the server... try opening a second browser window.
+<br/><br/>
+The button sends a message over the websocket link to ask the server
+to zero just this connection's number.
+          </td>
+         </tr>
+          <tr>
+           <td align=center><div id=number class="bigger"> </div></td>
+           <td align=center>
+            <input type=button id=offset value="Reset counter">
+            <input type=button id=junk value="Send junk">
+           </td>
+           </tr>
+        </table>
+       </div>
+       </div> 
+   </div>
+
+   <div class="tab">
+    <input type="radio" id="tab-2" name="tab-group-1">
+    <label for="tab-2">Mirror Demo</label>
+       
+    <div class="content">
+     <div id="mirror" class="group2">
+      <table>
+       <tr>
+        <td colspan=1 id=wslm_statustd align=center class="wsstatus">
+        <span id=wslm_status>Websocket connection not initialized</span>
+       </td>
+        <td>
+         <span class="title">lws-mirror-protocol</span>
+        </td>
+       </tr>
+       <tr>
+       <td colspan=2>
+         <div class="explain">
+Use the mouse to draw on the canvas below -- all other browser windows open
+on this page see your drawing in realtime and you can see any of theirs as
+well.
+<br/><br/>
+The lws-mirror protocol doesn't interpret what is being sent to it, it just
+re-sends it to every other websocket it has a connection with using that
+protocol, including the guy who sent the packet.
+<br/><br/>
+<b>libwebsockets-test-client</b> joins in by spamming circles on to this
+shared canvas when run.
+         </div>
+        </td>
+       </tr>
+       <tr>
+       <td colspan=2>Drawing color:
+         <select id="color">
+               <option value=#000000>Black</option>
+               <option value=#0000ff>Blue</option>
+               <option value=#20ff20>Green</option>
+               <option value=#802020>Dark Red</option>
+         </select>
+       </tr>
+       <tr>
+        <td colspan=2 width=500 height=320>
+               <div id="wslm_drawing" class="bgw"></div>
+       </td>
+       </tr>
+      </table>
+     </div>
+    </div> 
+   </div>
+    
+    <div class="tab">
+       <input type="radio" id="tab-3" name="tab-group-1">
+       <label for="tab-3">Close Testing</label>
+     
+       <div class="content">
+<div id="ot" class="group2">
+      <table>
+       <tr>
+        <td>
+
+               </td></tr>
+               <tr><td id=ot_statustd align=center class="wsstatus">
+                <span id=ot_status>Websocket connection not initialized</span>
+               </td>
+               <td colspan=2><span class="title">Open and close testing
+                               </span></td>
+               </tr>
+               <tr>    
+<td class="explain" colspan=3 >
+To help with open and close testing, you can open and close a connection by
+hand using the buttons.<br>
+ "<b>Close</b>" closes the connection from the browser with code 3000
+  and reason 'Bye!".<br>
+ "<b>Request Server Close</b>" sends a message asking the server to
+initiate the close, which it does with code 1001 and reason "Seeya".
+</td></tr>
+       <tr>
+       <td align=center>
+         <input type="button" id="ot_open_btn" value="Open"></td>
+       <td align=center>
+         <input type="button" id="ot_close_btn" disabled value="Close" ></td>
+       <td align=center>
+         <input type="button" id="ot_req_close_btn" disabled
+                value="Request Server Close" ></td>
+       </tr>
+
+</table>
+
+</div>
+       </div> 
+   </div>
+   
+    <div class="tab">
+       <input type="radio" id="tab-4" name="tab-group-1">
+       <label for="tab-4">Server info</label>
+
+       <div class="content">
+<div id="ot" class="group2">
+      <table>
+       <tr>
+       <td id=s_statustd align=center class="wsstatus">
+        <div id=s_status>Websocket connection not initialized</div>
+       </td>
+               <td colspan=1>
+       <span class="title">Server Info</span>
+               <input type=button id=pmd value="Test pmd">
+
+       </td>
+       </tr><tr>
+<td class="explain" colspan=2>
+This information is sent by the server over a ws[s] link and updated live
+whenever the information changes server-side.
+</td></tr>
+       <tr>
+       <td align=center colspan=2><div id=servinfo></div></td>
+       </tr>
+       <tr>
+       <td align=center colspan=2><div id=conninfo class="conninfo"></div></td>
+       </tr>
+</table>
+</div>
+       </div> 
+   </div>
+
+    <div class="tab">
+       <input type="radio" id="tab-5" name="tab-group-1">
+       <label for="tab-5">POST</label>
+
+       <div class="content">
+<div id="ot" class="group2">
+      <table width=100%>
+       <tr>
+               <td colspan=1>
+<span class="title">POST Form testing</span>
+       </td>
+       </tr><tr>
+<td class="explain" colspan=2>
+This tests POST handling in lws.
+</td></tr>
+       <tr>
+       <td align=center colspan=2 class=tdform><div id=postinfo>
+       FORM 1: send with urlencoded POST body args<br>
+       <form action="formtest" method="post">
+ <span class="f12">Some text: </span>
+  <input type="text" name="text" value="Give me some text"><br>
+  <input type="submit" name="send" value="Send the form">
+       </form>
+       </div></td>
+       </tr>
+
+
+       <tr>
+       <td align=center colspan=2 class=tdform><div id=postinfo >
+       FORM 2: send with multipart/form-data<br>
+       (can handle file upload, test limited to 100KB)<br>
+       <form name=multipart action="formtest" method="post"
+             enctype="multipart/form-data">
+  <span class="f12">Some text: </span>
+  <input type="text" name="text" value="Give me some text">
+<br>
+  <input type="file" name="file" id="file" size="20">&nbsp;
+   <span id=file_info class="f12"></span><br>
+    <input type="submit" id="upload" name="upload" disabled=1 value="Upload">
+       </form>
+       </div></td>
+       </tr>
+       
+</table>
+</div>
+       </div> 
+   </div>
+
+</div>
+</td></tr></table>
+
+Looking for support?
+<a href="https://libwebsockets.org">https://libwebsockets.org</a>,
+<a href="https://github.com/warmcat/libwebsockets">
+       https://github.com/warmcat/libwebsockets</a></a><br/>
+Join the mailing list:
+<a href="https://libwebsockets.org/mailman/listinfo/libwebsockets">
+       https://libwebsockets.org/mailman/listinfo/libwebsockets</a>
+
+</article>
+
+</body>
+</html>
diff --git a/test-apps/test.js b/test-apps/test.js
new file mode 100644 (file)
index 0000000..74bba69
--- /dev/null
@@ -0,0 +1,543 @@
+(function () {
+function check_file()
+{
+       var f = document.getElementById("file").files[0];
+       var max_len = 100000;
+       var dis = 0;
+       
+       if (f) {
+               if (f.size >= max_len) {
+                       dis = 1;
+                       document.getElementById("file_info").innerHTML =
+                               "<span style=\"color:red;font-weight:bold\">File larger than " +
+                                                       max_len+"</span>";
+               } else
+                       document.getElementById("file_info").innerHTML =
+                               "File length "+f.size;
+       } else
+               dis = 1;
+       
+       document.getElementById("upload").disabled = dis;
+}
+
+/*
+ * We display untrusted stuff in html context... reject anything
+ * that has HTML stuff in it
+ */
+
+function san(s)
+{
+       if (s.search("<") !== -1)
+               return "invalid string";
+       
+       return s;
+}
+
+/* BrowserDetect came from http://www.quirksmode.org/js/detect.html */
+
+var BrowserDetect = {
+       init: function () {
+               this.browser = this.searchString(this.dataBrowser) ||
+                                               "An unknown browser";
+               this.version = this.searchVersion(navigator.userAgent)
+                       || this.searchVersion(navigator.appVersion)
+                       || "an unknown version";
+               this.OS = this.searchString(this.dataOS) || "an unknown OS";
+       },
+       searchString: function (data) {
+               for (var i=0;i<data.length;i++) {
+                       var dataString = data[i].string;
+                       var dataProp = data[i].prop;
+                       this.versionSearchString = data[i].versionSearch || data[i].identity;
+                       if (dataString) {
+                               if (dataString.indexOf(data[i].subString) !== -1)
+                                       return data[i].identity;
+                       }
+                       else if (dataProp)
+                               return data[i].identity;
+               }
+       },
+       searchVersion: function (dataString) {
+               var index = dataString.indexOf(this.versionSearchString);
+               if (index === -1) return 0;
+               return parseFloat(dataString.substring(index +
+                                                                               this.versionSearchString.length + 1));
+       },
+       dataBrowser: [
+               {
+                       string: navigator.userAgent,
+                       subString: "Chrome",
+                       identity: "Chrome"
+               },
+               {       string: navigator.userAgent,
+                       subString: "OmniWeb",
+                       versionSearch: "OmniWeb/",
+                       identity: "OmniWeb"
+               },
+               {
+                       string: navigator.vendor,
+                       subString: "Apple",
+                       identity: "Safari",
+                       versionSearch: "Version"
+               },
+               {
+                       prop: window.opera,
+                       identity: "Opera",
+                       versionSearch: "Version"
+               },
+               {
+                       string: navigator.vendor,
+                       subString: "iCab",
+                       identity: "iCab"
+               },
+               {
+                       string: navigator.vendor,
+                       subString: "KDE",
+                       identity: "Konqueror"
+               },
+               {
+                       string: navigator.userAgent,
+                       subString: "Firefox",
+                       identity: "Firefox"
+               },
+               {
+                       string: navigator.vendor,
+                       subString: "Camino",
+                       identity: "Camino"
+               },
+               {               // for newer Netscapes (6+)
+                       string: navigator.userAgent,
+                       subString: "Netscape",
+                       identity: "Netscape"
+               },
+               {
+                       string: navigator.userAgent,
+                       subString: "MSIE",
+                       identity: "Explorer",
+                       versionSearch: "MSIE"
+               },
+               {
+                       string: navigator.userAgent,
+                       subString: "Gecko",
+                       identity: "Mozilla",
+                       versionSearch: "rv"
+               },
+               {               // for older Netscapes (4-)
+                       string: navigator.userAgent,
+                       subString: "Mozilla",
+                       identity: "Netscape",
+                       versionSearch: "Mozilla"
+               }
+       ],
+       dataOS : [
+               {
+                       string: navigator.platform,
+                       subString: "Win",
+                       identity: "Windows"
+               },
+               {
+                       string: navigator.platform,
+                       subString: "Mac",
+                       identity: "Mac"
+               },
+               {
+                          string: navigator.userAgent,
+                          subString: "iPhone",
+                          identity: "iPhone/iPod"
+           },
+               {
+                       string: navigator.platform,
+                       subString: "Linux",
+                       identity: "Linux"
+               }
+       ]
+
+};
+
+var pos = 0;
+
+function get_appropriate_ws_url(extra_url)
+{
+       var pcol;
+       var u = document.URL;
+
+       /*
+        * We open the websocket encrypted if this page came on an
+        * https:// url itself, otherwise unencrypted
+        */
+
+       if (u.substring(0, 5) === "https") {
+               pcol = "wss://";
+               u = u.substr(8);
+       } else {
+               pcol = "ws://";
+               if (u.substring(0, 4) === "http")
+                       u = u.substr(7);
+       }
+
+       u = u.split("/");
+
+       /* + "/xxx" bit is for IE10 workaround */
+
+       return pcol + u[0] + "/" + extra_url;
+}
+
+var params = {};
+
+if (location.search) {
+    var parts = location.search.substring(1).split("&");
+
+    for (var i = 0; i < parts.length; i++) {
+        var nv = parts[i].split("=");
+        if (!nv[0]) continue;
+        params[nv[0]] = nv[1] || true;
+    }
+}
+
+var socket_di;
+
+var mirror_name = "";
+if (params.mirror)
+       mirror_name = params.mirror;
+
+       console.log(mirror_name);
+
+function ws_open_dumb_increment()
+{
+       socket_di = new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol");
+
+       try {
+               socket_di.onopen = function() {
+                       document.getElementById("wsdi_statustd").style.backgroundColor =
+                                                                                                                               "#40ff40";
+                       document.getElementById("wsdi_status").innerHTML =
+                               " <b>websocket connection opened</b><br>" +
+                               san(socket_di.extensions);
+               };
+
+               socket_di.onmessage =function got_packet(msg) {
+                       document.getElementById("number").textContent = msg.data + "\n";
+               };
+
+               socket_di.onclose = function(){
+                       document.getElementById("wsdi_statustd").style.backgroundColor =
+                                                                                                                               "#ff4040";
+                       document.getElementById("wsdi_status").textContent =
+                                                                       " websocket connection CLOSED ";
+               };
+       } catch(exception) {
+               alert("<p>Error" + exception);  
+       }
+}
+       
+       var socket_status, jso, s;
+       
+function ws_open_status()
+{      
+       
+       socket_status = new_ws(get_appropriate_ws_url(""), "lws-status");
+
+       try {
+               socket_status.onopen = function() {
+                       document.getElementById("s_statustd").style.backgroundColor =
+                                                                                                                               "#40ff40";
+                       document.getElementById("s_status").innerHTML =
+                               " <b>websocket connection opened</b><br>" +
+                               san(socket_status.extensions);
+               };
+
+               socket_status.onmessage =function got_packet(msg) {
+                       var s;
+                       
+                       console.log(msg.data);
+                       
+                       jso = JSON.parse(msg.data);
+                       
+                       if (jso.wss_over_h2 === "1")
+                               document.getElementById("wstransport").innerHTML =
+                                                                               "<img src=\"./wss-over-h2.png\">";
+                       
+                       document.getElementById("servinfo").innerHTML = 
+                               "<table><tr><td class=l>Build info</td><td>"+
+                                       san(jso.version) + "</td></tr>" +
+                                       "<tr><td class=l>Server info</td><td>" +
+                                       san(jso.hostname) + "</td></tr>" +
+                                       "</table>";
+                       s="<table>";
+                       var n;
+                       for (n = 0; n < jso.conns.length; n++) {
+                               var d = new Date(parseInt(jso.conns[n].time, 10) * 1000);
+                               
+                               s = s + "<tr><td class=l>client " + (n + 1) +
+                               "</td><td><b>" + san(jso.conns[n].peer) +
+                               "</b><br>" + san(d.toString()) +
+                               "<br>" + san(jso.conns[n].ua) +
+                               "</td></tr>";
+                       }
+                       s = s + "</table>";
+                       
+                       document.getElementById("conninfo").innerHTML = s;
+               };
+
+               socket_status.onclose = function(){
+                       document.getElementById("s_statustd").style.backgroundColor =
+                                                                                                                                       "#ff4040";
+                       document.getElementById("s_status").textContent =
+                                                               " websocket connection CLOSED ";
+               };
+       } catch(exception) {
+               alert("<p>Error" + exception);  
+       }
+}
+
+function reset() {
+       socket_di.send("reset\n");
+}
+
+
+function junk() {
+       for(var word = ""; word.length < 9000; word += "a"){}
+       socket_di.send(word);
+}
+
+function on_pmd() {
+       socket_status.send("{ \"RequestType\":\"DDoS\", \"blob\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAJbElEQVR4Xu2af4xUVxXHv+e9O0OhIEibgrRlF8rOG6CWKiumrUlTo6byj6mmNFta2DdQsRJJrP5j+0cnjSVp1BoSU1yzO28LEeL6h7GJNhpJNa2CBQ1gd9k3QPlRBU2LLV0W2p173zFvZvbtzOybmbd7J9DEd/+befece97nfe+95/4gxEWLAGlZx8aIAWqKIAYYA9QkoGkeKzAGqElA0zxWYAxQk4CmeazAGKAmAU3zWIExQE0CmuaxAmOAmgQ0zWMFxgA1CWiaxwqMAWoS0DRvrQJ3ujeb17Hrx6SuzLwV32x7VzO+1pj3HJprJue8BcBQCSONRzr+2RrHaO2OtHDc1wDcUwqOj0s7nWpVoDp+hOMeA5Au+zggbesuHX+Vti1VoOh3j4BxRxEf8xmVSbe3KlAdP2bOPUmEpaXviiGZsVbq+IsBxgCnpp9YgVPjNan2RwagmXMfUpnUAEAc9k6Rx8AHB0zzy3euU5nUXk02RXMzl+9Ssw8PYN06FeYvOkAmM5dfpzLWL6LGFXkSMR13jIAEmKXMpBM6AIXjeih+BVLqTEcSWfJ/T71ks4bZ9vAYgU1/epC2ZegAFLnhAogEM0tV5x1r/UcGKBw3UJ1sTyVwH8laZ5EU2DO4WCTFmXFbOTbnemxZdHnq9AAMvDVTjF4ObKVS7di8IvA97jOSArNsiLZ8oGBpW5HYRKrkB1IFcGwkiS2dhWkB7B1qE6Z5uiUAew7NEsk5o4EveEtgLw98TwngwIApRlcFoogBVnzdSArUAWg6+T4QS3U69Xi98eiqK7A4vnX1+YsldWbPJmSzk8fJa6FAP67FXTv9sVLZqU1k9rvHibGsmKQzBlTGeih0gqgcA69CFxaO+zKA+4uxEF6W3dbaSXFdA4Bmzt1DhK5SLHycTMd9n4A5pT9oSNqp0GXO1VagcFx/cphZhnZF2tasjwJAkRs+CqJPFvEBIzHA8a8ScQyMAdbbTGgJwAY7FS3LA3edXCyUbJoHitzwKIhK3Zb5ssykr5/Uha9BHigc9whQ3nHyu7DIDe8A0TY/OI/xtJexngnP5ocLRCRAkLLbas1KhOGps6lE2MxvOO6TBvBsMS7Pe8rbtHz7pLhavRLpdwtgCDCkzIS/o9Gff8pg/n55EtlRSqT7h+8B0xhs62AYvHJlf524QZ3duzs0pfCT7aj7gQNsmqPHH1V2qr9+ewBePHYXIICNHfsb1TNzw7Y6e343svdNWh35dpHyQL9iKUV5VGVSu+qt94tx7DrZCa8wA93pP0deiTR80fLDyACjOGthncgAp9FmqwG+CsbnynHkpW1Z04ip5Sai3x0CY3kpU+P9sjt9d6saaSlA9JybJWaMXARDyRFjLrZ1fNiqQLX8DAwmxai4CCAhzblzsWFhsH7W8lvM8eOiRSAGqIUvVqAmvhhgDFCbgKaDqY+B2ayBBV2fQJJnwzQlSL6HjcsvhMaRHUwG/2dXjjWMtXdwPjBjPryCARKXIcQ7sJd8EOn9fFuTbkCBCDOMUZw69596SXWVv4HB2bicXAClBARdwqm95+stEurFER1gz6GESM45DGBFmLNJW+C5E7cKUmfH68pkoh3rl046r8CL7s3CQ+hdFQY2KNvaXS94o8/\" }");
+       socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"283463389\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAGqAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArlviX43Pw78H3fipdHl1RreSGJLSKQRtK0kqxgBiDjlvSuprhvjNpmoav4INnpljPdznU9Ok8qCMu2xbuJmbA5wFBJPYA0AL4r+K2j+G/hxH8Rra0k1G3ureOezto3CPOXXcFBwcHbk9O1bmg+L9H12wW7juEimSygvrmAtlreOVCy7jjngN+VeOal4L8Vz3Wv8Ag99DvG0Hw5b317pEixMUupLpS0UUf94w5ZMD1FWdLfW/CFxqcF34S126l1vwxpttZfZdPllU3EUMitFIyqREcuOXIHXninYD0y8+KPgCwNkt34ntU/tCGO4tyA7BopPuOSBhQc8bsVYXxtpEc+tHULm2tbPRVheW5afIKyAkEjHy9OOTnNeEHQNX0bw/pTQaH4l0/W38NabbNCNFkvbS/kjgUeTMqrmF1bKncVx1rZ1Lwz4tuV1bVL3wpdSJDf8Ah+/urCGBmFzFAxaeOIf8tNv90ZzgDvRYD2fw34w8NeLoppvDurRXqwNslChlZD1GVYAj8q0rq9tLIRtd3CRCVxGhc4BY9Bn3rlfBl/Z61reqazp/g660q3kjhiW8u7SS0muyoOR5UiqwVc4DEc89q2fFQSTRprVtFk1VrjESWyjhmPQs38AHXdkYx60gNivPb74uwWXhPW/EX/CPzyXmjal/ZQ05Zh5k85ZFjCtjjcZFxxXXeGdO1LSdDtNP1bUPtt1CmJJvXngZ74HGcDOM4FeYXfhPXm+Nq28elXDeG7yaHXbi68s+SLmGORRGW6bi7RNjr8hoA6XU/i3p1pYeHr+w043a+INPk1NMziMQwIiNuY7T/fA/OtS8+J3gnSntLbWtftbO6u4IpxESzhA4BG5gMKPc4ryPSvBHipIvGdheaDei20HT7jRtB/ct/pMLzSSK0Yx8w8t4k47oaj8XaLqVglzJpmjeIbfV7rRLSAWw0WS/sdUdIiBFJsX9yQSVJZhgHPanYD1B/jD4PsfEWseHNdvo9Nm0q7itUeRiwn8yGOQN8q/IP3m3k9R1ra8ceLoPBfgzVfGTWpvYdMs3vPKSQL5qqucBsHGfXFebDwpq15YfE681LwvIuoaotuLdRbl/NK2EAKxHHzgSBxx3Bre+IukavqHwA1TRbPTbq41GXw75C2scTNM0vkAbAgGS2eMYzmgDt9c1xNF0CfXXiRlhjWQo8mwckDlsHHX0rMf4leCYNUg0O78QW0OoT+WogO4gO+Nql8bQTkYBIrjfFvjJfFvgjUfDeleE/Fq3s1qgQXHh68gQlXTI3PGBnr3rjdc0rWtM1K8Ph3QdeXVbq6tJW0250iS50/UGUIPN89V2wEYOSzfKVziiwHsPhL4k+GfGer6zoujXLvcaLcfZ5gyMA3yg7gSOmTj8K6qvPfh9aT6V418b2d3ot5bG81NL22uDZusE0Jt4l+WXGwkMrZUHI9K9CpAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVma34m8OeGvsf/CQ67YaZ/aNyllafa7hIvtFw/3Yk3EbnPZRyaANOiisbQfGfhHxTc39l4a8T6XqtxpUohvorO7jme1kOcLIFJKHg8HHQ0AbNFZ2u+ItA8MWa6j4j1qx0u1eVIFmvLhYUMjnCoGYgbieg6mi08R+H7/VrnQbLW7G41KzjSa4s47hWmhjb7rOgOVB7EjmgDRoorO1/wAQ6D4V0uXXPE2tWOladAVEt3ezrDChZgq5diAMkgDnqRQBo0UgORkUtABRVbUtT07RrCfVNWvreys7VDJPcTyCOONB1ZmPAHuaNO1LT9XsYNT0q9gvLO5QSQzwSB45EPRlYcEe4oAs0VT1fWNJ8P6Zcazrup2un2FonmT3V1KsUUS+rMxAA9zU9tc297bRXlnPHPBOiyxSxsGV0YZDAjggggg0AS0UUmRnGeaAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqrqGpWumQ+dcsQCcKBySaaTk7ITairstUVz3/AAmdh/z63H/jv+NH/CZ2H/Prcf8Ajv8AjW31ar/KZfWKfc6Giue/4TOw/wCfW4/8d/xo/wCEzsP+fW4/8d/xo+rVf5Q+sU+50NFc9/wmdh/z63H/AI7/AI0f8JnYf8+tx/47/jR9Wq/yh9Yp9zoaK57/AITOw/59bj/x3/GtDTNcstVLJBuWRRkow5xUyoVIK8kVGtCTsmaNFFFZGgUUUUAFFFFABXin7YfhS78TfAbxBeaUCup+HVj16ykUZaOS1cSkr7lVYfjXtdVtRsLXVLC50y+hWW3u4nhljboyMMEH8DQBxNz8UNOb4IH4tQyrHaz6AurREH7peEMg/wC+mAr52/Zu8OXnwh+KfhZtT3xn4seHLi9u1JO3+0Y5DcA/UxMwA9qxNG1PUX+COl/s0vcNJrFt46m8ITg9Xs7aYzE/TyvK9sGvaf2ptPTwl4J8LfEvS4WB+HGuWGolYx8xsg4imT6eU7E/SgBn7Qif8Jp8Ufhb8KU/eQTanJ4h1KP0gtgBG3/fbH8q6zw7rXw8j+M/jO3stIltPENlpdrNq2oSSnypbfLbAATgbcEk4rmPh40fj39pfx146jdZ7DwtYWnhrT5lOV8woJ7gA/70gH4Vm6DDpFx+0b8XLfX7mO30yXwzZpeTSSCNY4T5gdix4UAZ57UAbEH7S2r67YyeJfAvwY8TeIfDCFzHq0UkcX2iNTgyRRN8zrwcHIzXK/tQ+P8Aw78Sf2QNf8X+Grh3sriWzVllXZJDIt5EHjcdmB6irvg3wv8AtE/BnwxZ6B4BPhj4ieEbGEDSkkujZXv2Xqi7yDG+FxghuRiuE+Mvivwf4x/Y78djw34Tl8MXVtq8EOs6TKuJLe+a7hMhPJB3cEEcGgD2LU/2htUjt5tY8J/CHxL4j8O2u7fq9u0cccqL96SFG+aROCQeMjpXo/gHx34f+JPhSx8Y+GLhpbC/Qsu9drowOGRh2YHgitXTNNsdM0q10mxtkitLaBIIoVGFWNVwFA9McV4l+xt8vw28QQrwkPjDV0Reyr5q8D86AO2/aF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBqP8AaF/5Ij40/wCwRN/KpPgF/wAkX8G/9giD/wBBoAn+Nt74T074UeJb7xzpcupaDDZFr+0ico8se5flBBBHOO9Y3iH4w+Gfh/4c8Jabo3h/UtV1HxDZxLoWh2QDTyRLCG5ZjhVVcZY1D+1X/wAm8eOv+wWf/Q1rktd+Hep+Lrb4aeLPh5440vRvHnhbQIns7W+Hmw3NrNbxrKskaneFPHzAH9aAOn8P/H+ZvF2neC/iJ8OtY8GX2tFk0uW6lSe3unUZKCRcbWx2IryTxZ8WPF1p+1VodxD8JvFM/wBj0TULaOxjeLddruT9+g3Y2jHfnmuq174jeNPDus+HbL9o/wCEumHSm1SKHT/EWk3v2m3t71uI2eMhZI89Bwa1vEP/ACeL4Q/7FbUv/Qo6APcrOeS6tIbmW3e3eWNXaKT70ZIztOO46VNRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXKeNSfNtRnjax/UV1dcp41/wBda/7rfzFdOD/jI58V/CZzVeear8Q9V0r4tWHg6e3g/sa8tlDT4PmR3D58vJ6YYgL9WFeh15/4t+HOo+IdY1fVrW/t4JLnTYobB23boLuKQSRyHA+6GVTxzxX0eF9lzNVdrf0/keFiPacqdPe5Qb4sXMPxG1fRbiGIeH9Js2Zp1UmWSdCBJjnGFZtv1U11Ws/ELw1oKeZqc8sajS5NX3bMj7OhQHv97Mi8Vx8fwf1MWcEMmq2rTto89reTYbMt7LIZXlAx90uSfXmn/wDCAePdSujfaxceHo3h8PzaNDAvmTxu7PEdz7lX5SI8EDkZ4zXVKGFk009Fo/Pz+ZzxniIp3Wr/AK/A2n+JNneQwG3t77S5jfQWzx39n8zrIpK7drYwcdcnGOlWrP4m+H77V49LtrXUmimuTaRX/wBm/wBFeYdUD5znIxnGM965DTvhN4lW4M00+n6fafbbW5XT4Lya4iTyg+91aRQQW3gbeny1veGPCXjXw+bTQFvdK/sKyvJLlZlLm6ljZy4jKFdo+ZuWDHipqU8Mk+V/j/V/QcJ4htcyO/rV8LkjWoAD1D5/75NZVavhf/kNwfR//QTXk1v4cvRnp0f4kfU7uiiivBPZCiiigAooooAKKKKAPEbP9mu3tf2kLv47HX1ayngLx6P5JxFetFHG9wGzjJEYzxnnrXqHjzwlZePfBWueC9RIW31uwnsJGIztEiFdw9xnNb1FAHl/7O/wZl+B/wAPh4Tv9eGt6ncXk99f6j5Rj+0SyOSCQSTwu1eT2p0HwVtp/iH428Wa3qEV7pfjPSItIn0/yirLGoYPl88hgxHAFenUUAeC+G/hh+0R8OdFh8D+CPiV4fvvD9kgttNm1nS3lvLK3UYSPcjqsm0YA3L0Apus/suyan8E/EXw0HjBpNc8VX8Wp6prdzb7vNuFmRziNSMLhNoGeM175RQAijaoX0GK8/8Agp8L7j4TeG9T0G51iPUm1DW73VhIkJjCLOwITBJyRjr3r0GigDm/iP4Tl8d+BNc8HQ3q2b6vZvarOybxGW7lQRn868h8L/CT9qDwj4e0/wAM6R8dPC62WmQLbwB/C+5gijjJMnNfQVFAHhmsfCL46eNfBviXwX8QPi1oWo2euaebSE2ugfZzBIWB3kh8sMDGOK0PGHwM128bwh4o8B+MF0Pxh4Q09NNivHtvNtr238tUkimiJ5VtoI5yp6GvY6KAPDL/AOEXxa+Juo6Snxj8ZaIdB0i9i1AaVolg8Qu54jmNpZJHZtoPO1cVr/FL4ReMvEPxB8PfFD4c+MLLRNa0O1uLGSO+sftMFzBNjIIDKQRtGCDXrlFAENmt0lpCl9KklwsaiV0XarPjkgdhntU1FFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFZ+r6RDq0IR2KOnKOO1aFFVGTg+aO4pRUlZnKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFb/XK3cw+q0uxyn/AAhU3/QQT/v2f8aP+EKm/wCggn/fs/411dFH1yt3D6rS7HKf8IVN/wBBBP8Av2f8aP8AhCpv+ggn/fs/411dFH1yt3D6rS7HKf8ACFTf9BBP+/Z/xrT0bw7FpUhuHl82XGAcYArYoqZ4mrNcrZUcPTg7pBRRRWBsFFFFABRRRQAVFcPcRqDbwLK2eQX24H5GpaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNH2jVP+gbH/wCBH/2NXaKAKX2jVP8AoGx/+BH/ANjR9o1T/oGx/wDgR/8AY1dooApfaNU/6Bsf/gR/9jR9o1T/AKBsf/gR/wDY1dooApfaNU/6Bsf/AIEf/Y0faNU/6Bsf/gR/9jV2igCl9o1T/oGx/wDgR/8AY0faNU/6Bsf/AIEf/Y1dooApfaNU/wCgbH/4Ef8A2NH2jVP+gbH/AOBH/wBjV2igCl9o1T/oGx/+BH/2NH2jVP8AoGx/+BH/ANjV2igCl9o1T/oGx/8AgR/9jR9o1T/oGx/+BH/2NXaKAKX2jVP+gbH/AOBH/wBjR9o1T/oGx/8AgR/9jV2igCl9o1T/AKBsf/gR/wDY0faNU/6Bsf8A4Ef/AGNXaKAKX2jVP+gbH/4Ef/Y0faNU/wCgbH/4Ef8A2NXaKAKX2jVP+gbH/wCBH/2NH2jVP+gbH/4Ef/Y1dooApfaNU/6Bsf8A4Ef/AGNOSfUWdRJp8aqSMsJ84HrjFW6KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACioZry0t/8AX3Mcf+8wFUZvEekxcfaN5/2VJosBqUVz03jC3GRBau3+8QKozeLb9/8AVRRxj86fKwOvorg5tc1Sb7124HoOKjtdUvba4WcXDtgjILZBFPlA9AoooqQCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACori5gtYzLcSqijualrkPFsztfpAWO1EBA9zTSuBqzeK9NjyIxJIfYYFUpvGMh/497NR/vtn+Vc3RV8qA1pvE+rS/dlWMf7Kj+tUZtRv7j/XXcrD0LnFV6KdkAUUUUAFFFFABQOtFA60AelDpS0g6UtZAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcb4r/5Cv/bJf612Vcb4r/5Cv/bJf61UdwMaiiirAKKKVVZztVSSewFACUVch0fU5/8AV2UuD3K4H61eh8KanJ/rDFF/vNn+VF0Bi0V08Pg5BzPeE+yrV2HwxpUX3o3kP+01LmQHF0DrXb3ui6b9ilCWqIVQkEdQRXEDrQncD0odKWkHSlrMAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigArjfFf8AyFf+2S/1rsq43xX/AMhX/tkv9aqO4FfSNGl1UuVkEaJwWIzzW7D4RsU5lmlkPpnApvg//jzn/wCuv9BW/Q27gZ8Og6TD92zRv9/5v51djhihG2KJUHooxT6KkAooooAKKKKAIbz/AI9Jv+ubfyrzoda9FvP+PSb/AK5t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv8A5Cv/AGyX+tdlXG+K/wDkK/8AbJf61UdwNPwf/wAec3/XX+grfrA8H/8AHnN/11/oK36T3AKKKKQBRRRQAUUUUAQ3n/HpN/1zb+VedDrXot5/x6Tf9c2/lXnQ61cQPSh0paQdKWoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK43xX/yFf+2S/wBa7KuN8V/8hX/tkv8AWqjuBp+D/wDjzm/66/0Fb9YHg/8A485v+uv9BW/Se4BRRRSAKKKKACiiigCG8/49Jv8Arm38q86HWvRbz/j0m/65t/KvOh1q4gelDpS0g6UtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVxviv/AJCv/bJf612Vcb4r/wCQr/2yX+tVHcDT8H/8ec3/AF1/oK36wPB//HnN/wBdf6Ct+k9wCiiikAUUVDc3dtZp5lzMsY7ZPX6UATUViy+K9MQ4QSye4XH86j/4S+w/59p/yH+NOzA2Lw4tJs/882/lXnQ610GqeKBdW7W9pCyBxhmbGce1c+OtXFWA9KHSlpB0pazAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiioLy9t7GEz3MgVR09SfQUAT0Vy9z4vkJItLYAdi55/Kqh8Vaqe8Q+iVXKwOzri/FDq+qttOdqKp+vNNfxNqrqV81Fz3C81mO7yOXdizMcknvTirAdV4P/AOPOb/rr/QVv1geD/wDjzm/66/0Fb9S9wCiiikBXv7tLG1kuX/gHA9TXBXd3PeztPO5Zj09h6Cuq8WFhpqgdDIM/rXH1cUAUUUVQBQOtFS2sTT3MUKjJdwv5mgD0UdKWkpayAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACuH8QX73l+67j5cR2qP5mu4rzzUI2ivZ43HIc/zqo7gV6KKKsAooooA6zweP8AQpj/ANNf6Ct+snwzbNb6WjMMGUl/w7fpWtWb3AKKKKQFXU7IX9lJbHgsMqfQ1wU8EttK0MyFWU4INej1UvtLstQXFzECezDgiqTsB5/RXUyeDoScxXjKPQrmmr4OT+K+P4J/9eq5kBzFdH4Y0h/MGo3CFQv+rB7n1rSs/DWnWrB2VpWHQv0/KtUAAYAwBUuXYBaKKKkAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK5/xFocl03220XMgHzr/e966CimnYDzZ0eNtsiFSOxGKbXok9laXP8Ar7dH9yOar/2FpH/PjH+tVzAcGAScAZNbOjeH57yRZrpCkAOcHgt7V1EOm2Fucw2kan6Zq1ScgEVQoCqAABgAUtFFSAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUV4145vPGfw28Sv8A8InG2oW3jeZbK0jnmythqTD5ZOTnyioJIHdR60Aey0V4pot5490ix1zSvB+pWctl4LPk3UupK8s+qXIjE07bs/uwS5A68+grpPCfxLv/ABPLr08dvFFb2Oj2WoWyEfMJJUlLqx7gGMYp2A9HorzjVPEWp678BR4qlYR3+oaBDev5JKASvGrHb3AyaxtB8X/ELw7NpFh4ludIvYNT0CW9tgmYPIlhiVgskrHDKRnLHGDSA9gorwvw/wDFbxvqGrSaLDrOj61PdaLcajbS2dlIkEU0WCYlkJ2zAhsblP4Vrat8a7qy0hPENnZxT2lj4dl1fUI8c/aMhI4Qf4cvv59ENOwHr1FeA/8AC8fFNlb38S3mn6zP/Z5u4JYbCa3jt5lkRTE+8fMCH4bg8Hiuz8P+IfHL+K7fwp4tu9Omi1zRZdRtZLOFo2tmVo1ZGyfm4k4PHTpRYD0uivJNOtPG2hfFTw/4WTxTHcaRZ+H4xLHLAS8/l/Izls/fLDOfTitz4oeLdY8OzaXZaVruk6QL1pDJcXiGeVto4SKBSGkJ7kdAKQHf0V4x4c+KXjbxtpnhvStE+wWWs6oL57u7uLd/KjjtZjESsJw25yM7SRt71l6B4v8AH2malq3h1prKTxFrPij+zop3LNbQItuZXkCHnGxGwvHJ/GnYD3uivAL3x54w+HviHxjdeKLy0vbyKPTbW0liV1t2MgbEhi5K4AOQM5IqxafG7xKTceH7aSy1fU7uezttNv1s5bW3Ek8yxESK4z8m4NwTkA9KLAe70V5R4afxxa/GWXTPF2qWl6q6BHJDJaI0UbgzSZLRkkBgQRnJyAPpTvin4+8SeHNXaw0bX9H08Q2LXSwy27Xl1dOOiiGM7lX/AG8HrSA9Vorxaw+I/wAR/GCyT+Gm0nToYfDljrjG5haUl5oi5iGCOOOvb0Naum/Fy9tlj1TxNBBBpt14ck1iFkHPmwf69M98g7gPRTTsB6pRXC6v4n8W6D8JD4q1C2tR4gisIZ5odpESzuVDJjrgFiPwrmbfxD8Y7vxPH4STVPD8ct5pX9rrdm1ci3UMqmLZn5yTIvzZHAPFKwHsFFeY/Dn4ma14vv8AQre/treFdR8PR6lOsYJxP5jo20/3fl4FcrffGDxsdO0y/mu7HQ7C5F75uqz6dLcWwlimKRxPs4iBUElmOOKdgPeKK8XvPibfaZfT+IpYbG6n/wCER0+8X7LcmSCS4muJUARgdpTcQQwGSK7fwkfiZDqYj8Xy6Ve2Fxa+cs9mhia3myP3RUklxgn5h6dOaQHY0V5D4m8S+L/DfxV1e9l1iCXQ9P8ADceoiwEJ3N+9lUDdn7xZfvemB2q5pPjD4gaXqXhe68XSaXcab4skEEcVpEySWUjxNJGCxP7wELtJ4wTTsB6lRXI+J/Fl74Z8WaNb3nlDRNThuI5JSvzx3KAMoz6Mu7j/AGTXIaV48+IXi2+0/QdFfTdNury1uNVkubmAyLFarKI4kCAjczbgxORgUgPXaK8s8JfE7xBrHiPRvDWqWtrFdedq1lqZiyVaa0dEDxk9FbcTg+uO1ZsnxM+IGq67beHPD0GmLcXusahp6zXCNshighVw5xyTyeO5wOOtFgPZaK5Pxz4i1jwnpWl6rH5EsS39vBqRKHHkvlSy+nzlfzrhrD4weJNTn1TToLS1S6m1mytNGJUkSWssxjkkbnkoEkY49BRYD2WivBrn4i+KtFspTYPDpmnyeINcgutVlspryK3MV46xq6qcoGAPzEhRjirEGv8AjrxV408MTeHNf0dLi98OXslxcxMbmzIW4hAkRAQGJ4HJGNx9KdgPcaK4zwL4n1TxX8Pm1jV44Y9Qj+2WlwYMhDLBI8TMueQCUJH1rzPRfFvibS9J0a40XTpNX1NPCmrXkELO7PNLHdIFXGfm6+hPGBRYD3+ivALz4i+Mte8F+JU0zxto095YW8E4kjs3trq3LPh45LdzvXthz15GK9p8J/2ofDOmNrV3Hc3rWsbTTRpsV2IzkDtxilYDWooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACsPxJ4TsvE11ot3eXM8TaJqCajCI8Yd1UgK2QePm7YNblFAHCa58K4tU1PVb7TPFWraNBrwX+1bWz8ry7ohAhbLoWRigCkoVJAqG/+D2nyXckuieI9U0W1u9Oi0u9tbPy9lxbxghRllLI2GYblIPJ5r0Gii4HNxeBtOh8AwfD1bu5Nlb6dFpqzEr5pjRAoY8Y3YX0xWf4l+FeheKrS1sdSu7wQ2ulXGkBY2Ub4povLYk4zux0x3rtKKAOD0b4XXGneItL8T3/AI21fUrvSraSzjWaOBI2gfb8hVI1GcqDuHzH1xTtK+D3hTTNL8R6M/2i6tPE25blJnH7uMhv3cZABVQXcjuCa7qigDgH+FDahY3Nl4h8ba5qwmtltImnMKmGMOGyAiAMx2jLMCfeuh/4RCxHiTTPE32mf7Rpeny6dHHxsZHZCWbjOf3Y6HHJreooA57UvB0V94v07xjDq13aXNhA1q8MQjMdzESTtfcpI5OcqQfeqninwE+v6/YeJtN8SX+jajYW8toJbaOGTfDIVLKVlRgOVUggZ4rrKKAPNrD4KWekWNlDo/i3WLS90y7u7iyvx5TzRpcPvlibehEiljn5wT70sHwT0u3spwPE2sNqkuqLrMeqM0Znhugu3co27SCuQVIIwSMYr0iincDzmP4L6dM2s3eteJtX1PUNb+zvLeSmNHhkh/1bxBFCoR0wBj25NWbj4Tw6rp93B4h8Waxqd7cNBJBfu0cUlo8L743iWNVRWDAEnbzjnIrvaKVwOM8OfDh9G8UzeMtU8WaprWqT2a2LPdLEiCNWLAKkaKq8seg569aj174YprHiG+16z8U6ppn9rWkdlqEFsIitxEm7ADMheM4ZhlCp5rt6KAOH8IfCrTfCNlNaRazf3hm0mDRy84jBEMKFEI2qBu2nGfauf8UfDf8AtF/Bvgmz028l07RLtbq41GRlCmBQ2+FsY3eYG2kYxgk9q9YoouBleJ/D1r4q0G78P3k0sMF2qqzxY3ABg3GQR2qtb+D7G28SxeJ0uZzcQ6YdKEZxsMZdG3dM7sxjvjk1vUUAedW/wZtNLg0ZNA8Wavpk+kWTad9oiELNcW5YttcMhAOScMoBGetGn/B86BYWdn4Y8c65pr2scsLyfuZ/PSR953pKjLuB6MAD716LRQB55ZfBDwjZae2lJLePato0OjGNnH3I5XlWXIGQ+9yfTgcVr+GPAb6DqY1jU/E+p65dxWpsrd7wRqIISQSFWNVBJ2rljknHWusooA5TXvh7p+v+JD4huNRuoxNpp0q7tFCGK5t9zsA25SwIMjcqQaz9D+FUWl6npV7qXirVtYt9B3f2VaXfleXakqUDZRAzkKxUFyxANd3RQBwHxh0C/wDF+jWXhWw0q4ma8u45GvEYKlmqn5mY5zkqWUAepq/rXw5t7y70zVNA12+0HUdKtDYRXNmsbl7chcxukisjDKqRkZBHFdhRQB55/wAKc0+0tNK/sTxJqunanpc9zc/2mhjknuJLhg07SiRWVtzDPTjtirHhj4R6P4Y1Cy1ODWNSu57K7u70NcOrGSS4jCPuwo44yMfy4ru6KLgZfifw/Z+KtAvvD2oPIlvfRGJ2jIDLzkMM9wQCPpXM6V8IfDuk614c1uC7vGm8N2ktrAjsu2YuCDJIMcuNzYIx9413VFAHBn4VfY2a48PeMNY0m6a+vr1pYhFIr/apmldGjkRkIDMdpIyB3qfwl8K9E8IajZ6rY3t3LPa2dzaMZSuJTPKkjyMABhtyDgYGCeK7WigDC8N+EbHwzoM3h+0uZ5YZp7qcvJjcDPK8jDgAYBcgewFc6vwc0NbW1tU1fVI/sel3WlRSRSiOQJPKspkDKAQ6soxjj1Brv6KAPPf+FQwX76jdeJvFWp6xe39gNNW5ljgiaGAMWG0RIqk7jnLA10Wg+F73RZ7SWfxVqmoJa2IsvJn8tY5CGBEpVFA3gDbxgYzxmugooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Z\"}");
+       socket_status.send("{ \"RequestType\":\"SendImage\", \"RequestID\":\"788346414\", \"toType\":\"toUser\", \"toID\":\"1036\", \"fileType\":\"image/jpeg\", \"blob\":\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAHgAoADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/AHh+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv8AeH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/wB4fnRuX+8PzoAWik3L/eH50bl/vD86AFopNy/3h+dG5f7w/OgBaKTcv94fnRuX+8PzoAxst6mky3940UmRQA7J9TRk+ppKKAAFh/EaCWP8RoooAXJ9TRk+ppKKADL/AN40Zf8AvGkwKMCgA3N/eNG5v7xoI70Ad6AF3Mf4iPxpMsP4j+dHSlxmgBCWH8R/OlBb+8fzpCM96OgoAVWb+8fzoLN6n86OBTSc0AOy3940Zb+8aTg0YFACkv8A3jRlv7xpNvvSYx3oAXLn+I0Ev/eNAwOc0YBNABubHU/nSbm/vH86VaMc5oATc394/nTt3+0aTb70baAAl8/eP50uW/vGk20baADc3940B27k0babQA/Lf3jRlv7xpMAdTRgHpQA4lj/EaTLf3jTSMUvFAC5b+8aMt/eNJjHNGAelAC5b+8aMt/eNJxQSD3oAXLf3jRlv7xpNvvRjHegBSW/vGgFv7xpuB60YHrQA7Lf3jRlv7xpAQO9IMDvQA7Lf3jRlv7xpuM96XbQAuW/vGjLf3jSfLR8tAC5b+8aAW/vGkAFJgetADst/eNGW/vGk2+9IAPWgB5LH+I0mW/vGmkYpeKAFy3940Zb+8aTGOaMA9KAFy3940Zb+8aTigkHvQAuW/vGjLf3jSbfejGO9ACkt/eNALf3jTcD1owPWgB2Wz940mWzjcaP4aMDFAC5b+8aPm/vGmjA70pGaAF+b+8aPm/vGm4HrRgetADst/eNALf3jTcD1pQPegBQX/vGjLf3jSAClOO9AAS2fvGgFs/eNJgYzRgYzQApLf3jQS3940hA9aQjHegB2W/vGjLf3jTcD1owPWgB1FFFABRRRQAUUUUAGc0UnzUtACZFGRRk+lGT6UALSZx2paMigApMr1paTJ9KAA57UDPelpPmoAMCjAoyKMigA+Wj5aMn0oyfSgBcYowKKKAEwKCAO1Lz6UhJoAMg0ZApfwo/CgBMCjApAT0FOoATAowKWigBMCjgUZPpRk+lAAeDmlpDycUtACfNSgGkzntRz9KADn1oOR3oyBQSD3oAXoKRTnqaXp1ozmgAooooAKKT5qPmoAXPGaQEmlBzScCgAwfWjB9aMijIoADntQM96BnvQc9qAFpP4qXPOKOM0AITilHNHtRQAnzUoBpM57Uc/SgA59aDkd6MgUEg96AF6CkU56ml6daM5oAKKKKACik+aj5qADqOKFpQc0ZFACEZpQMUgPrS0AIc9qBnvQc9qBnvQAtJto3UtACE54FKOKAc0ZxQAUUm72o3e1AAc9qTBFKCaDntQAtFITigHNACg5oJxQBigjNAATQPeikyKAFoozijOaACg57UUUAJgUv4Ugz3paAEwKMCl59KOfSgAooooATgUGl59KKADgUmRS0mT6UALRwKKTJ9KADg0vAoOR2pMn0oACfalyKTJ9KUnFABRjFFGRQAcdBzQeODwPSuN+IXxa8EfDLTW1DxZq8NrGASFJyx/Ac189aj/AMFFvhRaXTQ2K/aVU4D/ADCgD635AyRj3oPTI596+evh3+218HvHl8mlx6wlrdyHARwcZ+pr362vrW8tFvbW4SSJ13KynIIoAm45/hPal6ngY968W179q34XeHfGjeBtQ1YDUldY/L2Hqa9lgnS6hSdPuSorg+xGRQA89KWkPSloAQDFLRRQAc+lI1LnFITxQANSjgUUUAIc8sB16ilHp+leZ/Fz4++Bfgy1jH4xvxbPqG7yvlJzt+ldN8PfH+hfErw1beKfDtx59ldZ8t8Yzg4oA6XHt06UuOpHJpMkHPf0rB8ceM9H8A+HLnxNrkvlWVqAZG69aAN4nC5xk96Mc4HXtXlvwq/aJ8AfF2+ubHwjqQmltSBINpHX616iB6jPvQA7A60UUhGaAA57UtIc9qWgABzSYpQMUZoAQ9KWkPSloAQDFLRRQAc+lI1LnFITxQANSjiiigAooooAMYoPNIc9qWgAHSiijPOKACiiigBPmo+ag57Upz2oATrQDmjpQBigBaKKKAEBz2paQYzSk4oAQnFKeKQjNKeaAEY0ueM0YzQelAAcnvR+NJxnOaOtAC4zRSEZpaACiiigAooooAKKBzRQAUUUUAH40UHHeigAopMCjAoAX8aPxpDjvScetADufWjn1pDjvQMdqADB9aMH1oyKMigA59aXn1pDjvSkjvQAgyc4rmfiR40sfh94O1LxPqMgWO0iJBJ744/Wum6jBr5Z/wCChOuXen/Bi6s7aRkW5xvIPo1AHxBdah8R/wBrj4vnTo7u4aGe4ZQoY7Y0B/LpX2j4Q/4J4/CnTdESDX4ft96yAyS5K849K8x/4Ji+FbSX/hIvEVzErzr5XkuRyvXNfoGAoHAz70Afl5+1T+x3cfBy2HjPwJPM2nodzxxAgxc8c9TXuP7Anxn1/wAV+Hb3wT4j8+STTY8RSSggnIPrX2B4h8N6N4p02TStbtEubab78bDg1z/hv4V+BfBBnu/DWix2csincy9+KAPzF+M8m39rm4ZjhRdxdT06V+j+rfHf4X+BrCytPEfii3t5fs8YKr838I9K/MH9qI6j/wANIawulyFblp40jYDueK+h/CP7Auv+OvDCeJfG3jO4i1O6hDrEyltvHHf0oA+1vBnxW8B+P4xJ4X1+C6B7AgH8utdaCBkk5B6e1fjday+NP2avjpHoEGq3BksrqOMqWO11cgdM46Gv0i/aI+Nn/Cqvg5/wk0L41G6tIzAueSWUZP60Ad74w+MHw+8CKW8SeIbe229VDBiPwFZ3hX4+/CzxpdCz8PeKLeeU8YY7f51+dfwU+A/j39qnVr3xb4p8Tz2tk8m4SsxIcEnjGe1bHx6/ZA8SfAnSR448G+LJruCyIeXYSu054780Afp7G6yrvQh1PRgeKz9a8RaN4dtGvdX1CG0iQZLO4Gfzr5i/Yq/aIu/iN4GvNK8S3e/UdDiy0jHlxg/0FfLPx9+LPjv9oP4vt4A8MXs6WInMMMUTEdPvHIoA+/JP2pfgjHfGwk8YwifO3btOM/WvQ/D/AIr0DxVbC70LU4LqMjIKOCfyr4bsv+CbUkvhcXdx41mGpvF5m3YchsZxnNeS/BzWfiz+zv8AGhPC98uoXGmpOIrhSrMrg8Kc84oA9N/4KiFheeEDuwczYHr0r6E/YZOf2fNAYntJwe3zV86f8FN5zcDwRc7cNMkjn2yAa2vhh+0h4f8Agn+y3owa5SbWJ45RBbhuc7utAH2jrXxE8HeHbr7Hq+u20Ew6ozjNeTftU69pev8A7P2v32lXAmhZF+ZenWvjP4OfCf4lftUeO38ceKdQu4tIabe77yFK54AANfX/AO0f4N0rwN+zTq+g6OhjhtokVcsWJOeetAHy/wD8E6NWsNI8R+Ib7VLyO2hjZSWdgB39a+1Zf2nfgvFqR0mTxhCtyG2425GfrX5UfAnwN45+JnimTwV4TvJrW3vpMXUkf8IBr6s8Xf8ABOO7sfC8moaL4xmn1OCIyFdpBY4yec0Afd+i6/pHiG0F9o2oRXMDAEFGBq+8ixxtJKQEHcnFflj+yj8a/GPwg+K8fw78V3s7afLMYZklYnZjgdfU19O/tw/tEX/w08I2eieGLny9Q1qM7ZF6oMD+hoA9y8VfHn4XeDJWt9f8UW8MgOMKd38qd4T+Ovwy8bTeToHii3mc8BWIX+dfnz8AP2Q/F3x7sH8b+L/EN1Z2t0S0buWbfzzxnioPj/8AsneMv2ereLxr4T8Q3V5ZW7b5HQlAnPHGeaAP1Gmu7eC3N08gMaruLA8Yrmbb4p+BLu8TT4fEVs1ySVEe8A5/Ovn/APYt+Ok/xm8DXXhjxROZb+yjEUjE8sDx/KvmD9rj4NeKfgj8QI/G/hm5vP7Knm85cSMRHg5PfuaAP1Ha4hihNzJIqx7dxYngCuWh+KngWe7+wQ+ILd7gttCKwJJ/CvhLxj+3edT+BltoemztH4luofJlYdUxx+oq5+wr+z7rXiPUh8VfGU939n3mSzSSRsSEk7uM0AfoYjh1DL0IBFOPtTY0WNAi9FAApSaAFOT3opOBRkGgBaKKKAAmijIoJxQAmD60YPrRkUZFAC/jQOKQAUvAoATPGaM8ZoPSg9KAFooooAAc0E4oooAKKM5ooATgUvBpODS8CgAopMijIoAWiiigBM84paT+KjHOaAAUZ5xS0e9ABRRRQAUUUUAJ81HzUmB60YHrQAvzUfNSYHrRgetAClvSgZ70tJkUALRSA5oJxQAtI1GRQT2oAGoPShqD0oAAcmj+KgHn60fxUAB7YoPSgnmgnigAB4znrXzR+3v4TvPEHwS1C7so2lktApCqOeWr6XyNpBXkdKy/Evh+x8T6Jd6HqcKvFdxlCp5HSgD8+f8Agml4/sNH1vXPB+ozrHPfmNbdWOOVzmv0ZJ6gYr8mPjT8BviX+zf8Qz4u8JJcPYrMZra5hUngnJGB+VereEv+Ckuv6XoyWPiXwXNdXUKBfOZypcgemKAPsT9oX4sR/B/4dX3ilNjXUQHkxFgCxzivF/2Vf2pfFvx31nVLLUtNMNvZr97dkcg18ffFn41fFX9qjxFbaLY6Rdw2Uj7YrRVJVQfVsV94/slfs/j4LeAWfU4h/a9/FuuOORxxQB8MfGlFk/a4uFcAj7ZD1/Cv1f0dVTRbJEGB9mj/APQBX5UfGWyvH/a2uHWznK/a4fmEZI7d6/VjSfl0ezXv9mj/APQRQB+U/wC2QAP2oJ8Lg/bLbt7ivb/+Ch73q/DXwWsDN5JtB5uOn3VxXi/7Y1jezftNzSRWk7j7ZbfMsZI6r3r7e/aK+DUvxf8AgfDpmnW+7Uraxia3Hf7oJ/lQB8Ufs/eG/wBrDUvBFvP8JrnGjHOxVKZHPfPNdx4q+FX7c3ifRbjR/EtwJrCcASI7RqPzrhPgN+0J45/Zf1a78I+JvDt1PYb9vlEECPB7HHeu5+Nf7dPib4laGfCXgHw7dWcl5hWmj3E5zx2oA2P2afgB8T/hND4l1XxBbRwQ3FuxPlzq+fkPYGvKP2NTbP8AtMRPqpUuLqfbv79fWvrb9jb4Z+OrHwXfav8AEW5nkl1iLEcMpJ2jBH9a+Tfjp8LfHv7OvxjPjnw7p85sTOZraWJSQRnLdKAP1aBVUBBBGPyFeb618Qvg1ZeJX0jVbywGrKyqytEpbcenNfJw/wCCj8p8IG1bwfMdX8nys7jndjGeleU/s7/Df4h/H74zr478QxXkVgJ/NuJX3KMdVGKAPRf+CoLo7eDnh6MsxXHcYGK+VdI+C/xL8TfD6bx3a2E8mjaeAyHdkcnnAr6u/wCCmunXIbwXb20EswgWRPkQnoAO1e7/ALHPh201j9mrS9I1awGy5ilR1dMHknrQB5F/wT7+PdlNp/8Awq7W3igurc7bbIAL85NfQX7YRB+A2vt1BROfxr8+/jp8MvFP7N/xmXXvDkVwtq9x5ttJEhIIzlhxX1r45+KsPxf/AGTdR1eCGUXawRrPEUO4EHFAHj//AATVOlDxTr3nbBdl18vPXvnFfovPsMTBz8m07s9xivxa+Cni/wCIPwo8Uv448PaReTW9lJm4QIwyCcelfVnij/go7cXPhOW10nwjPDqs8Xl5ycqcYJxigDwj9oQRp+1tfroG3aLy32lOnUZrr/29/tv/AAmvhtrzd5XlQ7M9Purmsz9lb4Q+MfjV8YY/Hviayn/s+GYzXM0qkbs8r1r6l/bh/Z51H4leDrPXPDFoZNQ0OM7Y1HMg4/kBQB67+zCbD/hTegiwKeWYjkrjk1T/AGsf7OPwR13+0dhTyxgN65r4c/Z7/a68W/AnTj4H8WeHrq7sbViibwy+Wc89uai/aF/ay8X/AB/ij8FeEvD11a2M7BHCBm83J47cUAdD/wAE2zc/8J9riwqRbiQbj2xzivq79sXxF4E0j4TalB4yjhla4TFvG33ic9u9cN+x78G5PgT8Or3xj4xgMF5eRCaRCMlcAkfnXyf8cPHnjX9qD4vw+G9Ftbs6atx5dvFsYKFzhjQB876W9tZ67bajfWbyaeLoOFIIBTf6/Sv2n+Avinwh4q+G+kah4N8lbIQhfKTqpAAOfxr5/wDHv7E3h6b4EW+haPZr/bunW5lS4A+ZifmIr54/ZA+NXiT4JfECTwL4qhu00u4n8ly0bERkHA7dzQB+poPpSDOeaitLqG+tormE/LKgcfQjNS8YwaADvR3o70d6AAnFHQUEZpaAAc80HnikJxS4xQAU3JpQc0EZoAU8UgNKeaQCgA3UZFHy0fLQAZFGRR8tHy0AGQaMgUny0fLQAuRRn3pDtxSjaaADvR3o70d6ADn1oOR3oIHrRgHpQAEkDrQARzRjPNAAoACcUtITiloAQDvS0UUAJ3o70d6O9AC0UUGgBAeaCOc0mBig4xQDVg5zkUvPrQMdKBjPFJK/UNWHA4ozQwxQRTt5hdCE0ueKRqXHFHzC6BaAecYoFIetO6DQUkjvQcgdaQ896OtK6DQXIHSjIoA4pB1o1DUM0ZoHWlajUNRaMUh6YoWlqBS1PRdM1u2az1OyhuYmGCsiA/zrzLUv2V/gjrF013f+DIWlY5JDkD9K9ZODgmg4DYLZFPUZyHhL4SfD7wOAPDvh62tj2YoGI/E115A/1eARjFKeRjHSj5cZzk+lOzE7HJXnwr8DX+sHXbzQoJL1iG84gZyK6tFVVWNQAEGAPQU8YIwBSdyCdxPalqLc5TWvhd4I17UjrOraFBPeFg3mMBnI6V1EUccUaQxgeWi7QuO1PHXnj0o3EfL2FFhnGeLPg98PPG0vmeI/DltPJ/eChT+grO8O/AL4U+FrsX2jeFreKVTkFhu/nXooOBn0oJzyOlFgGRxRQxpHDGqIvACjAFZ+u+GtE8SWr2OuWEN1E4xh0BxWkOufXtRz0zg0rtB7zPK3/Zf+Cz3f2tvBkPmk7t27jP0r0LQvDei+GrNNP0TT4bWKMYARAP1rSIz06+tKD2LZI600nILM57xR4C8LeMjC3iPS4rtrfOwuM4zWlouh6X4c0+PStHtVt7SP7iKOBV7nlg2c0vG0ZGCKAOf8TeBPCvjARf8ACQaPDeeVnbvUZGarWXw08GaZpU+i2mjRR2VxjzIh0P4V1HGN4GCetB9hgetAHJad8KvAWmW1xaWfh21SO5H71SgOf0rmpv2Zvgxc3IvJfCEAk3bg2e/0r1IEgbs5IoXptxwaAMzQfDWheGLJdP0OwhtYIhgBEAzWk6LIrCVAynjBGQaXg8E5pM8H+8egoA4PxN8C/hj4vuTda74Xt5ZW5LKoX+VL4X+B/wAMfB9yLnQfC9vFKvIZlDY/MV3mcDBPTtS8AdeT2p2sw0XQpalpOn6rZNpl9CJLZxhkAwMVzuifCjwJ4d1AappPh+3guUJKOFBPPWuu7YB4WjqcjqaSTb0YIR1V1MTKCMYxXGXvwe+Heo6k2qXXh23a5dg28KAcjvXaZ6ZOcUDjhjgetF0O5Fb20NpAlvAMKgCgegFSnOQc0ZA+ajjv3ouguLRSAYpScUm3HYQhOKWjIFFHqAhGaWk3Uuad0AUUgOaDzTugFHFNPWnZpo60XQCnpQOlLRRdAIM96Dk0ZFGRS0QW1uGD60YPrRkUZFK6YO973A9KAMUtJupgAOaCcUZA4oPSgBc0mD60ZAoyKADJ9KMn0oyKMigANABpcZpuB60ALg+tGD60ZFGRQAvPrRRkUUAJg+tGD60YFGBQAHPagjNGBQCACc9KG1b3gbSXvBjvnHvRjuR+NeX/ABB/aE8BfD+8bTr7UUe7X70YycfiK4cftneAedqcDqea46mMoU92cVTMMNSdnI+iM/7X6UZ/2v0r54/4bO8Bdk/nR/w2d4B/ufzqf7QofzELNMK/tH0OcHvQMDvXzv8A8NneAv7n86P+GzvAX9z+dH9oYf8AmD+1ML/MfRHPrRz6187/APDZ3gP+5/Oj/hs7wH/c/nR/aFD+YP7Uwv8AMfRB3HvSYP8AEa+eD+2d4C7J/Omy/tpeAIo8uvT61rTxtGbsmH9qYX+Y+icn1oHHevmj/huj4c/3P50f8Nz/AA6/ufzr2FleKkrqI/7Uw38x9L4PpQQT2r5nP7c/w4/55/zo/wCG6Phx3jP60/7KxX8of2phv5j6ZNA4/wD118zf8N0fDf8A55n9aP8Ahuj4b/8APM/rR/ZWK/lF/aWH/mPpgcUdTXzP/wAN0fDf/nmf1o/4bn+HHaM/rT/srFfyh/aeG/mPpnnOB0pAMn6V8zn9uf4c8/uzx9asWf7bnw6vrlLaKP5n+tH9lYr+Ul5thIK8pH0jn/apPoa8NH7WHgkgEJ/Oj/hq/wAFdk/nT/snFP7Bxf6yZbF2lUPc8H1owfWvDP8Ahq/wX/d/nR/w1f4L/u/zo/sjF/yh/rLlv/Pw9zz/ALX6UZ9/0rwz/hrDwV/c/nR/w1f4K/ufzpf2Ti/5Q/1ly3/n4e5k+9A69a8M/wCGsPBX9z+dH/DV/gofwfzprKMU/sjXEuXJfGe5kkKTnk0AgMQK8MP7V/gv72z+dXNL/ae8Iatex2FvH+8f61FTLMVBX5S6Gf4CtKymez5HrRn/AGv0rhB8VtNIBFu2D9aX/haunf8APu361wNW0Z7SaaujusH1owfWuE/4Wrp3/Pu360f8LV07/n3b9aBnd5/2v0oz/tfpXCf8LV07/n3b9aP+Fq6d/wA+7frQB3fXvSH1zXC/8LV07/n3b9aP+Fq6d/z7t+tAHdHjk85pQDjrxXC/8LV0wHi3bH41R1T41aJpVv8AabqEhR9axxFanhoe2qOyNKVGVeXLBHpBDY4NAz3NePf8NI+Ev7v86P8AhpHwlj7v868KXFWWN6VLHpvIsdNXjA9hyfSjn0rx7/hpHwn/AHf50n/DSXhP+7/Oj/WrK/8An4P+wcd/IexfhRz6V49/w0n4U/u/zo/4aT8J+n86P9asr/5+B/YWO/kPYefSgZHavHv+GkvCfp/Oj/hpLwp6fzprivLLXdQP7Ax/8h7CB37mgnHJPSvHz+0l4U/u9PrWN4j/AGtvBHhu3FxdD5W+taUeJMtxMuWNQqPD+Pe0D3nPtRn2/WvmA/t2/Dj+7/Ok/wCG7fhx/cP616Sx9D+Y2/1WzP8A59M+oM+360Z9v1r5f/4bu+HH9w/rR/w3d8OP7h/Wj6/Q/mD/AFWzT/n0z6gz7frRn2/Wvl//AIbu+HB/gP60f8N3fDj+4f1p/XqH8wf6rZp/z6Z9QZpMj0r5g/4bt+HH9w/rSj9u34b/ANw/rS+v0P5h/wCq2af8+mfTxzwc8GlIGeelfMcP7dfw2aRUYbVY8nnivZfhx8X/AAZ8TrP7V4Z1RLgoBuToR+dXTxdGo7JnLisix+DhzVoWR3GBRnFIDmlxmult9DyNluIRmgDFAIoJxQAfNR81Hy0fLQApz2oGe9J8tHy0AAOe1BOO1G6jdQAZ5xR/FS55xSfxUALRz6UnzUc+tAC0UUUAFFFFACcHLetY3i6+k07w7f3kJ+ZIWI/I1skk4OOBXPeP9p8I6mB3gb+RqKvwMyry9x+h+ZHiPUr7Xtcu9Sv5WkkkmcfMc9CapfZMZIA5q3sBvLknn9/Jj/vo1YWMAZ71+e1qj52flderLnkjM+yn+7R9kI6CtTyh6UeUPSsXOSdkY+0ktDK+yn+7S/ZT6VqeUPSjyhjOKfPJD9pK9rmX9k9qPsp9K1PKHpR5Q9KFO60H7WWzMo2xGeKo6pBttJDjtXRGNcEkVl62irYyYHQV6GVyviYJ9wjVu7HExQbh071L9m5HFS2Y+Q8d6thAa/pXDQjKlGy6IcqjUtWUPsgoFoK0PKo8ut/ZQWpPtJt6Gf8AZBR9kHpWh5dHl0eyXQftGt2Z5tKPsnPStDy6PLpuiJVuhnG268Ve0CDbq0P1pzR8ZqxoqY1WH60vZR7GGJqP2TPSoLYsi8dqn+xHd0q1Yx5iX6VdEIHOKtU0fn9WvJS3Mj7H7Uv2L2rW8kelHkj0o9mk0kiPrGm5k/YvQUfYj6VriIelHlewodKKshfWJRd4syPsXtR9i9q1vJHpR5I9KPZK6G8RJK9zHNlz0rc8CwbPFFnx/FUTQgdK0vByAeKLPA/jrz8ypWw0rHs5BXlLHU031Pqe00xXhjIQcqO3tVj+x1I/1Y/KtbS4Fa2iyP4F/lWkLZcdK/LJ/Ez+jKXwL0OY/sdP+eQ/Kj+x0/55D8q6j7MPQUfZl9BUlnL/ANjp/wA8h+VH9jp/zyH5V1H2ZfQUfZh6CgDl/wCx0/55D8qP7HT/AJ5D8q6j7MPQUfZh6CgDljpA/wCeYx9K4H4tWSw+HnOwD8PevZZLdQM4ry741RKvhtyPUfzr5/idtZbUPWyTXGwR4BHaZHAqUWRx0rQtIlKjirawJ6V/M9SvKMnqftlOlHkWhifYj6UGxz2rb8lPSgQIe1R9Zk1uV7GOljE+xcfdo+w/7Nbfkp6UeSnpU/Wp7Jh7GLaujE+xe1H2I+lbnkL6UeQvpVfWJ8urGqMXJJGEbJvSvNfjNBs0mH8a9kkhXB4ryj42pjSocep/nXs5DXlLGQVzowlH98rnhywbuop32fBq1EgqXy81+tqbaPs44eLitCh9nGelH2cDtWh5Q9KPL9qfPbqX9VjfYz/s6+lH2celaHlD+7R5Y9KTqX0QfVoJNWM/7MB2pfs6+lX/ACx/dpPLX0oc7ISw0exnmADOT16V7T+yX4n1TQ/i3pem21w/kXjMJV3ccDjivJWjHOF6da9F/ZsG340+Hzjje+fyrrwVR+1jqeDxHhYf2fUuuh+qcZLRo3qoNPOccVFBnyY/9wfyqT5q+6Xwn8vTilKXqLQBimg4pQc1RAfxUNRjnNH8VABkUuRRz6Uc+lACZFGRRk+lHPpQAHpQOlLRQAUUUUAFFFFABRRRQAh6Vzvj/wD5FLUv+uDfyNdEOc1zvxA/5FLUv+uDfyNZVvgZlX/hy9D8yf8Al5uP+u8n/oRqyOlVx/x9XH/XeT/0I1aAr86qfGz8nr/HL1A9RluvQ0AHgbcepz1o7EYz6V6P8Lvgn4i+JQlns42htYlyZcZyadKlOtLlp7ioYedafLT1Z5zgnIxmun+H/wAP9b+IGsw6VpVs5RmHmNjhRW7pfwR8YXvjM+E202RXjkAd8cBfX8q+0/AHw/8ACvwd8K+dKYklSPdNO3UnFelg8ulXqc1TRI9fL8oliJ81TRLc+Zvi5+zTN4H8PRa3pdyJljXNxkYwa8AKkEgr0OGr3r9oH493XjO7m0DRJCumxkqWH8deDHnG7qefrXPmHsvaWo7HLmaoKrbDidjWRrufsMn0rXI4NZGu/wDHjJ9KrKrPFwXmcCepylkBtH1NXF6VTs/uD8auL0r+n8M7YeK8kKau7sXoQSeOwpOnbHtS4J4ByB0969Z+Fn7PHiz4oabcarZwNDbwrujcj79KtXpYWHNVZpRoVMTK1M8mABbIruvhP8J/EHxR12HTdMt38ncPOl28KK2vB37PvjXxH43bwpNp0sIgkAmkI4Uf/qr738G+DPBfwI8Fb5DDB5EW6aZgMsa8TM86jh48lDWTPZy7KJV3zV9Io+Pfj/8Asv3Pwy0iLxDpFwJrJFHng8bD/XmvnVjghvzFe9ftG/tD6h8TtSl0XSnMejwsVAB4krwckkliceld+VLEOhzYndnDmUqCq8uH2Qh6VNo//ITi+tQnpU+jf8hOH616LR5GJ/gs9Z08fukq+DVHT/8AUp9KvDvVxR+d1viF6YK9uvtR1BP3g3X2o5+WvRPBnwW8ReMdGn1eCMxxqu6IY+/61jiMRTw0eao7GuDwNbHVHToRuzzvAHAGAK7T4cfDXVfH2pi3ghZLYEeZLjgCr3gr4P8AiDxJ4jOk3No8Mdu+JnI7V9X6bp/hn4VeGOTFBHAmST1Jrw81zqNL93h9ZM+u4f4XliZe3xq5acT5l+LnwYn8CRrqFk4ltSBu7bT/AFrycAZGOlel/F74sX3j3UmtYnKWEbEKg715qMngngdK9PK/b/V08R8R4GffVPrklgvgQ1hxitHweP8Aip7PP9+s9q0fB/Piiz/36WaL/ZpJmnDzisfTXW59jaT/AMe0X+4v8q1FrL0n/j2i/wBxf5VqDpX5RP4mf0lS+Begc8DGcdqD97nr29qGIXndhQCSfSvCfH37W3gHwJ42tPB9xcpIXcpPID/qj2qSz3bv0+prxn9oL9ozw58F9Ckle4S41KUfuIA3JP8ASs745ftQ+Efh14OXUtNv4ry+vYibaNG7kf8A16/O1U+IX7SHxCABnu7i8l99qLn8hxQB+hP7MX7TVl8arB7G/iaDU7cncuCQwJ45+lfQGSTnHTrXjf7O37PehfBfw6kcSh9WuEU3E2OSa9lOeMNn8KAGS/dryz43f8i5J+H869Tl+7Xlnxt/5FyT6j+dfP8AE7Tyyoevkf8AvsDxW05UfSrvQVStBhV+lXB0r+X63xP1P3Ck7U0GB/hS8HqKFUllK9+MV3WmfCTxBqXh+TWkjYEDdHFjlh3roweW4jMZNYdXsYYnG0MIlKu7XOF2jcAOQf1rufh18M7/AMY3PmyQtHZp1cjrVj4d/C3VPE2pbr+FobWFvm3DrX0BqGpeH/h1oG3EcSxJgKOpNfa8OcJqSeLzBWprv1Pl864hcGsNgdZv8D5x+I3w9n8GXow++3f7prjM/wAIHPb2rqPHnji/8Y6m9zKSLdSfLT0Fct83ds4r5LOvqzxklgvgPosqWIeGj9Z+LqNkHymvJfjeP+JVDj3/AJ1605yDXk3xw50mH6n+dbcO6Y6ET2cIl7eJ4zF0qeoYugqev2B7H29NqMUg4+8TgUnQYIx71JDDLcTLDCpZ2IUL6k17Rbfss+ObnwL/AMJgsDl9m8W+3kitaeHlVWhw47M8Nl0l9Zmo32PFApY/J1PQV7v+z9+zVrHxRn/tPVIXt9LQH5mX73HH61e/Z5/Zm1jx7rSal4jspLfTLaTL7xjfg/8A1q+zvHPjnwT8B/BXlRiCHyItsUS4yxxXr4HLo8nta2x8FxPxhUVRYDKveqy6rofn18dPgxqHwi8QiwkkElpOT5MnHzAdeO1eY46DORXc/Fn4qa38U/EUmrapKRAHPkx/3BXC8keory8T7P2j9nsfdZOsX9Sh9dd6ltRH6V6H+zf/AMlq0D/ff+VeeP0r0P8AZv8A+S16B/vP/KrwS/fROTiPXL6nofqfbHMMf+4KkJxUdt/qI/8AdH8qlr71fCfyrW+J+oUgHNLRVGYhGe9KRmiigApPmpaT5qAFPtSDPelPtSDPegBaMiijAoATrR0paKACiiigAooooAMVzvxAA/4RLUuf+WDfyNdDj3rnviAMeEtSP/TBv5Gsq2kGY19KTPzJH/H1cf8AXeT/ANCNWR1FV1/4+rn/AK7yf+hGrA7V+d1fjZ+UV9arFP6Gvoz9mr462/hMxeEteVFs3O2ObAG3/GvnPHG3OPUUqM8Tb0Yqy8hga0wteeHmpwNcJip4OoqkT9RZ9S8M2FhJ4ldrdUZN5m4yRj1r4z+Pvx+v/Gd5LoegzNHpsRKsVb79eb3XxX8Y3vhmPwrPqsn2KIYxnkj61xuSSWLEnv716eLzV4iHJT0PYx+ePE0/Z0lbuKxLEsxJJ9aKQUA5rxNz53fcQ96ydd/48ZPpWse9ZOu/8eMn0r0Mq/3uHqVHc5Sz+4KuLVSy+4PqatrX9PYbWhG/ZClJ82gobDAgYAORX1v+yl+0ZZ6EsPgfxN5cUBIWGUgDFfJBA4+bOf0p0c8tvIkkLFGQ5BBwc1jj8DTzCl7OR04HFTwVX2iP1y1bW/CfhnTJvFVw9rFGyeY0wAy3HFfAn7Rf7ReqfEnVZdH0e4eLSIWKqqnHmV57rfxi8ca94atvCmoavLJZW4KhemR9a4jcxADHNeNluQrCy9rX1fQ9bMM6liV7Ojog5PJPWgZ70c0gz3r6bW10fP2V7MG61Po//ITh+tQN1qfR/wDkJw/WkzDE/wAFnrdgcxJ9BV/PSqFgMRJ9BV/0qon53W+MVSVYSDqpyPevpH4BfGWyihh8K63sjI+WKQ8Bq+bQOPX29KfBPJbyLNBKUKHII4INcOY4Cnj6bpz3PTyXN6mT4lVobdT9BNa1vw74Z06bW53hhTbuLgD5vSvkL4s/FvUfHWoyW9vM0dhGxEag9a5zXPiJ4m1/TLfS9Qv3eCEFQOma5gf7TV5eVZEsHP2lfWXQ+gz/AIulmUVRw/ux6hnNFFFfSHw71dxDWh4OP/FT2n+/Wea0PB3/ACM9p/v15uaf7tI93h7/AJGEPU+x9J/49ov9xf5VqLWXpP8Ax7Rf7i/yrUWvyifxM/pKl8C9Bs8azRPC4yJFKt9CK/PX9sD9ljV9H1G5+IPhKOW4tpG8yaMEkx/jX6Gjhsk9aq6lplpq1nLY6hAs8MylWVhkEVJZ+MngnwV43+KviK28MWSXVy6vsO8kiMd+tfp9+z1+zz4d+Dfh6FRbRy6pMgM9wV5zXVeBfgt4E+Ht/d6j4d0aK3mum3O/U59s13g65z06UAAAPNAwe1LSAY70ANk/1Zryv42f8i3J+H869Uk/1Zryv42f8i3J+H86+f4n/wCRZUPXyP8A32B4ta/dH0q6eoqla/dH0q6eor+Xq/xM/caX8NDomMUqP3Qgivo74RfFCx1i0j0PVCkdyg2qT0YV83H72c/Sp7O9ubC4jubSUxOhySK9zh7PquR1lUirxe6PJznKaebUeSWjWzPsbxJ4k0Pwfpcl6/lJxkBcDJr5b8deOdQ8X6k8s07CIE+XGOgFUfEPi7WfEpRdSu2kSMYVegFYoO3kHr39K9niXi6ebr2ND3YHnZJw5HLf3tb3ph0FA5FIOuKOp4r4Y+pW42Toa8m+N/8AyCYfqa9ak6V5L8cP+QVF9TXucPf79A6MJ/GR41D92phyKhh+7Uw4r9etdWZ9vT1SZa02/m028hv7fG+FwwyM5wa/Q39mv9obQfH+jw+GtcaGHUIkCbWxh/6V+dJycnOCa0dB1/VPDmpRappFy8E8JyCprvwWM+pvyPm+JuHKfENDlbtNbM/Ur4n/ABP8H/CDw3Les0ETlSYokABY/hX5vfFf4seIfijr8+p6lcP5Ic+VDu4UVn+OviX4q+IN0s/iHUZJ/KUCNScDp6VynGPQ1vjsyeI92HwnmcK8H08kXtsR71TuLRRRXk+h92rX0GSdK9D/AGbv+S16B/vP/KvPJfu16H+zd/yWrQP99/5V2YP+NE8LiPTL6iXY/VG34gj/ANwfyqSo4P8AUx/7g/lUlfexfuo/lOprNt92FAGKCcUVRmJjnNB6UtFACHpS0h6UtABRRRQAUUUUAFFFFABRRRQAUUUUAGMsR61zvxAwPCGp5/54N/I10OTuA9Ky/E2ntquh3lggy0sLAfXBqKq5oMyrq9Nn5eJzdXBzx50n/oRqyvI54Bq/4q8N6l4U8RXuk6payRPHMxUlTggknrWasqbfvZPpX53WpyjN6H5Xiaco1XoS0UzzV9aTzU9RWXJNdDD2c+iJKTB9ab5qetIZU7mhwl0QOnN7okoqMyoO4pfNX1/Wjkl2Dkl2HHpWVrv/AB5S/StEypjiszXHU2UmPSvRyqLWLhddSowl2OUs87B9aueuTVKzYBOfWrXmJzzX9M4acPZRbfRBKEnokSYPrRg+tM80daXzV9a256b1TJ5Ki0aHUUzzEHejzEPWhVV3B02ug/B9aMH1pvmrSeatNVIN2uHsprWw5j2qxo3Opw896qmRc1Y0ZgdVi+tJzj3McTCXsXoeuaef3S89hV8YwOazrB1ES5PYVd81OOaqM49z88rUanN8LJKKZ5y+opfMT+8Kp1ILVsyWHqb8rHUUzzk9R+dHmp6inzR6MPYztqmPopnnJ6j86POT1H50c8e4OjU/lY5ulaPg8Z8T2f8Av1lGZDxkVp+D2U+J7PB/jrzcznF4eVme5w/TmsfBtM+yNJ/49ov9xf5VqD2NYulX1oLeMGZPuD+IelaI1Cz7zx/99CvyqfxM/o+l8CLLUo+tVf7Qs/8AnvH/AN9Cg6hZ9p4/++hUllnB9aMH1qr/AGhZ/wDPeP8A76o/tCz/AOe8f/fVAFoYoOKrf2hZf890/wC+hR/aFl/z3T/voUATy/dxmvK/jdn/AIRyTnuP516W+oWewkTx/wDfQrzD403EEvh6QJIrHjgHPevA4mTeW1Ej1skaWNg2eN2h4H0q4elUbWRNo57Va82PH3hX8w1qNTmfus/b6dSHs1qSUH61H5sfrR5sfZh+dZOjWbvys09pB63Hk89aUnio/MT+8KPNTGMj86FRqr7LEqkU7XHnpQtM8yP1A/GgSx+o/On7Gp/KxKpC+4rk7SDXk3xw/wCQVF9T/OvV3ljKk5FeTfG8g6XDj1P869zh6lNY6LaZ0YWpD2y1PHIcham5x1qCJuKl3DGK/XeSUldI+2hXpctnJfePopu8elIXHalyN6NF+2pS2kvvHUtM3il3ijkklZIft6ezkvvHUU3eKTeKfI3okL29GWikr+oPggdh6V6J+zfx8a9Ay3LO/wCHFedFwScjPpXt/wCyR4B1rX/ihY67HZyCz09iZJGXA5HFdmChL2sdD5zijF06WXT95bH6S25P2eL/AHR/KpabEu2NU/ugCnGvu0vdR/LtWXNNpAelIBigHNLTICiiigAooooAKRaWigAopcH0NGD6GgBo6UtAVsdDS4PoaAEowKXB9DRg+hoASilwfQ0bT6GgBoPFID+tP2N/dP5Umw56Gl7z0YWvuch4u+Ffgfxud3iLRI7lu7A7T+YrlG/Zb+C+Qf8AhFj/AN/mr1raR2NG3sQR+FYywtOW6OWeEoTeqPJf+GW/gvjJ8Lf+R2o/4Zb+C2M/8It/5HavWtr47/lRtOO/5VH1Sl/KT9Rw/Y8lH7LnwWH/ADK//kZqP+GW/gt/0K//AJGavW9rf3T+VG1v7p/Kj6pS/lD6jh/5TyQ/sufBU/8AMrn/AL/NR/wy78F/+hW/8jtXrWxvQ/lRhvf8qHhKP8ofUcP/ACnkv/DLnwXzz4WP/f8Aakk/ZX+Ckow3hXI/67NXrZR+pB/KlKsRwD+VXDD0ou6QfUcP/KeM/wDDI3wJ/wChR/8AI7Uf8Mi/Ar/oUv8AyO1eyhWx0P5UYbP3TXorG10rKTH9Rw/8p4z/AMMi/Av/AKFL/wAjtS/8Mi/Ar/oUv/I7V7LsPoaNh9DT+u4j+Zh9Rw/8p41/wyL8Cv8AoUv/ACO1H/DI3wK/6FL/AMjtXsuG9D+VGG9DR9exH8zD6jh/5Txr/hkb4Ff9Cj/5Hag/si/Ar/oUf/I7V7LsPoaNh9DR9dxH8zD6jh/5Txo/sjfAwAf8UlwP+m7VLbfsn/A+1mE9t4Sww/6btXsAQg5IODQVPoQPpR9er/zMmWX0JKzieZ/8M6fCYdPD2P8Atq1H/DO3wn/6F8/9/Wr0zBHY0YJ7H8qP7Qr/AMzOZ5LgnvBHmZ/Z1+FA/wCZfP8A39agfs6/Cg/8y+f+/rV6YVbPQ/lQA3ofyp/X6/8AMw/sTBfyI8z/AOGdvhP/ANC8f+/rUf8ADO3wn/6F4/8Af1q9M5/umjn+6aX16v8AzMP7EwX/AD7R5of2dPhT/wBC8f8Av81N/wCGdPhV/wBC8f8Av61emkE/wGk2n+4aax9f+Zg8jwT+wjzT/hnP4UFiP+EePP8A01arFj8Avhhp1yt3Z6BslTofNNeiEEjaSQfpS7Tx8pI7molja8tHJmlPKMHSd1BXOVHwz8I9rBx/21NH/CtPCX/PjJ/39aup2n+6aNp/umuZu56KVlY5b/hWnhH/AJ8ZP+/rUf8ACtPCP/PjJ/39aup2n+6aNh7KaBnLf8K08Jf8+En/AH9aj/hWnhL/AJ8JP+/rV1JDd0NADdkNAHLH4a+Ev+fCQ/8AbVqT/hWnhP8A58JP+/zV1O0/3DRtP9w0Acv/AMK08In5Rp8mP+uzVXvfhP4Hvo/Ju9LZl95WNdkEI4OfypMMOCpIrKrRjiI8s1oVCq6UrxPPj8Cvhz/DouP+2ho/4UV8Of8AoDH/AL+GvQQpH8JoCE/wGvLWQZfJ3nTR3vM8Sl7s39558fgV8Ou2jH/v6aT/AIUV8O/+gN/5FNehFTn7po2kdFNP+wMv/wCfaJ/tTF/zv7zz7/hRfw7/AOgMf+/rUn/Civh3/wBAb/yK1eh4/wBk0Y/2TR/YGX/8+0P+1MX/ADv7zz0/Ar4ddtGP/f00n/Ci/h0P+YL/AORGr0LaT2NJtP8AdNC4fwFv4aB5pi/53955/wD8KL+HWMHRj/38NZ2sfs2/CfXIfJ1PQPNT08xq9SKnGMGja4GApAqqWTYKjK6poazXFr7b+88U/wCGP/gX28J/+Rmo/wCGP/gZ/wBCn/5Hava9r/5FG1/Su76rSX2TX+2sb/z8f3s8UP7IHwK7+Ff/ACM1A/ZA+BX/AEKn/kZq9rCnupoKHspo+q0v5Q/trHf8/X97PFP+GPvgX/0Kf/kZqP8Ahj/4Gf8AQp/+R2r2sq3YGja/p+lH1Wl/KH9tY7/n6/vZ4mf2QPgX28K/+RmpR+x/8CyP+RU/8jNXtW1v7hpdpHRTR9Vpdg/trHf8/H97PF4v2QvgdG4b/hE+VOR++avSvCvgnw54LshYeH9MjtYlGPlHP51vBTkkgk+lCq3J2EVUcPGHQwrZlisQuWrNteoCil2n+6fyo2n0NbX6HBs7oTpRS7T6GjafQ0AJSYFO2n0NGD6GgBM4oo2k9jS7T6GgBKM5pdp9DSbSOxoA2tq/3R+VG1f7o/KlooATav8AdH5UbV/uj8qWigBNq/3R+VG1f7o/KlooATav90flRtX+6PypaKAGlR2UflSbB6CnfjSHPehXFZMNqjsPypML/dFKtLkdKA2EwvoKMJ6D8qNvvRt96Bi/L6CjC+gpNvvRt96A0FwPQUbV/uj8qTHvRt96QC4X0FJhfQUbfejb70wDC/3R+VGF/uj8qXHSjHSgBML/AHR+VGF/uj8qXHSjHSkGgmF/uj8qML/dH5UuOlGOlAaCYX+6Pyowv90flS46UY96YaBtX+6PypNo/uj8qdSZFAtRNq/3RRgDHyilyKTbS1GLtX+6Pyo2r/dH5UYo20C0Dav90flRtX+6Pyo20baA0Dav90flRtX+6PypNvvS7aY9g2r/AHR+VN2+wp9FK7Fa4m1f7o/Kjav90flS0UxibV/uj8qNq/3R+VLRQAm1f7o/Kjav90flS0UAJtX+6Pyo2r/dH5UtFACbV7gflSYT0H5U6kyKV2hbjSF7AUYHoKfRRa4xuxfQUbF9BTqKYDdi+go2L6D8qdRQAzavt+VKFX0FKDmlpagN2p3Ao2r/AHRTqTFMWobV/uj8qNq/3R+VGKNtINA2r/dH5UbV/uj8qNtG2gNA2r/dH5UbV/uj8qNtG2gNA2r/AHR+VG1f7o/KjbRtoDQQKOuBS7V9BR+NLRqwGkKP4RS7V/uj8qWm7fejYYu1f7o/Kjav90flS0UwE2r/AHR+VG1f7o/KlooATav90flRtX+6PypaKAE2r/dH5UbV/uj8qWigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9k=\"}");
+}
+
+var socket_ot;
+
+function ot_open() {
+       socket_ot = new_ws(get_appropriate_ws_url(""), "dumb-increment-protocol");
+
+       console.log("ot_open");
+
+       try {
+               socket_ot.onopen = function() {
+                       document.getElementById("ot_statustd").style.backgroundColor =
+                                                                                                                               "#40ff40";
+                       document.getElementById("ot_status").innerHTML =
+                                       " <b>websocket connection opened</b><br>" +
+                                       san(socket_di.extensions);
+                       document.getElementById("ot_open_btn").disabled = true;
+                       document.getElementById("ot_close_btn").disabled = false;
+                       document.getElementById("ot_req_close_btn").disabled = false;
+                       console.log("ot_open.onopen");
+               };
+
+               socket_ot.onclose = function(e){
+                       document.getElementById("ot_statustd").style.backgroundColor =
+                                                                                                                               "#ff4040";
+                       document.getElementById("ot_status").textContent =
+                                               " websocket connection CLOSED, code: " + e.code +
+                                               ", reason: " + e.reason;
+                       document.getElementById("ot_open_btn").disabled = false;
+                       document.getElementById("ot_close_btn").disabled = true;
+                       document.getElementById("ot_req_close_btn").disabled = true;
+               };
+       } catch(exception) {
+               alert("<p>Error" + exception);  
+       }
+}
+
+/* browser will close the ws in a controlled way */
+function ot_close() {
+       socket_ot.close(3000, "Bye!");
+}
+
+/* we ask the server to close the ws in a controlled way */
+function ot_req_close() {
+       socket_ot.send("closeme\n");
+}
+
+var socket_lm;
+var pending = "";
+
+function lm_timer_handler(ev) {
+       socket_lm.send(pending);
+       pending="";
+}
+
+/* lws-mirror protocol */
+
+var down = 0;
+var no_last = 1;
+var last_x = 0, last_y = 0;
+var ctx;
+var color = "#000000";
+var lm_timer;
+
+function ev_mousemove (ev) {
+       var x, y;
+
+       if (ev.offsetX) {
+               x = ev.offsetX;
+               y = ev.offsetY;
+       } else {
+               x = ev.layerX - offsetX;
+               y = ev.layerY - offsetY;
+
+       }
+
+       if (!down)
+               return;
+       if (no_last) {
+               no_last = 0;
+               last_x = x;
+               last_y = y;
+               return;
+       }
+       pending = pending + "d " + color + " " + last_x + " " + last_y +
+                       " " + x + " " + y + ";";
+                       
+       if (pending.length > 400) {
+               socket_lm.send(pending);
+               clearTimeout(lm_timer);
+               pending = "";
+       } else
+               lm_timer = setTimeout(lm_timer_handler, 1);
+
+       last_x = x;
+       last_y = y;
+}
+
+function ev_mousedown (ev) {
+       down = 1;
+}
+
+function ev_mouseup(ev) {
+       down = 0;
+       no_last = 1;
+}
+
+
+function ws_open_mirror()
+{      
+       socket_lm = new_ws(get_appropriate_ws_url("?mirror=" + mirror_name),
+                       "lws-mirror-protocol");
+       try {
+               socket_lm.onopen = function() {
+                       document.getElementById("wslm_statustd").style.backgroundColor =
+                                                                                                                                       "#40ff40";
+                       document.getElementById("wslm_status").innerHTML =
+                                                               " <b>websocket connection opened</b><br>" +
+                                                               san(socket_lm.extensions);
+                       lws_gray_out(false);
+               };
+
+               socket_lm.onmessage =function got_packet(msg) {
+                       j = msg.data.split(";");
+                       var f = 0;
+                       while (f < j.length - 1) {
+                               i = j[f].split(" ");
+                               if (i[0] === "d") {
+                                       ctx.strokeStyle = i[1];
+                                       ctx.beginPath();
+                                       ctx.moveTo(+(i[2]), +(i[3]));
+                                       ctx.lineTo(+(i[4]), +(i[5]));
+                                       ctx.stroke();
+                               }
+                               if (i[0] === "c") {
+                                       ctx.strokeStyle = i[1];
+                                       ctx.beginPath();
+                                       ctx.arc(+(i[2]), +(i[3]), +(i[4]), 0, Math.PI*2, true); 
+                                       ctx.stroke();
+                               }
+
+                               f++;
+                       }
+               };
+
+               socket_lm.onclose = function(){
+                       document.getElementById("wslm_statustd").style.backgroundColor =
+                                                                                                                                       "#ff4040";
+                       document.getElementById("wslm_status").textContent =
+                                                                                       " websocket connection CLOSED ";
+                       lws_gray_out(true,{"zindex":"499"});
+               };
+       } catch(exception) {
+               alert("<p>Error" + exception);  
+       }
+
+       var canvas = document.createElement("canvas");
+       canvas.height = 300;
+       canvas.width = 480;
+       ctx = canvas.getContext("2d");
+
+       document.getElementById("wslm_drawing").appendChild(canvas);
+
+       canvas.addEventListener("mousemove", ev_mousemove, false);
+       canvas.addEventListener("mousedown", ev_mousedown, false);
+       canvas.addEventListener("mouseup", ev_mouseup, false);
+
+       offsetX = offsetY = 0;
+       element = canvas;
+      if (element.offsetParent) {
+        do {
+          offsetX += element.offsetLeft;
+          offsetY += element.offsetTop;
+          element = element.offsetParent;
+        } while (element);
+      }
+}
+
+function update_color() {
+       color = document.getElementById("color").value;
+}
+
+/* stuff that has to be delayed until all the page assets are loaded */
+
+window.addEventListener("load", function() {
+       
+       lws_gray_out(true,{"zindex":"499"});
+
+       document.getElementById("file").onchange = check_file;
+       document.getElementById("offset").onclick = reset;
+       document.getElementById("junk").onclick = junk;
+       document.getElementById("color").onclick = update_color;
+       document.getElementById("ot_open_btn").onclick = ot_open;
+       document.getElementById("ot_close_btn").onclick = ot_close;
+       document.getElementById("ot_req_close_btn").onclick = ot_req_close;
+       document.getElementById("pmd").onclick = on_pmd;
+
+       var transport_protocol = "";
+
+       if ( performance && performance.timing.nextHopProtocol ) {
+           transport_protocol = performance.timing.nextHopProtocol;
+       } else if ( window.chrome && window.chrome.loadTimes ) {
+           transport_protocol = window.chrome.loadTimes().connectionInfo;
+       } else {
+
+         var p = performance.getEntriesByType("resource");
+         for (var i=0; i < p.length; i++) {
+       var value = "nextHopProtocol" in p[i];
+         if (value)
+           transport_protocol = p[i].nextHopProtocol;
+           }
+          }
+          
+          console.log("transport protocol " + transport_protocol);
+          
+          if (transport_protocol === "h2")
+                  document.getElementById("transport").innerHTML =
+                                                                                       "<img src=\"./http2.png\">";
+
+          BrowserDetect.init();
+
+          document.getElementById("brow").textContent = " " +
+                       BrowserDetect.browser + " " + BrowserDetect.version + " " +
+                       BrowserDetect.OS +" ";
+
+          document.getElementById("number").textContent =
+                  get_appropriate_ws_url(mirror_name);
+          
+          /* create the ws connections back to the server */
+          
+          ws_open_dumb_increment();
+          ws_open_status();
+          ws_open_mirror();
+
+}, false);
+
+}());
diff --git a/test-apps/wss-over-h2.png b/test-apps/wss-over-h2.png
new file mode 100644 (file)
index 0000000..1a62d83
Binary files /dev/null and b/test-apps/wss-over-h2.png differ
diff --git a/test-server/.gitignore b/test-server/.gitignore
deleted file mode 100644 (file)
index 53ba6cb..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#Ignore build files
-libwebsockets-test-*
-Makefile
-*.o
-*.lo
-*.la
-.libs
-.deps
-
diff --git a/test-server/fuzxy.c b/test-server/fuzxy.c
deleted file mode 100644 (file)
index c6a0f0d..0000000
+++ /dev/null
@@ -1,969 +0,0 @@
-/*
- * fuzzing proxy - network-level fuzzing injection proxy
- *
- * Copyright (C) 2016 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA 02110-1301  USA
- *
- *
- * fuzxy is designed to go on the client path
- *
- * [ client <-> fuzxy ] <-> server
- *
- * you can arrange that with, eg,
- *
- *  http_proxy=localhost:8880
- *
- * env var before starting the client.
- *
- * Even though he is on the client side, he is able to see and change traffic
- * in both directions, and so fuzz both the client and the server.
- */
-
-#if defined(_WIN32) && defined(EXTERNAL_POLL)
-#define WINVER 0x0600
-#define _WIN32_WINNT 0x0600
-#define poll(fdArray, fds, timeout)  WSAPoll((LPWSAPOLLFD)(fdArray), (ULONG)(fds), (INT)(timeout))
-#endif
-
-#include "lws_config.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <signal.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <assert.h>
-#include <errno.h>
-#include "../lib/libwebsockets.h"
-
-#ifdef _WIN32
-#include <io.h>
-#include "gettimeofday.h"
-#else
-#include <syslog.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#endif
-
-#if defined(__NetBSD__)
-#include <netinet/in.h>
-#endif
-
-#if defined(__sun)
-#include <strings.h> /* bzero */
-#endif
-
-#define MAX_FUZZ_BUF (1024 * 1024)
-
-enum types {
-       FZY_S_DEAD              = 0,
-       FZY_S_LISTENING         = 1,
-       FZY_S_ACCEPTED          = 2,
-       FZY_S_PROXIED           = 3,
-       FZY_S_ONWARD            = 4,
-};
-
-enum proxy_parser_states {
-       FZY_PP_CONNECT          = 0,
-       FZY_PP_ADDRESS          = 1,
-       FZY_PP_PORT             = 2,
-       FZY_PP_CRLFS            = 3,
-};
-
-enum fuzzer_parser_states {
-       FZY_FP_SEARCH           = 0,
-       FZY_FP_SEARCH2          = 1,
-       FZY_FP_INJECT_PREPARE   = 2,
-       FZY_FP_INJECT           = 3,
-       FZY_FP_PENDING          = 4,
-};
-
-struct ring {
-       char buf[4096];
-       int head;
-       int tail;
-};
-
-struct state {
-       enum types type;
-       enum proxy_parser_states pp;
-       int ppc;
-
-       struct ring in;
-
-       char address[256];
-       int port;
-
-       enum fuzzer_parser_states fp;
-       int fuzc;
-       int pending;
-
-       int twin; /* must be fixed up when arrays lose guys */
-       unsigned int outbound:1; /* from local -> remote */
-       unsigned int is_pending:1;
-
-       unsigned char buf[MAX_FUZZ_BUF];
-       unsigned int inject_len;
-};
-
-struct test {
-       const char *s[3];
-       int len[2];
-       unsigned int swallow:1;
-};
-
-int force_exit = 0;
-int which = 5;
-
-static const struct test tests[] = {
-       { { NULL, "\x0d\x0a\x0d\x0a",
-           "{ 0xd9, 0x87, 0xd2, 0x88, 0xd2, (248){ 0x89, 0xd2 }, 0x0d, 0x0a },"
-         }, { 0, 4 }, 1 },
-       { { NULL, "\x0d\x0a\x0d\x0a",
-           "{ 0xd9, 0x87, 0xd2, 0x88, 0xd2, (1373){ 0x89, 0xd2 }, 0x0d, 0x0a },"
-         }, { 0, 4 }, 1 },
-       { { NULL, "\x0d\x0a\x0d\x0a",
-           "{ 0xd9, 0x87, 0xd2, 0x88, 0xd2, (16967){ 0x89, 0xd2 }, (87){ 0xe2, 0x82, 0xac }, 0x0d, 0x0a },"
-         }, { 0, 4 }, 1 },
-       { { NULL, "\x0d\x0a\x0d\x0a",
-               "0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, "
-               "0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, 0x65, "
-               "0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, "
-               "0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, 0x65, "
-               "0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, 0x3a, "
-               "0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, 0x42, "
-               "0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, 0x69, "
-               "0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, "
-               "0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, 0x0a, "
-               "0xef, 0xbb, 0xbf, 0xc2, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, "
-               "0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, "
-               "0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, "
-               "0x3a, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, "
-               "0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x0d, 0x0a, "
-         }, { 0, 4 }, 1 },
-       { { NULL, "\x0d\x0a\x0d\x0a",
-               "(20){0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, "
-               "0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, 0x65, "
-               "0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, "
-               "0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, 0x65, "
-               "0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, 0x3a, "
-               "0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, 0x42, "
-               "0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, 0x69, "
-               "0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, "
-               "0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, 0x0a, "
-               "0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, "
-               "0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, 0x65, "
-               "0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, "
-               "0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, 0x65, "
-               "0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, 0x3a, "
-               "0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, 0x42, "
-               "0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, 0x69, "
-               "0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, "
-               "0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, 0x0a, "
-               "0xc2, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, "
-               "0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, "
-               "0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, "
-               "0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, "
-               "0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, "
-               "0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, "
-               "0x3a, 0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, "
-               "0x42, 0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, "
-               "0x69, 0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, "
-               "0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, "
-               "0x6b, 0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, "
-               "0x0a, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, "
-               "0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, "
-               "0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, "
-               "0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, "
-               "0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, "
-               "0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, "
-               "0x3a, 0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, "
-               "0x42, 0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, "
-               "0x69, 0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, "
-               "0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, "
-               "0x6b, 0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, "
-               "0x0a, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, "
-               "0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, "
-               "0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, "
-               "0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, "
-               "0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, "
-               "0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, "
-               "0x3a, 0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, "
-               "0x42, 0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, "
-               "0x69, 0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, "
-               "0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, "
-               "0x6b, 0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, "
-               "0x0a, 0xc0, 0x80, 0xef, 0xb7, 0x90, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, "
-               "0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, "
-               "0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, "
-               "0x64, 0x65, 0x3a, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, "
-               "0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, "
-               "0x64, 0x65, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, "
-               "0x74, 0x2d, 0x4b, 0x65, 0x79, 0x3a, 0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, "
-               "0x58, 0x42, 0x73, 0x5a, 0x53, 0x42, 0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, "
-               "0x0a, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, "
-               "0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, "
-               "0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, "
-               "0x3a, 0x20, 0x31, 0x33, 0x0d, 0x0a, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, "
-               "0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, "
-               "0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, "
-               "0x64, 0x65, 0x3a, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, "
-               "0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, "
-               "0x64, 0x65, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, "
-               "0x74, 0x2d, 0x4b, 0x65, 0x79, 0x3a, 0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, "
-               "0x58, 0x42, 0x73, 0x5a, 0x53, 0x42, 0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, "
-               "0x0a, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, "
-               "0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, "
-               "0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, "
-               "0x3a, 0x20, 0x31, 0x33, 0x0d, 0x0a, 0xc2, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, "
-               "0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, "
-               "0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, "
-               "0x61, 0x64, 0x0d, 0x0a, }"
-       }, { 0, 4 }, 1 },
-       { { NULL, "\x0d\x0a\x0d\x0a",
-               "0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, "
-               "0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x3a, 0x20, 0x77, 0x65, "
-               "0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, "
-               "0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x53, 0x65, "
-               "0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x4b, 0x65, 0x79, 0x3a, "
-               "0x20, 0x64, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x4e, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, 0x42, "
-               "0x75, 0x62, 0x32, 0x35, 0x6a, 0x5a, 0x51, 0x3d, 0x3d, 0x0d, 0x0a, 0x4f, 0x72, 0x69, 0x67, 0x69, "
-               "0x6e, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, "
-               "0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, "
-               "0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, 0x0d, 0x0a, "
-               "0xef, 0xbb, 0xbf, 0xc2, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x48, 0x54, "
-               "0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x31, 0x32, "
-               "0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, "
-               "0x3a, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, "
-               "0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x0d, 0x0a, (2048){ 0x0d, 0x0a}"
-         }, { 0, 4 }, 1 },
-};
-
-static int ring_size(struct ring *r)
-{
-       return sizeof(r->buf);
-}
-static int ring_used(struct ring *r)
-{
-       return (r->head - r->tail) & (ring_size(r) - 1);
-}
-static int ring_free(struct ring *r)
-{
-       return (ring_size(r) - 1) - ring_used(r);
-}
-static int ring_get_one(struct ring *r)
-{
-       int n = r->buf[r->tail] & 255;
-
-       if (r->tail == r->head)
-               return -1;
-
-       r->tail++;
-       if (r->tail == ring_size(r))
-               r->tail = 0;
-
-       return n;
-}
-
-static int hex(char c)
-{
-       if (c >= '0' && c <= '9')
-               return c -'0';
-       if (c >= 'a' && c <= 'f')
-               return c - 'a' + 10;
-       if (c >='A' && c <= 'F')
-               return c - 'A' + 10;
-
-       return -1;
-}
-
-static int
-fuzxy_tok(const char **src, unsigned char **buf, int *len)
-{
-       unsigned char *start;
-       unsigned int count, rlen;
-
-       while (**src) {
-
-               if (**src == ' ' || **src == ','  || **src == '\n') {
-                       (*src)++;
-                       continue;
-               }
-
-               if ((*src)[0] == '}') {
-                       (*src)++;
-                       return 0;
-               }
-
-               if ((*src)[0] == '0' && (*src)[1] == 'x') {
-                       if (!len) {
-                               lwsl_err("out of space\n");
-                               return -1;
-                       }
-
-                       ((*buf)++)[0] = (hex((*src)[2]) << 4) | hex((*src)[3]);
-                       *src += 4;
-                       (*len)--;
-               }
-
-               if (*src[0] == '(') {
-                       start = *buf;
-                       (*src)++;
-                       count = atoi(*src) - 1;
-                       lwsl_err("count %d\n", count);
-                       while (**src && **src != ')')
-                               (*src)++;
-                       if (!(*src)[0]) {
-                               lwsl_err("unexpected end in (\n");
-                               return -1;
-                       }
-                       (*src)++;
-                       while (**src == ' ')
-                               (*src)++;
-                       if (**src != '{') {
-                               lwsl_err("missing {\n");
-
-                               return -1;
-                       }
-                       (*src)++;
-                       if (fuzxy_tok(src, buf, len))
-                               return -1;
-                       rlen = *buf - start;
-                       while (count--) {
-                               if (*len < rlen) {
-                                       lwsl_err("out of space\n");
-                                       return -1;
-                               }
-                               memcpy(*buf, start, rlen);
-                               *buf += rlen;
-                               *len -= rlen;
-                       }
-               }
-       }
-
-       return 0;
-}
-
-static int
-fuzxy_create_pattern(const char *src, unsigned char *buf, int len)
-{
-       unsigned char *old = buf;
-       int n;
-
-       while (*src && (*src == '{' || *src == ' '))
-               src++;
-
-       if (!*src)
-               return -1;
-
-       n = fuzxy_tok(&src, &buf, &len);
-       if (n)
-               return -1;
-
-       return buf - old;
-}
-
-void sighandler(int sig)
-{
-       force_exit = 1;
-}
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 's' },
-       { "allow-non-ssl",      no_argument,    NULL, 'a' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "closetest",  no_argument,            NULL, 'c' },
-       { "libev",  no_argument,                NULL, 'e' },
-#ifndef LWS_NO_DAEMONIZE
-       { "daemonize",  no_argument,            NULL, 'D' },
-#endif
-       { "resource_path", required_argument,   NULL, 'r' },
-       { NULL, 0, 0, 0 }
-};
-
-static struct pollfd pfd[128];
-static struct state state[128];
-static int pfds = 0;
-
-static void close_and_remove_fd(int index)
-{
-       int n;
-
-       lwsl_notice("%s: closing index %d\n", __func__, index);
-       close(pfd[index].fd);
-       pfd[index].fd = -1;
-
-       n = state[index].twin;
-       if (n) {
-               assert(state[n].twin == index);
-       }
-       state[index].type = FZY_S_DEAD;
-
-       if (index == pfds - 1) {
-               if (state[index].twin)
-                       state[state[index].twin].twin = 0;
-               state[index].twin = 0;
-               goto bail;
-       }
-
-       /* swap the end guy into the deleted guy and trim back one */
-
-       if (state[pfds - 1].twin) {
-               state[state[pfds - 1].twin].twin = index;
-               if (n && n == pfds - 1)
-                       n = index;
-       }
-
-       /* swap the last guy into dead guy's place and trim by one */
-       pfd[index] = pfd[pfds - 1];
-       state[index] = state[pfds - 1];
-
-       if (n) {
-               pfds--;
-               state[n].twin = 0;
-               close_and_remove_fd(n);
-               return;
-       }
-
-bail:
-       pfds--;
-}
-
-static void construct_state(int n, enum types s, int flags)
-{
-       memset(&state[n], 0, sizeof state[n]);
-       state[n].type = s;
-       pfd[n].events = flags | POLLHUP;
-}
-
-static int
-fuzxy_listen(const char *interface_name, int port, int *sockfd)
-{
-       struct sockaddr_in serv_addr4;
-       socklen_t len = sizeof(struct sockaddr);
-       struct sockaddr_in sin;
-       int n, opt = 1;
-
-       *sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
-       if (*sockfd == -1) {
-               lwsl_err("ERROR opening socket\n");
-               goto bail1;
-       }
-
-       if (setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR,
-                      (const void *)&opt, sizeof(opt)) < 0) {
-               lwsl_err("unable to set listen socket options\n");
-               goto bail2;
-       }
-
-       bzero((char *) &serv_addr4, sizeof(serv_addr4));
-       serv_addr4.sin_addr.s_addr = INADDR_ANY;
-       serv_addr4.sin_family = AF_INET;
-
-       if (interface_name[0] &&
-           lws_interface_to_sa(0, interface_name, (struct sockaddr_in *)
-                               (struct sockaddr *)&serv_addr4,
-                               sizeof(serv_addr4)) < 0) {
-               lwsl_err("Unable to find interface %s\n", interface_name);
-               goto bail2;
-       }
-
-       serv_addr4.sin_port = htons(port);
-
-       n = bind(*sockfd, (struct sockaddr *)&serv_addr4,
-                               sizeof(serv_addr4));
-       if (n < 0) {
-               lwsl_err("ERROR on binding to port %d (%d %d)\n",
-                        port, n, errno);
-               goto bail2;
-       }
-
-       if (getsockname(*sockfd, (struct sockaddr *)&sin, &len) == -1)
-               lwsl_warn("getsockname: %s\n", strerror(errno));
-       else
-               port = ntohs(sin.sin_port);
-
-       listen(*sockfd, SOMAXCONN);
-
-       return 0;
-bail2:
-       close(*sockfd);
-bail1:
-       return -1;
-}
-
-
-static int fuzz(int n, char *out, int len)
-{
-       struct state *s = &state[n];
-       const struct test *t = &tests[which];
-       int m = 0;
-       int c;
-
-       while (m < len) {
-               switch (s->fp) {
-               case FZY_FP_SEARCH:
-                       if (t->s[0] == NULL) {
-                               s->fuzc = 0;
-                               s->is_pending = 0;
-                               s->fp = FZY_FP_SEARCH2;
-                               goto search2;
-                       }
-                       c = ring_get_one(&state[s->twin].in);
-                       if (c < 0)
-                               return m;
-                       if (c == tests[which].s[0][s->fuzc++]) {
-                               if (s->fuzc == t->len[0]) {
-                                       s->fuzc = 0;
-                                       s->fp = FZY_FP_SEARCH2;
-                               }
-                       } else
-                               s->fuzc = 0;
-                       out[m++] = c;
-                       break;
-
-               case FZY_FP_SEARCH2:
-search2:
-                       if (tests[which].s[1] == NULL) {
-                               s->fuzc = 0;
-                               s->is_pending = 0;
-                               s->fp = FZY_FP_INJECT_PREPARE;
-                               goto inject;
-                       }
-                       c = ring_get_one(&state[s->twin].in);
-                       if (c < 0)
-                               return m;
-                       if (c == tests[which].s[1][s->fuzc++]) {
-                               if (s->fuzc == tests[which].len[1]) {
-                                       lwsl_notice("+++++++fuzzer hit...\n");
-                                       s->fuzc = 0;
-                                       s->fp = FZY_FP_INJECT_PREPARE;
-                                       s->is_pending = !t->swallow;
-                                       s->pending = c;
-                                       goto inject;
-                               }
-                       } else
-                               s->fuzc = 0;
-                       if (!t->swallow)
-                               out[m++] = c;
-                       break;
-
-               case FZY_FP_INJECT_PREPARE:
-inject:
-                       s->inject_len = fuzxy_create_pattern(t->s[2],
-                               s->buf, sizeof(s->buf));
-                       if (s->inject_len == (unsigned int) -1)
-                               return -1;
-                       s->fp = FZY_FP_INJECT;
-                       /* fallthru */
-
-               case FZY_FP_INJECT:
-                       out[m++] = s->buf[s->fuzc++];
-                       if (s->fuzc == s->inject_len)
-                               s->fp = FZY_FP_PENDING;
-                       break;
-
-               case FZY_FP_PENDING:
-                       if (s->is_pending)
-                               out[m++] = s->pending;
-                       s->fp = FZY_FP_SEARCH;
-                       s->fuzc = 0;
-                       break;
-               }
-       }
-
-       return m;
-}
-
-static int
-handle_accept(int n)
-{
-       struct addrinfo ai, *res, *result;
-       struct sockaddr_in serv_addr4;
-       struct state *s = &state[n];
-       void *p = NULL;
-       int m, sockfd;
-
-       while (1) {
-               m = ring_get_one(&s->in);
-               if (m < 0)
-                       return 0;
-
-               switch (s->pp) {
-               case FZY_PP_CONNECT:
-                       if (m != "CONNECT "[s->ppc++]) {
-                               lwsl_notice("failed CONNECT match\n");
-                               return 1;
-                       }
-                       if (s->ppc == 8) {
-                               s->pp = FZY_PP_ADDRESS;
-                               s->ppc = 0;
-                       }
-                       break;
-               case FZY_PP_ADDRESS:
-                       if (m == ':') {
-                               s->address[s->ppc++] = '\0';
-                               s->pp = FZY_PP_PORT;
-                               s->ppc = 0;
-                               break;
-                       }
-                       if (m == ' ') {
-                               s->address[s->ppc++] = '\0';
-                               s->pp = FZY_PP_CRLFS;
-                               s->ppc = 0;
-                               break;
-                       }
-
-
-                       s->address[s->ppc++] = m;
-                       if (s->ppc == sizeof(s->address)) {
-                               lwsl_notice("Failed on address length\n");
-                               return 1;
-                       }
-                       break;
-               case FZY_PP_PORT:
-                       if (m == ' ') {
-                               s->pp = FZY_PP_CRLFS;
-                               s->ppc = 0;
-                               break;
-                       }
-                       if (m >= '0' && m <= '9') {
-                               s->port *= 10;
-                               s->port += m - '0';
-                               break;
-                       }
-                       return 1;
-
-               case FZY_PP_CRLFS:
-                       if (m != "\x0d\x0a\x0d\x0a"[s->ppc++])
-                               s->ppc = 0;
-                       if (s->ppc != 4)
-                               break;
-                       s->type = FZY_S_PROXIED;
-
-                       memset (&ai, 0, sizeof ai);
-                       ai.ai_family = PF_UNSPEC;
-                       ai.ai_socktype = SOCK_STREAM;
-                       ai.ai_flags = AI_CANONNAME;
-
-                       if (getaddrinfo(s->address, NULL, &ai, &result)) {
-                               lwsl_notice("failed to lookup %s\n",
-                                           s->address);
-                               return 1;
-                       }
-
-                       res = result;
-                       while (!p && res) {
-                               switch (res->ai_family) {
-                               case AF_INET:
-                                       p = &((struct sockaddr_in *)res->
-                                             ai_addr)->sin_addr;
-                                       break;
-                               }
-
-                               res = res->ai_next;
-                       }
-
-                       if (!p) {
-                               lwsl_notice("Failed to get address result %s\n",
-                                           s->address);
-                               freeaddrinfo(result);
-                               return 1;
-                       }
-
-                       serv_addr4.sin_family = AF_INET;
-                       serv_addr4.sin_addr = *((struct in_addr *)p);
-                       serv_addr4.sin_port = htons(s->port);
-                       bzero(&serv_addr4.sin_zero, 8);
-                       freeaddrinfo(result);
-
-                       lwsl_err("Conn %d req '%s' port %d\n", n,
-                                s->address, s->port);
-                       /* we need to open the associated onward connection */
-                       sockfd = socket(AF_INET, SOCK_STREAM, 0);
-                       if (sockfd < 0) {
-                               lwsl_err("Could not get socket\n");
-                               return -1;
-                       }
-
-                       if (connect(sockfd, (struct sockaddr *)&serv_addr4,
-                                   sizeof(struct sockaddr)) == -1 ||
-                           errno == EISCONN) {
-                               close(sockfd);
-                               lwsl_err("proxied onward connection failed\n");
-                               return 1;
-                       }
-                       s->twin = pfds;
-                       construct_state(pfds, FZY_S_ONWARD,
-                                       POLLOUT | POLLIN | POLLERR);
-                       state[pfds].twin = n;
-                       lwsl_notice("binding conns %d and %d\n", n, pfds);
-                       state[pfds].outbound = s->outbound;
-                       state[pfds].ppc = 0;
-                       pfd[pfds++].fd = sockfd;
-
-                       lwsl_notice("onward connection in progress\n");
-                       if (ring_used(&s->in))
-                               pfd[s->twin].events |= POLLOUT;
-                       if (write(pfd[n].fd,
-                                 "HTTP/1.0 200 \x0d\x0a\x0d\x0a", 17) < 17)
-                               return 1;
-               }
-       }
-
-       return 0;
-}
-
-static void sigpipe_handler(int x)
-{
-}
-
-int
-main(int argc, char **argv)
-{
-       char interface_name[128] = "", interface_name_local[128] = "lo";
-       int port_local = 8880, accept_fd;
-       struct sockaddr_in cli_addr;
-       int debug_level = 7;
-       socklen_t clilen;
-       struct state *s;
-       char out[4096];
-       int opts = 0;
-       int n = 0, m;
-
-#ifndef _WIN32
-/* LOG_PERROR is not POSIX standard, and may not be portable */
-#ifdef __sun
-       int syslog_options = LOG_PID;
-#else
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-       signal(SIGPIPE, sigpipe_handler);
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "eci:hsap:d:Dr:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'e':
-                       opts |= LWS_SERVER_OPTION_LIBEV;
-                       break;
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-                       #if !defined(_WIN32) && !defined(__sun)
-                       syslog_options &= ~LOG_PERROR;
-                       #endif
-                       break;
-#endif
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 'p':
-                       port_local = atoi(optarg);
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       break;
-               case 'h':
-                       fprintf(stderr, "Usage: libwebsockets-test-fuzxy "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
-                       exit(1);
-               }
-       }
-
-#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 1;
-       }
-#endif
-
-       signal(SIGINT, sighandler);
-
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("fuzxy", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-       lwsl_notice("libwebsockets fuzzing proxy - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2016 Andy Green <andy@warmcat.com>\n");
-
-       /* listen on local side */
-
-       if (fuzxy_listen(interface_name, port_local, &pfd[pfds].fd)) {
-               lwsl_err("Failed to listen on local side\n");
-               goto bail1;
-       }
-       construct_state(pfds, FZY_S_LISTENING, POLLIN | POLLERR);
-       pfds++;
-
-       (void)interface_name_local;
-       lwsl_notice("Local side listening on %s:%u\n",
-                   interface_name_local, port_local);
-
-       while (!force_exit) {
-
-               m = poll(pfd, pfds, 50);
-               if (m < 0)
-                       continue;
-               for (n = 0; n < pfds; n++) {
-                       s = &state[n];
-                       if (s->type == FZY_S_LISTENING &&
-                           (pfd[n].revents & POLLIN)) {
-                               /* first do the accept entry */
-
-                               clilen = sizeof(cli_addr);
-                               accept_fd = accept(pfd[0].fd,
-                                        (struct sockaddr *)&cli_addr, &clilen);
-                               if (accept_fd < 0) {
-                                       if (errno == EAGAIN ||
-                                           errno == EWOULDBLOCK)
-                                               continue;
-
-                                       lwsl_warn("ERROR on accept: %s\n",
-                                                 strerror(errno));
-                                       continue;
-                               }
-                               construct_state(pfds, FZY_S_ACCEPTED,
-                                               POLLIN | POLLERR);
-                               state[pfds].outbound = n == 0;
-                               state[pfds].pp = FZY_PP_CONNECT;
-                               state[pfds].ppc = 0;
-                               pfd[pfds++].fd = accept_fd;
-                               lwsl_notice("new connect accepted\n");
-                               continue;
-                       }
-                       if (pfd[n].revents & POLLIN) {
-                               assert(ring_free(&s->in));
-                               m = (ring_size(&s->in) - 1) -
-                                   s->in.head;
-                               if (s->in.head == ring_size(&s->in) - 1 &&
-                                   s->in.tail)
-                                       m = 1;
-                               m = read(pfd[n].fd, s->in.buf + s->in.head, m);
-//                             lwsl_notice("read %d\n", m);
-                               if (m <= 0) {
-                                       lwsl_err("Error on read\n");
-                                       goto drop;
-                               }
-                               s->in.head += m;
-                               if (s->in.head == ring_size(&s->in))
-                                       s->in.head = 0;
-
-                               switch (s->type) {
-                               case FZY_S_ACCEPTED: /* parse proxy CONNECT */
-                                       if (handle_accept(n))
-                                               goto drop;
-                                       break;
-                               case FZY_S_PROXIED:
-                               case FZY_S_ONWARD:
-                                       if (ring_used(&s->in))
-                                               pfd[s->twin].events |= POLLOUT;
-                                       break;
-                               default:
-                                       assert(0);
-                                       break;
-                               }
-                               if (s->in.head == s->in.tail) {
-                                       s->in.head = s->in.tail = 0;
-                                       pfd[n].events |= POLLIN;
-                               }
-                               if (!ring_free(&s->in))
-                                       pfd[n].events &= ~POLLIN;
-                       }
-                       if (pfd[n].revents & POLLOUT) {
-                               switch (s->type) {
-                               case FZY_S_PROXIED:
-                               case FZY_S_ONWARD:
-                                       /*
-                                        * draw down enough of the partner's
-                                        * in ring to either exhaust it
-                                        * or fill an output buffer
-                                        */
-                                       m = fuzz(n, out, sizeof(out));
-                                       if (m < 0) {
-                                               lwsl_err("Error on fuzz\n");
-                                               goto drop;
-                                       }
-                                       lwsl_notice("got block %d\n", m);
-                                       if (m) {
-                                               m = write(pfd[n].fd, out, m);
-                                               if (m <= 0) {
-                                                       lwsl_err("Error on write\n");
-                                                       goto drop;
-                                               } else
-                                                       pfd[s->twin].events &= ~POLLIN;
-                                       } else {
-                                               pfd[n].events &= ~POLLOUT;
-
-                                               if (ring_free(&state[s->twin].in))
-                                                       pfd[s->twin].events |= POLLIN;
-                                       }
-
-                                       break;
-                               default:
-                                       break;
-                               }
-                       }
-
-                       continue;
-drop:
-                       close_and_remove_fd(n);
-                       n--; /* redo this slot */
-               }
-       }
-
-bail1:
-       lwsl_notice("%s exited cleanly\n", argv[0]);
-
-#ifndef _WIN32
-       closelog();
-#endif
-
-       return 0;
-}
diff --git a/test-server/lws-common.js b/test-server/lws-common.js
deleted file mode 100644 (file)
index 1e94eaa..0000000
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * This section around grayOut came from here:
- * http://www.codingforums.com/archive/index.php/t-151720.html
- * Assumed public domain
- *
- * Init like this in your main html script, this also reapplies the gray
- *
- *    lws_gray_out(true,{'zindex':'499'});
- *
- * To remove the gray
- *
- *    lws_gray_out(false);
- *
- */
-
-function lws_gray_out(vis, options) {
-       var options = options || {};
-       var zindex = options.zindex || 50;
-       var opacity = options.opacity || 70;
-       var opaque = (opacity / 100);
-       var bgcolor = options.bgcolor || '#000000';
-       var dark = document.getElementById('darkenScreenObject');
-
-       if (!dark) {
-               var tbody = document.getElementsByTagName("body")[0];
-               var tnode = document.createElement('div');
-               tnode.style.position = 'absolute';
-               tnode.style.top = '0px';
-               tnode.style.left = '0px';
-               tnode.style.overflow = 'hidden';
-               tnode.style.display ='none';
-               tnode.id = 'darkenScreenObject';
-               tbody.appendChild(tnode);
-               dark = document.getElementById('darkenScreenObject');
-       }
-       if (vis) {
-               dark.style.opacity = opaque;
-               dark.style.MozOpacity = opaque;
-               dark.style.filter ='alpha(opacity='+opacity+')';
-               dark.style.zIndex = zindex;
-               dark.style.backgroundColor = bgcolor;
-               dark.style.width = gsize(1);
-               dark.style.height = gsize(0);
-               dark.style.display ='block';
-               addEvent(window, "resize",
-                       function() {
-                               dark.style.height = gsize(0);
-                               dark.style.width = gsize(1);
-                       }
-               );
-       } else {
-               dark.style.display = 'none';
-               removeEvent(window, "resize",
-                       function() {
-                               dark.style.height = gsize(0);
-                               dark.style.width = gsize(1);
-                       }
-               );
-       }
-}
-
-function gsize(ptype)
-{
-       var h = document.compatMode == 'CSS1Compat' &&
-               !window.opera ?
-                       document.documentElement.clientHeight :
-                                               document.body.clientHeight;
-       var w = document.compatMode == 'CSS1Compat' &&
-               !window.opera ? 
-                       document.documentElement.clientWidth :
-                                               document.body.clientWidth;
-       if (document.body && 
-                   (document.body.scrollWidth || document.body.scrollHeight)) {
-               var pageWidth = (w > (t = document.body.scrollWidth)) ?
-                                       ("" + w + "px") : ("" + (t) + "px");
-               var pageHeight = (h > (t = document.body.scrollHeight)) ?
-                                       ("" + h + "px") : ("" + (t) + "px");
-       } else if (document.body.offsetWidth) {
-               var pageWidth = (w > (t = document.body.offsetWidth)) ?
-                                       ("" + w + "px") : ("" + (t) + "px");
-               var pageHeight =(h > (t = document.body.offsetHeight)) ?
-                                       ("" + h + "px") : ("" + (t) + "px");
-       } else {
-               var pageWidth = '100%';
-               var pageHeight = '100%';
-       }
-       return (ptype == 1) ? pageWidth : pageHeight;
-}
-
-function addEvent( obj, type, fn ) {
-       if ( obj.attachEvent ) {
-               obj['e' + type + fn] = fn;
-               obj[type+fn] = function() { obj['e' + type+fn]( window.event );}
-               obj.attachEvent('on' + type, obj[type + fn]);
-       } else
-               obj.addEventListener(type, fn, false);
-}
-
-function removeEvent( obj, type, fn ) {
-       if ( obj.detachEvent ) {
-               obj.detachEvent('on' + type, obj[type + fn]);
-               obj[type + fn] = null;
-       } else
-               obj.removeEventListener(type, fn, false);
-}
-
-/*
- * end of grayOut related stuff
- */
-/*
- * lws-meta helpers
- */
-
-var lws_meta_cmd = {
-       OPEN_SUBCHANNEL: 0x41,
-       /**< Client requests to open new subchannel
-        */
-       OPEN_RESULT: 0x42,
-       /**< Result of client request to open new subchannel */
-       CLOSE_NOT: 0x43,
-       CLOSE_RQ: 0x44,
-       /**< client requests to close a subchannel */
-       WRITE: 0x45,
-       /**< connection writes something to specific channel index */
-       RX: 0x46,
-};
-
-function new_ws(urlpath, protocol)
-{
-       if (typeof MozWebSocket != "undefined")
-               return new MozWebSocket(urlpath, protocol);
-
-       return new WebSocket(urlpath, protocol);
-}
-
-function lws_meta_ws() {
-       var real;
-       
-       var channel_id_to_child;
-       var pending_children;
-       var active_children;
-}
-
-function lws_meta_ws_child() {
-       var onopen;
-       var onmessage;
-       var onclose;
-       
-       var channel_id;
-       
-       var subprotocol;
-       var suburl;
-       var cookie;
-       
-       var extensions;
-       
-       var parent;
-}
-
-lws_meta_ws_child.prototype.send = function(data)
-{
-
-       if (typeof data == "string") {
-               data = String.fromCharCode(lws_meta_cmd.WRITE) +
-                       String.fromCharCode(this.channel_id) +
-                       data;
-               
-               return this.parent.real.send(data);
-       }
-       
-       {
-
-               var ab = new Uint8Array(data.length + 2);
-
-               ab[0] = lws_meta_cmd.WRITE;
-               ab[1] = this.channel_id;
-               ab.set(data, 2);
-       
-               return this.parent.real.send(ab);
-       }
-}
-
-lws_meta_ws_child.prototype.close = function(close_code, close_string)
-{
-       var pkt = new Uint8Array(129), m = 0, pkt1;
-       
-       pkt[m++] = lws_meta_cmd.CLOSE_RQ;
-       pkt[m++] = this.channel_id;
-       
-       pkt[m++] = close_string.length + 0x20;
-       
-       pkt[m++] = close_code / 256;
-       pkt[m++] = close_code % 256;
-       
-       for (i = 0; i < close_string.length; i++)
-               pkt[m++] = close_string.charCodeAt(i);
-       
-       pkt1 = new Uint8Array(m);
-       for (n = 0; n < m; n++)
-               pkt1[n] = pkt[n];
-               
-       this.parent.real.send(pkt1.buffer);
-}
-
-/* make a real ws connection using lws_meta*/
-lws_meta_ws.prototype.new_parent = function(urlpath)
-{
-       var n, i, m = 0, pkt1;
-       
-       this.ordinal = 1;
-       this.pending_children = [];
-       this.active_children = [];
-       this.real = new_ws(urlpath, "lws-meta");
-       
-       this.real.binaryType = 'arraybuffer';
-       this.real.myparent = this;
-
-       this.real.onopen = function() {
-               pkt = new Uint8Array(1024);
-                       var n, i, m = 0, pkt1;
-               console.log("real open - pending children " + this.myparent.pending_children.length);
-               for (n = 0; n < this.myparent.pending_children.length; n++) {
-               
-                       var p = this.myparent.pending_children[n];
-               
-                       pkt[m++] = lws_meta_cmd.OPEN_SUBCHANNEL;
-                       for (i = 0; i < p.subprotocol.length; i++)
-                               pkt[m++] = p.subprotocol.charCodeAt(i);
-                       pkt[m++] = 0;
-                       for (i = 0; i < p.suburl.length; i++)
-                               pkt[m++] = p.suburl.charCodeAt(i);
-                       pkt[m++] = 0;
-                       for (i = 0; i < p.cookie.length; i++)
-                               pkt[m++] = p.cookie.charCodeAt(i);
-                       pkt[m++] = 0;
-               }
-               
-               pkt1 = new Uint8Array(m);
-               for (n = 0; n < m; n++)
-                       pkt1[n] = pkt[n];
-               
-               console.log(this.myparent.pending_children[0].subprotocol);
-               console.log(pkt1);
-               
-               this.send(pkt1.buffer);
-       }
-
-
-       this.real.onmessage = function(msg) {
-       
-               if (typeof msg.data != "string") {
-                       var ba = new Uint8Array(msg.data), n = 0;
-                       
-                       while (n < ba.length) {
-
-                               switch (ba[n++]) {
-                               case lws_meta_cmd.OPEN_RESULT:
-                               {
-                                       var m = 0, cookie = "", protocol = "", ch = 0;
-                                       var ws = this.myparent;
-                                       /* cookie NUL
-                                        * channel index + 0x20
-                                        * protocol NUL
-                                        */
-                                        while (ba[n])
-                                               cookie = cookie + String.fromCharCode(ba[n++]);
-                                        n++;
-                                        ch = ba[n++];
-                                        
-                                        while (ba[n])
-                                               protocol = protocol + String.fromCharCode(ba[n++]);
-                                               
-                                       console.log("open result " + cookie + " " + protocol + " " + ch + " pending len " + ws.pending_children.length);
-                                       
-                                       for (m = 0; m < ws.pending_children.length; m++) {
-                                               if (ws.pending_children[m].cookie == cookie) {
-                                                       var newchild = ws.pending_children[m];
-                       
-                                                       /* found it */
-                                                       ws.pending_children[m].channel_id = ch;
-                                                       /* add to active children array */
-                                                       ws.active_children.push(ws.pending_children[m]);
-                                                       /* remove from pending children array */
-                                                       ws.pending_children.splice(m, 1);
-                                                       
-                                                       newchild.parent = ws;
-                                                       newchild.extensions = this.extensions;
-                                                       
-                                                       newchild.onopen();
-                                                       
-                                                       console.log("made active " + cookie);
-                                                       break;
-                                               }
-                                       }
-                                       break;
-                               }
-       
-                               case lws_meta_cmd.CLOSE_NOT:
-                               {
-                                       var code = 0, str = "", ch = 0, m, le;
-                                       var ba = new Uint8Array(msg.data);
-                                       /*
-                                        * BYTE: channel
-                                        * BYTE: MSB status code
-                                        * BYTE: LSB status code
-                                        * BYTES: rest of message is close status string
-                                        */
-                                        
-                                        ch = ba[n++];
-                                        le = ba[n++] - 0x20;
-                                        code = ba[n++] * 256;
-                                        code += ba[n++];
-                                        
-                                        while (le--)
-                                               str += String.fromCharCode(ba[n++]);
-                                               
-                                       console.log("channel id " + ch + " code " + code + " str " + str + " len " + str.length);
-                                               
-                                       for (m = 0; m < this.myparent.active_children.length; m++)
-                                               if (this.myparent.active_children[m].channel_id == ch) {
-                                                       var child = this.myparent.active_children[m];
-                                                       var ms = new CloseEvent("close", { code:code, reason:str } );
-                                                       
-                                                       /* reply with close ack */
-                                                       this.send(msg.data);
-                                                       
-                                                       if (child.onclose)
-                                                               child.onclose(ms);
-                                                       
-                                                       this.myparent.active_children.splice(m, 1);
-                                                       break;
-                                               }
-
-                               }
-                               } // switch
-                       }
-               } else {
-                       if (msg.data.charCodeAt(0) == lws_meta_cmd.WRITE ) {
-                               var ch = msg.data.charCodeAt(1), m, ms;
-                               var ws = this.myparent, ms;
-                                                               
-                               for (m = 0; m < ws.active_children.length; m++) {
-                                       if (ws.active_children[m].channel_id == ch) {
-                                               ms = new MessageEvent("WebSocket", { data: msg.data.substr(2, msg.data.length - 2) } );
-                                               if (ws.active_children[m].onmessage)
-                                                       ws.active_children[m].onmessage(ms);
-                                               break;
-                                       }
-                               }
-                       }
-               }
-       }
-       this.real.onclose = function() {
-               var ws = this.myparent, m;
-               for (m = 0; m < ws.active_children.length; m++) {
-                       var child = ws.active_children[m];
-                       var ms = new CloseEvent("close", { code:1000, reason:"parent closed" } );
-                       
-                       if (child.onclose)
-                               child.onclose(ms);
-               }
-       }
-
-}
-
-
-
-/* make a child connection using existing lws_meta real ws connection */
-lws_meta_ws.prototype.new_ws = function(suburl, protocol)
-{
-       var ch = new lws_meta_ws_child();
-       
-       ch.suburl = suburl;
-       ch.subprotocol = protocol;
-       ch.cookie = "C" + this.ordinal++;
-       
-       this.pending_children.push(ch);
-       
-       if (this.real.readyState == 1)
-               this.real.onopen();
-       
-       return ch;
-}
-
-
-/*
- * end of lws-meta helpers
- */
-function lws_san(s)
-{
-       if (s.search("<") != -1)
-               return "invalid string";
-       
-       return s;
-}
diff --git a/test-server/test-echo.c b/test-server/test-echo.c
deleted file mode 100644 (file)
index 1b1115f..0000000
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * libwebsockets-test-echo
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary. So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <string.h>
-#include <assert.h>
-#include <signal.h>
-
-#include "../lib/libwebsockets.h"
-
-#ifndef _WIN32
-#include <syslog.h>
-#include <sys/time.h>
-#include <unistd.h>
-#else
-#include "gettimeofday.h"
-#include <process.h>
-#endif
-
-static volatile int force_exit = 0;
-static int versa, state;
-static int times = -1;
-
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-
-#define MAX_ECHO_PAYLOAD 1024
-
-struct per_session_data__echo {
-       size_t rx, tx;
-       unsigned char buf[LWS_PRE + MAX_ECHO_PAYLOAD];
-       unsigned int len;
-       unsigned int index;
-       int final;
-       int continuation;
-       int binary;
-};
-
-static int
-callback_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user,
-             void *in, size_t len)
-{
-       struct per_session_data__echo *pss =
-                       (struct per_session_data__echo *)user;
-       int n;
-
-       switch (reason) {
-
-#ifndef LWS_NO_SERVER
-
-       case LWS_CALLBACK_ESTABLISHED:
-               pss->index = 0;
-               pss->len = -1;
-               break;
-
-       case LWS_CALLBACK_SERVER_WRITEABLE:
-do_tx:
-
-               n = LWS_WRITE_CONTINUATION;
-               if (!pss->continuation) {
-                       if (pss->binary)
-                               n = LWS_WRITE_BINARY;
-                       else
-                               n = LWS_WRITE_TEXT;
-                       pss->continuation = 1;
-               }
-               if (!pss->final)
-                       n |= LWS_WRITE_NO_FIN;
-               lwsl_info("+++ test-echo: writing %d, with final %d\n",
-                         pss->len, pss->final);
-
-               pss->tx += pss->len;
-               n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, n);
-               if (n < 0) {
-                       lwsl_err("ERROR %d writing to socket, hanging up\n", n);
-                       return 1;
-               }
-               if (n < (int)pss->len) {
-                       lwsl_err("Partial write\n");
-                       return -1;
-               }
-               pss->len = -1;
-               if (pss->final)
-                       pss->continuation = 0;
-               lws_rx_flow_control(wsi, 1);
-               break;
-
-       case LWS_CALLBACK_RECEIVE:
-do_rx:
-               pss->final = lws_is_final_fragment(wsi);
-               pss->binary = lws_frame_is_binary(wsi);
-               lwsl_info("+++ test-echo: RX len %ld final %ld, pss->len=%ld\n",
-                         (long)len, (long)pss->final, (long)pss->len);
-
-               memcpy(&pss->buf[LWS_PRE], in, len);
-               assert((int)pss->len == -1);
-               pss->len = (unsigned int)len;
-               pss->rx += len;
-
-               lws_rx_flow_control(wsi, 0);
-               lws_callback_on_writable(wsi);
-               break;
-#endif
-
-#ifndef LWS_NO_CLIENT
-       /* when the callback is used for client operations --> */
-
-       case LWS_CALLBACK_CLOSED:
-       case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
-               lwsl_debug("closed\n");
-               state = 0;
-               break;
-
-       case LWS_CALLBACK_CLIENT_ESTABLISHED:
-               lwsl_debug("Client has connected\n");
-               pss->index = 0;
-               pss->len = -1;
-               state = 2;
-               break;
-
-       case LWS_CALLBACK_CLIENT_RECEIVE:
-#ifndef LWS_NO_SERVER
-               if (versa)
-                       goto do_rx;
-#endif
-               lwsl_notice("Client RX: %s", (char *)in);
-               break;
-
-       case LWS_CALLBACK_CLIENT_WRITEABLE:
-#ifndef LWS_NO_SERVER
-               if (versa) {
-                       if (pss->len != (unsigned int)-1)
-                               goto do_tx;
-                       break;
-               }
-#endif
-               /* we will send our packet... */
-               pss->len = sprintf((char *)&pss->buf[LWS_PRE],
-                                  "hello from libwebsockets-test-echo client pid %d index %d\n",
-                                  getpid(), pss->index++);
-               lwsl_notice("Client TX: %s", &pss->buf[LWS_PRE]);
-               n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, LWS_WRITE_TEXT);
-               if (n < 0) {
-                       lwsl_err("ERROR %d writing to socket, hanging up\n", n);
-                       return -1;
-               }
-               if (n < (int)pss->len) {
-                       lwsl_err("Partial write\n");
-                       return -1;
-               }
-               break;
-#endif
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-
-
-static struct lws_protocols protocols[] = {
-       /* first protocol must always be HTTP handler */
-
-       {
-               "",             /* name - can be overridden with -e */
-               callback_echo,
-               sizeof(struct per_session_data__echo),  /* per_session_data_size */
-               MAX_ECHO_PAYLOAD,
-       },
-       {
-               NULL, NULL, 0           /* End of list */
-       }
-};
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-
-void sighandler(int sig)
-{
-       force_exit = 1;
-}
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl-cert",   required_argument,      NULL, 'C' },
-       { "ssl-key",    required_argument,      NULL, 'k' },
-#ifndef LWS_NO_CLIENT
-       { "client",     required_argument,      NULL, 'c' },
-       { "ratems",     required_argument,      NULL, 'r' },
-#endif
-       { "ssl",        no_argument,            NULL, 's' },
-       { "versa",      no_argument,            NULL, 'v' },
-       { "uri",        required_argument,      NULL, 'u' },
-       { "passphrase", required_argument,      NULL, 'P' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "times",      required_argument,      NULL, 'n' },
-       { "echogen",    no_argument,            NULL, 'e' },
-#ifndef LWS_NO_DAEMONIZE
-       { "daemonize",  no_argument,            NULL, 'D' },
-#endif
-       { NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-       int n = 0;
-       int port = 7681;
-       int use_ssl = 0;
-       struct lws_context *context;
-       int opts = 0;
-       char interface_name[128] = "";
-       const char *_interface = NULL;
-       char ssl_cert[256] = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
-       char ssl_key[256] = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
-#ifndef _WIN32
-/* LOG_PERROR is not POSIX standard, and may not be portable */
-#ifdef __sun
-       int syslog_options = LOG_PID;
-#else
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#endif
-       int client = 0;
-       int listen_port = 80;
-       struct lws_context_creation_info info;
-       char passphrase[256];
-       char uri[256] = "/";
-#ifndef LWS_NO_CLIENT
-       char address[256], ads_port[256 + 30];
-       int rate_us = 250000;
-       unsigned long long oldus;
-       struct lws *wsi;
-       int disallow_selfsigned = 0;
-       struct timeval tv;
-       const char *connect_protocol = NULL;
-       struct lws_client_connect_info i;
-#endif
-
-       int debug_level = 7;
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-
-       memset(&info, 0, sizeof info);
-
-#ifndef LWS_NO_CLIENT
-       lwsl_notice("Built to support client operations\n");
-#endif
-#ifndef LWS_NO_SERVER
-       lwsl_notice("Built to support server operations\n");
-#endif
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "i:hsp:d:DC:k:P:vu:n:e"
-#ifndef LWS_NO_CLIENT
-                       "c:r:"
-#endif
-                               , options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'P':
-                       strncpy(passphrase, optarg, sizeof(passphrase));
-                       passphrase[sizeof(passphrase) - 1] = '\0';
-                       info.ssl_private_key_password = passphrase;
-                       break;
-               case 'C':
-                       strncpy(ssl_cert, optarg, sizeof(ssl_cert));
-                       ssl_cert[sizeof(ssl_cert) - 1] = '\0';
-                       disallow_selfsigned = 1;
-                       break;
-               case 'k':
-                       strncpy(ssl_key, optarg, sizeof(ssl_key));
-                       ssl_key[sizeof(ssl_key) - 1] = '\0';
-                       break;
-               case 'u':
-                       strncpy(uri, optarg, sizeof(uri));
-                       uri[sizeof(uri) - 1] = '\0';
-                       break;
-
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-#if !defined(_WIN32) && !defined(__sun)
-                       syslog_options &= ~LOG_PERROR;
-#endif
-                       break;
-#endif
-#ifndef LWS_NO_CLIENT
-               case 'c':
-                       client = 1;
-                       strncpy(address, optarg, sizeof(address) - 1);
-                       address[sizeof(address) - 1] = '\0';
-                       port = 80;
-                       break;
-               case 'r':
-                       rate_us = atoi(optarg) * 1000;
-                       break;
-#endif
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 's':
-                       use_ssl = 1; /* 1 = take care about cert verification, 2 = allow anything */
-                       break;
-               case 'p':
-                       port = atoi(optarg);
-                       break;
-               case 'v':
-                       versa = 1;
-                       break;
-               case 'e':
-                       protocols[0].name = "lws-echogen";
-                       connect_protocol = protocols[0].name;
-                       lwsl_err("using lws-echogen\n");
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       _interface = interface_name;
-                       break;
-               case 'n':
-                       times = atoi(optarg);
-                       break;
-               case '?':
-               case 'h':
-                       fprintf(stderr, "Usage: libwebsockets-test-echo\n"
-                               "  --debug      / -d <debug bitfield>\n"
-                               "  --port       / -p <port>\n"
-                               "  --ssl-cert   / -C <cert path>\n"
-                               "  --ssl-key    / -k <key path>\n"
-#ifndef LWS_NO_CLIENT
-                               "  --client     / -c <server IP>\n"
-                               "  --ratems     / -r <rate in ms>\n"
-#endif
-                               "  --ssl        / -s\n"
-                               "  --passphrase / -P <passphrase>\n"
-                               "  --interface  / -i <interface>\n"
-                               "  --uri        / -u <uri path>\n"
-                               "  --times      / -n <-1 unlimited or times to echo>\n"
-#ifndef LWS_NO_DAEMONIZE
-                               "  --daemonize  / -D\n"
-#endif
-                       );
-                       exit(1);
-               }
-       }
-
-#ifndef LWS_NO_DAEMONIZE
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-#if defined(WIN32) || defined(_WIN32)
-#else
-       if (!client && daemonize && lws_daemonize("/tmp/.lwstecho-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 1;
-       }
-#endif
-#endif
-
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-       lwsl_notice("libwebsockets test server echo - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-#ifndef LWS_NO_CLIENT
-       if (client) {
-               lwsl_notice("Running in client mode\n");
-               listen_port = CONTEXT_PORT_NO_LISTEN;
-               if (use_ssl && !disallow_selfsigned) {
-                       lwsl_info("allowing selfsigned\n");
-                       use_ssl = 2;
-               } else {
-                       lwsl_info("requiring server cert validation against %s\n",
-                                 ssl_cert);
-                       info.ssl_ca_filepath = ssl_cert;
-               }
-       } else {
-#endif
-#ifndef LWS_NO_SERVER
-               lwsl_notice("Running in server mode\n");
-               listen_port = port;
-#endif
-#ifndef LWS_NO_CLIENT
-       }
-#endif
-
-       info.port = listen_port;
-       info.iface = _interface;
-       info.protocols = protocols;
-       if (use_ssl && !client) {
-               info.ssl_cert_filepath = ssl_cert;
-               info.ssl_private_key_filepath = ssl_key;
-       } else
-               if (use_ssl && client) {
-                       info.ssl_cert_filepath = NULL;
-                       info.ssl_private_key_filepath = NULL;
-               }
-       info.gid = -1;
-       info.uid = -1;
-       info.extensions = exts;
-       info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8;
-
-       if (use_ssl)
-               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-#ifndef LWS_NO_EXTENSIONS
-       info.extensions = exts;
-#endif
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               lwsl_err("libwebsocket init failed\n");
-               return -1;
-       }
-
-
-       signal(SIGINT, sighandler);
-
-#ifndef LWS_NO_CLIENT
-       gettimeofday(&tv, NULL);
-       oldus = ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
-#endif
-
-       n = 0;
-       while (n >= 0 && !force_exit) {
-#ifndef LWS_NO_CLIENT
-               if (client && !state && times) {
-                       state = 1;
-                       lwsl_notice("Client connecting to %s:%u....\n",
-                                   address, port);
-                       /* we are in client mode */
-
-                       address[sizeof(address) - 1] = '\0';
-                       sprintf(ads_port, "%s:%u", address, port & 65535);
-                       if (times > 0)
-                               times--;
-
-                       memset(&i, 0, sizeof(i));
-
-                       i.context = context;
-                       i.address = address;
-                       i.port = port;
-                       i.ssl_connection = use_ssl;
-                       i.path = uri;
-                       i.host = ads_port;
-                       i.origin = ads_port;
-                       i.protocol = connect_protocol;
-
-                       wsi = lws_client_connect_via_info(&i);
-                       if (!wsi) {
-                               lwsl_err("Client failed to connect to %s:%u\n",
-                                        address, port);
-                               goto bail;
-                       }
-               }
-
-               if (client && !versa && times) {
-                       gettimeofday(&tv, NULL);
-
-                       if (((((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec) - oldus) > rate_us) {
-                               lws_callback_on_writable_all_protocol(context,
-                                               &protocols[0]);
-                               oldus = ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
-                               if (times > 0)
-                                       times--;
-                       }
-               }
-
-               if (client && !state && !times)
-                       break;
-#endif
-               n = lws_service(context, 10);
-       }
-#ifndef LWS_NO_CLIENT
-bail:
-#endif
-       lws_context_destroy(context);
-
-       lwsl_notice("libwebsockets-test-echo exited cleanly\n");
-#ifndef _WIN32
-       closelog();
-#endif
-
-       return 0;
-}
diff --git a/test-server/test-fraggle.c b/test-server/test-fraggle.c
deleted file mode 100644 (file)
index 1de7360..0000000
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * libwebsockets-test-fraggle - random fragmentation test
- *
- * Copyright (C) 2011-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <string.h>
-#include "../lib/libwebsockets.h"
-
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-
-static int client;
-static int terminate;
-
-enum demo_protocols {
-       PROTOCOL_FRAGGLE,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-/* fraggle protocol */
-
-enum fraggle_states {
-       FRAGSTATE_START_MESSAGE,
-       FRAGSTATE_RANDOM_PAYLOAD,
-       FRAGSTATE_POST_PAYLOAD_SUM,
-};
-
-struct per_session_data__fraggle {
-       int packets_left;
-       int total_message;
-       unsigned long sum;
-       enum fraggle_states state;
-};
-
-static int
-callback_fraggle(struct lws *wsi, enum lws_callback_reasons reason,
-                void *user, void *in, size_t len)
-{
-       int n;
-       unsigned char buf[LWS_PRE + 8000];
-       struct per_session_data__fraggle *psf = user;
-       int chunk;
-       int write_mode = LWS_WRITE_CONTINUATION;
-       unsigned long sum;
-       unsigned char *p = (unsigned char *)in;
-       unsigned char *bp = &buf[LWS_PRE];
-       int ran;
-
-       switch (reason) {
-
-       case LWS_CALLBACK_ESTABLISHED:
-
-               fprintf(stderr, "server sees client connect\n");
-               psf->state = FRAGSTATE_START_MESSAGE;
-               /* start the ball rolling */
-               lws_callback_on_writable(wsi);
-               break;
-
-       case LWS_CALLBACK_CLIENT_ESTABLISHED:
-
-               fprintf(stderr, "client connects to server\n");
-               psf->state = FRAGSTATE_START_MESSAGE;
-               break;
-
-       case LWS_CALLBACK_CLIENT_RECEIVE:
-
-               switch (psf->state) {
-
-               case FRAGSTATE_START_MESSAGE:
-
-                       psf->state = FRAGSTATE_RANDOM_PAYLOAD;
-                       psf->sum = 0;
-                       psf->total_message = 0;
-                       psf->packets_left = 0;
-
-                       /* fallthru */
-
-               case FRAGSTATE_RANDOM_PAYLOAD:
-
-                       for (n = 0; (unsigned int)n < len; n++)
-                               psf->sum += p[n];
-
-                       psf->total_message += len;
-                       psf->packets_left++;
-
-                       if (lws_is_final_fragment(wsi))
-                               psf->state = FRAGSTATE_POST_PAYLOAD_SUM;
-                       break;
-
-               case FRAGSTATE_POST_PAYLOAD_SUM:
-
-                       sum = ((unsigned int)p[0]) << 24;
-                       sum |= p[1] << 16;
-                       sum |= p[2] << 8;
-                       sum |= p[3];
-                       if (sum == psf->sum)
-                               fprintf(stderr, "EOM received %d correctly "
-                                               "from %d fragments\n",
-                                       psf->total_message, psf->packets_left);
-                       else
-                               fprintf(stderr, "**** ERROR at EOM: "
-                                               "length %d, rx sum = 0x%lX, "
-                                               "server says it sent 0x%lX\n",
-                                            psf->total_message, psf->sum, sum);
-
-                       psf->state = FRAGSTATE_START_MESSAGE;
-                       break;
-               }
-               break;
-
-       case LWS_CALLBACK_SERVER_WRITEABLE:
-
-               switch (psf->state) {
-
-               case FRAGSTATE_START_MESSAGE:
-                       lws_get_random(lws_get_context(wsi), &ran, sizeof(ran));
-                       psf->packets_left = (ran & 1023) + 1;
-                       fprintf(stderr, "Spamming %d random fragments\n",
-                                                            psf->packets_left);
-                       psf->sum = 0;
-                       psf->total_message = 0;
-                       write_mode = LWS_WRITE_BINARY;
-                       psf->state = FRAGSTATE_RANDOM_PAYLOAD;
-
-                       /* fallthru */
-
-               case FRAGSTATE_RANDOM_PAYLOAD:
-
-                       /*
-                        * note how one chunk can be 8000, but we use the
-                        * default rx buffer size of 4096, so we exercise the
-                        * code for rx spill because the rx buffer is full
-                        */
-
-                       lws_get_random(lws_get_context(wsi), &ran, sizeof(ran));
-                       chunk = (ran & 511) + 1;
-                       psf->total_message += chunk;
-
-                       lws_get_random(lws_get_context(wsi), bp, chunk);
-                       for (n = 0; n < chunk; n++)
-                               psf->sum += bp[n];
-
-                       psf->packets_left--;
-                       if (psf->packets_left)
-                               write_mode |= LWS_WRITE_NO_FIN;
-                       else
-                               psf->state = FRAGSTATE_POST_PAYLOAD_SUM;
-
-                       n = lws_write(wsi, bp, chunk, write_mode);
-                       if (n < 0)
-                               return -1;
-                       if (n < chunk) {
-                               lwsl_err("Partial write\n");
-                               return -1;
-                       }
-
-                       lws_callback_on_writable(wsi);
-                       break;
-
-               case FRAGSTATE_POST_PAYLOAD_SUM:
-
-                       fprintf(stderr, "Spamming session over, "
-                                       "len = %d. sum = 0x%lX\n",
-                                                 psf->total_message, psf->sum);
-
-                       bp[0] = psf->sum >> 24;
-                       bp[1] = (unsigned char)(psf->sum >> 16);
-                       bp[2] = (unsigned char)(psf->sum >> 8);
-                       bp[3] = (unsigned char)psf->sum;
-
-                       n = lws_write(wsi, (unsigned char *)bp,
-                                                          4, LWS_WRITE_BINARY);
-                       if (n < 0)
-                               return -1;
-                       if (n < 4) {
-                               lwsl_err("Partial write\n");
-                               return -1;
-                       }
-
-                       psf->state = FRAGSTATE_START_MESSAGE;
-
-                       lws_callback_on_writable(wsi);
-                       break;
-               }
-               break;
-
-       case LWS_CALLBACK_CLOSED:
-
-               terminate = 1;
-               break;
-
-       /* because we are protocols[0] ... */
-
-       case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
-               if (strcmp(in, "deflate-stream") == 0) {
-                       fprintf(stderr, "denied deflate-stream extension\n");
-                       return 1;
-               }
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-       {
-               "fraggle-protocol",
-               callback_fraggle,
-               sizeof(struct per_session_data__fraggle),
-       },
-       {
-               NULL, NULL, 0           /* End of list */
-       }
-};
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-       },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 's' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "client",     no_argument,            NULL, 'c' },
-       { NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-       int n = 0;
-       int port = 7681;
-       int use_ssl = 0;
-       struct lws_context *context;
-       int opts = 0;
-       char interface_name[128] = "", ads_port[300];
-       const char *iface = NULL;
-       struct lws *wsi;
-       const char *address = NULL;
-       int server_port = port;
-       struct lws_context_creation_info info;
-
-       memset(&info, 0, sizeof info);
-       lwsl_notice("libwebsockets test server fraggle - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "ci:hsp:d:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'd':
-                       lws_set_log_level(atoi(optarg), NULL);
-                       break;
-               case 's':
-                       use_ssl = 1;
-                       break;
-               case 'p':
-                       port = atoi(optarg);
-                       server_port = port;
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       iface = interface_name;
-                       break;
-               case 'c':
-                       client = 1;
-                       fprintf(stderr, " Client mode\n");
-                       break;
-               case 'h':
-                       fprintf(stderr, "Usage: libwebsockets-test-fraggle "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--client]\n");
-                       exit(1);
-               }
-       }
-
-       if (client) {
-               server_port = CONTEXT_PORT_NO_LISTEN;
-               if (optind >= argc) {
-                       fprintf(stderr, "Must give address of server\n");
-                       return 1;
-               }
-       }
-
-       info.port = server_port;
-       info.iface = iface;
-       info.protocols = protocols;
-       info.extensions = exts;
-
-       if (use_ssl) {
-               info.ssl_cert_filepath = LOCAL_RESOURCE_PATH
-                               "/libwebsockets-test-server.pem";
-               info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH
-                               "/libwebsockets-test-server.key.pem";
-       }
-       info.gid = -1;
-       info.uid = -1;
-       info.options = opts;
-       info.extensions = exts;
-
-       if (use_ssl)
-               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               fprintf(stderr, "libwebsocket init failed\n");
-               return -1;
-       }
-
-       if (client) {
-               struct lws_client_connect_info i;
-
-               address = argv[optind];
-               lws_snprintf(ads_port, sizeof(ads_port), "%s:%u",
-                        address, port & 65535);
-               memset(&i, 0, sizeof(i));
-               i.context = context;
-               i.address = address;
-               i.port = port;
-               i.ssl_connection = use_ssl;
-               i.path = "/";
-               i.host = ads_port;
-               i.origin = ads_port;
-               i.protocol = protocols[PROTOCOL_FRAGGLE].name;
-
-               lwsl_notice("Connecting to %s:%u\n", address, port);
-               wsi = lws_client_connect_via_info(&i);
-               if (wsi == NULL) {
-                       fprintf(stderr, "Client connect to server failed\n");
-                       goto bail;
-               }
-       }
-
-       n = 0;
-       while (!n && !terminate)
-               n = lws_service(context, 50);
-
-       fprintf(stderr, "Terminating...\n");
-
-bail:
-       lws_context_destroy(context);
-
-       return 0;
-}
diff --git a/test-server/test-ping.c b/test-server/test-ping.c
deleted file mode 100644 (file)
index 3917781..0000000
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * libwebsockets-test-ping - libwebsockets test floodping
- *
- * Copyright (C) 2011-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary. So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <string.h>
-#include <signal.h>
-#include <sys/types.h>
-
-#include "../lib/libwebsockets.h"
-
-#ifndef _WIN32
-#include <netdb.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/ioctl.h>
-#include <poll.h>
-#include <unistd.h>
-#else
-#include "gettimeofday.h"
-#endif
-
-#ifdef __ANDROID__
-#include <termiosh>
-#endif
-
-#ifdef __sun
-#include <sys/termios.h>
-#endif
-
-/*
- * this is specified in the 04 standard, control frames can only have small
- * payload length styles
- */
-#define MAX_PING_PAYLOAD 125
-#define MAX_MIRROR_PAYLOAD 4096
-#define MAX_PING_CLIENTS 256
-#define PING_RINGBUFFER_SIZE 256
-
-static struct lws *ping_wsi[MAX_PING_CLIENTS];
-static unsigned int interval_us = 1000000;
-static unsigned int size = 64;
-static int flood;
-static const char *address;
-static unsigned char pingbuf[LWS_PRE + MAX_MIRROR_PAYLOAD];
-static char peer_name[128];
-static unsigned long started;
-static int screen_width = 80;
-static int use_mirror;
-static unsigned int write_options;
-
-static unsigned long rtt_min = 100000000;
-static unsigned long rtt_max;
-static unsigned long rtt_avg;
-static unsigned long global_rx_count;
-static unsigned long global_tx_count;
-static int clients = 1;
-static unsigned long interrupted_time;
-
-struct ping {
-       unsigned long issue_timestamp;
-       unsigned long index;
-       unsigned int seen;
-};
-
-struct per_session_data__ping {
-       unsigned long long ping_index;
-
-       struct ping ringbuffer[PING_RINGBUFFER_SIZE];
-       int ringbuffer_head;
-       int ringbuffer_tail;
-
-       unsigned long rx_count;
-};
-
-/*
- * uses the ping pong protocol features to provide an equivalent for the
- * ping utility for 04+ websockets
- */
-
-enum demo_protocols {
-
-       PROTOCOL_LWS_MIRROR,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-
-static int
-callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
-                   void *user, void *in, size_t len)
-{
-       struct per_session_data__ping *psd = user;
-       struct timeval tv;
-       unsigned char *p;
-       unsigned long iv;
-       int match = 0;
-       unsigned long long l;
-       int shift;
-       int n;
-
-       switch (reason) {
-       case LWS_CALLBACK_CLOSED:
-               fprintf(stderr, "LWS_CALLBACK_CLOSED on %p\n", (void *)wsi);
-
-               /* remove closed guy */
-
-               for (n = 0; n < clients; n++)
-                       if (ping_wsi[n] == wsi) {
-                               clients--;
-                               while (n < clients) {
-                                       ping_wsi[n] = ping_wsi[n + 1];
-                                       n++;
-                               }
-                       }
-
-               break;
-
-       case LWS_CALLBACK_CLIENT_ESTABLISHED:
-
-               psd->rx_count = 0;
-               psd->ping_index = 1;
-               psd->ringbuffer_head = 0;
-               psd->ringbuffer_tail = 0;
-
-               /*
-                * start the ball rolling,
-                * LWS_CALLBACK_CLIENT_WRITEABLE will come next service
-                */
-
-               lws_callback_on_writable(wsi);
-               break;
-
-       case LWS_CALLBACK_CLIENT_RECEIVE:
-       case LWS_CALLBACK_CLIENT_RECEIVE_PONG:
-               gettimeofday(&tv, NULL);
-               iv = (tv.tv_sec * 1000000) + tv.tv_usec;
-
-               psd->rx_count++;
-
-               shift = 56;
-               p = in;
-               l = 0;
-
-               while (shift >= 0) {
-                       l |= ((lws_intptr_t)*p++) << shift;
-                       shift -= 8;
-               }
-
-               /* find it in the ringbuffer, look backwards from head */
-               n = psd->ringbuffer_head;
-               while (!match) {
-
-                       if (psd->ringbuffer[n].index == l) {
-                               psd->ringbuffer[n].seen++;
-                               match = 1;
-                               continue;
-                       }
-
-                       if (n == psd->ringbuffer_tail) {
-                               match = -1;
-                               continue;
-                       }
-
-                       if (n == 0)
-                               n = PING_RINGBUFFER_SIZE - 1;
-                       else
-                               n--;
-               }
-
-               if (match < 1) {
-
-                       if (!flood)
-                               fprintf(stderr, "%d bytes from %s: req=%ld "
-                                     "time=(unknown)\n", (int)len, address,
-                                       (long)l);
-                       else
-                               fprintf(stderr, "\b \b");
-
-                       break;
-               }
-
-               if (psd->ringbuffer[n].seen > 1)
-                       fprintf(stderr, "DUP! ");
-
-               if ((iv - psd->ringbuffer[n].issue_timestamp) < rtt_min)
-                       rtt_min = iv - psd->ringbuffer[n].issue_timestamp;
-
-               if ((iv - psd->ringbuffer[n].issue_timestamp) > rtt_max)
-                       rtt_max = iv - psd->ringbuffer[n].issue_timestamp;
-
-               rtt_avg += iv - psd->ringbuffer[n].issue_timestamp;
-               global_rx_count++;
-
-               if (!flood)
-                       fprintf(stderr, "%d bytes from %s: req=%ld "
-                               "time=%lu.%lums\n", (int)len, address, (long)l,
-                              (iv - psd->ringbuffer[n].issue_timestamp) / 1000,
-                       ((iv - psd->ringbuffer[n].issue_timestamp) / 100) % 10);
-               else
-                       fprintf(stderr, "\b \b");
-               break;
-
-       case LWS_CALLBACK_CLIENT_WRITEABLE:
-
-               shift = 56;
-               p = &pingbuf[LWS_PRE];
-
-               /* 64-bit ping index in network byte order */
-
-               while (shift >= 0) {
-                       *p++ = (unsigned char)(psd->ping_index >> shift);
-                       shift -= 8;
-               }
-
-               while ((unsigned int)(p - &pingbuf[LWS_PRE]) < size)
-                       *p++ = 0;
-
-               gettimeofday(&tv, NULL);
-
-               psd->ringbuffer[psd->ringbuffer_head].issue_timestamp =
-                                            (tv.tv_sec * 1000000) + tv.tv_usec;
-               psd->ringbuffer[psd->ringbuffer_head].index = (unsigned long)psd->ping_index++;
-               psd->ringbuffer[psd->ringbuffer_head].seen = 0;
-
-               if (psd->ringbuffer_head == PING_RINGBUFFER_SIZE - 1)
-                       psd->ringbuffer_head = 0;
-               else
-                       psd->ringbuffer_head++;
-
-               /* snip any re-used tail so we keep to the ring length */
-
-               if (psd->ringbuffer_tail == psd->ringbuffer_head) {
-                       if (psd->ringbuffer_tail == PING_RINGBUFFER_SIZE - 1)
-                               psd->ringbuffer_tail = 0;
-                       else
-                               psd->ringbuffer_tail++;
-               }
-
-               global_tx_count++;
-
-               if (use_mirror)
-                       n = lws_write(wsi,
-                               &pingbuf[LWS_PRE],
-                                       size, write_options | LWS_WRITE_BINARY);
-               else
-                       n = lws_write(wsi,
-                               &pingbuf[LWS_PRE],
-                                       size, write_options | LWS_WRITE_PING);
-
-               if (n < 0)
-                       return -1;
-               if (n < (int)size) {
-                       lwsl_err("Partial write\n");
-                       return -1;
-               }
-
-               if (flood &&
-                        (psd->ping_index - psd->rx_count) < (screen_width - 1))
-                       fprintf(stderr, ".");
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-
-       {
-               "lws-mirror-protocol",
-               callback_lws_mirror,
-               sizeof (struct per_session_data__ping),
-       },
-       {
-               NULL, NULL, 0/* end of list */
-       }
-};
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-       },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 't' },
-       { "interval",   required_argument,      NULL, 'i' },
-       { "size",       required_argument,      NULL, 's' },
-       { "protocol",   required_argument,      NULL, 'n' },
-       { "flood",      no_argument,            NULL, 'f' },
-       { "mirror",     no_argument,            NULL, 'm' },
-       { "replicate",  required_argument,      NULL, 'r' },
-       { "killmask",   no_argument,            NULL, 'k' },
-       { "version",    required_argument,      NULL, 'v' },
-       { NULL, 0, 0, 0 }
-};
-
-#ifndef _WIN32
-static void
-signal_handler(int sig, siginfo_t *si, void *v)
-{
-       struct timeval tv;
-
-       gettimeofday(&tv, NULL);
-       interrupted_time = (tv.tv_sec * 1000000) + tv.tv_usec;
-}
-#endif
-
-int main(int argc, char **argv)
-{
-       int n = 0;
-       int port = 7681;
-       int use_ssl = 0;
-       struct lws_context *context;
-       char protocol_name[256], ads_port[300];
-       char ip[30];
-#ifndef _WIN32
-       struct sigaction sa;
-       struct winsize w;
-#endif
-       struct timeval tv;
-       unsigned long oldus = 0;
-       unsigned long l;
-       int ietf_version = -1;
-       struct lws_context_creation_info info;
-       struct lws_client_connect_info i;
-
-       memset(&info, 0, sizeof info);
-
-       if (argc < 2)
-               goto usage;
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "v:kr:hmfts:n:i:p:d:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'd':
-                       lws_set_log_level(atoi(optarg), NULL);
-                       break;
-               case 'm':
-                       use_mirror = 1;
-                       break;
-               case 't':
-                       use_ssl = 2; /* 2 = allow selfsigned */
-                       break;
-               case 'p':
-                       port = atoi(optarg);
-                       break;
-               case 'n':
-                       strncpy(protocol_name, optarg, sizeof protocol_name);
-                       protocol_name[(sizeof protocol_name) - 1] = '\0';
-                       protocols[PROTOCOL_LWS_MIRROR].name = protocol_name;
-                       break;
-               case 'i':
-                       interval_us = (unsigned int)(1000000.0 * atof(optarg));
-                       break;
-               case 's':
-                       size = atoi(optarg);
-                       break;
-               case 'f':
-                       flood = 1;
-                       break;
-               case 'r':
-                       clients = atoi(optarg);
-                       if (clients > MAX_PING_CLIENTS || clients < 1) {
-                               fprintf(stderr, "Max clients supported = %d\n",
-                                                             MAX_PING_CLIENTS);
-                               return 1;
-                       }
-                       break;
-               case 'k':
-                       write_options = LWS_WRITE_CLIENT_IGNORE_XOR_MASK;
-                       break;
-               case 'v':
-                       ietf_version = atoi(optarg);
-                       break;
-
-               case 'h':
-                       goto usage;
-               }
-       }
-
-       if (!use_mirror) {
-               if (size > MAX_PING_PAYLOAD) {
-                       fprintf(stderr, "Max ping opcode payload size %d\n",
-                                                             MAX_PING_PAYLOAD);
-                       return 1;
-               }
-       } else {
-               if (size > MAX_MIRROR_PAYLOAD) {
-                       fprintf(stderr, "Max mirror payload size %d\n",
-                                                           MAX_MIRROR_PAYLOAD);
-                       return 1;
-               }
-       }
-
-#ifndef _WIN32
-       if (isatty(STDOUT_FILENO))
-               if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)
-                       if (w.ws_col > 0)
-                               screen_width = w.ws_col;
-#endif
-
-       info.port = CONTEXT_PORT_NO_LISTEN;
-       info.protocols = protocols;
-       info.extensions = exts;
-
-       info.gid = -1;
-       info.uid = -1;
-
-       if (use_ssl)
-               info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               fprintf(stderr, "Creating libwebsocket context failed\n");
-               return 1;
-       }
-
-       /* create client websockets using dumb increment protocol */
-
-       address = argv[optind];
-       lws_snprintf(ads_port, sizeof(ads_port), "%s:%u",
-                address, port & 65535);
-       lwsl_notice("Connecting to %s...\n", ads_port);
-       memset(&i, 0, sizeof(i));
-       i.context = context;
-       i.address = address;
-       i.port = port;
-       i.ssl_connection = use_ssl;
-       i.path = "/";
-       i.host = ads_port;
-       i.origin = ads_port;
-       i.protocol = protocols[PROTOCOL_LWS_MIRROR].name;
-       i.ietf_version_or_minus_one = ietf_version;
-
-       for (n = 0; n < clients; n++) {
-               ping_wsi[n] = lws_client_connect_via_info(&i);
-               if (ping_wsi[n] == NULL) {
-                       lwsl_err("client %d failed to connect\n", n);
-                       return 1;
-               }
-       }
-
-       lws_get_peer_addresses(ping_wsi[0], lws_get_socket_fd(ping_wsi[0]),
-                                   peer_name, sizeof peer_name, ip, sizeof ip);
-
-       lwsl_notice("libwebsockets test server ping - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-       fprintf(stderr, "Websocket PING %s (%s) %d bytes of data.\n",
-                                                          peer_name, ip, size);
-
-#ifndef _WIN32
-       /* set the ^C handler */
-       sa.sa_sigaction = signal_handler;
-       sa.sa_flags = SA_SIGINFO;
-       sigemptyset(&sa.sa_mask);
-       sigaction(SIGINT, &sa, NULL);
-#endif
-
-       gettimeofday(&tv, NULL);
-       started = (tv.tv_sec * 1000000) + tv.tv_usec;
-
-       /* service loop */
-
-       n = 0;
-       while (n >= 0) {
-
-               gettimeofday(&tv, NULL);
-               l = (tv.tv_sec * 1000000) + tv.tv_usec;
-
-               /* servers can hang up on us */
-
-               if (clients == 0) {
-                       n = -1;
-                       continue;
-               }
-
-               if (!interrupted_time) {
-                       if ((l - oldus) > interval_us) {
-                               for (n = 0; n < clients; n++)
-                                       lws_callback_on_writable(ping_wsi[n]);
-                               oldus = l;
-                       }
-               } else
-
-                       /* allow time for in-flight pongs to come */
-
-                       if ((l - interrupted_time) > 250000) {
-                               n = -1;
-                               continue;
-                       }
-
-               if (!interval_us)
-                       n = lws_service(context, 0);
-               else
-                       n = lws_service(context, 1);
-       }
-
-       /* stats */
-
-       if (global_rx_count && global_tx_count)
-               fprintf(stderr, "\n--- %s websocket ping statistics "
-               "using %d connections ---\n"
-               "%lu packets transmitted, %lu received, "
-               "%lu%% packet loss, time %ldms\n"
-               "rtt min/avg/max = %0.3f/%0.3f/%0.3f ms\n"
-               "payload bandwidth average %0.3f KiBytes/sec\n",
-               peer_name, clients, global_tx_count, global_rx_count,
-               ((global_tx_count - global_rx_count) * 100) / global_tx_count,
-               (l - started) / 1000,
-               ((double)rtt_min) / 1000.0,
-               ((double)rtt_avg / global_rx_count) / 1000.0,
-               ((double)rtt_max) / 1000.0,
-               ((double)global_rx_count * (double)size) /
-                                 ((double)(l - started) / 1000000.0) / 1024.0);
-
-       lws_context_destroy(context);
-
-       return 0;
-
-usage:
-       fprintf(stderr, "Usage: libwebsockets-test-ping "
-                                            "<server address> [--port=<p>] "
-                                            "[--ssl] [--interval=<float sec>] "
-                                            "[--size=<bytes>] "
-                                            "[--protocol=<protocolname>] "
-                                            "[--mirror] "
-                                            "[--replicate=clients>] "
-                                            "[--version <version>] "
-                                            "[-d <log bitfield> ]"
-                                            "\n");
-       return 1;
-}
diff --git a/test-server/test-server-dumb-increment.c b/test-server/test-server-dumb-increment.c
deleted file mode 100644 (file)
index dc2548b..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * libwebsockets-test-server - libwebsockets test implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-#include "test-server.h"
-
-/* dumb_increment protocol */
-
-int
-callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
-                       void *user, void *in, size_t len)
-{
-       unsigned char buf[LWS_PRE + 512];
-       struct per_session_data__dumb_increment *pss =
-                       (struct per_session_data__dumb_increment *)user;
-       unsigned char *p = &buf[LWS_PRE];
-       int n, m;
-
-       switch (reason) {
-
-       case LWS_CALLBACK_ESTABLISHED:
-               pss->number = 0;
-               break;
-
-       case LWS_CALLBACK_SERVER_WRITEABLE:
-               n = sprintf((char *)p, "%d", pss->number++);
-               m = lws_write(wsi, p, n, LWS_WRITE_TEXT);
-               if (m < n) {
-                       lwsl_err("ERROR %d writing to di socket\n", n);
-                       return -1;
-               }
-               if (close_testing && pss->number == 50) {
-                       lwsl_info("close tesing limit, closing\n");
-                       return -1;
-               }
-               break;
-
-       case LWS_CALLBACK_RECEIVE:
-               if (len < 6)
-                       break;
-               if (strcmp((const char *)in, "reset\n") == 0)
-                       pss->number = 0;
-               if (strcmp((const char *)in, "closeme\n") == 0) {
-                       lwsl_notice("dumb_inc: closing as requested\n");
-                       lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY,
-                                        (unsigned char *)"seeya", 5);
-                       return -1;
-               }
-               break;
-       /*
-        * this just demonstrates how to use the protocol filter. If you won't
-        * study and reject connections based on header content, you don't need
-        * to handle this callback
-        */
-       case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
-               dump_handshake_info(wsi);
-               /* you could return non-zero here and kill the connection */
-               break;
-
-       /*
-        * this just demonstrates how to handle
-        * LWS_CALLBACK_WS_PEER_INITIATED_CLOSE and extract the peer's close
-        * code and auxiliary data.  You can just not handle it if you don't
-        * have a use for this.
-        */
-       case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
-               lwsl_notice("LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: len %lu\n",
-                           (unsigned long)len);
-               for (n = 0; n < (int)len; n++)
-                       lwsl_notice(" %d: 0x%02X\n", n,
-                                   ((unsigned char *)in)[n]);
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c
deleted file mode 100644 (file)
index 070bf7e..0000000
+++ /dev/null
@@ -1,806 +0,0 @@
-/*
- * libwebsockets-test-server - libwebsockets test implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-#include "test-server.h"
-
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *                             ascii string is sent down it every 50ms.
- *                             If you send "reset\n" on the websocket, then
- *                             the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *                             using this protocol, including the sender
- */
-
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-/* location of the certificate revocation list */
-extern char crl_path[1024];
-#endif
-
-extern int debug_level;
-
-enum demo_protocols {
-       /* always first */
-       PROTOCOL_HTTP = 0,
-
-       PROTOCOL_DUMB_INCREMENT,
-       PROTOCOL_LWS_MIRROR,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-/*
- * We take a strict whitelist approach to stop ../ attacks
- */
-struct serveable {
-       const char *urlpath;
-       const char *mimetype;
-};
-
-/*
- * this is just an example of parsing handshake headers, you don't need this
- * in your code unless you will filter allowing connections by the header
- * content
- */
-void
-dump_handshake_info(struct lws *wsi)
-{
-       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(wsi, n);
-               if (!len || len > sizeof(buf) - 1) {
-                       n++;
-                       continue;
-               }
-
-               lws_hdr_copy(wsi, buf, sizeof buf, n);
-               buf[sizeof(buf) - 1] = '\0';
-
-               fprintf(stderr, "    %s = %s\n", (char *)c, buf);
-               n++;
-       } while (c);
-}
-
-const char * get_mimetype(const char *file)
-{
-       int n = strlen(file);
-
-       if (n < 5)
-               return NULL;
-
-       if (!strcmp(&file[n - 4], ".ico"))
-               return "image/x-icon";
-
-       if (!strcmp(&file[n - 4], ".png"))
-               return "image/png";
-
-       if (!strcmp(&file[n - 5], ".html"))
-               return "text/html";
-
-       if (!strcmp(&file[n - 4], ".css"))
-               return "text/css";
-
-       if (!strcmp(&file[n - 3], ".js"))
-               return "text/javascript";
-
-       return NULL;
-}
-
-
-static const char * const param_names[] = {
-       "text",
-       "send",
-       "file",
-       "upload",
-};
-
-enum enum_param_names {
-       EPN_TEXT,
-       EPN_SEND,
-       EPN_FILE,
-       EPN_UPLOAD,
-};
-
-static int
-file_upload_cb(void *data, const char *name, const char *filename,
-              char *buf, int len, enum lws_spa_fileupload_states state)
-{
-       struct per_session_data__http *pss =
-                       (struct per_session_data__http *)data;
-       int n;
-
-       (void)n;
-
-       switch (state) {
-       case LWS_UFS_OPEN:
-               strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
-               /* 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  */
-               pss->post_fd = (lws_filefd_type)open("/tmp/post-file",
-                              O_CREAT | O_TRUNC | O_RDWR, 0600);
-               break;
-       case LWS_UFS_FINAL_CONTENT:
-       case LWS_UFS_CONTENT:
-               if (len) {
-                       pss->file_length += len;
-
-                       /* if the file length is too big, drop it */
-                       if (pss->file_length > 100000)
-                               return 1;
-
-                       n = write((int)pss->post_fd, buf, len);
-                       lwsl_notice("%s: write %d says %d\n", __func__, len, n);
-               }
-               if (state == LWS_UFS_CONTENT)
-                       break;
-               close((int)pss->post_fd);
-               pss->post_fd = LWS_INVALID_FILE;
-               break;
-       }
-
-       return 0;
-}
-
-/* this protocol server (always the first one) handles HTTP,
- *
- * Some misc callbacks that aren't associated with a protocol also turn up only
- * here on the first protocol server.
- */
-
-int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
-                 void *in, size_t len)
-{
-       struct per_session_data__http *pss =
-                       (struct per_session_data__http *)user;
-       unsigned char buffer[4096 + LWS_PRE];
-       lws_filepos_t amount, file_len, sent;
-       char leaf_path[1024];
-       const char *mimetype;
-       char *other_headers;
-       unsigned char *end, *start;
-       struct timeval tv;
-       unsigned char *p;
-#ifndef LWS_NO_CLIENT
-       struct per_session_data__http *pss1;
-       struct lws *wsi1;
-#endif
-       char buf[256];
-       char b64[64];
-       int n, m;
-#ifdef EXTERNAL_POLL
-       struct lws_pollargs *pa = (struct lws_pollargs *)in;
-#endif
-
-
-       switch (reason) {
-       case LWS_CALLBACK_HTTP:
-
-               lwsl_info("lws_http_serve: %s\n", (const char *)in);
-
-               if (debug_level & LLL_INFO) {
-                       dump_handshake_info(wsi);
-
-                       /* dump the individual URI Arg parameters */
-                       n = 0;
-                       while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf),
-                                                    WSI_TOKEN_HTTP_URI_ARGS, n) > 0) {
-                               lwsl_notice("URI Arg %d: %s\n", ++n, buf);
-                       }
-               }
-
-               {
-                       lws_get_peer_simple(wsi, buf, sizeof(buf));
-                       lwsl_info("HTTP connect from %s\n", buf);
-               }
-
-               if (len < 1) {
-                       lws_return_http_status(wsi,
-                                               HTTP_STATUS_BAD_REQUEST, NULL);
-                       goto try_to_reuse;
-               }
-
-#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT)
-               if (!strncmp(in, "/proxytest", 10)) {
-                       struct lws_client_connect_info i;
-                       char *rootpath = "/git/";
-                       const char *p = (const char *)in;
-
-                       if (lws_get_child(wsi))
-                               break;
-
-                       pss->client_finished = 0;
-                       memset(&i, 0, sizeof(i));
-                       i.context = lws_get_context(wsi);
-                       i.address = "libwebsockets.org";
-                       i.port = 443;
-                       i.ssl_connection = 1;
-                       if (p[10])
-                               i.path = (char *)in + 10;
-                       else
-                               i.path = rootpath;
-                       i.host = i.address;
-                       i.origin = NULL;
-                       i.method = "GET";
-                       i.parent_wsi = wsi;
-                       i.uri_replace_from = "libwebsockets.org/git/";
-                       i.uri_replace_to = "/proxytest/";
-
-                       if (!lws_client_connect_via_info(&i)) {
-                               lwsl_err("proxy connect fail\n");
-                               break;
-                       }
-
-                       break;
-               }
-#endif
-
-#if 1
-               /* this example server has no concept of directories */
-               if (strchr((const char *)in + 1, '/')) {
-                       lws_return_http_status(wsi, HTTP_STATUS_NOT_ACCEPTABLE, NULL);
-                       goto try_to_reuse;
-               }
-#endif
-
-               /* if a legal POST URL, let it continue and accept data */
-               if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
-                       return 0;
-
-               /* check for the "send a big file by hand" example case */
-
-               if (!strcmp((const char *)in, "/leaf.jpg")) {
-                       lws_fop_flags_t flags = LWS_O_RDONLY;
-
-                       if (strlen(resource_path) > sizeof(leaf_path) - 10)
-                               return -1;
-                       sprintf(leaf_path, "%s/leaf.jpg", resource_path);
-
-                       /* well, let's demonstrate how to send the hard way */
-
-                       p = buffer + LWS_PRE;
-                       end = p + sizeof(buffer) - LWS_PRE;
-
-                       pss->fop_fd = lws_vfs_file_open(
-                                       lws_get_fops(lws_get_context(wsi)),
-                                       leaf_path, &flags);
-                       if (!pss->fop_fd) {
-                               lwsl_err("failed to open file %s\n", leaf_path);
-                               return -1;
-                       }
-                       file_len = lws_vfs_get_length(pss->fop_fd);
-
-                       /*
-                        * we will send a big jpeg file, but it could be
-                        * anything.  Set the Content-Type: appropriately
-                        * so the browser knows what to do with it.
-                        *
-                        * Notice we use the APIs to build the header, which
-                        * will do the right thing for HTTP 1/1.1 and HTTP2
-                        * depending on what connection it happens to be working
-                        * on
-                        */
-                       if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
-                               return 1;
-                       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
-                                       (unsigned char *)"libwebsockets",
-                                       13, &p, end))
-                               return 1;
-                       if (lws_add_http_header_by_token(wsi,
-                                       WSI_TOKEN_HTTP_CONTENT_TYPE,
-                                       (unsigned char *)"image/jpeg",
-                                       10, &p, end))
-                               return 1;
-                       if (lws_add_http_header_content_length(wsi,
-                                                              file_len, &p,
-                                                              end))
-                               return 1;
-                       if (lws_finalize_http_header(wsi, &p, end))
-                               return 1;
-
-                       /*
-                        * send the http headers...
-                        * this won't block since it's the first payload sent
-                        * on the connection since it was established
-                        * (too small for partial)
-                        *
-                        * Notice they are sent using LWS_WRITE_HTTP_HEADERS
-                        * which also means you can't send body too in one step,
-                        * this is mandated by changes in HTTP2
-                        */
-
-                       *p = '\0';
-                       lwsl_info("%s\n", buffer + LWS_PRE);
-
-                       n = lws_write(wsi, buffer + LWS_PRE,
-                                     p - (buffer + LWS_PRE),
-                                     LWS_WRITE_HTTP_HEADERS);
-                       if (n < 0) {
-                               lws_vfs_file_close(&pss->fop_fd);
-                               return -1;
-                       }
-                       /*
-                        * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
-                        */
-                       lws_callback_on_writable(wsi);
-                       break;
-               }
-
-               /* if not, send a file the easy way */
-               if (!strncmp(in, "/cgit-data/", 11)) {
-                       in = (char *)in + 11;
-                       strcpy(buf, "/usr/share/cgit");
-               } else
-                       strcpy(buf, resource_path);
-
-               if (strcmp(in, "/")) {
-                       if (*((const char *)in) != '/')
-                               strcat(buf, "/");
-                       strncat(buf, in, sizeof(buf) - strlen(buf) - 1);
-               } else /* default file to serve */
-                       strcat(buf, "/test.html");
-               buf[sizeof(buf) - 1] = '\0';
-
-               /* refuse to serve files we don't understand */
-               mimetype = get_mimetype(buf);
-               if (!mimetype) {
-                       lwsl_err("Unknown mimetype for %s\n", buf);
-                       lws_return_http_status(wsi,
-                                     HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, "Unknown Mimetype");
-                       return -1;
-               }
-
-               /* demonstrates how to set a cookie on / */
-
-               other_headers = leaf_path;
-               p = (unsigned char *)leaf_path;
-               if (!strcmp((const char *)in, "/") &&
-                          !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
-                       /* this isn't very unguessable but it'll do for us */
-                       gettimeofday(&tv, NULL);
-                       n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000",
-                               (unsigned int)tv.tv_sec,
-                               (unsigned int)tv.tv_usec);
-
-                       if (lws_add_http_header_by_name(wsi,
-                               (unsigned char *)"set-cookie:",
-                               (unsigned char *)b64, n, &p,
-                               (unsigned char *)leaf_path + sizeof(leaf_path)))
-                               return 1;
-               }
-               if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi,
-                                               (unsigned char *)
-                                               "Strict-Transport-Security:",
-                                               (unsigned char *)
-                                               "max-age=15768000 ; "
-                                               "includeSubDomains", 36, &p,
-                                               (unsigned char *)leaf_path +
-                                                       sizeof(leaf_path)))
-                       return 1;
-               n = (char *)p - leaf_path;
-
-               n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n);
-               if (n < 0)
-                       return -1; /* error*/
-
-               /*
-                * notice that the sending of the file completes asynchronously,
-                * 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.
-                */
-               break;
-
-        case LWS_CALLBACK_CLIENT_RECEIVE:
-                ((char *)in)[len] = '\0';
-                lwsl_info("rx %d '%s'\n", (int)len, (char *)in);
-                break;
-
-       case LWS_CALLBACK_HTTP_BODY:
-               /* create the POST argument parser if not already existing */
-               if (!pss->spa) {
-                       pss->spa = lws_spa_create(wsi, param_names,
-                                       ARRAY_SIZE(param_names), 1024,
-                                       file_upload_cb, pss);
-                       if (!pss->spa)
-                               return -1;
-
-                       pss->filename[0] = '\0';
-                       pss->file_length = 0;
-               }
-
-               /* let it parse the POST data */
-               if (lws_spa_process(pss->spa, in, len))
-                       return -1;
-               break;
-
-       case LWS_CALLBACK_HTTP_BODY_COMPLETION:
-               lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
-               /*
-                * the whole of the sent body arrived,
-                * respond to the client with a redirect to show the
-                * results
-                */
-
-               /* call to inform no more payload data coming */
-               lws_spa_finalize(pss->spa);
-
-               p = (unsigned char *)pss->result + LWS_PRE;
-               end = p + sizeof(pss->result) - LWS_PRE - 1;
-               p += sprintf((char *)p,
-                       "<html><body><h1>Form results (after urldecoding)</h1>"
-                       "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>");
-
-               for (n = 0; n < ARRAY_SIZE(param_names); n++)
-                       p += lws_snprintf((char *)p, end - p,
-                                   "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>",
-                                   param_names[n],
-                                   lws_spa_get_length(pss->spa, n),
-                                   lws_spa_get_string(pss->spa, n));
-
-               p += lws_snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld",
-                               pss->filename, pss->file_length);
-
-               p += lws_snprintf((char *)p, end - p, "</body></html>");
-               pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE);
-
-               p = buffer + LWS_PRE;
-               start = p;
-               end = p + sizeof(buffer) - LWS_PRE;
-
-               if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
-                       return 1;
-
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
-                               (unsigned char *)"text/html", 9, &p, end))
-                       return 1;
-               if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
-                       return 1;
-               if (lws_finalize_http_header(wsi, &p, end))
-                       return 1;
-
-               n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
-               if (n < 0)
-                       return 1;
-
-               n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
-                             pss->result_len, LWS_WRITE_HTTP);
-               if (n < 0)
-                       return 1;
-               goto try_to_reuse;
-       case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
-               lwsl_debug("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n");
-
-               /* called when our wsi user_space is going to be destroyed */
-               if (pss->spa) {
-                       lws_spa_destroy(pss->spa);
-                       pss->spa = NULL;
-               }
-               break;
-       case LWS_CALLBACK_HTTP_FILE_COMPLETION:
-               goto try_to_reuse;
-
-       case LWS_CALLBACK_HTTP_WRITEABLE:
-               lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n");
-
-               if (pss->client_finished)
-                       return -1;
-
-               if (!lws_get_child(wsi) && !pss->fop_fd) {
-                       lwsl_notice("fop_fd NULL\n");
-                       goto try_to_reuse;
-               }
-
-#ifndef LWS_NO_CLIENT
-               if (pss->reason_bf & 2) {
-                       char *px = buf + LWS_PRE;
-                       int lenx = sizeof(buf) - LWS_PRE;
-                       /*
-                        * our sink is writeable and our source has something
-                        * to read.  So read a lump of source material of
-                        * suitable size to send or what's available, whichever
-                        * is the smaller.
-                        */
-
-
-                       pss->reason_bf &= ~2;
-                       wsi1 = lws_get_child(wsi);
-                       if (!wsi1)
-                               break;
-                       if (lws_http_client_read(wsi1, &px, &lenx) < 0)
-                               return -1;
-
-                       if (pss->client_finished)
-                               return -1;
-
-                       break;
-               }
-
-               if (lws_get_child(wsi))
-                       break;
-
-#endif
-               /*
-                * we can send more of whatever it is we were sending
-                */
-               sent = 0;
-               do {
-                       /* we'd like the send this much */
-                       n = sizeof(buffer) - LWS_PRE;
-
-                       /* but if the peer told us he wants less, we can adapt */
-                       m = lws_get_peer_write_allowance(wsi);
-
-                       /* -1 means not using a protocol that has this info */
-                       if (m == 0)
-                               /* right now, peer can't handle anything */
-                               goto later;
-
-                       if (m != -1 && m < n)
-                               /* he couldn't handle that much */
-                               n = m;
-
-                       n = lws_vfs_file_read(pss->fop_fd,
-                                              &amount, buffer + LWS_PRE, n);
-                       /* problem reading, close conn */
-                       if (n < 0) {
-                               lwsl_err("problem reading file\n");
-                               goto bail;
-                       }
-                       n = (int)amount;
-                       /* sent it all, close conn */
-                       if (n == 0)
-                               goto penultimate;
-                       /*
-                        * To support HTTP2, must take care about preamble space
-                        *
-                        * identification of when we send the last payload frame
-                        * is handled by the library itself if you sent a
-                        * content-length header
-                        */
-                       m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP);
-                       if (m < 0) {
-                               lwsl_err("write failed\n");
-                               /* write failed, close conn */
-                               goto bail;
-                       }
-                       if (m) /* while still active, extend timeout */
-                               lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5);
-                       sent += m;
-
-               } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024));
-later:
-               lws_callback_on_writable(wsi);
-               break;
-penultimate:
-               lws_vfs_file_close(&pss->fop_fd);
-               pss->fop_fd = NULL;
-               goto try_to_reuse;
-
-bail:
-               lws_vfs_file_close(&pss->fop_fd);
-
-               return -1;
-
-       /*
-        * callback for confirming to continue with client IP appear in
-        * protocol 0 callback since no websocket protocol has been agreed
-        * yet.  You can just ignore this if you won't filter on client IP
-        * since the default unhandled callback return is 0 meaning let the
-        * connection continue.
-        */
-       case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
-               /* if we returned non-zero from here, we kill the connection */
-               break;
-
-#ifndef LWS_NO_CLIENT
-       case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
-               char ctype[64], ctlen = 0;
-               lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n");
-               p = buffer + LWS_PRE;
-               end = p + sizeof(buffer) - LWS_PRE;
-               if (lws_add_http_header_status(lws_get_parent(wsi), HTTP_STATUS_OK, &p, end))
-                       return 1;
-               if (lws_add_http_header_by_token(lws_get_parent(wsi),
-                               WSI_TOKEN_HTTP_SERVER,
-                               (unsigned char *)"libwebsockets",
-                               13, &p, end))
-                       return 1;
-
-               ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE);
-               if (ctlen > 0) {
-                       if (lws_add_http_header_by_token(lws_get_parent(wsi),
-                               WSI_TOKEN_HTTP_CONTENT_TYPE,
-                               (unsigned char *)ctype, ctlen, &p, end))
-                               return 1;
-               }
-#if 0
-               if (lws_add_http_header_content_length(lws_get_parent(wsi),
-                                                      file_len, &p, end))
-                       return 1;
-#endif
-               if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
-                       return 1;
-
-               *p = '\0';
-               lwsl_info("%s\n", buffer + LWS_PRE);
-
-               n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE,
-                             p - (buffer + LWS_PRE),
-                             LWS_WRITE_HTTP_HEADERS);
-               if (n < 0)
-                       return -1;
-
-               break; }
-       case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
-               //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
-               return -1;
-               break;
-       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
-               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi);
-               assert(lws_get_parent(wsi));
-               if (!lws_get_parent(wsi))
-                       break;
-               pss1 = lws_wsi_user(lws_get_parent(wsi));
-               pss1->reason_bf |= 2;
-               lws_callback_on_writable(lws_get_parent(wsi));
-               break;
-       case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
-               //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", (int)len);
-               assert(lws_get_parent(wsi));
-               m = lws_write(lws_get_parent(wsi), (unsigned char *)in,
-                               len, LWS_WRITE_HTTP);
-               if (m < 0)
-                       return -1;
-               break;
-       case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
-               //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
-               assert(lws_get_parent(wsi));
-               if (!lws_get_parent(wsi))
-                       break;
-               pss1 = lws_wsi_user(lws_get_parent(wsi));
-               pss1->client_finished = 1;
-               break;
-#endif
-
-       /*
-        * callbacks for managing the external poll() array appear in
-        * protocol 0 callback
-        */
-
-       case LWS_CALLBACK_LOCK_POLL:
-               /*
-                * lock mutex to protect pollfd state
-                * called before any other POLL related callback
-                * if protecting wsi lifecycle change, len == 1
-                */
-               test_server_lock(len);
-               break;
-
-       case LWS_CALLBACK_UNLOCK_POLL:
-               /*
-                * unlock mutex to protect pollfd state when
-                * called after any other POLL related callback
-                * if protecting wsi lifecycle change, len == 1
-                */
-               test_server_unlock(len);
-               break;
-
-#ifdef EXTERNAL_POLL
-       case LWS_CALLBACK_ADD_POLL_FD:
-
-               if (count_pollfds >= max_poll_elements) {
-                       lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
-                       return 1;
-               }
-
-               fd_lookup[pa->fd] = count_pollfds;
-               pollfds[count_pollfds].fd = pa->fd;
-               pollfds[count_pollfds].events = pa->events;
-               pollfds[count_pollfds++].revents = 0;
-               break;
-
-       case LWS_CALLBACK_DEL_POLL_FD:
-               if (!--count_pollfds)
-                       break;
-               m = fd_lookup[pa->fd];
-               /* have the last guy take up the vacant slot */
-               pollfds[m] = pollfds[count_pollfds];
-               fd_lookup[pollfds[count_pollfds].fd] = m;
-               break;
-
-       case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
-               pollfds[fd_lookup[pa->fd]].events = pa->events;
-               break;
-#endif
-
-       case LWS_CALLBACK_GET_THREAD_ID:
-               /*
-                * if you will call "lws_callback_on_writable"
-                * from a different thread, return the caller thread ID
-                * here so lws can use this information to work out if it
-                * should signal the poll() loop to exit and restart early
-                */
-
-               /* return pthread_getthreadid_np(); */
-
-               break;
-
-#if defined(LWS_OPENSSL_SUPPORT)
-       case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION:
-               /* Verify the client certificate */
-               if (!len || (SSL_get_verify_result((SSL*)in) != X509_V_OK)) {
-                       int err = X509_STORE_CTX_get_error((X509_STORE_CTX*)user);
-                       int depth = X509_STORE_CTX_get_error_depth((X509_STORE_CTX*)user);
-                       const char* msg = X509_verify_cert_error_string(err);
-                       lwsl_err("LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: SSL error: %s (%d), depth: %d\n", msg, err, depth);
-                       return 1;
-               }
-               break;
-#if defined(LWS_HAVE_SSL_CTX_set1_param)
-       case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
-               if (crl_path[0]) {
-                       /* Enable CRL checking */
-                       X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
-                       X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
-                       SSL_CTX_set1_param((SSL_CTX*)user, param);
-                       X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user);
-                       X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
-                       n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM);
-                       X509_VERIFY_PARAM_free(param);
-                       if (n != 1) {
-                               char errbuf[256];
-                               n = ERR_get_error();
-                               lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n);
-                               return 1;
-                       }
-               }
-               break;
-#endif
-#endif
-
-       default:
-               break;
-       }
-
-       return 0;
-
-       /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */
-try_to_reuse:
-       if (lws_http_transaction_completed(wsi))
-               return -1;
-
-       return 0;
-}
diff --git a/test-server/test-server-libev.c b/test-server/test-server-libev.c
deleted file mode 100644 (file)
index fab64a4..0000000
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * libwebsockets-test-server - libwebsockets test implementation
- *
- * Copyright (C) 2011-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-#include "test-server.h"
-
-int close_testing;
-int max_poll_elements;
-int debug_level = 7;
-volatile int force_exit = 0;
-struct lws_context *context;
-struct lws_plat_file_ops fops_plat;
-
-/* http server gets files from this path */
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-char *resource_path = LOCAL_RESOURCE_PATH;
-
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-char crl_path[1024] = "";
-#endif
-
-#define LWS_PLUGIN_STATIC
-#include "../plugins/protocol_lws_mirror.c"
-#include "../plugins/protocol_lws_status.c"
-
-/* singlethreaded version --> no locks */
-
-void test_server_lock(int care)
-{
-}
-void test_server_unlock(int care)
-{
-}
-
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *                             ascii string is sent down it every 50ms.
- *                             If you send "reset\n" on the websocket, then
- *                             the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *                             using this protocol, including the sender
- */
-
-enum demo_protocols {
-       /* always first */
-       PROTOCOL_HTTP = 0,
-
-       PROTOCOL_DUMB_INCREMENT,
-       PROTOCOL_LWS_MIRROR,
-       PROTOCOL_LWS_STATUS,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-       /* first protocol must always be HTTP handler */
-
-       {
-               "http-only",            /* name */
-               callback_http,          /* callback */
-               sizeof (struct per_session_data__http), /* per_session_data_size */
-               0,                      /* max frame size / rx buffer */
-       },
-       {
-               "dumb-increment-protocol",
-               callback_dumb_increment,
-               sizeof(struct per_session_data__dumb_increment),
-               10, /* rx buf size must be >= permessage-deflate rx size
-                    * dumb-increment only sends very small packets, so we set
-                    * this accordingly.  If your protocol will send bigger
-                    * things, adjust this to match */
-       },
-       LWS_PLUGIN_PROTOCOL_MIRROR,
-       LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-       { NULL, NULL, 0, 0 } /* terminator */
-};
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-       },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-/* this shows how to override the lws file operations.  You don't need
- * to do any of this unless you have a reason (eg, want to serve
- * compressed files without decompressing the whole archive)
- */
-static lws_fop_fd_t
-test_server_fops_open(const struct lws_plat_file_ops *fops,
-                     const char *vfs_path, const char *vpath,
-                     lws_fop_flags_t *flags)
-{
-       lws_fop_fd_t n;
-
-       /* call through to original platform implementation */
-       n = fops_plat.open(fops, vfs_path, vpath, flags);
-
-       lwsl_notice("%s: opening %s, ret %p\n", __func__, vfs_path, n);
-
-       return n;
-}
-
-void signal_cb(struct ev_loop *loop, struct ev_signal* watcher, int revents)
-{
-       lwsl_notice("Signal caught, exiting...\n");
-       force_exit = 1;
-       switch (watcher->signum) {
-       case SIGTERM:
-       case SIGINT:
-               ev_break(loop, EVBREAK_ALL);
-               break;
-       default:
-               signal(SIGABRT, SIG_DFL);
-               abort();
-               break;
-       }
-}
-
-static void
-ev_timeout_cb (EV_P_ ev_timer *w, int revents)
-{
-       lws_callback_on_writable_all_protocol(context,
-                                       &protocols[PROTOCOL_DUMB_INCREMENT]);
-}
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 's' },
-       { "allow-non-ssl",      no_argument,    NULL, 'a' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "closetest",  no_argument,            NULL, 'c' },
-       { "libev",  no_argument,                NULL, 'e' },
-#ifndef LWS_NO_DAEMONIZE
-       { "daemonize",  no_argument,            NULL, 'D' },
-#endif
-       { "resource_path", required_argument,   NULL, 'r' },
-       { NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-       int sigs[] = { SIGINT, SIGKILL, SIGTERM, SIGSEGV, SIGFPE };
-       struct ev_signal signals[ARRAY_SIZE(sigs)];
-       struct ev_loop *loop = ev_default_loop(0);
-       struct lws_context_creation_info info;
-       char interface_name[128] = "";
-       const char *iface = NULL;
-       ev_timer timeout_watcher;
-       char cert_path[1024];
-       char key_path[1024];
-       int use_ssl = 0;
-       int opts = 0;
-       int n = 0;
-#ifndef _WIN32
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-
-       /*
-        * take care to zero down the info struct, he contains random garbaage
-        * from the stack otherwise
-        */
-       memset(&info, 0, sizeof info);
-       info.port = 7681;
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "eci:hsap:d:Dr:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'e':
-                       opts |= LWS_SERVER_OPTION_LIBEV;
-                       break;
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-                       #ifndef _WIN32
-                       syslog_options &= ~LOG_PERROR;
-                       #endif
-                       break;
-#endif
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 's':
-                       use_ssl = 1;
-                       break;
-               case 'a':
-                       opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
-                       break;
-               case 'p':
-                       info.port = atoi(optarg);
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       iface = interface_name;
-                       break;
-               case 'c':
-                       close_testing = 1;
-                       fprintf(stderr, " Close testing mode -- closes on "
-                                          "client after 50 dumb increments"
-                                          "and suppresses lws_mirror spam\n");
-                       break;
-               case 'r':
-                       resource_path = optarg;
-                       printf("Setting resource path to \"%s\"\n", resource_path);
-                       break;
-               case 'h':
-                       fprintf(stderr, "Usage: test-server "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
-                       exit(1);
-               }
-       }
-
-#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 1;
-       }
-#endif
-
-       for (n = 0; n < ARRAY_SIZE(sigs); n++) {
-               ev_init(&signals[n], signal_cb);
-               ev_signal_set(&signals[n], sigs[n]);
-               ev_signal_start(loop, &signals[n]);
-       }
-
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-       lwsl_notice("libwebsockets test server libev - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-       printf("Using resource path \"%s\"\n", resource_path);
-
-       info.iface = iface;
-       info.protocols = protocols;
-       info.extensions = exts;
-
-       info.ssl_cert_filepath = NULL;
-       info.ssl_private_key_filepath = NULL;
-
-       if (use_ssl) {
-               if (strlen(resource_path) > sizeof(cert_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(cert_path, "%s/libwebsockets-test-server.pem",
-                                                               resource_path);
-               if (strlen(resource_path) > sizeof(key_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
-                                                               resource_path);
-
-               info.ssl_cert_filepath = cert_path;
-               info.ssl_private_key_filepath = key_path;
-
-               opts |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-       }
-       info.gid = -1;
-       info.uid = -1;
-       info.max_http_header_pool = 1;
-       info.options = opts | LWS_SERVER_OPTION_LIBEV;
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               lwsl_err("libwebsocket init failed\n");
-               return -1;
-       }
-
-       /*
-        * this shows how to override the lws file operations.  You don't need
-        * to do any of this unless you have a reason (eg, want to serve
-        * compressed files without decompressing the whole archive)
-        */
-       /* stash original platform fops */
-       fops_plat = *(lws_get_fops(context));
-       /* override the active fops */
-       lws_get_fops(context)->open = test_server_fops_open;
-
-       lws_ev_initloop(context, loop, 0);
-
-       ev_timer_init(&timeout_watcher, ev_timeout_cb, 0.05, 0.05);
-       ev_timer_start(loop, &timeout_watcher);
-       ev_run(loop, 0);
-
-       lws_context_destroy(context);
-       lwsl_notice("libwebsockets-test-server exited cleanly\n");
-
-#ifndef _WIN32
-       closelog();
-#endif
-
-       return 0;
-}
diff --git a/test-server/test-server-libevent.c b/test-server/test-server-libevent.c
deleted file mode 100644 (file)
index a18576b..0000000
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * libwebsockets-test-server - libwebsockets test implementation
- *
- * Copyright (C) 2011-2017 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-#include "test-server.h"
-
-int close_testing;
-int max_poll_elements;
-int debug_level = 7;
-volatile int force_exit = 0;
-struct lws_context *context;
-struct lws_plat_file_ops fops_plat;
-
-/* http server gets files from this path */
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-char *resource_path = LOCAL_RESOURCE_PATH;
-
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-char crl_path[1024] = "";
-#endif
-
-#define LWS_PLUGIN_STATIC
-#include "../plugins/protocol_lws_mirror.c"
-#include "../plugins/protocol_lws_status.c"
-#include "../plugins/protocol_lws_meta.c"
-
-/* singlethreaded version --> no locks */
-
-void test_server_lock(int care)
-{
-}
-void test_server_unlock(int care)
-{
-}
-
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *        ascii string is sent down it every 50ms.
- *        If you send "reset\n" on the websocket, then
- *        the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *        using this protocol, including the sender
- */
-
-enum demo_protocols {
-       /* always first */
-       PROTOCOL_HTTP = 0,
-
-       PROTOCOL_DUMB_INCREMENT,
-       PROTOCOL_LWS_MIRROR,
-       PROTOCOL_LWS_META,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-               /* first protocol must always be HTTP handler */
-
-               {
-                               "http-only",    /* name */
-                               callback_http,    /* callback */
-                               sizeof (struct per_session_data__http),  /* per_session_data_size */
-                               0,      /* max frame size / rx buffer */
-               },
-               {
-                               "dumb-increment-protocol",
-                               callback_dumb_increment,
-                               sizeof(struct per_session_data__dumb_increment),
-                               10,
-               },
-
-               LWS_PLUGIN_PROTOCOL_MIRROR,
-               LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-               LWS_PLUGIN_PROTOCOL_LWS_META,
-               { NULL, NULL, 0, 0 } /* terminator */
-};
-
-static const struct lws_extension exts[] = {
-               {
-                               "permessage-deflate",
-                               lws_extension_callback_pm_deflate,
-                               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-               },
-               {
-                               "deflate-frame",
-                               lws_extension_callback_pm_deflate,
-                               "deflate_frame"
-               },
-               { NULL, NULL, NULL /* terminator */ }
-};
-
-/* this shows how to override the lws file operations.  You don't need
- * to do any of this unless you have a reason (eg, want to serve
- * compressed files without decompressing the whole archive)
- */
-static lws_fop_fd_t
-test_server_fops_open(const struct lws_plat_file_ops *fops,
-               const char *vfs_path, const char *vpath,
-               lws_fop_flags_t *flags)
-{
-       lws_fop_fd_t n;
-
-       /* call through to original platform implementation */
-       n = fops_plat.open(fops, vfs_path, vpath, flags);
-
-       lwsl_notice("%s: opening %s, ret %p\n", __func__, vfs_path, n);
-
-       return n;
-}
-
-void signal_cb(evutil_socket_t sock_fd, short events, void *ctx)
-{
-       struct event_base *event_base_loop = ctx;
-
-       lwsl_notice("Signal caught, exiting...\n");
-       force_exit = 1;
-       if (events & EV_SIGNAL)
-               event_base_loopbreak(event_base_loop);
-}
-
-static void
-ev_timeout_cb (evutil_socket_t sock_fd, short events, void *ctx)
-{
-       lws_callback_on_writable_all_protocol(context,
-                       &protocols[PROTOCOL_DUMB_INCREMENT]);
-}
-
-static struct option options[] = {
-               { "help",  no_argument,    NULL, 'h' },
-               { "debug",  required_argument,  NULL, 'd' },
-               { "port",  required_argument,  NULL, 'p' },
-               { "ssl",  no_argument,    NULL, 's' },
-               { "allow-non-ssl",  no_argument,  NULL, 'a' },
-               { "interface",  required_argument,  NULL, 'i' },
-               { "closetest",  no_argument,    NULL, 'c' },
-               { "libevent",  no_argument,    NULL, 'e' },
-#ifndef LWS_NO_DAEMONIZE
-               { "daemonize",   no_argument,    NULL, 'D' },
-#endif
-               { "resource_path", required_argument,  NULL, 'r' },
-               { NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-       int sigs[] = { SIGINT, SIGKILL, SIGTERM, SIGSEGV, SIGFPE };
-       struct event *signals[ARRAY_SIZE(sigs)];
-       struct event_base *event_base_loop = event_base_new();
-       struct lws_context_creation_info info;
-       char interface_name[128] = "";
-       const char *iface = NULL;
-       struct event *timeout_watcher;
-       char cert_path[1024];
-       char key_path[1024];
-       int use_ssl = 0;
-       int opts = 0;
-       int n = 0;
-#ifndef _WIN32
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-
-       /*
-        * take care to zero down the info struct, he contains random garbaage
-        * from the stack otherwise
-        */
-       memset(&info, 0, sizeof info);
-       info.port = 7681;
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "eci:hsap:d:Dr:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'e':
-                       opts |= LWS_SERVER_OPTION_LIBEVENT;
-                       break;
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-#ifndef _WIN32
-                       syslog_options &= ~LOG_PERROR;
-#endif
-                       break;
-#endif
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 's':
-                       use_ssl = 1;
-                       break;
-               case 'a':
-                       opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
-                       break;
-               case 'p':
-                       info.port = atoi(optarg);
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       iface = interface_name;
-                       break;
-               case 'c':
-                       close_testing = 1;
-                       fprintf(stderr, " Close testing mode -- closes on "
-                                       "client after 50 dumb increments"
-                                       "and suppresses lws_mirror spam\n");
-                       break;
-               case 'r':
-                       resource_path = optarg;
-                       printf("Setting resource path to \"%s\"\n", resource_path);
-                       break;
-               case 'h':
-                       fprintf(stderr, "Usage: test-server "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
-                       exit(1);
-               }
-       }
-
-#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 1;
-       }
-#endif
-
-       for (n = 0; n < ARRAY_SIZE(sigs); n++) {
-               signals[n] = evsignal_new(event_base_loop, sigs[n], signal_cb, event_base_loop);
-
-               evsignal_add(signals[n], NULL);
-       }
-
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-       lwsl_notice("libwebsockets test server libevent - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-       printf("Using resource path \"%s\"\n", resource_path);
-
-       info.iface = iface;
-       info.protocols = protocols;
-       info.extensions = exts;
-
-       info.ssl_cert_filepath = NULL;
-       info.ssl_private_key_filepath = NULL;
-
-       if (use_ssl) {
-               if (strlen(resource_path) > sizeof(cert_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(cert_path, "%s/libwebsockets-test-server.pem",
-                               resource_path);
-               if (strlen(resource_path) > sizeof(key_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
-                               resource_path);
-
-               info.ssl_cert_filepath = cert_path;
-               info.ssl_private_key_filepath = key_path;
-
-               opts |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-       }
-       info.gid = -1;
-       info.uid = -1;
-       info.max_http_header_pool = 1;
-       info.options = opts | LWS_SERVER_OPTION_LIBEVENT;
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               lwsl_err("libwebsocket init failed\n");
-               return -1;
-       }
-
-       /*
-        * this shows how to override the lws file operations.  You don't need
-        * to do any of this unless you have a reason (eg, want to serve
-        * compressed files without decompressing the whole archive)
-        */
-       /* stash original platform fops */
-       fops_plat = *(lws_get_fops(context));
-       /* override the active fops */
-       lws_get_fops(context)->open = test_server_fops_open;
-
-       // Don't use the default Signal Event Watcher & Handler
-       lws_event_sigint_cfg(context, 0, NULL);
-       // Initialize the LWS with libevent loop
-       lws_event_initloop(context, event_base_loop, 0);
-
-       timeout_watcher = event_new(event_base_loop, -1, EV_PERSIST, ev_timeout_cb, NULL);
-       struct timeval tv = {0, 50000};
-       evtimer_add(timeout_watcher, &tv);
-       event_base_dispatch(event_base_loop);
-
-       lws_context_destroy(context);
-       lwsl_notice("libwebsockets-test-server exited cleanly\n");
-
-#ifndef _WIN32
-       closelog();
-#endif
-
-       return 0;
-}
diff --git a/test-server/test-server-libuv.c b/test-server/test-server-libuv.c
deleted file mode 100644 (file)
index c8b305f..0000000
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * libwebsockets-test-server for libev - libwebsockets test implementation
- *
- * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser General Public
- *  License as published by the Free Software Foundation:
- *  version 2.1 of the License.
- *
- *  This library is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public
- *  License along with this library; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- *  MA  02110-1301  USA
- */
-
-#define DI_HANDLED_BY_PLUGIN
-#include "test-server.h"
-#include <uv.h>
-
-int close_testing;
-int max_poll_elements;
-int debug_level = 7;
-struct lws_context *context;
-struct lws_plat_file_ops fops_plat;
-
-/* http server gets files from this path */
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-char *resource_path = LOCAL_RESOURCE_PATH;
-
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-char crl_path[1024] = "";
-#endif
-
-/* singlethreaded version --> no locks */
-
-void test_server_lock(int care)
-{
-}
-void test_server_unlock(int care)
-{
-}
-
-#define LWS_PLUGIN_STATIC
-#include "../plugins/protocol_dumb_increment.c"
-#include "../plugins/protocol_lws_mirror.c"
-#include "../plugins/protocol_lws_status.c"
-#include "../plugins/protocol_lws_meta.c"
-
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *                             ascii string is sent down it every 50ms.
- *                             If you send "reset\n" on the websocket, then
- *                             the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *                             using this protocol, including the sender
- */
-
-enum demo_protocols {
-       /* always first */
-       PROTOCOL_HTTP = 0,
-
-       PROTOCOL_DUMB_INCREMENT,
-       PROTOCOL_LWS_MIRROR,
-       PROTOCOL_LWS_STATUS,
-       PROTOCOL_LWS_META,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-       /* first protocol must always be HTTP handler */
-
-       {
-               "http-only",            /* name */
-               callback_http,          /* callback */
-               sizeof (struct per_session_data__http), /* per_session_data_size */
-               0,                      /* max frame size / rx buffer */
-       },
-       LWS_PLUGIN_PROTOCOL_DUMB_INCREMENT,
-       LWS_PLUGIN_PROTOCOL_MIRROR,
-       LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-       LWS_PLUGIN_PROTOCOL_LWS_META,
-       { NULL, NULL, 0, 0 } /* terminator */
-};
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-       },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-void signal_cb(uv_signal_t *watcher, int signum)
-{
-       lwsl_err("Signal %d caught, exiting...\n", watcher->signum);
-       switch (watcher->signum) {
-       case SIGTERM:
-       case SIGINT:
-               break;
-       default:
-               signal(SIGABRT, SIG_DFL);
-               abort();
-               break;
-       }
-       lws_libuv_stop(context);
-}
-
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 's' },
-       { "allow-non-ssl",      no_argument,    NULL, 'a' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "closetest",  no_argument,            NULL, 'c' },
-       { "libev",  no_argument,                NULL, 'e' },
-       { "foreign",  no_argument,              NULL, 'f' },
-#ifndef LWS_NO_DAEMONIZE
-       { "daemonize",  no_argument,            NULL, 'D' },
-#endif
-       { "resource_path", required_argument,   NULL, 'r' },
-       { NULL, 0, 0, 0 }
-};
-
-#if UV_VERSION_MAJOR > 0
-/* ----- this code is only needed for foreign / external libuv tests -----*/
-struct counter
-{
-       int cur, lim;
-       int stop_loop;
-};
-
-static void timer_cb(uv_timer_t *t)
-{
-       struct counter *c = t->data;
-
-       lwsl_notice("  timer %p cb, count %d, loop has %d handles\n",
-                   t, c->cur, t->loop->active_handles);
-
-       if (c->cur++ == c->lim) {
-               lwsl_debug("stop loop from timer\n");
-               uv_timer_stop(t);
-               if (c->stop_loop)
-                       uv_stop(t->loop);
-       }
-}
-
-static void timer_close_cb(uv_handle_t *h)
-{
-       lwsl_notice("timer close cb %p, loop has %d handles\n",
-                   h, h->loop->active_handles);
-}
-
-void outer_signal_cb(uv_signal_t *s, int signum)
-{
-       lwsl_notice("Foreign loop got signal %d\n", signum);
-       uv_signal_stop(s);
-       uv_stop(s->loop);
-}
-
-static void lws_uv_close_cb(uv_handle_t *handle)
-{
-       //lwsl_err("%s\n", __func__);
-}
-
-static void lws_uv_walk_cb(uv_handle_t *handle, void *arg)
-{
-       uv_close(handle, lws_uv_close_cb);
-}
-
-/* --- end of foreign test code ---- */
-#endif
-
-int main(int argc, char **argv)
-{
-       struct lws_context_creation_info info;
-       char interface_name[128] = "";
-#if UV_VERSION_MAJOR > 0
-/* --- only needed for foreign loop test ---> */
-       uv_loop_t loop;
-       uv_signal_t signal_outer;
-       uv_timer_t timer_outer;
-       struct counter ctr;
-       int foreign_libuv_loop = 0;
-/* <--- only needed for foreign loop test --- */
-#endif
-       const char *iface = NULL;
-       char cert_path[1024];
-       char key_path[1024];
-       int use_ssl = 0;
-       int opts = 0;
-       int n = 0;
-#ifndef _WIN32
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-
-       /*
-        * take care to zero down the info struct, he contains random garbaage
-        * from the stack otherwise
-        */
-       memset(&info, 0, sizeof info);
-       info.port = 7681;
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "feci:hsap:d:Dr:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'f':
-#if UV_VERSION_MAJOR > 0
-                       foreign_libuv_loop = 1;
-#endif
-                       break;
-               case 'e':
-                       opts |= LWS_SERVER_OPTION_LIBEV;
-                       break;
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-                       #ifndef _WIN32
-                       syslog_options &= ~LOG_PERROR;
-                       #endif
-                       break;
-#endif
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 's':
-                       use_ssl = 1;
-                       break;
-               case 'a':
-                       opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
-                       break;
-               case 'p':
-                       info.port = atoi(optarg);
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       iface = interface_name;
-                       break;
-               case 'c':
-                       close_testing = 1;
-                       fprintf(stderr, " Close testing mode -- closes on "
-                                          "client after 50 dumb increments"
-                                          "and suppresses lws_mirror spam\n");
-                       break;
-               case 'r':
-                       resource_path = optarg;
-                       printf("Setting resource path to \"%s\"\n", resource_path);
-                       break;
-               case 'h':
-                       fprintf(stderr, "Usage: test-server "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
-                       exit(1);
-               }
-       }
-
-#if !defined(WIN32)
-#if !defined(LWS_NO_DAEMONIZE)
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 1;
-       }
-#endif
-
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-       lwsl_notice("libwebsockets test server libuv - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-       lwsl_info("Using resource path \"%s\"\n", resource_path);
-
-       info.iface = iface;
-       info.protocols = protocols;
-       info.extensions = exts;
-
-       info.ssl_cert_filepath = NULL;
-       info.ssl_private_key_filepath = NULL;
-
-       if (use_ssl) {
-               if (strlen(resource_path) > sizeof(cert_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(cert_path, "%s/libwebsockets-test-server.pem",
-                       resource_path);
-               if (strlen(resource_path) > sizeof(key_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
-                       resource_path);
-
-               info.ssl_cert_filepath = cert_path;
-               info.ssl_private_key_filepath = key_path;
-               opts |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-       }
-       info.gid = -1;
-       info.uid = -1;
-       info.max_http_header_pool = 1;
-       info.timeout_secs = 5;
-       info.options = opts | LWS_SERVER_OPTION_LIBUV;
-
-#if UV_VERSION_MAJOR > 0
-       if (foreign_libuv_loop) {
-               /* create the foreign loop */
-               uv_loop_init(&loop);
-
-               /* run some timer on that loop just so loop is not 'clean' */
-
-               uv_signal_init(&loop, &signal_outer);
-               uv_signal_start(&signal_outer, outer_signal_cb, SIGINT);
-
-               uv_timer_init(&loop, &timer_outer);
-               timer_outer.data = &ctr;
-               ctr.cur = 0;
-               ctr.lim = ctr.cur + 5;
-               ctr.stop_loop = 1;
-               uv_timer_start(&timer_outer, timer_cb, 0, 1000);
-               lwsl_notice("running loop without libwebsockets for %d s\n", ctr.lim);
-
-               uv_run(&loop, UV_RUN_DEFAULT);
-
-               /* timer will stop loop and we will get here */
-       }
-#endif
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               lwsl_err("libwebsocket init failed\n");
-               return -1;
-       }
-
-       lws_uv_sigint_cfg(context, 1, signal_cb);
-
-#if UV_VERSION_MAJOR > 0
-       if (foreign_libuv_loop)
-               /* we have our own uv loop outside of lws */
-               lws_uv_initloop(context, &loop, 0);
-       else
-#endif
-       {
-               /*
-                * lws will create his own libuv loop in the context
-                */
-               if (lws_uv_initloop(context, NULL, 0)) {
-                       lwsl_err("lws_uv_initloop failed\n");
-
-                       goto bail;
-               }
-       }
-
-#if UV_VERSION_MAJOR > 0
-       if (foreign_libuv_loop) {
-               /*
-                * prepare inner timer on loop, to run along with lws.
-                * Will exit after 5s while lws keeps running
-                */
-               struct counter ctr_inner = { 0, 3, 0 };
-               int e;
-               uv_timer_t timer_inner;
-               uv_timer_init(&loop, &timer_inner);
-               timer_inner.data = &ctr_inner;
-               uv_timer_start(&timer_inner, timer_cb, 200, 1000);
-
-               /* make this timer long-lived, should keep
-                * firing after lws exits */
-               ctr.cur = 0;
-               ctr.lim = ctr.cur + 1000;
-               uv_timer_start(&timer_outer, timer_cb, 0, 1000);
-
-               uv_run(&loop, UV_RUN_DEFAULT);
-
-               /* we are here either because signal stopped us,
-                * or outer timer expired */
-
-               /* close short timer */
-               uv_timer_stop(&timer_inner);
-               uv_close((uv_handle_t*)&timer_inner, timer_close_cb);
-
-
-               lwsl_notice("Destroying lws context\n");
-
-               /* detach lws */
-               lws_context_destroy(context);
-
-               lwsl_notice("Please wait while the outer libuv test continues for 10s\n");
-
-               ctr.lim = ctr.cur + 10;
-
-               /* try and run outer timer for 10 more seconds,
-                * (or sigint outer handler) after lws has left the loop */
-               uv_run(&loop, UV_RUN_DEFAULT);
-
-               /* Clean up the foreign loop now */
-
-               /* PHASE 1: stop and close things we created
-                *          outside of lws */
-
-               uv_timer_stop(&timer_outer);
-               uv_close((uv_handle_t*)&timer_outer, timer_close_cb);
-               uv_signal_stop(&signal_outer);
-
-               e = 100;
-               while (e--)
-                       uv_run(&loop, UV_RUN_NOWAIT);
-
-               /* PHASE 2: close anything remaining */
-
-               uv_walk(&loop, lws_uv_walk_cb, NULL);
-
-               e = 100;
-               while (e--)
-                       uv_run(&loop, UV_RUN_NOWAIT);
-
-               /* PHASE 3: close the UV loop itself */
-
-               e = uv_loop_close(&loop);
-               lwsl_notice("uv loop close rc %s\n",
-                           e ? uv_strerror(e) : "ok");
-
-               /* PHASE 4: finalize context destruction */
-
-               lws_context_destroy2(context);
-       } else
-#endif
-       {
-               lws_libuv_run(context, 0);
-
-bail:
-               lws_context_destroy(context);
-               lws_context_destroy2(context);
-       }
-
-       lwsl_notice("libwebsockets-test-server exited cleanly\n");
-
-       context = NULL;
-
-       return 0;
-}
diff --git a/test-server/test-server-pthreads.c b/test-server/test-server-pthreads.c
deleted file mode 100644 (file)
index 62cb685..0000000
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * libwebsockets-test-server - libwebsockets test implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary. So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#include "test-server.h"
-#include <pthread.h>
-
-int close_testing;
-int max_poll_elements;
-int debug_level = 7;
-
-#ifdef EXTERNAL_POLL
-struct lws_pollfd *pollfds;
-int *fd_lookup;
-int count_pollfds;
-#endif
-volatile int force_exit = 0;
-struct lws_context *context;
-
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-char crl_path[1024] = "";
-#endif
-
-#define LWS_PLUGIN_STATIC
-#include "../plugins/protocol_lws_mirror.c"
-#include "../plugins/protocol_lws_status.c"
-
-/*
- * This mutex lock protects code that changes or relies on wsi list outside of
- * the service thread. The service thread will acquire it when changing the
- * wsi list and other threads should acquire it while dereferencing wsis or
- * calling apis like lws_callback_on_writable_all_protocol() which
- * use the wsi list and wsis from a different thread context.
- */
-pthread_mutex_t lock_established_conns;
-
-/* http server gets files from this path */
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-char *resource_path = LOCAL_RESOURCE_PATH;
-
-/*
- * multithreaded version - protect wsi lifecycle changes in the library
- * these are called from protocol 0 callbacks
- */
-
-void test_server_lock(int care)
-{
-       if (care)
-               pthread_mutex_lock(&lock_established_conns);
-}
-void test_server_unlock(int care)
-{
-       if (care)
-               pthread_mutex_unlock(&lock_established_conns);
-}
-
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *                             ascii string is sent down it every 50ms.
- *                             If you send "reset\n" on the websocket, then
- *                             the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *                             using this protocol, including the sender
- */
-
-enum demo_protocols {
-       /* always first */
-       PROTOCOL_HTTP = 0,
-
-       PROTOCOL_DUMB_INCREMENT,
-       PROTOCOL_LWS_MIRROR,
-       PROTOCOL_LWS_STATUS,
-
-       /* always last */
-       DEMO_PROTOCOL_COUNT
-};
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-       /* first protocol must always be HTTP handler */
-
-       {
-               "http-only",            /* name */
-               callback_http,          /* callback */
-               sizeof (struct per_session_data__http), /* per_session_data_size */
-               0,                      /* max frame size / rx buffer */
-       },
-       {
-               "dumb-increment-protocol",
-               callback_dumb_increment,
-               sizeof(struct per_session_data__dumb_increment),
-               10, /* rx buf size must be >= permessage-deflate rx size
-                    * dumb-increment only sends very small packets, so we set
-                    * this accordingly.  If your protocol will send bigger
-                    * things, adjust this to match */
-       },
-       LWS_PLUGIN_PROTOCOL_MIRROR,
-       LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-       { NULL, NULL, 0, 0 } /* terminator */
-};
-
-void *thread_dumb_increment(void *threadid)
-{
-       while (!force_exit) {
-               /*
-                * this lock means wsi in the active list cannot
-                * disappear underneath us, because the code to add and remove
-                * them is protected by the same lock
-                */
-               pthread_mutex_lock(&lock_established_conns);
-               lws_callback_on_writable_all_protocol(context,
-                               &protocols[PROTOCOL_DUMB_INCREMENT]);
-               pthread_mutex_unlock(&lock_established_conns);
-               usleep(100000);
-       }
-
-       pthread_exit(NULL);
-}
-
-void *thread_service(void *threadid)
-{
-       while (lws_service_tsi(context, 50, (int)(lws_intptr_t)threadid) >= 0 && !force_exit)
-               ;
-
-       pthread_exit(NULL);
-}
-
-void sighandler(int sig)
-{
-       force_exit = 1;
-       lws_cancel_service(context);
-}
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate; client_no_context_takeover; client_max_window_bits"
-       },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-static struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 's' },
-       { "allow-non-ssl",      no_argument,    NULL, 'a' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "closetest",  no_argument,            NULL, 'c' },
-       { "libev",  no_argument,                NULL, 'e' },
-       { "threads",  required_argument,        NULL, 'j' },
-#ifndef LWS_NO_DAEMONIZE
-       { "daemonize",  no_argument,            NULL, 'D' },
-#endif
-       { "resource_path", required_argument,   NULL, 'r' },
-       { NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-       struct lws_context_creation_info info;
-       char interface_name[128] = "";
-       const char *iface = NULL;
-       pthread_t pthread_dumb, pthread_service[32];
-       char cert_path[1024];
-       char key_path[1024];
-       int threads = 1;
-       int use_ssl = 0;
-       void *retval;
-       int opts = 0;
-       int n = 0;
-#ifndef _WIN32
-/* LOG_PERROR is not POSIX standard, and may not be portable */
-#ifdef __sun
-       int syslog_options = LOG_PID;
-#else
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-
-       /*
-        * take care to zero down the info struct, he contains random garbaage
-        * from the stack otherwise
-        */
-       memset(&info, 0, sizeof info);
-       info.port = 7681;
-
-       pthread_mutex_init(&lock_established_conns, NULL);
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "eci:hsap:d:Dr:j:", options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-               case 'j':
-                       threads = atoi(optarg);
-                       if (threads > ARRAY_SIZE(pthread_service)) {
-                               lwsl_err("Max threads %lu\n",
-                                        (unsigned long)ARRAY_SIZE(pthread_service));
-                               return 1;
-                       }
-                       break;
-               case 'e':
-                       opts |= LWS_SERVER_OPTION_LIBEV;
-                       break;
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-                       #if !defined(_WIN32) && !defined(__sun)
-                       syslog_options &= ~LOG_PERROR;
-                       #endif
-                       break;
-#endif
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 's':
-                       use_ssl = 1;
-                       break;
-               case 'a':
-                       opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
-                       break;
-               case 'p':
-                       info.port = atoi(optarg);
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       iface = interface_name;
-                       break;
-               case 'c':
-                       close_testing = 1;
-                       fprintf(stderr, " Close testing mode -- closes on "
-                                          "client after 50 dumb increments"
-                                          "and suppresses lws_mirror spam\n");
-                       break;
-               case 'r':
-                       resource_path = optarg;
-                       printf("Setting resource path to \"%s\"\n", resource_path);
-                       break;
-               case 'h':
-                       fprintf(stderr, "Usage: test-server "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
-                       exit(1);
-               }
-       }
-
-#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 1;
-       }
-#endif
-
-       signal(SIGINT, sighandler);
-
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-       lwsl_notice("libwebsockets test server pthreads - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-       printf("Using resource path \"%s\"\n", resource_path);
-#ifdef EXTERNAL_POLL
-       max_poll_elements = getdtablesize();
-       pollfds = malloc(max_poll_elements * sizeof (struct lws_pollfd));
-       fd_lookup = malloc(max_poll_elements * sizeof (int));
-       if (pollfds == NULL || fd_lookup == NULL) {
-               lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
-               return -1;
-       }
-#endif
-
-       info.iface = iface;
-       info.protocols = protocols;
-       info.extensions = exts;
-
-       info.ssl_cert_filepath = NULL;
-       info.ssl_private_key_filepath = NULL;
-
-       if (use_ssl) {
-               if (strlen(resource_path) > sizeof(cert_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(cert_path, "%s/libwebsockets-test-server.pem",
-                       resource_path);
-               if (strlen(resource_path) > sizeof(key_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
-                       resource_path);
-
-               info.ssl_cert_filepath = cert_path;
-               info.ssl_private_key_filepath = key_path;
-       }
-       info.gid = -1;
-       info.uid = -1;
-       info.options = opts;
-       info.count_threads = threads;
-       info.extensions = exts;
-       info.max_http_header_pool = 4;
-
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               lwsl_err("libwebsocket init failed\n");
-               return -1;
-       }
-
-       /* start the dumb increment thread */
-
-       n = pthread_create(&pthread_dumb, NULL, thread_dumb_increment, 0);
-       if (n) {
-               lwsl_err("Unable to create dumb thread\n");
-               goto done;
-       }
-
-       /*
-        * notice the actual number of threads may be capped by the library,
-        * so use lws_get_count_threads() to get the actual amount of threads
-        * initialized.
-        */
-
-       for (n = 0; n < lws_get_count_threads(context); n++)
-               if (pthread_create(&pthread_service[n], NULL, thread_service,
-                                  (void *)(lws_intptr_t)n))
-                       lwsl_err("Failed to start service thread\n");
-
-       /* wait for all the service threads to exit */
-
-       while ((--n) >= 0)
-               pthread_join(pthread_service[n], &retval);
-
-       /* wait for pthread_dumb to exit */
-       pthread_join(pthread_dumb, &retval);
-
-done:
-       lws_context_destroy(context);
-       pthread_mutex_destroy(&lock_established_conns);
-
-       lwsl_notice("libwebsockets-test-server exited cleanly\n");
-
-#ifndef _WIN32
-       closelog();
-#endif
-
-       return 0;
-}
diff --git a/test-server/test-server-v2.0.c b/test-server/test-server-v2.0.c
deleted file mode 100644 (file)
index d26e2b2..0000000
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * libwebsockets-test-server-v2.0 - libwebsockets test implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#include <libwebsockets.h>
-#include <string.h>
-#include <getopt.h>
-#ifndef WIN32
-#include <syslog.h>
-#endif
-
-/* windows has no SIGUSR1 */
-#if !defined(WIN32) && !defined(_WIN32)
-#define TEST_DYNAMIC_VHOST
-#endif
-
-struct lws_context_creation_info info;
-int debug_level = 7;
-struct lws_context *context;
-
-#if defined(TEST_DYNAMIC_VHOST)
-volatile int dynamic_vhost_enable = 0;
-struct lws_vhost *dynamic_vhost;
-uv_timer_t timeout_watcher;
-#endif
-
-/* http server gets files from this path */
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-char *resource_path = LOCAL_RESOURCE_PATH;
-
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-char crl_path[1024] = "";
-#endif
-
-/*
- * This test server is ONLY this .c file, it's radically simpler than the
- * pre-v2.0 test servers.  For example it has no user callback content or
- * defines any protocols.
- *
- * To achieve that, it uses the LWS protocol plugins.  Those in turn
- * use libuv.  So you must configure with LWS_WITH_PLUGINS (which implies
- * libuv) to get this to build.
- *
- * You can find the individual protocol plugin sources in ../plugins
- */
-
-#if defined(TEST_DYNAMIC_VHOST)
-
-/*
- *  to test dynamic vhost creation, fire a SIGUSR1 at the test server.
- * It will toggle the existence of a second identical vhost at port + 1
- *
- * To synchronize with the event loop, it uses a libuv timer with 0 delay
- * to get the business end called as the next event.
- */
-
-static void
-uv_timeout_dynamic_vhost_toggle(uv_timer_t *w
-#if UV_VERSION_MAJOR == 0
-               , int status
-#endif
-)
-{
-       if (dynamic_vhost_enable && !dynamic_vhost) {
-               lwsl_notice("creating dynamic vhost...\n");
-               dynamic_vhost = lws_create_vhost(context, &info);
-       } else
-               if (!dynamic_vhost_enable && dynamic_vhost) {
-                       lwsl_notice("destroying dynamic vhost...\n");
-                       lws_vhost_destroy(dynamic_vhost);
-                       dynamic_vhost = NULL;
-               }
-}
-
-void sighandler_USR1(int sig)
-{
-       dynamic_vhost_enable ^= 1;
-       lwsl_notice("SIGUSR1: dynamic_vhost_enable: %d\n",
-                       dynamic_vhost_enable);
-       uv_timer_start(&timeout_watcher,
-                      uv_timeout_dynamic_vhost_toggle, 0, 0);
-}
-#endif
-
-void sighandler(int sig)
-{
-       lws_cancel_service(context);
-}
-
-static const struct lws_extension exts[] = {
-       {
-               "permessage-deflate",
-               lws_extension_callback_pm_deflate,
-               "permessage-deflate"
-       },
-       {
-               "deflate-frame",
-               lws_extension_callback_pm_deflate,
-               "deflate_frame"
-       },
-       { NULL, NULL, NULL /* terminator */ }
-};
-
-/*
- * mount handlers for sections of the URL space
- */
-
-static const struct lws_http_mount mount_ziptest = {
-       NULL,                   /* linked-list pointer to next*/
-       "/ziptest",             /* mountpoint in URL namespace on this vhost */
-       LOCAL_RESOURCE_PATH"/candide.zip",      /* handler */
-       NULL,   /* default filename if none given */
-       NULL,
-       NULL,
-       NULL,
-       NULL,
-       0,
-       0,
-       0,
-       0,
-       0,
-       0,
-       LWSMPRO_FILE,   /* origin points to a callback */
-       8,                      /* strlen("/ziptest"), ie length of the mountpoint */
-       NULL,
-
-       { NULL, NULL } // sentinel
-};
-
-static const struct lws_http_mount mount_post = {
-       (struct lws_http_mount *)&mount_ziptest, /* linked-list pointer to next*/
-       "/formtest",            /* mountpoint in URL namespace on this vhost */
-       "protocol-post-demo",   /* handler */
-       NULL,   /* default filename if none given */
-       NULL,
-       NULL,
-       NULL,
-       NULL,
-       0,
-       0,
-       0,
-       0,
-       0,
-       0,
-       LWSMPRO_CALLBACK,       /* origin points to a callback */
-       9,                      /* strlen("/formtest"), ie length of the mountpoint */
-       NULL,
-
-       { NULL, NULL } // sentinel
-};
-
-/*
- * mount a filesystem directory into the URL space at /
- * point it to our /usr/share directory with our assets in
- * stuff from here is autoserved by the library
- */
-
-static const struct lws_http_mount mount = {
-       (struct lws_http_mount *)&mount_post,   /* linked-list pointer to next*/
-       "/",            /* mountpoint in URL namespace on this vhost */
-       LOCAL_RESOURCE_PATH, /* where to go on the filesystem for that */
-       "test.html",    /* default filename if none given */
-       NULL,
-       NULL,
-       NULL,
-       NULL,
-       0,
-       0,
-       0,
-       0,
-       0,
-       0,
-       LWSMPRO_FILE,   /* mount type is a directory in a filesystem */
-       1,              /* strlen("/"), ie length of the mountpoint */
-       NULL,
-
-       { NULL, NULL } // sentinel
-};
-
-/*
- * this sets a per-vhost, per-protocol option name:value pair
- * the effect is to set this protocol to be the default one for the vhost,
- * ie, selected if no Protocol: header is sent with the ws upgrade.
- */
-#if 0
-static const struct lws_protocol_vhost_options pvo_opt = {
-       NULL,
-       NULL,
-       "default",
-       "1"
-};
-#endif
-
-static const struct lws_protocol_vhost_options pvo_opt4a = {
-       NULL,
-       NULL,
-       "raw", /* indicate we are the protocol that gets raw connections */
-       "1"
-};
-
-static const struct lws_protocol_vhost_options pvo_opt4 = {
-       &pvo_opt4a,
-       NULL,
-       "fifo-path", /* tell the raw test plugin to open a raw file here */
-       "/tmp/lws-test-raw"
-};
-
-/*
- * We must enable the plugin protocols we want into our vhost with a
- * linked-list.  We can also give the plugin per-vhost options here.
- */
-
-static const struct lws_protocol_vhost_options pvo_5 = {
-       NULL,
-       NULL,
-       "lws-meta",
-       "" /* ignored, just matches the protocol name above */
-};
-
-static const struct lws_protocol_vhost_options pvo_4 = {
-       &pvo_5,
-       &pvo_opt4, /* set us as the protocol who gets raw connections */
-       "protocol-lws-raw-test",
-       "" /* ignored, just matches the protocol name above */
-};
-
-static const struct lws_protocol_vhost_options pvo_3 = {
-       &pvo_4,
-       NULL,
-       "protocol-post-demo",
-       "" /* ignored, just matches the protocol name above */
-};
-
-static const struct lws_protocol_vhost_options pvo_2 = {
-       &pvo_3,
-       NULL,
-       "lws-status",
-       "" /* ignored, just matches the protocol name above */
-};
-
-static const struct lws_protocol_vhost_options pvo_1 = {
-       &pvo_2,
-       NULL,
-       "lws-mirror-protocol",
-       ""
-};
-
-static const struct lws_protocol_vhost_options pvo = {
-       &pvo_1,
-       NULL, // &pvo_opt,
-       "dumb-increment-protocol",
-       ""
-};
-
-static void signal_cb(uv_signal_t *watcher, int signum)
-{
-       lwsl_err("Signal %d caught, exiting...\n", watcher->signum);
-       switch (watcher->signum) {
-       case SIGTERM:
-       case SIGINT:
-               break;
-       default:
-               signal(SIGABRT, SIG_DFL);
-               abort();
-               break;
-       }
-       lws_libuv_stop(context);
-}
-
-static const struct option options[] = {
-       { "help",       no_argument,            NULL, 'h' },
-       { "debug",      required_argument,      NULL, 'd' },
-       { "port",       required_argument,      NULL, 'p' },
-       { "ssl",        no_argument,            NULL, 's' },
-       { "ssl-alerts", no_argument,            NULL, 'S' },
-       { "allow-non-ssl",      no_argument,    NULL, 'a' },
-       { "interface",  required_argument,      NULL, 'i' },
-       { "ssl-cert",  required_argument,       NULL, 'C' },
-       { "ssl-key",  required_argument,        NULL, 'K' },
-       { "ssl-ca",  required_argument,         NULL, 'A' },
-#if defined(LWS_OPENSSL_SUPPORT)
-       { "ssl-verify-client",  no_argument,            NULL, 'v' },
-#if defined(LWS_HAVE_SSL_CTX_set1_param)
-       { "ssl-crl",  required_argument,                NULL, 'R' },
-#endif
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       { "daemonize",  no_argument,            NULL, 'D' },
-#endif
-       { "resource_path", required_argument,   NULL, 'r' },
-       { NULL, 0, 0, 0 }
-};
-
-static const char * const plugin_dirs[] = {
-               INSTALL_DATADIR"/libwebsockets-test-server/plugins/",
-               NULL
-};
-
-int main(int argc, char **argv)
-{
-       struct lws_vhost *vhost;
-       char interface_name[128] = "";
-       const char *iface = NULL;
-       char cert_path[1024] = "";
-       char key_path[1024] = "";
-       char ca_path[1024] = "";
-       int uid = -1, gid = -1;
-       int use_ssl = 0;
-       int opts = 0;
-       int n = 0;
-#ifndef _WIN32
-       int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#ifndef LWS_NO_DAEMONIZE
-       int daemonize = 0;
-#endif
-
-       /*
-        * take care to zero down the info struct, he contains random garbaage
-        * from the stack otherwise
-        */
-       memset(&info, 0, sizeof info);
-       info.port = 7681;
-
-       while (n >= 0) {
-               n = getopt_long(argc, argv, "i:hsap:d:Dr:C:K:A:R:vu:g:S",
-                               (struct option *)options, NULL);
-               if (n < 0)
-                       continue;
-               switch (n) {
-#ifndef LWS_NO_DAEMONIZE
-               case 'D':
-                       daemonize = 1;
-                       #ifndef _WIN32
-                       syslog_options &= ~LOG_PERROR;
-                       #endif
-                       break;
-#endif
-               case 'u':
-                       uid = atoi(optarg);
-                       break;
-               case 'g':
-                       gid = atoi(optarg);
-                       break;
-               case 'd':
-                       debug_level = atoi(optarg);
-                       break;
-               case 's':
-                       use_ssl = 1;
-                       break;
-               case 'S':
-#if defined(LWS_OPENSSL_SUPPORT)
-                       info.ssl_info_event_mask |= SSL_CB_ALERT;
-#endif
-                       break;
-               case 'a':
-                       opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
-                       break;
-               case 'p':
-                       info.port = atoi(optarg);
-                       break;
-               case 'i':
-                       strncpy(interface_name, optarg, sizeof interface_name);
-                       interface_name[(sizeof interface_name) - 1] = '\0';
-                       iface = interface_name;
-                       break;
-               case 'r':
-                       resource_path = optarg;
-                       printf("Setting resource path to \"%s\"\n", resource_path);
-                       break;
-               case 'C':
-                       strncpy(cert_path, optarg, sizeof(cert_path) - 1);
-                       cert_path[sizeof(cert_path) - 1] = '\0';
-                       break;
-               case 'K':
-                       strncpy(key_path, optarg, sizeof(key_path) - 1);
-                       key_path[sizeof(key_path) - 1] = '\0';
-                       break;
-               case 'A':
-                       strncpy(ca_path, optarg, sizeof(ca_path) - 1);
-                       ca_path[sizeof(ca_path) - 1] = '\0';
-                       break;
-#if defined(LWS_OPENSSL_SUPPORT)
-               case 'v':
-                       use_ssl = 1;
-                       opts |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
-                       break;
-
-#if defined(LWS_HAVE_SSL_CTX_set1_param)
-               case 'R':
-                       strncpy(crl_path, optarg, sizeof(crl_path) - 1);
-                       crl_path[sizeof(crl_path) - 1] = '\0';
-                       break;
-#endif
-#endif
-               case 'h':
-                       fprintf(stderr, "Usage: test-server "
-                                       "[--port=<p>] [--ssl] "
-                                       "[-d <log bitfield>] "
-                                       "[--resource_path <path>]\n");
-                       exit(1);
-               }
-       }
-
-#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
-       /*
-        * normally lock path would be /var/lock/lwsts or similar, to
-        * simplify getting started without having to take care about
-        * permissions or running as root, set to /tmp/.lwsts-lock
-        */
-       if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
-               fprintf(stderr, "Failed to daemonize\n");
-               return 10;
-       }
-#endif
-
-       signal(SIGINT, sighandler);
-#if defined(TEST_DYNAMIC_VHOST)
-       signal(SIGUSR1, sighandler_USR1);
-#endif
-
-#ifndef _WIN32
-       /* we will only try to log things according to our debug_level */
-       setlogmask(LOG_UPTO (LOG_DEBUG));
-       openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-       /* tell the library what debug level to emit and to send it to syslog */
-       lws_set_log_level(debug_level, lwsl_emit_syslog);
-
-       lwsl_notice("libwebsockets test server - license LGPL2.1+SLE\n");
-       lwsl_notice("(C) Copyright 2010-2017 Andy Green <andy@warmcat.com>\n");
-
-       lwsl_notice(" Using resource path \"%s\"\n", resource_path);
-
-       info.iface = iface;
-       info.protocols = NULL; /* all protocols from lib / plugins */
-       info.ssl_cert_filepath = NULL;
-       info.ssl_private_key_filepath = NULL;
-       info.gid = gid;
-       info.uid = uid;
-       info.max_http_header_pool = 16;
-       info.options = opts | LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
-                       LWS_SERVER_OPTION_FALLBACK_TO_RAW |
-                       LWS_SERVER_OPTION_VALIDATE_UTF8 |
-                       LWS_SERVER_OPTION_LIBUV; /* plugins require this */
-
-       if (use_ssl) {
-               if (strlen(resource_path) > sizeof(cert_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               if (!cert_path[0])
-                       sprintf(cert_path, "%s/libwebsockets-test-server.pem",
-                               resource_path);
-               if (strlen(resource_path) > sizeof(key_path) - 32) {
-                       lwsl_err("resource path too long\n");
-                       return -1;
-               }
-               if (!key_path[0])
-                       sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
-                               resource_path);
-
-               info.ssl_cert_filepath = cert_path;
-               info.ssl_private_key_filepath = key_path;
-               if (ca_path[0])
-                       info.ssl_ca_filepath = ca_path;
-
-               /* redirect guys coming on http */
-               info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
-       }
-
-       info.extensions = exts;
-       info.timeout_secs = 5;
-       info.ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
-                              "ECDHE-RSA-AES256-GCM-SHA384:"
-                              "DHE-RSA-AES256-GCM-SHA384:"
-                              "ECDHE-RSA-AES256-SHA384:"
-                              "HIGH:!aNULL:!eNULL:!EXPORT:"
-                              "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
-                              "!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
-                              "!DHE-RSA-AES128-SHA256:"
-                              "!AES128-GCM-SHA256:"
-                              "!AES128-SHA256:"
-                              "!DHE-RSA-AES256-SHA256:"
-                              "!AES256-GCM-SHA384:"
-                              "!AES256-SHA256";
-
-       /* tell lws to look for protocol plugins here */
-       info.plugin_dirs = plugin_dirs;
-
-       /* tell lws about our mount we want */
-       info.mounts = &mount;
-       /*
-        * give it our linked-list of Per-Vhost Options, these control
-        * which protocols (from plugins) are allowed to be enabled on
-        * our vhost
-        */
-       info.pvo = &pvo;
-
-       /*
-        * Since we used LWS_SERVER_OPTION_EXPLICIT_VHOSTS, this only creates
-        * the context.  We can modify info and create as many vhosts as we
-        * like subsequently.
-        */
-       context = lws_create_context(&info);
-       if (context == NULL) {
-               lwsl_err("libwebsocket init failed\n");
-               return -1;
-       }
-
-       /*
-        *  normally we would adapt at least info.name to reflect the
-        * external hostname for this server.
-        */
-       vhost = lws_create_vhost(context, &info);
-       if (!vhost) {
-               lwsl_err("vhost creation failed\n");
-               return -1;
-       }
-
-#if defined(TEST_DYNAMIC_VHOST)
-       /* our dynamic vhost is on port + 1 */
-       info.port++;
-#endif
-
-       /* libuv event loop */
-       lws_uv_sigint_cfg(context, 1, signal_cb);
-       if (lws_uv_initloop(context, NULL, 0)) {
-               lwsl_err("lws_uv_initloop failed\n");
-               goto bail;
-       }
-
-#if defined(TEST_DYNAMIC_VHOST)
-       uv_timer_init(lws_uv_getloop(context, 0), &timeout_watcher);
-#endif
-       lws_libuv_run(context, 0);
-
-#if defined(TEST_DYNAMIC_VHOST)
-       uv_timer_stop(&timeout_watcher);
-       uv_close((uv_handle_t *)&timeout_watcher, NULL);
-#endif
-
-bail:
-       /* when we decided to exit the event loop */
-       lws_context_destroy(context);
-       lws_context_destroy2(context);
-       lwsl_notice("libwebsockets-test-server exited cleanly\n");
-
-#ifndef _WIN32
-       closelog();
-#endif
-
-       return 0;
-}
diff --git a/test-server/test-server.h b/test-server/test-server.h
deleted file mode 100644 (file)
index 29e6717..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * libwebsockets-test-server - libwebsockets test implementation
- *
- * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * The person who associated a work with this deed has dedicated
- * the work to the public domain by waiving all of his or her rights
- * to the work worldwide under copyright law, including all related
- * and neighboring rights, to the extent allowed by law. You can copy,
- * modify, distribute and perform the work, even for commercial purposes,
- * all without asking permission.
- *
- * The test apps are intended to be adapted for use in your code, which
- * may be proprietary.  So unlike the library itself, they are licensed
- * Public Domain.
- */
-
-#if defined(_WIN32) && defined(EXTERNAL_POLL)
-#define WINVER 0x0600
-#define _WIN32_WINNT 0x0600
-#define poll(fdArray, fds, timeout)  WSAPoll((LPWSAPOLLFD)(fdArray), (ULONG)(fds), (INT)(timeout))
-#endif
-
-#include "lws_config.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <signal.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <assert.h>
-
-#include "../lib/libwebsockets.h"
-
-#ifdef _WIN32
-#include <io.h>
-#include "gettimeofday.h"
-#else
-#include <syslog.h>
-#include <sys/time.h>
-#include <unistd.h>
-#endif
-
-extern int close_testing;
-extern int max_poll_elements;
-
-#ifdef EXTERNAL_POLL
-extern struct lws_pollfd *pollfds;
-extern int *fd_lookup;
-extern int count_pollfds;
-#endif
-extern volatile int force_exit;
-extern struct lws_context *context;
-extern char *resource_path;
-#if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
-extern char crl_path[1024];
-#endif
-
-extern void test_server_lock(int care);
-extern void test_server_unlock(int care);
-
-#ifndef __func__
-#define __func__ __FUNCTION__
-#endif
-
-struct per_session_data__http {
-       lws_fop_fd_t fop_fd;
-#ifdef LWS_WITH_CGI
-       struct lws_cgi_args args;
-#endif
-#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT)
-       int reason_bf;
-#endif
-       unsigned int client_finished:1;
-
-
-       struct lws_spa *spa;
-       char result[500 + LWS_PRE];
-       int result_len;
-
-       char filename[256];
-       long file_length;
-       lws_filefd_type post_fd;
-};
-
-/*
- * one of these is auto-created for each connection and a pointer to the
- * appropriate instance is passed to the callback in the user parameter
- *
- * for this example protocol we use it to individualize the count for each
- * connection.
- */
-
-#if !defined(DI_HANDLED_BY_PLUGIN)
-struct per_session_data__dumb_increment {
-       int number;
-};
-#endif
-
-
-extern int
-callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
-             void *in, size_t len);
-
-#if !defined(DI_HANDLED_BY_PLUGIN)
-extern int
-callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
-                       void *user, void *in, size_t len);
-#endif
-
-
-extern void
-dump_handshake_info(struct lws *wsi);
diff --git a/travis_install.sh b/travis_install.sh
deleted file mode 100755 (executable)
index f51f087..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-if [ "$COVERITY_SCAN_BRANCH" == 1 ]; then exit; fi
-
-if [ "$TRAVIS_OS_NAME" == "linux" ];
-then
-       sudo apt-get update -qq
-
-       if [ "$LWS_METHOD" == "libev" ];
-       then
-               sudo apt-get install -y -qq libev-dev;
-       fi
-
-       if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" ];
-       then
-               sudo apt-get install -y -qq libuv-dev;
-       fi
-
-fi
-
-if [ "$TRAVIS_OS_NAME" == "osx" ];
-then
-       if [ "$LWS_METHOD" == "libev" ];
-       then
-               brew install libev;
-       fi
-
-       if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" ];
-       then
-               brew install libuv;
-       fi
-
-fi
-
-       
diff --git a/win32port/version.rc.in b/win32port/version.rc.in
new file mode 100644 (file)
index 0000000..8a70f2d
--- /dev/null
@@ -0,0 +1,34 @@
+#include <winver.h>
+
+#define LWS_VERSION    @LWS_LIBRARY_VERSION_MAJOR@,@LWS_LIBRARY_VERSION_MINOR@,@LWS_LIBRARY_VERSION_PATCH@,0
+#define LWS_VERSION_STR "@LWS_LIBRARY_VERSION_MAJOR@.@LWS_LIBRARY_VERSION_MINOR@.@LWS_LIBRARY_VERSION_PATCH@\0"
+#define LWS_PACKAGE_NAME "@PACKAGE@\0"
+
+VS_VERSION_INFO VERSIONINFO
+  FILEVERSION    LWS_VERSION
+  PRODUCTVERSION LWS_VERSION
+  FILEFLAGSMASK  VS_FFI_FILEFLAGSMASK
+  FILEFLAGS      0
+  FILEOS         VOS__WINDOWS32
+  FILETYPE       VFT_DLL
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "ProductName", LWS_PACKAGE_NAME
+            VALUE "ProductVersion", LWS_VERSION_STR
+            VALUE "FileVersion", LWS_VERSION_STR
+            VALUE "FileDescription", "Libwebsockets is a lightweight pure C library built to use minimal CPU and memory resources, and provide fast throughput in both directions as client or server.\0"
+                       VALUE "InternalName", "websockets.dll\0"
+            VALUE "OriginalFilename", "websockets.dll\0"
+            VALUE "CompanyName", "Open Source Software community LGPL\0"
+            VALUE "LegalCopyright", "Copyright (C) Project contributors\0"
+            VALUE "Comments", "https://libwebsockets.org\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
index 43a5cba..22e5fa8 100644 (file)
@@ -38,6 +38,9 @@
 #include <string.h>\r
 #include "getopt.h"\r
 \r
+#define lws_ptr_diff(head, tail) \\r
+                       ((int)((char *)(head) - (char *)(tail)))\r
+\r
 extern int       opterr;       /* if error message should be printed */\r
 extern int       optind;       /* index into parent argv vector */\r
 extern int       optopt;       /* character checked for validity */\r
@@ -183,10 +186,10 @@ getopt_long(nargc, nargv, options, long_options, index)
                        return(-1);\r
                }\r
                if ((has_equal = strchr(current_argv, '=')) != NULL) {\r
-                       current_argv_len = has_equal - current_argv;\r
+                       current_argv_len = lws_ptr_diff(has_equal, current_argv);\r
                        has_equal++;\r
                } else\r
-                       current_argv_len = strlen(current_argv);\r
+                       current_argv_len = (int)strlen(current_argv);\r
 \r
                for (i = 0; long_options[i].name; i++) { \r
                        if (strncmp(current_argv, long_options[i].name, current_argv_len))\r
index bf04a55..66ad185 100644 (file)
@@ -295,7 +295,7 @@ local unsigned long crc32_little(crc, buf, len)
 }\r
 \r
 /* ========================================================================= */\r
-#define DOBIG4 c ^= *++buf4; \\r
+#define DOBIG4 c ^= *buf4++; \\r
         c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \\r
             crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]\r
 #define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4\r
@@ -317,7 +317,6 @@ local unsigned long crc32_big(crc, buf, len)
     }\r
 \r
     buf4 = (const u4 FAR *)(const void FAR *)buf;\r
-    buf4--;\r
     while (len >= 32) {\r
         DOBIG32;\r
         len -= 32;\r
@@ -326,7 +325,6 @@ local unsigned long crc32_big(crc, buf, len)
         DOBIG4;\r
         len -= 4;\r
     }\r
-    buf4++;\r
     buf = (const unsigned char FAR *)buf4;\r
 \r
     if (len) do {\r
index c7a5b69..0f6fc1e 100644 (file)
@@ -397,6 +397,18 @@ int ZEXPORT deflateSetHeader (strm, head)
 }\r
 \r
 /* ========================================================================= */\r
+int ZEXPORT deflatePending (strm, pending, bits)\r
+    unsigned *pending;\r
+    int *bits;\r
+    z_streamp strm;\r
+{\r
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;\r
+    *pending = strm->state->pending;\r
+    *bits = strm->state->bi_valid;\r
+    return Z_OK;\r
+}\r
+\r
+/* ========================================================================= */\r
 int ZEXPORT deflatePrime (strm, bits, value)\r
     z_streamp strm;\r
     int bits;\r
diff --git a/win32port/zlib/gzclose.c b/win32port/zlib/gzclose.c
deleted file mode 100755 (executable)
index cbf0db6..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/* gzclose.c -- zlib gzclose() function\r
- * Copyright (C) 2004, 2010 Mark Adler\r
- * For conditions of distribution and use, see copyright notice in zlib.h\r
- */\r
-\r
-#include "gzguts.h"\r
-\r
-/* gzclose() is in a separate file so that it is linked in only if it is used.\r
-   That way the other gzclose functions can be used instead to avoid linking in\r
-   unneeded compression or decompression routines. */\r
-int ZEXPORT gzclose(file)\r
-    gzFile file;\r
-{\r
-#ifndef NO_GZCOMPRESS\r
-    gz_statep state;\r
-\r
-    if (file == NULL)\r
-        return Z_STREAM_ERROR;\r
-    state = (gz_statep)file;\r
-\r
-    return state->mode == GZ_READ ? gzclose_r(file) : gzclose_w(file);\r
-#else\r
-    return gzclose_r(file);\r
-#endif\r
-}\r
diff --git a/win32port/zlib/gzio.c b/win32port/zlib/gzio.c
deleted file mode 100644 (file)
index 2b85d12..0000000
+++ /dev/null
@@ -1,1006 +0,0 @@
-/* gzio.c -- IO on .gz files\r
- * Copyright (C) 1995-2003 Jean-loup Gailly.\r
- * For conditions of distribution and use, see copyright notice in zlib.h\r
- *\r
- * Compile this file with -DNO_GZCOMPRESS to avoid the compression code.\r
- */\r
-\r
-/* \param (#) $Id$ */\r
-\r
-#include <stdio.h>\r
-\r
-#include "zutil.h"\r
-#include "gzguts.h"\r
-\r
-#ifdef NO_DEFLATE       /* for compatiblity with old definition */\r
-#  define NO_GZCOMPRESS\r
-#endif\r
-\r
-#ifndef NO_DUMMY_DECL\r
-struct internal_state {int dummy;}; /* for buggy compilers */\r
-#endif\r
-\r
-#ifndef Z_BUFSIZE\r
-#  ifdef MAXSEG_64K\r
-#    define Z_BUFSIZE 4096 /* minimize memory usage for 16-bit DOS */\r
-#  else\r
-#    define Z_BUFSIZE 16384\r
-#  endif\r
-#endif\r
-#ifndef Z_PRINTF_BUFSIZE\r
-#  define Z_PRINTF_BUFSIZE 4096\r
-#endif\r
-\r
-#ifdef __MVS__\r
-#  pragma map (fdopen , "\174\174FDOPEN")\r
-   FILE *fdopen(int, const char *);\r
-#endif\r
-\r
-#ifndef STDC\r
-extern voidp  malloc OF((uInt size));\r
-extern void   free   OF((voidpf ptr));\r
-#endif\r
-\r
-#define ALLOC(size) malloc(size)\r
-#define TRYFREE(p) {if (p) free(p);}\r
-\r
-static int const gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */\r
-\r
-/* gzip flag byte */\r
-#define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */\r
-#define HEAD_CRC     0x02 /* bit 1 set: header CRC present */\r
-#define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */\r
-#define ORIG_NAME    0x08 /* bit 3 set: original file name present */\r
-#define COMMENT      0x10 /* bit 4 set: file comment present */\r
-#define RESERVED     0xE0 /* bits 5..7: reserved */\r
-\r
-typedef struct gz_stream {\r
-    z_stream stream;\r
-    int      z_err;   /* error code for last stream operation */\r
-    int      z_eof;   /* set if end of input file */\r
-    FILE     *file;   /* .gz file */\r
-    Byte     *inbuf;  /* input buffer */\r
-    Byte     *outbuf; /* output buffer */\r
-    uLong    crc;     /* crc32 of uncompressed data */\r
-    char     *msg;    /* error message */\r
-    char     *path;   /* path name for debugging only */\r
-    int      transparent; /* 1 if input file is not a .gz file */\r
-    char     mode;    /* 'w' or 'r' */\r
-    z_off_t  start;   /* start of compressed data in file (header skipped) */\r
-    z_off_t  in;      /* bytes into deflate or inflate */\r
-    z_off_t  out;     /* bytes out of deflate or inflate */\r
-    int      back;    /* one character push-back */\r
-    int      last;    /* true if push-back is last character */\r
-} gz_stream;\r
-\r
-\r
-local gzFile gz_open      OF((const char *path, const char *mode, int  fd));\r
-local int do_flush        OF((gzFile file, int flush));\r
-local int    get_byte     OF((gz_stream *s));\r
-local void   check_header OF((gz_stream *s));\r
-local int    destroy      OF((gz_stream *s));\r
-local void   putLong      OF((FILE *file, uLong x));\r
-local uLong  getLong      OF((gz_stream *s));\r
-\r
-/* ===========================================================================\r
-     Opens a gzip (.gz) file for reading or writing. The mode parameter\r
-   is as in fopen ("rb" or "wb"). The file is given either by file descriptor\r
-   or path name (if fd == -1).\r
-     gz_open returns NULL if the file could not be opened or if there was\r
-   insufficient memory to allocate the (de)compression state; errno\r
-   can be checked to distinguish the two cases (if errno is zero, the\r
-   zlib error is Z_MEM_ERROR).\r
-*/\r
-local gzFile gz_open (path, mode, fd)\r
-    const char *path;\r
-    const char *mode;\r
-    int  fd;\r
-{\r
-    int err;\r
-    int level = Z_DEFAULT_COMPRESSION; /* compression level */\r
-    int strategy = Z_DEFAULT_STRATEGY; /* compression strategy */\r
-    char *p = (char*)mode;\r
-    gz_stream *s;\r
-    char fmode[80]; /* copy of mode, without the compression level */\r
-    char *m = fmode;\r
-\r
-    if (!path || !mode) return Z_NULL;\r
-\r
-    s = (gz_stream *)ALLOC(sizeof(gz_stream));\r
-    if (!s) return Z_NULL;\r
-\r
-    s->stream.zalloc = (alloc_func)0;\r
-    s->stream.zfree = (free_func)0;\r
-    s->stream.opaque = (voidpf)0;\r
-    s->stream.next_in = s->inbuf = Z_NULL;\r
-    s->stream.next_out = s->outbuf = Z_NULL;\r
-    s->stream.avail_in = s->stream.avail_out = 0;\r
-    s->file = NULL;\r
-    s->z_err = Z_OK;\r
-    s->z_eof = 0;\r
-    s->in = 0;\r
-    s->out = 0;\r
-    s->back = EOF;\r
-    s->crc = crc32(0L, Z_NULL, 0);\r
-    s->msg = NULL;\r
-    s->transparent = 0;\r
-\r
-    s->path = (char*)ALLOC(strlen(path)+1);\r
-    if (s->path == NULL) {\r
-        return destroy(s), (gzFile)Z_NULL;\r
-    }\r
-    strcpy(s->path, path); /* do this early for debugging */\r
-\r
-    s->mode = '\0';\r
-    do {\r
-        if (*p == 'r') s->mode = 'r';\r
-        if (*p == 'w' || *p == 'a') s->mode = 'w';\r
-        if (*p >= '0' && *p <= '9') {\r
-            level = *p - '0';\r
-        } else if (*p == 'f') {\r
-          strategy = Z_FILTERED;\r
-        } else if (*p == 'h') {\r
-          strategy = Z_HUFFMAN_ONLY;\r
-        } else if (*p == 'R') {\r
-          strategy = Z_RLE;\r
-        } else {\r
-            *m++ = *p; /* copy the mode */\r
-        }\r
-    } while (*p++ && m != fmode + sizeof(fmode));\r
-    if (s->mode == '\0') return destroy(s), (gzFile)Z_NULL;\r
-\r
-    if (s->mode == 'w') {\r
-#ifdef NO_GZCOMPRESS\r
-        err = Z_STREAM_ERROR;\r
-#else\r
-        err = deflateInit2(&(s->stream), level,\r
-                           Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, strategy);\r
-        /* windowBits is passed < 0 to suppress zlib header */\r
-\r
-        s->stream.next_out = s->outbuf = (Byte*)ALLOC(Z_BUFSIZE);\r
-#endif\r
-        if (err != Z_OK || s->outbuf == Z_NULL) {\r
-            return destroy(s), (gzFile)Z_NULL;\r
-        }\r
-    } else {\r
-        s->stream.next_in  = s->inbuf = (Byte*)ALLOC(Z_BUFSIZE);\r
-\r
-        err = inflateInit2(&(s->stream), -MAX_WBITS);\r
-        /* windowBits is passed < 0 to tell that there is no zlib header.\r
-         * Note that in this case inflate *requires* an extra "dummy" byte\r
-         * after the compressed stream in order to complete decompression and\r
-         * return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes are\r
-         * present after the compressed stream.\r
-         */\r
-        if (err != Z_OK || s->inbuf == Z_NULL) {\r
-            return destroy(s), (gzFile)Z_NULL;\r
-        }\r
-    }\r
-    s->stream.avail_out = Z_BUFSIZE;\r
-\r
-    errno = 0;\r
-    s->file = fd < 0 ? F_OPEN(path, fmode) : (FILE*)fdopen(fd, fmode);\r
-\r
-    if (s->file == NULL) {\r
-        return destroy(s), (gzFile)Z_NULL;\r
-    }\r
-    if (s->mode == 'w') {\r
-        /* Write a very simple .gz header:\r
-         */\r
-        fprintf(s->file, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],\r
-             Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE);\r
-        s->start = 10L;\r
-        /* We use 10L instead of ftell(s->file) to because ftell causes an\r
-         * fflush on some systems. This version of the library doesn't use\r
-         * start anyway in write mode, so this initialization is not\r
-         * necessary.\r
-         */\r
-    } else {\r
-        check_header(s); /* skip the .gz header */\r
-        s->start = ftell(s->file) - s->stream.avail_in;\r
-    }\r
-\r
-    return (gzFile)s;\r
-}\r
-\r
-/* ===========================================================================\r
-     Opens a gzip (.gz) file for reading or writing.\r
-*/\r
-gzFile ZEXPORT gzopen (path, mode)\r
-    const char *path;\r
-    const char *mode;\r
-{\r
-    return gz_open (path, mode, -1);\r
-}\r
-\r
-/* ===========================================================================\r
-     Associate a gzFile with the file descriptor fd. fd is not dup'ed here\r
-   to mimic the behavio(u)r of fdopen.\r
-*/\r
-gzFile ZEXPORT gzdopen (fd, mode)\r
-    int fd;\r
-    const char *mode;\r
-{\r
-    char name[20];\r
-\r
-    if (fd < 0) return (gzFile)Z_NULL;\r
-    sprintf(name, "<fd:%d>", fd); /* for debugging */\r
-\r
-    return gz_open (name, mode, fd);\r
-}\r
-\r
-/* ===========================================================================\r
- * Update the compression level and strategy\r
- */\r
-int ZEXPORT gzsetparams (file, level, strategy)\r
-    gzFile file;\r
-    int level;\r
-    int strategy;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR;\r
-\r
-    /* Make room to allow flushing */\r
-    if (s->stream.avail_out == 0) {\r
-\r
-        s->stream.next_out = s->outbuf;\r
-        if (fwrite(s->outbuf, 1, Z_BUFSIZE, s->file) != Z_BUFSIZE) {\r
-            s->z_err = Z_ERRNO;\r
-        }\r
-        s->stream.avail_out = Z_BUFSIZE;\r
-    }\r
-\r
-    return deflateParams (&(s->stream), level, strategy);\r
-}\r
-\r
-/* ===========================================================================\r
-     Read a byte from a gz_stream; update next_in and avail_in. Return EOF\r
-   for end of file.\r
-   IN assertion: the stream s has been sucessfully opened for reading.\r
-*/\r
-local int get_byte(s)\r
-    gz_stream *s;\r
-{\r
-    if (s->z_eof) return EOF;\r
-    if (s->stream.avail_in == 0) {\r
-        errno = 0;\r
-        s->stream.avail_in = fread(s->inbuf, 1, Z_BUFSIZE, s->file);\r
-        if (s->stream.avail_in == 0) {\r
-            s->z_eof = 1;\r
-            if (ferror(s->file)) s->z_err = Z_ERRNO;\r
-            return EOF;\r
-        }\r
-        s->stream.next_in = s->inbuf;\r
-    }\r
-    s->stream.avail_in--;\r
-    return *(s->stream.next_in)++;\r
-}\r
-\r
-/* ===========================================================================\r
-      Check the gzip header of a gz_stream opened for reading. Set the stream\r
-    mode to transparent if the gzip magic header is not present; set s->err\r
-    to Z_DATA_ERROR if the magic header is present but the rest of the header\r
-    is incorrect.\r
-    IN assertion: the stream s has already been created sucessfully;\r
-       s->stream.avail_in is zero for the first time, but may be non-zero\r
-       for concatenated .gz files.\r
-*/\r
-local void check_header(s)\r
-    gz_stream *s;\r
-{\r
-    int method; /* method byte */\r
-    int flags;  /* flags byte */\r
-    uInt len;\r
-    int c;\r
-\r
-    /* Assure two bytes in the buffer so we can peek ahead -- handle case\r
-       where first byte of header is at the end of the buffer after the last\r
-       gzip segment */\r
-    len = s->stream.avail_in;\r
-    if (len < 2) {\r
-        if (len) s->inbuf[0] = s->stream.next_in[0];\r
-        errno = 0;\r
-        len = fread(s->inbuf + len, 1, Z_BUFSIZE >> len, s->file);\r
-        if (len == 0 && ferror(s->file)) s->z_err = Z_ERRNO;\r
-        s->stream.avail_in += len;\r
-        s->stream.next_in = s->inbuf;\r
-        if (s->stream.avail_in < 2) {\r
-            s->transparent = s->stream.avail_in;\r
-            return;\r
-        }\r
-    }\r
-\r
-    /* Peek ahead to check the gzip magic header */\r
-    if (s->stream.next_in[0] != gz_magic[0] ||\r
-        s->stream.next_in[1] != gz_magic[1]) {\r
-        s->transparent = 1;\r
-        return;\r
-    }\r
-    s->stream.avail_in -= 2;\r
-    s->stream.next_in += 2;\r
-\r
-    /* Check the rest of the gzip header */\r
-    method = get_byte(s);\r
-    flags = get_byte(s);\r
-    if (method != Z_DEFLATED || (flags & RESERVED) != 0) {\r
-        s->z_err = Z_DATA_ERROR;\r
-        return;\r
-    }\r
-\r
-    /* Discard time, xflags and OS code: */\r
-    for (len = 0; len < 6; len++) (void)get_byte(s);\r
-\r
-    if ((flags & EXTRA_FIELD) != 0) { /* skip the extra field */\r
-        len  =  (uInt)get_byte(s);\r
-        len += ((uInt)get_byte(s))<<8;\r
-        /* len is garbage if EOF but the loop below will quit anyway */\r
-        while (len-- != 0 && get_byte(s) != EOF) ;\r
-    }\r
-    if ((flags & ORIG_NAME) != 0) { /* skip the original file name */\r
-        while ((c = get_byte(s)) != 0 && c != EOF) ;\r
-    }\r
-    if ((flags & COMMENT) != 0) {   /* skip the .gz file comment */\r
-        while ((c = get_byte(s)) != 0 && c != EOF) ;\r
-    }\r
-    if ((flags & HEAD_CRC) != 0) {  /* skip the header crc */\r
-        for (len = 0; len < 2; len++) (void)get_byte(s);\r
-    }\r
-    s->z_err = s->z_eof ? Z_DATA_ERROR : Z_OK;\r
-}\r
-\r
- /* ===========================================================================\r
- * Cleanup then free the given gz_stream. Return a zlib error code.\r
-   Try freeing in the reverse order of allocations.\r
- */\r
-local int destroy (s)\r
-    gz_stream *s;\r
-{\r
-    int err = Z_OK;\r
-\r
-    if (!s) return Z_STREAM_ERROR;\r
-\r
-    TRYFREE(s->msg);\r
-\r
-    if (s->stream.state != NULL) {\r
-        if (s->mode == 'w') {\r
-#ifdef NO_GZCOMPRESS\r
-            err = Z_STREAM_ERROR;\r
-#else\r
-            err = deflateEnd(&(s->stream));\r
-#endif\r
-        } else if (s->mode == 'r') {\r
-            err = inflateEnd(&(s->stream));\r
-        }\r
-    }\r
-    if (s->file != NULL && fclose(s->file)) {\r
-#ifdef ESPIPE\r
-        if (errno != ESPIPE) /* fclose is broken for pipes in HP/UX */\r
-#endif\r
-            err = Z_ERRNO;\r
-    }\r
-    if (s->z_err < 0) err = s->z_err;\r
-\r
-    TRYFREE(s->inbuf);\r
-    TRYFREE(s->outbuf);\r
-    TRYFREE(s->path);\r
-    TRYFREE(s);\r
-    return err;\r
-}\r
-\r
-/* ===========================================================================\r
-     Reads the given number of uncompressed bytes from the compressed file.\r
-   gzread returns the number of bytes actually read (0 for end of file).\r
-*/\r
-int ZEXPORT gzread (file, buf, len)\r
-    gzFile file;\r
-    voidp buf;\r
-    unsigned len;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-    Bytef *start = (Bytef*)buf; /* starting point for crc computation */\r
-    Byte  *next_out; /* == stream.next_out but not forced far (for MSDOS) */\r
-\r
-    if (s == NULL || s->mode != 'r') return Z_STREAM_ERROR;\r
-\r
-    if (s->z_err == Z_DATA_ERROR || s->z_err == Z_ERRNO) return -1;\r
-    if (s->z_err == Z_STREAM_END) return 0;  /* EOF */\r
-\r
-    next_out = (Byte*)buf;\r
-    s->stream.next_out = (Bytef*)buf;\r
-    s->stream.avail_out = len;\r
-\r
-    if (s->stream.avail_out && s->back != EOF) {\r
-        *next_out++ = s->back;\r
-        s->stream.next_out++;\r
-        s->stream.avail_out--;\r
-        s->back = EOF;\r
-        s->out++;\r
-        if (s->last) {\r
-            s->z_err = Z_STREAM_END;\r
-            return 1;\r
-        }\r
-    }\r
-\r
-    while (s->stream.avail_out != 0) {\r
-\r
-        if (s->transparent) {\r
-            /* Copy first the lookahead bytes: */\r
-            uInt n = s->stream.avail_in;\r
-            if (n > s->stream.avail_out) n = s->stream.avail_out;\r
-            if (n > 0) {\r
-                zmemcpy(s->stream.next_out, s->stream.next_in, n);\r
-                next_out += n;\r
-                s->stream.next_out = next_out;\r
-                s->stream.next_in   += n;\r
-                s->stream.avail_out -= n;\r
-                s->stream.avail_in  -= n;\r
-            }\r
-            if (s->stream.avail_out > 0) {\r
-                s->stream.avail_out -= fread(next_out, 1, s->stream.avail_out,\r
-                                             s->file);\r
-            }\r
-            len -= s->stream.avail_out;\r
-            s->in  += len;\r
-            s->out += len;\r
-            if (len == 0) s->z_eof = 1;\r
-            return (int)len;\r
-        }\r
-        if (s->stream.avail_in == 0 && !s->z_eof) {\r
-\r
-            errno = 0;\r
-            s->stream.avail_in = fread(s->inbuf, 1, Z_BUFSIZE, s->file);\r
-            if (s->stream.avail_in == 0) {\r
-                s->z_eof = 1;\r
-                if (ferror(s->file)) {\r
-                    s->z_err = Z_ERRNO;\r
-                    break;\r
-                }\r
-            }\r
-            s->stream.next_in = s->inbuf;\r
-        }\r
-        s->in += s->stream.avail_in;\r
-        s->out += s->stream.avail_out;\r
-        s->z_err = inflate(&(s->stream), Z_NO_FLUSH);\r
-        s->in -= s->stream.avail_in;\r
-        s->out -= s->stream.avail_out;\r
-\r
-        if (s->z_err == Z_STREAM_END) {\r
-            /* Check CRC and original size */\r
-            s->crc = crc32(s->crc, start, (uInt)(s->stream.next_out - start));\r
-            start = s->stream.next_out;\r
-\r
-            if (getLong(s) != s->crc) {\r
-                s->z_err = Z_DATA_ERROR;\r
-            } else {\r
-                (void)getLong(s);\r
-                /* The uncompressed length returned by above getlong() may be\r
-                 * different from s->out in case of concatenated .gz files.\r
-                 * Check for such files:\r
-                 */\r
-                check_header(s);\r
-                if (s->z_err == Z_OK) {\r
-                    inflateReset(&(s->stream));\r
-                    s->crc = crc32(0L, Z_NULL, 0);\r
-                }\r
-            }\r
-        }\r
-        if (s->z_err != Z_OK || s->z_eof) break;\r
-    }\r
-    s->crc = crc32(s->crc, start, (uInt)(s->stream.next_out - start));\r
-\r
-    return (int)(len - s->stream.avail_out);\r
-}\r
-\r
-\r
-/* ===========================================================================\r
-      Reads one byte from the compressed file. gzgetc returns this byte\r
-   or -1 in case of end of file or error.\r
-*/\r
-int ZEXPORT gzgetc(file)\r
-    gzFile file;\r
-{\r
-    unsigned char c;\r
-\r
-    return gzread(file, &c, 1) == 1 ? c : -1;\r
-}\r
-\r
-\r
-/* ===========================================================================\r
-      Push one byte back onto the stream.\r
-*/\r
-int ZEXPORT gzungetc(c, file)\r
-    int c;\r
-    gzFile file;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL || s->mode != 'r' || c == EOF || s->back != EOF) return EOF;\r
-    s->back = c;\r
-    s->out--;\r
-    s->last = (s->z_err == Z_STREAM_END);\r
-    if (s->last) s->z_err = Z_OK;\r
-    s->z_eof = 0;\r
-    return c;\r
-}\r
-\r
-\r
-/* ===========================================================================\r
-      Reads bytes from the compressed file until len-1 characters are\r
-   read, or a newline character is read and transferred to buf, or an\r
-   end-of-file condition is encountered.  The string is then terminated\r
-   with a null character.\r
-      gzgets returns buf, or Z_NULL in case of error.\r
-\r
-      The current implementation is not optimized at all.\r
-*/\r
-char * ZEXPORT gzgets(file, buf, len)\r
-    gzFile file;\r
-    char *buf;\r
-    int len;\r
-{\r
-    char *b = buf;\r
-    if (buf == Z_NULL || len <= 0) return Z_NULL;\r
-\r
-    while (--len > 0 && gzread(file, buf, 1) == 1 && *buf++ != '\n') ;\r
-    *buf = '\0';\r
-    return b == buf && len > 0 ? Z_NULL : b;\r
-}\r
-\r
-\r
-#ifndef NO_GZCOMPRESS\r
-/* ===========================================================================\r
-     Writes the given number of uncompressed bytes into the compressed file.\r
-   gzwrite returns the number of bytes actually written (0 in case of error).\r
-*/\r
-int ZEXPORT gzwrite (file, buf, len)\r
-    gzFile file;\r
-    voidpc buf;\r
-    unsigned len;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR;\r
-\r
-    s->stream.next_in = (Bytef*)buf;\r
-    s->stream.avail_in = len;\r
-\r
-    while (s->stream.avail_in != 0) {\r
-\r
-        if (s->stream.avail_out == 0) {\r
-\r
-            s->stream.next_out = s->outbuf;\r
-            if (fwrite(s->outbuf, 1, Z_BUFSIZE, s->file) != Z_BUFSIZE) {\r
-                s->z_err = Z_ERRNO;\r
-                break;\r
-            }\r
-            s->stream.avail_out = Z_BUFSIZE;\r
-        }\r
-        s->in += s->stream.avail_in;\r
-        s->out += s->stream.avail_out;\r
-        s->z_err = deflate(&(s->stream), Z_NO_FLUSH);\r
-        s->in -= s->stream.avail_in;\r
-        s->out -= s->stream.avail_out;\r
-        if (s->z_err != Z_OK) break;\r
-    }\r
-    s->crc = crc32(s->crc, (const Bytef *)buf, len);\r
-\r
-    return (int)(len - s->stream.avail_in);\r
-}\r
-\r
-\r
-/* ===========================================================================\r
-     Converts, formats, and writes the args to the compressed file under\r
-   control of the format string, as in fprintf. gzprintf returns the number of\r
-   uncompressed bytes actually written (0 in case of error).\r
-*/\r
-#ifdef STDC\r
-#include <stdarg.h>\r
-\r
-int ZEXPORTVA gzprintf (gzFile file, const char *format, /* args */ ...)\r
-{\r
-    char buf[Z_PRINTF_BUFSIZE];\r
-    va_list va;\r
-    int len;\r
-\r
-    buf[sizeof(buf) - 1] = 0;\r
-    va_start(va, format);\r
-#ifdef NO_vsnprintf\r
-#  ifdef HAS_vsprintf_void\r
-    (void)vsprintf(buf, format, va);\r
-    va_end(va);\r
-    for (len = 0; len < sizeof(buf); len++)\r
-        if (buf[len] == 0) break;\r
-#  else\r
-    len = vsprintf(buf, format, va);\r
-    va_end(va);\r
-#  endif\r
-#else\r
-#  ifdef HAS_vsnprintf_void\r
-    (void)vsnprintf(buf, sizeof(buf), format, va);\r
-    va_end(va);\r
-    len = strlen(buf);\r
-#  else\r
-    len = vsnprintf(buf, sizeof(buf), format, va);\r
-    va_end(va);\r
-#  endif\r
-#endif\r
-    if (len <= 0 || len >= (int)sizeof(buf) || buf[sizeof(buf) - 1] != 0)\r
-        return 0;\r
-    return gzwrite(file, buf, (unsigned)len);\r
-}\r
-#else /* not ANSI C */\r
-\r
-int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,\r
-                       a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)\r
-    gzFile file;\r
-    const char *format;\r
-    int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,\r
-        a11, a12, a13, a14, a15, a16, a17, a18, a19, a20;\r
-{\r
-    char buf[Z_PRINTF_BUFSIZE];\r
-    int len;\r
-\r
-    buf[sizeof(buf) - 1] = 0;\r
-#ifdef NO_snprintf\r
-#  ifdef HAS_sprintf_void\r
-    sprintf(buf, format, a1, a2, a3, a4, a5, a6, a7, a8,\r
-            a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);\r
-    for (len = 0; len < sizeof(buf); len++)\r
-        if (buf[len] == 0) break;\r
-#  else\r
-    len = sprintf(buf, format, a1, a2, a3, a4, a5, a6, a7, a8,\r
-                a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);\r
-#  endif\r
-#else\r
-#  ifdef HAS_snprintf_void\r
-    snprintf(buf, sizeof(buf), format, a1, a2, a3, a4, a5, a6, a7, a8,\r
-             a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);\r
-    len = strlen(buf);\r
-#  else\r
-    len = snprintf(buf, sizeof(buf), format, a1, a2, a3, a4, a5, a6, a7, a8,\r
-                 a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);\r
-#  endif\r
-#endif\r
-    if (len <= 0 || len >= sizeof(buf) || buf[sizeof(buf) - 1] != 0)\r
-        return 0;\r
-    return gzwrite(file, buf, len);\r
-}\r
-#endif\r
-\r
-/* ===========================================================================\r
-      Writes c, converted to an unsigned char, into the compressed file.\r
-   gzputc returns the value that was written, or -1 in case of error.\r
-*/\r
-int ZEXPORT gzputc(file, c)\r
-    gzFile file;\r
-    int c;\r
-{\r
-    unsigned char cc = (unsigned char) c; /* required for big endian systems */\r
-\r
-    return gzwrite(file, &cc, 1) == 1 ? (int)cc : -1;\r
-}\r
-\r
-\r
-/* ===========================================================================\r
-      Writes the given null-terminated string to the compressed file, excluding\r
-   the terminating null character.\r
-      gzputs returns the number of characters written, or -1 in case of error.\r
-*/\r
-int ZEXPORT gzputs(file, s)\r
-    gzFile file;\r
-    const char *s;\r
-{\r
-    return gzwrite(file, (char*)s, (unsigned)strlen(s));\r
-}\r
-\r
-\r
-/* ===========================================================================\r
-     Flushes all pending output into the compressed file. The parameter\r
-   flush is as in the deflate() function.\r
-*/\r
-local int do_flush (file, flush)\r
-    gzFile file;\r
-    int flush;\r
-{\r
-    uInt len;\r
-    int done = 0;\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR;\r
-\r
-    s->stream.avail_in = 0; /* should be zero already anyway */\r
-\r
-    for (;;) {\r
-        len = Z_BUFSIZE - s->stream.avail_out;\r
-\r
-        if (len != 0) {\r
-            if ((uInt)fwrite(s->outbuf, 1, len, s->file) != len) {\r
-                s->z_err = Z_ERRNO;\r
-                return Z_ERRNO;\r
-            }\r
-            s->stream.next_out = s->outbuf;\r
-            s->stream.avail_out = Z_BUFSIZE;\r
-        }\r
-        if (done) break;\r
-        s->out += s->stream.avail_out;\r
-        s->z_err = deflate(&(s->stream), flush);\r
-        s->out -= s->stream.avail_out;\r
-\r
-        /* Ignore the second of two consecutive flushes: */\r
-        if (len == 0 && s->z_err == Z_BUF_ERROR) s->z_err = Z_OK;\r
-\r
-        /* deflate has finished flushing only when it hasn't used up\r
-         * all the available space in the output buffer:\r
-         */\r
-        done = (s->stream.avail_out != 0 || s->z_err == Z_STREAM_END);\r
-\r
-        if (s->z_err != Z_OK && s->z_err != Z_STREAM_END) break;\r
-    }\r
-    return  s->z_err == Z_STREAM_END ? Z_OK : s->z_err;\r
-}\r
-\r
-int ZEXPORT gzflush (file, flush)\r
-     gzFile file;\r
-     int flush;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-    int err = do_flush (file, flush);\r
-\r
-    if (err) return err;\r
-    fflush(s->file);\r
-    return  s->z_err == Z_STREAM_END ? Z_OK : s->z_err;\r
-}\r
-#endif /* NO_GZCOMPRESS */\r
-\r
-/* ===========================================================================\r
-      Sets the starting position for the next gzread or gzwrite on the given\r
-   compressed file. The offset represents a number of bytes in the\r
-      gzseek returns the resulting offset location as measured in bytes from\r
-   the beginning of the uncompressed stream, or -1 in case of error.\r
-      SEEK_END is not implemented, returns error.\r
-      In this version of the library, gzseek can be extremely slow.\r
-*/\r
-z_off_t ZEXPORT gzseek (file, offset, whence)\r
-    gzFile file;\r
-    z_off_t offset;\r
-    int whence;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL || whence == SEEK_END ||\r
-        s->z_err == Z_ERRNO || s->z_err == Z_DATA_ERROR) {\r
-        return -1L;\r
-    }\r
-\r
-    if (s->mode == 'w') {\r
-#ifdef NO_GZCOMPRESS\r
-        return -1L;\r
-#else\r
-        if (whence == SEEK_SET) {\r
-            offset -= s->in;\r
-        }\r
-        if (offset < 0) return -1L;\r
-\r
-        /* At this point, offset is the number of zero bytes to write. */\r
-        if (s->inbuf == Z_NULL) {\r
-            s->inbuf = (Byte*)ALLOC(Z_BUFSIZE); /* for seeking */\r
-            if (s->inbuf == Z_NULL) return -1L;\r
-            zmemzero(s->inbuf, Z_BUFSIZE);\r
-        }\r
-        while (offset > 0)  {\r
-            uInt size = Z_BUFSIZE;\r
-            if (offset < Z_BUFSIZE) size = (uInt)offset;\r
-\r
-            size = gzwrite(file, s->inbuf, size);\r
-            if (size == 0) return -1L;\r
-\r
-            offset -= size;\r
-        }\r
-        return s->in;\r
-#endif\r
-    }\r
-    /* Rest of function is for reading only */\r
-\r
-    /* compute absolute position */\r
-    if (whence == SEEK_CUR) {\r
-        offset += s->out;\r
-    }\r
-    if (offset < 0) return -1L;\r
-\r
-    if (s->transparent) {\r
-        /* map to fseek */\r
-        s->back = EOF;\r
-        s->stream.avail_in = 0;\r
-        s->stream.next_in = s->inbuf;\r
-        if (fseek(s->file, offset, SEEK_SET) < 0) return -1L;\r
-\r
-        s->in = s->out = offset;\r
-        return offset;\r
-    }\r
-\r
-    /* For a negative seek, rewind and use positive seek */\r
-    if (offset >= s->out) {\r
-        offset -= s->out;\r
-    } else if (gzrewind(file) < 0) {\r
-        return -1L;\r
-    }\r
-    /* offset is now the number of bytes to skip. */\r
-\r
-    if (offset != 0 && s->outbuf == Z_NULL) {\r
-        s->outbuf = (Byte*)ALLOC(Z_BUFSIZE);\r
-        if (s->outbuf == Z_NULL) return -1L;\r
-    }\r
-    if (offset && s->back != EOF) {\r
-        s->back = EOF;\r
-        s->out++;\r
-        offset--;\r
-        if (s->last) s->z_err = Z_STREAM_END;\r
-    }\r
-    while (offset > 0)  {\r
-        int size = Z_BUFSIZE;\r
-        if (offset < Z_BUFSIZE) size = (int)offset;\r
-\r
-        size = gzread(file, s->outbuf, (uInt)size);\r
-        if (size <= 0) return -1L;\r
-        offset -= size;\r
-    }\r
-    return s->out;\r
-}\r
-\r
-/* ===========================================================================\r
-     Rewinds input file.\r
-*/\r
-int ZEXPORT gzrewind (file)\r
-    gzFile file;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL || s->mode != 'r') return -1;\r
-\r
-    s->z_err = Z_OK;\r
-    s->z_eof = 0;\r
-    s->back = EOF;\r
-    s->stream.avail_in = 0;\r
-    s->stream.next_in = s->inbuf;\r
-    s->crc = crc32(0L, Z_NULL, 0);\r
-    if (!s->transparent) (void)inflateReset(&s->stream);\r
-    s->in = 0;\r
-    s->out = 0;\r
-    return fseek(s->file, s->start, SEEK_SET);\r
-}\r
-\r
-/* ===========================================================================\r
-     Returns the starting position for the next gzread or gzwrite on the\r
-   given compressed file. This position represents a number of bytes in the\r
-   uncompressed data stream.\r
-*/\r
-z_off_t ZEXPORT gztell (file)\r
-    gzFile file;\r
-{\r
-    return gzseek(file, 0L, SEEK_CUR);\r
-}\r
-\r
-/* ===========================================================================\r
-     Returns 1 when EOF has previously been detected reading the given\r
-   input stream, otherwise zero.\r
-*/\r
-int ZEXPORT gzeof (file)\r
-    gzFile file;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    /* With concatenated compressed files that can have embedded\r
-     * crc trailers, z_eof is no longer the only/best indicator of EOF\r
-     * on a gz_stream. Handle end-of-stream error explicitly here.\r
-     */\r
-    if (s == NULL || s->mode != 'r') return 0;\r
-    if (s->z_eof) return 1;\r
-    return s->z_err == Z_STREAM_END;\r
-}\r
-\r
-/* ===========================================================================\r
-   Outputs a long in LSB order to the given file\r
-*/\r
-local void putLong (file, x)\r
-    FILE *file;\r
-    uLong x;\r
-{\r
-    int n;\r
-    for (n = 0; n < 4; n++) {\r
-        fputc((int)(x & 0xff), file);\r
-        x >>= 8;\r
-    }\r
-}\r
-\r
-/* ===========================================================================\r
-   Reads a long in LSB order from the given gz_stream. Sets z_err in case\r
-   of error.\r
-*/\r
-local uLong getLong (s)\r
-    gz_stream *s;\r
-{\r
-    uLong x = (uLong)get_byte(s);\r
-    int c;\r
-\r
-    x += ((uLong)get_byte(s))<<8;\r
-    x += ((uLong)get_byte(s))<<16;\r
-    c = get_byte(s);\r
-    if (c == EOF) s->z_err = Z_DATA_ERROR;\r
-    x += ((uLong)c)<<24;\r
-    return x;\r
-}\r
-\r
-/* ===========================================================================\r
-     Flushes all pending output if necessary, closes the compressed file\r
-   and deallocates all the (de)compression state.\r
-*/\r
-int ZEXPORT gzclose (file)\r
-    gzFile file;\r
-{\r
-    int err;\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL) return Z_STREAM_ERROR;\r
-\r
-    if (s->mode == 'w') {\r
-#ifdef NO_GZCOMPRESS\r
-        return Z_STREAM_ERROR;\r
-#else\r
-        err = do_flush (file, Z_FINISH);\r
-        if (err != Z_OK) return destroy((gz_stream*)file);\r
-\r
-        putLong (s->file, s->crc);\r
-        putLong (s->file, (uLong)(s->in & 0xffffffff));\r
-#endif\r
-    }\r
-    return destroy((gz_stream*)file);\r
-}\r
-\r
-/* ===========================================================================\r
-     Returns the error message for the last error which occured on the\r
-   given compressed file. errnum is set to zlib error number. If an\r
-   error occured in the file system and not in the compression library,\r
-   errnum is set to Z_ERRNO and the application may consult errno\r
-   to get the exact error code.\r
-*/\r
-const char * ZEXPORT gzerror (file, errnum)\r
-    gzFile file;\r
-    int *errnum;\r
-{\r
-    char *m;\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL) {\r
-        *errnum = Z_STREAM_ERROR;\r
-        return (const char*)ERR_MSG(Z_STREAM_ERROR);\r
-    }\r
-    *errnum = s->z_err;\r
-    if (*errnum == Z_OK) return (const char*)"";\r
-\r
-    m = (char*)(*errnum == Z_ERRNO ? zstrerror() : s->stream.msg);\r
-\r
-    if (m == NULL || *m == '\0') m = (char*)ERR_MSG(s->z_err);\r
-\r
-    TRYFREE(s->msg);\r
-    s->msg = (char*)ALLOC(strlen(s->path) + strlen(m) + 3);\r
-    if (s->msg == Z_NULL) return (const char*)ERR_MSG(Z_MEM_ERROR);\r
-    strcpy(s->msg, s->path);\r
-    strcat(s->msg, ": ");\r
-    strcat(s->msg, m);\r
-    return (const char*)s->msg;\r
-}\r
-\r
-/* ===========================================================================\r
-     Clear the error and end-of-file flags, and do the same for the real file.\r
-*/\r
-void ZEXPORT gzclearerr (file)\r
-    gzFile file;\r
-{\r
-    gz_stream *s = (gz_stream*)file;\r
-\r
-    if (s == NULL) return;\r
-    if (s->z_err != Z_STREAM_END) s->z_err = Z_OK;\r
-    s->z_eof = 0;\r
-    clearerr(s->file);\r
-}\r
index 5bec31e..664c4b9 100644 (file)
@@ -1472,9 +1472,10 @@ z_streamp strm;
 {\r
     struct inflate_state FAR *state;\r
 \r
-    if (strm == Z_NULL || strm->state == Z_NULL) return -1L << 16;\r
-    state = (struct inflate_state FAR *)strm->state;\r
-    return ((long)(state->back) << 16) +\r
-        (state->mode == COPY ? state->length :\r
-            (state->mode == MATCH ? state->was - state->length : 0));\r
+    if (strm == Z_NULL || strm->state == Z_NULL)\r
+        return (long)(((unsigned long)0 - 1) << 16);\r
+     state = (struct inflate_state FAR *)strm->state;\r
+    return (long)(((unsigned long)((long)state->back)) << 16) +\r
+         (state->mode == COPY ? state->length :\r
+             (state->mode == MATCH ? state->was - state->length : 0));\r
 }\r
index d66b7be..863bb39 100644 (file)
@@ -40,6 +40,7 @@
 #  define deflateInit2_         z_deflateInit2_\r
 #  define deflateInit_          z_deflateInit_\r
 #  define deflateParams         z_deflateParams\r
+#  define deflatePending       z_deflatePending\r
 #  define deflatePrime          z_deflatePrime\r
 #  define deflateReset          z_deflateReset\r
 #  define deflateSetDictionary  z_deflateSetDictionary\r